| author | |
| committer | |
| log | 569edfc3aff1680ad8c61865a798657543717ef1 |
| tree | 833d911d4d84358192ceba9fc6c7cfd8e48338be |
| parent | a5bd9c290e07a3d724b254b0306bba8ea76268d2 |
| signature | Signed by SSH key SHA256:xbd+BjjhyBfwk7GVoURf9Yx0gzDerHbvYv7SddNWmAs |
two bugs:
- chrome without av1 and version >=142 would use hls native.
- hls polyfill was never being used because of a bundling mistake.
for chrome, they made `video.canPlayType("application/x-mpegURL")`
return "maybe", which is total propaganda because chrome does not
actually support this type of stream. they support some insane subset
that isn't real world use cases.
since i was testing windows 7, decided to also fix some IE9 bugs.7 files changed, 92 insertions(+), 48 deletions(-)
src/file-viewer/backend.tsx-1| ... | ... | @@ -62,7 +62,6 @@ app.get("/file/*", async (c, next) => { |
| 62 | 62 | // The permissions system is currently binary, using the friend auth. |
| 63 | 63 | const permissions = FilePermissions.getByPrefix(rawFilePath); |
| 64 | 64 | if (permissions !== 0) { |
| 65 | console.log(rawFilePath, getForFile(rawFilePath)); | |
| 66 | 65 | const friendAuthChallenge = requireFriendAuth(c, getForFile(rawFilePath)); |
| 67 | 66 | if (friendAuthChallenge) return friendAuthChallenge; |
| 68 | 67 | } |
src/global.css+10-4| ... | ... | @@ -66,8 +66,10 @@ body { |
| 66 | 66 | } |
| 67 | 67 | |
| 68 | 68 | main { |
| 69 | display: block; | |
| 69 | 70 | margin: auto; |
| 70 | max-width: 80ch; | |
| 71 | width: auto; | |
| 72 | max-width: 768px; | |
| 71 | 73 | padding: 1em; |
| 72 | 74 | } |
| 73 | 75 | |
| ... | ... | @@ -86,7 +88,7 @@ figure, |
| 86 | 88 | ul, |
| 87 | 89 | ol { |
| 88 | 90 | margin-top: 1em; |
| 89 | margin-bottom: 1rem; | |
| 91 | margin-bottom: 1em; | |
| 90 | 92 | } |
| 91 | 93 | |
| 92 | 94 | ul, |
| ... | ... | @@ -100,11 +102,15 @@ li { |
| 100 | 102 | |
| 101 | 103 | a { |
| 102 | 104 | text-decoration: none; |
| 105 | color: var(--primary); | |
| 106 | } | |
| 107 | a.custom { | |
| 108 | color: inherit; | |
| 103 | 109 | } |
| 104 | 110 | a:where([href]:not(.custom)) { |
| 105 | 111 | color: var(--primary); |
| 106 | margin: -0.2rem -0.3rem; | |
| 107 | padding: 0.2rem 0.3rem; | |
| 112 | margin: -0.2em -0.3em; | |
| 113 | padding: 0.2em 0.3em; | |
| 108 | 114 | border-radius: 4px; |
| 109 | 115 | &:hover { |
| 110 | 116 | text-decoration: underline; |
src/pages/history-of-japan-reanimated.client.ts-1| ... | ... | @@ -124,7 +124,6 @@ function updateCredits(video) { |
| 124 | 124 | } |
| 125 | 125 | |
| 126 | 126 | globalThis.onCloverVideoInit = (id: string, video: HTMLVideoElement) => { |
| 127 | console.log(1, id, video); | |
| 128 | 127 | if (id === "main-player") { |
| 129 | 128 | updateCredits(video); |
| 130 | 129 | video.addEventListener("timeupdate", () => { |
src/pages/history-of-japan-reanimated.marko+17-17| ... | ... | @@ -26,7 +26,7 @@ export const theme = { |
| 26 | 26 | me and 16 other artists (music, narration, and animation) have been |
| 27 | 27 | spending the past couple months on a recreation of bill wurtz's legendary |
| 28 | 28 | "<a href="https://billwurtz.com/history-of-japan.html">history of japan</a>" to celebrate the 10 year anniversary of his release. i |
| 29 | contributed two animation sections. the canonical release is on | |
| 29 | contributed two animation sections. the canonical release is on | |
| 30 | 30 | <a href="https://youtu.be/nqsoti-OuRM">LunaPogi's YouTube page</a>, but i |
| 31 | 31 | am providing this ad-free video player. |
| 32 | 32 | </p> |
| ... | ... | @@ -49,22 +49,22 @@ export const theme = { |
| 49 | 49 | </p> |
| 50 | 50 | <ul> |
| 51 | 51 | <li><a href="https://www.youtube.com/channel/UClywJ-scBa8hcCywQ_7gWbg">Kappacap</a> - thumbnail artist</li> |
| 52 | <li><a href="https://www.tumblr.com/atamai-m8">atamai</a> - animation <span style="color:#fff9">(0:03-1:02)</></li> | |
| 53 | <li><a href="https://www.youtube.com/channel/UCNJ6EbYQcnqpB6cQLGYXQow">moxley</a> - music and narration <span style="color:#fff9">(0:03-3:10)</>, animation <span style="color:#fff9">(3:51-4:03)</></li> | |
| 54 | <li><a href="https://www.youtube.com/channel/UCdy0IJXbdKyTWyKajyEvodw">TheJustinator</a> - animation <span style="color:#fff9">(1:03-1:33, 2:02-2:33)</>, music <span style="color:#fff9">(3:11-6:04)</></li> | |
| 55 | <li><a href="https://chaosyumi.net">chaosyumi</a> - animation <span style="color:#fff9">(1:34-2:01, 5:34-6:05)</></li> | |
| 56 | <li><a href="https://www.youtube.com/channel/UC7NHQWu3awWcC0rm8nB5CfQ">LunaPogi</a> - animation <span style="color:#fff9">(3:36-3:51, 8:01-9:10)</>, music <span style="color:#fff9">(6:05-9:10)</>, narration <span style="color:#fff9">(8:01-9:10)</></li> | |
| 57 | <li><a href="https://www.youtube.com/channel/UChec240BfC1UKX9DvBBIyLQ">zeccet (bgongo)</a> - animation <span style="color:#fff9">(2:23-3:10)</></li> | |
| 58 | <li><a href="https://www.youtube.com/channel/UCLfv-6_J0WLsA2jAsJxiLOg">TheRealTinyWorld</a> - animation <span style="color:#fff9">(3:11-3:35)</></li> | |
| 59 | <li><a href="https://www.castingcall.club/fredfrenchiii">Fred French VA</a> - narration <span style="color:#fff9">(3:11-4:35)</></li> | |
| 60 | <li><a href="https://www.youtube.com/channel/UCqeAGMquQNtrSfd-quZX3KQ">rqfirqfo</a> - animation <span style="color:#fff9">(4:04-4:35)</></li> | |
| 61 | <li><a href="https://www.youtube.com/@krispykarim">krispykarim</a> - animation <span style="color:#fff9">(4:36 - 5:04)</>, narration <span style="color:#fff9">(4:36-8:01)</></li> | |
| 62 | <li><a href="https://www.youtube.com/channel/UCkhbgaqiIkliOKyEORnkhDg">SimplyDoodled Studios</a> - animation <span style="color:#fff9">(5:05-5:33)</></li> | |
| 63 | <li><span style="color:#ff7de9">paper clover (me)</span> - animation <span style="color:#fff9">(6:05-6:38)</>, credits animation <span style="color:#fff9">(9:11-9:35)</></li> | |
| 64 | <li><a href="https://www.youtube.com/channel/UCftnPCmG6dfQ50YOMRMtkjg">Coffee</a> - transcription <span style="color:#fff9">(6:05-9:10)</></li> | |
| 65 | <li><a href="https://www.youtube.com/channel/UCPQv3ueB-E771gnwcgALzxw">harjjw</a> - animation <span style="color:#fff9">(6:38-7:03)</></li> | |
| 66 | <li><a href="https://www.youtube.com/channel/UCcR5G761gkEKvWskjEqWFkQ">yellowmarkers</a> - animation <span style="color:#fff9">(7:04-7:36)</></li> | |
| 67 | <li><a href="https://www.youtube.com/channel/UC8SZGQ32RQxiQeTnQ5hgYAg">TheNothingGuy07</a> - animation <span style="color:#fff9">(7:37-7:59)</></li> | |
| 52 | <li><a href="https://www.tumblr.com/atamai-m8">atamai</a> - animation <span style="color:#B4A7AD">(0:03-1:02)</></li> | |
| 53 | <li><a href="https://www.youtube.com/channel/UCNJ6EbYQcnqpB6cQLGYXQow">moxley</a> - music and narration <span style="color:#B4A7AD">(0:03-3:10)</>, animation <span style="color:#B4A7AD">(3:51-4:03)</></li> | |
| 54 | <li><a href="https://www.youtube.com/channel/UCdy0IJXbdKyTWyKajyEvodw">TheJustinator</a> - animation <span style="color:#B4A7AD">(1:03-1:33, 2:02-2:33)</>, music <span style="color:#B4A7AD">(3:11-6:04)</></li> | |
| 55 | <li><a href="https://chaosyumi.net">chaosyumi</a> - animation <span style="color:#B4A7AD">(1:34-2:01, 5:34-6:05)</></li> | |
| 56 | <li><a href="https://www.youtube.com/channel/UC7NHQWu3awWcC0rm8nB5CfQ">LunaPogi</a> - animation <span style="color:#B4A7AD">(3:36-3:51, 8:01-9:10)</>, music <span style="color:#B4A7AD">(6:05-9:10)</>, narration <span style="color:#B4A7AD">(8:01-9:10)</></li> | |
| 57 | <li><a href="https://www.youtube.com/channel/UChec240BfC1UKX9DvBBIyLQ">zeccet (bgongo)</a> - animation <span style="color:#B4A7AD">(2:23-3:10)</></li> | |
| 58 | <li><a href="https://www.youtube.com/channel/UCLfv-6_J0WLsA2jAsJxiLOg">TheRealTinyWorld</a> - animation <span style="color:#B4A7AD">(3:11-3:35)</></li> | |
| 59 | <li><a href="https://www.castingcall.club/fredfrenchiii">Fred French VA</a> - narration <span style="color:#B4A7AD">(3:11-4:35)</></li> | |
| 60 | <li><a href="https://www.youtube.com/channel/UCqeAGMquQNtrSfd-quZX3KQ">rqfirqfo</a> - animation <span style="color:#B4A7AD">(4:04-4:35)</></li> | |
| 61 | <li><a href="https://www.youtube.com/@krispykarim">krispykarim</a> - animation <span style="color:#B4A7AD">(4:36 - 5:04)</>, narration <span style="color:#B4A7AD">(4:36-8:01)</></li> | |
| 62 | <li><a href="https://www.youtube.com/channel/UCkhbgaqiIkliOKyEORnkhDg">SimplyDoodled Studios</a> - animation <span style="color:#B4A7AD">(5:05-5:33)</></li> | |
| 63 | <li><span style="color:#ff7de9">paper clover (me)</span> - animation <span style="color:#B4A7AD">(6:05-6:38)</>, credits animation <span style="color:#B4A7AD">(9:11-9:35)</></li> | |
| 64 | <li><a href="https://www.youtube.com/channel/UCftnPCmG6dfQ50YOMRMtkjg">Coffee</a> - transcription <span style="color:#B4A7AD">(6:05-9:10)</></li> | |
| 65 | <li><a href="https://www.youtube.com/channel/UCPQv3ueB-E771gnwcgALzxw">harjjw</a> - animation <span style="color:#B4A7AD">(6:38-7:03)</></li> | |
| 66 | <li><a href="https://www.youtube.com/channel/UCcR5G761gkEKvWskjEqWFkQ">yellowmarkers</a> - animation <span style="color:#B4A7AD">(7:04-7:36)</></li> | |
| 67 | <li><a href="https://www.youtube.com/channel/UC8SZGQ32RQxiQeTnQ5hgYAg">TheNothingGuy07</a> - animation <span style="color:#B4A7AD">(7:37-7:59)</></li> | |
| 68 | 68 | </ul> |
| 69 | 69 | |
| 70 | 70 | <p> |
src/pages/index.css+1-1| ... | ... | @@ -4,7 +4,7 @@ main { |
| 4 | 4 | flex-direction: column; |
| 5 | 5 | justify-content: center; |
| 6 | 6 | align-items: center; |
| 7 | height: 100vh; | |
| 7 | min-height: 100vh; | |
| 8 | 8 | & > div { |
| 9 | 9 | width: 450px; |
| 10 | 10 | margin: auto; |
src/q+a/views/permalink.marko-1| ... | ... | @@ -4,7 +4,6 @@ export interface Input { |
| 4 | 4 | } |
| 5 | 5 | |
| 6 | 6 | server export function meta({ context, question }) { |
| 7 | console.log(context.req.header("user-agent")); | |
| 8 | 7 | const isDiscord = context.req.header("user-agent") |
| 9 | 8 | ?.toLowerCase() |
| 10 | 9 | .includes("discord"); |
src/tags/clover-video.client.ts+64-23| ... | ... | @@ -2,6 +2,12 @@ |
| 2 | 2 | |
| 3 | 3 | // Polyfill for old browsers. |
| 4 | 4 | type PlaybackMode = "av1" | "hls-polyfill" | "hls-native" | "none"; |
| 5 | declare global { | |
| 6 | function hydrateVideo(container: HTMLElement): void; | |
| 7 | var ManagedMediaSource: typeof MediaSource | null; | |
| 8 | var onCloverVideoInit: null | ((id: string, video: HTMLVideoElement) => void); | |
| 9 | } | |
| 10 | ||
| 5 | 11 | const forceMode = location.search.match(/(?:\?|&)force-player=(.*)/)?.[1] as |
| 6 | 12 | | PlaybackMode |
| 7 | 13 | | undefined; |
| ... | ... | @@ -16,7 +22,6 @@ Promise.allSettled ??= <T>(promises: Iterable<T | PromiseLike<T>>) => |
| 16 | 22 | (reason) => ({ status: "rejected" as const, reason }), |
| 17 | 23 | ))); |
| 18 | 24 | |
| 19 | declare function hydrateVideo(container: HTMLElement): void; | |
| 20 | 25 | globalThis.hydrateVideo = function hydrateVideo(container) { |
| 21 | 26 | mode ??= decidePlaybackMode(); |
| 22 | 27 | mode.then((mode) => hydrateVideoInner(container, mode)); |
| ... | ... | @@ -27,9 +32,6 @@ Array.from( |
| 27 | 32 | (container) => hydrateVideo(container), |
| 28 | 33 | ); |
| 29 | 34 | |
| 30 | // TODO: be less stupid about this | |
| 31 | const onCloverVideoInit = window.onCloverVideoInit; | |
| 32 | ||
| 33 | 35 | function decidePlaybackMode(): Promise<PlaybackMode> { |
| 34 | 36 | if (forceMode) { |
| 35 | 37 | if (forceMode === "hls-polyfill") { |
| ... | ... | @@ -49,27 +51,41 @@ function decidePlaybackMode(): Promise<PlaybackMode> { |
| 49 | 51 | // Software decoding is too slow to use, unfortunately. |
| 50 | 52 | // Consider a device like a 2014 two core laptop. |
| 51 | 53 | console.warn(`implements non-accelerated av1+opus`); |
| 54 | ||
| 55 | // but on my slow Windows 7 laptop on Chrome, the HLS polyfill lags | |
| 56 | // even on stable internet, so the av1 encoder ends up being better. | |
| 57 | if (navigator.platform === "Win32") { | |
| 58 | const chromeVersion = /Chrome\/(\d+)/.exec(navigator.userAgent)?.[1]; | |
| 59 | if (chromeVersion && Number(chromeVersion) < 110) { | |
| 60 | console.warn("using av1 since win32 chrome runs hls.js badly"); | |
| 61 | return "none"; | |
| 62 | } | |
| 63 | } | |
| 52 | 64 | } else { |
| 53 | 65 | console.warn(`does not implement av1+opus`); |
| 54 | 66 | } |
| 55 | 67 | |
| 56 | // Native HLS support (Safari v6) | |
| 57 | const video = document.querySelector("video")!; | |
| 58 | if (video.canPlayType("application/x-mpegURL")) { | |
| 59 | return "hls-native"; | |
| 60 | } | |
| 61 | ||
| 62 | // @ts-expect-error | |
| 63 | return (hls ??= import("/js/scripts/vendor/hls.js")) | |
| 64 | .then((hls): PlaybackMode => { | |
| 65 | // Polyfill HLS (Chrome 23, Firefox 42, IE11 on Win8) | |
| 66 | // TODO: ES modules have a minimum of Chrome 63 or Firefox 60 | |
| 67 | if (hls.default.isSupported()) return "hls-polyfill"; | |
| 68 | // Other browsers | |
| 69 | return "none"; | |
| 70 | }); | |
| 68 | return canNativelyPlayHls().then((nativeHls): PlaybackMode => { | |
| 69 | if (nativeHls) return "hls-native"; | |
| 70 | // @ts-expect-error | |
| 71 | return (hls ??= import("/js/scripts/vendor/hls.js")) | |
| 72 | .then((hls): PlaybackMode => { | |
| 73 | // Polyfill HLS (Chrome 23, Firefox 42, IE11 on Win8) | |
| 74 | // TODO: ES modules have a minimum of Chrome 63 or Firefox 60 | |
| 75 | if (hls?.default?.isSupported?.() || hls?.isSupported()) { | |
| 76 | return "hls-polyfill"; | |
| 77 | } | |
| 78 | console.warn("does not support hls.js"); | |
| 79 | // Other browsers | |
| 80 | return "none"; | |
| 81 | }); | |
| 82 | }); | |
| 83 | }) | |
| 84 | .catch((e): PlaybackMode => { | |
| 85 | console.warn(e); | |
| 86 | console.warn("falling back to default player"); | |
| 87 | return "none"; | |
| 71 | 88 | }) |
| 72 | .catch((): PlaybackMode => "none") | |
| 73 | 89 | .then((mode) => { |
| 74 | 90 | console.info(`selected playback mode: ${mode}`); |
| 75 | 91 | return mode; |
| ... | ... | @@ -115,7 +131,8 @@ function hydrateVideoInner( |
| 115 | 131 | enableSafariAirplay(); |
| 116 | 132 | player.initialize(video, dashFile, false); |
| 117 | 133 | video.controls = true; |
| 118 | onCloverVideoInit?.(id, video); | |
| 134 | // TODO: be less stupid about this | |
| 135 | window.onCloverVideoInit?.(id, video); | |
| 119 | 136 | return; |
| 120 | 137 | } |
| 121 | 138 | |
| ... | ... | @@ -219,7 +236,6 @@ function hydrateVideoInner( |
| 219 | 236 | onCloverVideoInit?.(id, video); |
| 220 | 237 | } else if (mode === "hls-polyfill") { |
| 221 | 238 | hls!.then((hls) => { |
| 222 | console.log(hls); | |
| 223 | 239 | // @ts-expect-error suspicious |
| 224 | 240 | const player = new (hls.default ?? hls.Hls)(); |
| 225 | 241 | player.loadSource(hlsFile); |
| ... | ... | @@ -234,7 +250,6 @@ function hydrateVideoInner( |
| 234 | 250 | } |
| 235 | 251 | } |
| 236 | 252 | |
| 237 | declare var ManagedMediaSource: typeof MediaSource | null; | |
| 238 | 253 | function shouldPlayAv1(): Promise<{ hardware: boolean; supported: boolean }> { |
| 239 | 254 | const MediaSource = globalThis.ManagedMediaSource ?? globalThis.MediaSource; |
| 240 | 255 | if (!MediaSource.isTypeSupported('audio/webm;codecs="opus"')) { |
| ... | ... | @@ -268,3 +283,29 @@ function shouldPlayAv1(): Promise<{ hardware: boolean; supported: boolean }> { |
| 268 | 283 | })); |
| 269 | 284 | } |
| 270 | 285 | } |
| 286 | ||
| 287 | function canNativelyPlayHls() { | |
| 288 | const video = document.createElement("video"); | |
| 289 | const canPlay = video.canPlayType("application/x-mpegURL"); | |
| 290 | ||
| 291 | if (!canPlay) return Promise.resolve(false); | |
| 292 | ||
| 293 | return new Promise((resolve) => { | |
| 294 | video.src = "data:application/x-mpegURL,#EXTM3U"; | |
| 295 | video.load(); | |
| 296 | ||
| 297 | function cleanup() { | |
| 298 | clearTimeout(timeout); | |
| 299 | video.removeEventListener("error", onError); | |
| 300 | video.removeEventListener("loadedmetadata", onLoad); | |
| 301 | video.src = ""; | |
| 302 | } | |
| 303 | ||
| 304 | const onError = () => (cleanup(), resolve(false)); | |
| 305 | const onLoad = () => (cleanup(), resolve(true)); | |
| 306 | const timeout = setTimeout(onError, 100); | |
| 307 | ||
| 308 | video.addEventListener("error", onError); | |
| 309 | video.addEventListener("loadedmetadata", onLoad); | |
| 310 | }); | |
| 311 | } |