Universal hybrid P2P-CDN middleware for standards-based live streaming.
OpenStreamGrid adds peer-assisted delivery to HLS streams with MPEG-TS (.ts)
or CMAF/fMP4 (.m4s) media segments; LL-HLS partial segments are a roadmap
item. Peers share and SHA-256-verify media segments, while fMP4 init segments
and playlists always load from your origin or CDN. Your
origin continues to encode and publish media while OpenStreamGrid discovers
peers, verifies segments, limits resource use, reports delivery metrics, and
immediately falls back to the origin when P2P cannot meet the playback deadline.
OpenStreamGrid is delivery middleware, not a complete streaming platform. It integrates with your existing origin, CDN, and player stack.
With Docker Compose v2 installed, start the tracker, test origin, and two peers with one command:
git clone --branch v0.6.1 --depth 1 https://github.com/mytrashcan/OpenStreamGrid.git
cd OpenStreamGrid
docker compose up --build --detach
docker compose ps| Service | Local URL |
|---|---|
| Monitoring dashboard | http://localhost:7070/dashboard |
| Tracker health | http://localhost:7070/health |
| Master HLS playlist | http://localhost:8080/hls/stream.m3u8 |
| Peer A / Peer B | http://localhost:9091 / http://localhost:9092 |
The Compose stack generates a live test pattern with FFmpeg. Once healthy, the
peers begin exchanging verified segments and fall back to the origin whenever a
peer is unavailable or too slow. Stop the stack with docker compose down.
OpenStreamGrid v0.6.1 is the current stable release. It is a patch release on top of v0.6.0's reproducible QoE benchmarks, pluggable scheduling policies, and deadline-aware P2P/Origin hedging: it hardens peer joins and WebSocket upgrades, fixes the WebSocket Origin allowlist for Node peers, corrects Helm image tags, and ships multi-architecture container images. Use the release tag for reproducible deployments and review the release notes before upgrading. See the changelog for the complete history.
Tagged container images are published to GHCR for production deployment:
| Component | Image |
|---|---|
| Tracker | ghcr.io/mytrashcan/openstreamgrid-tracker:v0.6.1 |
| Origin | ghcr.io/mytrashcan/openstreamgrid-origin:v0.6.1 |
| Node peer | ghcr.io/mytrashcan/openstreamgrid-peer:v0.6.1 |
Images are built for linux/amd64 and linux/arm64 and tagged both vX.Y.Z
and X.Y.Z (from v0.6.1; earlier releases are linux/amd64 with vX.Y.Z only).
Use immutable version tags in deployments; latest is provided for evaluation.
The browser SDK package is build- and publish-dry-run verified in CI but is not
yet published to npm. Until the first registry release, consume it from a tagged
source checkout and build it locally:
npm ci --prefix sdk
npm run build --prefix sdk- Live streaming platforms — reduce origin and CDN segment traffic while preserving reliable playback during peer churn.
- Enterprise webinars and town halls — distribute simultaneous internal viewing load across participating clients.
- E-learning and virtual classrooms — share live lecture segments among learners without replacing the existing HLS workflow.
The current prototype is best suited to evaluation and controlled deployments. Review Security and the release notes before planning a production rollout.
HTTP behind TLS proxy / API key
encoder ──► origin ─────────────────────────► tracker + SQLite WAL
│ │ │ │
│ ├── HLS renditions │ ├── REST + WebSocket
│ └── SHA-256 sidecars │ └── dashboard + SSE
│ │
│ peer discovery / stats ───┘
│
└──────────────────────── CDN / origin fallback
│
┌───────────┴───────────┐
▼ ▼
Node peer A ◄──────────► Node peer B
│ HTTP/WebRTC │
│ STUN/TURN │
▼ ▼
segment cache segment cache
│ │
└──── Hls.js browser SDK┘
The tracker isolates state by broadcast ID, so multiple channels can share one deployment. The origin can generate multiple test channels and three HLS renditions per channel. Production media origins can register their own playlist URLs through the same API.
OpenStreamGrid uses the shared SegmentScheduler contract with runtime-specific
default policies. Node peers use WeightedScoreScheduler, which combines
latency, success rate, upload bandwidth, and trust from longer-lived peer
observations. The browser SDK uses TrustLatencyProbeScheduler, which ranks the
tracker snapshot by trust and then latency before probing up to three peers in
parallel. The policies differ because browser playback sessions have less
persistent peer-quality history than Node peers.
Applications can inject a custom scheduler through the Node fetcher or the
browser SDK's scheduler option. Browser decisions are observable through the
scheduler_decision SDK event. Regardless of policy, the delivery layer treats
Origin/CDN fallback as authoritative whenever P2P is unavailable or a plan
chooses Origin.
- Hybrid HTTP/WebRTC segment delivery with immediate origin fallback.
- Admin API-key authentication plus short-lived, peer-scoped session tokens.
- HTTPS-compatible clients; terminate TLS at a trusted reverse proxy.
- Configurable STUN and TURN servers for WebRTC NAT traversal.
- Multi-channel isolation and multi-rendition HLS generation.
- Quality-based peer ranking, parallel downloads, and request coalescing.
- SHA-256 verification, peer trust scoring, and integrity-failure exclusion.
- TTL-aware LRU caching, upload bandwidth limiting, and connection limits.
- Real-time dashboard, Prometheus metrics, SSE updates, and SQLite history.
- Grafana dashboard and Prometheus Operator
ServiceMonitorin the Helm chart. - Docker Compose, load-test scenarios, Kubernetes/Helm manifests, and CI.
The Hls.js SDK turns each viewer into a WebRTC peer automatically. It registers the viewer, caches verified segments, advertises availability, serves cached bytes through a rate-limited DataChannel, reports traffic, and unregisters on detach. Viewers do not install an executable or browser extension.
The following example assumes your bundler resolves the locally built
@openstreamgrid/sdk package:
import Hls from "hls.js";
import { OpenStreamGridHlsPlugin } from "@openstreamgrid/sdk";
const hls = new Hls();
const plugin = new OpenStreamGridHlsPlugin({
trackerUrl: "https://stream.example.com",
broadcastId: "live",
originBaseUrl: "https://stream.example.com/hls",
maxUploadBitrate: 1_000_000,
maxUploadConnections: 3,
iceServers: [
{ urls: "stun:stun.example.com:3478" },
{
urls: "turns:turn.example.com:5349",
username: "viewer",
credential: "short-lived-credential",
},
],
});
plugin.attach(hls);
hls.loadSource("https://stream.example.com/hls/stream.m3u8");
hls.attachMedia(document.querySelector("video")!);Rather than embedding TURN credentials in viewer code, set TURN_URLS and
TURN_SHARED_SECRET on the tracker. Each peer then receives short-lived TURN
credentials with its session, the SDK adds them to iceServers, and they are
renewed with the session.
Set peerParticipation: false for receive-only playback. A native agent or
browser extension can still be useful as an optional dedicated seed in managed
environments, but it is not part of the normal viewer path.
Browser peers automatically receive a short-lived, identity-scoped session when
they register. Do not pass or embed TRACKER_API_KEY in viewer code; reserve the
administrator credential for trusted origin and operations components.
OpenStreamGridShakaPlugin provides the same delivery core for Shaka Player. It
registers an HTTP(S) scheme plugin, so media segment requests go through the
grid and every other request uses Shaka's fetch plugin:
import shaka from "shaka-player";
import { OpenStreamGridShakaPlugin } from "@openstreamgrid/sdk";
const plugin = new OpenStreamGridShakaPlugin({
trackerUrl: "https://stream.example.com",
broadcastId: "live",
originBaseUrl: "https://stream.example.com/hls",
});
plugin.attach(shaka);
const player = new shaka.Player();
await player.attach(document.querySelector("video")!);
await player.load("https://stream.example.com/hls/stream.m3u8");It takes the same options as the Hls.js plugin. Shaka scheme plugins are global,
so attach one instance per page; detach() restores Shaka's fetch plugin.
Shaka does not expose the playback position to network plugins, so the adapter
derives a deadline from each request's retry and stall timeouts: peers get the
first part of that budget, and the origin is used before Shaka would abort.
Tracker resources are under /api/v1. Administrators send X-API-Key: <key>
or, for scrapers such as Prometheus, Authorization: Bearer <key>.
Peer join returns a short-lived session token; peer REST calls use
Authorization: Bearer <token> and WebSocket upgrades use ?sessionToken=<token>.
| Method | Path | Purpose |
|---|---|---|
GET |
/health |
Check tracker readiness |
GET |
/dashboard |
Open the monitoring UI |
GET |
/metrics |
Read Prometheus metrics |
GET |
/ws |
Upgrade to WebSocket signaling |
GET |
/api/v1/stats |
Read global traffic totals |
GET |
/api/v1/stats/events |
Subscribe to monitoring SSE |
POST |
/api/v1/broadcasts |
Register or update a broadcast |
GET |
/api/v1/broadcasts |
List broadcasts |
GET |
/api/v1/broadcasts/:id |
Read broadcast details and peers |
DELETE |
/api/v1/broadcasts/:id |
Unregister a broadcast |
GET |
/api/v1/broadcasts/:id/stats |
Read broadcast traffic totals |
POST |
/api/v1/broadcasts/:id/peers |
Join a broadcast |
GET |
/api/v1/broadcasts/:id/peers |
List peers; filter with ?segment= |
DELETE |
/api/v1/broadcasts/:id/peers/:peerId |
Leave a broadcast |
POST |
/api/v1/broadcasts/:id/peers/:peerId/segments |
Report cached segments |
PUT |
/api/v1/broadcasts/:id/peers/:peerId/heartbeat |
Refresh peer health |
POST |
/api/v1/broadcasts/:id/peers/:peerId/stats |
Report traffic totals |
POST |
/api/v1/broadcasts/:id/peers/:peerId/reports |
Report a peer failure |
See API_REFERENCE.md for schemas, signaling messages, status codes, and origin/peer endpoints.
| Variable | Default | Description |
|---|---|---|
PORT |
7070 |
HTTP listen port |
HOST |
0.0.0.0 |
Listen address |
STALE_PEER_MS |
30000 |
Peer inactivity timeout |
STORE_TYPE |
sqlite |
Persistence backend: sqlite or memory |
DB_PATH |
./data/tracker.db |
SQLite database path |
TRACKER_API_KEY |
unset | Admin credential for broadcast management, monitoring, and metrics |
PEER_SESSION_SECRET |
random per process | HMAC secret of at least 32 bytes; set explicitly in deployments |
PEER_SESSION_TTL_MS |
3600000 |
Peer session lifetime |
RATE_LIMIT_RPS |
100 |
Sustained requests per second per client |
RATE_LIMIT_BURST |
200 |
Token-bucket burst capacity per client |
MAX_PEERS_PER_BROADCAST |
500 |
Active-peer limit per broadcast |
TRACKER_WS_ALLOWED_ORIGINS |
unset | Comma-separated http(s) origins allowed to open /ws from a browser; other browser origins receive 403. Upgrades without an Origin header, such as Node peers, still require a valid peer session |
TRACKER_WS_MAX_CONNECTIONS_PER_IP |
20 |
Concurrent /ws connections per client address; raise it when many viewers share one NAT address |
TRUST_PROXY_HOPS |
0 |
Number of trusted reverse proxies in front of the tracker; per-client limits then use the client address they record in X-Forwarded-For. Set to 1 behind a single ingress or TLS proxy |
TURN_URLS |
unset | Comma-separated turn:/turns: URIs returned with short-lived credentials on peer join; browser and Node peers add them to their ICE servers |
TURN_SHARED_SECRET |
unset | TURN server shared secret (coturn static-auth-secret, at least 16 bytes); required with TURN_URLS |
| Variable | Default | Description |
|---|---|---|
PORT |
8080 |
Origin listen port |
HOST |
0.0.0.0 |
Listen address |
TRACKER_URL |
http://tracker:7070 |
Tracker base URL |
TRACKER_API_KEY |
unset | API key used for tracker registration |
BROADCAST_ID |
live |
Base broadcast/channel ID |
MULTI_STREAM_COUNT |
1 |
Generated test-channel count |
PUBLIC_ORIGIN_URL |
http://origin:<PORT> |
URL advertised to clients |
HLS_DIRECTORY |
/tmp/openstreamgrid-hls |
Generated HLS directory |
SEGMENT_DURATION_SECONDS |
2 |
HLS target duration |
HLS_SEGMENT_TYPE |
mpegts |
mpegts for .ts segments or fmp4 for CMAF/fMP4 (.m4s plus a per-rendition init segment) |
PLAYLIST_SIZE |
8 |
Segments retained per media playlist |
HASH_INTERVAL_MS |
250 |
Sidecar hash discovery interval |
FFMPEG_PATH |
ffmpeg on PATH |
FFmpeg executable override |
| Variable | Default | Description |
|---|---|---|
TRACKER_URL |
http://tracker:7070 |
Tracker base URL |
TRACKER_API_KEY |
unset | Optional admin key; normal peer traffic uses the issued session token |
BROADCAST_ID |
live |
Broadcast to join |
ORIGIN_URL |
required | Rendition directory or .m3u8 URL |
PEER_ADDRESS |
required | Advertised http://host:port address |
PEER_ID |
hostname | Stable peer identifier |
UPLOAD_HOST |
0.0.0.0 |
Upload server bind address |
CACHE_SIZE |
512MB |
Maximum segment-cache size |
CACHE_TTL_MS |
300000 |
Absolute cache-entry lifetime in milliseconds |
MAX_UPLOAD_SPEED |
1Mbps |
Token-bucket upload bit rate |
MAX_CONNECTIONS |
3 |
Concurrent peer uploads |
MAX_PARALLEL_DOWNLOADS |
3 |
Concurrent segment downloads |
PLAYLIST_POLL_MS |
500 |
HLS playlist poll interval |
P2P_TIMEOUT_MS |
2000 |
Peer request deadline |
WEBRTC_ENABLED |
true |
Try WebRTC before HTTP |
STUN_SERVER |
public Google STUN | stun: or stuns: ICE server URL |
TURN_SERVER |
unset | turn: or turns: relay URL |
TURN_USERNAME |
unset | TURN username |
TURN_CREDENTIAL |
unset | TURN credential |
Equivalent CLI flags are available for the primary peer options. Benchmark configuration is available through the following environment variables.
| Option | Default | Description |
|---|---|---|
peerParticipation |
true |
Register and upload as a zero-install browser peer |
iceServers |
public Google STUN | STUN/TURN servers for WebRTC NAT traversal |
maxUploadBitrate |
1000000 |
Browser upload limit in bits per second |
maxUploadConnections |
3 |
Concurrent browser DataChannel uploads |
peerTimeoutMs |
3000 |
Peer request and negotiation deadline |
maxCacheBytes |
100 MB |
Browser segment-cache limit |
hashUrlResolver |
adjacent .sha256 |
Override signed/query-aware sidecar URL resolution |
| Variable | Default | Description |
|---|---|---|
PEER_COUNT |
10 |
Number of virtual peers |
DURATION_SECONDS |
60 |
Measurement duration |
RAMP_UP_SECONDS |
5 |
Peer startup ramp |
CHURN_RATE |
0.15 |
Per-cycle churn probability |
REPORT_INTERVAL_SECONDS |
10 |
Console report interval |
BENCHMARK_OUTPUT |
benchmark-results.json |
JSON output path |
BENCHMARK_PROJECT_NAME |
openstreamgrid-benchmark |
Isolated Compose project name |
TRACKER_URL |
http://127.0.0.1:7070 |
Host health-check URL |
ORIGIN_URL |
http://127.0.0.1:8080 |
Host health-check URL |
The reproducible local Docker baseline for the default 10-peer, 60-second
scenario achieved 46.07% P2P efficiency and 47.44% CDN traffic reduction. These
figures are regression data, not a production capacity claim. See the raw
benchmark result and release notes
for the full scenario and latency measurements. Run it locally with
bash scripts/benchmark.sh.
| Policy | Deadline met | Peer wins | Origin wins | Hedges started | Completion p50 | Completion p95 |
|---|---|---|---|---|---|---|
| Node-legacy | 85% | 77.5% | 22.5% | 0% | 860.54 ms | 1,365.97 ms |
| Node-deadline-aware | 100% | 63.75% | 36.25% | 6.75% | 769.12 ms | 1,332.58 ms |
| Browser-legacy | 84.5% | 98.75% | 1.25% | 0% | 801.72 ms | 1,271.02 ms |
| Browser-deadline-aware | 100% | 81.5% | 18.5% | 0.75% | 722.18 ms | 1,265.83 ms |
Fixed seed phase3-mixed-2026; 400 deterministic requests per policy. Controlled simulation using production schedulers—not a production capacity claim. See the scheduler lab.
Benchmark data is generated by running bash scripts/benchmark.sh --scenario benchmarks/scenarios/ci-smoke.json. See benchmark methodology for details.
For local development, install Node.js 22+, npm, Docker Compose v2, and FFmpeg.
npm ci
npm ci --prefix sdk
npm run build
npm run typecheck
npm test
npm run lintRepository layout: tracker/ owns discovery, persistence, signaling, and
monitoring; origin/ owns test HLS generation and serving; peer/ is the Node
peer; sdk/ is the browser/Hls.js package; common/ contains shared contracts;
test/ and scripts/ contain integration and load tooling.
Tip
Want to contribute? Read CONTRIBUTING.md for setup, testing expectations, coding guidelines, and pull request requirements.
- Ask usage questions and propose ideas in GitHub Discussions.
- Report reproducible defects through GitHub Issues.
- Report security vulnerabilities privately as described in SECURITY.md.
- Participation is governed by the Code of Conduct.
The Discord community is not open yet. This badge will be linked to the official server once one is available.
If OpenStreamGrid is useful to you, consider starring the repository.
- Korean README
- API reference
- Contributing guide
- Code of conduct
- Security policy
- Release notes
- Changelog
- Kubernetes deployment guide
- ADR 0001: Tracker horizontal scaling
- Tracker capacity
- Browser SDK example
OpenStreamGrid is licensed under the MIT License.