diff --git a/VERSION b/VERSION index f412377..64c3512 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.36.3 \ No newline at end of file +0.36.4 \ No newline at end of file diff --git a/frontend/src/components/PlayerModal.tsx b/frontend/src/components/PlayerModal.tsx index 9d592f5..88568a5 100644 --- a/frontend/src/components/PlayerModal.tsx +++ b/frontend/src/components/PlayerModal.tsx @@ -20,14 +20,6 @@ import { useBackToClose } from "../lib/history"; // How close to the end (seconds) counts as "finished" → auto-mark watched. const FINISH_MARGIN = 10; -// One-time HD-unlock (see attemptHdUnlock). YouTube hard-caps a windowed embed to ~360p and only a -// real fullscreen lifts the cap; the lift then persists for the whole page session. So on the first -// video opened we briefly flash the player to fullscreen and back to coax YouTube past the cap once. -// Cap the flash at this many ms, but exit as soon as we see the quality actually rise. -const HD_UNLOCK_MAX_MS = 2500; -// Module-scoped so the flash happens only ONCE per page session (the unlock persists afterwards). -let hdUnlockDone = false; - // Persistent playback settings (stored in users.preferences). Auto-advance = what plays when a // video ends; loop = whether it repeats the current video ("one"), wraps the list at its ends // ("all"), or neither ("off"). Both apply to any queued player (feed or playlist). @@ -132,9 +124,6 @@ export default function PlayerModal({ // modal adjusts volume (not just the small player area). const dialogRef = useRef(null); const volTimerRef = useRef(undefined); - // HD-unlock flash bookkeeping (see attemptHdUnlock / finishHdUnlock). - const unlockTimerRef = useRef(undefined); - const unlockingRef = useRef(false); // Volume level to flash in the on-player overlay (null = hidden). Auto-fades after a moment. const [volumeUi, setVolumeUi] = useState(null); // When the user interacts with YouTube's own controls (gear/seek/CC), focus moves into the @@ -157,32 +146,6 @@ export default function PlayerModal({ if (document.fullscreenElement) document.exitFullscreen?.(); else el.requestFullscreen?.(); }; - // End the HD-unlock flash: exit fullscreen (if we're still in it) and mark the session unlocked so - // it never flashes again. Called either when YouTube's quality is seen to rise or on the max timeout. - const finishHdUnlock = () => { - if (!unlockingRef.current) return; - unlockingRef.current = false; - window.clearTimeout(unlockTimerRef.current); - hdUnlockDone = true; - if (document.fullscreenElement) document.exitFullscreen?.().catch(() => {}); - }; - // Coax YouTube past its windowed 360p cap: on the first opened video, briefly enter fullscreen so - // YouTube lifts the cap (which then persists for the session), then exit back to the small player. - // Needs live user activation (the modal-open click) — silently retried on the next click if it's - // already expired, and a no-op once the session is unlocked. Best-effort; can't be forced further. - const attemptHdUnlock = () => { - if (hdUnlockDone || unlockingRef.current) return; - const el = stageRef.current; - if (!el || !el.requestFullscreen || document.fullscreenElement) return; - el.requestFullscreen() - .then(() => { - unlockingRef.current = true; - unlockTimerRef.current = window.setTimeout(finishHdUnlock, HD_UNLOCK_MAX_MS); - }) - .catch(() => { - /* no user activation yet / fullscreen blocked — leave hdUnlockDone false to retry on a click */ - }); - }; const flashVolume = (level: number) => { setVolumeUi(level); window.clearTimeout(volTimerRef.current); @@ -525,24 +488,18 @@ export default function PlayerModal({ playsinline: 1, // Best-effort HD hint. The IFrame API's setPlaybackQuality/suggestedQuality are hard no-ops // now (YouTube removed them), and the undocumented `vq` URL param is only occasionally - // honoured — kept because it's harmless. YouTube hard-caps an embed's max quality by the - // player's on-screen size; a windowed embed is stuck ~360p and only true fullscreen lifts - // the cap (then it persists for the session). A CSS transform-scale trick does NOT fool it; - // we instead do a one-time fullscreen flash on open (see attemptHdUnlock) to lift the cap. + // honoured — kept because it's harmless. NOTE: YouTube hard-caps an embed's max quality by + // the player's on-screen size; a windowed embed is stuck ~360p and only true fullscreen + // lifts the cap (after which the higher pick persists for the session). We can't beat that + // programmatically — a CSS transform-scale trick was tried and does NOT fool the cap. vq: "hd1080", }, events: { - // Keep keyboard focus on the modal, not the iframe, so shortcuts work right away, and try - // the one-time HD-unlock flash while the modal-open click is still a live user activation. - onReady: () => { - focusModal(); - attemptHdUnlock(); - }, + // Keep keyboard focus on the modal, not the iframe, so shortcuts work right away. + onReady: () => focusModal(), onStateChange: (e: any) => { - // 1 === playing → sync the navigated-to video's title/author for display, and retry the - // HD-unlock (a second chance at the flash right as playback begins). + // 1 === playing → sync the navigated-to video's title/author for display. if (e?.data === 1) { - attemptHdUnlock(); const p = playerRef.current; const d = p && typeof p.getVideoData === "function" ? p.getVideoData() : null; if (d) setLiveData({ title: d.title, author: d.author }); @@ -556,15 +513,6 @@ export default function PlayerModal({ advanceOnEnd(); } }, - // While the HD-unlock flash is running, exit fullscreen as soon as YouTube's quality is - // seen to rise (no need to hold the flash the full timeout). onPlaybackQualityChange is one - // of the few still-live quality signals; e.data is a label ("hd1080"/"hd720"/"large"/…). - onPlaybackQualityChange: (e: any) => { - const q = String(e?.data || ""); - if (unlockingRef.current && (q === "hd1080" || q === "hd720" || q === "large" || q === "highres")) { - finishHdUnlock(); - } - }, // The embed couldn't play this video (embedding disabled, removed, private…). // Surface our own message + an "Open on YouTube" escape hatch. onError: (e: any) => setPlayerError(typeof e?.data === "number" ? e.data : -1), @@ -578,12 +526,6 @@ export default function PlayerModal({ return () => { cancelled = true; window.clearInterval(timer); - // If the modal closes / advances mid-flash, don't leave the player stuck in fullscreen. - window.clearTimeout(unlockTimerRef.current); - if (unlockingRef.current) { - unlockingRef.current = false; - if (document.fullscreenElement) document.exitFullscreen?.().catch(() => {}); - } // Final flush, then refresh the feed + playlists so resume bars reflect this session. Promise.resolve(persist()).finally(() => { qc.invalidateQueries({ queryKey: ["feed"] }); @@ -651,7 +593,6 @@ export default function PlayerModal({
{ focusModal(); - attemptHdUnlock(); // fallback: a real click is a fresh user activation for the flash togglePlay(); }} title={t("player.shortcutsHint")} diff --git a/frontend/src/lib/releaseNotes.ts b/frontend/src/lib/releaseNotes.ts index b2755e0..7ae346b 100644 --- a/frontend/src/lib/releaseNotes.ts +++ b/frontend/src/lib/releaseNotes.ts @@ -15,19 +15,10 @@ export interface ReleaseEntry { export const RELEASE_NOTES: ReleaseEntry[] = [ { - version: "0.36.3", + version: "0.36.4", date: "2026-07-10", - summary: "Experimental: the player tries to unlock higher video quality automatically.", - features: [ - "The in-app player now tries to lift YouTube's windowed quality limit for you. YouTube caps a small embedded player to a low resolution and only real fullscreen unlocks HD — so on the first video you open in a session, the player briefly flashes to fullscreen and back to trigger that unlock, which then sticks for the rest of the session. You may notice a short flash on that first video; later videos won't flash. (It's still bandwidth-dependent, and can be reverted if it doesn't help on your setup.)", - ], - }, - { - version: "0.36.2", - date: "2026-07-10", - summary: "Player's YouTube controls back to normal size (a quality experiment was reverted).", - fixes: [ - "Reverted an experiment that tried to raise video quality in the small (windowed) player: it only shrank YouTube's on-screen controls without actually improving quality, because YouTube caps an embedded player's quality by its on-screen size. Tip for the sharpest picture: switch the player to fullscreen once — YouTube then keeps the higher quality even after you return to the small player.", + chores: [ + "Internal in-app player cleanup.", ], }, { @@ -41,9 +32,8 @@ export const RELEASE_NOTES: ReleaseEntry[] = [ { version: "0.36.0", date: "2026-07-10", - summary: "Sharper in-app playback and full access to YouTube's own player menu.", + summary: "Full access to YouTube's own player menu in the in-app player.", features: [ - "The in-app player is now larger and asks YouTube for higher quality, so videos look sharper than the old low-resolution default. (For the maximum your connection allows, go fullscreen.)", "You can now open and navigate YouTube's own player menu — Quality, Speed, Captions and More options — all the way down. When you use YouTube's built-in controls, the app's click-to-play and scroll-for-volume shortcuts briefly step aside (a small badge shows this), and they return the moment you move the pointer off the video.", ], },