Preload and precache videos

Preload and precache upcoming playlist items in the FastPix iOS Player to start the next video instantly.

The FastPix iOS Player SDK can prepare upcoming playlist items ahead of time, so a viewer who advances to the next video sees little or no buffering. It offers two complementary mechanisms:

  • Preloading initializes AVPlayerItem instances for upcoming videos in the background using a shadow AVPlayer. This warms up AVFoundation’s URL session cache, so when the SDK loads the same stream URL, the initial buffering is already complete.
  • Precaching downloads and stores HLS segments to disk, so the content is served from the local cache on later playback requests. DRM-protected items (those with a drmToken) are skipped during precaching, because their segments are encrypted and can’t be cached.

Both managers expose delegate callbacks, so your app can react to preload and cache state changes in the UI.


Before you begin

Make sure you have the following:


How preloading and precaching differ

PreloadingPrecaching
What it doesWarms AVFoundation’s buffer using a shadow AVPlayerDownloads HLS segments to disk
ManagerPreloadManager.sharedPrecacheManager.shared
DRM-protected itemsSupported. AVFoundation handles license fetching separately.Skipped. Encrypted segments can’t be cached.
Best forAn instant start on the next itemServing repeat playback from a local cache

Set up the managers

Initialize both managers and assign their delegates before playback starts. Start preloading and precaching once the playlist is ready.

1private let preloadManager = PreloadManager.shared
2private let precacheManager = PrecacheManager.shared
3
4override func viewDidLoad() {
5 super.viewDidLoad()
6 playerViewController.addPlaylist(playlist)
7
8 // Assign delegates to receive status callbacks
9 preloadManager.delegate = self
10 precacheManager.delegate = self
11
12 // Start preloading and precaching after the playlist is ready
13 preloadNextVideos()
14 precacheUpcomingVideos()
15}

Preload upcoming videos

Preload the next items after the currently playing index. This example preloads the next two items, and you can change how many to preload. Call this method whenever the playlist position changes, such as after next(), previous(), jumpTo(), or a FastPixPlaylistStateChanged notification.

buildPlaybackURL(for:) is your own helper that builds the stream URL for a playlist item, using the same URL you’d use for playback.

1private func preloadNextVideos() {
2 let currentIndex = playerViewController.currentPlaylistIndex
3 let currentId = currentIndex < playlist.count ? playlist[currentIndex].playbackId : ""
4 let upcoming = playlist
5 .enumerated()
6 .filter { $0.offset > currentIndex }
7 .prefix(2)
8 .map { $0.element }
9 .filter {
10 // Skip if already loading, ready, or currently playing
11 let status = preloadManager.preloadStatus(forVideo: $0.playbackId)
12 switch status {
13 case .idle: return $0.playbackId != currentId
14 default: return false
15 }
16 }
17
18 let itemsToPreload: [(id: String, item: AVPlayerItem)] = upcoming.compactMap { playlistItem -> (id: String, item: AVPlayerItem)? in
19 guard let url = buildPlaybackURL(for: playlistItem) else { return nil }
20 let playerItem = AVPlayerItem(url: url)
21 return (id: playlistItem.playbackId, item: playerItem)
22 }
23
24 guard !itemsToPreload.isEmpty else { return }
25 preloadManager.preload(items: itemsToPreload)
26}

Precache upcoming videos

Precache the HLS segments for the current and next video. DRM-protected items are skipped automatically.

1private func precacheUpcomingVideos() {
2 let currentIndex = playerViewController.currentPlaylistIndex
3 let upcoming = playlist
4 .enumerated()
5 .filter { $0.offset >= currentIndex }
6 .prefix(2)
7 .map { $0.element }
8
9 for item in upcoming {
10 // Skip DRM-protected content. Encrypted segments cannot be cached.
11 guard item.drmToken.isEmpty else { continue }
12
13 let host = item.customDomain.isEmpty == false
14 ? item.customDomain
15 : "stream.fastpix.io"
16 var urlString = "https://\(host)/\(item.playbackId).m3u8"
17 if !item.token.isEmpty {
18 urlString += "?token=\(item.token)"
19 }
20
21 guard let url = URL(string: urlString) else { continue }
22 precacheManager.startPrecaching(url: url)
23 }
24}

Consume a preloaded item before navigating

When you move to the next item, call consumePreloadedItem(for:) before you call next(). This detaches the shadow player, so the SDK can reuse AVFoundation’s URL session cache when it loads the same stream URL.

