Offline Playback

This tutorial will walk you through the facilities that the Bitmovin Player iOS SDK offers for working with offline content and offline playback.

Manage Offline Content For Playback

One of the challenges of enabling offline playback is downloading and managing assets. The Bitmovin SDK provides the ability to download content, as well as to pause, resume and cancel downloads. It also enables downloaded content to be deleted. These operations are provided via the OfflineContentManager API, which is responsible for managing the state of the assets. Application lifecycle events such as the application going into background, being terminated by the user, or crashing are handled by the OfflineManager.

Initialize the OfflineManager

You first need to initialize the singleton class OfflineManager in your AppDelegate class. To do so, you add the following code snippet in the application(_:didFinishLaunchingWithOptions:) method:

OfflineManager.initializeOfflineManager()

App Lifecycle Support

In order to appropriately handle application lifecycle events, such as the application going into background, add the following code into application(_:handleEventsForBackgroundURLSession:completionHandler:) in your AppDelegate:

OfflineManager.sharedInstance().add(completionHandler: completionHandler, for: identifier)

Download protected or unprotected content

Before you can download the content, you first need to create an instance of a SourceConfig that represents the asset to be downloaded.

let sourceConfig = SourceConfig(url: URL(string: "https://www.example.com/stream.m3u8")!)
sourceConfig.title = "Sample Content"
sourceConfig.sourceDescription = "Description of this content"

In case the source is protected by Digital Rights Management (DRM), you just need to provide its DRM configuration like you would do for online playback:

let fpsConfig = FairplayConfig(license: licenseUrl, certificateURL: certificateUrl)  
sourceConfig.drmConfig = fpsConfig

You will also need a reference to the OfflineManager using the sharedInstance() method, which you can then use to get access to an OfflineContentManager for your SourceConfig. The OfflineContentManager instance manages offline content and offline DRM related tasks for this single SourceConfig:

let offlineManager = OfflineManager.sharedInstance()  
do {  
    let offlineContentManager = try offlineManager.offlineContentManager(for: sourceConfig)  
} catch {  
    print("error creating offline content manager \(error)")  
}

Now that you have both the offline content manager and source config for the asset, you can already kick off the download operation, by calling the download() method.

offlineContentManager.download()

OfflineState

You can check the state of the download operation with the offlineState property, which is very useful for checking on the state of an asset before performing further actions. For example, checking the asset's offline state before updating the UI or allowing the user to cancel a download:

switch offlineContentManager.offlineState {  
    case .downloading:  
        print("[MyViewController] asset download is in progress")  
    case .suspended:  
        print("[MyViewController] asset download is suspended")  
    case .downloaded:  
        print("[MyViewController] asset download is finished")  
    case .canceling:  
        print("[MyViewController] canceling asset download")  
    case .notDownloaded:  
        print("[MyViewController] asset is not downloaded")  
    @unknown default:  
        break  
}

Events

Additionally, the SDK allows you to add event listeners for tracking all the offline states mentioned above.

offlineContentManager.add(listener: self)

And then you can track the events for further action:

extension MyViewController: OfflineContentManagerListener {
    func onOfflineError(
        _ event: OfflineErrorEvent, 
        offlineContentManager: OfflineContentManager
    ) {
        let errorMessage = event.message
        print("[MyViewController] Download resulted in error: \(errorMessage)")
    }
  
    func onContentDownloadFinished(
        _ event: ContentDownloadFinishedEvent,
        offlineContentManager: OfflineContentManager
    ) {
        print("[MyViewController] Download Finished")
    }
  
    func onContentDownloadProgressChanged(
        _ event: ContentDownloadProgressChangedEvent, 
        offlineContentManager: OfflineContentManager
    ) {
        print("[MyViewController] Progress")
    }
  
    func onContentDownloadSuspended(
        _ event: ContentDownloadSuspendedEvent,
        offlineContentManager: OfflineContentManager
    ) {
        print("[MyViewController] Suspended")
    }
  
