improve website, add "sleep" playback backend

This commit is contained in:
Mark
2025-02-28 20:38:30 +01:00
parent d39d030640
commit f1dd980b52
10 changed files with 534 additions and 18 deletions

View File

@@ -17,5 +17,6 @@ default = []
playback = []
default-playback = ["playback-via-playback-rs"]
# default-playback = ["playback-via-rodio"]
playback-via-sleep = ["playback"]
playback-via-playback-rs = ["playback", "dep:playback-rs"]
playback-via-rodio = ["playback", "dep:rodio"]

View File

@@ -186,7 +186,7 @@ impl CachedData {
}
/// Gets the cached data, if available.
/// If a thread is running to load the data, it is awaited.
/// This function doesn't block.
/// This function blocks!
pub fn cached_data_await(&self) -> Option<Arc<Vec<u8>>> {
let mut cd = self.0.lock().unwrap();
let (out, next) = match replace(&mut cd.0, Err(None)) {

View File

@@ -2,6 +2,8 @@
pub mod playback_rs;
#[cfg(feature = "playback-via-rodio")]
pub mod rodio;
#[cfg(feature = "playback-via-sleep")]
pub mod sleep;
#[cfg(feature = "playback-via-playback-rs")]
pub type PlayerBackendFeat<T> = playback_rs::PlayerBackendPlaybackRs<T>;
#[cfg(feature = "playback-via-rodio")]
@@ -10,7 +12,11 @@ pub type PlayerBackendFeat<T> = rodio::PlayerBackendRodio<T>;
use std::{collections::HashMap, ffi::OsStr, sync::Arc};
use crate::{
data::{database::Database, song::CachedData, SongId},
data::{
database::Database,
song::{CachedData, Song},
SongId,
},
server::Action,
};
@@ -28,6 +34,7 @@ pub trait PlayerBackend<T> {
fn load_next_song(
&mut self,
id: SongId,
song: &Song,
filename: &OsStr,
bytes: Arc<Vec<u8>>,
load_duration: bool,
@@ -158,6 +165,7 @@ impl<T: PlayerBackend<SongCustomData>> Player<T> {
let load_duration = song.duration_millis == 0;
self.backend.load_next_song(
id,
song,
song.location
.rel_path
.file_name()
@@ -211,6 +219,7 @@ impl<T: PlayerBackend<SongCustomData>> Player<T> {
let load_duration = song.duration_millis == 0;
self.backend.load_next_song(
id,
song,
song.location
.rel_path
.file_name()

View File

@@ -3,7 +3,7 @@ use std::{ffi::OsStr, io::Cursor, path::Path, sync::Arc, time::Duration};
use playback_rs::Hint;
use crate::{
data::SongId,
data::{song::Song, SongId},
server::{Action, Command},
};
@@ -41,6 +41,7 @@ impl<T> PlayerBackend<T> for PlayerBackendPlaybackRs<T> {
fn load_next_song(
&mut self,
id: SongId,
_song: &Song,
filename: &OsStr,
bytes: Arc<Vec<u8>>,
_load_duration: bool,

View File

@@ -4,7 +4,7 @@ use rc_u8_reader::ArcU8Reader;
use rodio::{decoder::DecoderError, Decoder, OutputStream, OutputStreamHandle, Sink, Source};
use crate::{
data::SongId,
data::{song::Song, SongId},
server::{Action, Command},
};
@@ -52,6 +52,7 @@ impl<T> PlayerBackend<T> for PlayerBackendRodio<T> {
fn load_next_song(
&mut self,
id: SongId,
_song: &Song,
_filename: &OsStr,
bytes: Arc<Vec<u8>>,
_load_duration: bool,

View File

@@ -0,0 +1,146 @@
use std::{
ffi::OsStr,
sync::Arc,
time::{Duration, Instant},
};
use crate::{
data::{song::Song, SongId},
server::Command,
};
use super::PlayerBackend;
pub struct PlayerBackendSleep<T> {
current: Option<(SongId, u64, T)>,
next: Option<(SongId, u64, T)>,
/// unused, but could be used to do something smarter than polling at some point
#[allow(unused)]
command_sender: Option<std::sync::mpsc::Sender<(Command, Option<u64>)>>,
finished: SongFinished,
}
#[derive(Debug)]
enum SongFinished {
Never,
In(Duration),
At(Instant),
}
impl<T> PlayerBackendSleep<T> {
pub fn new(
command_sender: Option<std::sync::mpsc::Sender<(Command, Option<u64>)>>,
) -> Result<Self, Box<dyn std::error::Error>> {
Ok(Self {
current: None,
next: None,
command_sender,
finished: SongFinished::Never,
})
}
fn set_finished(&mut self, play: bool) {
self.finished = if let Some((_, duration, _)) = &self.current {
let duration = Duration::from_millis(*duration);
if play {
SongFinished::At(Instant::now() + duration)
} else {
SongFinished::In(duration)
}
} else {
SongFinished::Never
};
}
}
impl<T> PlayerBackend<T> for PlayerBackendSleep<T> {
fn load_next_song(
&mut self,
id: SongId,
song: &Song,
_filename: &OsStr,
_bytes: Arc<Vec<u8>>,
_load_duration: bool,
custom_data: T,
) {
self.next = Some((id, song.duration_millis, custom_data));
}
fn pause(&mut self) {
match self.finished {
SongFinished::Never | SongFinished::In(_) => {}
SongFinished::At(time) => {
self.finished = SongFinished::In(time.saturating_duration_since(Instant::now()));
}
}
}
fn stop(&mut self) {
self.set_finished(false);
}
fn resume(&mut self) {
match self.finished {
SongFinished::Never | SongFinished::At(_) => {}
SongFinished::In(dur) => {
self.finished = SongFinished::At(Instant::now() + dur);
}
}
}
fn next(&mut self, play: bool, _load_duration: bool) {
self.current = self.next.take();
self.set_finished(play);
}
fn clear(&mut self) {
self.current = None;
self.next = None;
self.finished = SongFinished::Never;
}
fn playing(&self) -> bool {
match self.finished {
SongFinished::Never => false,
SongFinished::In(_) => false,
SongFinished::At(_) => true,
}
}
fn current_song(&self) -> Option<(SongId, bool, &T)> {
self.current
.as_ref()
.map(|(id, _, custom)| (*id, true, custom))
}
fn next_song(&self) -> Option<(SongId, bool, &T)> {
self.next
.as_ref()
.map(|(id, _, custom)| (*id, true, custom))
}
fn gen_data_mut(&mut self) -> (Option<&mut T>, Option<&mut T>) {
(
self.current.as_mut().map(|(_, _, t)| t),
self.next.as_mut().map(|(_, _, t)| t),
)
}
fn song_finished_polling(&self) -> bool {
true
}
fn song_finished(&self) -> bool {
match self.finished {
SongFinished::Never => true,
SongFinished::In(dur) => dur <= Duration::ZERO,
SongFinished::At(time) => time <= Instant::now(),
}
}
fn current_song_duration(&self) -> Option<u64> {
self.current
.as_ref()
.map(|(_, dur, _)| *dur)
.filter(|dur| *dur > 0)
}
fn current_song_playback_position(&self) -> Option<u64> {
if let Some(duration) = self.current_song_duration() {
match self.finished {
SongFinished::Never => None,
SongFinished::In(dur) => Some(duration.saturating_sub(dur.as_millis() as u64)),
SongFinished::At(time) => Some(duration.saturating_sub(
time.saturating_duration_since(Instant::now()).as_millis() as u64,
)),
}
} else {
None
}
}
}

View File

@@ -292,6 +292,8 @@ pub fn run_server_caching_thread_opt(
use crate::player::playback_rs::PlayerBackendPlaybackRs;
#[cfg(feature = "playback-via-rodio")]
use crate::player::rodio::PlayerBackendRodio;
#[cfg(feature = "playback-via-sleep")]
use crate::player::sleep::PlayerBackendSleep;
#[cfg(any(
feature = "playback",
feature = "playback-via-playback-rs",
@@ -305,6 +307,8 @@ pub fn run_server_caching_thread_opt(
#[cfg(feature = "playback")]
let mut player = if play_audio {
#[cfg(feature = "playback-via-sleep")]
let backend = PlayerBackendSleep::new(Some(command_sender.clone())).unwrap();
#[cfg(feature = "playback-via-playback-rs")]
let backend = PlayerBackendPlaybackRs::new(command_sender.clone()).unwrap();
#[cfg(feature = "playback-via-rodio")]