summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-09 17:16:29 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-09 17:16:29 +0200
commit0b9b85bf2d434177968dfc6e27555d54d703ff7e (patch)
tree7a8bf615f3e60f4bb68d1175c2e1acfce5a18d69 /packages/meshbay-node/src
parent4d5a07299c792de1251622a41ce7f94870b363fa (diff)
downloadmeshbay-0b9b85bf2d434177968dfc6e27555d54d703ff7e.tar.gz
fix(node): a video the browser cannot decode is re-encoded, not refused
Streaming an Xvid/MP3 .avi answered "Unsupported video codec" — a refusal, on a file ffmpeg re-encodes at about six times playback speed on the machine that reported it. Nothing about the source was wrong. The node simply never reached its own re-encode path. `probe_video` maps a source codec to an MSE codec string and knows four: h264, hevc, vp9, av1. Everything else returns None, because there is no MediaSource decoder in any mainstream browser to give a string to — MPEG-4 Part 2 (Xvid, DivX), MPEG-2, VC-1, WMV, Theora. `_stream_video_inner` read that None as a verdict on the file and refused, while the re-encode sitting twenty lines below it was gated on `raw_video_codec in BROWSER_INCOMPATIBLE_VIDEO_CODECS` — a set containing "hevc" and nothing else. So the whole ffmpeg fallback existed, worked, and was unreachable for every codec that most needed it. The setting that governs the fallback has documented the intended behaviour since it was introduced: draft-v6 §2.11 says `transcode_incompatible_video` covers "HEVC *and other browser-incompatible video codecs*". Only HEVC was ever wired up. Two questions were being answered by one value, and they are separated now. "Is there a video stream at all" is the only thing this path genuinely cannot serve, and the only refusal left. "Can it be copied" needs both an MSE string to put in `stream_init` and a codec browsers decode; a source failing either is re-encoded. The operator's opt-out keeps meaning what it says, and it no longer means the same thing for every source, because it cannot: HEVC has a codec string, so `transcode_incompatible_video = false` falls back to a copy and the viewer's own decoder decides (unchanged). MPEG-4 Part 2 has none, so there is nothing to fall back to — a `stream_init` with no codec string is one the client refuses before the first byte — and the stream is refused naming the setting. "Unsupported video codec" is what sent this report to the file, and the file was fine. Verified against the reported file end to end: ffprobe reports mpeg4/mp3 720x404, the decision comes out `can_copy=False`, and the pipeline's exact argv produces H264 High level 4.1 plus stereo AAC-LC — matching the `avc1.640029,mp4a.40.2` that `stream_init` advertises and that the client puts through MediaSource.isTypeSupported byte for byte. test_stream_hevc_transcode.py becomes test_stream_video_transcode.py: it was always about the policy rather than about one codec, and it now carries both halves of it, with a synthetic Xvid/MP3 .avi built the same way as the HEVC clip. Its module-level skip on libx265 went with it — an ffmpeg without x265 still encodes MPEG-4 Part 2, so that marker was skipping the reported defect entirely on any box without it; it now gates the HEVC cases alone. Three cases added, checked against the unfixed source. Hub and node suites 2269 passed, 4 skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019GXmScYB1uR29YCt74si9J
Diffstat (limited to 'packages/meshbay-node/src')
-rw-r--r--packages/meshbay-node/src/meshbay_node/media_probe.py8
-rw-r--r--packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py49
2 files changed, 51 insertions, 6 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/media_probe.py b/packages/meshbay-node/src/meshbay_node/media_probe.py
index e0c59e3..ad668fa 100644
--- a/packages/meshbay-node/src/meshbay_node/media_probe.py
+++ b/packages/meshbay-node/src/meshbay_node/media_probe.py
@@ -43,6 +43,14 @@ async def probe_video(
faithfully reports "hev1..." for a source this pipeline cannot actually
deliver copied.
+ **A `None` codec string means "this must be re-encoded", not "this cannot
+ be played".** Only the four codecs a browser can decode through MediaSource
+ are mapped; everything else — MPEG-4 Part 2 (Xvid, DivX), MPEG-2, VC-1,
+ WMV, Theora — has no string to report because there is no browser decoder
+ to report it to, and the streaming path answers that by re-encoding to
+ H264. It answered it by refusing until 2026-09-09, which read to the
+ operator as a broken file rather than as an unwired code path.
+
width/height come from the same ffprobe call (one extra `-show_entries`
field, no second process spawn) — resolution is deliberately never
guessed from the filename (docs/mediacenter.md §3.5).
diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
index 507650a..a0efa83 100644
--- a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
+++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
@@ -5673,8 +5673,13 @@ class WebRTCPeerSession:
self._send({"type": "error", "detail": f"Probe failed: {e}"})
return
- if not codec_str:
- self._send({"type": "error", "detail": "Unsupported video codec"})
+ # No video stream at all is the only thing this path cannot serve, and
+ # it is the only thing refused here. A source with no MSE codec string
+ # is emphatically not that — it is the case the re-encode below exists
+ # for, and refusing it here (as "Unsupported video codec") is what this
+ # fixed.
+ if not raw_video_codec:
+ self._send({"type": "error", "detail": "No video stream in this file"})
return
# Where to begin. Seeking is a stream restarted somewhere else: the
@@ -5713,12 +5718,44 @@ class WebRTCPeerSession:
# decode problem as E-AC-3, just without ffmpeg also refusing to mux
# it). Transcoding audio is cheap; it does not change the cost model
# the transcode-slot semaphore is sized around.
- transcode_video = (
- raw_video_codec in BROWSER_INCOMPATIBLE_VIDEO_CODECS
- and self._ctx.get("transcode_incompatible_video", True)
- )
+ #
+ # Two kinds of source cannot be copied, and both re-encode:
+ #
+ # - one whose MSE codec string is real but that no mainstream browser
+ # decodes — HEVC, BROWSER_INCOMPATIBLE_VIDEO_CODECS;
+ # - one with **no MSE codec string at all**: MPEG-4 Part 2 (Xvid,
+ # DivX), MPEG-2, VC-1, WMV, Theora. `stream_init` has to carry a
+ # string the client puts through MediaSource.isTypeSupported, and
+ # `probe_video` returns None for these precisely because no browser
+ # has a MediaSource decoder for them, so there is none to carry.
+ # This second kind used to be refused outright with "Unsupported
+ # video codec" — which named the source's problem and not the
+ # node's answer to it, since ffmpeg re-encodes these in real time on
+ # any machine that can run this daemon. Reported live against an
+ # Xvid/MP3 .avi. `transcode_incompatible_video`'s own documentation
+ # (draft-v6 §2.11) already said "HEVC *and other browser-
+ # incompatible video codecs*"; only HEVC was ever wired up.
+ can_copy = (bool(codec_str)
+ and raw_video_codec not in BROWSER_INCOMPATIBLE_VIDEO_CODECS)
+ allow_transcode = self._ctx.get("transcode_incompatible_video", True)
+ transcode_video = not can_copy and allow_transcode
+ if not can_copy and not allow_transcode and not codec_str:
+ # The operator turned the fallback off and there is nothing to fall
+ # back *to*: a stream_init with no codec string is one the client
+ # refuses before the first byte arrives. Which of the two it is
+ # matters — "unsupported codec" sends the reader to look at the
+ # file, and the file is fine.
+ log.info("stream: %s is %s, which needs a re-encode, and "
+ "transcode_incompatible_video is off — refusing",
+ entry.name, raw_video_codec)
+ self._send({"type": "error",
+ "detail": "This video needs transcoding, which the "
+ "operator has turned off"})
+ return
map_args = ["-map", "0:v:0"]
if transcode_video:
+ log.info("stream: re-encoding %s (%s) to H264", entry.name,
+ raw_video_codec)
# -pix_fmt yuv420p: a 10-bit or 4:4:4 HEVC source (common for HDR
# WEB-DLs) fails "-profile:v high" outright otherwise — libx264's
# High profile is 8-bit 4:2:0 only. Downsampling loses nothing a