2023-12-05 23:12:31 +00:00
|
|
|
use crate::core::MaybeRwSignal;
|
|
|
|
use cfg_if::cfg_if;
|
|
|
|
use default_struct_builder::DefaultBuilder;
|
2023-11-24 15:06:35 -05:00
|
|
|
use leptos::*;
|
2023-12-05 23:12:31 +00:00
|
|
|
use wasm_bindgen::{JsCast, JsValue};
|
2023-11-24 15:06:35 -05:00
|
|
|
|
2023-12-05 23:12:31 +00:00
|
|
|
/// Reactive [`mediaDevices.getDisplayMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia) streaming.
|
2023-11-24 15:23:30 -05:00
|
|
|
///
|
|
|
|
/// ## Demo
|
|
|
|
///
|
|
|
|
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_display_media)
|
|
|
|
///
|
|
|
|
/// ## Usage
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// # use leptos::*;
|
2023-12-05 23:12:31 +00:00
|
|
|
/// # use leptos_use::{use_display_media, UseDisplayMediaReturn};
|
2023-11-24 15:23:30 -05:00
|
|
|
/// #
|
|
|
|
/// # #[component]
|
|
|
|
/// # fn Demo() -> impl IntoView {
|
2023-12-05 23:12:31 +00:00
|
|
|
/// let video_ref = create_node_ref::<leptos::html::Video>();
|
|
|
|
///
|
|
|
|
/// let UseDisplayMediaReturn { stream, start, .. } = use_display_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> }
|
2023-11-24 15:23:30 -05:00
|
|
|
/// # }
|
|
|
|
/// ```
|
2023-12-05 23:12:31 +00:00
|
|
|
///
|
|
|
|
/// ## 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_display_media() -> UseDisplayMediaReturn<impl Fn() + Clone, impl Fn() + Clone> {
|
|
|
|
use_display_media_with_options(UseDisplayMediaOptions::default())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Version of [`use_display_media`] that accepts a [`UseDisplayMediaOptions`].
|
|
|
|
pub fn use_display_media_with_options(
|
|
|
|
options: UseDisplayMediaOptions,
|
|
|
|
) -> UseDisplayMediaReturn<impl Fn() + Clone, impl Fn() + Clone> {
|
|
|
|
let UseDisplayMediaOptions { enabled, audio } = options;
|
|
|
|
|
|
|
|
let (enabled, set_enabled) = enabled.into_signal();
|
|
|
|
|
2023-12-06 00:10:33 +00:00
|
|
|
let (stream, set_stream) = create_signal(None::<Result<web_sys::MediaStream, JsValue>>);
|
2023-12-05 23:12:31 +00:00
|
|
|
|
|
|
|
let _start = move || async move {
|
|
|
|
cfg_if! { if #[cfg(not(feature = "ssr"))] {
|
|
|
|
if stream.get_untracked().is_some() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let stream = create_media(audio).await;
|
|
|
|
|
|
|
|
set_stream.update(|s| *s = Some(stream));
|
2023-12-06 00:10:33 +00:00
|
|
|
} else {
|
|
|
|
let _ = audio;
|
2023-12-05 23:12:31 +00:00
|
|
|
}}
|
|
|
|
};
|
|
|
|
|
|
|
|
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,
|
|
|
|
);
|
|
|
|
|
|
|
|
UseDisplayMediaReturn {
|
|
|
|
stream: stream.into(),
|
|
|
|
start,
|
|
|
|
stop,
|
|
|
|
enabled,
|
|
|
|
set_enabled,
|
|
|
|
}
|
2023-11-24 15:06:35 -05:00
|
|
|
}
|
|
|
|
|
2023-12-05 23:12:31 +00:00
|
|
|
#[cfg(not(feature = "ssr"))]
|
2023-12-06 00:10:33 +00:00
|
|
|
async fn create_media(audio: bool) -> Result<web_sys::MediaStream, JsValue> {
|
2024-02-09 03:18:53 +00:00
|
|
|
use crate::js_fut;
|
2023-12-06 00:10:33 +00:00
|
|
|
use crate::use_window::use_window;
|
|
|
|
|
2023-11-29 18:52:39 -05:00
|
|
|
let media = use_window()
|
|
|
|
.navigator()
|
|
|
|
.ok_or_else(|| JsValue::from_str("Failed to access window.navigator"))
|
|
|
|
.and_then(|n| n.media_devices())?;
|
|
|
|
|
2024-08-13 13:45:36 +01:00
|
|
|
let constraints = web_sys::DisplayMediaStreamConstraints::new();
|
2023-12-05 23:12:31 +00:00
|
|
|
if audio {
|
2024-08-13 13:45:36 +01:00
|
|
|
constraints.set_audio(&JsValue::from(true));
|
2023-12-05 23:12:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let promise = media.get_display_media_with_constraints(&constraints)?;
|
2024-02-09 03:18:53 +00:00
|
|
|
let res = js_fut!(promise).await?;
|
2023-12-05 23:12:31 +00:00
|
|
|
|
2023-12-06 00:10:33 +00:00
|
|
|
Ok::<_, JsValue>(web_sys::MediaStream::unchecked_from_js(res))
|
2023-11-29 18:52:39 -05:00
|
|
|
}
|
|
|
|
|
2023-12-05 23:12:31 +00:00
|
|
|
// NOTE: there's no video value because it has to be `true`. Otherwise the stream would always resolve to an Error.
|
|
|
|
/// Options for [`use_display_media`].
|
|
|
|
#[derive(DefaultBuilder, Clone, Copy, Debug)]
|
|
|
|
pub struct UseDisplayMediaOptions {
|
|
|
|
/// If the stream is enabled. Defaults to `false`.
|
|
|
|
enabled: MaybeRwSignal<bool>,
|
2023-11-29 18:52:39 -05:00
|
|
|
|
2023-12-05 23:12:31 +00:00
|
|
|
/// A value of `true` indicates that the returned [`MediaStream`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStream)
|
|
|
|
/// will contain an audio track, if audio is supported and available for the display surface chosen by the user.
|
|
|
|
/// The default value is `false`.
|
|
|
|
audio: bool,
|
|
|
|
}
|
2023-11-29 18:52:39 -05:00
|
|
|
|
2023-12-05 23:12:31 +00:00
|
|
|
impl Default for UseDisplayMediaOptions {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
enabled: false.into(),
|
|
|
|
audio: false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return type of [`use_display_media`]
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct UseDisplayMediaReturn<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(...))`.
|
2023-12-06 00:10:33 +00:00
|
|
|
pub stream: Signal<Option<Result<web_sys::MediaStream, JsValue>>>,
|
2023-12-05 23:12:31 +00:00
|
|
|
|
|
|
|
/// 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>,
|
|
|
|
}
|