WWDC Quick Look 💓 By SwiftGGTeam
Deliver a great playback experience on tvOS

Deliver a great playback experience on tvOS

观看原视频

Highlight

Apple 在 tvOS 15 重新设计 AVPlayerViewController 标准播放界面,开发者可以把标题、播放控制、内容标签和上下文操作接入系统播放器,减少自定义播放 UI 的维护成本。

核心内容

做 tvOS 媒体 App,最难的部分常常不在播放视频本身。

用户拿着遥控器,想打开字幕、切换音轨、收藏当前节目、跳过片头、查看章节。以前如果只用基础播放器,这些入口要散落在自定义菜单里。你还要处理遥控器手势、焦点移动、画中画、Siri 指令和未来系统行为。

这类自定义播放器很容易和系统播放界面打架。演讲里明确建议,不要给播放器额外添加手势,因为它会干扰标准播放 UI,并且未来可能失效。

Apple 在 tvOS 15 重新设计了标准播放界面。AVPlayerViewController 继续负责播放、跳转、扫描、拖动、Siri 指令、Interstitials(插播内容)和遥控器支持。开发者在它上面补充应用自己的信息和操作。

新的入口集中在四个地方。

Title View 显示当前内容的标题和副标题。Transport Bar Controls 把收藏、设置等操作放到进度条上方。Content Tabs 在播放界面内展示信息、章节和推荐内容。Contextual Actions 在特定时间段显示“跳过片头”或“回顾前情”这类操作。

用户不用离开播放器。开发者也不用重新实现一套播放器外壳。

要使用这些新界面,App 必须链接 tvOS 15 SDK。旧 App 会继续得到 tvOS 14 的默认播放界面,这是 Apple 用来保持兼容性的边界。

详细内容

Title View:让播放器知道当前在播什么

03:08

Title View 位于 transport bar(播放控制条)上方。它从当前 AVPlayerItem 的 metadata(元数据)读取标题和副标题。

演讲提到两个字段:commonIdentifierTitleiTunesMetadataTrackSubtitle。如果媒体文件没有内嵌元数据,或者你需要补充信息,可以设置 AVPlayerItem.externalMetadata

import AVFoundation
import AVKit

func configureTitleView(
    playerItem: AVPlayerItem,
    playerViewController: AVPlayerViewController,
    title: String,
    subtitle: String
) {
    let titleItem = AVMutableMetadataItem()
    titleItem.identifier = .commonIdentifierTitle
    titleItem.value = title as NSString

    let subtitleItem = AVMutableMetadataItem()
    subtitleItem.identifier = .iTunesMetadataTrackSubtitle
    subtitleItem.value = subtitle as NSString

    playerItem.externalMetadata = [titleItem, subtitleItem]
    playerViewController.transportBarIncludesTitleView = true
}

关键点:

  • import AVFoundation 引入 AVPlayerItem 和 metadata 类型。
  • import AVKit 引入 AVPlayerViewController
  • configureTitleView 接收当前播放项和播放器控制器。
  • AVMutableMetadataItem() 创建可写元数据项。
  • titleItem.identifier = .commonIdentifierTitle 对应演讲中用于 Title View 的标题字段。
  • titleItem.value = title as NSString 写入标题文本。
  • subtitleItem.identifier = .iTunesMetadataTrackSubtitle 对应演讲中的副标题字段。
  • subtitleItem.value = subtitle as NSString 写入副标题文本。
  • playerItem.externalMetadata 用外部元数据补齐媒体信息。
  • transportBarIncludesTitleView = true 保持 Title View 显示;如果不想显示,可以设为 false

Transport Bar Controls:把常用操作放进系统播放控制条

04:01

Transport Bar Controls 是进度条上方的一组控件。系统会提供字幕、音频语言、画中画等标准控件。

应用自己的操作通过 AVPlayerViewController.transportBarCustomMenuItems 添加。它支持 UIActionUIMenu。演讲还说明,AVPlayerViewController 展示的 UIMenu 支持一层嵌套,并可使用 displayInline 选项。

import AVKit
import UIKit

func configureTransportBarControls(playerViewController: AVPlayerViewController) {
    let favoriteAction = UIAction(
        title: "Favorites",
        image: UIImage(systemName: "heart")
    ) { _ in
        // Add to favorites
    }

    let normalSpeedAction = UIAction(title: "Normal", state: .on) { _ in }
    let fastSpeedAction = UIAction(title: "Fast") { _ in }

    let speedMenu = UIMenu(
        title: "Speed",
        options: [.displayInline, .singleSelection],
        children: [normalSpeedAction, fastSpeedAction]
    )

    let settingsMenu = UIMenu(
        image: UIImage(systemName: "gearshape"),
        children: [speedMenu]
    )

    playerViewController.transportBarCustomMenuItems = [
        favoriteAction,
        settingsMenu
    ]
}

