diff --git a/src/pages/PlayerPage.tsx b/src/pages/PlayerPage.tsx index b7e94e3..63e4d1c 100644 --- a/src/pages/PlayerPage.tsx +++ b/src/pages/PlayerPage.tsx @@ -361,35 +361,21 @@ export default function PlayerPage() { transcodingUrlHasRuntimeTicks: hasRuntimeTicks, 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(null) + const resumeItemId = item?.Id + const resumePositionTicks = item?.UserData?.PlaybackPositionTicks + /* Reset transient flags on item change */ useEffect(() => { setUpNextDismissed(false) @@ -398,29 +384,20 @@ export default function PlayerPage() { setStreamAudioIndex(null) setEndCardOpen(false) pendingSeekRef.current = null + resumePromptShownRef.current = null usePlayerRuntimeStore.getState().resetForNewItem() }, [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(null) - const resumeItemId = item?.Id - const resumePositionTicks = item?.UserData?.PlaybackPositionTicks useEffect(() => { if (!resumeItemId || resumePromptShownRef.current === resumeItemId) return const pos = Number(resumePositionTicks ?? 0) const thresholdSec = usePreferencesStore.getState().resumeThresholdSec ?? 5 const threshold = thresholdSec * 10_000_000 - const fromQueue = searchParams.get('resume') === 'true' - if (showResumePromptPref && !fromQueue && pos > threshold) { + if (showResumePromptPref && pos > threshold) { setResumePromptOpen(true) resumePromptShownRef.current = resumeItemId } - }, [resumeItemId, resumePositionTicks, searchParams, showResumePromptPref]) + }, [resumeItemId, resumePositionTicks, showResumePromptPref]) /* Auto-rewatch counter: when an already-played item starts playing * again, record the rewatch. We trip it at most once per item-mount