feat: channel failures, updater, and a tidier Settings
Failing channels are visible instead of buried in a toast count. Each refresh records its outcome per channel, the sidebar marks the failures and counts them in its heading, and selecting one explains why above its videos. Yours turn out to be three channels returning HTTP 404 — removed or renamed on YouTube. Video lengths now fill for what is on screen. Filling the newest across all subscriptions meant a channel's videos stayed blank forever, since the global newest always won the queue. yt-dlp can be updated from Settings, which also says whether it is current. It breaks whenever YouTube changes something, so it lands in app data and takes precedence over the bundled copy; ffmpeg is stable and ships with each release, so it is shown but not updated. Also: 'Downloaded only' is now 'Local'; Hide Shorts moved to Settings; the seven-step Takeout guide moved behind a button, since it dominated the panel; the player names the height it is actually streaming, which changes as an adaptive stream switches rendition; the player's delete is an icon; and the window minimum drops to 1080 now that the control row has one fewer button, while still never wrapping.
This commit is contained in:
@@ -107,6 +107,10 @@ export default function Player({
|
||||
const [src, setSrc] = useState<string | null>(path ? fileUrl(path) : null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [buffering, setBuffering] = useState(true);
|
||||
// The height actually being decoded. With an adaptive stream this changes as
|
||||
// the player switches rendition, so it is read from the element rather than
|
||||
// assumed from the setting.
|
||||
const [height, setHeight] = useState(0);
|
||||
// WebVTT files yt-dlp saved next to a download, so subtitles work offline.
|
||||
const [sidecars, setSidecars] = useState<Array<[string, string]>>([]);
|
||||
|
||||
@@ -264,7 +268,11 @@ export default function Player({
|
||||
|
||||
{streaming && (
|
||||
<span className="text-[11px] text-slate-400 dark:text-slate-500">
|
||||
{error ? "Unavailable" : src && !buffering ? "Streaming" : "Loading…"}
|
||||
{error
|
||||
? "Unavailable"
|
||||
: src && !buffering
|
||||
? `Streaming${height ? ` · ${height}p` : ""}`
|
||||
: "Loading…"}
|
||||
</span>
|
||||
)}
|
||||
</header>
|
||||
@@ -330,6 +338,8 @@ export default function Player({
|
||||
onCanPlay={() => setBuffering(false)}
|
||||
onPlaying={() => setBuffering(false)}
|
||||
onSeeked={() => setBuffering(false)}
|
||||
onResize={() => setHeight(videoRef.current?.videoHeight ?? 0)}
|
||||
onLoadedData={() => setHeight(videoRef.current?.videoHeight ?? 0)}
|
||||
className="absolute inset-0 size-full object-contain"
|
||||
>
|
||||
{sidecars.map(([lang, file]) => (
|
||||
@@ -409,10 +419,14 @@ export default function Player({
|
||||
<button
|
||||
onClick={onDelete}
|
||||
title="Delete this download"
|
||||
className={`${navBtn} whitespace-nowrap hover:border-red-500! hover:text-red-600!
|
||||
aria-label="Delete download"
|
||||
className={`${navIcon} hover:border-red-500! hover:text-red-600!
|
||||
dark:hover:border-red-500! dark:hover:text-red-400!`}
|
||||
>
|
||||
Delete
|
||||
<svg viewBox="0 0 24 24" className="size-4" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path strokeLinecap="round" strokeLinejoin="round"
|
||||
d="M4 7h16M9 7V5h6v2M6 7l1 13h10l1-13M10 11v6M14 11v6" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user