summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/transport
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-17 10:12:31 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-17 10:12:31 +0200
commita98445ce246509b0d2600df14907f72b448a6d10 (patch)
tree023e1c09d8fa42f67f5da8ff32a22f271359d843 /packages/meshbay-node/src/meshbay_node/transport
parentf4fd6db8faa15bf02f38a14b652cc46936f8d6bf (diff)
downloadmeshbay-a98445ce246509b0d2600df14907f72b448a6d10.tar.gz
feat: let the viewer pick the audio track
The streaming path mapped 0:a:0 unconditionally, so a dubbed film played in whichever language was muxed first and the others were unreachable. The node now enumerates the tracks in stream_init and honours audio_track in stream_req; switching is the seek path, since one ffmpeg carries one track. MNP 3.2, additive: the player draws its selector from the node's own list and never from a version number, so an older node is never asked for a track it would ignore. MNP_MIN_SUPPORTED does not move. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/transport')
-rw-r--r--packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py46
1 files changed, 41 insertions, 5 deletions
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 99ba3c9..b39941d 100644
--- a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
+++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
@@ -5948,11 +5948,14 @@ class WebRTCPeerSession:
file_hash = bytes.fromhex(entry.id)
try:
- codec_str, duration, has_audio, _width, _height, raw_video_codec = \
- await _probe_video(str(file_path))
+ probe = await _probe_video(str(file_path))
except Exception as e:
self._send({"type": "error", "detail": f"Probe failed: {e}"})
return
+ codec_str = probe.codec
+ duration = probe.duration
+ has_audio = probe.has_audio
+ raw_video_codec = probe.raw_codec_name
# 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
@@ -6051,8 +6054,23 @@ class WebRTCPeerSession:
codec_str = "avc1.640029,mp4a.40.2" if has_audio else "avc1.640029"
else:
codec_args = ["-c:v", "copy"]
+ # Which audio track. A dubbed film carries several and the first one is
+ # not a neutral default — it is whatever the person who muxed the file
+ # happened to put first, which across a real library is overwhelmingly
+ # one language. Out of range falls back to the first rather than
+ # refusing: the client's list comes from a `stream_init` that may
+ # predate the file being replaced on disk, and a viewer who asked for
+ # the second track of a file that now has one wants the film, not an
+ # error. `stream_init` says which track was actually used, the same way
+ # it says which `start` was actually used and for the same reason.
+ try:
+ audio_track = int(msg.get("audio_track", 0) or 0)
+ except (TypeError, ValueError):
+ audio_track = 0
+ if not 0 <= audio_track < len(probe.audio_tracks):
+ audio_track = 0
if has_audio:
- map_args += ["-map", "0:a:0"]
+ map_args += ["-map", f"0:a:{audio_track}"]
# Downmixed to stereo: a WEB-DL's 5.1 track becomes 6-channel AAC
# with no "-ac", which ffprobe and VLC accept fine but which some
# browsers' MSE decoder rejects outright once real fragments are
@@ -6081,6 +6099,22 @@ class WebRTCPeerSession:
# this is what the client adds back (`SourceBuffer.timestampOffset`)
# to put the fragments where they belong on the timeline.
"start": start,
+ # The track list is how a client discovers that this node can
+ # switch language at all — there is no version check anywhere in
+ # the player. A node that does not send it gets no selector, and
+ # the client then never sends `audio_track` to a peer that would
+ # ignore it and serve the wrong language without saying so.
+ "audio_tracks": [
+ {
+ "i": tr.ordinal,
+ "lang": tr.language,
+ "title": tr.title,
+ "codec": tr.codec_name,
+ "ch": tr.channels,
+ }
+ for tr in probe.audio_tracks
+ ],
+ "audio_track": audio_track if has_audio else None,
})
# A client that says nothing gets the old behaviour, which is why this
@@ -6097,8 +6131,10 @@ class WebRTCPeerSession:
self._stream_started_at = time.monotonic()
self._stream_segments = 0
reason = "eof"
- log.info("stream: stream_init sent file=%s paced=%s credits=%d start=%.1fs",
- file_id[:12], paced, self._stream_credit, start)
+ log.info("stream: stream_init sent file=%s paced=%s credits=%d start=%.1fs "
+ "audio=%s/%d",
+ file_id[:12], paced, self._stream_credit, start,
+ audio_track if has_audio else "-", len(probe.audio_tracks))
try:
while True:
if paced and not await self._await_stream_credit():