Sample CodetvOSReviewed 2026-07-21View on Apple Developer

Working with Overlays and Parental Controls in tvOS

At a glance

Item Summary
Purpose Adds interactive overlays, playback-restriction checks, and live-channel navigation to tvOS playback.
App architecture The menu owns a reusable playback screen; that screen owns and replaces the AVKit controller, player, overlay, pending item, and observations for each playback request.
Main patterns Screen-owned coordinator, framework delegate, KVO observer, metadata-driven policy, and child-view-controller composition.
Project style A compact storyboard/XIB UIKit app with one menu controller, one playback controller, and one overlay controller.

Project structure

TVAVPlayerViewControllerDemo/
└── TVAVPlayerViewControllerDemo/
    ├── AppDelegate.swift
    ├── MenuViewController.swift
    ├── VideoPlaybackViewController.swift
    ├── CustomInteractiveVideoOverlayViewController.swift
    └── Base.lproj/
        ├── Main.storyboard
        └── CustomInteractiveVideoOverlay.xib

Structure observations

  • Screen controllers map directly to the menu, playback, and interactive-overlay responsibilities.
  • The overlay’s visual hierarchy lives in a XIB; playback navigation lives in the main storyboard.
  • All player lifecycle and policy logic stays in the playback controller rather than the menu or overlay.

Overall architecture

Reference code

TVAVPlayerViewControllerDemo/TVAVPlayerViewControllerDemo/VideoPlaybackViewController.swift:100 — the playback screen replaces the AVKit controller and attaches its owned overlay before playback begins.

private func loadNewPlayerViewController() {
    let newPlayerViewController = AVPlayerViewController()
    newPlayerViewController.delegate = self
    newPlayerViewController.customOverlayViewController = customInteractiveVideoOverlay
    if let oldPlayerViewController = self.playerViewController {
        oldPlayerViewController.removeFromParent()
        oldPlayerViewController.viewIfLoaded?.removeFromSuperview()
    }
    self.playerViewController = newPlayerViewController
    player = AVQueuePlayer()
    addChild(newPlayerViewController)
}

Interpretation

The menu supplies content facts and presents one playback owner. The playback controller turns those facts into AV metadata, gates the pending item through parental authorization, and composes the AVKit UI with a custom overlay.

Ownership and state

Ownership evidence

TVAVPlayerViewControllerDemo/TVAVPlayerViewControllerDemo/VideoPlaybackViewController.swift:18 — one controller retains all mutable playback collaborators and policy-observation state.

@objc dynamic var playerViewController: AVPlayerViewController?
let customInteractiveVideoOverlay = CustomInteractiveVideoOverlay(
    nibName: "CustomInteractiveVideoOverlay", bundle: nil)
var player: AVQueuePlayer?
@objc dynamic var pendingPlayerItem: AVPlayerItem?
var playerItemStatusObservation: NSKeyValueObservation?
var currentItemErrorObservation: NSKeyValueObservation?
var isPlaybackActive = false
Owner Object or state Relationship Mutation authority
MenuViewController VideoPlaybackViewController Creates in either initializer and retains Menu presents and loads content
Playback controller AVKit controller and queue player Recreates for each load and tears down on dismissal Playback controller only in practice
Playback controller Overlay Creates once and assigns to each new AVKit controller Playback controller
Playback controller Pending item and KVO tokens Retains during readiness/policy gate KVO callbacks clear or dismiss
AVKit controller Player and overlay references Receives after setup/authorization Playback owner and AVKit lifecycle

Class and protocol design

Type or protocol Responsibility Depends on
MenuViewController Convert poster selection into content metadata and present playback TVUIKit poster views and playback controller
VideoPlaybackViewController Build metadata, gate playback, compose AVKit UI, handle errors and dismissal AVKit, KVO, UIKit containment
AVPlayerViewControllerDelegate conformance Approve dismissal and implement next/previous channel requests AVPlayerViewController
CustomInteractiveVideoOverlay Supply focusable custom controls over video XIB outlets and tvOS focus engine

There is no app-defined protocol layer. The sample uses AVKit’s delegate directly because one playback controller is both the framework adapter and lifecycle owner.

Access control

Symbol Access Verified effect Likely rationale
Metadata factories, player-controller loader, and _dismiss private Only the playback implementation can build policy metadata or alter containment directly. Preserve the load/authorize/play/dismiss sequence.
Controllers and stored playback state implicit internal Other files in the app target can refer to them; nothing is exported as public. The sample is an application, not a reusable framework.
playerViewController and pendingPlayerItem implicit internal, plus @objc dynamic Module code can access them; Objective-C dynamic dispatch enables key-path observation. KVO requires runtime-visible properties; dynamic does not itself make them public.
checkParentalControlsExplicitly implicit internal global constant Any source file in the target can read the sample switch. Makes automatic versus explicit behavior easy to compare.
Overlay outlet implicit internal Storyboard/XIB wiring and app code may access it. Simple target-local UI integration.

