Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[APT-10467] Retry with Exponential Backoff #49

Merged
merged 1 commit into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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 > 6) {
C.TIME_UNSET //stop retrying after around 10 minutes
} else {
//exponential backoff based on a 10 second interval
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you put in what the max backoff will be? My reading is that will be up to 40 minutes, but it should be written down.

(1 shl (loadErrorInfo.errorCount - 1)) * 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 > 3) {
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)) * 1000L
}
} else {
super.getRetryDelayMsFor(loadErrorInfo)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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)
}
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading