Created
January 12, 2026 21:26
-
-
Save XReyRobert/21eabbbcf69a7b23c976d568173f395f to your computer and use it in GitHub Desktop.
Local web page to use full screen youtube videos with plast (macos)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!doctype html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="referrer" content="strict-origin-when-cross-origin" /> | |
| <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1" /> | |
| <style> | |
| html, body { | |
| margin: 0 !important; | |
| width: 100% !important; | |
| height: 100% !important; | |
| overflow: hidden !important; | |
| background: #000 !important; | |
| } | |
| /* Fill the viewport unconditionally */ | |
| #stage { | |
| position: fixed !important; | |
| inset: 0 !important; | |
| overflow: hidden !important; | |
| background: #000 !important; | |
| } | |
| /* The YT API inserts wrappers; force them all to occupy the stage */ | |
| #player, | |
| #player > div { | |
| position: absolute !important; | |
| inset: 0 !important; | |
| width: 100% !important; | |
| height: 100% !important; | |
| } | |
| /* | |
| Cover sizing: | |
| Use CSS max() so we cover both portrait/landscape viewports without “small corner” behavior. | |
| 16:9 math: | |
| 16/9 = 1.7777778 | |
| 9/16 = 0.5625 | |
| */ | |
| #player iframe { | |
| position: absolute !important; | |
| top: 50% !important; | |
| left: 50% !important; | |
| transform: translate(-50%, -50%) !important; | |
| width: max(100vw, 177.7778vh) !important; | |
| height: max(100vh, 56.25vw) !important; | |
| border: 0 !important; | |
| } | |
| /* Invisible click-catcher to enable audio (no message) */ | |
| #unlock { | |
| position: fixed; | |
| inset: 0; | |
| background: transparent; | |
| z-index: 10; | |
| cursor: default; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="stage"> | |
| <div id="player"></div> | |
| </div> | |
| <div id="unlock" aria-hidden="true"></div> | |
| <script src="https://www.youtube.com/iframe_api"></script> | |
| <script> | |
| function getVideoIdFromQuery() { | |
| const qs = new URLSearchParams(location.search); | |
| const raw = (qs.get("v") || qs.get("video") || "").trim(); | |
| // Safe charset; supports leading '-' (your ID includes it). | |
| return /^[A-Za-z0-9_-]{6,32}$/.test(raw) ? raw : null; | |
| } | |
| const VIDEO_ID = getVideoIdFromQuery() || "-Yye4owQMsQ"; | |
| const ORIGIN = location.origin; | |
| let player; | |
| function setDesktopPassthrough(enabled) { | |
| const iframe = document.querySelector("#player iframe"); | |
| if (iframe) iframe.style.pointerEvents = enabled ? "none" : "auto"; | |
| } | |
| function kickMutedAutoplay(maxMs = 7000) { | |
| const deadline = Date.now() + maxMs; | |
| const t = setInterval(() => { | |
| try { | |
| const st = player.getPlayerState(); | |
| if (st !== YT.PlayerState.PLAYING) { | |
| player.mute(); | |
| player.playVideo(); | |
| } else { | |
| clearInterval(t); | |
| } | |
| } catch (_) {} | |
| if (Date.now() > deadline) clearInterval(t); | |
| }, 250); | |
| } | |
| function onYouTubeIframeAPIReady() { | |
| player = new YT.Player("player", { | |
| host: "https://www.youtube-nocookie.com", | |
| videoId: VIDEO_ID, | |
| playerVars: { | |
| autoplay: 1, | |
| controls: 0, | |
| disablekb: 1, | |
| fs: 0, | |
| iv_load_policy: 3, | |
| loop: 1, | |
| playlist: VIDEO_ID, // required to loop a single video [oai_citation:1‡Google for Developers](https://developers.google.com/youtube/player_parameters?utm_source=chatgpt.com) | |
| playsinline: 1, | |
| rel: 0, | |
| origin: ORIGIN, | |
| enablejsapi: 1 | |
| }, | |
| events: { | |
| onReady: (e) => { | |
| // Improve autoplay reliability: ensure iframe explicitly allows autoplay. | |
| try { | |
| const iframe = e.target.getIframe(); | |
| iframe.setAttribute("allow", "autoplay; encrypted-media; picture-in-picture"); | |
| } catch (_) {} | |
| // Restore initial behavior: autoplay muted. | |
| try { e.target.mute(); } catch (_) {} | |
| try { e.target.playVideo(); } catch (_) {} | |
| // Keep clicks available until audio is enabled. | |
| setDesktopPassthrough(false); | |
| // If the big red button appears, keep nudging play() while muted. | |
| kickMutedAutoplay(); | |
| }, | |
| onStateChange: (e) => { | |
| if (e.data === YT.PlayerState.ENDED) { | |
| try { e.target.seekTo(0, true); e.target.playVideo(); } catch (_) {} | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| // One real click to enable sound (no on-screen message). | |
| document.getElementById("unlock").addEventListener("click", () => { | |
| if (!player) return; | |
| try { player.unMute(); player.setVolume(100); } catch (_) {} | |
| try { player.playVideo(); } catch (_) {} | |
| // Remove click-catcher and make the iframe non-interactive so desktop clicks pass through. | |
| document.getElementById("unlock").remove(); | |
| setTimeout(() => setDesktopPassthrough(true), 100); | |
| }); | |
| </script> | |
| </body> | |
| </html> |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
start a local server with:
python3 -m http.server 8099 --bind 127.0.0.1
point plash to http://127.0.0.1:8099/plash-youtube-audio.html?v=-Yye4owQMsQ
(replace -Yye4owQMsQ by the target video id)