Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show max 20 canvases #158

Merged
merged 8 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 88 additions & 147 deletions yew-ui/src/components/attendants.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
use super::icons::push_pin::PushPinIcon;
use crate::constants::{USERS_ALLOWED_TO_STREAM, WEBTRANSPORT_HOST};
use crate::components::{canvas_generator, peer_list::PeerList};
use crate::constants::{CANVAS_LIMIT, USERS_ALLOWED_TO_STREAM, WEBTRANSPORT_HOST};
use crate::{components::host::Host, constants::ACTIX_WEBSOCKET};
use log::{error, warn};
use std::rc::Rc;
use types::protos::media_packet::media_packet::MediaType;
use videocall_client::{MediaDeviceAccess, VideoCallClient, VideoCallClientOptions};
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use web_sys::*;
use yew::prelude::*;
use yew::virtual_dom::VNode;
use yew::{html, Component, Context, Html};

#[derive(Debug)]
Expand All @@ -31,11 +28,16 @@ pub enum MeetingAction {
ToggleVideoOnOff,
}

pub enum UserScreenAction {
TogglePeerList,
}

pub enum Msg {
WsAction(WsAction),
MeetingAction(MeetingAction),
OnPeerAdded(String),
OnFirstFrame((String, MediaType)),
UserScreenAction(UserScreenAction),
}

impl From<WsAction> for Msg {
Expand All @@ -44,6 +46,12 @@ impl From<WsAction> for Msg {
}
}

impl From<UserScreenAction> for Msg {
fn from(action: UserScreenAction) -> Self {
Msg::UserScreenAction(action)
}
}

impl From<MeetingAction> for Msg {
fn from(action: MeetingAction) -> Self {
Msg::MeetingAction(action)
Expand All @@ -69,6 +77,7 @@ pub struct AttendantsComponent {
pub share_screen: bool,
pub mic_enabled: bool,
pub video_enabled: bool,
pub peer_list_open: bool,
pub error: Option<String>,
}

Expand Down Expand Up @@ -133,6 +142,7 @@ impl Component for AttendantsComponent {
share_screen: false,
mic_enabled: false,
video_enabled: false,
peer_list_open: false,
error: None,
}
}
Expand Down Expand Up @@ -197,162 +207,93 @@ impl Component for AttendantsComponent {
}
true
}
Msg::UserScreenAction(action) => {
match action {
UserScreenAction::TogglePeerList => {
self.peer_list_open = !self.peer_list_open;
}
}
true
}
}
}

