media: H.264 fallback beside every AV1, and the post links the H.264
All checks were successful
Deploy / build-deploy (push) Successful in 7m30s
All checks were successful
Deploy / build-deploy (push) Successful in 7m30s
The AV1-only publish lasted four hours in the wild: the first viewer on
mobile Safari got "bad media error" from the raw file, because a post's
link is fetched raw — Lemmy apps and browsers play that exact URL, with
no <source> negotiation in front of it. The previous commit's note
("--raw ... if that audience matters") had it backwards: the audience
that cannot play AV1 is not a per-post judgement call, it is whoever
happens to open the thread on an iPhone.
So publish-media.sh now emits two encodings and points the post at the
compatible one:
* Every video transcode also produces <hash>.h264.mp4 (x264 crf 23,
same denoise, same frames), uploaded beside the AV1 under the AV1's
hash — the poster's sibling-naming trick, reused, so fetch-media.sh
finds it by name with nothing to look up.
* The printed URL to paste into the post is the H.264 one. pict-rs
can thumbnail it too, so instance thumbnails come back as a bonus.
fetch-media.sh adopting an own-origin .h264.mp4 URL swaps the AV1 back
in as the page's primary when it is on the mount, and records the H.264
as `fallback` in posts.json. The renderer turns a non-empty fallback
into a <source> pair: the AV1 first with an explicit codecs parameter —
both files are video/mp4, so the parameter is the only thing that lets
a non-AV1 browser skip to the file it can play — and the H.264 second.
Browsers with AV1 keep downloading the small file; Safari before 17
gets one that plays instead of an element that will not.
e2e gains the matching conditional gate: a page offering an av01
<source> must offer an .h264.mp4 one, so an AV1 video can never again
ship without its fallback.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
c21aa636aa
commit
a625176c7d
6 changed files with 142 additions and 25 deletions
|
|
@ -50,6 +50,12 @@ export struct PostMedia {
|
|||
// play — and these posts are their video, so the black box is the page.
|
||||
// Empty for images, and empty when the instance generated no thumbnail.
|
||||
std::string poster;
|
||||
// H.264 rendition of an AV1 video, when tools/publish-media.sh uploaded one
|
||||
// beside it. Non-empty means the renderer emits a <source> pair instead of
|
||||
// a bare src, so a browser without AV1 (Safari before 17, Apple hardware
|
||||
// without the decoder) gets a file it can play. Empty for images and for
|
||||
// videos that need no fallback.
|
||||
std::string fallback;
|
||||
std::int64_t width = 0;
|
||||
std::int64_t height = 0;
|
||||
};
|
||||
|
|
@ -311,6 +317,7 @@ export std::vector<Post> LoadPosts(std::string_view json) {
|
|||
pm.src = std::string(mv.Str("src"));
|
||||
pm.kind = std::string(mv.Str("kind", "image"));
|
||||
pm.poster = std::string(mv.Str("poster"));
|
||||
pm.fallback = std::string(mv.Str("fallback"));
|
||||
pm.width = mv.Int("w");
|
||||
pm.height = mv.Int("h");
|
||||
// Only the two kinds the renderer knows how to emit. Anything
|
||||
|
|
|
|||
|
|
@ -353,10 +353,29 @@ SafeHtml RenderPostMedia(std::span<const PostMedia> media) {
|
|||
: SafeHtml{};
|
||||
if (m.kind == "video") {
|
||||
SafeHtml poster = m.poster.empty() ? SafeHtml{} : Url("poster", m.poster);
|
||||
items.push_back(Format(
|
||||
R"(<video class="post-media__item" controls preload="metadata" )"
|
||||
R"(playsinline{}{}{}></video>)",
|
||||
Url("src", m.src), poster, dims));
|
||||
if (m.fallback.empty() || m.fallback == m.src) {
|
||||
items.push_back(Format(
|
||||
R"(<video class="post-media__item" controls preload="metadata" )"
|
||||
R"(playsinline{}{}{}></video>)",
|
||||
Url("src", m.src), poster, dims));
|
||||
} else {
|
||||
// An AV1 video with its H.264 rendition. Both are .mp4, so the
|
||||
// container alone cannot tell them apart: the codecs parameter
|
||||
// on the first <source> is what lets a browser without AV1
|
||||
// (Safari before 17, Apple hardware without the decoder) skip
|
||||
// it and take the H.264 instead of failing on a file it cannot
|
||||
// decode. The string is advisory and used only for selection —
|
||||
// once a source is picked the browser reads the actual stream —
|
||||
// so the canonical profile-0 8-bit form is right for anything
|
||||
// publish-media.sh emits (yuv420p is pinned there).
|
||||
items.push_back(Format(
|
||||
R"(<video class="post-media__item" controls preload="metadata" )"
|
||||
R"(playsinline{}{}>)"
|
||||
R"(<source{} type="video/mp4; codecs=av01.0.08M.08">)"
|
||||
R"(<source{} type="video/mp4">)"
|
||||
R"(</video>)",
|
||||
poster, dims, Url("src", m.src), Url("src", m.fallback)));
|
||||
}
|
||||
} else {
|
||||
// alt is empty and aria-hidden is absent on purpose: these are
|
||||
// screenshots whose meaning is already in the post title and
|
||||
|
|
|
|||
Loading…
Reference in a new issue