    func onContentDownloadResumed(
        _ event: ContentDownloadResumedEvent,
        offlineContentManager: OfflineContentManager
    ) {
        print("[MyViewController] Resumed")
    }
  
    func onContentDownloadCanceled(
        _ event: ContentDownloadCanceledEvent,
        offlineContentManager: OfflineContentManager
    ) {
        print("[MyViewController] Canceled")
    }
}

You can find more information about each of the above events in our documentation:

  • onOfflineError
  • onContentDownloadFinished
  • onContentDownloadProgressChanged
  • onContentDownloadSuspended
  • onContentDownloadResumed
  • onContentDownloadCanceled

Cancel an ongoing download

The OfflineContentManager offers the cancelDownload() method that cancels a running or suspended download operation. When a download was canceled successfully, also all partially downloaded data is deleted automatically.

guard offlineContentManager.offlineState == .downloading ||
      offlineContentManager.offlineState == .suspended else {
    return
}
offlineContentManager.cancelDownload()

Suspend an ongoing download

The suspendDownload() method can be used to suspend (pause) an ongoing download operation.

guard offlineContentManager.offlineState == .downloading else {  
    return  
}  
offlineContentManager.suspendDownload()

Resume a suspended download

If the download of a given source config has .suspended state, it can be resumed at any time with the resumeDownload() method:

guard offlineContentManager.offlineState == .suspended else {  
    return  
}  
offlineContentManager.resumeDownload()

Delete all downloaded data

You can force to delete all cached data for a given source config with the deleteOfflineData() method:

guard offlineContentManager.offlineState == .downloaded else {  
    return  
}  
offlineContentManager.deleteOfflineData()

Overview of states and allowed method calls

The following table outlines all allowed actions in all possible offline states a source config can have. It also shows which listener methods are called on the OfflineContentManagerListener:

Current StateMethod call triggering transitionOfflineContentManagerListener EventFollowing State
NotDownloadeddownloadDownloading
DownloadingcancelDownloadCanceling
DownloadingsuspendDownloadonContentDownloadSuspendedSuspended
DownloadingonContentDownloadFinishedDownloaded
DownloadingonContentDownloadProgressChangedDownloading
DownloadeddeleteOfflineDataNotDownloaded
SuspendedresumeDownloadonContentDownloadResumedDownloading
SuspendedcancelDownloadCanceling
CancelingonContentDownloadCanceledNotDownloaded
DownloadedrenewOfflineLicenseonOfflineContentLicenseRenewedDownloaded

Play online/offline content seamlessly

Now that you know how to manage assets for offline playback, you can focus on the what is needed to play back the asset. Assuming the offline state of of the offline content manager for the source config is .downloaded, .downloading or .suspended, before passing the source config to the player, you should check whether the device has access to the internet or not. If the device does not have internet access, the method offlineContentManager.createOfflineSourceConfig(restrictedToAssetCache:) can be used to create an offline source config from the initial downloaded source config:

guard offlineContentManager.offlineState == .downloaded,
      let offlineSourceConfig = offlineContentManager.createOfflineSourceConfig(restrictedToAssetCache: true) else {
    finishWithError(title: "Error", message: "The device seems to be offline, but no offline content for the selected source available.")
    return
}
sourceConfig = offlineSourceConfig

This method gives you the option to restrictToAssetCache, which will restrict the playback to the audio and subtitle tracks which are cached on disk. As a result, tracks which are not cached, do not show up as selectable in the player UI. If the device has access to the internet, you should probably set false to this parameter as it would allow the user to select tracks that are not cached yet. In the case that the offline state for the offline content manager for the source config is .notDownloaded or .canceling, and the device does not have access to the internet, then the playback will not be possible and you should notify the user. Once these checks are done and playback is possible, you can load the source config into the player:

let player = PlayerFactory.createPlayer()

player.add(listener: self)
player.load(sourceConfig: sourceConfig) // this can be an instance of `SourceConfig` or `OfflineSourceConfig`

Sample App

Here you will find a set of iOS sample applications. There is also a dedicated app that demonstrates the offline playback capabilities.