bump dioxus version & icons in desktop & logging

This commit is contained in:
2024-11-11 13:43:58 -07:00
parent 30a94323b3
commit 2211be5324
17 changed files with 1073 additions and 1242 deletions
+14
View File
@@ -1,2 +1,16 @@
[build] [build]
rustflags = ["--cfg=web_sys_unstable_apis"] rustflags = ["--cfg=web_sys_unstable_apis"]
[profile]
[profile.dioxus-wasm]
inherits = "dev"
opt-level = 2
[profile.dioxus-server]
inherits = "dev"
opt-level = 2
[profile.dioxus-android]
inherits = "dev"
opt-level = 2
+2
View File
@@ -0,0 +1,2 @@
[language-server.rust-analyzer]
config = { cargo = { features = "all" } }
Generated
+792 -1011
View File
File diff suppressed because it is too large Load Diff
+15 -1
View File
@@ -1,2 +1,16 @@
[workspace] [workspace]
members = ["gui"] resolver = "2"
members = [ "common","gui"]
[workspace.dependencies]
serde = { version = "1.0.214", features = ["derive"] }
asynchronous-codec = "0.6.2"
mumble-web2-common = { path = "common" }
[workspace.dependencies.mumble-protocol]
version = "0.5.0"
package = "mumble-protocol-2x"
default-features = false
features = [
"asynchronous-codec",
]
+7
View File
@@ -0,0 +1,7 @@
[package]
name = "mumble-web2-common"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
+9
View File
@@ -0,0 +1,9 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Deserialize, Default)]
pub struct GuiConfig {
#[serde(default)]
pub force_proxy: bool,
pub proxy_url: Option<String>,
pub cert_hash: Option<Vec<u8>>,
}
+31 -19
View File
@@ -4,18 +4,9 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
dioxus = { version = "0.5.6" } # Web Dependencies
dioxus-web = { version = "0.5.6", optional = true } # ================
manganis = "0.2.2" dioxus-web = { version = "0.6.0-alpha.4", optional = true }
once_cell = "1.19.0"
asynchronous-codec = "0.6.2"
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", "compat"] }
wasm-bindgen = { version = "0.2.92", optional = true } wasm-bindgen = { version = "0.2.92", optional = true }
wasm-bindgen-futures = { version = "0.4.42", optional = true } wasm-bindgen-futures = { version = "0.4.42", optional = true }
wasm-streams = { version = "0.4.0", optional = true } wasm-streams = { version = "0.4.0", optional = true }
@@ -63,18 +54,38 @@ web-sys = { version = "0.3.72", features = [
"AudioSampleFormat", "AudioSampleFormat",
"Storage", "Storage",
], optional = true} ], optional = true}
anyhow = "1.0.86" gloo-timers = { version = "0.3.0", features = ["futures"], optional = true }
tracing-web = { version = "0.1.3", optional = true }
# Desktop Dependecies
# ===================
dioxus-desktop = { version = "0.6.0-alpha.4", optional = true}
tokio = { version = "1.41.1", features = ["net", "rt"], optional = true }
tokio-rustls = { version = "0.26.0", optional = true }
# Base Dependencies
# ================
dioxus = { version = "0.6.0-alpha.4" }
once_cell = "1.19.0"
asynchronous-codec = { workspace = true }
futures = "0.3.30"
merge-io = "0.3.0"
mumble-protocol = { workspace = true }
serde_json = "1.0.117"
tokio-util = { version = "0.7.11", features = ["codec", "compat"] }
byteorder = "1.5.0" byteorder = "1.5.0"
ogg = "0.9.1" ogg = "0.9.1"
ordermap = "0.5.3" ordermap = "0.5.3"
html-purifier = "0.3.0" html-purifier = "0.3.0"
markdown = "0.3.0" markdown = "0.3.0"
gloo-timers = { version = "0.3.0", features = ["futures"], optional = true }
futures-channel = "0.3.30" futures-channel = "0.3.30"
sir = { version = "0.5.0", features = ["dioxus"] } sir = { git = "https://gitlab.com/samsartor/sir", features = ["dioxus"] } # dioxus 0.6
tokio = { version = "1.41.1", features = ["net", "rt"], optional = true } mumble-web2-common = { workspace = true }
tokio-rustls = { version = "0.26.0", optional = true } serde = { workspace = true }
serde = { version = "1.0.214", features = ["derive"] } tracing-subscriber = { version = "0.3.18", default-features = false, features = ["ansi"] }
tracing = "0.1.40"
color-eyre = "0.6.3"
[features] [features]
web = [ web = [
@@ -87,5 +98,6 @@ web = [
"js-sys", "js-sys",
"web-sys", "web-sys",
"gloo-timers", "gloo-timers",
"tracing-web",
] ]
desktop = ["dioxus/desktop", "tokio", "tokio-rustls"] desktop = ["dioxus/desktop", "tokio", "tokio-rustls", "tracing-subscriber/env-filter"]

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before

Width:  |  Height:  |  Size: 737 B

After

Width:  |  Height:  |  Size: 737 B

+27 -18
View File
@@ -1,7 +1,6 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use dioxus::prelude::*; use dioxus::prelude::*;
use manganis::mg;
use ordermap::OrderSet; use ordermap::OrderSet;
use sir::{css, global_css}; use sir::{css, global_css};
use std::collections::HashMap; use std::collections::HashMap;
@@ -111,15 +110,15 @@ pub enum UserIcon {
} }
impl UserIcon { impl UserIcon {
pub fn url(self) -> Option<&'static str> { pub fn url(self) -> Option<Asset> {
// speaker from https://www.svgrepo.com/collection/ikono-bold-line-icons/ // speaker from https://www.svgrepo.com/collection/ikono-bold-line-icons/
// mic from https://www.svgrepo.com/collection/hashicorp-line-interface-icons/ // mic from https://www.svgrepo.com/collection/hashicorp-line-interface-icons/
use UserIcon::*; use UserIcon::*;
Some(match self { Some(match self {
Normal => "/mic-svgrepo-com.svg", Normal => asset!("assets/mic-svgrepo-com.svg"),
Muted => "/mic-off-svgrepo-com.svg", Muted => asset!("assets/mic-off-svgrepo-com.svg"),
Deafened => "/speaker-muted-svgrepo-com.svg", Deafened => asset!("assets/speaker-muted-svgrepo-com.svg"),
None => return Option::None, None => return Option::None,
}) })
} }
@@ -160,19 +159,26 @@ pub fn UserPill(name: String, icon: UserIcon) -> Element {
#[component] #[component]
pub fn User(id: UserId) -> Element { pub fn User(id: UserId) -> Element {
let server = STATE.server.read(); let server = STATE.server.read();
let state = server.users.get(&id)?; match server.users.get(&id) {
rsx!(UserPill { Some(state) => rsx!(UserPill {
name: state.name.clone(), name: state.name.clone(),
icon: state.icon(), icon: state.icon(),
}) }),
None => rsx!(UserPill {
name: format!("unknown user ({id})"),
icon: UserIcon::None,
}),
}
} }
#[component] #[component]
pub fn Channel(id: ChannelId) -> Element { pub fn Channel(id: ChannelId) -> Element {
let net: Coroutine<Command> = use_coroutine_handle(); let net: Coroutine<Command> = use_coroutine_handle();
let server = STATE.server.read(); let server = STATE.server.read();
let user = server.session?; let user = server.session.unwrap();
let state = server.channels.get(&id)?; let Some(state) = server.channels.get(&id) else {
return rsx!("missing channel {id}");
};
let channel_details = css!( let channel_details = css!(
" "
@@ -318,13 +324,16 @@ pub fn ChatView() -> Element {
pub fn ServerView() -> Element { pub fn ServerView() -> Element {
let net: Coroutine<Command> = use_coroutine_handle(); let net: Coroutine<Command> = use_coroutine_handle();
let server = STATE.server.read(); let server = STATE.server.read();
let &UserState { let Some(&UserState {
deaf, deaf,
self_deaf, self_deaf,
mute, mute,
self_mute, self_mute,
.. ..
} = server.this_user()?; }) = server.this_user()
else {
return rsx!();
};
let grid = css!( let grid = css!(
r#" r#"
@@ -408,8 +417,8 @@ pub fn ServerView() -> Element {
disabled: mute, disabled: mute,
onclick: move |_| net.send(SetMute { mute: !self_mute }), onclick: move |_| net.send(SetMute { mute: !self_mute }),
match mute || self_mute { match mute || self_mute {
true => rsx!(img { src: "/mic-off-svgrepo-com.svg" }), true => rsx!(img { src: asset!("assets/mic-off-svgrepo-com.svg") }),
false => rsx!(img { src: "/mic-svgrepo-com.svg" }), false => rsx!(img { src: asset!("assets/mic-svgrepo-com.svg") }),
} }
"\u{00A0}Mute" "\u{00A0}Mute"
} }
@@ -419,8 +428,8 @@ pub fn ServerView() -> Element {
disabled: deaf, disabled: deaf,
onclick: move |_| net.send(SetDeaf { deaf: !self_deaf }), onclick: move |_| net.send(SetDeaf { deaf: !self_deaf }),
match deaf || self_deaf { match deaf || self_deaf {
true => rsx!(img { src: "/speaker-muted-svgrepo-com.svg" }), true => rsx!(img { src: asset!("assets/speaker-muted-svgrepo-com.svg") }),
false => rsx!(img { src: "/speaker-medium-svgrepo-com.svg" }), false => rsx!(img { src: asset!("assets/speaker-medium-svgrepo-com.svg") }),
} }
"\u{00A0}Deafen" "\u{00A0}Deafen"
} }
+25 -48
View File
@@ -1,13 +1,13 @@
use crate::app::Command; use crate::app::Command;
use anyhow::Result; use color_eyre::eyre::Error;
use dioxus::hooks::{UnboundedReceiver, UnboundedSender}; use dioxus::hooks::{UnboundedReceiver, UnboundedSender};
use futures::io::{AsyncRead, AsyncWrite}; use futures::io::{AsyncRead, AsyncWrite};
use mumble_protocol::control::{ClientControlCodec, ControlPacket}; use mumble_protocol::control::{ClientControlCodec, ControlPacket};
use mumble_protocol::Serverbound; use mumble_protocol::Serverbound;
use mumble_web2_common::GuiConfig;
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
use std::{fmt, io, sync::Arc}; use std::{fmt, io, sync::Arc};
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio::task::LocalSet;
use tokio_rustls::rustls; use tokio_rustls::rustls;
use tokio_rustls::rustls::client::danger::{HandshakeSignatureValid, ServerCertVerifier}; use tokio_rustls::rustls::client::danger::{HandshakeSignatureValid, ServerCertVerifier};
use tokio_rustls::rustls::pki_types::{CertificateDer, ServerName, UnixTime}; use tokio_rustls::rustls::pki_types::{CertificateDer, ServerName, UnixTime};
@@ -19,60 +19,22 @@ use tokio_util::compat::{TokioAsyncReadCompatExt as _, TokioAsyncWriteCompatExt
pub use tokio::task::spawn; pub use tokio::task::spawn;
pub use tokio::time::sleep; pub use tokio::time::sleep;
pub struct Error(anyhow::Error);
pub trait ImpRead: AsyncRead + Unpin + Send + 'static {} pub trait ImpRead: AsyncRead + Unpin + Send + 'static {}
impl<T: AsyncRead + Unpin + Send + 'static> ImpRead for T {} impl<T: AsyncRead + Unpin + Send + 'static> ImpRead for T {}
pub trait ImpWrite: AsyncWrite + Unpin + Send + 'static {} pub trait ImpWrite: AsyncWrite + Unpin + Send + 'static {}
impl<T: AsyncWrite + Unpin + Send + 'static> ImpWrite for T {} impl<T: AsyncWrite + Unpin + Send + 'static> ImpWrite for T {}
impl From<anyhow::Error> for Error {
fn from(value: anyhow::Error) -> Self {
Error(value)
}
}
impl From<io::Error> for Error {
fn from(value: io::Error) -> Self {
Error(value.into())
}
}
impl Error {
pub fn new(text: String) -> Self {
Self(anyhow::Error::msg(text))
}
pub fn log(&self) {
eprintln!("{}", self.0);
}
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
pub struct AudioSystem(); pub struct AudioSystem();
impl AudioSystem { impl AudioSystem {
pub fn new(sender: UnboundedSender<ControlPacket<Serverbound>>) -> Result<Self, Error> { pub fn new(sender: UnboundedSender<ControlPacket<Serverbound>>) -> Result<Self, Error> {
// dbg!("todo"); // TODO
Ok(AudioSystem()) Ok(AudioSystem())
} }
pub fn create_player(&mut self) -> Result<AudioPlayer, Error> { pub fn create_player(&mut self) -> Result<AudioPlayer, Error> {
// dbg!("todo"); // TODO
Ok(AudioPlayer()) Ok(AudioPlayer())
} }
} }
@@ -81,7 +43,7 @@ pub struct AudioPlayer();
impl AudioPlayer { impl AudioPlayer {
pub fn play_opus(&mut self, payload: &[u8]) { pub fn play_opus(&mut self, payload: &[u8]) {
// dbg!("todo"); // TODO
} }
} }
@@ -157,17 +119,17 @@ pub async fn network_connect(
let server_tcp = TcpStream::connect(addr).await?; let server_tcp = TcpStream::connect(addr).await?;
let server_stream = connector let server_stream = connector
//.connect("127.0.0.1".try_into()?, server_tcp) //.connect("127.0.0.1".try_into()?, server_tcp)
.connect(address.try_into().map_err(anyhow::Error::from)?, server_tcp) .connect(address.try_into()?, server_tcp)
.await?; .await?;
let (read_server, write_server) = tokio::io::split(server_stream); let (read_server, write_server) = tokio::io::split(server_stream);
let read_codec = ClientControlCodec::new(); let read_codec = ClientControlCodec::new();
let write_codec = ClientControlCodec::new(); let write_codec = ClientControlCodec::new();
let mut reader = asynchronous_codec::FramedRead::new(read_server.compat(), read_codec); let reader = asynchronous_codec::FramedRead::new(read_server.compat(), read_codec);
let mut writer = asynchronous_codec::FramedWrite::new(write_server.compat_write(), write_codec); let writer = asynchronous_codec::FramedWrite::new(write_server.compat_write(), write_codec);
super::network_loop(username, event_rx, reader, writer).await crate::network_loop(username, event_rx, reader, writer).await
} }
pub fn set_default_username(username: &str) -> Option<()> { pub fn set_default_username(username: &str) -> Option<()> {
@@ -178,6 +140,21 @@ pub fn load_username() -> Option<String> {
return None; return None;
} }
pub fn load_config() -> Option<super::GuiConfig> { pub fn load_config() -> Option<GuiConfig> {
None None
} }
pub fn init_logging() {
use tracing::level_filters::LevelFilter;
use tracing_subscriber::filter::EnvFilter;
let env_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy();
tracing_subscriber::fmt()
.with_target(true)
.with_level(true)
.with_env_filter(env_filter)
.init();
}
+11
View File
@@ -0,0 +1,11 @@
#[cfg(feature = "web")]
mod web;
#[cfg(feature = "desktop")]
mod desktop;
#[cfg(all(feature = "web", not(feature = "desktop")))]
pub use web::*;
#[cfg(feature = "desktop")]
pub use desktop::*;
+124 -112
View File
@@ -1,27 +1,19 @@
use crate::app::Command; use crate::app::Command;
use crate::bail;
use crate::CONFIG; use crate::CONFIG;
use color_eyre::eyre::{bail, eyre, Error};
use dioxus::prelude::*; use dioxus::prelude::*;
use futures::AsyncRead; use futures::{AsyncRead, AsyncWrite};
use futures::AsyncWrite;
use futures_channel::mpsc::UnboundedSender; use futures_channel::mpsc::UnboundedSender;
use gloo_timers::future::TimeoutFuture; use gloo_timers::future::TimeoutFuture;
use manganis::mg; use mumble_protocol::control::{ClientControlCodec, ControlPacket};
use mumble_protocol::control::ClientControlCodec; use mumble_protocol::voice::{VoicePacket, VoicePacketPayload};
use mumble_protocol::control::ControlPacket;
use mumble_protocol::voice::VoicePacket;
use mumble_protocol::voice::VoicePacketPayload;
use mumble_protocol::Serverbound; use mumble_protocol::Serverbound;
use std::fmt; use mumble_web2_common::GuiConfig;
use std::io;
use std::time::Duration; use std::time::Duration;
use tracing::{debug, error, info};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture; use wasm_bindgen_futures::JsFuture;
use web_sys::console; use web_sys::js_sys::{Promise, Reflect, Uint8Array};
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::AudioContext;
use web_sys::AudioContextOptions; use web_sys::AudioContextOptions;
use web_sys::AudioData; use web_sys::AudioData;
@@ -44,6 +36,7 @@ use web_sys::WebTransport;
use web_sys::WebTransportBidirectionalStream; use web_sys::WebTransportBidirectionalStream;
use web_sys::WebTransportOptions; use web_sys::WebTransportOptions;
use web_sys::WorkletOptions; use web_sys::WorkletOptions;
use web_sys::{console, window};
pub use wasm_bindgen_futures::spawn_local as spawn; pub use wasm_bindgen_futures::spawn_local as spawn;
@@ -57,58 +50,27 @@ pub async fn sleep(d: Duration) {
TimeoutFuture::new(d.as_millis() as u32).await TimeoutFuture::new(d.as_millis() as u32).await
} }
pub struct Error(JsValue); trait ResultExt<T> {
fn ey(self) -> Result<T, Error>;
}
impl From<anyhow::Error> for Error { impl<T> ResultExt<T> for Result<T, JsValue> {
fn from(value: anyhow::Error) -> Self { fn ey(self) -> Result<T, Error> {
Error(JsError::new(&value.to_string()).into()) match self {
Ok(x) => Ok(x),
Err(e) => match e.dyn_into::<js_sys::Error>() {
Ok(e) => Err(eyre!("{}: {}", e.name(), e.message())),
Err(e) => Err(eyre!("{:?}", e)),
},
}
} }
} }
impl From<io::Error> for Error { impl<T> ResultExt<T> for Result<T, JsError> {
fn from(value: io::Error) -> Self { fn ey(self) -> Result<T, Error> {
Error(JsError::new(&value.to_string()).into()) self.map_err(|e| JsValue::from(e)).ey()
} }
} }
impl From<JsValue> for Error {
fn from(value: JsValue) -> Self {
Error(value)
}
}
impl From<JsError> for Error {
fn from(value: JsError) -> Self {
Error(JsError::from(value).into())
}
}
impl Error {
pub fn new(text: String) -> Self {
wasm_bindgen::JsError::new(&text).into()
}
pub fn log(&self) {
console::error_1(&self.0);
}
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let text: String = js_sys::Object::from(self.0.clone()).to_string().into();
f.write_str(&text)
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let text: String = js_sys::Object::from(self.0.clone()).to_string().into();
f.write_str(&text)
}
}
pub struct AudioSystem(AudioContext); pub struct AudioSystem(AudioContext);
impl AudioSystem { impl AudioSystem {
@@ -120,8 +82,8 @@ impl AudioSystem {
let audio_context_worklet = audio_context.clone(); let audio_context_worklet = audio_context.clone();
spawn(async move { spawn(async move {
match create_encoder_worklet(&audio_context_worklet, sender).await { match create_encoder_worklet(&audio_context_worklet, sender).await {
Ok(node) => console::log_2(&"Created audio worklet:".into(), &node), Ok(node) => info!("created encoder worklet: {:?}", &node),
Err(err) => err.log(), Err(err) => error!("could not create encoder worklet: {err}"),
} }
}); });
@@ -132,21 +94,25 @@ impl AudioSystem {
let audio_context = &self.0; let audio_context = &self.0;
let audio_stream_generator = let audio_stream_generator =
MediaStreamTrackGenerator::new(&MediaStreamTrackGeneratorInit::new("audio"))?; MediaStreamTrackGenerator::new(&MediaStreamTrackGeneratorInit::new("audio")).ey()?;
// Create MediaStream from MediaStreamTrackGenerator // Create MediaStream from MediaStreamTrackGenerator
let js_tracks = web_sys::js_sys::Array::new(); let js_tracks = web_sys::js_sys::Array::new();
js_tracks.push(&audio_stream_generator); js_tracks.push(&audio_stream_generator);
let media_stream = MediaStream::new_with_tracks(&js_tracks)?; let media_stream = MediaStream::new_with_tracks(&js_tracks).ey()?;
// Create MediaStreamAudioSourceNode // Create MediaStreamAudioSourceNode
let audio_source = audio_context.create_media_stream_source(&media_stream)?; let audio_source = audio_context
.create_media_stream_source(&media_stream)
.ey()?;
// Connect output of audio_source to audio_context (browser audio) // Connect output of audio_source to audio_context (browser audio)
audio_source.connect_with_audio_node(&audio_context.destination())?; audio_source
.connect_with_audio_node(&audio_context.destination())
.ey()?;
// Create callback functions for AudioDecoder // Create callback functions for AudioDecoder
let error = Closure::wrap(Box::new(move |e: JsValue| { let decoder_error = Closure::wrap(Box::new(move |e: JsValue| {
console::error_1(&e); error!("error decoding audio {:?}", e);
}) as Box<dyn FnMut(JsValue)>); }) as Box<dyn FnMut(JsValue)>);
// This knows what MediaStreamTrackGenerator to use as it closes around it // This knows what MediaStreamTrackGenerator to use as it closes around it
@@ -157,29 +123,33 @@ impl AudioSystem {
} }
if let Err(e) = writable.get_writer().map(|writer| { if let Err(e) = writable.get_writer().map(|writer| {
spawn(async move { spawn(async move {
if let Err(e) = JsFuture::from(writer.ready()).await { if let Err(e) = JsFuture::from(writer.ready()).await.ey() {
console::error_1(&format!("write chunk ready error {:?}", e).into()); error!("write chunk ready error {:?}", e);
} }
if let Err(e) = JsFuture::from(writer.write_with_chunk(&audio_data)).await { if let Err(e) = JsFuture::from(writer.write_with_chunk(&audio_data))
console::error_1(&format!("write chunk error {:?}", e).into()); .await
.ey()
{
error!("write chunk error {:?}", e);
}; };
writer.release_lock(); writer.release_lock();
}); });
}) { }) {
console::error_1(&e); error!("error writing audio data {:?}", e);
} }
}) as Box<dyn FnMut(AudioData)>); }) as Box<dyn FnMut(AudioData)>);
let audio_decoder = AudioDecoder::new(&AudioDecoderInit::new( let audio_decoder = AudioDecoder::new(&AudioDecoderInit::new(
error.as_ref().unchecked_ref(), decoder_error.as_ref().unchecked_ref(),
output.as_ref().unchecked_ref(), output.as_ref().unchecked_ref(),
))?; ))
.ey()?;
audio_decoder.configure(&AudioDecoderConfig::new("opus", 1, 48000)); audio_decoder.configure(&AudioDecoderConfig::new("opus", 1, 48000));
console::log_1(&"Created Audio Decoder".into()); info!("created audio decoder");
// This is required to prevent these from being deallocated // This is required to prevent these from being deallocated
error.forget(); decoder_error.forget();
output.forget(); output.forget();
Ok(AudioPlayer(audio_decoder)) Ok(AudioPlayer(audio_decoder))
@@ -228,32 +198,41 @@ async fn create_encoder_worklet(
let stream = window() let stream = window()
.unwrap() .unwrap()
.navigator() .navigator()
.media_devices()? .media_devices()
.get_user_media_with_constraints(MediaStreamConstraints::new().audio(&JsValue::TRUE))? .ey()?
.get_user_media_with_constraints(MediaStreamConstraints::new().audio(&JsValue::TRUE))
.ey()?
.into_future() .into_future()
.await? .await
.ey()?
.dyn_into() .dyn_into()
.map_err(|e| JsError::new(&format!("not a stream: {e:?}")))?; .map_err(|e| JsError::new(&format!("not a stream: {e:?}")))
.ey()?;
let options = WorkletOptions::new(); let options = WorkletOptions::new();
Reflect::set( Reflect::set(
&options, &options,
&"processorOptions".into(), &"processorOptions".into(),
&wasm_bindgen::module(), &wasm_bindgen::module(),
)?; )
.ey()?;
let module = "/rust_mic_worklet.js"; let module = asset!("assets/rust_mic_worklet.js").to_string();
console::log_1(&format!("Loading mic worklet from {module:?}").into()); info!("loading mic worklet from {module:?}");
audio_context audio_context
.audio_worklet()? .audio_worklet()
.add_module_with_options(module, &options)? .ey()?
.add_module_with_options(&module, &options)
.ey()?
.into_future() .into_future()
.await?; .await
.ey()?;
let source = audio_context.create_media_stream_source(&stream)?; let source = audio_context.create_media_stream_source(&stream).ey()?;
let worklet_node = AudioWorkletNode::new(audio_context, "rust_mic_worklet")?; let worklet_node = AudioWorkletNode::new(audio_context, "rust_mic_worklet").ey()?;
let error: Closure<dyn FnMut(JsValue)> = Closure::new(|e| console::error_1(&e)); let encoder_error: Closure<dyn FnMut(JsValue)> =
Closure::new(|e| error!("error encoding audio {:?}", e));
let download_buffer = std::cell::RefCell::new(Vec::new()); let download_buffer = std::cell::RefCell::new(Vec::new());
@@ -287,13 +266,13 @@ async fn create_encoder_worklet(
}); });
let audio_encoder = AudioEncoder::new(&AudioEncoderInit::new( let audio_encoder = AudioEncoder::new(&AudioEncoderInit::new(
error.as_ref().unchecked_ref(), encoder_error.as_ref().unchecked_ref(),
output.as_ref().unchecked_ref(), output.as_ref().unchecked_ref(),
)) ))
.unwrap(); .unwrap();
// This is required to prevent these from being deallocated // This is required to prevent these from being deallocated
error.forget(); encoder_error.forget();
output.forget(); output.forget();
let encoder_config = AudioEncoderConfig::new("opus"); let encoder_config = AudioEncoderConfig::new("opus");
encoder_config.set_number_of_channels(1); encoder_config.set_number_of_channels(1);
@@ -301,7 +280,7 @@ async fn create_encoder_worklet(
encoder_config.set_bitrate(72_000.0); encoder_config.set_bitrate(72_000.0);
audio_encoder.configure(&encoder_config); audio_encoder.configure(&encoder_config);
console::log_1(&"Created Audio Encoder".into()); info!("created audio encoder");
let download_buffer = std::cell::RefCell::new(Vec::new()); let download_buffer = std::cell::RefCell::new(Vec::new());
@@ -322,20 +301,25 @@ async fn create_encoder_worklet(
audio_encoder.encode(&data); audio_encoder.encode(&data);
} }
Err(err) => { Err(err) => {
console::error_1(&err); error!(
console::debug_1(&event); "error creating AudioData object {:?} during event {:?}",
err, event,
);
} }
} }
}); });
Reflect::set( Reflect::set(
&Reflect::get(&worklet_node, &"port".into())?, &Reflect::get(&worklet_node, &"port".into()).ey()?,
&"onmessage".into(), &"onmessage".into(),
onmessage.as_ref(), onmessage.as_ref(),
)?; )
.ey()?;
onmessage.forget(); onmessage.forget();
source.connect_with_audio_node(&worklet_node)?; source.connect_with_audio_node(&worklet_node).ey()?;
worklet_node.connect_with_audio_node(&audio_context.destination())?; worklet_node
.connect_with_audio_node(&audio_context.destination())
.ey()?;
Ok(worklet_node) Ok(worklet_node)
} }
@@ -345,7 +329,7 @@ pub async fn network_connect(
username: String, username: String,
event_rx: &mut UnboundedReceiver<Command>, event_rx: &mut UnboundedReceiver<Command>,
) -> Result<(), Error> { ) -> Result<(), Error> {
console::log_1(&"Rust via WASM!".into()); info!("Rust via WASM!");
let object = web_sys::js_sys::Object::new(); let object = web_sys::js_sys::Object::new();
@@ -353,37 +337,38 @@ pub async fn network_connect(
&object, &object,
&JsValue::from_str("algorithm"), &JsValue::from_str("algorithm"),
&JsValue::from_str("sha-256"), &JsValue::from_str("sha-256"),
)?; )
.ey()?;
if let Some(server_hash) = &CONFIG.cert_hash { if let Some(server_hash) = &CONFIG.cert_hash {
let hash = web_sys::js_sys::Uint8Array::from(server_hash.as_slice()); let hash = web_sys::js_sys::Uint8Array::from(server_hash.as_slice());
web_sys::js_sys::Reflect::set(&object, &"value".into(), &hash)?; web_sys::js_sys::Reflect::set(&object, &"value".into(), &hash).ey()?;
} }
let array = web_sys::js_sys::Array::new(); let array = web_sys::js_sys::Array::new();
array.push(&object); array.push(&object);
console::log_1(&object.clone().into()); debug!("created option object: {:?}", &object);
console::log_1(&"Created option object!".into());
let mut options = WebTransportOptions::new(); let mut options = WebTransportOptions::new();
options.server_certificate_hashes(&array); options.server_certificate_hashes(&array);
console::log_1(&"Created WebTransportOptions!".into()); debug!("created WebTransportOptions");
let transport = WebTransport::new_with_options(&address, &options)?; let transport = WebTransport::new_with_options(&address, &options).ey()?;
console::log_1(&"Created WebTransport connection object.".into()); debug!("created WebTransport connection object");
console::log_1(&transport.clone().into()); console::log_1(&transport.clone().into());
if let Err(e) = wasm_bindgen_futures::JsFuture::from(transport.ready()).await { if let Err(e) = wasm_bindgen_futures::JsFuture::from(transport.ready()).await {
bail!("could not connect to transport: {e:?}"); bail!("could not connect to transport: {e:?}");
} }
console::log_1(&"Transport is ready.".into()); info!("transport is ready");
let stream: WebTransportBidirectionalStream = let stream: WebTransportBidirectionalStream =
wasm_bindgen_futures::JsFuture::from(transport.create_bidirectional_stream()) wasm_bindgen_futures::JsFuture::from(transport.create_bidirectional_stream())
.await? .await
.ey()?
.into(); .into();
let wasm_stream_readable = wasm_streams::ReadableStream::from_raw(stream.readable().into()); let wasm_stream_readable = wasm_streams::ReadableStream::from_raw(stream.readable().into());
@@ -397,7 +382,7 @@ pub async fn network_connect(
let writer = let writer =
asynchronous_codec::FramedWrite::new(wasm_stream_writable.into_async_write(), write_codec); asynchronous_codec::FramedWrite::new(wasm_stream_writable.into_async_write(), write_codec);
super::network_loop(username, event_rx, reader, writer).await crate::network_loop(username, event_rx, reader, writer).await
} }
pub fn set_default_username(username: &str) -> Option<()> { pub fn set_default_username(username: &str) -> Option<()> {
@@ -417,6 +402,33 @@ pub fn load_username() -> Option<String> {
.ok()? .ok()?
} }
pub fn load_config() -> Option<super::GuiConfig> { fn load_config_from_window() -> Option<GuiConfig> {
serde_wasm_bindgen::from_value(Reflect::get(window()?.as_ref(), &"config".into()).ok()?).ok() serde_wasm_bindgen::from_value(Reflect::get(window()?.as_ref(), &"config".into()).ok()?).ok()
} }
fn load_config_from_env() -> Option<GuiConfig> {
serde_json::from_str(option_env!("MUMBLE_WEB2_GUI_CONFIG")?).ok()?
}
pub fn load_config() -> Option<GuiConfig> {
load_config_from_window().or_else(load_config_from_env)
}
pub fn init_logging() {
// copied from tracing_web example usage
use tracing_subscriber::fmt::format::Pretty;
use tracing_subscriber::prelude::*;
use tracing_web::{performance_layer, MakeWebConsoleWriter};
let fmt_layer = tracing_subscriber::fmt::layer()
.with_ansi(false) // Only partially supported across browsers
.without_time() // std::time is not available in browsers
.with_writer(MakeWebConsoleWriter::new()); // write events to the console
let perf_layer = performance_layer().with_details_from_fields(Pretty::default());
tracing_subscriber::registry()
.with(fmt_layer)
.with(perf_layer)
.init();
}
+16 -33
View File
@@ -4,6 +4,7 @@ use app::ConnectionState;
use app::STATE; use app::STATE;
use asynchronous_codec::FramedRead; use asynchronous_codec::FramedRead;
use asynchronous_codec::FramedWrite; use asynchronous_codec::FramedWrite;
use color_eyre::eyre::{bail, Error};
use dioxus::prelude::*; use dioxus::prelude::*;
use futures::select; use futures::select;
use futures::FutureExt as _; use futures::FutureExt as _;
@@ -11,54 +12,35 @@ use futures::SinkExt as _;
use futures::StreamExt as _; use futures::StreamExt as _;
use futures_channel::mpsc::UnboundedSender; use futures_channel::mpsc::UnboundedSender;
pub use imp::spawn; pub use imp::spawn;
pub use imp::Error;
use mumble_protocol::control::msgs; use mumble_protocol::control::msgs;
use mumble_protocol::control::ControlCodec; use mumble_protocol::control::ControlCodec;
use mumble_protocol::control::ControlPacket; use mumble_protocol::control::ControlPacket;
use mumble_protocol::voice::VoicePacketPayload; use mumble_protocol::voice::VoicePacketPayload;
use mumble_protocol::Clientbound; use mumble_protocol::Clientbound;
use mumble_protocol::Serverbound; use mumble_protocol::Serverbound;
use mumble_web2_common::GuiConfig;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use serde::Deserialize;
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
use std::collections::HashMap; use std::collections::HashMap;
use std::time::Duration; use std::time::Duration;
use tracing::debug;
use tracing::error;
pub mod app; pub mod app;
#[cfg(feature = "web")]
#[path = "imp/web.rs"]
pub mod imp; pub mod imp;
#[cfg(feature = "desktop")]
#[path = "imp/desktop.rs"]
pub mod imp;
#[derive(Clone, Deserialize, Default)]
pub struct GuiConfig {
pub proxy_url: Option<String>,
pub cert_hash: Option<Vec<u8>>,
}
pub static CONFIG: Lazy<GuiConfig> = Lazy::new(|| imp::load_config().unwrap_or_default()); pub static CONFIG: Lazy<GuiConfig> = Lazy::new(|| imp::load_config().unwrap_or_default());
#[macro_export]
macro_rules! bail {
($($x:tt)*) => {
return Err(Error::new(format!($($x)*)))
};
}
pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) { pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) {
loop { loop {
let Some(Command::Connect { address, username }) = event_rx.next().await else { let Some(Command::Connect { address, username }) = event_rx.next().await else {
panic!("Did not receive connect command") panic!("did not receive connect command")
}; };
*STATE.server.write() = Default::default(); *STATE.server.write() = Default::default();
*STATE.status.write() = ConnectionState::Connecting; *STATE.status.write() = ConnectionState::Connecting;
if let Err(error) = imp::network_connect(address, username, &mut event_rx).await { if let Err(error) = imp::network_connect(address, username, &mut event_rx).await {
error.log(); error!("could not connect {:?}", error);
*STATE.status.write() = ConnectionState::Failed(error.to_string()); *STATE.status.write() = ConnectionState::Failed(error.to_string());
} else { } else {
*STATE.status.write() = ConnectionState::Disconnected; *STATE.status.write() = ConnectionState::Disconnected;
@@ -76,10 +58,10 @@ pub async fn network_loop<R: imp::ImpRead, W: imp::ImpWrite>(
spawn(async move { spawn(async move {
while let Some(msg) = writer_recv_chan.next().await { while let Some(msg) = writer_recv_chan.next().await {
if !matches!(msg, ControlPacket::Ping(_) | ControlPacket::UDPTunnel(_)) { if !matches!(msg, ControlPacket::Ping(_) | ControlPacket::UDPTunnel(_)) {
eprintln!("sending {:#?}", msg); debug!("sending {:#?}", msg);
} }
if let Err(e) = writer.send(msg).await { if let Err(e) = writer.send(msg).await {
eprintln!("ERROR: {}", e); error!("error sending message {:?}", e);
break; break;
} }
} }
@@ -91,8 +73,7 @@ pub async fn network_loop<R: imp::ImpRead, W: imp::ImpWrite>(
Some(Err(err)) => bail!("bad version packet: {err:?}"), Some(Err(err)) => bail!("bad version packet: {err:?}"),
None => bail!("no version was recieved"), None => bail!("no version was recieved"),
}; };
println!("Got version packet"); debug!("got version packet {:#?}", version);
println!("{:#?}", version);
// Send version packet // Send version packet
let mut msg = msgs::Version::new(); let mut msg = msgs::Version::new();
@@ -136,28 +117,30 @@ pub async fn network_loop<R: imp::ImpRead, W: imp::ImpWrite>(
match packet { match packet {
Some(Ok(msg)) => { Some(Ok(msg)) => {
if !matches!(msg, ControlPacket::UDPTunnel(_) | ControlPacket::Ping(_)) { if !matches!(msg, ControlPacket::UDPTunnel(_) | ControlPacket::Ping(_)) {
println!("receiving {:#?}", msg); debug!("receiving {:#?}", msg);
} }
let res = accept_packet(msg, &mut audio, &mut decoder_map); let res = accept_packet(msg, &mut audio, &mut decoder_map);
if let Err(err) = res { if let Err(err) = res {
err.log(); error!("error accepting packet {:?}", err)
} }
}, },
Some(Err(err)) => Error::from(err).log(), Some(Err(err)) => {
error!("error receiving packet {:?}", err)
},
None => break, None => break,
} }
} }
command = command_future => { command = command_future => {
command_future = event_rx.next(); command_future = event_rx.next();
if let Some(command) = &command { if let Some(command) = &command {
println!("commanding {:#?}", command); debug!("commanding {:#?}", command);
} }
match command { match command {
Some(Command::Disconnect) => break, Some(Command::Disconnect) => break,
Some(command) => { Some(command) => {
let res = accept_command(command, &mut send_chan); let res = accept_command(command, &mut send_chan);
if let Err(err) = res { if let Err(err) = res {
err.log(); error!("error accepting command {:?}", err)
} }
} }
None => continue, None => continue,