1use std::sync::Arc;
4use std::time::Duration;
5
6use anyhow::{Context, Result};
7use async_trait::async_trait;
8use parking_lot::RwLock;
9pub use playlist::Playlist;
10use termusiclib::config::v2::server::config_extra::ServerConfigVersionedDefaulted;
11use termusiclib::config::SharedServerSettings;
12use termusiclib::library_db::DataBase;
13use termusiclib::player::playlist_helpers::{
14 PlaylistAddTrack, PlaylistPlaySpecific, PlaylistRemoveTrackIndexed, PlaylistSwapTrack,
15};
16use termusiclib::player::{
17 PlayerProgress, PlayerTimeUnit, RunningStatus, TrackChangedInfo, UpdateEvents,
18};
19use termusiclib::podcast::db::Database as DBPod;
20use termusiclib::track::{MediaTypesSimple, Track};
21use termusiclib::utils::get_app_config_path;
22use tokio::runtime::Handle;
23use tokio::sync::mpsc::error::SendError;
24use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
25use tokio::sync::{broadcast, oneshot};
26
27pub use backends::{Backend, BackendSelect};
28
29mod discord;
30mod mpris;
31pub mod playlist;
32
33#[macro_use]
34extern crate log;
35
36mod backends;
37
38pub mod __bench {
42 pub use super::backends::rusty::source::async_ring;
43}
44
45pub type PlayerCmdCallback = oneshot::Receiver<()>;
46pub type PlayerCmdReciever = UnboundedReceiver<(PlayerCmd, PlayerCmdCallbackSender)>;
47
48#[derive(Debug)]
50pub struct PlayerCmdCallbackSender(Option<oneshot::Sender<()>>);
51
52impl PlayerCmdCallbackSender {
53 pub fn call(self) {
55 let Some(sender) = self.0 else {
56 return;
57 };
58 let _ = sender.send(());
59 }
60}
61
62#[derive(Debug, Clone)]
64pub struct PlayerCmdSender(UnboundedSender<(PlayerCmd, PlayerCmdCallbackSender)>);
65
66impl PlayerCmdSender {
67 pub fn send(
72 &self,
73 cmd: PlayerCmd,
74 ) -> Result<(), SendError<(PlayerCmd, PlayerCmdCallbackSender)>> {
75 self.0.send((cmd, PlayerCmdCallbackSender(None)))
76 }
77
78 pub fn send_cb(
83 &self,
84 cmd: PlayerCmd,
85 ) -> Result<PlayerCmdCallback, SendError<(PlayerCmd, PlayerCmdCallbackSender)>> {
86 let (tx, rx) = oneshot::channel();
87 self.0.send((cmd, PlayerCmdCallbackSender(Some(tx))))?;
88 Ok(rx)
89 }
90
91 #[must_use]
92 pub fn new(tx: UnboundedSender<(PlayerCmd, PlayerCmdCallbackSender)>) -> Self {
93 Self(tx)
94 }
95}
96
97#[derive(Clone, Debug, Copy, PartialEq)]
98pub enum PlayerErrorType {
99 Current,
101 Enqueue,
103}
104
105#[derive(Clone, Debug)]
106pub enum PlayerCmd {
107 AboutToFinish,
108 CycleLoop,
109 Eos,
110 GetProgress,
111 SkipPrevious,
112 Pause,
113 Play,
114 Quit,
115 ReloadConfig,
116 ReloadPlaylist,
117 SeekBackward,
118 SeekForward,
119 SkipNext,
120 SpeedDown,
121 SpeedUp,
122 Tick,
123 ToggleGapless,
124 TogglePause,
125 VolumeDown,
126 VolumeUp,
127 Error(PlayerErrorType),
132
133 PlaylistPlaySpecific(PlaylistPlaySpecific),
134 PlaylistAddTrack(PlaylistAddTrack),
135 PlaylistRemoveTrack(PlaylistRemoveTrackIndexed),
136 PlaylistClear,
137 PlaylistSwapTrack(PlaylistSwapTrack),
138 PlaylistShuffle,
139 PlaylistRemoveDeletedTracks,
140}
141
142pub type StreamTX = broadcast::Sender<UpdateEvents>;
143pub type SharedPlaylist = Arc<RwLock<Playlist>>;
144
145#[allow(clippy::module_name_repetitions)]
146pub struct GeneralPlayer {
147 pub backend: Backend,
148 pub playlist: SharedPlaylist,
149 pub config: SharedServerSettings,
150 pub current_track_updated: bool,
151 pub mpris: Option<mpris::Mpris>,
152 pub discord: Option<discord::Rpc>,
153 pub db: DataBase,
154 pub db_podcast: DBPod,
155 pub cmd_tx: PlayerCmdSender,
156 pub stream_tx: StreamTX,
157
158 pub errors_since_last_progress: usize,
160}
161
162impl GeneralPlayer {
163 pub fn new_backend(
170 backend: BackendSelect,
171 config: SharedServerSettings,
172 cmd_tx: PlayerCmdSender,
173 stream_tx: StreamTX,
174 playlist: SharedPlaylist,
175 ) -> Result<Self> {
176 let backend = Backend::new_select(backend, config.clone(), cmd_tx.clone());
177
178 let db_path = get_app_config_path().with_context(|| "failed to get podcast db path.")?;
179
180 let db_podcast = DBPod::new(&db_path).with_context(|| "error connecting to podcast db.")?;
181 let config_read = config.read();
182 let db = DataBase::new(&config_read)?;
183
184 let mpris = if config.read().settings.player.use_mediacontrols {
185 Some(mpris::Mpris::new(cmd_tx.clone()))
186 } else {
187 None
188 };
189 let discord = if config.read().get_discord_status_enable() {
190 Some(discord::Rpc::default())
191 } else {
192 None
193 };
194
195 drop(config_read);
196
197 Ok(Self {
198 backend,
199 playlist,
200 config,
201 mpris,
202 discord,
203 db,
204 db_podcast,
205 cmd_tx,
206 stream_tx,
207 current_track_updated: false,
208
209 errors_since_last_progress: 0,
210 })
211 }
212
213 pub fn increment_errors(&mut self) {
215 self.errors_since_last_progress += 1;
216 }
217
218 pub fn reset_errors(&mut self) {
220 self.errors_since_last_progress = 0;
221 }
222
223 pub fn new(
230 config: SharedServerSettings,
231 cmd_tx: PlayerCmdSender,
232 stream_tx: StreamTX,
233 playlist: SharedPlaylist,
234 ) -> Result<Self> {
235 Self::new_backend(
236 BackendSelect::default(),
237 config,
238 cmd_tx,
239 stream_tx,
240 playlist,
241 )
242 }
243
244 pub fn reload_config(&mut self) -> Result<()> {
250 info!("Reloading config");
251 let mut config = self.config.write();
252 let parsed = ServerConfigVersionedDefaulted::from_config_path()?.into_settings();
253 config.settings = parsed;
254
255 if config.settings.player.use_mediacontrols && self.mpris.is_none() {
256 let mut mpris = mpris::Mpris::new(self.cmd_tx.clone());
258 if let Some(track) = self.playlist.read().current_track() {
260 mpris.add_and_play(track);
261 }
262 mpris.update_volume(self.volume());
264 self.mpris.replace(mpris);
265 } else if !config.settings.player.use_mediacontrols && self.mpris.is_some() {
266 self.mpris.take();
268 }
269
270 if config.get_discord_status_enable() && self.discord.is_none() {
271 let discord = discord::Rpc::default();
273
274 if let Some(track) = self.playlist.read().current_track() {
276 discord.update(track);
277 }
278
279 self.discord.replace(discord);
280 } else if !config.get_discord_status_enable() && self.discord.is_some() {
281 self.discord.take();
283 }
284
285 info!("Config Reloaded");
286
287 Ok(())
288 }
289
290 fn get_player(&self) -> &dyn PlayerTrait {
291 self.backend.as_player()
292 }
293
294 fn get_player_mut(&mut self) -> &mut (dyn PlayerTrait + Send) {
295 self.backend.as_player_mut()
296 }
297
298 pub fn toggle_gapless(&mut self) -> bool {
299 let new_gapless = !<Self as PlayerTrait>::gapless(self);
300 <Self as PlayerTrait>::set_gapless(self, new_gapless);
301 self.config.write().settings.player.gapless = new_gapless;
302 new_gapless
303 }
304
305 pub fn start_play(&mut self) {
311 let mut playlist = self.playlist.write();
312 if playlist.is_stopped() | playlist.is_paused() {
313 playlist.set_status(RunningStatus::Running);
314 }
315
316 playlist.proceed();
317
318 if let Some(track) = playlist.current_track().cloned() {
319 info!("Starting Track {track:#?}");
320
321 if playlist.has_next_track() {
322 playlist.set_next_track(None);
323 drop(playlist);
324 self.current_track_updated = true;
325 info!("gapless next track played");
326 self.add_and_play_mpris_discord();
327 return;
328 }
329 drop(playlist);
330
331 self.current_track_updated = true;
332 let wait = async {
333 self.add_and_play(&track).await;
334 };
335 Handle::current().block_on(wait);
336
337 self.add_and_play_mpris_discord();
338 self.player_restore_last_position();
339
340 self.send_stream_ev(UpdateEvents::TrackChanged(TrackChangedInfo {
341 current_track_index: u64::try_from(self.playlist.read().get_current_track_index())
342 .unwrap(),
343 current_track_updated: self.current_track_updated,
344 title: self.media_info().media_title,
345 progress: self.get_progress(),
346 }));
347 }
348 }
349
350 fn add_and_play_mpris_discord(&mut self) {
351 if let Some(track) = self.playlist.read().current_track() {
352 if let Some(ref mut mpris) = self.mpris {
353 mpris.add_and_play(track);
354 }
355
356 if let Some(ref discord) = self.discord {
357 discord.update(track);
358 }
359 }
360 }
361 pub fn enqueue_next_from_playlist(&mut self) {
362 let mut playlist = self.playlist.write();
363 if playlist.has_next_track() {
364 return;
365 }
366
367 let Some(track) = playlist.fetch_next_track().cloned() else {
368 return;
369 };
370 drop(playlist);
371
372 self.enqueue_next(&track);
373
374 info!("Next track enqueued: {track:#?}");
375 }
376
377 pub fn next(&mut self) {
379 if self.playlist.read().current_track().is_some() {
380 info!("skip route 1 which is in most cases.");
381 self.playlist.write().set_next_track(None);
382 self.skip_one();
383 } else {
384 info!("skip route 2 cause no current track.");
385 self.stop();
386 }
387 }
388
389 pub fn previous(&mut self) {
391 let mut playlist = self.playlist.write();
392 playlist.previous();
393 playlist.proceed_false();
394 drop(playlist);
395 self.next();
396 }
397
398 pub fn toggle_pause(&mut self) {
400 let status = self.playlist.read().status();
403 match status {
404 RunningStatus::Running => {
405 <Self as PlayerTrait>::pause(self);
406 }
407 RunningStatus::Stopped => {}
408 RunningStatus::Paused => {
409 <Self as PlayerTrait>::resume(self);
410 }
411 }
412 }
413
414 pub fn pause(&mut self) {
416 let status = self.playlist.read().status();
419 match status {
420 RunningStatus::Running => {
421 <Self as PlayerTrait>::pause(self);
422 }
423 RunningStatus::Stopped | RunningStatus::Paused => {}
424 }
425 }
426
427 pub fn play(&mut self) {
429 let status = self.playlist.read().status();
432 match status {
433 RunningStatus::Running | RunningStatus::Stopped => {}
434 RunningStatus::Paused => {
435 <Self as PlayerTrait>::resume(self);
436 }
437 }
438 }
439 pub fn seek_relative(&mut self, forward: bool) {
443 let track_len = self
445 .playlist
446 .read()
447 .current_track()
448 .and_then(Track::duration)
449 .unwrap_or(Duration::from_secs(5))
450 .as_secs();
451
452 let mut offset = self
453 .config
454 .read()
455 .settings
456 .player
457 .seek_step
458 .get_step(track_len);
459
460 if !forward {
461 offset = -offset;
462 }
463 self.seek(offset).expect("Error in player seek.");
464 }
465
466 #[allow(clippy::cast_sign_loss)]
467 pub fn player_save_last_position(&mut self) {
468 let playlist = self.playlist.read();
469 let Some(track) = playlist.current_track() else {
470 info!("Not saving Last position as there is no current track");
471 return;
472 };
473 let Some(position) = self.position() else {
474 info!("Not saving Last position as there is no position");
475 return;
476 };
477
478 let Some(time_before_save) = self
479 .config
480 .read()
481 .settings
482 .player
483 .remember_position
484 .get_time(track.media_type())
485 else {
486 info!(
487 "Not saving Last position as \"Remember last position\" is not enabled for {:#?}",
488 track.media_type()
489 );
490 return;
491 };
492
493 if time_before_save < position.as_secs() {
494 match track.media_type() {
495 MediaTypesSimple::Music => {
496 if let Err(err) = self.db.set_last_position(track, position) {
497 error!("Saving last_position for music failed, Error: {err:#?}");
498 }
499 }
500 MediaTypesSimple::LiveRadio => (),
501 MediaTypesSimple::Podcast => {
502 if let Err(err) = self.db_podcast.set_last_position(track, position) {
503 error!("Saving last_position for podcast failed, Error: {err:#?}");
504 }
505 }
506 }
507 } else {
508 info!("Not saving Last position as the position is lower than time_before_save");
509 }
510 }
511
512 pub fn player_restore_last_position(&mut self) {
513 let playlist = self.playlist.read();
514 let Some(track) = playlist.current_track().cloned() else {
515 info!("Not restoring Last position as there is no current track");
516 return;
517 };
518 drop(playlist);
519
520 let mut restored = false;
521
522 if self
523 .config
524 .read()
525 .settings
526 .player
527 .remember_position
528 .is_enabled_for(track.media_type())
529 {
530 match track.media_type() {
531 MediaTypesSimple::Music => {
532 if let Ok(last_pos) = self.db.get_last_position(&track) {
533 self.seek_to(last_pos);
534 restored = true;
535 }
536 }
537 MediaTypesSimple::LiveRadio => (),
538 MediaTypesSimple::Podcast => {
539 if let Ok(last_pos) = self.db_podcast.get_last_position(&track) {
540 self.seek_to(last_pos);
541 restored = true;
542 }
543 }
544 }
545 } else {
546 info!(
547 "Not restoring Last position as it is not enabled for {:#?}",
548 track.media_type()
549 );
550 }
551
552 if restored {
553 if let Err(err) = self.db.set_last_position(&track, Duration::from_secs(0)) {
555 error!("Resetting last_position failed, Error: {err:#?}");
556 }
557 }
558 }
559
560 fn send_stream_ev(&self, ev: UpdateEvents) {
562 if self.stream_tx.send(ev).is_err() {
564 debug!("Stream Event not send: No Receivers");
565 }
566 }
567}
568
569#[async_trait]
570impl PlayerTrait for GeneralPlayer {
571 async fn add_and_play(&mut self, track: &Track) {
572 self.get_player_mut().add_and_play(track).await;
573 }
574 fn volume(&self) -> Volume {
575 self.get_player().volume()
576 }
577 fn add_volume(&mut self, volume: VolumeSigned) -> Volume {
578 let vol = self.get_player_mut().add_volume(volume);
579 self.send_stream_ev(UpdateEvents::VolumeChanged { volume: vol });
580
581 vol
582 }
583 fn set_volume(&mut self, volume: Volume) -> Volume {
584 let vol = self.get_player_mut().set_volume(volume);
585 self.send_stream_ev(UpdateEvents::VolumeChanged { volume: vol });
586
587 vol
588 }
589 fn pause(&mut self) {
591 self.playlist.write().set_status(RunningStatus::Paused);
592 self.get_player_mut().pause();
593 if let Some(ref mut mpris) = self.mpris {
594 mpris.pause();
595 }
596 if let Some(ref discord) = self.discord {
597 discord.pause();
598 }
599
600 self.send_stream_ev(UpdateEvents::PlayStateChanged {
601 playing: RunningStatus::Paused.as_u32(),
602 });
603 }
604 fn resume(&mut self) {
606 self.playlist.write().set_status(RunningStatus::Running);
607 self.get_player_mut().resume();
608 if let Some(ref mut mpris) = self.mpris {
609 mpris.resume();
610 }
611 let time_pos = self.get_player().position();
612 if let Some(ref discord) = self.discord {
613 discord.resume(time_pos);
614 }
615
616 self.send_stream_ev(UpdateEvents::PlayStateChanged {
617 playing: RunningStatus::Running.as_u32(),
618 });
619 }
620 fn is_paused(&self) -> bool {
621 self.get_player().is_paused()
622 }
623 fn seek(&mut self, secs: i64) -> Result<()> {
624 self.get_player_mut().seek(secs)
625 }
626 fn seek_to(&mut self, position: Duration) {
627 self.get_player_mut().seek_to(position);
628 }
629
630 fn set_speed(&mut self, speed: Speed) -> Speed {
631 let speed = self.get_player_mut().set_speed(speed);
632 self.send_stream_ev(UpdateEvents::SpeedChanged { speed });
633
634 speed
635 }
636
637 fn add_speed(&mut self, speed: SpeedSigned) -> Speed {
638 let speed = self.get_player_mut().add_speed(speed);
639 self.send_stream_ev(UpdateEvents::SpeedChanged { speed });
640
641 speed
642 }
643
644 fn speed(&self) -> Speed {
645 self.get_player().speed()
646 }
647
648 fn stop(&mut self) {
649 self.playlist.write().stop();
650 self.get_player_mut().stop();
651 }
652
653 fn get_progress(&self) -> Option<PlayerProgress> {
654 self.get_player().get_progress()
655 }
656
657 fn gapless(&self) -> bool {
658 self.get_player().gapless()
659 }
660
661 fn set_gapless(&mut self, to: bool) {
662 self.get_player_mut().set_gapless(to);
663 self.send_stream_ev(UpdateEvents::GaplessChanged { gapless: to });
664 }
665
666 fn skip_one(&mut self) {
667 self.get_player_mut().skip_one();
668 }
669
670 fn position(&self) -> Option<PlayerTimeUnit> {
671 self.get_player().position()
672 }
673
674 fn enqueue_next(&mut self, track: &Track) {
675 self.get_player_mut().enqueue_next(track);
676 }
677
678 fn media_info(&self) -> MediaInfo {
679 self.get_player().media_info()
680 }
681}
682
683#[derive(Debug, Clone, PartialEq, Default)]
687pub struct MediaInfo {
688 pub media_title: Option<String>,
690}
691
692pub type Volume = u16;
693pub type VolumeSigned = i16;
695pub type Speed = i32;
696pub type SpeedSigned = Speed;
698
699pub const MIN_SPEED: Speed = 1;
700pub const MAX_SPEED: Speed = 30;
701
702#[allow(clippy::module_name_repetitions)]
703#[async_trait]
704pub trait PlayerTrait {
705 async fn add_and_play(&mut self, track: &Track);
707 fn volume(&self) -> Volume;
709 fn add_volume(&mut self, volume: VolumeSigned) -> Volume {
713 let volume = self.volume().saturating_add_signed(volume);
714 self.set_volume(volume)
715 }
716 fn set_volume(&mut self, volume: Volume) -> Volume;
720 fn pause(&mut self);
721 fn resume(&mut self);
722 fn is_paused(&self) -> bool;
723 fn seek(&mut self, secs: i64) -> Result<()>;
729 fn seek_to(&mut self, position: Duration);
732 fn get_progress(&self) -> Option<PlayerProgress>;
734 fn set_speed(&mut self, speed: Speed) -> Speed;
738 fn add_speed(&mut self, speed: SpeedSigned) -> Speed {
742 let speed = (self.speed() + speed).clamp(MIN_SPEED, MAX_SPEED);
744 self.set_speed(speed)
745 }
746 fn speed(&self) -> Speed;
748 fn stop(&mut self);
749 fn gapless(&self) -> bool;
750 fn set_gapless(&mut self, to: bool);
751 fn skip_one(&mut self);
752 fn position(&self) -> Option<PlayerTimeUnit> {
756 self.get_progress()?.position
757 }
758 fn enqueue_next(&mut self, track: &Track);
760 fn media_info(&self) -> MediaInfo;
762}