Skip to content

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

stream-doctor

stream-doctor detecting a 500 ms audio delay in a buggy RTMP to HLS pipeline

The demo runs an RTMP to HLS pipeline that delays the audio track by 500 ms. A viewer of the output would notice that the lips are out of sync, but not by how much. stream-doctor measures the mismatch and reports its exact value.

npm CI

Automated end-to-end testing for video infrastructure.

Status: early proof of concept. This repository shows the core idea working on a single RTMP to HLS scenario with a single metric, the audio/video drift. It is a preview of the approach, not a finished product. The planned scope, including synthetic sources, a wider set of metrics, chaos testing (packet loss, bandwidth limits, jitter), more ingest and playback protocols, and SDKs for popular test runners, is described in issue #1. Feedback on it is welcome.

Join the conversation in discussion #16.

stream-doctor publishes a stream into your pipeline, watches what comes out the other end and turns the comparison into metrics you can assert on. The goal is that regressions in sync, latency, frame delivery or playback quality get caught by CI, not by someone clicking through a list of manual checks before a release.

Built on the Membrane Framework and Boombox.

The project consists of:

Installation

Requires Node 20+.

npm install stream-doctor

This installs the TypeScript SDK. The SDK talks to the daemon, which does the actual streaming and measuring, so you also need to make sure it is available. There are three ways to do that.

Precompiled daemon

On macOS (Apple Silicon), Linux (x86_64) and Linux (arm64) the daemon binary is downloaded automatically along with the package, as an optional dependency on @stream-doctor/<platform>. Nothing else is needed.

Running the daemon from source

On other platforms, or to run the latest code, start the daemon from the Mix project in daemon/. This needs Elixir 1.19+, see Running from source, then connect with session({ daemonUrl: "http://localhost:4040" }).

Building the daemon binary

To get a standalone executable like the precompiled ones, see Standalone binary and pass it with session({ binary }).

Getting started

Tests are written the same way you already write browser tests. A session groups a publisher and any number of viewers, the publisher streams marked media into your infrastructure, and each viewer collects metrics from the playback URL your product exposes:

import * as stream_doc from "stream-doctor";

const session = await stream_doc.session(); // spawns the bundled daemon on a free port
const streamer = session.publish(rtmpUrl, { file: "test.mp4" });
await streamer.waitUntilLive();

const viewer = await session.watch(hlsUrl);
// ... let it measure ...
const metrics = await viewer.stop();

expect(Math.abs(metrics.av_drift.drift_ms)).toBeLessThan(50); // any test runner's assertion

The stream carries markers in both audio and video that identify each moment of the source. Because a viewer can recover the original position from what it receives, it can measure exactly what the pipeline did to the stream.

To try it against a local pipeline, start one of the examples (they need ffmpeg and python3):

examples/working_infra.sh # a local RTMP -> HLS pipeline to measure against

Then run a script like the one above against it, for example examples/av_drift.ts with node examples/av_drift.ts. The example publishes an .mp4 fixture, test.mp4 in the current directory by default, or the path given as the first argument. The repository does not ship one, so bring any short file with both audio and video. examples/buggy_infra.sh is the same pipeline with the audio delayed by 500 ms, which the check should catch.

session() spawns the daemon bundled with the npm package on a free port, session({ port }) picks the port and session({ binary }) the executable. To use a daemon you started yourself, pass session({ daemonUrl: "http://..." }), then nothing is spawned.

HTTP API

The TypeScript client above starts the binary for you. If you prefer to drive it directly, it listens on port 4040 (PORT to change it) and speaks JSON:

  • POST /streamer {"input": "test.mp4", "rtmp_url": "rtmp://..."}, GET /streamer, DELETE /streamer,
  • POST /viewers {"hls_url": "https://....m3u8"}, GET /viewers/:id, DELETE /viewers/:id,
  • GET /status.

Metrics collected by a viewer are returned under metrics.

Copyright and License

Copyright 2026, Software Mansion

Software Mansion

Licensed under the Apache License, Version 2.0

About

Playwright for your video pipeline

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages