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
flowchart LR
UI["AssetListTableViewController"] --> List["AssetListManager.shared"]
UI --> Playback["AssetPlaybackManager.shared"]
UI --> Persist["AssetPersistenceManager.shared"]
List --> Catalog["StreamListManager.shared"]
List --> Persist
Persist --> Download["AVAssetDownloadURLSession"]
Playback --> Player["AVPlayer"]
Persist --> Events["NotificationCenter"]
Events --> UI
Playback --> UI
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
classDiagram
AssetPersistenceManager *-- AVAssetDownloadURLSession
AssetPersistenceManager *-- AVAssetDownloadTask : active map
AssetPlaybackManager *-- AVPlayer
AssetPlaybackManager *-- AVPlayerItem
AssetListManager *-- Asset : catalog list
AssetListTableViewController --> AssetPlaybackManager : weak delegate target
AssetListTableViewController --> AssetPersistenceManager : shared use
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
classDiagram
class AssetPlaybackDelegate {
<<protocol>>
playerReadyToPlay()
playerCurrentItemDidChange()
}
AssetListTableViewController ..|> AssetPlaybackDelegate
AssetPlaybackManager --> AssetPlaybackDelegate : weak delegate
AssetPersistenceManager ..|> AVAssetDownloadDelegate
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
Managerand instances usesharedManagerorshared. - Event names describe completed state changes (
DidRestoreState,DidLoad,StateChanged). - Delegate methods include the source manager as their first argument.
AssetandStreamare domain nouns; AVFoundation implementation types stay in managers.
Architecture takeaways
- Match background-download session lifetime with a stable owner that can restore tasks.
- Use
fileprivatewhen 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 |