1#![allow(dead_code)] use std::sync::{Arc, Mutex};
18use std::time::Duration;
19
20use libmpv::Mpv;
21use log::{debug, info, warn};
22
23use super::backend::PlayerError;
24use super::media_player::{
25 duration_from_secs, Capabilities, MediaPlayer, OpenRequest, Phase, PlaybackSnapshot,
26};
27use crate::utils::lock::MutexSafe;
28
29#[derive(Debug)]
31struct Shared {
32 phase: Phase,
33 position: Duration,
34 duration: Option<Duration>,
35 seekable: bool,
36 deferred_seek: Option<Duration>,
38 open_generation: u64,
41}
42
43impl Default for Shared {
44 fn default() -> Self {
45 Self {
46 phase: Phase::Idle,
47 position: Duration::ZERO,
48 duration: None,
49 seekable: false,
50 deferred_seek: None,
51 open_generation: 0,
52 }
53 }
54}
55
56pub struct MpvPlayer {
57 mpv: Arc<Mpv>,
58 shared: Arc<Mutex<Shared>>,
59 volume: f32,
60 muted: bool,
61 rate: f64,
62 audio_track: Option<i32>,
63 subtitle_track: Option<i32>,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum Output {
69 Real,
71 Null,
74}
75
76impl MpvPlayer {
77 pub fn new(output: Output) -> Result<Self, PlayerError> {
78 unsafe {
81 let c = std::ffi::CString::new("C").unwrap();
82 libc::setlocale(libc::LC_NUMERIC, c.as_ptr());
83 }
84
85 let mpv = Mpv::new().map_err(|e| PlayerError {
86 message: format!("mpv_create failed: {e:?}"),
87 })?;
88 super::mpv_command::harden(&mpv).map_err(|message| PlayerError { message })?;
90
91 let set = |k: &str, v: &str| {
92 if let Err(e) = mpv.set_property(k, v) {
93 warn!("[MpvPlayer] could not set {k}={v}: {e:?}");
94 }
95 };
96 match output {
97 Output::Real => {
98 set("vo", "libmpv");
99 }
100 Output::Null => {
101 set("ao", "null");
102 set("vo", "null");
103 }
104 }
105 set("msg-level", "all=warn");
106 set(
108 "stream-lavf-o",
109 "reconnect=1,reconnect_streamed=1,reconnect_on_network_error=1,reconnect_delay_max=5",
110 );
111
112 let player = Self {
113 mpv: Arc::new(mpv),
114 shared: Arc::new(Mutex::new(Shared::default())),
115 volume: 1.0,
116 muted: false,
117 rate: 1.0,
118 audio_track: None,
119 subtitle_track: None,
120 };
121 player.spawn_events();
122 Ok(player)
123 }
124
125 fn spawn_events(&self) {
126 let mpv = self.mpv.clone();
127 let shared = self.shared.clone();
128
129 std::thread::spawn(move || {
130 let mut ev = mpv.create_event_context();
131 let _ = ev.disable_deprecated_events();
132 for prop in ["pause", "eof-reached"] {
135 if let Err(e) = ev.observe_property(prop, libmpv::Format::Flag, 0) {
136 warn!("[MpvPlayer] could not observe {prop}: {e:?}");
137 }
138 }
139
140 loop {
141 match ev.wait_event(0.25) {
142 Some(Ok(libmpv::events::Event::FileLoaded)) => {
143 let deferred = {
144 let mut s = shared.lock_safe();
145 if s.phase == Phase::Idle {
147 continue;
148 }
149 s.duration = mpv
150 .get_property::<f64>("duration")
151 .ok()
152 .and_then(duration_from_secs);
153 s.seekable = mpv.get_property::<bool>("seekable").unwrap_or(true);
154 s.phase = Phase::Playing;
155 s.deferred_seek.take()
156 };
157 if let Some(to) = deferred {
158 debug!("[MpvPlayer] applying deferred seek to {to:?}");
159 if let Err(e) = mpv.set_property("time-pos", to.as_secs_f64()) {
160 warn!("[MpvPlayer] deferred seek failed: {e:?}");
161 }
162 }
163 }
164 Some(Ok(libmpv::events::Event::PropertyChange { name: "pause", .. })) => {
165 if let Ok(paused) = mpv.get_property::<bool>("pause") {
166 let mut s = shared.lock_safe();
167 if s.phase.has_media() {
168 s.phase = if paused {
169 Phase::Paused
170 } else {
171 Phase::Playing
172 };
173 }
174 }
175 }
176 Some(Ok(libmpv::events::Event::EndFile(reason))) => {
177 let mut s = shared.lock_safe();
178 s.phase = if reason == 0 {
181 Phase::Ended
182 } else {
183 Phase::Idle
184 };
185 }
186 Some(Ok(libmpv::events::Event::Shutdown)) => break,
187 _ => {}
188 }
189
190 if let Ok(pos) = mpv.get_property::<f64>("time-pos") {
191 let mut s = shared.lock_safe();
192 if s.phase.has_media() && s.deferred_seek.is_none() {
193 s.position = Duration::from_secs_f64(pos.max(0.0));
194 }
195 }
196 }
197 });
198 }
199}
200
201impl MediaPlayer for MpvPlayer {
202 fn open(&mut self, req: OpenRequest) -> Result<(), PlayerError> {
203 {
204 let mut s = self.shared.lock_safe();
205 *s = Shared {
206 phase: Phase::Opening,
207 open_generation: s.open_generation + 1,
208 ..Shared::default()
209 };
210 s.position = req.start;
213 }
214
215 let start = if req.start.is_zero() {
218 "none".to_string()
219 } else {
220 format!("{:.3}", req.start.as_secs_f64())
221 };
222 self.mpv
223 .set_property("start", start.as_str())
224 .map_err(|e| PlayerError {
225 message: format!("could not set start position: {e:?}"),
226 })?;
227 self.mpv
228 .set_property("pause", !req.autoplay)
229 .map_err(|e| PlayerError {
230 message: format!("could not set pause: {e:?}"),
231 })?;
232
233 info!("[MpvPlayer] open {} at {:?}", req.selection.url, req.start);
234 super::mpv_command::command(&self.mpv, &["loadfile", &req.selection.url, "replace"])
236 .map_err(|e| PlayerError {
237 message: format!("loadfile failed: {e}"),
238 })?;
239 Ok(())
240 }
241
242 fn play(&mut self) -> Result<(), PlayerError> {
243 self.mpv
244 .set_property("pause", false)
245 .map_err(|e| PlayerError {
246 message: format!("play failed: {e:?}"),
247 })?;
248 let mut s = self.shared.lock_safe();
249 if s.phase.has_media() && s.phase != Phase::Opening {
250 s.phase = Phase::Playing;
251 }
252 Ok(())
253 }
254
255 fn pause(&mut self) -> Result<(), PlayerError> {
256 self.mpv
257 .set_property("pause", true)
258 .map_err(|e| PlayerError {
259 message: format!("pause failed: {e:?}"),
260 })?;
261 let mut s = self.shared.lock_safe();
262 if s.phase.has_media() && s.phase != Phase::Opening {
263 s.phase = Phase::Paused;
264 }
265 Ok(())
266 }
267
268 fn close(&mut self) -> Result<(), PlayerError> {
269 {
272 let mut s = self.shared.lock_safe();
273 *s = Shared {
274 open_generation: s.open_generation,
275 ..Shared::default()
276 };
277 }
278 if let Err(e) = super::mpv_command::command(&self.mpv, &["stop"]) {
281 debug!("[MpvPlayer] stop on an idle player: {e}");
282 }
283 Ok(())
284 }
285
286 fn seek(&mut self, to: Duration) -> Result<(), PlayerError> {
287 {
288 let mut s = self.shared.lock_safe();
289 match s.phase {
290 Phase::Opening => {
292 s.deferred_seek = Some(to);
293 s.position = to;
294 return Ok(());
295 }
296 Phase::Idle | Phase::Failed(_) => {
297 return Err(PlayerError {
298 message: "seek with nothing open".to_string(),
299 })
300 }
301 _ => s.position = to,
302 }
303 }
304 self.mpv
305 .set_property("time-pos", to.as_secs_f64())
306 .map_err(|e| PlayerError {
307 message: format!("seek failed: {e:?}"),
308 })
309 }
310
311 fn set_volume(&mut self, volume: f32) -> Result<(), PlayerError> {
312 let clamped = volume.clamp(0.0, 1.0);
313 self.volume = clamped;
314 self.mpv
315 .set_property("volume", (clamped as f64) * 100.0)
316 .map_err(|e| PlayerError {
317 message: format!("set_volume failed: {e:?}"),
318 })
319 }
320
321 fn set_muted(&mut self, muted: bool) -> Result<(), PlayerError> {
322 self.muted = muted;
323 self.mpv
324 .set_property("mute", muted)
325 .map_err(|e| PlayerError {
326 message: format!("set_muted failed: {e:?}"),
327 })
328 }
329
330 fn set_rate(&mut self, rate: f64) -> Result<(), PlayerError> {
331 self.rate = rate;
332 self.mpv
333 .set_property("speed", rate)
334 .map_err(|e| PlayerError {
335 message: format!("set_rate failed: {e:?}"),
336 })
337 }
338
339 fn select_audio_track(&mut self, index: Option<i32>) -> Result<(), PlayerError> {
340 self.audio_track = index;
341 let value = index.map(|i| i.to_string()).unwrap_or_else(|| "no".into());
342 self.mpv
343 .set_property("aid", value.as_str())
344 .map_err(|e| PlayerError {
345 message: format!("select_audio_track failed: {e:?}"),
346 })
347 }
348
349 fn select_subtitle_track(&mut self, index: Option<i32>) -> Result<(), PlayerError> {
350 self.subtitle_track = index;
351 let value = index.map(|i| i.to_string()).unwrap_or_else(|| "no".into());
352 self.mpv
353 .set_property("sid", value.as_str())
354 .map_err(|e| PlayerError {
355 message: format!("select_subtitle_track failed: {e:?}"),
356 })
357 }
358
359 fn snapshot(&self) -> PlaybackSnapshot {
360 let s = self.shared.lock_safe();
361 PlaybackSnapshot {
362 phase: s.phase.clone(),
363 position: s.position,
364 duration: s.duration,
365 seekable: s.seekable,
366 volume: self.volume,
367 muted: self.muted,
368 rate: self.rate,
369 audio_track: self.audio_track,
370 subtitle_track: self.subtitle_track,
371 }
372 }
373
374 fn capabilities(&self) -> Capabilities {
375 Capabilities {
376 video: true,
377 audio_settings: true,
378 subtitle_switching: true,
379 audio_track_switching: true,
380 seeks_transcoded_in_place: false,
383 }
384 }
385}