@@ -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) => {
750931watch (() => 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 }
0 commit comments