1use std::time::Duration;
2
3use async_io::Timer;
4use freya_core::prelude::*;
5
6use crate::{
7 VideoClient,
8 VideoEvent,
9 VideoFrame,
10 VideoSource,
11};
12
13#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
15pub enum PlaybackState {
16 #[default]
17 Idle,
18 Loading,
19 Playing,
20 Paused,
21 Ended,
22 Errored,
23}
24
25const SEEK_DEBOUNCE: Duration = Duration::from_millis(150);
27
28#[derive(Clone, Copy, PartialEq, Eq)]
30pub struct VideoPlayer {
31 frame: State<Option<VideoFrame>>,
32 playback: State<PlaybackState>,
33 forwarder: State<Option<OwnedTaskHandle>>,
34 source: State<Option<VideoSource>>,
35 position: State<Duration>,
36 duration: State<Option<Duration>>,
37 client: State<Option<VideoClient>>,
38}
39
40impl VideoPlayer {
41 pub fn create() -> Self {
43 Self {
44 frame: State::create(None),
45 playback: State::create(PlaybackState::Idle),
46 forwarder: State::create(None),
47 source: State::create(None),
48 position: State::create(Duration::ZERO),
49 duration: State::create(None),
50 client: State::create(None),
51 }
52 }
53
54 pub fn frame(&self) -> Option<VideoFrame> {
56 self.frame.read().as_ref().cloned()
57 }
58
59 pub fn state(&self) -> PlaybackState {
61 *self.playback.read()
62 }
63
64 pub fn position(&self) -> Duration {
66 *self.position.read()
67 }
68
69 pub fn duration(&self) -> Option<Duration> {
71 *self.duration.read()
72 }
73
74 pub fn progress(&self) -> f64 {
76 let position = *self.position.read();
77 let Some(duration) = *self.duration.read() else {
78 return 0.0;
79 };
80 if duration.is_zero() {
81 return 0.0;
82 }
83 (position.as_secs_f64() / duration.as_secs_f64() * 100.0).clamp(0.0, 100.0)
84 }
85
86 pub fn stop(&mut self) {
88 self.forwarder.set(None);
89 self.client.set(None);
90 self.frame.set(None);
91 self.playback.set(PlaybackState::Idle);
92 self.position.set(Duration::ZERO);
93 }
94
95 pub fn play(&mut self) {
97 if (self.playback)() == PlaybackState::Paused {
98 self.playback.set(PlaybackState::Playing);
99 if let Some(client) = self.client.peek().as_ref() {
100 client.play();
101 }
102 }
103 }
104
105 pub fn pause(&mut self) {
107 if (self.playback)() == PlaybackState::Playing {
108 self.playback.set(PlaybackState::Paused);
109 if let Some(client) = self.client.peek().as_ref() {
110 client.pause();
111 }
112 }
113 }
114
115 pub fn toggle(&mut self) {
117 match (self.playback)() {
118 PlaybackState::Playing => self.pause(),
119 PlaybackState::Paused => self.play(),
120 PlaybackState::Ended => self.seek(Duration::ZERO),
121 _ => {}
122 }
123 }
124
125 pub fn seek(&mut self, position: Duration) {
127 self.position.set(position);
128 self.client.set(None);
129 self.playback.set(PlaybackState::Loading);
130
131 let Some(source) = self.source.peek().as_ref().cloned() else {
132 self.forwarder.set(None);
133 return;
134 };
135 let player = *self;
136 let handle = spawn(async move {
137 Timer::after(SEEK_DEBOUNCE).await;
138 run_playback(source, position, player).await;
139 })
140 .owned();
141 self.forwarder.set(Some(handle));
142 }
143}
144
145pub fn use_video(init: impl FnOnce() -> VideoSource + 'static) -> VideoPlayer {
147 use_hook(move || {
148 let source = init();
149 let mut player = VideoPlayer::create();
150 player.source.set(Some(source.clone()));
151 player.playback.set(PlaybackState::Loading);
152 let handle = spawn(run_playback(source, Duration::ZERO, player)).owned();
153 player.forwarder.set(Some(handle));
154 player
155 })
156}
157
158async fn run_playback(source: VideoSource, start_offset: Duration, mut player: VideoPlayer) {
160 let client = VideoClient::new(source, start_offset);
161 let events = client.events().clone();
162 player.client.set(Some(client));
163
164 while let Ok(event) = events.recv().await {
165 match event {
166 VideoEvent::Duration(duration) => {
167 player.duration.set(Some(duration));
168 }
169 VideoEvent::Frame { frame, position } => {
170 player.frame.set(Some(frame));
171 player.position.set(position);
172 if (player.playback)() == PlaybackState::Loading {
173 player.playback.set(PlaybackState::Playing);
174 }
175 }
176 VideoEvent::Ended => {
177 player.playback.set(PlaybackState::Ended);
178 break;
179 }
180 VideoEvent::Errored => {
181 player.playback.set(PlaybackState::Errored);
182 break;
183 }
184 }
185 }
186}