Skip to content

Commit

Permalink
Merge pull request #27 from scribd/karl/APT-9543-dash
Browse files Browse the repository at this point in the history
Add MPEG-DASH support to playback
  • Loading branch information
kschults authored Dec 6, 2023
2 parents f59bf24 + e5b9ce6 commit 4abdf58
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 50 deletions.
1 change: 1 addition & 0 deletions Armadillo/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -56,4 +58,8 @@ internal class PlaybackModule {
@Provides
@Singleton
fun mediaSourceRetriever(mediaSourceRetrieverImpl: MediaSourceRetrieverImpl): MediaSourceRetriever = mediaSourceRetrieverImpl

@Provides
@Singleton
fun mediaSourceHelper(mediaSourceHelperImpl: HeadersMediaSourceHelperImpl): HeadersMediaSourceHelper = mediaSourceHelperImpl
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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<String, DefaultHttpDataSource.Factory>()

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)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<String, DefaultHttpDataSource.Factory>()

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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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")
}
Expand Down
3 changes: 3 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 4abdf58

Please sign in to comment.