From 9a0667def4154be2858c1ea3aef08428157da5b9 Mon Sep 17 00:00:00 2001 From: Thomas Symborski Date: Tue, 24 Feb 2026 23:54:47 -0500 Subject: [PATCH 1/4] Move Go packages from internal/ to module root for public API All 11 directories (12 packages including ingest/srt) moved from internal/ to the module root so they can be imported externally as github.com/zsiec/prism/distribution, etc. Global import rewrite across 25 files, documentation paths updated in CLAUDE.md, README.md, and CONTRIBUTING.md. --- .gitignore | 1 + CONTRIBUTING.md | 6 ++--- README.md | 24 +++++++++---------- {internal/certs => certs}/selfsigned.go | 0 {internal/certs => certs}/selfsigned_test.go | 0 cmd/prism/main.go | 12 +++++----- {internal/demux => demux}/aac.go | 0 {internal/demux => demux}/aac_bench_test.go | 0 {internal/demux => demux}/aac_fuzz_test.go | 0 {internal/demux => demux}/aac_test.go | 0 .../demux => demux}/caption_harness_test.go | 2 +- {internal/demux => demux}/diag_test.go | 2 +- {internal/demux => demux}/doc.go | 0 {internal/demux => demux}/h264.go | 0 {internal/demux => demux}/h264_bench_test.go | 0 {internal/demux => demux}/h264_fuzz_test.go | 0 {internal/demux => demux}/h264_test.go | 0 {internal/demux => demux}/h265.go | 0 {internal/demux => demux}/h265_test.go | 0 {internal/demux => demux}/mpegts.go | 6 ++--- .../moq_catalog.go | 2 +- .../moq_catalog_test.go | 0 .../moq_session.go | 6 ++--- .../moq_session_test.go | 6 ++--- .../moq_writer.go | 4 ++-- .../moq_writer_test.go | 2 +- .../distribution => distribution}/protocol.go | 4 ++-- .../distribution => distribution}/relay.go | 4 ++-- .../relay_test.go | 2 +- .../distribution => distribution}/server.go | 6 ++--- .../server_test.go | 2 +- .../session_helpers.go | 2 +- .../session_helpers_test.go | 2 +- .../streamstats.go | 2 +- .../streamstats_test.go | 0 {internal/ingest => ingest}/ingest.go | 0 {internal/ingest => ingest}/ingest_test.go | 0 {internal/ingest => ingest}/srt/caller.go | 2 +- {internal/ingest => ingest}/srt/doc.go | 0 {internal/ingest => ingest}/srt/server.go | 2 +- .../ingest => ingest}/srt/server_test.go | 0 {internal/media => media}/frame.go | 0 {internal/moq => moq}/control.go | 0 {internal/moq => moq}/control_test.go | 0 {internal/moq => moq}/doc.go | 2 +- {internal/moq => moq}/errors.go | 0 {internal/moq => moq}/format.go | 2 +- {internal/moq => moq}/format_bench_test.go | 0 {internal/moq => moq}/format_test.go | 0 {internal/mpegts => mpegts}/accumulator.go | 0 .../mpegts => mpegts}/accumulator_test.go | 0 {internal/mpegts => mpegts}/crc32.go | 0 {internal/mpegts => mpegts}/debug_test.go | 0 {internal/mpegts => mpegts}/demuxer.go | 0 {internal/mpegts => mpegts}/demuxer_test.go | 0 {internal/mpegts => mpegts}/packet.go | 0 .../mpegts => mpegts}/packet_fuzz_test.go | 0 {internal/mpegts => mpegts}/packet_test.go | 0 {internal/mpegts => mpegts}/pes.go | 0 {internal/mpegts => mpegts}/pes_test.go | 0 {internal/mpegts => mpegts}/psi.go | 0 {internal/mpegts => mpegts}/psi_test.go | 0 {internal/mpegts => mpegts}/types.go | 0 {internal/pipeline => pipeline}/pipeline.go | 8 +++---- .../pipeline_integration_test.go | 4 ++-- .../pipeline => pipeline}/pipeline_test.go | 2 +- {internal/scte35 => scte35}/bits.go | 0 {internal/scte35 => scte35}/bits_test.go | 0 {internal/scte35 => scte35}/crc.go | 0 {internal/scte35 => scte35}/crc_test.go | 0 {internal/scte35 => scte35}/descriptor.go | 0 {internal/scte35 => scte35}/scte35.go | 0 .../scte35 => scte35}/scte35_bench_test.go | 0 .../scte35 => scte35}/scte35_fuzz_test.go | 0 {internal/scte35 => scte35}/scte35_test.go | 0 {internal/scte35 => scte35}/splice_insert.go | 0 {internal/scte35 => scte35}/splice_null.go | 0 {internal/scte35 => scte35}/time_signal.go | 0 {internal/stream => stream}/manager.go | 0 {internal/stream => stream}/manager_test.go | 0 test/tools/inject-scte35/main.go | 2 +- .../webtransport => webtransport}/doc.go | 0 .../webtransport => webtransport}/errors.go | 0 .../webtransport => webtransport}/protocol.go | 0 .../webtransport => webtransport}/server.go | 0 .../webtransport => webtransport}/session.go | 0 .../session_manager.go | 0 .../webtransport => webtransport}/stream.go | 0 .../streams_map.go | 0 89 files changed, 61 insertions(+), 60 deletions(-) rename {internal/certs => certs}/selfsigned.go (100%) rename {internal/certs => certs}/selfsigned_test.go (100%) rename {internal/demux => demux}/aac.go (100%) rename {internal/demux => demux}/aac_bench_test.go (100%) rename {internal/demux => demux}/aac_fuzz_test.go (100%) rename {internal/demux => demux}/aac_test.go (100%) rename {internal/demux => demux}/caption_harness_test.go (98%) rename {internal/demux => demux}/diag_test.go (98%) rename {internal/demux => demux}/doc.go (100%) rename {internal/demux => demux}/h264.go (100%) rename {internal/demux => demux}/h264_bench_test.go (100%) rename {internal/demux => demux}/h264_fuzz_test.go (100%) rename {internal/demux => demux}/h264_test.go (100%) rename {internal/demux => demux}/h265.go (100%) rename {internal/demux => demux}/h265_test.go (100%) rename {internal/demux => demux}/mpegts.go (99%) rename {internal/distribution => distribution}/moq_catalog.go (98%) rename {internal/distribution => distribution}/moq_catalog_test.go (100%) rename {internal/distribution => distribution}/moq_session.go (99%) rename {internal/distribution => distribution}/moq_session_test.go (99%) rename {internal/distribution => distribution}/moq_writer.go (98%) rename {internal/distribution => distribution}/moq_writer_test.go (99%) rename {internal/distribution => distribution}/protocol.go (96%) rename {internal/distribution => distribution}/relay.go (99%) rename {internal/distribution => distribution}/relay_test.go (99%) rename {internal/distribution => distribution}/server.go (99%) rename {internal/distribution => distribution}/server_test.go (99%) rename {internal/distribution => distribution}/session_helpers.go (95%) rename {internal/distribution => distribution}/session_helpers_test.go (98%) rename {internal/distribution => distribution}/streamstats.go (99%) rename {internal/distribution => distribution}/streamstats_test.go (100%) rename {internal/ingest => ingest}/ingest.go (100%) rename {internal/ingest => ingest}/ingest_test.go (100%) rename {internal/ingest => ingest}/srt/caller.go (99%) rename {internal/ingest => ingest}/srt/doc.go (100%) rename {internal/ingest => ingest}/srt/server.go (98%) rename {internal/ingest => ingest}/srt/server_test.go (100%) rename {internal/media => media}/frame.go (100%) rename {internal/moq => moq}/control.go (100%) rename {internal/moq => moq}/control_test.go (100%) rename {internal/moq => moq}/doc.go (84%) rename {internal/moq => moq}/errors.go (100%) rename {internal/moq => moq}/format.go (99%) rename {internal/moq => moq}/format_bench_test.go (100%) rename {internal/moq => moq}/format_test.go (100%) rename {internal/mpegts => mpegts}/accumulator.go (100%) rename {internal/mpegts => mpegts}/accumulator_test.go (100%) rename {internal/mpegts => mpegts}/crc32.go (100%) rename {internal/mpegts => mpegts}/debug_test.go (100%) rename {internal/mpegts => mpegts}/demuxer.go (100%) rename {internal/mpegts => mpegts}/demuxer_test.go (100%) rename {internal/mpegts => mpegts}/packet.go (100%) rename {internal/mpegts => mpegts}/packet_fuzz_test.go (100%) rename {internal/mpegts => mpegts}/packet_test.go (100%) rename {internal/mpegts => mpegts}/pes.go (100%) rename {internal/mpegts => mpegts}/pes_test.go (100%) rename {internal/mpegts => mpegts}/psi.go (100%) rename {internal/mpegts => mpegts}/psi_test.go (100%) rename {internal/mpegts => mpegts}/types.go (100%) rename {internal/pipeline => pipeline}/pipeline.go (97%) rename {internal/pipeline => pipeline}/pipeline_integration_test.go (98%) rename {internal/pipeline => pipeline}/pipeline_test.go (96%) rename {internal/scte35 => scte35}/bits.go (100%) rename {internal/scte35 => scte35}/bits_test.go (100%) rename {internal/scte35 => scte35}/crc.go (100%) rename {internal/scte35 => scte35}/crc_test.go (100%) rename {internal/scte35 => scte35}/descriptor.go (100%) rename {internal/scte35 => scte35}/scte35.go (100%) rename {internal/scte35 => scte35}/scte35_bench_test.go (100%) rename {internal/scte35 => scte35}/scte35_fuzz_test.go (100%) rename {internal/scte35 => scte35}/scte35_test.go (100%) rename {internal/scte35 => scte35}/splice_insert.go (100%) rename {internal/scte35 => scte35}/splice_null.go (100%) rename {internal/scte35 => scte35}/time_signal.go (100%) rename {internal/stream => stream}/manager.go (100%) rename {internal/stream => stream}/manager_test.go (100%) rename {internal/webtransport => webtransport}/doc.go (100%) rename {internal/webtransport => webtransport}/errors.go (100%) rename {internal/webtransport => webtransport}/protocol.go (100%) rename {internal/webtransport => webtransport}/server.go (100%) rename {internal/webtransport => webtransport}/session.go (100%) rename {internal/webtransport => webtransport}/session_manager.go (100%) rename {internal/webtransport => webtransport}/stream.go (100%) rename {internal/webtransport => webtransport}/streams_map.go (100%) diff --git a/.gitignore b/.gitignore index 987eb6b..0710f85 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ bin/ # Web web/node_modules/ web/dist/ +web/dist-lib/ # TLS *.pem diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0490d85..fbc3885 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,13 +47,13 @@ All changes must pass `make check`, which runs: make test # Run tests for a specific package -go test -v -race ./internal/demux/ +go test -v -race ./demux/ # Run fuzz tests (default 10s, adjust as needed) -go test -fuzz=FuzzParseAnnexB -fuzztime=30s ./internal/demux/ +go test -fuzz=FuzzParseAnnexB -fuzztime=30s ./demux/ # Run benchmarks -go test -bench=. -benchmem ./internal/distribution/ +go test -bench=. -benchmem ./distribution/ ``` ## Project Structure diff --git a/README.md b/README.md index 452a00e..9120e61 100644 --- a/README.md +++ b/README.md @@ -64,18 +64,18 @@ Single Go binary, vanilla TypeScript frontend: | Package | Purpose | |---|---| | `cmd/prism/` | Entry point, wires everything together | -| `internal/ingest/` | Stream ingest registry | -| `internal/ingest/srt/` | SRT server (push) and caller (pull) | -| `internal/demux/` | MPEG-TS demuxer, H.264/H.265/AAC parsers | -| `internal/media/` | Frame types (`VideoFrame`, `AudioFrame`) | -| `internal/distribution/` | WebTransport server, MoQ sessions, relay fan-out | -| `internal/moq/` | MoQ Transport wire protocol codec | -| `internal/pipeline/` | Demux-to-distribution orchestration | -| `internal/stream/` | Stream lifecycle management | -| `internal/mpegts/` | Low-level MPEG-TS packet/PES/PSI parsing | -| `internal/scte35/` | SCTE-35 splice info encoding/decoding | -| `internal/certs/` | Self-signed ECDSA certificate generation | -| `internal/webtransport/` | WebTransport server on quic-go/HTTP3 | +| `ingest/` | Stream ingest registry | +| `ingest/srt/` | SRT server (push) and caller (pull) | +| `demux/` | MPEG-TS demuxer, H.264/H.265/AAC parsers | +| `media/` | Frame types (`VideoFrame`, `AudioFrame`) | +| `distribution/` | WebTransport server, MoQ sessions, relay fan-out | +| `moq/` | MoQ Transport wire protocol codec | +| `pipeline/` | Demux-to-distribution orchestration | +| `stream/` | Stream lifecycle management | +| `mpegts/` | Low-level MPEG-TS packet/PES/PSI parsing | +| `scte35/` | SCTE-35 splice info encoding/decoding | +| `certs/` | Self-signed ECDSA certificate generation | +| `webtransport/` | WebTransport server on quic-go/HTTP3 | | `web/` | Vanilla TypeScript viewer (Vite, WebTransport, WebCodecs) | ## Configuration diff --git a/internal/certs/selfsigned.go b/certs/selfsigned.go similarity index 100% rename from internal/certs/selfsigned.go rename to certs/selfsigned.go diff --git a/internal/certs/selfsigned_test.go b/certs/selfsigned_test.go similarity index 100% rename from internal/certs/selfsigned_test.go rename to certs/selfsigned_test.go diff --git a/cmd/prism/main.go b/cmd/prism/main.go index 55df531..e570090 100644 --- a/cmd/prism/main.go +++ b/cmd/prism/main.go @@ -16,12 +16,12 @@ import ( "golang.org/x/sync/errgroup" - "github.com/zsiec/prism/internal/certs" - "github.com/zsiec/prism/internal/distribution" - "github.com/zsiec/prism/internal/ingest" - srtingest "github.com/zsiec/prism/internal/ingest/srt" - "github.com/zsiec/prism/internal/pipeline" - "github.com/zsiec/prism/internal/stream" + "github.com/zsiec/prism/certs" + "github.com/zsiec/prism/distribution" + "github.com/zsiec/prism/ingest" + srtingest "github.com/zsiec/prism/ingest/srt" + "github.com/zsiec/prism/pipeline" + "github.com/zsiec/prism/stream" ) var version = "dev" diff --git a/internal/demux/aac.go b/demux/aac.go similarity index 100% rename from internal/demux/aac.go rename to demux/aac.go diff --git a/internal/demux/aac_bench_test.go b/demux/aac_bench_test.go similarity index 100% rename from internal/demux/aac_bench_test.go rename to demux/aac_bench_test.go diff --git a/internal/demux/aac_fuzz_test.go b/demux/aac_fuzz_test.go similarity index 100% rename from internal/demux/aac_fuzz_test.go rename to demux/aac_fuzz_test.go diff --git a/internal/demux/aac_test.go b/demux/aac_test.go similarity index 100% rename from internal/demux/aac_test.go rename to demux/aac_test.go diff --git a/internal/demux/caption_harness_test.go b/demux/caption_harness_test.go similarity index 98% rename from internal/demux/caption_harness_test.go rename to demux/caption_harness_test.go index 3a293f1..7d1d690 100644 --- a/internal/demux/caption_harness_test.go +++ b/demux/caption_harness_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/zsiec/ccx" - "github.com/zsiec/prism/internal/mpegts" + "github.com/zsiec/prism/mpegts" ) func TestCaptionHarness(t *testing.T) { diff --git a/internal/demux/diag_test.go b/demux/diag_test.go similarity index 98% rename from internal/demux/diag_test.go rename to demux/diag_test.go index e93c04f..81c34c6 100644 --- a/internal/demux/diag_test.go +++ b/demux/diag_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/zsiec/ccx" - "github.com/zsiec/prism/internal/mpegts" + "github.com/zsiec/prism/mpegts" ) func TestDiag_DecodedCaptions(t *testing.T) { diff --git a/internal/demux/doc.go b/demux/doc.go similarity index 100% rename from internal/demux/doc.go rename to demux/doc.go diff --git a/internal/demux/h264.go b/demux/h264.go similarity index 100% rename from internal/demux/h264.go rename to demux/h264.go diff --git a/internal/demux/h264_bench_test.go b/demux/h264_bench_test.go similarity index 100% rename from internal/demux/h264_bench_test.go rename to demux/h264_bench_test.go diff --git a/internal/demux/h264_fuzz_test.go b/demux/h264_fuzz_test.go similarity index 100% rename from internal/demux/h264_fuzz_test.go rename to demux/h264_fuzz_test.go diff --git a/internal/demux/h264_test.go b/demux/h264_test.go similarity index 100% rename from internal/demux/h264_test.go rename to demux/h264_test.go diff --git a/internal/demux/h265.go b/demux/h265.go similarity index 100% rename from internal/demux/h265.go rename to demux/h265.go diff --git a/internal/demux/h265_test.go b/demux/h265_test.go similarity index 100% rename from internal/demux/h265_test.go rename to demux/h265_test.go diff --git a/internal/demux/mpegts.go b/demux/mpegts.go similarity index 99% rename from internal/demux/mpegts.go rename to demux/mpegts.go index a6d4b16..d919dc5 100644 --- a/internal/demux/mpegts.go +++ b/demux/mpegts.go @@ -8,9 +8,9 @@ import ( "time" "github.com/zsiec/ccx" - "github.com/zsiec/prism/internal/media" - "github.com/zsiec/prism/internal/mpegts" - "github.com/zsiec/prism/internal/scte35" + "github.com/zsiec/prism/media" + "github.com/zsiec/prism/mpegts" + "github.com/zsiec/prism/scte35" ) const ( diff --git a/internal/distribution/moq_catalog.go b/distribution/moq_catalog.go similarity index 98% rename from internal/distribution/moq_catalog.go rename to distribution/moq_catalog.go index eb071d0..8358e0c 100644 --- a/internal/distribution/moq_catalog.go +++ b/distribution/moq_catalog.go @@ -7,7 +7,7 @@ import ( "fmt" "github.com/quic-go/quic-go/quicvarint" - "github.com/zsiec/prism/internal/webtransport" + "github.com/zsiec/prism/webtransport" ) // moqCatalog is the top-level catalog structure per draft-ietf-moq-catalogformat-01. diff --git a/internal/distribution/moq_catalog_test.go b/distribution/moq_catalog_test.go similarity index 100% rename from internal/distribution/moq_catalog_test.go rename to distribution/moq_catalog_test.go diff --git a/internal/distribution/moq_session.go b/distribution/moq_session.go similarity index 99% rename from internal/distribution/moq_session.go rename to distribution/moq_session.go index c8cfdb5..4dd4213 100644 --- a/internal/distribution/moq_session.go +++ b/distribution/moq_session.go @@ -13,9 +13,9 @@ import ( "time" "github.com/zsiec/ccx" - "github.com/zsiec/prism/internal/media" - "github.com/zsiec/prism/internal/moq" - "github.com/zsiec/prism/internal/webtransport" + "github.com/zsiec/prism/media" + "github.com/zsiec/prism/moq" + "github.com/zsiec/prism/webtransport" ) // moqTrackSub holds state for a single track subscription within a MoQ session. diff --git a/internal/distribution/moq_session_test.go b/distribution/moq_session_test.go similarity index 99% rename from internal/distribution/moq_session_test.go rename to distribution/moq_session_test.go index eed4ade..94278e2 100644 --- a/internal/distribution/moq_session_test.go +++ b/distribution/moq_session_test.go @@ -11,9 +11,9 @@ import ( "github.com/quic-go/quic-go" "github.com/quic-go/quic-go/quicvarint" "github.com/zsiec/ccx" - "github.com/zsiec/prism/internal/media" - "github.com/zsiec/prism/internal/moq" - "github.com/zsiec/prism/internal/webtransport" + "github.com/zsiec/prism/media" + "github.com/zsiec/prism/moq" + "github.com/zsiec/prism/webtransport" ) // buildClientSetupPayload builds a CLIENT_SETUP payload for testing. diff --git a/internal/distribution/moq_writer.go b/distribution/moq_writer.go similarity index 98% rename from internal/distribution/moq_writer.go rename to distribution/moq_writer.go index be96ac1..5120cbf 100644 --- a/internal/distribution/moq_writer.go +++ b/distribution/moq_writer.go @@ -4,8 +4,8 @@ import ( "io" "github.com/quic-go/quic-go/quicvarint" - "github.com/zsiec/prism/internal/media" - "github.com/zsiec/prism/internal/moq" + "github.com/zsiec/prism/media" + "github.com/zsiec/prism/moq" ) // Compile-time interface check. diff --git a/internal/distribution/moq_writer_test.go b/distribution/moq_writer_test.go similarity index 99% rename from internal/distribution/moq_writer_test.go rename to distribution/moq_writer_test.go index c707e8e..4bbff73 100644 --- a/internal/distribution/moq_writer_test.go +++ b/distribution/moq_writer_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/quic-go/quic-go/quicvarint" - "github.com/zsiec/prism/internal/media" + "github.com/zsiec/prism/media" ) func TestMoQWriterSubgroupHeader(t *testing.T) { diff --git a/internal/distribution/protocol.go b/distribution/protocol.go similarity index 96% rename from internal/distribution/protocol.go rename to distribution/protocol.go index fe06a5a..106db06 100644 --- a/internal/distribution/protocol.go +++ b/distribution/protocol.go @@ -1,13 +1,13 @@ // Package distribution implements the WebTransport-based viewer delivery // layer, including the fan-out relay, MoQ session management, and the // HTTP/QUIC server that ties them together. The low-level MoQ wire protocol -// codec lives in [github.com/zsiec/prism/internal/moq]. +// codec lives in [github.com/zsiec/prism/moq]. package distribution import ( "io" - "github.com/zsiec/prism/internal/media" + "github.com/zsiec/prism/media" ) // Track ID constants used to identify media types in the MoQ catalog diff --git a/internal/distribution/relay.go b/distribution/relay.go similarity index 99% rename from internal/distribution/relay.go rename to distribution/relay.go index d3ab8e3..57d9284 100644 --- a/internal/distribution/relay.go +++ b/distribution/relay.go @@ -6,8 +6,8 @@ import ( "sync" "github.com/zsiec/ccx" - "github.com/zsiec/prism/internal/media" - "github.com/zsiec/prism/internal/moq" + "github.com/zsiec/prism/media" + "github.com/zsiec/prism/moq" ) // Viewer is the interface that a viewer session (single or mux) must implement diff --git a/internal/distribution/relay_test.go b/distribution/relay_test.go similarity index 99% rename from internal/distribution/relay_test.go rename to distribution/relay_test.go index bffd54d..c4d89d0 100644 --- a/internal/distribution/relay_test.go +++ b/distribution/relay_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/zsiec/ccx" - "github.com/zsiec/prism/internal/media" + "github.com/zsiec/prism/media" ) // mockViewer implements the Viewer interface for testing. diff --git a/internal/distribution/server.go b/distribution/server.go similarity index 99% rename from internal/distribution/server.go rename to distribution/server.go index 23b47e8..b5d6b38 100644 --- a/internal/distribution/server.go +++ b/distribution/server.go @@ -13,9 +13,9 @@ import ( "github.com/quic-go/quic-go" "github.com/quic-go/quic-go/http3" - "github.com/zsiec/prism/internal/certs" - "github.com/zsiec/prism/internal/moq" - "github.com/zsiec/prism/internal/webtransport" + "github.com/zsiec/prism/certs" + "github.com/zsiec/prism/moq" + "github.com/zsiec/prism/webtransport" ) // StatsProvider is implemented by Pipeline to supply stream statistics diff --git a/internal/distribution/server_test.go b/distribution/server_test.go similarity index 99% rename from internal/distribution/server_test.go rename to distribution/server_test.go index 947f6ad..ab7b4f8 100644 --- a/internal/distribution/server_test.go +++ b/distribution/server_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/zsiec/prism/internal/certs" + "github.com/zsiec/prism/certs" ) func newTestServer(t *testing.T) *Server { diff --git a/internal/distribution/session_helpers.go b/distribution/session_helpers.go similarity index 95% rename from internal/distribution/session_helpers.go rename to distribution/session_helpers.go index 275ff2f..0c4def1 100644 --- a/internal/distribution/session_helpers.go +++ b/distribution/session_helpers.go @@ -3,7 +3,7 @@ package distribution import ( "sync/atomic" - "github.com/zsiec/prism/internal/media" + "github.com/zsiec/prism/media" ) // trySendVideo implements the damaged-group-aware video send logic shared diff --git a/internal/distribution/session_helpers_test.go b/distribution/session_helpers_test.go similarity index 98% rename from internal/distribution/session_helpers_test.go rename to distribution/session_helpers_test.go index 1db31c9..125bfee 100644 --- a/internal/distribution/session_helpers_test.go +++ b/distribution/session_helpers_test.go @@ -4,7 +4,7 @@ import ( "sync/atomic" "testing" - "github.com/zsiec/prism/internal/media" + "github.com/zsiec/prism/media" ) func TestTrySendVideoKeyframeResetsGroup(t *testing.T) { diff --git a/internal/distribution/streamstats.go b/distribution/streamstats.go similarity index 99% rename from internal/distribution/streamstats.go rename to distribution/streamstats.go index 5557369..779c17c 100644 --- a/internal/distribution/streamstats.go +++ b/distribution/streamstats.go @@ -5,7 +5,7 @@ import ( "sync/atomic" "time" - "github.com/zsiec/prism/internal/demux" + "github.com/zsiec/prism/demux" ) // Compile-time interface check. diff --git a/internal/distribution/streamstats_test.go b/distribution/streamstats_test.go similarity index 100% rename from internal/distribution/streamstats_test.go rename to distribution/streamstats_test.go diff --git a/internal/ingest/ingest.go b/ingest/ingest.go similarity index 100% rename from internal/ingest/ingest.go rename to ingest/ingest.go diff --git a/internal/ingest/ingest_test.go b/ingest/ingest_test.go similarity index 100% rename from internal/ingest/ingest_test.go rename to ingest/ingest_test.go diff --git a/internal/ingest/srt/caller.go b/ingest/srt/caller.go similarity index 99% rename from internal/ingest/srt/caller.go rename to ingest/srt/caller.go index 47debc8..770c0ad 100644 --- a/internal/ingest/srt/caller.go +++ b/ingest/srt/caller.go @@ -11,7 +11,7 @@ import ( srtgo "github.com/zsiec/srtgo" - "github.com/zsiec/prism/internal/ingest" + "github.com/zsiec/prism/ingest" ) // PullRequest describes a remote SRT source to pull from. diff --git a/internal/ingest/srt/doc.go b/ingest/srt/doc.go similarity index 100% rename from internal/ingest/srt/doc.go rename to ingest/srt/doc.go diff --git a/internal/ingest/srt/server.go b/ingest/srt/server.go similarity index 98% rename from internal/ingest/srt/server.go rename to ingest/srt/server.go index b29ead8..9ba9790 100644 --- a/internal/ingest/srt/server.go +++ b/ingest/srt/server.go @@ -10,7 +10,7 @@ import ( srtgo "github.com/zsiec/srtgo" - "github.com/zsiec/prism/internal/ingest" + "github.com/zsiec/prism/ingest" ) // srtReadBufferSize is the read buffer for SRT socket reads. diff --git a/internal/ingest/srt/server_test.go b/ingest/srt/server_test.go similarity index 100% rename from internal/ingest/srt/server_test.go rename to ingest/srt/server_test.go diff --git a/internal/media/frame.go b/media/frame.go similarity index 100% rename from internal/media/frame.go rename to media/frame.go diff --git a/internal/moq/control.go b/moq/control.go similarity index 100% rename from internal/moq/control.go rename to moq/control.go diff --git a/internal/moq/control_test.go b/moq/control_test.go similarity index 100% rename from internal/moq/control_test.go rename to moq/control_test.go diff --git a/internal/moq/doc.go b/moq/doc.go similarity index 84% rename from internal/moq/doc.go rename to moq/doc.go index 42dcf39..593d184 100644 --- a/internal/moq/doc.go +++ b/moq/doc.go @@ -4,5 +4,5 @@ // decoder configuration records), and typed error definitions. // // This package contains no session or relay logic; those higher-level -// concerns live in [github.com/zsiec/prism/internal/distribution]. +// concerns live in [github.com/zsiec/prism/distribution]. package moq diff --git a/internal/moq/errors.go b/moq/errors.go similarity index 100% rename from internal/moq/errors.go rename to moq/errors.go diff --git a/internal/moq/format.go b/moq/format.go similarity index 99% rename from internal/moq/format.go rename to moq/format.go index c360855..2a55d1c 100644 --- a/internal/moq/format.go +++ b/moq/format.go @@ -3,7 +3,7 @@ package moq import ( "encoding/binary" - "github.com/zsiec/prism/internal/demux" + "github.com/zsiec/prism/demux" ) // AnnexBToAVC1 converts Annex B NALUs (4-byte start code prefixed) to AVC1 diff --git a/internal/moq/format_bench_test.go b/moq/format_bench_test.go similarity index 100% rename from internal/moq/format_bench_test.go rename to moq/format_bench_test.go diff --git a/internal/moq/format_test.go b/moq/format_test.go similarity index 100% rename from internal/moq/format_test.go rename to moq/format_test.go diff --git a/internal/mpegts/accumulator.go b/mpegts/accumulator.go similarity index 100% rename from internal/mpegts/accumulator.go rename to mpegts/accumulator.go diff --git a/internal/mpegts/accumulator_test.go b/mpegts/accumulator_test.go similarity index 100% rename from internal/mpegts/accumulator_test.go rename to mpegts/accumulator_test.go diff --git a/internal/mpegts/crc32.go b/mpegts/crc32.go similarity index 100% rename from internal/mpegts/crc32.go rename to mpegts/crc32.go diff --git a/internal/mpegts/debug_test.go b/mpegts/debug_test.go similarity index 100% rename from internal/mpegts/debug_test.go rename to mpegts/debug_test.go diff --git a/internal/mpegts/demuxer.go b/mpegts/demuxer.go similarity index 100% rename from internal/mpegts/demuxer.go rename to mpegts/demuxer.go diff --git a/internal/mpegts/demuxer_test.go b/mpegts/demuxer_test.go similarity index 100% rename from internal/mpegts/demuxer_test.go rename to mpegts/demuxer_test.go diff --git a/internal/mpegts/packet.go b/mpegts/packet.go similarity index 100% rename from internal/mpegts/packet.go rename to mpegts/packet.go diff --git a/internal/mpegts/packet_fuzz_test.go b/mpegts/packet_fuzz_test.go similarity index 100% rename from internal/mpegts/packet_fuzz_test.go rename to mpegts/packet_fuzz_test.go diff --git a/internal/mpegts/packet_test.go b/mpegts/packet_test.go similarity index 100% rename from internal/mpegts/packet_test.go rename to mpegts/packet_test.go diff --git a/internal/mpegts/pes.go b/mpegts/pes.go similarity index 100% rename from internal/mpegts/pes.go rename to mpegts/pes.go diff --git a/internal/mpegts/pes_test.go b/mpegts/pes_test.go similarity index 100% rename from internal/mpegts/pes_test.go rename to mpegts/pes_test.go diff --git a/internal/mpegts/psi.go b/mpegts/psi.go similarity index 100% rename from internal/mpegts/psi.go rename to mpegts/psi.go diff --git a/internal/mpegts/psi_test.go b/mpegts/psi_test.go similarity index 100% rename from internal/mpegts/psi_test.go rename to mpegts/psi_test.go diff --git a/internal/mpegts/types.go b/mpegts/types.go similarity index 100% rename from internal/mpegts/types.go rename to mpegts/types.go diff --git a/internal/pipeline/pipeline.go b/pipeline/pipeline.go similarity index 97% rename from internal/pipeline/pipeline.go rename to pipeline/pipeline.go index 4013352..3dd88ba 100644 --- a/internal/pipeline/pipeline.go +++ b/pipeline/pipeline.go @@ -11,10 +11,10 @@ import ( "time" "github.com/zsiec/ccx" - "github.com/zsiec/prism/internal/demux" - "github.com/zsiec/prism/internal/distribution" - "github.com/zsiec/prism/internal/media" - "github.com/zsiec/prism/internal/moq" + "github.com/zsiec/prism/demux" + "github.com/zsiec/prism/distribution" + "github.com/zsiec/prism/media" + "github.com/zsiec/prism/moq" ) // Broadcaster is the subset of distribution.Relay that the pipeline uses diff --git a/internal/pipeline/pipeline_integration_test.go b/pipeline/pipeline_integration_test.go similarity index 98% rename from internal/pipeline/pipeline_integration_test.go rename to pipeline/pipeline_integration_test.go index 4992b58..f3db75d 100644 --- a/internal/pipeline/pipeline_integration_test.go +++ b/pipeline/pipeline_integration_test.go @@ -9,8 +9,8 @@ import ( "time" "github.com/zsiec/ccx" - "github.com/zsiec/prism/internal/distribution" - "github.com/zsiec/prism/internal/media" + "github.com/zsiec/prism/distribution" + "github.com/zsiec/prism/media" ) // testViewer implements distribution.Viewer to collect frames from the relay. diff --git a/internal/pipeline/pipeline_test.go b/pipeline/pipeline_test.go similarity index 96% rename from internal/pipeline/pipeline_test.go rename to pipeline/pipeline_test.go index aed39ef..719e33f 100644 --- a/internal/pipeline/pipeline_test.go +++ b/pipeline/pipeline_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/zsiec/prism/internal/distribution" + "github.com/zsiec/prism/distribution" ) func TestNew(t *testing.T) { diff --git a/internal/scte35/bits.go b/scte35/bits.go similarity index 100% rename from internal/scte35/bits.go rename to scte35/bits.go diff --git a/internal/scte35/bits_test.go b/scte35/bits_test.go similarity index 100% rename from internal/scte35/bits_test.go rename to scte35/bits_test.go diff --git a/internal/scte35/crc.go b/scte35/crc.go similarity index 100% rename from internal/scte35/crc.go rename to scte35/crc.go diff --git a/internal/scte35/crc_test.go b/scte35/crc_test.go similarity index 100% rename from internal/scte35/crc_test.go rename to scte35/crc_test.go diff --git a/internal/scte35/descriptor.go b/scte35/descriptor.go similarity index 100% rename from internal/scte35/descriptor.go rename to scte35/descriptor.go diff --git a/internal/scte35/scte35.go b/scte35/scte35.go similarity index 100% rename from internal/scte35/scte35.go rename to scte35/scte35.go diff --git a/internal/scte35/scte35_bench_test.go b/scte35/scte35_bench_test.go similarity index 100% rename from internal/scte35/scte35_bench_test.go rename to scte35/scte35_bench_test.go diff --git a/internal/scte35/scte35_fuzz_test.go b/scte35/scte35_fuzz_test.go similarity index 100% rename from internal/scte35/scte35_fuzz_test.go rename to scte35/scte35_fuzz_test.go diff --git a/internal/scte35/scte35_test.go b/scte35/scte35_test.go similarity index 100% rename from internal/scte35/scte35_test.go rename to scte35/scte35_test.go diff --git a/internal/scte35/splice_insert.go b/scte35/splice_insert.go similarity index 100% rename from internal/scte35/splice_insert.go rename to scte35/splice_insert.go diff --git a/internal/scte35/splice_null.go b/scte35/splice_null.go similarity index 100% rename from internal/scte35/splice_null.go rename to scte35/splice_null.go diff --git a/internal/scte35/time_signal.go b/scte35/time_signal.go similarity index 100% rename from internal/scte35/time_signal.go rename to scte35/time_signal.go diff --git a/internal/stream/manager.go b/stream/manager.go similarity index 100% rename from internal/stream/manager.go rename to stream/manager.go diff --git a/internal/stream/manager_test.go b/stream/manager_test.go similarity index 100% rename from internal/stream/manager_test.go rename to stream/manager_test.go diff --git a/test/tools/inject-scte35/main.go b/test/tools/inject-scte35/main.go index b979044..07f6bb5 100644 --- a/test/tools/inject-scte35/main.go +++ b/test/tools/inject-scte35/main.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/zsiec/prism/internal/scte35" + "github.com/zsiec/prism/scte35" ) const tsPacketSize = 188 diff --git a/internal/webtransport/doc.go b/webtransport/doc.go similarity index 100% rename from internal/webtransport/doc.go rename to webtransport/doc.go diff --git a/internal/webtransport/errors.go b/webtransport/errors.go similarity index 100% rename from internal/webtransport/errors.go rename to webtransport/errors.go diff --git a/internal/webtransport/protocol.go b/webtransport/protocol.go similarity index 100% rename from internal/webtransport/protocol.go rename to webtransport/protocol.go diff --git a/internal/webtransport/server.go b/webtransport/server.go similarity index 100% rename from internal/webtransport/server.go rename to webtransport/server.go diff --git a/internal/webtransport/session.go b/webtransport/session.go similarity index 100% rename from internal/webtransport/session.go rename to webtransport/session.go diff --git a/internal/webtransport/session_manager.go b/webtransport/session_manager.go similarity index 100% rename from internal/webtransport/session_manager.go rename to webtransport/session_manager.go diff --git a/internal/webtransport/stream.go b/webtransport/stream.go similarity index 100% rename from internal/webtransport/stream.go rename to webtransport/stream.go diff --git a/internal/webtransport/streams_map.go b/webtransport/streams_map.go similarity index 100% rename from internal/webtransport/streams_map.go rename to webtransport/streams_map.go From d1cf6e8c89054889443d9dc6a559f26707ca5684 Mon Sep 17 00:00:00 2001 From: Thomas Symborski Date: Tue, 24 Feb 2026 23:54:54 -0500 Subject: [PATCH 2/4] Add example programs for package consumers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit minimal-server: SRT ingest → demux → pipeline → relay → WebTransport custom-ingest: feed MPEG-TS file directly without SRT --- examples/custom-ingest/main.go | 71 +++++++++++++++++++++++++++ examples/minimal-server/main.go | 86 +++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 examples/custom-ingest/main.go create mode 100644 examples/minimal-server/main.go diff --git a/examples/custom-ingest/main.go b/examples/custom-ingest/main.go new file mode 100644 index 0000000..de5c94b --- /dev/null +++ b/examples/custom-ingest/main.go @@ -0,0 +1,71 @@ +// Custom ingest: feed an MPEG-TS file directly without SRT. +// This demonstrates that the ingest layer is optional — any io.Reader +// producing MPEG-TS data can drive the pipeline. +// +// Usage: +// +// go run ./examples/custom-ingest input.ts +// open https://localhost:4443/?stream=file +package main + +import ( + "context" + "log" + "log/slog" + "os" + "os/signal" + "time" + + "github.com/zsiec/prism/certs" + "github.com/zsiec/prism/distribution" + "github.com/zsiec/prism/pipeline" +) + +func main() { + if len(os.Args) < 2 { + log.Fatal("usage: custom-ingest ") + } + + slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo}))) + + f, err := os.Open(os.Args[1]) + if err != nil { + log.Fatal(err) + } + defer f.Close() + + cert, err := certs.Generate(14 * 24 * time.Hour) + if err != nil { + log.Fatal(err) + } + + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) + defer cancel() + + distSrv, err := distribution.NewServer(distribution.ServerConfig{ + Addr: ":4443", + WebDir: "web/dist", + Cert: cert, + }) + if err != nil { + log.Fatal(err) + } + + relay := distSrv.RegisterStream("file") + p := pipeline.New("file", f, relay) + p.SetProtocol("File") + distSrv.SetPipeline("file", p) + + go func() { + if err := p.Run(ctx); err != nil { + slog.Error("pipeline finished", "error", err) + } + slog.Info("file playback complete") + }() + + slog.Info("serving file stream", "webtransport", ":4443", "cert_hash", cert.FingerprintBase64()) + + if err := distSrv.Start(ctx); err != nil { + slog.Error("distribution server error", "error", err) + } +} diff --git a/examples/minimal-server/main.go b/examples/minimal-server/main.go new file mode 100644 index 0000000..c355465 --- /dev/null +++ b/examples/minimal-server/main.go @@ -0,0 +1,86 @@ +// Minimal Prism server: SRT ingest → demux → pipeline → relay → WebTransport. +// This demonstrates wiring the core packages together — the same pattern +// used by cmd/prism but stripped to the essentials. +// +// Usage: +// +// go run ./examples/minimal-server +// ffmpeg -re -i input.ts -c copy -f mpegts srt://localhost:6000?streamid=demo +// open https://localhost:4443 +package main + +import ( + "context" + "io" + "log" + "log/slog" + "os" + "os/signal" + "time" + + "github.com/zsiec/prism/certs" + "github.com/zsiec/prism/distribution" + "github.com/zsiec/prism/ingest" + srtingest "github.com/zsiec/prism/ingest/srt" + "github.com/zsiec/prism/pipeline" + "github.com/zsiec/prism/stream" +) + +func main() { + slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo}))) + + cert, err := certs.Generate(14 * 24 * time.Hour) + if err != nil { + log.Fatal(err) + } + + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) + defer cancel() + + mgr := stream.NewManager(nil) + + var distSrv *distribution.Server + + registry := ingest.NewRegistry(func(key string, input io.Reader, _ ingest.InputFormat) { + if _, created := mgr.Create(key); !created { + return + } + defer func() { + distSrv.UnregisterStream(key) + mgr.Remove(key) + }() + + relay := distSrv.RegisterStream(key) + p := pipeline.New(key, input, relay) + p.SetProtocol("SRT") + distSrv.SetPipeline(key, p) + + if err := p.Run(ctx); err != nil { + slog.Error("pipeline error", "stream", key, "error", err) + } + }) + + distSrv, err = distribution.NewServer(distribution.ServerConfig{ + Addr: ":4443", + WebDir: "web/dist", + Cert: cert, + }) + if err != nil { + log.Fatal(err) + } + + srtSrv := srtingest.NewServer(":6000", registry, nil) + + go func() { + if err := srtSrv.Start(ctx); err != nil { + slog.Error("SRT server error", "error", err) + cancel() + } + }() + + slog.Info("prism minimal server", "srt", ":6000", "webtransport", ":4443", "cert_hash", cert.FingerprintBase64()) + + if err := distSrv.Start(ctx); err != nil { + slog.Error("distribution server error", "error", err) + } +} From b7bf797dd5d84d02ba2c6ce0225f4b5795071f11 Mon Sep 17 00:00:00 2001 From: Thomas Symborski Date: Tue, 24 Feb 2026 23:55:02 -0500 Subject: [PATCH 3/4] Add web player library build entry point Barrel export (lib.ts) of headless player, MoQ transport, metrics, and protocol types. Vite library config produces ES module bundle in dist-lib/. Existing demo build unchanged. --- web/package.json | 1 + web/src/lib.ts | 57 ++++++++++++++++++++++++++++++++++++++++++ web/vite.lib.config.ts | 19 ++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 web/src/lib.ts create mode 100644 web/vite.lib.config.ts diff --git a/web/package.json b/web/package.json index a59244e..ba94d3e 100644 --- a/web/package.json +++ b/web/package.json @@ -7,6 +7,7 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", + "build:lib": "tsc && vite build --config vite.lib.config.ts", "preview": "vite preview" }, "devDependencies": { diff --git a/web/src/lib.ts b/web/src/lib.ts new file mode 100644 index 0000000..0be8668 --- /dev/null +++ b/web/src/lib.ts @@ -0,0 +1,57 @@ +// Headless library entry point for embedding Prism's player and transport +// in external applications. Build with: npx vite build --config vite.lib.config.ts + +// Player +export { PrismPlayer } from "./player"; +export type { TilePerfStats } from "./player"; + +// MoQ Transport +export { MoQTransport } from "./moq-transport"; +export type { MoQTransportCallbacks } from "./moq-transport"; + +// MoQ Multiview Transport +export { MoQMultiviewTransport } from "./moq-multiview-transport"; +export type { MoQMultiviewCallbacks } from "./moq-multiview-transport"; + +// Metrics +export { MetricsStore } from "./metrics-store"; +export type { + FrameEvent, + VideoMetrics, + AudioMetrics, + SyncMetrics, + TransportMetrics, + CaptionMetrics, + HealthStatus, + StreamInfo, + ErrorCounters, +} from "./metrics-store"; + +// Transport types +export type { + TrackInfo, + ServerAudioTrackStats, + ServerViewerStats, + ServerSCTE35Event, + ServerStats, +} from "./transport"; + +// Protocol types +export { parseCaptionData } from "./protocol"; +export type { + CaptionSpan, + CaptionRow, + CaptionRegion, + CaptionData, + ProtocolDiagnostics, +} from "./protocol"; + +// Stream buffer +export { StreamBuffer } from "./stream-buffer"; + +// Multiview types +export type { + MuxStreamEntry, + MuxStreamCallbacks, + MuxViewerStats, +} from "./multiview-types"; diff --git a/web/vite.lib.config.ts b/web/vite.lib.config.ts new file mode 100644 index 0000000..cb644c9 --- /dev/null +++ b/web/vite.lib.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "vite"; +import { resolve } from "path"; + +export default defineConfig({ + build: { + target: "ES2022", + outDir: "dist-lib", + lib: { + entry: resolve(__dirname, "src/lib.ts"), + formats: ["es"], + fileName: "prism", + }, + rollupOptions: { + output: { + entryFileNames: "prism.js", + }, + }, + }, +}); From 617292133eb37d1a7e39d0ba7a5160acdaec8ce0 Mon Sep 17 00:00:00 2001 From: Thomas Symborski Date: Wed, 25 Feb 2026 00:06:10 -0500 Subject: [PATCH 4/4] Add standalone library demo page and documentation Add web/examples/standalone.html demonstrating PrismPlayer imported from the built library bundle, with a demo:lib npm script to build and serve it. Add README files for web/ and examples/ to help new users get started quickly. Document all examples in the main README. Remove govulncheck from make check and update CONTRIBUTING.md to list staticcheck. --- CONTRIBUTING.md | 1 + Makefile | 1 - README.md | 58 +++++++++++++++++++- examples/README.md | 49 +++++++++++++++++ web/README.md | 79 +++++++++++++++++++++++++++ web/examples/standalone.html | 102 +++++++++++++++++++++++++++++++++++ web/package.json | 3 +- 7 files changed, 289 insertions(+), 4 deletions(-) create mode 100644 examples/README.md create mode 100644 web/README.md create mode 100644 web/examples/standalone.html diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fbc3885..f69ce12 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,6 +21,7 @@ All changes must pass `make check`, which runs: - `gofmt -s` — Go code formatting with simplification - `go vet` — Go static analysis +- `staticcheck` — Extended static analysis ([install](https://staticcheck.dev/)) - `go test -race` — All tests with the race detector enabled - `npx tsc --noEmit` — TypeScript type checking (strict mode) diff --git a/Makefile b/Makefile index ecd2671..a514fc7 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,6 @@ check: fmt vet go mod tidy staticcheck ./... go test -race -cover ./... - govulncheck ./... cd web && npx tsc --noEmit web-install: diff --git a/README.md b/README.md index 9120e61..bcf9cbc 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,61 @@ ffmpeg -re -i input.ts -c copy -f mpegts srt://localhost:6000?streamid=mystream Then open `https://localhost:4444/?stream=mystream`. +## Examples + +Prism's packages are designed to be used as a library. The `examples/` directory contains standalone programs showing how to embed Prism in your own application, and `web/examples/` shows how to use the player in a browser. + +### Minimal server (Go) + +A stripped-down version of `cmd/prism` — SRT ingest, demux, and WebTransport delivery in ~60 lines: + +```bash +go run ./examples/minimal-server +ffmpeg -re -i input.ts -c copy -f mpegts srt://localhost:6000?streamid=demo +open https://localhost:4443 +``` + +### Custom ingest (Go) + +Feed any MPEG-TS `io.Reader` directly into the pipeline — no SRT required: + +```bash +go run ./examples/custom-ingest input.ts +open https://localhost:4443/?stream=file +``` + +### Standalone web player (TypeScript) + +Embed `PrismPlayer` in your own page using the built library bundle: + +```bash +cd web && npm run demo:lib # builds dist-lib/prism.js + starts Vite dev server +# (start the Prism server in another terminal: make run) +open http://localhost:5173/examples/standalone.html?stream=demo +``` + +The HTML is ~80 lines and shows the full API: create a player, connect to a stream key, handle lifecycle callbacks. See [`web/examples/standalone.html`](web/examples/standalone.html). + +### Building the web player library + +To use `PrismPlayer` in your own project: + +```bash +cd web && npm run build:lib # outputs web/dist-lib/prism.js +``` + +```js +import { PrismPlayer } from "./dist-lib/prism.js"; + +const player = new PrismPlayer(document.getElementById("container"), { + onStreamConnected(key) { console.log("connected:", key); }, + onStreamDisconnected(key) { console.log("disconnected:", key); }, +}); +player.connect("demo"); +``` + +The library also exports `MoQTransport`, `MoQMultiviewTransport`, `MetricsStore`, and related types for advanced use cases. + ## Architecture ``` @@ -131,11 +186,10 @@ make demo make demo-full ``` -`make check` requires [staticcheck](https://staticcheck.dev/) and [govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck): +`make check` requires [staticcheck](https://staticcheck.dev/): ```bash go install honnef.co/go/tools/cmd/staticcheck@latest -go install golang.org/x/vuln/cmd/govulncheck@latest ``` ## Security Considerations diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..680b10b --- /dev/null +++ b/examples/README.md @@ -0,0 +1,49 @@ +# Examples + +Standalone programs demonstrating how to use Prism's packages as a library. + +## Go + +### Minimal Server + +[`minimal-server/main.go`](minimal-server/main.go) — A complete SRT-to-WebTransport server in ~60 lines. Same architecture as `cmd/prism` but stripped to the essentials. + +```bash +go run ./examples/minimal-server +ffmpeg -re -i input.ts -c copy -f mpegts srt://localhost:6000?streamid=demo +open https://localhost:4443 +``` + +### Custom Ingest + +[`custom-ingest/main.go`](custom-ingest/main.go) — Feed any MPEG-TS `io.Reader` directly into the pipeline, bypassing SRT entirely. + +```bash +go run ./examples/custom-ingest input.ts +open https://localhost:4443/?stream=file +``` + +## Web + +### Standalone Player + +[`../web/examples/standalone.html`](../web/examples/standalone.html) — Embed `PrismPlayer` in a plain HTML page using the built library bundle. + +```bash +cd web && npm run demo:lib # builds dist-lib/prism.js + starts dev server +# (start the Prism server in another terminal: make run) +open http://localhost:5173/examples/standalone.html?stream=demo +``` + +## Key Packages + +These are the main packages you'll use when embedding Prism: + +| Package | Description | +|---|---| +| `certs` | Generate self-signed ECDSA certificates for WebTransport | +| `distribution` | WebTransport server, MoQ sessions, relay fan-out | +| `ingest` | Stream ingest registry (pairs stream keys with pipelines) | +| `ingest/srt` | SRT push server and pull caller | +| `pipeline` | Connects a demuxer to a distribution relay | +| `stream` | Stream lifecycle management | diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..5e73ad5 --- /dev/null +++ b/web/README.md @@ -0,0 +1,79 @@ +# Prism Web + +Vanilla TypeScript frontend for Prism. Provides a single-stream player, a 9-up multiview grid, and an embeddable library for use in external applications. + +## Quick Start + +```bash +npm install +npm run dev # Vite dev server on :5173, proxies /api to Prism on :4444 +``` + +Make sure the Prism server is running (`make run` from the repo root). + +- Single stream: `http://localhost:5173/?stream=demo` +- Multiview: `http://localhost:5173/` + +## Scripts + +| Script | Description | +|---|---| +| `npm run dev` | Vite dev server with hot reload and API proxy | +| `npm run build` | Production build to `dist/` | +| `npm run build:lib` | Library build to `dist-lib/prism.js` | +| `npm run demo:lib` | Build library + start dev server (for testing `examples/standalone.html`) | +| `npm run preview` | Preview the production build | + +## Library + +The library build (`npm run build:lib`) produces `dist-lib/prism.js` — an ES module exporting the player and transport classes for embedding in external applications. + +```js +import { PrismPlayer } from "./dist-lib/prism.js"; + +const player = new PrismPlayer(document.getElementById("container"), { + onStreamConnected(key) { console.log("connected:", key); }, + onStreamDisconnected(key) { console.log("disconnected:", key); }, +}); +player.connect("demo"); +``` + +### Exports + +| Export | Description | +|---|---| +| `PrismPlayer` | Single-stream player — creates canvas, audio, captions, and transport internally | +| `MoQTransport` | Low-level MoQ Transport client for a single stream | +| `MoQMultiviewTransport` | Manages N `MoQTransport` instances for multiview | +| `MetricsStore` | Frame-level metrics collection (video, audio, sync, transport, captions) | +| `StreamBuffer` | Buffered stream reader | +| `parseCaptionData` | CEA-608/708 caption parser | + +See [`examples/standalone.html`](examples/standalone.html) for a complete working example. + +## Source Structure + +| File | Purpose | +|---|---| +| `main.ts` | App entry point — routes to single-stream or multiview | +| `lib.ts` | Library entry point — barrel export for `build:lib` | +| `player.ts` | `PrismPlayer` — orchestrates decoding, rendering, and transport for one stream | +| `multiview.ts` | Multiview manager — 9-tile grid with per-tile audio solo | +| `moq-transport.ts` | MoQ Transport client — WebTransport + MoQ control/data parsing | +| `moq-multiview-transport.ts` | Multi-stream MoQ coordinator | +| `video-decoder.ts` | WebCodecs video decoder with worker offload | +| `video-decoder-worker.ts` | Web Worker for `VideoDecoder` | +| `audio-decoder.ts` | WebCodecs audio decoder with AudioWorklet output | +| `renderer.ts` | Canvas 2D / WebGPU video renderer | +| `captions.ts` | CEA-608/708 caption overlay renderer | +| `metrics-store.ts` | Per-frame metrics collection and health scoring | +| `hud.ts` | Heads-up display badges (codec, resolution, bitrate, etc.) | +| `inspector.ts` | Stream inspector panel with real-time charts | +| `protocol.ts` | Wire protocol types and caption parsing | + +## Build Configuration + +Two Vite configs serve different purposes: + +- **`vite.config.ts`** — Main app build. Sets `Cross-Origin-Opener-Policy` and `Cross-Origin-Embedder-Policy` headers required by `SharedArrayBuffer` (used by WebCodecs workers). Proxies `/api` to the Prism server during development. +- **`vite.lib.config.ts`** — Library build. Produces a single ES module (`dist-lib/prism.js`) with worker chunks in `dist-lib/assets/`. diff --git a/web/examples/standalone.html b/web/examples/standalone.html new file mode 100644 index 0000000..6546d79 --- /dev/null +++ b/web/examples/standalone.html @@ -0,0 +1,102 @@ + + + + + + +Prism Player — Standalone Demo + + + + +

Prism Player

+ +
+ + +
+
Not connected
+
+ + + + + diff --git a/web/package.json b/web/package.json index ba94d3e..526e526 100644 --- a/web/package.json +++ b/web/package.json @@ -8,7 +8,8 @@ "dev": "vite", "build": "tsc && vite build", "build:lib": "tsc && vite build --config vite.lib.config.ts", - "preview": "vite preview" + "preview": "vite preview", + "demo:lib": "npm run build:lib && vite dev" }, "devDependencies": { "@webgpu/types": "^0.1.69",