diff --git a/Armadillo/build.gradle b/Armadillo/build.gradle index f560e51..ec14ca0 100644 --- a/Armadillo/build.gradle +++ b/Armadillo/build.gradle @@ -49,15 +49,18 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' - implementation "com.google.android.exoplayer:exoplayer-core:${EXOPLAYER_VERSION}" - implementation "com.google.android.exoplayer:exoplayer-hls:${EXOPLAYER_VERSION}" - implementation "com.google.android.exoplayer:extension-mediasession:${EXOPLAYER_VERSION}" + implementation "androidx.media3:media3-exoplayer:${EXOPLAYER_VERSION}" + implementation "androidx.media3:media3-exoplayer-hls:${EXOPLAYER_VERSION}" + implementation "androidx.media3:media3-session:${EXOPLAYER_VERSION}" implementation "io.reactivex.rxjava2:rxjava:${RXJAVA_VERSION}" implementation "io.reactivex.rxjava2:rxandroid:${RXANDROID_VERSION}" implementation "com.google.dagger:dagger:${DAGGER_VERSION}" kapt "com.google.dagger:dagger-compiler:${DAGGER_VERSION}" implementation 'androidx.media:media:1.6.0' + + implementation 'androidx.core:core-ktx:1.10.1' + testImplementation "org.robolectric:robolectric:4.5.1" testImplementation 'junit:junit:4.13.2' testImplementation("org.assertj:assertj-core:3.10.0") diff --git a/Armadillo/src/main/java/com/scribd/armadillo/ArmadilloPlayerChoreographer.kt b/Armadillo/src/main/java/com/scribd/armadillo/ArmadilloPlayerChoreographer.kt index 0e072e8..397fcf8 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/ArmadilloPlayerChoreographer.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/ArmadilloPlayerChoreographer.kt @@ -54,7 +54,6 @@ import javax.inject.Inject interface ArmadilloPlayer { var skipDistance: Milliseconds var playbackSpeed: Float - var isInForeground: Boolean val downloadCacheSize: Long val playbackCacheSize: Long @@ -178,14 +177,6 @@ internal class ArmadilloPlayerChoreographer : ArmadilloPlayer { } } - override var isInForeground: Boolean = false - set(value) { - field = value - doWhenPlaybackReady { - it.sendCustomAction(CustomAction.SetIsInForeground(isInForeground)) - } - } - @Inject internal lateinit var context: Context @Inject diff --git a/Armadillo/src/main/java/com/scribd/armadillo/Constants.kt b/Armadillo/src/main/java/com/scribd/armadillo/Constants.kt index 91af7fb..94001f2 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/Constants.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/Constants.kt @@ -1,14 +1,14 @@ package com.scribd.armadillo import android.content.Context -import com.google.android.exoplayer2.util.Util +import androidx.annotation.OptIn +import androidx.media3.common.util.UnstableApi +import androidx.media3.common.util.Util import com.scribd.armadillo.time.seconds object Constants { const val LIBRARY_VERSION = BuildConfig.VERSION_NAME - internal const val PLAYBACK_LOADING_STATUS = "playback_loading_status" - internal const val DEFAULT_PLAYBACK_SPEED = 1.0f internal const val DEBUG_MAX_SIZE = 20 @@ -23,6 +23,7 @@ object Constants { internal val MAX_POSITION_FOR_SEEK_TO_PREVIOUS = 3.seconds.inMilliseconds internal val AUDIO_SKIP_DURATION = 30.seconds.inMilliseconds + @OptIn(UnstableApi::class) internal fun getUserAgent(context: Context): String = Util.getUserAgent(context, context.getString(APP_NAME)) private val APP_NAME = R.string.arm_app_name @@ -55,14 +56,12 @@ object Constants { } internal object Actions { - const val SET_IS_IN_FOREGROUND = "set_is_in_foreground_action" const val SET_PLAYBACK_SPEED = "set_playback_speed_action" const val UPDATE_PROGRESS = "updated_progress_action" const val UPDATE_METADATA = "update_metadata" const val UPDATE_MEDIA_REQUEST = "update_media_request" object Extras { - const val IS_IN_FOREGROUND = "is_in_foreground" const val PLAYBACK_SPEED = "playback_speed" const val METADATA_TITLE = "metadata_title" const val METADATA_CHAPTERS = "metadata_chapters" diff --git a/Armadillo/src/main/java/com/scribd/armadillo/Util.kt b/Armadillo/src/main/java/com/scribd/armadillo/Util.kt index 268c1f7..d6b1c06 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/Util.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/Util.kt @@ -2,13 +2,16 @@ package com.scribd.armadillo import android.content.Context import android.os.Build +import androidx.annotation.ChecksSdkIntAtLeast import com.scribd.armadillo.models.Chapter import com.scribd.armadillo.time.Interval import com.scribd.armadillo.time.Millisecond import com.scribd.armadillo.time.milliseconds import java.io.File +import kotlin.math.floor +import kotlin.math.roundToInt -internal typealias ExoplayerDownload = com.google.android.exoplayer2.offline.Download +internal typealias ExoplayerDownload = androidx.media3.exoplayer.offline.Download internal typealias Milliseconds = Interval fun exoplayerExternalDirectory(context: Context): File = @@ -23,12 +26,12 @@ fun sanitizeChapters(chapters: List): List { } chapter.copy( startTime = 0.milliseconds, - duration = Math.round(chapter.duration.value).milliseconds) + duration = chapter.duration.value.roundToInt().milliseconds) } chapters.size - 1 -> { // Math.floor used because chapter runtime must be slightly less then audioPlayable runtime in order to detect end of book - val endTime = Math.floor(chapters.last().startTime.value + chapters.last().duration.value).milliseconds + val endTime = floor(chapters.last().startTime.value + chapters.last().duration.value).milliseconds val previousChapter = chapters[i - 1] val startTime = previousChapter.startTime + previousChapter.duration chapter.copy( @@ -38,12 +41,13 @@ fun sanitizeChapters(chapters: List): List { else -> { val previousChapter = chapters[i - 1] chapter.copy( - startTime = Math.round(previousChapter.startTime.value + previousChapter.duration.value).milliseconds, - duration = Math.round(chapter.duration.value).milliseconds) + startTime = (previousChapter.startTime.value + previousChapter.duration.value).roundToInt().milliseconds, + duration = chapter.duration.value.roundToInt().milliseconds) } } } } +@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S) fun hasSnowCone() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S \ No newline at end of file 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 f07089a..f2611d8 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/di/DownloadModule.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/di/DownloadModule.kt @@ -1,20 +1,21 @@ package com.scribd.armadillo.di import android.content.Context -import com.google.android.exoplayer2.offline.DownloadManager -import com.google.android.exoplayer2.offline.DownloadService -import com.google.android.exoplayer2.offline.DownloaderFactory -import com.google.android.exoplayer2.upstream.DefaultHttpDataSource -import com.google.android.exoplayer2.upstream.cache.Cache -import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor -import com.google.android.exoplayer2.upstream.cache.SimpleCache +import androidx.annotation.OptIn +import androidx.media3.common.util.UnstableApi +import androidx.media3.datasource.DefaultHttpDataSource +import androidx.media3.datasource.cache.Cache +import androidx.media3.datasource.cache.NoOpCacheEvictor +import androidx.media3.datasource.cache.SimpleCache +import androidx.media3.exoplayer.offline.DownloadManager +import androidx.media3.exoplayer.offline.DownloadService +import androidx.media3.exoplayer.offline.DownloaderFactory import com.scribd.armadillo.Constants import com.scribd.armadillo.download.ArmadilloDatabaseProvider import com.scribd.armadillo.download.ArmadilloDatabaseProviderImpl import com.scribd.armadillo.download.ArmadilloDownloadManagerFactory import com.scribd.armadillo.download.CacheManager import com.scribd.armadillo.download.CacheManagerImpl -import com.scribd.armadillo.download.MaxAgeCacheEvictor import com.scribd.armadillo.download.DefaultExoplayerDownloadService import com.scribd.armadillo.download.DownloadEngine import com.scribd.armadillo.download.DownloadManagerFactory @@ -22,6 +23,7 @@ import com.scribd.armadillo.download.DownloadTracker import com.scribd.armadillo.download.ExoplayerDownloadEngine import com.scribd.armadillo.download.ExoplayerDownloadTracker import com.scribd.armadillo.download.HeaderAwareDownloaderFactory +import com.scribd.armadillo.download.MaxAgeCacheEvictor import com.scribd.armadillo.encryption.ExoplayerEncryption import com.scribd.armadillo.encryption.ExoplayerEncryptionImpl import com.scribd.armadillo.exoplayerExternalDirectory @@ -33,6 +35,7 @@ import javax.inject.Qualifier import javax.inject.Singleton @Module +@OptIn(UnstableApi::class) internal class DownloadModule { @Singleton @Provides diff --git a/Armadillo/src/main/java/com/scribd/armadillo/download/ArmadilloDatabaseProvider.kt b/Armadillo/src/main/java/com/scribd/armadillo/download/ArmadilloDatabaseProvider.kt index 421ef73..6383d0e 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/download/ArmadilloDatabaseProvider.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/download/ArmadilloDatabaseProvider.kt @@ -1,9 +1,9 @@ package com.scribd.armadillo.download import android.content.Context -import com.google.android.exoplayer2.database.DatabaseProvider -import com.google.android.exoplayer2.database.ExoDatabaseProvider -import com.google.android.exoplayer2.upstream.cache.SimpleCache +import androidx.media3.common.util.UnstableApi +import androidx.media3.database.DatabaseProvider +import androidx.media3.database.StandaloneDatabaseProvider import javax.inject.Inject import javax.inject.Singleton @@ -14,7 +14,8 @@ interface ArmadilloDatabaseProvider { val database: DatabaseProvider } +@UnstableApi @Singleton class ArmadilloDatabaseProviderImpl @Inject constructor(context: Context) : ArmadilloDatabaseProvider { - override val database: DatabaseProvider = ExoDatabaseProvider(context) + override val database: DatabaseProvider = StandaloneDatabaseProvider(context) } \ No newline at end of file diff --git a/Armadillo/src/main/java/com/scribd/armadillo/download/CacheManager.kt b/Armadillo/src/main/java/com/scribd/armadillo/download/CacheManager.kt index 3d58aa1..556487e 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/download/CacheManager.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/download/CacheManager.kt @@ -1,10 +1,12 @@ package com.scribd.armadillo.download import android.content.Context -import com.google.android.exoplayer2.upstream.DataSource -import com.google.android.exoplayer2.upstream.FileDataSource -import com.google.android.exoplayer2.upstream.cache.Cache -import com.google.android.exoplayer2.upstream.cache.CacheDataSource +import androidx.annotation.OptIn +import androidx.media3.common.util.UnstableApi +import androidx.media3.datasource.DataSource +import androidx.media3.datasource.FileDataSource +import androidx.media3.datasource.cache.Cache +import androidx.media3.datasource.cache.CacheDataSource import com.scribd.armadillo.Constants import com.scribd.armadillo.encryption.ExoplayerEncryption import javax.inject.Inject @@ -34,6 +36,7 @@ interface CacheManager { val downloadCacheSize: Long } +@OptIn(UnstableApi::class) class CacheManagerImpl @Inject constructor( @Named(Constants.DI.PLAYBACK_CACHE) private val playbackCache: Cache, @Named(Constants.DI.DOWNLOAD_CACHE) private val downloadCache: Cache, diff --git a/Armadillo/src/main/java/com/scribd/armadillo/download/DefaultExoplayerDownloadService.kt b/Armadillo/src/main/java/com/scribd/armadillo/download/DefaultExoplayerDownloadService.kt index e805d39..3a8e110 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/download/DefaultExoplayerDownloadService.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/download/DefaultExoplayerDownloadService.kt @@ -1,12 +1,14 @@ package com.scribd.armadillo.download import android.app.Notification -import com.google.android.exoplayer2.offline.Download -import com.google.android.exoplayer2.offline.DownloadManager -import com.google.android.exoplayer2.offline.DownloadService -import com.google.android.exoplayer2.scheduler.PlatformScheduler -import com.google.android.exoplayer2.scheduler.Scheduler -import com.google.android.exoplayer2.util.Util +import androidx.annotation.OptIn +import androidx.media3.common.util.UnstableApi +import androidx.media3.common.util.Util +import androidx.media3.exoplayer.offline.Download +import androidx.media3.exoplayer.offline.DownloadManager +import androidx.media3.exoplayer.offline.DownloadService +import androidx.media3.exoplayer.scheduler.PlatformScheduler +import androidx.media3.exoplayer.scheduler.Scheduler import com.scribd.armadillo.R import com.scribd.armadillo.di.Injector import com.scribd.armadillo.models.DownloadState @@ -16,7 +18,7 @@ import javax.inject.Inject * This is a basic [DownloadService] that provides a simple default notification. It will store downloads in external storage. If you * need more control over the downloads or notifications, you should provide your own implementation class to the download configuration. */ -internal class DefaultExoplayerDownloadService : DownloadService(FOREGROUND_NOTIFICATION_ID, +@OptIn(UnstableApi::class) internal class DefaultExoplayerDownloadService : DownloadService(FOREGROUND_NOTIFICATION_ID, DEFAULT_FOREGROUND_NOTIFICATION_UPDATE_INTERVAL, CHANNEL_ID, CHANNEL_NAME, 0) { init { 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 e99bea7..d372456 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadEngine.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadEngine.kt @@ -2,16 +2,17 @@ package com.scribd.armadillo.download import android.app.ForegroundServiceStartNotAllowedException import android.content.Context -import com.google.android.exoplayer2.C -import com.google.android.exoplayer2.MediaItem -import com.google.android.exoplayer2.offline.DownloadHelper -import com.google.android.exoplayer2.offline.DownloadManager -import com.google.android.exoplayer2.offline.DownloadRequest -import com.google.android.exoplayer2.offline.DownloadService -import com.google.android.exoplayer2.upstream.DefaultDataSource -import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory -import com.google.android.exoplayer2.upstream.DefaultHttpDataSource -import com.google.android.exoplayer2.util.Util +import androidx.annotation.OptIn +import androidx.media3.common.C +import androidx.media3.common.MediaItem +import androidx.media3.common.util.UnstableApi +import androidx.media3.common.util.Util +import androidx.media3.datasource.DefaultDataSource +import androidx.media3.datasource.DefaultHttpDataSource +import androidx.media3.exoplayer.offline.DownloadHelper +import androidx.media3.exoplayer.offline.DownloadManager +import androidx.media3.exoplayer.offline.DownloadRequest +import androidx.media3.exoplayer.offline.DownloadService import com.scribd.armadillo.Constants import com.scribd.armadillo.HeadersStore import com.scribd.armadillo.StateStore @@ -40,6 +41,7 @@ internal interface DownloadEngine { * Starts the [DownloadService] when necessary */ @Singleton +@OptIn(UnstableApi::class) internal class ExoplayerDownloadEngine @Inject constructor(private val context: Context, private val downloadHeadersStore: HeadersStore, private val downloadService: Class, @@ -94,9 +96,9 @@ internal class ExoplayerDownloadEngine @Inject constructor(private val context: .setUri(uri) .build() return when (@C.ContentType val type = Util.inferContentType(uri)) { - C.TYPE_HLS -> + C.CONTENT_TYPE_HLS -> DownloadHelper.forMediaItem(context, mediaItem, renderersFactory, DefaultDataSource.Factory(context, dataSourceFactory)) - C.TYPE_OTHER -> DownloadHelper.forMediaItem(context, mediaItem) + C.CONTENT_TYPE_OTHER -> DownloadHelper.forMediaItem(context, mediaItem) else -> throw IllegalStateException("Unsupported type: $type") } } diff --git a/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadManagerExt.kt b/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadManagerExt.kt index a1d7594..da9c625 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadManagerExt.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadManagerExt.kt @@ -1,6 +1,6 @@ package com.scribd.armadillo.download -import com.google.android.exoplayer2.offline.DownloadManager +import androidx.media3.common.util.UnstableApi import com.scribd.armadillo.ExoplayerDownload import com.scribd.armadillo.extensions.decodeToInt import com.scribd.armadillo.models.DownloadProgressInfo @@ -10,6 +10,7 @@ import com.scribd.armadillo.models.DownloadState * [ExoplayerDownload] is not easy to test. It's a final class with private constructor. This class is a valuable intermediate * for being able to use this class in testing. */ +@UnstableApi internal data class TestableDownloadState(val id: Int, val url: String, val state: Int, diff --git a/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadManagerFactory.kt b/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadManagerFactory.kt index 643cd61..f79f536 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadManagerFactory.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadManagerFactory.kt @@ -1,12 +1,13 @@ package com.scribd.armadillo.download import android.content.Context -import com.google.android.exoplayer2.database.DatabaseProvider -import com.google.android.exoplayer2.offline.DefaultDownloadIndex -import com.google.android.exoplayer2.offline.DownloadManager -import com.google.android.exoplayer2.offline.DownloaderFactory +import androidx.annotation.OptIn +import androidx.media3.common.util.UnstableApi +import androidx.media3.database.DatabaseProvider +import androidx.media3.exoplayer.offline.DefaultDownloadIndex +import androidx.media3.exoplayer.offline.DownloadManager +import androidx.media3.exoplayer.offline.DownloaderFactory import com.scribd.armadillo.Constants -import com.scribd.armadillo.encryption.ExoplayerEncryption import javax.inject.Inject import javax.inject.Singleton @@ -18,6 +19,7 @@ internal interface DownloadManagerFactory { * All instances of Armadillo must receive the same instance of [DownloadManager]. This is probably best accomplished through DI */ @Singleton +@OptIn(UnstableApi::class) internal class ArmadilloDownloadManagerFactory @Inject constructor( private val downloaderFactory: DownloaderFactory) : DownloadManagerFactory { 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 9952d56..f47df30 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/download/ExoplayerDownloadTracker.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/download/ExoplayerDownloadTracker.kt @@ -2,9 +2,11 @@ package com.scribd.armadillo.download import android.net.Uri import android.util.Log +import androidx.annotation.OptIn import androidx.annotation.VisibleForTesting -import com.google.android.exoplayer2.offline.Download -import com.google.android.exoplayer2.offline.DownloadManager +import androidx.media3.common.util.UnstableApi +import androidx.media3.exoplayer.offline.Download +import androidx.media3.exoplayer.offline.DownloadManager import com.scribd.armadillo.Constants import com.scribd.armadillo.ExoplayerDownload import com.scribd.armadillo.StateStore @@ -22,8 +24,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.IOException -import java.lang.Exception -import java.util.HashMap import javax.inject.Inject import javax.inject.Named import javax.inject.Singleton @@ -49,6 +49,7 @@ internal interface DownloadTracker { * The client will be notified that a download is complete once. */ @Singleton +@OptIn(UnstableApi::class) internal class ExoplayerDownloadTracker @Inject constructor( @Named(Constants.DI.GLOBAL_SCOPE) private val globalScope: CoroutineScope, private val downloadManager: DownloadManager, diff --git a/Armadillo/src/main/java/com/scribd/armadillo/download/HeaderAwareDownloaderFactory.kt b/Armadillo/src/main/java/com/scribd/armadillo/download/HeaderAwareDownloaderFactory.kt index 0e5280f..ce5bba8 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/download/HeaderAwareDownloaderFactory.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/download/HeaderAwareDownloaderFactory.kt @@ -1,11 +1,13 @@ package com.scribd.armadillo.download import android.content.Context -import com.google.android.exoplayer2.offline.DefaultDownloaderFactory -import com.google.android.exoplayer2.offline.DownloadRequest -import com.google.android.exoplayer2.offline.Downloader -import com.google.android.exoplayer2.upstream.DefaultDataSource -import com.google.android.exoplayer2.upstream.DefaultHttpDataSource +import androidx.annotation.OptIn +import androidx.media3.common.util.UnstableApi +import androidx.media3.datasource.DefaultDataSource +import androidx.media3.datasource.DefaultHttpDataSource +import androidx.media3.exoplayer.offline.DefaultDownloaderFactory +import androidx.media3.exoplayer.offline.DownloadRequest +import androidx.media3.exoplayer.offline.Downloader import com.scribd.armadillo.Constants import com.scribd.armadillo.HeadersStore import java.util.concurrent.ExecutorService @@ -20,6 +22,7 @@ import javax.inject.Inject * * // TODO [29]: Remove this once exoplayer is fixed */ +@OptIn(UnstableApi::class) class HeaderAwareDownloaderFactory @Inject constructor(private val context: Context, private val headersStore: HeadersStore, private val cacheManager: CacheManager, diff --git a/Armadillo/src/main/java/com/scribd/armadillo/download/MaxAgeCacheEvictor.kt b/Armadillo/src/main/java/com/scribd/armadillo/download/MaxAgeCacheEvictor.kt index 406e89b..4e53ffb 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/download/MaxAgeCacheEvictor.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/download/MaxAgeCacheEvictor.kt @@ -1,10 +1,12 @@ package com.scribd.armadillo.download +import androidx.annotation.OptIn import androidx.annotation.VisibleForTesting -import com.google.android.exoplayer2.C -import com.google.android.exoplayer2.upstream.cache.Cache -import com.google.android.exoplayer2.upstream.cache.CacheEvictor -import com.google.android.exoplayer2.upstream.cache.CacheSpan +import androidx.media3.common.C +import androidx.media3.common.util.UnstableApi +import androidx.media3.datasource.cache.Cache +import androidx.media3.datasource.cache.CacheEvictor +import androidx.media3.datasource.cache.CacheSpan import java.util.Deque import java.util.LinkedList @@ -14,6 +16,7 @@ import java.util.LinkedList * - then function as an lru cache for valid content * - if [Int.MAX_VALUE] is passed for [contentMaxAgeMillis], this cache will function as an LRU cache */ +@UnstableApi internal class MaxAgeCacheEvictor( private val maxBytes: Long, private val contentMaxAgeMillis: Int = CONTENT_MAX_AGE_MILLIS, @@ -144,5 +147,6 @@ internal class MaxAgeCacheEvictor( private fun expirationTimestamp() = clock.currentTimeMillis() + contentMaxAgeMillis } +@OptIn(UnstableApi::class) @VisibleForTesting internal fun CacheSpan.key(): Int = this.toString().hashCode() diff --git a/Armadillo/src/main/java/com/scribd/armadillo/encryption/ExoplayerEncryption.kt b/Armadillo/src/main/java/com/scribd/armadillo/encryption/ExoplayerEncryption.kt index 5cf6cff..a1a83e8 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/encryption/ExoplayerEncryption.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/encryption/ExoplayerEncryption.kt @@ -1,12 +1,14 @@ package com.scribd.armadillo.encryption import android.content.Context -import com.google.android.exoplayer2.upstream.DataSink -import com.google.android.exoplayer2.upstream.DataSource -import com.google.android.exoplayer2.upstream.cache.Cache -import com.google.android.exoplayer2.upstream.cache.CacheDataSink -import com.google.android.exoplayer2.upstream.crypto.AesCipherDataSink -import com.google.android.exoplayer2.upstream.crypto.AesCipherDataSource +import androidx.annotation.OptIn +import androidx.media3.common.util.UnstableApi +import androidx.media3.datasource.AesCipherDataSink +import androidx.media3.datasource.AesCipherDataSource +import androidx.media3.datasource.DataSink +import androidx.media3.datasource.DataSource +import androidx.media3.datasource.cache.Cache +import androidx.media3.datasource.cache.CacheDataSink import javax.inject.Inject import javax.inject.Singleton @@ -19,6 +21,7 @@ interface ExoplayerEncryption { * This class provides the plumbing for encrypting downloaded content & then reading this encrypted content. */ @Singleton +@OptIn(UnstableApi::class) internal class ExoplayerEncryptionImpl @Inject constructor(applicationContext: Context) : ExoplayerEncryption { private val secureStorage: SecureStorage = ArmadilloSecureStorage() diff --git a/Armadillo/src/main/java/com/scribd/armadillo/extensions/ExoplayerDownloadExt.kt b/Armadillo/src/main/java/com/scribd/armadillo/extensions/ExoplayerDownloadExt.kt index 33bfbf7..da593a3 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/extensions/ExoplayerDownloadExt.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/extensions/ExoplayerDownloadExt.kt @@ -1,6 +1,6 @@ package com.scribd.armadillo.extensions -import com.google.android.exoplayer2.C +import androidx.media3.common.C import com.scribd.armadillo.ExoplayerDownload /** diff --git a/Armadillo/src/main/java/com/scribd/armadillo/extensions/TransportControlsExt.kt b/Armadillo/src/main/java/com/scribd/armadillo/extensions/TransportControlsExt.kt index 0907067..db00719 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/extensions/TransportControlsExt.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/extensions/TransportControlsExt.kt @@ -2,6 +2,7 @@ package com.scribd.armadillo.extensions import android.os.Bundle import android.support.v4.media.session.MediaControllerCompat +import androidx.core.os.BundleCompat import com.scribd.armadillo.Constants import com.scribd.armadillo.models.AudioPlayable import com.scribd.armadillo.models.Chapter @@ -24,16 +25,13 @@ internal sealed class CustomAction { ?: Constants.DEFAULT_PLAYBACK_SPEED SetPlaybackSpeed(playbackSpeed) } - Constants.Actions.SET_IS_IN_FOREGROUND -> { - val isInForeground = bundle?.getBoolean(Constants.Actions.Extras.IS_IN_FOREGROUND) ?: false - SetIsInForeground(isInForeground) - } Constants.Actions.UPDATE_METADATA -> { val title = bundle?.getString(Constants.Actions.Extras.METADATA_TITLE)!! - val chapters = bundle.getParcelableArrayList(Constants.Actions.Extras.METADATA_CHAPTERS)!! + val chapters = BundleCompat.getParcelableArrayList(bundle, Constants.Actions.Extras.METADATA_CHAPTERS, Chapter::class.java)!! UpdatePlaybackMetadata(title, chapters) } Constants.Actions.UPDATE_MEDIA_REQUEST -> { + @Suppress("DEPRECATION") val mediaRequest = bundle?.getSerializable(Constants.Actions.Extras.MEDIA_REQUEST) as AudioPlayable.MediaRequest UpdateMediaRequest(mediaRequest) } @@ -67,12 +65,4 @@ internal sealed class CustomAction { putSerializable(Constants.Actions.Extras.MEDIA_REQUEST, mediaRequest) } } - - /** - * Signals to the engine that the player is visible and needs more frequent updates - */ - data class SetIsInForeground(val isInForeground: Boolean) : CustomAction() { - override val action: String = Constants.Actions.SET_IS_IN_FOREGROUND - override fun toBundle(): Bundle = Bundle().apply { putBoolean(Constants.Actions.Extras.IS_IN_FOREGROUND, isInForeground) } - } } \ No newline at end of file diff --git a/Armadillo/src/main/java/com/scribd/armadillo/models/ArmadilloState.kt b/Armadillo/src/main/java/com/scribd/armadillo/models/ArmadilloState.kt index b51fb44..9cd8b23 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/models/ArmadilloState.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/models/ArmadilloState.kt @@ -1,6 +1,6 @@ package com.scribd.armadillo.models -import com.google.android.exoplayer2.C +import androidx.media3.common.C import com.scribd.armadillo.ArmadilloDebugView import com.scribd.armadillo.ArmadilloPlayer import com.scribd.armadillo.Constants diff --git a/Armadillo/src/main/java/com/scribd/armadillo/playback/ArmadilloAudioAttributes.kt b/Armadillo/src/main/java/com/scribd/armadillo/playback/ArmadilloAudioAttributes.kt index fb7f417..63c8c9f 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/ArmadilloAudioAttributes.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/ArmadilloAudioAttributes.kt @@ -1,7 +1,7 @@ package com.scribd.armadillo.playback import android.media.AudioAttributes -import com.google.android.exoplayer2.C +import androidx.media3.common.C /** * Creates AudioAttributes - for ExoPlayer and also for Android notifications. @@ -12,15 +12,15 @@ import com.google.android.exoplayer2.C * [getAndroidAttributes] is useful for declaring attributes for notifications. */ interface ArmadilloAudioAttributes { - val exoPlayerAttrs: com.google.android.exoplayer2.audio.AudioAttributes + val exoPlayerAttrs: androidx.media3.common.AudioAttributes fun getAndroidAttributes(): AudioAttributes } class AudioAttributesBuilderImpl : ArmadilloAudioAttributes { - override val exoPlayerAttrs: com.google.android.exoplayer2.audio.AudioAttributes - get() = com.google.android.exoplayer2.audio.AudioAttributes.Builder().run { + override val exoPlayerAttrs: androidx.media3.common.AudioAttributes + get() = androidx.media3.common.AudioAttributes.Builder().run { setUsage(C.USAGE_MEDIA) - setContentType(C.CONTENT_TYPE_SPEECH) + setContentType(C.AUDIO_CONTENT_TYPE_SPEECH) build() } diff --git a/Armadillo/src/main/java/com/scribd/armadillo/playback/ExoPlaybackExceptionExt.kt b/Armadillo/src/main/java/com/scribd/armadillo/playback/ExoPlaybackExceptionExt.kt index 3771541..53219ea 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/ExoPlaybackExceptionExt.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/ExoPlaybackExceptionExt.kt @@ -1,10 +1,12 @@ package com.scribd.armadillo.playback -import com.google.android.exoplayer2.ExoPlaybackException -import com.google.android.exoplayer2.ExoPlaybackException.TYPE_RENDERER -import com.google.android.exoplayer2.ExoPlaybackException.TYPE_SOURCE -import com.google.android.exoplayer2.audio.AudioSink -import com.google.android.exoplayer2.upstream.HttpDataSource +import androidx.annotation.OptIn +import androidx.media3.common.util.UnstableApi +import androidx.media3.datasource.HttpDataSource +import androidx.media3.exoplayer.ExoPlaybackException +import androidx.media3.exoplayer.ExoPlaybackException.TYPE_RENDERER +import androidx.media3.exoplayer.ExoPlaybackException.TYPE_SOURCE +import androidx.media3.exoplayer.audio.AudioSink import com.scribd.armadillo.error.ArmadilloException import com.scribd.armadillo.error.ArmadilloIOException import com.scribd.armadillo.error.HttpResponseCodeException @@ -16,6 +18,7 @@ import com.scribd.armadillo.error.UnknownRendererException import java.net.SocketTimeoutException import java.net.UnknownHostException +@OptIn(UnstableApi::class) internal fun ExoPlaybackException.toArmadilloException(): ArmadilloException { return if (TYPE_SOURCE == type) { return this.sourceException.let { source -> diff --git a/Armadillo/src/main/java/com/scribd/armadillo/playback/ExoplayerExt.kt b/Armadillo/src/main/java/com/scribd/armadillo/playback/ExoplayerExt.kt index 903bc6a..d6861ea 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/ExoplayerExt.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/ExoplayerExt.kt @@ -1,17 +1,16 @@ package com.scribd.armadillo.playback import android.content.Context -import com.google.android.exoplayer2.C -import com.google.android.exoplayer2.ExoPlayer -import com.google.android.exoplayer2.RenderersFactory -import com.google.android.exoplayer2.SimpleExoPlayer -import com.google.android.exoplayer2.audio.AudioAttributes -import com.google.android.exoplayer2.audio.AudioCapabilities -import com.google.android.exoplayer2.audio.DefaultAudioSink -import com.google.android.exoplayer2.audio.DefaultAudioSink.DefaultAudioProcessorChain -import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer -import com.google.android.exoplayer2.mediacodec.MediaCodecSelector -import com.google.android.exoplayer2.source.hls.HlsManifest +import androidx.annotation.OptIn +import androidx.media3.common.AudioAttributes +import androidx.media3.common.C +import androidx.media3.common.util.UnstableApi +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.exoplayer.RenderersFactory +import androidx.media3.exoplayer.audio.DefaultAudioSink +import androidx.media3.exoplayer.audio.MediaCodecAudioRenderer +import androidx.media3.exoplayer.hls.HlsManifest +import androidx.media3.exoplayer.mediacodec.MediaCodecSelector import com.scribd.armadillo.Milliseconds import com.scribd.armadillo.time.milliseconds @@ -19,12 +18,14 @@ import com.scribd.armadillo.time.milliseconds * Call this method before queries to player progress. * During setup, [ExoPlayer.getCurrentManifest] will be null. */ +@OptIn(UnstableApi::class) internal fun ExoPlayer.hasProgressAvailable(): Boolean { return (isPlayingHls() && (currentManifest as HlsManifest).mediaPlaylist.durationUs != C.TIME_UNSET) || (currentManifest == null && !currentTimeline.isEmpty) } +@OptIn(UnstableApi::class) internal fun ExoPlayer.isPlayingHls(): Boolean = currentManifest is HlsManifest /** @@ -42,22 +43,22 @@ internal fun ExoPlayer.playerDuration(): Milliseconds? = if (duration == C.TIME_ * * We provide our own renderers factory so that Proguard can remove any non-audio rendering code. */ +@OptIn(UnstableApi::class) internal fun createExoplayerInstance(context: Context, attributes: AudioAttributes): ExoPlayer = - SimpleExoPlayer.Builder(context, createRenderersFactory(context)) + ExoPlayer.Builder(context, createRenderersFactory(context)) .build().apply { setAudioAttributes(attributes, true) } +@OptIn(UnstableApi::class) internal fun createRenderersFactory(context: Context): RenderersFactory = RenderersFactory { eventHandler, _, audioRendererEventListener, _, _ -> // Default audio sink taken from DefaultRenderersFactory. We need to provide it in order to enable offloading // Note that we need to provide a new audio sink for each call - playback fails if we reuse the sink val audioSink = DefaultAudioSink.Builder(context) - .setAudioCapabilities(AudioCapabilities.getCapabilities(context)) - .setAudioProcessorChain(DefaultAudioProcessorChain()) + .setAudioProcessorChain(DefaultAudioSink.DefaultAudioProcessorChain()) .setEnableFloatOutput(false) .setEnableAudioTrackPlaybackParams(true) - .setOffloadMode(DefaultAudioSink.OFFLOAD_MODE_DISABLED) .build() arrayOf(MediaCodecAudioRenderer(context, MediaCodecSelector.DEFAULT, eventHandler, audioRendererEventListener, audioSink)) diff --git a/Armadillo/src/main/java/com/scribd/armadillo/playback/MediaSessionCallback.kt b/Armadillo/src/main/java/com/scribd/armadillo/playback/MediaSessionCallback.kt index 1da9a8b..f15a6d7 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/MediaSessionCallback.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/MediaSessionCallback.kt @@ -10,6 +10,7 @@ import android.support.v4.media.session.MediaSessionCompat import android.util.Log import android.view.KeyEvent import androidx.annotation.VisibleForTesting +import androidx.core.content.IntentCompat import com.scribd.armadillo.ArmadilloConfiguration import com.scribd.armadillo.Constants import com.scribd.armadillo.Constants.AUDIO_POSITION_SHIFT_IN_MS @@ -83,7 +84,7 @@ internal class MediaSessionCallback(private val onMediaSessionEventListener: OnM override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean { // double-tapping play/pause buttons to skip is default behaviour for API < Oreo if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { - val keyEvent = mediaButtonEvent.getParcelableExtra(Intent.EXTRA_KEY_EVENT)!! + val keyEvent = IntentCompat.getParcelableExtra(mediaButtonEvent, Intent.EXTRA_KEY_EVENT, KeyEvent::class.java)!! when (keyEvent.keyCode) { KeyEvent.KEYCODE_HEADSETHOOK, // used to hang up calls @@ -124,7 +125,7 @@ internal class MediaSessionCallback(private val onMediaSessionEventListener: OnM } override fun onPlayFromUri(uri: Uri, extras: Bundle) { - val newAudioPlayable = extras.getSerializable(Constants.Keys.KEY_AUDIO_PLAYABLE) as AudioPlayable + @Suppress("DEPRECATION") val newAudioPlayable = extras.getSerializable(Constants.Keys.KEY_AUDIO_PLAYABLE) as AudioPlayable val isAutoPlay = extras.getBoolean(Constants.Keys.KEY_IS_AUTO_PLAY, false) val maxDurationDiscrepancy = extras.getInt(Constants.Keys.KEY_MAX_DURATION_DISCREPANCY, ArmadilloConfiguration.MAX_DISCREPANCY_DEFAULT) @@ -138,7 +139,7 @@ internal class MediaSessionCallback(private val onMediaSessionEventListener: OnM onStop() } - @Suppress("UNCHECKED_CAST") val initialOffset = extras.getSerializable(Constants.Keys.KEY_INITIAL_OFFSET) as Milliseconds + @Suppress("UNCHECKED_CAST", "DEPRECATION") val initialOffset = extras.getSerializable(Constants.Keys.KEY_INITIAL_OFFSET) as Milliseconds playbackEngine = playbackEngineFactory.createPlaybackEngine(newAudioPlayable) playbackEngine?.beginPlayback(isAutoPlay, maxDurationDiscrepancy, initialOffset) playbackEngine?.seekTo(initialOffset) @@ -210,9 +211,6 @@ internal class MediaSessionCallback(private val onMediaSessionEventListener: OnM playbackEngine?.setPlaybackSpeed(customAction.playbackSpeed) Log.v(TAG, "SetPlaybackSpeed") } - is CustomAction.SetIsInForeground -> { - playbackEngine?.offloadAudio = !customAction.isInForeground - } is CustomAction.UpdatePlaybackMetadata -> { playbackEngine?.updateMetadata(customAction.title, customAction.chapters) } 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 5935668..8fe4dd0 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/PlaybackEngine.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/PlaybackEngine.kt @@ -1,11 +1,13 @@ package com.scribd.armadillo.playback import android.content.Context +import androidx.annotation.OptIn import androidx.annotation.VisibleForTesting -import com.google.android.exoplayer2.ExoPlayer -import com.google.android.exoplayer2.PlaybackParameters -import com.google.android.exoplayer2.Player -import com.google.android.exoplayer2.upstream.cache.Cache +import androidx.media3.common.PlaybackParameters +import androidx.media3.common.Player +import androidx.media3.common.util.UnstableApi +import androidx.media3.datasource.cache.Cache +import androidx.media3.exoplayer.ExoPlayer import com.scribd.armadillo.ArmadilloPlayerChoreographer import com.scribd.armadillo.Constants import com.scribd.armadillo.Milliseconds @@ -41,11 +43,6 @@ internal interface AudioPlaybackEngine { */ val currentChapterIndex: Int - /** - * Can the engine offload audio processing. Note that this may have no effect if the engine is not able to offload. - */ - var offloadAudio: Boolean - fun beginPlayback(isAutoPlay: Boolean, maxDurationDiscrepancy: Int, initialOffset: Milliseconds = 0.milliseconds) /** @@ -109,22 +106,13 @@ internal class ExoplayerPlaybackEngine(private var audioPlayable: AudioPlayable) get() = audioPlayable.getChapterAtOffset(exoPlayer.currentPositionInDuration()) ?: throw MissingDataException("currentChapter null") - override var offloadAudio: Boolean = false - set(value) { - // It is always valid to disable offloading. It is invalid to enable offloading if the playback speed is modified - if (exoPlayer.playbackParameters.speed == 1f || !value) { - field = value - exoPlayer.experimentalSetOffloadSchedulingEnabled(value) - } - } - - override fun beginPlayback(isAutoPlay: Boolean, maxDurationDiscrepancy: Int, initialOffset: Milliseconds) { + @OptIn(UnstableApi::class) override fun beginPlayback(isAutoPlay: Boolean, maxDurationDiscrepancy: Int, initialOffset: Milliseconds) { stateModifier.dispatch(NewAudioPlayableAction(audioPlayable, maxDurationDiscrepancy, initialOffset)) exoPlayer = createExoplayerInstance(context, audioAttributes.exoPlayerAttrs) val mediaSource = mediaSourceRetriever.generateMediaSource(audioPlayable.request, context) - exoPlayer.setMediaSource(mediaSource) + exoPlayer.setMediaSource(mediaSource, false) exoPlayer.prepare() exoPlayer.addListener(playerEventListener) @@ -212,10 +200,6 @@ internal class ExoplayerPlaybackEngine(private var audioPlayable: AudioPlayable) } override fun setPlaybackSpeed(playbackSpeed: Float) { - if (playbackSpeed != 1f) { - // Audio processing effects are disabled in offloading mode, including playback speed. Cannot offload if speed != 1 - exoPlayer.experimentalSetOffloadSchedulingEnabled(false) - } exoPlayer.playbackParameters = PlaybackParameters(playbackSpeed) stateModifier.dispatch(PlaybackSpeedAction(playbackSpeed)) } diff --git a/Armadillo/src/main/java/com/scribd/armadillo/playback/PlaybackService.kt b/Armadillo/src/main/java/com/scribd/armadillo/playback/PlaybackService.kt index aa3646f..f10b9e5 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/PlaybackService.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/PlaybackService.kt @@ -6,6 +6,7 @@ import android.support.v4.media.MediaBrowserCompat import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.PlaybackStateCompat import android.util.Log +import androidx.core.app.ServiceCompat import androidx.core.content.ContextCompat import androidx.media.MediaBrowserServiceCompat import androidx.media.session.MediaButtonReceiver @@ -82,10 +83,7 @@ class PlaybackService : MediaBrowserServiceCompat() { Log.v(TAG, "onCreate") playbackNotificationManager = PlaybackNotificationManager(this, notificationBuilder) mediaSession = MediaSessionCompat(this, TAG) - mediaSession.setFlags( - MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or - MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS or - MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS) + mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) PlaybackActionListenerHolder.stateListener = AudioPlaybackStateListener() @@ -242,14 +240,14 @@ class PlaybackService : MediaBrowserServiceCompat() { override fun updateNotificationForPause(audiobook: AudioPlayable, currentChapterIndex: Int) { val token = sessionToken ?: throw MissingDataException("token should not be null") - stopForeground(false) + ServiceCompat.stopForeground(this@PlaybackService, ServiceCompat.STOP_FOREGROUND_DETACH) val notification = playbackNotificationManager.getNotification(audiobook, currentChapterIndex, false, token) notificationDeleteReceiver.setDeleteIntentOnNotification(notification) playbackNotificationManager.notificationManager.notify(notificationBuilder.notificationId, notification) } override fun removeNotification() { - stopForeground(true) + ServiceCompat.stopForeground(this@PlaybackService, ServiceCompat.STOP_FOREGROUND_REMOVE) isNotificationShown = false } @@ -269,7 +267,7 @@ class PlaybackService : MediaBrowserServiceCompat() { if (stopPlayer) { mediaSession.controller.transportControls.stop() } - stopForeground(true) + ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE) stopSelf() isInForeground = false isNotificationShown = false diff --git a/Armadillo/src/main/java/com/scribd/armadillo/playback/PlayerEventListener.kt b/Armadillo/src/main/java/com/scribd/armadillo/playback/PlayerEventListener.kt index 5271099..f5f7ffb 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/PlayerEventListener.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/PlayerEventListener.kt @@ -1,10 +1,10 @@ package com.scribd.armadillo.playback import android.util.Log -import com.google.android.exoplayer2.ExoPlaybackException -import com.google.android.exoplayer2.ExoPlayer -import com.google.android.exoplayer2.PlaybackException -import com.google.android.exoplayer2.Player +import androidx.media3.common.PlaybackException +import androidx.media3.common.Player +import androidx.media3.common.util.UnstableApi +import androidx.media3.exoplayer.ExoPlaybackException import com.scribd.armadillo.StateStore import com.scribd.armadillo.actions.Action import com.scribd.armadillo.actions.ContentEndedAction @@ -22,7 +22,7 @@ import javax.inject.Inject * * It communicates changes by sending [Action]s with [StateStore.Modifier]. */ -internal class PlayerEventListener : Player.Listener { +@UnstableApi internal class PlayerEventListener : Player.Listener { init { Injector.mainComponent.inject(this) } 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 71b8c5c..04cd059 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 @@ -1,14 +1,16 @@ package com.scribd.armadillo.playback.mediasource import android.content.Context -import com.google.android.exoplayer2.MediaItem -import com.google.android.exoplayer2.offline.Download -import com.google.android.exoplayer2.offline.DownloadHelper -import com.google.android.exoplayer2.source.MediaSource -import com.google.android.exoplayer2.source.hls.HlsMediaSource -import com.google.android.exoplayer2.upstream.DataSource -import com.google.android.exoplayer2.upstream.DefaultDataSource -import com.google.android.exoplayer2.upstream.DefaultHttpDataSource +import androidx.annotation.OptIn +import androidx.media3.common.MediaItem +import androidx.media3.common.util.UnstableApi +import androidx.media3.datasource.DataSource +import androidx.media3.datasource.DefaultDataSource +import androidx.media3.datasource.DefaultHttpDataSource +import androidx.media3.exoplayer.hls.HlsMediaSource +import androidx.media3.exoplayer.offline.Download +import androidx.media3.exoplayer.offline.DownloadHelper +import androidx.media3.exoplayer.source.MediaSource import com.scribd.armadillo.Constants import com.scribd.armadillo.HeadersStore import com.scribd.armadillo.download.CacheManager @@ -21,6 +23,7 @@ import javax.inject.Inject * creates an HLS media source for [ExoPlayer] from a master playlist url * */ +@OptIn(UnstableApi::class) internal class HlsMediaSourceGenerator @Inject constructor( private val cacheManager: CacheManager, private val headersStore: HeadersStore, 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 6274a80..3c6a81e 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 @@ -1,7 +1,7 @@ package com.scribd.armadillo.playback.mediasource import android.content.Context -import com.google.android.exoplayer2.source.MediaSource +import androidx.media3.exoplayer.source.MediaSource import com.scribd.armadillo.models.AudioPlayable /** Creates a MediaSource for starting playback in Exoplayer when this 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 56b0d74..b73c6d2 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 @@ -1,9 +1,9 @@ package com.scribd.armadillo.playback.mediasource import android.content.Context -import com.google.android.exoplayer2.C -import com.google.android.exoplayer2.source.MediaSource -import com.google.android.exoplayer2.util.Util +import androidx.media3.common.C +import androidx.media3.common.util.Util +import androidx.media3.exoplayer.source.MediaSource import com.scribd.armadillo.di.Injector import com.scribd.armadillo.extensions.toUri import com.scribd.armadillo.models.AudioPlayable @@ -39,14 +39,12 @@ class MediaSourceRetrieverImpl @Inject constructor(): MediaSourceRetriever { buildMediaGenerator(request).updateMediaSourceHeaders(request) } - private fun buildMediaGenerator(request: AudioPlayable.MediaRequest): MediaSourceGenerator = buildMediaGenerator(request, null) - - private fun buildMediaGenerator(request: AudioPlayable.MediaRequest, overrideExtension: String?): MediaSourceGenerator { + private fun buildMediaGenerator(request: AudioPlayable.MediaRequest): MediaSourceGenerator { val uri = request.url.toUri() - return when (@C.ContentType val type = Util.inferContentType(uri, overrideExtension)) { - C.TYPE_HLS -> hlsGenerator - C.TYPE_OTHER -> progressiveMediaSourceGenerator + return when (@C.ContentType val type = Util.inferContentType(uri)) { + C.CONTENT_TYPE_HLS -> hlsGenerator + C.CONTENT_TYPE_OTHER -> progressiveMediaSourceGenerator else -> throw IllegalStateException("Unsupported type: $type") } } 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 1cfa594..43cbef3 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 @@ -1,17 +1,20 @@ package com.scribd.armadillo.playback.mediasource import android.content.Context -import com.google.android.exoplayer2.MediaItem -import com.google.android.exoplayer2.source.MediaSource -import com.google.android.exoplayer2.source.ProgressiveMediaSource -import com.google.android.exoplayer2.upstream.DataSource -import com.google.android.exoplayer2.upstream.DefaultDataSource -import com.google.android.exoplayer2.upstream.DefaultHttpDataSource +import androidx.annotation.OptIn +import androidx.media3.common.MediaItem +import androidx.media3.common.util.UnstableApi +import androidx.media3.datasource.DataSource +import androidx.media3.datasource.DefaultDataSource +import androidx.media3.datasource.DefaultHttpDataSource +import androidx.media3.exoplayer.source.MediaSource +import androidx.media3.exoplayer.source.ProgressiveMediaSource import com.scribd.armadillo.Constants import com.scribd.armadillo.download.CacheManager import com.scribd.armadillo.models.AudioPlayable import javax.inject.Inject +@OptIn(UnstableApi::class) internal class ProgressiveMediaSourceGenerator @Inject constructor( private val cacheManager: CacheManager) : MediaSourceGenerator { diff --git a/Armadillo/src/test/java/com/scribd/armadillo/ArmadilloPlayerChoreographerTest.kt b/Armadillo/src/test/java/com/scribd/armadillo/ArmadilloPlayerChoreographerTest.kt index 4757353..2c39170 100644 --- a/Armadillo/src/test/java/com/scribd/armadillo/ArmadilloPlayerChoreographerTest.kt +++ b/Armadillo/src/test/java/com/scribd/armadillo/ArmadilloPlayerChoreographerTest.kt @@ -93,6 +93,7 @@ class ArmadilloPlayerChoreographerTest { verify(choreographer.stateModifier).dispatch(MediaRequestUpdateAction(newRequest)) verify(transportControls).sendCustomAction(eq("update_media_request"), argWhere { + @Suppress("DEPRECATION") it.getSerializable("media_request") == newRequest }) } diff --git a/Armadillo/src/test/java/com/scribd/armadillo/ExoplayerPlaybackEngineTest.kt b/Armadillo/src/test/java/com/scribd/armadillo/ExoplayerPlaybackEngineTest.kt index b5e56d5..7079791 100644 --- a/Armadillo/src/test/java/com/scribd/armadillo/ExoplayerPlaybackEngineTest.kt +++ b/Armadillo/src/test/java/com/scribd/armadillo/ExoplayerPlaybackEngineTest.kt @@ -1,21 +1,18 @@ package com.scribd.armadillo -import com.google.android.exoplayer2.C -import com.google.android.exoplayer2.ExoPlayer -import com.google.android.exoplayer2.PlaybackParameters -import com.google.android.exoplayer2.Player -import com.google.android.exoplayer2.Timeline +import androidx.media3.common.C +import androidx.media3.common.PlaybackParameters +import androidx.media3.common.Player +import androidx.media3.common.Timeline +import androidx.media3.exoplayer.ExoPlayer import com.scribd.armadillo.actions.PlaybackProgressAction import com.scribd.armadillo.models.AudioPlayable import com.scribd.armadillo.playback.ExoplayerPlaybackEngine import com.scribd.armadillo.time.milliseconds -import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mockito.kotlin.any import org.mockito.kotlin.mock -import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.verifyZeroInteractions import org.mockito.kotlin.whenever @@ -151,42 +148,13 @@ class ExoplayerPlaybackEngineTest { playbackEngine.setPlaybackSpeed(1f) verify(exoplayer).setPlaybackParameters(PlaybackParameters(1f)) - verify(exoplayer, never()).experimentalSetOffloadSchedulingEnabled(any()) } @Test fun setPlaybackSpeed_notOne_setsSpeedAndDisablesOffloading() { playbackEngine.setPlaybackSpeed(2f) - verify(exoplayer).setPlaybackParameters(PlaybackParameters(2f)) - verify(exoplayer).experimentalSetOffloadSchedulingEnabled(false) - } - - @Test - fun setOffloading_playbackSpeedOne_setsOffloading() { - whenever(exoplayer.playbackParameters).thenReturn(PlaybackParameters(1f)) - playbackEngine.offloadAudio = true - - verify(exoplayer).experimentalSetOffloadSchedulingEnabled(true) - assertThat(playbackEngine.offloadAudio).isTrue() - } - - @Test - fun setOffloading_playbackSpeedNotOne_doesNotSetOffloading() { - whenever(exoplayer.playbackParameters).thenReturn(PlaybackParameters(2f)) - playbackEngine.offloadAudio = true - - verify(exoplayer, never()).experimentalSetOffloadSchedulingEnabled(any()) - assertThat(playbackEngine.offloadAudio).isFalse() - } - - @Test - fun setOffloading_playbackSpeedNotOneDisabling_setsOffloading() { - whenever(exoplayer.playbackParameters).thenReturn(PlaybackParameters(2f)) - playbackEngine.offloadAudio = false - - verify(exoplayer).experimentalSetOffloadSchedulingEnabled(false) - assertThat(playbackEngine.offloadAudio).isFalse() + verify(exoplayer).playbackParameters = PlaybackParameters(2f) } @Test diff --git a/Armadillo/src/test/java/com/scribd/armadillo/download/ExoplayerDownloadTrackerTest.kt b/Armadillo/src/test/java/com/scribd/armadillo/download/ExoplayerDownloadTrackerTest.kt index 33d1af5..e47c2ac 100644 --- a/Armadillo/src/test/java/com/scribd/armadillo/download/ExoplayerDownloadTrackerTest.kt +++ b/Armadillo/src/test/java/com/scribd/armadillo/download/ExoplayerDownloadTrackerTest.kt @@ -1,7 +1,7 @@ package com.scribd.armadillo.download -import com.google.android.exoplayer2.offline.DownloadIndex -import com.google.android.exoplayer2.offline.DownloadManager +import androidx.media3.exoplayer.offline.DownloadIndex +import androidx.media3.exoplayer.offline.DownloadManager import com.scribd.armadillo.StateStore import com.scribd.armadillo.actions.ErrorAction import com.scribd.armadillo.actions.StopTrackingDownloadAction diff --git a/Armadillo/src/test/java/com/scribd/armadillo/download/MaxAgeCacheEvictorTest.kt b/Armadillo/src/test/java/com/scribd/armadillo/download/MaxAgeCacheEvictorTest.kt index f6534ea..1cbfc7b 100644 --- a/Armadillo/src/test/java/com/scribd/armadillo/download/MaxAgeCacheEvictorTest.kt +++ b/Armadillo/src/test/java/com/scribd/armadillo/download/MaxAgeCacheEvictorTest.kt @@ -1,7 +1,7 @@ package com.scribd.armadillo.download -import com.google.android.exoplayer2.upstream.cache.Cache -import com.google.android.exoplayer2.upstream.cache.CacheSpan +import androidx.media3.datasource.cache.Cache +import androidx.media3.datasource.cache.CacheSpan import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test diff --git a/Armadillo/src/test/java/com/scribd/armadillo/models/ModelsTest.kt b/Armadillo/src/test/java/com/scribd/armadillo/models/ModelsTest.kt index db5f45a..e23d59f 100644 --- a/Armadillo/src/test/java/com/scribd/armadillo/models/ModelsTest.kt +++ b/Armadillo/src/test/java/com/scribd/armadillo/models/ModelsTest.kt @@ -3,16 +3,9 @@ package com.scribd.armadillo.models import com.scribd.armadillo.MockModels import com.scribd.armadillo.time.milliseconds import org.assertj.core.api.Assertions.assertThat -import org.junit.Rule import org.junit.Test -import org.junit.rules.ExpectedException class ModelsTest { - - @Rule - @JvmField - val exception: ExpectedException = ExpectedException.none() - @Test fun findChapterAtOffset_bookHasJustBegun_returnsChapter() { val audiobook = MockModels.audiobook() diff --git a/Armadillo/src/test/java/com/scribd/armadillo/playback/MediaSessionCallbackTest.kt b/Armadillo/src/test/java/com/scribd/armadillo/playback/MediaSessionCallbackTest.kt index 29636e8..28845a9 100644 --- a/Armadillo/src/test/java/com/scribd/armadillo/playback/MediaSessionCallbackTest.kt +++ b/Armadillo/src/test/java/com/scribd/armadillo/playback/MediaSessionCallbackTest.kt @@ -210,16 +210,6 @@ class MediaSessionCallbackTest { verify(playbackEngine).setPlaybackSpeed(playbackSpeed) } - @Test - fun onIsInForegroundAction_updatesEngineOffloading() { - val isInForeground = true - mediaSessionCallback.onCustomAction(Constants.Actions.SET_IS_IN_FOREGROUND, Bundle().apply { - putBoolean(Constants.Actions.Extras.IS_IN_FOREGROUND, isInForeground) - }) - - verify(playbackEngine).offloadAudio = !isInForeground - } - @Test fun onMetadataUpdateAction_updatesMetadata() { val newTitle = "New Title" diff --git a/TestApp/build.gradle b/TestApp/build.gradle index a69a438..1341fc7 100644 --- a/TestApp/build.gradle +++ b/TestApp/build.gradle @@ -7,7 +7,7 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' android { - compileSdkVersion 33 + compileSdkVersion 34 compileOptions { targetCompatibility JavaVersion.VERSION_1_8 diff --git a/TestApp/src/main/java/com/scribd/armadillotestapp/presentation/AudioPlayerActivity.kt b/TestApp/src/main/java/com/scribd/armadillotestapp/presentation/AudioPlayerActivity.kt index c876b04..d9e4575 100644 --- a/TestApp/src/main/java/com/scribd/armadillotestapp/presentation/AudioPlayerActivity.kt +++ b/TestApp/src/main/java/com/scribd/armadillotestapp/presentation/AudioPlayerActivity.kt @@ -48,6 +48,7 @@ class AudioPlayerActivity : AppCompatActivity() { stopPlayback = findViewById(R.id.stopPlaybackBtn) clearCacheButton = findViewById(R.id.clearPlaybackCacheBtn) removeAllDownloadsButton = findViewById(R.id.removeAllDownloadsBtn) + @Suppress("DEPRECATION") audiobook = intent.getSerializableExtra(MainActivity.AUDIOBOOK_EXTRA) as AudioPlayable val previousTrackBtn: View = findViewById(R.id.previousTrack) @@ -75,18 +76,6 @@ class AudioPlayerActivity : AppCompatActivity() { initArmadilloAndBeginPlayback() } - override fun onStart() { - super.onStart() - - armadilloPlayer.isInForeground = true - } - - override fun onStop() { - super.onStop() - - armadilloPlayer.isInForeground = false - } - override fun onDestroy() { super.onDestroy() disposables.clear() diff --git a/build.gradle b/build.gradle index 0f86ac0..0034002 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { ext.githubPassword = "$System.env.GITHUB_PASSWORD" ext.githubRepo = "$System.env.GITHUB_REPOSITORY" - ext.kotlin_version = '1.6.0' + ext.kotlin_version = '1.8.0' apply from: "$project.rootDir/gradle/shared.gradle" diff --git a/gradle.properties b/gradle.properties index 3ba2beb..faa94e4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,7 @@ org.gradle.jvmargs=-Xmx1536m # org.gradle.parallel=true PACKAGE_NAME=com.scribd.armadillo LIBRARY_VERSION=1.1.1 -EXOPLAYER_VERSION=2.19.1 +EXOPLAYER_VERSION=1.2.0 RXJAVA_VERSION=2.2.4 RXANDROID_VERSION=2.0.1 DAGGER_VERSION=2.16