Android ExoPlayer合并不同时长HLS流并投屏Chromecast技术咨询
解决不同时长HLS音视频流合并并投送Chromecast的方案
一、替代MergingMediaSource的本地音视频同步方案
由于MergingMediaSource强制要求所有源流时长一致,你可以拆分音视频源的加载逻辑,用以下方式实现同步:
- 分别创建视频流A和音频流B的
HlsMediaSource实例 - 通过
ExoPlayer的TrackSelector手动指定播放视频A的视频轨和音频B的音频轨 - 添加播放状态监听,自定义时长不一致时的同步逻辑:
- 当较短的流播放结束时,可选择暂停较长流,或者循环较短流以匹配时长
- 示例代码片段:
val videoSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(videoUri) val audioSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(audioUri) // 创建复合媒体源,无需强制时长一致 val compositeSource = CompositeMediaSource<Void>() compositeSource.addMediaSource(0, videoSource) compositeSource.addMediaSource(0, audioSource) player.setMediaSource(compositeSource) player.prepare() // 添加监听处理时长不一致逻辑 player.addListener(object : Player.Listener { override fun onPositionDiscontinuity(oldPosition: PositionInfo, newPosition: PositionInfo, reason: Int) { val videoDuration = player.currentTimeline.getWindow(0, Timeline.Window()).durationMs val audioDuration = player.currentTimeline.getWindow(1, Timeline.Window()).durationMs if (videoDuration != audioDuration) { // 自定义同步逻辑,比如循环较短的流 if (player.currentPosition >= min(videoDuration, audioDuration)) { player.seekTo(0) player.play() } } } })
二、Chromecast投送的适配逻辑
Chromecast投送时,无需在本地合成流,直接将音视频双源流信息发送给Cast设备,让其加载并同步播放:
- 通过
MediaRouter获取Cast路由会话 - 使用
RemoteMediaClient设置包含音视频URI的媒体信息,部分设备支持通过自定义字段传递音频流地址:
注:如果使用通用Cast应用,可将音视频流打包为包含双轨道的HLS主播放列表后再投送,Cast设备会自动识别并加载对应轨道。val remoteMediaClient = castSession.remoteMediaClient val mediaMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE) val mediaInfo = MediaInfo.Builder(videoUri.toString()) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setContentType("application/x-mpegURL") .setMetadata(mediaMetadata) .setCustomData(mapOf("audioTrackUri" to audioUri.toString())) .build() remoteMediaClient.load(mediaInfo, true)
三、备选方案:动态适配流时长
如果必须使用合并类MediaSource,可通过裁剪或循环流来强制匹配时长:
- 裁剪较长流至较短流的时长:
val videoDuration = videoSource.duration val audioDuration = audioSource.duration val targetDuration = min(videoDuration, audioDuration) val clippedSource = if (videoDuration > targetDuration) { ClippingMediaSource(videoSource, 0, targetDuration) } else { ClippingMediaSource(audioSource, 0, targetDuration) } val mergedSource = MergingMediaSource(if (videoDuration > targetDuration) clippedSource else videoSource, if (audioDuration > targetDuration) clippedSource else audioSource) - 循环较短流以匹配较长流时长:
该方案适合能接受裁剪或循环的场景,若流时长动态变化,需实时重新计算并更新MediaSource。val loopCount = ceil(max(videoDuration, audioDuration).toFloat() / min(videoDuration, audioDuration)).toInt() val loopedSource = if (videoDuration < audioDuration) { LoopingMediaSource(videoSource, loopCount) } else { LoopingMediaSource(audioSource, loopCount) } val mergedSource = MergingMediaSource(if (videoDuration < audioDuration) loopedSource else videoSource, if (audioDuration < videoDuration) loopedSource else audioSource)
内容的提问来源于stack exchange,提问作者VirtualProdigy
相关产品推荐
相关产品推荐

