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
89 let set = |k: &str, v: &str| {
90 if let Err(e) = mpv.set_property(k, v) {
91 warn!("[MpvPlayer] could not set {k}={v}: {e:?}");
92 }
93 };
94 match output {
95 Output::Real => {
96 set("vo", "libmpv");
97 }
98 Output::Null => {
99 set("ao", "null");
100 set("vo", "null");
101 }
102 }
103 set("msg-level", "all=warn");
104 set(
106 "stream-lavf-o",
107 "reconnect=1,reconnect_streamed=1,reconnect_on_network_error=1,reconnect_delay_max=5",
108 );
109
110 let player = Self {
111 mpv: Arc::new(mpv),
112 shared: Arc::new(Mutex::new(Shared::default())),
113 volume: 1.0,
114 muted: false,
115 rate: 1.0,
116 audio_track: None,
117 subtitle_track: None,
118 };
119 player.spawn_events();
120 Ok(player)
121 }
122
123 fn spawn_events(&self) {
124 let mpv = self.mpv.clone();
125 let shared = self.shared.clone();
126
127 std::thread::spawn(move || {
128 let mut ev = mpv.create_event_context();
129 let _ = ev.disable_deprecated_events();
130 for prop in ["pause", "eof-reached"] {
133 if let Err(e) = ev.observe_property(prop, libmpv::Format::Flag, 0) {
134 warn!("[MpvPlayer] could not observe {prop}: {e:?}");
135 }
136 }
137
138 loop {
139 match ev.wait_event(0.25) {
140 Some(Ok(libmpv::events::Event::FileLoaded)) => {
141 let deferred = {
142 let mut s = shared.lock_safe();
143 if s.phase == Phase::Idle {
145 continue;
146 }
147 s.duration = mpv
148 .get_property::<f64>("duration")
149 .ok()
150 .and_then(duration_from_secs);
151 s.seekable = mpv.get_property::<bool>("seekable").unwrap_or(true);
152 s.phase = Phase::Playing;
153 s.deferred_seek.take()
154 };
155 if let Some(to) = deferred {
156 debug!("[MpvPlayer] applying deferred seek to {to:?}");
157 if let Err(e) = mpv.set_property("time-pos", to.as_secs_f64()) {
158 warn!("[MpvPlayer] deferred seek failed: {e:?}");
159 }
160 }
161 }
162 Some(Ok(libmpv::events::Event::PropertyChange { name: "pause", .. })) => {
163 if let Ok(paused) = mpv.get_property::<bool>("pause") {
164 let mut s = shared.lock_safe();
165 if s.phase.has_media() {
166 s.phase = if paused {
167 Phase::Paused
168 } else {
169 Phase::Playing
170 };
171 }
172 }
173 }
174 Some(Ok(libmpv::events::Event::EndFile(reason))) => {
175 let mut s = shared.lock_safe();
176 s.phase = if reason == 0 {
179 Phase::Ended
180 } else {
181 Phase::Idle
182 };
183 }
184 Some(Ok(libmpv::events::Event::Shutdown)) => break,
185 _ => {}
186 }
187
188 if let Ok(pos) = mpv.get_property::<f64>("time-pos") {
189 let mut s = shared.lock_safe();
190 if s.phase.has_media() && s.deferred_seek.is_none() {
191 s.position = Duration::from_secs_f64(pos.max(0.0));
192 }
193 }
194 }
195 });
196 }
197}
198
199impl MediaPlayer for MpvPlayer {
200 fn open(&mut self, req: OpenRequest) -> Result<(), PlayerError> {
201 {
202 let mut s = self.shared.lock_safe();
203 *s = Shared {
204 phase: Phase::Opening,
205 open_generation: s.open_generation + 1,
206 ..Shared::default()
207 };
208 s.position = req.start;
211 }
212
213 let start = if req.start.is_zero() {
216 "none".to_string()
217 } else {
218 format!("{:.3}", req.start.as_secs_f64())
219 };
220 self.mpv
221 .set_property("start", start.as_str())
222 .map_err(|e| PlayerError {
223 message: format!("could not set start position: {e:?}"),
224 })?;
225 self.mpv
226 .set_property("pause", !req.autoplay)
227 .map_err(|e| PlayerError {
228 message: format!("could not set pause: {e:?}"),
229 })?;
230
231 info!("[MpvPlayer] open {} at {:?}", req.selection.url, req.start);
232 self.mpv
233 .command("loadfile", &[&req.selection.url, "replace"])
234 .map_err(|e| PlayerError {
235 message: format!("loadfile failed: {e:?}"),
236 })?;
237 Ok(())
238 }
239
240 fn play(&mut self) -> Result<(), PlayerError> {
241 self.mpv
242 .set_property("pause", false)
243 .map_err(|e| PlayerError {
244 message: format!("play failed: {e:?}"),
245 })?;
246 let mut s = self.shared.lock_safe();
247 if s.phase.has_media() && s.phase != Phase::Opening {
248 s.phase = Phase::Playing;
249 }
250 Ok(())
251 }
252
253 fn pause(&mut self) -> Result<(), PlayerError> {
254 self.mpv
255 .set_property("pause", true)
256 .map_err(|e| PlayerError {
257 message: format!("pause failed: {e:?}"),
258 })?;
259 let mut s = self.shared.lock_safe();
260 if s.phase.has_media() && s.phase != Phase::Opening {
261 s.phase = Phase::Paused;
262 }
263 Ok(())
264 }
265
266 fn close(&mut self) -> Result<(), PlayerError> {
267 {
270 let mut s = self.shared.lock_safe();
271 *s = Shared {
272 open_generation: s.open_generation,
273 ..Shared::default()
274 };
275 }
276 if let Err(e) = self.mpv.command("stop", &[]) {
279 debug!("[MpvPlayer] stop on an idle player: {e:?}");
280 }
281 Ok(())
282 }
283
284 fn seek(&mut self, to: Duration) -> Result<(), PlayerError> {
285 {
286 let mut s = self.shared.lock_safe();
287 match s.phase {
288 Phase::Opening => {
290 s.deferred_seek = Some(to);
291 s.position = to;
292 return Ok(());
293 }
294 Phase::Idle | Phase::Failed(_) => {
295 return Err(PlayerError {
296 message: "seek with nothing open".to_string(),
297 })
298 }
299 _ => s.position = to,
300 }
301 }
302 self.mpv
303 .set_property("time-pos", to.as_secs_f64())
304 .map_err(|e| PlayerError {
305 message: format!("seek failed: {e:?}"),
306 })
307 }
308
309 fn set_volume(&mut self, volume: f32) -> Result<(), PlayerError> {
310 let clamped = volume.clamp(0.0, 1.0);
311 self.volume = clamped;
312 self.mpv
313 .set_property("volume", (clamped as f64) * 100.0)
314 .map_err(|e| PlayerError {
315 message: format!("set_volume failed: {e:?}"),
316 })
317 }
318
319 fn set_muted(&mut self, muted: bool) -> Result<(), PlayerError> {
320 self.muted = muted;
321 self.mpv
322 .set_property("mute", muted)
323 .map_err(|e| PlayerError {
324 message: format!("set_muted failed: {e:?}"),
325 })
326 }
327
328 fn set_rate(&mut self, rate: f64) -> Result<(), PlayerError> {
329 self.rate = rate;
330 self.mpv
331 .set_property("speed", rate)
332 .map_err(|e| PlayerError {
333 message: format!("set_rate failed: {e:?}"),
334 })
335 }
336
337 fn select_audio_track(&mut self, index: Option<i32>) -> Result<(), PlayerError> {
338 self.audio_track = index;
339 let value = index.map(|i| i.to_string()).unwrap_or_else(|| "no".into());
340 self.mpv
341 .set_property("aid", value.as_str())
342 .map_err(|e| PlayerError {
343 message: format!("select_audio_track failed: {e:?}"),
344 })
345 }
346
347 fn select_subtitle_track(&mut self, index: Option<i32>) -> Result<(), PlayerError> {
348 self.subtitle_track = index;
349 let value = index.map(|i| i.to_string()).unwrap_or_else(|| "no".into());
350 self.mpv
351 .set_property("sid", value.as_str())
352 .map_err(|e| PlayerError {
353 message: format!("select_subtitle_track failed: {e:?}"),
354 })
355 }
356
357 fn snapshot(&self) -> PlaybackSnapshot {
358 let s = self.shared.lock_safe();
359 PlaybackSnapshot {
360 phase: s.phase.clone(),
361 position: s.position,
362 duration: s.duration,
363 seekable: s.seekable,
364 volume: self.volume,
365 muted: self.muted,
366 rate: self.rate,
367 audio_track: self.audio_track,
368 subtitle_track: self.subtitle_track,
369 }
370 }
371
372 fn capabilities(&self) -> Capabilities {
373 Capabilities {
374 video: true,
375 audio_settings: true,
376 subtitle_switching: true,
377 audio_track_switching: true,
378 seeks_transcoded_in_place: false,
381 }
382 }
383}