GoForum🌐 V2EX

Transparent WebM in Browsers: Alpha, Fallbacks, and Range Requests

sbwffgli · 2026-08-15 14:57 · 0 次点赞 · 0 条回复

I have been working on the output side of a video background-removal pipeline. My first assumption was that once the model returned a file with an alpha channel, the web integration would be little more than adding a <video> element. It turned out that there are quite a few conditions between “the file contains alpha” and “the user sees transparency in a browser.”

The first issue was the output format. MP4, MOV, WebM, and MKV are containers, so the extension alone tells you very little about transparency. The usual MP4/H.264 combination does not carry the kind of alpha channel expected in normal web playback. MOV is not automatically transparent either; the codec inside still needs alpha support, as with an appropriate ProRes 4444 configuration.

I eventually reduced the server-side list of transparent outputs to three combinations:

const TRANSPARENT_OUTPUT_FORMATS = new Set([
  'webm_vp9',
  'mov_proresks',
  'mkv_vp9',
]);

function isOutputCompatible(background: string, format: string) {
  return background !== 'transparent' ||
    TRANSPARENT_OUTPUT_FORMATS.has(format);
}

This check has to live on the server. Hiding incompatible options in a dropdown is not enough; an older client or a handwritten request could still submit a job asking for a transparent background and an H.264 MP4 result.

I later put the same constraint into the actual video background-removal workflow. The page itself was the easy part. Most of the work went into the format matrix and browser behavior rather than the upload UI.

Alpha in ffprobe does not guarantee alpha in the browser

When checking an output file, I usually start with:

ffprobe -v error \
  -select_streams v:0 \
  -show_entries stream=codec_name,pix_fmt:stream_tags=alpha_mode \
  -of json \
  output.webm

Pixel formats such as yuva420p, rgba, and bgra are useful signals. WebM has a trap, though: alpha data may be stored separately in BlockAdditional. The main video stream can still report yuv420p while exposing an alpha_mode tag. A check as simple as pix_fmt.includes('a') will therefore reject some valid transparent files.

Even after confirming that the file is correct, the browser remains a separate problem. Safari can decode VP8 or VP9 video without necessarily rendering the alpha channel. The same WebM can be transparent in Chrome and opaque in Safari.

The obvious <source> fallback is less helpful than it first appears:

<video autoplay muted loop playsinline>
  <source src="overlay-alpha.webm" type="video/webm" />
  <source src="overlay-solid.mp4" type="video/mp4" />
</video>

The browser chooses a source primarily by whether it can decode the format, not whether it can composite that format’s alpha channel correctly. If Safari decides that it can play the WebM, it may select the first source and never try the MP4, even when the transparent region is rendered as opaque.

I have not found a completely reliable way to detect alpha-rendering support with canPlayType() alone. The safer approach has been an explicit client policy: use transparent WebM only in environments covered by actual tests, and send everyone else a pre-composited MP4 or a still image. If the transparent video is decorative, removing it is still better than covering the page with a black rectangle.

Previewable and downloadable formats should be separate concepts

I initially wanted every output to be previewable in the page. That was unnecessary. MOV and MKV behavior varies too much across browsers and system decoders. Being a valid downloadable result does not make a format a good candidate for native <video> playback.

The application now treats browser preview support as a separate allowlist:

const BROWSER_PREVIEWABLE = new Set([
  'webm_vp9',
  'mp4_h264',
  'mp4_h265',
  'gif',
]);

MOV and MKV results can still be downloaded, but the page does not promise to play them. This is deliberately conservative, and it is easier to explain than a broken player that works only on certain machines.

There is another source of confusion: seeing black in a standalone player does not always mean the alpha channel is gone. Some players simply use black as the canvas behind transparent pixels. I now verify the file over a checkerboard, a light color, and a dark color. Otherwise, dark edge contamination can disappear into the player’s black background.

Private video previews need proper Range support

Processed files cannot be exposed through permanent public URLs, so previews and downloads go through an authenticated endpoint. A normal file download can return 200, but video seeking causes the browser to send a Range request and expect 206 Partial Content.

I support single ranges and reject multipart ranges. The parser handles roughly these cases:

type ParsedRange =
  | { kind: 'none' }
  | { kind: 'valid'; offset: number; length: number }
  | { kind: 'invalid' };

// Supported:
// bytes=100-199
// bytes=100-
// bytes=-500
//
// Rejected:
// bytes=0-99,200-299

A valid partial response needs at least the following headers:

HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Range: bytes 100-199/1234567
Content-Length: 100
Content-Type: video/webm
Cache-Control: private, no-store

Several edge cases are easy to miss:

  • bytes=-500 means the final 500 bytes, not bytes zero through 500.
  • When the requested end exceeds the file size, it can be clamped to size - 1.
  • A start offset beyond the end of the file should return 416.
  • A 416 response should include Content-Range: bytes */actual-size.
  • The translated offset and length should be passed to object storage. Fetching the complete object and slicing it inside the Worker defeats the point of Range requests.

Once Range handling was correct, seeking and browser preloading became predictable. I previously had a bug where the first frame played, but dragging the timeline caused the player to remain in a loading state. The file encoding was fine; the download endpoint had handled an open-ended range incorrectly.

Two parts are still not especially elegant

The first is transparent WebM capability detection. User-agent branching is ugly, but codec detection alone mixes together “can decode the video” and “can render its alpha channel.” I would be interested in a lightweight pixel-based test that does not introduce cross-origin canvas issues or a visible flash during the initial page load.

The second is H.265 preview support. The current implementation includes mp4_h265 in the previewable set, but support still varies across operating systems, hardware decoders, and browsers. I may eventually move it into the same category as MOV and MKV: available for download, but not guaranteed to preview natively.

The main lesson from this work is that “video processing completed successfully” only describes half of the pipeline. The container, codec, alpha representation, HTTP Range behavior, and final rendering environment all need separate verification. If any one of them is skipped, the user reports the same symptom: the video will not play, or the background is still black.

0 条回复
添加回复
你还需要 登录 后发表回复

登录后可发帖和回复

登录 注册
主题信息
作者: sbwffgli
发布: 2026-08-15
点赞: 0
回复: 0