mirror of
https://github.com/jhbruhn/jellyfin-radio.git
synced 2025-07-05 11:18:49 +00:00
186 lines
5.6 KiB
Rust
186 lines
5.6 KiB
Rust
use awedio::NextSample;
|
|
use awedio::Sound;
|
|
use std::sync::mpsc;
|
|
|
|
/// Heavily Based on awedios SoundList and Controllable implementations
|
|
|
|
pub struct Player {
|
|
sounds: Vec<Box<dyn Sound>>,
|
|
was_empty: bool,
|
|
song_prefetch: u32,
|
|
}
|
|
|
|
type Command<S> = Box<dyn FnOnce(&mut S) + Send>;
|
|
|
|
pub struct PlayerControllable {
|
|
inner: Player,
|
|
command_receiver: mpsc::Receiver<Command<Player>>,
|
|
queue_next_song_sender: tokio::sync::mpsc::Sender<()>,
|
|
finished: bool,
|
|
}
|
|
|
|
pub struct PlayerController {
|
|
command_sender: mpsc::Sender<Command<Player>>,
|
|
queue_next_song_receiver: tokio::sync::mpsc::Receiver<()>,
|
|
}
|
|
|
|
impl Player {
|
|
/// Create a new empty Player.
|
|
pub fn new(song_prefetch: u32) -> (PlayerControllable, PlayerController) {
|
|
let (queue_next_song_sender, queue_next_song_receiver) = tokio::sync::mpsc::channel(1);
|
|
let inner = Player {
|
|
sounds: Vec::new(),
|
|
was_empty: false,
|
|
song_prefetch,
|
|
};
|
|
|
|
let (command_sender, command_receiver) = mpsc::channel::<Command<Player>>();
|
|
let controllable = PlayerControllable {
|
|
inner,
|
|
queue_next_song_sender,
|
|
command_receiver,
|
|
finished: false,
|
|
};
|
|
let controller = PlayerController {
|
|
command_sender,
|
|
queue_next_song_receiver,
|
|
};
|
|
|
|
(controllable, controller)
|
|
}
|
|
|
|
/// Add a Sound to be played after any existing sounds have `Finished`.
|
|
pub fn add(&mut self, sound: Box<dyn Sound>) {
|
|
if self.sounds.is_empty() {
|
|
self.was_empty = true;
|
|
}
|
|
self.sounds.push(sound);
|
|
}
|
|
|
|
fn last_song_playing_or_empty(&self) -> bool {
|
|
self.sounds.len() <= self.song_prefetch as usize
|
|
}
|
|
}
|
|
|
|
// Returned only when no sounds exist so they shouldn't be used in practice.
|
|
const DEFAULT_CHANNEL_COUNT: u16 = 2;
|
|
const DEFAULT_SAMPLE_RATE: u32 = 44100;
|
|
|
|
impl Sound for Player {
|
|
fn channel_count(&self) -> u16 {
|
|
self.sounds
|
|
.first()
|
|
.map(|s| s.channel_count())
|
|
.unwrap_or(DEFAULT_CHANNEL_COUNT)
|
|
}
|
|
|
|
fn sample_rate(&self) -> u32 {
|
|
self.sounds
|
|
.first()
|
|
.map(|s| s.sample_rate())
|
|
.unwrap_or(DEFAULT_SAMPLE_RATE)
|
|
}
|
|
|
|
fn on_start_of_batch(&mut self) {
|
|
for sound in &mut self.sounds {
|
|
sound.on_start_of_batch();
|
|
}
|
|
}
|
|
|
|
fn next_sample(&mut self) -> Result<NextSample, awedio::Error> {
|
|
let Some(next_sound) = self.sounds.first_mut() else {
|
|
return Ok(NextSample::Finished);
|
|
};
|
|
if self.was_empty {
|
|
self.was_empty = false;
|
|
return Ok(NextSample::MetadataChanged);
|
|
}
|
|
|
|
let next_sample = next_sound.next_sample();
|
|
if let Err(e) = &next_sample {
|
|
println!("Error playing track: {:?}", e);
|
|
}
|
|
|
|
let ret = match next_sample {
|
|
Ok(NextSample::Sample(_) | NextSample::MetadataChanged | NextSample::Paused) => next_sample.unwrap(),
|
|
Ok(NextSample::Finished) | Err(_) => { // Just ignore the error
|
|
self.sounds.remove(0);
|
|
if self.sounds.is_empty() {
|
|
NextSample::Finished
|
|
} else {
|
|
// The next sample might have different metadata. Instead of
|
|
// normalizing here let downstream normalize.
|
|
NextSample::MetadataChanged
|
|
}
|
|
}
|
|
};
|
|
Ok(ret)
|
|
}
|
|
}
|
|
|
|
impl Sound for PlayerControllable {
|
|
fn channel_count(&self) -> u16 {
|
|
self.inner.channel_count()
|
|
}
|
|
|
|
fn sample_rate(&self) -> u32 {
|
|
self.inner.sample_rate()
|
|
}
|
|
|
|
fn next_sample(&mut self) -> Result<awedio::NextSample, awedio::Error> {
|
|
let next = self.inner.next_sample()?;
|
|
match next {
|
|
awedio::NextSample::Sample(_)
|
|
| awedio::NextSample::MetadataChanged
|
|
| awedio::NextSample::Paused => Ok(next),
|
|
// Since this is controllable we might add another sound later.
|
|
// Ideally we would do this only if the inner sound can have sounds
|
|
// added to it but I don't think we can branch on S: AddSound here.
|
|
// We could add a Sound::is_addable but lets avoid that until we see
|
|
// a reason why it is necessary.
|
|
awedio::NextSample::Finished => {
|
|
if self.finished {
|
|
Ok(awedio::NextSample::Finished)
|
|
} else {
|
|
Ok(awedio::NextSample::Paused)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn on_start_of_batch(&mut self) {
|
|
loop {
|
|
match self.command_receiver.try_recv() {
|
|
Ok(command) => command(&mut self.inner),
|
|
Err(mpsc::TryRecvError::Empty) => break,
|
|
Err(mpsc::TryRecvError::Disconnected) => {
|
|
self.finished = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if self.inner.last_song_playing_or_empty() {
|
|
let _ = self.queue_next_song_sender.try_send(());
|
|
}
|
|
self.inner.on_start_of_batch();
|
|
}
|
|
}
|
|
|
|
impl PlayerController {
|
|
pub fn send_command(&mut self, command: Command<Player>) {
|
|
// Ignore the error since it only happens if the receiver
|
|
// has been dropped which is not expected after it has been
|
|
// sent to the manager.
|
|
let _ = self.command_sender.send(command);
|
|
}
|
|
}
|
|
|
|
impl PlayerController {
|
|
pub fn add(&mut self, sound: Box<dyn Sound>) {
|
|
self.send_command(Box::new(|s: &mut Player| s.add(sound)));
|
|
}
|
|
|
|
pub async fn wait_for_queue(&mut self) {
|
|
self.queue_next_song_receiver.recv().await;
|
|
}
|
|
}
|