关键点:

  • import AVKit 提供 AVPlayerViewController
  • import UIKit 提供 UIActionUIMenuUIImage
  • favoriteAction 创建一个收藏操作,对应演讲中的 Favorites 示例。
  • UIImage(systemName: "heart") 给收藏操作加系统图标。
  • handler 里的注释位置执行“加入收藏”的业务逻辑。
  • normalSpeedActionfastSpeedAction 是菜单子项。
  • state: .on 标记当前选中的菜单项。
  • speedMenu 创建 Speed 菜单,对应演讲中的 Speed 子菜单。
  • .displayInline 让子菜单以内联方式展示。
  • .singleSelection 表示同一组里只选一个操作。
  • settingsMenu 使用齿轮图标承载子菜单。
  • transportBarCustomMenuItems 把自定义操作交给系统播放界面展示。

Content Tabs:播放时展示信息、章节和推荐内容

05:19

Content Tabs 让播放界面承载补充信息。媒体有 metadata 时,AVPlayerViewController 会显示 Info 标签。AVPlayerItem 提供 navigationMarkerGroups 时,系统会显示 Chapters 标签。

应用可以通过 customInfoViewControllers 添加更多标签。tvOS 15 用这个新属性替代旧的 customInfoViewController,因为新的接口支持多个 view controller。

import AVKit
import UIKit

func configureContentTabs(
    playerViewController: AVPlayerViewController,
    customViewController: UIViewController
) {
    customViewController.preferredContentSize = CGSize(width: 0, height: 140)
    customViewController.title = "Recommended"

    playerViewController.customInfoViewControllers = [customViewController]
}

关键点:

  • configureContentTabs 接收播放器控制器和自定义内容控制器。
  • preferredContentSize 指定内容标签需要的高度。
  • width: 0 表示这里重点设置高度,宽度由系统布局处理。
  • height: 140 对应演讲代码片段中的示例高度。
  • title = "Recommended" 设置标签标题,系统会用它显示 tab 名称。
  • customInfoViewControllers 添加自定义内容标签数组。
  • 多个标签同时存在时,系统会按最高的内容标签计算整体高度。

TVMediaItemContentConfiguration:用系统样式展示推荐内容

07:08

如果 Content Tabs 里要展示一组相关视频,可以用 TVUIKit(tvOS UI 工具框架)提供的新内容配置。

TVMediaItemContentConfiguration.wideCell() 用于 16:9 媒体卡片。它能设置图片、标题、副标题、徽章和播放进度。

import TVUIKit
import UIKit

func configureMediaCell(cell: UICollectionViewCell) {
    var contentConfiguration = TVMediaItemContentConfiguration.wideCell()
    contentConfiguration.image = UIImage(imageLiteralResourceName: "tanu")
    contentConfiguration.text = "Title"
    contentConfiguration.secondaryText = "Secondary text"
    contentConfiguration.badgeText = "NEW"
    contentConfiguration.badgeProperties.backgroundColor = .systemRed
    contentConfiguration.playbackProgress = 0.75

    cell.contentConfiguration = contentConfiguration
}

关键点:

  • import TVUIKit 引入 tvOS 的内容配置类型。
  • import UIKit 引入 UICollectionViewCellUIImage
  • configureMediaCell 配置一个 collection view cell。
  • TVMediaItemContentConfiguration.wideCell() 创建 16:9 媒体卡片配置。
  • image 设置卡片图片。
  • text 设置主标题。
  • secondaryText 设置副标题。
  • badgeText = "NEW" 给卡片显示徽章文字。
  • badgeProperties.backgroundColor = .systemRed 设置徽章背景色。
  • playbackProgress = 0.75 显示 75% 的观看进度。
  • cell.contentConfiguration 把配置应用到 cell。

TVMonogramContentConfiguration:在播放界面展示人物

07:36

如果内容标签展示的是人物或角色,演讲建议使用 TVMonogramContentConfiguration

它适合在当前媒体相关区域展示演员、嘉宾、讲者或角色信息。

import TVUIKit
import UIKit

func configureMonogramCell(cell: UICollectionViewCell) {
    var contentConfiguration = TVMonogramContentConfiguration.cell()
    contentConfiguration.image = UIImage(imageLiteralResourceName: "jad")
    contentConfiguration.text = "Jad"

    cell.contentConfiguration = contentConfiguration
}

关键点:

  • TVMonogramContentConfiguration.cell() 创建人物头像样式的 cell 配置。
  • image 设置人物图片。
  • text 设置人物名称。
  • cell.contentConfiguration 让 collection view cell 使用这套系统样式。
  • 这个配置和媒体卡片配置都服务于 Content Tabs 内的列表内容。

