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
flowchart LR
UI["ViewController"] --> Audio["AVAudioSession.prepareRouteSelectionForPlayback"]
Audio --> Local["local route"]
Audio --> External["external route"]
Local --> AVKit["AVPlayerViewController"]
External --> Remote["RemoteControlViewController"]
AVKit --> Player["AVPlayer"]
Remote --> Player
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
classDiagram
ViewController ..> AVPlayer : creates per action
ViewController *-- AVPlayerViewController : creates for local route
AVPlayerViewController *-- AVPlayer : retains local player
ViewController *-- RemoteControlViewController : instantiates and presents
RemoteControlViewController *-- AVPlayer : receives and retains external player
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 |