avkit

avkit

热门

使用 AVKit 构建媒体播放体验。适用于通过 AVPlayerViewController 添加视频播放器、开启画中画(Picture-in-Picture)、通过 AirPlay 投屏/路由媒体、使用 SwiftUI VideoPlayer 视图、配置播放控制条、显示字幕与隐藏字幕(Closed Captions),或将 AVFoundation 播放功能与系统 UI 进行集成等场景。

961Star
48Fork
更新于 2026/7/31
SKILL.md
只读
名称
avkit
描述

使用 AVKit 构建媒体播放体验。适用于通过 AVPlayerViewController 添加视频播放器、开启画中画(Picture-in-Picture)、通过 AirPlay 投屏/路由媒体、使用 SwiftUI VideoPlayer 视图、配置播放控制条、显示字幕与隐藏字幕(Closed Captions),或将 AVFoundation 播放功能与系统 UI 进行集成等场景。

AVKit

基于 AVFoundation 构建的高层级媒体播放 UI。提供系统标准视频播放器、画中画(Picture-in-Picture)、AirPlay 投屏路由、播放控制条以及字幕/隐藏字幕显示功能。面向 Swift 6.3 / iOS 26+。

目录

准备工作

音频会话配置

支持后台音频、AirPlay 或画中画(PiP)的播放类 App 需要配置相应的音频会话类别(Audio Session Category)及对应的后台模式(Background Mode)。

  1. 启用 Background Modes > Audio, AirPlay, and Picture in Picture(即 UIBackgroundModes 中的 audio 值)
  2. 将音频会话类别设置为 .playback
  3. 延迟调用 setActive(true) 直至开始播放,避免过早打断其它应用播放的音频
import AVFoundation

func configureAudioSessionForPlayback() {
    let session = AVAudioSession.sharedInstance()
    do {
        try session.setCategory(.playback, mode: .moviePlayback)
    } catch {
        print("Audio session category failed: \(error)")
    }
}

