diff --git a/Armadillo/src/main/java/com/scribd/armadillo/di/PlaybackModule.kt b/Armadillo/src/main/java/com/scribd/armadillo/di/PlaybackModule.kt index 9ecaa41..0c2d7de 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/di/PlaybackModule.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/di/PlaybackModule.kt @@ -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 @@ -62,4 +64,8 @@ internal class PlaybackModule { @Provides @Singleton fun mediaSourceHelper(mediaSourceHelperImpl: HeadersMediaSourceHelperImpl): HeadersMediaSourceHelper = mediaSourceHelperImpl + + @Provides + @Singleton + fun drmMediaSourceHelper(drmMediaSourceHelperImpl: DrmMediaSourceHelperImpl): DrmMediaSourceHelper = drmMediaSourceHelperImpl } \ No newline at end of file diff --git a/Armadillo/src/main/java/com/scribd/armadillo/error/ArmadilloException.kt b/Armadillo/src/main/java/com/scribd/armadillo/error/ArmadilloException.kt index 78fb6d3..94511b9 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/error/ArmadilloException.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/error/ArmadilloException.kt @@ -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 +} + diff --git a/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/DashMediaSourceGenerator.kt b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/DashMediaSourceGenerator.kt index 2b17704..bef0a39 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/DashMediaSourceGenerator.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/DashMediaSourceGenerator.kt @@ -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 @@ -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) diff --git a/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/DrmMediaSourceHelper.kt b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/DrmMediaSourceHelper.kt new file mode 100644 index 0000000..c906987 --- /dev/null +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/DrmMediaSourceHelper.kt @@ -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() +}