Sample CodeiOS 18.0+, iPadOS 18.0+, Mac Catalyst 18.0+Reviewed 2026-07-21View on Apple Developer

Using AVFoundation to play and persist HTTP live streams

At a glance

Item Summary
Purpose List HLS assets, play them, download them for offline use, restore background tasks, and delete local copies.
App architecture Storyboard UIKit MVC backed by singleton list, playback, persistence, and stream-catalog managers.
Main patterns MVC, manager singletons, delegate callbacks, NotificationCenter event propagation, KVO.
Project style Controllers/cells at target root, data under Model, service-like global objects under Managers.

Project structure

HLSCatalog/
├── AssetListTableViewController.swift
├── AssetListTableViewCell.swift
├── Managers/
│   ├── AssetListManager.swift
│   ├── AssetPersistenceManager.swift
│   ├── AssetPlaybackManager.swift
│   └── StreamListManager.swift
├── Model/
│   ├── Asset.swift
│   └── Stream.swift
├── PerfMeasurements.swift
├── Streams.plist
└── Base.lproj/Main.storyboard

The architecture is singleton-centric and event-driven. It is an observed source characteristic, not a recommendation to make all playback services global.

Overall architecture

Reference code

HLSCatalog/AssetListTableViewController.swift:87 — the controller dispatches download state transitions to the persistence manager (abridged).

let downloadState = AssetPersistenceManager.sharedManager.downloadState(for: asset)

switch downloadState {
case .notDownloaded:
    try await AssetPersistenceManager.sharedManager.downloadStream(for: asset)
case .downloading:
    AssetPersistenceManager.sharedManager.cancelDownload(for: asset)
case .downloaded:
    AssetPersistenceManager.sharedManager.deleteAsset(asset)
}

The controller handles presentation and user choice. The persistence manager owns the actual background tasks, maps, bookmarks, and notifications.

Ownership and state

Ownership evidence

HLSCatalog/Managers/AssetPersistenceManager.swift:17 — singleton and task-state ownership.

class AssetPersistenceManager: NSObject {
    static let sharedManager = AssetPersistenceManager()
    private var didRestorePersistenceManager = false
    fileprivate var assetDownloadURLSession: AVAssetDownloadURLSession!
    fileprivate var activeDownloadsMap = [AVAssetDownloadTask: Asset]()
    fileprivate var willDownloadToUrlMap = [AVAssetDownloadTask: URL]()
    fileprivate var progressObservers: [NSKeyValueObservation] = []
    override private init() { /* background session setup */ }
}
Owner Object or state Relationship Mutation authority
AssetPersistenceManager Download session/tasks, destination map, bookmarks Process-wide singleton Manager methods and its same-file download delegate extension.
AssetPlaybackManager Player, item, KVO, performance measurements Process-wide singleton Setting asset drives item/player changes.
AssetListManager Materialized asset array Process-wide singleton Builds list only after persistence restoration.
AssetListTableViewController Player view-controller reference Keeps presentation-only reference Delegate callbacks attach the manager’s player.

Class and protocol design

Reference code

HLSCatalog/Managers/AssetPlaybackManager.swift:165 — a narrow player-to-presentation protocol.

protocol AssetPlaybackDelegate: AnyObject {
    func streamPlaybackManager(_ streamPlaybackManager: AssetPlaybackManager,
                               playerReadyToPlay player: AVPlayer)
    func streamPlaybackManager(_ streamPlaybackManager: AssetPlaybackManager,
                               playerCurrentItemDidChange player: AVPlayer)
}

Delegate callbacks are used for directed playback events. Broadcast download/list changes use named notifications because several cells/controllers may observe them.

Access control

Symbol Access Verified effect Likely rationale
Manager initializers override private init Only each static singleton can construct the manager. Enforces one global owner for each subsystem.
Persistence session/maps fileprivate Main class and its same-file AVAssetDownloadDelegate extension share mutation; other files cannot. Precise boundary for split delegate implementation.
Playback player/item/observers private Controllers cannot replace or observe internals directly. All presentation updates go through the delegate.
playerViewController in list controller fileprivate Same-file conformance extensions can attach the player. Supports a type split across extensions in one file.
Manager public-facing methods internal default Callable throughout app target, not exported from a library. This is an app target, not a reusable framework.

HLSCatalog/Managers/AssetPersistenceManager.swift:255 shows why fileprivate is used:

extension AssetPersistenceManager: AVAssetDownloadDelegate {
    func urlSession(_ session: URLSession,
                    assetDownloadTask: AVAssetDownloadTask,
                    willDownloadTo location: URL) {
        willDownloadToUrlMap[assetDownloadTask] = location
    }
}

Logic ownership and placement

Logic Owning type or file Placement rationale
Background HLS download/restore/delete AssetPersistenceManager Owns session and persistent task identity.
Online/local asset materialization AssetListManager Waits for restored tasks to avoid duplicate AVURLAsset instances.
Playability, item replacement, KVO AssetPlaybackManager One player owner manages observation cleanup.
User interaction and AVKit presentation AssetListTableViewController UIKit controller translates UI actions and delegate results.
Stream decoding StreamListManager Keeps bundled catalog lookup separate from asset state.

Design patterns

Pattern Source evidence Purpose or tradeoff
MVC HLSCatalog/AssetListTableViewController.swift:20 Controller renders state and sends commands to managers.
Singleton managers HLSCatalog/Managers/AssetPersistenceManager.swift:20 Gives background-session callbacks stable process-lifetime owners; increases global coupling.
Delegate HLSCatalog/Managers/AssetPlaybackManager.swift:165 Directed player readiness/item changes reach the presenting controller.
Notification observer HLSCatalog/Managers/AssetListManager.swift:34 Broadcasts restoration, progress, and list readiness across independent objects.
KVO lifecycle HLSCatalog/Managers/AssetPlaybackManager.swift:42 Item property observers are installed/released alongside the item.

Naming conventions

  • Singleton types use Manager and instances use sharedManager or shared.
  • Event names describe completed state changes (DidRestoreState, DidLoad, StateChanged).
  • Delegate methods include the source manager as their first argument.
  • Asset and Stream are domain nouns; AVFoundation implementation types stay in managers.

Architecture takeaways

  • Match background-download session lifetime with a stable owner that can restore tasks.
  • Use fileprivate when a same-file protocol extension genuinely needs internal state.
  • Separate asset catalog construction, persistence, and playback even in a small UIKit app.
  • The source mixes weak delegates, notifications, and KVO based on event cardinality and framework API style.

Source map

Source file Relevant symbols
HLSCatalog/AssetListTableViewController.swift UI orchestration and delegate conformances
HLSCatalog/Managers/AssetPersistenceManager.swift Background downloads/persistence
HLSCatalog/Managers/AssetPlaybackManager.swift Player/item/KVO ownership
HLSCatalog/Managers/AssetListManager.swift Restored asset list
HLSCatalog/Managers/StreamListManager.swift Bundled stream catalog