@ -46,6 +46,11 @@ import { computed, onBeforeUnmount, ref, watch } from 'vue'
const STARTUP_RETRY_DELAYS_MS = [ 500 , 1000 , 2000 , 4000 , 6000 , 8000 ]
const STARTUP_RETRY_DELAYS_MS = [ 500 , 1000 , 2000 , 4000 , 6000 , 8000 ]
const STARTUP_WINDOW_MS = 30 _000
const STARTUP_WINDOW_MS = 30 _000
/ / 落 后 边 缘 的 分 片 数 上 限 ; 必 须 > L I V E _ S Y N C _ D U R A T I O N _ C O U N T , 否 则 h l s . j s 直 接 抛 错
const LIVE_SYNC_DURATION_COUNT = 2
const LIVE_MAX_LATENCY_DURATION_COUNT = 4
/ / 落 后 超 过 该 秒 数 时 强 制 跳 到 直 播 边 缘 ( h l s . j s 内 置 s y n c h r o n i z e T o L i v e E d g e 的 上 限 )
const LIVE_EDGE_HARD_LIMIT_SEC = 20
const props = defineProps ( {
const props = defineProps ( {
playUrl : { type : String , default : '' } ,
playUrl : { type : String , default : '' } ,
@ -199,6 +204,22 @@ function scheduleSameUrlRetry(url, seq, httpCode = 404) {
return true
return true
}
}
function logLiveLatency ( ) {
if ( ! hls ) return
const latency = Number ( hls . latency )
if ( ! Number . isFinite ( latency ) || latency <= 0 ) return
if ( latency > LIVE_EDGE_HARD_LIMIT_SEC ) {
console . warn ( ` [live-player] 落后直播边缘 ${ latency . toFixed ( 1 ) } s,强制贴边 ` )
const video = videoEl . value
const syncPosition = hls . liveSyncPosition
if ( video && Number . isFinite ( syncPosition ) ) {
video . currentTime = syncPosition
}
return
}
console . debug ( ` [live-player] 落后直播边缘 ${ latency . toFixed ( 1 ) } s ` )
}
function bindMediaListeners ( video , seq ) {
function bindMediaListeners ( video , seq ) {
clearMediaListeners ( )
clearMediaListeners ( )
const onPlay = ( ) => {
const onPlay = ( ) => {
@ -328,10 +349,18 @@ async function attachPlayer(url, { seq: existingSeq } = {}) {
return
return
}
}
/ / 现 网 为 普 通 6 – 1 2 s H L S , 不 是 L L - H L S ; 放 宽 分 片 重 试 , 启 动 4 0 4 仍 由 同 U R L 窗 口 控 制
/ / 现 网 为 普 通 6 – 1 2 s H L S ( 非 L L - H L S ) : l o w L a t e n c y M o d e 无 p a r t H o l d B a c k 可 用 ,
/ / 靠 l i v e S y n c D u r a t i o n C o u n t / l i v e M a x L a t e n c y D u r a t i o n C o u n t 追 边 , 重 建 后 也 能 贴 近 边 缘 。
hls = new Hls ( {
hls = new Hls ( {
enableWorker : true ,
enableWorker : true ,
/ / 非 L L - H L S 下 t r u e 无 收 益 ( p a r t H o l d B a c k = 0 时 回 落 h o l d B a c k ) , 保 持 关 闭
lowLatencyMode : false ,
lowLatencyMode : false ,
liveSyncDurationCount : LIVE_SYNC_DURATION_COUNT ,
liveMaxLatencyDurationCount : LIVE_MAX_LATENCY_DURATION_COUNT ,
maxLiveSyncPlaybackRate : 1.5 ,
maxBufferLength : 8 ,
/ / 0 会 被 内 部 抬 到 1 × t a r g e t d u r a t i o n , 且 卡 顿 后 无 法 回 退 ; 留 3 0 s 回 退 窗
backBufferLength : 30 ,
manifestLoadingMaxRetry : 2 ,
manifestLoadingMaxRetry : 2 ,
manifestLoadingRetryDelay : 1000 ,
manifestLoadingRetryDelay : 1000 ,
levelLoadingMaxRetry : 2 ,
levelLoadingMaxRetry : 2 ,
@ -409,6 +438,11 @@ async function attachPlayer(url, { seq: existingSeq } = {}) {
video . muted = true
video . muted = true
tryPlay ( )
tryPlay ( )
} )
} )
/ / 观 测 落 后 直 播 边 缘 的 秒 数 ; 重 建 后 按 需 强 制 贴 边
hls . on ( Hls . Events . LEVEL_UPDATED , ( ) => {
if ( seq !== attachSeq ) return
logLiveLatency ( )
} )
} catch ( _ ) {
} catch ( _ ) {
if ( seq === attachSeq ) emitFatal ( buildPlayError ( { message : 'hls init failed' } ) )
if ( seq === attachSeq ) emitFatal ( buildPlayError ( { message : 'hls init failed' } ) )
}
}