1use std::time::Duration;
2
3use async_io::Timer;
4use freya_core::{
5 elements::image::ImageHandle,
6 prelude::*,
7};
8
9use crate::{
10 VideoClient,
11 VideoEvent,
12 VideoSource,
13};
14
15#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
17pub enum PlaybackState {
18 #[default]
19 Idle,
20 Loading,
21 Playing,
22 Paused,
23 Ended,
24 Errored,
25}
26
27#[derive(Clone, Copy, PartialEq, Eq)]
29pub struct VideoPlayer {
30 frame: State<Option<ImageHandle>>,
31 playback: State<PlaybackState>,
32 forwarder: State<Option<OwnedTaskHandle>>,
33 source: State<VideoSource>,
34 position: State<Duration>,
35 duration: State<Option<Duration>>,
36 volume: State<f32>,
37 client: State<Option<VideoClient>>,
38}
39
40impl VideoPlayer {
41 pub fn create(source: VideoSource) -> Self {
42 Self {
43 frame: State::create(None),
44 playback: State::create(PlaybackState::default()),
45 forwarder: State::create(None),
46 source: State::create(source),
47 position: State::create(Duration::ZERO),
48 duration: State::create(None),
49 volume: State::create(1.0),
50 client: State::create(None),
51 }
52 }
53
54 pub fn frame(&self) -> Option<ImageHandle> {
56 self.frame.read().clone()
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 volume(&self) -> f32 {
76 *self.volume.read()
77 }
78
79 pub fn set_volume(&mut self, volume: f32) {
81 let volume = volume.clamp(0.0, 1.0);
82 self.volume.set(volume);
83 if let Some(client) = self.client.peek().as_ref() {
84 client.set_volume(volume);
85 }
86 }
87
88 pub fn progress(&self) -> f64 {
90 let Some(duration) = *self.duration.read() else {
91 return 0.0;
92 };
93 if duration.is_zero() {
94 return 0.0;
95 }
96 (self.position().as_secs_f64() / duration.as_secs_f64() * 100.0).clamp(0.0, 100.0)
97 }
98
99 pub fn stop(&mut self) {
101 self.forwarder.set(None);
102 self.client.set(None);
103 self.frame.set(None);
104 self.playback.set(PlaybackState::Idle);
105 self.position.set(Duration::ZERO);
106 }
107
108 pub fn play(&mut self) {
110 match self.state() {
111 PlaybackState::Paused => {
112 self.playback.set(PlaybackState::Playing);
113 if let Some(client) = self.client.peek().as_ref() {
114 client.play();
115 }
116 }
117 PlaybackState::Idle => self.seek(self.position(), Duration::ZERO),
118 _ => {}
119 }
120 }
121
122 pub fn pause(&mut self) {
124 if self.state() == PlaybackState::Playing {
125 self.playback.set(PlaybackState::Paused);
126 if let Some(client) = self.client.peek().as_ref() {
127 client.pause();
128 }
129 }
130 }
131
132 pub fn toggle(&mut self) {
134 match self.state() {
135 PlaybackState::Playing => self.pause(),
136 PlaybackState::Paused => self.play(),
137 PlaybackState::Ended => self.seek(Duration::ZERO, Duration::ZERO),
138 _ => {}
139 }
140 }
141
142 pub fn set_source(&mut self, source: impl Into<VideoSource>) {
144 self.source.set(source.into());
145 self.seek(Duration::ZERO, Duration::ZERO);
146 }
147
148 pub fn seek(&mut self, position: Duration, debounce: Duration) {
150 let start_paused = self.state() == PlaybackState::Paused;
151 self.position.set(position);
152 self.client.set(None);
153 self.playback.set(PlaybackState::Loading);
154
155 let source = self.source.peek().clone();
156 let player = *self;
157 let handle = spawn(async move {
158 Timer::after(debounce).await;
159 player.run(source, position, start_paused).await;
160 })
161 .owned();
162 self.forwarder.set(Some(handle));
163 }
164
165 async fn run(mut self, source: VideoSource, start_offset: Duration, start_paused: bool) {
167 let client = VideoClient::new(source, start_offset, start_paused, *self.volume.peek());
168 let events = client.events().clone();
169 self.client.set(Some(client));
170
171 while let Ok(event) = events.recv().await {
172 match event {
173 VideoEvent::Duration(duration) => {
174 self.duration.set(Some(duration));
175 }
176 VideoEvent::Frame { frame, position } => {
177 self.frame.set(Some(frame));
178 self.position.set(position);
179 if self.state() == PlaybackState::Loading {
180 self.playback.set(if start_paused {
181 PlaybackState::Paused
182 } else {
183 PlaybackState::Playing
184 });
185 }
186 }
187 VideoEvent::Ended => {
188 self.playback.set(PlaybackState::Ended);
189 break;
190 }
191 VideoEvent::Errored => {
192 tracing::warn!("Video stream errored");
193 self.playback.set(PlaybackState::Errored);
194 break;
195 }
196 }
197 }
198 }
199}
200
201pub fn use_video<Source: Into<VideoSource>>(
219 video_source: impl FnOnce() -> Source + 'static,
220) -> VideoPlayer {
221 use_hook(move || {
222 let mut player = VideoPlayer::create(video_source().into());
223 player.play();
224 player
225 })
226}