[object Object] Icon

Encoding
Learn how to create, start, manage and modify Encodings

[object Object] Icon

Player
Learn how to create, start, manage and modify Players

[object Object] Icon

Analytics
Learn how to create, start, manage and modify Analyticss

Docs Home
User shortcuts for search
Focus by pressing f
Hide results by pressing Esc
Navigate via   keys

Thu Sep 13 2018

Offline Playback with the Bitmovin iOS/tvOS SDK

Manage Offline Content For PlaybackLink Icon

One of the main challenges of an implementation to enable offline playback is to provide the operations to work with assets, such as starting content downloads, pausing resuming downloads, canceling downloads and deleting already downloaded content. The Bitmovin SDK provides a class called OfflineManager that encapsulates all these operations and seamlessly controls the state, taking into account events like application going into background and application getting killed by the user or getting crashed.

Initialize an OfflineManagerLink Icon

In your AppDelegate class, add the following to initialize the singleton OfflineManager which is used to manage the whole lifecycle of offline assets.

1OfflineManager.initializeOfflineManager()

Download protected or unprotected contentLink Icon

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

1let sourceItem = SourceItem(url: URL(string: "https://www.example.com/stream.m3u8")!)
2sourceItem.itemTitle = "Sample Content"
3sourceItem.itemDescription = "Description of this content"

In case the source is DRM-protected, you just need to provide its DRM configuration like you would normally do for an online playback use case:

1let fpsConfig = FairplayConfiguration(license: licenseUrl, certificateURL: certificateUrl)
2sourceItem.add(drmConfiguration: fpsConfig)

You will also need a reference to the OfflineManager singleton instance:

1let offlineManager = OfflineManager.sharedInstance()

Now that you have both the offline manager and source item, you can already kick off the download operation, calling the download(sourceItem:) method.

1offlineManager.download(sourceItem: sourceItem)

You can check the state of the download operation with the offlineState method:

1offlineManager.offlineState(for: sourceItem) == .downloading
2offlineManager.offlineState(for: sourceItem) == .downloaded
3offlineManager.offlineState(for: sourceItem) == .suspended
4offlineManager.offlineState(for: sourceItem) == .notDownloaded
5offlineManager.offlineState(for: sourceItem) == .canceling

The offlineState(for:) method is very useful especially to perform pre-validations, for example, before allowing the user to cancel a download. However, the SDK also allows you to add event listeners where you can track all the states mentioned above.

1offlineManager.add(listener: self, for: sourceItem)

And then:

1extension MyViewController: OfflineManagerListener {
2
3 func offlineManager(_ offlineManager: OfflineManager, didFailWithError error: Error?) {
4 let errorMessage = error?.localizedDescription ?? "unknown"
5 print("[MyViewController] Download resulted in error: \(errorMessage)")
6 setViewState(.notDownloaded)
7 }
8
9 func offlineManagerDidFinishDownload(_ offlineManager: OfflineManager) {
10 print("[MyViewController] Download Finished")
11 setViewState(.downloaded)
12 }
13
14 func offlineManager(_ offlineManager: OfflineManager, didProgressTo progress: Double) {
15 print("[MyViewController] Progress")
16 // update ui with current progress
17 setViewState(.downloading, withProgress: progress)
18 }
19
20 func offlineManagerDidSuspendDownload(_ offlineManager: OfflineManager) {
21 print("[MyViewController] Suspended")
22 setViewState(.suspended)
23 }
24
25 func offlineManager(_ offlineManager: OfflineManager, didResumeDownloadWithProgress progress: Double) {
26 print("[MyViewController] Resumed")
27 setViewState(.downloading, withProgress: progress)
28 }
29
30 func offlineManagerDidCancelDownload(_ offlineManager: OfflineManager) {
31 print("[MyViewController] Cancelled")
32 setViewState(.notDownloaded)
33 }
34}

Cancel an ongoing downloadLink Icon

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

1guard offlineManager.offlineState(for: sourceItem) == .downloading ||
2 offlineManager.offlineState(for: sourceItem) == .suspended else {
3 return
4}
5offlineManager.cancelDownload(for: sourceItem)

