Skip to content

Commit

Permalink
✨ feat: singleton
Browse files Browse the repository at this point in the history
Signed-off-by: SimonShiki <[email protected]>
  • Loading branch information
SimonShiki committed Nov 30, 2024
1 parent 8f2f2b4 commit 2748266
Show file tree
Hide file tree
Showing 8 changed files with 240 additions and 110 deletions.
46 changes: 46 additions & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ walkdir = "2.5.0"
num_cpus = "1.16.0"
lofty = "0.21.0"
base64 = "0.22.1"

[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-single-instance = "2"
104 changes: 74 additions & 30 deletions src-tauri/src/audio.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::error::AppError;
use rodio::{Decoder, OutputStreamHandle, Sink, Source};
use std::fs::File;
use std::io::{BufReader, Cursor, Read, Seek, SeekFrom};
use std::sync::{Arc, Condvar, Mutex};
use std::time::Duration;
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use crate::error::AppError;
use std::sync::{Arc, Condvar, Mutex};
use std::time::Duration;
use tauri::{Emitter, State};

type Result<T> = std::result::Result<T, AppError>;
Expand All @@ -28,7 +28,12 @@ struct StreamingSource {
}

impl StreamingSource {
fn new(buffer: Arc<Mutex<Vec<u8>>>, is_stream_ended: Arc<AtomicBool>, decoder: Arc<Mutex<Option<Decoder<Cursor<Vec<u8>>>>>>, data_available: Arc<(Mutex<bool>, Condvar)>) -> Self {
fn new(
buffer: Arc<Mutex<Vec<u8>>>,
is_stream_ended: Arc<AtomicBool>,
decoder: Arc<Mutex<Option<Decoder<Cursor<Vec<u8>>>>>>,
data_available: Arc<(Mutex<bool>, Condvar)>,
) -> Self {
StreamingSource {
buffer,
is_stream_ended,
Expand All @@ -53,7 +58,9 @@ impl StreamingSource {
}; // The lock is released here

if target_sample >= total_samples {
return Err(rodio::source::SeekError::NotSupported { underlying_source: "streaming" });
return Err(rodio::source::SeekError::NotSupported {
underlying_source: "streaming",
});
}

self.position = target_sample * 4;
Expand All @@ -70,7 +77,8 @@ impl Read for StreamingSource {
if self.position < buffer.len() {
let remaining = buffer.len() - self.position;
let to_read = std::cmp::min(remaining, buf.len() - total_read);
buf[total_read..total_read + to_read].copy_from_slice(&buffer[self.position..self.position + to_read]);
buf[total_read..total_read + to_read]
.copy_from_slice(&buffer[self.position..self.position + to_read]);
self.position += to_read;
total_read += to_read;
} else if self.is_stream_ended.load(Ordering::Relaxed) {
Expand Down Expand Up @@ -99,7 +107,10 @@ impl Seek for StreamingSource {
};

if new_pos > (buffer_len as i64).try_into().unwrap() {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid seek position"));
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Invalid seek position",
));
}

self.position = new_pos as usize;
Expand All @@ -119,15 +130,15 @@ impl Iterator for StreamingSource {
return Some(sample);
}
}

drop(decoder);

let buffer = self.buffer.lock().unwrap();
if self.position < buffer.len() {
let new_buffer = buffer[self.position..].to_vec();
self.position = buffer.len();
drop(buffer);

let mut decoder = self.decoder.lock().unwrap();
*decoder = Decoder::new(Cursor::new(new_buffer)).ok();
} else if self.is_stream_ended.load(Ordering::Relaxed) {
Expand All @@ -148,21 +159,37 @@ impl Iterator for StreamingSource {

impl Source for StreamingSource {
fn current_frame_len(&self) -> Option<usize> {
self.decoder.lock().unwrap().as_ref().and_then(|d| d.current_frame_len())
self.decoder
.lock()
.unwrap()
.as_ref()
.and_then(|d| d.current_frame_len())
}

fn channels(&self) -> u16 {
self.decoder.lock().unwrap().as_ref().map(|d| d.channels()).unwrap_or(2)
self.decoder
.lock()
.unwrap()
.as_ref()
.map(|d| d.channels())
.unwrap_or(2)
}

fn sample_rate(&self) -> u32 {
self.decoder.lock().unwrap().as_ref().map(|d| d.sample_rate()).unwrap_or(44100)
self.decoder
.lock()
.unwrap()
.as_ref()
.map(|d| d.sample_rate())
.unwrap_or(44100)
}

fn total_duration(&self) -> Option<Duration> {
let buffer = self.buffer.lock().unwrap();
let total_samples = buffer.len() / 2 / self.channels() as usize;
Some(Duration::from_secs_f64(total_samples as f64 / self.sample_rate() as f64))
Some(Duration::from_secs_f64(
total_samples as f64 / self.sample_rate() as f64,
))
}

fn try_seek(&mut self, pos: Duration) -> std::result::Result<(), rodio::source::SeekError> {
Expand Down Expand Up @@ -208,10 +235,7 @@ pub async fn start_streaming(audio_state: State<'_, AudioState>) -> Result<()> {
}

#[tauri::command]
pub async fn add_stream_chunk(
audio_state: State<'_, AudioState>,
chunk: Vec<u8>,
) -> Result<()> {
pub async fn add_stream_chunk(audio_state: State<'_, AudioState>, chunk: Vec<u8>) -> Result<()> {
let mut buffer = audio_state.buffer.lock().unwrap();
buffer.extend_from_slice(&chunk);

Expand All @@ -235,7 +259,7 @@ pub async fn add_stream_chunk(
#[tauri::command]
pub async fn end_stream(audio_state: State<'_, AudioState>) -> Result<()> {
audio_state.is_stream_ended.store(true, Ordering::Relaxed);

// Signal that the stream has ended
let (lock, cvar) = &*audio_state.data_available;
let mut available = lock.lock().unwrap();
Expand Down Expand Up @@ -279,7 +303,11 @@ pub async fn play_local_file(audio_state: State<'_, AudioState>, file_path: Stri
}

#[tauri::command]
pub async fn play_arraybuffer(app: tauri::AppHandle, audio_state: State<'_, AudioState>, buffer: Vec<u8>) -> Result<()> {
pub async fn play_arraybuffer(
app: tauri::AppHandle,
audio_state: State<'_, AudioState>,
buffer: Vec<u8>,
) -> Result<()> {
let mut sink_guard = audio_state.sink.lock().unwrap();
if let Some(sink) = sink_guard.take() {
sink.stop();
Expand All @@ -296,8 +324,11 @@ pub async fn play_arraybuffer(app: tauri::AppHandle, audio_state: State<'_, Audi
.map_err(|e| AppError::SinkCreationError(e.to_string()))?;

// Report the actual duration
let _ = app.emit("update_duration", source.total_duration().unwrap().as_millis());

let _ = app.emit(
"update_duration",
source.total_duration().unwrap().as_millis(),
);

// Append the source to the sink
sink.append(source);

Expand All @@ -324,7 +355,9 @@ pub async fn pause(audio_state: State<'_, AudioState>) -> Result<()> {
s.pause();
Ok(())
} else {
Err(AppError::InvalidOperation("No active playback to pause".to_string()))
Err(AppError::InvalidOperation(
"No active playback to pause".to_string(),
))
}
}

Expand All @@ -335,7 +368,9 @@ pub async fn resume(audio_state: State<'_, AudioState>) -> Result<()> {
s.play();
Ok(())
} else {
Err(AppError::InvalidOperation("No active playback to play".to_string()))
Err(AppError::InvalidOperation(
"No active playback to play".to_string(),
))
}
}

Expand All @@ -346,7 +381,9 @@ pub async fn set_volume(audio_state: State<'_, AudioState>, volume: f32) -> Resu
s.set_volume(volume);
Ok(())
} else {
Err(AppError::InvalidOperation("No active playback to set volume".to_string()))
Err(AppError::InvalidOperation(
"No active playback to set volume".to_string(),
))
}
}

Expand All @@ -361,23 +398,28 @@ pub async fn get_volume(audio_state: State<'_, AudioState>) -> Result<f32> {
}

#[tauri::command]
pub async fn set_playback_progress(audio_state: State<'_, AudioState>, progress: f32) -> Result<()> {
pub async fn set_playback_progress(
audio_state: State<'_, AudioState>,
progress: f32,
) -> Result<()> {
let mut sink = audio_state.sink.lock().unwrap();

if let Some(s) = sink.as_mut() {
let duration = Duration::from_secs_f32(progress);
s.try_seek(duration)
.map_err(|e| AppError::SeekError(e.to_string()))?;
Ok(())
} else {
Err(AppError::InvalidOperation("No active playback to set progress".to_string()))
Err(AppError::InvalidOperation(
"No active playback to set progress".to_string(),
))
}
}

#[tauri::command]
pub async fn get_playback_progress(audio_state: State<'_, AudioState>) -> Result<f32> {
let sink = audio_state.sink.lock().unwrap();

if let Some(s) = &*sink {
Ok(s.get_pos().as_secs_f32())
} else {
Expand All @@ -392,6 +434,8 @@ pub async fn set_speed(audio_state: State<'_, AudioState>, speed: f32) -> Result
s.set_speed(speed);
Ok(())
} else {
Err(AppError::InvalidOperation("No active playback to set speed".to_string()))
Err(AppError::InvalidOperation(
"No active playback to set speed".to_string(),
))
}
}
4 changes: 3 additions & 1 deletion src-tauri/src/cache_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ impl CacheManager {
let item = CacheItem {
path: entry.path(),
size: metadata.len(),
last_accessed: metadata.modified().unwrap_or_else(|_| std::time::SystemTime::now()),
last_accessed: metadata
.modified()
.unwrap_or_else(|_| std::time::SystemTime::now()),
};

current_size += item.size;
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use thiserror::Error;
use serde::Serialize;
use thiserror::Error;

#[derive(Debug, Error, Serialize)]
pub(crate) enum AppError {
Expand Down
Loading

0 comments on commit 2748266

Please sign in to comment.