mirror of
https://github.com/adoyle0/leptos-use.git
synced 2025-01-23 09:09:21 -05:00
d4330c9fb1
Also removed a bunch of deprecated warnings. Closes #144
204 lines
6.1 KiB
Rust
204 lines
6.1 KiB
Rust
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::<leptos::html::Video>();
|
|
///
|
|
/// 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! { <video node_ref=video_ref controls=false autoplay=true muted=true></video> }
|
|
/// # }
|
|
/// ```
|
|
///
|
|
/// ## 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<impl Fn() + Clone, impl Fn() + Clone> {
|
|
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<impl Fn() + Clone, impl Fn() + Clone> {
|
|
let UseUserMediaOptions {
|
|
enabled,
|
|
video,
|
|
audio,
|
|
..
|
|
} = options;
|
|
|
|
let (enabled, set_enabled) = enabled.into_signal();
|
|
|
|
let (stream, set_stream) = create_signal(None::<Result<web_sys::MediaStream, JsValue>>);
|
|
|
|
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::<web_sys::MediaStreamTrack>().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<web_sys::MediaStream, JsValue> {
|
|
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<bool>,
|
|
/// 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<StartFn, StopFn>
|
|
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<Option<Result<web_sys::MediaStream, JsValue>>>,
|
|
|
|
/// 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<bool>,
|
|
|
|
/// A value of `true` is the same as calling `start()` whereas `false` is the same as calling `stop()`.
|
|
pub set_enabled: WriteSignal<bool>,
|
|
}
|