View in English

  • Apple 开发者
    • 入门汇总

    探索“入门汇总”

    • 概览
    • 学习
    • Apple Developer Program

    及时了解最新动态

    • 最新动态
    • 开发者你好
    • 平台

    探索“平台”

    • Apple 平台
    • iOS
    • iPadOS
    • macOS
    • Apple tvOS
    • visionOS
    • watchOS
    • App Store

    精选

    • 设计
    • 分发
    • 游戏
    • 配件
    • 网页
    • Home
    • CarPlay 车载
    • 技术

    探索“技术”

    • 概览
    • Xcode
    • Swift
    • SwiftUI

    精选

    • 辅助功能
    • App Intents
    • Apple 智能
    • 游戏
    • 机器学习与 AI
    • 安全性
    • Xcode Cloud
    • 社区

    探索“社区”

    • 概览
    • “与 Apple 会面交流”活动
    • 社区主导的活动
    • 开发者论坛
    • 开源

    精选

    • WWDC
    • Swift Student Challenge
    • 开发者故事
    • App Store 大奖
    • Apple 设计大奖
    • Apple Developer Centers
    • 文档

    探索“文档”

    • 文档库
    • 技术概述
    • 示例代码
    • 《人机界面指南》
    • 视频

    发布说明

    • 精选更新
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • Apple tvOS
    • Xcode
    • 下载

    探索“下载”

    • 所有下载
    • 操作系统
    • 应用程序
    • 设计资源

    精选

    • Xcode
    • TestFlight
    • 字体
    • SF Symbols
    • Icon Composer
    • 支持

    探索“支持”

    • 概览
    • 帮助指南
    • 开发者论坛
    • “反馈助理”
    • 联系我们

    精选

    • 《开发者账户帮助》
    • 《App 审核指南》
    • 《App Store Connect 帮助》
    • 即将实行的要求
    • 协议和准则
    • 系统状态
  • 快速链接

    • 活动
    • 新闻
    • 论坛
    • 示例代码
    • 视频
 

视频

打开菜单 关闭菜单
  • 专题
  • 所有视频
  • 关于