1let nextIndex = playerViewController.currentPlaylistIndex + 1
2if nextIndex < playlist.count {
3 let nextId = playlist[nextIndex].playbackId
4 if preloadManager.consumePreloadedItem(for: nextId) != nil {
5 print("Preloaded item consumed. AVFoundation cache warm for: \(nextId)")
6 }
7}
8_ = playerViewController.next()

Re-trigger after playlist changes

Re-trigger preloading and precaching inside the FastPixPlaylistStateChanged observer, so the window stays ahead of the current position.

1@objc private func playlistStateChanged(_ notification: Notification) {
2 DispatchQueue.main.async {
3 // ... your existing reset logic ...
4 self.preloadNextVideos()
5 self.precacheUpcomingVideos()
6 }
7}

Clean up

Stop all in-flight preload and precache tasks when the view controller is deallocated.

1deinit {
2 preloadManager.clearAll()
3 precacheManager.stopAllPrecaching()
4}

Handle preload events

Conform to PreloadManagerDelegate to receive preload lifecycle callbacks.

1extension VideoPlayerViewController: PreloadManagerDelegate {
2 // Called when preloading begins for a video
3 func videoPreloadDidStart(forId id: String) {
4 print("Preload started for: \(id)")
5 }
6
7 // Called when the preloaded AVPlayerItem is buffered and ready
8 func videoPreloadDidBecomeReady(forId id: String) {
9 print("Preload ready. Instant playback available for: \(id)")
10 }
11
12 // Called when preloading fails
13 func videoPreloadDidFail(forId id: String, error: Error?) {
14 print("Preload failed for \(id): \(error?.localizedDescription ?? "unknown error")")
15 }
16
17 // Called when a preload task is cancelled
18 func videoPreloadDidCancel(forId id: String) {
19 print("Preload cancelled for: \(id)")
20 }
21
22 // Called when the SDK auto-advances and consumes the preloaded buffer
23 func videoPreloadDidAutoAdvance(toId id: String) {
24 print("Auto-advance consumed preloaded item: \(id)")
25 }
26}

Handle precache events

Conform to PrecacheManagerDelegate to observe whether segments are served from disk or fetched from the network.

1extension VideoPlayerViewController: PrecacheManagerDelegate {
2 // Called when a request is served from the local disk cache
3 func videoCacheDidHit(url: URL) {
4 print("Cache HIT. Served from disk: \(url.lastPathComponent)")
5 }
6
7 // Called when a request is not in the cache and is being downloaded
8 func videoCacheDidMiss(url: URL) {
9 print("Cache MISS. Downloading and caching: \(url.lastPathComponent)")
10 }
11}

Best practices

  • Call preloadNextVideos() and precacheUpcomingVideos() after every playlist navigation event (next(), previous(), jumpTo()), and inside playlistStateChanged, to keep the preload window current.
  • DRM-protected items (where drmToken is non-empty) are excluded from precaching automatically. Preloading still applies to DRM items, because AVFoundation handles license fetching separately.
  • Always call consumePreloadedItem(for:) before next(), to hand off the buffered data to AVFoundation’s URL session cache.
  • Call preloadManager.clearAll() and precacheManager.stopAllPrecaching() in deinit, to avoid memory leaks and dangling background tasks.

Frequently asked questions

What's the difference between preloading and precaching?

Preloading warms AVFoundation’s buffer for an upcoming item using a background shadow AVPlayer, so the next video starts instantly. Precaching downloads the HLS segments to disk, so later playback of the same item is served from the local cache.

Do preloading and precaching work with DRM-protected videos?

Preloading works with DRM items, because AVFoundation handles license fetching separately. Precaching skips DRM items automatically, because their segments are encrypted and can’t be cached.

How many upcoming items should I preload?

The examples preload and precache the next two items, which balances instant navigation against bandwidth and storage. Adjust the prefix(2) count to preload more or fewer items.

When should I trigger preloading and precaching?

Call preloadNextVideos() and precacheUpcomingVideos() after every navigation event (next(), previous(), jumpTo()) and inside the FastPixPlaylistStateChanged observer, so the window stays ahead of the current position.

How do I avoid memory leaks from preloading?

Call preloadManager.clearAll() and precacheManager.stopAllPrecaching() in deinit to stop all in-flight tasks and release resources.


What’s next