use crate::core::MaybeRwSignal; use cfg_if::cfg_if; use default_struct_builder::DefaultBuilder; use leptos::*; use wasm_bindgen::{JsCast, JsValue}; /// Reactive [`mediaDevices.getUserMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia) streaming. /// /// ## Demo /// /// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_user_media) /// /// ## Usage /// /// ``` /// # use leptos::*; /// # use leptos_use::{use_user_media, UseUserMediaReturn}; /// # /// # #[component] /// # fn Demo() -> impl IntoView { /// let video_ref = create_node_ref::(); /// /// let UseUserMediaReturn { stream, start, .. } = use_user_media(); /// /// start(); /// /// create_effect(move |_| /// video_ref.get().map(|v| { /// match stream.get() { /// Some(Ok(s)) => v.set_src_object(Some(&s)), /// Some(Err(e)) => logging::error!("Failed to get media stream: {:?}", e), /// None => logging::log!("No stream yet"), /// } /// }) /// ); /// /// view! { } /// # } /// ``` /// /// ## Server-Side Rendering /// /// On the server calls to `start` or any other way to enable the stream will be ignored /// and the stream will always be `None`. pub fn use_user_media() -> UseUserMediaReturn { use_user_media_with_options(UseUserMediaOptions::default()) } /// Version of [`use_user_media`] that takes a `UseUserMediaOptions`. See [`use_user_media`] for how to use. pub fn use_user_media_with_options( options: UseUserMediaOptions, ) -> UseUserMediaReturn { let UseUserMediaOptions { enabled, video, audio, .. } = options; let (enabled, set_enabled) = enabled.into_signal(); let (stream, set_stream) = create_signal(None::>); let _start = move || async move { cfg_if! { if #[cfg(not(feature = "ssr"))] { if stream.get_untracked().is_some() { return; } let stream = create_media(video, audio).await; set_stream.update(|s| *s = Some(stream)); } else { let _ = video; let _ = audio; }} }; let _stop = move || { if let Some(Ok(stream)) = stream.get_untracked() { for track in stream.get_tracks() { track.unchecked_ref::().stop(); } } set_stream.set(None); }; let start = move || { cfg_if! { if #[cfg(not(feature = "ssr"))] { spawn_local(async move { _start().await; stream.with_untracked(move |stream| { if let Some(Ok(_)) = stream { set_enabled.set(true); } }); }); }} }; let stop = move || { _stop(); set_enabled.set(false); }; let _ = watch( move || enabled.get(), move |enabled, _, _| { if *enabled { spawn_local(async move { _start().await; }); } else { _stop(); } }, true, ); UseUserMediaReturn { stream: stream.into(), start, stop, enabled, set_enabled, } } #[cfg(not(feature = "ssr"))] async fn create_media(video: bool, audio: bool) -> Result { use crate::js_fut; use crate::use_window::use_window; let media = use_window() .navigator() .ok_or_else(|| JsValue::from_str("Failed to access window.navigator")) .and_then(|n| n.media_devices())?; let constraints = web_sys::MediaStreamConstraints::new(); if video { constraints.set_video(&JsValue::from(true)); } if audio { constraints.set_audio(&JsValue::from(true)); } let promise = media.get_user_media_with_constraints(&constraints)?; let res = js_fut!(promise).await?; Ok::<_, JsValue>(web_sys::MediaStream::unchecked_from_js(res)) } /// Options for [`use_user_media_with_options`]. /// Either or both constraints must be specified. /// If the browser cannot find all media tracks with the specified types that meet the constraints given, /// then the returned promise is rejected with `NotFoundError` #[derive(DefaultBuilder, Clone, Copy, Debug)] pub struct UseUserMediaOptions { /// If the stream is enabled. Defaults to `false`. enabled: MaybeRwSignal, /// Constraint parameter describing video media type requested /// The default value is `false`. video: bool, /// Constraint parameter describing audio media type requested /// The default value is `false`. audio: bool, } impl Default for UseUserMediaOptions { fn default() -> Self { Self { enabled: false.into(), video: true, audio: false, } } } /// Return type of [`use_user_media`]. #[derive(Clone)] pub struct UseUserMediaReturn where StartFn: Fn() + Clone, StopFn: Fn() + Clone, { /// The current [`MediaStream`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStream) if it exists. /// Initially this is `None` until `start` resolved successfully. /// In case the stream couldn't be started, for example because the user didn't grant permission, /// this has the value `Some(Err(...))`. pub stream: Signal>>, /// Starts the screen streaming. Triggers the ask for permission if not already granted. pub start: StartFn, /// Stops the screen streaming pub stop: StopFn, /// A value of `true` indicates that the returned [`MediaStream`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStream) /// has resolved successfully and thus the stream is enabled. pub enabled: Signal, /// A value of `true` is the same as calling `start()` whereas `false` is the same as calling `stop()`. pub set_enabled: WriteSignal, }