GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — GitHub - kt81/Spangle: Media server for those who want to sparkle✨

BranchesTags
Open more actions menu

Latest commit

 

History

177 Commits
177 Commits

Folders and files

NameName
Last commit message
Last commit date
.github/workflows
.github/workflows
 
 
.idea/.idea.Spangle/.idea
.idea/.idea.Spangle/.idea
 
 
benchmarks
benchmarks
 
 
docs
docs
 
 
examples
examples
 
 
src
src
 
 
tests
tests
 
 
tools
tools
 
 
.dockerignore
.dockerignore
 
 
.editorconfig
.editorconfig
 
 
.gitattributes
.gitattributes
 
 
.gitignore
.gitignore
 
 
Directory.Build.props
Directory.Build.props
 
 
LICENSE
LICENSE
 
 
README.md
README.md
 
 
Spangle.sln.DotSettings
Spangle.sln.DotSettings
 
 
Spangle.slnx
Spangle.slnx
 
 

Repository files navigation

Spangle Media Server

Media server for those who want to sparkle✨ — a live streaming server in almost pure C#, aiming for clarity of data models / data flow / state, minimal allocations on modern IO, and top-tier .NET performance.

Pre-1.0 / under active development. Interfaces may change without notice.

Current status

Three ingest protocols, one canonical internal form, two output shapes:

  • Ingest: RTMP (classic + enhanced; H.264/H.265/AV1 + AAC/Opus), SRT (MPEG-TS; H.264/H.265 + AAC/Opus, streamid routing, optional passphrase encryption), and RTSP — both pull (IP cameras; auto-reconnect) and push/listen (a client RECORDs to us, e.g. ffmpeg -f rtsp); TCP-interleaved or UDP transport, H.264/H.265 + AAC, Basic/Digest auth
  • Output: HLS with MPEG-2 TS segments, or CMAF/fMP4 — optionally LL-HLS (partial segments + blocking playlist reload), served over HTTP with per-stream routing (/hls/{stream}/playlist.m3u8)
  • Performance (see docs/PERFORMANCE.md): zero allocations on the steady-state media path; RTMP chunk parsing at ≈3.2 GB/s; TS keyframe packetization at ~464 ns — faster than our hand-packed baseline, through the declarative bit fields of Spangle.LusterBits

See docs/ARCHITECTURE.md for the data flow, the data formats at each boundary, and where state lives.

Quick try

$ dotnet run --project src/Spangle.MediaServer

# publish via RTMP:
$ ffmpeg -re -f lavfi -i testsrc2=size=640x360:rate=30 -f lavfi -i sine=frequency=440 \
    -c:v libx264 -g 60 -pix_fmt yuv420p -c:a aac -f flv rtmp://localhost:1935/live/test

# ...or via SRT:
$ ffmpeg -re -f lavfi -i testsrc2=size=640x360:rate=30 -f lavfi -i sine=frequency=440 \
    -c:v libx264 -g 60 -pix_fmt yuv420p -c:a aac -f mpegts "srt://localhost:9998?streamid=test"

# ...or push over RTSP (enable Rtsp.Listen first; use -bf 0 for live):
$ ffmpeg -re -f lavfi -i testsrc2=size=640x360:rate=30 -f lavfi -i sine=frequency=440 \
    -c:v libx264 -g 60 -bf 0 -pix_fmt yuv420p -c:a aac \
    -f rtsp -rtsp_transport tcp rtsp://localhost:8554/live/test

# then open http://localhost:8080/ (test player) or http://localhost:8080/hls/test/playlist.m3u8

Configuration lives in src/Spangle.MediaServer/spanglesettings.yaml (ports, segment format TS/fMP4, LL-HLS, SRT passphrase, TLS per listener, publish allowlist), overridable with SMS_-prefixed environment variables.

Family