Suspend a ongoing downloadLink Icon

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

1guard offlineManager.offlineState(for: sourceItem) == .downloading else {
2 return
3}
4offlineManager.suspendDownload(for: sourceItem)

Resume an suspended downloadLink Icon

If the download of a given source item is .suspended state it can be resumed at any time with the following instructions:

1guard offlineManager.offlineState(for: sourceItem) == .suspended else {
2 return
3}
4offlineManager.resumeDownload(for: sourceItem)

Delete all downloaded dataLink Icon

You can force to delete all cached data for a given source item with the following:

1guard offlineManager.offlineState(for: sourceItem) == .downloaded else {
2 return
3}
4offlineManager.deleteOfflineData(for: sourceItem)

Overview of states and allowed method callsLink Icon

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

Current StateMethod call triggering transitionOfflineManagerListener EventFollowing State
NotDownloadeddownload(sourceItem:)-Downloading
DownloadingcancelDownload(for:)-Canceling
DownloadingsuspendDownload(for:)offlineManagerDidSuspendDownload(:)Suspended
Downloading-offlineManagerDidFinishDownload(:)Downloaded
Downloading-offlineManager(:didProgressTo:)Downloading
DownloadeddeleteOfflineData(for:)-NotDownloaded
SuspendedresumeDownload(for:)offlineManager(:didResumeDownloadWithProgress:)Downloading
SuspendedcancelDownload(for:)-Canceling
Canceling-offlineManagerDidCancelDownload(:)NotDownloaded

Play online/offline content seamlesslyLink Icon

Now that you know how to manage assets for offline playback, it's time to focus on the playback side of things. Assuming the state of the source item is .downloaded, .downloading or .suspended, before passing the source item to the player configuration object you should check whether the device has access to the internet or not. If it doesn't, you can make sure the asset is playable offline by calling the isPlayableOffline method:

1guard offlineManager.isPlayableOffline(sourceItem: sourceItem),
2 let offlineSourceItem = offlineManager.createOfflineSourceItem(for: sourceItem, restrictedToAssetCache: true) else {
3 finishWithError(title: "Error", message: "The device seems to be offline, but no offline content for the selected source available.")
4 return
5}
6sourceItem = offlineSourceItem

The code snippet above also shows off how to create an OfflineSourceItem for the given SourceItem. This method gives you the option to restrictToAssetCache, which will basically restrict the playback to the audio and subtitle tracks which are cached on disk. As a result, tracks which are not cached, does 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 case the state of the source item is .notDownloaded or .canceling and the device does not have access to the internet, then the playback won't be possible and you should notify the user. Now you have the source item to pass to the player configuration:

1let config = PlayerConfiguration()
2config.sourceItem = sourceItem // this can be a SourceItem or a OfflineSourceItem
3
4let player = BitmovinPlayer(configuration: config)
5let playerView = BMPBitmovinPlayerView(player: player, frame: CGRect.zero)
6
7player.add(listener: self)
8playerView.add(listener: self)
9
10playerView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
11playerView.frame = playerViewContainer.bounds
12
13playerViewContainer.addSubview(playerView)
14playerViewContainer.bringSubview(toFront: playerView)
15
16self.playerView = playerView
17self.player = player

Sample AppLink Icon

Here you will find a set of iOS sample applications and we have a dedicated app to show off the offline playback capabilities. Here is a short description of the important files of that project:

Models/SampleViewModel.swift: The source items are declared here. As a sample application the source items are hardcoded, but you could build that list fetching from a remote server for example.

Controllers/SamplesTableViewController.swift: shows a table with all the assets declared in the SampleViewModel class.

Controllers/SampleDetailViewController.swift: It receives a sourceItem and attaches the click handlers for the buttons: download, pause, resume, cancel, delete and play. This class invokes most of the methods of the OfflineManager.

Controllers/PlaybackViewController.swift: it also takes a source item and based on the offline state of it combined with the internet connectivity status it decides whether to use the same sourceItem that was received or to create an OfflineSourceItem out of the original sourceItem.

Utils/Reachability.h: useful class to check the connectivity with the internet.

Give us feedback