androidx / media

Jetpack Media3 support libraries for media use cases, including ExoPlayer, an extensible media player for Android
Apache License 2.0
1.37k stars 322 forks source link

How to switch between hls and progressive factory in playlist #374

Open MrBzik opened 1 year ago

MrBzik commented 1 year ago

Hi. I am getting lists of urls for audio streams, and some of them are hls. So I am using this logic, which is suitable for playing separate streams:


val mediaSource = if(lastPath?.contains("m3u8") == true ||
lastPath?.contains("m3u") == true) {

HlsMediaSource.Factory(dataSourceFactory)
setAllowChunklessPreparation(true)
.createMediaSource(mediaItem)
        } else {

ProgressiveMediaSource.Factory(dataSourceFactory)
 .setContinueLoadingCheckIntervalBytes(checkInterval)
.createMediaSource(mediaItem)
        }

To play next/prev stream from notification/lock screen I need to pass list of mediaitems or concatenating mediasource to the player, and I don't see how I can switch between those two factories then. Maybe with DefaultMediaSourceFactory, but ideally I would like to keep setContinueLoadingCheckIntervalBytes setting. Is it possible, or should I look into custom actions of notification (i am using PlayerNotificationManager)?

marcbaechinger commented 1 year ago

I think you can inject your own MediaSource.Factory that does this for you and in normal cases delegates to the default factory.

Something along these lines:

 player =
  ExoPlayer.Builder(this)
    .setMediaSourceFactory(MyMediaSourceFactroy())
    .build()

class MyMediaSourceFactory(context: Context, datasourceFactory: DataSource.Factory) : MediaSource.Factory {
    private val defaultMediaSourceFactory: DefaultMediaSourceFactory
    private val progressiveFactory : MediaSource.Factory
    init {
      defaultMediaSourceFactory = DefaultMediaSourceFactory(context)
      progressiveFactory = ProgressiveMediaSource.Factory(datasourceFactory)
              .setContinueLoadingCheckIntervalBytes(2028 * 2048)
    }

    override fun setDrmSessionManagerProvider(drmSessionManagerProvider: DrmSessionManagerProvider): MediaSource.Factory {
      defaultMediaSourceFactory.setDrmSessionManagerProvider(drmSessionManagerProvider)
      progressiveFactory.setDrmSessionManagerProvider(drmSessionManagerProvider)
      return this;
    }

    override fun setLoadErrorHandlingPolicy(loadErrorHandlingPolicy: LoadErrorHandlingPolicy): MediaSource.Factory {
      defaultMediaSourceFactory.setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)
      progressiveFactory.setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)
      return this
    }

    override fun getSupportedTypes(): IntArray {
      return defaultMediaSourceFactory.supportedTypes
    }

    override fun createMediaSource(mediaItem: MediaItem): MediaSource {
      if (mediaItem.localConfiguration?.uri?.path?.contains("m3u8") == true) {
        return progressiveFactory.createMediaSource(mediaItem)
      }
      return defaultMediaSourceFactory.createMediaSource(mediaItem);
    }
  }

If you do this you can also take advantage of using player.setMediaItems(mediaItems) instead of the MediaSource version.

MrBzik commented 1 year ago

Hi. Thanks! This works perfectly Btw, m3u is not hls and should not necessarily handled by defaultMediaSourceFactory?

marcbaechinger commented 1 year ago

