mcpField
A
avkit

avkit

github.com/dpearson2699开发1.2k3.2k 次安装13 周前更新
代码仓库

关于 avkit

这项 Swift 技能介绍如何在 iOS 应用中集成 AVKit 媒体播放,面向开发视频播放体验的开发者。内容涵盖系统播放器界面、画中画、AirPlay、播放控制和字幕。

主要功能

  1. 01UIKit 与 SwiftUI 播放器集成
  2. 02画中画配置
  3. 03AirPlay 路由
  4. 04播放速度与定位
  5. 05字幕轨道选择

适用场景

  1. 01呈现全屏或内嵌视频
  2. 02支持浮动窗口播放视频
  3. 03将播放路由到 AirPlay 设备
  4. 04提供字幕选择和播放速度选项

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill avkit

安装后在 Claude Code 中输入/avkit确认已加载。

附带文件evals/evals.jsonreferences/avkit-patterns.md

详细介绍

AVKit

基于 AVFoundation 构建的高级媒体播放界面。提供系统标准的视频播放器、画中画、AirPlay 路由、播放控制和字幕显示。面向 Swift 6.3 / iOS 26+。

内容

设置

音频会话配置

支持后台音频、AirPlay 或画中画时,播放应用需要设置音频会话类别和相应的后台模式。

  1. 启用 Background Modes > Audio、AirPlay 和 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 播放器。它开箱即用,提供系统播放控制、画中画、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                    // Show/hide system controls
playerVC.videoGravity = .resizeAspect                    // .resizeAspectFill to crop
playerVC.entersFullScreenWhenPlaybackBegins = false
playerVC.exitsFullScreenWhenPlaybackEnds = true
playerVC.updatesNowPlayingInfoCenter = true              // Auto-updates MPNowPlayingInfoCenter

使用 contentOverlayView 在视频和播放控制之间添加非交互式视图(如水印、徽标)。

代理

采用 AVPlayerViewControllerDelegate,以响应全屏切换、画中画生命周期事件、插播播放和媒体选择变更。使用转场协调器的 animate(alongsideTransition:completion:),使界面与全屏动画同步。

显示就绪状态

显示播放器前,先观察 isReadyForDisplay,以避免闪现黑屏:

let observation = playerVC.observe(\.isReadyForDisplay) { observed, _ in
    if observed.isReadyForDisplay {
        // Safe to show the player view
    }
}

SwiftUI VideoPlayer

SwiftUI 视图 VideoPlayer 封装了 AVKit 的播放界面。

基本用法
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 的所有属性。要配置画中画、接收代理回调或控制播放速度,请使用 UIViewControllerRepresentable 封装 AVPlayerViewController。完整模式请参阅 references/avkit-patterns.md。

画中画

画中画允许用户在使用其他应用时通过浮动窗口观看视频。只要应用已完成配置、设备支持画中画,并且当前 AVPlayerItem 是 AVPlayer 兼容格式的可播放视频内容,AVPlayerViewController 就会自动支持画中画。仅含音频的项目、不受支持的容器格式或编解码器,或尚未准备好显示视频的项目,即使应用和设备的配置正确,也可能导致画中画不可用。对于自定义播放器界面,请直接使用 AVPictureInPictureController。

前提条件
  1. 音频会话类别设为 .playback(参见设置)
  2. 启用 Background Modes > Audio、AirPlay 和 Picture in Picture
  3. AVPlayerItem 已就绪,且包含可播放的视频媒体,而非仅含音频的内容
  4. 当前播放上下文允许使用画中画;对于自定义播放器,请观察 isPictureInPicturePossible
标准播放器画中画

AVPlayerViewController 默认启用画中画。控制自动启动以及从内嵌播放切换到画中画:

let playerVC = AVPlayerViewController()
playerVC.player = player

// PiP enabled by default; set false to disable
playerVC.allowsPictureInPicturePlayback = true

// Auto-start PiP when app backgrounds (for inline/non-fullscreen players)
playerVC.canStartPictureInPictureAutomaticallyFromInline = true
PiP 停止时恢复界面

当用户点击 PiP 中的恢复按钮时,实现代理方法以重新呈现播放器。调用完成处理程序并传入 true,通知系统完成恢复动画。

func playerViewController(
    _ playerViewController: AVPlayerViewController,
    restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
) {
    // Re-present or re-embed the player view controller
    present(playerViewController, animated: false) {
        completionHandler(true)
    }
}
自定义播放器 PiP

对于自定义播放器 UI,请将 AVPictureInPictureController 与 AVPlayerLayer 或采样缓冲区内容源配合使用。创建 PiP UI 之前,请检查设备是否支持;然后在当前播放上下文中启动 PiP 之前,检查控制器的 isPictureInPicturePossible。有关完整的自定义播放器和采样缓冲区 PiP 模式,请参阅 references/avkit-patterns.md。

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

// Call this from the user's PiP button action, never automatically.
if pipController.isPictureInPicturePossible {
    pipController.startPictureInPicture()
}
广告期间的线性播放

插播广告段可以来自媒体流/清单,AVFoundation 通过 AVPlayerItem.interstitialTimeRanges 提供这些广告段;也可以来自应用自有的 AVPlayerInterstitialEventController 计划。在 iOS 上,不要直接设置 interstitialTimeRanges。仅在必须播放广告或法律声明片段期间,为阻止跳转而使用 requiresLinearPlayback:

// During an ad
playerVC.requiresLinearPlayback = true

// After the ad completes
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  // Show video-capable routes first
    containerView.addSubview(routePicker)
}
外部播放

