| 1 | use super::{ |
| 2 | constants::{base_url_builder, PATH_AUDIO}, |
| 3 | download_result_files::save_result_files, |
| 4 | }; |
| 5 | use crate::{ |
| 6 | command::{ |
| 7 | download_result_files::{download_result_files, get_provider_id_dir_path}, |
| 8 | player_set_volume::Mode, |
| 9 | }, get_soundcard, models::player::{AudioFile, AudioFileResponse, PreparePlayerResponse, TauriResponse}, players::{BASS_SINK, DRUMS_SINK, OTHER_SINK, VOCALS_SINK}, record::record_instrument::AudioDevice |
| 10 | }; |
| 11 | use log::{debug, error, info}; |
| 12 | use reqwest::Client; |
| 13 | use rodio::{Decoder, DeviceTrait, OutputStream, Sink}; |
| 14 | use std::{ |
| 15 | fs::{self, DirEntry, File}, |
| 16 | io::BufReader, |
| 17 | path::{Path, PathBuf}, |
| 18 | }; |
| 19 | use tauri::async_runtime::spawn; |
| 20 | |
| 21 | #[tauri::command] |
| 22 | pub async fn player_prepare(audio_file_id: String) -> PreparePlayerResponse { |
| 23 | debug!("Preparing player for provider_id {}.", audio_file_id); |
| 24 | |
| 25 | // Get audio file response |
| 26 | let response = Client::new() |
| 27 | .get(build_audio_detail_url(audio_file_id)) |
| 28 | .send() |
| 29 | .await; |
| 30 | |
| 31 | match response { |
| 32 | Ok(response) => { |
| 33 | debug!("Got response: {:?}", response); |
| 34 | let res: AudioFileResponse = match response.json().await { |
| 35 | Ok(audio_file) => audio_file, |
| 36 | Err(e) => { |
| 37 | error!("Failed to parse response: {:?}", e); |
| 38 | return PreparePlayerResponse { |
| 39 | status: TauriResponse::Error, |
| 40 | message: e.to_string(), |
| 41 | audio_file_name: None, |
| 42 | }; |
| 43 | } |
| 44 | }; |
| 45 | |
| 46 | prepare_result_files(res.audio_file).await |
| 47 | } |
| 48 | Err(e) => { |
| 49 | error!("Failed to get response: {:?}", e); |
| 50 | PreparePlayerResponse { |
| 51 | status: TauriResponse::Error, |
| 52 | message: e.to_string(), |
| 53 | audio_file_name: None, |
| 54 | } |
| 55 | } |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | async fn prepare_result_files(audio_file: AudioFile) -> PreparePlayerResponse { |
| 60 | debug!("Preparing result files: {:?}", audio_file); |
| 61 | match audio_file.results { |
| 62 | Some(results) => { |
| 63 | if results.is_empty() { |
| 64 | return PreparePlayerResponse { |
| 65 | status: TauriResponse::Error, |
| 66 | message: "No results found.".to_string(), |
| 67 | audio_file_name: Some(audio_file.name), |
| 68 | }; |
| 69 | } |
| 70 | save_result_files(&audio_file.id.to_string(), &results).await; |
| 71 | download_result_files(audio_file.id.to_string(), results).await; |
| 72 | prepare_players(audio_file.id.to_string()).await; |
| 73 | PreparePlayerResponse { |
| 74 | status: TauriResponse::Success, |
| 75 | message: "Success".to_string(), |
| 76 | audio_file_name: Some(audio_file.name), |
| 77 | } |
| 78 | } |
| 79 | None => { |
| 80 | return PreparePlayerResponse { |
| 81 | status: TauriResponse::Error, |
| 82 | message: "No results found.".to_string(), |
| 83 | audio_file_name: Some(audio_file.name), |
| 84 | }; |
| 85 | } |
| 86 | |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | fn build_audio_detail_url(id: String) -> String { |
| 91 | let mut url_builder = base_url_builder(); |
| 92 | url_builder.add_route(PATH_AUDIO); |
| 93 | url_builder.add_route(&id); |
| 94 | url_builder.build() |
| 95 | } |
| 96 | |
| 97 | pub async fn prepare_players(audio_id: String) { |
| 98 | info!("Preparing player audio files: {}", audio_id); |
| 99 | |
| 100 | // Check if we have the ~/.sfai/audio/{audio_id} folder |
| 101 | let audio_dir_path: PathBuf = match get_provider_id_dir_path(&audio_id).await { |
| 102 | Ok(path) => path, |
| 103 | Err(e) => { |
| 104 | info!("Failed to get audio directory: {:?}", e); |
| 105 | return; |
| 106 | } |
| 107 | }; |
| 108 | |
| 109 | // get the files in the directory |
| 110 | let dir_entries = if let Ok(paths) = fs::read_dir(audio_dir_path) { |
| 111 | paths |
| 112 | } else { |
| 113 | info!("Failed to read audio directory."); |
| 114 | return; |
| 115 | }; |
| 116 | |
| 117 | let get_path = |dir_entry: Result<DirEntry, std::io::Error>| -> Option<PathBuf> { |
| 118 | debug!("Getting path to audio file."); |
| 119 | let dir_entry = match dir_entry { |
| 120 | Ok(dir_entry) => dir_entry, |
| 121 | Err(e) => { |
| 122 | info!("Failed to get dir entry: {:?}", e); |
| 123 | return None; |
| 124 | } |
| 125 | }; |
| 126 | Some(dir_entry.path()) |
| 127 | }; |
| 128 | |
| 129 | let sink = |path: &Path| -> Option<(Mode, Sink)> { |
| 130 | debug!("Playing: {:?}", path); |
| 131 | let device = if let Ok(device) = get_soundcard(AudioDevice::Output, true) { |
| 132 | device |
| 133 | } else { |
| 134 | info!("Failed to get soundcard output."); |
| 135 | return None; |
| 136 | }; |
| 137 | |
| 138 | if let Ok(name) = device.name() { |
| 139 | info!("Output device is: {}", name); |
| 140 | } |
| 141 | |
| 142 | let (_stream, handle) = if let Ok(x) = OutputStream::try_from_device(&device) { |
| 143 | x |
| 144 | } else { |
| 145 | info!("Failed to get output stream."); |
| 146 | return None; |
| 147 | }; |
| 148 | |
| 149 | let sink = if let Ok(z) = Sink::try_new(&handle) { |
| 150 | z |
| 151 | } else { |
| 152 | info!("Failed to get sink."); |
| 153 | return None; |
| 154 | }; |
| 155 | |
| 156 | let file = if let Ok(f) = File::open(path) { |
| 157 | f |
| 158 | } else { |
| 159 | info!("Failed to open file."); |
| 160 | return None; |
| 161 | }; |
| 162 | |
| 163 | match Decoder::new(BufReader::new(file)) { |
| 164 | Ok(decoder) => { |
| 165 | sink.append(decoder); |
| 166 | sink.pause(); |
| 167 | Some((get_mode(path), sink)) |
| 168 | } |
| 169 | Err(e) => { |
| 170 | info!("Failed to create decoder: {:?}", e); |
| 171 | None |
| 172 | } |
| 173 | } |
| 174 | }; |
| 175 | |
| 176 | let file_paths = dir_entries |
| 177 | .into_iter() |
| 178 | .flat_map(get_path) |
| 179 | .collect::<Vec<_>>(); |
| 180 | |
| 181 | file_paths |
| 182 | .into_iter() |
| 183 | .filter_map(|x| { |
| 184 | // Only play .wav |
| 185 | if let Some("wav") = x.extension().and_then(|x| x.to_str()) { |
| 186 | Some(x) |
| 187 | } else { |
| 188 | None |
| 189 | } |
| 190 | }) |
| 191 | .map(|x| { |
| 192 | spawn(async move { |
| 193 | let sink = sink(&x); |
| 194 | persist_sinks(sink) |
| 195 | }) |
| 196 | }) |
| 197 | .for_each(drop); |
| 198 | } |
| 199 | |
| 200 | fn persist_sinks(sink: Option<(Mode, Sink)>) { |
| 201 | // Return early if we don't have a sink |
| 202 | let (mode, sink) = match sink { |
| 203 | Some(x) => x, |
| 204 | None => return, |
| 205 | }; |
| 206 | match mode { |
| 207 | Mode::Vocals => { |
| 208 | info!("Persisting vocals sink."); |
| 209 | let _ = unsafe { VOCALS_SINK.set(sink) }; |
| 210 | } |
| 211 | Mode::Bass => { |
| 212 | info!("Persisting bass sink."); |
| 213 | let _ = unsafe { BASS_SINK.set(sink) }; |
| 214 | } |
| 215 | Mode::Other => { |
| 216 | info!("Persisting other sink."); |
| 217 | let _ = unsafe { OTHER_SINK.set(sink) }; |
| 218 | } |
| 219 | Mode::Drums => { |
| 220 | info!("Persisting drums sink."); |
| 221 | let _ = unsafe { DRUMS_SINK.set(sink) }; |
| 222 | } |
| 223 | Mode::Unknown => { |
| 224 | error!("Got an unkown mode."); |
| 225 | panic!("Should not be here.") |
| 226 | } |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | fn get_mode(path: &Path) -> Mode { |
| 231 | if let Some(file_name) = path.file_name() { |
| 232 | if let Some(file_name) = file_name.to_str() { |
| 233 | return get_mode_from_filename(file_name); |
| 234 | } else { |
| 235 | return Mode::Unknown; |
| 236 | } |
| 237 | } |
| 238 | Mode::Unknown |
| 239 | } |
| 240 | |
| 241 | pub fn get_mode_from_filename(filename: &str) -> Mode { |
| 242 | if filename.contains("vocal") { |
| 243 | Mode::Vocals |
| 244 | } else if filename.contains("drums") { |
| 245 | Mode::Drums |
| 246 | } else if filename.contains("bass") { |
| 247 | Mode::Bass |
| 248 | } else if filename.contains("other") { |
| 249 | Mode::Other |
| 250 | } else { |
| 251 | Mode::Unknown |
| 252 | } |
| 253 | } |