diff --git a/Armadillo/build.gradle b/Armadillo/build.gradle index f560e51..94a98b8 100644 --- a/Armadillo/build.gradle +++ b/Armadillo/build.gradle @@ -51,6 +51,7 @@ dependencies { implementation "com.google.android.exoplayer:exoplayer-core:${EXOPLAYER_VERSION}" implementation "com.google.android.exoplayer:exoplayer-hls:${EXOPLAYER_VERSION}" + implementation "com.google.android.exoplayer:exoplayer-dash:${EXOPLAYER_VERSION}" implementation "com.google.android.exoplayer:extension-mediasession:${EXOPLAYER_VERSION}" implementation "io.reactivex.rxjava2:rxjava:${RXJAVA_VERSION}" diff --git a/Armadillo/src/main/java/com/scribd/armadillo/di/PlaybackModule.kt b/Armadillo/src/main/java/com/scribd/armadillo/di/PlaybackModule.kt index 092405a..9ecaa41 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/di/PlaybackModule.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/di/PlaybackModule.kt @@ -15,6 +15,8 @@ import com.scribd.armadillo.playback.MediaMetadataCompatBuilderImpl import com.scribd.armadillo.playback.PlaybackEngineFactoryHolder import com.scribd.armadillo.playback.PlaybackStateBuilderImpl import com.scribd.armadillo.playback.PlaybackStateCompatBuilder +import com.scribd.armadillo.playback.mediasource.HeadersMediaSourceHelper +import com.scribd.armadillo.playback.mediasource.HeadersMediaSourceHelperImpl import com.scribd.armadillo.playback.mediasource.MediaSourceRetriever import com.scribd.armadillo.playback.mediasource.MediaSourceRetrieverImpl import dagger.Module @@ -56,4 +58,8 @@ internal class PlaybackModule { @Provides @Singleton fun mediaSourceRetriever(mediaSourceRetrieverImpl: MediaSourceRetrieverImpl): MediaSourceRetriever = mediaSourceRetrieverImpl + + @Provides + @Singleton + fun mediaSourceHelper(mediaSourceHelperImpl: HeadersMediaSourceHelperImpl): HeadersMediaSourceHelper = mediaSourceHelperImpl } \ No newline at end of file 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..3bb643c 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadEngine.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/download/DownloadEngine.kt @@ -9,7 +9,6 @@ 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 com.scribd.armadillo.Constants @@ -94,7 +93,8 @@ internal class ExoplayerDownloadEngine @Inject constructor(private val context: .setUri(uri) .build() return when (@C.ContentType val type = Util.inferContentType(uri)) { - C.TYPE_HLS -> + C.TYPE_HLS, + C.TYPE_DASH -> DownloadHelper.forMediaItem(context, mediaItem, renderersFactory, DefaultDataSource.Factory(context, dataSourceFactory)) C.TYPE_OTHER -> DownloadHelper.forMediaItem(context, mediaItem) else -> throw IllegalStateException("Unsupported type: $type") 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 0468c25..e82d64a 100644 --- a/Armadillo/src/main/java/com/scribd/armadillo/playback/ExoplayerExt.kt +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/ExoplayerExt.kt @@ -11,6 +11,7 @@ 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.dash.manifest.DashManifest import com.google.android.exoplayer2.source.hls.HlsManifest import com.scribd.armadillo.Milliseconds import com.scribd.armadillo.time.milliseconds @@ -20,13 +21,12 @@ import com.scribd.armadillo.time.milliseconds * During setup, [ExoPlayer.getCurrentManifest] will be null. */ internal fun ExoPlayer.hasProgressAvailable(): Boolean { - return (isPlayingHls() && (currentManifest as HlsManifest).mediaPlaylist.durationUs != C.TIME_UNSET) - || - (currentManifest == null && !currentTimeline.isEmpty) + return when (val m = currentManifest) { + is HlsManifest -> m.mediaPlaylist.durationUs != C.TIME_UNSET + is DashManifest -> m.durationMs != C.TIME_UNSET + else -> m == null && !currentTimeline.isEmpty + } } - -internal fun ExoPlayer.isPlayingHls(): Boolean = currentManifest is HlsManifest - /** * Current position in relation to all audio files. */ 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 new file mode 100644 index 0000000..be5b637 --- /dev/null +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/DashMediaSourceGenerator.kt @@ -0,0 +1,36 @@ +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.dash.DashMediaSource +import com.scribd.armadillo.download.DownloadTracker +import com.scribd.armadillo.extensions.toUri +import com.scribd.armadillo.models.AudioPlayable +import javax.inject.Inject + +internal class DashMediaSourceGenerator @Inject constructor( + private val mediaSourceHelper: HeadersMediaSourceHelper, + private val downloadTracker: DownloadTracker) : MediaSourceGenerator { + + override fun generateMediaSource(context: Context, request: AudioPlayable.MediaRequest): MediaSource { + val dataSourceFactory = mediaSourceHelper.createDataSourceFactory(context, request) + + downloadTracker.getDownload(request.url.toUri())?.let { + if (it.state != Download.STATE_FAILED) { + return DownloadHelper.createMediaSource(it.request, dataSourceFactory) + } + } + + val mediaItem = MediaItem.Builder() + .setUri(request.url) + .build() + + return DashMediaSource.Factory(dataSourceFactory) + .createMediaSource(mediaItem) + } + + override fun updateMediaSourceHeaders(request: AudioPlayable.MediaRequest) = mediaSourceHelper.updateMediaSourceHeaders(request) +} \ No newline at end of file diff --git a/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/HeadersMediaSourceHelper.kt b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/HeadersMediaSourceHelper.kt new file mode 100644 index 0000000..accb10d --- /dev/null +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/HeadersMediaSourceHelper.kt @@ -0,0 +1,10 @@ +package com.scribd.armadillo.playback.mediasource + +import android.content.Context +import com.google.android.exoplayer2.upstream.DataSource +import com.scribd.armadillo.models.AudioPlayable + +internal interface HeadersMediaSourceHelper { + fun createDataSourceFactory(context: Context, request: AudioPlayable.MediaRequest): DataSource.Factory + fun updateMediaSourceHeaders(request: AudioPlayable.MediaRequest) +} \ No newline at end of file diff --git a/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/HeadersMediaSourceHelperImpl.kt b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/HeadersMediaSourceHelperImpl.kt new file mode 100644 index 0000000..644333c --- /dev/null +++ b/Armadillo/src/main/java/com/scribd/armadillo/playback/mediasource/HeadersMediaSourceHelperImpl.kt @@ -0,0 +1,52 @@ +package com.scribd.armadillo.playback.mediasource + +import android.content.Context +import com.google.android.exoplayer2.upstream.DataSource +import com.google.android.exoplayer2.upstream.DefaultDataSource +import com.google.android.exoplayer2.upstream.DefaultHttpDataSource +import com.scribd.armadillo.Constants +import com.scribd.armadillo.HeadersStore +import com.scribd.armadillo.download.CacheManager +import com.scribd.armadillo.models.AudioPlayable +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +internal class HeadersMediaSourceHelperImpl @Inject constructor( + private val cacheManager: CacheManager, + private val headersStore: HeadersStore +): HeadersMediaSourceHelper { + private val previousRequests = mutableMapOf() + + override fun createDataSourceFactory(context: Context, request: AudioPlayable.MediaRequest): DataSource.Factory { + val httpDataSourceFactory = DefaultHttpDataSource.Factory() + .setUserAgent(Constants.getUserAgent(context)) + .setConnectTimeoutMs(DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS) + .setReadTimeoutMs(DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS) + .setAllowCrossProtocolRedirects(true) + + previousRequests[request.url] = httpDataSourceFactory + if (request.headers.isNotEmpty()) { + headersStore.keyForUrl(request.url)?.let { + headersStore.setHeaders(it, request.headers) + } + httpDataSourceFactory.setDefaultRequestProperties(request.headers) + } + + val upstreamFactory = DefaultDataSource.Factory(context, httpDataSourceFactory) + return cacheManager.playbackDataSourceFactory(context, upstreamFactory) + + } + + override fun updateMediaSourceHeaders(request: AudioPlayable.MediaRequest) { + previousRequests[request.url]?.let { factory -> + if (request.headers.isNotEmpty()) { + headersStore.keyForUrl(request.url)?.let { + headersStore.setHeaders(it, request.headers) + } + // Updating the factory instance updates future requests generated from this factory by ExoPlayer + factory.setDefaultRequestProperties(request.headers) + } + } + } +} \ No newline at end of file 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..a8b5ece 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 @@ -6,12 +6,6 @@ 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 com.scribd.armadillo.Constants -import com.scribd.armadillo.HeadersStore -import com.scribd.armadillo.download.CacheManager import com.scribd.armadillo.download.DownloadTracker import com.scribd.armadillo.extensions.toUri import com.scribd.armadillo.models.AudioPlayable @@ -22,50 +16,21 @@ import javax.inject.Inject * */ internal class HlsMediaSourceGenerator @Inject constructor( - private val cacheManager: CacheManager, - private val headersStore: HeadersStore, + private val mediaSourceHelper: HeadersMediaSourceHelper, private val downloadTracker: DownloadTracker) : MediaSourceGenerator { - private val previousRequests = mutableMapOf() override fun generateMediaSource(context: Context, request: AudioPlayable.MediaRequest): MediaSource { + val dataSourceFactory = mediaSourceHelper.createDataSourceFactory(context, request) + downloadTracker.getDownload(request.url.toUri())?.let { if (it.state != Download.STATE_FAILED) { - return DownloadHelper.createMediaSource(it.request, buildDataSourceFactory(context, request)) + return DownloadHelper.createMediaSource(it.request, dataSourceFactory) } } - return HlsMediaSource.Factory(buildDataSourceFactory(context, request)) + return HlsMediaSource.Factory(dataSourceFactory) .createMediaSource(MediaItem.fromUri(request.url)) } - override fun updateMediaSourceHeaders(request: AudioPlayable.MediaRequest) { - previousRequests[request.url]?.let { factory -> - if (request.headers.isNotEmpty()) { - headersStore.keyForUrl(request.url)?.let { - headersStore.setHeaders(it, request.headers) - } - // Updating the factory instance updates future requests generated from this factory by ExoPlayer - factory.setDefaultRequestProperties(request.headers) - } - } - } - - private fun buildDataSourceFactory(context: Context, request: AudioPlayable.MediaRequest): DataSource.Factory { - val httpDataSourceFactory = DefaultHttpDataSource.Factory() - .setUserAgent(Constants.getUserAgent(context)) - .setConnectTimeoutMs(DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS) - .setReadTimeoutMs(DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS) - .setAllowCrossProtocolRedirects(true) - - previousRequests[request.url] = httpDataSourceFactory - if (request.headers.isNotEmpty()) { - headersStore.keyForUrl(request.url)?.let { - headersStore.setHeaders(it, request.headers) - } - httpDataSourceFactory.setDefaultRequestProperties(request.headers) - } - - val upstreamFactory = DefaultDataSource.Factory(context, httpDataSourceFactory) - return cacheManager.playbackDataSourceFactory(context, upstreamFactory) - } + override fun updateMediaSourceHeaders(request: AudioPlayable.MediaRequest) = mediaSourceHelper.updateMediaSourceHeaders(request) } \ No newline at end of file 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..51426dc 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 @@ -22,6 +22,9 @@ class MediaSourceRetrieverImpl @Inject constructor(): MediaSourceRetriever { @Inject internal lateinit var hlsGenerator: HlsMediaSourceGenerator + @Inject + internal lateinit var dashGenerator: DashMediaSourceGenerator + @Inject internal lateinit var progressiveMediaSourceGenerator: ProgressiveMediaSourceGenerator @@ -46,6 +49,7 @@ class MediaSourceRetrieverImpl @Inject constructor(): MediaSourceRetriever { return when (@C.ContentType val type = Util.inferContentType(uri, overrideExtension)) { C.TYPE_HLS -> hlsGenerator + C.TYPE_DASH -> dashGenerator C.TYPE_OTHER -> progressiveMediaSourceGenerator else -> throw IllegalStateException("Unsupported type: $type") } diff --git a/RELEASE.md b/RELEASE.md index 0a47558..20be961 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,5 +1,8 @@ # Project Armadillo Release Notes +## 1.2.0 +- Adds support for MPEG-DASH audio + ## 1.1.1 - Added a fix for seek from notification player diff --git a/gradle.properties b/gradle.properties index cee3a3e..67a9b1e 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.1.1 +LIBRARY_VERSION=1.2.0 EXOPLAYER_VERSION=2.17.1 RXJAVA_VERSION=2.2.4 RXANDROID_VERSION=2.0.1