图像与设计
在 iOS 应用接入 Apple Music 时,用…
当 iOS 应用需要搜索 Apple Music 目录、检查订阅、用 ApplicationMusicPlayer 管理播放队列,并展示锁屏/控制中心/CarPlay/AirPods 的元数据与远程控制时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
在 iOS 应用接入 Apple Mus特定
解决思路
- 先核对 MusicKit App Service、Bundle ID、用途说明字符串和 background-audio 模式,再调试代码。
- 请求授权,并为所有非 authorized 状态显式建模。
- 搜索或加载目录内容,并在入队播放前检查 MusicSubscription.current。
- 需要应用内独立播放时选择 ApplicationMusicPlayer;只有应用拥有 Now Playing 和远程命令界面时才接入它们。
- 测试 authorized、denied、unsubscribed、offline、queue failure、interruption 和 track-change 状态;修复最小失败层,恢复 fixture,再重跑同一状态矩阵。
- 在 Apple Developer portal 为应用的显式 Bundle ID 启用 MusicKit App Service,使 MusicKit 能自动生成 developer token。
- 在 Info.plist 添加 NSAppleMusicUsageDescription,说明应用为何访问用户媒体库。
- 如需后台播放,在 UIBackgroundModes 添加 audio 后台模式。
- 导入 import MusicKit 用于目录、授权、播放;导入 import MediaPlayer 用于 MPRemoteCommandCenter、MPNowPlayingInfoCenter。
- 在访问用户音乐数据或播放 Apple Music 内容前调用 MusicAuthorization.request() 请求权限;需要显示同意对话框时由它弹出,用 MusicAuthorization.currentStatus 在不提示的情况下读取当前状态。
- 处理 requestMusicAccess() 返回状态:.authorized 表示完整访问;.denied/.restricted 时引导用户去 Settings 开启;.notDetermined 和 @unknown default 也显式处理。
- 用 MusicCatalogSearchRequest(term:types:) 搜索 Apple Music 目录;例如 types 传 [Song.self],设置 request.limit = 25,然后 let response = try await request.response() 取 response.songs。
- 目录查询可以拉取 Apple Music 资源,但播放订阅目录内容仍须受 MusicSubscription.current.canPlayCatalogContent 限制。
- 展示结果时读取 song.title、song.artistName;若有 song.artwork,用 artwork.url(width:300,height:300) 加载封面。
- 提供播放功能前,用 let subscription = try await MusicSubscription.current 检查 subscription.canPlayCatalogContent,返回是否有有效 Apple Music 订阅。
- 用 for await subscription in MusicSubscription.subscriptionUpdates 观察订阅变化:canPlayCatalogContent 为 true 时启用完整播放 UI,否则显示订阅引导。
- 用户未订阅时展示 Apple Music 订阅 offer sheet;先检查 subscription?.canBecomeSubscriber == true,再通过 .musicSubscriptionOffer(isPresented:options:onLoadCompletion:) 展示。
- 订阅 sheet 需要上下文元数据时传 MusicSubscriptionOffer.Options;需要处理加载错误时在 onLoadCompletion 中接收并打印/上报 error。
- 使用 let player = ApplicationMusicPlayer.shared,使 Apple Music 内容独立于 Music app 播放,不影响系统播放器状态。
- 播放单曲:player.queue = [song]; try await player.play()。
- 暂停:player.pause()。
- 跳到下一首:try await player.skipToNextEntry()。
- 观察播放状态:读取 player.state(@Observable),根据 state.playbackStatus 处理 .playing、.paused、.stopped、.interrupted、.seekingForward、.seekingBackward 和 @unknown default。
- 播放专辑:player.queue = [album]; try await player.play()。
- 追加歌曲到队列尾部:try await player.queue.insert(songs, position: .tail)。
- 插入下一首播放:try await player.queue.insert(song, position: .afterCurrentEntry)。
- 用 MPNowPlayingInfoCenter 更新锁屏、控制中心、CarPlay 的当前曲目元数据;播放非 MusicKit 来源的自定义音频时必须这样做;ApplicationMusicPlayer 对 Apple Music 内容会自动处理。
- updateNowPlaying 时构建 info 字典:MPMediaItemPropertyTitle、MPMediaItemPropertyArtist、MPMediaItemPropertyPlaybackDuration、MPNowPlayingInfoPropertyElapsedPlaybackTime、MPNowPlayingInfoPropertyPlaybackRate=1.0、MPNowPlayingInfoPropertyMediaType=MPNowPlayingInfoMediaType.audio.rawValue,然后赋给 MPNowPlayingInfoCenter.default().nowPlayingInfo。
- 清除 Now Playing:MPNowPlayingInfoCenter.default().nowPlayingInfo = nil。
- 添加封面:用 MPMediaItemArtwork(boundsSize: image.size) { _ in image } 创建 artwork,读取当前 nowPlayingInfo,设置 MPMediaItemPropertyArtwork,再写回 nowPlayingInfo。
- 用 MPRemoteCommandCenter.shared() 注册锁屏、AirPods 点击手势、CarPlay 按钮的处理器。
- 为 center.playCommand.addTarget 返回 .success 并调用恢复播放;pauseCommand 返回 .success 并调用暂停。
- 为 nextTrackCommand.addTarget 返回 .success 并调用 skipToNext();previousTrackCommand.addTarget 返回 .success 并调用 skipToPrevious()。
- 对不支持的远程命令禁用:center.seekForwardCommand.isEnabled = false;center.seekBackwardCommand.isEnabled = false。
- 若支持拖动进度,为 center.changePlaybackPositionCommand.addTarget 处理 event,guard let positionEvent = event as? MPChangePlaybackPositionCommandEvent,否则返回 .commandFailed;然后 seek(to: positionEvent.positionTime) 并返回 .success。
- 不要在配置 App Service 和 purpose string 之前调试授权;先核对 service、Bundle ID 和 NSAppleMusicUsageDescription。
- 不要在没有订阅门槛时排队目录内容;先检查 canPlayCatalogContent,仅在 canBecomeSubscriber 时提供订阅。
- 应用自有播放不要用 SystemMusicPlayer;改用 ApplicationMusicPlayer,因为 SystemMusicPlayer 会改变 Music app 的全局队列。
- 不要只发布一次 Now Playing 元数据;在曲目、时长、速率和已播放时间变化时刷新。
- 不要注册不支持的远程命令;禁用它们,受支持处理器必须执行动作并返回 .success。
- 提交或评审前确认 MusicKit App Service 已为应用的显式 Bundle ID 启用。
- 确认 Info.plist 已添加 NSAppleMusicUsageDescription。
- 确认任何 MusicKit 访问前已调用 MusicAuthorization.request()。
- 确认尝试目录播放前已检查订阅。
- 确认展示订阅 offer 前已检查 canBecomeSubscriber。
- 确认写音乐库前已检查 hasCloudLibraryEnabled。
- 确认应用范围播放使用 ApplicationMusicPlayer,而不是 SystemMusicPlayer。
- 确认音乐后台播放时已启用 background audio mode。
- 确认自定义音频每次切歌都更新 Now Playing info。
- 确认受支持的远程命令处理器返回 .success。
- 确认不支持的远程命令用 isEnabled = false 禁用。
- 确认 Now Playing info 提供 artwork 供锁屏显示。
- 确认定期更新 elapsed playback time 以保证 scrubber 准确。
- 确认用户缺少 Apple Music 订阅时展示订阅 offer。
适用边界 · 注意事项
- 不适用于 Android、Web 等非 Apple 平台,或不用 MusicKit/MediaPlayer 的场景。
- 不适用于没有 Apple Developer 账号、无法启用 MusicKit App Service 或无法接受 Apple Music 依赖的应用。
- 如果只播放普通本地/自定义音频而不需要 Apple Music 目录内容,可只使用 MediaPlayer 的 Now Playing 与 Remote Command 部分,不需要 MusicKit 授权与订阅检查。