Almost ready
parent
5154c220bf
commit
2e273cdcde
|
@ -1 +0,0 @@
|
||||||
patreon: rodabafilms
|
|
|
@ -1249,7 +1249,7 @@ dependencies = [
|
||||||
"shell-words",
|
"shell-words",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"zerocopy",
|
"zerocopy 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2377,7 +2377,7 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"zerocopy",
|
"zerocopy 0.7.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3137,7 +3137,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20707b61725734c595e840fb3704378a0cd2b9c74cc9e6e20724838fc6a1e2f9"
|
checksum = "20707b61725734c595e840fb3704378a0cd2b9c74cc9e6e20724838fc6a1e2f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"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]]
|
[[package]]
|
||||||
|
@ -3151,6 +3161,17 @@ dependencies = [
|
||||||
"syn 2.0.37",
|
"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]]
|
[[package]]
|
||||||
name = "zeroize"
|
name = "zeroize"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
|
23
Cargo.toml
23
Cargo.toml
|
@ -13,14 +13,19 @@ dotenv = "0.15.0"
|
||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
lazy_static = { version = "1.4.0", optional = true }
|
lazy_static = { version = "1.4.0", optional = true }
|
||||||
librespot = { version = "0.4.2", default-features = false }
|
librespot = { version = "0.4.2", default-features = false }
|
||||||
log = "0.4.17"
|
log = "0.4.20"
|
||||||
reqwest = "0.11.18"
|
reqwest = "0.11.20"
|
||||||
samplerate = "0.2.4"
|
samplerate = "0.2.4"
|
||||||
serde = "1.0.163"
|
serde = "1.0.188"
|
||||||
serde_json = "1.0.96"
|
serde_json = "1.0.107"
|
||||||
serenity = { version = "0.11.5", features = ["framework", "cache", "standard_framework"], default-features = false }
|
serenity = { version = "0.11.6", features = ["framework", "cache", "standard_framework"], default-features = false }
|
||||||
songbird = "0.3.2"
|
songbird = "0.3.2"
|
||||||
thiserror = "1.0.40"
|
thiserror = "1.0.48"
|
||||||
time = "0.3.21"
|
time = "0.3.28"
|
||||||
tokio = { version = "1.28.1", features = ["rt", "full"] }
|
tokio = { version = "1.32.0", features = ["rt", "full"] }
|
||||||
zerocopy = "0.6.1"
|
zerocopy = "0.7.5"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = 3
|
||||||
|
lto = true
|
||||||
|
debug = true
|
|
@ -1,3 +1,7 @@
|
||||||
|
pub mod stream;
|
||||||
|
|
||||||
|
use self::stream::Stream;
|
||||||
|
|
||||||
use librespot::playback::audio_backend::{Sink, SinkAsBytes, SinkError, SinkResult};
|
use librespot::playback::audio_backend::{Sink, SinkAsBytes, SinkError, SinkResult};
|
||||||
use librespot::playback::convert::Converter;
|
use librespot::playback::convert::Converter;
|
||||||
use librespot::playback::decoder::AudioPacket;
|
use librespot::playback::decoder::AudioPacket;
|
||||||
|
@ -5,8 +9,6 @@ use log::error;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
|
||||||
use crate::player::stream::Stream;
|
|
||||||
|
|
||||||
pub enum SinkEvent {
|
pub enum SinkEvent {
|
||||||
Start,
|
Start,
|
||||||
Stop,
|
Stop,
|
||||||
|
@ -26,6 +28,8 @@ impl StreamSink {
|
||||||
impl Sink for StreamSink {
|
impl Sink for StreamSink {
|
||||||
fn start(&mut self) -> SinkResult<()> {
|
fn start(&mut self) -> SinkResult<()> {
|
||||||
if let Err(why) = self.sender.send(SinkEvent::Start) {
|
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}");
|
error!("Failed to send start playback event: {why}");
|
||||||
return Err(SinkError::ConnectionRefused(why.to_string()));
|
return Err(SinkError::ConnectionRefused(why.to_string()));
|
||||||
}
|
}
|
||||||
|
@ -35,10 +39,14 @@ impl Sink for StreamSink {
|
||||||
|
|
||||||
fn stop(&mut self) -> SinkResult<()> {
|
fn stop(&mut self) -> SinkResult<()> {
|
||||||
if let Err(why) = self.sender.send(SinkEvent::Stop) {
|
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}");
|
error!("Failed to send start playback event: {why}");
|
||||||
return Err(SinkError::ConnectionRefused(why.to_string()));
|
return Err(SinkError::ConnectionRefused(why.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.stream.flush().ok();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,10 @@ use std::{
|
||||||
|
|
||||||
use songbird::input::reader::MediaSource;
|
use songbird::input::reader::MediaSource;
|
||||||
|
|
||||||
// TODO: Find optimal value
|
/// The lower the value, the less latency
|
||||||
const MAX_SIZE: usize = 1024 * 1024;
|
///
|
||||||
|
/// Too low of a value results in unpredictable audio
|
||||||
|
const MAX_SIZE: usize = 32 * 1024;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Stream {
|
pub struct Stream {
|
||||||
|
@ -26,15 +28,19 @@ impl Read for Stream {
|
||||||
let (mutex, condvar) = &*self.inner;
|
let (mutex, condvar) = &*self.inner;
|
||||||
let mut buffer = mutex.lock().expect("Mutex was poisoned");
|
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() {
|
return Ok(buf.len());
|
||||||
buffer = condvar.wait(buffer).expect("Mutex was poisoned");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let max_read = usize::min(buf.len(), buffer.len());
|
let max_read = usize::min(buf.len(), buffer.len());
|
||||||
buf[0..max_read].copy_from_slice(&buffer[0..max_read]);
|
buf[0..max_read].copy_from_slice(&buffer[0..max_read]);
|
||||||
buffer.drain(0..max_read);
|
buffer.drain(0..max_read);
|
||||||
|
condvar.notify_all();
|
||||||
|
|
||||||
Ok(max_read)
|
Ok(max_read)
|
||||||
}
|
}
|
||||||
|
@ -56,6 +62,12 @@ impl Write for Stream {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&mut self) -> std::io::Result<()> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -25,7 +25,7 @@ pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandO
|
||||||
author
|
author
|
||||||
.name("Maintained by: RoDaBaFilms")
|
.name("Maintained by: RoDaBaFilms")
|
||||||
.url("https://rodabafilms.com/")
|
.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))
|
.description(format!("Current version: {}\n\nSpoticord is open source, check out [our GitHub](https://github.com/SpoticordMusic)", VERSION))
|
||||||
.color(Status::Info as u64)
|
.color(Status::Info as u64)
|
||||||
|
|
|
@ -338,7 +338,7 @@ pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandO
|
||||||
.title("Connected to voice channel")
|
.title("Connected to voice channel")
|
||||||
.icon_url("https://spoticord.com/speaker.png")
|
.icon_url("https://spoticord.com/speaker.png")
|
||||||
.description(format!("Come listen along in <#{}>", channel_id))
|
.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)
|
.status(Status::Info)
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -91,6 +91,7 @@ async fn main() {
|
||||||
_ = tokio::signal::ctrl_c() => {
|
_ = tokio::signal::ctrl_c() => {
|
||||||
info!("Received interrupt signal, shutting down...");
|
info!("Received interrupt signal, shutting down...");
|
||||||
|
|
||||||
|
session_manager.shutdown().await;
|
||||||
shard_manager.lock().await.shutdown_all().await;
|
shard_manager.lock().await.shutdown_all().await;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -110,6 +111,7 @@ async fn main() {
|
||||||
}, if term.is_some() => {
|
}, if term.is_some() => {
|
||||||
info!("Received terminate signal, shutting down...");
|
info!("Received terminate signal, shutting down...");
|
||||||
|
|
||||||
|
session_manager.shutdown().await;
|
||||||
shard_manager.lock().await.shutdown_all().await;
|
shard_manager.lock().await.shutdown_all().await;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
pub mod stream;
|
|
||||||
|
|
||||||
use librespot::{
|
use librespot::{
|
||||||
connect::spirc::Spirc,
|
connect::spirc::Spirc,
|
||||||
core::{config::ConnectConfig, session::Session},
|
core::{
|
||||||
|
config::{ConnectConfig, SessionConfig},
|
||||||
|
session::Session,
|
||||||
|
},
|
||||||
discovery::Credentials,
|
discovery::Credentials,
|
||||||
playback::{
|
playback::{
|
||||||
config::{Bitrate, PlayerConfig, VolumeCtrl},
|
config::{Bitrate, PlayerConfig, VolumeCtrl},
|
||||||
|
@ -13,13 +14,11 @@ use librespot::{
|
||||||
use tokio::sync::mpsc::UnboundedReceiver;
|
use tokio::sync::mpsc::UnboundedReceiver;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
audio::{SinkEvent, StreamSink},
|
audio::{stream::Stream, SinkEvent, StreamSink},
|
||||||
librespot_ext::discovery::CredentialsExt,
|
librespot_ext::discovery::CredentialsExt,
|
||||||
utils,
|
utils,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::stream::Stream;
|
|
||||||
|
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
stream: Stream,
|
stream: Stream,
|
||||||
session: Option<Session>,
|
session: Option<Session>,
|
||||||
|
@ -59,7 +58,16 @@ impl Player {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect the session
|
// 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());
|
self.session = Some(session.clone());
|
||||||
|
|
||||||
let mixer = (mixer::find(Some("softvol")).expect("to exist"))(MixerConfig {
|
let mixer = (mixer::find(Some("softvol")).expect("to exist"))(MixerConfig {
|
||||||
|
|
|
@ -115,6 +115,10 @@ impl InnerSessionManager {
|
||||||
|
|
||||||
count
|
count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sessions(&self) -> Vec<SpoticordSession> {
|
||||||
|
self.sessions.values().cloned().collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SessionManager {
|
impl SessionManager {
|
||||||
|
@ -183,4 +187,13 @@ impl SessionManager {
|
||||||
pub async fn get_active_session_count(&self) -> usize {
|
pub async fn get_active_session_count(&self) -> usize {
|
||||||
self.0.read().await.get_active_session_count().await
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,13 @@ use songbird::{
|
||||||
tracks::TrackHandle,
|
tracks::TrackHandle,
|
||||||
Call, Event, EventContext, EventHandler,
|
Call, Event, EventContext, EventHandler,
|
||||||
};
|
};
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{
|
||||||
use tokio::sync::Mutex;
|
io::Write,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
sync::Arc,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
use tokio::sync::{Mutex, RwLockReadGuard, RwLockWriteGuard};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SpoticordSession(Arc<RwLock<InnerSpoticordSession>>);
|
pub struct SpoticordSession(Arc<RwLock<InnerSpoticordSession>>);
|
||||||
|
@ -55,6 +60,8 @@ struct InnerSpoticordSession {
|
||||||
|
|
||||||
spirc: Option<Spirc>,
|
spirc: Option<Spirc>,
|
||||||
|
|
||||||
|
player: Option<Player>,
|
||||||
|
|
||||||
/// Whether the session has been disconnected
|
/// Whether the session has been disconnected
|
||||||
/// If this is true then this instance should no longer be used and dropped
|
/// If this is true then this instance should no longer be used and dropped
|
||||||
disconnected: bool,
|
disconnected: bool,
|
||||||
|
@ -97,6 +104,7 @@ impl SpoticordSession {
|
||||||
playback_info: None,
|
playback_info: None,
|
||||||
disconnect_handle: None,
|
disconnect_handle: None,
|
||||||
spirc: None,
|
spirc: None,
|
||||||
|
player: None,
|
||||||
disconnected: false,
|
disconnected: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -132,14 +140,13 @@ impl SpoticordSession {
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut inner = self.0.write().await;
|
let mut inner = self.acquire_write().await;
|
||||||
inner.owner = Some(owner_id);
|
inner.owner = Some(owner_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let inner = self.0.clone();
|
let guild_id = self.acquire_read().await.guild_id;
|
||||||
let inner = inner.read().await;
|
session_manager.set_owner(owner_id, guild_id).await;
|
||||||
session_manager.set_owner(owner_id, inner.guild_id).await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the player
|
// Create the player
|
||||||
|
@ -150,28 +157,28 @@ impl SpoticordSession {
|
||||||
|
|
||||||
/// Advance to the next track
|
/// Advance to the next track
|
||||||
pub async fn next(&mut self) {
|
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();
|
spirc.next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rewind to the previous track
|
/// Rewind to the previous track
|
||||||
pub async fn previous(&mut self) {
|
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();
|
spirc.prev();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pause the current track
|
/// Pause the current track
|
||||||
pub async fn pause(&mut self) {
|
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();
|
spirc.pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resume the current track
|
/// Resume the current track
|
||||||
pub async fn resume(&mut self) {
|
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();
|
spirc.play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,8 +244,7 @@ impl SpoticordSession {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle IPC packets
|
// Handle events
|
||||||
// This will automatically quit once the IPC connection is closed
|
|
||||||
tokio::spawn({
|
tokio::spawn({
|
||||||
let track = track_handle.clone();
|
let track = track_handle.clone();
|
||||||
let ctx = ctx.clone();
|
let ctx = ctx.clone();
|
||||||
|
@ -367,126 +373,16 @@ impl SpoticordSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
SinkEvent::Stop => {
|
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
|
// Clean up session
|
||||||
|
@ -497,9 +393,10 @@ impl SpoticordSession {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update inner client and track
|
// 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.track = Some(track_handle);
|
||||||
inner.spirc = Some(spirc);
|
inner.spirc = Some(spirc);
|
||||||
|
inner.player = Some(player);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -566,7 +463,7 @@ impl SpoticordSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update track/episode
|
// 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() {
|
if let Some(pbi) = inner.playback_info.as_mut() {
|
||||||
pbi.update_track_episode(spotify_id, track, episode);
|
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
|
/// Called when the player must stop, but not leave the call
|
||||||
async fn player_stopped(&self) {
|
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() {
|
if let Some(spirc) = inner.spirc.take() {
|
||||||
spirc.shutdown();
|
spirc.shutdown();
|
||||||
|
@ -617,7 +514,7 @@ impl SpoticordSession {
|
||||||
// read lock to read the current owner.
|
// read lock to read the current owner.
|
||||||
// This would deadlock if we have an active write lock
|
// 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;
|
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 {
|
if is_none {
|
||||||
inner.playback_info = Some(PlaybackInfo::new(duration_ms, position_ms, playing));
|
inner.playback_info = Some(PlaybackInfo::new(duration_ms, position_ms, playing));
|
||||||
|
@ -659,8 +556,8 @@ impl SpoticordSession {
|
||||||
async fn start_disconnect_timer(&self) {
|
async fn start_disconnect_timer(&self) {
|
||||||
self.stop_disconnect_timer().await;
|
self.stop_disconnect_timer().await;
|
||||||
|
|
||||||
let inner_arc = self.0.clone();
|
let arc_handle = self.0.clone();
|
||||||
let mut inner = inner_arc.write().await;
|
let mut inner = self.acquire_write().await;
|
||||||
|
|
||||||
// Check if we are already disconnected
|
// Check if we are already disconnected
|
||||||
if inner.disconnected {
|
if inner.disconnected {
|
||||||
|
@ -668,7 +565,7 @@ impl SpoticordSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
inner.disconnect_handle = Some(tokio::spawn({
|
inner.disconnect_handle = Some(tokio::spawn({
|
||||||
let inner = inner_arc.clone();
|
let inner = arc_handle.clone();
|
||||||
let instance = self.clone();
|
let instance = self.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
|
@ -705,7 +602,7 @@ impl SpoticordSession {
|
||||||
|
|
||||||
/// Stop the disconnect timer (if one is running)
|
/// Stop the disconnect timer (if one is running)
|
||||||
async fn stop_disconnect_timer(&self) {
|
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() {
|
if let Some(handle) = inner.disconnect_handle.take() {
|
||||||
handle.abort();
|
handle.abort();
|
||||||
}
|
}
|
||||||
|
@ -714,7 +611,7 @@ impl SpoticordSession {
|
||||||
/// Disconnect from the VC and send a message to the text channel
|
/// Disconnect from the VC and send a message to the text channel
|
||||||
pub async fn disconnect_with_message(&self, content: &str) {
|
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
|
// Firstly we disconnect
|
||||||
inner.disconnect_no_abort().await;
|
inner.disconnect_no_abort().await;
|
||||||
|
@ -745,48 +642,97 @@ impl SpoticordSession {
|
||||||
|
|
||||||
/// Get the owner
|
/// Get the owner
|
||||||
pub async fn owner(&self) -> Option<UserId> {
|
pub async fn owner(&self) -> Option<UserId> {
|
||||||
self.0.read().await.owner
|
self.acquire_read().await.owner
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the session manager
|
/// Get the session manager
|
||||||
pub async fn session_manager(&self) -> SessionManager {
|
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
|
/// Get the guild id
|
||||||
pub async fn guild_id(&self) -> GuildId {
|
pub async fn guild_id(&self) -> GuildId {
|
||||||
self.0.read().await.guild_id
|
self.acquire_read().await.guild_id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the channel id
|
/// Get the channel id
|
||||||
pub async fn channel_id(&self) -> ChannelId {
|
pub async fn channel_id(&self) -> ChannelId {
|
||||||
self.0.read().await.channel_id
|
self.acquire_read().await.channel_id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the channel id
|
/// Get the channel id
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn text_channel_id(&self) -> ChannelId {
|
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
|
/// Get the playback info
|
||||||
pub async fn playback_info(&self) -> Option<PlaybackInfo> {
|
pub async fn playback_info(&self) -> Option<PlaybackInfo> {
|
||||||
self.0.read().await.playback_info.clone()
|
self.acquire_read().await.playback_info.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn call(&self) -> Arc<Mutex<Call>> {
|
pub async fn call(&self) -> Arc<Mutex<Call>> {
|
||||||
self.0.read().await.call.clone()
|
self.acquire_read().await.call.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn http(&self) -> Arc<Http> {
|
pub async fn http(&self) -> Arc<Http> {
|
||||||
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 {
|
impl InnerSpoticordSession {
|
||||||
/// Internal version of disconnect, which does not abort the disconnect timer
|
/// Internal version of disconnect, which does not abort the disconnect timer
|
||||||
async fn disconnect_no_abort(&mut self) {
|
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.disconnected = true;
|
||||||
self
|
self
|
||||||
.session_manager
|
.session_manager
|
||||||
|
@ -813,12 +759,6 @@ impl InnerSpoticordSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for InnerSpoticordSession {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
trace!("Dropping inner session");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl EventHandler for SpoticordSession {
|
impl EventHandler for SpoticordSession {
|
||||||
async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> {
|
async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> {
|
||||||
|
|
Loading…
Reference in New Issue