Browse Source

fix(live): keep HLS playback near the live edge

Normal 6-12s HLS kept the hls.js defaults (3 fragments back, no max
latency), so playback drifted far behind the edge. Target two
fragments, cap at four, and add a latency probe that snaps back when
past 20s.
main
xiaosi 1 month ago
parent
commit
d179b6d3d9
  1. 36
      src/components/LivePlayer.vue

36
src/components/LivePlayer.vue

@ -46,6 +46,11 @@ import { computed, onBeforeUnmount, ref, watch } from 'vue'
const STARTUP_RETRY_DELAYS_MS = [500, 1000, 2000, 4000, 6000, 8000]
const STARTUP_WINDOW_MS = 30_000
// 落后边缘的分片数上限;必须 > LIVE_SYNC_DURATION_COUNT,否则 hls.js 直接抛错
const LIVE_SYNC_DURATION_COUNT = 2
const LIVE_MAX_LATENCY_DURATION_COUNT = 4
// 落后超过该秒数时强制跳到直播边缘(hls.js 内置 synchronizeToLiveEdge 的上限)
const LIVE_EDGE_HARD_LIMIT_SEC = 20
const props = defineProps({
playUrl: { type: String, default: '' },
@ -199,6 +204,22 @@ function scheduleSameUrlRetry(url, seq, httpCode = 404) {
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) {
clearMediaListeners()
const onPlay = () => {
@ -328,10 +349,18 @@ async function attachPlayer(url, { seq: existingSeq } = {}) {
return
}
// 现网为普通 6–12s HLS,不是 LL-HLS;放宽分片重试,启动 404 仍由同 URL 窗口控制
// 现网为普通 6–12s HLS(非 LL-HLS):lowLatencyMode 无 partHoldBack 可用,
// 靠 liveSyncDurationCount/liveMaxLatencyDurationCount 追边,重建后也能贴近边缘。
hls = new Hls({
enableWorker: true,
// 非 LL-HLS 下 true 无收益(partHoldBack=0 时回落 holdBack),保持关闭
lowLatencyMode: false,
liveSyncDurationCount: LIVE_SYNC_DURATION_COUNT,
liveMaxLatencyDurationCount: LIVE_MAX_LATENCY_DURATION_COUNT,
maxLiveSyncPlaybackRate: 1.5,
maxBufferLength: 8,
// 0 会被内部抬到 1×targetduration,且卡顿后无法回退;留 30s 回退窗
backBufferLength: 30,
manifestLoadingMaxRetry: 2,
manifestLoadingRetryDelay: 1000,
levelLoadingMaxRetry: 2,
@ -409,6 +438,11 @@ async function attachPlayer(url, { seq: existingSeq } = {}) {
video.muted = true
tryPlay()
})
// 观测落后直播边缘的秒数;重建后按需强制贴边
hls.on(Hls.Events.LEVEL_UPDATED, () => {
if (seq !== attachSeq) return
logLiveLatency()
})
} catch (_) {
if (seq === attachSeq) emitFatal(buildPlayError({ message: 'hls init failed' }))
}

Loading…
Cancel
Save