feat: webrtc gui interaction

This commit is contained in:
Ahmet Kaan Gümüş 2025-04-25 04:54:41 +03:00
parent cde03367c3
commit 8391ef31ba
2 changed files with 66 additions and 64 deletions

View file

@ -1,40 +1,41 @@
use leptos::{ use leptos::{
IntoView, ev, IntoView, ev,
html::{ElementChild, button}, html::{ElementChild, button},
logging::log,
prelude::{OnAttribute, Read, Show, ShowProps, ToChildren}, prelude::{OnAttribute, Read, Show, ShowProps, ToChildren},
server::LocalResource, server::LocalResource,
task::spawn_local,
}; };
use wasm_bindgen_futures::JsFuture;
use web_sys::HtmlAudioElement;
use crate::media::audio; use crate::{media::audio, webrtc::WebRTC};
pub fn app() -> impl IntoView { pub fn app() -> impl IntoView {
let audio_stream = LocalResource::new(|| audio()); let audio_stream = LocalResource::new(|| audio());
let props = ShowProps::builder() let offer_props = ShowProps::builder()
.when(move || audio_stream.read().is_some()) .when(move || audio_stream.read().is_some())
.children(ToChildren::to_children(move || { .children(ToChildren::to_children(move || {
let audio_element = HtmlAudioElement::new().unwrap();
let audio_stream = audio_stream.read();
let audio_stream = audio_stream.as_deref();
audio_element.set_src_object(audio_stream);
button() button()
.on(ev::click, move |_| match audio_element.play() { .on(ev::click, move |_| {
Ok(audio_element_play_promise) => { WebRTC::init(Some(audio_stream), None, None);
log!("{}", "Play will"); LocalResource::new(|| WebRTC::offer());
spawn_local(async move {
JsFuture::from(audio_element_play_promise).await.ok();
});
log!("{}", "Play must");
}
Err(err_val) => log!("{:#?}", err_val),
}) })
.child("Happy Button") .child("Offer")
.into_view() .into_view()
})) }))
.fallback(|| button().child("Sad Button")) .fallback(|| button().child("Sad Offer Button"))
.build(); .build();
Show(props)
let answer_props = ShowProps::builder()
.when(move || audio_stream.read().is_some())
.children(ToChildren::to_children(move || {
button()
.on(ev::click, move |_| {
WebRTC::init(Some(audio_stream), None, None);
LocalResource::new(|| WebRTC::answer());
})
.child("Answer")
.into_view()
}))
.fallback(|| button().child("Sad Answer Button"))
.build();
(Show(offer_props), Show(answer_props))
} }

View file