fn view(&self, ctx: &Context<Self>) -> Html {
let email = ctx.props().email.clone();
let media_access_granted = self.media_device_access.is_granted();
let rows: Vec<VNode> = self
.client
.sorted_peer_keys()
.iter()
.map(|key| {
if !USERS_ALLOWED_TO_STREAM.is_empty()
&& !USERS_ALLOWED_TO_STREAM.iter().any(|host| host == key)
{
return html! {};
}
let screen_share_css = if self.client.is_awaiting_peer_screen_frame(key) {
"grid-item hidden"
} else {
"grid-item"
};
let screen_share_div_id = Rc::new(format!("screen-share-{}-div", &key));
let peer_video_div_id = Rc::new(format!("peer-video-{}-div", &key));
html! {
<>
<div class={screen_share_css} id={(*screen_share_div_id).clone()}>
// Canvas for Screen share.
<div class="canvas-container">
<canvas id={format!("screen-share-{}", &key)}></canvas>
<h4 class="floating-name">{format!("{}-screen", &key)}</h4>
<button onclick={Callback::from(move |_| {
toggle_pinned_div(&(*screen_share_div_id).clone());
})} class="pin-icon">
<PushPinIcon/>
</button>
</div>
</div>
<div class="grid-item" id={(*peer_video_div_id).clone()}>
// One canvas for the User Video
<div class="canvas-container">
<UserVideo id={key.clone()}></UserVideo>
<h4 class="floating-name">{key.clone()}</h4>
<button onclick={
Callback::from(move |_| {
toggle_pinned_div(&(*peer_video_div_id).clone());
})} class="pin-icon">
<PushPinIcon/>
</button>
</div>
</div>
</>
}
})
.collect();

let toggle_peer_list = ctx.link().callback(|_| UserScreenAction::TogglePeerList);

let peers = self.client.sorted_peer_keys();
let rows = canvas_generator::generate(
&self.client,
peers.iter().take(CANVAS_LIMIT).cloned().collect(),
);

html! {
<div class="grid-container">
{ self.error.as_ref().map(|error| html! { <p>{ error }</p> }) }
{ rows }
{
if USERS_ALLOWED_TO_STREAM.iter().any(|host| host == &email) || USERS_ALLOWED_TO_STREAM.is_empty() {
html! {
<nav class="host">
<div class="controls">
<button
class="bg-yew-blue p-2 rounded-md text-white"
onclick={ctx.link().callback(|_| MeetingAction::ToggleScreenShare)}>
{ if self.share_screen { "Stop Screen Share"} else { "Share Screen"} }
</button>
<button
class="bg-yew-blue p-2 rounded-md text-white"
onclick={ctx.link().callback(|_| MeetingAction::ToggleVideoOnOff)}>
{ if !self.video_enabled { "Start Video"} else { "Stop Video"} }
</button>
<button
class="bg-yew-blue p-2 rounded-md text-white"
onclick={ctx.link().callback(|_| MeetingAction::ToggleMicMute)}>
{ if !self.mic_enabled { "Unmute"} else { "Mute"} }
<div id="main-container">
<div id="grid-container" style={if self.peer_list_open {"width: 80%;"} else {"width: 100%;"}}>
{ self.error.as_ref().map(|error| html! { <p>{ error }</p> }) }
{ rows }
{
if USERS_ALLOWED_TO_STREAM.iter().any(|host| host == &email) || USERS_ALLOWED_TO_STREAM.is_empty() {
html! {
<nav class="host">
<div class="controls">
<button
class="bg-yew-blue p-2 rounded-md text-white"
onclick={ctx.link().callback(|_| MeetingAction::ToggleScreenShare)}>
{ if self.share_screen { "Stop Screen Share"} else { "Share Screen"} }
</button>
</div>
{
if media_access_granted {
html! {<Host client={self.client.clone()} share_screen={self.share_screen} mic_enabled={self.mic_enabled} video_enabled={self.video_enabled} />}
} else {
html! {<></>}
<button
class="bg-yew-blue p-2 rounded-md text-white"
onclick={ctx.link().callback(|_| MeetingAction::ToggleVideoOnOff)}>
{ if !self.video_enabled { "Start Video"} else { "Stop Video"} }
</button>
<button
class="bg-yew-blue p-2 rounded-md text-white"
onclick={ctx.link().callback(|_| MeetingAction::ToggleMicMute)}>
{ if !self.mic_enabled { "Unmute"} else { "Mute"} }
</button>
<button
class="bg-yew-blue p-2 rounded-md text-white"
onclick={toggle_peer_list.clone()}>
{ if !self.peer_list_open { "Open Peers"} else { "Close Peers"} }
</button>
</div>
{
if media_access_granted {
html! {<Host client={self.client.clone()} share_screen={self.share_screen} mic_enabled={self.mic_enabled} video_enabled={self.video_enabled} />}
} else {
html! {<></>}
}
}
}
<h4 class="floating-name">{email}</h4>
<h4 class="floating-name">{email}</h4>

{if !self.client.is_connected() {
html! {<h4>{"Connecting"}</h4>}
} else {
html! {<h4>{"Connected"}</h4>}
}}
{if !self.client.is_connected() {
html! {<h4>{"Connecting"}</h4>}
} else {
html! {<h4>{"Connected"}</h4>}
}}

{if ctx.props().e2ee_enabled {
html! {<h4>{"End to End Encryption Enabled"}</h4>}
} else {
html! {<h4>{"End to End Encryption Disabled"}</h4>}
}}
</nav>
{if ctx.props().e2ee_enabled {
html! {<h4>{"End to End Encryption Enabled"}</h4>}
} else {
html! {<h4>{"End to End Encryption Disabled"}</h4>}
}}
</nav>
}
} else {
error!("User not allowed to stream");
error!("allowed users {}", USERS_ALLOWED_TO_STREAM.join(", "));
html! {}
}
} else {
error!("User not allowed to stream");
error!("allowed users {}", USERS_ALLOWED_TO_STREAM.join(", "));
html! {}
}
}
</div>
<div id="peer-list-container" class={if self.peer_list_open {"visible"} else {""}}>
<PeerList peers={peers} onclose={toggle_peer_list} />
</div>
</div>
}
}
}

// props for the video component
#[derive(Properties, Debug, PartialEq)]
pub struct UserVideoProps {
pub id: String,
}

// user video functional component
#[function_component(UserVideo)]
fn user_video(props: &UserVideoProps) -> Html {
// create use_effect hook that gets called only once and sets a thumbnail
// for the user video
let video_ref = use_state(NodeRef::default);
let video_ref_clone = video_ref.clone();
use_effect_with_deps(
move |_| {
// Set thumbnail for the video
let video = (*video_ref_clone).cast::<HtmlCanvasElement>().unwrap();
let ctx = video
.get_context("2d")
.unwrap()
.unwrap()
.unchecked_into::<CanvasRenderingContext2d>();
ctx.clear_rect(0.0, 0.0, video.width() as f64, video.height() as f64);
|| ()
},
vec![props.id.clone()],
);

html! {
<canvas ref={(*video_ref).clone()} id={props.id.clone()}></canvas>
}
}

fn toggle_pinned_div(div_id: &str) {
if let Some(div) = window()
.and_then(|w| w.document())
.and_then(|doc| doc.get_element_by_id(div_id))
{
// if the div does not have the grid-item-pinned css class, add it to it
if !div.class_list().contains("grid-item-pinned") {
div.class_list().add_1("grid-item-pinned").unwrap();
} else {
// else remove it
div.class_list().remove_1("grid-item-pinned").unwrap();
}
}
}
Loading
Loading