diff --git a/Armadillo/src/main/java/com/scribd/armadillo/di/DownloadModule.kt b/Armadillo/src/main/java/com/scribd/armadillo/di/DownloadModule.kt index 8c8270f..26c6dcb 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/di/DownloadModule.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/di/DownloadModule.kt @@ -38,6 +38,7 @@ import com.scribd.armadillo.exoplayerExternalDirectory import dagger.Module import dagger.Provides import java.io.File +import java.security.KeyStore import javax.inject.Named import javax.inject.Qualifier import javax.inject.Singleton @@ -122,35 +123,40 @@ internal class DownloadModule { @Provides @Named(Constants.DI.STANDARD_SECURE_STORAGE) fun standardSecureStorage(context: Context): SharedPreferences { - val keys = MasterKeys.getOrCreate( - KeyGenParameterSpec.Builder("armadilloStandard", PURPOSE_ENCRYPT or PURPOSE_DECRYPT) - .setKeySize(256) - .setBlockModes(BLOCK_MODE_GCM) - .setEncryptionPaddings(ENCRYPTION_PADDING_NONE) - .build() - ) - return EncryptedSharedPreferences.create( - "armadillo.standard.secure", - keys, - context, - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM - ) + val keystoreAlias = "armadilloStandard" + val fileName = "armadillo.standard.secure" + return createEncryptedSharedPrefsKeyStore(context = context, fileName = fileName, keystoreAlias = keystoreAlias) } @Singleton @Provides @Named(Constants.DI.DRM_SECURE_STORAGE) fun drmSecureStorage(context: Context): SharedPreferences { - val keys = MasterKeys.getOrCreate( - KeyGenParameterSpec.Builder("armadillo", PURPOSE_ENCRYPT or PURPOSE_DECRYPT) - .setKeySize(256) - .setBlockModes(BLOCK_MODE_GCM) - .setEncryptionPaddings(ENCRYPTION_PADDING_NONE) - .build() - ) + val keystoreAlias = "armadillo" + val fileName = "armadillo.download.secure" + return createEncryptedSharedPrefsKeyStore(context = context, fileName = fileName, keystoreAlias = keystoreAlias) + } + + private fun createEncryptedSharedPrefsKeyStore(context: Context, fileName: String, keystoreAlias: String) + : SharedPreferences { + val keySpec = KeyGenParameterSpec.Builder(keystoreAlias, PURPOSE_ENCRYPT or PURPOSE_DECRYPT) + .setKeySize(256) + .setBlockModes(BLOCK_MODE_GCM) + .setEncryptionPaddings(ENCRYPTION_PADDING_NONE) + .build() + + val keys = try { + MasterKeys.getOrCreate(keySpec) + } catch (ex: Exception) { + //clear corrupted store, contents will be lost + val keyStore = KeyStore.getInstance("AndroidKeyStore") + keyStore.load(null) + keyStore.deleteEntry(keystoreAlias) + context.getSharedPreferences(fileName, Context.MODE_PRIVATE).edit().clear().apply() + MasterKeys.getOrCreate(keySpec) + } return EncryptedSharedPreferences.create( - "armadillo.download.secure", + fileName, keys, context, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, diff --git a/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadEngine.kt b/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadEngine.kt index 57a8c46..283a89d 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadEngine.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadEngine.kt @@ -40,7 +40,7 @@ internal interface DownloadEngine { fun removeDownload(audioPlayable: AudioPlayable) fun removeAllDownloads() fun updateProgress() - fun redownloadDrmLicense(request: AudioPlayable.MediaRequest) + fun redownloadDrmLicense(id: String, request: AudioPlayable.MediaRequest) } /** @@ -70,15 +70,20 @@ internal class ExoplayerDownloadEngine @Inject constructor( scope.launch(errorHandler) { launch { // Download DRM license for offline use - offlineDrmManager.downloadDrmLicenseForOffline(audioPlayable.request) + offlineDrmManager.downloadDrmLicenseForOffline(id = audioPlayable.id.toString(), request = audioPlayable.request) } launch { - val downloadHelper = downloadHelper(context, audioPlayable.request) + val downloadHelper = downloadHelper( + id = audioPlayable.id.toString(), + context = context, + mediaRequest = audioPlayable.request + ) downloadHelper.prepare(object : DownloadHelper.Callback { override fun onPrepared(helper: DownloadHelper) { val request = helper.getDownloadRequest(audioPlayable.id.encodeInByteArray()) + .copyWithId(audioPlayable.id.toString()) try { startDownload(context, request) } catch (e: Exception) { @@ -99,8 +104,8 @@ internal class ExoplayerDownloadEngine @Inject constructor( override fun removeDownload(audioPlayable: AudioPlayable) { scope.launch(errorHandler) { - launch { downloadManager.removeDownload(audioPlayable.request.url) } - launch { offlineDrmManager.removeDownloadedDrmLicense(audioPlayable.request) } + launch { downloadManager.removeDownload(audioPlayable.id.toString()) } + launch { offlineDrmManager.removeDownloadedDrmLicense(id = audioPlayable.id.toString(), request = audioPlayable.request) } } } @@ -113,10 +118,10 @@ internal class ExoplayerDownloadEngine @Inject constructor( override fun updateProgress() = downloadTracker.updateProgress() - override fun redownloadDrmLicense(request: AudioPlayable.MediaRequest) { + override fun redownloadDrmLicense(id: String, request: AudioPlayable.MediaRequest) { scope.launch(errorHandler) { try { - offlineDrmManager.downloadDrmLicenseForOffline(request) + offlineDrmManager.downloadDrmLicenseForOffline(id = id, request = request) } catch (ex: DrmDownloadException){ //continue to try and use old license - a playback error appears elsewhere } @@ -126,7 +131,7 @@ internal class ExoplayerDownloadEngine @Inject constructor( private fun startDownload(context: Context, downloadRequest: DownloadRequest) = DownloadService.sendAddDownload(context, downloadService, downloadRequest, true) - private fun downloadHelper(context: Context, mediaRequest: AudioPlayable.MediaRequest): DownloadHelper { + private fun downloadHelper(id: String, context: Context, mediaRequest: AudioPlayable.MediaRequest): DownloadHelper { val uri = mediaRequest.url.toUri() val renderersFactory = createRenderersFactory(context) val dataSourceFactory = DefaultHttpDataSource.Factory().setUserAgent(Constants.getUserAgent(context)) @@ -139,6 +144,7 @@ internal class ExoplayerDownloadEngine @Inject constructor( } val mediaItem = MediaItem.Builder() .setUri(uri) + .setMediaId(id) .build() return when (@C.ContentType val type = Util.inferContentType(uri)) { C.TYPE_HLS, diff --git a/Armadillo/src/main/java/com/scribd/armadillo/download/ExoplayerDownloadTracker.kt b/Armadillo/src/main/java/com/scribd/armadillo/download/ExoplayerDownloadTracker.kt index b200cd3..a15fb8d 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/download/ExoplayerDownloadTracker.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/download/ExoplayerDownloadTracker.kt @@ -1,6 +1,5 @@ package com.scribd.armadillo.download -import android.net.Uri import android.util.Log import androidx.annotation.VisibleForTesting import com.google.android.exoplayer2.offline.Download @@ -14,7 +13,6 @@ import com.scribd.armadillo.actions.StopTrackingDownloadAction import com.scribd.armadillo.actions.UpdateDownloadAction import com.scribd.armadillo.error.DownloadFailed import com.scribd.armadillo.error.UnableToLoadDownloadInfo -import com.scribd.armadillo.extensions.toUri import com.scribd.armadillo.models.DownloadProgressInfo import com.scribd.armadillo.models.DownloadState import kotlinx.coroutines.CoroutineScope @@ -35,8 +33,8 @@ import javax.inject.Singleton */ internal interface DownloadTracker { fun init() - fun trackDownload(download: ExoplayerDownload) - fun getDownload(uri: Uri): ExoplayerDownload? + fun trackDownload(id: String, download: ExoplayerDownload) + fun getDownload(id: String, uri: String): ExoplayerDownload? fun updateProgress() suspend fun loadDownloads() } @@ -58,7 +56,7 @@ internal class ExoplayerDownloadTracker @Inject constructor( private const val TAG = "DownloadTracker" } - private val downloads = HashMap() + private val downloads = HashMap() private val downloadIndex = downloadManager.downloadIndex private var isInitialized = false @@ -89,8 +87,8 @@ internal class ExoplayerDownloadTracker @Inject constructor( .use { loadedDownloads -> while (loadedDownloads.moveToNext()) { val download = loadedDownloads.download - downloads[download.request.uri] = download - // If we want to resume downloads we should make a call here to the download service to begin download for this uri + downloads[download.request.id] = download + //If we want to resume downloads we should make a call here to the download service to begin download for this id } } } catch (e: IOException) { @@ -102,18 +100,22 @@ internal class ExoplayerDownloadTracker @Inject constructor( } } - override fun trackDownload(download: ExoplayerDownload) { - if (downloads.containsKey(download.request.uri)) { + override fun trackDownload(id: String, download: ExoplayerDownload) { + if (downloads.containsKey(id) || downloads.containsKey(download.request.uri.toString())) { return } - downloads[download.request.uri] = download + downloads[id] = download } - override fun getDownload(uri: Uri): ExoplayerDownload? = downloads[uri] + override fun getDownload(id: String, uri: String): ExoplayerDownload? = downloads[id] ?: downloads[uri] //older usage override fun updateProgress() { downloadManager.currentDownloads.forEach { download -> - downloads[download.request.uri] = download + if(downloads.containsKey(download.request.uri.toString())){ + downloads[download.request.uri.toString()] = download //older usage + } else { + downloads[download.request.id] = download + } TestableDownloadState(download).toDownloadInfo()?.let { dispatchActionsForProgress(it) } @@ -127,7 +129,11 @@ internal class ExoplayerDownloadTracker @Inject constructor( override fun onDownloadChanged(downloadManager: DownloadManager, download: Download, finalException: Exception?) { Log.v(TAG, "onDownloadChanged") - downloads[download.request.uri] = download + if(downloads.containsKey(download.request.uri.toString())){ + downloads[download.request.uri.toString()] = download //older usage + } else { + downloads[download.request.id] = download + } TestableDownloadState(download).toDownloadInfo()?.let { dispatchActionsForProgress(it) } @@ -138,7 +144,8 @@ internal class ExoplayerDownloadTracker @Inject constructor( */ override fun onDownloadRemoved(downloadManager: DownloadManager, download: ExoplayerDownload) { Log.v(TAG, "onDownloadRemoved") - downloads.remove(download.request.uri) + downloads.remove(download.request.id) + downloads.remove(download.request.uri.toString()) //older usage TestableDownloadState(download).toDownloadInfo()?.let { dispatchActionsForProgress(it) } @@ -175,6 +182,7 @@ internal class ExoplayerDownloadTracker @Inject constructor( } private fun stopTracking(downloadInfo: DownloadProgressInfo) { - downloads.remove(downloadInfo.url.toUri()) + downloads.remove(downloadInfo.id.toString()) + downloads.remove(downloadInfo.url) //older usage } } diff --git a/Armadillo/src/main/java/com/scribd/armadillo/download/drm/OfflineDrmManager.kt b/Armadillo/src/main/java/com/scribd/armadillo/download/drm/OfflineDrmManager.kt index 9ade829..859e351 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/download/drm/OfflineDrmManager.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/download/drm/OfflineDrmManager.kt @@ -28,7 +28,7 @@ internal class OfflineDrmManager @Inject constructor( private const val TAG = "OfflineDrmManager" } - suspend fun downloadDrmLicenseForOffline(request: AudioPlayable.MediaRequest) { + suspend fun downloadDrmLicenseForOffline(id: String, request: AudioPlayable.MediaRequest) { withContext(Dispatchers.IO) { request.drmInfo?.let { drmInfo -> val drmResult = when (@C.ContentType val type = Util.inferContentType(request.url.toUri(), null)) { @@ -41,18 +41,18 @@ internal class OfflineDrmManager @Inject constructor( ) // Persist DRM result, which includes the key ID that can be used to retrieve the offline license - secureStorage.saveDrmDownload(context, request.url, drmResult) + secureStorage.saveDrmDownload(context, id, drmResult) Log.i(TAG, "DRM license ready for offline usage") } } } - suspend fun removeDownloadedDrmLicense(request: AudioPlayable.MediaRequest) { + suspend fun removeDownloadedDrmLicense(id: String, request: AudioPlayable.MediaRequest) { withContext(Dispatchers.IO) { request.drmInfo?.let { drmInfo -> - secureStorage.getDrmDownload(context, request.url, drmInfo.drmType)?.let { drmDownload -> + secureStorage.getDrmDownload(context = context, id = id, drmType = drmInfo.drmType)?.let { drmDownload -> // Remove the persisted download info immediately so audio playback would stop using the offline license - secureStorage.removeDrmDownload(context, request.url, drmInfo.drmType) + secureStorage.removeDrmDownload(context = context, id = id, drmType = drmInfo.drmType) // Release the DRM license when (val type = drmDownload.audioType) { diff --git a/Armadillo/src/main/java/com/scribd/armadillo/download/drm/events/WidevineSessionEventListener.kt b/Armadillo/src/main/java/com/scribd/armadillo/download/drm/events/WidevineSessionEventListener.kt index 2c19ee2..c871f37 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/download/drm/events/WidevineSessionEventListener.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/download/drm/events/WidevineSessionEventListener.kt @@ -7,7 +7,6 @@ import com.scribd.armadillo.StateStore import com.scribd.armadillo.actions.LicenseAcquiredAction import com.scribd.armadillo.actions.LicenseReleasedAction import com.scribd.armadillo.di.Injector -import com.scribd.armadillo.encryption.SecureStorage import com.scribd.armadillo.models.DrmType import javax.inject.Inject @@ -17,9 +16,6 @@ internal class WidevineSessionEventListener @Inject internal lateinit var stateStore: StateStore.Modifier - @Inject - internal lateinit var secureStorage: SecureStorage - @Inject internal lateinit var context: Context diff --git a/Armadillo/src/main/java/com/scribd/armadillo/encryption/SecureStorage.kt b/Armadillo/src/main/java/com/scribd/armadillo/encryption/SecureStorage.kt index 140543d..17de01e 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/encryption/SecureStorage.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/encryption/SecureStorage.kt @@ -18,10 +18,10 @@ import javax.inject.Singleton internal interface SecureStorage { fun downloadSecretKey(context: Context): ByteArray - fun saveDrmDownload(context: Context, audioUrl: String, drmDownload: DrmDownload) - fun getDrmDownload(context: Context, audioUrl: String, drmType: DrmType): DrmDownload? + fun saveDrmDownload(context: Context, id: String, drmDownload: DrmDownload) + fun getDrmDownload(context: Context, id: String, drmType: DrmType): DrmDownload? fun getAllDrmDownloads(context: Context): Map - fun removeDrmDownload(context: Context, audioUrl: String, drmType: DrmType) + fun removeDrmDownload(context: Context, id: String, drmType: DrmType) fun removeDrmDownload(context: Context, key: String) } @@ -42,14 +42,14 @@ internal class ArmadilloSecureStorage @Inject constructor( override fun downloadSecretKey(context: Context): ByteArray { return if (secureStandardStorage.contains(DOWNLOAD_KEY)) { - val storedKey = secureDrmStorage.getString(DOWNLOAD_KEY, DEFAULT)!! + val storedKey = secureDrmStorage.getString(DOWNLOAD_KEY, DEFAULT) ?: DEFAULT if (storedKey == DEFAULT) { Log.e(TAG, "Storage Is Out of Alignment") } storedKey.toSecretByteArray } else if(legacyStandardStorage.contains(DOWNLOAD_KEY)) { //migrate to secured version - val storedKey = legacyStandardStorage.getString(DOWNLOAD_KEY, DEFAULT)!! + val storedKey = legacyStandardStorage.getString(DOWNLOAD_KEY, DEFAULT) ?: DEFAULT if (storedKey == DEFAULT) { Log.e(TAG, "Storage Is Out of Alignment") } @@ -70,14 +70,14 @@ internal class ArmadilloSecureStorage @Inject constructor( .joinToString("") } - override fun saveDrmDownload(context: Context, audioUrl: String, drmDownload: DrmDownload) { - val alias = getDrmDownloadAlias(audioUrl, drmDownload.drmType) + override fun saveDrmDownload(context: Context, id: String, drmDownload: DrmDownload) { + val alias = getDrmDownloadAlias(id, drmDownload.drmType) val value = Base64.encodeToString(Json.encodeToString(drmDownload).toByteArray(StandardCharsets.UTF_8), Base64.NO_WRAP) secureDrmStorage.edit().putString(alias, value).apply() } - override fun getDrmDownload(context: Context, audioUrl: String, drmType: DrmType): DrmDownload? { - val alias = getDrmDownloadAlias(audioUrl, drmType) + override fun getDrmDownload(context: Context, id: String, drmType: DrmType): DrmDownload? { + val alias = getDrmDownloadAlias(id, drmType) var download = secureDrmStorage.getString(alias, null)?.decodeToDrmDownload() if (download == null && legacyDrmStorage.contains(alias)) { //migrate old storage to secure storage @@ -101,11 +101,11 @@ internal class ArmadilloSecureStorage @Inject constructor( } }.toMap() - return drmDownloads.plus(legacyDownloads) + return legacyDownloads.plus(drmDownloads) } - override fun removeDrmDownload(context: Context, audioUrl: String, drmType: DrmType) { - val alias = getDrmDownloadAlias(audioUrl, drmType) + override fun removeDrmDownload(context: Context, id: String, drmType: DrmType) { + val alias = getDrmDownloadAlias(id, drmType) legacyDrmStorage.edit().remove(alias).apply() secureDrmStorage.edit().remove(alias).apply() } @@ -124,8 +124,8 @@ internal class ArmadilloSecureStorage @Inject constructor( return keyBytes } - private fun getDrmDownloadAlias(audioUrl: String, drmType: DrmType) = - Base64.encodeToString(audioUrl.toSecretByteArray + drmType.name.toSecretByteArray, Base64.NO_WRAP) + private fun getDrmDownloadAlias(id: String, drmType: DrmType) = + Base64.encodeToString(id.toSecretByteArray + drmType.name.toSecretByteArray, Base64.NO_WRAP) private fun String.decodeToDrmDownload(): DrmDownload = Base64.decode(this, Base64.NO_WRAP).let { resultByteArray -> diff --git a/Armadillo/src/main/java/com/scribd/armadillo/playback/PlaybackEngine.kt b/Armadillo/src/main/java/com/scribd/armadillo/playback/PlaybackEngine.kt index df37281..08d303d 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/PlaybackEngine.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/PlaybackEngine.kt @@ -141,7 +141,10 @@ internal class ExoplayerPlaybackEngine(private var audioPlayable: AudioPlayable) exoPlayer = createExoplayerInstance(context, audioAttributes.exoPlayerAttrs, loadControl) try { - val mediaSource = mediaSourceRetriever.generateMediaSource(audioPlayable.request, context) + val mediaSource = mediaSourceRetriever.generateMediaSource( + mediaId = audioPlayable.id.toString(), + request = audioPlayable.request, + context = context) exoPlayer.setMediaSource(mediaSource) exoPlayer.prepare() 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 567dba7..4c41bb7 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 @@ -30,20 +30,25 @@ internal class DashMediaSourceGenerator @Inject constructor( private val drmHandler = Handler(context.mainLooper) - override fun generateMediaSource(context: Context, request: AudioPlayable.MediaRequest): MediaSource { + override fun generateMediaSource(mediaId: String, context: Context, request: AudioPlayable.MediaRequest): MediaSource { if (request.drmInfo != null) { stateStore.dispatch(OpeningLicenseAction(request.drmInfo.drmType)) } val dataSourceFactory = mediaSourceHelper.createDataSourceFactory(context, request) - val download = downloadTracker.getDownload(request.url.toUri()) + val download = downloadTracker.getDownload(id = mediaId, uri = request.url) val isDownloaded = download != null && download.state == Download.STATE_COMPLETED - val mediaItem = drmMediaSourceHelper.createMediaItem(context = context, request = request, isDownload = isDownloaded) + val mediaItem = drmMediaSourceHelper.createMediaItem( + context = context, + id = mediaId, + request = request, + isDownload = isDownloaded + ) return if (isDownloaded) { val drmManager = drmSessionManagerProvider.get(mediaItem) if(request.drmInfo?.drmType == DrmType.WIDEVINE) { - downloadEngine.redownloadDrmLicense(request) + downloadEngine.redownloadDrmLicense(id = mediaId, request = request) } DownloadHelper.createMediaSource(download!!.request, dataSourceFactory, drmManager) } else { 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 index ef4841e..f500ff2 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/DrmMediaSourceHelper.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/DrmMediaSourceHelper.kt @@ -18,6 +18,7 @@ import javax.inject.Singleton internal interface DrmMediaSourceHelper { fun createMediaItem( context: Context, + id: String, request: AudioPlayable.MediaRequest, isDownload: Boolean, ): MediaItem @@ -26,7 +27,7 @@ internal interface DrmMediaSourceHelper { @Singleton internal class DrmMediaSourceHelperImpl @Inject constructor(private val secureStorage: SecureStorage) : DrmMediaSourceHelper { - override fun createMediaItem(context: Context, request: AudioPlayable.MediaRequest, isDownload: Boolean): MediaItem = + override fun createMediaItem(context: Context, id: String, request: AudioPlayable.MediaRequest, isDownload: Boolean): MediaItem = MediaItem.Builder() .setUri(request.url) .apply { @@ -39,7 +40,7 @@ internal class DrmMediaSourceHelperImpl @Inject constructor(private val secureSt // 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.getDrmDownload(context, request.url, drmInfo.drmType)?.let { drmDownload -> + secureStorage.getDrmDownload(context = context, id = id, drmType = drmInfo.drmType)?.let { drmDownload -> setKeySetId(drmDownload.drmKeyId) } ?: throw DrmPlaybackException(IllegalStateException("No DRM key id saved for download content")) } diff --git a/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/HlsMediaSourceGenerator.kt b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/HlsMediaSourceGenerator.kt index 7d40f47..8893c95 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/HlsMediaSourceGenerator.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/HlsMediaSourceGenerator.kt @@ -21,10 +21,10 @@ internal class HlsMediaSourceGenerator @Inject constructor( private val downloadTracker: DownloadTracker) : MediaSourceGenerator { - override fun generateMediaSource(context: Context, request: AudioPlayable.MediaRequest): MediaSource { + override fun generateMediaSource(mediaId: String, context: Context, request: AudioPlayable.MediaRequest): MediaSource { val dataSourceFactory = mediaSourceHelper.createDataSourceFactory(context, request) - downloadTracker.getDownload(request.url.toUri())?.let { + downloadTracker.getDownload(id = mediaId, uri = request.url)?.let { if (it.state != Download.STATE_FAILED) { return DownloadHelper.createMediaSource(it.request, dataSourceFactory) } diff --git a/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/MediaSourceGenerator.kt b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/MediaSourceGenerator.kt index 5cdaef0..f16b601 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/MediaSourceGenerator.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/MediaSourceGenerator.kt @@ -11,7 +11,7 @@ internal interface MediaSourceGenerator { const val TAG = "MediaSourceGenerator" } - fun generateMediaSource(context: Context, request: AudioPlayable.MediaRequest): MediaSource + fun generateMediaSource(mediaId: String, context: Context, request: AudioPlayable.MediaRequest): MediaSource fun updateMediaSourceHeaders(request: AudioPlayable.MediaRequest) } \ No newline at end of file diff --git a/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/MediaSourceRetriever.kt b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/MediaSourceRetriever.kt index 51426dc..a312dc1 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/MediaSourceRetriever.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/MediaSourceRetriever.kt @@ -12,8 +12,7 @@ import javax.inject.Inject /** Creates a MediaSource for starting playback in Exoplayer based on what type * of audio content is passed into it. */ interface MediaSourceRetriever { - fun generateMediaSource(request: AudioPlayable.MediaRequest, - context: Context): MediaSource + fun generateMediaSource(mediaId: String, request: AudioPlayable.MediaRequest, context: Context): MediaSource fun updateMediaSourceHeaders(request: AudioPlayable.MediaRequest) } @@ -32,10 +31,11 @@ class MediaSourceRetrieverImpl @Inject constructor(): MediaSourceRetriever { Injector.mainComponent.inject(this) } - override fun generateMediaSource(request: AudioPlayable.MediaRequest, + override fun generateMediaSource(mediaId: String, + request: AudioPlayable.MediaRequest, context: Context): MediaSource { - return buildMediaGenerator(request).generateMediaSource(context, request) + return buildMediaGenerator(request).generateMediaSource(mediaId = mediaId, context = context, request = request) } override fun updateMediaSourceHeaders(request: AudioPlayable.MediaRequest) { diff --git a/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/ProgressiveMediaSourceGenerator.kt b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/ProgressiveMediaSourceGenerator.kt index 375ad9a..6f4f0b5 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/ProgressiveMediaSourceGenerator.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/ProgressiveMediaSourceGenerator.kt @@ -16,7 +16,7 @@ import javax.inject.Inject internal class ProgressiveMediaSourceGenerator @Inject constructor( private val cacheManager: CacheManager) : MediaSourceGenerator { - override fun generateMediaSource(context: Context, request: AudioPlayable.MediaRequest): MediaSource = + override fun generateMediaSource(mediaId: String, context: Context, request: AudioPlayable.MediaRequest): MediaSource = ProgressiveMediaSource.Factory(buildDataSourceFactory(context)).createMediaSource(MediaItem.fromUri(request.url)).also { if (request.drmInfo != null) { Log.e(MediaSourceGenerator.TAG, "Progressive media does not currently support DRM") diff --git a/RELEASE.md b/RELEASE.md index 0d1f930..8d20b0a 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,5 +1,9 @@ # Project Armadillo Release Notes +## 1.6.3 +- Prevents downloaded content with rotating URLs from being lost in the download system after the URL moves. +- Prevents devices that fail to initialize encrypted storage from crashing during initialization. + ## 1.6.2 - Prevents fatal crashing for actions are being performed before the player is initialized diff --git a/gradle.properties b/gradle.properties index cd06b72..d28f589 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,7 @@ org.gradle.jvmargs=-Xmx1536m # org.gradle.parallel=true PACKAGE_NAME=com.scribd.armadillo GRADLE_PLUGIN_VERSION=7.2.0 -LIBRARY_VERSION=1.6.2 +LIBRARY_VERSION=1.6.3 EXOPLAYER_VERSION=2.19.1 RXJAVA_VERSION=2.2.4 RXANDROID_VERSION=2.0.1