func activateAudioSessionWhenPlaybackBegins() {
    do {
        try AVAudioSession.sharedInstance().setActive(true)
    } catch {
        print("Audio session activation failed: \(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 作为子视图控制器(Child View Controller)添加。依次调用 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 等)。

Delegate 代理回调

遵循 AVPlayerViewControllerDelegate 协议可以响应全屏切换、画中画生命周期事件、插播广告播放以及媒体选择变更等。利用转场协调器的 animate(alongsideTransition:completion:) 方法,能够使自定义 UI 与全屏动画保持同步。

准备就绪状态检测

在展示播放器之前观察 isReadyForDisplay 属性,以避免画面出现黑屏闪烁:

let observation = playerVC.observe(\.isReadyForDisplay) { observed, _ in
    if observed.isReadyForDisplay {
        // 安全展示播放器视图
    }
}

SwiftUI VideoPlayer

SwiftUI 中的 VideoPlayer 视图封装了 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 覆盖层(Overlay)。该覆盖层支持交互,但仅会接收系统控制条未处理的事件。

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 中的完整模式说明。

画中画 (Picture-in-Picture)

画中画(PiP)允许用户在使用其它 App 的同时,通过浮窗观看视频。只要 App 完成相应配置、设备支持 PiP,且当前 AVPlayerItem 包含符合 AVPlayer 格式的可播放视频内容,AVPlayerViewController 就会自动支持画中画。哪怕 App 和设备配置均无误,纯音频资源、不支持的容器/编解码格式、或尚未准备好展示视频的资源都会导致 PiP 无法启动。对于自定义播放器 UI,请直接使用 AVPictureInPictureController

前提条件

  1. 音频会话类别设置为 .playback(参见准备工作
  2. 已启用 Background Modes > Audio, AirPlay, and Picture in Picture
  3. 处于就绪状态且包含可播放视频媒体(而非纯音频内容)的 AVPlayerItem
  4. 当前播放上下文允许使用 PiP;自定义播放器需观察 isPictureInPicturePossible 属性

标准播放器画中画

AVPlayerViewController 默认开启 PiP 支持。可通过以下属性控制自动激活以及内联转画中画过渡:

let playerVC = AVPlayerViewController()
playerVC.player = player

// PiP 默认启用;设为 false 可禁用
playerVC.allowsPictureInPicturePlayback = true

// 当 App 切到后台时自动开启 PiP(适用于内联/非全屏播放器)
playerVC.canStartPictureInPictureAutomaticallyFromInline = true

画中画停止时恢复 UI

当用户点击画中画浮窗中的恢复按钮时,需实现代理方法重新展示播放器。调用完成回调(completion handler)并传入 true,以通知系统完成 UI 恢复动画。

func playerViewController(
    _ playerViewController: AVPlayerViewController,
    restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
) {
    // 重新展示或重新嵌入播放器视图控制器
    present(playerViewController, animated: false) {
        completionHandler(true)
    }
}

自定义播放器画中画

针对自定义播放器 UI,需配合 AVPlayerLayer 或 Sample Buffer 内容源使用 AVPictureInPictureController。在创建画中画 UI 之前先检查设备支持情况,而后在当前播放上下文中启动 PiP 前检查控制器的 isPictureInPicturePossible。完整自定义播放器及 Sample Buffer 画中画模式请参阅 references/avkit-patterns.md

guard AVPictureInPictureController.isPictureInPictureSupported() else { return }
let pipController = AVPictureInPictureController(playerLayer: playerLayer)
pipController.delegate = self
pipController.canStartPictureInPictureAutomaticallyFromInline = true

// 应由用户的画中画按钮点击事件触发,切勿自动调用
if pipController.isPictureInPicturePossible {
    pipController.startPictureInPicture()
}

广告期间的线性播放

插播广告(Interstitial breaks)可以来自媒体流/Manifest 说明文件(AVFoundation 会通过 AVPlayerItem.interstitialTimeRanges 暴露),也可以来自 App 自定义的 AVPlayerInterstitialEventController 调度计划。在 iOS 上切勿直接为 interstitialTimeRanges 赋值。仅在必须禁止快进/快退的广告或法律声明片段中将 requiresLinearPlayback 设为 true

// 播放广告期间
playerVC.requiresLinearPlayback = true

// 广告播放结束后
playerVC.requiresLinearPlayback = false

AirPlay

只要 App 配置、媒体资源、网络路由和设备支持允许外部播放,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 默认允许外部播放。请保持其开启状态以保证 AirPlay 正常工作;如果项目其它位置的代码可能关闭它,则需显式设置:

player.allowsExternalPlayback = true

仅当希望播放器在外部屏幕模式激活时自动切换到外部播放,才需要设置 usesExternalPlaybackWhileExternalScreenIsActive

播放控制与播放速度

自定义播放速度

在播放器 UI 中提供可供用户选择的倍速选项:

let playerVC = AVPlayerViewController()
playerVC.speeds = [
    AVPlaybackSpeed(rate: 0.5, localizedName: "0.5 倍速"),
    AVPlaybackSpeed(rate: 1.0, localizedName: "正常"),
    AVPlaybackSpeed(rate: 1.5, localizedName: "1.5 倍速"),
    AVPlaybackSpeed(rate: 2.0, localizedName: "2.0 倍速")
]

使用 AVPlaybackSpeed.systemDefaultSpeeds 可恢复系统默认的倍速选项。

快进/快退与进度拖拽

在 iOS 上,请使用标准的播放控制条,自定义控件则使用 AVPlayer.seek(...)AVPlayerViewController 的快进快退行为 API(如 isSkipForwardEnabledisSkipBackwardEnabledskippingBehavior)主要面向 tvOS,iOS 播放器实现中应避免使用。

正在播放 (Now Playing) 信息集成

AVPlayerViewController 默认会自动更新 MPNowPlayingInfoCenter。如果您需要手动管理“正在播放”信息,可将其禁用:

playerVC.updatesNowPlayingInfoCenter = false

字幕与隐藏字幕

当媒体包含相应的文本轨道时,AVKit 会自动处理字幕及隐藏字幕(Closed Captions)的显示。用户可以在“设置 > 辅助功能 > 字幕与隐藏字幕”中控制字幕偏好设置。

代码控制字幕选择

let asset = player.currentItem?.asset

if let group = try await asset?.loadMediaSelectionGroup(for: .legible),
   let english = group.options.first(where: { option in
       option.locale?.language.languageCode?.identifier == "en"
   }) {
    player.currentItem?.se

<!-- truncated for translation batch; full body continues in source -->