Browse Source

feat: cache media play-url by session and debounce open

main
xiaosi 2 weeks ago
parent
commit
78f387c890
  1. 81
      src/views/MediaView/MediaView.vue

81
src/views/MediaView/MediaView.vue

@ -102,13 +102,19 @@ const activeLive = reactive({
dockId: '', dockId: '',
sessionId: '', sessionId: '',
playUrl: '', playUrl: '',
playUrlExpiresAt: 0,
phase: '', phase: '',
leaseExpiresAt: 0, leaseExpiresAt: 0,
}) })
const PLAY_URL_FRESH_MS = 30_000
const PLAY_EXPIRE_FALLBACK_SEC = 270
let heartbeatTimer = null let heartbeatTimer = null
let openSeq = 0 let openSeq = 0
let disposed = false let disposed = false
let rejoinBusy = false let rejoinBusy = false
let playUrlPromise = null
let playUrlSessionId = ''
let opening = false
const LEASE_INVALID_CODE = 57006 const LEASE_INVALID_CODE = 57006
@ -144,14 +150,59 @@ function clearHeartbeat() {
function resetActiveLive() { function resetActiveLive() {
clearHeartbeat() clearHeartbeat()
playUrlPromise = null
playUrlSessionId = ''
activeLive.id = '' activeLive.id = ''
activeLive.dockId = '' activeLive.dockId = ''
activeLive.sessionId = '' activeLive.sessionId = ''
activeLive.playUrl = '' activeLive.playUrl = ''
activeLive.playUrlExpiresAt = 0
activeLive.phase = '' activeLive.phase = ''
activeLive.leaseExpiresAt = 0 activeLive.leaseExpiresAt = 0
} }
async function ensurePlayUrl(dockId, sessionId, { force = false } = {}) {
if (!dockId || !sessionId) return null
if (
!force &&
activeLive.sessionId === sessionId &&
activeLive.playUrl &&
activeLive.playUrlExpiresAt * 1000 - Date.now() > PLAY_URL_FRESH_MS
) {
return {
playUrl: activeLive.playUrl,
expiresAt: activeLive.playUrlExpiresAt,
}
}
if (playUrlPromise && playUrlSessionId === sessionId) {
return playUrlPromise
}
const seq = openSeq
playUrlSessionId = sessionId
playUrlPromise = getLivePlayURL(dockId, sessionId)
.then((play) => {
if (disposed || seq !== openSeq || activeLive.sessionId !== sessionId) return play
const nowSec = Math.floor(Date.now() / 1000)
const expiresAt = Number(play?.expiresAt) || nowSec + PLAY_EXPIRE_FALLBACK_SEC
if (play?.playUrl) {
activeLive.playUrl = play.playUrl
activeLive.playUrlExpiresAt = expiresAt
}
return play
})
.finally(() => {
if (playUrlSessionId === sessionId) {
playUrlPromise = null
playUrlSessionId = ''
}
})
return playUrlPromise
}
function scheduleHeartbeat(expiresAt) { function scheduleHeartbeat(expiresAt) {
clearHeartbeat() clearHeartbeat()
if (disposed || !activeLive.dockId || !activeLive.sessionId) return if (disposed || !activeLive.dockId || !activeLive.sessionId) return
@ -165,6 +216,18 @@ function scheduleHeartbeat(expiresAt) {
const result = await heartbeatLive(dockId, sessionId) const result = await heartbeatLive(dockId, sessionId)
if (disposed || activeLive.sessionId !== sessionId || activeLive.dockId !== dockId) return if (disposed || activeLive.sessionId !== sessionId || activeLive.dockId !== dockId) return
if (result?.session?.phase) activeLive.phase = result.session.phase if (result?.session?.phase) activeLive.phase = result.session.phase
if (result?.session?.phase === 'streaming' && !activeLive.playUrl) {
try {
await ensurePlayUrl(dockId, sessionId)
} catch (playErr) {
if (disposed || activeLive.sessionId !== sessionId) return
if (isLeaseInvalidError(playErr)) {
await rejoinAfterLeaseInvalid(streamId, dockId)
return
}
console.warn('[media] ensure play-url after heartbeat failed', playErr)
}
}
scheduleHeartbeat(result.leaseExpiresAt) scheduleHeartbeat(result.leaseExpiresAt)
} catch (e) { } catch (e) {
if (disposed || activeLive.sessionId !== sessionId || activeLive.dockId !== dockId) return if (disposed || activeLive.sessionId !== sessionId || activeLive.dockId !== dockId) return
@ -236,24 +299,23 @@ async function load() {
} }
async function openLive(stream) { async function openLive(stream) {
if (disposed || !stream?.dockId) return
if (disposed || !stream?.dockId || opening) return
if (activeLive.id === stream.id && activeLive.playUrl) { if (activeLive.id === stream.id && activeLive.playUrl) {
await closeActiveLive() await closeActiveLive()
return return
} }
// closeActiveLive bump openSeq使 in-flight join seq
await closeActiveLive() await closeActiveLive()
if (disposed) return
opening = true
const seq = ++openSeq const seq = ++openSeq
try { try {
const result = await joinLiveWithRetry(stream.dockId, {}, { const result = await joinLiveWithRetry(stream.dockId, {}, {
shouldContinue: () => seq === openSeq && !disposed,
shouldContinue: () => seq === openSeq && !disposed && opening,
}) })
const session = result?.session const session = result?.session
const sessionId = session?.id const sessionId = session?.id
// / viewer lease
if (seq !== openSeq || disposed) { if (seq !== openSeq || disposed) {
if (sessionId) await safeLeave(stream.dockId, sessionId) if (sessionId) await safeLeave(stream.dockId, sessionId)
return return
@ -269,6 +331,7 @@ async function openLive(stream) {
activeLive.sessionId = sessionId activeLive.sessionId = sessionId
activeLive.phase = session.phase || '' activeLive.phase = session.phase || ''
activeLive.playUrl = '' activeLive.playUrl = ''
activeLive.playUrlExpiresAt = 0
scheduleHeartbeat(result.leaseExpiresAt) scheduleHeartbeat(result.leaseExpiresAt)
if (session.phase !== 'streaming') { if (session.phase !== 'streaming') {
@ -276,11 +339,13 @@ async function openLive(stream) {
return return
} }
const play = await getLivePlayURL(stream.dockId, sessionId)
const play = await ensurePlayUrl(stream.dockId, sessionId)
if (seq !== openSeq || disposed || activeLive.sessionId !== sessionId) return if (seq !== openSeq || disposed || activeLive.sessionId !== sessionId) return
if (play?.playUrl && !play.playUrl.startsWith('fake://')) { if (play?.playUrl && !play.playUrl.startsWith('fake://')) {
if (!activeLive.playUrl) {
activeLive.playUrl = play.playUrl activeLive.playUrl = play.playUrl
} else {
}
} else if (!activeLive.playUrl) {
ui.toast('控制面已验证,当前未配置可播放媒体流') ui.toast('控制面已验证,当前未配置可播放媒体流')
} }
} catch (e) { } catch (e) {
@ -291,6 +356,8 @@ async function openLive(stream) {
} }
await closeActiveLive() await closeActiveLive()
ui.toast(e.message || '打开直播失败') ui.toast(e.message || '打开直播失败')
} finally {
opening = false
} }
} }

Loading…
Cancel
Save