更多视频

  • 简介
  • 概要
  • 转写文稿
  • 代码
  • 了解 NowPlaying 框架

    抢先了解 NowPlaying——利用这个 Swift 框架,将你 App 的媒体播放搬上锁定屏幕、控制中心、灵动岛和 CarPlay 车载等系统界面。了解如何使用其中的可观察 API 发布播放状态并响应命令。探索远程播放会话,这项新功能可让你的 App 在外部设备上呈现媒体播放,并将完整的播放控件带到同样的系统界面。

    章节

    • 0:00 - Introduction
    • 1:08 - Media sessions
    • 5:03 - Remote media sessions
    • 10:31 - Media sharing extensions

    资源

    • Routing media to third-party devices
    • Publishing remote media sessions
    • Publishing media sessions
    • Setting up a remote notification server
      • 高清视频
      • 标清视频
  • 搜索此视频…

    大家好 我叫Leo Formaggio 我是Media Frameworks团队 的一名工程师 媒体在我们的日常生活中 占据着重要的位置 比如开车回家时收听播客 锻炼时播放充满活力的歌单 或者在长途飞行中观看电影 无论是独处时光 还是与人互动 媒体始终陪伴在我们左右 在iPhone上 它直接显示 在锁定屏幕上 控制中心以及灵动岛中 将iPhone放下充电时 可在StandBy中一览无余 上车时 它在CarPlay中 显眼呈现 这就是系统正在播放的体验 在所有Apple平台上均可使用 包括Apple Watch Apple Vision Pro和Apple TV 我来介绍NowPlaying框架 如何轻松将App中的媒体 接入系统

    首先介绍媒体会话API 演示如何将内容呈现在 系统正在播放的体验中

    接着说明如何将其他设备上 正在播放的内容 通过远程媒体会话 接入系统

    最后讲解Media Sharing Extensions 如何简化 将iPhone上的媒体播放到 其他设备的流程 我将以我开发的一款App 为例进行说明 它播放环境音效 帮助人们专注和放松 在我的App中 可以选择不同的音效 还可以暂停和恢复播放 为了将内容呈现在系统中 我使用了NowPlaying的 媒体会话API 接下来展示具体实现方式 这是我的PlayerModel — 一个@Observable类 引用了App的音频引擎 以及一个追踪当前播放音效 的属性 MediaSessionRepresentable协议 是我的App与系统之间 的契约 当PlayerModel遵循该协议时 系统便能理解 App正在播放的内容 以及如何处理跳过或暂停等交互 每个会话表示 都需要唯一标识符 content属性用于描述 当前播放的音效 NowPlaying提供特定内容类型 如Music Podcast和MovieContent 用于描述App正在播放 的媒体类型 GenericContent适合我的使用场景 所以我选择了它 每个内容以sound.id 作为标识 我用sound.name作为内容标题 用简短的sound.description 作为副标题 媒体类型可以是.audio或.video 但我的App只播放音频 我将时长设置为.continuous 因为App会无限循环 播放环境音效 我通过异步闭包 提供Artwork 系统会在需要特定尺寸图像时 调用它 现在来看看它在 锁定屏幕上的效果 音效名称和描述 会与封面图一起显示

    PlaybackSnapshot属性用于 显示当前播放状态 由于环境音效是连续播放的 只需指明内容是否 处于isPlaying状态 对于有固定时长的内容 还应在快照中指定 elapsedTime参数 通过commands属性 定义App支持的所有操作 每个命令都有一个闭包 当用户执行该操作时 系统会调用它 例如 当我点按锁定屏幕上的 暂停按钮时 暂停命令闭包被调用 我就可以暂停播放器 注意按钮已变化 反映出暂停状态 现在 如果我点按手机上的播放 播放命令闭包被调用 我就恢复播放器 同样 当我点按锁定屏幕上的 下一首按钮时 下一首命令闭包被调用 可以跳到下一个音效 锁定屏幕随即更新 显示新内容

    采用MediaSessionRepresentable之后 还需再做一件事 让内容对系统可见 MediaSession将会话表示 与系统连接起来 我用PlayerModel对其初始化 在设置音频引擎的 同一位置 完成后MediaSession开始 监听模型 自动保持正在播放的界面 实时更新 这就是我通过媒体会话 将App内容与系统 正在播放体验整合的方式 更多信息请查看 《Publishing Media Sessions》 文章 位于Apple Developer文档

    除了在iPhone上播放外 我还让音频引擎支持 智能音箱 可由我的App进行控制 在App的设备选择器菜单中 选择要控制的音箱 点按Living Room Speaker 开始控制它 通过Web服务器 App连接到选定的音箱 请求播放状态 并发送命令 为了将该音箱正在播放的内容 呈现在系统中 我使用了远程媒体会话API 该API使用App扩展 和推送通知 接收有关音箱的更新 来看看这一交互 的具体流程 当用户与音箱交互时 音箱将状态变更 通知服务器 服务器随后通过 Apple推送通知服务APNs 向iPhone发送推送通知 携带更新后的状态 系统使用更新后的状态 启动App扩展 来自推送通知载荷 App扩展随后向系统提供 该会话的 更新表示 更多关于通过APNs发送 推送通知的信息 请查阅文章 《Setting up a remote notification server》 位于developer.apple.com 当交互来自 iPhone系统UI时 系统调用App扩展中的 命令处理程序 App扩展将命令 发送至服务器 服务器通知音箱 音箱响应变更

    以下是我在App中采用 远程媒体会话的方式 首先我创建了一个App扩展 遵循 RemoteMediaSessionExtension 协议来实现 设置时 我使用了 NowPlaying 的 RemoteMediaSessionExtensionConfiguration 以及 remote-media extensionPoint 标识符 系统需要与远程会话 表示进行交互时 会调用 session(:) 方法 例如 更新用户界面 或处理某次交互 我可以用 RemotePlayerState 创建模型并将其返回 App 扩展配置完成后 我来演示如何用模型表示 Remote Media Session 这是我的 RemotePlayerModel 它是一个 @Observable 类 引用了 ServerClient 我用这个类与 服务器进行通信 它也会追踪服务器状态 我将以此为基础构建 Remote Media Session 表示 每个 Remote Media Session 表示 都需要一个唯一标识符 我使用了服务器状态中的 sessionID content 属性用于描述 扬声器正在播放的声音 同样 我使用了 GenericContent 并传入声音标识符 声音名称和声音描述 媒体类型为 .audio 时长为 .continuous 我提供了一个 Artwork 对象 用于加载当前声音的图像

    服务器状态会指示 扬声器 isPlaying 状态 我可以用它创建 对应状态的 PlaybackSnapshot 由于我控制的是 远程设备的播放 每个命令 Closure 都会向服务器 发送对应操作的请求 例如 在 iPhone 上点击播放 就会调用播放命令 Closure 我向服务器发送播放请求 扬声器随即恢复播放 同样 点击下一个按钮时 请求会发送至服务器 扬声器随即切换到下一个声音

    目前为止 采用 RemoteMediaSessionRepresentable 与本地播放媒体会话的 体验非常相似 接下来介绍剩余的 属性和方法 这些是远程会话特有的 devices 属性告知系统 该会话中正在播放的设备 我将服务器的设备列表 映射为 MediaDevice 值 每个设备需要一个唯一标识符 在不同会话间保持稳定 我提供设备名称 以及设备类型 如 .speaker 还有能力列表 例如设备的音量控制类型 这是它在 ControlCenter 中的样子 设备名称会与 音量级别一同显示

    当我使用系统音量滑块 调整音量时 音量变更 Closure 会被调用 并传入更新后的音量级别 我可以向服务器 发送音量变更请求

    update(:) 函数会在 收到推送通知 且含有新状态时被调用 例如 扬声器上的 内容发生变化时 RemotePlayerState 是我定义的 一个结构体 它遵循 RemoteMediaSessionAttributes 用于表示服务器状态 和推送通知载荷 在这里 我用新数据 更新状态变量 由于模型是可观测的 NowPlaying 会检测到变化 并自动更新系统 这就是我将 App 的 远程媒体会话集成到系统的方式 更多信息 请查阅文章 《Publishing remote media sessions》 我还想介绍 Media Sharing Extensions — 一组 API 用于将媒体 从 iPhone 播放到其他扬声器和电视 全部通过统一的系统界面实现 Media Sharing Extensions 让你 使用系统设备选择器 支持 App 所有 媒体协议的设备 这简化了 App 中 媒体设备的选择流程 所选结果也会在 Control Center 等系统界面中体现

    以往 支持某种媒体协议 意味着将其 SDK 嵌入 App 包中 有了 Media Sharing Extensions 协议实现位于 App 之外 由系统统一管理 App 可专注于媒体内容 而非播放技术本身

    随着更多协议推出 基于 Media Sharing Extensions 构建的 App 无需再接入其他 SDK

    以上介绍了如何将本地 和远程媒体会话 借助 NowPlaying 框架 融入系统正在播放体验 以及 Media Sharing Extensions 如何 简化向其他设备传送媒体的流程 对于本地播放或控制 远程设备播放的 App 接入 NowPlaying 可将内容 呈现于锁定屏幕 Control Center 及更多界面 这一集成简单直接 让用户可控制媒体 即便在 App 外也不例外 了解更多 Media Sharing Extensions 信息 它们让 App 使用 系统媒体设备选择器 并在向其他设备播放媒体时 扩大覆盖范围 更多关于 Media Sharing Extensions 的信息 请查阅文章 《Routing media to third-party devices》 期待看到你的 App 扩展至 系统正在播放体验 感谢观看 祝一切顺利

    • 1:57 - Existing PlayerModel implementation

      import Observation
      
      @Observable
      final class PlayerModel {
          let player: SoundPlayer
          var sound: Sound { player.currentSound }
      
          init(player: SoundPlayer) {
              self.player = player
          }
      }
    • 2:06 - Adopt MediaSessionRepresentable

      import NowPlaying
      
      extension PlayerModel: MediaSessionRepresentable {
          var id: String { "ambient-sound-session" }
      
          var content: (any MediaContentRepresentable)? {
              return GenericContent(
                  id: sound.id,
                  title: sound.name,
                  subtitle: sound.description,
                  type: .audio,
                  duration: .live,
                  artwork: Artwork(id: sound.id) { size in
                      let data = try await self.artworkData(size: size)
                      return try ArtworkRepresentation(data: data)
                  }
              )
          }
      
          var playbackSnapshot: MediaPlaybackSnapshot? {
              MediaPlaybackSnapshot(
                  state: player.isPlaying ? .playing() : .paused
              )
          }
      
          var commands: [MediaCommand] {[
              .play { self.player.play() },
              .pause { self.player.pause() },
              .previous { self.player.previous() },
              .next { self.player.next() }
          ]}
      }
    • 4:31 - MediaSession initialization

      import NowPlaying
      
      struct PlayerController {
          let player: SoundPlayer
          let model: PlayerModel
          let session: MediaSession<PlayerModel>
      
          init() {
              self.player = SoundPlayer()
              self.model = PlayerModel(player: player)
              self.session = MediaSession(model)
          }
      }
    • 6:42 - App extension entry point

      import ExtensionFoundation
      import NowPlaying
      
      @main
      final class SampleAppExtension: @MainActor RemoteMediaSessionExtension {
          var configuration: some AppExtensionConfiguration {
              RemoteMediaSessionExtensionConfiguration(extension: self)
          }
      
          var extensionPoint: AppExtensionPoint {
              AppExtensionPoint.Identifier(host: "com.apple.nowplaying", name: "remote-media")
          }
      
          func session(_ state: RemotePlayerState) async throws -> RemotePlayerModel {
              RemotePlayerModel(state: state)
          }
      }
    • 7:23 - Existing RemotePlayerModel implementation

      import Observation
      
      @Observable
      @MainActor
      final class RemotePlayerModel {
          let client: ServerClient
          var state: RemotePlayerState
      
          init(state: RemotePlayerState) {
              self.client = ServerClient(sessionID: state.sessionID)
              self.state = state
          }
      }
    • 7:40 - Adopt RemoteMediaSessionRepresentable in app extension

      import NowPlaying
      
      extension RemotePlayerModel: @MainActor RemoteMediaSessionRepresentable {
          var id: String { state.sessionID }
      
          var content: (any MediaContentRepresentable)? {
              GenericContent(
                  id: state.sound.id,
                  title: state.sound.name,
                  subtitle: state.sound.description,
                  type: .audio,
                  duration: .live,
                  artwork: Artwork(id: state.sound.id) { size in
                      let data = try await self.artworkData(size: size)
                      return try ArtworkRepresentation(data: data)
                  }
              )
          }
      
          var playbackSnapshot: MediaPlaybackSnapshot? {
              MediaPlaybackSnapshot(
                  state: state.isPlaying ? .playing() : .paused
              )
          }
      
          var commands: [MediaCommand] {[
              .play { try await self.client.send(.play) },
              .pause { try await self.client.send(.pause) },
              .previous { try await self.client.send(.previous) },
              .next { try await self.client.send(.next) }
          ]}
      
          var devices: [MediaDevice] {
              state.devices.map { device in
                  MediaDevice(
                      id: device.id,
                      name: device.name,
                      type: .speaker,
                      capabilities: [
                          .absoluteVolume(device.volume) { volume in
                              // send volume change to server
                          }
                      ]
                  )
              }
          }
      
          func update(_ state: RemotePlayerState) {
              self.state = state
          }
      }
    • 0:00 - Introduction
    • Discover the Now Playing system experience, available across all Apple platforms. It allows apps to surface currently playing media info on system surfaces like the Lock Screen, Dynamic Island, and CarPlay.

    • 1:08 - Media sessions
    • Learn how to use the media sessions API to bring audio or video from your app into the system's Now Playing experience by adopting the MediaSessionRepresentable protocol.

    • 5:03 - Remote media sessions
    • Discover how to extend playback control to devices like smart speakers by adopting RemoteMediaSessionRepresentable and utilizing Apple Push Notification service (APNs).

    • 10:31 - Media sharing extensions
    • Find out how Media Sharing Extensions simplify routing media from iPhone to other devices by leveraging the system device picker without needing to embed additional SDKs.

