Repository navigation
feat(website): serve the hero film in two qualities - #839
Conversation
FilmScreen grows an optional lowSrc: the full-quality source stays the default <source>, and an inline pick swaps in the low variant before the browser commits to the download for clients that ask for it — navigator.connection.saveData, a slow effectiveType, or a small viewport on a non-retina screen. The swap preserves playback position and plays state. Hero passes /media/hero-low.mp4 (committed alongside as the current video so the attribute never dangles); build-media.mjs encodes both variants (crf 15 full, crf 22 low) from the high and low hero-animation renders and records both sources in hero.meta.json, refusing a low variant whose film.meta.json disagrees with the full one on NIP count.
🦋 Changeset detectedLatest commit: 728427c The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
[Medium impact] Adds dual-quality video serving to the hero section. Fix the render-pair check before merging so an old low film cannot pass as a match for a new full film. Findings
|
| const lowMeta = readFilmMeta(landingLowVideo) | ||
| if (lowMeta?.nipCount === filmMeta.nipCount) { | ||
| encodeHero(landingLowVideo, path.join(MEDIA, 'hero-low.mp4'), 22) |
There was a problem hiding this comment.
lowMeta and filmMeta read the same output/film.meta.json for the default full and low renders. render.ts overwrites that file after either render. If an old low render remains when a new full render finishes, this comparison always passes and ships the old film alongside the new one. Write and check metadata tied to each video before accepting the pair.
| <video muted loop playsinline preload="auto" poster={poster} data-autoplay data-low-src={lowSrc} aria-label={title}> | ||
| <source src={src} type="video/mp4" /> |
There was a problem hiding this comment.
Full video downloads before selection
The full video is already discoverable with preload="auto" before the quality picker runs. Astro processes this ordinary <script> as a module, so it does not block parsing before the browser starts loading the full source. Data-saver and small-screen visitors can download part of the full video and then start another download for the low video. Keep the full URL out of the active source until the choice is made, with a separate fallback for visitors without JavaScript.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| navigator.connection?.saveData === true || | ||
| (navigator.connection?.effectiveType ?? '4g') in { 'slow-2g': 1, '2g': 1, '3g': 1 } || |
There was a problem hiding this comment.
Browser checks add type errors
navigator.connection is not declared by the standard TypeScript DOM types, and the site has no declaration that adds it. Optional chaining makes unsupported browsers safe, but does not fix the type error. These two accesses add errors to the site's check command. Add a typed optional connection property and use it for both checks.
| nipCount: filmMeta.nipCount, | ||
| generatedAt: filmMeta.generatedAt, | ||
| sourceVideo: path.relative(HERO_DIR, landingVideo), | ||
| ...(landingLowVideo ? { sourceLowVideo: path.relative(HERO_DIR, landingLowVideo) } : {}), |
There was a problem hiding this comment.
Metadata names a rejected source
sourceLowVideo is recorded whenever a candidate exists, even when its metadata disagrees and the script skips encoding it. With a mismatched HERO_LOW_SRC, hero.meta.json therefore names a rejected source rather than the source of the served file. Record this field only after the low video is successfully encoded.
What
Prepares the site for the upcoming high-res hero render:
FilmScreengrows an optionallowSrc. The full-quality source remains the default<source>; an inline pick swaps in the low variant before the browser commits to the full download when the client asks for it —navigator.connection.saveData, a sloweffectiveType, or a small viewport on a ≤2× display. The swap preserves playback position and plays state.Heropasses/media/hero-low.mp4. That file is committed now as a copy of the current video, so the attribute never dangles; the GPU render (next step, needscontainers/gpu-render/gpu-render-setup.sh --applyon the host) replaces both files with real high/low variants.build-media.mjsencodes both variants (crf 15 full, crf 22 low) from the two hero-animation renders, records both sources inhero.meta.json, and refuses a low variant whosefilm.meta.jsondisagrees with the full render on NIP count (both renders must come from the same code state).HERO_LOW_SRCenv override mirrorsHERO_SRCfor the low leg.Verified
pnpm run buildin website/site: 14 pages,data-low-src="/media/hero-low.mp4"on the hero video, pick logic inline in the page.build-media.mjsexercised against fixture renders in both shapes: full-only (skips the low leg with a reason) and full+low (encodes both, writes both sources to meta). Fixture outputs discarded; the committed media stays the current real video.Next
The GPU render session (host side) produces the real high-res film;
build-mediathen shipshero.mp4(very_high, crf 15) +hero-low.mp4(low, crf 22), the film guard flips back to hard-fail, and the NIP-77 staleness closes in the same render.