diff --git a/examples/meet/Cargo.lock b/examples/meet/Cargo.lock
new file mode 100644
index 0000000..eff81bd
--- /dev/null
+++ b/examples/meet/Cargo.lock
@@ -0,0 +1,1631 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[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.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[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 = "anyhow"
+version = "1.0.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
+
+[[package]]
+name = "anymap2"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c"
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "backtrace"
+version = "0.3.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
+dependencies = [
+ "addr2line",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+ "windows-targets",
+]
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
+
+[[package]]
+name = "boolinator"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9"
+
+[[package]]
+name = "bumpalo"
+version = "3.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
+
+[[package]]
+name = "bytes"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
+
+[[package]]
+name = "cc"
+version = "1.2.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
+
+[[package]]
+name = "chrono"
+version = "0.4.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "serde",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "console_error_panic_hook"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "console_log"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f"
+dependencies = [
+ "log",
+ "web-sys",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[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 = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasi 0.14.3+wasi-0.2.4",
+]
+
+[[package]]
+name = "gimli"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
+
+[[package]]
+name = "gloo"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28999cda5ef6916ffd33fb4a7b87e1de633c47c0dc6d97905fee1cdaa142b94d"
+dependencies = [
+ "gloo-console 0.2.3",
+ "gloo-dialogs 0.1.1",
+ "gloo-events 0.1.2",
+ "gloo-file 0.2.3",
+ "gloo-history 0.1.5",
+ "gloo-net 0.3.1",
+ "gloo-render 0.1.1",
+ "gloo-storage 0.2.2",
+ "gloo-timers 0.2.6",
+ "gloo-utils 0.1.7",
+ "gloo-worker 0.2.1",
+]
+
+[[package]]
+name = "gloo"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd35526c28cc55c1db77aed6296de58677dbab863b118483a27845631d870249"
+dependencies = [
+ "gloo-console 0.3.0",
+ "gloo-dialogs 0.2.0",
+ "gloo-events 0.2.0",
+ "gloo-file 0.3.0",
+ "gloo-history 0.2.2",
+ "gloo-net 0.4.0",
+ "gloo-render 0.2.0",
+ "gloo-storage 0.3.0",
+ "gloo-timers 0.3.0",
+ "gloo-utils 0.2.0",
+ "gloo-worker 0.4.0",
+]
+
+[[package]]
+name = "gloo-console"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f"
+dependencies = [
+ "gloo-utils 0.1.7",
+ "js-sys",
+ "serde",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-console"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a17868f56b4a24f677b17c8cb69958385102fa879418052d60b50bc1727e261"
+dependencies = [
+ "gloo-utils 0.2.0",
+ "js-sys",
+ "serde",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-dialogs"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67062364ac72d27f08445a46cab428188e2e224ec9e37efdba48ae8c289002e6"
+dependencies = [
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-dialogs"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf4748e10122b01435750ff530095b1217cf6546173459448b83913ebe7815df"
+dependencies = [
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-events"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68b107f8abed8105e4182de63845afcc7b69c098b7852a813ea7462a320992fc"
+dependencies = [
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-events"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27c26fb45f7c385ba980f5fa87ac677e363949e065a083722697ef1b2cc91e41"
+dependencies = [
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-file"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8d5564e570a38b43d78bdc063374a0c3098c4f0d64005b12f9bbe87e869b6d7"
+dependencies = [
+ "gloo-events 0.1.2",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-file"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97563d71863fb2824b2e974e754a81d19c4a7ec47b09ced8a0e6656b6d54bd1f"
+dependencies = [
+ "futures-channel",
+ "gloo-events 0.2.0",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-history"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85725d90bf0ed47063b3930ef28e863658a7905989e9929a8708aab74a1d5e7f"
+dependencies = [
+ "gloo-events 0.1.2",
+ "gloo-utils 0.1.7",
+ "serde",
+ "serde-wasm-bindgen 0.5.0",
+ "serde_urlencoded",
+ "thiserror",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-history"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "903f432be5ba34427eac5e16048ef65604a82061fe93789f2212afc73d8617d6"
+dependencies = [
+ "getrandom 0.2.16",
+ "gloo-events 0.2.0",
+ "gloo-utils 0.2.0",
+ "serde",
+ "serde-wasm-bindgen 0.6.5",
+ "serde_urlencoded",
+ "thiserror",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-net"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a66b4e3c7d9ed8d315fd6b97c8b1f74a7c6ecbbc2320e65ae7ed38b7068cc620"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-sink",
+ "gloo-utils 0.1.7",
+ "http 0.2.12",
+ "js-sys",
+ "pin-project",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-net"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ac9e8288ae2c632fa9f8657ac70bfe38a1530f345282d7ba66a1f70b72b7dc4"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-sink",
+ "gloo-utils 0.2.0",
+ "http 0.2.12",
+ "js-sys",
+ "pin-project",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-net"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-sink",
+ "gloo-utils 0.2.0",
+ "http 1.3.1",
+ "js-sys",
+ "pin-project",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-render"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fd9306aef67cfd4449823aadcd14e3958e0800aa2183955a309112a84ec7764"
+dependencies = [
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-render"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56008b6744713a8e8d98ac3dcb7d06543d5662358c9c805b4ce2167ad4649833"
+dependencies = [
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-storage"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480"
+dependencies = [
+ "gloo-utils 0.1.7",
+ "js-sys",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-storage"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a"
+dependencies = [
+ "gloo-utils 0.2.0",
+ "js-sys",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-timers"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "gloo-timers"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "gloo-utils"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e"
+dependencies = [
+ "js-sys",
+ "serde",
+ "serde_json",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-utils"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa"
+dependencies = [
+ "js-sys",
+ "serde",
+ "serde_json",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-worker"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13471584da78061a28306d1359dd0178d8d6fc1c7c80e5e35d27260346e0516a"
+dependencies = [
+ "anymap2",
+ "bincode",
+ "gloo-console 0.2.3",
+ "gloo-utils 0.1.7",
+ "js-sys",
+ "serde",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-worker"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76495d3dd87de51da268fa3a593da118ab43eb7f8809e17eb38d3319b424e400"
+dependencies = [
+ "bincode",
+ "futures",
+ "gloo-utils 0.2.0",
+ "gloo-worker-macros",
+ "js-sys",
+ "pinned",
+ "serde",
+ "thiserror",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-worker-macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "956caa58d4857bc9941749d55e4bd3000032d8212762586fa5705632967140e7"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+
+[[package]]
+name = "http"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "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 = "implicit-clone"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8a9aa791c7b5a71b636b7a68207fdebf171ddfc593d9c8506ec4cbc527b6a84"
+dependencies = [
+ "implicit-clone-derive",
+ "indexmap",
+]
+
+[[package]]
+name = "implicit-clone-derive"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "699c1b6d335e63d0ba5c1e1c7f647371ce989c3bcbe1f7ed2b85fa56e3bd1a21"
+dependencies = [
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "io-uring"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "js-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.175"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
+
+[[package]]
+name = "log"
+version = "0.4.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+
+[[package]]
+name = "meet"
+version = "0.1.0"
+dependencies = [
+ "bytes",
+ "chrono",
+ "console_log",
+ "futures",
+ "gloo-net 0.6.0",
+ "gloo-timers 0.3.0",
+ "gloo-utils 0.2.0",
+ "js-sys",
+ "log",
+ "prost",
+ "prost-types",
+ "serde",
+ "serde_json",
+ "urlencoding",
+ "uuid",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wasm-logger",
+ "web-sys",
+ "yew",
+ "yew-router",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+]
+
+[[package]]
+name = "mio"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
+dependencies = [
+ "libc",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+ "windows-sys",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "object"
+version = "0.36.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "pin-project"
+version = "1.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pinned"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a829027bd95e54cfe13e3e258a1ae7b645960553fb82b75ff852c29688ee595b"
+dependencies = [
+ "futures",
+ "rustversion",
+ "thiserror",
+]
+
+[[package]]
+name = "prettyplease"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+dependencies = [
+ "proc-macro2",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
+dependencies = [
+ "once_cell",
+ "toml_edit",
+]
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "prokio"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03b55e106e5791fa5a13abd13c85d6127312e8e09098059ca2bc9b03ca4cf488"
+dependencies = [
+ "futures",
+ "gloo 0.8.1",
+ "num_cpus",
+ "once_cell",
+ "pin-project",
+ "pinned",
+ "tokio",
+ "tokio-stream",
+ "wasm-bindgen-futures",
+]
+
+[[package]]
+name = "prost"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29"
+dependencies = [
+ "bytes",
+ "prost-derive",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
+dependencies = [
+ "anyhow",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0"
+dependencies = [
+ "prost",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "route-recognizer"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746"
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "serde"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde-wasm-bindgen"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e"
+dependencies = [
+ "js-sys",
+ "serde",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "serde-wasm-bindgen"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
+dependencies = [
+ "js-sys",
+ "serde",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.143"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "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 = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "slab"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "tokio"
+version = "1.47.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
+dependencies = [
+ "backtrace",
+ "io-uring",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
+
+[[package]]
+name = "toml_edit"
+version = "0.19.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "urlencoding"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
+
+[[package]]
+name = "uuid"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be"
+dependencies = [
+ "getrandom 0.3.3",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasi"
+version = "0.14.3+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "once_cell",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "wasm-logger"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "074649a66bb306c8f2068c9016395fa65d8e08d2affcbf95acf3c24c3ab19718"
+dependencies = [
+ "log",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+
+[[package]]
+name = "windows-result"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+dependencies = [
+ "windows-link",
+]
+
+[[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.5.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814"
+
+[[package]]
+name = "yew"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f1a03f255c70c7aa3e9c62e15292f142ede0564123543c1cc0c7a4f31660cac"
+dependencies = [
+ "console_error_panic_hook",
+ "futures",
+ "gloo 0.10.0",
+ "implicit-clone",
+ "indexmap",
+ "js-sys",
+ "prokio",
+ "rustversion",
+ "serde",
+ "slab",
+ "thiserror",
+ "tokio",
+ "tracing",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "yew-macro",
+]
+
+[[package]]
+name = "yew-macro"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02fd8ca5166d69e59f796500a2ce432ff751edecbbb308ca59fd3fe4d0343de2"
+dependencies = [
+ "boolinator",
+ "once_cell",
+ "prettyplease",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "yew-router"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca1d5052c96e6762b4d6209a8aded597758d442e6c479995faf0c7b5538e0c6"
+dependencies = [
+ "gloo 0.10.0",
+ "js-sys",
+ "route-recognizer",
+ "serde",
+ "serde_urlencoded",
+ "tracing",
+ "urlencoding",
+ "wasm-bindgen",
+ "web-sys",
+ "yew",
+ "yew-router-macro",
+]
+
+[[package]]
+name = "yew-router-macro"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42bfd190a07ca8cfde7cd4c52b3ac463803dc07323db8c34daa697e86365978c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
diff --git a/examples/meet/Cargo.toml b/examples/meet/Cargo.toml
new file mode 100644
index 0000000..3ce9ac4
--- /dev/null
+++ b/examples/meet/Cargo.toml
@@ -0,0 +1,73 @@
+[workspace]
+
+[package]
+name = "meet"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+yew = { version = "0.21", features = ["csr"] }
+yew-router = "0.18"
+wasm-bindgen = "0.2"
+wasm-bindgen-futures = "0.4"
+js-sys = "0.3"
+web-sys = { version = "0.3", features = [
+ "console",
+ "Document",
+ "Element",
+ "HtmlElement",
+ "HtmlInputElement",
+ "HtmlSelectElement",
+ "HtmlTextAreaElement",
+ "HtmlVideoElement",
+ "HtmlAudioElement",
+ "Window",
+ "Location",
+ "History",
+ "Url",
+ "UrlSearchParams",
+ "MediaDevices",
+ "MediaStream",
+ "MediaStreamTrack",
+ "MediaStreamConstraints",
+ "MediaDeviceInfo",
+ "MediaDeviceKind",
+ "Navigator",
+ "Event",
+ "EventTarget",
+ "KeyboardEvent",
+ "MouseEvent",
+ "SubmitEvent",
+ "MessageEvent",
+ "WebSocket",
+ "CloseEvent",
+ "RtcPeerConnection",
+ "RtcConfiguration",
+ "RtcIceServer",
+ "RtcSessionDescription",
+ "RtcSessionDescriptionInit",
+ "RtcIceCandidate",
+ "RtcIceCandidateInit",
+ "RtcPeerConnectionIceEvent",
+ "RtcTrackEvent",
+ "RtcDataChannel",
+ "RtcDataChannelEvent",
+ "RtcSdpType",
+ "HtmlCanvasElement",
+ "CanvasRenderingContext2d",
+] }
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+gloo-timers = { version = "0.3", features = ["futures"] }
+gloo-net = "0.6"
+gloo-utils = "0.2"
+log = "0.4"
+wasm-logger = "0.2"
+console_log = "1.0"
+uuid = { version = "1.0", features = ["v4", "js"] }
+chrono = { version = "0.4", features = ["serde", "wasmbind"] }
+futures = "0.3"
+urlencoding = "2.1"
+prost = "0.12"
+prost-types = "0.12"
+bytes = "1.0"
diff --git a/examples/meet/README.md b/examples/meet/README.md
new file mode 100644
index 0000000..34f2a07
--- /dev/null
+++ b/examples/meet/README.md
@@ -0,0 +1,117 @@
+# LiveKit Meet - Yew WASM Port
+
+A complete port of the LiveKit Meet video conferencing application to Rust/Yew WebAssembly.
+
+## Features
+
+- **Home Page**: Demo and custom connection options with E2E encryption support
+- **Pre-join Screen**: Camera/microphone preview and device selection
+- **Video Conference**: Grid layout with participant video tiles
+- **Media Controls**: Mute/unmute audio, enable/disable video, screen sharing, chat toggle
+- **Chat**: Real-time messaging sidebar
+- **Settings Menu**: Device selection and configuration
+- **Responsive Design**: Mobile-friendly interface
+
+## Architecture
+
+This port maintains the same functionality as the original React LiveKit Meet app while leveraging:
+
+- **Yew**: Modern Rust frontend framework with component-based architecture
+- **WebAssembly**: High-performance execution in the browser
+- **LiveKit Rust SDK**: Native Rust integration with LiveKit (WASM-compatible)
+- **Web APIs**: Direct browser API access via web-sys
+
+## Building and Running
+
+### Prerequisites
+
+- Rust (latest stable)
+- Trunk (WASM build tool)
+- wasm-pack
+
+```bash
+# Install Trunk
+cargo install trunk
+
+# Install wasm-pack
+curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
+```
+
+### Development
+
+```bash
+# Start development server
+trunk serve
+
+# Build for production
+trunk build --release
+```
+
+The application will be available at `http://localhost:8080`.
+
+## Project Structure
+
+```
+src/
+├── main.rs # Application entry point
+├── app.rs # Main app component and routing
+├── utils/ # Utility functions
+│ └── mod.rs
+├── pages/ # Page components
+│ ├── mod.rs
+│ ├── home.rs # Home page with tabs
+│ ├── room.rs # Main meeting room
+│ └── custom.rs # Custom connection page
+└── components/ # Reusable UI components
+ ├── mod.rs
+ ├── video_tile.rs # Participant video display
+ ├── chat.rs # Chat sidebar
+ ├── controls.rs # Media control buttons
+ ├── prejoin.rs # Pre-join screen
+ └── settings_menu.rs # Settings overlay
+```
+
+## Key Differences from React Version
+
+1. **State Management**: Uses Yew's `use_state` hooks instead of React state
+2. **Event Handling**: Rust callbacks with type safety
+3. **Async Operations**: Rust futures with `spawn_local`
+4. **WebRTC Integration**: Direct web-sys bindings instead of JavaScript interop
+5. **Type Safety**: Full compile-time type checking for all data structures
+
+## LiveKit Integration
+
+The application is designed to work with:
+- LiveKit Cloud
+- Self-hosted LiveKit Server
+- End-to-end encryption support
+- All standard LiveKit features (audio, video, screen share, chat)
+
+## Browser Support
+
+Supports all modern browsers with WebAssembly and WebRTC capabilities:
+- Chrome 57+
+- Firefox 52+
+- Safari 11+
+- Edge 16+
+
+## Development Notes
+
+- Video/audio device enumeration via MediaDevices API
+- Real-time media stream handling with web-sys
+- Responsive CSS Grid layout for video tiles
+- Dark theme with CSS custom properties
+- Accessibility features with proper ARIA labels
+
+## TODO: LiveKit SDK Integration
+
+Currently, the application has placeholder implementations for LiveKit functionality. The next phase involves:
+
+1. Integrating the LiveKit Rust SDK for WASM
+2. Implementing real-time audio/video streaming
+3. Adding participant management
+4. Enabling chat messaging
+5. Supporting screen sharing
+6. Adding recording capabilities
+
+The UI and component structure are complete and ready for LiveKit integration.
diff --git a/examples/meet/Trunk.toml b/examples/meet/Trunk.toml
new file mode 100644
index 0000000..b2e4b82
--- /dev/null
+++ b/examples/meet/Trunk.toml
@@ -0,0 +1,16 @@
+[build]
+target = "index.html"
+dist = "dist"
+
+[watch]
+watch = ["src", "assets"]
+
+[serve]
+address = "127.0.0.1"
+port = 8080
+open = false
+
+[[hooks]]
+stage = "pre_build"
+command = "wasm-pack"
+command_arguments = ["--version"]
diff --git a/examples/meet/index.html b/examples/meet/index.html
new file mode 100644
index 0000000..bcd4fe9
--- /dev/null
+++ b/examples/meet/index.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+ LiveKit Meet - Yew
+
+
+
+
+
+
+
+
diff --git a/examples/meet/scripts/environment.sh b/examples/meet/scripts/environment.sh
new file mode 100755
index 0000000..76f40cf
--- /dev/null
+++ b/examples/meet/scripts/environment.sh
@@ -0,0 +1,3 @@
+export LIVEKIT_API_KEY="APIK6arpkALy6dB"
+export LIVEKIT_API_SECRET="Oo8umkiBd9bxR3QXakfB6TZvR1S0sTwpZjzMFNN6lTA"
+export LIVEKIT_URL="wss://helloworld-3rmno4kd.livekit.cloud"
\ No newline at end of file
diff --git a/examples/meet/server/Cargo.lock b/examples/meet/server/Cargo.lock
new file mode 100644
index 0000000..b088d5b
--- /dev/null
+++ b/examples/meet/server/Cargo.lock
@@ -0,0 +1,1245 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[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.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[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 = "async-trait"
+version = "0.1.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "axum"
+version = "0.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
+dependencies = [
+ "async-trait",
+ "axum-core",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "serde_json",
+ "serde_path_to_error",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tower 0.5.2",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "rustversion",
+ "sync_wrapper",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "backtrace"
+version = "0.3.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
+dependencies = [
+ "addr2line",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+ "windows-targets",
+]
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bitflags"
+version = "2.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
+
+[[package]]
+name = "bumpalo"
+version = "3.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
+
+[[package]]
+name = "bytes"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
+
+[[package]]
+name = "cc"
+version = "1.2.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
+
+[[package]]
+name = "chrono"
+version = "0.4.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "serde",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "deranged"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+
+[[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-core",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasi 0.14.3+wasi-0.2.4",
+]
+
+[[package]]
+name = "gimli"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
+
+[[package]]
+name = "http"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
+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.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "http-range-header"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
+[[package]]
+name = "hyper"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "pin-utils",
+ "smallvec",
+ "tokio",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "hyper",
+ "pin-project-lite",
+ "tokio",
+ "tower-service",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "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 = "io-uring"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "js-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "jsonwebtoken"
+version = "9.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde"
+dependencies = [
+ "base64",
+ "js-sys",
+ "pem",
+ "ring",
+ "serde",
+ "serde_json",
+ "simple_asn1",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.175"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
+
+[[package]]
+name = "lock_api"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+
+[[package]]
+name = "matchit"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
+
+[[package]]
+name = "meet-server"
+version = "0.1.0"
+dependencies = [
+ "axum",
+ "chrono",
+ "jsonwebtoken",
+ "serde",
+ "serde_json",
+ "tokio",
+ "tower 0.4.13",
+ "tower-http",
+ "uuid",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "mime_guess"
+version = "2.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+]
+
+[[package]]
+name = "mio"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
+dependencies = [
+ "libc",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+ "windows-sys 0.59.0",
+]
+
+[[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-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "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.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets",
+]
+
+[[package]]
+name = "pem"
+version = "3.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3"
+dependencies = [
+ "base64",
+ "serde",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.16",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "serde"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.143"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_path_to_error"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a"
+dependencies = [
+ "itoa",
+ "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 = "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.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "simple_asn1"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "thiserror",
+ "time",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "socket2"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
+dependencies = [
+ "libc",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
+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"
+
+[[package]]
+name = "thiserror"
+version = "2.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.3.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
+
+[[package]]
+name = "time-macros"
+version = "0.2.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tokio"
+version = "1.47.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "io-uring",
+ "libc",
+ "mio",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "slab",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tower"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
+dependencies = [
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
+dependencies = [
+ "bitflags",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "http-range-header",
+ "httpdate",
+ "mime",
+ "mime_guess",
+ "percent-encoding",
+ "pin-project-lite",
+ "tokio",
+ "tokio-util",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[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.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "unicase"
+version = "2.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "uuid"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be"
+dependencies = [
+ "getrandom 0.3.3",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasi"
+version = "0.14.3+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+
+[[package]]
+name = "windows-result"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+dependencies = [
+ "windows-link",
+]
+
+[[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 = "wit-bindgen"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814"
diff --git a/examples/meet/server/Cargo.toml b/examples/meet/server/Cargo.toml
new file mode 100644
index 0000000..324cff4
--- /dev/null
+++ b/examples/meet/server/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "meet-server"
+version = "0.1.0"
+edition = "2021"
+
+[workspace]
+
+[dependencies]
+tokio = { version = "1.0", features = ["full"] }
+axum = "0.7"
+tower = "0.4"
+tower-http = { version = "0.5", features = ["cors", "fs"] }
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+uuid = { version = "1.0", features = ["v4"] }
+jsonwebtoken = "9.0"
+chrono = { version = "0.4", features = ["serde"] }
diff --git a/examples/meet/server/README.md b/examples/meet/server/README.md
new file mode 100644
index 0000000..c98f3ca
--- /dev/null
+++ b/examples/meet/server/README.md
@@ -0,0 +1,63 @@
+# LiveKit Meet Server
+
+A simple backend server to provide connection details for the LiveKit Meet demo app.
+
+## Features
+
+- `/api/connection-details` endpoint for room connection details
+- CORS support for frontend requests
+- Static file serving for the built WASM app
+- Health check endpoint
+
+## Usage
+
+1. Build the frontend first:
+```bash
+cd .. && trunk build
+```
+
+2. Run the server:
+```bash
+cd server && cargo run
+```
+
+3. Access the app at: http://localhost:8083
+
+## API Endpoints
+
+- `GET /api/connection-details?roomName=&participantName=` - Get connection details
+- `GET /health` - Health check
+
+## Production Setup
+
+For production use, you'll need to:
+
+1. Replace the mock token generation with proper LiveKit JWT tokens
+2. Add your actual LiveKit server URL
+3. Add proper authentication and validation
+4. Use environment variables for configuration
+
+Example with real LiveKit tokens:
+
+```rust
+use livekit_api::access_token::{AccessToken, VideoGrant};
+
+fn generate_token(room_name: &str, participant_name: &str) -> String {
+ let api_key = std::env::var("LIVEKIT_API_KEY").unwrap();
+ let api_secret = std::env::var("LIVEKIT_API_SECRET").unwrap();
+
+ let grant = VideoGrant {
+ room_join: true,
+ room: room_name.to_string(),
+ ..Default::default()
+ };
+
+ let token = AccessToken::new(&api_key, &api_secret)
+ .with_identity(participant_name)
+ .with_video_grant(grant)
+ .to_jwt()
+ .unwrap();
+
+ token
+}
+```
diff --git a/examples/meet/server/src/main.rs b/examples/meet/server/src/main.rs
new file mode 100644
index 0000000..322b565
--- /dev/null
+++ b/examples/meet/server/src/main.rs
@@ -0,0 +1,139 @@
+use axum::{
+ extract::Query,
+ http::StatusCode,
+ response::Json,
+ routing::get,
+ Router,
+};
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+use tower_http::cors::CorsLayer;
+use tower_http::services::ServeDir;
+use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};
+use chrono::{Utc, Duration};
+
+#[derive(Debug, Deserialize)]
+struct ConnectionRequest {
+ #[serde(rename = "roomName")]
+ room_name: String,
+ #[serde(rename = "participantName")]
+ participant_name: String,
+}
+
+#[derive(Debug, Serialize)]
+struct ConnectionDetails {
+ server_url: String,
+ participant_token: String,
+}
+
+async fn connection_details(
+ Query(params): Query,
+) -> Result, StatusCode> {
+ println!("Connection request: room={}, participant={}",
+ params.room_name, params.participant_name);
+
+ // For demo purposes, use a mock LiveKit server URL
+ // In production, you would:
+ // 1. Validate the room and participant
+ // 2. Generate a proper JWT token with LiveKit API key/secret
+ // 3. Return your actual LiveKit server URL
+
+ let token = match generate_livekit_token(¶ms.room_name, ¶ms.participant_name) {
+ Ok(token) => token,
+ Err(e) => {
+ println!("Failed to generate token: {}", e);
+ return Err(StatusCode::INTERNAL_SERVER_ERROR);
+ }
+ };
+
+ let details = ConnectionDetails {
+ server_url: std::env::var("LIVEKIT_URL")
+ .unwrap_or_else(|_| "wss://meet-demo.livekit.cloud".to_string()),
+ participant_token: token,
+ };
+
+ Ok(Json(details))
+}
+
+#[derive(Debug, Serialize)]
+struct LiveKitClaims {
+ iss: String,
+ sub: String,
+ iat: i64,
+ exp: i64,
+ video: VideoGrant,
+}
+
+#[derive(Debug, Serialize)]
+struct VideoGrant {
+ #[serde(rename = "roomJoin")]
+ room_join: bool,
+ room: String,
+}
+
+fn generate_livekit_token(room_name: &str, participant_name: &str) -> Result {
+ // For demo purposes - you should set these as environment variables
+ let api_key = std::env::var("LIVEKIT_API_KEY")
+ .unwrap_or_else(|_| "devkey".to_string());
+ let api_secret = std::env::var("LIVEKIT_API_SECRET")
+ .unwrap_or_else(|_| "secret".to_string());
+
+ println!("Generating token with:");
+ println!(" API Key: {}", api_key);
+ println!(" API Secret: {} (length: {})",
+ if api_secret.len() > 10 { format!("{}...", &api_secret[..10]) } else { api_secret.clone() },
+ api_secret.len());
+ println!(" Room: {}", room_name);
+ println!(" Participant: {}", participant_name);
+
+ let now = Utc::now();
+ let exp = now + Duration::hours(6); // Token valid for 6 hours
+
+ let claims = LiveKitClaims {
+ iss: api_key.clone(),
+ sub: participant_name.to_string(),
+ iat: now.timestamp(),
+ exp: exp.timestamp(),
+ video: VideoGrant {
+ room_join: true,
+ room: room_name.to_string(),
+ },
+ };
+
+ let header = Header::new(Algorithm::HS256);
+ let encoding_key = EncodingKey::from_secret(api_secret.as_ref());
+
+ encode(&header, &claims, &encoding_key)
+ .map_err(|e| format!("Failed to generate token: {}", e))
+}
+
+async fn health() -> &'static str {
+ "OK"
+}
+
+#[tokio::main]
+async fn main() {
+ println!("Starting LiveKit Meet server on http://localhost:8083");
+
+ // Log environment variables for debugging
+ println!("Environment variables:");
+ println!(" LIVEKIT_URL: {:?}", std::env::var("LIVEKIT_URL"));
+ println!(" LIVEKIT_API_KEY: {:?}", std::env::var("LIVEKIT_API_KEY"));
+ println!(" LIVEKIT_API_SECRET: {:?}", std::env::var("LIVEKIT_API_SECRET"));
+
+ let app = Router::new()
+ .route("/api/connection-details", get(connection_details))
+ .route("/health", get(health))
+ // Serve static files from the dist directory
+ .nest_service("/", ServeDir::new("../dist"))
+ .layer(CorsLayer::permissive());
+
+ let listener = tokio::net::TcpListener::bind("0.0.0.0:8083")
+ .await
+ .unwrap();
+
+ println!("Server running on http://localhost:8083");
+ println!("API endpoint: http://localhost:8083/api/connection-details");
+
+ axum::serve(listener, app).await.unwrap();
+}
diff --git a/examples/meet/src/app.rs b/examples/meet/src/app.rs
new file mode 100644
index 0000000..cb2f71a
--- /dev/null
+++ b/examples/meet/src/app.rs
@@ -0,0 +1,31 @@
+use yew::prelude::*;
+use yew_router::prelude::*;
+
+use crate::pages::{HomePage, RoomPage, CustomPage};
+
+#[derive(Clone, Routable, PartialEq)]
+pub enum Route {
+ #[at("/")]
+ Home,
+ #[at("/rooms/:room_name")]
+ Room { room_name: String },
+ #[at("/custom")]
+ Custom,
+}
+
+pub fn switch(routes: Route) -> Html {
+ match routes {
+ Route::Home => html! { },
+ Route::Room { room_name } => html! { },
+ Route::Custom => html! { },
+ }
+}
+
+#[function_component(App)]
+pub fn app() -> Html {
+ html! {
+
+ render={switch} />
+
+ }
+}
diff --git a/examples/meet/src/components/chat.rs b/examples/meet/src/components/chat.rs
new file mode 100644
index 0000000..f722184
--- /dev/null
+++ b/examples/meet/src/components/chat.rs
@@ -0,0 +1,115 @@
+use yew::prelude::*;
+use web_sys::HtmlTextAreaElement;
+
+use crate::pages::room::ChatMessage;
+
+#[derive(Properties, PartialEq)]
+pub struct ChatSidebarProps {
+ pub messages: Vec,
+ pub on_send_message: Callback,
+}
+
+#[function_component(ChatSidebar)]
+pub fn chat_sidebar(props: &ChatSidebarProps) -> Html {
+ let input_ref = use_node_ref();
+ let message_input = use_state(|| String::new());
+
+ let on_input_change = {
+ let message_input = message_input.clone();
+ Callback::from(move |e: InputEvent| {
+ let input: HtmlTextAreaElement = e.target_unchecked_into();
+ message_input.set(input.value());
+ })
+ };
+
+ let on_send = {
+ let message_input = message_input.clone();
+ let on_send_message = props.on_send_message.clone();
+ let input_ref = input_ref.clone();
+ Callback::from(move |e: KeyboardEvent| {
+ if e.key() == "Enter" && !e.shift_key() {
+ e.prevent_default();
+ let content = (*message_input).trim().to_string();
+ if !content.is_empty() {
+ on_send_message.emit(content);
+ message_input.set(String::new());
+ if let Some(input) = input_ref.cast::() {
+ input.set_value("");
+ }
+ }
+ }
+ })
+ };
+
+ let on_send_button = {
+ let message_input = message_input.clone();
+ let on_send_message = props.on_send_message.clone();
+ let input_ref = input_ref.clone();
+ Callback::from(move |_: MouseEvent| {
+ let content = (*message_input).trim().to_string();
+ if !content.is_empty() {
+ on_send_message.emit(content);
+ message_input.set(String::new());
+ if let Some(input) = input_ref.cast::() {
+ input.set_value("");
+ }
+ }
+ })
+ };
+
+ html! {
+
+ }
+}
diff --git a/examples/meet/src/components/controls.rs b/examples/meet/src/components/controls.rs
new file mode 100644
index 0000000..93a994d
--- /dev/null
+++ b/examples/meet/src/components/controls.rs
@@ -0,0 +1,92 @@
+use yew::prelude::*;
+
+#[derive(Properties, PartialEq)]
+pub struct MediaControlsProps {
+ pub audio_enabled: bool,
+ pub video_enabled: bool,
+ pub screen_share_enabled: bool,
+ pub on_toggle_audio: Callback<()>,
+ pub on_toggle_video: Callback<()>,
+ pub on_toggle_screen_share: Callback<()>,
+ pub on_toggle_chat: Callback<()>,
+ pub on_toggle_settings: Callback<()>,
+ pub on_leave_room: Callback<()>,
+}
+
+#[function_component(MediaControls)]
+pub fn media_controls(props: &MediaControlsProps) -> Html {
+ html! {
+
+ // Audio toggle
+
+
+ // Video toggle
+
+
+ // Screen share toggle
+
+
+ // Chat toggle
+
+
+ // Settings toggle
+
+
+ // Leave room
+
+
+ }
+}
diff --git a/examples/meet/src/components/mod.rs b/examples/meet/src/components/mod.rs
new file mode 100644
index 0000000..c67b349
--- /dev/null
+++ b/examples/meet/src/components/mod.rs
@@ -0,0 +1,11 @@
+pub mod video_tile;
+pub mod chat;
+pub mod settings_menu;
+pub mod controls;
+pub mod prejoin;
+
+pub use video_tile::VideoTile;
+pub use chat::ChatSidebar;
+pub use settings_menu::SettingsMenu;
+pub use controls::MediaControls;
+pub use prejoin::PreJoin;
diff --git a/examples/meet/src/components/prejoin.rs b/examples/meet/src/components/prejoin.rs
new file mode 100644
index 0000000..cf47f50
--- /dev/null
+++ b/examples/meet/src/components/prejoin.rs
@@ -0,0 +1,445 @@
+use yew::prelude::*;
+use web_sys::{HtmlInputElement, HtmlSelectElement, HtmlVideoElement, MediaDevices, MediaStreamConstraints};
+use wasm_bindgen_futures::spawn_local;
+use wasm_bindgen::prelude::*;
+use wasm_bindgen::JsCast;
+use js_sys::Object;
+
+use crate::pages::room::LocalUserChoices;
+
+#[derive(Properties, PartialEq)]
+pub struct PreJoinProps {
+ pub defaults: LocalUserChoices,
+ pub on_submit: Callback,
+ pub on_error: Callback,
+ pub loading: bool,
+}
+
+#[function_component(PreJoin)]
+pub fn prejoin(props: &PreJoinProps) -> Html {
+ let username = use_state(|| props.defaults.username.clone());
+ let video_enabled = use_state(|| props.defaults.video_enabled);
+ let audio_enabled = use_state(|| props.defaults.audio_enabled);
+ let video_devices = use_state(|| Vec::<(String, String)>::new()); // (id, label)
+ let audio_devices = use_state(|| Vec::<(String, String)>::new());
+ let selected_video_device = use_state(|| props.defaults.video_device_id.clone());
+ let selected_audio_device = use_state(|| props.defaults.audio_device_id.clone());
+ let preview_stream = use_state(|| None::);
+ let video_ref = use_node_ref();
+
+
+ // Load available devices on mount
+ use_effect_with((), {
+ let video_devices = video_devices.clone();
+ let audio_devices = audio_devices.clone();
+ let on_error = props.on_error.clone();
+
+ move |_| {
+ spawn_local(async move {
+ match get_media_devices().await {
+ Ok((video_devs, audio_devs)) => {
+ video_devices.set(video_devs);
+ audio_devices.set(audio_devs);
+ }
+ Err(e) => {
+ on_error.emit(format!("Failed to get media devices: {}", e));
+ }
+ }
+ });
+ || ()
+ }
+ });
+
+ // Setup preview stream when video is enabled
+ use_effect_with((*video_enabled, selected_video_device.clone()), {
+ let preview_stream = preview_stream.clone();
+ let on_error = props.on_error.clone();
+
+ move |(enabled, device_id)| {
+ if *enabled {
+ let preview_stream = preview_stream.clone();
+ let device_id = device_id.clone();
+ let on_error = on_error.clone();
+
+ spawn_local(async move {
+ match get_user_media((*device_id).clone(), None).await {
+ Ok(stream) => {
+ preview_stream.set(Some(stream));
+ }
+ Err(e) => {
+ on_error.emit(format!("Failed to access camera: {}", e));
+ }
+ }
+ });
+ } else {
+ // Stop existing stream
+ if let Some(stream) = &*preview_stream {
+ let tracks = stream.get_tracks();
+ for i in 0..tracks.length() {
+ let track_js = tracks.get(i);
+ if let Ok(track) = track_js.dyn_into::() {
+ track.stop();
+ }
+ }
+ }
+ preview_stream.set(None);
+ }
+ || ()
+ }
+ });
+
+ // Assign stream to video element when stream changes
+ use_effect_with(preview_stream.clone(), {
+ let video_ref = video_ref.clone();
+
+ move |stream| {
+ if let Some(video_element) = video_ref.cast::() {
+ if let Some(stream) = stream.as_ref() {
+ video_element.set_src_object(Some(stream));
+ let _ = video_element.play();
+ } else {
+ video_element.set_src_object(None);
+ }
+ }
+ || ()
+ }
+ });
+
+ let on_username_change = {
+ let username = username.clone();
+ Callback::from(move |e: Event| {
+ let input: HtmlInputElement = e.target_unchecked_into();
+ username.set(input.value());
+ })
+ };
+
+ let on_video_toggle = {
+ let video_enabled = video_enabled.clone();
+ Callback::from(move |e: Event| {
+ let input: HtmlInputElement = e.target_unchecked_into();
+ video_enabled.set(input.checked());
+ })
+ };
+
+ let on_audio_toggle = {
+ let audio_enabled = audio_enabled.clone();
+ Callback::from(move |e: Event| {
+ let input: HtmlInputElement = e.target_unchecked_into();
+ audio_enabled.set(input.checked());
+ })
+ };
+
+ let on_video_device_change = {
+ let selected_video_device = selected_video_device.clone();
+ Callback::from(move |e: Event| {
+ let select: HtmlSelectElement = e.target_unchecked_into();
+ let value = if select.value().is_empty() { None } else { Some(select.value()) };
+ selected_video_device.set(value);
+ })
+ };
+
+ let on_audio_device_change = {
+ let selected_audio_device = selected_audio_device.clone();
+ Callback::from(move |e: Event| {
+ let select: HtmlSelectElement = e.target_unchecked_into();
+ let value = if select.value().is_empty() { None } else { Some(select.value()) };
+ selected_audio_device.set(value);
+ })
+ };
+
+ let on_submit = {
+ let username = username.clone();
+ let video_enabled = video_enabled.clone();
+ let audio_enabled = audio_enabled.clone();
+ let selected_video_device = selected_video_device.clone();
+ let selected_audio_device = selected_audio_device.clone();
+ let on_submit = props.on_submit.clone();
+ let on_error = props.on_error.clone();
+
+ Callback::from(move |e: SubmitEvent| {
+ e.prevent_default();
+
+ if username.trim().is_empty() {
+ on_error.emit("Please enter your name".to_string());
+ return;
+ }
+
+ let choices = LocalUserChoices {
+ username: username.trim().to_string(),
+ video_enabled: *video_enabled,
+ audio_enabled: *audio_enabled,
+ video_device_id: (*selected_video_device).clone(),
+ audio_device_id: (*selected_audio_device).clone(),
+ };
+
+ on_submit.emit(choices);
+ })
+ };
+
+ html! {
+
+
{"Join Meeting"}
+
+
+ {if *video_enabled && preview_stream.is_some() {
+ html! {
+
+ }
+ } else {
+ html! {
+
+ {if *video_enabled {
+ "Loading camera..."
+ } else {
+ "Camera off"
+ }}
+
+ }
+ }}
+
+
+
+
+ }
+}
+
+async fn get_media_devices() -> Result<(Vec<(String, String)>, Vec<(String, String)>), String> {
+ let window = web_sys::window().ok_or("No window object")?;
+ let navigator = window.navigator();
+ let media_devices = navigator
+ .media_devices()
+ .map_err(|_| "MediaDevices not supported")?;
+
+ let promise = media_devices.enumerate_devices()
+ .map_err(|_| "Failed to enumerate devices")?;
+ let devices = wasm_bindgen_futures::JsFuture::from(promise)
+ .await
+ .map_err(|_| "Failed to enumerate devices")?;
+
+ let devices: js_sys::Array = devices.into();
+ let mut video_devices = Vec::new();
+ let mut audio_devices = Vec::new();
+
+ for i in 0..devices.length() {
+ if let Some(device) = devices.get(i).dyn_into::().ok() {
+ let device_id = device.device_id();
+ let label = device.label();
+ let kind = device.kind();
+
+ match kind {
+ web_sys::MediaDeviceKind::Videoinput => {
+ video_devices.push((device_id, if label.is_empty() { "Camera".to_string() } else { label }));
+ }
+ web_sys::MediaDeviceKind::Audioinput => {
+ audio_devices.push((device_id, if label.is_empty() { "Microphone".to_string() } else { label }));
+ }
+ _ => {}
+ }
+ }
+ }
+
+ Ok((video_devices, audio_devices))
+}
+
+async fn get_user_media(video_device_id: Option, audio_device_id: Option) -> Result {
+ let window = web_sys::window().ok_or("No window object")?;
+ let navigator = window.navigator();
+ let media_devices = navigator
+ .media_devices()
+ .map_err(|_| "MediaDevices not supported")?;
+
+ // First, check if devices are available
+ log::info!("Checking available media devices...");
+ match get_media_devices().await {
+ Ok((video_devices, audio_devices)) => {
+ log::info!("Found {} video devices and {} audio devices", video_devices.len(), audio_devices.len());
+ if video_devices.is_empty() {
+ return Err("No video devices found. Please connect a camera and refresh the page.".to_string());
+ }
+ }
+ Err(e) => {
+ log::warn!("Could not enumerate devices: {}", e);
+ }
+ }
+
+ let constraints = web_sys::MediaStreamConstraints::new();
+
+ // Try different constraint strategies
+ let video_constraints = if let Some(device_id) = video_device_id {
+ log::info!("Using specific video device: {}", device_id);
+ let video_constraints = Object::new();
+ js_sys::Reflect::set(&video_constraints, &"deviceId".into(), &device_id.into())
+ .map_err(|_| "Failed to set video device constraint")?;
+ video_constraints
+ } else {
+ log::info!("Using default video constraints");
+ // Try with minimal constraints first
+ let video_constraints = Object::new();
+ js_sys::Reflect::set(&video_constraints, &"width".into(), &320.into())
+ .map_err(|_| "Failed to set video width")?;
+ js_sys::Reflect::set(&video_constraints, &"height".into(), &240.into())
+ .map_err(|_| "Failed to set video height")?;
+ video_constraints
+ };
+
+ constraints.set_video(&video_constraints.into());
+
+ // Set audio constraints
+ if let Some(device_id) = audio_device_id {
+ let audio_constraints = Object::new();
+ js_sys::Reflect::set(&audio_constraints, &"deviceId".into(), &device_id.into())
+ .map_err(|_| "Failed to set audio device constraint")?;
+ constraints.set_audio(&audio_constraints.into());
+ } else {
+ constraints.set_audio(&true.into());
+ }
+
+ log::info!("Requesting user media with constraints");
+
+ // Try getUserMedia with fallback strategies
+ match try_get_user_media(&media_devices, &constraints).await {
+ Ok(stream) => {
+ log::info!("Successfully obtained media stream");
+ Ok(stream)
+ }
+ Err(first_error) => {
+ log::warn!("First attempt failed: {}", first_error);
+
+ // Fallback: Try with just video, no specific constraints
+ log::info!("Trying fallback: basic video only");
+ let fallback_constraints = web_sys::MediaStreamConstraints::new();
+ fallback_constraints.set_video(&true.into());
+ fallback_constraints.set_audio(&false.into());
+
+ match try_get_user_media(&media_devices, &fallback_constraints).await {
+ Ok(stream) => {
+ log::info!("Fallback successful - got video-only stream");
+ Ok(stream)
+ }
+ Err(second_error) => {
+ log::error!("All attempts failed. First error: {}, Fallback error: {}", first_error, second_error);
+ Err(format!("Camera access failed. Please check:\n1. Camera is connected and not in use\n2. Browser permissions are granted\n3. Page is served over HTTPS\n\nError details: {}", first_error))
+ }
+ }
+ }
+ }
+}
+
+async fn try_get_user_media(media_devices: &web_sys::MediaDevices, constraints: &web_sys::MediaStreamConstraints) -> Result {
+ let promise = media_devices.get_user_media_with_constraints(constraints)
+ .map_err(|e| format!("Failed to create getUserMedia promise: {:?}", e))?;
+
+ let stream = wasm_bindgen_futures::JsFuture::from(promise)
+ .await
+ .map_err(|e| format!("getUserMedia failed: {:?}", e))?;
+
+ Ok(stream.into())
+}
diff --git a/examples/meet/src/components/settings_menu.rs b/examples/meet/src/components/settings_menu.rs
new file mode 100644
index 0000000..7f8821e
--- /dev/null
+++ b/examples/meet/src/components/settings_menu.rs
@@ -0,0 +1,222 @@
+use yew::prelude::*;
+use web_sys::HtmlSelectElement;
+use wasm_bindgen::JsCast;
+
+#[derive(Clone, PartialEq)]
+pub enum SettingsTab {
+ Media,
+ Recording,
+}
+
+#[derive(Properties, PartialEq)]
+pub struct SettingsMenuProps {
+ pub on_close: Callback<()>,
+}
+
+#[function_component(SettingsMenu)]
+pub fn settings_menu(props: &SettingsMenuProps) -> Html {
+ let active_tab = use_state(|| SettingsTab::Media);
+ let video_devices = use_state(|| Vec::<(String, String)>::new());
+ let audio_devices = use_state(|| Vec::<(String, String)>::new());
+ let audio_output_devices = use_state(|| Vec::<(String, String)>::new());
+ let selected_video_device = use_state(|| String::new());
+ let selected_audio_device = use_state(|| String::new());
+ let selected_audio_output = use_state(|| String::new());
+
+ // Load devices on mount
+ use_effect_with((), {
+ let video_devices = video_devices.clone();
+ let audio_devices = audio_devices.clone();
+ let audio_output_devices = audio_output_devices.clone();
+
+ move |_| {
+ wasm_bindgen_futures::spawn_local(async move {
+ if let Ok((video_devs, audio_devs, output_devs)) = get_all_media_devices().await {
+ video_devices.set(video_devs);
+ audio_devices.set(audio_devs);
+ audio_output_devices.set(output_devs);
+ }
+ });
+ || ()
+ }
+ });
+
+ let on_tab_change = {
+ let active_tab = active_tab.clone();
+ Callback::from(move |tab: SettingsTab| {
+ active_tab.set(tab);
+ })
+ };
+
+ let on_video_device_change = {
+ let selected_video_device = selected_video_device.clone();
+ Callback::from(move |e: Event| {
+ let select: HtmlSelectElement = e.target_unchecked_into();
+ selected_video_device.set(select.value());
+ // TODO: Update LiveKit video device
+ })
+ };
+
+ let on_audio_device_change = {
+ let selected_audio_device = selected_audio_device.clone();
+ Callback::from(move |e: Event| {
+ let select: HtmlSelectElement = e.target_unchecked_into();
+ selected_audio_device.set(select.value());
+ // TODO: Update LiveKit audio device
+ })
+ };
+
+ let on_audio_output_change = {
+ let selected_audio_output = selected_audio_output.clone();
+ Callback::from(move |e: Event| {
+ let select: HtmlSelectElement = e.target_unchecked_into();
+ selected_audio_output.set(select.value());
+ // TODO: Update LiveKit audio output device
+ })
+ };
+
+ html! {
+
+ }
+}
+
+async fn get_all_media_devices() -> Result<(Vec<(String, String)>, Vec<(String, String)>, Vec<(String, String)>), String> {
+ let window = web_sys::window().ok_or("No window object")?;
+ let navigator = window.navigator();
+ let media_devices = navigator
+ .media_devices()
+ .map_err(|_| "MediaDevices not supported")?;
+
+ let promise = media_devices.enumerate_devices()
+ .map_err(|_| "Failed to enumerate devices")?;
+ let devices = wasm_bindgen_futures::JsFuture::from(promise)
+ .await
+ .map_err(|_| "Failed to enumerate devices")?;
+
+ let devices: js_sys::Array = devices.into();
+ let mut video_devices = Vec::new();
+ let mut audio_devices = Vec::new();
+ let mut audio_output_devices = Vec::new();
+
+ for i in 0..devices.length() {
+ if let Some(device) = devices.get(i).dyn_into::().ok() {
+ let device_id = device.device_id();
+ let label = device.label();
+ let kind = device.kind();
+
+ match kind {
+ web_sys::MediaDeviceKind::Videoinput => {
+ video_devices.push((device_id, if label.is_empty() { "Camera".to_string() } else { label }));
+ }
+ web_sys::MediaDeviceKind::Audioinput => {
+ audio_devices.push((device_id, if label.is_empty() { "Microphone".to_string() } else { label }));
+ }
+ web_sys::MediaDeviceKind::Audiooutput => {
+ audio_output_devices.push((device_id, if label.is_empty() { "Speaker".to_string() } else { label }));
+ }
+ _ => {}
+ }
+ }
+ }
+
+ Ok((video_devices, audio_devices, audio_output_devices))
+}
diff --git a/examples/meet/src/components/video_tile.rs b/examples/meet/src/components/video_tile.rs
new file mode 100644
index 0000000..37cf840
--- /dev/null
+++ b/examples/meet/src/components/video_tile.rs
@@ -0,0 +1,161 @@
+use yew::prelude::*;
+use web_sys::{HtmlVideoElement, MediaStream, MediaStreamConstraints};
+use wasm_bindgen::prelude::*;
+use wasm_bindgen_futures::JsFuture;
+
+use crate::pages::room::Participant;
+
+#[derive(Properties, PartialEq)]
+pub struct VideoTileProps {
+ pub participant: Participant,
+}
+
+#[function_component(VideoTile)]
+pub fn video_tile(props: &VideoTileProps) -> Html {
+ let video_ref = use_node_ref();
+ let participant = &props.participant;
+
+ // Initialize video element and capture media
+ use_effect_with(participant.clone(), {
+ let video_ref = video_ref.clone();
+ move |participant| {
+ if let Some(video_element) = video_ref.cast::() {
+ if participant.is_local && participant.video_enabled {
+ // Set up local video stream using getUserMedia
+ wasm_bindgen_futures::spawn_local(async move {
+ match get_user_media().await {
+ Ok(stream) => {
+ video_element.set_src_object(Some(&stream));
+ web_sys::console::log_1(&"Local video stream set up successfully".into());
+ }
+ Err(e) => {
+ web_sys::console::error_1(&format!("Failed to get user media: {:?}", e).into());
+ }
+ }
+ });
+ } else if !participant.is_local && participant.video_enabled {
+ // For remote participants, attach actual LiveKit video tracks
+ web_sys::console::log_1(&format!("Setting up remote video for participant: {}", participant.id).into());
+ // Note: Track attachment will be handled by the room page when tracks are available
+ // This is just a placeholder for the video element
+ }
+ }
+ || ()
+ }
+ });
+
+ html! {
+
+ // Always render video element for remote participants to allow track attachment
+
+
+ {if !participant.video_enabled {
+ html! {
+
+
+ {"Video disabled"}
+
+ }
+ } else {
+ html! {}
+ }}
+
+
+
+ {&participant.name}
+ {if participant.is_local {
+ html! { {" (You)"} }
+ } else {
+ html! {}
+ }}
+
+
+
+ {if !participant.audio_enabled {
+ html! {
+
+ {"🔇"}
+
+ }
+ } else {
+ html! {}
+ }}
+
+ {if !participant.video_enabled {
+ html! {
+
+ {"📷"}
+
+ }
+ } else {
+ html! {}
+ }}
+
+ {if participant.screen_share_enabled {
+ html! {
+
+ {"🖥️"}
+
+ }
+ } else {
+ html! {}
+ }}
+
+
+
+ }
+}
+
+async fn get_user_media() -> Result {
+ let window = web_sys::window().ok_or("No window object")?;
+ let navigator = window.navigator();
+ let media_devices = navigator.media_devices()?;
+
+ let constraints = MediaStreamConstraints::new();
+ constraints.set_video(&JsValue::from(true));
+ constraints.set_audio(&JsValue::from(true));
+
+ // Try getUserMedia with fallback strategies
+ match try_get_user_media(&media_devices, &constraints).await {
+ Ok(stream) => {
+ web_sys::console::log_1(&"Successfully obtained media stream".into());
+ Ok(stream)
+ }
+ Err(first_error) => {
+ web_sys::console::warn_1(&format!("First attempt failed: {:?}", first_error).into());
+
+ // Fallback: Try with just video, no audio
+ web_sys::console::log_1(&"Trying fallback: basic video only".into());
+ let fallback_constraints = MediaStreamConstraints::new();
+ fallback_constraints.set_video(&JsValue::from(true));
+ fallback_constraints.set_audio(&JsValue::from(false));
+
+ match try_get_user_media(&media_devices, &fallback_constraints).await {
+ Ok(stream) => {
+ web_sys::console::log_1(&"Fallback successful - got video-only stream".into());
+ Ok(stream)
+ }
+ Err(second_error) => {
+ web_sys::console::error_1(&format!("All attempts failed. First error: {:?}, Fallback error: {:?}", first_error, second_error).into());
+ Err(JsValue::from_str(&format!("Camera access failed: {:?}", first_error)))
+ }
+ }
+ }
+ }
+}
+
+async fn try_get_user_media(media_devices: &web_sys::MediaDevices, constraints: &MediaStreamConstraints) -> Result {
+ let promise = media_devices.get_user_media_with_constraints(constraints)?;
+ let js_value = JsFuture::from(promise).await?;
+ let media_stream: MediaStream = js_value.dyn_into()?;
+ Ok(media_stream)
+}
+
diff --git a/examples/meet/src/livekit_client.rs b/examples/meet/src/livekit_client.rs
new file mode 100644
index 0000000..25a4019
--- /dev/null
+++ b/examples/meet/src/livekit_client.rs
@@ -0,0 +1,185 @@
+use wasm_bindgen::prelude::*;
+use web_sys::{WebSocket, MessageEvent, CloseEvent, Event, BinaryType, ErrorEvent};
+use wasm_bindgen_futures::spawn_local;
+use std::collections::HashMap;
+use std::rc::Rc;
+use std::cell::RefCell;
+use yew::UseStateHandle;
+use crate::livekit_protocol::LiveKitProtocolHandler;
+// Removed circular dependency - types moved to separate module if needed
+
+#[derive(Clone)]
+pub struct LiveKitClient {
+ pub participants: HashMap,
+ pub local_participant: Option,
+ pub room_name: String,
+ pub connected: bool,
+ pub websocket: Option,
+ pub protocol_handler: Rc>,
+ pub event_callback: Option>,
+}
+
+#[derive(Clone, Debug)]
+pub struct RemoteParticipant {
+ pub sid: String,
+ pub identity: String,
+ pub name: String,
+ pub audio_enabled: bool,
+ pub video_enabled: bool,
+}
+
+#[derive(Clone, Debug)]
+pub struct LocalParticipant {
+ pub identity: String,
+ pub name: String,
+}
+
+#[derive(Debug, Clone)]
+pub enum LiveKitEvent {
+ ParticipantConnected(RemoteParticipant),
+ ParticipantDisconnected(String),
+ TrackSubscribed { participant_sid: String, track_kind: String },
+ TrackUnsubscribed { participant_sid: String, track_kind: String },
+ Connected,
+ Disconnected,
+}
+
+impl LiveKitClient {
+ pub fn new(room_name: String) -> Self {
+ Self {
+ participants: HashMap::new(),
+ local_participant: None,
+ room_name,
+ connected: false,
+ websocket: None,
+ protocol_handler: Rc::new(RefCell::new(LiveKitProtocolHandler::new())),
+ event_callback: None,
+ }
+ }
+
+ pub fn set_event_callback(&mut self, callback: F)
+ where
+ F: Fn(crate::livekit_protocol::LiveKitEvent) + 'static,
+ {
+ self.event_callback = Some(Rc::new(callback));
+ }
+
+ pub async fn connect(&mut self, url: &str, token: &str) -> Result<(), String> {
+ web_sys::console::log_1(&format!("Connecting to LiveKit: {}", url).into());
+
+ // Create WebSocket URL for LiveKit
+ let ws_url = if url.starts_with("wss://") || url.starts_with("ws://") {
+ url.to_string()
+ } else if url.starts_with("https://") {
+ url.replace("https://", "wss://")
+ } else if url.starts_with("http://") {
+ url.replace("http://", "ws://")
+ } else {
+ format!("wss://{}", url)
+ };
+
+ let ws_url = format!("{}/rtc?access_token={}", ws_url, token);
+ web_sys::console::log_1(&format!("Connecting to LiveKit WebSocket: {}", ws_url).into());
+ web_sys::console::log_1(&format!("Server URL: {}, Token: {}", url, token).into());
+
+ let ws = WebSocket::new(&ws_url)
+ .map_err(|e| format!("Failed to create WebSocket: {:?}", e))?;
+
+ // Set binary type for protobuf messages
+ ws.set_binary_type(web_sys::BinaryType::Arraybuffer);
+
+ let ws_clone = ws.clone();
+ let onopen_callback = Closure::wrap(Box::new(move |_event: Event| {
+ web_sys::console::log_1(&"LiveKit WebSocket connected, sending join request".into());
+
+ // Send join request immediately after connection
+ match crate::livekit_protocol::LiveKitProtocolHandler::create_join_request("room", "user", "User") {
+ Ok(join_data) => {
+ if let Err(e) = ws_clone.send_with_u8_array(&join_data) {
+ web_sys::console::error_1(&format!("Failed to send join request: {:?}", e).into());
+ } else {
+ web_sys::console::log_1(&"Join request sent successfully".into());
+ }
+ },
+ Err(e) => {
+ web_sys::console::error_1(&format!("Failed to create join request: {}", e).into());
+ }
+ }
+ }) as Box);
+ ws.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
+ onopen_callback.forget();
+
+ let protocol_handler = self.protocol_handler.clone();
+ let event_callback = self.event_callback.clone();
+
+ let onmessage_callback = Closure::wrap(Box::new(move |event: MessageEvent| {
+ web_sys::console::log_1(&"LiveKit WebSocket message received".into());
+
+ if let Ok(array_buffer) = event.data().dyn_into::() {
+ let uint8_array = js_sys::Uint8Array::new(&array_buffer);
+ let bytes = uint8_array.to_vec();
+
+ web_sys::console::log_1(&format!("Received {} bytes from LiveKit", bytes.len()).into());
+
+ // Parse protobuf message
+ match protocol_handler.try_borrow_mut() {
+ Ok(mut handler_ref) => {
+ web_sys::console::log_1(&"Parsing protobuf message".into());
+ if let Some(events) = handler_ref.handle_message(&bytes) {
+ web_sys::console::log_1(&format!("Generated {} LiveKit events", events.len()).into());
+ for event in events {
+ web_sys::console::log_1(&format!("Processing event: {:?}", event).into());
+ if let Some(callback) = event_callback.as_ref() {
+ callback(event);
+ }
+ }
+ } else {
+ web_sys::console::log_1(&"Failed to parse protobuf message or no events generated".into());
+ }
+ }
+ Err(_) => {
+ web_sys::console::error_1(&"Could not borrow protocol handler".into());
+ }
+ }
+ } else if let Ok(text) = event.data().dyn_into::() {
+ let text_str: String = text.into();
+ web_sys::console::log_1(&format!("Received text message: {}", text_str).into());
+ } else {
+ web_sys::console::log_1(&"Received unknown message type".into());
+ }
+ }) as Box);
+ ws.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
+ onmessage_callback.forget();
+
+ let onerror_callback = Closure::wrap(Box::new(move |event: ErrorEvent| {
+ web_sys::console::error_1(&format!("LiveKit WebSocket error: {:?}", event.message()).into());
+ web_sys::console::error_1(&format!("Error details: {:?}", event).into());
+ }) as Box);
+ ws.set_onerror(Some(onerror_callback.as_ref().unchecked_ref()));
+ onerror_callback.forget();
+
+ let onclose_callback = Closure::wrap(Box::new(move |event: CloseEvent| {
+ web_sys::console::log_1(&format!("LiveKit WebSocket closed: code={}, reason={}, clean={}",
+ event.code(), event.reason(), event.was_clean()).into());
+ }) as Box);
+ ws.set_onclose(Some(onclose_callback.as_ref().unchecked_ref()));
+ onclose_callback.forget();
+
+ self.websocket = Some(ws);
+ self.connected = true;
+ self.local_participant = Some(LocalParticipant {
+ identity: "local_user".to_string(),
+ name: "Local User".to_string(),
+ });
+
+ Ok(())
+ }
+
+ pub fn get_participants(&self) -> Vec {
+ self.participants.values().cloned().collect()
+ }
+
+ pub fn is_connected(&self) -> bool {
+ self.connected
+ }
+}
diff --git a/examples/meet/src/livekit_js_bindings.rs b/examples/meet/src/livekit_js_bindings.rs
new file mode 100644
index 0000000..a9ba069
--- /dev/null
+++ b/examples/meet/src/livekit_js_bindings.rs
@@ -0,0 +1,183 @@
+use wasm_bindgen::prelude::*;
+use wasm_bindgen::JsCast;
+use web_sys::HtmlVideoElement;
+use js_sys::{Promise, Function};
+
+// LiveKit Room bindings
+#[wasm_bindgen]
+extern "C" {
+ #[wasm_bindgen(js_name = "Room", js_namespace = LivekitClient)]
+ pub type LiveKitRoom;
+
+ #[wasm_bindgen(constructor, js_namespace = LivekitClient, js_class = "Room")]
+ pub fn new(options: &JsValue) -> LiveKitRoom;
+
+ #[wasm_bindgen(method, js_name = connect)]
+ pub fn connect(this: &LiveKitRoom, url: &str, token: &str) -> Promise;
+
+ #[wasm_bindgen(method, js_name = disconnect)]
+ pub fn disconnect(this: &LiveKitRoom);
+
+ #[wasm_bindgen(method, js_name = on)]
+ pub fn on(this: &LiveKitRoom, event: &str, callback: &Function);
+
+ #[wasm_bindgen(method, js_name = publishTrack)]
+ pub fn publish_track(this: &LiveKitRoom, track: &JsValue, options: &JsValue) -> Promise;
+
+ #[wasm_bindgen(method, js_name = startVideo)]
+ pub fn start_video(this: &LiveKitRoom) -> Promise;
+
+ #[wasm_bindgen(method, js_name = startAudio)]
+ pub fn start_audio(this: &LiveKitRoom) -> Promise;
+
+ #[wasm_bindgen(method, getter, js_name = participants)]
+ pub fn participants(this: &LiveKitRoom) -> js_sys::Map;
+
+ #[wasm_bindgen(method, getter, js_name = audioTracks)]
+ pub fn audio_tracks(this: &LiveKitRoom) -> js_sys::Map;
+
+ #[wasm_bindgen(method, getter, js_name = videoTracks)]
+ pub fn video_tracks(this: &LiveKitRoom) -> js_sys::Map;
+
+ #[wasm_bindgen(method, getter, js_name = localParticipant)]
+ pub fn local_participant(this: &LiveKitRoom) -> LocalParticipant;
+}
+
+// LiveKit Participant bindings
+#[wasm_bindgen]
+extern "C" {
+ #[wasm_bindgen(js_name = "Participant", js_namespace = LivekitClient)]
+ pub type Participant;
+
+ #[wasm_bindgen(method, getter, js_name = sid)]
+ pub fn sid(this: &Participant) -> String;
+
+ #[wasm_bindgen(method, getter)]
+ pub fn identity(this: &Participant) -> String;
+
+ #[wasm_bindgen(method, getter, js_name = name)]
+ pub fn name(this: &Participant) -> Option;
+
+ #[wasm_bindgen(method, getter, js_name = videoTrackPublications)]
+ pub fn video_tracks(this: &Participant) -> js_sys::Map;
+
+ #[wasm_bindgen(method, getter, js_name = audioTrackPublications)]
+ pub fn audio_tracks(this: &Participant) -> js_sys::Map;
+
+ #[wasm_bindgen(method, js_name = setCameraEnabled)]
+ pub fn set_camera_enabled(this: &Participant, enabled: bool) -> Promise;
+
+ #[wasm_bindgen(method, js_name = setMicrophoneEnabled)]
+ pub fn set_microphone_enabled(this: &Participant, enabled: bool) -> Promise;
+
+ #[wasm_bindgen(method, js_name = on)]
+ fn on(this: &Participant, event: &str, callback: &Function);
+}
+
+// LiveKit LocalParticipant bindings (extends Participant)
+#[wasm_bindgen]
+extern "C" {
+ #[wasm_bindgen(js_name = "LocalParticipant", js_namespace = LivekitClient)]
+ pub type LocalParticipant;
+
+ #[wasm_bindgen(method, getter, js_name = sid)]
+ pub fn sid(this: &LocalParticipant) -> String;
+
+ #[wasm_bindgen(method, getter)]
+ pub fn identity(this: &LocalParticipant) -> String;
+
+ #[wasm_bindgen(method, getter, js_name = name)]
+ pub fn name(this: &LocalParticipant) -> Option;
+
+ #[wasm_bindgen(method, getter, js_name = videoTrackPublications)]
+ pub fn video_tracks(this: &LocalParticipant) -> js_sys::Map;
+
+ #[wasm_bindgen(method, getter, js_name = audioTrackPublications)]
+ pub fn audio_tracks(this: &LocalParticipant) -> js_sys::Map;
+
+ #[wasm_bindgen(method, js_name = setMicrophoneEnabled)]
+ pub fn set_microphone_enabled(this: &LocalParticipant, enabled: bool) -> Promise;
+
+ #[wasm_bindgen(method, js_name = setCameraEnabled)]
+ pub fn set_camera_enabled(this: &LocalParticipant, enabled: bool) -> Promise;
+
+ #[wasm_bindgen(method, js_name = setMicrophoneMuted)]
+ pub fn set_microphone_muted(this: &LocalParticipant, muted: bool) -> Promise;
+
+ #[wasm_bindgen(method, js_name = setCameraMuted)]
+ pub fn set_camera_muted(this: &LocalParticipant, muted: bool) -> Promise;
+
+ #[wasm_bindgen(method, js_name = setScreenShareEnabled)]
+ pub fn set_screen_share_enabled(this: &LocalParticipant, enabled: bool) -> Promise;
+
+ #[wasm_bindgen(method, js_name = publishData)]
+ pub fn publish_data(this: &LocalParticipant, data: &[u8], reliable: bool) -> Promise;
+}
+
+// LiveKit Track bindings
+#[wasm_bindgen]
+extern "C" {
+ #[wasm_bindgen(js_name = "Track", js_namespace = LivekitClient)]
+ pub type Track;
+
+ #[wasm_bindgen(method, getter, js_name = sid)]
+ pub fn sid(this: &Track) -> String;
+
+ #[wasm_bindgen(method, getter, js_name = kind)]
+ pub fn kind(this: &Track) -> String;
+
+ #[wasm_bindgen(method, js_name = attach)]
+ pub fn attach(this: &Track, element: &HtmlVideoElement);
+
+ #[wasm_bindgen(method, js_name = attach)]
+ pub fn attach_audio(this: &Track, element: &web_sys::HtmlAudioElement);
+
+ #[wasm_bindgen(method, js_name = detach)]
+ pub fn detach(this: &Track, element: &HtmlVideoElement);
+
+ #[wasm_bindgen(method, js_name = detach)]
+ pub fn detach_audio(this: &Track, element: &web_sys::HtmlAudioElement);
+}
+
+// LiveKit TrackPublication bindings
+#[wasm_bindgen]
+extern "C" {
+ #[wasm_bindgen(js_name = "TrackPublication", js_namespace = LivekitClient)]
+ pub type TrackPublication;
+
+ #[wasm_bindgen(method, getter, js_name = track)]
+ pub fn track(this: &TrackPublication) -> Option