Skip to content

Commit a462d44

Browse files
author
Taois
committed
feat: 修复跳过片头片尾
1 parent 8d24b7d commit a462d44

File tree

3 files changed

+374
-49
lines changed

3 files changed

+374
-49
lines changed

dashboard/src/components/players/ArtVideoPlayer.vue

Lines changed: 212 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,12 @@ const {
156156
initSkipSettings,
157157
resetSkipState,
158158
applySkipSettings,
159+
applyIntroSkipImmediate,
159160
handleTimeUpdate,
160161
closeSkipSettingsDialog,
161-
saveSkipSettings: saveSkipSettingsComposable
162+
saveSkipSettings: saveSkipSettingsComposable,
163+
onUserSeekStart,
164+
onUserSeekEnd
162165
} = useSkipSettings({
163166
onSkipToNext: () => {
164167
if (autoNextEnabled.value && hasNextEpisode()) {
@@ -247,16 +250,38 @@ const initArtPlayer = async (url) => {
247250
return
248251
}
249252
250-
// 清理之前的播放器实例
253+
// 如果播放器实例已存在,使用 switchUrl 方法切换视频源
251254
if (artPlayerInstance.value) {
252-
// 清理 HLS 实例
253-
if (artPlayerInstance.value.hls) {
254-
artPlayerInstance.value.hls.destroy()
255-
artPlayerInstance.value.hls = null
256-
}
255+
console.log('使用 switchUrl 方法切换视频源:', url)
257256
258-
artPlayerInstance.value.destroy()
259-
artPlayerInstance.value = null
257+
try {
258+
// 使用 switchUrl 方法切换视频源,这样可以保持全屏状态和其他用户设置
259+
await artPlayerInstance.value.switchUrl(url)
260+
console.log('视频源切换成功')
261+
262+
// 重新应用片头片尾设置
263+
applySkipSettings()
264+
265+
return // 切换成功,直接返回
266+
} catch (error) {
267+
console.error('switchUrl 切换失败,回退到销毁重建方式:', error)
268+
// 如果 switchUrl 失败,回退到原来的销毁重建方式
269+
270+
// 清理缓冲区清理定时器
271+
if (artPlayerInstance.value.bufferCleanupInterval) {
272+
clearInterval(artPlayerInstance.value.bufferCleanupInterval)
273+
artPlayerInstance.value.bufferCleanupInterval = null
274+
}
275+
276+
// 清理 HLS 实例
277+
if (artPlayerInstance.value.hls) {
278+
artPlayerInstance.value.hls.destroy()
279+
artPlayerInstance.value.hls = null
280+
}
281+
282+
artPlayerInstance.value.destroy()
283+
artPlayerInstance.value = null
284+
}
260285
}
261286
262287
try {
@@ -307,8 +332,42 @@ const initArtPlayer = async (url) => {
307332
const hls = new Hls({
308333
// HLS 配置选项
309334
enableWorker: true,
310-
lowLatencyMode: true,
311-
backBufferLength: 90,
335+
lowLatencyMode: false, // 关闭低延迟模式,提高稳定性
336+
337+
// 缓冲区配置 - 关键优化
338+
backBufferLength: 15, // 减少后缓冲长度,避免内存占用过多
339+
maxBufferLength: 30, // 减少最大缓冲长度到30秒,避免内存问题
340+
maxBufferSize: 30 * 1000 * 1000, // 减少最大缓冲大小到30MB
341+
maxBufferHole: 0.3, // 减少最大缓冲空洞到0.3秒
342+
343+
// 网络配置
344+
maxLoadingDelay: 3, // 减少最大加载延迟到3秒
345+
maxRetryDelay: 6, // 减少最大重试延迟到6秒
346+
maxRetry: 2, // 减少最大重试次数到2次,避免过度重试
347+
348+
// 片段配置
349+
fragLoadingTimeOut: 15000, // 减少片段加载超时到15秒
350+
manifestLoadingTimeOut: 8000, // 减少清单加载超时到8秒
351+
fragLoadingMaxRetry: 2, // 片段加载最大重试次数
352+
manifestLoadingMaxRetry: 2, // 清单加载最大重试次数
353+
354+
// 启用自动质量切换
355+
enableSoftwareAES: true,
356+
startLevel: -1, // 自动选择起始质量
357+
capLevelToPlayerSize: true, // 根据播放器大小限制质量
358+
359+
// 错误恢复配置
360+
liveSyncDurationCount: 3,
361+
liveMaxLatencyDurationCount: Infinity,
362+
liveDurationInfinity: false,
363+
364+
// 新增配置项,提高稳定性
365+
nudgeOffset: 0.1, // 微调偏移量
366+
nudgeMaxRetry: 3, // 微调最大重试次数
367+
maxSeekHole: 2, // 最大寻址空洞
368+
369+
// 调试配置(生产环境可关闭)
370+
debug: false,
312371
})
313372
314373
hls.loadSource(url)
@@ -322,25 +381,118 @@ const initArtPlayer = async (url) => {
322381
console.log('HLS manifest 解析完成')
323382
})
324383
384+
// 错误重试计数器
385+
let networkErrorRetries = 0
386+
let mediaErrorRetries = 0
387+
const maxErrorRetries = 2 // 减少重试次数
388+
325389
hls.on(Hls.Events.ERROR, (event, data) => {
326-
console.error('HLS 错误:', data)
390+
// 只记录致命错误,减少控制台噪音
327391
if (data.fatal) {
392+
console.error('HLS 致命错误:', data.type, data.details)
393+
328394
switch (data.type) {
329395
case Hls.ErrorTypes.NETWORK_ERROR:
330-
console.log('网络错误,尝试恢复...')
331-
hls.startLoad()
396+
networkErrorRetries++
397+
398+
if (networkErrorRetries <= maxErrorRetries) {
399+
console.log(`网络错误恢复中... (${networkErrorRetries}/${maxErrorRetries})`)
400+
// 延迟重试,避免频繁请求
401+
setTimeout(() => {
402+
hls.startLoad()
403+
}, 1000 * networkErrorRetries) // 递增延迟
404+
} else {
405+
console.error('网络错误重试次数超限')
406+
Message.error('网络连接不稳定,请检查网络后重试')
407+
hls.destroy()
408+
}
332409
break
410+
333411
case Hls.ErrorTypes.MEDIA_ERROR:
334-
console.log('媒体错误,尝试恢复...')
335-
hls.recoverMediaError()
412+
mediaErrorRetries++
413+
414+
if (mediaErrorRetries <= maxErrorRetries) {
415+
console.log(`媒体错误恢复中... (${mediaErrorRetries}/${maxErrorRetries})`)
416+
setTimeout(() => {
417+
hls.recoverMediaError()
418+
}, 500 * mediaErrorRetries) // 递增延迟
419+
} else {
420+
console.error('媒体错误恢复次数超限')
421+
Message.error('视频解码错误,请尝试刷新页面')
422+
hls.destroy()
423+
}
336424
break
425+
337426
default:
338-
console.log('无法恢复的错误,销毁 HLS 实例')
427+
// 对于其他致命错误,不显示用户提示,只记录日志
428+
console.error('无法恢复的HLS错误:', data.details)
339429
hls.destroy()
340430
break
341431
}
432+
} else {
433+
// 非致命错误,只在调试模式下记录
434+
if (data.details !== 'bufferAppendError' && data.details !== 'bufferStalledError') {
435+
console.debug('HLS 非致命错误:', data.details)
436+
}
437+
438+
// 对于缓冲区错误,尝试自动恢复
439+
if (data.details === 'bufferStalledError') {
440+
console.debug('检测到缓冲停滞,自动处理中...')
441+
// HLS.js 会自动处理这类错误,无需手动干预
442+
}
342443
}
343444
})
445+
446+
// 监听缓冲区事件,用于性能优化
447+
hls.on(Hls.Events.BUFFER_APPENDED, () => {
448+
// 缓冲区数据追加成功,可以在这里做一些清理工作
449+
})
450+
451+
hls.on(Hls.Events.BUFFER_EOS, () => {
452+
console.debug('缓冲区到达流结束')
453+
})
454+
455+
// 监听缓冲区清理事件
456+
hls.on(Hls.Events.BUFFER_FLUSHED, () => {
457+
console.debug('缓冲区已清理')
458+
})
459+
460+
// 重置错误计数器(当播放成功时)
461+
hls.on(Hls.Events.FRAG_LOADED, () => {
462+
// 片段加载成功,重置错误计数
463+
if (networkErrorRetries > 0 || mediaErrorRetries > 0) {
464+
console.log('连接恢复正常,重置错误计数器')
465+
networkErrorRetries = 0
466+
mediaErrorRetries = 0
467+
}
468+
})
469+
470+
// 监听质量切换事件
471+
hls.on(Hls.Events.LEVEL_SWITCHED, (event, data) => {
472+
console.debug(`质量切换到: ${data.level}`)
473+
})
474+
475+
// 定期清理缓冲区,避免内存占用过多
476+
let bufferCleanupInterval = setInterval(() => {
477+
if (hls && video && !video.paused) {
478+
const currentTime = video.currentTime
479+
// 清理当前播放位置前15秒以外的缓冲区
480+
if (currentTime > 15) {
481+
try {
482+
hls.trigger(Hls.Events.BUFFER_FLUSHING, {
483+
startOffset: 0,
484+
endOffset: currentTime - 15,
485+
type: 'video'
486+
})
487+
} catch (e) {
488+
console.debug('缓冲区清理失败:', e)
489+
}
490+
}
491+
}
492+
}, 30000) // 每30秒清理一次
493+
494+
// 存储清理定时器,用于后续清理
495+
art.bufferCleanupInterval = bufferCleanupInterval
344496
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
345497
// Safari 原生支持 HLS
346498
video.src = url
@@ -412,6 +564,8 @@ const initArtPlayer = async (url) => {
412564
413565
art.on('video:loadstart', () => {
414566
console.log('开始加载视频')
567+
// 重置片头片尾跳过状态
568+
resetSkipState()
415569
})
416570
417571
art.on('video:canplay', () => {
@@ -426,10 +580,37 @@ const initArtPlayer = async (url) => {
426580
handleTimeUpdate()
427581
})
428582
583+
// 监听用户拖动进度条事件
584+
art.on('video:seeking', () => {
585+
onUserSeekStart()
586+
})
587+
588+
art.on('video:seeked', () => {
589+
onUserSeekEnd()
590+
})
591+
429592
art.on('video:playing', () => {
430593
console.log('视频开始播放')
431594
// 视频开始播放时,重置重连计数器
432595
resetRetryState()
596+
597+
// 立即尝试片头跳过(针对视频刚开始播放的情况)
598+
const immediateSkipped = applyIntroSkipImmediate()
599+
600+
// 如果立即跳过未执行,则使用常规跳过逻辑
601+
if (!immediateSkipped) {
602+
applySkipSettings()
603+
604+
// 为了确保片头跳过生效,再次检查(短延迟)
605+
setTimeout(() => {
606+
applySkipSettings()
607+
}, 50) // 减少延迟到50ms
608+
}
609+
})
610+
611+
// 监听全屏状态变化
612+
art.on('fullscreen', (isFullscreen) => {
613+
console.log('全屏状态变化:', isFullscreen)
433614
})
434615
435616
art.on('video:error', (err) => {
@@ -750,6 +931,7 @@ const selectEpisode = (episode) => {
750931
watch(() => props.videoUrl, async (newUrl) => {
751932
if (newUrl && props.visible) {
752933
resetRetryState() // 重置重连状态
934+
resetSkipState() // 重置片头片尾跳过状态
753935
await nextTick()
754936
await initArtPlayer(newUrl)
755937
}
@@ -803,6 +985,19 @@ onUnmounted(() => {
803985
804986
// 销毁播放器实例
805987
if (artPlayerInstance.value) {
988+
// 清理缓冲区清理定时器
989+
if (artPlayerInstance.value.bufferCleanupInterval) {
990+
clearInterval(artPlayerInstance.value.bufferCleanupInterval)
991+
artPlayerInstance.value.bufferCleanupInterval = null
992+
}
993+
994+
// 清理 HLS 实例
995+
if (artPlayerInstance.value.hls) {
996+
artPlayerInstance.value.hls.destroy()
997+
artPlayerInstance.value.hls = null
998+
}
999+
1000+
// 销毁播放器实例
8061001
artPlayerInstance.value.destroy()
8071002
artPlayerInstance.value = null
8081003
}

dashboard/src/components/players/VideoPlayer.vue

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,19 +403,31 @@ const initVideoPlayer = (url) => {
403403
404404
const handleLoadStart = () => {
405405
console.log('开始加载视频')
406+
// 重置片头片尾跳过状态
407+
resetSkipState()
408+
}
409+
410+
const handlePlaying = () => {
411+
console.log('视频开始播放')
412+
// 延迟一点应用跳过设置,确保视频已经开始播放
413+
setTimeout(() => {
414+
applySkipSettings()
415+
}, 100)
406416
}
407417
408418
// 移除之前的事件监听器(如果有)
409419
video.removeEventListener('loadedmetadata', handleLoadedMetadata)
410420
video.removeEventListener('error', handleError)
411421
video.removeEventListener('loadstart', handleLoadStart)
422+
video.removeEventListener('playing', handlePlaying)
412423
video.removeEventListener('timeupdate', handleTimeUpdate)
413424
video.removeEventListener('ended', handleVideoEnded)
414425
415426
// 添加新的事件监听器
416427
video.addEventListener('loadedmetadata', handleLoadedMetadata)
417428
video.addEventListener('error', handleError)
418429
video.addEventListener('loadstart', handleLoadStart)
430+
video.addEventListener('playing', handlePlaying)
419431
video.addEventListener('timeupdate', handleTimeUpdate)
420432
video.addEventListener('ended', handleVideoEnded)
421433
@@ -473,6 +485,7 @@ const handlePlayerTypeChange = (newType) => {
473485
// 监听视频URL变化
474486
watch(() => props.videoUrl, (newUrl) => {
475487
if (newUrl && props.visible) {
488+
resetSkipState() // 重置片头片尾跳过状态
476489
nextTick(() => {
477490
initVideoPlayer(newUrl)
478491
})

0 commit comments

Comments
 (0)