Ah, sorry. I have missed the m3u extension. This is not supported by ExoPlayer (#Google/ExoPlayer/4066).

My example code was for HLS only.

FongMi commented 1 year ago

How to custom http header for each mediaItem in MyMediaSourceFactory ?

marcbaechinger commented 1 year ago

https://developer.android.com/guide/topics/media/exoplayer/customization#customizing-server

liuchuancong commented 8 months ago

@marcbaechinger You're doing great. but when url is m3u8 stream without m3u8 extensions,found err with "Source err", i got format Util.inferContentType(uri) is inter 4,means C.CONTENT_TYPE_OTHER, but it is hls, i can't change C.CONTENT_TYPE_OTHER -> ProgressiveMediaSource.Factory,because of some video is mp4,etc. here is my code

    private fun buildMediaSource(
        uri: Uri,
        mediaDataSourceFactory: DataSource.Factory,
        formatHint: String?,
        cacheKey: String?,
        context: Context
    ): MediaSource {
        val type: Int = if (formatHint == null) {
            Util.inferContentType(uri)
        } else {
            when (formatHint) {
                FORMAT_SS -> C.CONTENT_TYPE_SS
                FORMAT_DASH -> C.CONTENT_TYPE_DASH
                FORMAT_HLS -> C.CONTENT_TYPE_HLS
                FORMAT_OTHER -> C.CONTENT_TYPE_OTHER
                FORMAT_RTSP -> C.CONTENT_TYPE_RTSP
                else -> -1
            }
        }
        val mediaItemBuilder = MediaItem.Builder()
        mediaItemBuilder.setUri(uri)
        if (!cacheKey.isNullOrEmpty()) {
            mediaItemBuilder.setCustomCacheKey(cacheKey)
        }
        val mediaItem = mediaItemBuilder.build()
        var drmSessionManagerProvider: DrmSessionManagerProvider? = null
        drmSessionManager?.let { drmSessionManager ->
            drmSessionManagerProvider = DrmSessionManagerProvider { drmSessionManager }
        }
        return when (type) {
            C.CONTENT_TYPE_SS -> SsMediaSource.Factory(
                DefaultSsChunkSource.Factory(mediaDataSourceFactory),
                DefaultDataSource.Factory(context, mediaDataSourceFactory)
            ).apply {
                if (drmSessionManagerProvider != null) {
                    setDrmSessionManagerProvider(drmSessionManagerProvider!!)
                }
            }
                .createMediaSource(mediaItem)

            C.CONTENT_TYPE_DASH -> DashMediaSource.Factory(
                DefaultDashChunkSource.Factory(mediaDataSourceFactory),
                DefaultDataSource.Factory(context, mediaDataSourceFactory)
            ).apply {
                if (drmSessionManagerProvider != null) {
                    setDrmSessionManagerProvider(drmSessionManagerProvider!!)
                }
            }
                .createMediaSource(mediaItem)

            C.CONTENT_TYPE_HLS -> HlsMediaSource.Factory(mediaDataSourceFactory)
                .apply {
                    if (drmSessionManagerProvider != null) {
                        setDrmSessionManagerProvider(drmSessionManagerProvider!!)
                    }
                }
                .createMediaSource(mediaItem)

            C.CONTENT_TYPE_RTSP -> RtspMediaSource.Factory()
                .apply {
                    if (drmSessionManagerProvider != null) {
                        setDrmSessionManagerProvider(drmSessionManagerProvider!!)
                    }
                }
                .createMediaSource(mediaItem)

            C.CONTENT_TYPE_OTHER -> ProgressiveMediaSource.Factory(
                mediaDataSourceFactory,
                DefaultExtractorsFactory()
            )
                .apply {
                    if (drmSessionManagerProvider != null) {
                        setDrmSessionManagerProvider(drmSessionManagerProvider!!)
                    }
                }
                .createMediaSource(mediaItem)

            else -> {
                throw IllegalStateException("Unsupported type: $type")
            }
        }
    }

any idea? when i use mpvplayer,work great! it's amazing ! sorry about my english is so bad! @MrBzik

marcbaechinger commented 8 months ago

The library can't help you with this I'm afraid. Yeah, you need to know the type of stream of an URI and then create the corresponding media source.

liuchuancong commented 8 months ago

@marcbaechinger I fixed this! when i got err "Source err",

void debounceListen(Function? func, [int delay = 1000]) {
    if (_debounceTimer != null) {
      _debounceTimer?.cancel();
    }
    _debounceTimer = Timer(Duration(milliseconds: delay), () {
     try {
        tryHlsMethod() // try  buildMediaSource with C.CONTENT_TYPE_HLS
     } catch (e) {
       log(e);
     }

      _debounceTimer = null;
    });
  }