Sample CodeiOS, iPadOS, Mac CatalystReviewed 2026-07-21View on Apple Developer

Integrating AirPlay for long-form video apps

At a glance

Item Summary
Purpose Prepare route selection before playback and present either local AVKit playback or a dedicated remote-control screen.
App architecture Compact storyboard MVC with one route-selection controller and one externally playing remote-control controller.
Main patterns MVC, asynchronous callback, route-result branching, and property injection of AVPlayer.
Project style Three root-level controller/delegate files; storyboard provides navigation and outlets.

Project structure

LongFormVideoApp/
├── AppDelegate.swift                 # App entry and route-policy logging
├── ViewController.swift              # Route selection and playback creation
├── RemoteControlViewController.swift # External-playback controls
└── Base.lproj/Main.storyboard        # Screen wiring and identifiers

Structure observations

  • Playback routing policy is in the main controller rather than a separate service.
  • The remote screen receives an already configured player; it does not know how to create content or choose a route.
  • There are no custom model types or application protocols.

Overall architecture

Reference code

LongFormVideoApp/ViewController.swift:17 — route-driven composition

AVAudioSession.sharedInstance().prepareRouteSelectionForPlayback {
    shouldStartPlayback, routeSelection in
    if shouldStartPlayback {
        switch routeSelection {
        case .local:    /* present AVPlayerViewController */
        case .external: /* present RemoteControlViewController */
        case .none:     /* cancel */
        @unknown default: /* cancel */
        }
    }
}

Interpretation

ViewController is both UI controller and playback composition root. Route selection is the decision boundary: local playback delegates UI to AVKit, while external playback retains the same AVPlayer through a custom remote-control screen.

Ownership and state

Ownership evidence

LongFormVideoApp/ViewController.swift:43 — player injected before presentation

let player = AVPlayer(url: url)
let identifier = "RemoteControlViewControllerID"
if let remoteControlVC = storyboard.instantiateViewController(
    withIdentifier: identifier
) as? RemoteControlViewController {
    remoteControlVC.player = player
    self.present(remoteControlVC, animated: true, completion: nil)
    player.play()
}
Owner Object or state Relationship Mutation authority
Storyboard/view hierarchy Main and remote controllers, outlets Instantiates and retains UI UIKit lifecycle
ViewController action AVPlayer and destination controller Creates for selected route Creation/configuration during callback
AVPlayerViewController Local-route player Receives and retains AVKit controls and initial action
RemoteControlViewController External-route player Property injection and strong optional retention Remote button actions

Class and protocol design

LongFormVideoApp/RemoteControlViewController.swift:11 — focused remote controller

class RemoteControlViewController: UIViewController {
    var player: AVPlayer?

    @IBAction func playPauseButtonPressed(_ sender: UIButton) {
        player?.rate != 0 ? player?.pause() : player?.play()
    }
}
Type Responsibility Depends on
ViewController Ask for route selection, configure audio category, create player, present route-specific UI UIKit, AVKit, AVFoundation
RemoteControlViewController Seek and toggle a supplied externally playing player UIKit, AVPlayer
AppDelegate Application entry and diagnostic route-policy output UIKit, AVFoundation

The source uses UIKit inheritance and closure callbacks rather than defining a playback protocol.

Access control

Symbol Access Verified effect Likely rationale
Controllers, outlets, actions, and player internal by default Any code in the app module can access them; external modules cannot. Minimal ceremony for a storyboard sample.
RemoteControlViewController.player internal mutable optional Main controller can inject it before presentation. Supports simple property injection, though it does not enforce initialization.
Operation-local player/controller variables lexical local scope Only the route callback configures them before transfer to the presented controller. Keeps route-specific setup temporary.
No private or fileprivate declarations n/a Ownership is expressed by local scope and controller roles, not modifiers. Concise teaching code; a larger app would narrow outlets/actions/state.

Reference code

LongFormVideoApp/RemoteControlViewController.swift:15 — storyboard and injection surface

@IBOutlet weak var playPauseButton: UIButton!
var player: AVPlayer?

@IBAction func skipAhead30ButtonPressed(_ sender: UIButton) {
    // seek the injected player
}

Logic ownership and placement

Logic Owning type or file Placement rationale
Route preparation and local/external decision ViewController Main playback action owns navigation policy
Audio-session playback category Both branches in ViewController Applied immediately before player creation
Local playback controls AVPlayerViewController Uses system UI rather than duplicating controls
External playback controls RemoteControlViewController Keeps device UI useful while video plays externally
Skip range clamping Remote button actions Small presentation command near its control

Design patterns

Pattern Source evidence Purpose or tradeoff
MVC LongFormVideoApp/ViewController.swift:11, LongFormVideoApp/RemoteControlViewController.swift:11 Storyboard controllers own screen behavior and framework objects.
Async callback LongFormVideoApp/ViewController.swift:18 Continues only after the system resolves playback routing.
Route strategy by enum LongFormVideoApp/ViewController.swift:20 Chooses a UI strategy with a direct switch; no polymorphic abstraction needed.
Property injection LongFormVideoApp/ViewController.swift:47, LongFormVideoApp/RemoteControlViewController.swift:18 Reuses the configured player in the remote screen; optional state permits misuse if not set.

Naming conventions

  • Controller names follow UIKit conventions; RemoteControlViewController states its specialized role.
  • Outlet and action names include the control and event, such as skipBack30ButtonPressed.
  • Boolean callback values use system-provided intent (shouldStartPlayback).
  • The storyboard identifier appends ID, making dynamic lookup explicit.

Architecture takeaways

  • Let route selection complete before constructing route-specific playback UI.
  • Inject the same player into a dedicated remote screen for external playback controls.
  • Prefer the system player controller for local playback.
  • For production, constructor injection or a nonoptional player would make the remote controller’s dependency safer.

Source map

Source file Relevant symbols
LongFormVideoApp/ViewController.swift route callback, player creation, presentation
LongFormVideoApp/RemoteControlViewController.swift injected player and playback commands
LongFormVideoApp/AppDelegate.swift app entry and route-policy diagnostic