diff --git a/.gitignore b/.gitignore index c9a7c427..6103291d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ node_modules .env.test.local .env.production.local +.idea/ + # Testing coverage diff --git a/.patches/connectcheckskip.patch b/.patches/connectcheckskip.patch deleted file mode 100644 index 01b25ad2..00000000 --- a/.patches/connectcheckskip.patch +++ /dev/null @@ -1,28 +0,0 @@ -diff --git a/src/utils.c b/src/utils.c -index e00f3c5..4f1f0bf 100644 ---- a/src/utils.c -+++ b/src/utils.c -@@ -71,7 +71,7 @@ void for_each_active_monitor_output_x11(Display *display, active_monitor_callbac - char display_name[256]; - for(int i = 0; i < screen_res->noutput; ++i) { - XRROutputInfo *out_info = XRRGetOutputInfo(display, screen_res, screen_res->outputs[i]); -- if(out_info && out_info->crtc && out_info->connection == RR_Connected) { -+ if(out_info && out_info->crtc) { - XRRCrtcInfo *crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc); - if(crt_info && crt_info->mode) { - const XRRModeInfo *mode_info = get_mode_info(screen_res, crt_info->mode); -@@ -218,10 +218,10 @@ static void for_each_active_monitor_output_drm(const gsr_egl *egl, active_monito - if(connector_type) - ++connector_type->count; - -- if(connector->connection != DRM_MODE_CONNECTED) { -- drmModeFreeConnector(connector); -- continue; -- } -+ //if(connector->connection != DRM_MODE_CONNECTED) { -+ // drmModeFreeConnector(connector); -+ // continue; -+ //} - - if(connector_type) - ++connector_type->count_active; diff --git a/.patches/devicearg.patch b/.patches/devicearg.patch deleted file mode 100644 index 12fd803c..00000000 --- a/.patches/devicearg.patch +++ /dev/null @@ -1,23 +0,0 @@ -diff --git a/src/main.cpp b/src/main.cpp -index 112a6ac..57bd9bf 100644 ---- a/src/main.cpp -+++ b/src/main.cpp -@@ -1906,6 +1906,7 @@ int main(int argc, char **argv) { - { "-gopm", Arg { {}, true, false } }, // deprecated, used keyint instead - { "-keyint", Arg { {}, true, false } }, - { "-encoder", Arg { {}, true, false } }, -+ { "-device", Arg { {}, true, false } }, - }; - - for(int i = 1; i < argc; i += 2) { -@@ -2226,6 +2227,10 @@ int main(int argc, char **argv) { - overclock = false; - } - -+ const char *dri_device = args["-device"].value(); -+ if (dri_device) -+ egl.dri_card_path = dri_device; -+ - egl.card_path[0] = '\0'; - if(wayland || egl.gpu_info.vendor != GSR_GPU_VENDOR_NVIDIA) { - // TODO: Allow specifying another card, and in other places diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..b28e9ec9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3750 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys 0.59.0", +] + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "asn1-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +dependencies = [ + "asn1-rs-derive 0.4.0", + "asn1-rs-impl 0.1.0", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.66", +] + +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive 0.5.1", + "asn1-rs-impl 0.2.0", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.66", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure 0.12.6", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure 0.13.1", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atomic_refcell" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "aws-lc-rs" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7c2840b66236045acd2607d5866e274380afd87ef99d6226e961e2cb47df45" +dependencies = [ + "aws-lc-sys", + "mirai-annotations", + "paste", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad3a619a9de81e1d7de1f1186dcba4506ed661a0e483d84410fdef0ee87b2f96" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libc", + "paste", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.87", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[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 = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b9470d453346108f93a59222a9a1a5724db32d0a4727b7ab7ace4b4d822dc9" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "ccm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae3c82e4355234767756212c570e29833699ab63e6ffd161887314cc5b43847" +dependencies = [ + "aead", + "cipher", + "ctr", + "subtle", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-expr" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c360837f8f19e2e4468275138f1c0dec1647d1e17bb7c0215fe3cd7530e93c25" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.5.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" + +[[package]] +name = "cmake" +version = "0.1.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +dependencies = [ + "asn1-rs 0.5.2", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs 0.6.2", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "dev" +version = "0.1.0" +dependencies = [ + "reqwest", + "serde", + "serde_json", + "tokio", + "webrtc", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +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 = "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 2.0.87", +] + +[[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 = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "gio-sys" +version = "0.21.0" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=main#c3fffcacfb5c420c12475bbb853318dc54ce531e" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "windows-sys 0.59.0", +] + +[[package]] +name = "glib" +version = "0.21.0" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=main#c3fffcacfb5c420c12475bbb853318dc54ce531e" +dependencies = [ + "bitflags 2.6.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "smallvec", +] + +[[package]] +name = "glib-macros" +version = "0.21.0" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=main#c3fffcacfb5c420c12475bbb853318dc54ce531e" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "glib-sys" +version = "0.21.0" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=main#c3fffcacfb5c420c12475bbb853318dc54ce531e" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "gobject-sys" +version = "0.21.0" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=main#c3fffcacfb5c420c12475bbb853318dc54ce531e" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "gstreamer" +version = "0.24.0" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs#36eca7cca935f930a3dfa006688a199084f4197a" +dependencies = [ + "cfg-if", + "futures-channel", + "futures-core", + "futures-util", + "glib", + "gstreamer-sys", + "itertools 0.13.0", + "kstring", + "libc", + "muldiv", + "num-integer", + "num-rational", + "option-operations", + "paste", + "pin-project-lite", + "smallvec", + "thiserror 2.0.3", +] + +[[package]] +name = "gstreamer-app" +version = "0.24.0" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs#36eca7cca935f930a3dfa006688a199084f4197a" +dependencies = [ + "futures-core", + "futures-sink", + "glib", + "gstreamer", + "gstreamer-app-sys", + "gstreamer-base", + "libc", +] + +[[package]] +name = "gstreamer-app-sys" +version = "0.24.0" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs#36eca7cca935f930a3dfa006688a199084f4197a" +dependencies = [ + "glib-sys", + "gstreamer-base-sys", + "gstreamer-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer-base" +version = "0.24.0" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs#36eca7cca935f930a3dfa006688a199084f4197a" +dependencies = [ + "atomic_refcell", + "cfg-if", + "glib", + "gstreamer", + "gstreamer-base-sys", + "libc", +] + +[[package]] +name = "gstreamer-base-sys" +version = "0.24.0" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs#36eca7cca935f930a3dfa006688a199084f4197a" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer-sys" +version = "0.24.0" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs#36eca7cca935f930a3dfa006688a199084f4197a" +dependencies = [ + "cfg-if", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "h2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "hyper" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "interceptor" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4705c00485029e738bea8c9505b5ddb1486a8f3627a953e1e77e6abdf5eef90c" +dependencies = [ + "async-trait", + "bytes", + "log", + "portable-atomic", + "rand", + "rtcp", + "rtp", + "thiserror 1.0.66", + "tokio", + "waitgroup", + "webrtc-srtp", + "webrtc-util", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kstring" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.161" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "mirai-annotations" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" + +[[package]] +name = "muldiv" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0" + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nestri-server" +version = "0.1.0" +dependencies = [ + "chrono", + "clap", + "flate2", + "futures-util", + "gstreamer", + "gstreamer-app", + "log", + "num-derive", + "num-traits", + "rand", + "regex", + "rustls", + "serde", + "serde_json", + "tokio", + "tokio-tungstenite", + "tokio-util", + "webrtc", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs 0.6.2", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-operations" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c26d27bb1aeab65138e4bf7666045169d1717febcc9ff870166be8348b223d0" +dependencies = [ + "paste", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn 2.0.87", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +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 = "rcgen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54077e1872c46788540de1ea3d7f4ccb1983d12f9aa909b234468676c1a36779" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "x509-parser", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rtcp" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc9f775ff89c5fe7f0cc0abafb7c57688ae25ce688f1a52dd88e277616c76ab2" +dependencies = [ + "bytes", + "thiserror 1.0.66", + "webrtc-util", +] + +[[package]] +name = "rtp" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6870f09b5db96f8b9e7290324673259fd15519ebb7d55acf8e7eb044a9ead6af" +dependencies = [ + "bytes", + "portable-atomic", + "rand", + "serde", + "thiserror 1.0.66", + "webrtc-util", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "0.38.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f1a745511c54ba6d4465e8d5dfbd81b45791756de28d4981af70d6dca128f1e" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[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.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdp" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13254db766b17451aced321e7397ebf0a446ef0c8d2942b6e67a95815421093f" +dependencies = [ + "rand", + "substring", + "thiserror 1.0.66", + "url", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[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 = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "stun" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28fad383a1cc63ae141e84e48eaef44a1063e9d9e55bcb8f51a99b886486e01b" +dependencies = [ + "base64 0.21.7", + "crc", + "lazy_static", + "md-5", + "rand", + "ring", + "subtle", + "thiserror 1.0.66", + "tokio", + "url", + "webrtc-util", +] + +[[package]] +name = "substring" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86" +dependencies = [ + "autocfg", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "system-deps" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d171f59dbaa811dbbb1aee1e73db92ec2b122911a48e1390dfe327a821ddede" +dependencies = [ + "thiserror-impl 1.0.66", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl 2.0.3", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.41.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "native-tls", + "rand", + "sha1", + "thiserror 1.0.66", + "utf-8", +] + +[[package]] +name = "turn" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b000cebd930420ac1ed842c8128e3b3412512dfd5b82657eab035a3f5126acc" +dependencies = [ + "async-trait", + "base64 0.21.7", + "futures", + "log", + "md-5", + "portable-atomic", + "rand", + "ring", + "stun", + "thiserror 1.0.66", + "tokio", + "tokio-util", + "webrtc-util", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "waitgroup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1f50000a783467e6c0200f9d10642f4bc424e39efc1b770203e88b488f79292" +dependencies = [ + "atomic-waker", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.87", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" + +[[package]] +name = "web-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webrtc" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b3a840e31c969844714f93b5a87e73ee49f3bc2a4094ab9132c69497eb31db" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "cfg-if", + "hex", + "interceptor", + "lazy_static", + "log", + "portable-atomic", + "rand", + "rcgen", + "regex", + "ring", + "rtcp", + "rtp", + "rustls", + "sdp", + "serde", + "serde_json", + "sha2", + "smol_str", + "stun", + "thiserror 1.0.66", + "time", + "tokio", + "turn", + "url", + "waitgroup", + "webrtc-data", + "webrtc-dtls", + "webrtc-ice", + "webrtc-mdns", + "webrtc-media", + "webrtc-sctp", + "webrtc-srtp", + "webrtc-util", +] + +[[package]] +name = "webrtc-data" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8b7c550f8d35867b72d511640adf5159729b9692899826fe00ba7fa74f0bf70" +dependencies = [ + "bytes", + "log", + "portable-atomic", + "thiserror 1.0.66", + "tokio", + "webrtc-sctp", + "webrtc-util", +] + +[[package]] +name = "webrtc-dtls" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86e5eedbb0375aa04da93fc3a189b49ed3ed9ee844b6997d5aade14fc3e2c26e" +dependencies = [ + "aes", + "aes-gcm", + "async-trait", + "bincode", + "byteorder", + "cbc", + "ccm", + "der-parser 8.2.0", + "hkdf", + "hmac", + "log", + "p256", + "p384", + "portable-atomic", + "rand", + "rand_core", + "rcgen", + "ring", + "rustls", + "sec1", + "serde", + "sha1", + "sha2", + "subtle", + "thiserror 1.0.66", + "tokio", + "webrtc-util", + "x25519-dalek", + "x509-parser", +] + +[[package]] +name = "webrtc-ice" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4f0ca6d4df8d1bdd34eece61b51b62540840b7a000397bcfb53a7bfcf347c8" +dependencies = [ + "arc-swap", + "async-trait", + "crc", + "log", + "portable-atomic", + "rand", + "serde", + "serde_json", + "stun", + "thiserror 1.0.66", + "tokio", + "turn", + "url", + "uuid", + "waitgroup", + "webrtc-mdns", + "webrtc-util", +] + +[[package]] +name = "webrtc-mdns" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0804694f3b2acfdff48f6df217979b13cb0a00377c63b5effd111daaee7e8c4" +dependencies = [ + "log", + "socket2", + "thiserror 1.0.66", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-media" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c15b20e98167b22949abc1c20eca7c6d814307d187068fe7a48f0b87a4f6d46" +dependencies = [ + "byteorder", + "bytes", + "rand", + "rtp", + "thiserror 1.0.66", +] + +[[package]] +name = "webrtc-sctp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d850daa68639b9d7bb16400676e97525d1e52b15b4928240ae2ba0e849817a5" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "crc", + "log", + "portable-atomic", + "rand", + "thiserror 1.0.66", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-srtp" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbec5da43a62c228d321d93fb12cc9b4d9c03c9b736b0c215be89d8bd0774cfe" +dependencies = [ + "aead", + "aes", + "aes-gcm", + "byteorder", + "bytes", + "ctr", + "hmac", + "log", + "rtcp", + "rtp", + "sha1", + "subtle", + "thiserror 1.0.66", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-util" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc8d9bc631768958ed97b8d68b5d301e63054ae90b09083d43e2fefb939fd77e" +dependencies = [ + "async-trait", + "bitflags 1.3.2", + "bytes", + "ipnet", + "lazy_static", + "libc", + "log", + "nix", + "portable-atomic", + "rand", + "thiserror 1.0.66", + "tokio", + "winapi", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs 0.6.2", + "data-encoding", + "der-parser 9.0.0", + "lazy_static", + "nom", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror 1.0.66", + "time", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure 0.13.1", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure 0.13.1", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..c99fb87b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[workspace] +resolver = "2" +members = [ + "packages/server", + "packages/relay/dev" +] + +[workspace.package] +version = "0.1.0-alpha.1" +repository = "https://github.com/nestriness/nestri" +edition = "2021" +rust-version = "1.80" + +[workspace.dependencies] +gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", version = "0.24.0" } +gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", version = "0.24.0" } diff --git a/Containerfile.master b/Containerfile.master new file mode 100644 index 00000000..14777233 --- /dev/null +++ b/Containerfile.master @@ -0,0 +1,206 @@ +#! Runs the docker server that handles everything else +#****************************************************************************** +# base +#****************************************************************************** +FROM archlinux:base-20241027.0.273886 AS base +# How to run - docker run -it --rm --device /dev/dri nestri /bin/bash - DO NOT forget the ports +# TODO: Migrate XDG_RUNTIME_DIR to /run/user/1000 +# TODO: Add nestri-server to pulseaudio.conf +# TODO: Add our own entrypoint, with our very own zombie ripper 🧟🏾‍♀️ +# FIXME: Add user root to `pulse-access` group as well :D +# TODO: Test the whole damn thing + +# Update the pacman repo +RUN \ + pacman -Syu --noconfirm + +#****************************************************************************** +# builder +#****************************************************************************** + +FROM base AS builder + +RUN \ + pacman -Su --noconfirm \ + base-devel \ + git \ + sudo \ + vim + +WORKDIR /scratch + +# Allow nobody user to invoke pacman to install packages (as part of makepkg) and modify the system. +# This should never exist in a running image, just used by *-build Docker stages. +RUN \ + echo "nobody ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers; + +ENV ARTIFACTS=/artifacts \ + CARGO_TARGET_DIR=/build + +RUN \ + mkdir -p /artifacts \ + && mkdir -p /build + +RUN \ + chgrp nobody /scratch /artifacts /build \ + && chmod g+ws /scratch /artifacts /build + +#****************************************************************************** +# rust-builder +#****************************************************************************** + +FROM builder AS rust-builder + +RUN \ + pacman -Su --noconfirm \ + rustup + +RUN \ + rustup default stable + +#****************************************************************************** +# nestri-server-builder +#****************************************************************************** +# Builds nestri server binary +FROM rust-builder AS nestri-server-builder + +RUN \ + pacman -Su --noconfirm \ + wayland \ + vpl-gpu-rt \ + gstreamer \ + gst-plugin-va \ + gst-plugins-base \ + gst-plugins-good \ + mesa-utils \ + weston \ + xorg-xwayland + + +#****************************************************************************** +# nestri-server-build +#****************************************************************************** + +FROM nestri-server-builder AS nestri-server-build + +#Allow makepkg to be run as nobody. +RUN chgrp -R nobody /scratch && chmod -R g+ws /scratch + +# USER nobody + +# Perform the server build. +WORKDIR /scratch/server + +RUN \ + git clone https://github.com/nestriness/nestri + +WORKDIR /scratch/server/nestri + +RUN \ + git checkout feat/stream \ + && cargo build -j$(nproc) --release + +# COPY packages/server/build/ /scratch/server/ + +# RUN makepkg && cp *.zst "$ARTIFACTS" +#****************************************************************************** +# runtime_base_pkgs +#****************************************************************************** + +FROM base AS runtime_base_pkgs + +COPY --from=nestri-server-build /build/release/nestri-server /usr/bin/ + +#****************************************************************************** +# runtime_base +#****************************************************************************** + +FROM runtime_base_pkgs AS runtime_base + +RUN \ + pacman -Su --noconfirm \ + weston \ + sudo \ + xorg-xwayland \ + gstreamer \ + gst-plugins-base \ + gst-plugins-good \ + gst-plugin-qsv \ + gst-plugin-va \ + gst-plugin-fmp4 \ + mesa \ + # Grab GPU encoding packages + # Intel (modern VPL + VA-API) + vpl-gpu-rt \ + intel-media-driver \ + # AMD/ATI (VA-API) + libva-mesa-driver \ + # NVIDIA (proprietary) + nvidia-utils \ + # Audio + pulseaudio \ + # Supervisor + supervisor + +RUN \ + # Set up our non-root user $(nestri) + groupadd -g 1000 nestri \ + && useradd -ms /bin/bash nestri -u 1000 -g 1000 \ + && passwd -d nestri \ + # Setup Pulseaudio + && useradd -d /var/run/pulse -s /usr/bin/nologin -G audio pulse \ + && groupadd pulse-access \ + && usermod -aG audio,input,render,video,pulse-access nestri \ + && echo "nestri ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers \ + && echo "Users created" \ + # Create an empty machine-id file + && touch /etc/machine-id + +ENV \ + XDG_RUNTIME_DIR=/tmp + +#****************************************************************************** +# runtime +#****************************************************************************** + +FROM runtime_base AS runtime +# Setup supervisor # +RUN <<-EOF + echo -e " + [supervisord] + user=root + nodaemon=true + loglevel=info + logfile=/tmp/supervisord.log + pidfile=/tmp/supervisord.pid + + [program:dbus] + user=root + command=dbus-daemon --system --nofork + logfile=/tmp/dbus.log + pidfile=/tmp/dbus.pid + stopsignal=INT + autostart=true + autorestart=true + priority=1 + + [program:pulseaudio] + user=root + command=pulseaudio --daemonize=no --system --disallow-module-loading --disallow-exit --exit-idle-time=-1 + logfile=/tmp/pulseaudio.log + pidfile=/tmp/pulseaudio.pid + stopsignal=INT + autostart=true + autorestart=true + priority=10 + " | tee /etc/supervisord.conf +EOF + +RUN \ + chown -R nestri:nestri /tmp /etc/supervisord.conf + +ENV USER=nestri +USER 1000 + +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"] +# Debug - pactl list \ No newline at end of file diff --git a/Containerfile.relay b/Containerfile.relay new file mode 100644 index 00000000..ee36a64e --- /dev/null +++ b/Containerfile.relay @@ -0,0 +1,20 @@ +FROM docker.io/golang:1.23-alpine AS go-build +WORKDIR /builder +COPY packages/relay/ /builder/ +RUN go build + +FROM docker.io/golang:1.23-alpine +COPY --from=go-build /builder/relay /relay/relay +WORKDIR /relay + +# ENV flags +ENV VERBOSE=false +ENV ENDPOINT_PORT=8088 +ENV WEBRTC_UDP_START=10000 +ENV WEBRTC_UDP_END=20000 +ENV STUN_SERVER="stun.l.google.com:19302" + +EXPOSE $ENDPOINT_PORT +EXPOSE $WEBRTC_UDP_START-$WEBRTC_UDP_END/udp + +ENTRYPOINT ["/relay/relay"] \ No newline at end of file diff --git a/Containerfile.runner b/Containerfile.runner new file mode 100644 index 00000000..db0bfb0b --- /dev/null +++ b/Containerfile.runner @@ -0,0 +1,219 @@ +# Container build arguments # +ARG BASE_IMAGE=docker.io/cachyos/cachyos-v3:latest + +#****************************************************************************** +# gst-builder +#****************************************************************************** +FROM ${BASE_IMAGE} AS gst-builder +WORKDIR /builder/ + +# Grab build and rust packages # +RUN pacman -Syu --noconfirm meson pkgconf cmake git gcc make rustup \ + gstreamer gst-plugins-base gst-plugins-good + +# Setup stable rust toolchain # +RUN rustup default stable +# Clone nestri source # +RUN git clone -b feat/stream https://github.com/nestriness/nestri.git + +# Build nestri # +RUN cd nestri/packages/server/ && \ + cargo build --release + +#****************************************************************************** +# gstwayland-builder +#****************************************************************************** +FROM ${BASE_IMAGE} AS gstwayland-builder +WORKDIR /builder/ + +# Grab build and rust packages # +RUN pacman -Syu --noconfirm meson pkgconf cmake git gcc make rustup \ + libxkbcommon wayland gstreamer gst-plugins-base gst-plugins-good libinput + +# Setup stable rust toolchain # +RUN rustup default stable +# Build required cargo-c package # +RUN cargo install cargo-c +# Clone gst plugin source # +RUN git clone https://github.com/games-on-whales/gst-wayland-display.git + +# Build gst plugin # +RUN mkdir plugin && \ + cd gst-wayland-display && \ + cargo cinstall --prefix=/builder/plugin/ + + +#****************************************************************************** +# runtime +#****************************************************************************** +FROM ${BASE_IMAGE} AS runtime + +## Nestri Env Variables ## +ENV NESTRI_PARAMS="" +ENV RESOLUTION="1280x720" + +## Install Graphics, Media, and Audio packages ## +RUN pacman -Syu --noconfirm --needed \ + # Graphics packages + sudo mesa mesa-utils xorg-xwayland labwc wlr-randr mangohud \ + # Vulkan drivers + vulkan-intel vulkan-radeon nvidia-utils \ + # Media encoding packages + vpl-gpu-rt intel-media-driver libva-utils \ + # GStreamer plugins + gstreamer gst-plugins-base gst-plugins-good \ + gst-plugin-va gst-plugins-bad gst-plugin-fmp4 \ + gst-plugin-qsv gst-plugin-pipewire gst-plugin-rswebrtc \ + gst-plugins-ugly gst-plugin-rsrtp \ + # Audio packages + pipewire pipewire-pulse pipewire-alsa wireplumber \ + # Other requirements + supervisor \ + # Custom + umu-launcher && \ + # Clean up pacman cache and unnecessary files + pacman -Scc --noconfirm && \ + rm -rf /var/cache/pacman/pkg/* /tmp/* /var/tmp/* && \ + # Optionally clean documentation, man pages, and locales + find /usr/share/locale -mindepth 1 -maxdepth 1 ! -name "en*" -exec rm -rf {} + && \ + rm -rf /usr/share/doc /usr/share/man /usr/share/info + + +## User ## +# Create and setup user # +ENV USER="nestri" \ + UID=99 \ + GID=100 \ + USER_PASSWORD="nestri1234" \ + USER_HOME="/home/nestri" + +RUN mkdir -p ${USER_HOME} && \ + useradd -d ${USER_HOME} -u ${UID} -s /bin/bash ${USER} && \ + chown -R ${USER} ${USER_HOME} && \ + echo "${USER} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers && \ + echo "${USER}:${USER_PASSWORD}" | chpasswd + +# Run directory # +RUN mkdir -p /run/user/${UID} && \ + chown ${USER}:${USER} /run/user/${UID} + +# Home config directory # +RUN mkdir -p ${USER_HOME}/.config && \ + chown ${USER}:${USER} ${USER_HOME}/.config + +# Groups # +RUN usermod -aG input root && usermod -aG input ${USER} && \ + usermod -aG video root && usermod -aG video ${USER} && \ + usermod -aG render root && usermod -aG render ${USER} + +## Copy files from builders ## +# this is done here at end to not trigger full rebuild on changes to builder +# nestri +COPY --from=gst-builder /builder/nestri/target/release/nestri-server /usr/bin/nestri-server +# gstwayland +COPY --from=gstwayland-builder /builder/plugin/include/libgstwaylanddisplay /usr/include/ +COPY --from=gstwayland-builder /builder/plugin/lib/*libgstwayland* /usr/lib/ +COPY --from=gstwayland-builder /builder/plugin/lib/gstreamer-1.0/libgstwayland* /usr/lib/gstreamer-1.0/ +COPY --from=gstwayland-builder /builder/plugin/lib/pkgconfig/gstwayland* /usr/lib/pkgconfig/ +COPY --from=gstwayland-builder /builder/plugin/lib/pkgconfig/libgstwayland* /usr/lib/pkgconfig/ + +## Copy scripts ## +COPY packages/scripts/ /etc/nestri/ + +## Startup ## +# Setup supervisor # +RUN <<-EOF +echo -e " +[supervisord] +user=root +nodaemon=true +loglevel=info +logfile=/tmp/supervisord.log + +[program:dbus] +user=root +command=dbus-daemon --system --nofork --nopidfile +logfile=/tmp/dbus.log +autoerestart=true +autostart=true +startretries=3 +priority=1 + +[program:seatd] +user=root +command=seatd +logfile=/tmp/seatd.log +autoerestart=true +autostart=true +startretries=3 +priority=2 + +[program:pipewire] +user=nestri +command=dbus-launch pipewire +environment=XDG_RUNTIME_DIR=\"/run/user/${UID}\",HOME=\"${USER_HOME}\" +logfile=/tmp/pipewire.log +autoerestart=true +autostart=true +startretries=3 +priority=10 + +[program:pipewire-pulse] +user=nestri +command=dbus-launch pipewire-pulse +environment=XDG_RUNTIME_DIR=\"/run/user/${UID}\",HOME=\"${USER_HOME}\" +logfile=/tmp/pipewire-pulse.log +autoerestart=true +autostart=true +startretries=3 +priority=20 + +[program:wireplumber] +user=nestri +command=dbus-launch wireplumber +environment=XDG_RUNTIME_DIR=\"/run/user/${UID}\",HOME=\"${USER_HOME}\" +logfile=/tmp/wireplumber.log +autoerestart=true +autostart=true +startretries=3 +priority=30 + +[program:nestri-server] +user=nestri +command=sh -c 'nestri-server \$NESTRI_PARAMS' +environment=XDG_RUNTIME_DIR=\"/run/user/${UID}\",HOME=\"${USER_HOME}\" +logfile=/tmp/nestri-server.log +autoerestart=true +autostart=true +startretries=3 +priority=50 + +[program:labwc] +user=nestri +command=sh -c 'sleep 4 && rm -rf /tmp/.X11-unix && mkdir -p /tmp/.X11-unix && chown nestri:nestri /tmp/.X11-unix && labwc' +environment=XDG_RUNTIME_DIR=\"/run/user/${UID}\",HOME=\"${USER_HOME}\",WAYLAND_DISPLAY=\"wayland-1\",WLR_BACKENDS=\"wayland\",WLR_RENDERER=\"vulkan\" +logfile=/tmp/labwc.log +autoerestart=true +autostart=true +startretries=5 +priority=60 + +[program:wlrrandr] +user=nestri +command=sh -c 'sleep 6 && wlr-randr --output WL-1 --custom-mode \$RESOLUTION && read -n 1' +environment=XDG_RUNTIME_DIR=\"/run/user/${UID}\",HOME=\"${USER_HOME}\",WAYLAND_DISPLAY=\"wayland-0\" +logfile=/tmp/wlrrandr.log +autoerestart=true +autostart=true +startretries=10 +priority=70 +" | tee /etc/supervisord.conf +EOF + +# Wireplumber disable suspend # +# Remove suspend node +RUN sed -z -i 's/{[[:space:]]*name = node\/suspend-node\.lua,[[:space:]]*type = script\/lua[[:space:]]*provides = hooks\.node\.suspend[[:space:]]*}[[:space:]]*//g' /usr/share/wireplumber/wireplumber.conf +# Remove "hooks.node.suspend" want +RUN sed -i '/wants = \[/{s/hooks\.node\.suspend\s*//; s/,\s*\]/]/}' /usr/share/wireplumber/wireplumber.conf + +ENTRYPOINT ["supervisord", "-c", "/etc/supervisord.conf"] diff --git a/apps/docs/README.md b/apps/docs/README.md deleted file mode 100644 index 95051065..00000000 --- a/apps/docs/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Docus - -## Setup - -Install dependencies: - -```bash -npm install -``` - -## Development - -```bash -npm run dev -``` - -## Edge Side Rendering - -Can be deployed to Vercel Functions, Netlify Functions, AWS, and most Node-compatible environments. - -Look at all the available presets [here](https://v3.nuxtjs.org/guide/deploy/presets). - -```bash -npm build -``` - -## Static Generation - -Use the `generate` command to build your application. - -The HTML files will be generated in the .output/public directory and ready to be deployed to any static compatible hosting. - -```bash -npm run generate -``` - -## Preview build - -You might want to preview the result of your build locally, to do so, run the following command: - -```bash -yarn preview -``` - ---- - -For a detailed explanation of how things work, check out [Docus](https://docus.dev). diff --git a/apps/docs/package.json b/apps/docs/package.json index 261a83cc..27f773e5 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "nuxi dev", + "nestri.dev": "nuxi dev", "build": "nuxi build", "generate": "nuxi generate", "preview": "nuxi preview", diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json index ad2f8d27..59e048dc 100644 --- a/apps/docs/tsconfig.json +++ b/apps/docs/tsconfig.json @@ -1,4 +1,4 @@ { - "extends": "./.nuxt/tsconfig.json", + // "extends": "./.nuxt/tsconfig.json", "ignoreConfigErrors": true } diff --git a/apps/www/adapters/deno/vite.config.ts b/apps/www/adapters/deno/vite.config.ts new file mode 100644 index 00000000..bc45d6aa --- /dev/null +++ b/apps/www/adapters/deno/vite.config.ts @@ -0,0 +1,23 @@ +import { denoServerAdapter } from "@builder.io/qwik-city/adapters/deno-server/vite"; +import { extendConfig } from "@builder.io/qwik-city/vite"; +import baseConfig from "../../vite.config"; + +export default extendConfig(baseConfig, () => { + return { + build: { + ssr: true, + rollupOptions: { + input: ["src/entry.deno.ts", "@qwik-city-plan"], + }, + minify: false, + }, + plugins: [ + denoServerAdapter({ + ssg: { + include: ["/*"], + origin: "https://yoursite.dev", + }, + }), + ], + }; +}); diff --git a/apps/www/package.json b/apps/www/package.json index 7e56b847..b4d9828e 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -16,6 +16,7 @@ "build.client": "vite build", "build.preview": "vite build --ssr src/entry.preview.tsx", "build.server": "vite build -c adapters/cloudflare-pages/vite.config.ts", + "deno:build.server": "vite build -c adapters/deno/vite.config.ts", "build.types": "tsc --incremental --noEmit", "deploy": "wrangler pages deploy ./dist", "dev": "vite --mode ssr", @@ -25,6 +26,7 @@ "lint": "eslint \"src/**/*.ts*\"", "preview": "qwik build preview && vite preview --open", "serve": "wrangler pages dev ./dist --compatibility-flags=nodejs_als", + "deno:serve": "deno run --allow-net --allow-read --allow-env server/entry.deno.js", "start": "vite --open --mode ssr", "qwik": "qwik" }, @@ -34,7 +36,8 @@ "@builder.io/qwik-react": "0.5.0", "@modular-forms/qwik": "^0.27.0", "@nestri/eslint-config": "*", - "@nestri/moq": "*", + "@nestri/input": "*", + "@nestri/libmoq": "*", "@nestri/typescript-config": "*", "@nestri/ui": "*", "@types/eslint": "8.56.10", @@ -54,5 +57,9 @@ "vite": "5.3.5", "vite-tsconfig-paths": "^4.2.1", "wrangler": "^3.0.0" + }, + "dependencies": { + "@types/pako": "^2.0.3", + "pako": "^2.1.0" } } diff --git a/apps/www/src/entry.deno.ts b/apps/www/src/entry.deno.ts new file mode 100644 index 00000000..96a4d5d2 --- /dev/null +++ b/apps/www/src/entry.deno.ts @@ -0,0 +1,45 @@ +/* + * WHAT IS THIS FILE? + * + * It's the entry point for the Deno HTTP server when building for production. + * + * Learn more about the Deno integration here: + * - https://qwik.dev/docs/deployments/deno/ + * - https://docs.deno.com/runtime/tutorials/http_server + * + */ +import { createQwikCity } from "@builder.io/qwik-city/middleware/deno"; +import qwikCityPlan from "@qwik-city-plan"; +import { manifest } from "@qwik-client-manifest"; +import render from "./entry.ssr"; + +// Create the Qwik City Deno middleware +const { router, notFound, staticFile } = createQwikCity({ + render, + qwikCityPlan, + manifest, +}); + +// Allow for dynamic port +const port = Number(Deno.env.get("PORT") ?? 3009); + +/* eslint-disable */ +console.log(`Server starter: http://localhost:${port}/app/`); + +Deno.serve({ port }, async (request: Request, info: any) => { + const staticResponse = await staticFile(request); + if (staticResponse) { + return staticResponse; + } + + // Server-side render this request with Qwik City + const qwikCityResponse = await router(request, info); + if (qwikCityResponse) { + return qwikCityResponse; + } + + // Path not found + return notFound(request); +}); + +declare const Deno: any; diff --git a/apps/www/src/root.tsx b/apps/www/src/root.tsx index b9f71f3a..b7851bae 100644 --- a/apps/www/src/root.tsx +++ b/apps/www/src/root.tsx @@ -34,7 +34,7 @@ export default component$(() => { {/* {!isDev && } */} diff --git a/apps/www/src/routes/(moq)/moq/checker/index.tsx b/apps/www/src/routes/(moq)/moq/checker/index.tsx index aee03dc5..612f2666 100644 --- a/apps/www/src/routes/(moq)/moq/checker/index.tsx +++ b/apps/www/src/routes/(moq)/moq/checker/index.tsx @@ -1,5 +1,6 @@ import * as v from "valibot" -import { Broadcast } from "./tester"; +//FIXME: Make sure this works +// import { Broadcast } from "./tester"; import { cn } from "@nestri/ui/design"; import { routeLoader$ } from "@builder.io/qwik-city"; import { component$, $, useSignal } from "@builder.io/qwik"; @@ -36,11 +37,11 @@ export default component$(() => { const handleSubmit = $>(async (values) => { const randomNamespace = generateRandomWord(6); - const sub = await Broadcast.init({ url: values.url, fingerprint: undefined, namespace: randomNamespace }) + // const sub = await Broadcast.init({ url: values.url, fingerprint: undefined, namespace: randomNamespace }) - setTimeout(() => { - broadcasterOk.value = sub.isSubscribed() - }, 1000); + // setTimeout(() => { + // broadcasterOk.value = sub.isSubscribed() + // }, 1000); }); return ( diff --git a/apps/www/src/routes/(moq)/moq/checker/tester.ts b/apps/www/src/routes/(moq)/moq/checker/tester.ts index 11446ea8..09cca337 100644 --- a/apps/www/src/routes/(moq)/moq/checker/tester.ts +++ b/apps/www/src/routes/(moq)/moq/checker/tester.ts @@ -1,208 +1,208 @@ -import type { Connection, SubscribeRecv } from "@nestri/moq/transport" -import { asError } from "@nestri/moq/common/error" -import { Client } from "@nestri/moq/transport/client" -import * as Catalog from "@nestri/moq/media/catalog" -import { type GroupWriter } from "@nestri/moq/transport/objects" - -export interface BroadcastConfig { - namespace: string - connection: Connection -} -export interface BroadcasterConfig { - url: string - namespace: string - fingerprint?: string // URL to fetch TLS certificate fingerprint -} - -export interface BroadcastConfigTrack { - input: string - bitrate: number -} +// import type { Connection, SubscribeRecv } from "@nestri/libmoq/transport" +// import { asError } from "@nestri/moq/common/error" +// import { Client } from "@nestri/moq/transport/client" +// import * as Catalog from "@nestri/moq/media/catalog" +// import { type GroupWriter } from "@nestri/moq/transport/objects" + +// export interface BroadcastConfig { +// namespace: string +// connection: Connection +// } +// export interface BroadcasterConfig { +// url: string +// namespace: string +// fingerprint?: string // URL to fetch TLS certificate fingerprint +// } + +// export interface BroadcastConfigTrack { +// input: string +// bitrate: number +// } -export class Broadcast { - stream: GroupWriter | null - subscriber: SubscribeRecv | null - subscribed: boolean; +// export class Broadcast { +// stream: GroupWriter | null +// subscriber: SubscribeRecv | null +// subscribed: boolean; - readonly config: BroadcastConfig - readonly catalog: Catalog.Root - readonly connection: Connection - readonly namespace: string +// readonly config: BroadcastConfig +// readonly catalog: Catalog.Root +// readonly connection: Connection +// readonly namespace: string - #running: Promise +// #running: Promise - constructor(config: BroadcastConfig) { - this.subscribed = false - this.namespace = config.namespace - this.connection = config.connection - this.config = config - //Arbitrary values, just to keep TypeScript happy :) - this.catalog = { - version: 1, - streamingFormat: 1, - streamingFormatVersion: "0.2", - supportsDeltaUpdates: false, - commonTrackFields: { - packaging: "loc", - renderGroup: 1, - }, - tracks: [{ - name: "tester", - namespace: "tester", - selectionParams: {} - }], - } - this.stream = null - this.subscriber = null +// constructor(config: BroadcastConfig) { +// this.subscribed = false +// this.namespace = config.namespace +// this.connection = config.connection +// this.config = config +// //Arbitrary values, just to keep TypeScript happy :) +// this.catalog = { +// version: 1, +// streamingFormat: 1, +// streamingFormatVersion: "0.2", +// supportsDeltaUpdates: false, +// commonTrackFields: { +// packaging: "loc", +// renderGroup: 1, +// }, +// tracks: [{ +// name: "tester", +// namespace: "tester", +// selectionParams: {} +// }], +// } +// this.stream = null +// this.subscriber = null - this.#running = this.#run() - } +// this.#running = this.#run() +// } - static async init(config: BroadcasterConfig): Promise { - const client = new Client({ url: config.url, fingerprint: config.fingerprint, role: "publisher" }) - const connection = await client.connect(); +// static async init(config: BroadcasterConfig): Promise { +// const client = new Client({ url: config.url, fingerprint: config.fingerprint, role: "publisher" }) +// const connection = await client.connect(); - return new Broadcast({ connection, namespace: config.namespace }) - } +// return new Broadcast({ connection, namespace: config.namespace }) +// } - async #run() { - try { - await this.connection.announce(this.namespace) - this.subscribed = true - } catch (error) { +// async #run() { +// try { +// await this.connection.announce(this.namespace) +// this.subscribed = true +// } catch (error) { - this.subscribed = false - } +// this.subscribed = false +// } - for (; ;) { - const subscriber = await this.connection.subscribed() +// for (; ;) { +// const subscriber = await this.connection.subscribed() - if (!subscriber) { - this.subscribed = false +// if (!subscriber) { +// this.subscribed = false - break - } +// break +// } - await subscriber.ack() +// await subscriber.ack() - this.subscriber = subscriber +// this.subscriber = subscriber - this.subscribed = true +// this.subscribed = true - const bytes = Catalog.encode(this.catalog); +// const bytes = Catalog.encode(this.catalog); - const stream = await subscriber.group({ group: 0 }); +// const stream = await subscriber.group({ group: 0 }); - await stream.write({ object: 0, payload: bytes }) +// await stream.write({ object: 0, payload: bytes }) - this.stream = stream - } - } +// this.stream = stream +// } +// } - isSubscribed(): boolean { - return this.subscribed; - } +// isSubscribed(): boolean { +// return this.subscribed; +// } - // async #serveSubscribe(subscriber: SubscribeRecv) { - // try { +// // async #serveSubscribe(subscriber: SubscribeRecv) { +// // try { - // // Send a SUBSCRIBE_OK - // await subscriber.ack() +// // // Send a SUBSCRIBE_OK +// // await subscriber.ack() - // console.log("catalog track name:", subscriber.track) +// // console.log("catalog track name:", subscriber.track) - // const stream = await subscriber.group({ group: 0 }); +// // const stream = await subscriber.group({ group: 0 }); - // // const bytes = this.catalog.encode("Hello World") +// // // const bytes = this.catalog.encode("Hello World") - // await stream.write({ object: 0, payload: bytes }) +// // await stream.write({ object: 0, payload: bytes }) - // } catch (e) { - // const err = asError(e) - // await subscriber.close(1n, `failed to process publish: ${err.message}`) - // } finally { - // // TODO we can't close subscribers because there's no support for clean termination - // // await subscriber.close() - // } - // } +// // } catch (e) { +// // const err = asError(e) +// // await subscriber.close(1n, `failed to process publish: ${err.message}`) +// // } finally { +// // // TODO we can't close subscribers because there's no support for clean termination +// // // await subscriber.close() +// // } +// // } - // async mouseUpdatePosition({ x, y }: { x: number, y: number }, stream: GroupWriter) { +// // async mouseUpdatePosition({ x, y }: { x: number, y: number }, stream: GroupWriter) { - // const mouse_move = { - // input_type: "mouse_move", - // delta_y: y, - // delta_x: x, - // } +// // const mouse_move = { +// // input_type: "mouse_move", +// // delta_y: y, +// // delta_x: x, +// // } - // const bytes = Catalog.encode(this.catalog) +// // const bytes = Catalog.encode(this.catalog) - // await stream.write({ object: 0, payload: bytes }); - // } +// // await stream.write({ object: 0, payload: bytes }); +// // } - // async mouseUpdateButtons(e: MouseEvent, stream: GroupWriter) { - // const data: { input_type?: "mouse_key_down" | "mouse_key_up"; button: number; } = { button: e.button }; +// // async mouseUpdateButtons(e: MouseEvent, stream: GroupWriter) { +// // const data: { input_type?: "mouse_key_down" | "mouse_key_up"; button: number; } = { button: e.button }; - // if (e.type === "mousedown") { - // data["input_type"] = "mouse_key_down" - // } else if (e.type === "mouseup") { - // data["input_type"] = "mouse_key_up" - // } +// // if (e.type === "mousedown") { +// // data["input_type"] = "mouse_key_down" +// // } else if (e.type === "mouseup") { +// // data["input_type"] = "mouse_key_up" +// // } - // const bytes = Catalog.encode(this.catalog) +// // const bytes = Catalog.encode(this.catalog) - // await stream.write({ object: 0, payload: bytes }); - // } +// // await stream.write({ object: 0, payload: bytes }); +// // } - // async mouseUpdateWheel(e: WheelEvent, stream: GroupWriter) { - // const data: { input_type?: "mouse_wheel_up" | "mouse_wheel_down" } = {} +// // async mouseUpdateWheel(e: WheelEvent, stream: GroupWriter) { +// // const data: { input_type?: "mouse_wheel_up" | "mouse_wheel_down" } = {} - // if (e.deltaY < 0.0) { - // data["input_type"] = "mouse_wheel_up" - // } else { - // data["input_type"] = "mouse_wheel_down" - // } +// // if (e.deltaY < 0.0) { +// // data["input_type"] = "mouse_wheel_up" +// // } else { +// // data["input_type"] = "mouse_wheel_down" +// // } - // const bytes = Catalog.encode(this.catalog) +// // const bytes = Catalog.encode(this.catalog) - // await stream.write({ object: 0, payload: bytes }); - // } +// // await stream.write({ object: 0, payload: bytes }); +// // } - // async updateKeyUp(e: KeyboardEvent, stream: GroupWriter) { - // const data = { - // input_type: "key_up", - // key_code: e.keyCode - // } +// // async updateKeyUp(e: KeyboardEvent, stream: GroupWriter) { +// // const data = { +// // input_type: "key_up", +// // key_code: e.keyCode +// // } - // const bytes = Catalog.encode(this.catalog) +// // const bytes = Catalog.encode(this.catalog) - // await stream.write({ object: 0, payload: bytes }); - // } +// // await stream.write({ object: 0, payload: bytes }); +// // } - // async updateKeyDown(e: KeyboardEvent, stream: GroupWriter) { - // const data = { - // input_type: "key_down", - // key_code: e.keyCode - // } +// // async updateKeyDown(e: KeyboardEvent, stream: GroupWriter) { +// // const data = { +// // input_type: "key_down", +// // key_code: e.keyCode +// // } - // const bytes = Catalog.encode(this.catalog) +// // const bytes = Catalog.encode(this.catalog) - // await stream.write({ object: 0, payload: bytes }); - // } +// // await stream.write({ object: 0, payload: bytes }); +// // } - close() { - // TODO implement publish close - } +// close() { +// // TODO implement publish close +// } - // Returns the error message when the connection is closed - async closed(): Promise { - try { - await this.#running - return new Error("closed") // clean termination - } catch (e) { - return asError(e) - } - } -} \ No newline at end of file +// // Returns the error message when the connection is closed +// async closed(): Promise { +// try { +// await this.#running +// return new Error("closed") // clean termination +// } catch (e) { +// return asError(e) +// } +// } +// } \ No newline at end of file diff --git a/apps/www/src/routes/home/index.tsx b/apps/www/src/routes/home/index.tsx index 4e772a01..b1e2ba9e 100644 --- a/apps/www/src/routes/home/index.tsx +++ b/apps/www/src/routes/home/index.tsx @@ -12,13 +12,39 @@ export default component$(() => { return ( <> -
+ {/*
*/} + {/*
+ +
*/} + +
+
+ + +
+
+
+ {/* */} +

