feat: 4K downloads, watch progress, player controls, new icon
Window chrome: titleBarStyle Overlay (Transparent left the native bar in place, white in dark mode, with a dead strip beneath it). The app now paints that strip itself, so it matches the page background. Player: prev/next through the feed, a full-screen button, a spinner while a stream resolves, Open on YouTube on downloaded videos too, and the description collapsed behind a disclosure. Downloads default to Best, which reaches real 4K — above 1080p YouTube serves VP9/AV1, verified to play natively in WKWebView here. Audio stays pinned to AAC because Opus in MP4 would be silent. A Compatible setting keeps the old 1080p H.264 behaviour. Files are now named '<ISO date> - <title> [<id>].mp4'. Watch progress is recorded and drawn under thumbnails like YouTube's, and reopening a video resumes where it left off. Tiles clamp every text line to a fixed height so they share a baseline, and the search field no longer clips its placeholder. Fixes a Picture-in-Picture leak: WebKit kept a detached video playing after the player closed, so a second video could play over the first with no way to stop it. Every exit path now tears the element down.
This commit is contained in:
@@ -3,7 +3,7 @@ import {
|
||||
checkPrereqs, importTakeoutCsv, pickLibraryFolder, pickTakeoutFile, previewTakeoutImport,
|
||||
} from "../api";
|
||||
import { APPEARANCE_MODES, type Appearance } from "../hooks/useAppearance";
|
||||
import type { ImportPreview, Prereqs } from "../types";
|
||||
import type { ImportPreview, Prereqs, Quality } from "../types";
|
||||
import TakeoutGuide from "./TakeoutGuide";
|
||||
import {
|
||||
BTN_CHROME, BTN_PRIMARY, Dialog, HELP, LABEL, SectionHeading, Segmented, SUBPANEL,
|
||||
@@ -14,6 +14,8 @@ interface Props {
|
||||
onImported: (count: number) => void;
|
||||
appearance: Appearance;
|
||||
onAppearance: (a: Appearance) => void;
|
||||
quality: Quality;
|
||||
onQuality: (q: Quality) => void;
|
||||
onError: (message: string) => void;
|
||||
}
|
||||
|
||||
@@ -33,7 +35,7 @@ function StatusRow({ label, value }: { label: string; value: string | null }) {
|
||||
}
|
||||
|
||||
export default function Settings({
|
||||
onClose, onImported, appearance, onAppearance, onError,
|
||||
onClose, onImported, appearance, onAppearance, quality, onQuality, onError,
|
||||
}: Props) {
|
||||
const [prereqs, setPrereqs] = useState<Prereqs | null>(null);
|
||||
const [pending, setPending] = useState<{ path: string; preview: ImportPreview } | null>(null);
|
||||
@@ -121,6 +123,27 @@ export default function Settings({
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section className="border-b border-slate-200 px-5 py-4 dark:border-slate-800">
|
||||
<SectionHeading>Download quality</SectionHeading>
|
||||
<p className={`mt-1.5 ${HELP}`}>
|
||||
<b>Best</b> takes the highest resolution available, up to 4K — above 1080p
|
||||
that means VP9 or AV1, which your Mac decodes but older ones may not, and
|
||||
the files are several times larger. <b>Compatible</b> caps at 1080p H.264,
|
||||
which plays anywhere. Audio is AAC either way.
|
||||
</p>
|
||||
<div className="mt-2 flex items-center justify-between gap-3">
|
||||
<span className={LABEL}>Quality</span>
|
||||
<Segmented
|
||||
value={quality}
|
||||
onChange={onQuality}
|
||||
options={[
|
||||
{ value: "best", label: "Best (4K)" },
|
||||
{ value: "compatible", label: "Compatible" },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border-b border-slate-200 px-5 py-4 dark:border-slate-800">
|
||||
<SectionHeading>Appearance</SectionHeading>
|
||||
<div className="mt-2 flex items-center justify-between gap-3">
|
||||
|
||||
Reference in New Issue
Block a user