Reference code

TVAVPlayerViewControllerDemo/TVAVPlayerViewControllerDemo/VideoPlaybackViewController.swift:88 — metadata and controller-construction primitives are private; callers receive the higher-level loadAndPlay command.

private func metadataItem(_ identifier: AVMetadataIdentifier,
                          stringValue value: String) -> AVMutableMetadataItem {
    let metadataItem = AVMutableMetadataItem()
    metadataItem.value = value as NSString
    metadataItem.identifier = identifier
    return metadataItem
}

private func loadNewPlayerViewController() {
    let newPlayerViewController = AVPlayerViewController()
    // setup remains inside the playback owner
}

Logic ownership and placement

Logic Owning type or file Placement rationale
Poster-to-content mapping MenuViewController Menu presentation knows which catalog choice the user selected.
AV metadata creation, including content rating Playback controller Metadata directly drives system playback and restriction behavior.
Player/controller replacement and child containment Playback controller One owner maintains the UIKit and AVKit lifecycle.
Readiness, error, and authorization handling commonInit KVO closures These transitions originate from observed player state.
Focus order for custom controls Overlay controller Focus is a view-level concern.
Channel skip and dismissal integration AVKit delegate methods Framework-requested navigation belongs at the delegate boundary.

Design patterns

Pattern Source evidence Purpose or tradeoff
Screen-owned coordinator TVAVPlayerViewControllerDemo/TVAVPlayerViewControllerDemo/MenuViewController.swift:13 Reuses one playback authority across menu selections.
Framework delegate TVAVPlayerViewControllerDemo/TVAVPlayerViewControllerDemo/VideoPlaybackViewController.swift:166 Adapts AVKit dismissal and channel requests to app behavior.
KVO observer TVAVPlayerViewControllerDemo/TVAVPlayerViewControllerDemo/VideoPlaybackViewController.swift:31 Delays policy and playback decisions until item readiness; requires careful token ownership.
Metadata-driven policy TVAVPlayerViewControllerDemo/TVAVPlayerViewControllerDemo/VideoPlaybackViewController.swift:116 Places content rating alongside title/description on the item AVKit evaluates.
Child controller composition TVAVPlayerViewControllerDemo/TVAVPlayerViewControllerDemo/VideoPlaybackViewController.swift:100 Embeds system playback UI while retaining an app-owned presentation shell.

Main application flow

Reference code

TVAVPlayerViewControllerDemo/TVAVPlayerViewControllerDemo/VideoPlaybackViewController.swift:31 — the observer attaches the player to AVKit only after the pending item is ready and authorization succeeds.

playerItemStatusObservation = observe(\.pendingPlayerItem?.status) { object, _ in
    DispatchQueue.main.async {
        if object.pendingPlayerItem?.status == .readyToPlay {
            object.pendingPlayerItem?.requestPlaybackRestrictionsAuthorization { success, _ in
                if success {
                    object.pendingPlayerItem = nil
                    object.playerViewController?.player = object.player
                    object.player?.rate = 1.0
                } else {
                    _ = object._dismiss()
                }
            }
        }
    }
}

Naming conventions

  • Screen types use descriptive controller names: MenuViewController, VideoPlaybackViewController, and CustomInteractiveVideoOverlay.
  • Commands state intent: presentVideo, loadAndPlay, and loadNewPlayerViewController.
  • Observation tokens name the key path they protect: playerItemStatusObservation and currentItemErrorObservation.
  • pendingPlayerItem distinguishes gated content from the player’s authorized current item.
  • _dismiss is a private helper whose leading underscore separates its Boolean-returning cleanup operation from the AVKit delegate callback.

Architecture takeaways

  • Keep the player item pending until both readiness and policy checks succeed.
  • Put rating metadata on the item before exposing the player to system playback UI.
  • Let one playback owner create, embed, replace, and dismantle the AVKit controller.
  • Retain KVO tokens alongside the state they observe and marshal observed mutations to the main queue.
  • Use the system delegate directly when there is only one concrete playback implementation and no substitutable app capability.

Source map

Source file Relevant symbols
TVAVPlayerViewControllerDemo/TVAVPlayerViewControllerDemo/MenuViewController.swift Playback-screen ownership and catalog metadata
TVAVPlayerViewControllerDemo/TVAVPlayerViewControllerDemo/VideoPlaybackViewController.swift Player lifecycle, KVO, metadata, policy, and AVKit delegate
TVAVPlayerViewControllerDemo/TVAVPlayerViewControllerDemo/CustomInteractiveVideoOverlayViewController.swift Overlay focus behavior
TVAVPlayerViewControllerDemo/TVAVPlayerViewControllerDemo/Base.lproj/Main.storyboard Menu and playback navigation
TVAVPlayerViewControllerDemo/TVAVPlayerViewControllerDemo/Base.lproj/CustomInteractiveVideoOverlay.xib Overlay view hierarchy