feat: add card pairing, manager toggle and confirmation sounds
This commit is contained in:
parent
c390975a8d
commit
56f03fdb25
148
src/service.rs
148
src/service.rs
@ -4,11 +4,15 @@ use std::{
|
|||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crossbeam_channel::Receiver;
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
use rodio::{Decoder, OutputStream, Sink};
|
use rodio::{Decoder, OutputStream, Sink};
|
||||||
|
use tracing::{debug, error, info};
|
||||||
use wifi_rs::{prelude::*, WiFi};
|
use wifi_rs::{prelude::*, WiFi};
|
||||||
|
|
||||||
use crate::{card::Card, error::Error, library::Library};
|
use crate::{card::Card, error::Error, library::Library, manager};
|
||||||
|
|
||||||
|
static SUCCESS_SOUND: &str = "sounds/positive_confirmation.wav";
|
||||||
|
static FAILURE_SOUND: &str = "sounds/negative_confirmation.wav";
|
||||||
|
|
||||||
fn toggle_hotspot(enable: bool) -> Result<(), Error> {
|
fn toggle_hotspot(enable: bool) -> Result<(), Error> {
|
||||||
let config = Config {
|
let config = Config {
|
||||||
@ -26,6 +30,42 @@ fn toggle_hotspot(enable: bool) -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn play_card(card: &Card, sink: &Sink) {
|
||||||
|
match card {
|
||||||
|
Card::Play(music_file) => {
|
||||||
|
play_sound(sink, music_file);
|
||||||
|
}
|
||||||
|
Card::Pause => sink.pause(),
|
||||||
|
Card::Resume => sink.play(),
|
||||||
|
Card::Next | Card::Previous => sink.stop(),
|
||||||
|
Card::ToggleHotspot | Card::Shuffle => {}
|
||||||
|
|
||||||
|
// TODO: Volume management. Currently the volume is set independetly from the OS which leads to a horrible quality decrease.
|
||||||
|
Card::VolumeUp => sink.set_volume(sink.volume() + 1.0),
|
||||||
|
Card::VolumeDown => sink.set_volume(sink.volume() - 1.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn play_sound(sink: &Sink, file_path: &str) {
|
||||||
|
let file = match File::open(file_path) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to open file {file_path}: {err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let file = BufReader::new(file);
|
||||||
|
let source = match Decoder::new(file) {
|
||||||
|
Ok(source) => source,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to decode file {file_path}: {err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
sink.stop();
|
||||||
|
sink.append(source);
|
||||||
|
}
|
||||||
|
|
||||||
/// Runs the service.
|
/// Runs the service.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@ -36,57 +76,111 @@ fn toggle_hotspot(enable: bool) -> Result<(), Error> {
|
|||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns an error if there is an issue running the service.
|
/// Returns an error if there is an issue running the service.
|
||||||
pub fn run(rx: &Receiver<Arc<str>>, library: &Arc<Mutex<Library>>) -> Result<(), Error> {
|
pub fn run(
|
||||||
|
library: &Arc<Mutex<Library>>,
|
||||||
|
tx_manager_shutdown: Sender<()>,
|
||||||
|
rx_manager_shutdown: Receiver<()>,
|
||||||
|
rx: &Receiver<Arc<str>>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let tx_manager_shutdown = Arc::from(tx_manager_shutdown);
|
||||||
|
let rx_manager_shutdown = Arc::from(rx_manager_shutdown);
|
||||||
|
|
||||||
let (_stream, stream_handle) = OutputStream::try_default().map_err(Error::from)?;
|
let (_stream, stream_handle) = OutputStream::try_default().map_err(Error::from)?;
|
||||||
let sink = Sink::try_new(&stream_handle)?;
|
let sink = Sink::try_new(&stream_handle)?;
|
||||||
|
|
||||||
|
let (tx_pairing, rx_pairing): (Sender<()>, Receiver<()>) = crossbeam_channel::bounded(1);
|
||||||
|
let tx_pairing = Arc::from(tx_pairing);
|
||||||
|
let mut pairing_cards: Vec<Arc<str>> = vec![];
|
||||||
|
|
||||||
let mut hotspot_enabled = false;
|
let mut hotspot_enabled = false;
|
||||||
|
let mut is_pairing = false;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match rx.recv() {
|
match rx_pairing.try_recv() {
|
||||||
|
Ok(()) => {
|
||||||
|
is_pairing = !is_pairing;
|
||||||
|
if is_pairing {
|
||||||
|
info!("Pairing mode enabled");
|
||||||
|
pairing_cards.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => match e {
|
||||||
|
crossbeam_channel::TryRecvError::Empty => {}
|
||||||
|
crossbeam_channel::TryRecvError::Disconnected => unreachable!(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
match rx.try_recv() {
|
||||||
Ok(card_id) => {
|
Ok(card_id) => {
|
||||||
println!("Card ID: {card_id}");
|
debug!("Card ID: {card_id}");
|
||||||
|
|
||||||
let library_lock = library.lock()?;
|
let mut library_lock = library.lock()?;
|
||||||
let card = library_lock.get(&card_id);
|
let card = library_lock.get(&card_id);
|
||||||
|
|
||||||
if let Some(music_file) = card {
|
if let Some(music_file) = card {
|
||||||
println!("Playing: {music_file:?}");
|
info!("Playing: {music_file:?}");
|
||||||
|
|
||||||
match music_file {
|
match music_file {
|
||||||
Card::Play(music_file) => {
|
|
||||||
sink.stop();
|
|
||||||
let file = BufReader::new(File::open(music_file.as_ref())?);
|
|
||||||
let source = Decoder::new(file)?;
|
|
||||||
sink.append(source);
|
|
||||||
}
|
|
||||||
Card::Pause => sink.pause(),
|
|
||||||
Card::Resume => sink.play(),
|
|
||||||
Card::Next | Card::Previous => sink.stop(),
|
|
||||||
Card::Shuffle => {
|
Card::Shuffle => {
|
||||||
let card = library_lock.get_random();
|
let card = library_lock.get_random();
|
||||||
if let Some(Card::Play(music_file)) = card {
|
if let Some(card) = card {
|
||||||
sink.stop();
|
play_card(card, &sink);
|
||||||
let file = BufReader::new(File::open(music_file.as_ref())?);
|
|
||||||
let source = Decoder::new(file)?;
|
|
||||||
sink.append(source);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Card::ToggleHotspot => {
|
Card::ToggleHotspot => {
|
||||||
toggle_hotspot(!hotspot_enabled)?;
|
let msg = if hotspot_enabled { "disable" } else { "enable" };
|
||||||
|
match toggle_hotspot(!hotspot_enabled) {
|
||||||
|
Ok(()) => info!("hotspot {msg}d"),
|
||||||
|
Err(err) => error!("Failed to {msg} hotspot: {err}"),
|
||||||
|
}
|
||||||
|
if hotspot_enabled {
|
||||||
|
match tx_manager_shutdown.send(()) {
|
||||||
|
Ok(()) => info!("Sent shutdown message"),
|
||||||
|
Err(err) => error!("Failed to send shutdown message: {err}"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let tx_pairing_clone = tx_pairing.clone();
|
||||||
|
let rx_manager_shutdown_clone = rx_manager_shutdown.clone();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
match manager::serve(
|
||||||
|
"0.0.0.0:8080",
|
||||||
|
tx_pairing_clone,
|
||||||
|
rx_manager_shutdown_clone,
|
||||||
|
) {
|
||||||
|
Ok(()) => info!("Manager started"),
|
||||||
|
Err(err) => error!("Failed to start manager: {err}"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
hotspot_enabled = !hotspot_enabled;
|
hotspot_enabled = !hotspot_enabled;
|
||||||
}
|
}
|
||||||
// TODO: Volume management. Currently the volume is set independetly from the OS which leads to a horrible quality decrease.
|
card => play_card(card, &sink),
|
||||||
Card::VolumeUp => sink.set_volume(sink.volume() + 1.0),
|
}
|
||||||
Card::VolumeDown => sink.set_volume(sink.volume() - 1.0),
|
} else if is_pairing {
|
||||||
|
info!("Read card: {card_id}");
|
||||||
|
pairing_cards.push(card_id);
|
||||||
|
if let Some(most_common_card) = pairing_cards
|
||||||
|
.iter()
|
||||||
|
.find(|&card| pairing_cards.iter().filter(|&c| *c == *card).count() >= 3)
|
||||||
|
{
|
||||||
|
library_lock.add(most_common_card);
|
||||||
|
if let Err(err) = library_lock.save_to_file("music.json") {
|
||||||
|
library_lock.remove(most_common_card);
|
||||||
|
error!("Failed to save library: {err}");
|
||||||
|
play_sound(&sink, FAILURE_SOUND);
|
||||||
|
} else {
|
||||||
|
play_sound(&sink, SUCCESS_SOUND);
|
||||||
|
info!("Added card to library: {most_common_card}");
|
||||||
|
}
|
||||||
|
is_pairing = !is_pairing;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("No music file found for this card");
|
info!("Unknown card");
|
||||||
}
|
}
|
||||||
drop(library_lock);
|
drop(library_lock);
|
||||||
}
|
}
|
||||||
Err(e) => match e {
|
Err(e) => match e {
|
||||||
crossbeam_channel::RecvError => break,
|
crossbeam_channel::TryRecvError::Empty => continue,
|
||||||
|
crossbeam_channel::TryRecvError::Disconnected => break,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user