From a0be6bfcac07a256949082e935e1be06753ab872 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Thu, 15 Oct 2026 10:17:29 +0200 Subject: feat(cli): stream — a film or a song played in a media player MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `meshbay-cli stream PATH` serves one video or sound file over HTTP on 127.0.0.1 for VLC, mpv or any player that reads HTTP; `--player PROGRAM` starts the player, and closing it stops the stream, as Ctrl+C does. The file goes as the node stores it: no remux, no transcode, no stream slot on the node. Byte ranges are answered from leaseless file_req chunks decrypted in memory, eight ahead and the last 24 kept. Loopback only, a random token in the address, a foreign Host refused (DNS rebinding), an audio or video type with nosniff. The connection is pinged and reopened when it dies; a refusal by the node stops the stream (exit 5). Co-Authored-By: Claude Opus 5.5 --- packages/meshbay-cli/tests/test_e2e_local.py | 78 ++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) (limited to 'packages/meshbay-cli/tests/test_e2e_local.py') diff --git a/packages/meshbay-cli/tests/test_e2e_local.py b/packages/meshbay-cli/tests/test_e2e_local.py index fb251bd..c385bb6 100644 --- a/packages/meshbay-cli/tests/test_e2e_local.py +++ b/packages/meshbay-cli/tests/test_e2e_local.py @@ -1172,3 +1172,81 @@ def test_chat_follow_against_the_real_node(world): _, err = follower.communicate(timeout=20) assert follower.returncode == 130 assert "reconnecting" not in err, err + + +@pytest.mark.skipif(not (shutil.which("ffmpeg") and shutil.which("ffprobe") + and shutil.which("cvlc")), + reason="ffmpeg, ffprobe and VLC are needed") +def test_stream_a_film_to_real_players(world, tmp_path): + """A film put on the real node, then streamed: ffprobe reads it, ffmpeg seeks + near its end, a foreign Host is refused, Ctrl+C stops it; then VLC started + with --player plays it to the end and, closing, ends the stream.""" + import signal + _cli_signed_in(world, "cli-stream") + film = tmp_path / "Night Drive.mkv" + subprocess.run(["ffmpeg", "-v", "error", "-f", "lavfi", "-i", + "testsrc=duration=12:size=640x360:rate=25", "-f", "lavfi", "-i", + "sine=frequency=440:duration=12", "-vf", "noise=alls=30:allf=t", + "-c:v", "libx264", "-b:v", "3M", + "-g", "50", "-c:a", "aac", "-shortest", str(film)], check=True) + assert film.stat().st_size > 3 * 1024 * 1024 # several of the node's chunks + r = cli(world, "put", str(film), f"{_handle()}/share", home="cli-stream") + # After a test that withdrew the node's copy of the keys, this machine is a + # new device: approved as the application would approve it. + if r.returncode == 5 and "Waiting for this machine to be approved" in r.stderr: + code = re.search(r"code ([0-9A-Z]{4}-[0-9A-Z]{4})", r.stderr).group(1) + asyncio.run(world["app"].approve(code)) + r = cli(world, "put", str(film), f"{_handle()}/share", home="cli-stream") + assert r.returncode == 0, r.stderr + path = f"{_handle()}/share/Night Drive.mkv" + deadline = time.monotonic() + 60 # once the node has indexed it + while "Night Drive.mkv" not in cli(world, "ls", f"{_handle()}/share", + home="cli-stream").stdout: + assert time.monotonic() < deadline, "the film was never indexed" + time.sleep(1) + + env = {**os.environ, "MESHBAY_CLI_HOME": str(world["root"] / "cli-stream"), + "PYTHON_KEYRING_BACKEND": "keyring.backends.fail.Keyring"} + exe = os.environ.get("MESHBAY_CLI_E2E_BIN") or str(BIN / "meshbay-cli") + streamer = subprocess.Popen([exe, "--json", "stream", path], env=env, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + try: + line = streamer.stdout.readline() + assert line, streamer.stderr.read() + shown = json.loads(line) + url = shown["url"] + assert shown["size"] == film.stat().st_size + probe = subprocess.run(["ffprobe", "-v", "error", "-show_entries", + "format=duration:stream=codec_name", "-of", "json", url], + capture_output=True, text=True, timeout=60) + assert probe.returncode == 0, probe.stderr + info = json.loads(probe.stdout) + assert abs(float(info["format"]["duration"]) - 12) < 0.5 + assert {s["codec_name"] for s in info["streams"]} == {"h264", "aac"} + frame = tmp_path / "frame.png" + seek = subprocess.run(["ffmpeg", "-v", "error", "-ss", "10", "-i", url, + "-frames:v", "1", str(frame)], + capture_output=True, text=True, timeout=60) + assert seek.returncode == 0 and frame.stat().st_size > 0, seek.stderr + port = url.split("/")[2].split(":")[1] + foreign = httpx.get(url, headers={"Host": f"rebound.example:{port}"}, timeout=10, + trust_env=False) + assert foreign.status_code == 403 + finally: + streamer.send_signal(signal.SIGINT) + _, err = streamer.communicate(timeout=20) + assert streamer.returncode == 130, err + with pytest.raises(httpx.HTTPError): + httpx.get(url, timeout=5, trust_env=False) + + player = tmp_path / "vlc-to-the-end" + log = tmp_path / "vlc.log" + player.write_text("#!/bin/sh\nexec cvlc --play-and-exit --intf dummy --vout dummy " + f"--aout dummy \"$1\" >{log} 2>&1\n") + player.chmod(0o755) + started = time.monotonic() + r = subprocess.run([exe, "stream", "--player", str(player), path], env=env, + capture_output=True, text=True, timeout=120) + assert r.returncode == 0, r.stderr + log.read_text() + assert "The player was closed" in r.stderr + assert time.monotonic() - started > 10, "VLC played the film, in real time" -- cgit v1.2.3