1#[cfg(feature = "gst")]
26mod gstreamer_backend;
27#[cfg(feature = "mpv")]
28mod mpv_backend;
29mod rusty_backend;
30
31mod discord;
32mod mpris;
33pub mod playlist;
34
35use anyhow::{Context, Result};
36use async_trait::async_trait;
37pub use playlist::{Playlist, Status};
38use std::time::Duration;
39use termusiclib::config::v2::server::config_extra::ServerConfigVersionedDefaulted;
40use termusiclib::config::{ServerOverlay, SharedServerSettings};
41use termusiclib::library_db::DataBase;
42use termusiclib::player::{PlayerProgress, PlayerTimeUnit, TrackChangedInfo, UpdateEvents};
43use termusiclib::podcast::db::Database as DBPod;
44use termusiclib::track::{MediaType, Track};
45use termusiclib::utils::get_app_config_path;
46use tokio::runtime::Handle;
47use tokio::sync::mpsc::error::SendError;
48use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
49use tokio::sync::{broadcast, oneshot};
50
51#[macro_use]
52extern crate log;
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
55pub enum BackendSelect {
56 #[cfg(feature = "mpv")]
57 Mpv,
58 #[cfg(feature = "gst")]
59 GStreamer,
60 #[default]
67 Rusty,
68}
69
70#[non_exhaustive]
72pub enum Backend {
73 #[cfg(feature = "mpv")]
74 Mpv(mpv_backend::MpvBackend),
75 Rusty(rusty_backend::RustyBackend),
76 #[cfg(feature = "gst")]
77 GStreamer(gstreamer_backend::GStreamerBackend),
78}
79
80pub type PlayerCmdCallback = oneshot::Receiver<()>;
81pub type PlayerCmdReciever = UnboundedReceiver<(PlayerCmd, PlayerCmdCallbackSender)>;
82
83#[derive(Debug)]
85pub struct PlayerCmdCallbackSender(Option<oneshot::Sender<()>>);
86
87impl PlayerCmdCallbackSender {
88 pub fn call(self) {
90 let Some(sender) = self.0 else {
91 return;
92 };
93 let _ = sender.send(());
94 }
95}
96
97#[derive(Debug, Clone)]
99pub struct PlayerCmdSender(UnboundedSender<(PlayerCmd, PlayerCmdCallbackSender)>);
100
101impl PlayerCmdSender {
102 pub fn send(
107 &self,
108 cmd: PlayerCmd,
109 ) -> Result<(), SendError<(PlayerCmd, PlayerCmdCallbackSender)>> {
110 self.0.send((cmd, PlayerCmdCallbackSender(None)))
111 }
112
113 pub fn send_cb(
118 &self,
119 cmd: PlayerCmd,
120 ) -> Result<PlayerCmdCallback, SendError<(PlayerCmd, PlayerCmdCallbackSender)>> {
121 let (tx, rx) = oneshot::channel();
122 self.0.send((cmd, PlayerCmdCallbackSender(Some(tx))))?;
123 Ok(rx)
124 }
125
126 #[must_use]
127 pub fn new(tx: UnboundedSender<(PlayerCmd, PlayerCmdCallbackSender)>) -> Self {
128 Self(tx)
129 }
130}
131
132impl Backend {
133 fn new_select(backend: BackendSelect, config: &ServerOverlay, cmd_tx: PlayerCmdSender) -> Self {
135 match backend {
136 #[cfg(feature = "mpv")]
137 BackendSelect::Mpv => Self::new_mpv(config, cmd_tx),
138 #[cfg(feature = "gst")]
139 BackendSelect::GStreamer => Self::new_gstreamer(config, cmd_tx),
140 BackendSelect::Rusty => Self::new_rusty(config, cmd_tx),
141 }
142 }
143
144 fn new_rusty(config: &ServerOverlay, cmd_tx: PlayerCmdSender) -> Self {
158 info!("Using Backend \"rusty\"");
159 Self::Rusty(rusty_backend::RustyBackend::new(config, cmd_tx))
160 }
161
162 #[cfg(feature = "gst")]
164 fn new_gstreamer(config: &ServerOverlay, cmd_tx: PlayerCmdSender) -> Self {
165 info!("Using Backend \"GStreamer\"");
166 Self::GStreamer(gstreamer_backend::GStreamerBackend::new(config, cmd_tx))
167 }
168
169 #[cfg(feature = "mpv")]
171 fn new_mpv(config: &ServerOverlay, cmd_tx: PlayerCmdSender) -> Self {
172 info!("Using Backend \"mpv\"");
173 Self::Mpv(mpv_backend::MpvBackend::new(config, cmd_tx))
174 }
175
176 #[must_use]
177 pub fn as_player(&self) -> &dyn PlayerTrait {
178 match self {
179 #[cfg(feature = "mpv")]
180 Backend::Mpv(v) => v,
181 #[cfg(feature = "gst")]
182 Backend::GStreamer(v) => v,
183 Backend::Rusty(v) => v,
184 }
185 }
186
187 #[must_use]
188 pub fn as_player_mut(&mut self) -> &mut (dyn PlayerTrait + Send) {
189 match self {
190 #[cfg(feature = "mpv")]
191 Backend::Mpv(v) => v,
192 #[cfg(feature = "gst")]
193 Backend::GStreamer(v) => v,
194 Backend::Rusty(v) => v,
195 }
196 }
197}
198
199#[derive(Clone, Debug)]
200pub enum PlayerCmd {
201 AboutToFinish,
202 CycleLoop,
203 Eos,
204 GetProgress,
205 PlaySelected,
206 SkipPrevious,
207 Pause,
208 Play,
209 Quit,
210 ReloadConfig,
211 ReloadPlaylist,
212 SeekBackward,
213 SeekForward,
214 SkipNext,
215 SpeedDown,
216 SpeedUp,
217 Tick,
218 ToggleGapless,
219 TogglePause,
220 VolumeDown,
221 VolumeUp,
222}
223
224pub type StreamTX = broadcast::Sender<UpdateEvents>;
225
226#[allow(clippy::module_name_repetitions)]
227pub struct GeneralPlayer {
228 pub backend: Backend,
229 pub playlist: Playlist,
230 pub config: SharedServerSettings,
231 pub current_track_updated: bool,
232 pub mpris: Option<mpris::Mpris>,
233 pub discord: Option<discord::Rpc>,
234 pub db: DataBase,
235 pub db_podcast: DBPod,
236 pub cmd_tx: PlayerCmdSender,
237 pub stream_tx: StreamTX,
238}
239
240impl GeneralPlayer {
241 pub fn new_backend(
248 backend: BackendSelect,
249 config: SharedServerSettings,
250 cmd_tx: PlayerCmdSender,
251 stream_tx: StreamTX,
252 ) -> Result<Self> {
253 let config_read = config.read();
254 let backend = Backend::new_select(backend, &config_read, cmd_tx.clone());
255
256 let db_path = get_app_config_path().with_context(|| "failed to get podcast db path.")?;
257
258 let db_podcast = DBPod::new(&db_path).with_context(|| "error connecting to podcast db.")?;
259 let db = DataBase::new(&config_read)?;
260
261 let playlist = Playlist::new(&config).context("Failed to load playlist")?;
262 let mpris = if config.read().settings.player.use_mediacontrols {
263 Some(mpris::Mpris::new(cmd_tx.clone()))
264 } else {
265 None
266 };
267 let discord = if config.read().get_discord_status_enable() {
268 Some(discord::Rpc::default())
269 } else {
270 None
271 };
272
273 drop(config_read);
274
275 Ok(Self {
276 backend,
277 playlist,
278 config,
279 mpris,
280 discord,
281 db,
282 db_podcast,
283 cmd_tx,
284 stream_tx,
285 current_track_updated: false,
286 })
287 }
288
289 pub fn new(
296 config: SharedServerSettings,
297 cmd_tx: PlayerCmdSender,
298 stream_tx: StreamTX,
299 ) -> Result<Self> {
300 Self::new_backend(BackendSelect::Rusty, config, cmd_tx, stream_tx)
301 }
302
303 pub fn reload_config(&mut self) -> Result<()> {
309 info!("Reloading config");
310 let mut config = self.config.write();
311 let parsed = ServerConfigVersionedDefaulted::from_config_path()?.into_settings();
312 config.settings = parsed;
313
314 if config.settings.player.use_mediacontrols && self.mpris.is_none() {
315 let mut mpris = mpris::Mpris::new(self.cmd_tx.clone());
317 if let Some(track) = self.playlist.current_track() {
319 mpris.add_and_play(track);
320 }
321 mpris.update_volume(self.volume());
323 self.mpris.replace(mpris);
324 } else if !config.settings.player.use_mediacontrols && self.mpris.is_some() {
325 self.mpris.take();
327 }
328
329 if config.get_discord_status_enable() && self.discord.is_none() {
330 let discord = discord::Rpc::default();
332
333 if let Some(track) = self.playlist.current_track() {
335 discord.update(track);
336 }
337
338 self.discord.replace(discord);
339 } else if !config.get_discord_status_enable() && self.discord.is_some() {
340 self.discord.take();
342 }
343
344 info!("Config Reloaded");
345
346 Ok(())
347 }
348
349 fn get_player(&self) -> &dyn PlayerTrait {
350 self.backend.as_player()
351 }
352
353 fn get_player_mut(&mut self) -> &mut (dyn PlayerTrait + Send) {
354 self.backend.as_player_mut()
355 }
356
357 pub fn toggle_gapless(&mut self) -> bool {
358 let new_gapless = !<Self as PlayerTrait>::gapless(self);
359 <Self as PlayerTrait>::set_gapless(self, new_gapless);
360 self.config.write().settings.player.gapless = new_gapless;
361 new_gapless
362 }
363
364 pub fn start_play(&mut self) {
370 if self.playlist.is_stopped() | self.playlist.is_paused() {
371 self.playlist.set_status(Status::Running);
372 }
373
374 self.playlist.proceed();
375
376 if let Some(track) = self.playlist.current_track().cloned() {
377 info!("Starting Track {:#?}", track);
378
379 if self.playlist.has_next_track() {
380 self.playlist.set_next_track(None);
381 self.current_track_updated = true;
382 info!("gapless next track played");
383 #[allow(irrefutable_let_patterns)]
384 if let Backend::Rusty(ref mut backend) = self.backend {
385 backend.message_on_end();
386 }
387 self.add_and_play_mpris_discord();
388 return;
389 }
390
391 self.current_track_updated = true;
392 let wait = async {
393 self.add_and_play(&track).await;
394 };
395 Handle::current().block_on(wait);
396
397 self.add_and_play_mpris_discord();
398 self.player_restore_last_position();
399 #[allow(irrefutable_let_patterns)]
400 if let Backend::Rusty(ref mut backend) = self.backend {
401 backend.message_on_end();
402 }
403
404 self.send_stream_ev(UpdateEvents::TrackChanged(TrackChangedInfo {
405 current_track_index: u64::try_from(self.playlist.get_current_track_index())
406 .unwrap(),
407 current_track_updated: self.current_track_updated,
408 title: self.media_info().media_title,
409 progress: self.get_progress(),
410 }));
411 }
412 }
413
414 fn add_and_play_mpris_discord(&mut self) {
415 if let Some(track) = self.playlist.current_track() {
416 if let Some(ref mut mpris) = self.mpris {
417 mpris.add_and_play(track);
418 }
419
420 if let Some(ref discord) = self.discord {
421 discord.update(track);
422 }
423 }
424 }
425 pub fn enqueue_next_from_playlist(&mut self) {
426 if self.playlist.has_next_track() {
427 return;
428 }
429
430 let Some(track) = self.playlist.fetch_next_track().cloned() else {
431 return;
432 };
433
434 self.enqueue_next(&track);
435
436 info!("Next track enqueued: {:#?}", track);
437 }
438
439 pub fn next(&mut self) {
441 if self.playlist.current_track().is_some() {
442 info!("skip route 1 which is in most cases.");
443 self.playlist.set_next_track(None);
444 self.skip_one();
445 } else {
446 info!("skip route 2 cause no current track.");
447 self.stop();
448 }
449 }
450
451 pub fn previous(&mut self) {
453 self.playlist.previous();
454 self.playlist.proceed_false();
455 self.next();
456 }
457
458 pub fn toggle_pause(&mut self) {
460 match self.playlist.status() {
461 Status::Running => {
462 <Self as PlayerTrait>::pause(self);
463 }
464 Status::Stopped => {}
465 Status::Paused => {
466 <Self as PlayerTrait>::resume(self);
467 }
468 }
469 }
470
471 pub fn pause(&mut self) {
473 match self.playlist.status() {
474 Status::Running => {
475 <Self as PlayerTrait>::pause(self);
476 }
477 Status::Stopped | Status::Paused => {}
478 }
479 }
480
481 pub fn play(&mut self) {
483 match self.playlist.status() {
484 Status::Running | Status::Stopped => {}
485 Status::Paused => {
486 <Self as PlayerTrait>::resume(self);
487 }
488 }
489 }
490 pub fn seek_relative(&mut self, forward: bool) {
494 let track_len = if let Some(track) = self.playlist.current_track() {
495 track.duration().as_secs()
496 } else {
497 5
499 };
500
501 let mut offset = self
502 .config
503 .read()
504 .settings
505 .player
506 .seek_step
507 .get_step(track_len);
508
509 if !forward {
510 offset = -offset;
511 }
512 self.seek(offset).expect("Error in player seek.");
513 }
514
515 #[allow(clippy::cast_sign_loss)]
516 pub fn player_save_last_position(&mut self) {
517 let Some(track) = self.playlist.current_track() else {
518 info!("Not saving Last position as there is no current track");
519 return;
520 };
521 let Some(position) = self.position() else {
522 info!("Not saving Last position as there is no position");
523 return;
524 };
525
526 let Some(time_before_save) = self
527 .config
528 .read()
529 .settings
530 .player
531 .remember_position
532 .get_time(track.media_type)
533 else {
534 info!(
535 "Not saving Last position as \"Remember last position\" is not enabled for {:#?}",
536 track.media_type
537 );
538 return;
539 };
540
541 if time_before_save < position.as_secs() {
542 match track.media_type {
543 MediaType::Music => {
544 if let Err(err) = self.db.set_last_position(track, position) {
545 error!("Saving last_position for music failed, Error: {:#?}", err);
546 }
547 }
548 MediaType::Podcast => {
549 if let Err(err) = self.db_podcast.set_last_position(track, position) {
550 error!("Saving last_position for podcast failed, Error: {:#?}", err);
551 }
552 }
553 MediaType::LiveRadio => (),
554 }
555 } else {
556 info!("Not saving Last position as the position is lower than time_before_save");
557 }
558 }
559
560 pub fn player_restore_last_position(&mut self) {
561 let Some(track) = self.playlist.current_track() else {
562 info!("Not restoring Last position as there is no current track");
563 return;
564 };
565
566 let mut restored = false;
567
568 if self
569 .config
570 .read()
571 .settings
572 .player
573 .remember_position
574 .is_enabled_for(track.media_type)
575 {
576 match track.media_type {
577 MediaType::Music => {
578 if let Ok(last_pos) = self.db.get_last_position(track) {
579 self.seek_to(last_pos);
580 restored = true;
581 }
582 }
583 MediaType::Podcast => {
584 if let Ok(last_pos) = self.db_podcast.get_last_position(track) {
585 self.seek_to(last_pos);
586 restored = true;
587 }
588 }
589 MediaType::LiveRadio => (),
590 }
591 } else {
592 info!(
593 "Not restoring Last position as it is not enabled for {:#?}",
594 track.media_type
595 );
596 }
597
598 if restored {
599 if let Some(track) = self.playlist.current_track() {
600 if let Err(err) = self.db.set_last_position(track, Duration::from_secs(0)) {
601 error!("Resetting last_position failed, Error: {:#?}", err);
602 }
603 }
604 }
605 }
606
607 fn send_stream_ev(&self, ev: UpdateEvents) {
609 if self.stream_tx.send(ev).is_err() {
611 debug!("Stream Event not send: No Receivers");
612 }
613 }
614}
615
616#[async_trait]
617impl PlayerTrait for GeneralPlayer {
618 async fn add_and_play(&mut self, track: &Track) {
619 self.get_player_mut().add_and_play(track).await;
620 }
621 fn volume(&self) -> Volume {
622 self.get_player().volume()
623 }
624 fn add_volume(&mut self, volume: VolumeSigned) -> Volume {
625 let vol = self.get_player_mut().add_volume(volume);
626 self.send_stream_ev(UpdateEvents::VolumeChanged { volume: vol });
627
628 vol
629 }
630 fn set_volume(&mut self, volume: Volume) -> Volume {
631 let vol = self.get_player_mut().set_volume(volume);
632 self.send_stream_ev(UpdateEvents::VolumeChanged { volume: vol });
633
634 vol
635 }
636 fn pause(&mut self) {
638 self.playlist.set_status(Status::Paused);
639 self.get_player_mut().pause();
640 if let Some(ref mut mpris) = self.mpris {
641 mpris.pause();
642 }
643 if let Some(ref discord) = self.discord {
644 discord.pause();
645 }
646
647 self.send_stream_ev(UpdateEvents::PlayStateChanged {
648 playing: Status::Paused.as_u32(),
649 });
650 }
651 fn resume(&mut self) {
653 self.playlist.set_status(Status::Running);
654 self.get_player_mut().resume();
655 if let Some(ref mut mpris) = self.mpris {
656 mpris.resume();
657 }
658 let time_pos = self.get_player().position();
659 if let Some(ref discord) = self.discord {
660 discord.resume(time_pos);
661 }
662
663 self.send_stream_ev(UpdateEvents::PlayStateChanged {
664 playing: Status::Running.as_u32(),
665 });
666 }
667 fn is_paused(&self) -> bool {
668 self.get_player().is_paused()
669 }
670 fn seek(&mut self, secs: i64) -> Result<()> {
671 self.get_player_mut().seek(secs)
672 }
673 fn seek_to(&mut self, position: Duration) {
674 self.get_player_mut().seek_to(position);
675 }
676
677 fn set_speed(&mut self, speed: Speed) -> Speed {
678 let speed = self.get_player_mut().set_speed(speed);
679 self.send_stream_ev(UpdateEvents::SpeedChanged { speed });
680
681 speed
682 }
683
684 fn add_speed(&mut self, speed: SpeedSigned) -> Speed {
685 let speed = self.get_player_mut().add_speed(speed);
686 self.send_stream_ev(UpdateEvents::SpeedChanged { speed });
687
688 speed
689 }
690
691 fn speed(&self) -> Speed {
692 self.get_player().speed()
693 }
694
695 fn stop(&mut self) {
696 self.playlist.stop();
697 self.get_player_mut().stop();
698 }
699
700 fn get_progress(&self) -> Option<PlayerProgress> {
701 self.get_player().get_progress()
702 }
703
704 fn gapless(&self) -> bool {
705 self.get_player().gapless()
706 }
707
708 fn set_gapless(&mut self, to: bool) {
709 self.get_player_mut().set_gapless(to);
710 self.send_stream_ev(UpdateEvents::GaplessChanged { gapless: to });
711 }
712
713 fn skip_one(&mut self) {
714 self.get_player_mut().skip_one();
715 }
716
717 fn position(&self) -> Option<PlayerTimeUnit> {
718 self.get_player().position()
719 }
720
721 fn enqueue_next(&mut self, track: &Track) {
722 self.get_player_mut().enqueue_next(track);
723 }
724
725 fn media_info(&self) -> MediaInfo {
726 self.get_player().media_info()
727 }
728}
729
730#[derive(Debug, Clone, PartialEq, Default)]
734pub struct MediaInfo {
735 pub media_title: Option<String>,
737}
738
739pub type Volume = u16;
740pub type VolumeSigned = i16;
742pub type Speed = i32;
743pub type SpeedSigned = Speed;
745
746pub const MIN_SPEED: Speed = 1;
747pub const MAX_SPEED: Speed = 30;
748
749#[allow(clippy::module_name_repetitions)]
750#[async_trait]
751pub trait PlayerTrait {
752 async fn add_and_play(&mut self, track: &Track);
754 fn volume(&self) -> Volume;
756 fn add_volume(&mut self, volume: VolumeSigned) -> Volume {
760 let volume = self.volume().saturating_add_signed(volume);
761 self.set_volume(volume)
762 }
763 fn set_volume(&mut self, volume: Volume) -> Volume;
767 fn pause(&mut self);
768 fn resume(&mut self);
769 fn is_paused(&self) -> bool;
770 fn seek(&mut self, secs: i64) -> Result<()>;
776 fn seek_to(&mut self, position: Duration);
779 fn get_progress(&self) -> Option<PlayerProgress>;
781 fn set_speed(&mut self, speed: Speed) -> Speed;
785 fn add_speed(&mut self, speed: SpeedSigned) -> Speed {
789 let speed = (self.speed() + speed).clamp(MIN_SPEED, MAX_SPEED);
791 self.set_speed(speed)
792 }
793 fn speed(&self) -> Speed;
795 fn stop(&mut self);
796 fn gapless(&self) -> bool;
797 fn set_gapless(&mut self, to: bool);
798 fn skip_one(&mut self);
799 fn position(&self) -> Option<PlayerTimeUnit> {
803 self.get_progress()?.position
804 }
805 fn enqueue_next(&mut self, track: &Track);
807 fn media_info(&self) -> MediaInfo;
809}