minor cleanup

This commit is contained in:
Evan Cameron 2021-02-28 21:37:22 -05:00
parent 9d77fef008
commit 6a33eb4efa
No known key found for this signature in database
GPG key ID: 6878FBDAB459783C
27 changed files with 172 additions and 212 deletions

View file

@ -138,19 +138,19 @@ impl StreamLoaderController {
})
}
fn send_stream_loader_command(&mut self, command: StreamLoaderCommand) {
if let Some(ref mut channel) = self.channel_tx {
fn send_stream_loader_command(&self, command: StreamLoaderCommand) {
if let Some(ref channel) = self.channel_tx {
// ignore the error in case the channel has been closed already.
let _ = channel.send(command);
}
}
pub fn fetch(&mut self, range: Range) {
pub fn fetch(&self, range: Range) {
// signal the stream loader to fetch a range of the file
self.send_stream_loader_command(StreamLoaderCommand::Fetch(range));
}
pub fn fetch_blocking(&mut self, mut range: Range) {
pub fn fetch_blocking(&self, mut range: Range) {
// signal the stream loader to tech a range of the file and block until it is loaded.
// ensure the range is within the file's bounds.
@ -182,47 +182,43 @@ impl StreamLoaderController {
{
// For some reason, the requested range is neither downloaded nor requested.
// This could be due to a network error. Request it again.
// We can't use self.fetch here because self can't be borrowed mutably, so we access the channel directly.
if let Some(ref mut channel) = self.channel_tx {
// ignore the error in case the channel has been closed already.
let _ = channel.send(StreamLoaderCommand::Fetch(range));
}
self.fetch(range);
}
}
}
}
pub fn fetch_next(&mut self, length: usize) {
pub fn fetch_next(&self, length: usize) {
if let Some(ref shared) = self.stream_shared {
let range = Range {
start: shared.read_position.load(atomic::Ordering::Relaxed),
length: length,
length,
};
self.fetch(range)
}
}
pub fn fetch_next_blocking(&mut self, length: usize) {
pub fn fetch_next_blocking(&self, length: usize) {
if let Some(ref shared) = self.stream_shared {
let range = Range {
start: shared.read_position.load(atomic::Ordering::Relaxed),
length: length,
length,
};
self.fetch_blocking(range);
}
}
pub fn set_random_access_mode(&mut self) {
pub fn set_random_access_mode(&self) {
// optimise download strategy for random access
self.send_stream_loader_command(StreamLoaderCommand::RandomAccessMode());
}
pub fn set_stream_mode(&mut self) {
pub fn set_stream_mode(&self) {
// optimise download strategy for streaming
self.send_stream_loader_command(StreamLoaderCommand::StreamMode());
}
pub fn close(&mut self) {
pub fn close(&self) {
// terminate stream loading and don't load any more data for this file.
self.send_stream_loader_command(StreamLoaderCommand::Close());
}
@ -230,11 +226,8 @@ impl StreamLoaderController {
pub struct AudioFileStreaming {
read_file: fs::File,
position: u64,
stream_loader_command_tx: mpsc::UnboundedSender<StreamLoaderCommand>,
shared: Arc<AudioFileShared>,
}
@ -332,10 +325,7 @@ impl AudioFile {
}
pub fn is_cached(&self) -> bool {
match self {
AudioFile::Cached { .. } => true,
_ => false,
}
matches!(self, AudioFile::Cached { .. })
}
}
@ -359,7 +349,7 @@ impl AudioFileStreaming {
let size = BigEndian::read_u32(&data) as usize * 4;
let shared = Arc::new(AudioFileShared {
file_id: file_id,
file_id,
file_size: size,
stream_data_rate: streaming_data_rate,
cond: Condvar::new(),
@ -396,11 +386,10 @@ impl AudioFileStreaming {
session.spawn(fetcher);
Ok(AudioFileStreaming {
read_file: read_file,
read_file,
position: 0,
//seek: seek_tx,
stream_loader_command_tx: stream_loader_command_tx,
shared: shared,
stream_loader_command_tx,
shared,
})
}
}
@ -486,7 +475,7 @@ async fn audio_file_fetch_receive_data(
let data_size = data.len();
let _ = file_data_tx.send(ReceivedData::Data(PartialFileData {
offset: data_offset,
data: data,
data,
}));
data_offset += data_size;
if request_length < data_size {
@ -728,14 +717,12 @@ impl AudioFileFetch {
));
AudioFileFetch {
session: session,
shared: shared,
session,
shared,
output: Some(output),
file_data_tx: file_data_tx,
file_data_rx: file_data_rx,
stream_loader_command_rx: stream_loader_command_rx,
file_data_tx,
file_data_rx,
stream_loader_command_rx,
complete_tx: Some(complete_tx),
network_response_times_ms: Vec::new(),
}

View file

@ -1,6 +1,7 @@
use super::{AudioDecoder, AudioError, AudioPacket};
use lewton::inside_ogg::OggStreamReader;
use super::{AudioDecoder, AudioError, AudioPacket};
use std::error;
use std::fmt;
use std::io::{Read, Seek};
@ -24,16 +25,15 @@ where
fn seek(&mut self, ms: i64) -> Result<(), AudioError> {
let absgp = ms * 44100 / 1000;
match self.0.seek_absgp_pg(absgp as u64) {
Ok(_) => return Ok(()),
Err(err) => return Err(AudioError::VorbisError(err.into())),
Ok(_) => Ok(()),
Err(err) => Err(AudioError::VorbisError(err.into())),
}
}
fn next_packet(&mut self) -> Result<Option<AudioPacket>, AudioError> {
use lewton::audio::AudioReadError::AudioIsHeader;
use lewton::OggReadError::NoCapturePatternFound;
use lewton::VorbisError::BadAudio;
use lewton::VorbisError::OggError;
use lewton::VorbisError::{BadAudio, OggError};
loop {
match self.0.read_dec_packet_itl() {
Ok(Some(packet)) => return Ok(Some(AudioPacket::Samples(packet))),

View file

@ -1,4 +1,4 @@
#![allow(clippy::unused_io_amount)]
#![allow(clippy::unused_io_amount, clippy::too_many_arguments)]
#[macro_use]
extern crate log;
@ -85,13 +85,13 @@ impl fmt::Display for AudioError {
impl From<VorbisError> for AudioError {
fn from(err: VorbisError) -> AudioError {
AudioError::VorbisError(VorbisError::from(err))
AudioError::VorbisError(err)
}
}
impl From<PassthroughError> for AudioError {
fn from(err: PassthroughError) -> AudioError {
AudioError::PassthroughError(PassthroughError::from(err))
AudioError::PassthroughError(err)
}
}

View file

@ -27,7 +27,7 @@ fn write_headers<T: Read + Seek>(
// remove un-needed packets
rdr.delete_unread_packets();
return Ok(stream_serial);
Ok(stream_serial)
}
fn get_header<T>(
@ -65,7 +65,7 @@ where
)
.unwrap();
return Ok(*stream_serial);
Ok(*stream_serial)
}
pub struct PassthroughDecoder<R: Read + Seek> {
@ -87,13 +87,13 @@ impl<R: Read + Seek> PassthroughDecoder<R> {
let stream_serial = write_headers(&mut rdr, &mut wtr)?;
info!("Starting passthrough track with serial {}", stream_serial);
return Ok(PassthroughDecoder {
Ok(PassthroughDecoder {
rdr,
wtr,
lastgp_page: Some(0),
absgp_page: 0,
stream_serial,
});
})
}
}
@ -107,8 +107,8 @@ impl<R: Read + Seek> AudioDecoder for PassthroughDecoder<R> {
// hard-coded to 44.1 kHz
match self.rdr.seek_absgp(None, (ms * 44100 / 1000) as u64) {
Ok(_) => return Ok(()),
Err(err) => return Err(AudioError::PassthroughError(err.into())),
Ok(_) => Ok(()),
Err(err) => Err(AudioError::PassthroughError(err.into())),
}
}
@ -164,7 +164,7 @@ impl<R: Read + Seek> AudioDecoder for PassthroughDecoder<R> {
let data = self.wtr.inner_mut();
if data.len() > 0 {
if !data.is_empty() {
let result = AudioPacket::OggData(std::mem::take(data));
return Ok(Some(result));
}

View file

@ -16,14 +16,11 @@ impl fmt::Display for Range {
impl Range {
pub fn new(start: usize, length: usize) -> Range {
return Range {
start: start,
length: length,
};
Range { start, length }
}
pub fn end(&self) -> usize {
return self.start + self.length;
self.start + self.length
}
}
@ -50,7 +47,7 @@ impl RangeSet {
}
pub fn is_empty(&self) -> bool {
return self.ranges.is_empty();
self.ranges.is_empty()
}
pub fn len(&self) -> usize {
@ -58,11 +55,11 @@ impl RangeSet {
}
pub fn get_range(&self, index: usize) -> Range {
return self.ranges[index].clone();
self.ranges[index]
}
pub fn iter(&self) -> Iter<Range> {
return self.ranges.iter();
self.ranges.iter()
}
pub fn contains(&self, value: usize) -> bool {
@ -73,7 +70,7 @@ impl RangeSet {
return true;
}
}
return false;
false
}
pub fn contained_length_from_value(&self, value: usize) -> usize {
@ -84,7 +81,7 @@ impl RangeSet {
return range.end() - value;
}
}
return 0;
0
}
#[allow(dead_code)]
@ -144,7 +141,7 @@ impl RangeSet {
pub fn union(&self, other: &RangeSet) -> RangeSet {
let mut result = self.clone();
result.add_range_set(other);
return result;
result
}
pub fn subtract_range(&mut self, range: &Range) {
@ -204,7 +201,7 @@ impl RangeSet {
pub fn minus(&self, other: &RangeSet) -> RangeSet {
let mut result = self.clone();
result.subtract_range_set(other);
return result;
result
}
pub fn intersection(&self, other: &RangeSet) -> RangeSet {
@ -240,6 +237,6 @@ impl RangeSet {
}
}
return result;
result
}
}

View file

@ -5,34 +5,31 @@ use futures_core::Stream;
use hmac::{Hmac, Mac, NewMac};
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Method, Request, Response, StatusCode};
use num_bigint::BigUint;
use serde_json::json;
use sha1::{Digest, Sha1};
use tokio::sync::{mpsc, oneshot};
use std::borrow::Cow;
use std::convert::Infallible;
use std::net::{Ipv4Addr, SocketAddr};
use std::task::{Context, Poll};
#[cfg(feature = "with-dns-sd")]
use dns_sd::DNSService;
#[cfg(not(feature = "with-dns-sd"))]
use libmdns;
use num_bigint::BigUint;
use rand;
use std::collections::BTreeMap;
use std::io;
use std::pin::Pin;
use std::sync::Arc;
use url;
use librespot_core::authentication::Credentials;
use librespot_core::config::ConnectConfig;
use librespot_core::diffie_hellman::{DH_GENERATOR, DH_PRIME};
use librespot_core::util;
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::convert::Infallible;
use std::io;
use std::net::{Ipv4Addr, SocketAddr};
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
type HmacSha1 = Hmac<Sha1>;
#[derive(Clone)]

View file

@ -1,10 +1,10 @@
const AP_FALLBACK: &'static str = "ap.spotify.com:443";
const AP_FALLBACK: &str = "ap.spotify.com:443";
use url::Url;
cfg_if! {
if #[cfg(feature = "apresolve")] {
const APRESOLVE_ENDPOINT: &'static str = "http://apresolve.spotify.com:80";
const APRESOLVE_ENDPOINT: &str = "http://apresolve.spotify.com:80";
use std::error::Error;

View file

@ -27,7 +27,7 @@ pub struct Credentials {
impl Credentials {
pub fn with_password(username: String, password: String) -> Credentials {
Credentials {
username: username,
username,
auth_type: AuthenticationType::AUTHENTICATION_USER_PASS,
auth_data: password.into_bytes(),
}
@ -103,9 +103,9 @@ impl Credentials {
let auth_data = read_bytes(&mut cursor).unwrap();
Credentials {
username: username,
auth_type: auth_type,
auth_data: auth_data,
username,
auth_type,
auth_data,
}
}
}

View file

@ -93,7 +93,7 @@ impl ChannelManager {
}
pub fn get_download_rate_estimate(&self) -> usize {
return self.lock(|inner| inner.download_rate_estimate);
self.lock(|inner| inner.download_rate_estimate)
}
pub(crate) fn shutdown(&self) {
@ -139,7 +139,7 @@ impl Stream for Channel {
match self.state.clone() {
ChannelState::Closed => panic!("Polling already terminated channel"),
ChannelState::Header(mut data) => {
if data.len() == 0 {
if data.is_empty() {
data = match self.recv_packet(cx) {
Poll::Ready(Ok(x)) => x,
Poll::Ready(Err(x)) => return Poll::Ready(Some(Err(x))),
@ -168,7 +168,7 @@ impl Stream for Channel {
Poll::Ready(Err(x)) => return Poll::Ready(Some(Err(x))),
Poll::Pending => return Poll::Pending,
};
if data.len() == 0 {
if data.is_empty() {
self.receiver.close();
self.state = ChannelState::Closed;
return Poll::Ready(None);

View file

@ -30,8 +30,8 @@ impl DHLocalKeys {
let public_key = util::powm(&DH_GENERATOR, &private_key, &DH_PRIME);
DHLocalKeys {
private_key: private_key,
public_key: public_key,
private_key,
public_key,
}
}

View file

@ -211,8 +211,7 @@ impl MercuryManager {
if let Some(cb) = pending.callback {
let _ = cb.send(Err(MercuryError));
}
} else {
if cmd == 0xb5 {
} else if cmd == 0xb5 {
self.lock(|inner| {
let mut found = false;
inner.subscriptions.retain(|&(ref prefix, ref sub)| {
@ -236,7 +235,6 @@ impl MercuryManager {
let _ = cb.send(Ok(response));
}
}
}
pub(crate) fn shutdown(&self) {
self.lock(|inner| {

View file

@ -12,8 +12,8 @@ impl MercurySender {
// TODO: pub(super) when stable
pub(crate) fn new(mercury: MercuryManager, uri: String) -> MercurySender {
MercurySender {
mercury: mercury,
uri: uri,
mercury,
uri,
pending: VecDeque::new(),
}
}

View file

@ -105,25 +105,20 @@ impl Session {
debug!("new Session[{}]", session_id);
let session = Session(Arc::new(SessionInternal {
config: config,
config,
data: RwLock::new(SessionData {
country: String::new(),
canonical_username: username,
invalid: false,
time_delta: 0,
}),
tx_connection: sender_tx,
cache: cache.map(Arc::new),
audio_key: OnceCell::new(),
channel: OnceCell::new(),
mercury: OnceCell::new(),
handle,
session_id: session_id,
session_id,
}));
let sender_task = UnboundedReceiverStream::new(sender_rx)

View file

@ -45,7 +45,7 @@ impl SpotifyId {
const SIZE_BASE16: usize = 32;
const SIZE_BASE62: usize = 22;
fn as_track(n: u128) -> SpotifyId {
fn track(n: u128) -> SpotifyId {
SpotifyId {
id: n,
audio_type: SpotifyAudioType::Track,
@ -71,7 +71,7 @@ impl SpotifyId {
dst += p;
}
Ok(SpotifyId::as_track(dst))
Ok(SpotifyId::track(dst))
}
/// Parses a base62 encoded [Spotify ID] into a `SpotifyId`.
@ -94,7 +94,7 @@ impl SpotifyId {
dst += p;
}
Ok(SpotifyId::as_track(dst))
Ok(SpotifyId::track(dst))
}
/// Creates a `SpotifyId` from a copy of `SpotifyId::SIZE` (16) bytes in big-endian order.
@ -102,7 +102,7 @@ impl SpotifyId {
/// The resulting `SpotifyId` will default to a `SpotifyAudioType::TRACK`.
pub fn from_raw(src: &[u8]) -> Result<SpotifyId, SpotifyIdError> {
match src.try_into() {
Ok(dst) => Ok(SpotifyId::as_track(u128::from_be_bytes(dst))),
Ok(dst) => Ok(SpotifyId::track(u128::from_be_bytes(dst))),
Err(_) => Err(SpotifyIdError),
}
}

View file

@ -1,33 +1,34 @@
use librespot_core::*;
#[cfg(test)]
mod tests {
use super::*;
// Test AP Resolve
use apresolve::apresolve_or_fallback;
#[tokio::test]
async fn test_ap_resolve() {
env_logger::init();
let ap = apresolve_or_fallback(&None, &None).await;
println!("AP: {:?}", ap);
}
// TODO: test is broken
// #[cfg(test)]
// mod tests {
// use super::*;
// // Test AP Resolve
// use apresolve::apresolve_or_fallback;
// #[tokio::test]
// async fn test_ap_resolve() {
// env_logger::init();
// let ap = apresolve_or_fallback(&None, &None).await;
// println!("AP: {:?}", ap);
// }
// Test connect
use authentication::Credentials;
use config::SessionConfig;
#[tokio::test]
async fn test_connection() -> Result<(), Box<dyn std::error::Error>> {
println!("Running connection test");
let ap = apresolve_or_fallback(&None, &None).await;
let credentials = Credentials::with_password(String::from("test"), String::from("test"));
let session_config = SessionConfig::default();
let proxy = None;
// // Test connect
// use authentication::Credentials;
// use config::SessionConfig;
// #[tokio::test]
// async fn test_connection() -> Result<(), Box<dyn std::error::Error>> {
// println!("Running connection test");
// let ap = apresolve_or_fallback(&None, &None).await;
// let credentials = Credentials::with_password(String::from("test"), String::from("test"));
// let session_config = SessionConfig::default();
// let proxy = None;
println!("Connecting to AP \"{}\"", ap);
let mut connection = connection::connect(ap, &proxy).await?;
let rc = connection::authenticate(&mut connection, credentials, &session_config.device_id)
.await?;
println!("Authenticated as \"{}\"", rc.username);
Ok(())
}
}
// println!("Connecting to AP \"{}\"", ap);
// let mut connection = connection::connect(ap, &proxy).await?;
// let rc = connection::authenticate(&mut connection, credentials, &session_config.device_id)
// .await?;
// println!("Authenticated as \"{}\"", rc.username);
// Ok(())
// }
// }

View file

@ -292,7 +292,7 @@ impl Metadata for Playlist {
.get_items()
.iter()
.map(|item| {
let uri_split = item.get_uri().split(":");
let uri_split = item.get_uri().split(':');
let uri_parts: Vec<&str> = uri_split.collect();
SpotifyId::from_base62(uri_parts[2]).unwrap()
})

View file

@ -2,9 +2,10 @@ use super::{Open, Sink};
use crate::audio::AudioPacket;
use gst::prelude::*;
use gst::*;
use zerocopy::*;
use std::sync::mpsc::{sync_channel, SyncSender};
use std::{io, thread};
use zerocopy::*;
#[allow(dead_code)]
pub struct GstreamerSink {
@ -91,10 +92,7 @@ impl Open for GstreamerSink {
.set_state(gst::State::Playing)
.expect("Unable to set the pipeline to the `Playing` state");
GstreamerSink {
tx: tx,
pipeline: pipeline,
}
GstreamerSink { tx, pipeline }
}
}

View file

@ -60,7 +60,7 @@ impl Open for JackSink {
JackSink {
send: tx,
active_client: active_client,
active_client,
}
}
}

View file

@ -56,7 +56,7 @@ use self::pipe::StdoutSink;
mod subprocess;
use self::subprocess::SubprocessSink;
pub const BACKENDS: &'static [(&'static str, SinkBuilder)] = &[
pub const BACKENDS: &[(&str, SinkBuilder)] = &[
#[cfg(feature = "alsa-backend")]
("alsa", mk_sink::<AlsaSink>),
#[cfg(feature = "portaudio-backend")]

View file

@ -26,8 +26,8 @@ impl Open for PulseAudioSink {
PulseAudioSink {
s: None,
ss: ss,
device: device,
ss,
device,
}
}
}

View file

@ -43,7 +43,6 @@ pub enum RodioError {
pub struct RodioSink {
rodio_sink: rodio::Sink,
// will produce a TryRecvError on the receiver side when it is dropped.
_close_tx: mpsc::SyncSender<Infallible>,
}

View file

@ -29,7 +29,7 @@ impl Open for SdlSink {
.open_queue(None, &desired_spec)
.expect("Could not open SDL audio device");
SdlSink { queue: queue }
SdlSink { queue }
}
}

View file

@ -1,6 +1,8 @@
use super::{Open, Sink};
use crate::audio::AudioPacket;
use shell_words::split;
use std::io::{self, Write};
use std::mem;
use std::process::{Child, Command, Stdio};
@ -15,7 +17,7 @@ impl Open for SubprocessSink {
fn open(shell_command: Option<String>) -> SubprocessSink {
if let Some(shell_command) = shell_command {
SubprocessSink {
shell_command: shell_command,
shell_command,
child: None,
}
} else {

View file

@ -1,10 +1,7 @@
use super::AudioFilter;
use super::{Mixer, MixerConfig};
use std;
use std::error::Error;
use alsa;
const SND_CTL_TLV_DB_GAIN_MUTE: i64 = -9999999;
#[derive(Clone)]
@ -72,14 +69,14 @@ impl AlsaMixer {
}
Ok(AlsaMixer {
config: config,
config,
params: AlsaMixerVolumeParams {
min: min,
max: max,
min,
max,
range: (max - min) as f64,
min_db: min_db,
max_db: max_db,
has_switch: has_switch,
min_db,
max_db,
has_switch,
},
})
}

View file

@ -252,8 +252,8 @@ impl Player {
debug!("new Player[{}]", session.session_id());
let internal = PlayerInternal {
session: session,
config: config,
session,
config,
commands: cmd_rx,
state: PlayerState::Stopped,
@ -261,7 +261,7 @@ impl Player {
sink: sink_builder(),
sink_status: SinkStatus::Closed,
sink_event_callback: None,
audio_filter: audio_filter,
audio_filter,
event_senders: [event_sender].to_vec(),
};
@ -432,18 +432,12 @@ impl PlayerState {
#[allow(dead_code)]
fn is_stopped(&self) -> bool {
use self::PlayerState::*;
match *self {
Stopped => true,
_ => false,
}
matches!(self, Stopped)
}
fn is_loading(&self) -> bool {
use self::PlayerState::*;
match *self {
Loading { .. } => true,
_ => false,
}
matches!(self, Loading { .. })
}
fn decoder(&mut self) -> Option<&mut Decoder> {
@ -697,7 +691,7 @@ impl PlayerTrackLoader {
};
let is_cached = encrypted_file.is_cached();
let mut stream_loader_controller = encrypted_file.get_stream_loader_controller();
let stream_loader_controller = encrypted_file.get_stream_loader_controller();
if play_from_beginning {
// No need to seek -> we stream from the beginning
@ -908,11 +902,7 @@ impl Future for PlayerInternal {
.as_millis()
as i64
- stream_position_millis as i64;
if lag > 1000 {
true
} else {
false
}
lag > 1000
}
};
if notify_about_position {

View file

@ -2,6 +2,23 @@ use futures_util::{future, FutureExt, StreamExt};
use librespot_playback::player::PlayerEvent;
use log::{error, info, warn};
use sha1::{Digest, Sha1};
use tokio::sync::mpsc::UnboundedReceiver;
use url::Url;
use librespot::connect::spirc::Spirc;
use librespot::core::authentication::Credentials;
use librespot::core::cache::Cache;
use librespot::core::config::{ConnectConfig, DeviceType, SessionConfig, VolumeCtrl};
use librespot::core::session::Session;
use librespot::core::version;
use librespot::playback::audio_backend::{self, Sink, BACKENDS};
use librespot::playback::config::{Bitrate, NormalisationType, PlayerConfig};
use librespot::playback::mixer::{self, Mixer, MixerConfig};
use librespot::playback::player::Player;
mod player_event_handler;
use player_event_handler::{emit_sink_event, run_program_on_events};
use std::path::Path;
use std::process::exit;
use std::str::FromStr;
@ -10,24 +27,6 @@ use std::{
io::{stderr, Write},
pin::Pin,
};
use tokio::sync::mpsc::UnboundedReceiver;
use url::Url;
use librespot::core::authentication::Credentials;
use librespot::core::cache::Cache;
use librespot::core::config::{ConnectConfig, DeviceType, SessionConfig, VolumeCtrl};
use librespot::core::session::Session;
use librespot::core::version;
use librespot::connect::spirc::Spirc;
use librespot::playback::audio_backend::{self, Sink, BACKENDS};
use librespot::playback::config::{Bitrate, NormalisationType, PlayerConfig};
use librespot::playback::mixer::{self, Mixer, MixerConfig};
use librespot::playback::player::Player;
mod player_event_handler;
use player_event_handler::{emit_sink_event, run_program_on_events};
fn device_id(name: &str) -> String {
hex::encode(Sha1::digest(name.as_bytes()))

View file

@ -1,12 +1,12 @@
use librespot::playback::player::PlayerEvent;
use librespot::playback::player::SinkStatus;
use log::info;
use tokio::process::{Child as AsyncChild, Command as AsyncCommand};
use std::collections::HashMap;
use std::io;
use std::process::{Command, ExitStatus};
use librespot::playback::player::SinkStatus;
use tokio::process::{Child as AsyncChild, Command as AsyncCommand};
pub fn run_program_on_events(event: PlayerEvent, onevent: &str) -> Option<io::Result<AsyncChild>> {
let mut env_vars = HashMap::new();
match event {