1use std::cmp::{max_by, min_by};
2use std::rc::Rc;
3
4use wasm_bindgen::{prelude::*, JsValue};
5use web_sys::{HtmlMediaElement, TimeRanges};
6use yew::{prelude::*, TargetCast};
7
8use super::{use_event, use_mut_latest};
9
10#[derive(Default)]
12pub struct UseMediaOptions {
13 pub auto_play: bool,
15 pub onplay: Option<Box<dyn FnMut(Event)>>,
16 pub onplaying: Option<Box<dyn FnMut(Event)>>,
17 pub onwaiting: Option<Box<dyn FnMut(Event)>>,
18 pub onpause: Option<Box<dyn FnMut(Event)>>,
19 pub onvolumechange: Option<Box<dyn FnMut(Event)>>,
20 pub ondurationchange: Option<Box<dyn FnMut(Event)>>,
21 pub ontimeupdate: Option<Box<dyn FnMut(Event)>>,
22 pub onprogress: Option<Box<dyn FnMut(Event)>>,
23}
24
25impl UseMediaOptions {
26 pub fn enable_auto_play() -> Self {
28 Self {
29 auto_play: true,
30 ..Self::default()
31 }
32 }
33}
34
35pub struct UseMediaHandle {
37 pub buffered: UseStateHandle<Vec<(f64, f64)>>,
38 pub duration: UseStateHandle<f64>,
39 pub paused: UseStateHandle<bool>,
40 pub muted: UseStateHandle<bool>,
41 pub time: UseStateHandle<f64>,
42 pub volume: UseStateHandle<f64>,
43 pub playing: UseStateHandle<bool>,
44
45 play: Rc<dyn Fn()>,
46 pause: Rc<dyn Fn()>,
47 seek: Rc<dyn Fn(f64)>,
48 set_volume: Rc<dyn Fn(f64)>,
49 mute: Rc<dyn Fn()>,
50 unmute: Rc<dyn Fn()>,
51}
52
53impl UseMediaHandle {
54 pub fn play(&self) {
56 (self.play)();
57 }
58
59 pub fn pause(&self) {
61 (self.pause)();
62 }
63
64 pub fn mute(&self) {
66 (self.mute)();
67 }
68
69 pub fn unmute(&self) {
71 (self.unmute)();
72 }
73
74 pub fn set_volume(&self, value: f64) {
76 (self.set_volume)(value);
77 }
78
79 pub fn seek(&self, value: f64) {
81 (self.seek)(value);
82 }
83}
84
85impl Clone for UseMediaHandle {
86 fn clone(&self) -> Self {
87 Self {
88 buffered: self.buffered.clone(),
89 duration: self.duration.clone(),
90 paused: self.paused.clone(),
91 muted: self.muted.clone(),
92 time: self.time.clone(),
93 volume: self.volume.clone(),
94 playing: self.playing.clone(),
95
96 play: self.play.clone(),
97 pause: self.pause.clone(),
98 seek: self.seek.clone(),
99 set_volume: self.set_volume.clone(),
100 mute: self.mute.clone(),
101 unmute: self.unmute.clone(),
102 }
103 }
104}
105
106#[hook]
174pub fn use_media(node: NodeRef, src: String) -> UseMediaHandle {
175 use_media_with_options(node, src, UseMediaOptions::default())
176}
177
178#[hook]
181pub fn use_media_with_options(
182 node: NodeRef,
183 src: String,
184 options: UseMediaOptions,
185) -> UseMediaHandle {
186 let buffered = use_state(Vec::new);
187 let duration = use_state(|| 0.0);
188 let paused = use_state(|| true);
189 let muted = use_state(|| false);
190 let time = use_state(|| 0.0);
191 let volume = use_state(|| 1.0);
192 let playing = use_state(|| false);
193
194 let onplay_ref = use_mut_latest(options.onplay);
195 let onplaying_ref = use_mut_latest(options.onplaying);
196 let onwaiting_ref = use_mut_latest(options.onwaiting);
197 let onpause_ref = use_mut_latest(options.onpause);
198 let onvolumechange_ref = use_mut_latest(options.onvolumechange);
199 let ondurationchange_ref = use_mut_latest(options.ondurationchange);
200 let ontimeupdate_ref = use_mut_latest(options.ontimeupdate);
201 let onprogress_ref = use_mut_latest(options.onprogress);
202
203 let play_lock = use_mut_ref(|| false);
204
205 {
206 let node = node.clone();
207 let paused = paused.clone();
208 use_event(node, "play", move |e: Event| {
209 paused.set(false);
210
211 let onplay_ref = onplay_ref.current();
212 let onplay = &mut *onplay_ref.borrow_mut();
213 if let Some(onplay) = onplay {
214 onplay(e);
215 }
216 });
217 }
218
219 {
220 let node = node.clone();
221 let playing = playing.clone();
222 use_event(node, "playing", move |e: Event| {
223 playing.set(true);
224
225 let onplaying_ref = onplaying_ref.current();
226 let onplaying = &mut *onplaying_ref.borrow_mut();
227 if let Some(onplaying) = onplaying {
228 onplaying(e);
229 }
230 });
231 }
232
233 {
234 let node = node.clone();
235 let playing = playing.clone();
236 use_event(node, "waiting", move |e: Event| {
237 playing.set(false);
238
239 let onwaiting_ref = onwaiting_ref.current();
240 let onwaiting = &mut *onwaiting_ref.borrow_mut();
241 if let Some(onwaiting) = onwaiting {
242 onwaiting(e);
243 }
244 });
245 }
246
247 {
248 let node = node.clone();
249 let paused = paused.clone();
250 let playing = playing.clone();
251 use_event(node, "pause", move |e: Event| {
252 paused.set(true);
253 playing.set(false);
254
255 let onpause_ref = onpause_ref.current();
256 let onpause = &mut *onpause_ref.borrow_mut();
257 if let Some(onpause) = onpause {
258 onpause(e);
259 }
260 });
261 }
262
263 {
264 let node = node.clone();
265 let muted = muted.clone();
266 let volume = volume.clone();
267 use_event(node, "volumechange", move |e: Event| {
268 if let Some(media) = e.target_dyn_into::<HtmlMediaElement>() {
269 muted.set(media.muted());
270 volume.set(media.volume());
271 }
272
273 let onvolumechange_ref = onvolumechange_ref.current();
274 let onvolumechange = &mut *onvolumechange_ref.borrow_mut();
275 if let Some(onvolumechange) = onvolumechange {
276 onvolumechange(e);
277 }
278 });
279 }
280
281 {
282 let node = node.clone();
283 let duration = duration.clone();
284 let buffered = buffered.clone();
285 use_event(node, "durationchange", move |e: Event| {
286 if let Some(media) = e.target_dyn_into::<HtmlMediaElement>() {
287 duration.set(media.duration());
288 buffered.set(parse_time_ranges(media.buffered()));
289 }
290
291 let ondurationchange_ref = ondurationchange_ref.current();
292 let ondurationchange = &mut *ondurationchange_ref.borrow_mut();
293 if let Some(ondurationchange) = ondurationchange {
294 ondurationchange(e);
295 }
296 });
297 }
298
299 {
300 let node = node.clone();
301 let time = time.clone();
302 use_event(node, "timeupdate", move |e: Event| {
303 if let Some(media) = e.target_dyn_into::<HtmlMediaElement>() {
304 time.set(media.current_time());
305 }
306
307 let ontimeupdate_ref = ontimeupdate_ref.current();
308 let ontimeupdate = &mut *ontimeupdate_ref.borrow_mut();
309 if let Some(ontimeupdate) = ontimeupdate {
310 ontimeupdate(e);
311 }
312 });
313 }
314
315 {
316 let node = node.clone();
317 let buffered = buffered.clone();
318 use_event(node, "progress", move |e: Event| {
319 if let Some(media) = e.target_dyn_into::<HtmlMediaElement>() {
320 buffered.set(parse_time_ranges(media.buffered()));
321 }
322
323 let onprogress_ref = onprogress_ref.current();
324 let onprogress = &mut *onprogress_ref.borrow_mut();
325 if let Some(onprogress) = onprogress {
326 onprogress(e);
327 }
328 });
329 }
330
331 let play = {
332 let play_lock = play_lock.clone();
333 let node = node.clone();
334 Rc::new(move || {
335 if !*play_lock.borrow() {
336 if let Some(media) = node.cast::<HtmlMediaElement>() {
337 if let Ok(promise) = media.play() {
338 *play_lock.borrow_mut() = true;
339 let play_lock = play_lock.clone();
340 let closure = Closure::wrap(Box::new(move |_| {
341 *play_lock.borrow_mut() = false;
342 })
343 as Box<dyn FnMut(JsValue)>);
344 let _ = promise.then2(&closure, &closure);
345 closure.forget();
346 }
347 }
348 }
349 })
350 };
351
352 let pause = {
353 let node = node.clone();
354 Rc::new(move || {
355 if !*play_lock.borrow() {
356 if let Some(media) = node.cast::<HtmlMediaElement>() {
357 let _ = media.pause();
358 }
359 }
360 })
361 };
362
363 let seek = {
364 let duration = duration.clone();
365 let node = node.clone();
366 Rc::new(move |time: f64| {
367 if let Some(media) = node.cast::<HtmlMediaElement>() {
368 let time = max_by(0.0, time, |x, y| x.partial_cmp(y).unwrap());
369 let time = min_by(*duration, time, |x, y| x.partial_cmp(y).unwrap());
370 media.set_current_time(time);
371 }
372 })
373 };
374
375 let set_volume = {
376 let volume = volume.clone();
377 let node = node.clone();
378 Rc::new(move |vol: f64| {
379 if let Some(media) = node.cast::<HtmlMediaElement>() {
380 let vol = max_by(0.0, vol, |x, y| x.partial_cmp(y).unwrap());
381 let vol = min_by(1.0, vol, |x, y| x.partial_cmp(y).unwrap());
382 media.set_volume(vol);
383 volume.set(vol);
384 }
385 })
386 };
387
388 let mute = {
389 let node = node.clone();
390 let muted = muted.clone();
391 Rc::new(move || {
392 if let Some(media) = node.cast::<HtmlMediaElement>() {
393 media.set_muted(true);
394 muted.set(true);
395 }
396 })
397 };
398
399 let unmute = {
400 let node = node.clone();
401 let muted = muted.clone();
402 Rc::new(move || {
403 if let Some(media) = node.cast::<HtmlMediaElement>() {
404 media.set_muted(false);
405 muted.set(false);
406 }
407 })
408 };
409
410 {
411 let volume = volume.clone();
412 let muted = muted.clone();
413 let paused = paused.clone();
414 let play = play.clone();
415 let auto_play = options.auto_play;
416 use_effect_with((node, src), move |(node, src)| {
417 if let Some(media) = node.cast::<HtmlMediaElement>() {
418 media.set_controls(false);
419 media.set_src(src);
420
421 volume.set(media.volume());
422 muted.set(media.muted());
423 paused.set(media.paused());
424
425 if auto_play && media.paused() {
426 play();
427 }
428 }
429
430 || ()
431 });
432 }
433
434 UseMediaHandle {
435 buffered,
436 duration,
437 paused,
438 muted,
439 time,
440 volume,
441 playing,
442
443 play,
444 pause,
445 seek,
446 set_volume,
447 mute,
448 unmute,
449 }
450}
451
452fn parse_time_ranges(ranges: TimeRanges) -> Vec<(f64, f64)> {
453 let mut result = vec![];
454 if ranges.length() > 0 {
455 for index in 0..ranges.length() {
456 result.push((
457 ranges.start(index).unwrap_throw(),
458 ranges.end(index).unwrap_throw(),
459 ));
460 }
461 }
462 result
463}