Package What
Spangle.LusterBits Declarative bit fields for binary protocols (C# source generator); every TS/RTMP wire structure here is declared with it — the muxer composes and the demuxer reads through the same structs
Spangle.Net.Transport.SRT Self-contained SRT transport (native libsrt + mbedTLS bundled for 5 RIDs)

Roadmap

Near term — correctness & operations

  • Publish authorization hook (IPublishAuthorizer in DI; default allows all — register your own to validate RTMP stream keys / SRT streamids)
  • Same-name publishers: the newest session takes over by default (last-wins, so a reconnect after a crash just works); the output continues under the same playlist with an EXT-X-DISCONTINUITY. First-wins or liveness-aware policies are one authorizer away
  • Tail frames survive an abrupt publisher disconnect (the pipeline drains into a final fractional segment before the playlist is finalized)
  • CI for this repository (build + test on push, like the sibling repos)
  • H.265 over SRT/TS ingest (hvcC built from in-band VPS/SPS/PPS, dimensions parsed from the SPS; both TS and CMAF outputs verified)
  • SRT→TS-HLS passthrough: source TS packets are re-segmented as-is (cuts at random-access PES starts, cached PAT/PMT injected per segment) instead of demux+remux; on by default, Hls.TsPassthrough: false restores the old path
  • In-memory HLS output (IHLSStorage): the live window is served straight from process memory by default — nothing touches disk, works on read-only filesystems; Hls.Storage: File keeps the output on disk as an archive
  • Analyzers at full power: AnalysisLevel: latest-all + Meziantou + VS Threading analyzers, code style enforced in build, and warnings are errors (CI builds Release, so the gate holds there too). The deliberate exceptions live in .editorconfig, each with a written reason — spec-mirroring enums, numbered protocol-flow files, wire-struct fields
  • TLS on every listener that lacked it: HTTPS for HLS/DASH delivery (Http.Tls), HTTPS for the management port so the Bearer token never travels in cleartext (Management.Tls), and RTMPS (Rtmp.Tls) — PKCS#12 or PEM cert/key, plaintext stays the default
  • Publish allowlist out of the box: Publish.AllowedStreamNames switches the built-in authorizer from allow-all to an exact-match allowlist (a custom IPublishAuthorizer in DI still overrides everything); the metadata-injection endpoint on the public port takes an optional Bearer token (Http.MetadataInjectionToken)
  • Ended streams no longer hold memory forever: memory storage frees a stream's final window after Hls.EndedStreamTtlSeconds (default 300; 0 restores keep-until-republish), skipping keys a publisher still owns

Mid term — features

  • Timed metadata, source-driven: RTMP data events (onTextData, cue points, ...) → AmfDataToId3Spinner (the first DI-composed pipeline plugin) → timed ID3 as stream_type 0x15 PES (TS) / ID3-in-emsg (CMAF); Rtmp.TimedMetadata: false removes the spinner hop

  • Timed metadata, server-injected: POST /api/streams/{key}/metadata ({"name":..,"value":..}) → TimedMetadataInjector spinner stamps it onto the media timeline; timed ID3 in the source TS (stream_type 0x15) also passes through from SRT ingest. Not available on the raw TS passthrough path.

  • Audio-only streams over SRT/TS ingest (video-less PMT with the PCR on the audio PID; both TS and CMAF outputs cut segments on the audio timeline)

  • Opus audio, end to end on the CMAF path: enhanced-RTMP v2 envelope and Opus-over-TS (SRT) ingest → 'Opus' sample entry + dOps output. The TS output drops Opus with a warning (no interoperable HLS/TS mapping exists) — except SRT passthrough, which carries the source's Opus PES verbatim

  • Audio-only over RTMP: with no in-protocol way to declare "no video is coming", a session with audio but no video codec is wired audio-only after Rtmp.AudioOnlyFallbackMs (default 3s; 0 disables)

  • LL-HLS playlist delta updates (?_HLS_skip=YES → EXT-X-SKIP); pays off with a larger Hls.PlaylistWindow

  • DASH v1: a live-profile MPD (SegmentTimeline) over the same CMAF segments the HLS side serves — one publisher session, two manifests, zero extra media. /hls/{stream}/manifest.mpd, test player at /dash.html?s={stream}. Note: ffmpeg's dash demuxer cannot read muxed representations (its own limitation); real MSE players (dash.js) play it — verified live

  • Demuxed CMAF tracks: per-track init/segments (init_v/init_a, segV/segA), HLS multivariant (playlist.m3u8 → video.m3u8 + audio.m3u8 via EXT-X-MEDIA) and per-track DASH AdaptationSets — ffmpeg now plays the MPD too. The shared foundation for ABR ladders and MoQ tracks

  • LL-DASH: with Hls.LowLatency, the in-progress segment grows part by part in memory and is served over chunked transfer (measured: first byte in ~5ms, response streams until the segment completes); the MPD switches to fixed-duration arithmetic with availabilityTimeOffset + UTCTiming (/api/time). Memory storage backend by design (a future DVR serves from memory while archiving to files; file-only low latency is out of spec). Requires a steady keyframe interval from the encoder

  • Web console v1 + management API: http://localhost:8081/console/ — live sessions (codecs, measured ingest bitrate with sparklines, viewers, uptime), preview player, operator stop, timed-metadata injection, a log viewer (live tail, level/category/text filters) and server info. Blazor WASM, no JS beyond the players. Everything the console shows rides /api/manage/* (REST + SSE) on a dedicated management port — loopback by default; binding wider requires Management.Token (Bearer). Curl works as well as the UI does. Multi-server aggregation comes later on the same API

  • RTSP pull ingest (IP cameras): the server dials out to each Rtsp.Sources entry and republishes it as HLS/CMAF. Explicit numbered control flow like the RTMP receiver (00_Options → 01_Describe → 02_Setup → 03_Play + keepalive), TCP-interleaved RTP, H.264/H.265 (RFC 6184/7798) and AAC (RFC 3640) depacketization into the canonical MediaFrame form, Basic/Digest auth, RTP-Info/RTCP timeline anchoring, per-firmware quirks as overridable RtspDialect hooks (resolved through an extensible registry, custom dialects via DI), and one auto-reconnecting loop per source

  • RTSP push (listen server): with Rtsp.Listen, Spangle binds a port (default 8554) and accepts clients that ANNOUNCE/SETUP/RECORD (ffmpeg's default -f rtsp push). A push is a publish, so it rides the same IPublishAuthorizer/takeover path as RTMP/SRT; the stream key is the ANNOUNCE URL's last path segment (/live/key → /hls/key/…). The depacketizers, timeline and adapter are shared with the pull receiver (RtspMediaFrameAdapter<T> is generic over the receiver)

  • RTSP over UDP transport: pull sources set Transport: Udp (SETUP negotiates a client_port pair, RTP/RTCP arrive on their own sockets, resequenced through a bounded RtpReorderBuffer, with a NAT-punch to open the return path); the push server accepts whichever transport the client's SETUP asks for. TCP-interleaved stays the robust default. Verified with real ffmpeg over UDP, both directions

Backlog — decided, parked until kickoff

  • ONVIF camera discovery/onboarding — deliberately not in this repo or the media library: it is a SOAP/WS-* control-plane concern that sits above the media plane. If built, it lives as a separate optional library/repo that discovers cameras and emits Rtsp.Sources entries; the media core never depends on it
  • MoQ (Media over QUIC) ingest/egress: draft target and interop peer to be pinned at kickoff; raw QUIC first, WebTransport for browsers after

Long term — goals of effort

  • Failover with very little gap
  • DRM / transcoder integration (undecided)

Guiding constraints

  • Target mainly UGC content
  • Almost pure C# (native only where a protocol demands it, e.g. libsrt)
  • Clarity first: an implementer should be able to read the declarations like the protocol specs they mirror
  • Highly customizable (plugins via Spinners, forks welcome)

License

The server is licensed under AGPL-3.0 (see LICENSE). The reusable building blocks — LusterBits and the SRT transport — are published separately under MIT, so embedding them in your own software carries no copyleft.

About

Media server for those who want to sparkle✨

Topics

av1cmafcsharpdotnetenhanced-rtmpfmp4h264h265hlslive-streaminglow-latency-hlsmedia-servermpeg-tsrtmpsrtstreamingzero-allocation

Resources

Readme
AGPL-3.0 license
Activity

Stars

1 star

Watchers

1 watching

Forks

0 forks
Report repository

Releases

Packages

Used by

Contributors

Languages

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.