diff --git a/Armadillo/src/main/java/com/scribd/armadillo/download/drm/ArmadilloDrmSessionManagerProvider.kt b/Armadillo/src/main/java/com/scribd/armadillo/download/drm/ArmadilloDrmSessionManagerProvider.kt index 1098150..9b4061b 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/download/drm/ArmadilloDrmSessionManagerProvider.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/download/drm/ArmadilloDrmSessionManagerProvider.kt @@ -20,6 +20,7 @@ import com.scribd.armadillo.actions.LicenseDrmErrorAction import com.scribd.armadillo.actions.LicenseExpirationDetermined import com.scribd.armadillo.actions.LicenseExpiredAction import com.scribd.armadillo.actions.LicenseKeyIsUsableAction +import com.scribd.armadillo.playback.error.ArmadilloHttpErrorHandlingPolicy import com.scribd.armadillo.time.milliseconds import java.util.UUID import javax.inject.Inject @@ -67,7 +68,7 @@ internal class ArmadilloDrmSessionManagerProvider @Inject constructor(private va } } - /** Near identical to DefaultDrmSessionManagerProvider method, except for the indicated line */ + /** Near identical to DefaultDrmSessionManagerProvider method, except for the indicated lines */ private fun createManager(drmConfiguration: DrmConfiguration): DrmSessionManager { val dataSourceFactory = this.drmHttpDataSourceFactory ?: DefaultHttpDataSource.Factory().setUserAgent(this.userAgent) @@ -84,6 +85,8 @@ internal class ArmadilloDrmSessionManagerProvider @Inject constructor(private va .setMultiSession(drmConfiguration.multiSession) .setPlayClearSamplesWithoutKeys(drmConfiguration.playClearContentWithoutKey) .setUseDrmSessionsForClearContent(*Ints.toArray(drmConfiguration.forcedSessionTrackTypes)) + //this line is also different, adding custom error handling + .setLoadErrorHandlingPolicy(ArmadilloHttpErrorHandlingPolicy()) .build(httpDrmCallback) drmSessionManager.setMode(DefaultDrmSessionManager.MODE_PLAYBACK, drmConfiguration.keySetId) return drmSessionManager diff --git a/Armadillo/src/main/java/com/scribd/armadillo/playback/error/ArmadilloHttpErrorHandlingPolicy.kt b/Armadillo/src/main/java/com/scribd/armadillo/playback/error/ArmadilloHttpErrorHandlingPolicy.kt new file mode 100644 index 0000000..2eb4282 --- /dev/null +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/error/ArmadilloHttpErrorHandlingPolicy.kt @@ -0,0 +1,36 @@ +package com.scribd.armadillo.playback.error + +import com.google.android.exoplayer2.C +import com.google.android.exoplayer2.ParserException +import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy +import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy +import java.net.SocketTimeoutException +import java.net.UnknownHostException + +class ArmadilloHttpErrorHandlingPolicy : DefaultLoadErrorHandlingPolicy(DEFAULT_MIN_LOADABLE_RETRY_COUNT) { + override fun getRetryDelayMsFor(loadErrorInfo: LoadErrorHandlingPolicy.LoadErrorInfo): Long { + return if (loadErrorInfo.exception is UnknownHostException || loadErrorInfo.exception is SocketTimeoutException) { + //retry every 10 seconds for potential loss of internet -keep buffering - internet may later succeed. + if (loadErrorInfo.errorCount > 8) { + C.TIME_UNSET //stop retrying after a few minutes + } else { + //exponential backoff based on a 10 second interval + (1 shl (loadErrorInfo.errorCount - 1).coerceAtMost(8)) * 10 * 1000L + } + } else if (loadErrorInfo.exception is ParserException) { + /* + Exoplayer by default assumes ParserExceptions only occur because source content is malformed, + so Exoplayer will never retry ParserExceptions. + We care about content failing to checksum correctly over the internet, so we wish to retry these. + */ + if (loadErrorInfo.errorCount > 4) { + C.TIME_UNSET //stop retrying, the content is likely malformed + } else { + //This is exponential backoff based on a 1 second interval + (1 shl (loadErrorInfo.errorCount - 1).coerceAtMost(4)) * 1000L + } + } else { + super.getRetryDelayMsFor(loadErrorInfo) + } + } +} \ No newline at end of file diff --git a/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/DashMediaSourceGenerator.kt b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/DashMediaSourceGenerator.kt index c3a3e28..3265258 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/DashMediaSourceGenerator.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/DashMediaSourceGenerator.kt @@ -14,6 +14,7 @@ import com.scribd.armadillo.download.DownloadTracker import com.scribd.armadillo.download.drm.events.WidevineSessionEventListener import com.scribd.armadillo.models.AudioPlayable import com.scribd.armadillo.models.DrmType +import com.scribd.armadillo.playback.error.ArmadilloHttpErrorHandlingPolicy import javax.inject.Inject /** For playback, both streaming and downloaded */ @@ -55,6 +56,7 @@ internal class DashMediaSourceGenerator @Inject constructor( DownloadHelper.createMediaSource(download!!.request, dataSourceFactory, drmManager) } else { var factory = DashMediaSource.Factory(dataSourceFactory) + .setLoadErrorHandlingPolicy(ArmadilloHttpErrorHandlingPolicy()) if (request.drmInfo != null) { factory = factory.setDrmSessionManagerProvider(drmSessionManagerProvider) } diff --git a/README.md b/README.md index 6ecfd6e..8e310ae 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,10 @@ Welcome to Project Armadillo, a delightful audio player for Android. ## What is Armadillo? -Armadillo is a fully featured audio player. Armadillo leverages [Google's Exoplayer](https://github.com/google/ExoPlayer/) library for its audio engine. Exoplayer wraps a variety of low level audio and video apis but has few opinions of its own for actually using audio in an Android app. The leap required from Exoplayer to audio player is enormous both in terms of the amount of code needed as well as the amount of domain knowledge required about complex audio related subjects. Armadillo provides a turn key solution for powering an audio player and providing the information to update a UI. +Armadillo is a fully featured audio player. Armadillo leverages [Google's Exoplayer](https://github.com/google/ExoPlayer/) library for +its audio engine. Exoplayer wraps a variety of low level audio and video apis but has few opinions of its own for actually using +internet based audio in an Android app. The leap required from Exoplayer to audio player is enormous both in terms of the amount of code +needed as well as the amount of domain knowledge required about complex audio related subjects. Armadillo provides a turn key solution for powering an audio player and providing the information to update a UI. - **Easy-to-use** because it outputs state updates with everything needed for a UI or analytics. Works in the background state. - **Effective** because it uses [Google's Exoplayer](https://github.com/google/ExoPlayer/) as the playback engine. diff --git a/RELEASE.md b/RELEASE.md index 2e73592..c8040fd 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,5 +1,9 @@ # Project Armadillo Release Notes +## 1.6.6 +- Adds graceful exponential backoff toward internet connectivity errors. +- Retries streaming parsing exceptions, in case the network has not succeeded in retreiving valid data. + ## 1.6.5 - ArmadilloPlayer handles client calls from any thread appropriately, without blocking. For those recently updating since 1.5.1, this should resolve any strange bugs from client behavior. diff --git a/gradle.properties b/gradle.properties index a95d5d6..e6c8d26 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,7 @@ org.gradle.jvmargs=-Xmx1536m # org.gradle.parallel=true PACKAGE_NAME=com.scribd.armadillo GRADLE_PLUGIN_VERSION=7.2.0 -LIBRARY_VERSION=1.6.5 +LIBRARY_VERSION=1.6.6 EXOPLAYER_VERSION=2.19.1 RXJAVA_VERSION=2.2.4 RXANDROID_VERSION=2.0.1