@ -1,4 +1,4 @@
use leptos::logging::log; use leptos::{logging::log, prelude::Get, server::LocalResource};
use protocol::Error; use protocol::Error;
use wasm_bindgen_futures::{JsFuture, spawn_local}; use wasm_bindgen_futures::{JsFuture, spawn_local};
use web_sys::{ use web_sys::{
@ -14,14 +14,11 @@ use crate::signal::{
}; };
pub struct WebRTC { pub struct WebRTC {
audio_stream: Option<MediaStream>,
video_stream: Option<MediaStream>,
screen_stream: Option<MediaStream>,
peer_connection: RtcPeerConnection, peer_connection: RtcPeerConnection,
} }
thread_local! { thread_local! {
static WEBRTC:WebRTC = WebRTC::new().unwrap(); pub static WEBRTC:WebRTC = WebRTC::new().unwrap();
} }
impl WebRTC { impl WebRTC {
@ -51,27 +48,17 @@ impl WebRTC {
peer_connection.set_onicecandidate(Some(on_ice_candidate.as_ref().unchecked_ref())); peer_connection.set_onicecandidate(Some(on_ice_candidate.as_ref().unchecked_ref()));
on_ice_candidate.forget(); on_ice_candidate.forget();
let webrtc = Self { let webrtc = Self { peer_connection };
audio_stream: None,
video_stream: None,
screen_stream: None,
peer_connection,
};
Ok(webrtc) Ok(webrtc)
} }
async fn init( pub fn init(
&mut self, audio_stream: Option<LocalResource<MediaStream>>,
audio_stream: Option<MediaStream>, video_stream: Option<LocalResource<MediaStream>>,
video_stream: Option<MediaStream>, screen_stream: Option<LocalResource<MediaStream>>,
screen_stream: Option<MediaStream>,
) { ) {
self.audio_stream = audio_stream; Self::add_streams(audio_stream, video_stream, screen_stream);
self.video_stream = video_stream;
self.screen_stream = screen_stream;
self.add_streams();
spawn_local(async { spawn_local(async {
while let Ok(received_ice_candidate) = receive_ice_candidate() { while let Ok(received_ice_candidate) = receive_ice_candidate() {
@ -98,20 +85,32 @@ impl WebRTC {
}); });
} }
fn add_streams(&mut self) { fn add_streams(
if let Some(audio_stream) = &self.audio_stream { audio_stream: Option<LocalResource<MediaStream>>,
self.peer_connection.add_stream(audio_stream); video_stream: Option<LocalResource<MediaStream>>,
screen_stream: Option<LocalResource<MediaStream>>,
) {
WEBRTC.with(|webrtc| {
if let Some(audio_stream) = audio_stream {
if let Some(audio_stream) = audio_stream.get() {
webrtc.peer_connection.add_stream(&audio_stream);
} }
if let Some(video_stream) = &self.video_stream {
self.peer_connection.add_stream(video_stream);
} }
if let Some(screen_stream) = &self.screen_stream { if let Some(video_stream) = video_stream {
self.peer_connection.add_stream(screen_stream); if let Some(video_stream) = video_stream.get() {
webrtc.peer_connection.add_stream(&video_stream);
} }
} }
if let Some(screen_stream) = screen_stream {
if let Some(screen_stream) = screen_stream.get() {
webrtc.peer_connection.add_stream(&screen_stream);
}
}
});
}
async fn offer(&mut self) -> Result<(), Error> { pub async fn offer() -> Result<(), Error> {
let offer_promise = self.peer_connection.create_offer(); let offer_promise = WEBRTC.with(|webrtc| webrtc.peer_connection.create_offer());
match JsFuture::from(offer_promise) match JsFuture::from(offer_promise)
.await .await
.map_err(|_| Error::WebRTCOffer) .map_err(|_| Error::WebRTCOffer)
@ -127,8 +126,8 @@ impl WebRTC {
Ok(offer_session_description_protocol) => { Ok(offer_session_description_protocol) => {
let offer = RtcSessionDescriptionInit::new(RtcSdpType::Offer); let offer = RtcSessionDescriptionInit::new(RtcSdpType::Offer);
offer.set_sdp(&offer_session_description_protocol); offer.set_sdp(&offer_session_description_protocol);
let set_local_description_promise = let set_local_description_promise = WEBRTC
self.peer_connection.set_local_description(&offer); .with(|webrtc| webrtc.peer_connection.set_local_description(&offer));
JsFuture::from(set_local_description_promise) JsFuture::from(set_local_description_promise)
.await .await
@ -140,8 +139,9 @@ impl WebRTC {
let answer = RtcSessionDescriptionInit::new(RtcSdpType::Answer); let answer = RtcSessionDescriptionInit::new(RtcSdpType::Answer);
answer.set_sdp(&received_answer.get_data()); answer.set_sdp(&received_answer.get_data());
let set_remote_description_promise = let set_remote_description_promise = WEBRTC.with(|webrtc| {
self.peer_connection.set_remote_description(&answer); webrtc.peer_connection.set_remote_description(&answer)
});
JsFuture::from(set_remote_description_promise) JsFuture::from(set_remote_description_promise)
.await .await
.map_err(|_| Error::WebRTCSetRemoteDescription)?; .map_err(|_| Error::WebRTCSetRemoteDescription)?;
@ -164,18 +164,18 @@ impl WebRTC {
return Err(Error::WebRTCOffer); return Err(Error::WebRTCOffer);
} }
async fn answer(&mut self) -> Result<(), Error> { pub async fn answer() -> Result<(), Error> {
if let Ok(received_offer) = receive_offer() { if let Ok(received_offer) = receive_offer() {
let offer = RtcSessionDescriptionInit::new(RtcSdpType::Offer); let offer = RtcSessionDescriptionInit::new(RtcSdpType::Offer);
offer.set_sdp(&received_offer.get_data()); offer.set_sdp(&received_offer.get_data());
let set_remote_description_promise = let set_remote_description_promise =
self.peer_connection.set_remote_description(&offer); WEBRTC.with(|webrtc| webrtc.peer_connection.set_remote_description(&offer));
JsFuture::from(set_remote_description_promise) JsFuture::from(set_remote_description_promise)
.await .await
.map_err(|_| Error::WebRTCSetRemoteDescription)?; .map_err(|_| Error::WebRTCSetRemoteDescription)?;
let answer_promise = self.peer_connection.create_answer(); let answer_promise = WEBRTC.with(|webrtc| webrtc.peer_connection.create_answer());
match JsFuture::from(answer_promise) match JsFuture::from(answer_promise)
.await .await
.map_err(|_| Error::WebRTCAnswer) .map_err(|_| Error::WebRTCAnswer)
@ -192,8 +192,9 @@ impl WebRTC {
let answer = RtcSessionDescriptionInit::new(RtcSdpType::Answer); let answer = RtcSessionDescriptionInit::new(RtcSdpType::Answer);
answer.set_sdp(&answer_session_description_protocol); answer.set_sdp(&answer_session_description_protocol);
let set_local_description_promise = let set_local_description_promise = WEBRTC.with(|webrtc| {
self.peer_connection.set_local_description(&answer); webrtc.peer_connection.set_local_description(&answer)
});
JsFuture::from(set_local_description_promise) JsFuture::from(set_local_description_promise)
.await .await
.map_err(|_| Error::WebRTCSetLocalDescription)?; .map_err(|_| Error::WebRTCSetLocalDescription)?;