AVKit
基于 AVFoundation 的高层媒体播放 UI。提供系统标准的视频播放器、画中画 (Picture-in-Picture)、AirPlay 路由、传输控件以及字幕/隐藏式字幕显示。目标平台:Swift 6.3 / iOS 26+。
目录
- 设置
- AVPlayerViewController
- SwiftUI VideoPlayer
- 画中画 (Picture-in-Picture)
- AirPlay
- 传输控件和播放速度
- 字幕和隐藏式字幕
- 常见错误
- 审查清单
- 参考资料
设置
音频会话配置
在开始任何播放之前,先配置音频会话和后台模式。否则,画中画和后台音频将静默失败。
- 在 Info.plist 的
UIBackgroundModes中添加audio后台模式 - 将音频会话类别设置为
.playback
import AVFoundation
func configureAudioSession() {
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(.playback, mode: .moviePlayback)
try session.setActive(true)
} catch {
print("音频会话配置失败: \(error)")
}
}
导入
import AVKit // AVPlayerViewController, VideoPlayer, PiP
import AVFoundation // AVPlayer, AVPlayerItem, AVAsset
AVPlayerViewController
AVPlayerViewController 是标准的 UIKit 播放器。它开箱即用地提供系统播放控件、画中画、AirPlay、字幕和帧分析。不要对其进行子类化。
基本呈现(全屏)
import AVKit
func presentPlayer(from viewController: UIViewController, url: URL) {
let player = AVPlayer(url: url)
let playerVC = AVPlayerViewController()
playerVC.player = player
viewController.present(playerVC, animated: true) {
player.play()
}
}
内嵌播放
将 AVPlayerViewController 作为子视图控制器添加以实现内嵌播放。调用 addChild,添加视图并设置约束,然后调用 didMove(toParent:)。
func embedPlayer(in parent: UIViewController, container: UIView, url: URL) {
let playerVC = AVPlayerViewController()
playerVC.player = AVPlayer(url: url)
parent.addChild(playerVC)
container.addSubview(playerVC.view)
playerVC.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
playerVC.view.leadingAnchor.constraint(equalTo: container.leadingAnchor),
playerVC.view.trailingAnchor.constraint(equalTo: container.trailingAnchor),
playerVC.view.topAnchor.constraint(equalTo: container.topAnchor),
playerVC.view.bottomAnchor.constraint(equalTo: container.bottomAnchor)
])
playerVC.didMove(toParent: parent)
}
关键属性
playerVC.showsPlaybackControls = true // 显示/隐藏系统控件
playerVC.videoGravity = .resizeAspect // .resizeAspectFill 用于裁剪
playerVC.entersFullScreenWhenPlaybackBegins = false
playerVC.exitsFullScreenWhenPlaybackEnds = true
playerVC.updatesNowPlayingInfoCenter = true // 自动更新 MPNowPlayingInfoCenter
使用 contentOverlayView 在视频和传输控件之间添加非交互式视图(水印、Logo)。
委托
采用 AVPlayerViewControllerDelegate 以响应全屏切换、画中画生命周期事件、插播播放和媒体选择更改。使用过渡协调器的 animate(alongsideTransition:completion:) 将你的 UI 与全屏动画同步。
显示就绪
在显示播放器之前观察 isReadyForDisplay,以避免黑屏闪烁:
let observation = playerVC.observe(\.isReadyForDisplay) { observed, _ in
if observed.isReadyForDisplay {
// 可以安全地显示播放器视图
}
}
SwiftUI VideoPlayer
VideoPlayer SwiftUI 视图封装了 AVKit 的播放 UI。
基本用法
import SwiftUI
import AVKit
struct PlayerView: View {
@State private var player: AVPlayer?
var body: some View {
Group {
if let player {
VideoPlayer(player: player)
.frame(height: 300)
} else {
ProgressView()
}
}
.task {
let url = URL(string: "https://example.com/video.m3u8")!
player = AVPlayer(url: url)
}
}
}
视频叠加层
在视频内容上添加非交互式 SwiftUI 叠加层。
VideoPlayer(player: player) {
VStack {
Spacer()
HStack {
Image("logo")
.resizable()
.frame(width: 40, height: 40)
.padding()
Spacer()
}
}
}
用于高级控制的 UIKit 托管
VideoPlayer 并未暴露所有 AVPlayerViewController 的属性。对于画中画配置、委托回调或播放速度控制,将 AVPlayerViewController 封装在 UIViewControllerRepresentable 中。完整模式请参见 references/avkit-patterns.md。
画中画 (Picture-in-Picture)
画中画允许用户在浮动窗口中观看视频,同时使用其他应用。一旦音频会话配置完成,AVPlayerViewController 会自动支持画中画。对于自定义播放器 UI,直接使用 AVPictureInPictureController。
先决条件
- 音频会话类别设置为
.playback(参见设置) - 在
UIBackgroundModes中添加audio后台模式
标准播放器画中画
AVPlayerViewController 默认启用了画中画。控制自动激活和内嵌到画中画的切换:
let playerVC = AVPlayerViewController()
playerVC.player = player
// 画中画默认启用;设置为 false 可禁用
playerVC.allowsPictureInPicturePlayback = true
// 当应用进入后台时自动启动画中画(适用于内嵌/非全屏播放器)
playerVC.canStartPictureInPictureAutomaticallyFromInline = true
画中画停止时恢复 UI
当用户点击画中画中的恢复按钮时,实现委托方法来重新呈现播放器。调用完成处理程序并传递 true,以通知系统完成恢复动画。
func playerViewController(
_ playerViewController: AVPlayerViewController,
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
) {
// 重新呈现或重新嵌入播放器视图控制器
present(playerViewController, animated: false) {
completionHandler(true)
}
}
自定义播放器画中画
对于自定义播放器 UI,使用带有 AVPlayerLayer 或采样缓冲区内容源的 AVPictureInPictureController。首先检查 isPictureInPictureSupported()。有关完整的自定义播放器和采样缓冲区画中画模式,请参见 references/avkit-patterns.md。
guard AVPictureInPictureController.isPictureInPictureSupported() else { return }
let pipController = AVPictureInPictureController(playerLayer: playerLayer)
pipController.delegate = self
pipController.canStartPictureInPictureAutomaticallyFromInline = true
广告期间的线性播放
通过在广告或法律声明期间切换 requiresLinearPlayback 来阻止用户快进:
// 广告期间
playerVC.requiresLinearPlayback = true
// 广告完成后
playerVC.requiresLinearPlayback = false
AirPlay
AVPlayerViewController 自动支持 AirPlay。使用标准播放器时无需额外代码。当有支持 AirPlay 的设备可用时,系统会在传输控件中显示 AirPlay 按钮。
AVRoutePickerView
在播放器 UI 之外添加一个独立的 AirPlay 路由选择器按钮:
import AVKit
func addRoutePicker(to containerView: UIView) {
let routePicker = AVRoutePickerView(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
routePicker.activeTintColor = .systemBlue
routePicker.prioritizesVideoDevices = true // 优先显示支持视频的路由
containerView.addSubview(routePicker)
}
启用外部播放
确保 AVPlayer 允许外部播放(默认启用):
player.allowsExternalPlayback = true
player.usesExternalPlaybackWhileExternalScreenIsActive = true
传输控件和播放速度
自定义播放速度
在播放器 UI 中为用户提供可选的播放速度:
let playerVC = AVPlayerViewController()
playerVC.speeds = [
AVPlaybackSpeed(rate: 0.5, localizedName: "半速"),
AVPlaybackSpeed(rate: 1.0, localizedName: "正常"),
AVPlaybackSpeed(rate: 1.5, localizedName: "1.5倍速"),
AVPlaybackSpeed(rate: 2.0, localizedName: "双倍速")
]
使用 AVPlaybackSpeed.systemDefaultSpeeds 恢复默认速度选项。
跳转行为
配置前进/后退跳转控件:
playerVC.isSkipForwardEnabled = true
playerVC.isSkipBackwardEnabled = true
正在播放信息集成
AVPlayerViewController 默认自动更新 MPNowPlayingInfoCenter。如果你手动管理正在播放信息,请禁用此项:
playerVC.updatesNowPlayingInfoCenter = false
字幕和隐藏式字幕
当媒体包含适当的文本轨道时,AVKit 会自动处理字幕和隐藏式字幕的显示。用户可以在“设置”>“辅助功能”>“字幕与隐藏式字幕”中控制字幕偏好。
限制字幕语言
// 仅显示英语和西班牙语字幕选项
playerVC.allowedSubtitleOptionLanguages = ["en", "es"]
强制显示字幕
强制字幕始终显示(用户无法关闭):
playerVC.requiresFullSubtitles = true
媒体选择(委托)
响应用户更改字幕或音轨选择:
func playerViewController(
_ playerViewController: AVPlayerViewController,
didSelect mediaSelectionOption: AVMediaSelectionOption?,
in mediaSelectionGroup: AVMediaSelectionGroup
) {
if let option = mediaSelectionOption {
print("已选择: \(option.displayName)")
}
}
在 HLS 中提供字幕轨道
字幕和隐藏式字幕嵌入在 HLS 清单中。AVKit 从 AVAsset 的 AVMediaSelectionGroup 中读取它们。对于本地文件,使用 AVMutableComposition 添加 AVMediaCharacteristic.legible 轨道。
常见错误
不要:子类化 AVPlayerViewController
Apple 明确声明不支持这种做法。它可能导致未定义的行为或在未来 OS 版本上崩溃。
// 错误
class MyPlayerVC: AVPlayerViewController { } // 不支持
// 正确:使用组合与委托
let playerVC = AVPlayerViewController()
playerVC.delegate = coordinator
不要:为画中画跳过音频会话配置
如果音频会话未设置为 .playback,画中画会静默失败。后台音频也会停止工作。
// 错误:默认音频会话
let playerVC = AVPlayerViewController()
playerVC.player = player // 画中画无法工作
// 正确:先配置音频会话
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback)
try AVAudioSession.sharedInstance().setActive(true)
let playerVC = AVPlayerViewController()
playerVC.player = player
不要:忘记画中画恢复委托或其完成处理程序
如果没有 restoreUserInterfaceForPictureInPictureStopWithCompletionHandler,系统无法将用户返回到你的播放器。未能调用 completionHandler(true) 会使系统处于不一致状态。
// 错误:没有委托方法或缺少 completionHandler 调用
// 用户在画中画中点击恢复 -> 无响应或动画挂起
// 正确
func playerViewController(
_ playerViewController: AVPlayerViewController,
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
) {
present(playerViewController, animated: false) {
completionHandler(true)
}
}
不要:在 SwiftUI 视图的 init 中创建 AVPlayer
急切地创建播放器会导致性能问题。SwiftUI 可能会多次重新创建视图。
// 错误:每次视图初始化时创建
struct PlayerView: View {
let player = AVPlayer(url: videoURL) // 每次视图计算时重新创建
var body: some View { VideoPlayer(player: player) }
}
// 正确:使用 @State 并延迟创建
struct PlayerView: View {
@State private var player: AVPlayer?
var body: some View {
VideoPlayer(player: player)
.task { player = AVPlayer(url: videoURL) }
}
}
审查清单
- 音频会话类别设置为
.playback,模式为mode: .moviePlayback - 在 Info.plist 的
UIBackgroundModes中添加了audio后台模式 - 没有子类化
AVPlayerViewController - 实现了画中画恢复委托方法并调用了
completionHandler(true) - 在 SwiftUI 中将
AVPlayer延迟到.task创建(不急切创建) - 为内嵌播放器设置了
canStartPictureInPictureAutomaticallyFromInline - 仅在必要的广告/法律片段期间切换
requiresLinearPlayback - 在
AVPlayer上启用了allowsExternalPlayback以支持 AirPlay - 已使用实际媒体轨道测试了字幕语言限制
- 视频重力设置适当(
.resizeAspect与.resizeAspectFill) - 在显示播放器视图之前观察了
isReadyForDisplay - 处理了网络流内容的错误(HLS 故障、超时)
参考资料
- 高级模式(自定义播放器 UI、插播、后台播放、错误处理):references/avkit-patterns.md
- AVKit 框架
- AVPlayerViewController
- VideoPlayer (SwiftUI)