From 1ffb07300febfe53d33b3c503cc62edc46de1c3d Mon Sep 17 00:00:00 2001 From: DaXcess Date: Mon, 18 Sep 2023 14:02:59 +0200 Subject: [PATCH 1/8] Hello there I'm back --- .github/workflows/build-push.yml | 2 +- COMPILING.md | 7 - Cargo.lock | 2 +- Cargo.toml | 6 +- Dockerfile.metrics | 23 -- README.md | 1 - src/audio/backend.rs | 88 ----- src/audio/mod.rs | 161 +++++++++- src/bot/commands/music/playing.rs | 21 +- src/bot/events.rs | 9 - src/main.rs | 56 ---- src/metrics.rs | 114 ------- src/player.rs | 328 ------------------- src/player/mod.rs | 99 ++++++ src/player/stream.rs | 76 +++++ src/session/manager.rs | 4 +- src/session/mod.rs | 516 ++++++++++++++++-------------- 17 files changed, 614 insertions(+), 899 deletions(-) delete mode 100644 Dockerfile.metrics delete mode 100644 src/audio/backend.rs delete mode 100644 src/metrics.rs delete mode 100644 src/player.rs create mode 100644 src/player/mod.rs create mode 100644 src/player/stream.rs diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 507765c..b04fb13 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -28,7 +28,7 @@ jobs: uses: docker/build-push-action@v2 with: context: . - file: ./Dockerfile.metrics + file: ./Dockerfile tags: | ${{ secrets.REGISTRY_URL }}/spoticord/spoticord:latest push: ${{ github.ref == 'refs/heads/main' }} diff --git a/COMPILING.md b/COMPILING.md index cde8a47..db657bd 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -71,13 +71,6 @@ If you are actively developing Spoticord, you can use the following command to b cargo run ``` -# Features -As of now, Spoticord has one optional feature: `metrics`. This feature enables pushing metrics about the bot, like how many servers it is in, which tracks are being played and which commands are being executed. The metrics are designed to be pushed to a [Prometheus Pushgateway](https://prometheus.io/docs/instrumenting/pushing/). If you want to enable this feature, you can do so by running the following command: - -```sh -cargo build --release --features metrics -``` - # MSRV The current minimum supported rust version is `1.65.0`. diff --git a/Cargo.lock b/Cargo.lock index 2d6dab9..aff5e5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2603,7 +2603,7 @@ dependencies = [ [[package]] name = "spoticord" -version = "2.0.0" +version = "2.1.0" dependencies = [ "dotenv", "env_logger 0.10.0", diff --git a/Cargo.toml b/Cargo.toml index ce04333..deb901b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spoticord" -version = "2.0.0" +version = "2.1.0" edition = "2021" rust-version = "1.65.0" @@ -8,10 +8,6 @@ rust-version = "1.65.0" name = "spoticord" path = "src/main.rs" -[features] -default = [] -metrics = ["lazy_static", "prometheus"] - [dependencies] dotenv = "0.15.0" env_logger = "0.10.0" diff --git a/Dockerfile.metrics b/Dockerfile.metrics deleted file mode 100644 index 9d7dc42..0000000 --- a/Dockerfile.metrics +++ /dev/null @@ -1,23 +0,0 @@ -# Builder -FROM rust:1.65-buster as builder - -WORKDIR /app - -# Add extra build dependencies here -RUN apt-get update && apt-get install -y cmake - -COPY . . -RUN cargo install --path . --features metrics - -# Runtime -FROM debian:buster-slim - -WORKDIR /app - -# Add extra runtime dependencies here -RUN apt-get update && apt-get install -y openssl ca-certificates && rm -rf /var/lib/apt/lists/* - -# Copy spoticord binary from builder -COPY --from=builder /usr/local/cargo/bin/spoticord ./spoticord - -CMD ["./spoticord"] \ No newline at end of file diff --git a/README.md b/README.md index 5965bc1..673c2a5 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,6 @@ Spoticord uses environment variables to configure itself. The following variable Additionally you can configure the following variables: - `GUILD_ID`: The ID of the Discord server where this bot will create commands for. This is used during testing to prevent the bot from creating slash commands in other servers, as well as getting the commands quicker. This variable is optional, and if not set, the bot will create commands in all servers it is in (this may take up to 15 minutes). -- `METRICS_URL`: The connection URL of a Prometheus Push Gateway server used for pushing metrics. This variable is required when compiling with the `metrics` feature. #### Providing environment variables You can provide environment variables in a `.env` file at the root of the working directory of Spoticord. diff --git a/src/audio/backend.rs b/src/audio/backend.rs deleted file mode 100644 index 3241ae7..0000000 --- a/src/audio/backend.rs +++ /dev/null @@ -1,88 +0,0 @@ -use librespot::playback::audio_backend::{Sink, SinkAsBytes, SinkError, SinkResult}; -use librespot::playback::convert::Converter; -use librespot::playback::decoder::AudioPacket; -use log::error; -use std::io::{Stdout, Write}; - -use crate::ipc; -use crate::ipc::packet::IpcPacket; - -pub struct StdoutSink { - client: ipc::Client, - output: Option>, -} - -impl StdoutSink { - pub fn new(client: ipc::Client) -> Self { - StdoutSink { - client, - output: None, - } - } -} - -impl Sink for StdoutSink { - fn start(&mut self) -> SinkResult<()> { - if let Err(why) = self.client.send(IpcPacket::StartPlayback) { - error!("Failed to send start playback packet: {}", why); - return Err(SinkError::ConnectionRefused(why.to_string())); - } - - self.output.get_or_insert(Box::new(std::io::stdout())); - - Ok(()) - } - - fn stop(&mut self) -> SinkResult<()> { - if let Err(why) = self.client.send(IpcPacket::StopPlayback) { - error!("Failed to send stop playback packet: {}", why); - return Err(SinkError::ConnectionRefused(why.to_string())); - } - - self - .output - .take() - .ok_or_else(|| SinkError::NotConnected("StdoutSink is not connected".to_string()))? - .flush() - .map_err(|why| SinkError::OnWrite(why.to_string()))?; - - Ok(()) - } - - fn write(&mut self, packet: AudioPacket, converter: &mut Converter) -> SinkResult<()> { - use zerocopy::AsBytes; - - if let AudioPacket::Samples(samples) = packet { - let samples_f32: &[f32] = &converter.f64_to_f32(&samples); - - let resampled = samplerate::convert( - 44100, - 48000, - 2, - samplerate::ConverterType::Linear, - samples_f32, - ) - .expect("to succeed"); - - let samples_i16 = - &converter.f64_to_s16(&resampled.iter().map(|v| *v as f64).collect::>()); - - self.write_bytes(samples_i16.as_bytes())?; - } - - Ok(()) - } -} - -impl SinkAsBytes for StdoutSink { - fn write_bytes(&mut self, data: &[u8]) -> SinkResult<()> { - self - .output - .as_deref_mut() - .ok_or_else(|| SinkError::NotConnected("StdoutSink is not connected".to_string()))? - .write_all(data) - .map_err(|why| SinkError::OnWrite(why.to_string()))?; - - Ok(()) - } -} diff --git a/src/audio/mod.rs b/src/audio/mod.rs index fceb141..0286273 100644 --- a/src/audio/mod.rs +++ b/src/audio/mod.rs @@ -1 +1,160 @@ -pub mod backend; +use librespot::playback::audio_backend::{Sink, SinkAsBytes, SinkError, SinkResult}; +use librespot::playback::convert::Converter; +use librespot::playback::decoder::AudioPacket; +use log::error; +use std::io::{Stdout, Write}; +use tokio::sync::mpsc::UnboundedSender; + +use crate::ipc; +use crate::ipc::packet::IpcPacket; +use crate::player::stream::Stream; + +pub struct StdoutSink { + client: ipc::Client, + output: Option>, +} + +impl StdoutSink { + pub fn new(client: ipc::Client) -> Self { + StdoutSink { + client, + output: None, + } + } +} + +impl Sink for StdoutSink { + fn start(&mut self) -> SinkResult<()> { + if let Err(why) = self.client.send(IpcPacket::StartPlayback) { + error!("Failed to send start playback packet: {}", why); + return Err(SinkError::ConnectionRefused(why.to_string())); + } + + self.output.get_or_insert(Box::new(std::io::stdout())); + + Ok(()) + } + + fn stop(&mut self) -> SinkResult<()> { + if let Err(why) = self.client.send(IpcPacket::StopPlayback) { + error!("Failed to send stop playback packet: {}", why); + return Err(SinkError::ConnectionRefused(why.to_string())); + } + + self + .output + .take() + .ok_or_else(|| SinkError::NotConnected("StdoutSink is not connected".to_string()))? + .flush() + .map_err(|why| SinkError::OnWrite(why.to_string()))?; + + Ok(()) + } + + fn write(&mut self, packet: AudioPacket, converter: &mut Converter) -> SinkResult<()> { + use zerocopy::AsBytes; + + if let AudioPacket::Samples(samples) = packet { + let samples_f32: &[f32] = &converter.f64_to_f32(&samples); + + let resampled = samplerate::convert( + 44100, + 48000, + 2, + samplerate::ConverterType::Linear, + samples_f32, + ) + .expect("to succeed"); + + let samples_i16 = + &converter.f64_to_s16(&resampled.iter().map(|v| *v as f64).collect::>()); + + self.write_bytes(samples_i16.as_bytes())?; + } + + Ok(()) + } +} + +impl SinkAsBytes for StdoutSink { + fn write_bytes(&mut self, data: &[u8]) -> SinkResult<()> { + self + .output + .as_deref_mut() + .ok_or_else(|| SinkError::NotConnected("StdoutSink is not connected".to_string()))? + .write_all(data) + .map_err(|why| SinkError::OnWrite(why.to_string()))?; + + Ok(()) + } +} + +pub enum SinkEvent { + Start, + Stop, +} + +pub struct StreamSink { + stream: Stream, + sender: UnboundedSender, +} + +impl StreamSink { + pub fn new(stream: Stream, sender: UnboundedSender) -> Self { + Self { stream, sender } + } +} + +impl Sink for StreamSink { + fn start(&mut self) -> SinkResult<()> { + if let Err(why) = self.sender.send(SinkEvent::Start) { + error!("Failed to send start playback event: {why}"); + return Err(SinkError::ConnectionRefused(why.to_string())); + } + + Ok(()) + } + + fn stop(&mut self) -> SinkResult<()> { + if let Err(why) = self.sender.send(SinkEvent::Stop) { + error!("Failed to send start playback event: {why}"); + return Err(SinkError::ConnectionRefused(why.to_string())); + } + + Ok(()) + } + + fn write(&mut self, packet: AudioPacket, converter: &mut Converter) -> SinkResult<()> { + use zerocopy::AsBytes; + + let AudioPacket::Samples(samples) = packet else { return Ok(()); }; + let samples_f32: &[f32] = &converter.f64_to_f32(&samples); + + let resampled = samplerate::convert( + 44100, + 48000, + 2, + samplerate::ConverterType::Linear, + samples_f32, + ) + .expect("to succeed"); + + let samples_i16 = + &converter.f64_to_s16(&resampled.iter().map(|v| *v as f64).collect::>()); + + self.write_bytes(samples_i16.as_bytes())?; + + Ok(()) + } +} + +impl SinkAsBytes for StreamSink { + fn write_bytes(&mut self, data: &[u8]) -> SinkResult<()> { + self + .stream + .write_all(data) + .map_err(|why| SinkError::OnWrite(why.to_string()))?; + + Ok(()) + } +} diff --git a/src/bot/commands/music/playing.rs b/src/bot/commands/music/playing.rs index 27d6d26..f022575 100644 --- a/src/bot/commands/music/playing.rs +++ b/src/bot/commands/music/playing.rs @@ -280,35 +280,24 @@ pub fn component(ctx: Context, mut interaction: MessageComponentInteraction) -> }; // Send the desired command to the session - let success = match interaction.data.custom_id.as_str() { + match interaction.data.custom_id.as_str() { "playing::btn_pause_play" => { if pbi.is_playing { - session.pause().await.is_ok() + session.pause().await } else { - session.resume().await.is_ok() + session.resume().await } } - "playing::btn_previous_track" => session.previous().await.is_ok(), + "playing::btn_previous_track" => session.previous().await, - "playing::btn_next_track" => session.next().await.is_ok(), + "playing::btn_next_track" => session.next().await, _ => { error!("Unknown custom_id: {}", interaction.data.custom_id); - false } }; - if !success { - error_message( - "Cannot change playback state", - "An error occurred while trying to change the playback state", - ) - .await; - - return; - } - interaction.defer(&ctx.http).await.ok(); tokio::time::sleep(Duration::from_millis( if interaction.data.custom_id == "playing::btn_pause_play" { diff --git a/src/bot/events.rs b/src/bot/events.rs index 0fd1a77..3668150 100644 --- a/src/bot/events.rs +++ b/src/bot/events.rs @@ -15,9 +15,6 @@ use serenity::{ prelude::{Context, EventHandler}, }; -#[cfg(feature = "metrics")] -use crate::metrics::MetricsManager; - // If the GUILD_ID environment variable is set, only allow commands from that guild macro_rules! enforce_guild { ($interaction:ident) => { @@ -99,12 +96,6 @@ impl Handler { let data = ctx.data.read().await; let command_manager = data.get::().expect("to contain a value"); - #[cfg(feature = "metrics")] - { - let metrics = data.get::().expect("to contain a value"); - metrics.command_exec(&command.data.name); - } - command_manager.execute_command(&ctx, command).await; } diff --git a/src/main.rs b/src/main.rs index 1d1f17e..90ff3dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,15 +6,9 @@ use serenity::{framework::StandardFramework, prelude::GatewayIntents, Client}; use songbird::SerenityInit; use std::{any::Any, env, process::exit}; -#[cfg(feature = "metrics")] -use metrics::MetricsManager; - #[cfg(unix)] use tokio::signal::unix::SignalKind; -#[cfg(feature = "metrics")] -mod metrics; - mod audio; mod bot; mod consts; @@ -41,20 +35,6 @@ async fn main() { env_logger::init(); - let args: Vec = env::args().collect(); - - if args.len() > 2 && &args[1] == "--player" { - // Woah! We're running in player mode! - - debug!("Starting Spoticord player"); - - player::main().await; - - debug!("Player exited, shutting down"); - - return; - } - info!("It's a good day"); info!(" - Spoticord {}", time::OffsetDateTime::now_utc().year()); @@ -72,12 +52,6 @@ async fn main() { let token = env::var("DISCORD_TOKEN").expect("a token in the environment"); let db_url = env::var("DATABASE_URL").expect("a database URL in the environment"); - #[cfg(feature = "metrics")] - let metrics_manager = { - let metrics_url = env::var("METRICS_URL").expect("a prometheus pusher URL in the environment"); - MetricsManager::new(metrics_url) - }; - let session_manager = SessionManager::new(); // Create client @@ -97,9 +71,6 @@ async fn main() { data.insert::(Database::new(db_url, None)); data.insert::(CommandManager::new()); data.insert::(session_manager.clone()); - - #[cfg(feature = "metrics")] - data.insert::(metrics_manager.clone()); } let shard_manager = client.shard_manager.clone(); @@ -118,30 +89,6 @@ async fn main() { tokio::spawn(async move { loop { tokio::select! { - _ = tokio::time::sleep(std::time::Duration::from_secs(60)) => { - #[cfg(feature = "metrics")] - { - let guild_count = _cache.guilds().len(); - let active_count = session_manager.get_active_session_count().await; - let total_count = session_manager.get_session_count().await; - - metrics_manager.set_server_count(guild_count); - metrics_manager.set_active_sessions(active_count); - metrics_manager.set_total_sessions(total_count); - - // Yes, I like to handle my s's when I'm working with amounts - debug!( - "Updated metrics: {} guild{}, {} active session{}, {} total session{}", - guild_count, - if guild_count == 1 { "" } else { "s" }, - active_count, - if active_count == 1 { "" } else { "s" }, - total_count, - if total_count == 1 { "" } else { "s" } - ); - } - } - _ = tokio::signal::ctrl_c() => { info!("Received interrupt signal, shutting down..."); @@ -166,9 +113,6 @@ async fn main() { shard_manager.lock().await.shutdown_all().await; - #[cfg(feature = "metrics")] - metrics_manager.stop(); - break; } } diff --git a/src/metrics.rs b/src/metrics.rs deleted file mode 100644 index 21a3e0b..0000000 --- a/src/metrics.rs +++ /dev/null @@ -1,114 +0,0 @@ -use std::{ - collections::hash_map::RandomState, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - thread, - time::Duration, -}; - -use lazy_static::lazy_static; -use prometheus::{ - opts, push_metrics, register_int_counter_vec, register_int_gauge, IntCounterVec, IntGauge, -}; -use serenity::prelude::TypeMapKey; - -use crate::session::pbi::PlaybackInfo; - -lazy_static! { - static ref TOTAL_SERVERS: IntGauge = - register_int_gauge!("total_servers", "Total number of servers Spoticord is in").unwrap(); - static ref ACTIVE_SESSIONS: IntGauge = register_int_gauge!( - "active_sessions", - "Total number of servers with an active Spoticord session" - ) - .unwrap(); - static ref TOTAL_SESSIONS: IntGauge = register_int_gauge!( - "total_sessions", - "Total number of servers with Spoticord in a voice channel" - ) - .unwrap(); - static ref TRACKS_PLAYED: IntCounterVec = - register_int_counter_vec!(opts!("tracks_played", "Tracks Played"), &["type"]).unwrap(); - static ref COMMANDS_EXECUTED: IntCounterVec = register_int_counter_vec!( - opts!("commands_executed", "Commands Executed"), - &["command"] - ) - .unwrap(); -} - -#[derive(Clone)] -pub struct MetricsManager { - should_stop: Arc, -} - -impl MetricsManager { - pub fn new(pusher_url: impl Into) -> Self { - let instance = Self { - should_stop: Arc::new(AtomicBool::new(false)), - }; - - thread::spawn({ - let instance = instance.clone(); - let pusher_url = pusher_url.into(); - - move || loop { - thread::sleep(Duration::from_secs(5)); - - if instance.should_stop() { - break; - } - - if let Err(why) = push_metrics::( - "spoticord_metrics", - Default::default(), - &pusher_url, - prometheus::gather(), - None, - ) { - log::error!("Failed to push metrics: {}", why); - } - } - }); - - instance - } - - pub fn should_stop(&self) -> bool { - self.should_stop.load(Ordering::Relaxed) - } - - pub fn stop(&self) { - self.should_stop.store(true, Ordering::Relaxed); - } - - pub fn set_server_count(&self, count: usize) { - TOTAL_SERVERS.set(count as i64); - } - - pub fn set_total_sessions(&self, count: usize) { - TOTAL_SESSIONS.set(count as i64); - } - - pub fn set_active_sessions(&self, count: usize) { - ACTIVE_SESSIONS.set(count as i64); - } - - pub fn track_play(&self, track: &PlaybackInfo) { - let track_type = match track.get_type() { - Some(track_type) => track_type, - None => return, - }; - - TRACKS_PLAYED.with_label_values(&[&track_type]).inc(); - } - - pub fn command_exec(&self, command: &str) { - COMMANDS_EXECUTED.with_label_values(&[command]).inc(); - } -} - -impl TypeMapKey for MetricsManager { - type Value = MetricsManager; -} diff --git a/src/player.rs b/src/player.rs deleted file mode 100644 index 045d55e..0000000 --- a/src/player.rs +++ /dev/null @@ -1,328 +0,0 @@ -use std::{process::exit, time::Duration}; - -use ipc_channel::ipc::{IpcError, TryRecvError}; -use librespot::{ - connect::spirc::Spirc, - core::{ - config::{ConnectConfig, SessionConfig}, - session::Session, - }, - discovery::Credentials, - playback::{ - config::{Bitrate, PlayerConfig}, - mixer::{self, MixerConfig}, - player::{Player, PlayerEvent}, - }, -}; -use log::{debug, error, warn}; -use serde_json::json; - -use crate::{ - audio::backend::StdoutSink, - ipc::{self, packet::IpcPacket}, - librespot_ext::discovery::CredentialsExt, - utils, -}; - -pub struct SpoticordPlayer { - client: ipc::Client, - session: Option, - spirc: Option, -} - -impl SpoticordPlayer { - pub fn new(client: ipc::Client) -> Self { - Self { - client, - session: None, - spirc: None, - } - } - - pub async fn start(&mut self, token: impl Into, device_name: impl Into) { - let token = token.into(); - - // Get the username (required for librespot) - let username = utils::spotify::get_username(&token) - .await - .expect("to get the username"); - - let session_config = SessionConfig::default(); - let player_config = PlayerConfig { - bitrate: Bitrate::Bitrate96, - ..PlayerConfig::default() - }; - - // Log in using the token - let credentials = Credentials::with_token(username, &token); - - // Shutdown old session (cannot be done in the stop function) - if let Some(session) = self.session.take() { - session.shutdown(); - } - - // Connect the session - let (session, _) = match Session::connect(session_config, credentials, None, false).await { - Ok((session, credentials)) => (session, credentials), - Err(why) => { - error!("Failed to create Spotify session: {}", why); - - self - .client - .send(IpcPacket::ConnectError(why.to_string())) - .ok(); - return; - } - }; - - // Store session for later use - self.session = Some(session.clone()); - - // Volume mixer - let mixer = (mixer::find(Some("softvol")).expect("to exist"))(MixerConfig { - volume_ctrl: librespot::playback::config::VolumeCtrl::Linear, - ..MixerConfig::default() - }); - - let client = self.client.clone(); - - // Create the player - let (player, mut receiver) = Player::new( - player_config, - session.clone(), - mixer.get_soft_volume(), - move || Box::new(StdoutSink::new(client)), - ); - - let (spirc, spirc_task) = Spirc::new( - ConnectConfig { - name: device_name.into(), - // 50% - initial_volume: Some(65535 / 2), - ..ConnectConfig::default() - }, - session.clone(), - player, - mixer, - ); - - let device_id = session.device_id().to_owned(); - let ipc = self.client.clone(); - - // IPC Handler - tokio::spawn(async move { - let client = reqwest::Client::new(); - - let mut retries = 10; - - // Try to switch to the device - loop { - match client - .put("https://api.spotify.com/v1/me/player") - .bearer_auth(token.clone()) - .json(&json!({ - "device_ids": [device_id], - })) - .send() - .await - { - Ok(resp) => { - if resp.status() == 202 { - debug!("Successfully switched to device"); - break; - } - - retries -= 1; - - if retries == 0 { - error!("Failed to switch to device"); - ipc - .send(IpcPacket::ConnectError( - "Switch to Spoticord device timed out".to_string(), - )) - .ok(); - break; - } - } - Err(why) => { - error!("Failed to set device: {}", why); - ipc.send(IpcPacket::ConnectError(why.to_string())).ok(); - break; - } - } - - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - } - - // Do IPC stuff with these events - loop { - let event = match receiver.recv().await { - Some(event) => event, - None => break, - }; - - match event { - PlayerEvent::Playing { - play_request_id: _, - track_id, - position_ms, - duration_ms, - } => { - if let Err(why) = ipc.send(IpcPacket::Playing( - track_id.to_uri().expect("to not fail"), - position_ms, - duration_ms, - )) { - error!("Failed to send playing packet: {}", why); - } - } - - PlayerEvent::Paused { - play_request_id: _, - track_id, - position_ms, - duration_ms, - } => { - if let Err(why) = ipc.send(IpcPacket::Paused( - track_id.to_uri().expect("to not fail"), - position_ms, - duration_ms, - )) { - error!("Failed to send paused packet: {}", why); - } - } - - PlayerEvent::Changed { - old_track_id: _, - new_track_id, - } => { - if let Err(why) = ipc.send(IpcPacket::TrackChange( - new_track_id.to_uri().expect("to not fail"), - )) { - error!("Failed to send track change packet: {}", why); - } - } - - PlayerEvent::Stopped { - play_request_id: _, - track_id: _, - } => { - if let Err(why) = ipc.send(IpcPacket::Stopped) { - error!("Failed to send player stopped packet: {}", why); - } - } - - _ => {} - }; - } - - debug!("Player stopped"); - }); - - self.spirc = Some(spirc); - tokio::spawn(spirc_task); - } - - pub fn next(&mut self) { - if let Some(spirc) = &self.spirc { - spirc.next(); - } - } - - pub fn previous(&mut self) { - if let Some(spirc) = &self.spirc { - spirc.prev(); - } - } - - pub fn pause(&mut self) { - if let Some(spirc) = &self.spirc { - spirc.pause(); - } - } - - pub fn resume(&mut self) { - if let Some(spirc) = &self.spirc { - spirc.play(); - } - } - - pub fn stop(&mut self) { - if let Some(spirc) = self.spirc.take() { - spirc.shutdown(); - } - } -} - -pub async fn main() { - let args = std::env::args().collect::>(); - - let tx_name = args[2].clone(); - let rx_name = args[3].clone(); - - // Create IPC communication channel - let client = ipc::Client::connect(tx_name, rx_name).expect("Failed to connect to IPC"); - - // Create the player - let mut player = SpoticordPlayer::new(client.clone()); - - loop { - let message = match client.try_recv() { - Ok(message) => message, - Err(why) => { - if let TryRecvError::Empty = why { - // No message, wait a bit and try again - tokio::time::sleep(Duration::from_millis(25)).await; - - continue; - } else if let TryRecvError::IpcError(IpcError::Disconnected) = &why { - debug!("IPC connection closed, goodbye"); - break; - } - - error!("Failed to receive message: {}", why); - break; - } - }; - - match message { - IpcPacket::Connect(token, device_name) => { - debug!("Connecting to Spotify with device name {}", device_name); - - player.start(token, device_name).await; - } - - IpcPacket::Disconnect => { - debug!("Disconnecting from Spotify"); - - player.stop(); - } - - IpcPacket::Next => { - player.next(); - } - - IpcPacket::Previous => { - player.previous(); - } - - IpcPacket::Pause => { - player.pause(); - } - - IpcPacket::Resume => { - player.resume(); - } - - IpcPacket::Quit => { - debug!("Received quit packet, exiting"); - - exit(0); - } - - _ => { - warn!("Received unknown packet: {:?}", message); - } - } - } -} diff --git a/src/player/mod.rs b/src/player/mod.rs new file mode 100644 index 0000000..d78dcb5 --- /dev/null +++ b/src/player/mod.rs @@ -0,0 +1,99 @@ +pub mod stream; + +use librespot::{ + connect::spirc::Spirc, + core::{config::ConnectConfig, session::Session}, + discovery::Credentials, + playback::{ + config::{Bitrate, PlayerConfig, VolumeCtrl}, + mixer::{self, MixerConfig}, + player::{Player as SpotifyPlayer, PlayerEvent}, + }, +}; +use tokio::sync::mpsc::UnboundedReceiver; + +use crate::{ + audio::{SinkEvent, StreamSink}, + librespot_ext::discovery::CredentialsExt, + utils, +}; + +use self::stream::Stream; + +pub struct Player { + stream: Stream, + session: Option, +} + +impl Player { + pub fn create() -> Self { + Self { + stream: Stream::new(), + session: None, + } + } + + pub async fn start( + &mut self, + token: &str, + device_name: &str, + ) -> Result< + ( + Spirc, + (UnboundedReceiver, UnboundedReceiver), + ), + Box, + > { + let username = utils::spotify::get_username(token).await?; + + let player_config = PlayerConfig { + bitrate: Bitrate::Bitrate96, + ..Default::default() + }; + + let credentials = Credentials::with_token(username, token); + + // Shutdown old session (cannot be done in the stop function) + if let Some(session) = self.session.take() { + session.shutdown() + } + + // Connect the session + let (session, _) = Session::connect(Default::default(), credentials, None, false).await?; + self.session = Some(session.clone()); + + let mixer = (mixer::find(Some("softvol")).expect("to exist"))(MixerConfig { + volume_ctrl: VolumeCtrl::Linear, + ..Default::default() + }); + + let stream = self.get_stream(); + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + let (player, receiver) = SpotifyPlayer::new( + player_config, + session.clone(), + mixer.get_soft_volume(), + move || Box::new(StreamSink::new(stream, tx)), + ); + + let (spirc, spirc_task) = Spirc::new( + ConnectConfig { + name: device_name.into(), + // 50% + initial_volume: Some(65535 / 2), + ..Default::default() + }, + session.clone(), + player, + mixer, + ); + + tokio::spawn(spirc_task); + + Ok((spirc, (receiver, rx))) + } + + pub fn get_stream(&self) -> Stream { + self.stream.clone() + } +} diff --git a/src/player/stream.rs b/src/player/stream.rs new file mode 100644 index 0000000..e82e36a --- /dev/null +++ b/src/player/stream.rs @@ -0,0 +1,76 @@ +use std::{ + io::{Read, Seek, Write}, + sync::{Arc, Condvar, Mutex}, +}; + +use songbird::input::reader::MediaSource; + +const MAX_SIZE: usize = 1 * 1024 * 1024; + +#[derive(Clone)] +pub struct Stream { + inner: Arc<(Mutex>, Condvar)>, +} + +impl Stream { + pub fn new() -> Self { + Self { + inner: Arc::new((Mutex::new(Vec::new()), Condvar::new())), + } + } +} + +impl Read for Stream { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let (mutex, condvar) = &*self.inner; + let mut buffer = mutex.lock().expect("Mutex was poisoned"); + + log::trace!("Read!"); + + while buffer.is_empty() { + buffer = condvar.wait(buffer).expect("Mutex was poisoned"); + } + + let max_read = usize::min(buf.len(), buffer.len()); + buf[0..max_read].copy_from_slice(&buffer[0..max_read]); + buffer.drain(0..max_read); + + Ok(max_read) + } +} + +impl Write for Stream { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let (mutex, condvar) = &*self.inner; + let mut buffer = mutex.lock().expect("Mutex was poisoned"); + + while buffer.len() + buf.len() > MAX_SIZE { + buffer = condvar.wait(buffer).unwrap(); + } + + buffer.extend_from_slice(buf); + condvar.notify_all(); + + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +impl Seek for Stream { + fn seek(&mut self, _: std::io::SeekFrom) -> std::io::Result { + Ok(0) + } +} + +impl MediaSource for Stream { + fn byte_len(&self) -> Option { + None + } + + fn is_seekable(&self) -> bool { + false + } +} diff --git a/src/session/manager.rs b/src/session/manager.rs index 222a4d3..acaef69 100644 --- a/src/session/manager.rs +++ b/src/session/manager.rs @@ -25,8 +25,8 @@ pub enum SessionCreateError { #[error("Failed to join voice channel {0} ({1})")] JoinError(ChannelId, GuildId), - #[error("Failed to start player process")] - ForkError, + #[error("Failed to start the player")] + PlayerStartError, } #[derive(Clone)] diff --git a/src/session/mod.rs b/src/session/mod.rs index f4be02c..9210903 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -6,13 +6,17 @@ use self::{ pbi::PlaybackInfo, }; use crate::{ + audio::SinkEvent, consts::DISCONNECT_TIME, database::{Database, DatabaseError}, - ipc::{self, packet::IpcPacket, Client}, + player::Player, utils::{embed::Status, spotify}, }; -use ipc_channel::ipc::{IpcError, TryRecvError}; -use librespot::core::spotify_id::{SpotifyAudioType, SpotifyId}; +use librespot::{ + connect::spirc::Spirc, + core::spotify_id::{SpotifyAudioType, SpotifyId}, + playback::player::PlayerEvent, +}; use log::*; use serenity::{ async_trait, @@ -22,20 +26,13 @@ use serenity::{ }; use songbird::{ create_player, - input::{children_to_reader, Codec, Container, Input}, + input::{Codec, Container, Input, Reader}, tracks::TrackHandle, Call, Event, EventContext, EventHandler, }; -use std::{ - process::{Command, Stdio}, - sync::Arc, - time::Duration, -}; +use std::{sync::Arc, time::Duration}; use tokio::sync::Mutex; -#[cfg(feature = "metrics")] -use crate::metrics::MetricsManager; - #[derive(Clone)] pub struct SpoticordSession(Arc>); @@ -56,14 +53,11 @@ struct InnerSpoticordSession { disconnect_handle: Option>, - client: Option, + spirc: Option, /// Whether the session has been disconnected /// If this is true then this instance should no longer be used and dropped disconnected: bool, - - #[cfg(feature = "metrics")] - metrics: MetricsManager, } impl SpoticordSession { @@ -81,12 +75,6 @@ impl SpoticordSession { .expect("to contain a value") .clone(); - #[cfg(feature = "metrics")] - let metrics = data - .get::() - .expect("to contain a value") - .clone(); - // Join the voice channel let songbird = songbird::get(ctx).await.expect("to be present").clone(); @@ -108,15 +96,11 @@ impl SpoticordSession { track: None, playback_info: None, disconnect_handle: None, - client: None, + spirc: None, disconnected: false, - - #[cfg(feature = "metrics")] - metrics, }; let mut instance = Self(Arc::new(RwLock::new(inner))); - instance.create_player(ctx).await?; let mut call = call.lock().await; @@ -165,39 +149,31 @@ impl SpoticordSession { } /// Advance to the next track - pub async fn next(&mut self) -> Result<(), IpcError> { - if let Some(ref client) = self.0.read().await.client { - return client.send(IpcPacket::Next); + pub async fn next(&mut self) { + if let Some(ref spirc) = self.0.read().await.spirc { + spirc.next(); } - - Ok(()) } /// Rewind to the previous track - pub async fn previous(&mut self) -> Result<(), IpcError> { - if let Some(ref client) = self.0.read().await.client { - return client.send(IpcPacket::Previous); + pub async fn previous(&mut self) { + if let Some(ref spirc) = self.0.read().await.spirc { + spirc.prev(); } - - Ok(()) } /// Pause the current track - pub async fn pause(&mut self) -> Result<(), IpcError> { - if let Some(ref client) = self.0.read().await.client { - return client.send(IpcPacket::Pause); + pub async fn pause(&mut self) { + if let Some(ref spirc) = self.0.read().await.spirc { + spirc.pause(); } - - Ok(()) } /// Resume the current track - pub async fn resume(&mut self) -> Result<(), IpcError> { - if let Some(ref client) = self.0.read().await.client { - return client.send(IpcPacket::Resume); + pub async fn resume(&mut self) { + if let Some(ref spirc) = self.0.read().await.spirc { + spirc.play(); } - - Ok(()) } async fn create_player(&mut self, ctx: &Context) -> Result<(), SessionCreateError> { @@ -232,52 +208,17 @@ impl SpoticordSession { } }; - // Create IPC oneshot server - let (server, tx_name, rx_name) = match ipc::Server::create() { - Ok(server) => server, - Err(why) => { - error!("Failed to create IPC server: {:?}", why); - return Err(SessionCreateError::ForkError); - } - }; - - // Spawn player process - let child = - match Command::new(std::env::current_exe().expect("to know the current executable path")) - .args([ - "--player", - &tx_name, - &rx_name, - "--debug-guild-id", - &self.guild_id().await.to_string(), - ]) - .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) - .spawn() - { - Ok(child) => child, - Err(why) => { - error!("Failed to start player process: {:?}", why); - return Err(SessionCreateError::ForkError); - } - }; - - // Establish bi-directional IPC channel - let client = match server.accept() { - Ok(client) => client, - Err(why) => { - error!("Failed to accept IPC connection: {:?}", why); - - return Err(SessionCreateError::ForkError); - } - }; - - // Pipe player audio to the voice channel - let reader = children_to_reader::(vec![child]); + // Create player + let mut player = Player::create(); // Create track (paused, fixes audio glitches) - let (mut track, track_handle) = - create_player(Input::new(true, reader, Codec::Pcm, Container::Raw, None)); + let (mut track, track_handle) = create_player(Input::new( + true, + Reader::Extension(Box::new(player.get_stream())), + Codec::Pcm, + Container::Raw, + None, + )); track.pause(); let call = self.call().await; @@ -286,11 +227,20 @@ impl SpoticordSession { // Set call audio to track call.play_only(track); + let (spirc, (mut player_rx, mut sink_rx)) = match player.start(&token, &user.device_name).await + { + Ok(v) => v, + Err(why) => { + error!("Failed to start the player: {:?}", why); + + return Err(SessionCreateError::PlayerStartError); + } + }; + // Handle IPC packets // This will automatically quit once the IPC connection is closed tokio::spawn({ let track = track_handle.clone(); - let client = client.clone(); let ctx = ctx.clone(); let instance = self.clone(); let inner = self.0.clone(); @@ -315,138 +265,228 @@ impl SpoticordSession { break; } - let msg = match client.try_recv() { - Ok(msg) => msg, - Err(why) => { - if let TryRecvError::Empty = why { - // No message, wait a bit and try again - tokio::time::sleep(Duration::from_millis(25)).await; + tokio::select! { + event = player_rx.recv() => { + let Some(event) = event else { break; }; - continue; - } else if let TryRecvError::IpcError(IpcError::Disconnected) = &why { - trace!("IPC connection closed, exiting IPC handler"); - break; + match event { + PlayerEvent::Playing { + play_request_id: _, + track_id, + position_ms, + duration_ms, + } => { + let was_none = instance + .update_playback(duration_ms, position_ms, true) + .await; + + if was_none { + // Stop player if update track fails + if let Err(why) = instance.update_track(&ctx, &owner_id, track_id).await { + error!("Failed to update track: {:?}", why); + + instance.player_stopped().await; + return; + } + } + } + + PlayerEvent::Paused { + play_request_id: _, + track_id, + position_ms, + duration_ms, + } => { + instance.start_disconnect_timer().await; + + let was_none = instance + .update_playback(duration_ms, position_ms, false) + .await; + + if was_none { + // Stop player if update track fails + + if let Err(why) = instance.update_track(&ctx, &owner_id, track_id).await { + error!("Failed to update track: {:?}", why); + + instance.player_stopped().await; + return; + } + } + } + + PlayerEvent::Changed { + old_track_id: _, + new_track_id, + } => { + let instance = instance.clone(); + let ctx = ctx.clone(); + + // Fetch track info + // This is done in a separate task to avoid blocking the IPC handler + tokio::spawn(async move { + if let Err(why) = instance.update_track(&ctx, &owner_id, new_track_id).await { + error!("Failed to update track: {:?}", why); + + instance.player_stopped().await; + } + }); + } + + PlayerEvent::Stopped { + play_request_id: _, + track_id: _, + } => { + check_result(track.pause()); + + { + let mut inner = inner.write().await; + inner.playback_info.take(); + } + + instance.start_disconnect_timer().await; + } + + _ => {} + }; + } + + event = sink_rx.recv() => { + let Some(event) = event else { break; }; + + let check_result = |result| { + if let Err(why) = result { + error!("Failed to issue track command: {:?}", why); + } + }; + + + match event { + SinkEvent::Start => { + check_result(track.play()); + } + + SinkEvent::Stop => { + check_result(track.pause()); + } } - - error!("Failed to receive IPC message: {:?}", why); - break; } }; - match msg { - // Session connect error - IpcPacket::ConnectError(why) => { - error!("Failed to connect to Spotify: {:?}", why); + // match event { + // // Session connect error + // IpcPacket::ConnectError(why) => { + // error!("Failed to connect to Spotify: {:?}", why); - // Notify the user in the text channel - if let Err(why) = instance - .text_channel_id() - .await - .send_message(&instance.http().await, |message| { - message.embed(|embed| { - embed.title("Failed to connect to Spotify"); - embed.description(why); - embed.footer(|footer| footer.text("Please try again")); - embed.color(Status::Error as u64); + // // Notify the user in the text channel + // if let Err(why) = instance + // .text_channel_id() + // .await + // .send_message(&instance.http().await, |message| { + // message.embed(|embed| { + // embed.title("Failed to connect to Spotify"); + // embed.description(why); + // embed.footer(|footer| footer.text("Please try again")); + // embed.color(Status::Error as u64); - embed - }); + // embed + // }); - message - }) - .await - { - error!("Failed to send error message: {:?}", why); - } + // message + // }) + // .await + // { + // error!("Failed to send error message: {:?}", why); + // } - break; - } + // break; + // } - // Sink requests playback to start/resume - IpcPacket::StartPlayback => { - check_result(track.play()); - } + // // Sink requests playback to start/resume + // IpcPacket::StartPlayback => { + // check_result(track.play()); + // } - // Sink requests playback to pause - IpcPacket::StopPlayback => { - check_result(track.pause()); - } + // // Sink requests playback to pause + // IpcPacket::StopPlayback => { + // check_result(track.pause()); + // } - // A new track has been set by the player - IpcPacket::TrackChange(track) => { - // Convert to SpotifyId - let track_id = SpotifyId::from_uri(&track).expect("to be a valid uri"); + // // A new track has been set by the player + // IpcPacket::TrackChange(track) => { + // // Convert to SpotifyId + // let track_id = SpotifyId::from_uri(&track).expect("to be a valid uri"); - let instance = instance.clone(); - let ctx = ctx.clone(); + // let instance = instance.clone(); + // let ctx = ctx.clone(); - // Fetch track info - // This is done in a separate task to avoid blocking the IPC handler - tokio::spawn(async move { - if let Err(why) = instance.update_track(&ctx, &owner_id, track_id).await { - error!("Failed to update track: {:?}", why); + // // Fetch track info + // // This is done in a separate task to avoid blocking the IPC handler + // tokio::spawn(async move { + // if let Err(why) = instance.update_track(&ctx, &owner_id, track_id).await { + // error!("Failed to update track: {:?}", why); - instance.player_stopped().await; - } - }); - } + // instance.player_stopped().await; + // } + // }); + // } - // The player has started playing a track - IpcPacket::Playing(track, position_ms, duration_ms) => { - // Convert to SpotifyId - let track_id = SpotifyId::from_uri(&track).expect("to be a valid uri"); + // // The player has started playing a track + // IpcPacket::Playing(track, position_ms, duration_ms) => { + // // Convert to SpotifyId + // let track_id = SpotifyId::from_uri(&track).expect("to be a valid uri"); - let was_none = instance - .update_playback(duration_ms, position_ms, true) - .await; + // let was_none = instance + // .update_playback(duration_ms, position_ms, true) + // .await; - if was_none { - // Stop player if update track fails - if let Err(why) = instance.update_track(&ctx, &owner_id, track_id).await { - error!("Failed to update track: {:?}", why); + // if was_none { + // // Stop player if update track fails + // if let Err(why) = instance.update_track(&ctx, &owner_id, track_id).await { + // error!("Failed to update track: {:?}", why); - instance.player_stopped().await; - return; - } - } - } + // instance.player_stopped().await; + // return; + // } + // } + // } - IpcPacket::Paused(track, position_ms, duration_ms) => { - instance.start_disconnect_timer().await; + // IpcPacket::Paused(track, position_ms, duration_ms) => { + // instance.start_disconnect_timer().await; - // Convert to SpotifyId - let track_id = SpotifyId::from_uri(&track).expect("to be a valid uri"); + // // Convert to SpotifyId + // let track_id = SpotifyId::from_uri(&track).expect("to be a valid uri"); - let was_none = instance - .update_playback(duration_ms, position_ms, false) - .await; + // let was_none = instance + // .update_playback(duration_ms, position_ms, false) + // .await; - if was_none { - // Stop player if update track fails + // if was_none { + // // Stop player if update track fails - if let Err(why) = instance.update_track(&ctx, &owner_id, track_id).await { - error!("Failed to update track: {:?}", why); + // if let Err(why) = instance.update_track(&ctx, &owner_id, track_id).await { + // error!("Failed to update track: {:?}", why); - instance.player_stopped().await; - return; - } - } - } + // instance.player_stopped().await; + // return; + // } + // } + // } - IpcPacket::Stopped => { - check_result(track.pause()); + // IpcPacket::Stopped => { + // check_result(track.pause()); - { - let mut inner = inner.write().await; - inner.playback_info.take(); - } + // { + // let mut inner = inner.write().await; + // inner.playback_info.take(); + // } - instance.start_disconnect_timer().await; - } + // instance.start_disconnect_timer().await; + // } - // Ignore other packets - _ => {} - } + // // Ignore other packets + // _ => {} + // } } // Clean up session @@ -456,15 +496,10 @@ impl SpoticordSession { } }); - // Inform the player process to connect to Spotify - if let Err(why) = client.send(IpcPacket::Connect(token, user.device_name)) { - error!("Failed to send IpcPacket::Connect packet: {:?}", why); - } - // Update inner client and track let mut inner = self.0.write().await; inner.track = Some(track_handle); - inner.client = Some(client); + inner.spirc = Some(spirc); Ok(()) } @@ -537,14 +572,6 @@ impl SpoticordSession { pbi.update_track_episode(spotify_id, track, episode); } - // Send track play event to metrics - #[cfg(feature = "metrics")] - { - if let Some(ref pbi) = inner.playback_info { - inner.metrics.track_play(pbi); - } - } - Ok(()) } @@ -552,15 +579,11 @@ impl SpoticordSession { async fn player_stopped(&self) { let mut inner = self.0.write().await; - if let Some(client) = inner.client.take() { - // Ask player to quit (will cause defunct process) - if let Err(why) = client.send(IpcPacket::Quit) { - error!("Failed to send quit packet: {:?}", why); - } + if let Some(spirc) = inner.spirc.take() { + spirc.shutdown(); } if let Some(track) = inner.track.take() { - // Stop the playback, and freeing the child handle, removing the defunct process if let Err(why) = track.stop() { error!("Failed to stop track: {:?}", why); } @@ -598,11 +621,7 @@ impl SpoticordSession { inner.disconnect_no_abort().await; } - // Stop the disconnect timer, if one is running - let mut inner = self.0.write().await; - if let Some(handle) = inner.disconnect_handle.take() { - handle.abort(); - } + self.stop_disconnect_timer().await; } // Update playback info (duration, position, playing state) @@ -613,25 +632,33 @@ impl SpoticordSession { pbi.is_none() }; - let mut inner = self.0.write().await; + { + let mut inner = self.0.write().await; - if is_none { - inner.playback_info = Some(PlaybackInfo::new(duration_ms, position_ms, playing)); - } else { - // Update position, duration and playback state - inner - .playback_info - .as_mut() - .expect("to contain a value") - .update_pos_dur(position_ms, duration_ms, playing); + if is_none { + inner.playback_info = Some(PlaybackInfo::new(duration_ms, position_ms, playing)); + } else { + // Update position, duration and playback state + inner + .playback_info + .as_mut() + .expect("to contain a value") + .update_pos_dur(position_ms, duration_ms, playing); + }; }; + if playing { + self.stop_disconnect_timer().await; + } + is_none } /// Start the disconnect timer, which will disconnect the bot from the voice channel after a /// certain amount of time async fn start_disconnect_timer(&self) { + self.stop_disconnect_timer().await; + let inner_arc = self.0.clone(); let mut inner = inner_arc.write().await; @@ -640,11 +667,6 @@ impl SpoticordSession { return; } - // Abort the previous timer, if one is running - if let Some(handle) = inner.disconnect_handle.take() { - handle.abort(); - } - inner.disconnect_handle = Some(tokio::spawn({ let inner = inner_arc.clone(); let instance = self.clone(); @@ -681,6 +703,15 @@ impl SpoticordSession { })); } + /// Stop the disconnect timer (if one is running) + async fn stop_disconnect_timer(&self) { + let mut inner = self.0.write().await; + if let Some(handle) = inner.disconnect_handle.take() { + handle.abort(); + } + } + + /// Disconnect from the VC and send a message to the text channel pub async fn disconnect_with_message(&self, content: &str) { { let mut inner = self.0.write().await; @@ -707,12 +738,7 @@ impl SpoticordSession { } // Finally we stop and remove the disconnect timer - let mut inner = self.0.write().await; - - // Stop the disconnect timer, if one is running - if let Some(handle) = inner.disconnect_handle.take() { - handle.abort(); - } + self.stop_disconnect_timer().await; } /* Inner getters */ @@ -767,15 +793,11 @@ impl InnerSpoticordSession { let mut call = self.call.lock().await; - if let Some(client) = self.client.take() { - // Ask player to quit (will cause defunct process) - if let Err(why) = client.send(IpcPacket::Quit) { - error!("Failed to send quit packet: {:?}", why); - } + if let Some(spirc) = self.spirc.take() { + spirc.shutdown(); } if let Some(track) = self.track.take() { - // Stop the playback, and freeing the child handle, removing the defunct process if let Err(why) = track.stop() { error!("Failed to stop track: {:?}", why); } From f46bbee3ce083ef60d96b7938c29cb106071dac8 Mon Sep 17 00:00:00 2001 From: DaXcess Date: Mon, 18 Sep 2023 14:03:16 +0200 Subject: [PATCH 2/8] I forgot a few --- Cargo.lock | 432 ++++++------------------------------------- Cargo.toml | 2 - src/audio/mod.rs | 84 +-------- src/ipc/mod.rs | 69 ------- src/ipc/packet.rs | 46 ----- src/main.rs | 1 - src/player/stream.rs | 5 +- src/session/mod.rs | 2 + 8 files changed, 65 insertions(+), 576 deletions(-) delete mode 100644 src/ipc/mod.rs delete mode 100644 src/ipc/packet.rs diff --git a/Cargo.lock b/Cargo.lock index aff5e5c..2f72e10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,7 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ "generic-array", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -135,7 +135,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi 0.1.19", "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -172,7 +172,7 @@ checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" dependencies = [ "addr2line", "cc", - "cfg-if 1.0.0", + "cfg-if", "libc", "miniz_oxide 0.6.2", "object", @@ -191,15 +191,6 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -254,12 +245,6 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -278,7 +263,7 @@ dependencies = [ "num-traits 0.2.15", "time 0.1.45", "wasm-bindgen", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -350,28 +335,7 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "crossbeam-channel" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" -dependencies = [ - "crossbeam-utils 0.7.2", - "maybe-uninit", -] - -[[package]] -name = "crossbeam-utils" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" -dependencies = [ - "autocfg", - "cfg-if 0.1.10", - "lazy_static", + "cfg-if", ] [[package]] @@ -380,7 +344,7 @@ version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -418,7 +382,7 @@ version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "hashbrown", "lock_api", "once_cell", @@ -478,7 +442,7 @@ version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -604,22 +568,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -dependencies = [ - "bitflags", - "fuchsia-zircon-sys", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" - [[package]] name = "futures" version = "0.3.28" @@ -691,23 +639,6 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" -[[package]] -name = "futures-test" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84af27744870a4a325fa342ce65a940dfba08957b260b790ec278c1d81490349" -dependencies = [ - "futures-core", - "futures-executor", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "futures-util", - "pin-project", - "pin-utils", -] - [[package]] name = "futures-util" version = "0.3.28" @@ -758,24 +689,13 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", @@ -892,7 +812,7 @@ checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" dependencies = [ "libc", "match_cfg", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1040,7 +960,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbc0fa01ffc752e9dbc72818cdb072cd028b86be5e09dd04c5a643704fe101a9" dependencies = [ "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1059,7 +979,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -1073,36 +993,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", -] - -[[package]] -name = "ipc-channel" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cb1d9211085f0ea6f1379d944b93c4d07e8207aa3bcf49f37eda12b85081887" -dependencies = [ - "bincode", - "crossbeam-channel", - "fnv", - "futures", - "futures-test", - "lazy_static", - "libc", - "mio 0.6.23", - "rand 0.7.3", - "serde", - "tempfile", - "uuid 0.8.2", - "winapi 0.3.9", -] - [[package]] name = "ipnet" version = "2.7.2" @@ -1117,7 +1007,7 @@ checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", - "rustix 0.37.19", + "rustix", "windows-sys 0.48.0", ] @@ -1136,16 +1026,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -1194,11 +1074,11 @@ dependencies = [ "log", "multimap", "nix", - "rand 0.8.5", + "rand", "socket2", "thiserror", "tokio", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1258,7 +1138,7 @@ dependencies = [ "librespot-protocol", "log", "protobuf", - "rand 0.8.5", + "rand", "serde", "serde_json", "tokio", @@ -1292,7 +1172,7 @@ dependencies = [ "pbkdf2", "priority-queue", "protobuf", - "rand 0.8.5", + "rand", "serde", "serde_json", "sha-1 0.9.8", @@ -1321,7 +1201,7 @@ dependencies = [ "libmdns", "librespot-core", "log", - "rand 0.8.5", + "rand", "serde_json", "sha-1 0.9.8", "thiserror", @@ -1358,7 +1238,7 @@ dependencies = [ "log", "ogg", "parking_lot", - "rand 0.8.5", + "rand", "rand_distr", "shell-words", "thiserror", @@ -1386,12 +1266,6 @@ dependencies = [ "cmake", ] -[[package]] -name = "linux-raw-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" - [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1420,7 +1294,7 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "generator", "scoped-tls", "serde", @@ -1444,12 +1318,6 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - [[package]] name = "memchr" version = "2.5.0" @@ -1499,25 +1367,6 @@ dependencies = [ "adler", ] -[[package]] -name = "mio" -version = "0.6.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" -dependencies = [ - "cfg-if 0.1.10", - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", - "libc", - "log", - "miow", - "net2", - "slab", - "winapi 0.2.8", -] - [[package]] name = "mio" version = "0.8.8" @@ -1529,18 +1378,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "miow" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" -dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", -] - [[package]] name = "multimap" version = "0.8.3" @@ -1556,7 +1393,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" dependencies = [ - "getrandom 0.2.10", + "getrandom", ] [[package]] @@ -1577,17 +1414,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "net2" -version = "0.2.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d0df99cfcd2530b2e694f6e17e7f37b8e26bb23983ac530c0c97408837c631" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "winapi 0.3.9", -] - [[package]] name = "nix" version = "0.23.2" @@ -1596,7 +1422,7 @@ checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" dependencies = [ "bitflags", "cc", - "cfg-if 1.0.0", + "cfg-if", "libc", "memoffset", ] @@ -1608,7 +1434,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ "overload", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1620,7 +1446,7 @@ dependencies = [ "autocfg", "num-integer", "num-traits 0.2.15", - "rand 0.8.5", + "rand", ] [[package]] @@ -1699,7 +1525,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" dependencies = [ "bitflags", - "cfg-if 1.0.0", + "cfg-if", "foreign-types", "libc", "once_cell", @@ -1768,13 +1594,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "backtrace", - "cfg-if 1.0.0", + "cfg-if", "libc", "petgraph", "redox_syscall 0.3.5", "smallvec", "thread-id", - "windows-targets 0.48.0", + "windows-targets", ] [[package]] @@ -1904,37 +1730,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "procfs" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de8dacb0873f77e6aefc6d71e044761fcc68060290f5b1089fcdf84626bb69" -dependencies = [ - "bitflags", - "byteorder", - "hex", - "lazy_static", - "rustix 0.36.14", -] - -[[package]] -name = "prometheus" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" -dependencies = [ - "cfg-if 1.0.0", - "fnv", - "lazy_static", - "libc", - "memchr", - "parking_lot", - "procfs", - "protobuf", - "reqwest", - "thiserror", -] - [[package]] name = "protobuf" version = "2.28.0" @@ -1969,19 +1764,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - [[package]] name = "rand" version = "0.8.5" @@ -1989,18 +1771,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "rand_chacha", + "rand_core", ] [[package]] @@ -2010,16 +1782,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", + "rand_core", ] [[package]] @@ -2028,7 +1791,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.10", + "getrandom", ] [[package]] @@ -2038,16 +1801,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits 0.2.15", - "rand 0.8.5", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", + "rand", ] [[package]] @@ -2157,7 +1911,7 @@ dependencies = [ "spin 0.5.2", "untrusted", "web-sys", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2169,7 +1923,7 @@ dependencies = [ "libc", "serde", "serde_json", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2187,20 +1941,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "0.36.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14e4d67015953998ad0eb82887a0eb0129e18a7e2f3b7b0f6c422fddcd503d62" -dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.1.4", - "windows-sys 0.45.0", -] - [[package]] name = "rustix" version = "0.37.19" @@ -2211,7 +1951,7 @@ dependencies = [ "errno", "io-lifetimes", "libc", - "linux-raw-sys 0.3.8", + "linux-raw-sys", "windows-sys 0.48.0", ] @@ -2424,7 +2164,7 @@ dependencies = [ "base64 0.13.1", "bitflags", "bytes", - "cfg-if 1.0.0", + "cfg-if", "command_attr", "dashmap", "flate2", @@ -2467,7 +2207,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.9.0", "opaque-debug", @@ -2479,7 +2219,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.10.7", ] @@ -2490,7 +2230,7 @@ version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.10.7", ] @@ -2550,7 +2290,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2570,7 +2310,7 @@ dependencies = [ "futures", "parking_lot", "pin-project", - "rand 0.8.5", + "rand", "serde", "serde_json", "serenity", @@ -2607,11 +2347,9 @@ version = "2.1.0" dependencies = [ "dotenv", "env_logger 0.10.0", - "ipc-channel", "lazy_static", "librespot", "log", - "prometheus", "reqwest", "samplerate", "serde", @@ -2636,7 +2374,7 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71664755c349abb0758fda6218fb2d2391ca2a73f9302c03b145491db4fcea29" dependencies = [ - "crossbeam-utils 0.8.15", + "crossbeam-utils", "futures-util", "loom", ] @@ -2689,10 +2427,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ "autocfg", - "cfg-if 1.0.0", + "cfg-if", "fastrand", "redox_syscall 0.3.5", - "rustix 0.37.19", + "rustix", "windows-sys 0.48.0", ] @@ -2733,7 +2471,7 @@ checksum = "3ee93aa2b8331c0fec9091548843f2c90019571814057da3b783f9de09349d73" dependencies = [ "libc", "redox_syscall 0.2.16", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2742,7 +2480,7 @@ version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "once_cell", ] @@ -2754,7 +2492,7 @@ checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2808,7 +2546,7 @@ dependencies = [ "autocfg", "bytes", "libc", - "mio 0.8.8", + "mio", "num_cpus", "parking_lot", "pin-project-lite", @@ -2897,7 +2635,7 @@ version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -2982,7 +2720,7 @@ dependencies = [ "http", "httparse", "log", - "rand 0.8.5", + "rand", "rustls 0.20.8", "sha-1 0.10.1", "thiserror", @@ -3079,7 +2817,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.10", + "getrandom", ] [[package]] @@ -3088,7 +2826,7 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" dependencies = [ - "getrandom 0.2.10", + "getrandom", ] [[package]] @@ -3136,12 +2874,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -3160,7 +2892,7 @@ version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen-macro", ] @@ -3185,7 +2917,7 @@ version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "wasm-bindgen", "web-sys", @@ -3262,12 +2994,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - [[package]] name = "winapi" version = "0.3.9" @@ -3278,12 +3004,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -3296,7 +3016,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -3311,7 +3031,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets 0.48.0", + "windows-targets", ] [[package]] @@ -3329,37 +3049,13 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets", ] [[package]] @@ -3467,17 +3163,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ - "winapi 0.3.9", -] - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -dependencies = [ - "winapi 0.2.8", - "winapi-build", + "winapi", ] [[package]] @@ -3488,7 +3174,7 @@ checksum = "e68bcb965d6c650091450b95cea12f07dcd299a01c15e2f9433b0813ea3c0886" dependencies = [ "aead", "poly1305", - "rand_core 0.6.4", + "rand_core", "salsa20", "subtle", "zeroize", diff --git a/Cargo.toml b/Cargo.toml index deb901b..0d2b83c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,11 +11,9 @@ path = "src/main.rs" [dependencies] dotenv = "0.15.0" env_logger = "0.10.0" -ipc-channel = { version = "0.16.0", features = ["async"] } lazy_static = { version = "1.4.0", optional = true } librespot = { version = "0.4.2", default-features = false } log = "0.4.17" -prometheus = { version = "0.13.3", optional = true, features = ["push", "process"] } reqwest = "0.11.18" samplerate = "0.2.4" serde = "1.0.163" diff --git a/src/audio/mod.rs b/src/audio/mod.rs index 0286273..dd06c1c 100644 --- a/src/audio/mod.rs +++ b/src/audio/mod.rs @@ -2,93 +2,11 @@ use librespot::playback::audio_backend::{Sink, SinkAsBytes, SinkError, SinkResul use librespot::playback::convert::Converter; use librespot::playback::decoder::AudioPacket; use log::error; -use std::io::{Stdout, Write}; +use std::io::Write; use tokio::sync::mpsc::UnboundedSender; -use crate::ipc; -use crate::ipc::packet::IpcPacket; use crate::player::stream::Stream; -pub struct StdoutSink { - client: ipc::Client, - output: Option>, -} - -impl StdoutSink { - pub fn new(client: ipc::Client) -> Self { - StdoutSink { - client, - output: None, - } - } -} - -impl Sink for StdoutSink { - fn start(&mut self) -> SinkResult<()> { - if let Err(why) = self.client.send(IpcPacket::StartPlayback) { - error!("Failed to send start playback packet: {}", why); - return Err(SinkError::ConnectionRefused(why.to_string())); - } - - self.output.get_or_insert(Box::new(std::io::stdout())); - - Ok(()) - } - - fn stop(&mut self) -> SinkResult<()> { - if let Err(why) = self.client.send(IpcPacket::StopPlayback) { - error!("Failed to send stop playback packet: {}", why); - return Err(SinkError::ConnectionRefused(why.to_string())); - } - - self - .output - .take() - .ok_or_else(|| SinkError::NotConnected("StdoutSink is not connected".to_string()))? - .flush() - .map_err(|why| SinkError::OnWrite(why.to_string()))?; - - Ok(()) - } - - fn write(&mut self, packet: AudioPacket, converter: &mut Converter) -> SinkResult<()> { - use zerocopy::AsBytes; - - if let AudioPacket::Samples(samples) = packet { - let samples_f32: &[f32] = &converter.f64_to_f32(&samples); - - let resampled = samplerate::convert( - 44100, - 48000, - 2, - samplerate::ConverterType::Linear, - samples_f32, - ) - .expect("to succeed"); - - let samples_i16 = - &converter.f64_to_s16(&resampled.iter().map(|v| *v as f64).collect::>()); - - self.write_bytes(samples_i16.as_bytes())?; - } - - Ok(()) - } -} - -impl SinkAsBytes for StdoutSink { - fn write_bytes(&mut self, data: &[u8]) -> SinkResult<()> { - self - .output - .as_deref_mut() - .ok_or_else(|| SinkError::NotConnected("StdoutSink is not connected".to_string()))? - .write_all(data) - .map_err(|why| SinkError::OnWrite(why.to_string()))?; - - Ok(()) - } -} - pub enum SinkEvent { Start, Stop, diff --git a/src/ipc/mod.rs b/src/ipc/mod.rs deleted file mode 100644 index 266e908..0000000 --- a/src/ipc/mod.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::sync::{Arc, Mutex}; - -use ipc_channel::ipc::{self, IpcError, IpcOneShotServer, IpcReceiver, IpcSender, TryRecvError}; - -use self::packet::IpcPacket; - -pub mod packet; - -pub struct Server { - tx: IpcOneShotServer>, - rx: IpcOneShotServer>, -} - -impl Server { - pub fn create() -> Result<(Self, String, String), IpcError> { - let (tx, tx_name) = IpcOneShotServer::new().map_err(IpcError::Io)?; - let (rx, rx_name) = IpcOneShotServer::new().map_err(IpcError::Io)?; - - Ok((Self { tx, rx }, tx_name, rx_name)) - } - - pub fn accept(self) -> Result { - let (_, tx) = self.tx.accept().map_err(IpcError::Bincode)?; - let (_, rx) = self.rx.accept().map_err(IpcError::Bincode)?; - - Ok(Client::new(tx, rx)) - } -} - -#[derive(Clone)] -pub struct Client { - tx: Arc>>, - rx: Arc>>, -} - -impl Client { - pub fn new(tx: IpcSender, rx: IpcReceiver) -> Client { - Client { - tx: Arc::new(Mutex::new(tx)), - rx: Arc::new(Mutex::new(rx)), - } - } - - pub fn connect(tx_name: impl Into, rx_name: impl Into) -> Result { - let (tx, remote_rx) = ipc::channel().map_err(IpcError::Io)?; - let (remote_tx, rx) = ipc::channel().map_err(IpcError::Io)?; - - let ttx = IpcSender::connect(tx_name.into()).map_err(IpcError::Io)?; - let trx = IpcSender::connect(rx_name.into()).map_err(IpcError::Io)?; - - ttx.send(remote_tx).map_err(IpcError::Bincode)?; - trx.send(remote_rx).map_err(IpcError::Bincode)?; - - Ok(Client::new(tx, rx)) - } - - pub fn send(&self, packet: IpcPacket) -> Result<(), IpcError> { - self - .tx - .lock() - .expect("to be able to lock") - .send(packet) - .map_err(IpcError::Bincode) - } - - pub fn try_recv(&self) -> Result { - self.rx.lock().expect("to be able to lock").try_recv() - } -} diff --git a/src/ipc/packet.rs b/src/ipc/packet.rs deleted file mode 100644 index e25631d..0000000 --- a/src/ipc/packet.rs +++ /dev/null @@ -1,46 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum IpcPacket { - /// Quit the player process - Quit, - - /// Connect to Spotify with the given token and device name - Connect(String, String), - - /// Disconnect from Spotify (unused) - Disconnect, - - /// Unable to connect to Spotify - ConnectError(String), - - /// The audio sink has started writing - StartPlayback, - - /// The audio sink has stopped writing - StopPlayback, - - /// The current Spotify track was changed - TrackChange(String), - - /// Spotify playback was started/resumed - Playing(String, u32, u32), - - /// Spotify playback was paused - Paused(String, u32, u32), - - /// Sent when the user has switched their Spotify device away from Spoticord - Stopped, - - /// Request the player to advance to the next track - Next, - - /// Request the player to go back to the previous track - Previous, - - /// Request the player to pause playback - Pause, - - /// Request the player to resume playback - Resume, -} diff --git a/src/main.rs b/src/main.rs index 90ff3dd..0b4e320 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,6 @@ mod audio; mod bot; mod consts; mod database; -mod ipc; mod librespot_ext; mod player; mod session; diff --git a/src/player/stream.rs b/src/player/stream.rs index e82e36a..8b3972d 100644 --- a/src/player/stream.rs +++ b/src/player/stream.rs @@ -5,7 +5,8 @@ use std::{ use songbird::input::reader::MediaSource; -const MAX_SIZE: usize = 1 * 1024 * 1024; +// TODO: Find optimal value +const MAX_SIZE: usize = 1024 * 1024; #[derive(Clone)] pub struct Stream { @@ -45,7 +46,7 @@ impl Write for Stream { let mut buffer = mutex.lock().expect("Mutex was poisoned"); while buffer.len() + buf.len() > MAX_SIZE { - buffer = condvar.wait(buffer).unwrap(); + buffer = condvar.wait(buffer).expect("Mutex was poisoned"); } buffer.extend_from_slice(buf); diff --git a/src/session/mod.rs b/src/session/mod.rs index 9210903..7dbe91c 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -764,6 +764,7 @@ impl SpoticordSession { } /// Get the channel id + #[allow(dead_code)] pub async fn text_channel_id(&self) -> ChannelId { self.0.read().await.text_channel_id } @@ -777,6 +778,7 @@ impl SpoticordSession { self.0.read().await.call.clone() } + #[allow(dead_code)] pub async fn http(&self) -> Arc { self.0.read().await.http.clone() } From 5154c220bf809ad8103adc66656fb94a9ce7b73f Mon Sep 17 00:00:00 2001 From: DaXcess Date: Mon, 18 Sep 2023 14:05:29 +0200 Subject: [PATCH 3/8] Update deps --- Cargo.lock | 764 +++++++++++++++++++++++++---------------------------- 1 file changed, 357 insertions(+), 407 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f72e10..e94299f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -72,9 +72,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" dependencies = [ "memchr", ] @@ -96,19 +96,19 @@ dependencies = [ [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.37", ] [[package]] @@ -124,7 +124,7 @@ dependencies = [ "tokio", "tokio-rustls 0.23.4", "tungstenite", - "webpki-roots", + "webpki-roots 0.22.6", ] [[package]] @@ -166,15 +166,15 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", "cfg-if", "libc", - "miniz_oxide 0.6.2", + "miniz_oxide", "object", "rustc-demangle", ] @@ -187,9 +187,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.2" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "bitflags" @@ -197,6 +197,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + [[package]] name = "block-buffer" version = "0.9.0" @@ -217,15 +223,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bytemuck" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" [[package]] name = "byteorder" @@ -235,15 +241,18 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -253,17 +262,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", - "num-traits 0.2.15", - "time 0.1.45", + "num-traits 0.2.16", "wasm-bindgen", - "winapi", + "windows-targets", ] [[package]] @@ -295,9 +303,9 @@ dependencies = [ [[package]] name = "command_attr" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d999d4e7731150ee14aee8f619c7a9aa9a4385bca0606c4fa95aa2f36a05d9a" +checksum = "07b787d19b9806dd4c9c34b2b4147d1a61d6120d93ee289521ab9b0294d198e4" dependencies = [ "proc-macro2", "quote", @@ -322,9 +330,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -340,9 +348,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] @@ -378,18 +386,27 @@ dependencies = [ [[package]] name = "dashmap" -version = "5.4.0" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown", + "hashbrown 0.14.0", "lock_api", "once_cell", "parking_lot_core", "serde", ] +[[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +dependencies = [ + "serde", +] + [[package]] name = "derivative" version = "2.2.0" @@ -438,9 +455,9 @@ checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] @@ -480,14 +497,20 @@ dependencies = [ ] [[package]] -name = "errno" -version = "0.3.1" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -502,12 +525,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "fixedbitset" @@ -517,12 +537,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" dependencies = [ "crc32fast", - "miniz_oxide 0.7.1", + "miniz_oxide", ] [[package]] @@ -624,7 +644,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.37", ] [[package]] @@ -659,9 +679,9 @@ dependencies = [ [[package]] name = "generator" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e123d9ae7c02966b4d892e550bdc32164f05853cd40ab570650ad600596a8a" +checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" dependencies = [ "cc", "libc", @@ -698,15 +718,15 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "gimli" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "glob" @@ -716,9 +736,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "h2" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ "bytes", "fnv", @@ -726,7 +746,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -740,13 +760,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] -name = "headers" -version = "0.3.8" +name = "hashbrown" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64 0.13.1", - "bitflags", + "base64 0.21.4", "bytes", "headers-core", "http", @@ -775,18 +800,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -845,9 +861,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" @@ -857,9 +873,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.26" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes", "futures-channel", @@ -872,7 +888,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -896,15 +912,16 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7" +checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" dependencies = [ + "futures-util", "http", "hyper", - "rustls 0.21.1", + "rustls 0.21.7", "tokio", - "tokio-rustls 0.24.0", + "tokio-rustls 0.24.1", ] [[package]] @@ -970,58 +987,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", ] [[package]] -name = "instant" -version = "0.1.12" +name = "indexmap" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.1", - "libc", - "windows-sys 0.48.0", + "equivalent", + "hashbrown 0.14.0", ] [[package]] name = "ipnet" -version = "2.7.2" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" [[package]] name = "is-terminal" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", + "hermit-abi 0.3.2", "rustix", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -1051,9 +1057,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.146" +version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] name = "libm" @@ -1075,7 +1081,7 @@ dependencies = [ "multimap", "nix", "rand", - "socket2", + "socket2 0.4.9", "thiserror", "tokio", "winapi", @@ -1167,7 +1173,7 @@ dependencies = [ "log", "num-bigint", "num-integer", - "num-traits 0.2.15", + "num-traits 0.2.16", "once_cell", "pbkdf2", "priority-queue", @@ -1182,7 +1188,7 @@ dependencies = [ "tokio-stream", "tokio-util", "url", - "uuid 1.3.3", + "uuid 1.4.1", "vergen", ] @@ -1268,9 +1274,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.3.8" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" [[package]] name = "lock_api" @@ -1284,9 +1290,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.18" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "loom" @@ -1315,14 +1321,14 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "regex-automata", + "regex-automata 0.1.10", ] [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "memoffset" @@ -1349,15 +1355,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "miniz_oxide" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.7.1" @@ -1374,8 +1371,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", + "wasi", + "windows-sys", ] [[package]] @@ -1420,7 +1417,7 @@ version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cc", "cfg-if", "libc", @@ -1439,13 +1436,13 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", - "num-traits 0.2.15", + "num-traits 0.2.16", "rand", ] @@ -1456,7 +1453,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", - "num-traits 0.2.15", + "num-traits 0.2.16", ] [[package]] @@ -1465,14 +1462,14 @@ version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" dependencies = [ - "num-traits 0.2.15", + "num-traits 0.2.16", ] [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", "libm", @@ -1480,19 +1477,19 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi 0.3.2", "libc", ] [[package]] name = "object" -version = "0.30.4" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] @@ -1520,11 +1517,11 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.54" +version = "0.10.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" +checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" dependencies = [ - "bitflags", + "bitflags 2.4.0", "cfg-if", "foreign-types", "libc", @@ -1541,7 +1538,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.37", ] [[package]] @@ -1552,9 +1549,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.88" +version = "0.9.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" +checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" dependencies = [ "cc", "libc", @@ -1568,7 +1565,7 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" dependencies = [ - "num-traits 0.2.15", + "num-traits 0.2.16", ] [[package]] @@ -1621,39 +1618,39 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "petgraph" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 2.0.0", ] [[package]] name = "pin-project" -version = "1.1.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.37", ] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -1713,19 +1710,19 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "priority-queue" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca9c6be70d989d21a136eb86c2d83e4b328447fac4a88dace2143c179c86267" +checksum = "fff39edfcaec0d64e8d0da38564fad195d2d51b680940295fcc307366e101e61" dependencies = [ "autocfg", - "indexmap", + "indexmap 1.9.3", ] [[package]] name = "proc-macro2" -version = "1.0.60" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] @@ -1757,9 +1754,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.28" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -1800,7 +1797,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ - "num-traits 0.2.15", + "num-traits 0.2.16", "rand", ] @@ -1810,7 +1807,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1819,18 +1816,19 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.8.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.2", + "regex-automata 0.3.8", + "regex-syntax 0.7.5", ] [[package]] @@ -1842,6 +1840,17 @@ dependencies = [ "regex-syntax 0.6.29", ] +[[package]] +name = "regex-automata" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.5", +] + [[package]] name = "regex-syntax" version = "0.6.29" @@ -1850,17 +1859,17 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "reqwest" -version = "0.11.18" +version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "bytes", "encoding_rs", "futures-core", @@ -1880,14 +1889,14 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.1", + "rustls 0.21.7", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "tokio", "tokio-native-tls", - "tokio-rustls 0.24.0", + "tokio-rustls 0.24.1", "tokio-util", "tower-service", "url", @@ -1895,7 +1904,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots", + "webpki-roots 0.25.2", "winreg", ] @@ -1943,23 +1952,22 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.19" +version = "0.38.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" dependencies = [ - "bitflags", + "bitflags 2.4.0", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] name = "rustls" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" dependencies = [ "log", "ring", @@ -1969,9 +1977,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.1" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ "log", "ring", @@ -1981,18 +1989,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", ] [[package]] name = "rustls-webpki" -version = "0.100.1" +version = "0.101.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed" dependencies = [ "ring", "untrusted", @@ -2000,15 +2008,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "salsa20" @@ -2031,11 +2039,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -2046,9 +2054,9 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" @@ -2062,11 +2070,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -2075,9 +2083,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -2085,15 +2093,15 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.164" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] @@ -2110,20 +2118,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.164" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.37", ] [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", @@ -2132,13 +2140,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.12" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.37", ] [[package]] @@ -2155,14 +2163,14 @@ dependencies = [ [[package]] name = "serenity" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82fd5e7b5858ad96e99d440138f34f5b98e1b959ebcd3a1036203b30e78eb788" +checksum = "d007dc45584ecc47e791f2a9a7cf17bf98ac386728106f111159c846d624be3f" dependencies = [ "async-trait", "async-tungstenite", "base64 0.13.1", - "bitflags", + "bitflags 1.3.2", "bytes", "cfg-if", "command_attr", @@ -2179,7 +2187,7 @@ dependencies = [ "serde-value", "serde_json", "static_assertions", - "time 0.3.22", + "time", "tokio", "tracing", "typemap_rev", @@ -2193,7 +2201,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be3aec8849ca2fde1e8a5dfbed96fbd68e9b5f4283fbe277d8694ce811d4952" dependencies = [ - "bitflags", + "bitflags 1.3.2", "enum_primitive", "serde", "serde_json", @@ -2270,18 +2278,18 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "socket2" @@ -2293,6 +2301,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "songbird" version = "0.3.2" @@ -2357,7 +2375,7 @@ dependencies = [ "serenity", "songbird", "thiserror", - "time 0.3.22", + "time", "tokio", "zerocopy", ] @@ -2392,7 +2410,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c73eb88fee79705268cc7b742c7bc93a7b76e092ab751d0833866970754142" dependencies = [ "arrayvec", - "bitflags", + "bitflags 1.3.2", "bytemuck", "lazy_static", "log", @@ -2411,9 +2429,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.18" +version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", @@ -2422,16 +2440,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.6.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ - "autocfg", "cfg-if", "fastrand", "redox_syscall 0.3.5", "rustix", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -2445,29 +2462,29 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.37", ] [[package]] name = "thread-id" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee93aa2b8331c0fec9091548843f2c90019571814057da3b783f9de09349d73" +checksum = "79474f573561cdc4871a0de34a51c92f7f5a56039113fbb5b9c9f96bdb756669" dependencies = [ "libc", "redox_syscall 0.2.16", @@ -2486,21 +2503,11 @@ dependencies = [ [[package]] name = "time" -version = "0.1.45" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" +checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" dependencies = [ + "deranged", "itoa", "serde", "time-core", @@ -2515,9 +2522,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" dependencies = [ "time-core", ] @@ -2539,11 +2546,11 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.2" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ - "autocfg", + "backtrace", "bytes", "libc", "mio", @@ -2551,9 +2558,9 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.4", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -2564,7 +2571,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.37", ] [[package]] @@ -2583,18 +2590,18 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls 0.20.8", + "rustls 0.20.9", "tokio", "webpki", ] [[package]] name = "tokio-rustls" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.1", + "rustls 0.21.7", "tokio", ] @@ -2644,13 +2651,13 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.37", ] [[package]] @@ -2721,7 +2728,7 @@ dependencies = [ "httparse", "log", "rand", - "rustls 0.20.8", + "rustls 0.20.9", "sha-1 0.10.1", "thiserror", "url", @@ -2737,15 +2744,15 @@ checksum = "ed5b74f0a24b5454580a79abb6994393b09adf0ab8070f15827cb666255de155" [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicase" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ "version_check", ] @@ -2758,9 +2765,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -2795,9 +2802,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", @@ -2822,9 +2829,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.3.3" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ "getrandom", ] @@ -2853,7 +2860,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7141e445af09c8919f1d5f8a20dae0b20c3b57a45dee0d5823c6ed5d237f15a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "chrono", "rustc_version", ] @@ -2866,20 +2873,13 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2888,9 +2888,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2898,24 +2898,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.37", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", @@ -2925,9 +2925,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2935,28 +2935,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.37", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-streams" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bbae3363c08332cadccd13b67db371814cd214c2524020932f0804b8cf7c078" +checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" dependencies = [ "futures-util", "js-sys", @@ -2967,9 +2967,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -2977,9 +2977,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e" dependencies = [ "ring", "untrusted", @@ -2994,6 +2994,12 @@ dependencies = [ "webpki", ] +[[package]] +name = "webpki-roots" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" + [[package]] name = "winapi" version = "0.3.9" @@ -3034,21 +3040,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -3060,110 +3051,69 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winreg" -version = "0.10.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "winapi", + "cfg-if", + "windows-sys", ] [[package]] @@ -3182,9 +3132,9 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.6.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332f188cc1bcf1fe1064b8c58d150f497e697f49774aa846f2dc949d9a25f236" +checksum = "20707b61725734c595e840fb3704378a0cd2b9c74cc9e6e20724838fc6a1e2f9" dependencies = [ "byteorder", "zerocopy-derive", @@ -3192,13 +3142,13 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.3.2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6505e6815af7de1746a08f69c69606bb45695a17149517680f3b2149713b19a3" +checksum = "56097d5b91d711293a42be9289403896b68654625021732067eac7a4ca388a1f" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.37", ] [[package]] From 2e273cdcded24d76ba21d9b5c2c9029a4856d11a Mon Sep 17 00:00:00 2001 From: DaXcess Date: Mon, 18 Sep 2023 22:39:11 +0200 Subject: [PATCH 4/8] Almost ready --- .github/FUNDING.yml | 1 - Cargo.lock | 27 +++- Cargo.toml | 23 +-- src/audio/mod.rs | 12 +- src/{player => audio}/stream.rs | 22 ++- src/bot/commands/core/version.rs | 2 +- src/bot/commands/music/join.rs | 2 +- src/main.rs | 2 + src/player/mod.rs | 22 ++- src/session/manager.rs | 13 ++ src/session/mod.rs | 244 ++++++++++++------------------- 11 files changed, 189 insertions(+), 181 deletions(-) delete mode 100644 .github/FUNDING.yml rename src/{player => audio}/stream.rs (70%) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 07fc7c3..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -patreon: rodabafilms \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index e94299f..0c836ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ "shell-words", "thiserror", "tokio", - "zerocopy", + "zerocopy 0.6.4", ] [[package]] @@ -2377,7 +2377,7 @@ dependencies = [ "thiserror", "time", "tokio", - "zerocopy", + "zerocopy 0.7.5", ] [[package]] @@ -3137,7 +3137,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20707b61725734c595e840fb3704378a0cd2b9c74cc9e6e20724838fc6a1e2f9" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.6.4", +] + +[[package]] +name = "zerocopy" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870cdd4b8b867698aea998d95bcc06c1d75fe566267781ee6f5ae8c9c45a3930" +dependencies = [ + "byteorder", + "zerocopy-derive 0.7.5", ] [[package]] @@ -3151,6 +3161,17 @@ dependencies = [ "syn 2.0.37", ] +[[package]] +name = "zerocopy-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c6f95fa5657518b36c6784ba7cdd89e8bdf9a16e58266085248bfb950860c5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + [[package]] name = "zeroize" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index 0d2b83c..5f7231f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,14 +13,19 @@ dotenv = "0.15.0" env_logger = "0.10.0" lazy_static = { version = "1.4.0", optional = true } librespot = { version = "0.4.2", default-features = false } -log = "0.4.17" -reqwest = "0.11.18" +log = "0.4.20" +reqwest = "0.11.20" samplerate = "0.2.4" -serde = "1.0.163" -serde_json = "1.0.96" -serenity = { version = "0.11.5", features = ["framework", "cache", "standard_framework"], default-features = false } +serde = "1.0.188" +serde_json = "1.0.107" +serenity = { version = "0.11.6", features = ["framework", "cache", "standard_framework"], default-features = false } songbird = "0.3.2" -thiserror = "1.0.40" -time = "0.3.21" -tokio = { version = "1.28.1", features = ["rt", "full"] } -zerocopy = "0.6.1" +thiserror = "1.0.48" +time = "0.3.28" +tokio = { version = "1.32.0", features = ["rt", "full"] } +zerocopy = "0.7.5" + +[profile.release] +opt-level = 3 +lto = true +debug = true \ No newline at end of file diff --git a/src/audio/mod.rs b/src/audio/mod.rs index dd06c1c..d0cdd40 100644 --- a/src/audio/mod.rs +++ b/src/audio/mod.rs @@ -1,3 +1,7 @@ +pub mod stream; + +use self::stream::Stream; + use librespot::playback::audio_backend::{Sink, SinkAsBytes, SinkError, SinkResult}; use librespot::playback::convert::Converter; use librespot::playback::decoder::AudioPacket; @@ -5,8 +9,6 @@ use log::error; use std::io::Write; use tokio::sync::mpsc::UnboundedSender; -use crate::player::stream::Stream; - pub enum SinkEvent { Start, Stop, @@ -26,6 +28,8 @@ impl StreamSink { impl Sink for StreamSink { fn start(&mut self) -> SinkResult<()> { if let Err(why) = self.sender.send(SinkEvent::Start) { + // WARNING: Returning an error causes librespot-playback to exit the process with status 1 + error!("Failed to send start playback event: {why}"); return Err(SinkError::ConnectionRefused(why.to_string())); } @@ -35,10 +39,14 @@ impl Sink for StreamSink { fn stop(&mut self) -> SinkResult<()> { if let Err(why) = self.sender.send(SinkEvent::Stop) { + // WARNING: Returning an error causes librespot-playback to exit the process with status 1 + error!("Failed to send start playback event: {why}"); return Err(SinkError::ConnectionRefused(why.to_string())); } + self.stream.flush().ok(); + Ok(()) } diff --git a/src/player/stream.rs b/src/audio/stream.rs similarity index 70% rename from src/player/stream.rs rename to src/audio/stream.rs index 8b3972d..1f38215 100644 --- a/src/player/stream.rs +++ b/src/audio/stream.rs @@ -5,8 +5,10 @@ use std::{ use songbird::input::reader::MediaSource; -// TODO: Find optimal value -const MAX_SIZE: usize = 1024 * 1024; +/// The lower the value, the less latency +/// +/// Too low of a value results in unpredictable audio +const MAX_SIZE: usize = 32 * 1024; #[derive(Clone)] pub struct Stream { @@ -26,15 +28,19 @@ impl Read for Stream { let (mutex, condvar) = &*self.inner; let mut buffer = mutex.lock().expect("Mutex was poisoned"); - log::trace!("Read!"); + // Prevent Discord jitter by filling buffer with zeroes if we don't have any audio + // (i.e. when you skip too far ahead in a song which hasn't been downloaded yet) + if buffer.is_empty() { + buf.fill(0); + condvar.notify_all(); - while buffer.is_empty() { - buffer = condvar.wait(buffer).expect("Mutex was poisoned"); + return Ok(buf.len()); } let max_read = usize::min(buf.len(), buffer.len()); buf[0..max_read].copy_from_slice(&buffer[0..max_read]); buffer.drain(0..max_read); + condvar.notify_all(); Ok(max_read) } @@ -56,6 +62,12 @@ impl Write for Stream { } fn flush(&mut self) -> std::io::Result<()> { + let (mutex, condvar) = &*self.inner; + let mut buffer = mutex.lock().expect("Mutex was poisoned"); + + buffer.clear(); + condvar.notify_all(); + Ok(()) } } diff --git a/src/bot/commands/core/version.rs b/src/bot/commands/core/version.rs index 141d89e..8292870 100644 --- a/src/bot/commands/core/version.rs +++ b/src/bot/commands/core/version.rs @@ -25,7 +25,7 @@ pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandO author .name("Maintained by: RoDaBaFilms") .url("https://rodabafilms.com/") - .icon_url("https://rodabafilms.com/logo_2021_nobg.png") + .icon_url("https://cdn.discordapp.com/avatars/389786424142200835/6bfe3840b0aa6a1baf432bb251b70c9f.webp?size=128") }) .description(format!("Current version: {}\n\nSpoticord is open source, check out [our GitHub](https://github.com/SpoticordMusic)", VERSION)) .color(Status::Info as u64) diff --git a/src/bot/commands/music/join.rs b/src/bot/commands/music/join.rs index 65aa10f..2816779 100644 --- a/src/bot/commands/music/join.rs +++ b/src/bot/commands/music/join.rs @@ -338,7 +338,7 @@ pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandO .title("Connected to voice channel") .icon_url("https://spoticord.com/speaker.png") .description(format!("Come listen along in <#{}>", channel_id)) - .footer("Spotify will automatically start playing on Spoticord") + .footer("You must manually go to Spotify and select your device") .status(Status::Info) .build(), ) diff --git a/src/main.rs b/src/main.rs index 0b4e320..330ab66 100644 --- a/src/main.rs +++ b/src/main.rs @@ -91,6 +91,7 @@ async fn main() { _ = tokio::signal::ctrl_c() => { info!("Received interrupt signal, shutting down..."); + session_manager.shutdown().await; shard_manager.lock().await.shutdown_all().await; break; @@ -110,6 +111,7 @@ async fn main() { }, if term.is_some() => { info!("Received terminate signal, shutting down..."); + session_manager.shutdown().await; shard_manager.lock().await.shutdown_all().await; break; diff --git a/src/player/mod.rs b/src/player/mod.rs index d78dcb5..faa5539 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -1,8 +1,9 @@ -pub mod stream; - use librespot::{ connect::spirc::Spirc, - core::{config::ConnectConfig, session::Session}, + core::{ + config::{ConnectConfig, SessionConfig}, + session::Session, + }, discovery::Credentials, playback::{ config::{Bitrate, PlayerConfig, VolumeCtrl}, @@ -13,13 +14,11 @@ use librespot::{ use tokio::sync::mpsc::UnboundedReceiver; use crate::{ - audio::{SinkEvent, StreamSink}, + audio::{stream::Stream, SinkEvent, StreamSink}, librespot_ext::discovery::CredentialsExt, utils, }; -use self::stream::Stream; - pub struct Player { stream: Stream, session: Option, @@ -59,7 +58,16 @@ impl Player { } // Connect the session - let (session, _) = Session::connect(Default::default(), credentials, None, false).await?; + let (session, _) = Session::connect( + SessionConfig { + ap_port: Some(9999), // Force the use of ap.spotify.com, which has the lowest latency + ..Default::default() + }, + credentials, + None, + false, + ) + .await?; self.session = Some(session.clone()); let mixer = (mixer::find(Some("softvol")).expect("to exist"))(MixerConfig { diff --git a/src/session/manager.rs b/src/session/manager.rs index acaef69..4639bc3 100644 --- a/src/session/manager.rs +++ b/src/session/manager.rs @@ -115,6 +115,10 @@ impl InnerSessionManager { count } + + pub fn sessions(&self) -> Vec { + self.sessions.values().cloned().collect() + } } impl SessionManager { @@ -183,4 +187,13 @@ impl SessionManager { pub async fn get_active_session_count(&self) -> usize { self.0.read().await.get_active_session_count().await } + + /// Tell all sessions to instantly shut down + pub async fn shutdown(&self) { + let sessions = self.0.read().await.sessions(); + + for session in sessions { + session.disconnect().await; + } + } } diff --git a/src/session/mod.rs b/src/session/mod.rs index 7dbe91c..92a2b6b 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -30,8 +30,13 @@ use songbird::{ tracks::TrackHandle, Call, Event, EventContext, EventHandler, }; -use std::{sync::Arc, time::Duration}; -use tokio::sync::Mutex; +use std::{ + io::Write, + ops::{Deref, DerefMut}, + sync::Arc, + time::Duration, +}; +use tokio::sync::{Mutex, RwLockReadGuard, RwLockWriteGuard}; #[derive(Clone)] pub struct SpoticordSession(Arc>); @@ -55,6 +60,8 @@ struct InnerSpoticordSession { spirc: Option, + player: Option, + /// Whether the session has been disconnected /// If this is true then this instance should no longer be used and dropped disconnected: bool, @@ -97,6 +104,7 @@ impl SpoticordSession { playback_info: None, disconnect_handle: None, spirc: None, + player: None, disconnected: false, }; @@ -132,14 +140,13 @@ impl SpoticordSession { .clone(); { - let mut inner = self.0.write().await; + let mut inner = self.acquire_write().await; inner.owner = Some(owner_id); } { - let inner = self.0.clone(); - let inner = inner.read().await; - session_manager.set_owner(owner_id, inner.guild_id).await; + let guild_id = self.acquire_read().await.guild_id; + session_manager.set_owner(owner_id, guild_id).await; } // Create the player @@ -150,28 +157,28 @@ impl SpoticordSession { /// Advance to the next track pub async fn next(&mut self) { - if let Some(ref spirc) = self.0.read().await.spirc { + if let Some(ref spirc) = self.acquire_read().await.spirc { spirc.next(); } } /// Rewind to the previous track pub async fn previous(&mut self) { - if let Some(ref spirc) = self.0.read().await.spirc { + if let Some(ref spirc) = self.acquire_read().await.spirc { spirc.prev(); } } /// Pause the current track pub async fn pause(&mut self) { - if let Some(ref spirc) = self.0.read().await.spirc { + if let Some(ref spirc) = self.acquire_read().await.spirc { spirc.pause(); } } /// Resume the current track pub async fn resume(&mut self) { - if let Some(ref spirc) = self.0.read().await.spirc { + if let Some(ref spirc) = self.acquire_read().await.spirc { spirc.play(); } } @@ -237,8 +244,7 @@ impl SpoticordSession { } }; - // Handle IPC packets - // This will automatically quit once the IPC connection is closed + // Handle events tokio::spawn({ let track = track_handle.clone(); let ctx = ctx.clone(); @@ -367,126 +373,16 @@ impl SpoticordSession { } SinkEvent::Stop => { - check_result(track.pause()); + // EXPERIMENT: It may be beneficial to *NOT* pause songbird here + // We already have a fallback if no audio is present in the buffer (write all zeroes aka silence) + // So commenting this out may help prevent a substantial portion of jitter + // This comes at a cost of more bandwidth, though opus should compress it down to almost nothing + + // check_result(track.pause()); } } } }; - - // match event { - // // Session connect error - // IpcPacket::ConnectError(why) => { - // error!("Failed to connect to Spotify: {:?}", why); - - // // Notify the user in the text channel - // if let Err(why) = instance - // .text_channel_id() - // .await - // .send_message(&instance.http().await, |message| { - // message.embed(|embed| { - // embed.title("Failed to connect to Spotify"); - // embed.description(why); - // embed.footer(|footer| footer.text("Please try again")); - // embed.color(Status::Error as u64); - - // embed - // }); - - // message - // }) - // .await - // { - // error!("Failed to send error message: {:?}", why); - // } - - // break; - // } - - // // Sink requests playback to start/resume - // IpcPacket::StartPlayback => { - // check_result(track.play()); - // } - - // // Sink requests playback to pause - // IpcPacket::StopPlayback => { - // check_result(track.pause()); - // } - - // // A new track has been set by the player - // IpcPacket::TrackChange(track) => { - // // Convert to SpotifyId - // let track_id = SpotifyId::from_uri(&track).expect("to be a valid uri"); - - // let instance = instance.clone(); - // let ctx = ctx.clone(); - - // // Fetch track info - // // This is done in a separate task to avoid blocking the IPC handler - // tokio::spawn(async move { - // if let Err(why) = instance.update_track(&ctx, &owner_id, track_id).await { - // error!("Failed to update track: {:?}", why); - - // instance.player_stopped().await; - // } - // }); - // } - - // // The player has started playing a track - // IpcPacket::Playing(track, position_ms, duration_ms) => { - // // Convert to SpotifyId - // let track_id = SpotifyId::from_uri(&track).expect("to be a valid uri"); - - // let was_none = instance - // .update_playback(duration_ms, position_ms, true) - // .await; - - // if was_none { - // // Stop player if update track fails - // if let Err(why) = instance.update_track(&ctx, &owner_id, track_id).await { - // error!("Failed to update track: {:?}", why); - - // instance.player_stopped().await; - // return; - // } - // } - // } - - // IpcPacket::Paused(track, position_ms, duration_ms) => { - // instance.start_disconnect_timer().await; - - // // Convert to SpotifyId - // let track_id = SpotifyId::from_uri(&track).expect("to be a valid uri"); - - // let was_none = instance - // .update_playback(duration_ms, position_ms, false) - // .await; - - // if was_none { - // // Stop player if update track fails - - // if let Err(why) = instance.update_track(&ctx, &owner_id, track_id).await { - // error!("Failed to update track: {:?}", why); - - // instance.player_stopped().await; - // return; - // } - // } - // } - - // IpcPacket::Stopped => { - // check_result(track.pause()); - - // { - // let mut inner = inner.write().await; - // inner.playback_info.take(); - // } - - // instance.start_disconnect_timer().await; - // } - - // // Ignore other packets - // _ => {} - // } } // Clean up session @@ -497,9 +393,10 @@ impl SpoticordSession { }); // Update inner client and track - let mut inner = self.0.write().await; + let mut inner = self.acquire_write().await; inner.track = Some(track_handle); inner.spirc = Some(spirc); + inner.player = Some(player); Ok(()) } @@ -566,7 +463,7 @@ impl SpoticordSession { } // Update track/episode - let mut inner = self.0.write().await; + let mut inner = self.acquire_write().await; if let Some(pbi) = inner.playback_info.as_mut() { pbi.update_track_episode(spotify_id, track, episode); @@ -577,7 +474,7 @@ impl SpoticordSession { /// Called when the player must stop, but not leave the call async fn player_stopped(&self) { - let mut inner = self.0.write().await; + let mut inner = self.acquire_write().await; if let Some(spirc) = inner.spirc.take() { spirc.shutdown(); @@ -617,7 +514,7 @@ impl SpoticordSession { // read lock to read the current owner. // This would deadlock if we have an active write lock { - let mut inner = self.0.write().await; + let mut inner = self.acquire_write().await; inner.disconnect_no_abort().await; } @@ -633,7 +530,7 @@ impl SpoticordSession { }; { - let mut inner = self.0.write().await; + let mut inner = self.acquire_write().await; if is_none { inner.playback_info = Some(PlaybackInfo::new(duration_ms, position_ms, playing)); @@ -659,8 +556,8 @@ impl SpoticordSession { async fn start_disconnect_timer(&self) { self.stop_disconnect_timer().await; - let inner_arc = self.0.clone(); - let mut inner = inner_arc.write().await; + let arc_handle = self.0.clone(); + let mut inner = self.acquire_write().await; // Check if we are already disconnected if inner.disconnected { @@ -668,7 +565,7 @@ impl SpoticordSession { } inner.disconnect_handle = Some(tokio::spawn({ - let inner = inner_arc.clone(); + let inner = arc_handle.clone(); let instance = self.clone(); async move { @@ -705,7 +602,7 @@ impl SpoticordSession { /// Stop the disconnect timer (if one is running) async fn stop_disconnect_timer(&self) { - let mut inner = self.0.write().await; + let mut inner = self.acquire_write().await; if let Some(handle) = inner.disconnect_handle.take() { handle.abort(); } @@ -714,7 +611,7 @@ impl SpoticordSession { /// Disconnect from the VC and send a message to the text channel pub async fn disconnect_with_message(&self, content: &str) { { - let mut inner = self.0.write().await; + let mut inner = self.acquire_write().await; // Firstly we disconnect inner.disconnect_no_abort().await; @@ -745,48 +642,97 @@ impl SpoticordSession { /// Get the owner pub async fn owner(&self) -> Option { - self.0.read().await.owner + self.acquire_read().await.owner } /// Get the session manager pub async fn session_manager(&self) -> SessionManager { - self.0.read().await.session_manager.clone() + self.acquire_read().await.session_manager.clone() } /// Get the guild id pub async fn guild_id(&self) -> GuildId { - self.0.read().await.guild_id + self.acquire_read().await.guild_id } /// Get the channel id pub async fn channel_id(&self) -> ChannelId { - self.0.read().await.channel_id + self.acquire_read().await.channel_id } /// Get the channel id #[allow(dead_code)] pub async fn text_channel_id(&self) -> ChannelId { - self.0.read().await.text_channel_id + self.acquire_read().await.text_channel_id } /// Get the playback info pub async fn playback_info(&self) -> Option { - self.0.read().await.playback_info.clone() + self.acquire_read().await.playback_info.clone() } pub async fn call(&self) -> Arc> { - self.0.read().await.call.clone() + self.acquire_read().await.call.clone() } #[allow(dead_code)] pub async fn http(&self) -> Arc { - self.0.read().await.http.clone() + self.acquire_read().await.http.clone() + } + + async fn acquire_read(&self) -> ReadLock { + ReadLock(self.0.read().await) + } + + async fn acquire_write(&self) -> WriteLock { + WriteLock(self.0.write().await) + } +} + +struct ReadLock<'a>(RwLockReadGuard<'a, InnerSpoticordSession>); + +impl<'a> Deref for ReadLock<'a> { + type Target = RwLockReadGuard<'a, InnerSpoticordSession>; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> DerefMut for ReadLock<'a> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +struct WriteLock<'a>(RwLockWriteGuard<'a, InnerSpoticordSession>); + +impl<'a> Deref for WriteLock<'a> { + type Target = RwLockWriteGuard<'a, InnerSpoticordSession>; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> DerefMut for WriteLock<'a> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 } } impl InnerSpoticordSession { /// Internal version of disconnect, which does not abort the disconnect timer async fn disconnect_no_abort(&mut self) { + // Flush stream so that it is not permanently blocking the thread + if let Some(player) = self.player.take() { + player.get_stream().flush().ok(); + } + self.disconnected = true; self .session_manager @@ -813,12 +759,6 @@ impl InnerSpoticordSession { } } -impl Drop for InnerSpoticordSession { - fn drop(&mut self) { - trace!("Dropping inner session"); - } -} - #[async_trait] impl EventHandler for SpoticordSession { async fn act(&self, ctx: &EventContext<'_>) -> Option { From 1c38067785b2800334dc7e77fb2165d5c29ffce2 Mon Sep 17 00:00:00 2001 From: DaXcess Date: Tue, 19 Sep 2023 13:49:32 +0200 Subject: [PATCH 5/8] Bring back stats collection --- COMPILING.md | 8 ++++++++ Cargo.lock | 41 ++++++++++++++++++++++++++++++++++++----- Cargo.toml | 6 ++++-- Dockerfile | 4 +++- README.md | 1 + src/main.rs | 31 ++++++++++++++++++++++++++++++- src/stats.rs | 26 ++++++++++++++++++++++++++ 7 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 src/stats.rs diff --git a/COMPILING.md b/COMPILING.md index db657bd..fdca56a 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -71,6 +71,14 @@ If you are actively developing Spoticord, you can use the following command to b cargo run ``` +# Features + +As of now, Spoticord has one optional feature: `stats`. This feature enables collecting a few statistics, total and active servers. These statistics will be sent to a redis server, where they then can be read for whatever purpose. If you want to enable this feature, you can do so by running the following command: + +```sh +cargo build [--release] --features metrics +``` + # MSRV The current minimum supported rust version is `1.65.0`. diff --git a/Cargo.lock b/Cargo.lock index 0c836ae..b774a1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,9 +72,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +checksum = "0f2135563fb5c609d2b2b87c1e8ce7bc41b0b45430fa9661f457981503dd5bf0" dependencies = [ "memchr", ] @@ -301,6 +301,16 @@ dependencies = [ "cc", ] +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "command_attr" version = "0.4.2" @@ -1801,6 +1811,21 @@ dependencies = [ "rand", ] +[[package]] +name = "redis" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f49cdc0bb3f412bf8e7d1bd90fe1d9eb10bc5c399ba90973c14662a27b3f8ba" +dependencies = [ + "combine", + "itoa", + "percent-encoding", + "ryu", + "sha1_smol", + "socket2 0.4.9", + "url", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -2243,6 +2268,12 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + [[package]] name = "shannon" version = "0.2.0" @@ -2365,9 +2396,9 @@ version = "2.1.0" dependencies = [ "dotenv", "env_logger 0.10.0", - "lazy_static", "librespot", "log", + "redis", "reqwest", "samplerate", "serde", @@ -2453,9 +2484,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" dependencies = [ "winapi-util", ] diff --git a/Cargo.toml b/Cargo.toml index 5f7231f..571af67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,12 +8,15 @@ rust-version = "1.65.0" name = "spoticord" path = "src/main.rs" +[features] +stats = ["redis"] + [dependencies] dotenv = "0.15.0" env_logger = "0.10.0" -lazy_static = { version = "1.4.0", optional = true } librespot = { version = "0.4.2", default-features = false } log = "0.4.20" +redis = { version = "0.23.3", optional = true } reqwest = "0.11.20" samplerate = "0.2.4" serde = "1.0.188" @@ -28,4 +31,3 @@ zerocopy = "0.7.5" [profile.release] opt-level = 3 lto = true -debug = true \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 1564679..01b8d15 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,9 @@ WORKDIR /app RUN apt-get update && apt-get install -y cmake COPY . . -RUN cargo install --path . + +# Remove `--features stats` if you want to deploy without stats collection +RUN cargo install --path . --features stats # Runtime FROM debian:buster-slim diff --git a/README.md b/README.md index 673c2a5..03eb869 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Spoticord uses environment variables to configure itself. The following variable Additionally you can configure the following variables: - `GUILD_ID`: The ID of the Discord server where this bot will create commands for. This is used during testing to prevent the bot from creating slash commands in other servers, as well as getting the commands quicker. This variable is optional, and if not set, the bot will create commands in all servers it is in (this may take up to 15 minutes). +- `KV_URL`: The connection URL of a redis-server instance used for storing realtime data. This variable is required when compiling with the `stats` feature. #### Providing environment variables You can provide environment variables in a `.env` file at the root of the working directory of Spoticord. diff --git a/src/main.rs b/src/main.rs index 330ab66..89593f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,12 @@ mod player; mod session; mod utils; +#[cfg(feature = "stats")] +mod stats; + +#[cfg(feature = "stats")] +use crate::stats::StatsManager; + #[tokio::main] async fn main() { if std::env::var("RUST_LOG").is_err() { @@ -51,6 +57,11 @@ async fn main() { let token = env::var("DISCORD_TOKEN").expect("a token in the environment"); let db_url = env::var("DATABASE_URL").expect("a database URL in the environment"); + #[cfg(feature = "stats")] + let stats_manager = + StatsManager::new(env::var("KV_URL").expect("a redis URL in the environment")) + .expect("Failed to connect to redis"); + let session_manager = SessionManager::new(); // Create client @@ -73,7 +84,9 @@ async fn main() { } let shard_manager = client.shard_manager.clone(); - let _cache = client.cache_and_http.cache.clone(); + + #[cfg(feature = "stats")] + let cache = client.cache_and_http.cache.clone(); #[cfg(unix)] let mut term: Option> = Some(Box::new( @@ -88,6 +101,22 @@ async fn main() { tokio::spawn(async move { loop { tokio::select! { + _ = tokio::time::sleep(std::time::Duration::from_secs(60)) => { + #[cfg(feature = "stats")] + { + let guild_count = cache.guilds().len(); + let active_count = session_manager.get_active_session_count().await; + + if let Err(why) = stats_manager.set_server_count(guild_count) { + error!("Failed to update server count: {why}"); + } + + if let Err(why) = stats_manager.set_active_count(active_count) { + error!("Failed to update active count: {why}"); + } + } + } + _ = tokio::signal::ctrl_c() => { info!("Received interrupt signal, shutting down..."); diff --git a/src/stats.rs b/src/stats.rs new file mode 100644 index 0000000..7b54e2c --- /dev/null +++ b/src/stats.rs @@ -0,0 +1,26 @@ +use redis::{Client, Commands, RedisResult as Result}; + +#[derive(Clone)] +pub struct StatsManager { + redis: Client, +} + +impl StatsManager { + pub fn new(url: impl AsRef) -> Result { + let redis = Client::open(url.as_ref())?; + + Ok(StatsManager { redis }) + } + + pub fn set_server_count(&self, count: usize) -> Result<()> { + let mut con = self.redis.get_connection()?; + + con.set("sc-bot-total-servers", count.to_string()) + } + + pub fn set_active_count(&self, count: usize) -> Result<()> { + let mut con = self.redis.get_connection()?; + + con.set("sc-bot-active-servers", count.to_string()) + } +} From 2cebeb41ab3e825ab2fba2f58d0b24ca8910797f Mon Sep 17 00:00:00 2001 From: DaXcess Date: Tue, 19 Sep 2023 20:01:36 +0200 Subject: [PATCH 6/8] Less Spotify API calls, rearranged some player stuff --- Cargo.lock | 9 + Cargo.toml | 3 + src/bot/commands/music/playing.rs | 53 +---- src/consts.rs | 6 +- src/player/mod.rs | 349 ++++++++++++++++++++++++++---- src/session/mod.rs | 336 +++------------------------- src/session/pbi.rs | 130 +++++------ src/utils/spotify.rs | 135 +----------- 8 files changed, 440 insertions(+), 581 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b774a1e..7b2c40e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,6 +94,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + [[package]] name = "arrayvec" version = "0.7.4" @@ -2394,10 +2400,13 @@ dependencies = [ name = "spoticord" version = "2.1.0" dependencies = [ + "anyhow", "dotenv", "env_logger 0.10.0", + "hex", "librespot", "log", + "protobuf", "redis", "reqwest", "samplerate", diff --git a/Cargo.toml b/Cargo.toml index 571af67..cced65f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,13 @@ path = "src/main.rs" stats = ["redis"] [dependencies] +anyhow = "1.0.75" dotenv = "0.15.0" env_logger = "0.10.0" +hex = "0.4.3" librespot = { version = "0.4.2", default-features = false } log = "0.4.20" +protobuf = "2.28.0" redis = { version = "0.23.3", optional = true } reqwest = "0.11.20" samplerate = "0.2.4" diff --git a/src/bot/commands/music/playing.rs b/src/bot/commands/music/playing.rs index f022575..9dbe5ad 100644 --- a/src/bot/commands/music/playing.rs +++ b/src/bot/commands/music/playing.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use librespot::core::spotify_id::{SpotifyAudioType, SpotifyId}; +use librespot::core::spotify_id::SpotifyId; use log::error; use serenity::{ builder::{CreateApplicationCommand, CreateButton, CreateComponents, CreateEmbed}, @@ -82,15 +82,6 @@ pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandO } }; - let spotify_id = match pbi.spotify_id { - Some(spotify_id) => spotify_id, - None => { - not_playing.await; - - return; - } - }; - // Get owner of session let owner = match utils::discord::get_user(&ctx, owner).await { Some(user) => user, @@ -119,7 +110,7 @@ pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandO }; // Get metadata - let (title, description, audio_type, thumbnail) = get_metadata(spotify_id, &pbi); + let (title, description, thumbnail) = get_metadata(&pbi); if let Err(why) = command .create_interaction_response(&ctx.http, |response| { @@ -129,8 +120,8 @@ pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandO message .set_embed(build_playing_embed( title, - audio_type, - spotify_id, + pbi.get_type(), + pbi.spotify_id, description, owner, thumbnail, @@ -409,20 +400,7 @@ async fn update_embed(interaction: &mut MessageComponentInteraction, ctx: &Conte } }; - let spotify_id = match pbi.spotify_id { - Some(spotify_id) => spotify_id, - None => { - error_edit( - "Cannot change playback state", - "I'm currently not playing any music in this server", - ) - .await; - - return; - } - }; - - let (title, description, audio_type, thumbnail) = get_metadata(spotify_id, &pbi); + let (title, description, thumbnail) = get_metadata(&pbi); if let Err(why) = interaction .message @@ -430,8 +408,8 @@ async fn update_embed(interaction: &mut MessageComponentInteraction, ctx: &Conte message .set_embed(build_playing_embed( title, - audio_type, - spotify_id, + pbi.get_type(), + pbi.spotify_id, description, owner, thumbnail, @@ -477,20 +455,9 @@ fn build_playing_embed( embed } -fn get_metadata(spotify_id: SpotifyId, pbi: &PlaybackInfo) -> (String, String, String, String) { - // Get audio type - let audio_type = if spotify_id.audio_type == SpotifyAudioType::Track { - "track" - } else { - "episode" - }; - +fn get_metadata(pbi: &PlaybackInfo) -> (String, String, String) { // Create title - let title = format!( - "{} - {}", - pbi.get_artists().as_deref().unwrap_or("ID"), - pbi.get_name().as_deref().unwrap_or("ID") - ); + let title = format!("{} - {}", pbi.get_artists(), pbi.get_name()); // Create description let mut description = String::new(); @@ -518,5 +485,5 @@ fn get_metadata(spotify_id: SpotifyId, pbi: &PlaybackInfo) -> (String, String, S // Get the thumbnail image let thumbnail = pbi.get_thumbnail_url().expect("to contain a value"); - (title, description, audio_type.to_string(), thumbnail) + (title, description, thumbnail) } diff --git a/src/consts.rs b/src/consts.rs index de675d1..c050977 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,6 +1,10 @@ +#[cfg(not(debug_assertions))] pub const VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg(debug_assertions)] +pub const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "-dev"); + pub const MOTD: &str = "some good 'ol music"; /// The time it takes for Spoticord to disconnect when no music is being played pub const DISCONNECT_TIME: u64 = 5 * 60; - diff --git a/src/player/mod.rs b/src/player/mod.rs index faa5539..220ea8e 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -1,8 +1,12 @@ +use std::{io::Write, sync::Arc}; + +use anyhow::{anyhow, Result}; use librespot::{ connect::spirc::Spirc, core::{ config::{ConnectConfig, SessionConfig}, session::Session, + spotify_id::{SpotifyAudioType, SpotifyId}, }, discovery::Credentials, playback::{ @@ -10,39 +14,52 @@ use librespot::{ mixer::{self, MixerConfig}, player::{Player as SpotifyPlayer, PlayerEvent}, }, + protocol::metadata::{Episode, Track}, +}; +use log::error; +use protobuf::Message; +use songbird::tracks::TrackHandle; +use tokio::sync::{ + broadcast::{Receiver, Sender}, + mpsc::UnboundedReceiver, + Mutex, }; -use tokio::sync::mpsc::UnboundedReceiver; use crate::{ audio::{stream::Stream, SinkEvent, StreamSink}, librespot_ext::discovery::CredentialsExt, + session::pbi::{CurrentTrack, PlaybackInfo}, utils, }; +enum Event { + Player(PlayerEvent), + Sink(SinkEvent), + Command(PlayerCommand), +} + +#[derive(Clone)] +enum PlayerCommand { + Next, + Previous, + Pause, + Play, +} + +#[derive(Clone)] pub struct Player { - stream: Stream, - session: Option, + tx: Sender, + + pbi: Arc>>, } impl Player { - pub fn create() -> Self { - Self { - stream: Stream::new(), - session: None, - } - } - - pub async fn start( - &mut self, + pub async fn create( + stream: Stream, token: &str, device_name: &str, - ) -> Result< - ( - Spirc, - (UnboundedReceiver, UnboundedReceiver), - ), - Box, - > { + track: TrackHandle, + ) -> Result { let username = utils::spotify::get_username(token).await?; let player_config = PlayerConfig { @@ -52,12 +69,6 @@ impl Player { let credentials = Credentials::with_token(username, token); - // Shutdown old session (cannot be done in the stop function) - if let Some(session) = self.session.take() { - session.shutdown() - } - - // Connect the session let (session, _) = Session::connect( SessionConfig { ap_port: Some(9999), // Force the use of ap.spotify.com, which has the lowest latency @@ -68,27 +79,26 @@ impl Player { false, ) .await?; - self.session = Some(session.clone()); let mixer = (mixer::find(Some("softvol")).expect("to exist"))(MixerConfig { volume_ctrl: VolumeCtrl::Linear, ..Default::default() }); - let stream = self.get_stream(); - let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); - let (player, receiver) = SpotifyPlayer::new( - player_config, - session.clone(), - mixer.get_soft_volume(), - move || Box::new(StreamSink::new(stream, tx)), - ); + let (tx, rx_sink) = tokio::sync::mpsc::unbounded_channel(); + let (player, rx_player) = + SpotifyPlayer::new(player_config, session.clone(), mixer.get_soft_volume(), { + let stream = stream.clone(); + move || Box::new(StreamSink::new(stream, tx)) + }); let (spirc, spirc_task) = Spirc::new( ConnectConfig { name: device_name.into(), // 50% initial_volume: Some(65535 / 2), + // Default Spotify behaviour + autoplay: true, ..Default::default() }, session.clone(), @@ -96,12 +106,275 @@ impl Player { mixer, ); - tokio::spawn(spirc_task); + let (tx, rx) = tokio::sync::broadcast::channel(10); + let pbi = Arc::new(Mutex::new(None)); - Ok((spirc, (receiver, rx))) + let player_task = PlayerTask { + pbi: pbi.clone(), + session: session.clone(), + rx_player, + rx_sink, + rx, + spirc, + track, + stream, + }; + + tokio::spawn(spirc_task); + tokio::spawn(player_task.run()); + + Ok(Self { pbi, tx }) } - pub fn get_stream(&self) -> Stream { - self.stream.clone() + pub fn next(&self) { + self.tx.send(PlayerCommand::Next).ok(); + } + + pub fn prev(&self) { + self.tx.send(PlayerCommand::Previous).ok(); + } + + pub fn pause(&self) { + self.tx.send(PlayerCommand::Pause).ok(); + } + + pub fn play(&self) { + self.tx.send(PlayerCommand::Play).ok(); + } + + pub async fn pbi(&self) -> Option { + self.pbi.lock().await.as_ref().cloned() + } +} + +struct PlayerTask { + stream: Stream, + session: Session, + spirc: Spirc, + track: TrackHandle, + + rx_player: UnboundedReceiver, + rx_sink: UnboundedReceiver, + rx: Receiver, + + pbi: Arc>>, +} + +impl PlayerTask { + pub async fn run(mut self) { + let check_result = |result| { + if let Err(why) = result { + error!("Failed to issue track command: {:?}", why); + } + }; + + loop { + match self.next().await { + // Spotify player events + Some(Event::Player(event)) => match event { + PlayerEvent::Playing { + play_request_id: _, + track_id, + position_ms, + duration_ms, + } => { + self + .update_pbi(track_id, position_ms, duration_ms, true) + .await; + } + + PlayerEvent::Paused { + play_request_id: _, + track_id, + position_ms, + duration_ms, + } => { + self + .update_pbi(track_id, position_ms, duration_ms, false) + .await; + } + + PlayerEvent::Changed { + old_track_id: _, + new_track_id, + } => { + if let Ok(current) = self.resolve_audio_info(new_track_id).await { + let mut pbi = self.pbi.lock().await; + + if let Some(pbi) = pbi.as_mut() { + pbi.update_track(current); + } + } + } + + PlayerEvent::Stopped { + play_request_id: _, + track_id: _, + } => { + check_result(self.track.stop()); + } + + _ => {} + }, + + // Audio sink events + Some(Event::Sink(event)) => match event { + SinkEvent::Start => { + check_result(self.track.play()); + } + + SinkEvent::Stop => { + // EXPERIMENT: It may be beneficial to *NOT* pause songbird here + // We already have a fallback if no audio is present in the buffer (write all zeroes aka silence) + // So commenting this out may help prevent a substantial portion of jitter + // This comes at a cost of more bandwidth, though opus should compress it down to almost nothing + + // check_result(track.pause()); + } + }, + + // The `Player` has instructed us to do something + Some(Event::Command(command)) => match command { + PlayerCommand::Next => self.spirc.next(), + PlayerCommand::Previous => self.spirc.prev(), + PlayerCommand::Pause => self.spirc.pause(), + PlayerCommand::Play => self.spirc.play(), + }, + + None => { + // One of the channels died + log::debug!("Channel died"); + break; + } + } + } + } + + async fn next(&mut self) -> Option { + tokio::select! { + event = self.rx_player.recv() => { + event.map(Event::Player) + } + + event = self.rx_sink.recv() => { + event.map(Event::Sink) + } + + command = self.rx.recv() => { + command.ok().map(Event::Command) + } + } + } + + /// Update current playback info, or return early if not necessary + async fn update_pbi( + &self, + spotify_id: SpotifyId, + position_ms: u32, + duration_ms: u32, + playing: bool, + ) { + let mut pbi = self.pbi.lock().await; + + if let Some(pbi) = pbi.as_mut() { + pbi.update_pos_dur(position_ms, duration_ms, playing); + } + + if !pbi + .as_ref() + .map(|pbi| pbi.spotify_id == spotify_id) + .unwrap_or(true) + { + return; + } + + if let Ok(current) = self.resolve_audio_info(spotify_id).await { + match pbi.as_mut() { + Some(pbi) => { + pbi.update_track(current); + pbi.update_pos_dur(position_ms, duration_ms, true); + } + None => { + *pbi = Some(PlaybackInfo::new( + duration_ms, + position_ms, + true, + current, + spotify_id, + )); + } + } + } else { + log::error!("Failed to resolve audio info"); + } + } + + /// Retrieve the metadata for a `SpotifyId` + async fn resolve_audio_info(&self, spotify_id: SpotifyId) -> Result { + match spotify_id.audio_type { + SpotifyAudioType::Track => self.resolve_track_info(spotify_id).await, + SpotifyAudioType::Podcast => self.resolve_episode_info(spotify_id).await, + SpotifyAudioType::NonPlayable => Err(anyhow!("Cannot resolve non-playable audio type")), + } + } + + /// Retrieve the metadata for a Spotify Track + async fn resolve_track_info(&self, spotify_id: SpotifyId) -> Result { + let result = self + .session + .mercury() + .get(format!("hm://metadata/3/track/{}", spotify_id.to_base16()?)) + .await + .map_err(|_| anyhow!("Mercury metadata request failed"))?; + + if result.status_code != 200 { + return Err(anyhow!("Mercury metadata request invalid status code")); + } + + let message = match result.payload.get(0) { + Some(v) => v, + None => return Err(anyhow!("Mercury metadata request invalid payload")), + }; + + let proto_track = Track::parse_from_bytes(message)?; + + Ok(CurrentTrack::Track(proto_track)) + } + + /// Retrieve the metadata for a Spotify Podcast + async fn resolve_episode_info(&self, spotify_id: SpotifyId) -> Result { + let result = self + .session + .mercury() + .get(format!( + "hm://metadata/3/episode/{}", + spotify_id.to_base16()? + )) + .await + .map_err(|_| anyhow!("Mercury metadata request failed"))?; + + if result.status_code != 200 { + return Err(anyhow!("Mercury metadata request invalid status code")); + } + + let message = match result.payload.get(0) { + Some(v) => v, + None => return Err(anyhow!("Mercury metadata request invalid payload")), + }; + + let proto_episode = Episode::parse_from_bytes(message)?; + + Ok(CurrentTrack::Episode(proto_episode)) + } +} + +impl Drop for PlayerTask { + fn drop(&mut self) { + log::trace!("drop PlayerTask"); + + self.track.stop().ok(); + self.spirc.shutdown(); + self.session.shutdown(); + self.stream.flush().ok(); } } diff --git a/src/session/mod.rs b/src/session/mod.rs index 92a2b6b..1ff9e7c 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -6,16 +6,11 @@ use self::{ pbi::PlaybackInfo, }; use crate::{ - audio::SinkEvent, + audio::stream::Stream, consts::DISCONNECT_TIME, database::{Database, DatabaseError}, player::Player, - utils::{embed::Status, spotify}, -}; -use librespot::{ - connect::spirc::Spirc, - core::spotify_id::{SpotifyAudioType, SpotifyId}, - playback::player::PlayerEvent, + utils::embed::Status, }; use log::*; use serenity::{ @@ -31,7 +26,6 @@ use songbird::{ Call, Event, EventContext, EventHandler, }; use std::{ - io::Write, ops::{Deref, DerefMut}, sync::Arc, time::Duration, @@ -53,15 +47,10 @@ struct InnerSpoticordSession { call: Arc>, track: Option, - - playback_info: Option, + player: Option, disconnect_handle: Option>, - spirc: Option, - - player: Option, - /// Whether the session has been disconnected /// If this is true then this instance should no longer be used and dropped disconnected: bool, @@ -101,10 +90,8 @@ impl SpoticordSession { session_manager: session_manager.clone(), call: call.clone(), track: None, - playback_info: None, - disconnect_handle: None, - spirc: None, player: None, + disconnect_handle: None, disconnected: false, }; @@ -157,29 +144,29 @@ impl SpoticordSession { /// Advance to the next track pub async fn next(&mut self) { - if let Some(ref spirc) = self.acquire_read().await.spirc { - spirc.next(); + if let Some(ref player) = self.acquire_read().await.player { + player.next(); } } /// Rewind to the previous track pub async fn previous(&mut self) { - if let Some(ref spirc) = self.acquire_read().await.spirc { - spirc.prev(); + if let Some(ref player) = self.acquire_read().await.player { + player.prev(); } } /// Pause the current track pub async fn pause(&mut self) { - if let Some(ref spirc) = self.acquire_read().await.spirc { - spirc.pause(); + if let Some(ref player) = self.acquire_read().await.player { + player.pause(); } } /// Resume the current track pub async fn resume(&mut self) { - if let Some(ref spirc) = self.acquire_read().await.spirc { - spirc.play(); + if let Some(ref player) = self.acquire_read().await.player { + player.play(); } } @@ -215,13 +202,13 @@ impl SpoticordSession { } }; - // Create player - let mut player = Player::create(); + // Create stream + let stream = Stream::new(); // Create track (paused, fixes audio glitches) let (mut track, track_handle) = create_player(Input::new( true, - Reader::Extension(Box::new(player.get_stream())), + Reader::Extension(Box::new(stream.clone())), Codec::Pcm, Container::Raw, None, @@ -234,7 +221,7 @@ impl SpoticordSession { // Set call audio to track call.play_only(track); - let (spirc, (mut player_rx, mut sink_rx)) = match player.start(&token, &user.device_name).await + let player = match Player::create(stream, &token, &user.device_name, track_handle.clone()).await { Ok(v) => v, Err(why) => { @@ -244,242 +231,18 @@ impl SpoticordSession { } }; - // Handle events - tokio::spawn({ - let track = track_handle.clone(); - let ctx = ctx.clone(); - let instance = self.clone(); - let inner = self.0.clone(); - - async move { - let check_result = |result| { - if let Err(why) = result { - error!("Failed to issue track command: {:?}", why); - } - }; - - loop { - // Required for IpcPacket::TrackChange to work - tokio::task::yield_now().await; - - // Check if the session has been disconnected - let disconnected = { - let inner = inner.read().await; - inner.disconnected - }; - if disconnected { - break; - } - - tokio::select! { - event = player_rx.recv() => { - let Some(event) = event else { break; }; - - match event { - PlayerEvent::Playing { - play_request_id: _, - track_id, - position_ms, - duration_ms, - } => { - let was_none = instance - .update_playback(duration_ms, position_ms, true) - .await; - - if was_none { - // Stop player if update track fails - if let Err(why) = instance.update_track(&ctx, &owner_id, track_id).await { - error!("Failed to update track: {:?}", why); - - instance.player_stopped().await; - return; - } - } - } - - PlayerEvent::Paused { - play_request_id: _, - track_id, - position_ms, - duration_ms, - } => { - instance.start_disconnect_timer().await; - - let was_none = instance - .update_playback(duration_ms, position_ms, false) - .await; - - if was_none { - // Stop player if update track fails - - if let Err(why) = instance.update_track(&ctx, &owner_id, track_id).await { - error!("Failed to update track: {:?}", why); - - instance.player_stopped().await; - return; - } - } - } - - PlayerEvent::Changed { - old_track_id: _, - new_track_id, - } => { - let instance = instance.clone(); - let ctx = ctx.clone(); - - // Fetch track info - // This is done in a separate task to avoid blocking the IPC handler - tokio::spawn(async move { - if let Err(why) = instance.update_track(&ctx, &owner_id, new_track_id).await { - error!("Failed to update track: {:?}", why); - - instance.player_stopped().await; - } - }); - } - - PlayerEvent::Stopped { - play_request_id: _, - track_id: _, - } => { - check_result(track.pause()); - - { - let mut inner = inner.write().await; - inner.playback_info.take(); - } - - instance.start_disconnect_timer().await; - } - - _ => {} - }; - } - - event = sink_rx.recv() => { - let Some(event) = event else { break; }; - - let check_result = |result| { - if let Err(why) = result { - error!("Failed to issue track command: {:?}", why); - } - }; - - - match event { - SinkEvent::Start => { - check_result(track.play()); - } - - SinkEvent::Stop => { - // EXPERIMENT: It may be beneficial to *NOT* pause songbird here - // We already have a fallback if no audio is present in the buffer (write all zeroes aka silence) - // So commenting this out may help prevent a substantial portion of jitter - // This comes at a cost of more bandwidth, though opus should compress it down to almost nothing - - // check_result(track.pause()); - } - } - } - }; - } - - // Clean up session - if !inner.read().await.disconnected { - instance.player_stopped().await; - } - } - }); - // Update inner client and track let mut inner = self.acquire_write().await; inner.track = Some(track_handle); - inner.spirc = Some(spirc); inner.player = Some(player); Ok(()) } - /// Update current track - async fn update_track( - &self, - ctx: &Context, - owner_id: &UserId, - spotify_id: SpotifyId, - ) -> Result<(), String> { - let should_update = { - let pbi = self.playback_info().await; - - if let Some(pbi) = pbi { - pbi.spotify_id.is_none() || pbi.spotify_id != Some(spotify_id) - } else { - false - } - }; - - if !should_update { - return Ok(()); - } - - let data = ctx.data.read().await; - let database = data.get::().expect("to contain a value"); - - let token = match database.get_access_token(&owner_id.to_string()).await { - Ok(token) => token, - Err(why) => { - error!("Failed to get access token: {:?}", why); - return Err("Failed to get access token".to_string()); - } - }; - - let mut track: Option = None; - let mut episode: Option = None; - - if spotify_id.audio_type == SpotifyAudioType::Track { - let track_info = match spotify::get_track_info(&token, spotify_id).await { - Ok(track) => track, - Err(why) => { - error!("Failed to get track info: {:?}", why); - return Err("Failed to get track info".to_string()); - } - }; - - trace!("Received track info: {:?}", track_info); - - track = Some(track_info); - } else if spotify_id.audio_type == SpotifyAudioType::Podcast { - let episode_info = match spotify::get_episode_info(&token, spotify_id).await { - Ok(episode) => episode, - Err(why) => { - error!("Failed to get episode info: {:?}", why); - return Err("Failed to get episode info".to_string()); - } - }; - - trace!("Received episode info: {:?}", episode_info); - - episode = Some(episode_info); - } - - // Update track/episode - let mut inner = self.acquire_write().await; - - if let Some(pbi) = inner.playback_info.as_mut() { - pbi.update_track_episode(spotify_id, track, episode); - } - - Ok(()) - } - /// Called when the player must stop, but not leave the call async fn player_stopped(&self) { let mut inner = self.acquire_write().await; - if let Some(spirc) = inner.spirc.take() { - spirc.shutdown(); - } - if let Some(track) = inner.track.take() { if let Err(why) = track.stop() { error!("Failed to stop track: {:?}", why); @@ -491,9 +254,6 @@ impl SpoticordSession { inner.session_manager.remove_owner(owner_id).await; } - // Clear playback info - inner.playback_info = None; - // Unlock to prevent deadlock in start_disconnect_timer drop(inner); @@ -521,42 +281,11 @@ impl SpoticordSession { self.stop_disconnect_timer().await; } - // Update playback info (duration, position, playing state) - async fn update_playback(&self, duration_ms: u32, position_ms: u32, playing: bool) -> bool { - let is_none = { - let pbi = self.playback_info().await; - - pbi.is_none() - }; - - { - let mut inner = self.acquire_write().await; - - if is_none { - inner.playback_info = Some(PlaybackInfo::new(duration_ms, position_ms, playing)); - } else { - // Update position, duration and playback state - inner - .playback_info - .as_mut() - .expect("to contain a value") - .update_pos_dur(position_ms, duration_ms, playing); - }; - }; - - if playing { - self.stop_disconnect_timer().await; - } - - is_none - } - /// Start the disconnect timer, which will disconnect the bot from the voice channel after a /// certain amount of time async fn start_disconnect_timer(&self) { self.stop_disconnect_timer().await; - let arc_handle = self.0.clone(); let mut inner = self.acquire_write().await; // Check if we are already disconnected @@ -565,8 +294,7 @@ impl SpoticordSession { } inner.disconnect_handle = Some(tokio::spawn({ - let inner = arc_handle.clone(); - let instance = self.clone(); + let session = self.clone(); async move { let mut timer = tokio::time::interval(Duration::from_secs(DISCONNECT_TIME)); @@ -578,19 +306,15 @@ impl SpoticordSession { // Make sure this task has not been aborted, if it has this will automatically stop execution. tokio::task::yield_now().await; - let is_playing = { - let inner = inner.read().await; - - if let Some(ref pbi) = inner.playback_info { - pbi.is_playing - } else { - false - } - }; + let is_playing = session + .playback_info() + .await + .map(|pbi| pbi.is_playing) + .unwrap_or(false); if !is_playing { info!("Player is not playing, disconnecting"); - instance + session .disconnect_with_message( "The player has been inactive for too long, and has been disconnected.", ) @@ -668,7 +392,10 @@ impl SpoticordSession { /// Get the playback info pub async fn playback_info(&self) -> Option { - self.acquire_read().await.playback_info.clone() + let handle = self.acquire_read().await; + let player = handle.player.as_ref()?; + + player.pbi().await } pub async fn call(&self) -> Arc> { @@ -728,11 +455,6 @@ impl<'a> DerefMut for WriteLock<'a> { impl InnerSpoticordSession { /// Internal version of disconnect, which does not abort the disconnect timer async fn disconnect_no_abort(&mut self) { - // Flush stream so that it is not permanently blocking the thread - if let Some(player) = self.player.take() { - player.get_stream().flush().ok(); - } - self.disconnected = true; self .session_manager @@ -741,10 +463,6 @@ impl InnerSpoticordSession { let mut call = self.call.lock().await; - if let Some(spirc) = self.spirc.take() { - spirc.shutdown(); - } - if let Some(track) = self.track.take() { if let Err(why) = track.stop() { error!("Failed to stop track: {:?}", why); diff --git a/src/session/pbi.rs b/src/session/pbi.rs index 293805b..11629da 100644 --- a/src/session/pbi.rs +++ b/src/session/pbi.rs @@ -1,28 +1,41 @@ -use librespot::core::spotify_id::SpotifyId; +use librespot::{ + core::spotify_id::SpotifyId, + protocol::metadata::{Episode, Track}, +}; -use crate::utils::{self, spotify}; +use crate::utils; #[derive(Clone)] pub struct PlaybackInfo { last_updated: u128, position_ms: u32, - pub track: Option, - pub episode: Option, - pub spotify_id: Option, + pub track: CurrentTrack, + pub spotify_id: SpotifyId, pub duration_ms: u32, pub is_playing: bool, } +#[derive(Clone)] +pub enum CurrentTrack { + Track(Track), + Episode(Episode), +} + impl PlaybackInfo { /// Create a new instance of PlaybackInfo - pub fn new(duration_ms: u32, position_ms: u32, is_playing: bool) -> Self { + pub fn new( + duration_ms: u32, + position_ms: u32, + is_playing: bool, + track: CurrentTrack, + spotify_id: SpotifyId, + ) -> Self { Self { last_updated: utils::get_time_ms(), - track: None, - episode: None, - spotify_id: None, + track, + spotify_id, duration_ms, position_ms, is_playing, @@ -39,15 +52,8 @@ impl PlaybackInfo { } /// Update spotify id, track and episode - pub fn update_track_episode( - &mut self, - spotify_id: SpotifyId, - track: Option, - episode: Option, - ) { - self.spotify_id = Some(spotify_id); + pub fn update_track(&mut self, track: CurrentTrack) { self.track = track; - self.episode = episode; } /// Get the current playback position @@ -63,71 +69,73 @@ impl PlaybackInfo { } /// Get the name of the track or episode - pub fn get_name(&self) -> Option { - if let Some(track) = &self.track { - Some(track.name.clone()) - } else { - self.episode.as_ref().map(|episode| episode.name.clone()) + pub fn get_name(&self) -> String { + match &self.track { + CurrentTrack::Track(track) => track.get_name().to_string(), + CurrentTrack::Episode(episode) => episode.get_name().to_string(), } } /// Get the artist(s) or show name of the current track - pub fn get_artists(&self) -> Option { - if let Some(track) = &self.track { - Some( - track - .artists - .iter() - .map(|a| a.name.clone()) - .collect::>() - .join(", "), - ) - } else { - self - .episode - .as_ref() - .map(|episode| episode.show.name.clone()) + pub fn get_artists(&self) -> String { + match &self.track { + CurrentTrack::Track(track) => track + .get_artist() + .iter() + .map(|a| a.get_name().to_string()) + .collect::>() + .join(", "), + CurrentTrack::Episode(episode) => episode.get_show().get_name().to_string(), } } /// Get the album art url pub fn get_thumbnail_url(&self) -> Option { - if let Some(track) = &self.track { - let mut images = track.album.images.clone(); - images.sort_by(|a, b| b.width.cmp(&a.width)); + let file_id = match &self.track { + CurrentTrack::Track(track) => { + let mut images = track.get_album().get_cover_group().get_image().to_vec(); + images.sort_by_key(|b| std::cmp::Reverse(b.get_width())); - images.get(0).as_ref().map(|image| image.url.clone()) - } else if let Some(episode) = &self.episode { - let mut images = episode.show.images.clone(); - images.sort_by(|a, b| b.width.cmp(&a.width)); + images + .get(0) + .as_ref() + .map(|image| image.get_file_id()) + .map(hex::encode) + } + CurrentTrack::Episode(episode) => { + let mut images = episode.get_covers().get_image().to_vec(); + images.sort_by_key(|b| std::cmp::Reverse(b.get_width())); - images.get(0).as_ref().map(|image| image.url.clone()) - } else { - None - } + images + .get(0) + .as_ref() + .map(|image| image.get_file_id()) + .map(hex::encode) + } + }; + + file_id.map(|id| format!("https://i.scdn.co/image/{id}")) } /// Get the type of audio (track or episode) #[allow(dead_code)] - pub fn get_type(&self) -> Option { - if self.track.is_some() { - Some("track".into()) - } else if self.episode.is_some() { - Some("episode".into()) - } else { - None + pub fn get_type(&self) -> String { + match &self.track { + CurrentTrack::Track(_) => "track".to_string(), + CurrentTrack::Episode(_) => "episode".to_string(), } } /// Get the public facing url of the track or episode #[allow(dead_code)] pub fn get_url(&self) -> Option<&str> { - if let Some(ref track) = self.track { - Some(track.external_urls.spotify.as_str()) - } else if let Some(ref episode) = self.episode { - Some(episode.external_urls.spotify.as_str()) - } else { - None + match &self.track { + CurrentTrack::Track(track) => track + .get_external_id() + .iter() + .find(|id| id.get_typ() == "spotify") + .map(|v| v.get_id()), + CurrentTrack::Episode(episode) => Some(episode.get_external_url()), } } } diff --git a/src/utils/spotify.rs b/src/utils/spotify.rs index 3a7dfe4..7468693 100644 --- a/src/utils/spotify.rs +++ b/src/utils/spotify.rs @@ -1,55 +1,8 @@ -use std::error::Error; - -use librespot::core::spotify_id::SpotifyId; +use anyhow::{anyhow, Result}; use log::{error, trace}; -use serde::Deserialize; use serde_json::Value; -#[derive(Debug, Clone, Deserialize)] -pub struct Artist { - pub name: String, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct Image { - pub url: String, - pub height: u32, - pub width: u32, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct Album { - pub name: String, - pub images: Vec, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct ExternalUrls { - pub spotify: String, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct Track { - pub name: String, - pub artists: Vec, - pub album: Album, - pub external_urls: ExternalUrls, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct Show { - pub name: String, - pub images: Vec, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct Episode { - pub name: String, - pub show: Show, - pub external_urls: ExternalUrls, -} - -pub async fn get_username(token: impl Into) -> Result { +pub async fn get_username(token: impl Into) -> Result { let token = token.into(); let client = reqwest::Client::new(); @@ -65,7 +18,7 @@ pub async fn get_username(token: impl Into) -> Result { Ok(response) => response, Err(why) => { error!("Failed to get username: {}", why); - return Err(format!("{}", why)); + return Err(why.into()); } }; @@ -76,7 +29,7 @@ pub async fn get_username(token: impl Into) -> Result { if response.status() != 200 { error!("Failed to get username: {}", response.status()); - return Err(format!( + return Err(anyhow!( "Failed to get track info: Invalid status code: {}", response.status() )); @@ -86,7 +39,7 @@ pub async fn get_username(token: impl Into) -> Result { Ok(body) => body, Err(why) => { error!("Failed to parse body: {}", why); - return Err(format!("{}", why)); + return Err(why.into()); } }; @@ -96,82 +49,6 @@ pub async fn get_username(token: impl Into) -> Result { } error!("Missing 'id' field in body: {:#?}", body); - return Err("Failed to parse body: Invalid body received".to_string()); - } -} - -pub async fn get_track_info( - token: impl Into, - track: SpotifyId, -) -> Result> { - let token = token.into(); - let client = reqwest::Client::new(); - - let mut retries = 3; - - loop { - let response = client - .get(format!( - "https://api.spotify.com/v1/tracks/{}", - track.to_base62()? - )) - .bearer_auth(&token) - .send() - .await?; - - if response.status().as_u16() >= 500 && retries > 0 { - retries -= 1; - continue; - } - - if response.status() != 200 { - return Err( - format!( - "Failed to get track info: Invalid status code: {}", - response.status() - ) - .into(), - ); - } - - return Ok(response.json().await?); - } -} - -pub async fn get_episode_info( - token: impl Into, - episode: SpotifyId, -) -> Result> { - let token = token.into(); - let client = reqwest::Client::new(); - - let mut retries = 3; - - loop { - let response = client - .get(format!( - "https://api.spotify.com/v1/episodes/{}", - episode.to_base62()? - )) - .bearer_auth(&token) - .send() - .await?; - - if response.status().as_u16() >= 500 && retries > 0 { - retries -= 1; - continue; - } - - if response.status() != 200 { - return Err( - format!( - "Failed to get episode info: Invalid status code: {}", - response.status() - ) - .into(), - ); - } - - return Ok(response.json().await?); + return Err(anyhow!("Failed to parse body: Invalid body received")); } } From 8508883320aa26abb9676bec27fafcf339559676 Mon Sep 17 00:00:00 2001 From: DaXcess Date: Tue, 19 Sep 2023 21:42:28 +0200 Subject: [PATCH 7/8] Bring back 5 min timeout --- src/player/mod.rs | 46 +++++++++++++++++++++++++++++--------- src/session/mod.rs | 55 ++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 82 insertions(+), 19 deletions(-) diff --git a/src/player/mod.rs b/src/player/mod.rs index 220ea8e..41a3e7a 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -12,7 +12,7 @@ use librespot::{ playback::{ config::{Bitrate, PlayerConfig, VolumeCtrl}, mixer::{self, MixerConfig}, - player::{Player as SpotifyPlayer, PlayerEvent}, + player::{Player as SpotifyPlayer, PlayerEvent as SpotifyEvent}, }, protocol::metadata::{Episode, Track}, }; @@ -33,7 +33,7 @@ use crate::{ }; enum Event { - Player(PlayerEvent), + Player(SpotifyEvent), Sink(SinkEvent), Command(PlayerCommand), } @@ -44,6 +44,14 @@ enum PlayerCommand { Previous, Pause, Play, + Shutdown, +} + +#[derive(Clone, Debug)] +pub enum PlayerEvent { + Pause, + Play, + Stopped, } #[derive(Clone)] @@ -59,7 +67,7 @@ impl Player { token: &str, device_name: &str, track: TrackHandle, - ) -> Result { + ) -> Result<(Self, Receiver)> { let username = utils::spotify::get_username(token).await?; let player_config = PlayerConfig { @@ -107,6 +115,7 @@ impl Player { ); let (tx, rx) = tokio::sync::broadcast::channel(10); + let (tx_ev, rx_ev) = tokio::sync::broadcast::channel(10); let pbi = Arc::new(Mutex::new(None)); let player_task = PlayerTask { @@ -115,6 +124,7 @@ impl Player { rx_player, rx_sink, rx, + tx: tx_ev, spirc, track, stream, @@ -123,7 +133,7 @@ impl Player { tokio::spawn(spirc_task); tokio::spawn(player_task.run()); - Ok(Self { pbi, tx }) + Ok((Self { pbi, tx }, rx_ev)) } pub fn next(&self) { @@ -142,6 +152,10 @@ impl Player { self.tx.send(PlayerCommand::Play).ok(); } + pub fn shutdown(&self) { + self.tx.send(PlayerCommand::Shutdown).ok(); + } + pub async fn pbi(&self) -> Option { self.pbi.lock().await.as_ref().cloned() } @@ -153,9 +167,10 @@ struct PlayerTask { spirc: Spirc, track: TrackHandle, - rx_player: UnboundedReceiver, + rx_player: UnboundedReceiver, rx_sink: UnboundedReceiver, rx: Receiver, + tx: Sender, pbi: Arc>>, } @@ -172,7 +187,7 @@ impl PlayerTask { match self.next().await { // Spotify player events Some(Event::Player(event)) => match event { - PlayerEvent::Playing { + SpotifyEvent::Playing { play_request_id: _, track_id, position_ms, @@ -181,9 +196,11 @@ impl PlayerTask { self .update_pbi(track_id, position_ms, duration_ms, true) .await; + + self.tx.send(PlayerEvent::Play).ok(); } - PlayerEvent::Paused { + SpotifyEvent::Paused { play_request_id: _, track_id, position_ms, @@ -192,9 +209,11 @@ impl PlayerTask { self .update_pbi(track_id, position_ms, duration_ms, false) .await; + + self.tx.send(PlayerEvent::Pause).ok(); } - PlayerEvent::Changed { + SpotifyEvent::Changed { old_track_id: _, new_track_id, } => { @@ -207,11 +226,13 @@ impl PlayerTask { } } - PlayerEvent::Stopped { + SpotifyEvent::Stopped { play_request_id: _, track_id: _, } => { - check_result(self.track.stop()); + check_result(self.track.pause()); + + self.tx.send(PlayerEvent::Pause).ok(); } _ => {} @@ -230,6 +251,8 @@ impl PlayerTask { // This comes at a cost of more bandwidth, though opus should compress it down to almost nothing // check_result(track.pause()); + + self.tx.send(PlayerEvent::Pause).ok(); } }, @@ -239,6 +262,7 @@ impl PlayerTask { PlayerCommand::Previous => self.spirc.prev(), PlayerCommand::Pause => self.spirc.pause(), PlayerCommand::Play => self.spirc.play(), + PlayerCommand::Shutdown => break, }, None => { @@ -248,6 +272,8 @@ impl PlayerTask { } } } + + self.tx.send(PlayerEvent::Stopped).ok(); } async fn next(&mut self) -> Option { diff --git a/src/session/mod.rs b/src/session/mod.rs index 1ff9e7c..c52221b 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -9,7 +9,7 @@ use crate::{ audio::stream::Stream, consts::DISCONNECT_TIME, database::{Database, DatabaseError}, - player::Player, + player::{Player, PlayerEvent}, utils::embed::Status, }; use log::*; @@ -221,17 +221,44 @@ impl SpoticordSession { // Set call audio to track call.play_only(track); - let player = match Player::create(stream, &token, &user.device_name, track_handle.clone()).await - { - Ok(v) => v, - Err(why) => { - error!("Failed to start the player: {:?}", why); + let (player, mut rx) = + match Player::create(stream, &token, &user.device_name, track_handle.clone()).await { + Ok(v) => v, + Err(why) => { + error!("Failed to start the player: {:?}", why); - return Err(SessionCreateError::PlayerStartError); + return Err(SessionCreateError::PlayerStartError); + } + }; + + tokio::spawn({ + let session = self.clone(); + + async move { + loop { + match rx.recv().await { + Ok(event) => match event { + PlayerEvent::Pause => session.start_disconnect_timer().await, + PlayerEvent::Play => session.stop_disconnect_timer().await, + PlayerEvent::Stopped => { + session.player_stopped().await; + break; + } + }, + Err(why) => { + error!("Communication with player abruptly ended: {why}"); + session.player_stopped().await; + + break; + } + } + } } - }; + }); + + // Start DC timer by default, as automatic device switching is now gone + self.start_disconnect_timer().await; - // Update inner client and track let mut inner = self.acquire_write().await; inner.track = Some(track_handle); inner.player = Some(player); @@ -254,6 +281,11 @@ impl SpoticordSession { inner.session_manager.remove_owner(owner_id).await; } + // Disconnect from Spotify + if let Some(player) = inner.player.take() { + player.shutdown(); + } + // Unlock to prevent deadlock in start_disconnect_timer drop(inner); @@ -455,6 +487,11 @@ impl<'a> DerefMut for WriteLock<'a> { impl InnerSpoticordSession { /// Internal version of disconnect, which does not abort the disconnect timer async fn disconnect_no_abort(&mut self) { + // Disconnect from Spotify + if let Some(player) = self.player.take() { + player.shutdown(); + } + self.disconnected = true; self .session_manager From 2f9b27adb5129698242ded730df6e61dbb8df863 Mon Sep 17 00:00:00 2001 From: DaXcess Date: Wed, 20 Sep 2023 20:15:23 +0200 Subject: [PATCH 8/8] I'm ready --- src/player/mod.rs | 4 ++-- src/session/pbi.rs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/player/mod.rs b/src/player/mod.rs index 41a3e7a..b651852 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -221,7 +221,7 @@ impl PlayerTask { let mut pbi = self.pbi.lock().await; if let Some(pbi) = pbi.as_mut() { - pbi.update_track(current); + pbi.update_track(new_track_id, current); } } } @@ -317,7 +317,7 @@ impl PlayerTask { if let Ok(current) = self.resolve_audio_info(spotify_id).await { match pbi.as_mut() { Some(pbi) => { - pbi.update_track(current); + pbi.update_track(spotify_id, current); pbi.update_pos_dur(position_ms, duration_ms, true); } None => { diff --git a/src/session/pbi.rs b/src/session/pbi.rs index 11629da..e3402b8 100644 --- a/src/session/pbi.rs +++ b/src/session/pbi.rs @@ -52,7 +52,8 @@ impl PlaybackInfo { } /// Update spotify id, track and episode - pub fn update_track(&mut self, track: CurrentTrack) { + pub fn update_track(&mut self, spotify_id: SpotifyId, track: CurrentTrack) { + self.spotify_id = spotify_id; self.track = track; }