Loading...
Loading...
Create media playback experiences using AVKit. Use when adding video players with AVPlayerViewController, enabling Picture-in-Picture, routing media with AirPlay, using SwiftUI VideoPlayer views, configuring transport controls, displaying subtitles and closed captions, or integrating AVFoundation playback with system UI.
npx skill4agent add dpearson2699/swift-ios-skills avkitaudioUIBackgroundModes.playbackimport AVFoundation
func configureAudioSession() {
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(.playback, mode: .moviePlayback)
try session.setActive(true)
} catch {
print("Audio session configuration failed: \(error)")
}
}import AVKit // AVPlayerViewController, VideoPlayer, PiP
import AVFoundation // AVPlayer, AVPlayerItem, AVAssetAVPlayerViewControllerimport 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()
}
}AVPlayerViewControlleraddChilddidMove(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 MPNowPlayingInfoCentercontentOverlayViewAVPlayerViewControllerDelegateanimate(alongsideTransition:completion:)isReadyForDisplaylet observation = playerVC.observe(\.isReadyForDisplay) { observed, _ in
if observed.isReadyForDisplay {
// Safe to show the player view
}
}VideoPlayerimport 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)
}
}
}VideoPlayer(player: player) {
VStack {
Spacer()
HStack {
Image("logo")
.resizable()
.frame(width: 40, height: 40)
.padding()
Spacer()
}
}
}VideoPlayerAVPlayerViewControllerAVPlayerViewControllerUIViewControllerRepresentableAVPlayerViewControllerAVPictureInPictureController.playbackaudioUIBackgroundModesAVPlayerViewControllerlet 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 = truetruefunc playerViewController(
_ playerViewController: AVPlayerViewController,
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
) {
// Re-present or re-embed the player view controller
present(playerViewController, animated: false) {
completionHandler(true)
}
}AVPictureInPictureControllerAVPlayerLayerisPictureInPictureSupported()guard AVPictureInPictureController.isPictureInPictureSupported() else { return }
let pipController = AVPictureInPictureController(playerLayer: playerLayer)
pipController.delegate = self
pipController.canStartPictureInPictureAutomaticallyFromInline = truerequiresLinearPlayback// During an ad
playerVC.requiresLinearPlayback = true
// After the ad completes
playerVC.requiresLinearPlayback = falseAVPlayerViewControllerimport 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)
}AVPlayerplayer.allowsExternalPlayback = true
player.usesExternalPlaybackWhileExternalScreenIsActive = truelet 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.systemDefaultSpeedsplayerVC.isSkipForwardEnabled = true
playerVC.isSkipBackwardEnabled = trueAVPlayerViewControllerMPNowPlayingInfoCenterplayerVC.updatesNowPlayingInfoCenter = false// Only show English and Spanish subtitle options
playerVC.allowedSubtitleOptionLanguages = ["en", "es"]playerVC.requiresFullSubtitles = truefunc playerViewController(
_ playerViewController: AVPlayerViewController,
didSelect mediaSelectionOption: AVMediaSelectionOption?,
in mediaSelectionGroup: AVMediaSelectionGroup
) {
if let option = mediaSelectionOption {
print("Selected: \(option.displayName)")
}
}AVMediaSelectionGroupAVAssetAVMutableCompositionAVMediaCharacteristic.legible// WRONG
class MyPlayerVC: AVPlayerViewController { } // Unsupported
// CORRECT: Use composition with delegation
let playerVC = AVPlayerViewController()
playerVC.delegate = coordinator.playback// WRONG: Default audio session
let playerVC = AVPlayerViewController()
playerVC.player = player // PiP won't work
// CORRECT: Configure audio session first
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback)
try AVAudioSession.sharedInstance().setActive(true)
let playerVC = AVPlayerViewController()
playerVC.player = playerrestoreUserInterfaceForPictureInPictureStopWithCompletionHandlercompletionHandler(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)
}
}// 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) }
}
}.playbackmode: .moviePlaybackaudioUIBackgroundModesAVPlayerViewControllercompletionHandler(true)AVPlayer.taskcanStartPictureInPictureAutomaticallyFromInlinerequiresLinearPlaybackallowsExternalPlaybackAVPlayer.resizeAspect.resizeAspectFillisReadyForDisplay