use image::{buffer::ConvertBuffer, ImageBuffer, Rgb, RgbImage, Rgba};
use js_sys::{Array, JsString, Map, Object, Promise};
use nokhwa_core::{
error::NokhwaError,
types::{CameraIndex, CameraInfo, Resolution},
};
use std::{
borrow::{Borrow, Cow},
convert::TryFrom,
fmt::{Debug, Display, Formatter},
ops::Deref,
};
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use web_sys::{
console::log_1, CanvasRenderingContext2d, Document, Element, HtmlCanvasElement,
HtmlVideoElement, ImageData, MediaDeviceInfo, MediaDeviceKind, MediaDevices, MediaStream,
MediaStreamConstraints, MediaStreamTrack, MediaStreamTrackState, Navigator, Node, Window,
};
#[cfg(feature = "output-wgpu")]
use wgpu::{
Device, Extent3d, ImageCopyTexture, ImageDataLayout, Queue, Texture, TextureAspect,
TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
};
macro_rules! jsv {
($value:expr) => {{
JsValue::from($value)
}};
}
macro_rules! obj {
($(($key:expr, $value:expr)),+ ) => {{
use js_sys::{Map, Object};
use wasm_bindgen::JsValue;
let map = Map::new();
$(
map.set(&jsv!($key), &jsv!($value));
)+
Object::from(map)
}};
($object:expr, $(($key:expr, $value:expr)),+ ) => {{
use js_sys::{Map, Object};
use wasm_bindgen::JsValue;
let map = Map::new();
$(
map.set(&jsv!($key), &jsv!($value));
)+
let o = Object::from(map);
Object::assign(&$object, &o)
}};
}
fn window() -> Result<Window, NokhwaError> {
match web_sys::window() {
Some(win) => Ok(win),
None => Err(NokhwaError::StructureError {
structure: "web_sys Window".to_string(),
error: "None".to_string(),
}),
}
}
fn media_devices(navigator: &Navigator) -> Result<MediaDevices, NokhwaError> {
match navigator.media_devices() {
Ok(media) => Ok(media),
Err(why) => Err(NokhwaError::StructureError {
structure: "MediaDevices".to_string(),
error: format!("{why:?}"),
}),
}
}
fn document(window: &Window) -> Result<Document, NokhwaError> {
match window.document() {
Some(doc) => Ok(doc),
None => Err(NokhwaError::StructureError {
structure: "web_sys Document".to_string(),
error: "None".to_string(),
}),
}
}
fn document_select_elem(doc: &Document, element: &str) -> Result<Element, NokhwaError> {
match doc.get_element_by_id(element) {
Some(elem) => Ok(elem),
None => {
return Err(NokhwaError::StructureError {
structure: format!("Document {element}"),
error: "None".to_string(),
})
}
}
}
fn element_cast<T: JsCast, U: JsCast>(from: T, name: &str) -> Result<U, NokhwaError> {
if !from.has_type::<U>() {
return Err(NokhwaError::StructureError {
structure: name.to_string(),
error: "Cannot Cast - No Subtype".to_string(),
});
}
let casted = match from.dyn_into::<U>() {
Ok(cast) => cast,
Err(_) => {
return Err(NokhwaError::StructureError {
structure: name.to_string(),
error: "Casting Error".to_string(),
});
}
};
Ok(casted)
}
fn element_cast_ref<'a, T: JsCast, U: JsCast>(
from: &'a T,
name: &'a str,
) -> Result<&'a U, NokhwaError> {
if !from.has_type::<U>() {
return Err(NokhwaError::StructureError {
structure: name.to_string(),
error: "Cannot Cast - No Subtype".to_string(),
});
}
match from.dyn_ref::<U>() {
Some(v_e) => Ok(v_e),
None => Err(NokhwaError::StructureError {
structure: name.to_string(),
error: "Cannot Cast".to_string(),
}),
}
}
fn create_element(doc: &Document, element: &str) -> Result<Element, NokhwaError> {
match Document::create_element(doc, element) {
Ok(new_element) => Ok(new_element),
Err(why) => Err(NokhwaError::StructureError {
structure: "Document Video Element".to_string(),
error: format!("{:?}", why.as_string()),
}),
}
}
fn set_autoplay_inline(element: &Element) -> Result<(), NokhwaError> {
if let Err(why) = element.set_attribute("autoplay", "autoplay") {
return Err(NokhwaError::SetPropertyError {
property: "Video-autoplay".to_string(),
value: "autoplay".to_string(),
error: format!("{why:?}"),
});
}
if let Err(why) = element.set_attribute("playsinline", "playsinline") {
return Err(NokhwaError::SetPropertyError {
property: "Video-playsinline".to_string(),
value: "playsinline".to_string(),
error: format!("{why:?}"),
});
}
Ok(())
}
pub async fn request_permission() -> Result<(), NokhwaError> {
let window: Window = window()?;
let navigator = window.navigator();
let media_devices = media_devices(&navigator)?;
match media_devices.get_user_media_with_constraints(
MediaStreamConstraints::new()
.video(&JsValue::from_bool(true))
.audio(&JsValue::from_bool(false)),
) {
Ok(promise) => {
let js_future = JsFuture::from(promise);
match js_future.await {
Ok(stream) => {
let media_stream = MediaStream::from(stream);
media_stream
.get_tracks()
.iter()
.for_each(|track| MediaStreamTrack::from(track).stop());
Ok(())
}
Err(why) => Err(NokhwaError::OpenStreamError(format!("{why:?}"))),
}
}
Err(why) => Err(NokhwaError::StructureError {
structure: "UserMediaPermission".to_string(),
error: format!("{why:?}"),
}),
}
}
#[cfg(feature = "output-wasm")]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = requestPermissions))]
pub async fn js_request_permission() -> Result<(), JsValue> {
if let Err(why) = request_permission().await {
return Err(JsValue::from(why.to_string()));
}
Ok(())
}
pub async fn query_js_cameras() -> Result<Vec<CameraInfo>, NokhwaError> {
let window: Window = window()?;
let navigator = window.navigator();
let media_devices = media_devices(&navigator)?;
match media_devices.enumerate_devices() {
Ok(prom) => {
let prom: Promise = prom;
let future = JsFuture::from(prom);
match future.await {
Ok(v) => {
let array: Array = Array::from(&v);
let mut device_list = vec![];
request_permission().await.unwrap_or(()); for idx_device in 0_u32..array.length() {
if MediaDeviceInfo::instanceof(&array.get(idx_device)) {
let media_device_info =
MediaDeviceInfo::unchecked_from_js(array.get(idx_device));
if media_device_info.kind() == MediaDeviceKind::Videoinput {
match media_devices.get_user_media_with_constraints(
MediaStreamConstraints::new()
.audio(&jsv!(false))
.video(&jsv!(obj!((
"deviceId",
media_device_info.device_id()
)))),
) {
Ok(promised_stream) => {
let future_stream = JsFuture::from(promised_stream);
if let Ok(stream) = future_stream.await {
let stream = MediaStream::from(stream);
let tracks = stream.get_video_tracks();
let first = tracks.get(0);
let name = if first.is_undefined() {
format!(
"{:?}#{}",
media_device_info.kind(),
idx_device
)
} else {
MediaStreamTrack::from(first).label()
};
device_list.push(CameraInfo::new(
&name,
&format!("{:?}", media_device_info.kind()),
&format!(
"{} {}",
media_device_info.group_id(),
media_device_info.device_id()
),
CameraIndex::String(format!(
"{} {}",
media_device_info.group_id(),
media_device_info.device_id()
)),
));
tracks
.iter()
.for_each(|t| MediaStreamTrack::from(t).stop());
}
}
Err(_) => {
device_list.push(CameraInfo::new(
&format!(
"{:?}#{}",
media_device_info.kind(),
idx_device
),
&format!("{:?}", media_device_info.kind()),
&format!(
"{} {}",
media_device_info.group_id(),
media_device_info.device_id()
),
CameraIndex::String(format!(
"{} {}",
media_device_info.group_id(),
media_device_info.device_id()
)),
));
}
}
}
}
}
Ok(device_list)
}
Err(why) => Err(NokhwaError::StructureError {
structure: "EnumerateDevicesFuture".to_string(),
error: format!("{why:?}"),
}),
}
}
Err(why) => Err(NokhwaError::StructureError {
structure: "EnumerateDevices".to_string(),
error: format!("{why:?}"),
}),
}
}
#[cfg(feature = "output-wasm")]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = queryCameras))]
pub async fn js_query_js_cameras() -> Result<Array, JsValue> {
match query_js_cameras().await {
Ok(cameras) => Ok(cameras.into_iter().map(JsValue::from).collect()),
Err(why) => Err(JsValue::from(why.to_string())),
}
}
pub fn query_supported_constraints() -> Result<Vec<JSCameraSupportedCapabilities>, NokhwaError> {
let window: Window = window()?;
let navigator = window.navigator();
let media_devices = media_devices(&navigator)?;
let supported_constraints = JsValue::from(media_devices.get_supported_constraints());
let dict_supported_constraints = Object::from(supported_constraints);
let mut capabilities_vec = vec![];
for constraint in Object::keys(&dict_supported_constraints).iter() {
let constraint_str = JsValue::from(JsString::from(constraint))
.as_string()
.unwrap_or_default();
if let Ok(cap) = JSCameraSupportedCapabilities::try_from(constraint_str) {
capabilities_vec.push(cap);
}
}
Ok(capabilities_vec)
}
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = queryConstraints))]
pub fn query_supported_constraints_js() -> Result<Array, JsValue> {
match query_supported_constraints() {
Ok(constraints) => Ok(constraints
.into_iter()
.map(|c| JsValue::from(c.to_string()))
.collect()),
Err(why) => Err(JsValue::from(why.to_string())),
}
}
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = CameraSupportedCapabilities))]
#[derive(Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub enum JSCameraSupportedCapabilities {
DeviceID,
GroupID,
AspectRatio,
FacingMode,
FrameRate,
Height,
Width,
ResizeMode,
}
impl Display for JSCameraSupportedCapabilities {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let cap = match self {
JSCameraSupportedCapabilities::DeviceID => "deviceId",
JSCameraSupportedCapabilities::GroupID => "groupId",
JSCameraSupportedCapabilities::AspectRatio => "aspectRatio",
JSCameraSupportedCapabilities::FacingMode => "facingMode",
JSCameraSupportedCapabilities::FrameRate => "frameRate",
JSCameraSupportedCapabilities::Height => "height",
JSCameraSupportedCapabilities::Width => "width",
JSCameraSupportedCapabilities::ResizeMode => "resizeMode",
};
write!(f, "{cap}")
}
}
impl Debug for JSCameraSupportedCapabilities {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let str = self.to_string();
write!(f, "{str}")
}
}
impl TryFrom<String> for JSCameraSupportedCapabilities {
type Error = NokhwaError;
fn try_from(value: String) -> Result<Self, Self::Error> {
let value = value.as_str();
let result = match value {
"deviceId" => JSCameraSupportedCapabilities::DeviceID,
"groupId" => JSCameraSupportedCapabilities::GroupID,
"aspectRatio" => JSCameraSupportedCapabilities::AspectRatio,
"facingMode" => JSCameraSupportedCapabilities::FacingMode,
"frameRate" => JSCameraSupportedCapabilities::FrameRate,
"height" => JSCameraSupportedCapabilities::Height,
"width" => JSCameraSupportedCapabilities::Width,
"resizeMode" => JSCameraSupportedCapabilities::ResizeMode,
_ => {
return Err(NokhwaError::StructureError {
structure: "JSCameraSupportedCapabilities".to_string(),
error: "No Match Str".to_string(),
})
}
};
Ok(result)
}
}
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = CameraFacingMode))]
#[derive(Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub enum JSCameraFacingMode {
Any,
Environment,
User,
Left,
Right,
}
impl Display for JSCameraFacingMode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let cap = match self {
JSCameraFacingMode::Environment => "environment",
JSCameraFacingMode::User => "user",
JSCameraFacingMode::Left => "left",
JSCameraFacingMode::Right => "right",
JSCameraFacingMode::Any => "any",
};
write!(f, "{cap}")
}
}
impl Debug for JSCameraFacingMode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let str = self.to_string();
write!(f, "{str}")
}
}
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = CameraResizeMode))]
#[derive(Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub enum JSCameraResizeMode {
Any,
None,
CropAndScale,
}
impl Display for JSCameraResizeMode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let cap = match self {
JSCameraResizeMode::None => "none",
JSCameraResizeMode::CropAndScale => "crop-and-scale",
JSCameraResizeMode::Any => "",
};
write!(f, "{cap}")
}
}
impl Debug for JSCameraResizeMode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let str = self.to_string();
write!(f, "{str}")
}
}
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = CameraConstraintsBuilder))]
#[derive(Clone, Debug)]
#[allow(clippy::struct_excessive_bools)]
pub struct JSCameraConstraintsBuilder {
pub(crate) min_resolution: Option<Resolution>,
pub(crate) preferred_resolution: Resolution,
pub(crate) max_resolution: Option<Resolution>,
pub(crate) resolution_exact: bool,
pub(crate) min_aspect_ratio: Option<f64>,
pub(crate) aspect_ratio: f64,
pub(crate) max_aspect_ratio: Option<f64>,
pub(crate) aspect_ratio_exact: bool,
pub(crate) facing_mode: JSCameraFacingMode,
pub(crate) facing_mode_exact: bool,
pub(crate) min_frame_rate: Option<u32>,
pub(crate) frame_rate: u32,
pub(crate) max_frame_rate: Option<u32>,
pub(crate) frame_rate_exact: bool,
pub(crate) resize_mode: JSCameraResizeMode,
pub(crate) resize_mode_exact: bool,
pub(crate) device_id: String,
pub(crate) device_id_exact: bool,
pub(crate) group_id: String,
pub(crate) group_id_exact: bool,
}
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = CameraConstraintsBuilder))]
impl JSCameraConstraintsBuilder {
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(constructor))]
pub fn new() -> Self {
JSCameraConstraintsBuilder::default()
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = MinResolution)
)]
pub fn min_resolution(mut self, min_resolution: Resolution) -> JSCameraConstraintsBuilder {
self.min_resolution = Some(min_resolution);
self
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = Resolution)
)]
pub fn resolution(mut self, new_resolution: Resolution) -> JSCameraConstraintsBuilder {
self.preferred_resolution = new_resolution;
self
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = MaxResolution)
)]
pub fn max_resolution(mut self, max_resolution: Resolution) -> JSCameraConstraintsBuilder {
self.min_resolution = Some(max_resolution);
self
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = ResolutionExact)
)]
pub fn resolution_exact(mut self, value: bool) -> JSCameraConstraintsBuilder {
self.resolution_exact = value;
self
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = MinAspectRatio)
)]
pub fn min_aspect_ratio(mut self, ratio: f64) -> JSCameraConstraintsBuilder {
self.min_aspect_ratio = Some(ratio);
self
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = AspectRatio)
)]
pub fn aspect_ratio(mut self, ratio: f64) -> JSCameraConstraintsBuilder {
self.aspect_ratio = ratio;
self
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = MaxAspectRatio)
)]
pub fn max_aspect_ratio(mut self, ratio: f64) -> JSCameraConstraintsBuilder {
self.max_aspect_ratio = Some(ratio);
self
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = AspectRatioExact)
)]
pub fn aspect_ratio_exact(mut self, value: bool) -> JSCameraConstraintsBuilder {
self.aspect_ratio_exact = value;
self
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = FacingMode)
)]
pub fn facing_mode(mut self, facing_mode: JSCameraFacingMode) -> JSCameraConstraintsBuilder {
self.facing_mode = facing_mode;
self
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = FacingModeExact)
)]
pub fn facing_mode_exact(mut self, value: bool) -> JSCameraConstraintsBuilder {
self.facing_mode_exact = value;
self
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = MinFrameRate)
)]
pub fn min_frame_rate(mut self, fps: u32) -> JSCameraConstraintsBuilder {
self.min_frame_rate = Some(fps);
self
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = FrameRate)
)]
pub fn frame_rate(mut self, fps: u32) -> JSCameraConstraintsBuilder {
self.frame_rate = fps;
self
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = MaxFrameRate)
)]
pub fn max_frame_rate(mut self, fps: u32) -> JSCameraConstraintsBuilder {
self.max_frame_rate = Some(fps);
self
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = FrameRateExact)
)]
pub fn frame_rate_exact(mut self, value: bool) -> JSCameraConstraintsBuilder {
self.frame_rate_exact = value;
self
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = ResizeMode)
)]
pub fn resize_mode(mut self, resize_mode: JSCameraResizeMode) -> JSCameraConstraintsBuilder {
self.resize_mode = resize_mode;
self
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = ResizeModeExact)
)]
pub fn resize_mode_exact(mut self, value: bool) -> JSCameraConstraintsBuilder {
self.resize_mode_exact = value;
self
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = DeviceId)
)]
pub fn device_id(mut self, id: &str) -> JSCameraConstraintsBuilder {
self.device_id = id.to_string();
self
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = DeviceIdExact)
)]
pub fn device_id_exact(mut self, value: bool) -> JSCameraConstraintsBuilder {
self.device_id_exact = value;
self
}
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = GroupId))]
pub fn group_id(mut self, id: &str) -> JSCameraConstraintsBuilder {
self.group_id = id.to_string();
self
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = GroupIdExact)
)]
pub fn group_id_exact(mut self, value: bool) -> JSCameraConstraintsBuilder {
self.group_id_exact = value;
self
}
#[cfg(feature = "output-wasm")]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = buildCameraConstraints)
)]
#[must_use]
pub fn js_build(self) -> JSCameraConstraints {
self.build()
}
}
impl JSCameraConstraintsBuilder {
#[allow(clippy::too_many_lines)]
#[must_use]
pub fn build(self) -> JSCameraConstraints {
let null_resolution = Resolution::default();
let null_string = String::new();
let mut video_object = Object::new();
if self.resolution_exact {
if self.preferred_resolution != null_resolution {
video_object = obj!(
video_object,
("width", obj!(("exact", self.preferred_resolution.width())))
);
}
} else {
let mut width_object = Object::new();
if let Some(min_res) = self.min_resolution {
width_object = obj!(width_object, ("min", min_res.width()));
}
width_object = obj!(width_object, ("ideal", self.preferred_resolution.width()));
if let Some(max_res) = self.max_resolution {
width_object = obj!(width_object, ("max", max_res.width()));
}
video_object = obj!(video_object, ("width", width_object));
}
if self.resolution_exact {
if self.preferred_resolution != null_resolution {
video_object = obj!(
video_object,
(
"height",
obj!(("exact", self.preferred_resolution.height()))
)
);
}
} else {
let mut height_object = Object::new();
if let Some(min_res) = self.min_resolution {
height_object = obj!(height_object, ("min", min_res.height()));
}
height_object = obj!(height_object, ("ideal", self.preferred_resolution.height()));
if let Some(max_res) = self.max_resolution {
height_object = obj!(height_object, ("max", max_res.height()));
}
video_object = obj!(video_object, ("height", height_object));
}
if self.aspect_ratio_exact {
if self.aspect_ratio != 0_f64 {
video_object = obj!(
video_object,
("aspectRatio", obj!(("exact", self.aspect_ratio)))
);
}
} else {
let mut aspect_ratio_object = Object::new();
if let Some(min_ratio) = self.min_aspect_ratio {
aspect_ratio_object = obj!(aspect_ratio_object, ("min", min_ratio));
}
aspect_ratio_object = obj!(aspect_ratio_object, ("ideal", self.aspect_ratio));
if let Some(max_ratio) = self.max_aspect_ratio {
aspect_ratio_object = obj!(aspect_ratio_object, ("max", max_ratio));
}
video_object = obj!(video_object, ("aspectRatio", aspect_ratio_object));
}
if self.facing_mode != JSCameraFacingMode::Any && self.facing_mode_exact {
video_object = obj!(
video_object,
("facingMode", obj!(("exact", self.facing_mode.to_string())))
);
} else if self.facing_mode != JSCameraFacingMode::Any {
video_object = obj!(
video_object,
("facingMode", obj!(("ideal", self.facing_mode.to_string())))
);
}
if self.frame_rate_exact {
if self.frame_rate != 0 {
video_object = obj!(
video_object,
("frameRate", obj!(("exact", self.frame_rate)))
);
}
} else {
let mut frame_rate_object = Object::new();
if let Some(min_frame_rate) = self.min_frame_rate {
frame_rate_object = obj!(frame_rate_object, ("min", min_frame_rate));
}
frame_rate_object = obj!(frame_rate_object, ("ideal", self.frame_rate));
if let Some(max_frame_rate) = self.max_frame_rate {
frame_rate_object = obj!(frame_rate_object, ("max", max_frame_rate));
}
video_object = obj!(video_object, ("frameRate", frame_rate_object));
}
if self.resize_mode != JSCameraResizeMode::Any && self.resize_mode_exact {
video_object = obj!(
video_object,
("resizeMode", obj!(("exact", self.resize_mode.to_string())))
);
} else if self.resize_mode != JSCameraResizeMode::Any {
video_object = obj!(
video_object,
("resizeMode", obj!(("ideal", self.resize_mode.to_string())))
);
}
if self.device_id != null_string && self.device_id_exact {
video_object = obj!(video_object, ("deviceId", obj!(("exact", &self.device_id))));
} else if self.device_id != null_string {
video_object = obj!(video_object, ("deviceId", obj!(("ideal", &self.device_id))));
}
if self.group_id != null_string && self.group_id_exact {
video_object = obj!(video_object, ("groupId", obj!(("exact", &self.group_id))));
} else if self.group_id != null_string {
video_object = obj!(video_object, ("groupId", obj!(("ideal", &self.group_id))));
}
let media_stream_constraints = MediaStreamConstraints::new()
.audio(&jsv!(false))
.video(&jsv!(video_object))
.clone();
JSCameraConstraints {
media_constraints: media_stream_constraints,
min_resolution: self.min_resolution,
preferred_resolution: self.preferred_resolution,
max_resolution: self.max_resolution,
resolution_exact: self.resolution_exact,
min_aspect_ratio: self.min_aspect_ratio,
aspect_ratio: self.aspect_ratio,
max_aspect_ratio: self.max_aspect_ratio,
aspect_ratio_exact: self.aspect_ratio_exact,
facing_mode: self.facing_mode,
facing_mode_exact: self.facing_mode_exact,
min_frame_rate: self.min_frame_rate,
frame_rate: self.frame_rate,
max_frame_rate: self.max_frame_rate,
frame_rate_exact: self.frame_rate_exact,
resize_mode: self.resize_mode,
resize_mode_exact: self.resize_mode_exact,
device_id: self.device_id,
device_id_exact: self.device_id_exact,
group_id: self.group_id,
group_id_exact: self.device_id_exact,
}
}
}
impl Default for JSCameraConstraintsBuilder {
fn default() -> Self {
JSCameraConstraintsBuilder {
min_resolution: Some(Resolution::new(480, 234)),
preferred_resolution: Resolution::new(640, 360),
max_resolution: Some(Resolution::new(1920, 1080)),
resolution_exact: false,
min_aspect_ratio: Some(1_f64),
aspect_ratio: 1.777_777_777_78_f64,
max_aspect_ratio: Some(2_f64),
aspect_ratio_exact: false,
facing_mode: JSCameraFacingMode::Any,
facing_mode_exact: false,
min_frame_rate: Some(10),
frame_rate: 15,
max_frame_rate: Some(30),
frame_rate_exact: false,
resize_mode: JSCameraResizeMode::Any,
resize_mode_exact: false,
device_id: String::new(),
device_id_exact: false,
group_id: String::new(),
group_id_exact: false,
}
}
}
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = CameraConstraints))]
#[derive(Clone, Debug)]
#[allow(clippy::struct_excessive_bools)]
pub struct JSCameraConstraints {
pub(crate) media_constraints: MediaStreamConstraints,
pub(crate) min_resolution: Option<Resolution>,
pub(crate) preferred_resolution: Resolution,
pub(crate) max_resolution: Option<Resolution>,
pub(crate) resolution_exact: bool,
pub(crate) min_aspect_ratio: Option<f64>,
pub(crate) aspect_ratio: f64,
pub(crate) max_aspect_ratio: Option<f64>,
pub(crate) aspect_ratio_exact: bool,
pub(crate) facing_mode: JSCameraFacingMode,
pub(crate) facing_mode_exact: bool,
pub(crate) min_frame_rate: Option<u32>,
pub(crate) frame_rate: u32,
pub(crate) max_frame_rate: Option<u32>,
pub(crate) frame_rate_exact: bool,
pub(crate) resize_mode: JSCameraResizeMode,
pub(crate) resize_mode_exact: bool,
pub(crate) device_id: String,
pub(crate) device_id_exact: bool,
pub(crate) group_id: String,
pub(crate) group_id_exact: bool,
}
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = CameraConstraints))]
impl JSCameraConstraints {
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = MediaStreamConstraints)
)]
pub fn media_constraints(&self) -> MediaStreamConstraints {
self.media_constraints.clone()
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = MinResolution)
)]
#[must_use]
pub fn min_resolution(&self) -> Option<Resolution> {
self.min_resolution
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = MinResolution)
)]
pub fn set_min_resolution(&mut self, min_resolution: Resolution) {
self.min_resolution = Some(min_resolution);
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = Resolution)
)]
pub fn resolution(&self) -> Resolution {
self.preferred_resolution
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = Resolution)
)]
pub fn set_resolution(&mut self, preferred_resolution: Resolution) {
self.preferred_resolution = preferred_resolution;
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = MaxResolution)
)]
#[must_use]
pub fn max_resolution(&self) -> Option<Resolution> {
self.max_resolution
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = MaxResolution)
)]
pub fn set_max_resolution(&mut self, max_resolution: Resolution) {
self.max_resolution = Some(max_resolution);
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = ResolutionExact)
)]
pub fn resolution_exact(&self) -> bool {
self.resolution_exact
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = ResolutionExact)
)]
pub fn set_resolution_exact(&mut self, resolution_exact: bool) {
self.resolution_exact = resolution_exact;
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = MinAspectRatio)
)]
pub fn min_aspect_ratio(&self) -> Option<f64> {
self.min_aspect_ratio
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = MinAspectRatio)
)]
pub fn set_min_aspect_ratio(&mut self, min_aspect_ratio: f64) {
self.min_aspect_ratio = Some(min_aspect_ratio);
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = AspectRatio)
)]
pub fn aspect_ratio(&self) -> f64 {
self.aspect_ratio
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = AspectRatio)
)]
pub fn set_aspect_ratio(&mut self, aspect_ratio: f64) {
self.aspect_ratio = aspect_ratio;
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = MaxAspectRatio)
)]
#[must_use]
pub fn max_aspect_ratio(&self) -> Option<f64> {
self.max_aspect_ratio
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = MaxAspectRatio)
)]
pub fn set_max_aspect_ratio(&mut self, max_aspect_ratio: f64) {
self.max_aspect_ratio = Some(max_aspect_ratio);
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = AspectRatioExact)
)]
pub fn aspect_ratio_exact(&self) -> bool {
self.aspect_ratio_exact
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = AspectRatioExact)
)]
pub fn set_aspect_ratio_exact(&mut self, aspect_ratio_exact: bool) {
self.aspect_ratio_exact = aspect_ratio_exact;
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = FacingMode)
)]
pub fn facing_mode(&self) -> JSCameraFacingMode {
self.facing_mode
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = FacingMode)
)]
pub fn set_facing_mode(&mut self, facing_mode: JSCameraFacingMode) {
self.facing_mode = facing_mode;
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = FacingModeExact)
)]
pub fn facing_mode_exact(&self) -> bool {
self.facing_mode_exact
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = FacingModeExact)
)]
pub fn set_facing_mode_exact(&mut self, facing_mode_exact: bool) {
self.facing_mode_exact = facing_mode_exact;
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = MinFrameRate)
)]
#[must_use]
pub fn min_frame_rate(&self) -> Option<u32> {
self.min_frame_rate
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = MinFrameRate)
)]
pub fn set_min_frame_rate(&mut self, min_frame_rate: u32) {
self.min_frame_rate = Some(min_frame_rate);
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = FrameRate)
)]
pub fn frame_rate(&self) -> u32 {
self.frame_rate
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = FrameRate)
)]
pub fn set_frame_rate(&mut self, frame_rate: u32) {
self.frame_rate = frame_rate;
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = MaxFrameRate)
)]
#[must_use]
pub fn max_frame_rate(&self) -> Option<u32> {
self.max_frame_rate
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = MaxFrameRate)
)]
pub fn set_max_frame_rate(&mut self, max_frame_rate: u32) {
self.max_frame_rate = Some(max_frame_rate);
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = FrameRateExact)
)]
pub fn frame_rate_exact(&self) -> bool {
self.frame_rate_exact
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = FrameRateExact)
)]
pub fn set_frame_rate_exact(&mut self, frame_rate_exact: bool) {
self.frame_rate_exact = frame_rate_exact;
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = ResizeMode)
)]
pub fn resize_mode(&self) -> JSCameraResizeMode {
self.resize_mode
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = ResizeMode)
)]
pub fn set_resize_mode(&mut self, resize_mode: JSCameraResizeMode) {
self.resize_mode = resize_mode;
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = ResizeModeExact)
)]
pub fn resize_mode_exact(&self) -> bool {
self.resize_mode_exact
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = ResizeModeExact)
)]
pub fn set_resize_mode_exact(&mut self, resize_mode_exact: bool) {
self.resize_mode_exact = resize_mode_exact;
}
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = DeviceId))]
pub fn device_id(&self) -> String {
self.device_id.to_string()
}
#[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = DeviceId))]
pub fn set_device_id(&mut self, device_id: String) {
self.device_id = device_id;
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = DeviceIdExact)
)]
pub fn device_id_exact(&self) -> bool {
self.device_id_exact
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = DeviceIdExact)
)]
pub fn set_device_id_exact(&mut self, device_id_exact: bool) {
self.device_id_exact = device_id_exact;
}
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = GroupId))]
pub fn group_id(&self) -> String {
self.group_id.to_string()
}
#[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = GroupId))]
pub fn set_group_id(&mut self, group_id: String) {
self.group_id = group_id;
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = GroupIdExact)
)]
pub fn group_id_exact(&self) -> bool {
self.group_id_exact
}
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = GroupIdExact)
)]
pub fn set_group_id_exact(&mut self, group_id_exact: bool) {
self.group_id_exact = group_id_exact;
}
#[cfg(feature = "output-wasm")]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = applyConstraints))]
pub fn js_apply_constraints(&mut self) {
self.apply_constraints();
}
}
impl JSCameraConstraints {
pub fn apply_constraints(&mut self) {
let new_constraints = JSCameraConstraintsBuilder {
min_resolution: self.min_resolution(),
preferred_resolution: self.resolution(),
max_resolution: self.max_resolution(),
resolution_exact: self.resolution_exact(),
min_aspect_ratio: self.min_aspect_ratio(),
aspect_ratio: self.aspect_ratio(),
max_aspect_ratio: self.max_aspect_ratio(),
aspect_ratio_exact: self.aspect_ratio_exact(),
facing_mode: self.facing_mode(),
facing_mode_exact: self.facing_mode_exact(),
min_frame_rate: self.min_frame_rate(),
frame_rate: self.frame_rate(),
max_frame_rate: self.max_frame_rate(),
frame_rate_exact: self.frame_rate_exact(),
resize_mode: self.resize_mode(),
resize_mode_exact: self.resize_mode_exact(),
device_id: self.device_id(),
device_id_exact: self.device_id_exact(),
group_id: self.group_id(),
group_id_exact: self.group_id_exact(),
}
.build();
self.media_constraints = new_constraints.media_constraints;
}
}
impl Deref for JSCameraConstraints {
type Target = MediaStreamConstraints;
fn deref(&self) -> &Self::Target {
&self.media_constraints
}
}
#[cfg(feature = "input-jscam")]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = NokhwaCamera))]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-jscam")))]
pub struct JSCamera {
media_stream: MediaStream,
constraints: JSCameraConstraints,
attached: bool,
attached_node: Option<Node>,
measured_resolution: Resolution,
attached_canvas: Option<HtmlCanvasElement>,
canvas_context: Option<CanvasRenderingContext2d>,
}
#[cfg(feature = "input-jscam")]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = NokhwaCamera))]
impl JSCamera {
#[cfg(feature = "output-wasm")]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(constructor))]
pub async fn js_new(constraints: JSCameraConstraints) -> Result<JSCamera, JsValue> {
match JSCamera::new(constraints).await {
Ok(camera) => Ok(camera),
Err(why) => Err(JsValue::from(why.to_string())),
}
}
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Constraints))]
pub fn constraints(&self) -> JSCameraConstraints {
self.constraints.clone()
}
#[cfg(feature = "output-wasm")]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(setter = Constraints)
)]
pub fn js_set_constraints(&mut self, constraints: JSCameraConstraints) -> Result<(), JsValue> {
match self.set_constraints(constraints) {
Ok(_) => Ok(()),
Err(why) => Err(JsValue::from(why.to_string())),
}
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = Resolution)
)]
pub fn resolution(&self) -> Resolution {
self.measured_resolution
}
#[cfg(feature = "output-wasm")]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(js_name = measureResolution)
)]
pub fn js_measure_resolution(&mut self) -> Result<(), JsValue> {
if let Err(why) = self.measure_resolution() {
return Err(JsValue::from(why.to_string()));
}
Ok(())
}
#[cfg(feature = "output-wasm")]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = applyConstraints))]
pub fn js_apply_constraints(&mut self) -> Result<(), JsValue> {
if let Err(why) = self.apply_constraints() {
return Err(JsValue::from(why.to_string()));
}
Ok(())
}
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = MediaStream)
)]
pub fn media_stream(&self) -> MediaStream {
self.media_stream.clone()
}
#[cfg(feature = "output-wasm")]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = captureImageData))]
pub fn js_frame_image_data(&mut self) -> Result<ImageData, JsValue> {
match self.frame_image_data() {
Ok(img) => Ok(img),
Err(why) => Err(JsValue::from(why.to_string())),
}
}
#[cfg(feature = "output-wasm")]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = captureImageURI))]
pub fn js_frame_image_data_uri(
&mut self,
mime_type: &str,
image_quality: f64,
) -> Result<String, JsValue> {
match self.frame_uri(Some(mime_type), Some(image_quality)) {
Ok(uri) => Ok(uri),
Err(why) => Err(JsValue::from(why.to_string())),
}
}
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = captureFrameRawData))]
pub fn js_frame_raw(&mut self) -> Result<Box<[u8]>, JsValue> {
match self.frame_raw() {
Ok(frame) => Ok(frame.iter().copied().collect()),
Err(why) => Err(JsValue::from(why.to_string())),
}
}
#[cfg(feature = "output-wasm")]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = copyToCanvas))]
pub fn js_frame_canvas_copy(
&mut self,
html_id: &str,
generate_new: bool,
) -> Result<(), JsValue> {
match self.frame_canvas_copy(html_id, generate_new) {
Ok(_) => Ok(()),
Err(why) => Err(JsValue::from(why.to_string())),
}
}
#[cfg(feature = "output-wasm")]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = attachToElement))]
pub fn js_attach(&mut self, html_id: &str, generate_new: bool) -> Result<(), JsValue> {
if let Err(why) = self.attach(html_id, generate_new) {
return Err(JsValue::from(why.to_string()));
}
Ok(())
}
#[cfg(feature = "output-wasm")]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = detachCamera))]
pub fn js_detach(&mut self) -> Result<(), JsValue> {
if let Err(why) = self.detach() {
return Err(JsValue::from(why.to_string()));
}
Ok(())
}
#[cfg(feature = "output-wasm")]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = stopAll))]
pub fn js_stop_all(&mut self) -> Result<(), JsValue> {
if let Err(why) = self.stop_all() {
return Err(JsValue::from(why.to_string()));
}
Ok(())
}
}
impl JSCamera {
pub async fn new(constraints: JSCameraConstraints) -> Result<Self, NokhwaError> {
let window: Window = window()?;
let navigator = window.navigator();
let media_devices = media_devices(&navigator)?;
let stream: MediaStream = match media_devices.get_user_media_with_constraints(&constraints)
{
Ok(promise) => {
let future = JsFuture::from(promise);
match future.await {
Ok(stream) => {
let media_stream: MediaStream = MediaStream::from(stream);
media_stream
}
Err(why) => {
return Err(NokhwaError::StructureError {
structure: "MediaDevicesGetUserMediaJsFuture".to_string(),
error: format!("{why:?}"),
})
}
}
}
Err(why) => {
return Err(NokhwaError::StructureError {
structure: "MediaDevicesGetUserMedia".to_string(),
error: format!("{why:?}"),
})
}
};
let mut js_camera = JSCamera {
media_stream: stream,
constraints,
attached: false,
attached_node: None,
measured_resolution: Resolution::new(0, 0),
attached_canvas: None,
canvas_context: None,
};
js_camera.measure_resolution()?;
Ok(js_camera)
}
pub fn apply_constraints(&mut self) -> Result<(), NokhwaError> {
let new_constraints = JSCameraConstraintsBuilder {
min_resolution: self.constraints.min_resolution(),
preferred_resolution: self.constraints.resolution(),
max_resolution: self.constraints.max_resolution(),
resolution_exact: self.constraints.resolution_exact(),
min_aspect_ratio: self.constraints.min_aspect_ratio(),
aspect_ratio: self.constraints.aspect_ratio(),
max_aspect_ratio: self.constraints.max_aspect_ratio(),
aspect_ratio_exact: self.constraints.aspect_ratio_exact(),
facing_mode: self.constraints.facing_mode(),
facing_mode_exact: self.constraints.facing_mode_exact(),
min_frame_rate: self.constraints.min_frame_rate(),
frame_rate: self.constraints.frame_rate(),
max_frame_rate: self.constraints.max_frame_rate(),
frame_rate_exact: self.constraints.frame_rate_exact(),
resize_mode: self.constraints.resize_mode(),
resize_mode_exact: self.constraints.resize_mode_exact(),
device_id: self.constraints.device_id(),
device_id_exact: self.constraints.device_id_exact(),
group_id: self.constraints.group_id(),
group_id_exact: self.constraints.group_id_exact(),
}
.build();
self.constraints.media_constraints = new_constraints.media_constraints;
self.measure_resolution()?;
Ok(())
}
pub fn set_constraints(&mut self, constraints: JSCameraConstraints) -> Result<(), NokhwaError> {
let current = std::mem::replace(&mut self.constraints, constraints);
if let Err(why) = self.apply_constraints() {
self.constraints = current;
return Err(why);
}
Ok(())
}
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
pub fn measure_resolution(&mut self) -> Result<(), NokhwaError> {
let stream = self
.media_stream
.get_video_tracks()
.iter()
.next()
.unwrap_or_else(JsValue::undefined);
if !stream.is_undefined() {
let stream = MediaStreamTrack::from(stream);
let settings_map = {
let settings_array = Object::entries(&stream.get_settings());
let settings_map = Map::new();
settings_array.iter().for_each(|arr_elem| {
let arr_elem = Array::from(&arr_elem);
let key = arr_elem.get(0);
let value = arr_elem.get(1);
if key != JsValue::UNDEFINED && value != JsValue::UNDEFINED {
settings_map.set(&key, &value);
}
});
settings_map
};
self.measured_resolution = Resolution::new(
settings_map.get(&jsv!("width")).as_f64().unwrap_or(0_f64) as u32,
settings_map.get(&jsv!("height")).as_f64().unwrap_or(0_f64) as u32,
);
return Ok(());
}
Err(NokhwaError::ReadFrameError("Null Stream".to_string()))
}
pub fn attach(&mut self, html_id: &str, generate_new: bool) -> Result<(), NokhwaError> {
let window: Window = window()?;
let document: Document = document(&window)?;
let selected_element: Element = document_select_elem(&document, html_id)?;
self.measure_resolution()?;
if generate_new {
let video_element = create_element(&document, "video")?;
set_autoplay_inline(&video_element)?;
let video_element: HtmlVideoElement =
element_cast::<Element, HtmlVideoElement>(video_element, "HtmlVideoElement")?;
video_element.set_width(self.resolution().width());
video_element.set_height(self.resolution().height());
video_element.set_src_object(Some(&self.media_stream()));
video_element.set_id(&format!("{html_id}-video"));
return match selected_element.append_child(&Node::from(video_element)) {
Ok(n) => {
self.attached_node = Some(n);
self.attached = true;
Ok(())
}
Err(why) => Err(NokhwaError::StructureError {
structure: "Attach Error".to_string(),
error: format!("{why:?}"),
}),
};
}
set_autoplay_inline(&selected_element)?;
let selected_element =
element_cast::<Element, HtmlVideoElement>(selected_element, "HtmlVideoElement")?;
selected_element.set_width(self.resolution().width());
selected_element.set_height(self.resolution().height());
selected_element.set_src_object(Some(&self.media_stream()));
self.attached_node = Some(Node::from(selected_element));
self.attached = true;
Ok(())
}
pub fn detach(&mut self) -> Result<(), NokhwaError> {
if !self.attached {
return Ok(());
}
let attached: &Node = match &self.attached_node {
Some(node) => node,
None => return Ok(()),
};
let attached = element_cast_ref::<Node, HtmlVideoElement>(attached, "HtmlVideoElement")?;
attached.set_src_object(None);
self.attached_node = None;
self.attached = false;
Ok(())
}
#[allow(clippy::too_many_lines)]
fn draw_to_canvas(&mut self) -> Result<(), NokhwaError> {
let window: Window = window()?;
let document: Document = document(&window)?;
self.measure_resolution()?;
if self.attached_canvas.is_none() {
let canvas = create_element(&document, "canvas")?;
match document.body() {
Some(body) => {
if let Err(why) = body.append_child(&canvas) {
return Err(NokhwaError::ReadFrameError(format!(
"Failed to attach canvas: {:?}",
why
)));
}
}
None => {
return Err(NokhwaError::ReadFrameError(
"Failed to get body".to_string(),
))
}
}
let canvas = element_cast::<Element, HtmlCanvasElement>(canvas, "HtmlCanvasElement")?;
canvas.set_hidden(true);
self.canvas_context = match canvas.get_context("2d") {
Ok(maybe_ctx) => match maybe_ctx {
Some(ctx) => Some(element_cast::<Object, CanvasRenderingContext2d>(
ctx,
"CanvasRenderingContext2d",
)?),
None => {
return Err(NokhwaError::StructureError {
structure: "HtmlCanvasElement Context 2D".to_string(),
error: "None".to_string(),
});
}
},
Err(why) => {
return Err(NokhwaError::StructureError {
structure: "HtmlCanvasElement Context 2D".to_string(),
error: format!("{why:?}"),
});
}
};
self.attached_canvas = Some(canvas);
}
let canvas = match &self.attached_canvas {
Some(canvas) => canvas,
None => {
return Err(NokhwaError::GetPropertyError {
property: "Canvas".to_string(),
error: "None".to_string(),
});
}
};
canvas.set_width(self.resolution().width());
canvas.set_height(self.resolution().height());
let context = match &self.canvas_context {
Some(cc) => cc,
None => {
return Err(NokhwaError::StructureError {
structure: "CanvasContext".to_string(),
error: "None".to_string(),
})
}
};
if self.attached && self.attached_node.is_some() {
let video_element = match &self.attached_node {
Some(n) => element_cast_ref::<Node, HtmlVideoElement>(n, "HtmlVideoElement")?,
None => {
return Err(NokhwaError::StructureError {
structure: "Document Attached Video Element".to_string(),
error: "None".to_string(),
});
}
};
if let Err(why) = context.draw_image_with_html_video_element_and_dw_and_dh(
video_element,
0_f64,
0_f64,
self.resolution().width().into(),
self.resolution().height().into(),
) {
return Err(NokhwaError::ReadFrameError(format!("{why:?}")));
}
match context.get_image_data(
0_f64,
0_f64,
self.resolution().width().into(),
self.resolution().height().into(),
) {
Ok(data) => log_1(&jsv!(data)),
Err(why) => {
return Err(NokhwaError::ReadFrameError(format!("{why:?}")));
}
};
} else {
let video_element = match document.create_element("video") {
Ok(new_element) => new_element,
Err(why) => {
return Err(NokhwaError::StructureError {
structure: "Document Video Element".to_string(),
error: format!("{why:?}"),
})
}
};
set_autoplay_inline(&video_element)?;
let video_element: HtmlVideoElement =
element_cast::<Element, HtmlVideoElement>(video_element, "HtmlVideoElement")?;
video_element.set_width(self.resolution().width());
video_element.set_height(self.resolution().height());
video_element.set_src_object(Some(&self.media_stream()));
video_element.set_hidden(true);
match document.body() {
Some(body) => {
if let Err(why) = body.append_child(&video_element) {
return Err(NokhwaError::ReadFrameError(format!(
"Failed to attach video: {:?}",
why
)));
}
}
None => {
return Err(NokhwaError::ReadFrameError(
"Failed to get body".to_string(),
))
}
}
if let Err(why) = context.draw_image_with_html_video_element_and_dw_and_dh(
&video_element,
0_f64,
0_f64,
self.resolution().width().into(),
self.resolution().height().into(),
) {
return Err(NokhwaError::ReadFrameError(format!("{why:?}")));
}
match document.body() {
Some(body) => {
if let Err(why) = body.remove_child(&video_element) {
return Err(NokhwaError::ReadFrameError(format!(
"Failed to remove video: {why:?}"
)));
}
}
None => {
return Err(NokhwaError::ReadFrameError(
"Failed to get body".to_string(),
))
}
}
}
Ok(())
}
#[allow(clippy::must_use_candidate)]
#[allow(clippy::too_many_lines)]
pub fn frame_canvas_copy(
&mut self,
html_id: &str,
generate_new: bool,
) -> Result<(HtmlCanvasElement, CanvasRenderingContext2d), NokhwaError> {
let window: Window = window()?;
let document: Document = document(&window)?;
let selected_element: Element = document_select_elem(&document, html_id)?;
self.measure_resolution()?;
self.draw_to_canvas()?;
if generate_new {
let new_canvas = create_element(&document, "canvas")?;
let new_canvas =
element_cast::<Element, HtmlCanvasElement>(new_canvas, "HtmlCanvasElement")?;
new_canvas.set_width(self.resolution().width());
new_canvas.set_height(self.resolution().height());
new_canvas.set_id(&format!("{html_id}-canvas"));
if let Err(why) = selected_element.append_child(&new_canvas) {
return Err(NokhwaError::StructureError {
structure: "HtmlCanvasElement".to_string(),
error: format!("add child: {why:?}"),
});
}
let context = match new_canvas.get_context("2d") {
Ok(objcontext) => match objcontext {
Some(c2d) => c2d,
None => {
return Err(NokhwaError::StructureError {
structure: "CanvasRenderingContext2d".to_string(),
error: "No context".to_string(),
});
}
},
Err(why) => {
return Err(NokhwaError::StructureError {
structure: "CanvasRenderingContext2d".to_string(),
error: format!("context: {why:?}"),
});
}
};
let self_canvas = match &self.attached_canvas {
Some(c) => c,
None => {
return Err(NokhwaError::StructureError {
structure: "HtmlCanvasElement".to_string(),
error: "Is None?".to_string(),
});
}
};
let context = element_cast::<Object, CanvasRenderingContext2d>(
context,
"CanvasRenderingContext2d",
)?;
if let Err(why) = context.draw_image_with_html_canvas_element_and_dw_and_dh(
self_canvas,
0_f64,
0_f64,
self.resolution().width().into(),
self.resolution().height().into(),
) {
return Err(NokhwaError::ReadFrameError(format!(
"Failed to draw: {:?}",
why
)));
}
Ok((new_canvas, context))
} else {
let canvas =
element_cast::<Element, HtmlCanvasElement>(selected_element, "HtmlCanvasElement")?;
let context = match canvas.get_context("2d") {
Ok(objcontext) => match objcontext {
Some(c2d) => c2d,
None => {
return Err(NokhwaError::StructureError {
structure: "CanvasRenderingContext2d".to_string(),
error: "No context".to_string(),
});
}
},
Err(why) => {
return Err(NokhwaError::StructureError {
structure: "CanvasRenderingContext2d".to_string(),
error: format!("context: {why:?}"),
});
}
};
let self_canvas = match &self.attached_canvas {
Some(c) => c,
None => {
return Err(NokhwaError::StructureError {
structure: "HtmlCanvasElement".to_string(),
error: "Is None?".to_string(),
});
}
};
let context = element_cast::<Object, CanvasRenderingContext2d>(
context,
"CanvasRenderingContext2d",
)?;
if let Err(why) = context.draw_image_with_html_canvas_element_and_dw_and_dh(
self_canvas,
0_f64,
0_f64,
self.resolution().width().into(),
self.resolution().height().into(),
) {
return Err(NokhwaError::ReadFrameError(format!(
"Failed to draw: {:?}",
why
)));
}
Ok((canvas, context))
}
}
pub fn frame_image_data(&mut self) -> Result<ImageData, NokhwaError> {
self.draw_to_canvas()?;
let context = match &self.canvas_context {
Some(cc) => cc,
None => {
return Err(NokhwaError::StructureError {
structure: "CanvasContext".to_string(),
error: "None".to_string(),
})
}
};
let image_data = match context.get_image_data(
0_f64,
0_f64,
self.resolution().width().into(),
self.resolution().height().into(),
) {
Ok(data) => data,
Err(why) => {
return Err(NokhwaError::ReadFrameError(format!("{why:?}")));
}
};
Ok(image_data)
}
pub fn frame_uri(
&mut self,
mime_type: Option<&str>,
image_quality: Option<f64>,
) -> Result<String, NokhwaError> {
let mime_type = mime_type.unwrap_or("image/png");
let image_quality = JsValue::from(image_quality.unwrap_or(0.92_f64));
self.draw_to_canvas()?;
let canvas = match &self.attached_canvas {
Some(c) => c,
None => return Err(NokhwaError::ReadFrameError("No Canvas".to_string())),
};
match canvas.to_data_url_with_type_and_encoder_options(mime_type, &image_quality) {
Ok(uri) => Ok(uri),
Err(why) => Err(NokhwaError::ReadFrameError(format!("{why:?}"))),
}
}
pub fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
let image_data = self.frame_image_data()?.data().0;
Ok(Cow::from(image_data))
}
pub fn frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
let raw_data = self.frame_raw()?.to_vec();
let resolution = self.resolution();
let image_buf =
match ImageBuffer::from_vec(resolution.width(), resolution.height(), raw_data) {
Some(buf) => {
let rgba_buf: ImageBuffer<Rgba<u8>, Vec<u8>> = buf;
let rgb_image_converted: ImageBuffer<Rgb<u8>, Vec<u8>> = rgba_buf.convert();
rgb_image_converted
}
None => return Err(NokhwaError::ReadFrameError(
"ImageBuffer is not large enough! This is probably a bug, please report it!"
.to_string(),
)),
};
Ok(image_buf)
}
pub fn rgba_frame(&mut self) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>, NokhwaError> {
let raw_data = self.frame_raw()?.to_vec();
let resolution = self.resolution();
let image_buf =
match ImageBuffer::from_vec(resolution.width(), resolution.height(), raw_data) {
Some(buf) => {
let rgba_buf: ImageBuffer<Rgba<u8>, Vec<u8>> = buf;
rgba_buf
}
None => return Err(NokhwaError::ReadFrameError(
"ImageBuffer is not large enough! This is probably a bug, please report it!"
.to_string(),
)),
};
Ok(image_buf)
}
#[must_use]
pub fn min_buffer_size(&self, use_rgba: bool) -> usize {
let resolution = self.resolution();
if use_rgba {
(resolution.width() * resolution.height() * 4) as usize
} else {
(resolution.width() * resolution.height() * 3) as usize
}
}
pub fn write_frame_to_buffer(
&mut self,
buffer: &mut [u8],
convert_rgba: bool,
) -> Result<usize, NokhwaError> {
let resolution = self.resolution();
let frame = self.frame_raw()?;
if convert_rgba {
buffer.copy_from_slice(frame.borrow());
return Ok(frame.len());
}
let image = match ImageBuffer::from_raw(resolution.width(), resolution.height(), frame) {
Some(image) => {
let image: ImageBuffer<Rgba<u8>, Cow<[u8]>> = image;
let rgb_image: RgbImage = image.convert();
rgb_image
}
None => {
return Err(NokhwaError::ReadFrameError(
"Frame Cow Too Small".to_string(),
))
}
};
buffer.copy_from_slice(image.as_raw());
Ok(image.len())
}
#[cfg(feature = "output-wgpu")]
pub fn frame_texture<'a>(
&mut self,
device: &Device,
queue: &Queue,
label: Option<&'a str>,
) -> Result<Texture, NokhwaError> {
use std::num::NonZeroU32;
let resolution = self.resolution();
let frame = self.frame_raw()?;
let texture_size = Extent3d {
width: resolution.width(),
height: resolution.height(),
depth_or_array_layers: 1,
};
let texture = device.create_texture(&TextureDescriptor {
label,
size: texture_size,
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Rgba8UnormSrgb,
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
});
let width_nonzero = match NonZeroU32::try_from(4 * resolution.width()) {
Ok(w) => Some(w),
Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())),
};
let height_nonzero = match NonZeroU32::try_from(resolution.height()) {
Ok(h) => Some(h),
Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())),
};
queue.write_texture(
ImageCopyTexture {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: TextureAspect::All,
},
frame.borrow(),
ImageDataLayout {
offset: 0,
bytes_per_row: width_nonzero,
rows_per_image: height_nonzero,
},
texture_size,
);
Ok(texture)
}
pub fn is_open(&self) -> bool {
let stream = self
.media_stream()
.get_video_tracks()
.iter()
.next()
.unwrap_or_else(JsValue::undefined);
if !stream.is_undefined() {
let stream = MediaStreamTrack::from(stream);
if stream.ready_state() == MediaStreamTrackState::Live && stream.enabled() {
return true;
}
}
false
}
pub async fn restart(&mut self) -> Result<(), NokhwaError> {
let window: Window = window()?;
let navigator = window.navigator();
let media_devices = media_devices(&navigator)?;
let stream: MediaStream = match media_devices
.get_user_media_with_constraints(&self.constraints.media_constraints)
{
Ok(promise) => {
let future = JsFuture::from(promise);
match future.await {
Ok(stream) => {
let media_stream: MediaStream = MediaStream::from(stream);
media_stream
}
Err(why) => {
return Err(NokhwaError::StructureError {
structure: "MediaDevicesGetUserMediaJsFuture".to_string(),
error: format!("{why:?}"),
})
}
}
}
Err(why) => {
return Err(NokhwaError::StructureError {
structure: "MediaDevicesGetUserMedia".to_string(),
error: format!("{why:?}"),
})
}
};
self.media_stream = stream;
Ok(())
}
pub fn stop_all(&mut self) -> Result<(), NokhwaError> {
self.detach()?;
self.media_stream.get_tracks().iter().for_each(|track| {
let media_track = MediaStreamTrack::from(track);
media_track.stop();
});
Ok(())
}
}
impl Deref for JSCamera {
type Target = MediaStream;
fn deref(&self) -> &Self::Target {
&self.media_stream
}
}
impl Drop for JSCamera {
fn drop(&mut self) {
self.stop_all().unwrap_or(()); }
}
#[allow(clippy::non_send_fields_in_send_ty)]
unsafe impl Send for JSCamera {}