Add quality indicator and re-download at quality to Watch page
- Quality selector now always visible when idle (not just pre-download) - Saved chip shows actual downloaded resolution (e.g. "Saved · 1080p") - Re-download chip deletes existing file and starts new download at selected quality Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -562,6 +562,7 @@ export default function Watch() {
|
|||||||
const [queued, setQueued] = useState(null);
|
const [queued, setQueued] = useState(null);
|
||||||
const [liked, setLiked] = useState(null);
|
const [liked, setLiked] = useState(null);
|
||||||
const [disliked, setDisliked] = useState(null);
|
const [disliked, setDisliked] = useState(null);
|
||||||
|
const [isRedownloading, setIsRedownloading] = useState(false);
|
||||||
const [selectedQuality, setSelectedQuality] = useState(null);
|
const [selectedQuality, setSelectedQuality] = useState(null);
|
||||||
const [speed, setSpeed] = useState(1);
|
const [speed, setSpeed] = useState(1);
|
||||||
const [autoplay, setAutoplay] = useState(false);
|
const [autoplay, setAutoplay] = useState(false);
|
||||||
@@ -721,6 +722,22 @@ export default function Watch() {
|
|||||||
setPlayRequested(true);
|
setPlayRequested(true);
|
||||||
downloadMut.mutate();
|
downloadMut.mutate();
|
||||||
}, [downloadMut]);
|
}, [downloadMut]);
|
||||||
|
const handleRedownload = useCallback(async () => {
|
||||||
|
const dlId = downloadId ?? allDownloads.find(
|
||||||
|
d => d.youtube_video_id === youtubeVideoId && d.status === "complete"
|
||||||
|
)?.id;
|
||||||
|
if (!dlId) return;
|
||||||
|
setIsRedownloading(true);
|
||||||
|
try { await deleteDownload(dlId); } catch (_) {}
|
||||||
|
setFileReady(false);
|
||||||
|
setConfirmedFileUrl(null);
|
||||||
|
setDownloadId(null);
|
||||||
|
setPlayRequested(false);
|
||||||
|
setIsRedownloading(false);
|
||||||
|
qc.invalidateQueries({ queryKey: ["downloads"] });
|
||||||
|
refetchVideo();
|
||||||
|
downloadMut.mutate();
|
||||||
|
}, [downloadId, allDownloads, youtubeVideoId, downloadMut, refetchVideo, qc]);
|
||||||
|
|
||||||
const saveProgress = useCallback((secs) => {
|
const saveProgress = useCallback((secs) => {
|
||||||
if (!video?.id) return;
|
if (!video?.id) return;
|
||||||
@@ -792,6 +809,7 @@ export default function Watch() {
|
|||||||
const isLiked = liked ?? video?.liked ?? false;
|
const isLiked = liked ?? video?.liked ?? false;
|
||||||
const isDisliked = disliked ?? (video?.rating === -1) ?? false;
|
const isDisliked = disliked ?? (video?.rating === -1) ?? false;
|
||||||
const dlComplete = dlStatus?.status === "complete" || video?.is_downloaded;
|
const dlComplete = dlStatus?.status === "complete" || video?.is_downloaded;
|
||||||
|
const downloadedResolution = dlStatus?.resolution ?? video?.download_resolution;
|
||||||
const isFollowed = followMut.isSuccess || video?.channel_followed;
|
const isFollowed = followMut.isSuccess || video?.channel_followed;
|
||||||
const subs = formatSubs(channel?.subscriber_count);
|
const subs = formatSubs(channel?.subscriber_count);
|
||||||
|
|
||||||
@@ -912,7 +930,7 @@ export default function Watch() {
|
|||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex items-center gap-1.5 flex-wrap">
|
<div className="flex items-center gap-1.5 flex-wrap">
|
||||||
{!dlComplete && !isDownloading && !downloadMut.isPending && (
|
{!isDownloading && !downloadMut.isPending && !isRedownloading && (
|
||||||
<select
|
<select
|
||||||
value={selectedQuality ?? "best"}
|
value={selectedQuality ?? "best"}
|
||||||
onChange={(e) => setSelectedQuality(e.target.value)}
|
onChange={(e) => setSelectedQuality(e.target.value)}
|
||||||
@@ -952,7 +970,7 @@ export default function Watch() {
|
|||||||
onClick={() => !dlComplete && !isDownloading && downloadMut.mutate()}
|
onClick={() => !dlComplete && !isDownloading && downloadMut.mutate()}
|
||||||
>
|
>
|
||||||
{dlComplete ? (
|
{dlComplete ? (
|
||||||
<><svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M9 16.2l-3.5-3.5-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/></svg>Saved</>
|
<><svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M9 16.2l-3.5-3.5-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/></svg>Saved{downloadedResolution ? ` · ${downloadedResolution}` : ""}</>
|
||||||
) : isDownloading || downloadMut.isPending ? (
|
) : isDownloading || downloadMut.isPending ? (
|
||||||
<><svg className="w-3.5 h-3.5 animate-spin" fill="none" viewBox="0 0 24 24"><circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"/><path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/></svg>Downloading</>
|
<><svg className="w-3.5 h-3.5 animate-spin" fill="none" viewBox="0 0 24 24"><circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"/><path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/></svg>Downloading</>
|
||||||
) : (
|
) : (
|
||||||
@@ -969,6 +987,16 @@ export default function Watch() {
|
|||||||
</Chip>
|
</Chip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{dlComplete && (
|
||||||
|
<Chip onClick={handleRedownload} disabled={isRedownloading || downloadMut.isPending}>
|
||||||
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
||||||
|
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
||||||
|
</svg>
|
||||||
|
Re-download
|
||||||
|
</Chip>
|
||||||
|
)}
|
||||||
|
|
||||||
{video?.id && (
|
{video?.id && (
|
||||||
<>
|
<>
|
||||||
<Chip active={isLiked} onClick={() => likeMut.mutate()} disabled={likeMut.isPending}>
|
<Chip active={isLiked} onClick={() => likeMut.mutate()} disabled={likeMut.isPending}>
|
||||||
|
|||||||
Reference in New Issue
Block a user