feat: subtitles while streaming, and fetched when a download lacks them

YouTube's HLS manifest carries a dozen audio renditions and no
subtitles whatsoever — checked against a live master playlist: 16
EXT-X-MEDIA entries, all TYPE=AUDIO, zero SUBTITLES. So the track menu
could only ever list audio while streaming, which is what it did.

Subtitles are now fetched separately with yt-dlp, tidied through the
existing VTT cleanup, and cached per video and language. They reach the
player as blob URLs, which share the document's origin — a file:// or
127.0.0.1 track would be cross-origin to the page and need CORS the
media pipeline cannot supply. The same path fills in a download saved
before subtitles were switched on, without fetching the video again.

YouTube serves identical auto-generated captions under both "en" and
"en-orig", so byte-identical texts collapse to one entry rather than
offering the same track twice, and tracks are labelled "English"
rather than "en".

The subtitle preference now defaults to English. "None" is a poor
default for a setting whose whole purpose is captions: it silently
means no subtitles are downloaded, fetched, or offered anywhere, and
the Settings text now says so.
This commit is contained in:
vincent
2026-08-29 16:25:26 +02:00
parent 66cb80a044
commit 5b2be50852
10 changed files with 221 additions and 12 deletions
+5
View File
@@ -54,6 +54,11 @@ export const updateYtDlp = () => invoke<string>("update_yt_dlp");
export const listSubtitles = (videoId: string) =>
invoke<Array<[string, string]>>("list_subtitles", { videoId });
/** Subtitles as (language, WebVTT text) — fetched and cached when there are
* none beside the video. Streaming has no other source. */
export const fetchSubtitles = (videoId: string, lang: string) =>
invoke<Array<[string, string]>>("fetch_subtitles", { videoId, lang });
export const savePlayback = (videoId: string, position: number, duration: number) =>
invoke<void>("save_playback", { videoId, position, duration });