mcpField
A
avkit

avkit

github.com/dpearson2699開発1.2k3.2k 回インストール13 週間前に更新
リポジトリ

avkit について

iOS アプリへの AVKit メディア再生機能の組み込みを支援する Swift スキルです。動画プレーヤーを開発する人向けに、システム再生 UI、ピクチャ・イン・ピクチャ、AirPlay、再生操作、字幕を扱います。

主な機能

  1. 01UIKit と SwiftUI のプレーヤー連携
  2. 02ピクチャ・イン・ピクチャの設定
  3. 03AirPlay のルーティング
  4. 04再生速度とシーク
  5. 05字幕トラックの選択

活用シーン

  1. 01全画面またはインライン動画の表示
  2. 02フローティングウィンドウでの動画再生
  3. 03AirPlay デバイスへの再生出力
  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 を基盤とする高レベルのメディア再生 UI です。システム標準の ビデオプレーヤー、ピクチャ・イン・ピクチャ、AirPlay ルーティング、再生コントロール、 字幕/キャプション表示を提供します。Swift 6.3 / iOS 26+ を対象としています。

目次

セットアップ

オーディオセッションの設定

バックグラウンドオーディオ、AirPlay、または PiP に対応する場合、再生アプリには オーディオセッションカテゴリと、それに対応するバックグラウンドモードが必要です。

  1. 「バックグラウンドモード」>「オーディオ、AirPlay、ピクチャ・イン・ピクチャ」を有効にします(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 を子ビューコントローラーとして追加します。 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 を採用すると、フルスクリーンへの遷移、 PiP のライフサイクルイベント、インターstitial再生、メディア選択の変更に対応できます。 フルスクリーンアニメーションと UI を同期するには、トランジションコーディネーターの 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 の再生 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 を使うと、他のアプリを使用しながら、フローティングウィンドウで動画を視聴できます。 アプリが適切に設定され、デバイスが PiP に対応しており、現在の AVPlayerItem が AVPlayer 対応形式の再生可能な動画コンテンツであれば、AVPlayerViewController は PiP を自動的に サポートします。オーディオのみのアイテム、未対応のコンテナ/コーデック、または動画を表示できる状態になっていないアイテムでは、 アプリとデバイスが正しく設定されていても PiP を利用できない場合があります。カスタムプレーヤー UI では、 AVPictureInPictureController を直接使用します。

前提条件
  1. オーディオセッションカテゴリが .playback に設定されていること(セットアップを参照)
  2. 「バックグラウンドモード」>「オーディオ、AirPlay、ピクチャ・イン・ピクチャ」が有効であること
  3. オーディオのみのコンテンツではなく、再生可能な動画メディアを含む AVPlayerItem の準備ができていること
  4. 現在の再生コンテキストで PiP が許可されていること。カスタムプレーヤーでは isPictureInPicturePossible を監視します。
標準プレーヤーの PiP

PiP は AVPlayerViewController でデフォルトで有効になっています。自動起動と インラインから PiP への遷移を制御します。

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 停止時に UI を復元する

ユーザーが 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 では、AVPlayerLayer またはサンプルバッファーコンテンツソースとともに AVPictureInPictureController を使用します。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(...) を使用します。isSkipForwardEnabled、isSkipBackwardEnabled、skippingBehavior などの AVPlayerViewController のスキップ動作 API は tvOS 向けです。iOS のプレーヤー実装では使用しないでください。

再生中情報との連携

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 を指定している
  • 再生開始までオーディオセッションの有効化を保留している
  • UIBackgroundModes にオーディオ、AirPlay、ピクチャ・イン・ピクチャのバックグラウンドモードを追加している
  • AVPlayerViewController をサブクラス化していない
  • アプリ/デバイスの設定だけでなく、サポート対象の動画メディアで PiP をテストしている
  • PiP の復元デリゲートメソッドを実装し、completionHandler(true) を呼び出している
  • カスタム PiP でデバイスのサポート状況と現在の isPictureInPicturePossible の両方を確認している
  • カスタム PiP を明示的なユーザー操作からのみ開始している
  • SwiftUI では AVPlayer の作成を .task まで保留している(先行して作成していない)
  • インラインプレーヤーで canStartPictureInPictureAutomaticallyFromInline を設定している
  • requiresLinearPlayback の切り替えを、必要な広告/法的要件のある区間に限っている
  • iOS のトランスポートコントロールに tvOS 専用のスキップ API を使用していない
  • AirPlay が必要な場合に、外部再生を誤って無効にしていない
  • 実際のメディアトラックを使って字幕の選択をテストしている
  • 適切なビデオグラビティを設定している(.resizeAspect と .resizeAspectFill のどちらか)
  • プレーヤービューを表示する前に isReadyForDisplay を監視している
  • ネットワークストリーミングコンテンツのエラー処理を実装している(HLS の失敗、タイムアウト)

参考資料

コメント