Contextual Actions:只在合适时间显示操作

07:52

Contextual Actions 用于播放过程中的即时操作。演讲给出的例子是 Skip Intro 和 Recap。

做法是设置 AVPlayerViewController.contextualActions,提供一个或多个 UIAction。应用通常需要监听播放器时间,在相关片段出现时显示操作,结束后把属性重置为空数组。

import AVFoundation
import AVKit
import UIKit

func showSkipIntroAction(
    playerViewController: AVPlayerViewController,
    player: AVPlayer
) {
    let skipIntroAction = UIAction(title: "Skip Intro") { _ in
        let targetTime = CMTime(seconds: 90, preferredTimescale: 600)
        player.seek(to: targetTime)
    }

    playerViewController.contextualActions = [skipIntroAction]
}

func hideContextualActions(playerViewController: AVPlayerViewController) {
    playerViewController.contextualActions = []
}

关键点:

  • import AVFoundation 提供 AVPlayerCMTime
  • showSkipIntroAction 在合适片段出现时调用。
  • UIAction(title: "Skip Intro") 创建一个跳过片头操作,对应演讲中的 Skip Intro 示例。
  • CMTime(seconds: 90, preferredTimescale: 600) 表示要跳转到的播放时间。
  • player.seek(to:) 执行跳转。
  • contextualActions = [skipIntroAction] 把操作显示在播放器中。
  • hideContextualActions 在不需要时调用。
  • contextualActions = [] 隐藏当前上下文操作。

最少要遵守的 tvOS 播放实践

08:48

演讲最后给了三条实践建议。

第一,不要给播放器添加额外手势。它会干扰标准播放 UI,也可能在未来系统版本失效。

第二,补齐缺失的 metadata。标题、图片、描述这类信息可以通过 AVPlayerItem.externalMetadata 提供。系统会用这些信息填充 Title View 和 Info 标签。

第三,考虑启用 Picture in Picture(画中画)。它让用户在全屏内容和画中画之间切换,也能同时播放视频并使用其他内容。

import AVKit

func configurePlaybackExperience(playerViewController: AVPlayerViewController) {
    playerViewController.allowsPictureInPicturePlayback = true
    playerViewController.transportBarIncludesTitleView = true
}

关键点:

  • allowsPictureInPicturePlayback = true 允许播放器使用画中画能力。
  • transportBarIncludesTitleView = true 保持标题区域显示。
  • 这些设置建立在 AVPlayerViewController 的标准播放体验之上。
  • 手势、焦点和播放控制继续由系统界面处理。

核心启发

1. 给视频 App 增加“继续观看”推荐标签

  • 做什么:在播放器内添加 Recommended 标签,展示用户还没看完的剧集或相关内容。
  • 为什么值得做:Content Tabs 可以让用户浏览推荐内容,同时留在播放界面里。
  • 怎么开始:创建一个自定义 UIViewController,设置 preferredContentSizetitle,再放进 AVPlayerViewController.customInfoViewControllers

2. 给课程视频增加章节导航

  • 做什么:在播放界面显示课程章节,用户可以直接跳到某个小节。
  • 为什么值得做:演讲提到 AVPlayerItem.navigationMarkerGroups 会让系统显示 Chapters 标签。
  • 怎么开始:为 AVPlayerItem 提供章节 marker group,并用 AVPlayerViewController 播放该 item。

3. 给剧集增加“跳过片头”按钮

  • 做什么:片头出现时显示 Skip Intro,片头结束后隐藏。
  • 为什么值得做:Contextual Actions 专门用于播放过程中的相关操作,交互行为和系统焦点保持一致。
  • 怎么开始:用播放器的 periodic 或 boundary time observer 监听时间,进入片头区间时设置 contextualActions,离开后设为空数组。

4. 给播放控制条增加收藏入口

  • 做什么:用户看节目时,可以直接在播放控制条把当前内容加入收藏。
  • 为什么值得做transportBarCustomMenuItems 支持 UIAction,适合放置和当前媒体强相关的轻量操作。
  • 怎么开始:创建 UIAction(title:image:handler:),把它加入 playerViewController.transportBarCustomMenuItems

5. 给人物访谈添加嘉宾标签页

  • 做什么:在播放器里展示本期嘉宾、主持人或角色列表。
  • 为什么值得做TVMonogramContentConfiguration 提供人物头像样式,适合放在 Content Tabs 的 collection view 中。
  • 怎么开始:用 collection view 承载人物列表,在 cell 中设置 TVMonogramContentConfiguration.cell()

关联 Session

评论

GitHub Issues · utterances