AVPlayer 默认允许外部播放。使用 AirPlay 时请保持启用;如果其他代码可能会将其禁用,也可以显式设置:

player.allowsExternalPlayback = true

仅当你希望播放器在外接屏幕模式处于活动状态时自动切换到外部播放,才设置 usesExternalPlaybackWhileExternalScreenIsActive。

播放控制项与播放速度

自定义播放速度

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

let playerVC = AVPlayerViewController()
playerVC.speeds = [
    AVPlaybackSpeed(rate: 0.5, localizedName: "Half Speed"),
    AVPlaybackSpeed(rate: 1.0, localizedName: "Normal"),
    AVPlaybackSpeed(rate: 1.5, localizedName: "1.5x"),
    AVPlaybackSpeed(rate: 2.0, localizedName: "Double Speed")
]

使用 AVPlaybackSpeed.systemDefaultSpeeds 恢复默认速度选项。

跳过与跳转

在 iOS 上,自定义应用控制项请使用标准播放控制项和 AVPlayer.seek(...)。AVPlayerViewController 中的跳过行为 API(例如 isSkipForwardEnabled、isSkipBackwardEnabled 和 skippingBehavior)主要面向 tvOS;请勿在 iOS 播放器实现中使用这些 API。

正在播放集成

默认情况下,AVPlayerViewController 会自动更新 MPNowPlayingInfoCenter。如果你要手动管理正在播放的信息,请禁用此功能:

playerVC.updatesNowPlayingInfoCenter = false

字幕与隐藏式字幕

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

以编程方式选择
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?.select(english, in: group)
}

allowedSubtitleOptionLanguages、requiresFullSubtitles 和 AVPlayerViewControllerDelegate 媒体选择回调仅适用于 tvOS。对于 iOS,如果应用需要设置默认选项,请加载资源的 .legible 媒体选择组,并在 AVPlayerItem 上选择一个选项。

在 HLS 中提供字幕轨道

字幕和隐藏式字幕嵌入在 HLS 清单中。AVKit 会从 AVAsset 上的 AVMediaSelectionGroup 读取这些字幕。对于本地文件,请使用已包含易读字幕或隐藏式字幕轨道的媒体,或者在使用 AVKit 呈现可播放资源之前,为其添加这些轨道。

常见错误

不要:继承 AVPlayerViewController

Apple 明确指出不支持此做法。它可能导致未定义行为,或在未来的 OS 版本中崩溃。

// WRONG
class MyPlayerVC: AVPlayerViewController { } // Unsupported

// CORRECT: Use composition with delegation
let playerVC = AVPlayerViewController()
playerVC.delegate = coordinator
不要:为 PiP 跳过音频会话配置

PiP 和后台播放依赖于播放音频会话类别,以及“音频、AirPlay 和画中画”后台模式。

// WRONG: Default audio session
let playerVC = AVPlayerViewController()
playerVC.player = player // PiP won't work

// CORRECT: Configure the category, then activate when playback starts
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback)
try AVAudioSession.sharedInstance().setActive(true)
let playerVC = AVPlayerViewController()
playerVC.player = player
不要:忘记 PiP 恢复代理或其完成处理程序

如果没有 restoreUserInterfaceForPictureInPictureStopWithCompletionHandler,系统就无法将用户带回播放器。未调用 completionHandler(true) 会使系统处于不一致状态。

// WRONG: No delegate method or missing completionHandler call
// User taps restore in PiP -> nothing happens or animation hangs

// CORRECT
func playerViewController(
    _ playerViewController: AVPlayerViewController,
    restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
) {
    present(playerViewController, animated: false) {
        completionHandler(true)
    }
}
不要:在 SwiftUI 视图的 init 中创建 AVPlayer

过早创建播放器会导致性能问题。SwiftUI 可能会多次重新创建视图。

// WRONG: Created on every view init
struct PlayerView: View {
    let player = AVPlayer(url: videoURL) // Re-created on every view evaluation

    var body: some View { VideoPlayer(player: player) }
}

// CORRECT: Use @State and defer creation
struct PlayerView: View {
    @State private var player: AVPlayer?

    var body: some View {
        VideoPlayer(player: player)
            .task { player = AVPlayer(url: videoURL) }
    }
}

审查清单

  • 音频会话类别设为 .playback,并将 mode 设为 .moviePlayback
  • 将音频会话激活推迟到开始播放时
  • 将音频、AirPlay 和画中画后台模式添加到 UIBackgroundModes
  • 不对 AVPlayerViewController 进行子类化
  • 使用受支持的视频媒体测试 PiP,而不只是测试应用/设备设置
  • 实现 PiP 恢复代理方法,并调用 completionHandler(true)
  • 自定义 PiP 同时检查设备支持情况和当前的 isPictureInPicturePossible
  • 仅在用户明确交互后启动自定义 PiP
  • 在 SwiftUI 中将 AVPlayer 的创建推迟到 .task(而非过早创建)
  • 为内嵌播放器设置 canStartPictureInPictureAutomaticallyFromInline
  • 仅在必要的广告/法律声明片段期间切换 requiresLinearPlayback
  • 不要将仅限 tvOS 的跳过 API 用于 iOS 传输控件
  • 需要 AirPlay 时,不要意外禁用外部播放
  • 使用实际媒体轨道测试字幕选择
  • 适当设置视频重力(.resizeAspect 或 .resizeAspectFill)
  • 在显示播放器视图前观察 isReadyForDisplay
  • 为网络流媒体内容处理错误(HLS 故障、超时)

参考资料

评论

支持 Markdown