diff --git a/src/App.tsx b/src/App.tsx index 9031569..f4335b9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -49,6 +49,7 @@ export default function App() { const [search, setSearch] = useState(""); const [downloadedOnly, setDownloadedOnly] = useState(() => remembered("downloadedOnly")); const [hideShorts, setHideShorts] = useState(() => remembered("hideShorts")); + const [autoplayNext, setAutoplayNext] = useState(() => remembered("autoplayNext")); const [sidebarHidden, setSidebarHidden] = useState(() => remembered("sidebarHidden")); const [sidebarPeek, setSidebarPeek] = useState(false); const [view, setView] = useState(() => { @@ -161,12 +162,13 @@ export default function App() { localStorage.setItem("flighttube.browser", browser); localStorage.setItem("flighttube.downloadedOnly", downloadedOnly ? "1" : "0"); localStorage.setItem("flighttube.hideShorts", hideShorts ? "1" : "0"); + localStorage.setItem("flighttube.autoplayNext", autoplayNext ? "1" : "0"); localStorage.setItem("flighttube.sidebarHidden", sidebarHidden ? "1" : "0"); } catch { /* storage blocked */ } }, [view, quality, bulkLimit, streamQuality, subLang, subStyle, browser, downloadedOnly, - hideShorts, sidebarHidden]); + hideShorts, autoplayNext, sidebarHidden]); // The language a download embeds. Like the player's fetch, this does not // depend on the on/off preference: that says what is shown, and a file @@ -624,6 +626,7 @@ export default function App() { onSubLang={setSubLang} subStyle={subStyle} onSubStyle={setSubStyle} + autoplayNext={autoplayNext} onOpenChannel={() => { setChannelId(playing.item.channel_id); setSearch(""); @@ -683,6 +686,8 @@ export default function App() { onSubLang={setSubLang} hideShorts={hideShorts} onHideShorts={setHideShorts} + autoplayNext={autoplayNext} + onAutoplayNext={setAutoplayNext} browser={browser} onBrowser={setBrowser} onError={setFailure} diff --git a/src/components/Player.tsx b/src/components/Player.tsx index 9905359..a22e4a5 100644 --- a/src/components/Player.tsx +++ b/src/components/Player.tsx @@ -27,6 +27,8 @@ interface Props { onSubStyle: (s: SubStyle) => void; /** Leaves the player for this video's channel. */ onOpenChannel: () => void; + /** Roll straight on to the next video when this one ends. */ + autoplayNext: boolean; /** Persists a subtitle choice made from the transport bar. */ onSubLang: (l: string) => void; /** Position in the current feed, for the "3 of 180" readout. */ @@ -122,7 +124,7 @@ function placeCues(vtt: string, line: number | null): string { export default function Player({ item, path, onClose, onDelete, onPrev, onNext, onDownload, downloading, - maxHeight, subLang, onSubLang, subStyle, onSubStyle, onOpenChannel, + maxHeight, subLang, onSubLang, subStyle, onSubStyle, onOpenChannel, autoplayNext, index, total, titleBarInset, }: Props) { const streaming = path === null; @@ -457,6 +459,12 @@ export default function Player({ onTimeUpdate={onTimeUpdate} onLoadedMetadata={onLoadedMetadata} onPause={persist} + onEnded={() => { + persist(); + // Only where there is somewhere to go: the last video in the + // list simply stops. + if (autoplayNext) onNext?.(); + }} onLoadStart={() => setBuffering(true)} onWaiting={() => setBuffering(true)} onStalled={() => setBuffering(true)} diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index f95b9ce..62ff155 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -30,6 +30,8 @@ interface Props { onSubLang: (l: string) => void; hideShorts: boolean; onHideShorts: (v: boolean) => void; + autoplayNext: boolean; + onAutoplayNext: (v: boolean) => void; browser: string; onBrowser: (b: string) => void; onError: (message: string) => void; @@ -58,6 +60,7 @@ export default function Settings({ onClose, onImported, appearance, onAppearance, quality, onQuality, bulkLimit, onBulkLimit, streamQuality, onStreamQuality, subLang, onSubLang, hideShorts, onHideShorts, + autoplayNext, onAutoplayNext, browser, onBrowser, onError, }: Props) { const [prereqs, setPrereqs] = useState(null); @@ -301,6 +304,24 @@ export default function Settings({

+
+ Playback + +

+ When a video ends, the next one in the list starts on its own. Meant for + offline viewing, where it plays through your downloads one after another; + online it will stream the next video, and it stops at the end of the list. +

+
+
Sign in to YouTube