1use bardecoder::default_decoder;
2use gloo::timers::callback::Interval;
3use gloo::utils::errors::JsError;
4use gloo::utils::window;
5use js_sys::Uint8Array;
6use std::sync::Arc;
7use wasm_bindgen::closure::Closure;
8use wasm_bindgen::JsCast;
9use web_sys::{
10 Blob, CanvasRenderingContext2d, HtmlCanvasElement, HtmlVideoElement, MediaStream,
11 MediaStreamConstraints, MediaTrackConstraints, VideoFacingModeEnum,
12};
13use yew::prelude::*;
14
15pub struct Scanner {
16 video_ref: NodeRef,
17 canvas_ref: NodeRef,
18 stream: Option<MediaStream>,
19 scanner_interval: Option<Interval>,
20 canvas_closure: Option<Arc<Closure<dyn Fn(Blob) -> ()>>>,
21}
22
23pub enum ScannerMessage {
24 ReceivedStream(MediaStream),
25 CapturedImage(Blob),
26 Error(JsError),
27 ImageDone,
28}
29
30#[derive(Properties, PartialEq, Clone)]
31pub struct ScannerProps {
32 #[prop_or_default]
33 pub onscan: Callback<String>,
34 #[prop_or_default]
35 pub onerror: Callback<JsError>,
36}
37
38impl Component for Scanner {
39 type Message = ScannerMessage;
40 type Properties = ScannerProps;
41
42 fn create(ctx: &Context<Self>) -> Self {
43 ctx.link().send_future(async {
44 let mut constraints = MediaStreamConstraints::new();
45 let mut video_constraints = MediaTrackConstraints::new();
46 video_constraints
47 .facing_mode(&VideoFacingModeEnum::Environment.into())
48 .frame_rate(&4.into());
49 constraints.video(&video_constraints);
50 match window().navigator().media_devices() {
51 Ok(devs) => match devs.get_user_media_with_constraints(&constraints) {
52 Ok(promise) => match wasm_bindgen_futures::JsFuture::from(promise).await {
53 Ok(stream) => ScannerMessage::ReceivedStream(stream.unchecked_into()),
54 Err(e) => ScannerMessage::Error(JsError::try_from(e).unwrap()),
55 },
56 Err(e) => ScannerMessage::Error(JsError::try_from(e).unwrap()),
57 },
58 Err(e) => ScannerMessage::Error(JsError::try_from(e).unwrap()),
59 }
60 });
61 Self {
62 video_ref: NodeRef::default(),
63 canvas_ref: NodeRef::default(),
64 stream: None,
65 scanner_interval: None,
66 canvas_closure: None,
67 }
68 }
69
70 fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
71 match msg {
72 ScannerMessage::ReceivedStream(stream) => {
73 self.stream = Some(stream);
74 true
75 }
76 ScannerMessage::CapturedImage(image_src) => {
77 let decoder = default_decoder();
78 let callback = ctx.props().onscan.clone();
79 let link = ctx.link().clone();
80 ctx.link().send_future(async move {
81 match wasm_bindgen_futures::JsFuture::from(image_src.array_buffer()).await {
82 Ok(array_buffer) => {
83 let array = Uint8Array::new(&array_buffer);
84 let bytes: Vec<u8> = array.to_vec();
85 match image::load_from_memory(&bytes) {
86 Ok(image) => {
87 for decode_result in decoder.decode(&image).iter() {
88 match decode_result {
89 Ok(s) => callback.emit(s.clone()),
90 Err(e) => link.send_message(ScannerMessage::Error(
91 JsError::from(js_sys::Error::new(
92 e.to_string().as_str(),
93 )),
94 )),
95 }
96 }
97 ScannerMessage::ImageDone
98 }
99 Err(e) => {
100 let error = js_sys::Error::new(e.to_string().as_str());
101 ScannerMessage::Error(JsError::from(error))
102 }
103 }
104 }
105 Err(e) => ScannerMessage::Error(JsError::try_from(e).unwrap()),
106 }
107 });
108 false
109 }
110 ScannerMessage::Error(e) => {
111 ctx.props().onerror.emit(e);
112 false
113 }
114 ScannerMessage::ImageDone => false,
115 }
116 }
117
118 fn view(&self, _ctx: &Context<Self>) -> Html {
119 let html = html! {
120 <>
121 <video ref={&self.video_ref} autoPlay="true" style="width:300px;height:300px;" />
122 <canvas ref={&self.canvas_ref} width="1920" height="1080" style="display: none;"></canvas>
123 </>
124 };
125 html
126 }
127
128 fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
129 let video = self
130 .video_ref
131 .cast::<HtmlVideoElement>()
132 .expect("video should be an HtmlVideoElement");
133 video.set_src_object(self.stream.as_ref().clone());
134 let canvas = self
135 .canvas_ref
136 .cast::<HtmlCanvasElement>()
137 .expect("canvas should be an HtmlCanvasElement");
138 let context = canvas
139 .get_context("2d")
140 .expect("context should be available")
141 .unwrap()
142 .unchecked_into::<CanvasRenderingContext2d>();
143
144 let link = ctx.link().clone();
145 let link_callback = link.clone();
146 self.canvas_closure = Some(Arc::new(Closure::wrap(Box::new(move |blob: Blob| {
147 link_callback.send_message(ScannerMessage::CapturedImage(blob));
148 }) as Box<dyn Fn(Blob)>)));
149 let canvas_closure_ref = self.canvas_closure.as_ref().unwrap().clone();
150 self.scanner_interval = Some(Interval::new(500, move || {
151 context
152 .draw_image_with_html_video_element_and_dw_and_dh(&video, 0.0, 0.0, 1920.0, 1080.0)
153 .expect("rendering to canvas should work");
154 canvas
155 .to_blob(canvas_closure_ref.as_ref().as_ref().unchecked_ref())
156 .expect("getting blob failed");
157 }));
158 }
159}