大多数浏览器和
Developer App 均支持流媒体播放。
-
认识面向 Swift 的 MusicKit
MusicKit 可将 Apple 音乐轻松集成到您的 app 中。探索基于 Swift 的框架:我们将向您介绍使用 MusicKit 的基本流程,包括如何查找、请求和播放内容,并向您展示当用户尚未注册 Apple Music 时如何将音乐订阅工作流集成到您的 app 中。
资源
相关视频
WWDC22
WWDC21
-
下载
哈啰 欢迎来到WWDC 我是乔尔 我要告诉你 如何用MusicKit为你的应用程序 新增音乐 MusicKit是Apple平台的新架构 它能让你的应用程序通过表达式API 在Swift内存取音乐 它用新的Swift并发语句 而且是从最基础开始就是 基于SwiftUI的 MusicKit加速你的应用程序 与Apple Music API整合的方式 也就是服务器端API 用来从Apple Music存取宽排列的 目录内容 让你可以更轻松的构建 引人入胜的应用程序,并且 与Apple Music联系起来 首先 我们来聊聊如何用MusicKit 请求音乐内容 接着 我们来聊聊其他 对你的应用程序 与Apple Music整合方式 重要的话题 像是请求用户同意你的应用程序 存取Apple Music相关资料 管理存取Apple Music API 需要的密钥令牌 存取订阅信息 和相关性能 从Apple Music目录播放音乐 以及最后 如何显示Apple Music的 订阅方案提议 万一你的用户 还不是订阅者 MusicKit为取用音乐项目 提供全新模型层 用结构式请求 让你能从Apple Music API 索取内容 你也可以在Apple Music目录中 搜寻内容 或基于特定筛选器索取资源 这些请求产生在集合中 群组的项目的响应 带有内建分页 让你从初始回应 取得下一批项目 那么这些音乐项目长什么样子呢? 我们用一张专辑来看看一个特定案例 “专辑”是一个值类型,并且分组成 三组不同类别的属性 第一个类别包含简易属性 像是字符串属性如“title” 布尔属性如“isCompilation” 或更多架构式属性如“artwork” 让你存取作品的键接 连同相关尺寸信息和有关的颜色 专辑也提供多种关系 像是相关的歌手或风格 或指定专辑中歌曲的清单 例如 “tracks”的关系结果 是一系列“Track”类型的值 就是音乐项目的另一个类型 最后 除了这些 模型层级的强关系 “专辑”还提供几个相关内容 较弱的关联 关联和关系非常相似 但它们通常比较短暂 或是更受编辑上的驱使 举例来说 专辑上的 “appearsOn”关联 回传一系列播放列表 但不像关系集合 这也有个标题 使用MusicKit加载和存取关系 非常简单 举一张专辑为例 你能轻松地索取 同样这张专辑的 另一个说明 包含“歌手” 和“歌曲” 以及关联 像“有关的专辑”在单一作业中 “with”方法需要用特别的Swift 关键词“await”被呼叫 代表这会在背后 执行异步作业 这确实会通过网络 从Apple Music API 索取这张专辑更完整的说明 然后你就能取得这个包含细节的专辑 的歌曲清单 并迭代那些歌曲 如同用一般数组的作法 这是控制台给这段代码产出 存取关联像是“相关专辑” 做法相同 唯一差别在它们通常 还包含一个标题 在集合上能直接取用 然后你就能用相同方式迭代集合 印出一些相关专辑 这是控制台给这段代码产出 现在 我们来看看向MusicKit请求 音乐内容的展示 我在建置让我能从Apple Music 找到并享受专辑的应用程序 我可以用这个搜寻栏寻找专辑 这已经与一些代码连结 来配对搜寻结果 使用音乐目录搜寻请求 这个应用程序也能纪录 我最近检视过的专辑 我们能通过搜寻 找到这张 叫“抓住这氛围-单曲”专辑的 更多信息 而你可能有注意到 这张专辑的歌曲清单 在细节检视出现后 变得有生气 这是通过加载我们看到的这张专辑的 “歌曲”关系 并为SwiftUI检视更新状态变量 这接着用来填充清单 我们能开始播放这张专辑的音乐 按歌曲下方的 播放按钮 或通过选择特定歌曲 这让使用Musickit的回放API 用这些歌曲的列表来设定队列 和呼叫播放器上的播放方法变简单 我们来试试! 如果我选择卡伦和梦布鲁的 歌曲名“抓住这氛围” 这首歌开始播放 ♪喔 喔 不♪ 我们的应用程序甚至在锁屏时 自动在媒体控制中运作 让我能刷到这首歌的中段
但我原先是希望用这个用应用程序 帮我从我的CD库重新发现老音乐 像是来自凤凰的这张专辑 所以 我想新增一个功能让我 用iPhone相机对准老CD的条形码 让这个应用程序浮现 相同专辑的数字版 我已经这个功能新增 一些实验性代码 当我启用它 可以看到一个条形码按钮 在底部 这能展开相机检视 如果我对准这张CD的条形码 它会自动辨识条形码值并显示出来 我还缺的就只有一些使用MusicKit 来找对应专辑的代码 我们开始把那些加进应用程序中 选择制作一个albumsRequest 使用MusicCatalogResourceRequest 我只要找专辑
我想确保我们找到的专辑是 UPC属性 也就是通用产品代码 这是条形码的技术术语 是等于detectedBarcode 我可以异步地执行这个请求
albumsRequest.response
然后根据响应 我可以在结果中 寻找第一个专辑
我可以接着送这第一张专辑 给handleDetectedAlbum 下方的helper方法
handleDetectedAlbum(firstAlbum)
这个方法忽略了条形码扫描检视 并推送侦测到专辑的细节
它被标示在MainActor 来确保它在主线程会执行 当我们呼叫时,需要确保 加上await关键词
我们建置 再次执行应用程序 试试看
点条形码按钮 拿我的专辑
成功了!
现在要在Apple Music享受 我的旧音乐的数字版容易多了 MusicKit还提供通用目的数据请求 这跟结构式请求不同 里面让你从随意的 Apple Music API顶点 使用键接加载内容
你会从这个请求得到的是 从Apple Music API的 JSON响应原始数据 你需要用JSON解码器 解码这个原始数据 但做这不用很困难 因为你可以利用既有的音乐项目类型 因为它们遵守Codable规则 我们来看个范例 如果你想要从Apple Music加载 最高层风格的清单 你可以通过加载 这个特定键接的内容来做到 这是对应的JSON回应 如果你近一步看这个结果 能看见中间有看起来像 风格资源的东西 你会如何在Swift中表现它? 当然,MusicKit有风格类型 回到这 你会如何在Swift表现 整个回应? 你可以创造一个结构 用数据成员 这是一个简单的风格数组 接着 确保标记这个结构为可解码 这甚至不需要 写任何其他解码逻辑 因为“风格”本身也遵照可解码 所以 要在应用程序加载这些数据 你得放这个结构在你的档案最上方 接着你得为你的用户 用特定国家代码来建构键接 用这个键接创造音乐数据请求 并从那得到响应 随之是我们已经看过的相同模式 一旦你得到那个响应 你可以用JSON解码器 解码它的数据 通过传MyGenresResponse类型 进到解码方法 就完成了! 你现在可以存取个别风格 在你的强烈类型区别的风格中 如你所见 你能用能在MusicKit 其他请求索取的同类音乐项目 这就是你如何从Apple Music API 从随机键接加载内容的方式 现在我们知道如何加载音乐内容 我们来讨论一些整合Apple Music 和你的应用程序的重要前置步骤 从隐私权开始 我们希望用户能持有什么应用程序 能存取他们的数据的控制权 在你能从Apple Music API 请求任何数据前 这可能包含用户的聆听历史纪录 或他们的音乐库 你需要取得用户被告知的许同意 让你的应用程序存取Apple Music 询问该用户的内容是每个装置 每个应用程序都需要执行的 这是用户同意对话在一个叫Zova的 应用程序中呈现的样子 这是一个很棒的健身应用程序 让你跟着它在Apple Music上的 播放列表 或你自己的播放列表运动 当你第一次进入开始运动 Zova询问存取Apple Music的权限 这个对话需要向用户传达 为何你的应用程序 需要存取Apple Music 为此 应用程序的Apple Music 用途说明 也是你需要定义 在Info.plist的 含在这个对话的子标题中 这是一个你为Musickit请求 用户同意方式的范例 假如你的应用程序中有个功能 需要MusicKit 你用isAuthorizedForMusicKit 状态变量 控制这个功能的 存取 在你应用程序中合适的位置 尝试用MusicKit之前 你能用异步请求方法 请求授权存取Apple Music 这只会向还没授权的 用户提示 这个请求方法回传一个状态值 你可以设定你的 isAuthorizedForMusicKit变量 为“真” 如果这个状态等于“已授权” 我们来简短聊聊Apple Music API 加载数据需要的令牌 Apple Music API需要开发者令牌 这也就用API授权你的应用程序 在之前 要取得这个开发者令牌 你得在开发者入口网站创建 MusicKit私钥 放进你控制的服务器中 来确保密钥是私密的 并让应用程序从服务器 请求新的开发者密钥 而现在 有了Swift的MusicKit 你不再需要担心这些 因为它会自动为你的应用程序 产生开发者密钥 你只需要投过开发者入口网站登录 来选择这个新的自动方式 确切来说 在注册应用程序ID的页面 选择底部的应用程序服务分页 并勾选起MusicKit 就完成了! 此外 Apple Music API需要 用户密钥来做任何个人化端点 如同开发者密钥 今年的新调整 用户密钥会自动帮你产生 另个你可能需要在应用程序 用MusicKit使用的是 确认你的用户是否有 有效的Apple Music订阅 在MusicKit的订阅信息揭露成 三个特殊的能力 能告诉你用户是否能从Apple Music 目录播放内容 用户是否开启iCloud音乐库 或他们是否能成为订阅者 万一他们尚未有有效的订阅
记得要为你应用程序中 特定Apple Music相关的功能 检查相关能力 譬如你有个播放按钮 链接来播放音乐 你可能要让它不能用 万一你的用户 不能从Apple Music播放目录内容 你可以在检视中定义一个状态变量 追踪音乐订阅 接着 你可以应用停用调节器 在你的按钮 来确保它维持停用 假如音乐订阅属性 “无法播放目录内容”为“否” 最后 在异步块中 传给新任务调节器 你可以用新的“订阅更新”串流 在音乐订阅异动时被通知 我们来谈用MusicKit回放 MusicKit提供两个特殊的播放器 我们称作SystemMusicPlayer 和ApplicationMusicPlayer 我们用一个范例来看 两者之间的差异 虽然社群媒体应用程序可能希望用 SystemMusicPlayer改变 系统音乐应用程序正在播放的内容 健身应用程序可能倾向用 ApplicationMusicPlayer 维持它们的回放状态 与系统音乐应用程序完全独立 两个播放器自动回报 正在播放的信息并控制远程指令 这让我们能在锁屏时 与系统媒体控制深度整合 稍早的展示有说到 然而 正在播放的应用程序 是用不同方式回报 假如你用SystemMusicPlayer 音乐应用程序会回报 为正在播放的应用程序 但若用ApplicationMusicPlayer 你的应用程序会回报为 正在播放的应用程序 回放队列所有权也不同 在SystemMusicPlayer 你的应用程序 仅远程控制系统音乐应用程序 而在ApplicationMusicPlayer 你的应用程序拥有完全分离的 回放队列 两个编辑器都让你 用一个或更多项目设定队列 新增一个项目下个播放 或稍后播放 但只有ApplicationMusicPlayer 让你控制回放队列 让你在中间插入项目 或移除先前已经加过的项目 最后 如果你的用户还不是 Apple Music订阅用户 你可能要允许他们从你的应用程序 开始Apple Music免费试用 这样他们就能享受你在应用程序中 用音乐加强的用户体验 全面功能 订阅优惠可以通过自定义 显示给用户的主信息设定 更加呼应你应用程序的功能 像playMusic
它也能是语境上的 强调特定歌曲、专辑或播放列表
通过使用你应用程序的订阅优惠表单 你能因为带来新Apple Music 订阅用户受奖励 通过我们的联盟计划 我们称为Apple服务 效能伙伴计划 要在你的应用程序展示语境上的 音乐订阅优惠 你会需要追踪 音乐订阅 如我们稍早所见 你会需要另个状态变量 追踪这个优惠是否有显示出来 传送专辑的ID 在你的订阅优惠选项 作为itemID属性 让优惠按钮维持停用 每当canBecomeSubscriber 在音乐订阅上为“否” 接着 用音乐订阅优惠调节器 绑定isShowingOffer属性 并包含你的选项 最后 设定isShowingOffer 变量为“真” 我们响应用程序看看 给Apple Music的语境优惠 长什么样子 在稍早的展示中 我们已经用有效的 Apple Music订阅登入 因此要激活这个情境让它能正常 显示Apple Music的订阅优惠 你可以到设定 注销你的账号
接着 如果我回到应用程序 你能看到播放按钮 已经停用并移到左边 让出空间 给另个按钮来邀请用户 加入Apple Music 如果我点这个按钮 订阅优惠 就会出现 强调我们刚才 在应用程序看过的专辑 这就是你能如何让用户 从你的应用程序 开始Apple Music 免费试用的方法 总结来说 有多种类型的应用程序 能通过加上一些音乐来增强 它们的部分体验 例如 通过播放适合你的游戏风格的 背景音乐 你可以让游戏更沉浸式 或你可以播放雀跃的音乐 在运动应用程序中让用户持续被鼓舞 在社群媒体应用程序 你能用 强调出内容的音乐来提升 用户的互动性
想了解更多 记得去看 相关的会话 你就能学习 如何用ShazamKit掌握 Shazam的力量 并深度了解SwiftUI的并发 谢谢你的收看 有个美好的WWDC 2021! [音乐]
-
-
2:56 - Loading and accessing relationships
// Loading and accessing relationships let detailedAlbum = try await album.with([.artists, .tracks, .relatedAlbums]) print("\(detailedAlbum)") if let tracks = detailedAlbum.tracks { print(" Tracks:") tracks.prefix(2).forEach { track in print(" \(track)") } }
-
3:31 - Loading and accessing associations
// Loading and accessing associations let detailedAlbum = try await album.with([.artists, .tracks, .relatedAlbums]) print("\(detailedAlbum)") if let relatedAlbums = detailedAlbum.relatedAlbums { print(" \(relatedAlbums.title ?? ""):") relatedAlbums.prefix(2).forEach { relatedAlbum in print(" \(relatedAlbum)") } }
-
9:02 - Loading top level genres
// Loading top level genres struct MyGenresResponse: Decodable { let data: [Genre] } let countryCode = try await MusicDataRequest.currentCountryCode let url = URL(string: "https://api.music.apple.com/v1/catalog/\(countryCode)/genres")! let dataRequest = MusicDataRequest(urlRequest: URLRequest(url: url)) let dataResponse = try await dataRequest.response() let decoder = JSONDecoder() let genresResponse = try decoder.decode(MyGenresResponse.self, from: dataResponse.data) print("\(genresResponse.data[9])")
-
10:49 - Requesting user consent for MusicKit
// Requesting user consent for MusicKit @State var isAuthorizedForMusicKit = false func requestMusicAuthorization() { detach { let authorizationStatus = await MusicAuthorization.request() if authorizationStatus == .authorized { isAuthorizedForMusicKit = true } else { // User denied permission. } } }
-
12:54 - Using music subscription to drive state of a play button
// Using music subscription to drive state of a play button @State var musicSubscription: MusicSubscription? var body: some View { Button(action: handlePlayButtonSelected) { Image(systemName: "play.fill") } .disabled(!(musicSubscription?.canPlayCatalogContent ?? false)) .task { for await subscription in MusicSubscription.subscriptionUpdates { musicSubscription = subscription } } }
-
15:34 - Showing contextual music subscription offer
// Showing contextual music subscription offer @State var musicSubscription: MusicSubscription? @State var isShowingOffer = false var offerOptions: MusicSubscriptionOffer.Options { var offerOptions = MusicSubscriptionOffer.Options() offerOptions.itemID = album.id return offerOptions } var body: some View { Button("Show Subscription Offers", action: showSubscriptionOffer) .disabled(!(musicSubscription?.canBecomeSubscriber ?? false)) .musicSubscriptionOffer(isPresented: $isShowingOffer, options: offerOptions) } func showSubscriptionOffer() { isShowingOffer = true }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。