Search code examples
exoplayerexoplayer2.xexoplayer-media-item

Is there a way to add multiple DataSource.Factorys to ExoPlayer?


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 streaming MP4s, these sources are created using a ProgressiveMediaSource
  • For downloaded MP4s, the MP4s are stored in an obfuscated format so I have a custom DataSource to read and deobfuscate the bytes
  • For streaming HLS and DASH, I use an OkHttpDataSource
  • For downloaded HLS and DASH, I use a CacheDataSource

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?


Solution

  • 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.