diff options
3 files changed, 61 insertions, 3 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/music-player.js b/packages/meshbay-hub/src/meshbay_hub/static/music-player.js index 7c81f4e..5f2e33c 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/music-player.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/music-player.js @@ -36,9 +36,12 @@ const MIME_BY_EXT = { wav: 'audio/wav', aac: 'audio/aac', m4a: 'audio/mp4', }; -// Kept in sync with the node's BROWSER_INCOMPATIBLE_AUDIO_EXTS -// (webrtc_server.py) — both name the same two formats no mainstream -// browser's <audio> element decodes natively. +// The same two formats as the node's BROWSER_INCOMPATIBLE_AUDIO_EXTS +// (webrtc_server.py), which is the one that decides: the node refuses a +// transcode request for anything else. This is here so the player does not +// ask for one it knows will be refused — not because it enforces the rule. +// It used to be the only thing that did, and a member's own message never +// passed through it. const NEEDS_TRANSCODE_RE = /\.(wma|mpc)$/i; function guessMime(name) { 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 d2ec1de..ba8a3e4 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py +++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py @@ -4039,6 +4039,25 @@ class WebRTCPeerSession: if not entry: self._send({"type": "error", "detail": "File not found"}) return + + # The gate `BROWSER_INCOMPATIBLE_AUDIO_EXTS` exists for, applied where it + # costs something. Nothing on the node read it: the player asks for these + # two extensions and no others, and `music-player.js` described itself as + # "kept in sync with the node's" constant — so the whole restriction lived + # in the caller, and a member's own message is not the caller. + # + # What that let through: this converts a *whole file* and holds a + # transcode slot shared with video streaming while it runs. Pointed at a + # two-hour film it spends minutes of the operator's CPU and a slot every + # other viewer is queued behind. `AUDIO_TRANSCODE_MAX_BYTES` catches the + # result, after the work; only this catches the work. + if Path(entry.name).suffix.lower() not in BROWSER_INCOMPATIBLE_AUDIO_EXTS: + self._send({ + "type": "error", + "detail": "This file does not need transcoding — play it directly.", + "code": "transcode_not_applicable", + }) + return file_path, refusal = await off_disk(ctx["roots"], _locate, ctx["roots"], entry) if refusal is not None: self._send({"type": "error", "detail": refusal}) diff --git a/packages/meshbay-node/tests/test_audio_transcode.py b/packages/meshbay-node/tests/test_audio_transcode.py index 3e6015d..a6557f0 100644 --- a/packages/meshbay-node/tests/test_audio_transcode.py +++ b/packages/meshbay-node/tests/test_audio_transcode.py @@ -191,3 +191,39 @@ async def test_multi_chunk_cached_blob_reassembles_correctly(tmp_path, media_cac assert len(chunk_msgs) == total_chunks reassembled = _reassemble_file_chunks(session.sent, gek, blob_hash) assert reassembled == blob + + +async def test_only_the_two_formats_that_need_it_are_transcoded(tmp_path, media_cache): + """ + The gate that was written down and never applied. + + `BROWSER_INCOMPATIBLE_AUDIO_EXTS` was read by nobody: the player asked only + for `.wma` and `.mpc`, and the node converted whatever file id it was given. + A member's own message is not the player, and this conversion is whole-file + while holding a transcode slot shared with video streaming — so one message + naming a two-hour film spends minutes of the operator's CPU and a slot every + other viewer is queued behind. The size cap catches the result; only this + catches the work. + """ + clip = tmp_path / "feature.mkv" + clip.write_bytes(b"not really a film, and never opened") + session, file_id = _session(tmp_path, clip, generate_gek(), media_cache) + + await session._do_audio_transcode_request({"file_id": file_id}) + + (msg,) = session.sent + assert msg["type"] == "error" + assert msg["code"] == "transcode_not_applicable" + + +@pytest.mark.skipif(not _HAVE_FFMPEG, reason="ffmpeg/ffprobe not installed") +async def test_the_two_formats_that_do_need_it_still_pass(tmp_path, media_cache): + """The gate must admit what it exists for; a refusal of everything is not a gate.""" + clip = tmp_path / "clip.wma" + _make_wma_clip(clip) + session, file_id = _session(tmp_path, clip, generate_gek(), media_cache) + + await session._do_audio_transcode_request({"file_id": file_id}) + + assert [m for m in session.sent if m.get("type") == MNP.AUDIO_TRANSCODE_RESP], ( + f"a WMA file was refused: {session.sent}") |