Browse Source

fix: stop play-url retry storm on HLS TLS failures

vpull HTTPS handshake fails with ERR_SSL_VERSION_OR_CIPHER_MISMATCH;
refreshing auth_key cannot fix it and only floods Network.
main
xiaosi 2 weeks ago
parent
commit
999fbf82fc
  1. 26
      src/components/LivePlayer.vue
  2. 52
      src/composables/useMonitorLive.js

26
src/components/LivePlayer.vue

@ -99,7 +99,9 @@ async function attachPlayer(url) {
video.addEventListener('error', () => { video.addEventListener('error', () => {
if (seq !== attachSeq) return if (seq !== attachSeq) return
emit('error')
const err = new Error('media error')
err.name = 'HlsError'
emit('error', err)
}, { once: true }) }, { once: true })
async function tryPlay() { async function tryPlay() {
@ -109,7 +111,7 @@ async function attachPlayer(url) {
} catch (error) { } catch (error) {
console.warn('[live-player] autoplay blocked', error) console.warn('[live-player] autoplay blocked', error)
// NotAllowedError: autoplay policy. Do not storm play-url rebuilds. // NotAllowedError: autoplay policy. Do not storm play-url rebuilds.
if (error?.name !== 'NotAllowedError' && seq === attachSeq) emit('error')
if (error?.name !== 'NotAllowedError' && seq === attachSeq) emit('error', error)
} }
} }
@ -127,10 +129,26 @@ async function attachPlayer(url) {
return return
} }
hls = new Hls({ enableWorker: true, lowLatencyMode: true })
hls = new Hls({
enableWorker: true,
lowLatencyMode: true,
manifestLoadingMaxRetry: 1,
levelLoadingMaxRetry: 1,
fragLoadingMaxRetry: 1,
})
hls.on(Hls.Events.ERROR, (_event, data) => { hls.on(Hls.Events.ERROR, (_event, data) => {
if (seq !== attachSeq) return if (seq !== attachSeq) return
if (data?.fatal) emit('error')
if (!data?.fatal) return
const detail = [data.type, data.details, data.response?.code, data.error?.message]
.filter(Boolean)
.join(' ')
const err = new Error(detail || 'hls fatal')
err.name = /ssl|tls|cipher|certificate|cors|mixed content|err_ssl/i.test(detail)
? 'SecurityError'
: 'HlsError'
console.warn('[live-player] hls fatal', data)
destroyPlayer()
emit('error', err)
}) })
hls.loadSource(url) hls.loadSource(url)
hls.attachMedia(video) hls.attachMedia(video)

52
src/composables/useMonitorLive.js

@ -14,6 +14,21 @@ const PLAY_RETRY_MAX = 3
const PLAY_EXPIRE_FALLBACK_SEC = 270 const PLAY_EXPIRE_FALLBACK_SEC = 270
const PLAY_REFRESH_LEAD_MS = 30_000 const PLAY_REFRESH_LEAD_MS = 30_000
function isFatalPlayMediaError(err) {
const msg = String(err?.message || err || '').toLowerCase()
const name = String(err?.name || '')
return (
name === 'SecurityError' ||
msg.includes('ssl') ||
msg.includes('tls') ||
msg.includes('cipher') ||
msg.includes('err_ssl') ||
msg.includes('mixed content') ||
msg.includes('cors') ||
msg.includes('certificate')
)
}
export function useMonitorLive({ getDockId, ui, getLivePlayer }) { export function useMonitorLive({ getDockId, ui, getLivePlayer }) {
const live = reactive({ const live = reactive({
session: null, session: null,
@ -59,6 +74,15 @@ export function useMonitorLive({ getDockId, ui, getLivePlayer }) {
live.phasePollStartedAt = 0 live.phasePollStartedAt = 0
} }
function markPlayFatal(message) {
window.clearTimeout(live.playRefreshTimer)
live.playRefreshTimer = null
live.playUrl = ''
live.playUrlExpiresAt = 0
live.playRetryCount = PLAY_RETRY_MAX
ui.toast(message)
}
function toggleLive() { function toggleLive() {
if (live.session) closeLive() if (live.session) closeLive()
else openLive() else openLive()
@ -197,21 +221,35 @@ export function useMonitorLive({ getDockId, ui, getLivePlayer }) {
async function onPlayError(err) { async function onPlayError(err) {
if (!live.session) return if (!live.session) return
// 播放器媒体面失败(TLS/CORS/HLS network):换 auth_key 没用,禁止刷 play-url
if (isFatalPlayMediaError(err) || err?.name === 'HlsError') {
markPlayFatal(
isFatalPlayMediaError(err)
? '播放域名 HTTPS 不可用(TLS/证书异常),请检查阿里云播放域名证书'
: (err?.message || '播放失败')
)
return
}
if (live.playRetryCount >= PLAY_RETRY_MAX) { if (live.playRetryCount >= PLAY_RETRY_MAX) {
ui.toast(err?.message || '播放失败')
live.playRetryCount = 0
markPlayFatal(err?.message || '播放失败')
return return
} }
live.playRetryCount += 1 live.playRetryCount += 1
try { try {
await refreshPlayURL({ fromRetry: true }) await refreshPlayURL({ fromRetry: true })
} catch (e) { } catch (e) {
if (live.playRetryCount >= PLAY_RETRY_MAX) {
ui.toast(e?.message || err?.message || '播放失败')
live.playRetryCount = 0
} else {
await onPlayError(e)
if (isFatalPlayMediaError(e) || e?.name === 'HlsError' || live.playRetryCount >= PLAY_RETRY_MAX) {
markPlayFatal(
isFatalPlayMediaError(e)
? '播放域名 HTTPS 不可用(TLS/证书异常),请检查阿里云播放域名证书'
: (e?.message || err?.message || '播放失败')
)
return
} }
await onPlayError(e)
} }
} }

Loading…
Cancel
Save