Offline Playback

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

Overview

The Bitmovin Android SDK minimizes offline content management and playback to a few API calls, and allows fast integration of offline capabilities.

Setup

In order to use offline functionality, the following dependency must be added manually to your app's build.gradle file:

implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.1.0")

Additionally, the permission for checking the network state is required in the Android Manifest:

 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Depending on the location the offline data is saved, it may also be required to add the STORAGE permission to the Android Manifest:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Assets are downloaded using a foreground service. In order to be able to start it, the FOREGROUND_SERVICE permission is required. When targeting Android 14 (API level 34) and above, the FOREGROUND_SERVICE_DATA_SYNC permission also has to be added. See the Android Developers guide for more details.

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>

When targeting Android 13 (API level 33) and above, the following permission has to be declared in the manifest and requested at runtime before starting downloads, otherwise the notifications posted by the download service will not be displayed. See the Android Developers guide for more details.

<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

API Usage

The core component responsible for handling offline content is the OfflineContentManager. It allows fetching the current offline state, requesting state changes and generating SourceConfig for offline playback.

Initialization

One OfflineContentManager is linked with one SourceConfig. When instantiating an OfflineContentManager, it is required to provide a storage location, a unique ID for the content and a listener of type OfflineContentManagerListener.

val listener = object : OfflineContentManagerListener {
    override fun onCompleted(sourceConfig: SourceConfig, offlineContentOptions: OfflineContentOptions) {}
    override fun onError(sourceConfig: SourceConfig, errorEvent: ErrorEvent) {}
    override fun onProgress(sourceConfig: SourceConfig, progress: Float) {}
    override fun onOptionsAvailable(sourceConfig: SourceConfig, offlineContentOptions: OfflineContentOptions) {}
    override fun onDrmLicenseUpdated(sourceConfig: SourceConfig) {}
    override fun onSuspended(sourceConfig: SourceConfig) {}
    override fun onResumed(sourceConfig: SourceConfig) {}
}
val offlineContentManager = OfflineContentManager.getOfflineContentManager(
    sourceConfig = SourceConfig.fromUrl("//url.to/online/resource.mpd"),
    location = getDir("offline", ContextWrapper.MODE_PRIVATE).path,
    id = "uniqueID",
    listener = listener,
    context = androidContext
)

OfflineContentManager

Interaction with the OfflineContentManager is mostly done asynchronously. The results when calling getOptions, process and renewDrmLicense are received by the OfflineContentManagerListener, which is passed at creation of the OfflineContentManager. Only calls to getRemainingOfflineLicenseDuration, offlineSourceConfig and release are synchronous and return their values immediately.

OfflineContentManagerListener

The OfflineContentManagerListener provides seven callbacks:

  • onOptionsAvailable is called after a getOptions call, or when the state of an OfflineOptionEntry has changed. The states can be NOT_DOWNLOADED, DOWNLOADED, DOWNLOADING or DELETING.
  • onProgress is called when the progress for an ongoing operation has changed e.g. the download progress.
  • onCompleted is called when an operation has completed. This could be the downloading or deleting of content.
  • onDrmLicenseUpdated is called when the stored DRM license was updated.
  • onError is called when an error occurs.
  • onSuspended is called when all actions have been suspended.
  • onResumed is called when all actions have been resumed.

OfflineContentOptions

OfflineContentOptions can be received by calling getOptions on the OfflineContentManager for a specific SourceConfig. OfflineContentOptions represent the "offline state" of a stream and each available track (video, audio, captions/subtitles). It contains four lists, containing the different types: video, audio, text (captions/subtitles) and thumbnails.

Downloading Content

Once an OfflineContentManger is created, downloading of content can be started. To gain knowledge of the available tracks and their state, getOptions should be called:

offlineContentManager.getOptions()

As this is an asynchronous call, the response must be handled in the OfflineContentManagerListener callback:

override fun onOptionsAvailable(sourceConfig: SourceConfig, offlineContentOptions: OfflineContentOptions) {
    // Handle offline content options
}

From the received OfflineContentOptions the separate OfflineOptionEntry can be received and selected for download or deletion. As an example, the VideoOfflineOptionEntry with the highest bitrate will be selected here for download:

val bestVideoOption = offlineContentOptions.videoOptions.maxByOrNull { it.bitrate } ?: return
bestVideoOption.action = OfflineOptionEntryAction.Download

Note: Depending on the current state of the OfflineOptionEntry it might be not possible to set all actions. For example, already downloaded content cannot be downloaded again.

After successfully preparing the download, by setting the desired actions, the OfflineContentOptions object is pushed back to the (correct) OfflineContentManager using the process(...) call:

offlineContentManager.process(offlineContentOptions);

This triggers an onOptionsAvailable(...) listener callback. The OfflineOptionEntry entries which were selected to be downloaded will have the state DOWNLOADING now. Until the onCompleted(...) listener is invoked, the onProgress(...) listener is called frequently with updated information about the ongoing operation.

Playback of Offline Content

When the onCompleted(...) listener callback has been invoked, meaning at least one OfflineOptionEntry is in the DOWNLOADED state, an OfflineSourceConfig can be requested from the OfflineContentManager for playback. With this OfflineSourceConfig a SourceConfig can be created and loaded into the Player as usual:

...
bitmovinPlayer.load(offlineContentManager.offlineSourceConfig);

The Player is now ready to play the offline stored content.