From 339aef50e0301edebc3e5230e7b8f2c802c0d25f Mon Sep 17 00:00:00 2001 From: "every.channel" Date: Mon, 16 Feb 2026 12:54:42 -0500 Subject: [PATCH] ec-node: WebTransport publish + web hang-watch --- .envrc | 3 + .forgejo/workflows/deploy-cloudflare.yml | 4 +- .gitignore | 2 + Cargo.lock | 619 +++++- README.md | 17 + apps/web/Cargo.lock | 1857 ----------------- apps/web/Cargo.toml | 14 - apps/web/README.md | 5 +- apps/web/app.js | 181 ++ apps/web/index.html | 53 +- apps/web/src/main.rs | 125 -- apps/web/style.css | 407 ++-- crates/ec-chopper/Cargo.toml | 6 +- crates/ec-chopper/src/lib.rs | 37 + crates/ec-node/Cargo.toml | 6 +- crates/ec-node/src/main.rs | 179 ++ deploy/cloudflare-worker/wrangler.toml | 2 +- .../ECP-0063-cloudflare-moq-webtransport.md | 65 + flake.nix | 2 + 19 files changed, 1355 insertions(+), 2229 deletions(-) delete mode 100644 apps/web/Cargo.lock delete mode 100644 apps/web/Cargo.toml create mode 100644 apps/web/app.js delete mode 100644 apps/web/src/main.rs create mode 100644 evolution/proposals/ECP-0063-cloudflare-moq-webtransport.md diff --git a/.envrc b/.envrc index 95c114d..97a0b59 100644 --- a/.envrc +++ b/.envrc @@ -1,2 +1,5 @@ +# Work around `NO_COLOR=1` being interpreted as `--no-color 1` by some nix versions. +export NO_COLOR=true + use flake export EVERY_CHANNEL_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" diff --git a/.forgejo/workflows/deploy-cloudflare.yml b/.forgejo/workflows/deploy-cloudflare.yml index 5241b88..8947020 100644 --- a/.forgejo/workflows/deploy-cloudflare.yml +++ b/.forgejo/workflows/deploy-cloudflare.yml @@ -164,7 +164,7 @@ jobs: "https://codeberg.org/api/v1/repos/every-channel/every.channel/statuses/${GITHUB_SHA}" \ -d '{"context":"deploy-cloudflare/breadcrumb","state":"pending","description":"decrypt ok"}' >/dev/null - - name: Build site (Dioxus web) + - name: Build site (web) env: GITHUB_TOKEN: ${{ github.token }} shell: bash @@ -195,7 +195,7 @@ jobs: | tar -xz -C "$HOME/.local/bin" trunk fi - cd apps/tauri/ui + cd apps/web trunk build --release --public-url / curl -fsSL -X POST -H "Authorization: token ${GITHUB_TOKEN}" \ diff --git a/.gitignore b/.gitignore index 087072f..45c7b5e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ apps/tauri/ui/target/ apps/tauri/ui/apps/ apps/tauri/dist/ apps/tauri/gen/ +apps/web/dist/ +apps/web/target/ .direnv/ result .wrangler/ diff --git a/Cargo.lock b/Cargo.lock index da401d9..9da631e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,6 +51,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + [[package]] name = "adler2" version = "2.0.1" @@ -186,6 +195,9 @@ name = "anyhow" version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +dependencies = [ + "backtrace", +] [[package]] name = "arc-swap" @@ -239,6 +251,22 @@ dependencies = [ "time", ] +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive 0.6.0", + "asn1-rs-impl 0.2.0", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + [[package]] name = "asn1-rs-derive" version = "0.4.0" @@ -263,6 +291,18 @@ dependencies = [ "synstructure 0.13.2", ] +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure 0.13.2", +] + [[package]] name = "asn1-rs-impl" version = "0.1.0" @@ -310,6 +350,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-compression" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68650b7df54f0293fd061972a0fb05aaf4fc0879d3b3d21a638a182c5c543b9f" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -411,6 +463,29 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +dependencies = [ + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "axum" version = "0.7.9" @@ -477,6 +552,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link 0.2.1", +] + [[package]] name = "base16ct" version = "0.2.0" @@ -602,6 +692,15 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "buf-list" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6b175f9cf8fffedd4c4b18bcfef092356e952b81f596e148f18e98280994593" +dependencies = [ + "bytes", +] + [[package]] name = "bumpalo" version = "3.19.1" @@ -629,6 +728,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 = "cairo-rs" version = "0.18.5" @@ -712,6 +820,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -853,6 +963,15 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + [[package]] name = "cobs" version = "0.3.0" @@ -878,6 +997,23 @@ dependencies = [ "memchr", ] +[[package]] +name = "compression-codecs" +version = "0.4.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" +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" @@ -1300,6 +1436,20 @@ dependencies = [ "rusticata-macros", ] +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs 0.7.1", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.5.5" @@ -1644,7 +1794,7 @@ dependencies = [ "hex", "iroh", "iroh-moq", - "moq-lite", + "moq-lite 0.10.1", "serde", "serde_json", "tokio", @@ -1668,10 +1818,13 @@ dependencies = [ "ec-linux-iptv", "ec-moq", "futures-util", + "hang", "headless_chrome", "hex", "iroh", "just-webrtc", + "moq-mux", + "moq-native", "reqwest", "serde", "serde_json", @@ -1679,6 +1832,7 @@ dependencies = [ "tokio-tungstenite", "tracing", "tracing-subscriber", + "url", "urlencoding", "which 6.0.3", ] @@ -1984,6 +2138,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "fixedbitset" version = "0.5.7" @@ -2059,6 +2219,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futf" version = "0.1.5" @@ -2113,7 +2279,7 @@ version = "7.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175cd8cca9e1d45b87f18ffa75088f2099e3c4fe5e2f83e42de112560bea8ea6" dependencies = [ - "fixedbitset", + "fixedbitset 0.5.7", "futures-core", "futures-lite", "pin-project", @@ -2379,6 +2545,12 @@ dependencies = [ "polyval", ] +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + [[package]] name = "gio" version = "0.18.4" @@ -2569,6 +2741,35 @@ dependencies = [ "tracing", ] +[[package]] +name = "h264-parser" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "253b313319f7109de64e480ffb606f89475cd758bae82e096e00c5d95341d30e" + +[[package]] +name = "hang" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f548f7cdc8ec3b9eae085f7b61ff9603d6dc9f09192c5f4b0db4c02577786070" +dependencies = [ + "buf-list", + "bytes", + "derive_more 2.1.1", + "futures", + "hex", + "lazy_static", + "moq-lite 0.14.0", + "regex", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", + "tokio", + "tracing", + "url", +] + [[package]] name = "hash32" version = "0.2.1" @@ -2793,6 +2994,22 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[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 = "hybrid-array" version = "0.4.6" @@ -3257,7 +3474,7 @@ name = "iroh-moq" version = "0.1.0" dependencies = [ "iroh", - "moq-lite", + "moq-lite 0.10.1", "n0-error", "n0-future", "tokio", @@ -3472,6 +3689,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.85" @@ -3677,6 +3904,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 = "mac" version = "0.1.1" @@ -3869,6 +4106,99 @@ dependencies = [ "web-transport-trait", ] +[[package]] +name = "moq-lite" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8a4c4e66081bc21067488da13f4131540b38b1cb79fb5176ef4ddacd104786b" +dependencies = [ + "async-channel", + "bytes", + "futures", + "hex", + "num_enum", + "rand 0.9.2", + "serde", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-async", + "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 2.1.1", + "h264-parser", + "hang", + "m3u8-rs", + "moq-lite 0.14.0", + "mp4-atom", + "num_enum", + "reqwest", + "scuffle-av1", + "scuffle-h265", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "moq-native" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9848c21bf5db3f8ff5e5a7d89bf2c567f0eb526390c26d5f66f3fec99a6751a5" +dependencies = [ + "anyhow", + "clap", + "futures", + "hex", + "humantime", + "humantime-serde", + "moq-lite 0.14.0", + "parking_lot", + "quinn", + "rand 0.9.2", + "rcgen 0.14.7", + "reqwest", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-webpki", + "serde", + "serde_with", + "time", + "tokio", + "tracing", + "tracing-subscriber", + "url", + "web-transport-quinn", + "web-transport-ws", +] + +[[package]] +name = "mp4-atom" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e8e949244bbd26ea7eb6d936af3a6a0202be68bcfc9afce700f3c9026860ff7" +dependencies = [ + "bytes", + "derive_more 2.1.1", + "num", + "paste", + "serde", + "thiserror 1.0.69", + "tokio", + "tracing", +] + [[package]] name = "muda" version = "0.17.1" @@ -4187,6 +4517,20 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -4197,6 +4541,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.0" @@ -4212,6 +4565,28 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -4252,6 +4627,15 @@ dependencies = [ "libc", ] +[[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 = "objc2" version = "0.6.3" @@ -4480,6 +4864,15 @@ dependencies = [ "objc2-security", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "oid-registry" version = "0.7.1" @@ -4489,6 +4882,15 @@ dependencies = [ "asn1-rs 0.6.2", ] +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs 0.7.1", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -4604,8 +5006,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 0.2.1", @@ -4651,6 +5055,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 0.4.2", + "indexmap 2.13.0", +] + [[package]] name = "pharos" version = "0.5.3" @@ -5137,7 +5551,9 @@ version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ + "aws-lc-rs", "bytes", + "fastbloom", "getrandom 0.3.4", "lru-slab", "rand 0.9.2", @@ -5145,6 +5561,7 @@ dependencies = [ "rustc-hash", "rustls", "rustls-pki-types", + "rustls-platform-verifier", "slab", "thiserror 2.0.18", "tinyvec", @@ -5307,7 +5724,20 @@ dependencies = [ "ring", "rustls-pki-types", "time", - "x509-parser", + "x509-parser 0.16.0", + "yasna", +] + +[[package]] +name = "rcgen" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" +dependencies = [ + "aws-lc-rs", + "rustls-pki-types", + "time", + "x509-parser 0.18.1", "yasna", ] @@ -5448,7 +5878,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.17", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -5477,6 +5907,12 @@ dependencies = [ "webrtc-util", ] +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + [[package]] name = "rustc-hash" version = "2.1.1" @@ -5533,6 +5969,7 @@ version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", @@ -5554,6 +5991,15 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.14.0" @@ -5597,9 +6043,10 @@ version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -5695,6 +6142,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 2.10.0", + "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 = "sdp" version = "0.6.2" @@ -6015,6 +6517,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sfv" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d471eaefb14f4b30032525bdb124b36e55ba9cb1292080e06f1a236cd10fe87" +dependencies = [ + "base64 0.22.1", + "indexmap 2.13.0", + "ref-cast", +] + [[package]] name = "sha1" version = "0.10.6" @@ -7126,13 +7639,18 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ + "async-compression", "bitflags 2.10.0", "bytes", + "futures-core", "futures-util", "http", "http-body", + "http-body-util", "iri-string", "pin-project-lite", + "tokio", + "tokio-util", "tower", "tower-layer", "tower-service", @@ -7396,6 +7914,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -7767,7 +8291,7 @@ dependencies = [ "tokio", "tracing", "url", - "web-transport-proto", + "web-transport-proto 0.3.1", "web-transport-trait", ] @@ -7785,12 +8309,61 @@ dependencies = [ ] [[package]] -name = "web-transport-trait" -version = "0.3.1" +name = "web-transport-proto" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ae5c857e6b426610648b39c6b48f9e66ae97b27b166d7c2f1ec369596548271" +checksum = "17633ea7058419f87cbb7f341ab75ac5c1d6d187c154b0bd4c87539e66f4c4e4" dependencies = [ "bytes", + "http", + "sfv", + "thiserror 2.0.18", + "tokio", + "url", +] + +[[package]] +name = "web-transport-quinn" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b195557749e84091d7b912a25e190e9606283b5121d041faf538b0b55f40d7" +dependencies = [ + "bytes", + "futures", + "http", + "quinn", + "rustls", + "rustls-native-certs", + "thiserror 2.0.18", + "tokio", + "tracing", + "url", + "web-transport-proto 0.5.2", + "web-transport-trait", +] + +[[package]] +name = "web-transport-trait" +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 = "b7b1cd89c36a28eae759329839e85f7dbca733896f048a6daaf5f8fc80f3bcba" +dependencies = [ + "bytes", + "futures", + "thiserror 2.0.18", + "tokio", + "tokio-tungstenite", + "web-transport-proto 0.5.2", + "web-transport-trait", ] [[package]] @@ -7880,7 +8453,7 @@ dependencies = [ "log", "portable-atomic", "rand 0.8.5", - "rcgen", + "rcgen 0.13.2", "regex", "ring", "rtcp", @@ -7945,7 +8518,7 @@ dependencies = [ "portable-atomic", "rand 0.8.5", "rand_core 0.6.4", - "rcgen", + "rcgen 0.13.2", "ring", "rustls", "sec1", @@ -7957,7 +8530,7 @@ dependencies = [ "tokio", "webrtc-util", "x25519-dalek", - "x509-parser", + "x509-parser 0.16.0", ] [[package]] @@ -8868,13 +9441,31 @@ dependencies = [ "der-parser 9.0.0", "lazy_static", "nom", - "oid-registry", + "oid-registry 0.7.1", "ring", "rusticata-macros", "thiserror 1.0.69", "time", ] +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs 0.7.1", + "aws-lc-rs", + "data-encoding", + "der-parser 10.0.0", + "lazy_static", + "nom", + "oid-registry 0.8.1", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + [[package]] name = "xml-rs" version = "0.8.28" diff --git a/README.md b/README.md index c0fa768..f427005 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,23 @@ Runbook: cat docs/USAGE.md ``` +## WebTransport Watch (MoQ) + +Publish (node -> Cloudflare relay): + +```sh +cargo run -p ec-node -- wt-publish \ + --url https://relay.cloudflare.mediaoverquic.com/ \ + --name la-nbc \ + --input http:///auto/v4.1 +``` + +Watch (web): + +```txt +https://every.channel/watch?url=https%3A%2F%2Frelay.cloudflare.mediaoverquic.com%2F&name=la-nbc +``` + Coverage: ```sh diff --git a/apps/web/Cargo.lock b/apps/web/Cargo.lock deleted file mode 100644 index 7ca771a..0000000 --- a/apps/web/Cargo.lock +++ /dev/null @@ -1,1857 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -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 = "bumpalo" -version = "3.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - -[[package]] -name = "const-serialize" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08259976d62c715c4826cb4a3d64a3a9e5c5f68f964ff6087319857f569f93a6" -dependencies = [ - "const-serialize-macro", - "serde", -] - -[[package]] -name = "const-serialize-macro" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04382d0d9df7434af6b1b49ea1a026ef39df1b0738b1cc373368cf175354f6eb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "const_format" -version = "0.2.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" -dependencies = [ - "const_format_proc_macros", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core", - "quote", - "syn", -] - -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown", - "lock_api", - "once_cell", - "parking_lot_core", -] - -[[package]] -name = "data-encoding" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" - -[[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 = "dioxus" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a247114500f1a78e87022defa8173de847accfada8e8809dfae23a118a580c" -dependencies = [ - "dioxus-cli-config", - "dioxus-config-macro", - "dioxus-core", - "dioxus-core-macro", - "dioxus-devtools", - "dioxus-document", - "dioxus-fullstack", - "dioxus-history", - "dioxus-hooks", - "dioxus-html", - "dioxus-logger", - "dioxus-signals", - "dioxus-web", - "manganis", - "warnings", -] - -[[package]] -name = "dioxus-cli-config" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd16948f1ffdb068dd9a64812158073a4250e2af4e98ea31fdac0312e6bce86" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "dioxus-config-macro" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cbf582fbb1c32d34a1042ea675469065574109c95154468710a4d73ee98b49" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "dioxus-core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c03f451a119e47433c16e2d8eb5b15bf7d6e6734eb1a4c47574e6711dadff8d" -dependencies = [ - "const_format", - "dioxus-core-types", - "futures-channel", - "futures-util", - "generational-box", - "longest-increasing-subsequence", - "rustc-hash", - "rustversion", - "serde", - "slab", - "slotmap", - "tracing", - "warnings", -] - -[[package]] -name = "dioxus-core-macro" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "105c954caaaedf8cd10f3d1ba576b01e18aa8d33ad435182125eefe488cf0064" -dependencies = [ - "convert_case", - "dioxus-rsx", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dioxus-core-types" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91a82fccfa48574eb7aa183e297769540904694844598433a9eb55896ad9f93b" -dependencies = [ - "once_cell", -] - -[[package]] -name = "dioxus-devtools" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a7300f1e8181218187b03502044157eef04e0a25b518117c5ef9ae1096880" -dependencies = [ - "dioxus-core", - "dioxus-devtools-types", - "dioxus-signals", - "serde", - "serde_json", - "tracing", - "tungstenite", - "warnings", -] - -[[package]] -name = "dioxus-devtools-types" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f62434973c0c9c5a3bc42e9cd5e7070401c2062a437fb5528f318c3e42ebf4ff" -dependencies = [ - "dioxus-core", - "serde", -] - -[[package]] -name = "dioxus-document" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "802a2014d1662b6615eec0a275745822ee4fc66aacd9d0f2fb33d6c8da79b8f2" -dependencies = [ - "dioxus-core", - "dioxus-core-macro", - "dioxus-core-types", - "dioxus-html", - "futures-channel", - "futures-util", - "generational-box", - "lazy-js-bundle", - "serde", - "serde_json", - "tracing", -] - -[[package]] -name = "dioxus-fullstack" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe99b48a1348eec385b5c4bd3e80fd863b0d3b47257d34e2ddc58754dec5d128" -dependencies = [ - "base64", - "bytes", - "ciborium", - "dioxus-devtools", - "dioxus-history", - "dioxus-lib", - "dioxus-web", - "dioxus_server_macro", - "futures-channel", - "futures-util", - "generational-box", - "once_cell", - "serde", - "server_fn", - "tracing", - "web-sys", -] - -[[package]] -name = "dioxus-history" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ae4e22616c698f35b60727313134955d885de2d32e83689258e586ebc9b7909" -dependencies = [ - "dioxus-core", - "tracing", -] - -[[package]] -name = "dioxus-hooks" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "948e2b3f20d9d4b2c300aaa60281b1755f3298684448920b27106da5841896d0" -dependencies = [ - "dioxus-core", - "dioxus-signals", - "futures-channel", - "futures-util", - "generational-box", - "rustversion", - "slab", - "tracing", - "warnings", -] - -[[package]] -name = "dioxus-html" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59c9a40e6fee20ce7990095492dedb6a753eebe05e67d28271a249de74dc796d" -dependencies = [ - "async-trait", - "dioxus-core", - "dioxus-core-macro", - "dioxus-core-types", - "dioxus-hooks", - "dioxus-html-internal-macro", - "enumset", - "euclid", - "futures-channel", - "generational-box", - "keyboard-types", - "lazy-js-bundle", - "rustversion", - "tracing", -] - -[[package]] -name = "dioxus-html-internal-macro" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43ba87b53688a2c9f619ecdf4b3b955bc1f08bd0570a80a0d626c405f6d14a76" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dioxus-interpreter-js" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330707b10ca75cb0eb05f9e5f8d80217cd0d7e62116a8277ae363c1a09b57a22" -dependencies = [ - "js-sys", - "lazy-js-bundle", - "rustc-hash", - "sledgehammer_bindgen", - "sledgehammer_utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "dioxus-lib" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5405b71aa9b8b0c3e0d22728f12f34217ca5277792bd315878cc6ecab7301b72" -dependencies = [ - "dioxus-config-macro", - "dioxus-core", - "dioxus-core-macro", - "dioxus-document", - "dioxus-history", - "dioxus-hooks", - "dioxus-html", - "dioxus-rsx", - "dioxus-signals", - "warnings", -] - -[[package]] -name = "dioxus-logger" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545961e752f6c8bf59c274951b3c8b18a106db6ad2f9e2035b29e1f2a3e899b1" -dependencies = [ - "console_error_panic_hook", - "dioxus-cli-config", - "tracing", - "tracing-subscriber", - "tracing-wasm", -] - -[[package]] -name = "dioxus-rsx" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb588e05800b5a7eb90b2f40fca5bbd7626e823fb5e1ba21e011de649b45aa1" -dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn", -] - -[[package]] -name = "dioxus-signals" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10e032dbb3a2c0386ec8b8ee59bc20b5aeb67038147c855801237b45b13d72ac" -dependencies = [ - "dioxus-core", - "futures-channel", - "futures-util", - "generational-box", - "once_cell", - "parking_lot", - "rustc-hash", - "tracing", - "warnings", -] - -[[package]] -name = "dioxus-web" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7c12475c3d360058b8afe1b68eb6dfc9cbb7dcd760aed37c5f85c561c83ed1" -dependencies = [ - "async-trait", - "ciborium", - "dioxus-cli-config", - "dioxus-core", - "dioxus-core-types", - "dioxus-devtools", - "dioxus-document", - "dioxus-history", - "dioxus-html", - "dioxus-interpreter-js", - "dioxus-signals", - "futures-channel", - "futures-util", - "generational-box", - "js-sys", - "lazy-js-bundle", - "rustc-hash", - "serde", - "serde-wasm-bindgen", - "serde_json", - "tracing", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "dioxus_server_macro" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "371a5b21989a06b53c5092e977b3f75d0e60a65a4c15a2aa1d07014c3b2dda97" -dependencies = [ - "proc-macro2", - "quote", - "server_fn_macro", - "syn", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "ec-web" -version = "0.0.0" -dependencies = [ - "dioxus", - "js-sys", - "serde", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "enumset" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b07a8dfbbbfc0064c0a6bdf9edcf966de6b1c33ce344bdeca3b41615452634" -dependencies = [ - "enumset_derive", -] - -[[package]] -name = "enumset_derive" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43e744e4ea338060faee68ed933e46e722fb7f3617e722a5772d7e856d8b3ce" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "euclid" -version = "0.22.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63" -dependencies = [ - "num-traits", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generational-box" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a673cf4fb0ea6a91aa86c08695756dfe875277a912cdbf33db9a9f62d47ed82b" -dependencies = [ - "parking_lot", - "tracing", -] - -[[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.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "gloo-net" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580" -dependencies = [ - "futures-channel", - "futures-core", - "futures-sink", - "gloo-utils", - "http", - "js-sys", - "pin-project", - "serde", - "serde_json", - "thiserror", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "gloo-utils" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" -dependencies = [ - "js-sys", - "serde", - "serde_json", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "half" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" -dependencies = [ - "cfg-if", - "crunchy", - "zerocopy", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "icu_collections" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" - -[[package]] -name = "icu_properties" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" - -[[package]] -name = "icu_provider" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "itoa" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" - -[[package]] -name = "js-sys" -version = "0.3.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "keyboard-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" -dependencies = [ - "bitflags", -] - -[[package]] -name = "lazy-js-bundle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e49596223b9d9d4947a14a25c142a6e7d8ab3f27eb3ade269d238bb8b5c267e2" - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.180" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" - -[[package]] -name = "litemap" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "longest-increasing-subsequence" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bd0dd2cd90571056fdb71f6275fada10131182f84899f4b2a916e565d81d86" - -[[package]] -name = "manganis" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317af44b15e7605b85f04525449a3bb631753040156c9b318e6cba8a3ea4ef73" -dependencies = [ - "const-serialize", - "manganis-core", - "manganis-macro", -] - -[[package]] -name = "manganis-core" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c38bee65cc725b2bba23b5dbb290f57c8be8fadbe2043fb7e2ce73022ea06519" -dependencies = [ - "const-serialize", - "dioxus-cli-config", - "dioxus-core-types", - "serde", -] - -[[package]] -name = "manganis-macro" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f4f71310913c40174d9f0cfcbcb127dad0329ecdb3945678a120db22d3d065" -dependencies = [ - "dunce", - "manganis-core", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pin-project" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "potential_utf" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" -dependencies = [ - "zerovec", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "quote" -version = "1.0.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[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", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "send_wrapper" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" -dependencies = [ - "futures-core", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde-wasm-bindgen" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_qs" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" -dependencies = [ - "percent-encoding", - "serde", - "thiserror", -] - -[[package]] -name = "server_fn" -version = "0.6.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fae7a3038a32e5a34ba32c6c45eb4852f8affaf8b794ebfcd4b1099e2d62ebe" -dependencies = [ - "bytes", - "const_format", - "dashmap", - "futures", - "gloo-net", - "http", - "js-sys", - "once_cell", - "send_wrapper", - "serde", - "serde_json", - "serde_qs", - "server_fn_macro_default", - "thiserror", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "xxhash-rust", -] - -[[package]] -name = "server_fn_macro" -version = "0.6.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faaaf648c6967aef78177c0610478abb5a3455811f401f3c62d10ae9bd3901a1" -dependencies = [ - "const_format", - "convert_case", - "proc-macro2", - "quote", - "syn", - "xxhash-rust", -] - -[[package]] -name = "server_fn_macro_default" -version = "0.6.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2aa8119b558a17992e0ac1fd07f080099564f24532858811ce04f742542440" -dependencies = [ - "server_fn_macro", - "syn", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "sledgehammer_bindgen" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49e83e178d176459c92bc129cfd0958afac3ced925471b889b3a75546cfc4133" -dependencies = [ - "sledgehammer_bindgen_macro", - "wasm-bindgen", -] - -[[package]] -name = "sledgehammer_bindgen_macro" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb251b407f50028476a600541542b605bb864d35d9ee1de4f6cab45d88475e6d" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "sledgehammer_utils" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "debdd4b83524961983cea3c55383b3910fd2f24fd13a188f5b091d2d504a61ae" -dependencies = [ - "rustc-hash", -] - -[[package]] -name = "slotmap" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" -dependencies = [ - "serde", - "version_check", -] - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "syn" -version = "2.0.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "tinystr" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" -dependencies = [ - "sharded-slab", - "thread_local", - "tracing-core", -] - -[[package]] -name = "tracing-wasm" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" -dependencies = [ - "tracing", - "tracing-subscriber", - "wasm-bindgen", -] - -[[package]] -name = "tungstenite" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand", - "sha1", - "thiserror", - "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.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "warnings" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f68998838dab65727c9b30465595c6f7c953313559371ca8bf31759b3680ad" -dependencies = [ - "pin-project", - "tracing", - "warnings-macro", -] - -[[package]] -name = "warnings-macro" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59195a1db0e95b920366d949ba5e0d3fc0e70b67c09be15ce5abb790106b0571" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasm-bindgen" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" -dependencies = [ - "cfg-if", - "futures-util", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "writeable" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" - -[[package]] -name = "xxhash-rust" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" - -[[package]] -name = "yoke" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerotrie" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zmij" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" diff --git a/apps/web/Cargo.toml b/apps/web/Cargo.toml deleted file mode 100644 index c6d714e..0000000 --- a/apps/web/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "ec-web" -version = "0.0.0" -edition = "2021" - -[dependencies] -dioxus = { version = "0.6", features = ["web"] } -js-sys = "0.3" -serde = { version = "1", features = ["derive"] } -wasm-bindgen = "0.2" -wasm-bindgen-futures = "0.4" -web-sys = { version = "0.3", features = ["Window", "Navigator", "Clipboard"] } - -[workspace] diff --git a/apps/web/README.md b/apps/web/README.md index 7aa20ec..b905ca9 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -1,6 +1,8 @@ # every.channel web site (static) -This is a static web site built in Rust with Dioxus and compiled to WASM. +This is a static web watcher site. + +It embeds the upstream `@kixelated/hang` WebTransport player component (``). ## Dev @@ -15,4 +17,3 @@ nix develop -c bash -lc 'cd apps/web && trunk serve --port 1421 --public-url /' ```bash nix develop -c bash -lc 'cd apps/web && trunk build --release --public-url /' ``` - diff --git a/apps/web/app.js b/apps/web/app.js new file mode 100644 index 0000000..24cf99a --- /dev/null +++ b/apps/web/app.js @@ -0,0 +1,181 @@ +// every.channel web watcher +// +// This uses the upstream hang web component (WebTransport + WebCodecs). +// It is intentionally dependency-light: no framework, no bundler. + +import "https://cdn.jsdelivr.net/npm/@kixelated/hang@0.7.0/watch/element.js"; + +const DEFAULT_RELAY_URL = "https://relay.cloudflare.mediaoverquic.com/"; + +function $(id) { + const el = document.getElementById(id); + if (!el) throw new Error(`missing element: ${id}`); + return el; +} + +function normalizeRelayUrl(s) { + const trimmed = (s || "").trim(); + if (!trimmed) return DEFAULT_RELAY_URL; + // Ensure trailing slash so relative fetches behave consistently. + return trimmed.endsWith("/") ? trimmed : `${trimmed}/`; +} + +function normalizeName(s) { + return (s || "").trim(); +} + +function currentShareLink(relayUrl, name) { + const u = new URL(window.location.href); + u.pathname = "/watch"; + u.searchParams.set("url", relayUrl); + u.searchParams.set("name", name); + // Avoid leaking other params. + for (const k of [...u.searchParams.keys()]) { + if (k !== "url" && k !== "name") u.searchParams.delete(k); + } + return u.toString(); +} + +function setHint(text, kind) { + const el = $("hint"); + el.textContent = text || ""; + el.dataset.kind = kind || ""; +} + +function setShareLink(text) { + const el = $("shareLink"); + el.textContent = text || ""; +} + +function mountPlayer(relayUrl, name) { + const mount = $("playerMount"); + mount.textContent = ""; + + const watch = document.createElement("hang-watch"); + watch.setAttribute("url", relayUrl); + watch.setAttribute("name", name); + watch.setAttribute("controls", ""); + + // A canvas enables video rendering. Without it, only audio is played. + const canvas = document.createElement("canvas"); + canvas.className = "canvas"; + watch.appendChild(canvas); + + mount.appendChild(watch); +} + +async function copyToClipboard(text) { + if (!text) return; + if (navigator.clipboard && navigator.clipboard.writeText) { + await navigator.clipboard.writeText(text); + return; + } + // Fallback: best-effort. + const ta = document.createElement("textarea"); + ta.value = text; + document.body.appendChild(ta); + ta.select(); + document.execCommand("copy"); + document.body.removeChild(ta); +} + +function readParams() { + const u = new URL(window.location.href); + const relay = u.searchParams.get("url"); + const name = u.searchParams.get("name"); + return { + relayUrl: normalizeRelayUrl(relay || DEFAULT_RELAY_URL), + name: normalizeName(name || ""), + }; +} + +function writeParams(relayUrl, name) { + const u = new URL(window.location.href); + u.pathname = "/watch"; + u.searchParams.set("url", relayUrl); + u.searchParams.set("name", name); + window.history.replaceState({}, "", u.toString()); +} + +function hasWebTransport() { + return typeof window.WebTransport !== "undefined"; +} + +function main() { + const relayInput = $("relayUrl"); + const nameInput = $("broadcastName"); + const watchBtn = $("watchBtn"); + const copyBtn = $("copyLinkBtn"); + + const initial = readParams(); + relayInput.value = initial.relayUrl; + nameInput.value = initial.name; + + function updateSharePreview() { + const relayUrl = normalizeRelayUrl(relayInput.value); + const name = normalizeName(nameInput.value); + if (!name) { + setShareLink(""); + return; + } + setShareLink(currentShareLink(relayUrl, name)); + } + + function start() { + const relayUrl = normalizeRelayUrl(relayInput.value); + const name = normalizeName(nameInput.value); + + updateSharePreview(); + + if (!name) { + setHint("Enter a broadcast name to watch.", "warn"); + return; + } + + if (!hasWebTransport()) { + setHint( + "WebTransport is not available in this browser. Try Chrome or Firefox Nightly. Safari support is still incomplete.", + "warn", + ); + return; + } + + writeParams(relayUrl, name); + setHint(`Connecting to relay and subscribing: ${name}`, "ok"); + mountPlayer(relayUrl, name); + } + + relayInput.addEventListener("input", updateSharePreview); + nameInput.addEventListener("input", updateSharePreview); + + watchBtn.addEventListener("click", start); + nameInput.addEventListener("keydown", (e) => { + if (e.key === "Enter") start(); + }); + + copyBtn.addEventListener("click", async () => { + const relayUrl = normalizeRelayUrl(relayInput.value); + const name = normalizeName(nameInput.value); + if (!name) { + setHint("Enter a broadcast name first.", "warn"); + return; + } + const link = currentShareLink(relayUrl, name); + try { + await copyToClipboard(link); + setHint("Link copied.", "ok"); + setShareLink(link); + } catch (e) { + setHint(`Copy failed: ${String(e)}`, "warn"); + setShareLink(link); + } + }); + + updateSharePreview(); + + // Auto-start if a name was provided. + if (initial.name) start(); +} + +main(); + diff --git a/apps/web/index.html b/apps/web/index.html index 9ccd9ae..9a372da 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -9,9 +9,58 @@ content="Watch and share free over-the-air TV. Local first, global when you want." /> - + -
+
+
+
+
every.channel
+
Watch live streams over WebTransport.
+
+
+ WebTransport +
+
+ +
+
Watch
+
+ + + +
+
+ +
+ +
+
+
+
+
+ +
+
+
+ +
+
AGPLv3
+
+ For Safari: WebTransport is behind a Developer/Advanced feature flag and may be incomplete. +
+
+
+ + diff --git a/apps/web/src/main.rs b/apps/web/src/main.rs deleted file mode 100644 index cab97d4..0000000 --- a/apps/web/src/main.rs +++ /dev/null @@ -1,125 +0,0 @@ -use dioxus::prelude::*; -use wasm_bindgen_futures::JsFuture; - -fn main() { - dioxus::launch(App); -} - -#[component] -fn App() -> Element { - let mut link = use_signal(|| "".to_string()); - let mut status = use_signal(|| "".to_string()); - - rsx! { - div { class: "page", - header { class: "top", - div { class: "brand", - div { class: "brand-title", "every.channel" } - div { class: "brand-subtitle", - "Watch and share free over-the-air TV. Local first, global when you want." - } - } - nav { class: "nav", - a { href: "#watch", "Watch" } - a { href: "#directory", "Directory" } - a { href: "#join", "Join" } - a { href: "#about", "Info" } - } - } - - div { class: "grid", - section { class: "card section", id: "watch", - div { class: "card-title", "Watch" } - div { class: "h1", "Watch a link" } - div { class: "p", - "Got a link from a friend? Paste it here to copy, then open the desktop app." - } - div { class: "row", - input { - class: "input", - placeholder: "every.channel://watch?...", - value: "{link.read()}", - oninput: move |evt| link.set(evt.value()), - } - button { - class: "btn primary", - onclick: move |_| { - let value = link.read().trim().to_string(); - if value.is_empty() { - status.set("Paste a link first".to_string()); - return; - } - let mut status = status.clone(); - spawn(async move { - match copy_to_clipboard(value).await { - Ok(_) => status.set("Copied! Open the app and paste under Watch a Link.".to_string()), - Err(err) => status.set(format!("Copy failed: {err}")), - } - }); - }, - "Copy" - } - } - if !status.read().is_empty() { - div { class: "kicker", - span { class: "dot" } - span { "{status.read()}" } - } - } - } - - section { class: "card section", id: "directory", - div { class: "card-title", "Directory" } - div { class: "h1", "Find channels from people you trust" } - div { class: "p", - "The directory is opt-in. You choose what to share and who to connect with." - } - div { class: "kicker", - span { class: "dot" } - span { "Enable Nearby or Public reach in the app to find others." } - } - } - - section { class: "card section", id: "join", - div { class: "card-title", "Join" } - div { class: "h1", "Run your own" } - div { class: "p", - "Anyone can watch, share, and relay. Works with HDHomeRun, Linux TV tuners, and live streams." - } - div { class: "kicker", - span { class: "dot" } - span { "Desktop app and CLI available now." } - } - } - - section { class: "card section", id: "about", - div { class: "card-title", "About" } - div { class: "h1", "A small promise" } - div { class: "p", - "TV signals are just waves in the air. This project makes it easier to pick them up and share them with others." - } - div { class: "kicker", - span { class: "dot" } - span { "Open source. No central server." } - } - } - } - - footer { class: "footer", - span { "AGPLv3" } - span { "every.channel" } - a { href: "https://every.channel", "every.channel" } - } - } - } -} - -async fn copy_to_clipboard(text: String) -> Result<(), String> { - let window = web_sys::window().ok_or_else(|| "window unavailable".to_string())?; - let clipboard = window.navigator().clipboard(); - let promise = clipboard.write_text(&text); - JsFuture::from(promise) - .await - .map_err(|err| format!("clipboard write rejected: {err:?}"))?; - Ok(()) -} diff --git a/apps/web/style.css b/apps/web/style.css index 919fae1..8af748b 100644 --- a/apps/web/style.css +++ b/apps/web/style.css @@ -1,285 +1,272 @@ -@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500&display=swap"); - :root { - color-scheme: light; - --bg: #f8f5f0; - --bg-muted: #f2ede5; - --card: rgba(255, 255, 255, 0.82); - --ink: #1a1814; - --ink-muted: #5c574d; - --border: rgba(26, 24, 20, 0.10); - --shadow: 0 16px 40px rgba(26, 24, 20, 0.10); - --accent: #18a89b; - --accent-ink: #0c6f68; - --warm: #d4915a; - --warm-muted: rgba(232, 160, 92, 0.12); - font-family: "Space Grotesk", "IBM Plex Sans", "Segoe UI", system-ui, sans-serif; - font-feature-settings: "ss01" 1; + --bg0: #0b0f14; + --bg1: #0f1720; + --panel: rgba(255, 255, 255, 0.06); + --panel2: rgba(255, 255, 255, 0.08); + --text: rgba(255, 255, 255, 0.92); + --muted: rgba(255, 255, 255, 0.65); + --faint: rgba(255, 255, 255, 0.45); + --line: rgba(255, 255, 255, 0.12); + --accent: #ffb86c; + --accent2: #6ee7ff; + --ok: #7cf7a2; + --warn: #ffd36e; + --shadow: rgba(0, 0, 0, 0.55); } * { box-sizing: border-box; - margin: 0; - padding: 0; +} + +html, +body { + height: 100%; } body { - min-height: 100vh; - color: var(--ink); - background: linear-gradient(168deg, #fdfaf5 0%, #f8f5f0 35%, #f4f1ec 70%, #f0ece6 100%); + margin: 0; + color: var(--text); + background: + radial-gradient(1200px 700px at 15% 10%, rgba(255, 184, 108, 0.16), transparent 55%), + radial-gradient(900px 600px at 85% 20%, rgba(110, 231, 255, 0.12), transparent 60%), + radial-gradient(900px 900px at 50% 120%, rgba(255, 255, 255, 0.08), transparent 55%), + linear-gradient(180deg, var(--bg0), var(--bg1)); + font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", + "Segoe UI Emoji"; } -/* Subtle "old TV" nod: soft phosphor glow and faint scanlines */ -body::before { - content: ""; - position: fixed; - inset: 0; - background-image: - radial-gradient(ellipse 80% 60% at 15% 10%, rgba(232, 160, 92, 0.08), transparent 50%), - radial-gradient(ellipse 60% 50% at 85% 15%, rgba(24, 168, 155, 0.06), transparent 45%), - radial-gradient(ellipse 70% 40% at 50% 90%, rgba(232, 160, 92, 0.05), transparent 50%), - repeating-linear-gradient( - 0deg, - transparent 0px, - transparent 2px, - rgba(26, 24, 20, 0.012) 2px, - rgba(26, 24, 20, 0.012) 4px - ); - pointer-events: none; - z-index: -1; -} - -#main { - min-height: 100vh; -} - -.page { - max-width: 1120px; +.shell { + max-width: 1100px; margin: 0 auto; - padding: 24px clamp(16px, 4vw, 40px) 40px; - display: flex; - flex-direction: column; - gap: 20px; - animation: fadeIn 0.5s ease-out; + padding: 28px 18px 22px; + display: grid; + gap: 16px; } .top { display: flex; - align-items: flex-start; + align-items: center; justify-content: space-between; gap: 16px; - flex-wrap: wrap; -} - -.brand { - display: flex; - flex-direction: column; - gap: 4px; + padding: 14px 16px; + border: 1px solid var(--line); + border-radius: 16px; + background: linear-gradient(180deg, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.04)); + box-shadow: 0 18px 38px var(--shadow); } .brand-title { - font-size: clamp(24px, 2.6vw, 30px); - font-weight: 600; - letter-spacing: -0.025em; - color: var(--ink); + font-weight: 800; + letter-spacing: -0.02em; + font-size: 22px; + line-height: 1.05; } .brand-subtitle { - font-size: 12px; - color: var(--ink-muted); - max-width: 44ch; - line-height: 1.4; + margin-top: 4px; + color: var(--muted); + font-size: 13px; } -.nav { - display: flex; - gap: 5px; - flex-wrap: wrap; - justify-content: flex-end; -} - -.nav a { - text-decoration: none; - color: var(--ink-muted); +.badge { + padding: 8px 10px; font-size: 12px; - font-weight: 500; - padding: 6px 10px; + color: rgba(0, 0, 0, 0.78); + background: linear-gradient(135deg, var(--accent), var(--accent2)); border-radius: 999px; - border: 1px solid var(--border); - background: rgba(255, 255, 255, 0.65); - transition: transform 120ms ease, box-shadow 120ms ease, background 120ms ease, color 120ms ease; + font-weight: 700; } -.nav a:hover { - transform: translateY(-1px); - background: rgba(255, 255, 255, 0.9); - color: var(--ink); - box-shadow: 0 8px 20px rgba(26, 24, 20, 0.06); -} - -.grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 14px; -} - -@media (max-width: 720px) { - .grid { - grid-template-columns: 1fr; - } -} - -.card { +.panel { + padding: 14px 16px; + border: 1px solid var(--line); border-radius: 16px; - border: 1px solid var(--border); - background: var(--card); - box-shadow: var(--shadow); - padding: 18px; - backdrop-filter: blur(12px); + background: var(--panel); + box-shadow: 0 18px 38px var(--shadow); } -.card-title { - font-size: 10px; - text-transform: uppercase; - letter-spacing: 0.12em; - color: var(--ink-muted); - margin-bottom: 8px; -} - -.h1 { - font-size: clamp(16px, 1.8vw, 19px); - font-weight: 600; - letter-spacing: -0.015em; - margin-bottom: 8px; - line-height: 1.3; -} - -.p { +.panel-title { font-size: 12px; - line-height: 1.5; - color: var(--ink-muted); + letter-spacing: 0.22em; + text-transform: uppercase; + color: var(--muted); + margin-bottom: 12px; } .row { display: grid; - grid-template-columns: 1fr auto; - gap: 6px; - margin-top: 10px; + grid-template-columns: 1.15fr 1fr auto; + gap: 10px; + align-items: end; +} + +.field .label { + font-size: 12px; + color: var(--muted); + margin-bottom: 6px; } .input { - border: 1px solid var(--border); - border-radius: 10px; - padding: 8px 10px; - font-size: 11px; - background: rgba(242, 237, 229, 0.6); - color: var(--ink); + width: 100%; + padding: 11px 12px; + border: 1px solid var(--line); + border-radius: 12px; + background: rgba(0, 0, 0, 0.28); + color: var(--text); + outline: none; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06); } .input:focus { - outline: none; - border-color: rgba(24, 168, 155, 0.4); - box-shadow: 0 0 0 3px rgba(24, 168, 155, 0.08); + border-color: rgba(255, 184, 108, 0.55); + box-shadow: 0 0 0 3px rgba(255, 184, 108, 0.16); } .btn { - border: 1px solid var(--border); - border-radius: 10px; - padding: 8px 12px; - font-size: 11px; - font-weight: 500; + padding: 11px 14px; + border-radius: 12px; + border: 1px solid rgba(255, 184, 108, 0.35); + background: linear-gradient(180deg, rgba(255, 184, 108, 0.22), rgba(255, 184, 108, 0.12)); + color: var(--text); + font-weight: 700; cursor: pointer; - background: rgba(255, 255, 255, 0.85); - color: var(--ink); - transition: transform 120ms ease, box-shadow 120ms ease, background 120ms ease; + transition: transform 80ms ease, background 120ms ease, border-color 120ms ease; } .btn:hover { transform: translateY(-1px); - box-shadow: 0 6px 16px rgba(26, 24, 20, 0.06); + border-color: rgba(255, 184, 108, 0.55); + background: linear-gradient(180deg, rgba(255, 184, 108, 0.28), rgba(255, 184, 108, 0.14)); } -.btn.primary { - background: var(--accent-ink); - color: #fff; - border-color: rgba(12, 111, 104, 0.3); +.btn:active { + transform: translateY(0); } -.btn.primary:hover { - box-shadow: 0 6px 16px rgba(12, 111, 104, 0.2); +.btn.secondary { + border-color: rgba(255, 255, 255, 0.18); + background: rgba(255, 255, 255, 0.06); } -.kicker { +.hint { + margin-top: 10px; + font-size: 13px; + color: var(--muted); + min-height: 18px; +} + +.hint[data-kind="ok"] { + color: var(--ok); +} + +.hint[data-kind="warn"] { + color: var(--warn); +} + +.share { margin-top: 8px; - display: inline-flex; + grid-template-columns: auto 1fr; align-items: center; - gap: 6px; - padding: 6px 10px; - border-radius: 999px; - border: 1px solid var(--border); - background: var(--warm-muted); - color: var(--ink-muted); - font-size: 11px; } -.dot { - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--warm); - box-shadow: 0 0 0 3px rgba(232, 160, 92, 0.2); +.shareLink { + color: var(--faint); + font-size: 12px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + padding: 0 4px; } -.section { - scroll-margin-top: 12px; +.player { + padding: 0; } -code { - font-family: "IBM Plex Mono", ui-monospace, monospace; - font-size: 0.9em; - background: rgba(26, 24, 20, 0.05); - padding: 2px 5px; - border-radius: 4px; +.tv { + position: relative; + border-radius: 24px; + padding: 18px; + border: 1px solid rgba(255, 255, 255, 0.14); + background: + radial-gradient(900px 500px at 10% 10%, rgba(255, 184, 108, 0.10), transparent 55%), + radial-gradient(700px 500px at 90% 10%, rgba(110, 231, 255, 0.08), transparent 58%), + linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(0, 0, 0, 0.10)); + box-shadow: 0 28px 70px rgba(0, 0, 0, 0.62); } -.footer { - margin-top: 6px; - padding-top: 12px; - border-top: 1px solid var(--border); - font-size: 10px; - color: var(--ink-muted); +.tv-glow { + position: absolute; + inset: -40px -20px -20px -20px; + background: + radial-gradient(350px 220px at 18% 22%, rgba(255, 184, 108, 0.18), transparent 70%), + radial-gradient(320px 220px at 82% 26%, rgba(110, 231, 255, 0.14), transparent 72%); + filter: blur(18px); + opacity: 0.95; + pointer-events: none; +} + +.tv-frame { + position: relative; + border-radius: 18px; + padding: 12px; + background: + linear-gradient(180deg, rgba(0, 0, 0, 0.45), rgba(0, 0, 0, 0.22)); + border: 1px solid rgba(255, 255, 255, 0.12); +} + +.mount { + aspect-ratio: 16 / 9; + width: 100%; + border-radius: 12px; + overflow: hidden; + background: #000; + border: 1px solid rgba(255, 255, 255, 0.14); +} + +.canvas { + width: 100%; + height: 100%; + display: block; +} + +.tv-scanlines { + position: absolute; + inset: 12px; + border-radius: 12px; + background: repeating-linear-gradient( + to bottom, + rgba(255, 255, 255, 0.03), + rgba(255, 255, 255, 0.03) 1px, + rgba(0, 0, 0, 0.00) 3px, + rgba(0, 0, 0, 0.00) 6px + ); + mix-blend-mode: overlay; + pointer-events: none; + opacity: 0.25; +} + +.foot { display: flex; - flex-wrap: wrap; - gap: 10px; justify-content: space-between; + gap: 12px; + padding: 10px 2px 0; + color: var(--faint); + font-size: 12px; } -.footer a { - color: var(--ink-muted); - text-decoration: none; -} - -.footer a:hover { - color: var(--ink); -} - -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(4px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@media (max-width: 720px) { - .top { - flex-direction: column; +@media (max-width: 900px) { + .row { + grid-template-columns: 1fr; align-items: stretch; } - .nav { - justify-content: flex-start; + .share { + grid-template-columns: 1fr; + } + .badge { + display: none; + } + .foot { + flex-direction: column; } } + diff --git a/crates/ec-chopper/Cargo.toml b/crates/ec-chopper/Cargo.toml index fba74b7..5fb7d89 100644 --- a/crates/ec-chopper/Cargo.toml +++ b/crates/ec-chopper/Cargo.toml @@ -4,8 +4,12 @@ version = "0.0.0" edition.workspace = true license.workspace = true +[features] +default = [] +ffmpeg-ffi = ["dep:ac-ffmpeg"] + [dependencies] -ac-ffmpeg = "0.19.0" +ac-ffmpeg = { version = "0.19.0", optional = true } anyhow.workspace = true blake3.workspace = true ec-core = { path = "../ec-core" } diff --git a/crates/ec-chopper/src/lib.rs b/crates/ec-chopper/src/lib.rs index 790bfbe..b760dd7 100644 --- a/crates/ec-chopper/src/lib.rs +++ b/crates/ec-chopper/src/lib.rs @@ -1,5 +1,6 @@ //! Deterministic chunking and transcode scaffolding. +#[cfg(feature = "ffmpeg-ffi")] use ac_ffmpeg::format::{ demuxer::Demuxer, io::IO, @@ -15,6 +16,7 @@ use std::fs; use std::io::{Read, Write}; use std::path::{Path, PathBuf}; use std::process::{Child, Command, Stdio}; +#[cfg(feature = "ffmpeg-ffi")] use std::time::Duration; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -202,6 +204,14 @@ pub fn collect_segments(output_dir: &Path) -> Result { }) } +#[cfg(not(feature = "ffmpeg-ffi"))] +pub fn probe_read_stream(_stream: T) -> Result> { + Err(anyhow!( + "probe_read_stream requires feature `ec-chopper/ffmpeg-ffi` (ac-ffmpeg) and FFmpeg headers" + )) +} + +#[cfg(feature = "ffmpeg-ffi")] pub fn probe_read_stream(stream: T) -> Result> { let io = IO::from_read_stream(stream); let demuxer = Demuxer::builder() @@ -401,6 +411,19 @@ pub fn hash_file_blake3(path: &Path) -> Result { Ok(hasher.finalize().to_hex().to_string()) } +#[cfg(not(feature = "ffmpeg-ffi"))] +pub fn chunk_stream_ffmpeg( + _stream: T, + _output_dir: &Path, + _chunk_duration_ms: u64, + _max_chunks: Option, +) -> Result { + Err(anyhow!( + "chunk_stream_ffmpeg requires feature `ec-chopper/ffmpeg-ffi` (ac-ffmpeg)" + )) +} + +#[cfg(feature = "ffmpeg-ffi")] pub fn chunk_stream_ffmpeg( stream: T, output_dir: &Path, @@ -610,6 +633,20 @@ pub fn manifest_for_ts_chunks( Ok((body, hashed)) } +#[cfg(not(feature = "ffmpeg-ffi"))] +pub fn chunk_stream_ffmpeg_live Result<()>>( + _stream: T, + _output_dir: &Path, + _chunk_duration_ms: u64, + _max_chunks: Option, + _on_chunk: F, +) -> Result<()> { + Err(anyhow!( + "chunk_stream_ffmpeg_live requires feature `ec-chopper/ffmpeg-ffi` (ac-ffmpeg)" + )) +} + +#[cfg(feature = "ffmpeg-ffi")] pub fn chunk_stream_ffmpeg_live Result<()>>( stream: T, output_dir: &Path, diff --git a/crates/ec-node/Cargo.toml b/crates/ec-node/Cargo.toml index 5f89eae..711b78a 100644 --- a/crates/ec-node/Cargo.toml +++ b/crates/ec-node/Cargo.toml @@ -24,11 +24,15 @@ reqwest = { version = "0.12", default-features = false, features = ["json", "rus urlencoding = "2" serde.workspace = true serde_json.workspace = true -tokio = { version = "1", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1", features = ["full"] } tokio-tungstenite = { version = "0.24", default-features = false, features = ["connect", "rustls-tls-webpki-roots"] } futures-util = "0.3" tracing.workspace = true tracing-subscriber.workspace = true +hang = "0.14.0" +moq-mux = "0.2.1" +moq-native = { version = "0.13.1", default-features = true } +url = "2" [dev-dependencies] headless_chrome = "1" diff --git a/crates/ec-node/src/main.rs b/crates/ec-node/src/main.rs index a5d2342..835dd62 100644 --- a/crates/ec-node/src/main.rs +++ b/crates/ec-node/src/main.rs @@ -39,6 +39,11 @@ use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use tokio_tungstenite::tungstenite::Message as WsMessage; use futures_util::{SinkExt, StreamExt}; +use hang as hang_moq; +use moq_mux as moq_mux_lib; +use moq_native as moq_native_lib; +use tokio::process::Command as TokioCommand; +use url::Url; const DIRECT_WIRE_TAG_FRAME: u8 = 0x00; const DIRECT_WIRE_TAG_STREAM: u8 = 0x01; @@ -75,6 +80,8 @@ enum Commands { WsPublish(WsPublishArgs), /// Subscribe to the global one-to-many relay (`/api/stream/ws`) and capture CMAF fragments + an mp4 proof. WsSubscribe(WsSubscribeArgs), + /// Publish a CMAF (fMP4) stream to a MoQ relay over WebTransport (Cloudflare preview by default). + WtPublish(WtPublishArgs), } #[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] @@ -394,6 +401,33 @@ struct WsSubscribeArgs { mp4: Option, } +#[derive(Parser, Debug)] +struct WtPublishArgs { + /// Relay URL (WebTransport) to connect to. + /// Default points at Cloudflare's MoQ technical preview relay. + #[arg(long, default_value = "https://relay.cloudflare.mediaoverquic.com/")] + url: String, + /// Broadcast name to publish. + /// + /// This should be stable so you can share: + /// `https://every.channel/watch?url=...&name=`. + #[arg(long)] + name: String, + /// Input URL or file for ffmpeg (e.g. HDHomeRun `http://hdhomerun.local/auto/v4.1`). + #[arg(long)] + input: String, + /// If set, transcode to H.264/AAC before fragmenting to fMP4. + #[arg(long, default_value_t = true, action = clap::ArgAction::Set)] + transcode: bool, + /// Transmit fMP4 fragments directly (passthrough mode). + /// When false, the importer may reframe into CMAF fragments. + #[arg(long, default_value_t = true, action = clap::ArgAction::Set)] + passthrough: bool, + /// Danger: disable TLS verification for the relay. + #[arg(long, default_value_t = false)] + tls_disable_verify: bool, +} + #[derive(Subcommand, Debug)] enum IngestSource { /// Ingest from an HDHomeRun device. @@ -460,6 +494,7 @@ fn main() -> Result<()> { Commands::DirectSubscribe(args) => run_async(direct_subscribe(args))?, Commands::WsPublish(args) => run_async(ws_publish(args))?, Commands::WsSubscribe(args) => run_async(ws_subscribe(args))?, + Commands::WtPublish(args) => run_async(wt_publish(args))?, } Ok(()) @@ -4191,3 +4226,147 @@ fn wait_for_stable_file(path: &Path, timeout: Duration) -> Result<()> { timeout )) } + +async fn wt_publish(args: WtPublishArgs) -> Result<()> { + let relay_url = Url::parse(&args.url) + .with_context(|| format!("invalid relay url: {}", args.url))?; + + // Build the WebTransport client. + let mut client_cfg = moq_native_lib::ClientConfig::default(); + if args.tls_disable_verify { + client_cfg.tls.disable_verify = Some(true); + } + let client = client_cfg.init().context("failed to init moq-native client")?; + + // Build a hang broadcast producer + catalog + fMP4 importer. + // This matches the moq-dev/moq-cli publishing strategy, but with our own ffmpeg input. + let origin = hang_moq::moq_lite::Origin::produce(); + + let mut broadcast = hang_moq::moq_lite::BroadcastProducer::default(); + let catalog = hang_moq::Catalog::default().produce(); + broadcast.insert_track(catalog.track.clone()); + + let mut importer = moq_mux_lib::import::Fmp4::new( + broadcast.clone(), + catalog.clone(), + moq_mux_lib::import::Fmp4Config { + passthrough: args.passthrough, + }, + ); + + origin.publish_broadcast(&args.name, broadcast.consume()); + + tracing::info!(url=%relay_url, name=%args.name, "connecting to relay"); + let session = client + .with_publish(origin.consume()) + .connect(relay_url) + .await + .context("failed to connect to relay")?; + + // Spawn ffmpeg to generate fMP4 suitable for hang/moq-mux. + // We keep this conservative and deterministic-ish by default: + // - single threaded x264 + // - fixed GOP settings to reduce drift + let mut cmd = TokioCommand::new("ffmpeg"); + cmd.arg("-hide_banner") + .arg("-loglevel") + .arg("error") + .arg("-nostats") + .arg("-fflags") + .arg("+nobuffer") + .arg("-flags") + .arg("low_delay") + .arg("-i") + .arg(&args.input); + + if args.transcode { + cmd.args([ + "-c:v", + "libx264", + "-preset", + "veryfast", + "-tune", + "zerolatency", + "-pix_fmt", + "yuv420p", + "-profile:v", + "main", + "-g", + "48", + "-keyint_min", + "48", + "-sc_threshold", + "0", + "-threads", + "1", + "-c:a", + "aac", + "-b:a", + "128k", + "-ac", + "2", + "-ar", + "48000", + ]); + } else { + cmd.args(["-c", "copy"]); + } + + cmd.args([ + "-f", + "mp4", + "-movflags", + "empty_moov+frag_every_frame+separate_moof+omit_tfhd_offset", + "pipe:1", + ]); + + cmd.stdout(Stdio::piped()); + cmd.stderr(Stdio::inherit()); + + tracing::info!(input=%args.input, "spawning ffmpeg"); + let mut child = cmd.spawn().context("failed to spawn ffmpeg")?; + let mut stdout = child + .stdout + .take() + .ok_or_else(|| anyhow!("ffmpeg stdout unavailable"))?; + + tracing::info!("publishing fMP4 -> hang -> relay"); + let decode_task = tokio::spawn(async move { importer.decode_from(&mut stdout).await }); + + tokio::select! { + res = session.closed() => { + if let Err(err) = res { + // moq-lite errors are not always `std::error::Error`; keep this explicit. + return Err(anyhow!("relay session closed: {err:?}")); + } + let _ = child.kill().await; + Ok(()) + } + res = decode_task => { + match res { + Ok(Ok(())) => { + let status = child.wait().await.context("failed to wait for ffmpeg")?; + if !status.success() { + return Err(anyhow!("ffmpeg exited with {status}")); + } + Ok(()) + } + Ok(Err(err)) => { + let _ = child.kill().await; + Err(err).context("fmp4 import failed") + } + Err(err) => { + let _ = child.kill().await; + Err(anyhow!("import task join failed: {err}")) + } + } + } + _ = tokio::signal::ctrl_c() => { + tracing::info!("ctrl-c; shutting down"); + session.close(hang_moq::moq_lite::Error::Cancel); + let _ = child.kill().await; + tokio::time::sleep(Duration::from_millis(100)).await; + Ok(()) + } + } +} diff --git a/deploy/cloudflare-worker/wrangler.toml b/deploy/cloudflare-worker/wrangler.toml index 804e185..1e37734 100644 --- a/deploy/cloudflare-worker/wrangler.toml +++ b/deploy/cloudflare-worker/wrangler.toml @@ -14,7 +14,7 @@ routes = [ # Static assets built by Trunk (apps/tauri/ui -> apps/tauri/dist) [assets] -directory = "../../apps/tauri/dist" +directory = "../../apps/web/dist" [[durable_objects.bindings]] name = "EC_API" diff --git a/evolution/proposals/ECP-0063-cloudflare-moq-webtransport.md b/evolution/proposals/ECP-0063-cloudflare-moq-webtransport.md new file mode 100644 index 0000000..7470d7c --- /dev/null +++ b/evolution/proposals/ECP-0063-cloudflare-moq-webtransport.md @@ -0,0 +1,65 @@ +# ECP-0063: Cloudflare MoQ Relay + WebTransport-Only Web Watch + +Status: Draft + +## Decision + +Adopt Cloudflare's MoQ relay preview as the default "global" distribution layer and make the web watcher path WebTransport-only. + +Concrete changes: + +1. `ec-node` gains a WebTransport MoQ publisher path that can publish a live CMAF (fMP4) stream to a relay URL (default: `https://relay.cloudflare.mediaoverquic.com/`). +2. `every.channel` (the deployed static site) becomes a real web watcher by embedding a WebTransport-capable MoQ player component (``). +3. The existing WebRTC/WS bootstrap directory/relay remains temporarily for compatibility, but is treated as deprecated once MoQ/WebTransport is stable. + +## Motivation + +The project goal is one-to-many live streaming at global scale without rebuilding a bespoke stateful SFU stack. + +MoQ over WebTransport moves the core system boundary to: +- publisher produces timed objects (CMAF fragments), +- relays cache/fanout, +- subscribers fetch via a single WebTransport session. + +This aligns the "global watcher" story with an infrastructure-native model rather than point-to-point rendezvous. + +## Scope + +In scope for this ECP: +- "Watch from the web" using WebTransport to a MoQ relay. +- "Publish from a node" to the relay, using ffmpeg to create fMP4 fragments. +- A shareable link format for `every.channel` to open a specific relay + broadcast name. + +Out of scope (explicitly deferred): +- Global discovery/index of broadcasts. +- Manifest signing / Merkle availability / anti-junk (separate ECPs already exist). +- Safari support guarantees (implement anyway; provide guidance/flag notes). +- Multi-variant ladders and ABR (follow-on ECP once single-variant publish/watch works end-to-end). + +## Technical Notes + +### Relay default + +Default relay endpoint is: +- `https://relay.cloudflare.mediaoverquic.com/` + +Nodes may override via CLI flags for self-hosted relays or future Cloudflare relay endpoints. + +### Web player + +Use the `@kixelated/hang` web component: +- `` + +This is WebTransport + WebCodecs based, and is expected to interoperate with Cloudflare's current relay preview. + +### Share link + +Web share link: +- `https://every.channel/watch?url=&name=` + +## Rollout / Reversibility + +- Keep existing `/api/*` bootstrap endpoints during migration. +- Make the web site prefer MoQ/WebTransport; keep legacy paths hidden behind "advanced" until removed. +- Reversible by switching the deployed assets back to the previous UI build, and/or pointing users at the legacy paths. + diff --git a/flake.nix b/flake.nix index 3dda17c..30b9108 100644 --- a/flake.nix +++ b/flake.nix @@ -66,6 +66,8 @@ ] ++ lib.optionals stdenv.isLinux linuxTauriDeps; shellHook = '' + # Ensure nix-provided tools win over any host-local installs (e.g. a wrong-arch trunk). + export PATH=${pkgs.trunk}/bin:$PATH export FFMPEG_INCLUDE_DIR=${pkgs.ffmpeg.dev}/include export FFMPEG_LIB_DIR=${pkgs.ffmpeg.lib}/lib export LLVM_COV=${pkgs.llvmPackages.llvm}/bin/llvm-cov