Android Media3:如何将 HLS 流拆分为多个媒体项,每个媒体项都有自己的范围

问题描述 投票:0回答:1

我想将 HLS 音频流拆分为章节,并使用 MediaSessionService 将它们作为

MediaItem
列表添加到 Media3 播放器。我在 MediaController 中有以下代码。

从可组合屏幕:

fun getMediaItems(uri: String, chapters: List<Chapter>, token: String): List<MediaItem> {
    val requestMetaData = MediaItem.RequestMetadata.Builder()
        .setExtras(bundleOf( "TOKEN" to token,)) .build()

    var offsetInMs = 0L

    return chapters.map { chapter ->
        val mediaMetaData = MediaMetadata.Builder()
            .setTitle(chapter.title)
            .build()
        val clippingConfiguration = ClippingConfiguration.Builder()
            .setStartPositionMs(offsetInMs)
            .setEndPositionMs(offsetInMs + chapter.duration)
            .setRelativeToDefaultPosition(true)
            .build()
        offsetInMs += chapter.runtime

        MediaItem.Builder()
            .setMediaId(chapter.id)
            .setUri(uri)
            .setMimeType(MimeTypes.APPLICATION_M3U8)
            .setClippingConfiguration(clippingConfiguration)
            .setRequestMetadata(requestMetaData)
            .setMediaMetadata(mediaMetaData)
            .build()
    }
}

来自 ViewModel:

var mediaControllerFuture = MutableStateFlow<ListenableFuture<MediaController>?>(null)


fun prepare(context: Context, mediaItems: List<MediaItem>) {
    val sessionToken = SessionToken(
        context,
        ComponentName(context, PlaybackService::class.java),
    )
    MediaController.Builder(context, sessionToken)
        .buildAsync()
        .also {
            val listener = Runnable {
                mediaControllerFuture.value?.get()?.let { controller ->
                    controller.addListener(listener)
                    controller.addMediaItems(mediaItems)
                    controller.prepare()
                    controller.play()
                }
            }
            mediaControllerFuture.value = it
            mediaControllerFuture.value?.addListener(listener, MoreExecutors.directExecutor())
        } 
}

服务:

class PlaybackService : MediaSessionService() {
    private var mediaSession: MediaSession? = null

    override fun onCreate() {
        super.onCreate()
        val customFactory = object : MediaSource.Factory {
            override fun createMediaSource(mediaItem: MediaItem): MediaSource {
                val token = mediaItem.requestMetadata.extras?.getString("TOKEN") ?: ""
                val dataSourceFactory = DataSource.Factory {
                    val dataSource = DefaultHttpDataSource.Factory().createDataSource()
                    dataSource.setRequestProperty("Authorization", token)
                    dataSource
                }
                return HlsMediaSource.Factory(dataSourceFactory)
                    .createMediaSource(mediaItem)
            }
        }
        val player = ExoPlayer.Builder(this)
            .setMediaSourceFactory(customFactory)
            .build()
            .apply {
                playWhenReady = true
            }

        mediaSession = MediaSession.Builder(this, player)
            .build()
    }

    override fun onTaskRemoved(rootIntent: Intent?) { ... }

    override fun onDestroy() { ... }

    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? {
        return mediaSession
    }
}

但是,播放器仍在播放整个流(忽略开始/结束位置),并且看起来并没有移动到下一个 MediaItem。或者,如果它确实前进到后续 MediaItems,我如何才能找到播放器实际前进的时间(以便我可以更新播放器 UI 以指示当前正在播放的章节)?我可以跟踪任何侦听器事件吗?

我尝试了这个回调,但除了一开始的一次之外,从未见过它被调用。

override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int)
android-mediaplayer exoplayer android-mediasession android-media3 exoplayer-media-item
1个回答
0
投票

事实证明非常简单。需要用

ClippingMediaSource
包裹媒体源,如下所示。

            override fun createMediaSource(mediaItem: MediaItem): MediaSource {
                val token = mediaItem.requestMetadata.extras?.getString("TOKEN") ?: "" 
                val dataSourceFactory = DataSource.Factory {
                    val dataSource = DefaultHttpDataSource.Factory().createDataSource()
                    dataSource.setRequestProperty("Authorization", token)
                    dataSource
                }

                val hlsMediaSource = HlsMediaSource.Factory(dataSourceFactory)
                    .createMediaSource(mediaItem)

                return ClippingMediaSource(
                    hlsMediaSource,
                    mediaItem.clippingConfiguration.startPositionUs,
                    mediaItem.clippingConfiguration.endPositionUs,
                    /* enableInitialDiscontinuity= */
                    !mediaItem.clippingConfiguration.startsAtKeyFrame,
                    /* allowDynamicClippingUpdates= */
                    mediaItem.clippingConfiguration.relativeToLiveWindow,
                    mediaItem.clippingConfiguration.relativeToDefaultPosition,
                )
            }
© www.soinside.com 2019 - 2024. All rights reserved.