From 253af91f46da11f17f1b723c9dc13c7313555154 Mon Sep 17 00:00:00 2001 From: lashman Date: Sat, 6 Jun 2026 22:21:48 +0300 Subject: [PATCH] use direct stream for resume --- src/hooks/use-jellyfin.ts | 18 +++++++++++++++++- src/pages/PlayerPage.tsx | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/hooks/use-jellyfin.ts b/src/hooks/use-jellyfin.ts index 96b9dda..7d50ec8 100644 --- a/src/hooks/use-jellyfin.ts +++ b/src/hooks/use-jellyfin.ts @@ -622,6 +622,15 @@ export function useSimilarItems(itemId?: string, limit = 12) { * has to load first so the first PlaybackInfo request includes the * saved StartTimeTicks - otherwise the video streams from 0 and * reloads mid-playback when the second query returns. + * + * `requireDirectStream` is used for resume: the direct-play + * /Videos/{id}/stream endpoint with `static=true` returns the file + * from byte 0 even when StartTimeTicks is passed, because Chromium + * / WebView2 don't send a Range header for the initial GET. The + * direct-stream path has the server read the file from the start + * position itself and stream the bytes, so the response naturally + * starts at the saved offset. Direct-stream is not transcoding - + * it's just server-side file I/O with no encoding work. */ export function usePlaybackInfo( itemId?: string, @@ -629,6 +638,7 @@ export function usePlaybackInfo( audioStreamIndex?: number, maxStreamingBitrate?: number, enabled: boolean = true, + requireDirectStream: boolean = false, ) { const api = useApi() const audioPassthrough = usePreferencesStore(s => s.audioPassthrough) @@ -640,6 +650,7 @@ export function usePlaybackInfo( startTimeTicks, audioStreamIndex, maxStreamingBitrate, + requireDirectStream, ], queryFn: async () => { if (!api || !itemId) return null @@ -657,7 +668,12 @@ export function usePlaybackInfo( AudioStreamIndex: audioStreamIndex, DeviceProfile: await browserDeviceProfile(audioPassthrough) as any, AutoOpenLiveStream: true, - EnableDirectPlay: true, + // For resume, prefer direct-stream so the server starts the + // read at StartTimeTicks. Direct-play can't honour start + // time on the initial GET. Otherwise let the server pick + // direct-play for the no-resume case so the browser plays + // the file as-is. + EnableDirectPlay: !requireDirectStream, EnableDirectStream: true, EnableTranscoding: true, AllowVideoStreamCopy: true, diff --git a/src/pages/PlayerPage.tsx b/src/pages/PlayerPage.tsx index 9da389c..ebed0c0 100644 --- a/src/pages/PlayerPage.tsx +++ b/src/pages/PlayerPage.tsx @@ -331,6 +331,7 @@ export default function PlayerPage() { streamAudioIndex ?? undefined, maxBitrate, playbackInfoReady, + startTimeTicks !== undefined, ) const resolvedSource = playbackInfo?.MediaSources?.[0] const streamUrl = (() => {