show resume prompt for ?resume=true and reset on item change
This commit is contained in:
+16
-39
@@ -361,35 +361,21 @@ export default function PlayerPage() {
|
|||||||
transcodingUrlHasRuntimeTicks: hasRuntimeTicks,
|
transcodingUrlHasRuntimeTicks: hasRuntimeTicks,
|
||||||
streamUrl: streamUrl.replace(/api_key=[^&]+/, 'api_key=***'),
|
streamUrl: streamUrl.replace(/api_key=[^&]+/, 'api_key=***'),
|
||||||
})
|
})
|
||||||
if (transcodingUrl && !hasRuntimeTicks) {
|
|
||||||
const fullUrl = `${serverUrl}${transcodingUrl}`
|
|
||||||
fetch(fullUrl, { headers: { Authorization: `MediaBrowser Token=${token}` } })
|
|
||||||
.then(r => r.text())
|
|
||||||
.then(text => {
|
|
||||||
const lines = text.split(/\r?\n/)
|
|
||||||
const variantLine = lines.find(l => l.endsWith('.m3u8') && !l.startsWith('#'))
|
|
||||||
console.log('[player] hls master playlist', text)
|
|
||||||
if (variantLine) {
|
|
||||||
const variantUrl = variantLine.startsWith('http')
|
|
||||||
? variantLine
|
|
||||||
: new URL(variantLine, fullUrl).toString()
|
|
||||||
return fetch(variantUrl, { headers: { Authorization: `MediaBrowser Token=${token}` } }).then(r => r.text())
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
.then(variantText => {
|
|
||||||
if (!variantText) return
|
|
||||||
const lines = variantText.split(/\r?\n/)
|
|
||||||
const segmentLines = lines.filter(l => /\.(ts|m4s|mp4)(\?|$)/.test(l))
|
|
||||||
console.log('[player] hls variant playlist (first 20 lines)', lines.slice(0, 20))
|
|
||||||
console.log('[player] hls variant first 6 segment lines', segmentLines.slice(0, 6))
|
|
||||||
console.log('[player] hls variant segments have runtimeTicks', segmentLines.some(l => l.includes('runtimeTicks=')))
|
|
||||||
console.log('[player] hls variant segments have StartTimeTicks', segmentLines.some(l => l.includes('StartTimeTicks=')))
|
|
||||||
})
|
|
||||||
.catch(e => console.warn('[player] hls playlist fetch failed', e))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Resume prompt: show on first mount when there's a saved position
|
||||||
|
* past the threshold AND the user wants the prompt. The prompt auto-
|
||||||
|
* confirms after a few seconds, which queues the deferred seek via
|
||||||
|
* pendingSeekRef and seeks the underlying HTMLMediaElement directly
|
||||||
|
* (vidstack's MediaPlayerInstance.currentTime has a known bug on
|
||||||
|
* direct-play MP4 sources that restarts the video from 0).
|
||||||
|
* Intentionally scoped to item?.Id only - we only want to evaluate
|
||||||
|
* the resume condition once per item, not re-trigger when item data
|
||||||
|
* refreshes or prefs change mid-playback. */
|
||||||
|
const resumePromptShownRef = useRef<string | null>(null)
|
||||||
|
const resumeItemId = item?.Id
|
||||||
|
const resumePositionTicks = item?.UserData?.PlaybackPositionTicks
|
||||||
|
|
||||||
/* Reset transient flags on item change */
|
/* Reset transient flags on item change */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setUpNextDismissed(false)
|
setUpNextDismissed(false)
|
||||||
@@ -398,29 +384,20 @@ export default function PlayerPage() {
|
|||||||
setStreamAudioIndex(null)
|
setStreamAudioIndex(null)
|
||||||
setEndCardOpen(false)
|
setEndCardOpen(false)
|
||||||
pendingSeekRef.current = null
|
pendingSeekRef.current = null
|
||||||
|
resumePromptShownRef.current = null
|
||||||
usePlayerRuntimeStore.getState().resetForNewItem()
|
usePlayerRuntimeStore.getState().resetForNewItem()
|
||||||
}, [id, setPanel])
|
}, [id, setPanel])
|
||||||
|
|
||||||
/* Resume prompt: show on first mount when there's a saved position
|
|
||||||
* past the threshold AND the user wants the prompt AND the URL didn't
|
|
||||||
* already specify ?resume=true (queue navigation path).
|
|
||||||
* Intentionally scoped to item?.Id only - we only want to evaluate
|
|
||||||
* the resume condition once per item, not re-trigger when item data
|
|
||||||
* refreshes or prefs change mid-playback. */
|
|
||||||
const resumePromptShownRef = useRef<string | null>(null)
|
|
||||||
const resumeItemId = item?.Id
|
|
||||||
const resumePositionTicks = item?.UserData?.PlaybackPositionTicks
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!resumeItemId || resumePromptShownRef.current === resumeItemId) return
|
if (!resumeItemId || resumePromptShownRef.current === resumeItemId) return
|
||||||
const pos = Number(resumePositionTicks ?? 0)
|
const pos = Number(resumePositionTicks ?? 0)
|
||||||
const thresholdSec = usePreferencesStore.getState().resumeThresholdSec ?? 5
|
const thresholdSec = usePreferencesStore.getState().resumeThresholdSec ?? 5
|
||||||
const threshold = thresholdSec * 10_000_000
|
const threshold = thresholdSec * 10_000_000
|
||||||
const fromQueue = searchParams.get('resume') === 'true'
|
if (showResumePromptPref && pos > threshold) {
|
||||||
if (showResumePromptPref && !fromQueue && pos > threshold) {
|
|
||||||
setResumePromptOpen(true)
|
setResumePromptOpen(true)
|
||||||
resumePromptShownRef.current = resumeItemId
|
resumePromptShownRef.current = resumeItemId
|
||||||
}
|
}
|
||||||
}, [resumeItemId, resumePositionTicks, searchParams, showResumePromptPref])
|
}, [resumeItemId, resumePositionTicks, showResumePromptPref])
|
||||||
|
|
||||||
/* Auto-rewatch counter: when an already-played item starts playing
|
/* Auto-rewatch counter: when an already-played item starts playing
|
||||||
* again, record the rewatch. We trip it at most once per item-mount
|
* again, record the rewatch. We trip it at most once per item-mount
|
||||||
|
|||||||
Reference in New Issue
Block a user