diff --git a/Armadillo/src/main/java/com/scribd/armadillo/ArmadilloConfiguration.kt b/Armadillo/src/main/java/com/scribd/armadillo/ArmadilloConfiguration.kt index 2eefb76..03bbaba 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/ArmadilloConfiguration.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/ArmadilloConfiguration.kt @@ -1,7 +1,14 @@ package com.scribd.armadillo +import com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS +import com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS +import com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_MIN_BUFFER_MS +import com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_MAX_BUFFER_MS +import com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS +import com.google.android.exoplayer2.DefaultLoadControl.DEFAULT_TARGET_BUFFER_BYTES import com.scribd.armadillo.models.AudioPlayable import com.scribd.armadillo.time.milliseconds +import java.io.Serializable /** * Used to specify various settings when starting playback on a new [AudioPlayable] @@ -11,10 +18,27 @@ import com.scribd.armadillo.time.milliseconds * @property maxDurationDiscrepancy Armadillo will output errors if the metadata for the audio duration doesn't match the * actual duration of playback. This value can be used to set the allowed maximum difference in seconds between stated vs. actual duration. * Can also be set to a negative value to ignore any discrepancies. + * @property minBufferMs The minumum amount of audio attempted to be buffered at all times in milliseconds. + * @property maxBufferMs The maximum amount of audio attempted to be buffered at all times in milliseconds. + * @property bufferForPlaybackMs The duration of media that must be buffered for playback to start or + * resume following a user action such as a seek, in milliseconds. + * @property bufferForPlaybackAfterRebufferMs The duration of media that must be buffered for playback + * to resume after a rebuffer, in milliseconds. A rebuffer is defined to be caused by buffer depletion + * rather than a user action. + * @property targetBufferSize The desired size of the media buffer in bytes. An unset buffer size will + * will be calculated based on the selected tracks. + * @property prioritizeTimeOverSizeThresholds Whether the load control prioritizes buffer time constraints + * over buffer size constraints. */ data class ArmadilloConfiguration(val initialOffset: Milliseconds = 0.milliseconds, val isAutoPlay: Boolean = true, - val maxDurationDiscrepancy: Int = MAX_DISCREPANCY_DEFAULT) { + val maxDurationDiscrepancy: Int = MAX_DISCREPANCY_DEFAULT, + val minBufferMs: Int = DEFAULT_MIN_BUFFER_MS, + val maxBufferMs: Int = DEFAULT_MAX_BUFFER_MS, + val bufferForPlaybackMs: Int = DEFAULT_BUFFER_FOR_PLAYBACK_MS, + val bufferForPlaybackAfterRebufferMs: Int = DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS, + val targetBufferSize: Int = DEFAULT_TARGET_BUFFER_BYTES, + val prioritizeTimeOverSizeThresholds: Boolean = DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS): Serializable { companion object { // Default duration discrepancy values in seconds diff --git a/Armadillo/src/main/java/com/scribd/armadillo/ArmadilloPlayerChoreographer.kt b/Armadillo/src/main/java/com/scribd/armadillo/ArmadilloPlayerChoreographer.kt index 0e072e8..45d6a94 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/ArmadilloPlayerChoreographer.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/ArmadilloPlayerChoreographer.kt @@ -268,10 +268,8 @@ internal class ArmadilloPlayerChoreographer : ArmadilloPlayer { mediaSessionConnection.connectToMediaSession(object : MediaSessionConnection.Listener { override fun onConnectionCallback(transportControls: MediaControllerCompat.TransportControls) { val bundle = Bundle() + bundle.putSerializable(Constants.Keys.KEY_ARMADILLO_CONFIG, config) bundle.putSerializable(Constants.Keys.KEY_AUDIO_PLAYABLE, audioPlayable) - bundle.putSerializable(Constants.Keys.KEY_INITIAL_OFFSET, config.initialOffset) - bundle.putBoolean(Constants.Keys.KEY_IS_AUTO_PLAY, config.isAutoPlay) - bundle.putInt(Constants.Keys.KEY_MAX_DURATION_DISCREPANCY, config.maxDurationDiscrepancy) transportControls.playFromUri(audioPlayable.request.url.toUri(), bundle) updateProgressPollTask() } diff --git a/Armadillo/src/main/java/com/scribd/armadillo/Constants.kt b/Armadillo/src/main/java/com/scribd/armadillo/Constants.kt index 496c195..12f47c1 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/Constants.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/Constants.kt @@ -28,12 +28,8 @@ object Constants { private val APP_NAME = R.string.arm_app_name internal object Keys { + const val KEY_ARMADILLO_CONFIG = "armadillo_config" const val KEY_AUDIO_PLAYABLE = "audio_playable" - const val KEY_IS_AUTO_PLAY = "is_auto_play" - const val KEY_MAX_DURATION_DISCREPANCY = "max_duration_discrepancy" - const val KEY_PLAYBACK_READY = "playback_ready" - const val KEY_INITIAL_OFFSET = "initial_offset" - const val KEY_ERROR = "error" } internal object DI { 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 94f29bc..befd65b 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/ExoplayerExt.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/ExoplayerExt.kt @@ -3,6 +3,7 @@ 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.LoadControl import com.google.android.exoplayer2.RenderersFactory import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.audio.AudioCapabilities @@ -41,8 +42,9 @@ 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. */ -internal fun createExoplayerInstance(context: Context, attributes: AudioAttributes): ExoPlayer = +internal fun createExoplayerInstance(context: Context, attributes: AudioAttributes, loadControl: LoadControl): ExoPlayer = ExoPlayer.Builder(context, createRenderersFactory(context)) + .setLoadControl(loadControl) .build().apply { setAudioAttributes(attributes, true) } 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..0fdd9f0 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/MediaSessionCallback.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/MediaSessionCallback.kt @@ -13,7 +13,6 @@ import androidx.annotation.VisibleForTesting import com.scribd.armadillo.ArmadilloConfiguration import com.scribd.armadillo.Constants import com.scribd.armadillo.Constants.AUDIO_POSITION_SHIFT_IN_MS -import com.scribd.armadillo.Milliseconds import com.scribd.armadillo.StateStore import com.scribd.armadillo.actions.CustomMediaSessionAction import com.scribd.armadillo.actions.UpdateProgressAction @@ -125,9 +124,6 @@ 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 - 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) if (newAudioPlayable == stateProvider.currentState.playbackInfo?.audioPlayable && isPlaying) { Log.v(TAG, "onPlayFromUri: already playing audioPlayable: ${newAudioPlayable.id} - Skipping setup") @@ -138,10 +134,11 @@ internal class MediaSessionCallback(private val onMediaSessionEventListener: OnM onStop() } - @Suppress("UNCHECKED_CAST") val initialOffset = extras.getSerializable(Constants.Keys.KEY_INITIAL_OFFSET) as Milliseconds + val config = extras.getSerializable(Constants.Keys.KEY_ARMADILLO_CONFIG) as ArmadilloConfiguration + Log.v(TAG, "ArmadilloConfiguration: $config") playbackEngine = playbackEngineFactory.createPlaybackEngine(newAudioPlayable) - playbackEngine?.beginPlayback(isAutoPlay, maxDurationDiscrepancy, initialOffset) - playbackEngine?.seekTo(initialOffset) + playbackEngine?.beginPlayback(config) + playbackEngine?.seekTo(config.initialOffset) isPlaying = true 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 5d9c341..9bd94b9 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/PlaybackEngine.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/PlaybackEngine.kt @@ -2,10 +2,12 @@ package com.scribd.armadillo.playback import android.content.Context import androidx.annotation.VisibleForTesting +import com.google.android.exoplayer2.DefaultLoadControl 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 com.scribd.armadillo.ArmadilloConfiguration import com.scribd.armadillo.ArmadilloPlayerChoreographer import com.scribd.armadillo.Constants import com.scribd.armadillo.Milliseconds @@ -46,7 +48,7 @@ internal interface AudioPlaybackEngine { */ var offloadAudio: Boolean - fun beginPlayback(isAutoPlay: Boolean, maxDurationDiscrepancy: Int, initialOffset: Milliseconds = 0.milliseconds) + fun beginPlayback(config: ArmadilloConfiguration) /** * Updates the request for the currently playing media. This could be to change the request headers. @@ -118,10 +120,22 @@ internal class ExoplayerPlaybackEngine(private var audioPlayable: AudioPlayable) } } - override fun beginPlayback(isAutoPlay: Boolean, maxDurationDiscrepancy: Int, initialOffset: Milliseconds) { - stateModifier.dispatch(NewAudioPlayableAction(audioPlayable, maxDurationDiscrepancy, initialOffset)) + override fun beginPlayback(config: ArmadilloConfiguration) { + stateModifier.dispatch(NewAudioPlayableAction( + audioPlayable = audioPlayable, + maxDurationDiscrepancy = config.maxDurationDiscrepancy, + initialOffset = config.initialOffset)) - exoPlayer = createExoplayerInstance(context, audioAttributes.exoPlayerAttrs) + val loadControl = DefaultLoadControl.Builder() + .setBufferDurationsMs(config.minBufferMs, + config.maxBufferMs, + config.bufferForPlaybackMs, + config.bufferForPlaybackAfterRebufferMs) + .setTargetBufferBytes(config.targetBufferSize) + .setPrioritizeTimeOverSizeThresholds(config.prioritizeTimeOverSizeThresholds) + .build() + + exoPlayer = createExoplayerInstance(context, audioAttributes.exoPlayerAttrs, loadControl) val mediaSource = mediaSourceRetriever.generateMediaSource(audioPlayable.request, context) exoPlayer.setMediaSource(mediaSource) @@ -129,7 +143,7 @@ internal class ExoplayerPlaybackEngine(private var audioPlayable: AudioPlayable) exoPlayer.addListener(playerEventListener) - exoPlayer.playWhenReady = isAutoPlay + exoPlayer.playWhenReady = config.isAutoPlay stateModifier.dispatch(PlaybackEngineReady(true)) stateModifier.dispatch(PlayerStateAction(PlaybackState.PAUSED)) @@ -243,7 +257,7 @@ internal class ExoplayerPlaybackEngine(private var audioPlayable: AudioPlayable) } private fun seekToExo(position: Milliseconds) { - stateModifier.dispatch(PlaybackProgressAction(position, exoPlayer.playerDuration())) exoPlayer.seekTo(exoPlayer.currentMediaItemIndex, position.longValue) + stateModifier.dispatch(PlaybackProgressAction(position, exoPlayer.playerDuration())) } } \ No newline at end of file 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..f85ab01 100644 --- a/Armadillo/src/test/java/com/scribd/armadillo/playback/MediaSessionCallbackTest.kt +++ b/Armadillo/src/test/java/com/scribd/armadillo/playback/MediaSessionCallbackTest.kt @@ -138,7 +138,7 @@ class MediaSessionCallbackTest { mediaSessionCallback.onPlayFromUri(URL.toUri(), bundleTwo) verify(mediaSessionCallback.playbackEngine!!, times(1)).deinit() verify(mediaSessionCallback.playbackEngine!!, times(1)) - .beginPlayback(false, ArmadilloConfiguration.MAX_DISCREPANCY_DEFAULT, 0.milliseconds) + .beginPlayback(ArmadilloConfiguration()) } @Test @@ -147,7 +147,7 @@ class MediaSessionCallbackTest { whenever(playbackInfo.audioPlayable).thenReturn(audiobookTwo) mediaSessionCallback.onPlayFromUri(URL.toUri(), bundleOne) verify(mediaSessionCallback.playbackEngine!!, times(1)) - .beginPlayback(false, ArmadilloConfiguration.MAX_DISCREPANCY_DEFAULT, 0.milliseconds) + .beginPlayback(ArmadilloConfiguration()) } @Test @@ -321,6 +321,6 @@ class MediaSessionCallbackTest { private fun Bundle.addAudiobook(audiobook: AudioPlayable) { putSerializable(Constants.Keys.KEY_AUDIO_PLAYABLE, audiobook) - putSerializable(Constants.Keys.KEY_INITIAL_OFFSET, 0.milliseconds) + putSerializable(Constants.Keys.KEY_ARMADILLO_CONFIG, ArmadilloConfiguration()) } } diff --git a/RELEASE.md b/RELEASE.md index ffd0436..d17e4a0 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,5 +1,9 @@ # Project Armadillo Release Notes +## 1.3.3 +- Reverted fix in 1.3.2 as it may have affected listening progress not being correctly reported +- Added support for passing in load control parameters via ArmadilloConfiguration to the exo player instance + ## 1.3.2 - Added a fix for order of dispatched actions during seek related events diff --git a/gradle.properties b/gradle.properties index 1fb9cb9..0813728 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ org.gradle.jvmargs=-Xmx1536m # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true PACKAGE_NAME=com.scribd.armadillo -LIBRARY_VERSION=1.3.2 +LIBRARY_VERSION=1.3.3 EXOPLAYER_VERSION=2.17.1 RXJAVA_VERSION=2.2.4 RXANDROID_VERSION=2.0.1