use gloo_utils::window;
use videocall_types::Callback;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use web_sys::{MediaStream, MediaStreamConstraints, MediaStreamTrack};
#[derive(Clone, Copy)]
pub enum MediaAccessKind {
AudioCheck,
VideoCheck,
BothCheck,
}
#[derive(Clone, Debug, PartialEq)]
pub enum MediaPermissionsErrorState {
NoDevice,
PermissionDenied,
Other(JsValue),
}
#[derive(Clone, PartialEq, Debug)]
pub enum PermissionState {
Unknown,
Granted,
Denied(MediaPermissionsErrorState),
}
#[derive(Debug)]
pub struct MediaPermission {
pub audio: PermissionState,
pub video: PermissionState,
}
pub struct MediaDeviceAccess {
current_permission: MediaPermission,
pub on_result: Callback<MediaPermission>,
}
#[allow(clippy::new_without_default)]
impl MediaDeviceAccess {
pub fn new() -> Self {
Self {
current_permission: MediaPermission {
audio: PermissionState::Unknown,
video: PermissionState::Unknown,
},
on_result: Callback::noop(),
}
}
pub fn is_granted(&self, device: MediaAccessKind) -> bool {
match device {
MediaAccessKind::AudioCheck => {
matches!(self.current_permission.audio, PermissionState::Granted)
}
MediaAccessKind::VideoCheck => {
matches!(self.current_permission.video, PermissionState::Granted)
}
MediaAccessKind::BothCheck => true,
}
}
pub fn request(&self) {
let on_result = self.on_result.clone();
log::info!("start request of permission");
wasm_bindgen_futures::spawn_local(async move {
let perm_result = Self::request_media_permission().await;
on_result.emit(perm_result);
});
}
async fn request_media_permission() -> MediaPermission {
use futures::join;
let (audio, video) = join!(
Self::request_audio_permissions(),
Self::request_video_permissions()
);
MediaPermission {
audio: match audio {
Ok(_) => PermissionState::Granted,
Err(e) => PermissionState::Denied(e),
},
video: match video {
Ok(_) => PermissionState::Granted,
Err(e) => PermissionState::Denied(e),
},
}
}
fn stop_tracks(stream: &MediaStream) {
for track in stream.get_tracks().iter() {
let track: MediaStreamTrack = track.unchecked_into();
track.stop();
}
}
async fn request_audio_permissions() -> Result<(), MediaPermissionsErrorState> {
let navigator = window().navigator();
let media_devices = navigator
.media_devices()
.map_err(MediaPermissionsErrorState::Other)?;
let constraints = MediaStreamConstraints::new();
constraints.set_audio(&JsValue::from_bool(true));
let promise = media_devices
.get_user_media_with_constraints(&constraints)
.map_err(MediaPermissionsErrorState::Other)?;
match JsFuture::from(promise).await {
Ok(stream) => {
Self::stop_tracks(&stream.unchecked_into());
Ok(())
}
Err(err) => {
let name = js_sys::Reflect::get(&err, &JsValue::from_str("name"))
.ok()
.and_then(|v| v.as_string());
match name.as_deref() {
Some("NotFoundError") => Err(MediaPermissionsErrorState::NoDevice),
Some("NotAllowedError") => Err(MediaPermissionsErrorState::PermissionDenied),
_ => Err(MediaPermissionsErrorState::Other(err)),
}
}
}
}
async fn request_video_permissions() -> Result<(), MediaPermissionsErrorState> {
let navigator = window().navigator();
let media_devices = navigator
.media_devices()
.map_err(MediaPermissionsErrorState::Other)?;
let constraints = MediaStreamConstraints::new();
constraints.set_video(&JsValue::from_bool(true));
let promise = media_devices
.get_user_media_with_constraints(&constraints)
.map_err(MediaPermissionsErrorState::Other)?;
match JsFuture::from(promise).await {
Ok(stream) => {
Self::stop_tracks(&stream.unchecked_into());
Ok(())
}
Err(err) => {
let name = js_sys::Reflect::get(&err, &JsValue::from_str("name"))
.ok()
.and_then(|v| v.as_string());
match name.as_deref() {
Some("NotFoundError") => Err(MediaPermissionsErrorState::NoDevice),
Some("NotAllowedError") => Err(MediaPermissionsErrorState::PermissionDenied),
_ => Err(MediaPermissionsErrorState::Other(err)),
}
}
}
}
}