AVKit
以 AVFoundation 為基礎的高層級媒體播放 UI。提供系統標準的影片播放器、子母畫面、AirPlay 路由、傳輸控制項及字幕/隱藏式字幕顯示。目標為 Swift 6.3 / iOS 26+。
目錄
設定
音訊會話設定
在任何播放操作之前,請先設定音訊會話與背景模式。否則,PiP 與背景音訊會默默失敗。
- 在 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 播放器。它提供系統播放控制項、PiP、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 在影片與傳輸控制項之間加入非互動式視圖(例如浮水印、標誌)。
委派
採用 AVPlayerViewControllerDelegate 以回應全螢幕轉換、PiP 生命週期事件、插播播放及媒體選擇變更。使用轉場協調器的 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 屬性。若要設定 PiP、使用委派回呼或控制播放速度,請將 AVPlayerViewController 包裝在 UIViewControllerRepresentable 中。完整的模式請參閱 references/avkit-patterns.md。
子母畫面
PiP 讓使用者能在使用其他應用程式時,於浮動視窗中觀看影片。音訊會話設定完成後,AVPlayerViewController 會自動支援 PiP。對於自訂播放器 UI,請直接使用 AVPictureInPictureController。
先決條件
- 音訊會話類別設為
.playback(請參閱設定) - 在
UIBackgroundModes中啟用audio背景模式
標準播放器的 PiP
AVPlayerViewController 預設啟用 PiP。可控制自動啟動與內嵌到 PiP 的轉換:
let playerVC = AVPlayerViewController()
playerVC.player = player
// PiP 預設啟用;設定為 false 可停用
playerVC.allowsPictureInPicturePlayback = true
// 當應用程式進入背景時自動啟動 PiP(適用於內嵌/非全螢幕播放器)
playerVC.canStartPictureInPictureAutomaticallyFromInline = true
在 PiP 停止時還原 UI
當使用者在 PiP 中點擊還原按鈕時,請實作委派方法以重新呈現你的播放器。使用 true 呼叫完成處理常式,通知系統完成還原動畫。
func playerViewController(
_ playerViewController: AVPlayerViewController,
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
) {
// 重新呈現或重新嵌入播放器視圖控制器
present(playerViewController, animated: false) {
completionHandler(true)
}
}
自訂播放器的 PiP
對於自訂播放器 UI,請使用 AVPictureInPictureController 搭配 AVPlayerLayer 或取樣緩衝區內容來源。請先檢查 isPictureInPictureSupported()。完整的自訂播放器與取樣緩衝區 PiP 模式請參閱 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
錯誤:為 PiP 跳過音訊會話設定
如果音訊會話未設定為 .playback,PiP 會默默失敗。背景音訊也會停止運作。
// 錯誤:預設音訊會話
let playerVC = AVPlayerViewController()
playerVC.player = player // PiP 無法運作
// 正確:先設定音訊會話
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback)
try AVAudioSession.sharedInstance().setActive(true)
let playerVC = AVPlayerViewController()
playerVC.player = player
錯誤:忘記 PiP 還原委派或其完成處理常式
沒有 restoreUserInterfaceForPictureInPictureStopWithCompletionHandler,系統就無法將使用者帶回你的播放器。未呼叫 completionHandler(true) 會導致系統處於不一致狀態。
// 錯誤:沒有委派方法或缺少 completionHandler 呼叫
// 使用者在 PiP 中點擊還原 -> 無反應或動畫卡住
// 正確
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未被子類化 - 已實作 PiP 還原委派方法並呼叫
completionHandler(true) -
AVPlayer已在 SwiftUI 中延遲至.task(非急切建立) - 已為內嵌播放器設定
canStartPictureInPictureAutomaticallyFromInline - 僅在必要的廣告/法律區段切換
requiresLinearPlayback - 已在
AVPlayer上啟用allowsExternalPlayback以支援 AirPlay - 已使用實際媒體軌道測試字幕語言限制
- 已適當設定影片重力(
.resizeAspect與.resizeAspectFill) - 已在顯示播放器視圖前觀察
isReadyForDisplay - 已處理網路串流內容的錯誤(HLS 失敗、逾時)
參考資料
- 進階模式(自訂播放器 UI、插播、背景播放、錯誤處理):references/avkit-patterns.md
- AVKit 框架
- AVPlayerViewController
- VideoPlayer (SwiftUI)