diff --git a/Cargo.lock b/Cargo.lock index 0eb1114..aa28ad4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,6 +288,33 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "aws-lc-rs" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd82dba44d209fddb11c190e0a94b78651f95299598e472215667417a03ff1d" +dependencies = [ + "aws-lc-sys", + "mirai-annotations", + "paste", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7a4168111d7eb622a31b214057b8509c0a7e1794f44c546d742330dc793972" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libc", + "paste", +] + [[package]] name = "backtrace" version = "0.3.73" @@ -318,6 +345,29 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.68", + "which", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -441,6 +491,11 @@ name = "cc" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] [[package]] name = "cesu8" @@ -448,6 +503,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfb" version = "0.7.3" @@ -514,6 +578,26 @@ dependencies = [ "half", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a" +dependencies = [ + "cc", +] + [[package]] name = "cocoa" version = "0.25.0" @@ -1191,6 +1275,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "encoding_rs" version = "0.8.34" @@ -1393,6 +1483,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futf" version = "0.1.5" @@ -1739,6 +1835,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "global-hotkey" version = "0.5.5" @@ -2325,6 +2427,15 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[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 = "0.4.8" @@ -2382,6 +2493,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.70" @@ -2446,6 +2566,16 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.5", +] + [[package]] name = "libxdo" version = "0.6.0" @@ -2689,15 +2819,22 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi 0.3.9", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] +[[package]] +name = "mirai-annotations" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" + [[package]] name = "muda" version = "0.11.5" @@ -2731,7 +2868,7 @@ dependencies = [ ] [[package]] -name = "mumble-webtransport" +name = "mumble-web2" version = "0.1.0" dependencies = [ "anyhow", @@ -2754,6 +2891,8 @@ dependencies = [ "serde-wasm-bindgen 0.6.5", "serde_json", "sir", + "tokio", + "tokio-rustls", "tokio-util", "wasm-bindgen", "wasm-bindgen-futures", @@ -2891,16 +3030,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.9", - "libc", -] - [[package]] name = "num_enum" version = "0.5.11" @@ -3117,6 +3246,12 @@ dependencies = [ "windows-targets 0.52.5", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -3637,6 +3772,21 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rsass" version = "0.28.10" @@ -3688,6 +3838,21 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.23.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -3697,6 +3862,24 @@ dependencies = [ "base64", ] +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.18" @@ -3988,6 +4171,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook" version = "0.3.17" @@ -4135,6 +4324,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spinning" version = "0.1.0" @@ -4182,6 +4377,12 @@ dependencies = [ "quote", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.109" @@ -4381,26 +4582,25 @@ checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8" [[package]] name = "tokio" -version = "1.38.0" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "pin-project-lite", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", @@ -4417,6 +4617,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.11" @@ -4425,6 +4636,7 @@ checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "pin-project-lite", "tokio", @@ -4636,6 +4848,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.2" @@ -4908,6 +5126,18 @@ dependencies = [ "windows-core 0.52.0", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" @@ -4930,7 +5160,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -5442,6 +5672,12 @@ dependencies = [ "syn 2.0.68", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zvariant" version = "4.2.0" diff --git a/Cargo.toml b/Cargo.toml index b8b1a00..0053288 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mumble-webtransport" +name = "mumble-web2" version = "0.1.0" edition = "2021" @@ -13,7 +13,7 @@ futures = "0.3.30" merge-io = "0.3.0" mumble-protocol = { version = "0.5.0", package = "mumble-protocol-2x", default-features = false, features = ["asynchronous-codec"]} serde_json = "1.0.117" -tokio-util = { version = "0.7.11", features = ["codec"]} +tokio-util = { version = "0.7.11", features = ["codec", "compat"]} wasm-bindgen = { version = "0.2.92", optional = true } wasm-bindgen-futures = { version = "0.4.42", optional = true } wasm-streams = { version = "0.4.0", optional = true } @@ -29,7 +29,9 @@ markdown = "0.3.0" gloo-timers = { version = "0.3.0", features = ["futures"] } futures-channel = "0.3.30" sir = { version = "0.5.0", features = ["dioxus"] } +tokio = { version = "1.41.1", features = ["net", "rt"], optional = true } +tokio-rustls = { version = "0.26.0", optional = true } [features] web = ["dioxus/web", "dioxus-web", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "serde-wasm-bindgen", "js-sys", "web-sys"] -desktop = ["dioxus/desktop"] +desktop = ["dioxus/desktop", "tokio", "tokio-rustls"] diff --git a/src/imp/desktop.rs b/src/imp/desktop.rs index e23c5cb..33eab49 100644 --- a/src/imp/desktop.rs +++ b/src/imp/desktop.rs @@ -1,6 +1,22 @@ use crate::app::Command; -use dioxus::hooks::UnboundedReceiver; -use std::{fmt, io}; +use anyhow::Result; +use dioxus::hooks::{UnboundedReceiver, UnboundedSender}; +use mumble_protocol::control::{ClientControlCodec, ControlPacket}; +use mumble_protocol::Serverbound; +use std::net::ToSocketAddrs; +use std::{fmt, io, sync::Arc}; +use tokio::net::TcpStream; +use tokio::task::LocalSet; +use tokio_rustls::rustls; +use tokio_rustls::rustls::client::danger::{HandshakeSignatureValid, ServerCertVerifier}; +use tokio_rustls::rustls::pki_types::{CertificateDer, ServerName, UnixTime}; +use tokio_rustls::rustls::ClientConfig; +use tokio_rustls::rustls::DigitallySignedStruct; +use tokio_rustls::TlsConnector; +use tokio_util::compat::{TokioAsyncReadCompatExt as _, TokioAsyncWriteCompatExt as _}; + +pub use tokio::task::spawn_local as spawn; +pub use tokio::time::sleep; pub struct Error(anyhow::Error); @@ -40,7 +56,19 @@ impl fmt::Debug for Error { } } -pub struct AudioContext(); +pub struct AudioSystem(); + +impl AudioSystem { + pub fn new(sender: UnboundedSender>) -> Result { + dbg!("todo"); + Ok(AudioSystem()) + } + + pub fn create_player(&mut self) -> Result { + dbg!("todo"); + Ok(AudioPlayer()) + } +} pub struct AudioPlayer(); @@ -50,15 +78,90 @@ impl AudioPlayer { } } +#[derive(Debug)] +struct NoCertificateVerification; + +impl ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp: &[u8], + _now: UnixTime, + ) -> Result { + Ok(rustls::client::danger::ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + vec![ + rustls::SignatureScheme::RSA_PKCS1_SHA1, + rustls::SignatureScheme::ECDSA_SHA1_Legacy, + rustls::SignatureScheme::RSA_PKCS1_SHA256, + rustls::SignatureScheme::ECDSA_NISTP256_SHA256, + rustls::SignatureScheme::RSA_PKCS1_SHA384, + rustls::SignatureScheme::ECDSA_NISTP384_SHA384, + rustls::SignatureScheme::RSA_PKCS1_SHA512, + rustls::SignatureScheme::ECDSA_NISTP521_SHA512, + rustls::SignatureScheme::RSA_PSS_SHA256, + rustls::SignatureScheme::RSA_PSS_SHA384, + rustls::SignatureScheme::RSA_PSS_SHA512, + rustls::SignatureScheme::ED25519, + rustls::SignatureScheme::ED448, + ] + } +} + pub async fn network_connect( address: String, username: String, event_rx: &mut UnboundedReceiver, ) -> Result<(), Error> { - dbg!("todo"); - Ok(()) -} + let localset = LocalSet::new(); + let _guard = localset.enter(); -pub fn create_player(ctx: &AudioContext) -> Result { - Ok(AudioPlayer()) + let config = ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(Arc::new(NoCertificateVerification)) + .with_no_client_auth(); + + let connector = TlsConnector::from(Arc::new(config)); + + let addr = format!("{}:{}", address, 64738) + .to_socket_addrs()? + .next() + .unwrap(); + + let server_tcp = TcpStream::connect(addr).await?; + let server_stream = connector + //.connect("127.0.0.1".try_into()?, server_tcp) + .connect(address.try_into().map_err(anyhow::Error::from)?, server_tcp) + .await?; + let (read_server, write_server) = tokio::io::split(server_stream); + + let read_codec = ClientControlCodec::new(); + let write_codec = ClientControlCodec::new(); + + let mut reader = asynchronous_codec::FramedRead::new(read_server.compat(), read_codec); + let mut writer = asynchronous_codec::FramedWrite::new(write_server.compat_write(), write_codec); + + super::network_loop(username, event_rx, reader, writer).await } diff --git a/src/imp/web.rs b/src/imp/web.rs index d4be560..d3d12e6 100644 --- a/src/imp/web.rs +++ b/src/imp/web.rs @@ -1,26 +1,24 @@ use crate::app::Command; use crate::bail; use dioxus::prelude::*; -use futures::select; -use futures::FutureExt; -use futures::SinkExt; -use futures::StreamExt; use futures_channel::mpsc::UnboundedSender; use gloo_timers::future::TimeoutFuture; +use mumble_protocol::control::ClientControlCodec; use mumble_protocol::control::ControlPacket; -use mumble_protocol::control::{msgs, ClientControlCodec}; use mumble_protocol::voice::VoicePacket; use mumble_protocol::voice::VoicePacketPayload; -use std::collections::HashMap; +use mumble_protocol::Serverbound; use std::fmt; +use std::io; +use std::time::Duration; use wasm_bindgen::prelude::*; -use wasm_bindgen_futures::spawn_local as spawn; use wasm_bindgen_futures::JsFuture; use web_sys::console; use web_sys::js_sys::Promise; use web_sys::js_sys::Reflect; use web_sys::js_sys::Uint8Array; use web_sys::window; +use web_sys::AudioContext; use web_sys::AudioContextOptions; use web_sys::AudioData; use web_sys::AudioDecoder; @@ -43,7 +41,11 @@ use web_sys::WebTransportBidirectionalStream; use web_sys::WebTransportOptions; use web_sys::WorkletOptions; -pub use web_sys::AudioContext; +pub use wasm_bindgen_futures::spawn_local as spawn; + +pub async fn sleep(d: Duration) { + TimeoutFuture::new(d.as_millis() as u32).await +} pub struct Error(JsValue); @@ -53,6 +55,12 @@ impl From for Error { } } +impl From for Error { + fn from(value: io::Error) -> Self { + Error(JsError::new(&value.to_string()).into()) + } +} + impl From for Error { fn from(value: JsValue) -> Self { Error(value) @@ -91,6 +99,83 @@ impl fmt::Debug for Error { } } +pub struct AudioSystem(AudioContext); + +impl AudioSystem { + pub fn new(sender: UnboundedSender>) -> Result { + // Create MediaStreams to playback decoded audio + // The audio context is used to reproduce audio. + let audio_context = configure_audio_context(); + + let audio_context_worklet = audio_context.clone(); + spawn(async move { + match create_encoder_worklet(&audio_context_worklet, sender).await { + Ok(node) => console::log_2(&"Created audio worklet:".into(), &node), + Err(err) => err.log(), + } + }); + + Ok(AudioSystem(audio_context)) + } + + pub fn create_player(&mut self) -> Result { + let audio_context = &self.0; + + let audio_stream_generator = + MediaStreamTrackGenerator::new(&MediaStreamTrackGeneratorInit::new("audio"))?; + + // Create MediaStream from MediaStreamTrackGenerator + let js_tracks = web_sys::js_sys::Array::new(); + js_tracks.push(&audio_stream_generator); + let media_stream = MediaStream::new_with_tracks(&js_tracks)?; + + // Create MediaStreamAudioSourceNode + let audio_source = audio_context.create_media_stream_source(&media_stream)?; + // Connect output of audio_source to audio_context (browser audio) + audio_source.connect_with_audio_node(&audio_context.destination())?; + + // Create callback functions for AudioDecoder + let error = Closure::wrap(Box::new(move |e: JsValue| { + console::error_1(&e); + }) as Box); + + // This knows what MediaStreamTrackGenerator to use as it closes around it + let output = Closure::wrap(Box::new(move |audio_data: AudioData| { + let writable = audio_stream_generator.writable(); + if writable.locked() { + return; + } + if let Err(e) = writable.get_writer().map(|writer| { + spawn(async move { + if let Err(e) = JsFuture::from(writer.ready()).await { + console::error_1(&format!("write chunk ready error {:?}", e).into()); + } + if let Err(e) = JsFuture::from(writer.write_with_chunk(&audio_data)).await { + console::error_1(&format!("write chunk error {:?}", e).into()); + }; + writer.release_lock(); + }); + }) { + console::error_1(&e); + } + }) as Box); + + let audio_decoder = AudioDecoder::new(&AudioDecoderInit::new( + error.as_ref().unchecked_ref(), + output.as_ref().unchecked_ref(), + ))?; + + audio_decoder.configure(&AudioDecoderConfig::new("opus", 1, 48000)); + console::log_1(&"Created Audio Decoder".into()); + + // This is required to prevent these from being deallocated + error.forget(); + output.forget(); + + Ok(AudioPlayer(audio_decoder)) + } +} + pub struct AudioPlayer(AudioDecoder); impl AudioPlayer { @@ -245,61 +330,6 @@ async fn create_encoder_worklet( Ok(worklet_node) } -pub fn create_player(audio_context: &AudioContext) -> Result { - let audio_stream_generator = - MediaStreamTrackGenerator::new(&MediaStreamTrackGeneratorInit::new("audio"))?; - - // Create MediaStream from MediaStreamTrackGenerator - let js_tracks = web_sys::js_sys::Array::new(); - js_tracks.push(&audio_stream_generator); - let media_stream = MediaStream::new_with_tracks(&js_tracks)?; - - // Create MediaStreamAudioSourceNode - let audio_source = audio_context.create_media_stream_source(&media_stream)?; - // Connect output of audio_source to audio_context (browser audio) - audio_source.connect_with_audio_node(&audio_context.destination())?; - - // Create callback functions for AudioDecoder - let error = Closure::wrap(Box::new(move |e: JsValue| { - console::error_1(&e); - }) as Box); - - // This knows what MediaStreamTrackGenerator to use as it closes around it - let output = Closure::wrap(Box::new(move |audio_data: AudioData| { - let writable = audio_stream_generator.writable(); - if writable.locked() { - return; - } - if let Err(e) = writable.get_writer().map(|writer| { - spawn(async move { - if let Err(e) = JsFuture::from(writer.ready()).await { - console::error_1(&format!("write chunk ready error {:?}", e).into()); - } - if let Err(e) = JsFuture::from(writer.write_with_chunk(&audio_data)).await { - console::error_1(&format!("write chunk error {:?}", e).into()); - }; - writer.release_lock(); - }); - }) { - console::error_1(&e); - } - }) as Box); - - let audio_decoder = AudioDecoder::new(&AudioDecoderInit::new( - error.as_ref().unchecked_ref(), - output.as_ref().unchecked_ref(), - ))?; - - audio_decoder.configure(&AudioDecoderConfig::new("opus", 1, 48000)); - console::log_1(&"Created Audio Decoder".into()); - - // This is required to prevent these from being deallocated - error.forget(); - output.forget(); - - Ok(AudioPlayer(audio_decoder)) -} - pub async fn network_connect( address: String, username: String, @@ -360,118 +390,10 @@ pub async fn network_connect( let read_codec = ClientControlCodec::new(); let write_codec = ClientControlCodec::new(); - let mut reader = + let reader = asynchronous_codec::FramedRead::new(wasm_stream_readable.into_async_read(), read_codec); - let mut writer = + let writer = asynchronous_codec::FramedWrite::new(wasm_stream_writable.into_async_write(), write_codec); - let (mut send_chan, mut writer_recv_chan) = futures_channel::mpsc::unbounded(); - - // Spawn worker to send packets. - spawn(async move { - while let Some(msg) = writer_recv_chan.next().await { - if !matches!(msg, ControlPacket::Ping(_) | ControlPacket::UDPTunnel(_)) { - console::log_1(&format!("sending {:#?}", msg).into()); - } - if let Err(e) = writer.send(msg).await { - console::error_1(&e.to_string().into()); - break; - } - } - }); - - // Get version packet - let version = match reader.next().await { - Some(Ok(v)) => v, - Some(Err(err)) => bail!("bad version packet: {err:?}"), - None => bail!("no version was recieved"), - }; - console::log_1(&"Got version packet".into()); - console::log_1(&format!("{:#?}", version).into()); - - // Send version packet - let mut msg = msgs::Version::new(); - msg.set_version(0x000010204); - msg.set_release(format!("{} {}", "mumbleweb2", "6.9.0")); - //msg.set_os("Chrome".to_string()); - send_chan.send(msg.into()).await.unwrap(); - - // Send authenticate packet - let mut msg = msgs::Authenticate::new(); - msg.set_username(username); - msg.set_opus(true); - send_chan.send(msg.into()).await.unwrap(); - - // Spawn worker to send pings - { - let mut send_chan = send_chan.clone(); - spawn(async move { - loop { - if let Err(e) = send_chan.send(msgs::Ping::new().into()).await { - console::log_1(&"could not ping".into()); - console::log_1(&e.to_string().into()); - break; - } - - TimeoutFuture::new(3000).await; - } - }); - } - - // Create MediaStreams to playback decoded audio - // The audio context is used to reproduce audio. - let audio_context = configure_audio_context(); - - let audio_context_worklet = audio_context.clone(); - let packet_sender_worklet = send_chan.clone(); - spawn(async move { - match create_encoder_worklet(&audio_context_worklet, packet_sender_worklet).await { - Ok(node) => console::log_2(&"Created audio worklet:".into(), &node), - Err(err) => err.log(), - } - }); - - // Create map of session_id -> AudioDecoder - let mut decoder_map = HashMap::new(); - - let mut reader_future = reader.next().fuse(); - let mut command_future = event_rx.next(); - loop { - select! { - packet = reader_future => { - reader_future = reader.next().fuse(); - match packet { - Some(Ok(msg)) => { - if !matches!(msg, ControlPacket::UDPTunnel(_) | ControlPacket::Ping(_)) { - console::log_1(&format!("receiving {:#?}", msg).into()); - } - let res = super::accept_packet(msg, &audio_context, &mut decoder_map); - if let Err(err) = res { - err.log(); - } - }, - Some(Err(err)) => console::error_1(&err.to_string().into()), - None => break, - } - } - command = command_future => { - command_future = event_rx.next(); - if let Some(command) = &command { - console::log_1(&format!("commanding {:#?}", command).into()); - } - match command { - Some(Command::Disconnect) => break, - Some(command) => { - let res = super::accept_command(command, &mut send_chan); - if let Err(err) = res { - err.log(); - } - } - None => continue, - } - } - } - } - let _ = send_chan.close(); - Ok(()) + super::network_loop(username, event_rx, reader, writer).await } diff --git a/src/lib.rs b/src/lib.rs index cbdf206..0df7800 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,15 +2,32 @@ use app::Chat; use app::Command; use app::ConnectionState; use app::STATE; +use asynchronous_codec::FramedRead; +use asynchronous_codec::FramedWrite; use dioxus::prelude::*; +use futures::io::AsyncRead; +use futures::io::AsyncWrite; +use futures::select; +use futures::FutureExt as _; +use futures::Sink; +use futures::SinkExt as _; +use futures::Stream; use futures::StreamExt; use futures_channel::mpsc::UnboundedSender; +pub use imp::spawn; pub use imp::Error; use mumble_protocol::control::msgs; +use mumble_protocol::control::ControlCodec; use mumble_protocol::control::ControlPacket; use mumble_protocol::voice::VoicePacketPayload; +use mumble_protocol::Clientbound; +use mumble_protocol::Serverbound; use std::collections::hash_map::Entry; use std::collections::HashMap; +use std::future::Future; +use std::io; +use std::pin::Pin; +use std::time::Duration; pub mod app; @@ -51,6 +68,114 @@ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver) { } } +pub async fn network_loop( + username: String, + event_rx: &mut UnboundedReceiver, + mut reader: FramedRead>, + mut writer: FramedWrite>, +) -> Result<(), Error> +where + R: AsyncRead + Unpin + 'static, + W: AsyncWrite + Unpin + 'static, +{ + let (mut send_chan, mut writer_recv_chan) = futures_channel::mpsc::unbounded(); + spawn(async move { + while let Some(msg) = writer_recv_chan.next().await { + if !matches!(msg, ControlPacket::Ping(_) | ControlPacket::UDPTunnel(_)) { + eprintln!("sending {:#?}", msg); + } + if let Err(e) = writer.send(msg).await { + eprintln!("ERROR: {}", e); + break; + } + } + }); + + // Get version packet + let version = match reader.next().await { + Some(Ok(v)) => v, + Some(Err(err)) => bail!("bad version packet: {err:?}"), + None => bail!("no version was recieved"), + }; + println!("Got version packet"); + println!("{:#?}", version); + + // Send version packet + let mut msg = msgs::Version::new(); + msg.set_version(0x000010204); + msg.set_release(format!("{} {}", "mumbleweb2", "6.9.0")); + //msg.set_os("Chrome".to_string()); + send_chan.send(msg.into()).await.unwrap(); + + // Send authenticate packet + let mut msg = msgs::Authenticate::new(); + msg.set_username(username); + msg.set_opus(true); + send_chan.send(msg.into()).await.unwrap(); + + // Spawn worker to send pings + { + let mut send_chan = send_chan.clone(); + spawn(async move { + loop { + if let Err(_) = send_chan.send(msgs::Ping::new().into()).await { + break; + } + + imp::sleep(Duration::from_millis(3000)).await; + } + }); + } + + let mut audio = imp::AudioSystem::new(send_chan.clone())?; + + // Create map of session_id -> AudioDecoder + let mut decoder_map = HashMap::new(); + + let mut reader_future = reader.next().fuse(); + let mut command_future = event_rx.next(); + + loop { + select! { + packet = reader_future => { + reader_future = reader.next().fuse(); + match packet { + Some(Ok(msg)) => { + if !matches!(msg, ControlPacket::UDPTunnel(_) | ControlPacket::Ping(_)) { + println!("receiving {:#?}", msg); + } + let res = accept_packet(msg, &mut audio, &mut decoder_map); + if let Err(err) = res { + err.log(); + } + }, + Some(Err(err)) => Error::from(err).log(), + None => break, + } + } + command = command_future => { + command_future = event_rx.next(); + if let Some(command) = &command { + println!("commanding {:#?}", command); + } + match command { + Some(Command::Disconnect) => break, + Some(command) => { + let res = accept_command(command, &mut send_chan); + if let Err(err) = res { + err.log(); + } + } + None => continue, + } + } + } + } + let _ = send_chan.close(); + + Ok(()) +} + fn accept_command( command: Command, send_chan: &mut UnboundedSender>, @@ -98,7 +223,7 @@ fn accept_command( fn accept_packet( msg: ControlPacket, - audio_context: &imp::AudioContext, + audio_context: &mut imp::AudioSystem, player_map: &mut HashMap, ) -> Result<(), Error> { match msg { @@ -116,7 +241,7 @@ fn accept_packet( let audio_player = match player_map.entry(session_id) { Entry::Occupied(occupied_entry) => occupied_entry.into_mut(), Entry::Vacant(vacant_entry) => { - vacant_entry.insert(imp::create_player(&audio_context)?) + vacant_entry.insert(audio_context.create_player()?) } }; // This will over time (as users join and leave) leak diff --git a/src/main.rs b/src/main.rs index 9c1fbae..9f91b8e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,11 @@ -use mumble_webtransport::app; +use mumble_web2::app; pub fn main() { + #[cfg(feature = "desktop")] + let _guard = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .enter(); dioxus::launch(app::app); }