diff --git a/CLAUDE.md b/CLAUDE.md index cfdef64..e835bb5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -This is a GStreamer plugin for Media over QUIC (MoQ), written in Rust. It provides `hangsink` and `hangsrc` elements that enable publishing and subscribing to media streams using the MoQ protocol over QUIC transport. The project has been renamed from moq-gst to hang-gst and uses the updated hang/moq-lite dependencies. +This is a GStreamer plugin for Media over QUIC (MoQ), written in Rust. It provides `hangsink` and `hangsrc` elements that enable publishing and subscribing to media streams using the MoQ protocol over QUIC transport. ## Development Setup @@ -68,8 +68,10 @@ just sub bbb - `imp.rs`: Core implementation with async Tokio runtime ### Key Dependencies -- **hang**: Higher-level hang protocol utilities and CMAF handling (local path dependency) -- **moq-native**: Core MoQ protocol implementation with QUIC/TLS (local path dependency) +- **hang**: Higher-level hang protocol utilities and CMAF handling +- **moq-mux**: MoQ muxing/demuxing for media streams +- **moq-lite**: Lightweight MoQ protocol types +- **moq-native**: Core MoQ protocol implementation with QUIC/TLS - **gstreamer**: GStreamer bindings for Rust - **tokio**: Async runtime (single-threaded worker pool) @@ -88,5 +90,4 @@ Both elements use a shared Tokio runtime and support TLS configuration options. - Renamed from moq-gst to hang-gst - Element names changed from moqsink/moqsrc to hangsink/hangsrc - Added broadcast parameter requirement for both elements -- Updated dependencies to use local hang and moq-native packages - Updated justfile commands to include broadcast parameters \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 6ef13af..fdd879a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,6 +106,18 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-compression" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -194,6 +206,15 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "buf-list" version = "1.1.2" @@ -209,6 +230,12 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.10.1" @@ -218,6 +245,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bytestring" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +dependencies = [ + "bytes", +] + [[package]] name = "cc" version = "1.2.41" @@ -355,6 +391,23 @@ dependencies = [ "memchr", ] +[[package]] +name = "compression-codecs" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +dependencies = [ + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -380,12 +433,40 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "darling" version = "0.21.3" @@ -421,6 +502,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + [[package]] name = "deranged" version = "0.5.4" @@ -452,6 +539,16 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -516,7 +613,7 @@ checksum = "18c1ddb9231d8554c2d6bdf4cfaabf0c59251658c68b6c95cd52dd0c513a912a" dependencies = [ "getrandom 0.3.4", "libm", - "rand", + "rand 0.9.2", "siphasher", ] @@ -526,6 +623,22 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -636,6 +749,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -825,21 +948,17 @@ checksum = "ab2f85be813ce08f0569fcd816f256c6f4287c975d69a9a14ceacc309f1de967" [[package]] name = "hang" -version = "0.9.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b4ffdb9c72ec7f06cacea47589d3b2d71452e85cdb780033d99fbb00e46c98f" +checksum = "f548f7cdc8ec3b9eae085f7b61ff9603d6dc9f09192c5f4b0db4c02577786070" dependencies = [ - "anyhow", "buf-list", "bytes", "derive_more", "futures", - "h264-parser", "hex", "lazy_static", "moq-lite", - "mp4-atom", - "num_enum", "regex", "serde", "serde_json", @@ -847,6 +966,7 @@ dependencies = [ "thiserror 2.0.17", "tokio", "tracing", + "url", ] [[package]] @@ -913,6 +1033,22 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + [[package]] name = "hyper" version = "1.7.0" @@ -934,6 +1070,23 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots 1.0.6", +] + [[package]] name = "hyper-util" version = "0.1.17" @@ -1261,6 +1414,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "m3u8-rs" +version = "5.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1d7ba86f7ea62f17f4310c55e93244619ddc7dadfc7e565de1967e4e41e6e7" +dependencies = [ + "chrono", + "nom", +] + [[package]] name = "matchers" version = "0.2.0" @@ -1289,6 +1452,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -1313,8 +1477,8 @@ dependencies = [ "gstreamer-base", "hang", "moq-lite", + "moq-mux", "moq-native", - "once_cell", "tokio", "tracing", "tracing-subscriber", @@ -1323,15 +1487,16 @@ dependencies = [ [[package]] name = "moq-lite" -version = "0.10.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c2258f990ddd8465f8dcf343bd00714927cd8b8b81784b153020ca24e47898f" +checksum = "c8a4c4e66081bc21067488da13f4131540b38b1cb79fb5176ef4ddacd104786b" dependencies = [ "async-channel", "bytes", "futures", "hex", "num_enum", + "rand 0.9.2", "serde", "thiserror 2.0.17", "tokio", @@ -1340,18 +1505,46 @@ dependencies = [ "web-transport-trait", ] +[[package]] +name = "moq-mux" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2570aa39feef3aa00fa0990862dcdfb44937d3eb9c448c3a4eb1fb8ff43d3" +dependencies = [ + "anyhow", + "buf-list", + "bytes", + "derive_more", + "h264-parser", + "hang", + "m3u8-rs", + "moq-lite", + "mp4-atom", + "num_enum", + "reqwest", + "scuffle-av1", + "scuffle-h265", + "tokio", + "tracing", + "url", +] + [[package]] name = "moq-native" -version = "0.10.1" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e9c5c481870917a231c5c8d0ad8d293007b12efaf16ddf49c70b833c5f9af3e" +checksum = "9848c21bf5db3f8ff5e5a7d89bf2c567f0eb526390c26d5f66f3fec99a6751a5" dependencies = [ "anyhow", "clap", "futures", "hex", + "humantime", + "humantime-serde", "moq-lite", + "parking_lot", "quinn", + "rand 0.9.2", "rcgen", "reqwest", "rustls", @@ -1366,13 +1559,14 @@ dependencies = [ "tracing-subscriber", "url", "web-transport-quinn", + "web-transport-ws", ] [[package]] name = "mp4-atom" -version = "0.9.2" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58b9fcf396d53fdf1c43a9afd38953412b9d782d11391807b473927317bb28f9" +checksum = "5e8e949244bbd26ea7eb6d936af3a6a0202be68bcfc9afce700f3c9026860ff7" dependencies = [ "bytes", "derive_more", @@ -1510,6 +1704,15 @@ dependencies = [ "syn", ] +[[package]] +name = "nutype-enum" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e13adea6de269faa0724df58f43f6fe2a81af7094f1dcb8b5b968eb2103cb3" +dependencies = [ + "scuffle-workspace-hack", +] + [[package]] name = "object" version = "0.37.3" @@ -1568,8 +1771,10 @@ version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ + "backtrace", "cfg-if", "libc", + "petgraph", "redox_syscall", "smallvec", "windows-link", @@ -1587,6 +1792,16 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.12.0", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1688,7 +1903,7 @@ dependencies = [ "fastbloom", "getrandom 0.3.4", "lru-slab", - "rand", + "rand 0.9.2", "ring", "rustc-hash", "rustls", @@ -1730,14 +1945,35 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "rand_chacha", - "rand_core", + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", ] [[package]] @@ -1747,7 +1983,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", ] [[package]] @@ -1835,23 +2080,31 @@ version = "0.12.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ + "async-compression", "base64", "bytes", "futures-core", + "futures-util", "http", "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-rustls", + "tokio-util", "tower", "tower-http", "tower-service", @@ -1859,6 +2112,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots 1.0.6", ] [[package]] @@ -1896,6 +2150,7 @@ dependencies = [ "aws-lc-rs", "log", "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -2032,6 +2287,61 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scuffle-av1" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028eddc8b17fe9dba817b238c56d3acf03748bdbed4c35783cfb93857ef15955" +dependencies = [ + "byteorder", + "bytes", + "scuffle-bytes-util", + "scuffle-workspace-hack", +] + +[[package]] +name = "scuffle-bytes-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0417748c2a42f4a08d4e634b68b1d64f22a8c24bef2e7ac93df33aa61202a45b" +dependencies = [ + "byteorder", + "bytes", + "bytestring", + "scuffle-workspace-hack", +] + +[[package]] +name = "scuffle-expgolomb" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d21330974c941e4c0aedc1e7255ea809e8cbac51e135209f6d67843ad1b94d" +dependencies = [ + "scuffle-bytes-util", + "scuffle-workspace-hack", +] + +[[package]] +name = "scuffle-h265" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b04b276c2f79846b7968abe6f87cedf951e06fd2a2b72d99c457e85d7e40f3fb" +dependencies = [ + "bitflags", + "byteorder", + "bytes", + "nutype-enum", + "scuffle-bytes-util", + "scuffle-expgolomb", + "scuffle-workspace-hack", +] + +[[package]] +name = "scuffle-workspace-hack" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8028ded836a0d9fabdfa4d713389b76a2098b5153f50a135c8faed7e3a3d5ae2" + [[package]] name = "security-framework" version = "3.5.1" @@ -2150,6 +2460,28 @@ dependencies = [ "syn", ] +[[package]] +name = "sfv" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d471eaefb14f4b30032525bdb124b36e55ba9cb1292080e06f1a236cd10fe87" +dependencies = [ + "base64", + "indexmap 2.12.0", + "ref-cast", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2174,6 +2506,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "siphasher" version = "1.0.1" @@ -2403,6 +2741,45 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots 0.26.11", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.9.8" @@ -2566,6 +2943,32 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.8.5", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 1.0.69", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.20" @@ -2596,6 +2999,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -2620,6 +3029,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "walkdir" version = "2.5.0" @@ -2759,12 +3174,13 @@ dependencies = [ [[package]] name = "web-transport-proto" -version = "0.3.0" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b5400535d6dd4c07dc86e83651a838fd513de7f5011d4e4eafa239fa4d0ded4" +checksum = "5afe275c02f899650c5497b946552fcc04f3f378bd2c3bc1e8005ff915772b97" dependencies = [ "bytes", "http", + "sfv", "thiserror 2.0.17", "tokio", "url", @@ -2772,19 +3188,19 @@ dependencies = [ [[package]] name = "web-transport-quinn" -version = "0.10.1" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91815d3170c715230c94b5107a71ccf81646513e548ee1408c3ce285d021d6ca" +checksum = "6d356bedff779480f8d88d94bf50a2eb8dedabb84414442f73c16dfee9db55b8" dependencies = [ "bytes", "futures", "http", - "log", "quinn", "rustls", "rustls-native-certs", "thiserror 2.0.17", "tokio", + "tracing", "url", "web-transport-proto", "web-transport-trait", @@ -2792,11 +3208,26 @@ dependencies = [ [[package]] name = "web-transport-trait" -version = "0.3.0" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "802d6aa508f2c63c9050ceabc17265bbf90ed4d6f4e4357e987583883628e79c" +dependencies = [ + "bytes", +] + +[[package]] +name = "web-transport-ws" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4bafa8c6ff708042f67ef8031ca0f342822fd785b70f36a4b2c014760fc442" +checksum = "b7b1cd89c36a28eae759329839e85f7dbca733896f048a6daaf5f8fc80f3bcba" dependencies = [ "bytes", + "futures", + "thiserror 2.0.17", + "tokio", + "tokio-tungstenite", + "web-transport-proto", + "web-transport-trait", ] [[package]] @@ -2808,6 +3239,24 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.6", +] + +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi-util" version = "0.1.11" diff --git a/Cargo.toml b/Cargo.toml index e13634f..68c5099 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ license = "MIT OR Apache-2.0" version = "0.2.2" edition = "2021" +rust-version = "1.80" keywords = ["quic", "http3", "webtransport", "media", "live"] categories = ["multimedia", "network-programming", "web-programming"] @@ -19,10 +20,10 @@ path = "src/lib.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -# Using local path dependencies from moq-dev -hang = "0.9.0" -moq-lite = "0.10.1" -moq-native = "0.10.1" +hang = "0.14.0" +moq-mux = "0.2.1" +moq-lite = "0.14.0" +moq-native = "0.13.1" anyhow = { version = "1", features = ["backtrace"] } bytes = "1" @@ -30,7 +31,6 @@ gst = { package = "gstreamer", version = "0.23" } gst-base = { package = "gstreamer-base", version = "0.23" } #gst-app = { package = "gstreamer-app", version = "0.23", features = ["v1_20"] } -once_cell = "1" tokio = { version = "1", features = ["full"] } tracing = "0.1.41" tracing-subscriber = "0.3.19" diff --git a/src/sink/imp.rs b/src/sink/imp.rs index 5d4a8f6..aa77c7d 100644 --- a/src/sink/imp.rs +++ b/src/sink/imp.rs @@ -5,14 +5,12 @@ use gst::prelude::*; use gst::subclass::prelude::*; use gst_base::subclass::prelude::*; -use hang::moq_lite; - -use once_cell::sync::Lazy; use std::sync::Arc; +use std::sync::LazyLock; use std::sync::Mutex; use url::Url; -pub static RUNTIME: Lazy = Lazy::new(|| { +pub static RUNTIME: LazyLock = LazyLock::new(|| { tokio::runtime::Builder::new_multi_thread() .enable_all() .worker_threads(1) @@ -29,7 +27,7 @@ struct Settings { #[derive(Default)] struct State { - pub media: Option, + pub media: Option, pub buffer: BytesMut, } @@ -52,7 +50,7 @@ impl ObjectSubclass for MoqSink { impl ObjectImpl for MoqSink { fn properties() -> &'static [glib::ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { + static PROPERTIES: LazyLock> = LazyLock::new(|| { vec![ glib::ParamSpecString::builder("url") .nick("Source URL") @@ -99,7 +97,7 @@ impl GstObjectImpl for MoqSink {} impl ElementImpl for MoqSink { fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { - static ELEMENT_METADATA: Lazy = Lazy::new(|| { + static ELEMENT_METADATA: LazyLock = LazyLock::new(|| { gst::subclass::ElementMetadata::new( "MoQ Sink", "Sink/Network/MoQ", @@ -112,7 +110,7 @@ impl ElementImpl for MoqSink { } fn pad_templates() -> &'static [gst::PadTemplate] { - static PAD_TEMPLATES: Lazy> = Lazy::new(|| { + static PAD_TEMPLATES: LazyLock> = LazyLock::new(|| { let caps = gst::Caps::builder("video/quicktime") .field("variant", "iso-fragmented") .build(); @@ -170,36 +168,32 @@ impl MoqSink { let url = settings.url.as_ref().expect("url is required"); let url = Url::parse(url).context("invalid URL")?; + let name = settings.broadcast.as_ref().expect("broadcast is required").clone(); - // TODO support TLS certs and other options - let client = moq_native::ClientConfig { - tls: moq_native::ClientTls { - disable_verify: Some(settings.tls_disable_verify), - ..Default::default() - }, - ..Default::default() - } - .init()?; + let mut config = moq_native::ClientConfig::default(); + config.tls.disable_verify = Some(settings.tls_disable_verify); + + drop(settings); - RUNTIME.block_on(async move { - let session = client.connect(url.clone()).await.expect("failed to connect"); + let origin = moq_lite::Origin::produce(); + let mut broadcast = moq_lite::Broadcast::produce(); + let broadcast_consumer = broadcast.consume(); + let catalog_track = broadcast.create_track(hang::Catalog::default_track()); + let catalog = hang::CatalogProducer::new(catalog_track, Default::default()); - let origin = moq_lite::Origin::produce(); - let broadcast = moq_lite::Broadcast::produce(); + origin.publish_broadcast(&name, broadcast_consumer); - let name = settings.broadcast.as_ref().expect("broadcast is required"); - origin.producer.publish_broadcast(name, broadcast.consumer); + let client = config.init()?.with_publish(origin.consume()); - let _session = moq_lite::Session::connect(session, origin.consumer, None) - .await - .expect("failed to connect"); + RUNTIME.block_on(async { + let _session = client.connect(url).await.context("failed to connect")?; - let media = hang::import::Fmp4::new(broadcast.producer.into()); + let media = moq_mux::import::Fmp4::new(broadcast, catalog, Default::default()); let mut state = self.state.lock().unwrap(); state.media = Some(media); - }); - Ok(()) + anyhow::Ok(()) + }) } } diff --git a/src/source/imp.rs b/src/source/imp.rs index aa1ff48..1d682b4 100644 --- a/src/source/imp.rs +++ b/src/source/imp.rs @@ -3,16 +3,13 @@ use gst::glib; use gst::prelude::*; use gst::subclass::prelude::*; -use hang::moq_lite; - -use once_cell::sync::Lazy; use std::sync::LazyLock; use std::sync::Mutex; -static CAT: Lazy = - Lazy::new(|| gst::DebugCategory::new("moq-src", gst::DebugColorFlags::empty(), Some("MoQ Source Element"))); +static CAT: LazyLock = + LazyLock::new(|| gst::DebugCategory::new("moq-src", gst::DebugColorFlags::empty(), Some("MoQ Source Element"))); -pub static RUNTIME: Lazy = Lazy::new(|| { +pub static RUNTIME: LazyLock = LazyLock::new(|| { tokio::runtime::Builder::new_multi_thread() .enable_all() .worker_threads(1) @@ -30,6 +27,8 @@ struct Settings { #[derive(Default)] pub struct MoqSrc { settings: Mutex, + session: Mutex>, + tasks: Mutex>>, } #[glib::object_subclass] @@ -48,7 +47,7 @@ impl BinImpl for MoqSrc {} impl ObjectImpl for MoqSrc { fn properties() -> &'static [glib::ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { + static PROPERTIES: LazyLock> = LazyLock::new(|| { vec![ glib::ParamSpecString::builder("url") .nick("Source URL") @@ -93,7 +92,7 @@ impl ObjectImpl for MoqSrc { impl ElementImpl for MoqSrc { fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { - static ELEMENT_METADATA: Lazy = Lazy::new(|| { + static ELEMENT_METADATA: LazyLock = LazyLock::new(|| { gst::subclass::ElementMetadata::new( "MoQ Src", "Source/Network/MoQ", @@ -158,44 +157,41 @@ impl ElementImpl for MoqSrc { impl MoqSrc { async fn setup(&self) -> anyhow::Result<()> { - let (client, url, name) = { + let (client, url, name, origin_consumer) = { let settings = self.settings.lock().unwrap(); let url = url::Url::parse(settings.url.as_ref().expect("url is required"))?; let name = settings.broadcast.as_ref().expect("broadcast is required").clone(); - // TODO support TLS certs and other options - let client = moq_native::ClientConfig { - tls: moq_native::ClientTls { - disable_verify: Some(settings.tls_disable_verify), - ..Default::default() - }, - ..Default::default() - } - .init()?; + let mut config = moq_native::ClientConfig::default(); + config.tls.disable_verify = Some(settings.tls_disable_verify); + + let origin = moq_lite::Origin::produce(); + let origin_consumer = origin.consume(); - (client, url, name) + let client = config.init()?.with_consume(origin); + + (client, url, name, origin_consumer) }; let session = client.connect(url).await?; - let origin = moq_lite::Origin::produce(); - let _session = moq_lite::Session::connect(session, None, origin.producer).await?; + *self.session.lock().unwrap() = Some(session); - let broadcast = origin - .consumer + let broadcast = origin_consumer .consume_broadcast(&name) .ok_or_else(|| anyhow::anyhow!("Broadcast '{}' not found", name))?; - let catalog = broadcast.subscribe_track(&hang::catalog::Catalog::default_track()); - let mut catalog = hang::catalog::CatalogConsumer::new(catalog); + let catalog = broadcast.subscribe_track(&hang::Catalog::default_track()); + let mut catalog = hang::CatalogConsumer::new(catalog); // TODO handle catalog updates let catalog = catalog.next().await?.context("no catalog found")?.clone(); - if let Some(video) = catalog.video { - for (track_name, config) in video.renditions { - let track_ref = hang::moq_lite::Track::new(&track_name); + { + for (track_name, config) in catalog.video.renditions { + let track_ref = moq_lite::Track::new(&track_name); + let track_consumer = broadcast.subscribe_track(&track_ref); let mut track = - hang::TrackConsumer::new(broadcast.subscribe_track(&track_ref), std::time::Duration::from_secs(1)); + hang::container::OrderedConsumer::new(track_consumer, std::time::Duration::from_secs(1)); let caps = match config.codec { hang::catalog::VideoCodec::H264(_) => { @@ -238,9 +234,9 @@ impl MoqSrc { // Push to the srcpad in a background task. let mut reference = None; - tokio::spawn(async move { + let handle = tokio::spawn(async move { loop { - match track.read_frame().await { + match track.read().await { Ok(Some(frame)) => { let payload: Vec = frame.payload.into_iter().flatten().collect(); let mut buffer = gst::Buffer::from_slice(payload); @@ -283,14 +279,16 @@ impl MoqSrc { } } }); + self.tasks.lock().unwrap().push(handle); } } - if let Some(audio) = catalog.audio { - for (track_name, config) in audio.renditions { - let track_ref = hang::moq_lite::Track::new(&track_name); + { + for (track_name, config) in catalog.audio.renditions { + let track_ref = moq_lite::Track::new(&track_name); + let track_consumer = broadcast.subscribe_track(&track_ref); let mut track = - hang::TrackConsumer::new(broadcast.subscribe_track(&track_ref), std::time::Duration::from_secs(1)); + hang::container::OrderedConsumer::new(track_consumer, std::time::Duration::from_secs(1)); let caps = match &config.codec { hang::catalog::AudioCodec::AAC(_aac) => { @@ -347,9 +345,9 @@ impl MoqSrc { // Push to the srcpad in a background task. let mut reference = None; - tokio::spawn(async move { + let handle = tokio::spawn(async move { loop { - match track.read_frame().await { + match track.read().await { Ok(Some(frame)) => { let payload: Vec = frame.payload.into_iter().flatten().collect(); let mut buffer = gst::Buffer::from_slice(payload); @@ -388,6 +386,7 @@ impl MoqSrc { } } }); + self.tasks.lock().unwrap().push(handle); } } @@ -398,6 +397,9 @@ impl MoqSrc { } fn cleanup(&self) { - // TODO kill spawned tasks + for task in self.tasks.lock().unwrap().drain(..) { + task.abort(); + } + *self.session.lock().unwrap() = None; } }