Skip to content

Commit

Permalink
[APT-9563] Update to Media3 Exoplayer
Browse files Browse the repository at this point in the history
Exoplayer is now contained in Jetpack, and is maintained as part of the media3 library.

* Updates library to 1.2.0 (equivalent to 2.19.1)
* Updates kotlin runtime to 1.8.0
* Uses new jetpack compat methods
* Removes ability to offload audio. This only worked if the playback speed was 1, and now changing at runtime is fully removed from exoplayer
* Remove foreground checking. It was only used to support offloading.
  • Loading branch information
kschults committed Nov 27, 2023
1 parent 3d451c9 commit 91d2e7c
Show file tree
Hide file tree
Showing 39 changed files with 203 additions and 262 deletions.
9 changes: 6 additions & 3 deletions Armadillo/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 4 additions & 5 deletions Armadillo/src/main/java/com/scribd/armadillo/Constants.kt
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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"
Expand Down
14 changes: 9 additions & 5 deletions Armadillo/src/main/java/com/scribd/armadillo/Util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<Millisecond>

fun exoplayerExternalDirectory(context: Context): File =
Expand All @@ -23,12 +26,12 @@ fun sanitizeChapters(chapters: List<Chapter>): List<Chapter> {
}
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(
Expand All @@ -38,12 +41,13 @@ fun sanitizeChapters(chapters: List<Chapter>): List<Chapter> {
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
19 changes: 11 additions & 8 deletions Armadillo/src/main/java/com/scribd/armadillo/di/DownloadModule.kt
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
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
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
Expand All @@ -33,6 +35,7 @@ import javax.inject.Qualifier
import javax.inject.Singleton

@Module
@OptIn(UnstableApi::class)
internal class DownloadModule {
@Singleton
@Provides
Expand Down
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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)
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<out DownloadService>,
Expand Down Expand Up @@ -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")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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 {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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,
Expand Down
Loading

0 comments on commit 91d2e7c

Please sign in to comment.