Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 21 additions & 19 deletions core/rodio_player/BUILD → core/player/BUILD
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
load("@rules_rust//cargo:defs.bzl", "cargo_build_script")
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test")
load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")

cargo_build_script(
name = "build_script",
srcs = ["build.rs"],
crate_root = "build.rs",
data = ["//external/ffmpeg:gen_dir"],
visibility = ["//visibility:public"],
)

rust_library(
name = "rodio_player",
name = "player",
srcs = [
"src/decoder.rs",
"src/error.rs",
"src/generic.rs",
"src/lib.rs",
"src/tests.rs",
"src/mux_player.rs",
"src/queue.rs",
"src/rodio/decoder.rs",
"src/rodio/mod.rs",
"src/source.rs",
"src/test.rs",
],
crate_root = "src/lib.rs",
data = ["//external/ffmpeg:gen_dir"],
Expand All @@ -17,6 +31,7 @@ rust_library(
visibility = ["//visibility:public"],
deps = [
":build_script",
"//core/spotify_player",
"//core/types",
"//core/types/protos:extensions_rs",
"//core/types/protos:songs_rs",
Expand All @@ -30,20 +45,7 @@ rust_library(
],
)

cargo_build_script(
name = "build_script",
srcs = ["build.rs"],
crate_root = "build.rs",
data = ["//external/ffmpeg:gen_dir"],
visibility = ["//visibility:public"],
)

rust_test(
name = "rodio_player_test",
crate = ":rodio_player",
)

rust_binary(
name = "build",
srcs = ["build.rs"],
name = "player_test",
crate = ":player",
)
File renamed without changes.
16 changes: 16 additions & 0 deletions core/player/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use std::fmt::Debug;

use songs_proto::moosync::types::Song;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum PlayerError {
#[error("Could not find a player to play song: {0}")]
NoPlayerFound(String),
#[error("Could not find a src for song: {0:?}")]
NoSrcFound(Song),
#[error("Could not resolve playback url for song: {0:?}")]
PlaybackUrlResolutionFailed(Box<dyn std::error::Error + Send + Sync>),
#[error("Invalid song")]
InvalidSong,
}
17 changes: 17 additions & 0 deletions core/player/src/generic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use std::{fmt::Display, path::PathBuf};

use songs_proto::moosync::types::Song;
use types::prelude::SongsExt;

use crate::{error::PlayerError, source::ValidSrc};

pub(crate) trait PlayerExt {
fn play(&self) -> Result<(), PlayerError>;
fn pause(&self) -> Result<(), PlayerError>;
fn stop(&self) -> Result<(), PlayerError>;
fn set_volume(&self, volume: f32) -> Result<(), PlayerError>;
fn seek(&self, position: f64) -> Result<(), PlayerError>;
fn set_src(&self, src: ValidSrc) -> Result<(), PlayerError>;

fn can_play(&self, src: ValidSrc) -> bool;
}
83 changes: 83 additions & 0 deletions core/player/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Moosync
// Copyright (C) 2024, 2025 Moosync <[email protected]>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

use songs_proto::moosync::types::Song;
use spotify_player::LibrespotHolder;

use crate::{
error::PlayerError,
generic::PlayerExt,
mux_player::MuxPlayer,
source::{SourceResolver, SourceResolverFn},
};

mod error;
mod generic;
mod mux_player;
mod queue;
mod rodio;
mod source;

#[cfg(test)]
mod test;

pub struct PlayerHandler {
mux: MuxPlayer,
source_resolver: SourceResolver,
}

impl PlayerHandler {
pub fn new(f: SourceResolverFn) -> Self {
Self {
mux: MuxPlayer::new(),
source_resolver: SourceResolver::new(f),
}
}

pub fn load_song(&mut self, mut song: Song) -> Result<(), PlayerError> {
let res = self.mux.load(&song);

// Try to resolve the playback url if no player was found
// This basically runs 2 passes of load if no playback url was found
if let Err(e) = res {
match e {
PlayerError::NoPlayerFound(_) | PlayerError::NoSrcFound(_) => {
self.source_resolver.resolve_playback_url(&mut song)?;
self.mux.load(&song)?;
}
_ => return Err(e),
}
}

Ok(())
}

fn play(&self) -> Result<(), PlayerError> {
self.mux.play()
}

fn pause(&self) -> Result<(), PlayerError> {
self.mux.pause()
}

fn set_volume(&self, volume: f32) -> Result<(), PlayerError> {
self.mux.set_volume(volume)
}

fn seek(&self, position: f64) -> Result<(), PlayerError> {
self.mux.seek(position)
}
}
94 changes: 94 additions & 0 deletions core/player/src/mux_player.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use std::sync::Arc;

use songs_proto::moosync::types::Song;
use types::prelude::SongsExt;

use crate::{
error::PlayerError,
generic::PlayerExt,
rodio::RodioPlayer,
source::{ValidSrc, get_valid_src},
};

pub(crate) struct MuxPlayer {
players: Vec<Arc<Box<dyn PlayerExt>>>,
active_player: Arc<Box<dyn PlayerExt>>,
}

// Common holder for all players
// Decides which player to use for a given song and abstracts calls on the active player
impl MuxPlayer {
pub fn new() -> Self {
let players: Vec<Arc<Box<dyn PlayerExt>>> = vec![
Arc::new(Box::new(RodioPlayer::new())),
// Player::Spotify(LibrespotHolder::new()),
];
Self {
active_player: players[0].clone(),
players,
}
}

fn set_active_player(&mut self, player: Arc<Box<dyn PlayerExt>>) {
self.active_player = player;
}

fn find_best_player(&mut self, song: &Song) -> Result<(), PlayerError> {
let src = get_valid_src(&song)?;
for player in &self.players {
if player.can_play(src.clone()) {
self.set_active_player(player.clone());
return Ok(());
}
}

Err(PlayerError::NoPlayerFound(song.to_string()))
}

pub fn load(&mut self, song: &Song) -> Result<(), PlayerError> {
let src = get_valid_src(song)?;
self.find_best_player(song)?;
// SAFETY: find_best_player should throw an error if no player was found. At this point active player is not None.
self.active_player.set_src(src)?;
Ok(())
}
}

impl PlayerExt for MuxPlayer {
fn play(&self) -> Result<(), PlayerError> {
self.active_player.play()
}

fn pause(&self) -> Result<(), PlayerError> {
self.active_player.pause()
}

fn stop(&self) -> Result<(), PlayerError> {
self.active_player.stop()
}

fn set_volume(&self, volume: f32) -> Result<(), PlayerError> {
self.active_player.set_volume(volume)
}

fn seek(&self, position: f64) -> Result<(), PlayerError> {
self.active_player.seek(position)
}

fn set_src(&self, src: ValidSrc) -> Result<(), PlayerError> {
self.active_player.set_src(src)
}

// WARN: Do not call can_play on the mux player, it will always return false
// can_play should only be called on the individual players
fn can_play(&self, _: ValidSrc) -> bool {
tracing::warn!("Do not call can_play on the mux player, it will always return false");
false
}
}

impl Default for MuxPlayer {
fn default() -> Self {
Self::new()
}
}
Empty file added core/player/src/queue.rs
Empty file.
Loading
Loading