我想将 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)
事实证明非常简单。需要用
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,
)
}