Skip to content

Commit adfa93d

Browse files
authored
Refactor (#17)
* split reactor on app side into sink and stream * add effect: setup_frame_listener * add effect to setup video stream * add scheduler and sub workers * use scheduler without additional web worker * replace round robin with fifo * add box-sizing for frames * rm init sleep
1 parent 7ad7139 commit adfa93d

12 files changed

Lines changed: 386 additions & 179 deletions

File tree

webdemo/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webdemo/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ path = "src/bin/main.rs"
1111
name = "worker"
1212
path = "src/bin/worker.rs"
1313

14+
1415
[dependencies]
1516
leptos = { version = "0.7.7", features = ["csr"] }
1617
leptos-use = { version = "0.15.6", default-features = false, features = [
@@ -22,17 +23,20 @@ web-sys = { version = "0.3", features = [
2223
"HtmlCanvasElement",
2324
"HtmlVideoElement",
2425
"ImageData",
26+
"WorkerGlobalScope",
27+
"WorkerNavigator",
2528
] }
2629
wasm-bindgen = "0.2"
2730
anyhow = "1.0.98"
2831
imageproc = { version = "0.25.0", default-features = false }
2932
web-time = "1.1.0"
3033
gloo-worker = { version = "0.5.0", features = ["futures"] }
34+
gloo-timers = { version = "0.3.0", features = ["futures"] }
3135
futures = "0.3.31"
3236
serde = "1.0.219"
3337
base64 = "0.22.1"
3438
console_error_panic_hook = "0.1.7"
35-
gloo-timers = { version = "0.3.0", features = ["futures"] }
39+
wasm-bindgen-futures = "0.4.50"
3640

3741
[profile.dev]
3842
opt-level = 1

webdemo/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
}
2525

2626
.frame {
27+
box-sizing: border-box;
2728
height: 100%;
2829
width: 100%;
2930
object-fit: contain;

webdemo/src/app.rs

Lines changed: 8 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
use crate::{canvas::RenderState, hooks, reactor::ModelReactor};
2-
use gloo_worker::Spawnable;
3-
use leptos::{logging::*, prelude::*};
1+
use crate::{canvas::RenderState, effects};
2+
use leptos::prelude::*;
43
use leptos_use::{use_user_media, UseUserMediaReturn};
5-
use wasm_bindgen::{prelude::Closure, JsCast};
6-
use web_sys::Event;
74

85
#[component]
96
pub fn App() -> impl IntoView {
@@ -13,40 +10,17 @@ pub fn App() -> impl IntoView {
1310
let UseUserMediaReturn { stream, start, .. } = use_user_media();
1411
start();
1512

16-
let frame_listener = Closure::<dyn Fn(Event)>::wrap(Box::new(move |_: Event| {
17-
log!("entered frame listener");
18-
let reactor = ModelReactor::spawner().spawn("./worker.js");
13+
Effect::new(move |_| {
14+
effects::setup_video_stream(video_ref, &stream);
15+
});
16+
17+
Effect::new(move |_| {
1918
let state = RenderState {
2019
video: video_ref,
2120
canvas: canvas_ref,
2221
temporary_canvas: temporary_canvas_ref,
2322
};
24-
hooks::on_video_play(state, reactor);
25-
}));
26-
27-
Effect::new(move |_| {
28-
match stream.get() {
29-
Some(Ok(stream)) => {
30-
video_ref.with(|video| {
31-
if let Some(video) = video {
32-
video.set_src_object(Some(&stream))
33-
}
34-
});
35-
}
36-
Some(Err(e)) => error!("failed to get media stream: {:?}", e),
37-
None => log!("no stream yet"),
38-
};
39-
video_ref.with(|video| {
40-
let Some(video) = video else {
41-
log!("no video yet");
42-
return;
43-
};
44-
let _ = video
45-
.add_event_listener_with_callback("play", frame_listener.as_ref().unchecked_ref())
46-
.inspect_err(|e| {
47-
error!("failed to attach frame listener: {:?}", e);
48-
});
49-
})
23+
effects::setup_frame_listener(state);
5024
});
5125

5226
view! {

webdemo/src/bin/worker.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use gloo_worker::Registrable;
2-
use webdemo::ModelReactor;
32

43
fn main() {
54
console_error_panic_hook::set_once();
65

7-
ModelReactor::registrar().register();
6+
webdemo::Worker::registrar().register();
87
}

webdemo/src/effects.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use crate::{canvas::RenderState, on_video_play};
2+
use leptos::{logging::*, prelude::*};
3+
use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
4+
use web_sys::{Event, MediaStream};
5+
6+
pub fn setup_video_stream(
7+
video_ref: NodeRef<leptos::html::Video>,
8+
stream: &Signal<Option<Result<MediaStream, JsValue>>, LocalStorage>,
9+
) {
10+
let Some(stream) = stream.get() else {
11+
log!("setup_video_stream: stream is not ready yet");
12+
return;
13+
};
14+
let Some(video) = video_ref.get() else {
15+
log!("setup_video_stream: video is not ready yet");
16+
return;
17+
};
18+
19+
let stream = match stream {
20+
Ok(stream) => stream,
21+
Err(e) => {
22+
error!("failed to get media stream: {:?}", e);
23+
return;
24+
}
25+
};
26+
video.set_src_object(Some(&stream))
27+
}
28+
29+
pub fn setup_frame_listener(state: RenderState) {
30+
let Some(video) = state.video.get() else {
31+
log!("setup_frame_listener: video is not ready yet");
32+
return;
33+
};
34+
35+
let frame_listener = Closure::<dyn Fn(Event)>::wrap(Box::new(move |_: Event| {
36+
log!("entered frame listener");
37+
on_video_play(state);
38+
}));
39+
40+
let _ = video
41+
.add_event_listener_with_callback("play", frame_listener.as_ref().unchecked_ref())
42+
.inspect_err(|e| {
43+
error!("failed to attach frame listener: {:?}", e);
44+
});
45+
46+
frame_listener.forget();
47+
}

webdemo/src/hooks.rs

Lines changed: 0 additions & 40 deletions
This file was deleted.

webdemo/src/image_processing.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use image::{DynamicImage, Rgba, RgbaImage};
2+
use imageproc::image;
3+
use leptos::logging::log;
4+
5+
pub fn process_image_with_model(
6+
model: &scrfd::Scrfd,
7+
mut inout: RgbaImage,
8+
) -> anyhow::Result<RgbaImage> {
9+
let img = DynamicImage::ImageRgba8(inout.clone()).into_rgb8();
10+
let threshold = scrfd::Threshold {
11+
score: 0.5,
12+
iou: 0.6,
13+
};
14+
let clock = web_time::Instant::now();
15+
let faces = model.detect_with_threshold(&img, threshold)?;
16+
log!("face detection took: {:?}", clock.elapsed());
17+
18+
draw_faces(&mut inout, &faces);
19+
Ok(inout)
20+
}
21+
22+
fn draw_faces(img: &mut RgbaImage, faces: &[scrfd::Face]) {
23+
for &face in faces {
24+
draw_face(img, face);
25+
}
26+
}
27+
28+
fn draw_face(img: &mut RgbaImage, face: scrfd::Face) {
29+
let points = [
30+
face.keypoints.left_eye,
31+
face.keypoints.right_eye,
32+
face.keypoints.nose,
33+
face.keypoints.left_mouth,
34+
face.keypoints.right_mouth,
35+
];
36+
for point in points {
37+
let radius = 4;
38+
let color = Rgba([255, 0, 0, 255]);
39+
let center = (point.x as i32, point.y as i32);
40+
imageproc::drawing::draw_filled_circle_mut(img, center, radius, color);
41+
}
42+
43+
let color = Rgba([255, 0, 0, 255]);
44+
let x = face.bbox.upper_left.x as i32;
45+
let y = face.bbox.upper_left.y as i32;
46+
let (width, height) = (face.bbox.width() as u32, face.bbox.height() as u32);
47+
let rect = imageproc::rect::Rect::at(x, y).of_size(width, height);
48+
imageproc::drawing::draw_hollow_rect_mut(img, rect, color);
49+
}

webdemo/src/lib.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
mod app;
22
mod canvas;
3-
mod hooks;
4-
mod reactor;
3+
mod effects;
4+
mod image_processing;
5+
mod video_scheduler;
6+
mod worker;
7+
8+
use image_processing::process_image_with_model;
9+
use video_scheduler::on_video_play;
510

611
pub use app::App;
7-
pub use reactor::ModelReactor;
12+
pub use worker::Worker;

webdemo/src/reactor.rs

Lines changed: 0 additions & 99 deletions
This file was deleted.

0 commit comments

Comments
 (0)