pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/

persist caption across sources

Pas 314f9574 9009bf81

+47 -33
+5 -6
src/components/player/atoms/settings/CaptionsView.tsx
··· 294 294 const { t } = useTranslation(); 295 295 const lang = usePlayerStore((s) => s.caption.selected?.language); 296 296 const setCaption = usePlayerStore((s) => s.setCaption); 297 - const setCustomSubs = useSubtitleStore((s) => s.setCustomSubs); 297 + const setSubtitle = useSubtitleStore((s) => s.setSubtitle); 298 298 const fileInput = useRef<HTMLInputElement>(null); 299 299 const [error, setError] = useState<string | null>(null); 300 300 ··· 315 315 srtData: converted, 316 316 id: "custom-caption", 317 317 }); 318 - setCustomSubs(); 318 + setSubtitle(true, "custom", "custom-caption"); 319 319 } catch (err) { 320 320 setError( 321 321 err instanceof Error ··· 370 370 export function PasteCaptionOption(props: { selected?: boolean }) { 371 371 const { t } = useTranslation(); 372 372 const setCaption = usePlayerStore((s) => s.setCaption); 373 - const setCustomSubs = useSubtitleStore((s) => s.setCustomSubs); 373 + const setSubtitle = useSubtitleStore((s) => s.setSubtitle); 374 374 const setDelay = useSubtitleStore((s) => s.setDelay); 375 375 const [isLoading, setIsLoading] = useState(false); 376 376 const [error, setError] = useState<string | null>(null); ··· 409 409 srtData: converted, 410 410 id: "pasted-caption", 411 411 }); 412 - setCustomSubs(); 412 + setSubtitle(true, parsedData.language, "pasted-caption"); 413 413 414 414 // Set delay if included in the pasted data, otherwise reset to 0 415 415 if (parsedData.delay !== undefined) { ··· 476 476 ); 477 477 const delay = useSubtitleStore((s) => s.delay); 478 478 const appLanguage = useLanguageStore((s) => s.language); 479 - const setCustomSubs = useSubtitleStore((s) => s.setCustomSubs); 479 + const setSubtitle = useSubtitleStore((s) => s.setSubtitle); 480 480 const matchScore = useCaptionMatchScore(); 481 481 482 482 // Get combined caption list ··· 580 580 srtData: converted, 581 581 id: "custom-caption", 582 582 }); 583 - setCustomSubs(); 584 583 } catch (err) { 585 584 // Silently fail on drop - user can use the upload button for better error feedback 586 585 }
+20 -6
src/components/player/hooks/useCaptions.ts
··· 13 13 } from "../utils/captions"; 14 14 15 15 export function useCaptions() { 16 - const setLanguage = useSubtitleStore((s) => s.setLanguage); 16 + const setSubtitle = useSubtitleStore((s) => s.setSubtitle); 17 17 const enabled = useSubtitleStore((s) => s.enabled); 18 18 const resetSubtitleSpecificSettings = useSubtitleStore( 19 19 (s) => s.resetSubtitleSpecificSettings, ··· 21 21 const setCaption = usePlayerStore((s) => s.setCaption); 22 22 const currentTranslateTask = usePlayerStore((s) => s.caption.translateTask); 23 23 const lastSelectedLanguage = useSubtitleStore((s) => s.lastSelectedLanguage); 24 + const lastSelectedSubtitleId = useSubtitleStore( 25 + (s) => s.lastSelectedSubtitleId, 26 + ); 24 27 const setIsOpenSubtitles = useSubtitleStore((s) => s.setIsOpenSubtitles); 25 28 26 29 const captionList = usePlayerStore((s) => s.captionList); ··· 53 56 resetSubtitleSpecificSettings(); 54 57 } 55 58 56 - setLanguage(caption.language); 59 + setSubtitle(true, caption.language, caption.id); 57 60 58 61 // Use native tracks for MP4 streams instead of custom rendering 59 62 if (source?.type === "file" && enableNativeSubtitles) { ··· 65 68 }, 66 69 [ 67 70 setIsOpenSubtitles, 68 - setLanguage, 71 + setSubtitle, 69 72 setCaption, 70 73 resetSubtitleSpecificSettings, 71 74 source, ··· 135 138 const disable = useCallback(async () => { 136 139 setIsOpenSubtitles(false); 137 140 setCaption(null); 138 - setLanguage(null); 139 - }, [setCaption, setLanguage, setIsOpenSubtitles]); 141 + setSubtitle(false); 142 + }, [setCaption, setSubtitle, setIsOpenSubtitles]); 140 143 141 144 const selectLastUsedLanguage = useCallback(async () => { 145 + if (lastSelectedSubtitleId) { 146 + const caption = captions.find((v) => v.id === lastSelectedSubtitleId); 147 + if (caption) return selectCaptionById(caption.id); 148 + } 149 + 142 150 const language = lastSelectedLanguage ?? "en"; 143 151 await selectLanguage(language); 144 152 return true; 145 - }, [lastSelectedLanguage, selectLanguage]); 153 + }, [ 154 + lastSelectedLanguage, 155 + selectLanguage, 156 + lastSelectedSubtitleId, 157 + captions, 158 + selectCaptionById, 159 + ]); 146 160 147 161 const toggleLastUsed = useCallback(async () => { 148 162 if (enabled) disable();
+1 -5
src/components/player/hooks/useInitializePlayer.ts
··· 26 26 ); 27 27 const { selectLastUsedLanguageIfEnabled } = useCaptions(); 28 28 29 - // Only select subtitles on initial load, not when source changes 30 - const hasInitializedRef = useRef(false); 31 - 32 29 useEffect(() => { 33 - if (sourceIdentifier && !hasInitializedRef.current) { 34 - hasInitializedRef.current = true; 30 + if (sourceIdentifier) { 35 31 selectLastUsedLanguageIfEnabled(); 36 32 } 37 33 }, [sourceIdentifier, selectLastUsedLanguageIfEnabled]);
+21 -16
src/stores/subtitles/index.ts
··· 59 59 }; 60 60 enabled: boolean; 61 61 lastSelectedLanguage: string | null; 62 + lastSelectedSubtitleId: string | null; 62 63 isOpenSubtitles: boolean; 63 64 styling: SubtitleStyling; 64 65 overrideCasing: boolean; ··· 66 67 showDelayIndicator: boolean; 67 68 updateStyling(newStyling: Partial<SubtitleStyling>): void; 68 69 resetStyling(): void; 69 - setLanguage(language: string | null): void; 70 + setSubtitle( 71 + enabled: boolean, 72 + language?: string | null, 73 + subtitleId?: string | null, 74 + ): void; 70 75 setIsOpenSubtitles(isOpenSubtitles: boolean): void; 71 - setCustomSubs(): void; 72 76 setOverrideCasing(enabled: boolean): void; 73 77 setDelay(delay: number): void; 74 78 importSubtitleLanguage(lang: string | null): void; ··· 84 88 lastSelectedLanguage: null, 85 89 }, 86 90 lastSelectedLanguage: null, 91 + lastSelectedSubtitleId: null, 87 92 isOpenSubtitles: false, 88 93 overrideCasing: false, 89 94 delay: 0, ··· 105 110 s.overrideCasing = false; 106 111 }); 107 112 }, 113 + setIsOpenSubtitles(isOpenSubtitles) { 114 + set((s) => { 115 + s.isOpenSubtitles = isOpenSubtitles; 116 + }); 117 + }, 108 118 updateStyling(newStyling) { 109 119 set((s) => { 110 120 if (newStyling.backgroundOpacity !== undefined) ··· 153 163 }; 154 164 }); 155 165 }, 156 - setLanguage(lang) { 166 + setSubtitle(enabled, language, subtitleId) { 157 167 set((s) => { 158 - s.enabled = !!lang; 159 - if (lang) s.lastSelectedLanguage = lang; 160 - }); 161 - }, 162 - setIsOpenSubtitles(isOpenSubtitles) { 163 - set((s) => { 164 - s.isOpenSubtitles = isOpenSubtitles; 165 - }); 166 - }, 167 - setCustomSubs() { 168 - set((s) => { 169 - s.enabled = true; 170 - s.lastSelectedLanguage = null; 168 + s.enabled = enabled; 169 + if (enabled) { 170 + s.lastSelectedLanguage = language ?? null; 171 + s.lastSelectedSubtitleId = subtitleId ?? null; 172 + } else { 173 + s.lastSelectedLanguage = null; 174 + s.lastSelectedSubtitleId = null; 175 + } 171 176 }); 172 177 }, 173 178 setOverrideCasing(enabled) {