363 lines
12 KiB
Rust
363 lines
12 KiB
Rust
pub mod app;
|
|
use std::cell::RefCell;
|
|
use std::pin::pin;
|
|
use std::rc::Rc;
|
|
use std::time::Duration;
|
|
|
|
use app::STATE;
|
|
|
|
use asynchronous_codec::Decoder;
|
|
use asynchronous_codec::Encoder;
|
|
use futures::AsyncRead;
|
|
use futures::AsyncWrite;
|
|
use futures::Sink;
|
|
use futures::SinkExt;
|
|
use futures::Stream;
|
|
use futures::StreamExt;
|
|
use mumble_protocol::control::ControlCodec;
|
|
use mumble_protocol::control::ControlPacket;
|
|
use mumble_protocol::control::{msgs, ClientControlCodec};
|
|
use mumble_protocol::Clientbound;
|
|
use mumble_protocol::Serverbound;
|
|
use wasm_bindgen::prelude::*;
|
|
use wasm_bindgen_futures::JsFuture;
|
|
use web_sys::console;
|
|
use web_sys::js_sys::Uint8Array;
|
|
use web_sys::AudioData;
|
|
use web_sys::AudioContext;
|
|
use web_sys::AudioContextOptions;
|
|
use web_sys::MediaStream;
|
|
use web_sys::AudioDecoder;
|
|
use web_sys::AudioDecoderConfig;
|
|
use web_sys::AudioDecoderInit;
|
|
use web_sys::CodecState;
|
|
use web_sys::EncodedAudioChunk;
|
|
use web_sys::EncodedAudioChunkInit;
|
|
use web_sys::EncodedAudioChunkType;
|
|
use web_sys::MediaStreamTrackGenerator;
|
|
use web_sys::MediaStreamTrackGeneratorInit;
|
|
use web_sys::WebTransport;
|
|
use web_sys::WebTransportOptions;
|
|
|
|
use wasm_bindgen_futures::spawn_local as spawn;
|
|
|
|
//#[wasm_bindgen]
|
|
//extern "C" {
|
|
//}
|
|
|
|
fn configure_audio_context(
|
|
audio_stream_generator: &MediaStreamTrackGenerator,
|
|
) -> AudioContext {
|
|
let js_tracks = web_sys::js_sys::Array::new();
|
|
js_tracks.push(audio_stream_generator);
|
|
let media_stream = MediaStream::new_with_tracks(&js_tracks).unwrap();
|
|
let mut audio_context_options = AudioContextOptions::new();
|
|
audio_context_options.sample_rate(48000 as f32);
|
|
let audio_context = AudioContext::new_with_context_options(&audio_context_options).unwrap();
|
|
let gain_node = audio_context.create_gain().unwrap();
|
|
gain_node.set_channel_count(1);
|
|
let source = audio_context
|
|
.create_media_stream_source(&media_stream)
|
|
.unwrap();
|
|
let _ = source.connect_with_audio_node(&gain_node).unwrap();
|
|
let _ = gain_node
|
|
.connect_with_audio_node(&audio_context.destination())
|
|
.unwrap();
|
|
audio_context
|
|
}
|
|
|
|
pub async fn network_entrypoint() {
|
|
async_std::task::sleep(Duration::from_millis(3000)).await;
|
|
|
|
console::log_1(&"Rust via WASM!".into());
|
|
|
|
let server_hash = vec![
|
|
14, 162, 111, 176, 34, 113, 218, 69, 177, 18, 13, 180, 232, 204, 49, 65, 161, 195, 36, 238,
|
|
23, 95, 174, 190, 24, 216, 105, 89, 236, 147, 206, 139,
|
|
];
|
|
let hash = web_sys::js_sys::Uint8Array::from(server_hash.as_slice());
|
|
|
|
let object = web_sys::js_sys::Object::new();
|
|
|
|
web_sys::js_sys::Reflect::set(
|
|
&object,
|
|
&JsValue::from_str("algorithm"),
|
|
&JsValue::from_str("sha-256"),
|
|
)
|
|
.unwrap();
|
|
|
|
//web_sys::js_sys::Reflect::set(&object, &JsValue::from_str("value"), &hash).unwrap();
|
|
web_sys::js_sys::Reflect::set(&object, &"value".into(), &hash).unwrap();
|
|
|
|
let array = web_sys::js_sys::Array::new();
|
|
array.push(&object);
|
|
|
|
console::log_1(&object.clone().into());
|
|
console::log_1(&"Created option object!".into());
|
|
|
|
let mut options = WebTransportOptions::new();
|
|
options.server_certificate_hashes(&array);
|
|
|
|
console::log_1(&"Created WebTransportOptions!".into());
|
|
|
|
*STATE.status.write() = "Connecting to WebTransport...".into();
|
|
|
|
let transport = match WebTransport::new_with_options(
|
|
"https://localhost:4433/?hostname=ohea.xyz&port=64738&username=test",
|
|
&options,
|
|
) {
|
|
Ok(x) => x,
|
|
Err(e) => {
|
|
console::log_1(&e.into());
|
|
panic!();
|
|
}
|
|
};
|
|
|
|
console::log_1(&"Created WebTransport connection object.".into());
|
|
|
|
console::log_1(&transport.clone().into());
|
|
|
|
if let Err(e) = wasm_bindgen_futures::JsFuture::from(transport.ready()).await {
|
|
console::log_1(&e.into());
|
|
panic!();
|
|
}
|
|
|
|
console::log_1(&"Transport is ready.".into());
|
|
|
|
*STATE.status.write() = "Creating stream...".into();
|
|
|
|
let stream: web_sys::WebTransportBidirectionalStream =
|
|
match wasm_bindgen_futures::JsFuture::from(transport.create_bidirectional_stream()).await {
|
|
Ok(x) => x.into(),
|
|
Err(e) => {
|
|
console::log_1(&e.into());
|
|
panic!();
|
|
}
|
|
};
|
|
|
|
let wasm_stream_readable = wasm_streams::ReadableStream::from_raw(stream.readable().into());
|
|
let wasm_stream_writable = wasm_streams::WritableStream::from_raw(stream.writable().into());
|
|
|
|
let read_codec = ClientControlCodec::new();
|
|
let write_codec = ClientControlCodec::new();
|
|
|
|
let mut reader =
|
|
asynchronous_codec::FramedRead::new(wasm_stream_readable.into_async_read(), read_codec);
|
|
let mut writer =
|
|
asynchronous_codec::FramedWrite::new(wasm_stream_writable.into_async_write(), write_codec);
|
|
|
|
let (send_chan, writer_recv_chan) = async_std::channel::unbounded();
|
|
|
|
spawn(async move {
|
|
while let Ok(msg) = writer_recv_chan.recv().await {
|
|
if let Err(e) = writer.send(msg).await {
|
|
console::log_1(&e.to_string().into());
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Get version packet
|
|
let version = reader.next().await.unwrap().unwrap();
|
|
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();
|
|
console::log_1(&"Sent version packet".into());
|
|
|
|
// Send authenticate packet
|
|
let mut msg = msgs::Authenticate::new();
|
|
msg.set_username("mumbleweb2".to_string());
|
|
msg.set_opus(true);
|
|
send_chan.send(msg.into()).await.unwrap();
|
|
console::log_1(&"Sent authenticate packet".into());
|
|
|
|
{
|
|
let send_chan = send_chan.clone();
|
|
spawn(async move {
|
|
loop {
|
|
console::log_1(&"Sending ping".into());
|
|
if let Err(e) = send_chan.send(msgs::Ping::new().into()).await {
|
|
console::log_1(&e.to_string().into());
|
|
break;
|
|
}
|
|
|
|
async_std::task::sleep(Duration::from_millis(3000)).await;
|
|
}
|
|
});
|
|
}
|
|
|
|
let error = Closure::wrap(Box::new(move |e: JsValue| {
|
|
console::log_1(&e);
|
|
}) as Box<dyn FnMut(JsValue)>);
|
|
|
|
let audio_stream_generator =
|
|
MediaStreamTrackGenerator::new(&MediaStreamTrackGeneratorInit::new("audio")).unwrap();
|
|
// The audio context is used to reproduce audio.
|
|
let _audio_context = configure_audio_context(&audio_stream_generator);
|
|
|
|
let output = Closure::wrap(Box::new(move |audio_data: AudioData| {
|
|
console::log_1(&"Got audio".into());
|
|
console::log_1(&audio_data.clone().into());
|
|
let writable = audio_stream_generator.writable();
|
|
if writable.locked() {
|
|
return;
|
|
}
|
|
if let Err(e) = writable.get_writer().map(|writer| {
|
|
wasm_bindgen_futures::spawn_local(async move {
|
|
if let Err(e) = JsFuture::from(writer.ready()).await {
|
|
console::log_1(&format!("write chunk error {:?}", e).into());
|
|
}
|
|
if let Err(e) = JsFuture::from(writer.write_with_chunk(&audio_data)).await {
|
|
console::log_1(&format!("write chunk error {:?}", e).into());
|
|
};
|
|
writer.release_lock();
|
|
});
|
|
}) {
|
|
console::log_1(&e);
|
|
}
|
|
}) as Box<dyn FnMut(AudioData)>);
|
|
|
|
let audio_decoder = AudioDecoder::new(&AudioDecoderInit::new(
|
|
error.as_ref().unchecked_ref(),
|
|
output.as_ref().unchecked_ref(),
|
|
)).unwrap();
|
|
|
|
console::log_1(&"Created Audio Decoder".into());
|
|
console::log_1(&audio_decoder);
|
|
|
|
audio_decoder.configure(&AudioDecoderConfig::new(
|
|
"opus",
|
|
1,
|
|
48000,
|
|
));
|
|
|
|
|
|
loop {
|
|
match reader.next().await {
|
|
Some(Ok(msg)) => {
|
|
match msg {
|
|
ControlPacket::UDPTunnel(u) => {
|
|
match *u.clone() {
|
|
mumble_protocol::voice::VoicePacket::Audio {
|
|
_dst,
|
|
target,
|
|
session_id,
|
|
seq_num,
|
|
payload,
|
|
position_info,
|
|
} => {
|
|
console::log_1(&"Voice packet found".into());
|
|
if let mumble_protocol::voice::VoicePacketPayload::Opus(
|
|
audio_payload,
|
|
end_bit,
|
|
) = payload
|
|
{
|
|
let js_audio_payload = Uint8Array::from(audio_payload.as_ref());
|
|
audio_decoder.decode(&EncodedAudioChunk::new(
|
|
&EncodedAudioChunkInit::new(
|
|
&js_audio_payload.into(),
|
|
0.0,
|
|
EncodedAudioChunkType::Key,
|
|
),
|
|
).unwrap());
|
|
console::log_1(&"Oueued audio chunk for decoding".into());
|
|
|
|
|
|
//let mut encoded_audio_chunk_init =
|
|
// web_sys::EncodedAudioChunkInit::new(
|
|
// &js_audio_payload.into(),
|
|
// 0.0,
|
|
// web_sys::EncodedAudioChunkType::Delta,
|
|
// );
|
|
////encoded_audio_chunk_init.duration(1.0);
|
|
//let encoded_audio_chunk =
|
|
// web_sys::EncodedAudioChunk::new(&encoded_audio_chunk_init)
|
|
// .unwrap();
|
|
//console::log_1(&encoded_audio_chunk);
|
|
|
|
}
|
|
}
|
|
_ => {
|
|
unreachable!("TCP tunnels UDP packets should not contain pings");
|
|
// I think?
|
|
}
|
|
}
|
|
console::log_1(&"Got UDP tunnel".into());
|
|
console::log_1(&format!("{:#?}", u).into());
|
|
}
|
|
_ => {
|
|
console::log_1(&format!("{:#?}", msg).into());
|
|
}
|
|
}
|
|
}
|
|
None => {
|
|
break;
|
|
}
|
|
Some(Err(e)) => {
|
|
console::log_1(&e.to_string().into());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//async fn handle_send(
|
|
// mut writer: Rc<RefCell<asynchronous_codec::FramedWrite<impl AsyncRead + AsyncWrite + Unpin, ControlCodec<Serverbound, Clientbound>>>>,
|
|
// send_queue: Queue<mumble_protocol::Serverbound>,
|
|
//) {
|
|
// loop {
|
|
// let msg = send_queue.get();
|
|
// client.send(msg).await.unwrap();
|
|
// }
|
|
//}
|
|
|
|
//async fn handle_ping(
|
|
//) {
|
|
// //pin!(client);
|
|
// loop {
|
|
// let ping = msgs::Ping::new();
|
|
// client.borrow_mut().send(ping.into()).await.unwrap();
|
|
// console::log_1(&"Sent ping packet".into());
|
|
//
|
|
// async_std::task::sleep(Duration::from_millis(3000)).await;
|
|
// }
|
|
//}
|
|
|
|
//let queue_write, queue_read = Queue::new();
|
|
|
|
//spawn(handle_send(writer, queue_write));
|
|
//spawn(handle_ping(queue_read));
|
|
|
|
//loop {
|
|
// let msg = reader.next().await.unwrap().unwrap();
|
|
// console::log_1(&format!("{:#?}", msg).into());
|
|
//}
|
|
|
|
// let result: Option<(Version, u32)> = loop {
|
|
// match client.next().await {
|
|
// None => break None,
|
|
// Some(packet) => {
|
|
// let packet = packet.unwrap();
|
|
|
|
// }
|
|
// }
|
|
// };
|
|
|
|
//let client = ClientControlCodec::new().framed(stream);
|
|
//let client = ClientControlCodec::new().framed(stream);
|
|
|
|
//let reader = stream.readable();
|
|
//let writer = stream.writable();
|
|
|
|
//console::log_1(&stream.into());
|
|
|
|
//*STATE.status.write() = "Ready!".into();
|
|
|
|
//return stream.into();
|
|
}
|