Developer Footer

  • 视频
  • WWDC26
  • 了解 NowPlaying 框架
  • 打开菜单 关闭菜单
    • iOS
    • iPadOS
    • macOS
    • Apple tvOS
    • visionOS
    • watchOS
    打开菜单 关闭菜单
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    打开菜单 关闭菜单
    • 辅助功能
    • 配件
    • Apple 智能
    • App 扩展
    • App Store
    • 音频与视频 (英文)
    • 增强现实
    • 设计
    • 分发
    • 教育
    • 字体 (英文)
    • 游戏
    • 健康与健身
    • App 内购买项目
    • 本地化
    • 地图与位置
    • 机器学习与 AI
    • 开源资源 (英文)
    • 安全性
    • Safari 浏览器与网页 (英文)
    打开菜单 关闭菜单
    • 完整文档 (英文)
    • 部分主题文档 (简体中文)
    • 教程
    • 下载
    • 论坛 (英文)
    • 视频
    打开菜单 关闭菜单
    • 支持文档
    • 联系我们
    • 错误报告
    • 系统状态 (英文)
    打开菜单 关闭菜单
    • Apple 开发者
    • App Store Connect
    • 证书、标识符和描述文件 (英文)
    • 反馈助理
    打开菜单 关闭菜单
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program (英文)
    • Mini Apps Partner Program
    • News Partner Program (英文)
    • Video Partner Program (英文)
    • 安全赏金计划 (英文)
    • Security Research Device Program (英文)
    打开菜单 关闭菜单
    • 与 Apple 会面交流
    • Apple Developer Center
    • App Store 大奖 (英文)
    • Apple 设计大奖
    • Apple Developer Academies (英文)
    • WWDC
    阅读最近新闻。
    获取 Apple Developer App。
    版权所有 © 2026 Apple Inc. 保留所有权利。
    使用条款 隐私政策 协议和准则