我正在我的撰写应用程序中播放 HLS 流(m3u8 播放列表)。该流来自传感器,由 MPEG-TS 片段组成(仅视频,无音频)。问题是当流到来时,找不到第一个段,您可以看到传感器的日志(test..ts 404),只找到后续流:
Sensor 的 Android 案例日志 Android 行为似乎一次又一次地重试,导致我的流仅在 30 秒的巨大延迟后才显示。
IOS 的行为是简单地跳过丢失的段,然后继续检索下一个段并重复,如日志中所示: Sensor的IOS案例日志
这是 .ts 段的 mediaInfo 示例:
General
ID : 1 (0x1)
Complete name : test_19.ts
Format : MPEG-TS
File size : 51.2 KiB
Duration : 5 s 205 ms
Overall bit rate mode : Variable
Overall bit rate : 80.6 kb/s
Frame rate : 30.000 FPS
Video
ID : 65 (0x41)
Menu ID : 1 (0x1)
Format : AVC
Format/Info : Advanced Video Codec
Format profile : Baseline@L1
Format settings : 1 Ref Frames
Format settings, CABAC : No
Format settings, Reference frames : 1 frame
Format settings, GOP : M=1, N=30
Codec ID : 27
Duration : 5 s 49 ms
Bit rate : 76.6 kb/s
Width : 640 pixels
Height : 360 pixels
Display aspect ratio : 16:9
Frame rate : 30.000 FPS
Color space : YUV
Chroma subsampling : 4:2:0
Bit depth : 8 bits
Scan type : Progressive
Bits/(Pixel*Frame) : 0.011
Stream size : 47.1 KiB (92%)
这是 Hls 播放列表清单的示例:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-ALLOW-CACHE:NO
#EXT-X-MEDIA-SEQUENCE:19
#EXT-X-TARGETDURATION:5
#EXTINF:5.068028450012207,
test_17.ts
#EXTINF:5.0681610107421875,
test_05.ts
#EXTINF:5.067845344543457,
test_10.ts
#EXTINF:5.0680408477783203,
test_21.ts
#EXTINF:5.0680079460144043,
test_22.ts
我尝试了多种方法,特别是允许ChunklessPreparation / LoadErrorHandlingPolicy / autoSeeking /将不同的标志传递给mediaSource /使用内部使用exoplayer的已实现包播放HLS等,从我的代码和注释(在代码中)可以看出:
fun LiveStreamPlayer(url: String) {
val context = LocalContext.current
val mediaUrl = (context as MainActivity).mainViewModel.livestreamUrl.value!!.data
Timber.e("media item livestreamurl: $mediaUrl")
val livestreamLogs = remember { mutableStateListOf<String>() }
val exoPlayer = remember { ExoPlayer.Builder(context).build()}
var autoSought = false;
LaunchedEffect(key1 = Unit) {
val dataSourceFactory = DefaultHttpDataSource.Factory().setAllowCrossProtocolRedirects(true)
val extractorsFactory = DefaultHlsExtractorFactory(
FLAG_DETECT_ACCESS_UNITS or DefaultTsPayloadReaderFactory.FLAG_ALLOW_NON_IDR_KEYFRAMES or DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM,
true
)
val mediaSource = HlsMediaSource
.Factory(dataSourceFactory)
/*.setLoadErrorHandlingPolicy(
object : DefaultLoadErrorHandlingPolicy(0) {
override fun getRetryDelayMsFor(loadErrorInfo: LoadErrorHandlingPolicy.LoadErrorInfo): Long {
return when (loadErrorInfo.exception) {
is FileNotFoundException -> {
C.TIME_UNSET
}
else -> {
// Handle other exceptions or apply default retry logic
super.getRetryDelayMsFor(loadErrorInfo)
}
}
}
}
)*/
//.setExtractorFactory(extractorsFactory)
.setAllowChunklessPreparation(true)
.createMediaSource(
MediaItem.fromUri(mediaUrl)
/*.Builder()
.setMimeType(MimeTypes.APPLICATION_M3U8)
.setUri(mediaUrl)
.build()*/
)
exoPlayer.apply {
addListener(object : Player.Listener {
override fun onPlayerError(error: PlaybackException) {
Timber.e("ExoPlayer error: ${error.message}")
livestreamLogs.add("error ${error.errorCode}: ${error.errorCodeName}")
// Handle error
}
override fun onPlaybackStateChanged(playbackState: Int) {
Timber.e("Playback state changed: $playbackState")
livestreamLogs.add("playback state change : ${playbackState}")
if(!autoSought){
seekTo(11000)
autoSought = true
/*Handler(Looper.getMainLooper()).postDelayed(
{
val currentPosition = exoPlayer.currentPosition
exoPlayer.setMediaItem(MediaItem.fromUri(mediaUrl))
exoPlayer.seekTo(currentPosition + 5000)
exoPlayer.play()
autoSought = true
}, 1000
)*/
}
}
}
)
setMediaSource(mediaSource)
seekTo(1)
prepare()
playWhenReady = true
}
}
DisposableEffect(
Column(
modifier = Modifier
.fillMaxSize()
) {
AndroidView(
modifier = Modifier
.height(300.dp)
.fillMaxWidth(),
factory = {
val playerView = PlayerView(context)
playerView.apply {
player = exoPlayer
//useController = false
}
playerView
}
)
/*VideoPlayer(
mediaItems = listOf(
VideoPlayerMediaItem.NetworkMediaItem(
url = mediaUrl,
mediaMetadata = MediaMetadata.Builder()
.setTitle("Widevine DASH cbcs: Tears").build(),
mimeType = MimeTypes.APPLICATION_M3U8
)
),
handleLifecycle = true,
autoPlay = true,
usePlayerController = true,
enablePip = true,
handleAudioFocus = true,
controllerConfig = VideoPlayerControllerConfig(
showSpeedAndPitchOverlay = false,
showSubtitleButton = false,
showCurrentTimeAndTotalTime = true,
showBufferingProgress = false,
showForwardIncrementButton = true,
showBackwardIncrementButton = true,
showBackTrackButton = true,
showNextTrackButton = true,
showRepeatModeButton = true,
controllerShowTimeMilliSeconds = 5_000,
controllerAutoShow = true,
showFullScreenButton = false
),
volume = 0.5f, // volume 0.0f to 1.0f
repeatMode = RepeatMode.NONE, // or RepeatMode.ALL, RepeatMode.ONE
onCurrentTimeChanged = { // long type, current player time (millisec)
},
playerInstance = { // ExoPlayer instance (Experimental)
},
modifier = Modifier
.height(300.dp)
.fillMaxWidth(),
)*/
/*AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { context ->
VideoView(context).apply {
setVideoURI(Uri.parse(mediaUrl))
setOnPreparedListener {
setZOrderOnTop(true)
it.start()
}
}
},
)*/
Spacer(modifier = Modifier.height(10.dp))
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(horizontal = 16.dp)
.padding(bottom = 80.dp),
) {
items(livestreamLogs.size) { log ->
Text(livestreamLogs[log])
}
}
}
) {
onDispose {
Timber.e("exoplayer disposed")
exoPlayer.release()
}
}
}
总而言之,如果没有找到,我如何强制 Exoplayer 跳过一个片段并立即转到下一个片段? (不幸的是,我无法提供流 URL,因为它是根据传感器的命令动态提供的)
感谢任何帮助,如果需要更多信息或我的问题不清楚,请告诉我。
当它回复
test..ts 404
时,这意味着:文件未找到。双冒号很可疑 ..
.
虽然
#EXTM3U
甚至没有它(那是完全不同的)。只需修复播放列表即可。