Red Dead Redemption 2

+
+
+
+ {/*
+ +
+ +
+ +
+ +
*/} + {/*

{getGreeting()}, Wanjohi

What will you play today?

-
-
+
*/} + {/*
  • { />
-
- +
*/} ) }) \ No newline at end of file diff --git a/apps/www/src/routes/play/[id]/index.tsx b/apps/www/src/routes/play/[id]/index.tsx new file mode 100644 index 00000000..03d71add --- /dev/null +++ b/apps/www/src/routes/play/[id]/index.tsx @@ -0,0 +1,287 @@ +import {useLocation} from "@builder.io/qwik-city"; +import {Keyboard, Mouse, WebRTCStream} from "@nestri/input" +import {component$, useSignal, useVisibleTask$} from "@builder.io/qwik"; + +export default component$(() => { + const id = useLocation().params.id; + const canvas = useSignal(); + + useVisibleTask$(({track}) => { + track(() => canvas.value); + + if (!canvas.value) return; // Ensure canvas is available + + // Create video element and make it output to canvas (TODO: improve this) + let video = document.getElementById("webrtc-video-player"); + if (!video) { + video = document.createElement("video"); + video.id = "stream-video-player"; + video.style.visibility = "hidden"; + const webrtc = new WebRTCStream("https://relay.dathorse.com", id, (mediaStream) => { + if (video && mediaStream && (video as HTMLVideoElement).srcObject === null) { + console.log("Setting mediastream"); + (video as HTMLVideoElement).srcObject = mediaStream; + + // @ts-ignore + window.hasstream = true; + // @ts-ignore + window.roomOfflineElement?.remove(); + + const playbtn = document.createElement("button"); + playbtn.style.position = "absolute"; + playbtn.style.left = "50%"; + playbtn.style.top = "50%"; + playbtn.style.transform = "translateX(-50%) translateY(-50%)"; + playbtn.style.width = "12rem"; + playbtn.style.height = "6rem"; + playbtn.style.borderRadius = "1rem"; + playbtn.style.backgroundColor = "rgb(175, 50, 50)"; + playbtn.style.color = "black"; + playbtn.style.fontSize = "1.5em"; + playbtn.textContent = "< Start >"; + + playbtn.onclick = () => { + playbtn.remove(); + (video as HTMLVideoElement).play().then(() => { + if (canvas.value) { + canvas.value.width = (video as HTMLVideoElement).videoWidth; + canvas.value.height = (video as HTMLVideoElement).videoHeight; + + const ctx = canvas.value.getContext("2d"); + const renderer = () => { + // @ts-ignore + if (ctx && window.hasstream) { + ctx.drawImage((video as HTMLVideoElement), 0, 0); + (video as HTMLVideoElement).requestVideoFrameCallback(renderer); + } + } + (video as HTMLVideoElement).requestVideoFrameCallback(renderer); + } + }); + + document.addEventListener("pointerlockchange", () => { + if (!canvas.value) return; // Ensure canvas is available + // @ts-ignore + if (document.pointerLockElement && !window.nestrimouse && !window.nestrikeyboard) { + // @ts-ignore + window.nestrimouse = new Mouse({canvas: canvas.value, webrtc}); + // @ts-ignore + window.nestrikeyboard = new Keyboard({canvas: canvas.value, webrtc}); + // @ts-ignore + } else if (!document.pointerLockElement && window.nestrimouse && window.nestrikeyboard) { + // @ts-ignore + window.nestrimouse.dispose(); + // @ts-ignore + window.nestrimouse = undefined; + // @ts-ignore + window.nestrikeyboard.dispose(); + // @ts-ignore + window.nestrikeyboard = undefined; + } + }); + }; + document.body.append(playbtn); + } else if (mediaStream === null) { + console.log("MediaStream is null, Room is offline"); + // Add a message to the screen + const offline = document.createElement("div"); + offline.style.position = "absolute"; + offline.style.left = "50%"; + offline.style.top = "50%"; + offline.style.transform = "translateX(-50%) translateY(-50%)"; + offline.style.width = "auto"; + offline.style.height = "auto"; + offline.style.color = "lightgray"; + offline.style.fontSize = "2em"; + offline.textContent = "Offline"; + document.body.append(offline); + // @ts-ignore + window.roomOfflineElement = offline; + // @ts-ignore + window.hasstream = false; + // Clear canvas if it has been set + if (canvas.value) { + const ctx = canvas.value.getContext("2d"); + if (ctx) ctx.clearRect(0, 0, canvas.value.width, canvas.value.height); + } + } + }); + } + }) + + return ( + { + // @ts-ignore + if (canvas.value && window.hasstream) { + // Do not use - unadjustedMovement: true - breaks input on linux + await canvas.value.requestPointerLock(); + await canvas.value.requestFullscreen() + if (document.fullscreenElement !== null) { + // @ts-ignore + if ('keyboard' in window.navigator && 'lock' in window.navigator.keyboard) { + const keys = [ + "AltLeft", + "AltRight", + "Tab", + "Escape", + "ContextMenu", + "MetaLeft", + "MetaRight" + ]; + console.log("requesting keyboard lock"); + // @ts-ignore + window.navigator.keyboard.lock(keys).then( + () => { + console.log("keyboard lock success"); + } + ).catch( + (e: any) => { + console.log("keyboard lock failed: ", e); + } + ) + } else { + console.log("keyboard lock not supported, navigator is: ", window.navigator, navigator); + } + } + } + }} + //TODO: go full screen, then lock on "landscape" screen-orientation on mobile + class="aspect-video h-full w-full object-contain max-h-screen"/> + ) +}) + +{/** + .spinningCircleInner_b6db20 { + transform: rotate(280deg); + } + .inner_b6db20 { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + contain: paint; + } */ +} + +{/* */ +} +// .loadingPopout_a8c724 { +// background-color: var(--background-secondary); +// display: flex; +// justify-content: center; +// padding: 8px; +// } + +// .circular_b6db20 { +// animation: spinner-spinning-circle-rotate_b6db20 2s linear infinite; +// height: 100%; +// width: 100%; +// } + +// 100% { +// transform: rotate(360deg); +// } + + +{/* .path3_b6db20 { + animation-delay: .23s; + stroke: var(--text-brand); +} +.path_b6db20 { + animation: spinner-spinning-circle-dash_b6db20 2s ease-in-out infinite; + stroke-dasharray: 1, 200; + stroke-dashoffset: 0; + fill: none; + stroke-width: 6; + stroke-miterlimit: 10; + stroke-linecap: round; + stroke: var(--brand-500); +} +circle[Attributes Style] { + cx: 50; + cy: 50; + r: 20; +} +user agent stylesheet +:not(svg) { + transform-origin: 0px 0px; +} */ +} + + +// .path2_b6db20 { +// animation-delay: .15s; +// stroke: var(--text-brand); +// opacity: .6; +// } +// .path_b6db20 { +// animation: spinner-spinning-circle-dash_b6db20 2s ease-in-out infinite; +// stroke-dasharray: 1, 200; +// stroke-dashoffset: 0; +// fill: none; +// stroke-width: 6; +// stroke-miterlimit: 10; +// stroke-linecap: round; +// stroke: var(--brand-500); +// } +// circle[Attributes Style] { +// cx: 50; +// cy: 50; +// r: 20; + + +// function throttle(func, limit) { +// let inThrottle; +// return function(...args) { +// if (!inThrottle) { +// func.apply(this, args); +// inThrottle = true; +// setTimeout(() => inThrottle = false, limit); +// } +// } +// } + +// // Use it like this: +// const throttledMouseMove = throttle((x, y) => { +// websocket.send(JSON.stringify({ +// type: 'mousemove', +// x: x, +// y: y +// })); +// }, 16); // ~60fps + +// use std::time::Instant; + +// // Add these to your AppState +// struct AppState { +// pipeline: Arc>, +// last_mouse_move: Arc>, // Add this +// } + +// // Then in your MouseMove handler: +// InputMessage::MouseMove { x, y } => { +// let mut last_move = state.last_mouse_move.lock().unwrap(); +// let now = Instant::now(); + +// // Only process if coordinates are different or enough time has passed +// if (last_move.0 != x || last_move.1 != y) && +// (now.duration_since(last_move.2).as_millis() > 16) { // ~60fps + +// println!("Mouse moved to x: {}, y: {}", x, y); + +// let structure = gst::Structure::builder("MouseMoveRelative") +// .field("pointer_x", x as f64) +// .field("pointer_y", y as f64) +// .build(); + +// let event = gst::event::CustomUpstream::new(structure); +// pipeline.send_event(event); + +// // Update last position and time +// *last_move = (x, y, now); +// } +// } diff --git a/apps/www/tsconfig.json b/apps/www/tsconfig.json index ab18d603..6a9efe3a 100644 --- a/apps/www/tsconfig.json +++ b/apps/www/tsconfig.json @@ -41,5 +41,5 @@ "./*.config.ts", "./*.config.js", "content-collections.ts" - ] +, "../../packages/input/src/webrtc-stream.ts" ] } \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index 0a3752ae..58c988d8 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..47aa7c46 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1 @@ +#FIXME: A simple docker-compose file for running the MoQ relay and the cachyos server \ No newline at end of file diff --git a/.certs/.gitignore b/packages/certs/.gitignore similarity index 100% rename from .certs/.gitignore rename to packages/certs/.gitignore diff --git a/.certs/.terraform.lock.hcl b/packages/certs/.terraform.lock.hcl similarity index 100% rename from .certs/.terraform.lock.hcl rename to packages/certs/.terraform.lock.hcl diff --git a/.certs/README.md b/packages/certs/README.md similarity index 100% rename from .certs/README.md rename to packages/certs/README.md diff --git a/.certs/input.tf b/packages/certs/input.tf similarity index 100% rename from .certs/input.tf rename to packages/certs/input.tf diff --git a/.certs/main.tf b/packages/certs/main.tf similarity index 100% rename from .certs/main.tf rename to packages/certs/main.tf diff --git a/.certs/terraform.tfvars b/packages/certs/terraform.tfvars similarity index 100% rename from .certs/terraform.tfvars rename to packages/certs/terraform.tfvars diff --git a/packages/eslint-config/qwik.js b/packages/eslint-config/qwik.js index 3ee14854..374e759a 100644 --- a/packages/eslint-config/qwik.js +++ b/packages/eslint-config/qwik.js @@ -43,6 +43,7 @@ module.exports = { "prefer-spread": "off", "no-case-declarations": "off", "no-console": "off", + "qwik/no-use-visible-task": "off", "@typescript-eslint/consistent-type-imports": "warn", "@typescript-eslint/no-unnecessary-condition": "warn", }, diff --git a/packages/input/package.json b/packages/input/package.json new file mode 100644 index 00000000..7a0f8c51 --- /dev/null +++ b/packages/input/package.json @@ -0,0 +1,9 @@ +{ + "name": "@nestri/input", + "version": "0.0.0", + "private": true, + "sideEffects": false, + "exports": { + ".": "./src/index.ts" + } +} \ No newline at end of file diff --git a/packages/input/src/codes.ts b/packages/input/src/codes.ts new file mode 100644 index 00000000..c3e5a8d7 --- /dev/null +++ b/packages/input/src/codes.ts @@ -0,0 +1,113 @@ +export const keyCodeToLinuxEventCode: { [key: string]: number } = { + 'KeyA': 30, + 'KeyB': 48, + 'KeyC': 46, + 'KeyD': 32, + 'KeyE': 18, + 'KeyF': 33, + 'KeyG': 34, + 'KeyH': 35, + 'KeyI': 23, + 'KeyJ': 36, + 'KeyK': 37, + 'KeyL': 38, + 'KeyM': 50, + 'KeyN': 49, + 'KeyO': 24, + 'KeyP': 25, + 'KeyQ': 16, + 'KeyR': 19, + 'KeyS': 31, + 'KeyT': 20, + 'KeyU': 22, + 'KeyV': 47, + 'KeyW': 17, + 'KeyX': 45, + 'KeyY': 21, + 'KeyZ': 44, + 'Digit1': 2, + 'Digit2': 3, + 'Digit3': 4, + 'Digit4': 5, + 'Digit5': 6, + 'Digit6': 7, + 'Digit7': 8, + 'Digit8': 9, + 'Digit9': 10, + 'Digit0': 11, + 'Enter': 28, + 'Escape': 1, + 'Backspace': 14, + 'Tab': 15, + 'Space': 57, + 'Minus': 12, + 'Equal': 13, + 'BracketLeft': 26, + 'BracketRight': 27, + 'Backslash': 43, + 'Semicolon': 39, + 'Quote': 40, + 'Backquote': 41, + 'Comma': 51, + 'Period': 52, + 'Slash': 53, + 'CapsLock': 58, + 'F1': 59, + 'F2': 60, + 'F3': 61, + 'F4': 62, + 'F5': 63, + 'F6': 64, + 'F7': 65, + 'F8': 66, + 'F9': 67, + 'F10': 68, + 'F11': 87, + 'F12': 88, + 'Insert': 110, + 'Delete': 111, + 'ArrowUp': 103, + 'ArrowDown': 108, + 'ArrowLeft': 105, + 'ArrowRight': 106, + 'Home': 102, + 'End': 107, + 'PageUp': 104, + 'PageDown': 109, + 'NumLock': 69, + 'ScrollLock': 70, + 'Pause': 119, + 'Numpad0': 82, + 'Numpad1': 79, + 'Numpad2': 80, + 'Numpad3': 81, + 'Numpad4': 75, + 'Numpad5': 76, + 'Numpad6': 77, + 'Numpad7': 71, + 'Numpad8': 72, + 'Numpad9': 73, + 'NumpadDivide': 98, + 'NumpadMultiply': 55, + 'NumpadSubtract': 74, + 'NumpadAdd': 78, + 'NumpadEnter': 96, + 'NumpadDecimal': 83, + 'ControlLeft': 29, + 'ControlRight': 97, + 'ShiftLeft': 42, + 'ShiftRight': 54, + 'AltLeft': 56, + 'AltRight': 100, + //'MetaLeft': 125, // Disabled as will break input + //'MetaRight': 126, // Disabled as will break input + 'ContextMenu': 127, +}; + +export const mouseButtonToLinuxEventCode: { [button: number]: number } = { + 0: 272, + 2: 273, + 1: 274, + 3: 275, + 4: 276 +}; diff --git a/packages/input/src/index.ts b/packages/input/src/index.ts new file mode 100644 index 00000000..4ead3120 --- /dev/null +++ b/packages/input/src/index.ts @@ -0,0 +1,3 @@ +export * from "./keyboard" +export * from "./mouse" +export * from "./webrtc-stream" \ No newline at end of file diff --git a/packages/input/src/keyboard.ts b/packages/input/src/keyboard.ts new file mode 100644 index 00000000..a5541e3d --- /dev/null +++ b/packages/input/src/keyboard.ts @@ -0,0 +1,96 @@ +import {type Input} from "./types" +import {keyCodeToLinuxEventCode} from "./codes" +import {MessageInput, encodeMessage} from "./messages"; +import {WebRTCStream} from "./webrtc-stream"; +import {LatencyTracker} from "./latency"; + +interface Props { + webrtc: WebRTCStream; + canvas: HTMLCanvasElement; +} + +export class Keyboard { + protected wrtc: WebRTCStream; + protected canvas: HTMLCanvasElement; + protected connected!: boolean; + + // Store references to event listeners + private keydownListener: (e: KeyboardEvent) => void; + private keyupListener: (e: KeyboardEvent) => void; + + constructor({webrtc, canvas}: Props) { + this.wrtc = webrtc; + this.canvas = canvas; + this.keydownListener = this.createKeyboardListener("keydown", (e: any) => ({ + type: "KeyDown", + key: this.keyToVirtualKeyCode(e.code) + })); + this.keyupListener = this.createKeyboardListener("keyup", (e: any) => ({ + type: "KeyUp", + key: this.keyToVirtualKeyCode(e.code) + })); + this.run() + } + + private run() { + //calls all the other functions + if (!document.pointerLockElement) { + if (this.connected) { + this.stop() + } + return; + } + + if (document.pointerLockElement == this.canvas) { + this.connected = true + document.addEventListener("keydown", this.keydownListener, {passive: false}); + document.addEventListener("keyup", this.keyupListener, {passive: false}); + } else { + if (this.connected) { + this.stop() + } + } + } + + private stop() { + document.removeEventListener("keydown", this.keydownListener); + document.removeEventListener("keyup", this.keyupListener); + this.connected = false; + } + + // Helper function to create and return mouse listeners + private createKeyboardListener(type: string, dataCreator: (e: Event) => Partial): (e: Event) => void { + return (e: Event) => { + e.preventDefault(); + e.stopPropagation(); + // Prevent repeated key events from being sent (important for games) + if ((e as any).repeat) + return; + + const data = dataCreator(e as any); // type assertion because of the way dataCreator is used + const dataString = JSON.stringify({...data, type} as Input); + + // Latency tracking + const tracker = new LatencyTracker("input-keyboard"); + tracker.addTimestamp("client_send"); + const message: MessageInput = { + payload_type: "input", + data: dataString, + latency: tracker, + }; + this.wrtc.sendBinary(encodeMessage(message)); + }; + } + + public dispose() { + document.exitPointerLock(); + this.stop(); + this.connected = false; + } + + private keyToVirtualKeyCode(code: string) { + // Treat Home key as Escape - TODO: Make user-configurable + if (code === "Home") return 1; + return keyCodeToLinuxEventCode[code] || undefined; + } +} \ No newline at end of file diff --git a/packages/input/src/latency.ts b/packages/input/src/latency.ts new file mode 100644 index 00000000..3a556896 --- /dev/null +++ b/packages/input/src/latency.ts @@ -0,0 +1,54 @@ +type TimestampEntry = { + stage: string; + time: Date; +}; + +export class LatencyTracker { + sequence_id: string; + timestamps: TimestampEntry[]; + metadata?: Record; + + constructor(sequence_id: string, timestamps: TimestampEntry[] = [], metadata: Record = {}) { + this.sequence_id = sequence_id; + this.timestamps = timestamps; + this.metadata = metadata; + } + + addTimestamp(stage: string): void { + const timestamp: TimestampEntry = { + stage, + time: new Date(), + }; + this.timestamps.push(timestamp); + } + + // Calculates the total time between the first and last recorded timestamps. + getTotalLatency(): number { + if (this.timestamps.length < 2) return 0; + + const times = this.timestamps.map((entry) => entry.time.getTime()); + const minTime = Math.min(...times); + const maxTime = Math.max(...times); + return maxTime - minTime; + } + + toJSON(): Record { + return { + sequence_id: this.sequence_id, + timestamps: this.timestamps.map((entry) => ({ + stage: entry.stage, + // Fill nanoseconds with zeros to match the expected format + time: entry.time.toISOString().replace(/\.(\d+)Z$/, ".$1000000Z"), + })), + metadata: this.metadata, + }; + } + + static fromJSON(json: any): LatencyTracker { + const timestamps: TimestampEntry[] = json.timestamps.map((ts: any) => ({ + stage: ts.stage, + time: new Date(ts.time), + })); + return new LatencyTracker(json.sequence_id, timestamps, json.metadata); + } +} diff --git a/packages/input/src/messages.ts b/packages/input/src/messages.ts new file mode 100644 index 00000000..5d60bf07 --- /dev/null +++ b/packages/input/src/messages.ts @@ -0,0 +1,73 @@ +import {gzip, ungzip} from "pako"; +import {LatencyTracker} from "./latency"; + +export interface MessageBase { + payload_type: string; +} + +export interface MessageInput extends MessageBase { + payload_type: "input"; + data: string; + latency?: LatencyTracker; +} + +export interface MessageICE extends MessageBase { + payload_type: "ice"; + candidate: RTCIceCandidateInit; +} + +export interface MessageSDP extends MessageBase { + payload_type: "sdp"; + sdp: RTCSessionDescriptionInit; +} + +export enum JoinerType { + JoinerNode = 0, + JoinerClient = 1, +} + +export interface MessageJoin extends MessageBase { + payload_type: "join"; + joiner_type: JoinerType; +} + +export enum AnswerType { + AnswerOffline = 0, + AnswerInUse, + AnswerOK +} + +export interface MessageAnswer extends MessageBase { + payload_type: "answer"; + answer_type: AnswerType; +} + +function blobToUint8Array(blob: Blob): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onloadend = () => { + const arrayBuffer = reader.result as ArrayBuffer; + resolve(new Uint8Array(arrayBuffer)); + }; + reader.onerror = reject; + reader.readAsArrayBuffer(blob); + }); +} + +export function encodeMessage(message: T): Uint8Array { + // Convert the message to JSON string + const json = JSON.stringify(message); + // Compress the JSON string using gzip + return gzip(json); +} + +export async function decodeMessage(data: Blob): Promise { + // Convert the Blob to Uint8Array + const array = await blobToUint8Array(data); + // Decompress the gzip data + const decompressed = ungzip(array); + // Convert the Uint8Array to JSON string + const json = new TextDecoder().decode(decompressed); + // Parse the JSON string + return JSON.parse(json); +} diff --git a/packages/input/src/mouse.ts b/packages/input/src/mouse.ts new file mode 100644 index 00000000..0345a522 --- /dev/null +++ b/packages/input/src/mouse.ts @@ -0,0 +1,112 @@ +import {type Input} from "./types" +import {mouseButtonToLinuxEventCode} from "./codes" +import {MessageInput, encodeMessage} from "./messages"; +import {WebRTCStream} from "./webrtc-stream"; +import {LatencyTracker} from "./latency"; + +interface Props { + webrtc: WebRTCStream; + canvas: HTMLCanvasElement; +} + +export class Mouse { + protected wrtc: WebRTCStream; + protected canvas: HTMLCanvasElement; + protected connected!: boolean; + + // Store references to event listeners + private mousemoveListener: (e: MouseEvent) => void; + private mousedownListener: (e: MouseEvent) => void; + private mouseupListener: (e: MouseEvent) => void; + private mousewheelListener: (e: WheelEvent) => void; + + constructor({webrtc, canvas}: Props) { + this.wrtc = webrtc; + this.canvas = canvas; + + this.mousemoveListener = this.createMouseListener("mousemove", (e: any) => ({ + type: "MouseMove", + x: e.movementX, + y: e.movementY + })); + this.mousedownListener = this.createMouseListener("mousedown", (e: any) => ({ + type: "MouseKeyDown", + key: this.keyToVirtualKeyCode(e.button) + })); + + this.mouseupListener = this.createMouseListener("mouseup", (e: any) => ({ + type: "MouseKeyUp", + key: this.keyToVirtualKeyCode(e.button) + })); + this.mousewheelListener = this.createMouseListener("wheel", (e: any) => ({ + type: "MouseWheel", + x: e.deltaX, + y: e.deltaY + })); + + this.run() + } + + private run() { + //calls all the other functions + if (!document.pointerLockElement) { + console.log("no pointerlock") + if (this.connected) { + this.stop() + } + return; + } + + if (document.pointerLockElement == this.canvas) { + this.connected = true + this.canvas.addEventListener("mousemove", this.mousemoveListener, { passive: false }); + this.canvas.addEventListener("mousedown", this.mousedownListener, { passive: false }); + this.canvas.addEventListener("mouseup", this.mouseupListener, { passive: false }); + this.canvas.addEventListener("wheel", this.mousewheelListener, { passive: false }); + + } else { + if (this.connected) { + this.stop() + } + } + + } + + private stop() { + this.canvas.removeEventListener("mousemove", this.mousemoveListener); + this.canvas.removeEventListener("mousedown", this.mousedownListener); + this.canvas.removeEventListener("mouseup", this.mouseupListener); + this.canvas.removeEventListener("wheel", this.mousewheelListener); + this.connected = false; + } + + // Helper function to create and return mouse listeners + private createMouseListener(type: string, dataCreator: (e: Event) => Partial): (e: Event) => void { + return (e: Event) => { + e.preventDefault(); + e.stopPropagation(); + const data = dataCreator(e as any); // type assertion because of the way dataCreator is used + const dataString = JSON.stringify({...data, type} as Input); + + // Latency tracking + const tracker = new LatencyTracker("input-mouse"); + tracker.addTimestamp("client_send"); + const message: MessageInput = { + payload_type: "input", + data: dataString, + latency: tracker, + }; + this.wrtc.sendBinary(encodeMessage(message)); + }; + } + + public dispose() { + document.exitPointerLock(); + this.stop(); + this.connected = false; + } + + private keyToVirtualKeyCode(code: number) { + return mouseButtonToLinuxEventCode[code] || undefined; + } +} \ No newline at end of file diff --git a/packages/input/src/types.ts b/packages/input/src/types.ts new file mode 100644 index 00000000..b733b499 --- /dev/null +++ b/packages/input/src/types.ts @@ -0,0 +1,52 @@ +interface BaseInput { + timestamp?: number; // Add a timestamp for better context (optional) +} + +interface MouseMove extends BaseInput { + type: "MouseMove"; + x: number; + y: number; +} + +interface MouseMoveAbs extends BaseInput { + type: "MouseMoveAbs"; + x: number; + y: number; +} + +interface MouseWheel extends BaseInput { + type: "MouseWheel"; + x: number; + y: number; +} + +interface MouseKeyDown extends BaseInput { + type: "MouseKeyDown"; + key: number; +} + +interface MouseKeyUp extends BaseInput { + type: "MouseKeyUp"; + key: number; +} + +interface KeyDown extends BaseInput { + type: "KeyDown"; + key: number; +} + +interface KeyUp extends BaseInput { + type: "KeyUp"; + key: number; +} + + +export type Input = + | MouseMove + | MouseMoveAbs + | MouseWheel + | MouseKeyDown + | MouseKeyUp + | KeyDown + | KeyUp; + diff --git a/packages/input/src/webrtc-stream.ts b/packages/input/src/webrtc-stream.ts new file mode 100644 index 00000000..09a404c0 --- /dev/null +++ b/packages/input/src/webrtc-stream.ts @@ -0,0 +1,166 @@ +import { + MessageBase, + MessageICE, + MessageJoin, + MessageSDP, + MessageAnswer, + JoinerType, + AnswerType, + decodeMessage, + encodeMessage +} from "./messages"; + +export class WebRTCStream { + private _ws: WebSocket | undefined = undefined; + private _pc: RTCPeerConnection | undefined = undefined; + private _mediaStream: MediaStream | undefined = undefined; + private _dataChannel: RTCDataChannel | undefined = undefined; + private _onConnected: ((stream: MediaStream | null) => void) | undefined = undefined; + + constructor(serverURL: string, roomName: string, connectedCallback: (stream: MediaStream | null) => void) { + // If roomName is not provided, return + if (roomName.length <= 0) { + console.error("Room name not provided"); + return; + } + + this._onConnected = connectedCallback; + + console.log("Setting up WebSocket"); + // Replace http/https with ws/wss + const wsURL = serverURL.replace(/^http/, "ws"); + this._ws = new WebSocket(`${wsURL}/api/ws/${roomName}`); + this._ws.onopen = async () => { + console.log("WebSocket opened"); + + console.log("Setting up PeerConnection"); + this._pc = new RTCPeerConnection({ + iceServers: [ + { + urls: "stun:stun.l.google.com:19302" + } + ], + }); + + this._pc.ontrack = (e) => { + console.log("Track received: ", e.track); + this._mediaStream = e.streams[e.streams.length - 1]; + }; + + this._pc.onconnectionstatechange = () => { + console.log("Connection state: ", this._pc!.connectionState); + if (this._pc!.connectionState === "connected") { + if (this._onConnected && this._mediaStream) + this._onConnected(this._mediaStream); + } + }; + + this._pc.onicecandidate = (e) => { + if (e.candidate) { + const message: MessageICE = { + payload_type: "ice", + candidate: e.candidate + }; + this._ws!.send(encodeMessage(message)); + } + } + + this._pc.ondatachannel = (e) => { + this._dataChannel = e.channel; + this._setupDataChannelEvents(); + } + + // Send join message + const joinMessage: MessageJoin = { + payload_type: "join", + joiner_type: JoinerType.JoinerClient + }; + this._ws!.send(encodeMessage(joinMessage)); + } + + let iceHolder: RTCIceCandidateInit[] = []; + + this._ws.onmessage = async (e) => { + // allow only binary + if (typeof e.data !== "object") return; + if (!e.data) return; + const message = await decodeMessage(e.data); + switch (message.payload_type) { + case "sdp": + await this._pc!.setRemoteDescription((message as MessageSDP).sdp); + // Create our answer + const answer = await this._pc!.createAnswer(); + // Force stereo in Chromium browsers + answer.sdp = this.forceOpusStereo(answer.sdp!); + await this._pc!.setLocalDescription(answer); + this._ws!.send(encodeMessage({ + payload_type: "sdp", + sdp: answer + })); + break; + case "ice": + // If remote description is not set yet, hold the ICE candidates + if (this._pc!.remoteDescription) { + await this._pc!.addIceCandidate((message as MessageICE).candidate); + // Add held ICE candidates + for (const ice of iceHolder) { + await this._pc!.addIceCandidate(ice); + } + iceHolder = []; + } else { + iceHolder.push((message as MessageICE).candidate); + } + break; + case "answer": + switch ((message as MessageAnswer).answer_type) { + case AnswerType.AnswerOffline: + console.log("Room is offline"); + // Call callback with null stream + if (this._onConnected) + this._onConnected(null); + + break; + case AnswerType.AnswerInUse: + console.warn("Room is in use, we shouldn't even be getting this message"); + break; + case AnswerType.AnswerOK: + console.log("Joining Room was successful"); + break; + } + break; + default: + console.error("Unknown message type: ", message); + } + } + + this._ws.onclose = () => { + console.log("WebSocket closed"); + } + + this._ws.onerror = (e) => { + console.error("WebSocket error: ", e); + } + } + + // Forces opus to stereo in Chromium browsers, because of course + private forceOpusStereo(SDP: string): string { + // Look for "minptime=10;useinbandfec=1" and replace with "minptime=10;useinbandfec=1;stereo=1;sprop-stereo=1;" + return SDP.replace(/(minptime=10;useinbandfec=1)/, "$1;stereo=1;sprop-stereo=1;"); + } + + private _setupDataChannelEvents() { + if (!this._dataChannel) return; + + this._dataChannel.onclose = () => console.log('sendChannel has closed') + this._dataChannel.onopen = () => console.log('sendChannel has opened') + this._dataChannel.onmessage = e => console.log(`Message from DataChannel '${this._dataChannel?.label}' payload '${e.data}'`) + } + + // Send binary message through the data channel + public sendBinary(data: Uint8Array) { + if (this._dataChannel && this._dataChannel.readyState === "open") + this._dataChannel.send(data); + else + console.log("Data channel not open or not established."); + } +} diff --git a/packages/master/go.mod b/packages/master/go.mod new file mode 100644 index 00000000..7565b333 --- /dev/null +++ b/packages/master/go.mod @@ -0,0 +1,32 @@ +module master + +go 1.23.3 + +require github.com/docker/docker v27.3.1+incompatible + +require ( + github.com/Microsoft/go-winio v0.4.14 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect + go.opentelemetry.io/otel v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 // indirect + go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/otel/sdk v1.32.0 // indirect + go.opentelemetry.io/otel/trace v1.32.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/time v0.8.0 // indirect + gotest.tools/v3 v3.5.1 // indirect +) diff --git a/packages/master/go.sum b/packages/master/go.sum new file mode 100644 index 00000000..4acbdbf3 --- /dev/null +++ b/packages/master/go.sum @@ -0,0 +1,123 @@ +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= +github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/packages/master/main.go b/packages/master/main.go new file mode 100644 index 00000000..13164f8f --- /dev/null +++ b/packages/master/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "context" + "io" + "os" + + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/client" + "github.com/docker/docker/pkg/stdcopy" +) + +func main() { + ctx := context.Background() + + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + panic(err) + } + defer cli.Close() + + // Try to get the Docker version + _, err = cli.ServerVersion(ctx) + if err != nil { + // If an error occurs (e.g., Docker is not running), return false + panic(err) + } + + // Download the image + containerName := "hello-world" + + reader, err := cli.ImagePull(ctx, containerName, image.PullOptions{}) + if err != nil { + panic(err) + } + + defer reader.Close() + + // cli.ImagePull is asynchronous. + // The reader needs to be read completely for the pull operation to complete. + // If stdout is not required, consider using io.Discard instead of os.Stdout. + io.Copy(os.Stdout, reader) + + resp, err := cli.ContainerCreate(ctx, &container.Config{ + Image: "hello-world", + }, + nil, nil, nil, containerName) + if err != nil { + panic(err) + } + + // Start the container + if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil { + panic(err) + } + + // Wait for the container to finish and get its logs + statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning) + select { + case err := <-errCh: + if err != nil { + panic(err) + } + case <-statusCh: + } + + out, err := cli.ContainerLogs(ctx, resp.ID, container.LogsOptions{ShowStdout: true}) + if err != nil { + panic(err) + } + + stdcopy.StdCopy(os.Stdout, os.Stderr, out) + + // Remove the container + if err := cli.ContainerRemove(ctx, resp.ID, container.RemoveOptions{}); err != nil { + panic(err) + } + +} diff --git a/packages/moq/.eslintrc.cjs b/packages/moq/.eslintrc.cjs index 89e59ace..49848456 100644 --- a/packages/moq/.eslintrc.cjs +++ b/packages/moq/.eslintrc.cjs @@ -8,7 +8,7 @@ module.exports = { "prettier", ], parser: "@typescript-eslint/parser", - plugins: ["@typescript-eslint", "prettier"], + plugins: ["@typescript-eslint", "prettier", "solid"], root: true, env: { browser: true, diff --git a/packages/moq/common/async.ts b/packages/moq/common/async.ts index a0fa0b4e..2f91bbbf 100644 --- a/packages/moq/common/async.ts +++ b/packages/moq/common/async.ts @@ -1,7 +1,7 @@ export class Deferred { promise: Promise resolve!: (value: T | PromiseLike) => void - reject!: (reason: any) => void + reject!: (reason: unknown) => void pending = true constructor() { @@ -35,16 +35,19 @@ export class Watch { update(v: T | ((v: T) => T)) { if (!this.#next.pending) { - throw new Error("already closed") + throw new Error("closed") } // If we're given a function, call it with the current value + let value: T if (v instanceof Function) { - v = v(this.#current[0]) + value = v(this.#current[0]) + } else { + value = v } const next = new Deferred>() - this.#current = [v, next.promise] + this.#current = [value, next.promise] this.#next.resolve(this.#current) this.#next = next } @@ -53,6 +56,10 @@ export class Watch { this.#current[1] = undefined this.#next.resolve(this.#current) } + + closed() { + return !this.#next.pending + } } // Wakes up a multiple consumers. @@ -88,6 +95,7 @@ export class Queue { } async push(v: T) { + if (this.#closed) throw new Error("closed") const w = this.#stream.writable.getWriter() await w.write(v) w.releaseLock() diff --git a/packages/moq/common/error.ts b/packages/moq/common/error.ts index c627a0b9..d4171799 100644 --- a/packages/moq/common/error.ts +++ b/packages/moq/common/error.ts @@ -1,14 +1,14 @@ // I hate javascript -export function asError(e: any): Error { +export function asError(e: unknown): Error { if (e instanceof Error) { return e - } else if (typeof e === "string") { + } + if (typeof e === "string") { return new Error(e) - } else { - return new Error(String(e)) } + return new Error(String(e)) } -export function isError(e: any): e is Error { +export function isError(e: unknown): e is Error { return e instanceof Error } diff --git a/packages/moq/common/hex.ts b/packages/moq/common/hex.ts new file mode 100644 index 00000000..ca833dbc --- /dev/null +++ b/packages/moq/common/hex.ts @@ -0,0 +1,11 @@ +export function decode(str: string): Uint8Array { + const bytes = new Uint8Array(str.length / 2) + for (let i = 0; i < bytes.length; i += 1) { + bytes[i] = Number.parseInt(str.slice(2 * i, 2 * i + 2), 16) + } + return bytes +} + +export function encode(_bytes: Uint8Array): string { + throw "todo" +} diff --git a/packages/moq/common/ring.ts b/packages/moq/common/ring.ts index 97a73e65..8a805059 100644 --- a/packages/moq/common/ring.ts +++ b/packages/moq/common/ring.ts @@ -2,8 +2,8 @@ enum STATE { READ_POS = 0, // The current read position - WRITE_POS, // The current write position - LENGTH, // Clever way of saving the total number of enums values. + WRITE_POS = 1, // The current write position + LENGTH = 2, // Clever way of saving the total number of enums values. } interface FrameCopyToOptions { @@ -62,16 +62,12 @@ export class Ring { const readPos = Atomics.load(this.state, STATE.READ_POS) const writePos = Atomics.load(this.state, STATE.WRITE_POS) - const startPos = writePos - let endPos = writePos + frame.numberOfFrames + const available = this.capacity - (writePos - readPos) + if (available <= 0) return 0 - if (endPos > readPos + this.capacity) { - endPos = readPos + this.capacity - if (endPos <= startPos) { - // No space to write - return 0 - } - } + const toWrite = Math.min(frame.numberOfFrames, available) + const startPos = writePos + const endPos = writePos + toWrite const startIndex = startPos % this.capacity const endIndex = endPos % this.capacity @@ -114,7 +110,7 @@ export class Ring { Atomics.store(this.state, STATE.WRITE_POS, endPos) - return endPos - startPos + return toWrite } read(dst: Float32Array[]): number { diff --git a/packages/moq/contribute/audio.ts b/packages/moq/contribute/audio.ts index d42fe288..e170b59f 100644 --- a/packages/moq/contribute/audio.ts +++ b/packages/moq/contribute/audio.ts @@ -1,15 +1,67 @@ +import { Deferred } from "../common/async" +import type { Frame } from "../karp/frame" +import type { Group, Track } from "../transfork" +import { Closed } from "../transfork/error" + const SUPPORTED = [ // TODO support AAC // "mp4a" "Opus", ] +export class Packer { + #source: MediaStreamTrackProcessor + #encoder: Encoder + + #data: Track + #current?: Group + + constructor(track: MediaStreamAudioTrack, encoder: Encoder, data: Track) { + this.#source = new MediaStreamTrackProcessor({ track }) + this.#encoder = encoder + this.#data = data + } + + async run() { + const output = new WritableStream({ + write: (chunk) => this.#write(chunk), + close: () => this.#close(), + abort: (e) => this.#close(e), + }) + + return this.#source.readable.pipeThrough(this.#encoder.frames).pipeTo(output) + } + + #write(frame: Frame) { + // TODO use a fixed interval instead of keyframes (audio) + // TODO actually just align with video + if (!this.#current || frame.type === "key") { + if (this.#current) { + this.#current.close() + } + + this.#current = this.#data.appendGroup() + } + + this.#current.writeFrame(frame.data) + } + + #close(err?: unknown) { + const closed = Closed.from(err) + if (this.#current) { + this.#current.close(closed) + } + + this.#data.close(closed) + } +} + export class Encoder { #encoder!: AudioEncoder #encoderConfig: AudioEncoderConfig - #decoderConfig?: AudioDecoderConfig + #decoderConfig = new Deferred() - frames: TransformStream + frames: TransformStream constructor(config: AudioEncoderConfig) { this.#encoderConfig = config @@ -21,7 +73,7 @@ export class Encoder { }) } - #start(controller: TransformStreamDefaultController) { + #start(controller: TransformStreamDefaultController) { this.#encoder = new AudioEncoder({ output: (frame, metadata) => { this.#enqueue(controller, frame, metadata) @@ -40,17 +92,16 @@ export class Encoder { } #enqueue( - controller: TransformStreamDefaultController, + controller: TransformStreamDefaultController, frame: EncodedAudioChunk, metadata?: EncodedAudioChunkMetadata, ) { const config = metadata?.decoderConfig - if (config && !this.#decoderConfig) { + if (config && !this.#decoderConfig.pending) { const config = metadata.decoderConfig if (!config) throw new Error("missing decoder config") - controller.enqueue(config) - this.#decoderConfig = config + this.#decoderConfig.resolve(config) } controller.enqueue(frame) @@ -72,4 +123,8 @@ export class Encoder { get config() { return this.#encoderConfig } + + async decoderConfig(): Promise { + return await this.#decoderConfig.promise + } } diff --git a/packages/moq/contribute/broadcast.ts b/packages/moq/contribute/broadcast.ts index 2f8224fb..f0b61e46 100644 --- a/packages/moq/contribute/broadcast.ts +++ b/packages/moq/contribute/broadcast.ts @@ -1,15 +1,14 @@ -import { Connection, SubscribeRecv } from "../transport" -import { asError } from "../common/error" -import { Segment } from "./segment" -import { Track } from "./track" -import * as Catalog from "../media/catalog" +import * as Catalog from "../karp/catalog" +import * as Transfork from "../transfork" +import * as Audio from "./audio" +import * as Video from "./video" import { isAudioTrackSettings, isVideoTrackSettings } from "../common/settings" export interface BroadcastConfig { - namespace: string - connection: Connection + path: string[] media: MediaStream + id?: number audio?: AudioEncoderConfig video?: VideoEncoderConfig @@ -21,221 +20,89 @@ export interface BroadcastConfigTrack { } export class Broadcast { - #tracks = new Map() - - readonly config: BroadcastConfig - readonly catalog: Catalog.Root - readonly connection: Connection - readonly namespace: string - - #running: Promise + #config: BroadcastConfig + #path: string[] constructor(config: BroadcastConfig) { - this.connection = config.connection - this.config = config - this.namespace = config.namespace + const id = config.id || new Date().getTime() / 1000 - const tracks: Catalog.Track[] = [] + this.#config = config + this.#path = config.path.concat(id.toString()) + } - for (const media of this.config.media.getTracks()) { - const track = new Track(media, config) - this.#tracks.set(track.name, track) + async publish(connection: Transfork.Connection) { + const broadcast: Catalog.Broadcast = { + path: this.#config.path, + audio: [], + video: [], + } + for (const media of this.#config.media.getTracks()) { const settings = media.getSettings() + const info = { + name: media.id, // TODO way too verbose + priority: media.kind === "video" ? 1 : 2, + } + + const track = new Transfork.Track(this.#config.path.concat(info.name), info.priority) + if (isVideoTrackSettings(settings)) { - if (!config.video) { + if (!this.#config.video) { throw new Error("no video configuration provided") } - const video: Catalog.VideoTrack = { - namespace: this.namespace, - name: `${track.name}.m4s`, - initTrack: `${track.name}.mp4`, - selectionParams: { - mimeType: "video/mp4", - codec: config.video.codec, - width: settings.width, - height: settings.height, - framerate: settings.frameRate, - bitrate: config.video.bitrate, - }, + const encoder = new Video.Encoder(this.#config.video) + const packer = new Video.Packer(media as MediaStreamVideoTrack, encoder, track) + + // TODO handle error + packer.run().catch((err) => console.error("failed to run video packer: ", err)) + + const decoder = await encoder.decoderConfig() + const description = decoder.description ? new Uint8Array(decoder.description as ArrayBuffer) : undefined + + const video: Catalog.Video = { + track: info, + codec: decoder.codec, + description: description, + resolution: { width: settings.width, height: settings.height }, + frame_rate: settings.frameRate, + bitrate: this.#config.video.bitrate, } - tracks.push(video) + broadcast.video.push(video) } else if (isAudioTrackSettings(settings)) { - if (!config.audio) { + if (!this.#config.audio) { throw new Error("no audio configuration provided") } - const audio: Catalog.AudioTrack = { - namespace: this.namespace, - name: `${track.name}.m4s`, - initTrack: `${track.name}.mp4`, - selectionParams: { - mimeType: "audio/ogg", - codec: config.audio.codec, - samplerate: settings.sampleRate, - //sampleSize: settings.sampleSize, - channelConfig: `${settings.channelCount}`, - bitrate: config.audio.bitrate, - }, + const encoder = new Audio.Encoder(this.#config.audio) + const packer = new Audio.Packer(media as MediaStreamAudioTrack, encoder, track) + packer.run().catch((err) => console.error("failed to run audio packer: ", err)) // TODO handle error + + const decoder = await encoder.decoderConfig() + + const audio: Catalog.Audio = { + track: info, + codec: decoder.codec, + sample_rate: settings.sampleRate, + channel_count: settings.channelCount, + bitrate: this.#config.audio.bitrate, } - tracks.push(audio) + broadcast.audio.push(audio) } else { throw new Error(`unknown track type: ${media.kind}`) } - } - - this.catalog = { - version: 1, - streamingFormat: 1, - streamingFormatVersion: "0.2", - supportsDeltaUpdates: false, - commonTrackFields: { - packaging: "cmaf", - renderGroup: 1, - }, - tracks, - } - - this.#running = this.#run() - } - - async #run() { - await this.connection.announce(this.namespace) - - for (;;) { - const subscriber = await this.connection.subscribed() - if (!subscriber) break - - // Run an async task to serve each subscription. - this.#serveSubscribe(subscriber).catch((e) => { - const err = asError(e) - console.warn("failed to serve subscribe", err) - }) - } - } - async #serveSubscribe(subscriber: SubscribeRecv) { - try { - const [base, ext] = splitExt(subscriber.track) - if (ext === "catalog") { - await this.#serveCatalog(subscriber, base) - } else if (ext === "mp4") { - await this.#serveInit(subscriber, base) - } else if (ext === "m4s") { - await this.#serveTrack(subscriber, base) - } else { - throw new Error(`unknown subscription: ${subscriber.track}`) - } - } catch (e) { - const err = asError(e) - await subscriber.close(1n, `failed to process subscribe: ${err.message}`) - } finally { - // TODO we can't close subscribers because there's no support for clean termination - // await subscriber.close() + connection.publish(track.reader()) } - } - async #serveCatalog(subscriber: SubscribeRecv, name: string) { - // We only support ".catalog" - if (name !== "") throw new Error(`unknown catalog: ${name}`) + const track = new Transfork.Track(this.#config.path.concat("catalog.json"), 0) + track.appendGroup().writeFrames(Catalog.encode(broadcast)) - const bytes = Catalog.encode(this.catalog) - - // Send a SUBSCRIBE_OK - await subscriber.ack() - - const stream = await subscriber.group({ group: 0 }) - await stream.write({ object: 0, payload: bytes }) - await stream.close() + connection.publish(track.reader()) } - async #serveInit(subscriber: SubscribeRecv, name: string) { - const track = this.#tracks.get(name) - if (!track) throw new Error(`no track with name ${subscriber.track}`) - - // Send a SUBSCRIBE_OK - await subscriber.ack() - - const init = await track.init() - - const stream = await subscriber.group({ group: 0 }) - await stream.write({ object: 0, payload: init }) - await stream.close() - } - - async #serveTrack(subscriber: SubscribeRecv, name: string) { - const track = this.#tracks.get(name) - if (!track) throw new Error(`no track with name ${subscriber.track}`) - - // Send a SUBSCRIBE_OK - await subscriber.ack() - - const segments = track.segments().getReader() - - for (;;) { - const { value: segment, done } = await segments.read() - if (done) break - - // Serve the segment and log any errors that occur. - this.#serveSegment(subscriber, segment).catch((e) => { - const err = asError(e) - console.warn("failed to serve segment", err) - }) - } - } - - async #serveSegment(subscriber: SubscribeRecv, segment: Segment) { - // Create a new stream for each segment. - const stream = await subscriber.group({ - group: segment.id, - priority: 0, // TODO - }) - - let object = 0 - - // Pipe the segment to the stream. - const chunks = segment.chunks().getReader() - for (;;) { - const { value, done } = await chunks.read() - if (done) break - - await stream.write({ - object, - payload: value, - }) - - object += 1 - } - - await stream.close() - } - - // Attach the captured video stream to the given video element. - attach(video: HTMLVideoElement) { - video.srcObject = this.config.media - } - - close() { - // TODO implement publish close - } - - // Returns the error message when the connection is closed - async closed(): Promise { - try { - await this.#running - return new Error("closed") // clean termination - } catch (e) { - return asError(e) - } - } -} - -function splitExt(s: string): [string, string] { - const i = s.lastIndexOf(".") - if (i < 0) throw new Error(`no extension found`) - return [s.substring(0, i), s.substring(i + 1)] + close() {} } diff --git a/packages/moq/contribute/chunk.ts b/packages/moq/contribute/chunk.ts deleted file mode 100644 index 2fcfe334..00000000 --- a/packages/moq/contribute/chunk.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Extends EncodedVideoChunk, allowing a new "init" type -export interface Chunk { - type: "init" | "key" | "delta" - timestamp: number // microseconds - duration: number // microseconds - data: Uint8Array -} diff --git a/packages/moq/contribute/container.ts b/packages/moq/contribute/container.ts deleted file mode 100644 index ab1b4f1d..00000000 --- a/packages/moq/contribute/container.ts +++ /dev/null @@ -1,165 +0,0 @@ -import * as MP4 from "../media/mp4" -import { Chunk } from "./chunk" - -type DecoderConfig = AudioDecoderConfig | VideoDecoderConfig -type EncodedChunk = EncodedAudioChunk | EncodedVideoChunk - -export class Container { - #mp4: MP4.ISOFile - #frame?: EncodedAudioChunk | EncodedVideoChunk // 1 frame buffer - #track?: number - #segment = 0 - - encode: TransformStream - - constructor() { - this.#mp4 = new MP4.ISOFile() - this.#mp4.init() - - this.encode = new TransformStream({ - transform: (frame, controller) => { - if (isDecoderConfig(frame)) { - return this.#init(frame, controller) - } else { - return this.#enqueue(frame, controller) - } - }, - }) - } - - #init(frame: DecoderConfig, controller: TransformStreamDefaultController) { - if (this.#track) throw new Error("duplicate decoder config") - - let codec = frame.codec.substring(0, 4) - if (codec == "opus") { - codec = "Opus" - } - - const options: MP4.TrackOptions = { - type: codec, - timescale: 1_000_000, - } - - if (isVideoConfig(frame)) { - options.width = frame.codedWidth - options.height = frame.codedHeight - } else { - options.channel_count = frame.numberOfChannels - options.samplerate = frame.sampleRate - } - - if (!frame.description) throw new Error("missing frame description") - const desc = frame.description as ArrayBufferLike - - if (codec === "avc1") { - options.avcDecoderConfigRecord = desc - } else if (codec === "hev1") { - options.hevcDecoderConfigRecord = desc - } else if (codec === "Opus") { - // description is an identification header: https://datatracker.ietf.org/doc/html/rfc7845#section-5.1 - // The first 8 bytes are the magic string "OpusHead", followed by what we actually want. - const dops = new MP4.BoxParser.dOpsBox(undefined) - - // Annoyingly, the header is little endian while MP4 is big endian, so we have to parse. - const data = new MP4.Stream(desc, 8, MP4.Stream.LITTLE_ENDIAN) - dops.parse(data) - - dops.Version = 0 - options.description = dops - options.hdlr = "soun" - } else { - throw new Error(`unsupported codec: ${codec}`) - } - - this.#track = this.#mp4.addTrack(options) - if (!this.#track) throw new Error("failed to initialize MP4 track") - - const buffer = MP4.ISOFile.writeInitializationSegment(this.#mp4.ftyp!, this.#mp4.moov!, 0, 0) - const data = new Uint8Array(buffer) - - controller.enqueue({ - type: "init", - timestamp: 0, - duration: 0, - data, - }) - } - - #enqueue(frame: EncodedChunk, controller: TransformStreamDefaultController) { - // Check if we should create a new segment - if (frame.type == "key") { - this.#segment += 1 - } else if (this.#segment == 0) { - throw new Error("must start with keyframe") - } - - // We need a one frame buffer to compute the duration - if (!this.#frame) { - this.#frame = frame - return - } - - const duration = frame.timestamp - this.#frame.timestamp - - // TODO avoid this extra copy by writing to the mdat directly - // ...which means changing mp4box.js to take an offset instead of ArrayBuffer - const buffer = new Uint8Array(this.#frame.byteLength) - this.#frame.copyTo(buffer) - - if (!this.#track) throw new Error("missing decoder config") - - // Add the sample to the container - this.#mp4.addSample(this.#track, buffer, { - duration, - dts: this.#frame.timestamp, - cts: this.#frame.timestamp, - is_sync: this.#frame.type == "key", - }) - - const stream = new MP4.Stream(undefined, 0, MP4.Stream.BIG_ENDIAN) - - // Moof and mdat atoms are written in pairs. - // TODO remove the moof/mdat from the Box to reclaim memory once everything works - for (;;) { - const moof = this.#mp4.moofs.shift() - const mdat = this.#mp4.mdats.shift() - - if (!moof && !mdat) break - if (!moof) throw new Error("moof missing") - if (!mdat) throw new Error("mdat missing") - - moof.write(stream) - mdat.write(stream) - } - - // TODO avoid this extra copy by writing to the buffer provided in copyTo - const data = new Uint8Array(stream.buffer) - - controller.enqueue({ - type: this.#frame.type, - timestamp: this.#frame.timestamp, - duration: this.#frame.duration ?? 0, - data, - }) - - this.#frame = frame - } - - /* TODO flush the last frame - #flush(controller: TransformStreamDefaultController) { - if (this.#frame) { - // TODO guess the duration - this.#enqueue(this.#frame, 0, controller) - } - } - */ -} - -function isDecoderConfig(frame: DecoderConfig | EncodedChunk): frame is DecoderConfig { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - return (frame as DecoderConfig).codec !== undefined -} - -function isVideoConfig(frame: DecoderConfig): frame is VideoDecoderConfig { - return (frame as VideoDecoderConfig).codedWidth !== undefined -} diff --git a/packages/moq/contribute/segment.ts b/packages/moq/contribute/segment.ts index f0aa8195..fa5a65e6 100644 --- a/packages/moq/contribute/segment.ts +++ b/packages/moq/contribute/segment.ts @@ -1,10 +1,10 @@ -import { Chunk } from "./chunk" +import type { Frame } from "../karp/frame" export class Segment { id: number - // Take in a stream of chunks - input: WritableStream + // Take in a stream of frames + input: WritableStream // Output a stream of bytes, which we fork for each new subscriber. #cache: ReadableStream @@ -16,16 +16,18 @@ export class Segment { // Set a max size for each segment, dropping the tail if it gets too long. // We tee the reader, so this limit applies to the FASTEST reader. - const backpressure = new ByteLengthQueuingStrategy({ highWaterMark: 8_000_000 }) + const backpressure = new ByteLengthQueuingStrategy({ + highWaterMark: 8_000_000, + }) - const transport = new TransformStream( + const transport = new TransformStream( { - transform: (chunk: Chunk, controller) => { + transform: (frame: Frame, controller) => { // Compute the max timestamp of the segment - this.timestamp = Math.max(chunk.timestamp + chunk.duration) + this.timestamp = Math.max(this.timestamp, frame.timestamp) // Push the chunk to any listeners. - controller.enqueue(chunk.data) + controller.enqueue(frame.data) }, }, undefined, diff --git a/packages/moq/contribute/track.ts b/packages/moq/contribute/track.ts index cec70d81..d2a050ee 100644 --- a/packages/moq/contribute/track.ts +++ b/packages/moq/contribute/track.ts @@ -1,9 +1,8 @@ -import { Segment } from "./segment" import { Notify } from "../common/async" -import { Chunk } from "./chunk" -import { Container } from "./container" -import { BroadcastConfig } from "./broadcast" +import type { BroadcastConfig } from "./broadcast" +import { Segment } from "./segment" +import type { Frame } from "../karp/frame" import * as Audio from "./audio" import * as Video from "./video" @@ -36,7 +35,6 @@ export class Track { async #runAudio(track: MediaStreamAudioTrack, config: AudioEncoderConfig) { const source = new MediaStreamTrackProcessor({ track }) const encoder = new Audio.Encoder(config) - const container = new Container() // Split the container at keyframe boundaries const segments = new WritableStream({ @@ -45,13 +43,12 @@ export class Track { abort: (e) => this.#close(e), }) - return source.readable.pipeThrough(encoder.frames).pipeThrough(container.encode).pipeTo(segments) + return source.readable.pipeThrough(encoder.frames).pipeTo(segments) } async #runVideo(track: MediaStreamVideoTrack, config: VideoEncoderConfig) { const source = new MediaStreamTrackProcessor({ track }) const encoder = new Video.Encoder(config) - const container = new Container() // Split the container at keyframe boundaries const segments = new WritableStream({ @@ -60,18 +57,12 @@ export class Track { abort: (e) => this.#close(e), }) - return source.readable.pipeThrough(encoder.frames).pipeThrough(container.encode).pipeTo(segments) + return source.readable.pipeThrough(encoder.frames).pipeTo(segments) } - async #write(chunk: Chunk) { - if (chunk.type === "init") { - this.#init = chunk.data - this.#notify.wake() - return - } - + async #write(frame: Frame) { let current = this.#segments.at(-1) - if (!current || chunk.type === "key") { + if (!current || frame.type === "key") { if (current) { await current.input.close() } @@ -88,7 +79,7 @@ export class Track { const first = this.#segments[0] // Expire after 10s - if (chunk.timestamp - first.timestamp < 10_000_000) break + if (frame.timestamp - first.timestamp < 10_000_000) break this.#segments.shift() this.#offset += 1 @@ -99,7 +90,7 @@ export class Track { const writer = current.input.getWriter() if ((writer.desiredSize || 0) > 0) { - await writer.write(chunk) + await writer.write(frame) } else { console.warn("dropping chunk", writer.desiredSize) } @@ -147,7 +138,8 @@ export class Track { if (this.#error) { controller.error(this.#error) return - } else if (this.#closed) { + } + if (this.#closed) { controller.close() return } diff --git a/packages/moq/contribute/tsconfig.json b/packages/moq/contribute/tsconfig.json index 70899a7f..693a7071 100644 --- a/packages/moq/contribute/tsconfig.json +++ b/packages/moq/contribute/tsconfig.json @@ -9,10 +9,10 @@ "path": "../common" }, { - "path": "../transport" + "path": "../transfork" }, { - "path": "../media" + "path": "../karp" } ] } diff --git a/packages/moq/contribute/video.ts b/packages/moq/contribute/video.ts index 747c7659..fa25876c 100644 --- a/packages/moq/contribute/video.ts +++ b/packages/moq/contribute/video.ts @@ -1,3 +1,8 @@ +import { Deferred } from "../common/async" +import type { Frame } from "../karp/frame" +import type { Group, Track } from "../transfork" +import { Closed } from "../transfork/error" + const SUPPORTED = [ "avc1", // H.264 "hev1", // HEVC (aka h.265) @@ -8,10 +13,55 @@ export interface EncoderSupported { codecs: string[] } +export class Packer { + #source: MediaStreamTrackProcessor + #encoder: Encoder + + #data: Track + #current?: Group + + constructor(track: MediaStreamVideoTrack, encoder: Encoder, data: Track) { + this.#source = new MediaStreamTrackProcessor({ track }) + this.#encoder = encoder + this.#data = data + } + + async run() { + const output = new WritableStream({ + write: (chunk) => this.#write(chunk), + close: () => this.#close(), + abort: (e) => this.#close(e), + }) + + return this.#source.readable.pipeThrough(this.#encoder.frames).pipeTo(output) + } + + #write(frame: Frame) { + if (!this.#current || frame.type === "key") { + if (this.#current) { + this.#current.close() + } + + this.#current = this.#data.appendGroup() + } + + frame.encode(this.#current) + } + + #close(err?: unknown) { + const closed = Closed.from(err) + if (this.#current) { + this.#current.close(closed) + } + + this.#data.close(closed) + } +} + export class Encoder { #encoder!: VideoEncoder #encoderConfig: VideoEncoderConfig - #decoderConfig?: VideoDecoderConfig + #decoderConfig = new Deferred() // true if we should insert a keyframe, undefined when the encoder should decide #keyframeNext: true | undefined = true @@ -20,7 +70,7 @@ export class Encoder { #keyframeCounter = 0 // Converts raw rames to encoded frames. - frames: TransformStream + frames: TransformStream constructor(config: VideoEncoderConfig) { config.bitrateMode ??= "constant" @@ -53,12 +103,17 @@ export class Encoder { return !!res.supported } + async decoderConfig(): Promise { + return await this.#decoderConfig.promise + } + #start(controller: TransformStreamDefaultController) { this.#encoder = new VideoEncoder({ output: (frame, metadata) => { this.#enqueue(controller, frame, metadata) }, error: (err) => { + this.#decoderConfig.reject(err) throw err }, }) @@ -77,23 +132,22 @@ export class Encoder { } #enqueue( - controller: TransformStreamDefaultController, + controller: TransformStreamDefaultController, frame: EncodedVideoChunk, metadata?: EncodedVideoChunkMetadata, ) { - if (!this.#decoderConfig) { + if (this.#decoderConfig.pending) { const config = metadata?.decoderConfig if (!config) throw new Error("missing decoder config") - - controller.enqueue(config) - this.#decoderConfig = config + this.#decoderConfig.resolve(config) } if (frame.type === "key") { this.#keyframeCounter = 0 } else { this.#keyframeCounter += 1 - if (this.#keyframeCounter + this.#encoder.encodeQueueSize >= 2 * this.#encoderConfig.framerate!) { + const framesPerGop = this.#encoderConfig.framerate ? 2 * this.#encoderConfig.framerate : 60 + if (this.#keyframeCounter + this.#encoder.encodeQueueSize >= framesPerGop) { this.#keyframeNext = true } } diff --git a/packages/moq/karp/catalog/audio.ts b/packages/moq/karp/catalog/audio.ts new file mode 100644 index 00000000..00738479 --- /dev/null +++ b/packages/moq/karp/catalog/audio.ts @@ -0,0 +1,20 @@ +import { type Track, decodeTrack } from "./track" + +export interface Audio { + track: Track + codec: string + sample_rate: number + channel_count: number + bitrate?: number +} + +export function decodeAudio(o: unknown): o is Audio { + if (typeof o !== "object" || o === null) return false + + const obj = o as Partial