revert(player): drop the transform-scale HD hack (didn't beat YouTube's cap)
Confirmed on prod: rendering the iframe at 1920x1080 logical and CSS transform: scale()-ing it down does NOT lift YouTube's quality cap — the embed stays ~360p in the windowed player and manual HD still snaps back; only true fullscreen unlocks 1080p (which then persists for the session). YouTube caps by the on-screen size, not the iframe's window.innerWidth, so the transform only shrank YouTube's native controls for no benefit. Restore the plain 100%/100% mount. Kept: scroll-anywhere volume (works), the native-menu-yield fix, max-w-6xl, and the harmless vq=hd1080 hint.
This commit is contained in:
parent
42f112d4df
commit
71e0a40481
1 changed files with 9 additions and 43 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
@ -20,15 +20,6 @@ import { useBackToClose } from "../lib/history";
|
||||||
// How close to the end (seconds) counts as "finished" → auto-mark watched.
|
// How close to the end (seconds) counts as "finished" → auto-mark watched.
|
||||||
const FINISH_MARGIN = 10;
|
const FINISH_MARGIN = 10;
|
||||||
|
|
||||||
// Fixed logical size the player iframe is rendered at, then CSS-scaled down to fit the (smaller)
|
|
||||||
// on-screen stage. YouTube caps an embed's max quality to the player's OWN inner viewport size, so
|
|
||||||
// a physically small player is stuck at ~360p even if you pick 1080p manually — it snaps back. A
|
|
||||||
// CSS `transform: scale()` shrinks the visual box WITHOUT changing the iframe's window.innerWidth,
|
|
||||||
// so YouTube keeps seeing a 1920×1080 viewport and allows 1080p while we display it small. (Actual
|
|
||||||
// quality is still bandwidth-gated by YouTube, but manual HD selection now sticks.)
|
|
||||||
const PLAYER_BASE_W = 1920;
|
|
||||||
const PLAYER_BASE_H = 1080;
|
|
||||||
|
|
||||||
// Persistent playback settings (stored in users.preferences). Auto-advance = what plays when a
|
// 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
|
// 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).
|
// ("all"), or neither ("off"). Both apply to any queued player (feed or playlist).
|
||||||
|
|
@ -133,8 +124,6 @@ export default function PlayerModal({
|
||||||
// modal adjusts volume (not just the small player area).
|
// modal adjusts volume (not just the small player area).
|
||||||
const dialogRef = useRef<HTMLDivElement | null>(null);
|
const dialogRef = useRef<HTMLDivElement | null>(null);
|
||||||
const volTimerRef = useRef<number | undefined>(undefined);
|
const volTimerRef = useRef<number | undefined>(undefined);
|
||||||
// CSS scale that fits the 1920×1080 logical player onto the actual stage (see PLAYER_BASE_* above).
|
|
||||||
const [playerScale, setPlayerScale] = useState(0);
|
|
||||||
// Volume level to flash in the on-player overlay (null = hidden). Auto-fades after a moment.
|
// Volume level to flash in the on-player overlay (null = hidden). Auto-fades after a moment.
|
||||||
const [volumeUi, setVolumeUi] = useState<number | null>(null);
|
const [volumeUi, setVolumeUi] = useState<number | null>(null);
|
||||||
// When the user interacts with YouTube's own controls (gear/seek/CC), focus moves into the
|
// When the user interacts with YouTube's own controls (gear/seek/CC), focus moves into the
|
||||||
|
|
@ -416,25 +405,6 @@ export default function PlayerModal({
|
||||||
return () => el.removeEventListener("wheel", onWheel);
|
return () => el.removeEventListener("wheel", onWheel);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Keep the 1920×1080 logical player scaled to exactly fit the on-screen stage (windowed AND
|
|
||||||
// fullscreen). Measured before paint so the player never flashes at the wrong size.
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
const stage = stageRef.current;
|
|
||||||
if (!stage) return;
|
|
||||||
const update = () => {
|
|
||||||
const w = stage.clientWidth;
|
|
||||||
if (w > 0) setPlayerScale(w / PLAYER_BASE_W);
|
|
||||||
};
|
|
||||||
update();
|
|
||||||
const ro = new ResizeObserver(update);
|
|
||||||
ro.observe(stage);
|
|
||||||
document.addEventListener("fullscreenchange", update);
|
|
||||||
return () => {
|
|
||||||
ro.disconnect();
|
|
||||||
document.removeEventListener("fullscreenchange", update);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Yield the interaction overlay to YouTube's native UI. Clicking a native control (gear / seek
|
// Yield the interaction overlay to YouTube's native UI. Clicking a native control (gear / seek
|
||||||
// bar / CC) moves focus into the player iframe — the only cross-origin signal we get. While the
|
// bar / CC) moves focus into the player iframe — the only cross-origin signal we get. While the
|
||||||
// iframe holds focus we drop the overlay's pointer-events so the (arbitrarily tall) settings
|
// iframe holds focus we drop the overlay's pointer-events so the (arbitrarily tall) settings
|
||||||
|
|
@ -517,9 +487,11 @@ export default function PlayerModal({
|
||||||
origin: window.location.origin,
|
origin: window.location.origin,
|
||||||
playsinline: 1,
|
playsinline: 1,
|
||||||
// Best-effort HD hint. The IFrame API's setPlaybackQuality/suggestedQuality are hard no-ops
|
// Best-effort HD hint. The IFrame API's setPlaybackQuality/suggestedQuality are hard no-ops
|
||||||
// now (YouTube removed them), but the undocumented `vq` URL param is still honoured for many
|
// now (YouTube removed them), and the undocumented `vq` URL param is only occasionally
|
||||||
// videos and is harmless otherwise. The real quality lever is the player's rendered size
|
// honoured — kept because it's harmless. NOTE: YouTube hard-caps an embed's max quality by
|
||||||
// (see max-w-6xl below) — YouTube's ABR targets a resolution to match the pixel box.
|
// 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",
|
vq: "hd1080",
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
|
|
@ -609,15 +581,9 @@ export default function PlayerModal({
|
||||||
ref={stageRef}
|
ref={stageRef}
|
||||||
className="player-stage relative aspect-video w-full bg-black rounded-t-2xl overflow-hidden"
|
className="player-stage relative aspect-video w-full bg-black rounded-t-2xl overflow-hidden"
|
||||||
>
|
>
|
||||||
{/* The player rendered at a fixed 1920×1080 logical size, then CSS-scaled down to fit the
|
{/* Hide the iframe entirely on error so YouTube's own error screen can't bleed
|
||||||
stage — this is what unlocks 1080p in the small windowed player (see PLAYER_BASE_*).
|
through our overlay. */}
|
||||||
Hide the iframe entirely on error so YouTube's own error screen can't bleed through. */}
|
|
||||||
<div
|
|
||||||
className="absolute top-0 left-0 origin-top-left"
|
|
||||||
style={{ width: PLAYER_BASE_W, height: PLAYER_BASE_H, transform: `scale(${playerScale})` }}
|
|
||||||
>
|
|
||||||
<div ref={mountRef} className={`w-full h-full ${playerError != null ? "invisible" : ""}`} />
|
<div ref={mountRef} className={`w-full h-full ${playerError != null ? "invisible" : ""}`} />
|
||||||
</div>
|
|
||||||
{/* Interaction layer over the CENTRE of the video: catches click (play/pause) and stops
|
{/* Interaction layer over the CENTRE of the video: catches click (play/pause) and stops
|
||||||
the iframe stealing keyboard focus. It deliberately leaves the top AND bottom edges
|
the iframe stealing keyboard focus. It deliberately leaves the top AND bottom edges
|
||||||
uncovered so YouTube's native controls — the top-right cluster (volume / CC / settings)
|
uncovered so YouTube's native controls — the top-right cluster (volume / CC / settings)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue