My app supports several different types of media; both streaming and downloaded. The issue is, I have multiple DataSourceFactories to support each of these media types and the fact that they're streaming or downloaded.
For example
For HLS and DASH things aren't so bad. I can pass the CacheDataSource and set that as the DataSource in my MediaSourceFactory. If there is a cache miss, it will go and hit the upstream DataSource, which is the OkHttpDataSource.
Things get more complicated for MP4.
Right now, my code looks like this:
val cacheFactory = CacheDataSource.Factory()
.setCache(cache)
.setUpstreamDataSourceFactory(httpFactory)
.setCacheWriteDataSinkFactory(null)
.setCacheReadDataSourceFactory(FileDataSource.Factory())
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
val audioAttributes = AudioAttributes.Builder()
.setUsage(C.USAGE_MEDIA)
.setContentType(C.AUDIO_CONTENT_TYPE_SPEECH)
.build()
return ExoPlayer.Builder(context, DefaultRenderersFactory(context))
.setMediaSourceFactory(
DefaultMediaSourceFactory(context)
.setDrmSessionManagerProvider { drmSessionManager }
.setDataSourceFactory(cacheFactory)
)
As I said, this works fine for HLS and DASH since they both use the same cache and upstream datasource. It works fine for streaming MP4s also since DefaultMediaSourceFactory supports that out of the box. It does not work for downloaded MP4s though because it has a custom datasource.
Is there any way that I can get these all working together?
Ok I think I got this working. I ended up creating my own custom implementation of MediaSource.Factory
. Depending on what content type I get, I defer to the appropriate MediaSource.Factory
. My implementation looks like this...
class CustomMediaSourceFactory @Inject constructor(
@Named("hls") private val hlsFactory: DataSource.Factory,
private val dashStreamingFactory: DashMediaSource.Factory,
@Named("mp4") private val mp4DataSourceFactory: DataSource.Factory,
private val adaptiveStreamDownloadSource: AdaptiveStreamDownloadSource,
@Named("adaptiveStreamDownloadInternalDataSource") private val adaptiveStreamDownloadDataSource: CacheDataSource.Factory,
private val dashMediaSourceProvider: DashMediaSourceProvider,
private val extractorsFactory: ExtractorsFactory,
private val loadErrorHandlingPolicy: LoadErrorHandlingPolicy
) : MediaSource.Factory {
private val hlsStreamingFactory: HlsMediaSource.Factory by lazy(LazyThreadSafetyMode.NONE) {
HlsMediaSource.Factory(hlsFactory)
.setAllowChunklessPreparation(true)
.setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)
}
private val mp4Factory: ProgressiveMediaSource.Factory by lazy(LazyThreadSafetyMode.NONE) {
ProgressiveMediaSource.Factory(mp4DataSourceFactory, extractorsFactory)
.setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)
}
private var drmSessionManagerProvider: DrmSessionManagerProvider? = null
override fun setDrmSessionManagerProvider(drmSessionManagerProvider: DrmSessionManagerProvider): MediaSource.Factory {
this.drmSessionManagerProvider = drmSessionManagerProvider
return this
}
override fun setLoadErrorHandlingPolicy(loadErrorHandlingPolicy: LoadErrorHandlingPolicy): MediaSource.Factory {
return this
}
override fun getSupportedTypes(): IntArray = intArrayOf(
CONTENT_TYPE_DASH,
CONTENT_TYPE_HLS,
CONTENT_TYPE_OTHER
)
override fun createMediaSource(mediaItem: MediaItem): MediaSource {
val type = Util.inferContentTypeForUriAndMimeType(
mediaItem.localConfiguration!!.uri, mediaItem.localConfiguration!!.mimeType)
return when (type) {
CONTENT_TYPE_OTHER -> mp4Factory.createMediaSource(mediaItem)
CONTENT_TYPE_HLS -> handleAdaptiveMediaSourceCreation(mediaItem, hlsStreamingFactory)
CONTENT_TYPE_DASH -> handleAdaptiveMediaSourceCreation(mediaItem, dashStreamingFactory)
else -> throw IllegalArgumentException("MediaItem must be one of type DASH, HLS or MP4")
}
}
I hope this helps someone else facing a similar issue.