Skip to content

Commit

Permalink
[APT-9577] Add DRM for downloads for offline usage : add DRM support …
Browse files Browse the repository at this point in the history
…for MPEG-Dash audio playback in both online and offline mode

Create new helper DrmMediaSourceHelper to generate the correct media item with DRM info depending on if the content is downloaded/being downloaded.

If the content is streaming, we only need to include the general DRM info so the DRM license can be fetched from the server to decrypt the encrypted content.
If the content is a download, we need to include all DRM info as well as the DRM key ID so the local DRM license can be used for decryption instead.
  • Loading branch information
rubeus90 committed Feb 6, 2024
1 parent 73539d9 commit a569758
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import com.scribd.armadillo.playback.MediaMetadataCompatBuilderImpl
import com.scribd.armadillo.playback.PlaybackEngineFactoryHolder
import com.scribd.armadillo.playback.PlaybackStateBuilderImpl
import com.scribd.armadillo.playback.PlaybackStateCompatBuilder
import com.scribd.armadillo.playback.mediasource.DrmMediaSourceHelper
import com.scribd.armadillo.playback.mediasource.DrmMediaSourceHelperImpl
import com.scribd.armadillo.playback.mediasource.HeadersMediaSourceHelper
import com.scribd.armadillo.playback.mediasource.HeadersMediaSourceHelperImpl
import com.scribd.armadillo.playback.mediasource.MediaSourceRetriever
Expand Down Expand Up @@ -62,4 +64,8 @@ internal class PlaybackModule {
@Provides
@Singleton
fun mediaSourceHelper(mediaSourceHelperImpl: HeadersMediaSourceHelperImpl): HeadersMediaSourceHelper = mediaSourceHelperImpl

@Provides
@Singleton
fun drmMediaSourceHelper(drmMediaSourceHelperImpl: DrmMediaSourceHelperImpl): DrmMediaSourceHelper = drmMediaSourceHelperImpl
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,7 @@ data class DrmDownloadException(val exception: Exception) : ArmadilloException(e
override val errorCode = 701
}

data class DrmPlaybackException(val exception: Exception) : ArmadilloException(exception) {
override val errorCode = 702
}

Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package com.scribd.armadillo.playback.mediasource

import android.content.Context
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.MediaItem.DrmConfiguration
import com.google.android.exoplayer2.drm.DefaultDrmSessionManagerProvider
import com.google.android.exoplayer2.offline.Download
import com.google.android.exoplayer2.offline.DownloadHelper
import com.google.android.exoplayer2.source.MediaSource
Expand All @@ -14,31 +13,23 @@ import javax.inject.Inject

internal class DashMediaSourceGenerator @Inject constructor(
private val mediaSourceHelper: HeadersMediaSourceHelper,
private val downloadTracker: DownloadTracker) : MediaSourceGenerator {
private val downloadTracker: DownloadTracker,
private val drmMediaSourceHelper: DrmMediaSourceHelper,
) : MediaSourceGenerator {

override fun generateMediaSource(context: Context, request: AudioPlayable.MediaRequest): MediaSource {
val dataSourceFactory = mediaSourceHelper.createDataSourceFactory(context, request)

downloadTracker.getDownload(request.url.toUri())?.let {
if (it.state != Download.STATE_FAILED) {
return DownloadHelper.createMediaSource(it.request, dataSourceFactory)
val mediaItem = drmMediaSourceHelper.createMediaItem(context = context, request = request, isDownload = true)
return DownloadHelper.createMediaSource(it.request, dataSourceFactory, DefaultDrmSessionManagerProvider().get(mediaItem))
}
}

val mediaItemBuilder = MediaItem.Builder()
.setUri(request.url)

if (request.drmInfo != null) {
mediaItemBuilder.setDrmConfiguration(
DrmConfiguration.Builder(request.drmInfo.drmType.toExoplayerConstant())
.setLicenseUri(request.drmInfo.licenseServer)
.setLicenseRequestHeaders(request.drmInfo.drmHeaders)
.build()
)
}

val mediaItem = drmMediaSourceHelper.createMediaItem(context = context, request = request, isDownload = false)
return DashMediaSource.Factory(dataSourceFactory)
.createMediaSource(mediaItemBuilder.build())
.createMediaSource(mediaItem)
}

override fun updateMediaSourceHeaders(request: AudioPlayable.MediaRequest) = mediaSourceHelper.updateMediaSourceHeaders(request)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.scribd.armadillo.playback.mediasource

import android.content.Context
import com.google.android.exoplayer2.MediaItem
import com.scribd.armadillo.encryption.SecureStorage
import com.scribd.armadillo.error.DrmPlaybackException
import com.scribd.armadillo.models.AudioPlayable
import javax.inject.Inject
import javax.inject.Singleton

/**
* This is a helper responsible for generating the correct media source for an audio request.
*
* This will apply the correct DRM-related information needed for content decryption (if the content is DRM-protected).
* In case of a download media (the content is either downloaded or being downloaded), this includes the DRM key ID used for retrieving
* the local DRM license (instead of fetching DRM license from the server).
*/
internal interface DrmMediaSourceHelper {
fun createMediaItem(
context: Context,
request: AudioPlayable.MediaRequest,
isDownload: Boolean,
): MediaItem
}

@Singleton
internal class DrmMediaSourceHelperImpl @Inject constructor(private val secureStorage: SecureStorage) : DrmMediaSourceHelper {

override fun createMediaItem(context: Context, request: AudioPlayable.MediaRequest, isDownload: Boolean): MediaItem =
MediaItem.Builder()
.setUri(request.url)
.apply {
// Apply DRM config if content is DRM-protected
val drmConfig = request.drmInfo?.let { drmInfo ->
MediaItem.DrmConfiguration.Builder(drmInfo.drmType.toExoplayerConstant())
.setLicenseUri(drmInfo.licenseServer)
.setLicenseRequestHeaders(drmInfo.drmHeaders)
.apply {
// If the content is a download content, use the saved offline DRM key id.
// This ID is needed to retrieve the local DRM license for content decryption.
if (isDownload) {
secureStorage.getDrmKeyId(context, request.url, drmInfo.drmType.name)?.let { drmKeyId ->
setKeySetId(drmKeyId)
} ?: throw DrmPlaybackException(IllegalStateException("No DRM key id saved for download content"))
}
}
.build()
}
setDrmConfiguration(drmConfig)
}
.build()
}

0 comments on commit a569758

Please sign in to comment.