Driver, Tracks: Cleanup of leaky types (#20)
Main goal: a lot of nested future/result folding. This mainly modifies error handling for Tracks and TrackHandles to be more consistent, and hides the underlying channel result passing in get_info. Errors returned should be far clearer, and are domain specific rather than falling back to a very opaque use of the underlying channel error. It should be clearer to users why their handle commands failed, or why they can't make a ytdl track loop or similar. Also fixed/cleaned up Songbird::join(_gateway) to return in a single await, sparing the user from the underlying channel details and repeated Errs. I was trying for some time to extend the same graces to `Call`, but could not figure out a sane way to get a 'static version of the first future in the chain (i.e., the gateway send) so that the whole thing could happen after dropping the lock around the Call. I really wanted to fix this to happen as a single folded await too, but I think this might need some crazy hack or redesign.
This commit is contained in:
@@ -127,10 +127,9 @@ async fn join(msg: Message, state: State) -> Result<(), Box<dyn Error + Send + S
|
|||||||
|
|
||||||
let (_handle, success) = state.songbird.join(guild_id, channel_id).await;
|
let (_handle, success) = state.songbird.join(guild_id, channel_id).await;
|
||||||
|
|
||||||
let content = match success?.recv_async().await {
|
let content = match success {
|
||||||
Ok(Ok(())) => format!("Joined <#{}>!", channel_id),
|
Ok(()) => format!("Joined <#{}>!", channel_id),
|
||||||
Ok(Err(e)) => format!("Failed to join <#{}>! Why: {:?}", channel_id, e),
|
Err(e) => format!("Failed to join <#{}>! Why: {:?}", channel_id, e),
|
||||||
_ => format!("Failed to join <#{}>: Gateway error!", channel_id),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
state
|
state
|
||||||
@@ -237,7 +236,7 @@ async fn pause(msg: Message, state: State) -> Result<(), Box<dyn Error + Send +
|
|||||||
let store = state.trackdata.read().await;
|
let store = state.trackdata.read().await;
|
||||||
|
|
||||||
let content = if let Some(handle) = store.get(&guild_id) {
|
let content = if let Some(handle) = store.get(&guild_id) {
|
||||||
let info = handle.get_info()?.await?;
|
let info = handle.get_info().await?;
|
||||||
|
|
||||||
let paused = match info.playing {
|
let paused = match info.playing {
|
||||||
PlayMode::Play => {
|
PlayMode::Play => {
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ use xsalsa20poly1305::aead::Error as CryptoError;
|
|||||||
|
|
||||||
/// Errors encountered while connecting to a Discord voice server over the driver.
|
/// Errors encountered while connecting to a Discord voice server over the driver.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
/// The driver hung up an internal signaller, either due to another connection attempt
|
||||||
|
/// or a crash.
|
||||||
|
AttemptDiscarded,
|
||||||
/// An error occurred during [en/de]cryption of voice packets or key generation.
|
/// An error occurred during [en/de]cryption of voice packets or key generation.
|
||||||
Crypto(CryptoError),
|
Crypto(CryptoError),
|
||||||
/// Server did not return the expected crypto mode during negotiation.
|
/// Server did not return the expected crypto mode during negotiation.
|
||||||
@@ -83,6 +87,7 @@ impl fmt::Display for Error {
|
|||||||
write!(f, "Failed to connect to Discord RTP server: ")?;
|
write!(f, "Failed to connect to Discord RTP server: ")?;
|
||||||
use Error::*;
|
use Error::*;
|
||||||
match self {
|
match self {
|
||||||
|
AttemptDiscarded => write!(f, "connection attempt was aborted/discarded."),
|
||||||
Crypto(c) => write!(f, "cryptography error {}.", c),
|
Crypto(c) => write!(f, "cryptography error {}.", c),
|
||||||
CryptoModeInvalid => write!(f, "server changed negotiated encryption mode."),
|
CryptoModeInvalid => write!(f, "server changed negotiated encryption mode."),
|
||||||
CryptoModeUnavailable => write!(f, "server did not offer chosen encryption mode."),
|
CryptoModeUnavailable => write!(f, "server did not offer chosen encryption mode."),
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ mod decode_mode;
|
|||||||
pub(crate) mod tasks;
|
pub(crate) mod tasks;
|
||||||
|
|
||||||
pub use config::Config;
|
pub use config::Config;
|
||||||
use connection::error::Result;
|
use connection::error::{Error, Result};
|
||||||
pub use crypto::*;
|
pub use crypto::*;
|
||||||
pub use decode_mode::DecodeMode;
|
pub use decode_mode::DecodeMode;
|
||||||
|
|
||||||
@@ -30,7 +30,12 @@ use crate::{
|
|||||||
EventHandler,
|
EventHandler,
|
||||||
};
|
};
|
||||||
use audiopus::Bitrate;
|
use audiopus::Bitrate;
|
||||||
use flume::{Receiver, SendError, Sender};
|
use core::{
|
||||||
|
future::Future,
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
use flume::{r#async::RecvFut, SendError, Sender};
|
||||||
use tasks::message::CoreMessage;
|
use tasks::message::CoreMessage;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
@@ -80,13 +85,18 @@ impl Driver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Connects to a voice channel using the specified server.
|
/// Connects to a voice channel using the specified server.
|
||||||
|
///
|
||||||
|
/// This method instantly contacts the driver tasks, and its
|
||||||
|
/// does not need to be `await`ed to start the actual connection.
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
pub fn connect(&mut self, info: ConnectionInfo) -> Receiver<Result<()>> {
|
pub fn connect(&mut self, info: ConnectionInfo) -> Connect {
|
||||||
let (tx, rx) = flume::bounded(1);
|
let (tx, rx) = flume::bounded(1);
|
||||||
|
|
||||||
self.raw_connect(info, tx);
|
self.raw_connect(info, tx);
|
||||||
|
|
||||||
rx
|
Connect {
|
||||||
|
inner: rx.into_recv_async(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Connects to a voice channel using the specified server.
|
/// Connects to a voice channel using the specified server.
|
||||||
@@ -285,3 +295,24 @@ impl Drop for Driver {
|
|||||||
let _ = self.sender.send(CoreMessage::Poison);
|
let _ = self.sender.send(CoreMessage::Poison);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Future for a call to [`Driver::connect`].
|
||||||
|
///
|
||||||
|
/// This future awaits the *result* of a connection; the driver
|
||||||
|
/// is messaged at the time of the call.
|
||||||
|
///
|
||||||
|
/// [`Driver::connect`]: Driver::connect
|
||||||
|
pub struct Connect {
|
||||||
|
inner: RecvFut<'static, Result<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Future for Connect {
|
||||||
|
type Output = Result<()>;
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
match Pin::new(&mut self.inner).poll(cx) {
|
||||||
|
Poll::Ready(r) => Poll::Ready(r.map_err(|_| Error::AttemptDiscarded).and_then(|x| x)),
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -322,7 +322,7 @@ impl Mixer {
|
|||||||
if temp_len > 0 || opus_len.is_some() {
|
if temp_len > 0 || opus_len.is_some() {
|
||||||
track.step_frame();
|
track.step_frame();
|
||||||
} else if track.do_loop() {
|
} else if track.do_loop() {
|
||||||
if let Some(time) = track.seek_time(Default::default()) {
|
if let Ok(time) = track.seek_time(Default::default()) {
|
||||||
// have to reproduce self.fire_event here
|
// have to reproduce self.fire_event here
|
||||||
// to circumvent the borrow checker's lack of knowledge.
|
// to circumvent the borrow checker's lack of knowledge.
|
||||||
//
|
//
|
||||||
|
|||||||
20
src/error.rs
20
src/error.rs
@@ -14,6 +14,8 @@ use twilight_gateway::shard::CommandError;
|
|||||||
/// Error returned when a manager or call handler is
|
/// Error returned when a manager or call handler is
|
||||||
/// unable to send messages over Discord's gateway.
|
/// unable to send messages over Discord's gateway.
|
||||||
pub enum JoinError {
|
pub enum JoinError {
|
||||||
|
/// Request to join was dropped, cancelled, or replaced.
|
||||||
|
Dropped,
|
||||||
/// No available gateway connection was provided to send
|
/// No available gateway connection was provided to send
|
||||||
/// voice state update messages.
|
/// voice state update messages.
|
||||||
NoSender,
|
NoSender,
|
||||||
@@ -21,6 +23,9 @@ pub enum JoinError {
|
|||||||
///
|
///
|
||||||
/// [`Call`]: crate::Call
|
/// [`Call`]: crate::Call
|
||||||
NoCall,
|
NoCall,
|
||||||
|
#[cfg(feature = "driver")]
|
||||||
|
/// The driver failed to establish a voice connection.
|
||||||
|
Driver(ConnectionError),
|
||||||
#[cfg(feature = "serenity")]
|
#[cfg(feature = "serenity")]
|
||||||
/// Serenity-specific WebSocket send error.
|
/// Serenity-specific WebSocket send error.
|
||||||
Serenity(TrySendError<InterMessage>),
|
Serenity(TrySendError<InterMessage>),
|
||||||
@@ -34,8 +39,11 @@ impl fmt::Display for JoinError {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "Failed to Join Voice channel: ")?;
|
write!(f, "Failed to Join Voice channel: ")?;
|
||||||
match self {
|
match self {
|
||||||
|
JoinError::Dropped => write!(f, "request was cancelled/dropped."),
|
||||||
JoinError::NoSender => write!(f, "no gateway destination."),
|
JoinError::NoSender => write!(f, "no gateway destination."),
|
||||||
JoinError::NoCall => write!(f, "tried to leave a non-existent call."),
|
JoinError::NoCall => write!(f, "tried to leave a non-existent call."),
|
||||||
|
#[cfg(feature = "driver")]
|
||||||
|
JoinError::Driver(t) => write!(f, "internal driver error {}.", t),
|
||||||
#[cfg(feature = "serenity")]
|
#[cfg(feature = "serenity")]
|
||||||
JoinError::Serenity(t) => write!(f, "serenity failure {}.", t),
|
JoinError::Serenity(t) => write!(f, "serenity failure {}.", t),
|
||||||
#[cfg(feature = "twilight")]
|
#[cfg(feature = "twilight")]
|
||||||
@@ -61,9 +69,19 @@ impl From<CommandError> for JoinError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "driver", feature = "gateway"))]
|
||||||
|
impl From<ConnectionError> for JoinError {
|
||||||
|
fn from(e: ConnectionError) -> Self {
|
||||||
|
JoinError::Driver(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gateway")]
|
#[cfg(feature = "gateway")]
|
||||||
/// Convenience type for Discord gateway error handling.
|
/// Convenience type for Discord gateway error handling.
|
||||||
pub type JoinResult<T> = Result<T, JoinError>;
|
pub type JoinResult<T> = Result<T, JoinError>;
|
||||||
|
|
||||||
#[cfg(feature = "driver")]
|
#[cfg(feature = "driver")]
|
||||||
pub use crate::driver::connection::error::{Error as ConnectionError, Result as ConnectionResult};
|
pub use crate::{
|
||||||
|
driver::connection::error::{Error as ConnectionError, Result as ConnectionResult},
|
||||||
|
tracks::{TrackError, TrackResult},
|
||||||
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::{
|
|||||||
info::{ConnectionInfo, ConnectionProgress},
|
info::{ConnectionInfo, ConnectionProgress},
|
||||||
shards::Shard,
|
shards::Shard,
|
||||||
};
|
};
|
||||||
use flume::{Receiver, Sender};
|
use flume::{r#async::RecvFut, Sender};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
@@ -173,11 +173,21 @@ impl Call {
|
|||||||
|
|
||||||
#[cfg(feature = "driver")]
|
#[cfg(feature = "driver")]
|
||||||
/// Connect or switch to the given voice channel by its Id.
|
/// Connect or switch to the given voice channel by its Id.
|
||||||
|
///
|
||||||
|
/// This function acts as a future in two stages:
|
||||||
|
/// * The first `await` sends the request over the gateway.
|
||||||
|
/// * The second `await`s a the driver's connection attempt.
|
||||||
|
/// To prevent deadlock, any mutexes around this Call
|
||||||
|
/// *must* be released before this result is queried.
|
||||||
|
///
|
||||||
|
/// When using [`Songbird::join`], this pattern is correctly handled for you.
|
||||||
|
///
|
||||||
|
/// [`Songbird::join`]: crate::Songbird::join
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
pub async fn join(
|
pub async fn join(
|
||||||
&mut self,
|
&mut self,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
) -> JoinResult<Receiver<ConnectionResult<()>>> {
|
) -> JoinResult<RecvFut<'static, ConnectionResult<()>>> {
|
||||||
let (tx, rx) = flume::unbounded();
|
let (tx, rx) = flume::unbounded();
|
||||||
|
|
||||||
self.connection = Some((
|
self.connection = Some((
|
||||||
@@ -186,7 +196,7 @@ impl Call {
|
|||||||
Return::Conn(tx),
|
Return::Conn(tx),
|
||||||
));
|
));
|
||||||
|
|
||||||
self.update().await.map(|_| rx)
|
self.update().await.map(|_| rx.into_recv_async())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Join the selected voice channel, *without* running/starting an RTP
|
/// Join the selected voice channel, *without* running/starting an RTP
|
||||||
@@ -194,11 +204,21 @@ impl Call {
|
|||||||
///
|
///
|
||||||
/// Use this if you require connection info for lavalink,
|
/// Use this if you require connection info for lavalink,
|
||||||
/// some other voice implementation, or don't want to use the driver for a given call.
|
/// some other voice implementation, or don't want to use the driver for a given call.
|
||||||
|
///
|
||||||
|
/// This function acts as a future in two stages:
|
||||||
|
/// * The first `await` sends the request over the gateway.
|
||||||
|
/// * The second `await`s voice session data from Discord.
|
||||||
|
/// To prevent deadlock, any mutexes around this Call
|
||||||
|
/// *must* be released before this result is queried.
|
||||||
|
///
|
||||||
|
/// When using [`Songbird::join_gateway`], this pattern is correctly handled for you.
|
||||||
|
///
|
||||||
|
/// [`Songbird::join_gateway`]: crate::Songbird::join_gateway
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
pub async fn join_gateway(
|
pub async fn join_gateway(
|
||||||
&mut self,
|
&mut self,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
) -> JoinResult<Receiver<ConnectionInfo>> {
|
) -> JoinResult<RecvFut<'static, ConnectionInfo>> {
|
||||||
let (tx, rx) = flume::unbounded();
|
let (tx, rx) = flume::unbounded();
|
||||||
|
|
||||||
self.connection = Some((
|
self.connection = Some((
|
||||||
@@ -207,7 +227,7 @@ impl Call {
|
|||||||
Return::Info(tx),
|
Return::Info(tx),
|
||||||
));
|
));
|
||||||
|
|
||||||
self.update().await.map(|_| rx)
|
self.update().await.map(|_| rx.into_recv_async())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Leaves the current voice channel, disconnecting from it.
|
/// Leaves the current voice channel, disconnecting from it.
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ use tokio::process::Command as TokioCommand;
|
|||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
/// Opens an audio file through `ffmpeg` and creates an audio source.
|
/// Opens an audio file through `ffmpeg` and creates an audio source.
|
||||||
|
///
|
||||||
|
/// This source is not seek-compatible.
|
||||||
|
/// If you need looping or track seeking, then consider using
|
||||||
|
/// [`Restartable::ffmpeg`].
|
||||||
|
///
|
||||||
|
/// [`Restartable::ffmpeg`]: crate::input::restartable::Restartable::ffmpeg
|
||||||
pub async fn ffmpeg<P: AsRef<OsStr>>(path: P) -> Result<Input> {
|
pub async fn ffmpeg<P: AsRef<OsStr>>(path: P) -> Result<Input> {
|
||||||
_ffmpeg(path.as_ref()).await
|
_ffmpeg(path.as_ref()).await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,13 @@ const YOUTUBE_DL_COMMAND: &str = if cfg!(feature = "youtube-dlc") {
|
|||||||
|
|
||||||
/// Creates a streamed audio source with `youtube-dl` and `ffmpeg`.
|
/// Creates a streamed audio source with `youtube-dl` and `ffmpeg`.
|
||||||
///
|
///
|
||||||
/// Uses `youtube-dlc` if the `youtube-dlc` feature is enabled.
|
/// This source is not seek-compatible.
|
||||||
|
/// If you need looping or track seeking, then consider using
|
||||||
|
/// [`Restartable::ytdl`].
|
||||||
|
///
|
||||||
|
/// Uses `youtube-dlc` if the `"youtube-dlc"` feature is enabled.
|
||||||
|
///
|
||||||
|
/// [`Restartable::ytdl`]: crate::input::restartable::Restartable::ytdl
|
||||||
pub async fn ytdl(uri: &str) -> Result<Input> {
|
pub async fn ytdl(uri: &str) -> Result<Input> {
|
||||||
_ytdl(uri, &[]).await
|
_ytdl(uri, &[]).await
|
||||||
}
|
}
|
||||||
@@ -110,6 +116,14 @@ pub(crate) async fn _ytdl(uri: &str, pre_args: &[&str]) -> Result<Input> {
|
|||||||
|
|
||||||
/// Creates a streamed audio source from YouTube search results with `youtube-dl(c)`,`ffmpeg`, and `ytsearch`.
|
/// Creates a streamed audio source from YouTube search results with `youtube-dl(c)`,`ffmpeg`, and `ytsearch`.
|
||||||
/// Takes the first video listed from the YouTube search.
|
/// Takes the first video listed from the YouTube search.
|
||||||
|
///
|
||||||
|
/// This source is not seek-compatible.
|
||||||
|
/// If you need looping or track seeking, then consider using
|
||||||
|
/// [`Restartable::ytdl_search`].
|
||||||
|
///
|
||||||
|
/// Uses `youtube-dlc` if the `"youtube-dlc"` feature is enabled.
|
||||||
|
///
|
||||||
|
/// [`Restartable::ytdl_search`]: crate::input::restartable::Restartable::ytdl_search
|
||||||
pub async fn ytdl_search(name: &str) -> Result<Input> {
|
pub async fn ytdl_search(name: &str) -> Result<Input> {
|
||||||
ytdl(&format!("ytsearch1:{}", name)).await
|
ytdl(&format!("ytsearch1:{}", name)).await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ pub use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "gateway")]
|
#[cfg(feature = "gateway")]
|
||||||
pub use crate::{handler::Call, manager::Songbird};
|
pub use crate::{handler::*, manager::*};
|
||||||
|
|
||||||
#[cfg(feature = "serenity")]
|
#[cfg(feature = "serenity")]
|
||||||
pub use crate::serenity::*;
|
pub use crate::serenity::*;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#[cfg(feature = "driver")]
|
#[cfg(feature = "driver")]
|
||||||
use crate::{driver::Config, error::ConnectionResult};
|
use crate::driver::Config;
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{JoinError, JoinResult},
|
error::{JoinError, JoinResult},
|
||||||
id::{ChannelId, GuildId, UserId},
|
id::{ChannelId, GuildId, UserId},
|
||||||
@@ -9,7 +9,6 @@ use crate::{
|
|||||||
};
|
};
|
||||||
#[cfg(feature = "serenity")]
|
#[cfg(feature = "serenity")]
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use flume::Receiver;
|
|
||||||
#[cfg(feature = "serenity")]
|
#[cfg(feature = "serenity")]
|
||||||
use futures::channel::mpsc::UnboundedSender as Sender;
|
use futures::channel::mpsc::UnboundedSender as Sender;
|
||||||
use parking_lot::RwLock as PRwLock;
|
use parking_lot::RwLock as PRwLock;
|
||||||
@@ -114,7 +113,7 @@ impl Songbird {
|
|||||||
client_data.initialised = true;
|
client_data.initialised = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retreives a [`Call`] for the given guild, if one already exists.
|
/// Retrieves a [`Call`] for the given guild, if one already exists.
|
||||||
///
|
///
|
||||||
/// [`Call`]: Call
|
/// [`Call`]: Call
|
||||||
pub fn get<G: Into<GuildId>>(&self, guild_id: G) -> Option<Arc<Mutex<Call>>> {
|
pub fn get<G: Into<GuildId>>(&self, guild_id: G) -> Option<Arc<Mutex<Call>>> {
|
||||||
@@ -122,7 +121,7 @@ impl Songbird {
|
|||||||
map_read.get(&guild_id.into()).cloned()
|
map_read.get(&guild_id.into()).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retreives a [`Call`] for the given guild, creating a new one if
|
/// Retrieves a [`Call`] for the given guild, creating a new one if
|
||||||
/// none is found.
|
/// none is found.
|
||||||
///
|
///
|
||||||
/// This will not join any calls, or cause connection state to change.
|
/// This will not join any calls, or cause connection state to change.
|
||||||
@@ -186,11 +185,7 @@ impl Songbird {
|
|||||||
/// [`Call`]: Call
|
/// [`Call`]: Call
|
||||||
/// [`get`]: Songbird::get
|
/// [`get`]: Songbird::get
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn join<C, G>(
|
pub async fn join<C, G>(&self, guild_id: G, channel_id: C) -> (Arc<Mutex<Call>>, JoinResult<()>)
|
||||||
&self,
|
|
||||||
guild_id: G,
|
|
||||||
channel_id: C,
|
|
||||||
) -> (Arc<Mutex<Call>>, JoinResult<Receiver<ConnectionResult<()>>>)
|
|
||||||
where
|
where
|
||||||
C: Into<ChannelId>,
|
C: Into<ChannelId>,
|
||||||
G: Into<GuildId>,
|
G: Into<GuildId>,
|
||||||
@@ -203,14 +198,22 @@ impl Songbird {
|
|||||||
&self,
|
&self,
|
||||||
guild_id: GuildId,
|
guild_id: GuildId,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
) -> (Arc<Mutex<Call>>, JoinResult<Receiver<ConnectionResult<()>>>) {
|
) -> (Arc<Mutex<Call>>, JoinResult<()>) {
|
||||||
let call = self.get_or_insert(guild_id);
|
let call = self.get_or_insert(guild_id);
|
||||||
|
|
||||||
let result = {
|
let stage_1 = {
|
||||||
let mut handler = call.lock().await;
|
let mut handler = call.lock().await;
|
||||||
handler.join(channel_id).await
|
handler.join(channel_id).await
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let result = match stage_1 {
|
||||||
|
Ok(chan) => chan
|
||||||
|
.await
|
||||||
|
.map_err(|_| JoinError::Dropped)
|
||||||
|
.and_then(|x| x.map_err(JoinError::from)),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
};
|
||||||
|
|
||||||
(call, result)
|
(call, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,7 +229,7 @@ impl Songbird {
|
|||||||
&self,
|
&self,
|
||||||
guild_id: G,
|
guild_id: G,
|
||||||
channel_id: C,
|
channel_id: C,
|
||||||
) -> (Arc<Mutex<Call>>, JoinResult<Receiver<ConnectionInfo>>)
|
) -> (Arc<Mutex<Call>>, JoinResult<ConnectionInfo>)
|
||||||
where
|
where
|
||||||
C: Into<ChannelId>,
|
C: Into<ChannelId>,
|
||||||
G: Into<GuildId>,
|
G: Into<GuildId>,
|
||||||
@@ -238,14 +241,19 @@ impl Songbird {
|
|||||||
&self,
|
&self,
|
||||||
guild_id: GuildId,
|
guild_id: GuildId,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
) -> (Arc<Mutex<Call>>, JoinResult<Receiver<ConnectionInfo>>) {
|
) -> (Arc<Mutex<Call>>, JoinResult<ConnectionInfo>) {
|
||||||
let call = self.get_or_insert(guild_id);
|
let call = self.get_or_insert(guild_id);
|
||||||
|
|
||||||
let result = {
|
let stage_1 = {
|
||||||
let mut handler = call.lock().await;
|
let mut handler = call.lock().await;
|
||||||
handler.join_gateway(channel_id).await
|
handler.join_gateway(channel_id).await
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let result = match stage_1 {
|
||||||
|
Ok(chan) => chan.await.map_err(|_| JoinError::Dropped),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
};
|
||||||
|
|
||||||
(call, result)
|
(call, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
39
src/tracks/error.rs
Normal file
39
src/tracks/error.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
use std::{error::Error, fmt};
|
||||||
|
|
||||||
|
/// Errors associated with control and manipulation of tracks.
|
||||||
|
///
|
||||||
|
/// Unless otherwise stated, these don't invalidate an existing track,
|
||||||
|
/// but do advise on valid operations and commands.
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum TrackError {
|
||||||
|
/// The operation failed because the track has ended, has been removed
|
||||||
|
/// due to call closure, or some error within the driver.
|
||||||
|
Finished,
|
||||||
|
/// The supplied event listener can never be fired by a track, and should
|
||||||
|
/// be attached to the driver instead.
|
||||||
|
InvalidTrackEvent,
|
||||||
|
/// The track's underlying [`Input`] doesn't support seeking operations.
|
||||||
|
///
|
||||||
|
/// [`Input`]: crate::input::Input
|
||||||
|
SeekUnsupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TrackError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Failed to operate on track (handle): ")?;
|
||||||
|
match self {
|
||||||
|
TrackError::Finished => write!(f, "track ended."),
|
||||||
|
TrackError::InvalidTrackEvent =>
|
||||||
|
write!(f, "given event listener can't be fired on a track."),
|
||||||
|
TrackError::SeekUnsupported => write!(f, "track did not support seeking."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for TrackError {}
|
||||||
|
|
||||||
|
/// Alias for most calls to a [`TrackHandle`].
|
||||||
|
///
|
||||||
|
/// [`TrackHandle`]: super::TrackHandle
|
||||||
|
pub type TrackResult<T> = Result<T, TrackError>;
|
||||||
@@ -4,10 +4,7 @@ use crate::{
|
|||||||
input::Metadata,
|
input::Metadata,
|
||||||
};
|
};
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
use tokio::sync::{
|
use tokio::sync::{mpsc::UnboundedSender, oneshot};
|
||||||
mpsc::{error::SendError, UnboundedSender},
|
|
||||||
oneshot,
|
|
||||||
};
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -46,12 +43,12 @@ impl TrackHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Unpauses an audio track.
|
/// Unpauses an audio track.
|
||||||
pub fn play(&self) -> TrackResult {
|
pub fn play(&self) -> TrackResult<()> {
|
||||||
self.send(TrackCommand::Play)
|
self.send(TrackCommand::Play)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pauses an audio track.
|
/// Pauses an audio track.
|
||||||
pub fn pause(&self) -> TrackResult {
|
pub fn pause(&self) -> TrackResult<()> {
|
||||||
self.send(TrackCommand::Pause)
|
self.send(TrackCommand::Pause)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,12 +58,12 @@ impl TrackHandle {
|
|||||||
/// a [`TrackEvent::End`] event.
|
/// a [`TrackEvent::End`] event.
|
||||||
///
|
///
|
||||||
/// [`TrackEvent::End`]: crate::events::TrackEvent::End
|
/// [`TrackEvent::End`]: crate::events::TrackEvent::End
|
||||||
pub fn stop(&self) -> TrackResult {
|
pub fn stop(&self) -> TrackResult<()> {
|
||||||
self.send(TrackCommand::Stop)
|
self.send(TrackCommand::Stop)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the volume of an audio track.
|
/// Sets the volume of an audio track.
|
||||||
pub fn set_volume(&self, volume: f32) -> TrackResult {
|
pub fn set_volume(&self, volume: f32) -> TrackResult<()> {
|
||||||
self.send(TrackCommand::Volume(volume))
|
self.send(TrackCommand::Volume(volume))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,43 +80,43 @@ impl TrackHandle {
|
|||||||
|
|
||||||
/// Seeks along the track to the specified position.
|
/// Seeks along the track to the specified position.
|
||||||
///
|
///
|
||||||
/// If the underlying [`Input`] does not support this behaviour,
|
/// If the underlying [`Input`] does not support seeking,
|
||||||
/// then all calls will fail.
|
/// then all calls will fail with [`TrackError::SeekUnsupported`].
|
||||||
///
|
///
|
||||||
/// [`Input`]: crate::input::Input
|
/// [`Input`]: crate::input::Input
|
||||||
pub fn seek_time(&self, position: Duration) -> TrackResult {
|
/// [`TrackError::SeekUnsupported`]: TrackError::SeekUnsupported
|
||||||
|
pub fn seek_time(&self, position: Duration) -> TrackResult<()> {
|
||||||
if self.seekable {
|
if self.seekable {
|
||||||
self.send(TrackCommand::Seek(position))
|
self.send(TrackCommand::Seek(position))
|
||||||
} else {
|
} else {
|
||||||
Err(SendError(TrackCommand::Seek(position)))
|
Err(TrackError::SeekUnsupported)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attach an event handler to an audio track. These will receive [`EventContext::Track`].
|
/// Attach an event handler to an audio track. These will receive [`EventContext::Track`].
|
||||||
///
|
///
|
||||||
/// Users **must** ensure that no costly work or blocking occurs
|
/// Events which can only be fired by the global context return [`TrackError::InvalidTrackEvent`]
|
||||||
/// within the supplied function or closure. *Taking excess time could prevent
|
|
||||||
/// timely sending of packets, causing audio glitches and delays*.
|
|
||||||
///
|
///
|
||||||
/// [`Track`]: Track
|
/// [`Track`]: Track
|
||||||
/// [`EventContext::Track`]: crate::events::EventContext::Track
|
/// [`EventContext::Track`]: crate::events::EventContext::Track
|
||||||
pub fn add_event<F: EventHandler + 'static>(&self, event: Event, action: F) -> TrackResult {
|
/// [`TrackError::InvalidTrackEvent`]: TrackError::InvalidTrackEvent
|
||||||
|
pub fn add_event<F: EventHandler + 'static>(&self, event: Event, action: F) -> TrackResult<()> {
|
||||||
let cmd = TrackCommand::AddEvent(EventData::new(event, action));
|
let cmd = TrackCommand::AddEvent(EventData::new(event, action));
|
||||||
if event.is_global_only() {
|
if event.is_global_only() {
|
||||||
Err(SendError(cmd))
|
Err(TrackError::InvalidTrackEvent)
|
||||||
} else {
|
} else {
|
||||||
self.send(cmd)
|
self.send(cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform an arbitrary action on a raw [`Track`] object.
|
/// Perform an arbitrary synchronous action on a raw [`Track`] object.
|
||||||
///
|
///
|
||||||
/// Users **must** ensure that no costly work or blocking occurs
|
/// Users **must** ensure that no costly work or blocking occurs
|
||||||
/// within the supplied function or closure. *Taking excess time could prevent
|
/// within the supplied function or closure. *Taking excess time could prevent
|
||||||
/// timely sending of packets, causing audio glitches and delays*.
|
/// timely sending of packets, causing audio glitches and delays*.
|
||||||
///
|
///
|
||||||
/// [`Track`]: Track
|
/// [`Track`]: Track
|
||||||
pub fn action<F>(&self, action: F) -> TrackResult
|
pub fn action<F>(&self, action: F) -> TrackResult<()>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Track) + Send + Sync + 'static,
|
F: FnOnce(&mut Track) + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
@@ -127,38 +124,55 @@ impl TrackHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Request playback information and state from the audio context.
|
/// Request playback information and state from the audio context.
|
||||||
///
|
pub async fn get_info(&self) -> TrackResult<Box<TrackState>> {
|
||||||
/// Crucially, the audio thread will respond *at a later time*:
|
|
||||||
/// It is up to the user when or how this should be read from the returned channel.
|
|
||||||
pub fn get_info(&self) -> TrackQueryResult {
|
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
self.send(TrackCommand::Request(tx)).map(move |_| rx)
|
self.send(TrackCommand::Request(tx))?;
|
||||||
|
|
||||||
|
rx.await.map_err(|_| TrackError::Finished)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set an audio track to loop indefinitely.
|
/// Set an audio track to loop indefinitely.
|
||||||
pub fn enable_loop(&self) -> TrackResult {
|
///
|
||||||
|
/// If the underlying [`Input`] does not support seeking,
|
||||||
|
/// then all calls will fail with [`TrackError::SeekUnsupported`].
|
||||||
|
///
|
||||||
|
/// [`Input`]: crate::input::Input
|
||||||
|
/// [`TrackError::SeekUnsupported`]: TrackError::SeekUnsupported
|
||||||
|
pub fn enable_loop(&self) -> TrackResult<()> {
|
||||||
if self.seekable {
|
if self.seekable {
|
||||||
self.send(TrackCommand::Loop(LoopState::Infinite))
|
self.send(TrackCommand::Loop(LoopState::Infinite))
|
||||||
} else {
|
} else {
|
||||||
Err(SendError(TrackCommand::Loop(LoopState::Infinite)))
|
Err(TrackError::SeekUnsupported)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set an audio track to no longer loop.
|
/// Set an audio track to no longer loop.
|
||||||
pub fn disable_loop(&self) -> TrackResult {
|
///
|
||||||
|
/// If the underlying [`Input`] does not support seeking,
|
||||||
|
/// then all calls will fail with [`TrackError::SeekUnsupported`].
|
||||||
|
///
|
||||||
|
/// [`Input`]: crate::input::Input
|
||||||
|
/// [`TrackError::SeekUnsupported`]: TrackError::SeekUnsupported
|
||||||
|
pub fn disable_loop(&self) -> TrackResult<()> {
|
||||||
if self.seekable {
|
if self.seekable {
|
||||||
self.send(TrackCommand::Loop(LoopState::Finite(0)))
|
self.send(TrackCommand::Loop(LoopState::Finite(0)))
|
||||||
} else {
|
} else {
|
||||||
Err(SendError(TrackCommand::Loop(LoopState::Finite(0))))
|
Err(TrackError::SeekUnsupported)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set an audio track to loop a set number of times.
|
/// Set an audio track to loop a set number of times.
|
||||||
pub fn loop_for(&self, count: usize) -> TrackResult {
|
///
|
||||||
|
/// If the underlying [`Input`] does not support seeking,
|
||||||
|
/// then all calls will fail with [`TrackError::SeekUnsupported`].
|
||||||
|
///
|
||||||
|
/// [`Input`]: crate::input::Input
|
||||||
|
/// [`TrackError::SeekUnsupported`]: TrackError::SeekUnsupported
|
||||||
|
pub fn loop_for(&self, count: usize) -> TrackResult<()> {
|
||||||
if self.seekable {
|
if self.seekable {
|
||||||
self.send(TrackCommand::Loop(LoopState::Finite(count)))
|
self.send(TrackCommand::Loop(LoopState::Finite(count)))
|
||||||
} else {
|
} else {
|
||||||
Err(SendError(TrackCommand::Loop(LoopState::Finite(count))))
|
Err(TrackError::SeekUnsupported)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +196,11 @@ impl TrackHandle {
|
|||||||
/// Send a raw command to the [`Track`] object.
|
/// Send a raw command to the [`Track`] object.
|
||||||
///
|
///
|
||||||
/// [`Track`]: Track
|
/// [`Track`]: Track
|
||||||
pub fn send(&self, cmd: TrackCommand) -> TrackResult {
|
pub fn send(&self, cmd: TrackCommand) -> TrackResult<()> {
|
||||||
self.command_channel.send(cmd)
|
// As the send channels are unbounded, we can be reasonably certain
|
||||||
|
// that send failure == cancellation.
|
||||||
|
self.command_channel
|
||||||
|
.send(cmd)
|
||||||
|
.map_err(|_e| TrackError::Finished)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,24 +15,18 @@
|
|||||||
//! [`create_player`]: fn.create_player.html
|
//! [`create_player`]: fn.create_player.html
|
||||||
|
|
||||||
mod command;
|
mod command;
|
||||||
|
mod error;
|
||||||
mod handle;
|
mod handle;
|
||||||
mod looping;
|
mod looping;
|
||||||
mod mode;
|
mod mode;
|
||||||
mod queue;
|
mod queue;
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
pub use self::{command::*, handle::*, looping::*, mode::*, queue::*, state::*};
|
pub use self::{command::*, error::*, handle::*, looping::*, mode::*, queue::*, state::*};
|
||||||
|
|
||||||
use crate::{constants::*, driver::tasks::message::*, events::EventStore, input::Input};
|
use crate::{constants::*, driver::tasks::message::*, events::EventStore, input::Input};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::sync::{
|
use tokio::sync::mpsc::{self, error::TryRecvError, UnboundedReceiver};
|
||||||
mpsc::{
|
|
||||||
self,
|
|
||||||
error::{SendError, TryRecvError},
|
|
||||||
UnboundedReceiver,
|
|
||||||
},
|
|
||||||
oneshot::Receiver as OneshotReceiver,
|
|
||||||
};
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
/// Control object for audio playback.
|
/// Control object for audio playback.
|
||||||
@@ -63,18 +57,18 @@ use uuid::Uuid;
|
|||||||
/// # };
|
/// # };
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [`Driver::play_only`]: ../struct.Driver.html#method.play_only
|
/// [`Driver::play_only`]: crate::driver::Driver::play_only
|
||||||
/// [`Driver::play`]: ../struct.Driver.html#method.play
|
/// [`Driver::play`]: crate::driver::Driver::play
|
||||||
/// [`TrackHandle`]: struct.TrackHandle.html
|
/// [`TrackHandle`]: TrackHandle
|
||||||
/// [`create_player`]: fn.create_player.html
|
/// [`create_player`]: create_player
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Track {
|
pub struct Track {
|
||||||
/// Whether or not this sound is currently playing.
|
/// Whether or not this sound is currently playing.
|
||||||
///
|
///
|
||||||
/// Can be controlled with [`play`] or [`pause`] if chaining is desired.
|
/// Can be controlled with [`play`] or [`pause`] if chaining is desired.
|
||||||
///
|
///
|
||||||
/// [`play`]: #method.play
|
/// [`play`]: Track::play
|
||||||
/// [`pause`]: #method.pause
|
/// [`pause`]: Track::pause
|
||||||
pub(crate) playing: PlayMode,
|
pub(crate) playing: PlayMode,
|
||||||
|
|
||||||
/// The desired volume for playback.
|
/// The desired volume for playback.
|
||||||
@@ -83,7 +77,7 @@ pub struct Track {
|
|||||||
///
|
///
|
||||||
/// Can be controlled with [`volume`] if chaining is desired.
|
/// Can be controlled with [`volume`] if chaining is desired.
|
||||||
///
|
///
|
||||||
/// [`volume`]: #method.volume
|
/// [`volume`]: Track::volume
|
||||||
pub(crate) volume: f32,
|
pub(crate) volume: f32,
|
||||||
|
|
||||||
/// Underlying data access object.
|
/// Underlying data access object.
|
||||||
@@ -187,7 +181,7 @@ impl Track {
|
|||||||
|
|
||||||
/// Sets [`volume`] in a manner that allows method chaining.
|
/// Sets [`volume`] in a manner that allows method chaining.
|
||||||
///
|
///
|
||||||
/// [`volume`]: #structfield.volume
|
/// [`volume`]: Track::volume
|
||||||
pub fn set_volume(&mut self, volume: f32) -> &mut Self {
|
pub fn set_volume(&mut self, volume: f32) -> &mut Self {
|
||||||
self.volume = volume;
|
self.volume = volume;
|
||||||
|
|
||||||
@@ -209,12 +203,20 @@ impl Track {
|
|||||||
self.play_time
|
self.play_time
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets [`loops`] in a manner that allows method chaining.
|
/// Set an audio track to loop a set number of times.
|
||||||
///
|
///
|
||||||
/// [`loops`]: #structfield.loops
|
/// If the underlying [`Input`] does not support seeking,
|
||||||
pub fn set_loops(&mut self, loops: LoopState) -> &mut Self {
|
/// then all calls will fail with [`TrackError::SeekUnsupported`].
|
||||||
self.loops = loops;
|
///
|
||||||
self
|
/// [`Input`]: crate::input::Input
|
||||||
|
/// [`TrackError::SeekUnsupported`]: TrackError::SeekUnsupported
|
||||||
|
pub fn set_loops(&mut self, loops: LoopState) -> TrackResult<()> {
|
||||||
|
if self.source.is_seekable() {
|
||||||
|
self.loops = loops;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(TrackError::SeekUnsupported)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn do_loop(&mut self) -> bool {
|
pub(crate) fn do_loop(&mut self) -> bool {
|
||||||
@@ -234,11 +236,9 @@ impl Track {
|
|||||||
self.play_time += TIMESTEP_LENGTH;
|
self.play_time += TIMESTEP_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Receives and acts upon any commands forwarded by [`TrackHandle`]s.
|
/// Receives and acts upon any commands forwarded by TrackHandles.
|
||||||
///
|
///
|
||||||
/// *Used internally*, this should not be exposed to users.
|
/// *Used internally*, this should not be exposed to users.
|
||||||
///
|
|
||||||
/// [`TrackHandle`]: struct.TrackHandle.html
|
|
||||||
pub(crate) fn process_commands(&mut self, index: usize, ic: &Interconnect) {
|
pub(crate) fn process_commands(&mut self, index: usize, ic: &Interconnect) {
|
||||||
// Note: disconnection and an empty channel are both valid,
|
// Note: disconnection and an empty channel are both valid,
|
||||||
// and should allow the audio object to keep running as intended.
|
// and should allow the audio object to keep running as intended.
|
||||||
@@ -280,13 +280,13 @@ impl Track {
|
|||||||
TrackStateChange::Volume(self.volume),
|
TrackStateChange::Volume(self.volume),
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
Seek(time) => {
|
Seek(time) =>
|
||||||
self.seek_time(time);
|
if let Ok(new_time) = self.seek_time(time) {
|
||||||
let _ = ic.events.send(EventMessage::ChangeState(
|
let _ = ic.events.send(EventMessage::ChangeState(
|
||||||
index,
|
index,
|
||||||
TrackStateChange::Position(self.position),
|
TrackStateChange::Position(new_time),
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
AddEvent(evt) => {
|
AddEvent(evt) => {
|
||||||
let _ = ic.events.send(EventMessage::AddTrackEvent(index, evt));
|
let _ = ic.events.send(EventMessage::AddTrackEvent(index, evt));
|
||||||
},
|
},
|
||||||
@@ -300,13 +300,13 @@ impl Track {
|
|||||||
Request(tx) => {
|
Request(tx) => {
|
||||||
let _ = tx.send(Box::new(self.state()));
|
let _ = tx.send(Box::new(self.state()));
|
||||||
},
|
},
|
||||||
Loop(loops) => {
|
Loop(loops) =>
|
||||||
self.set_loops(loops);
|
if self.set_loops(loops).is_ok() {
|
||||||
let _ = ic.events.send(EventMessage::ChangeState(
|
let _ = ic.events.send(EventMessage::ChangeState(
|
||||||
index,
|
index,
|
||||||
TrackStateChange::Loops(self.loops, true),
|
TrackStateChange::Loops(self.loops, true),
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(TryRecvError::Closed) => {
|
Err(TryRecvError::Closed) => {
|
||||||
@@ -325,7 +325,7 @@ impl Track {
|
|||||||
/// The primary use-case of this is sending information across
|
/// The primary use-case of this is sending information across
|
||||||
/// threads in response to a [`TrackHandle`].
|
/// threads in response to a [`TrackHandle`].
|
||||||
///
|
///
|
||||||
/// [`TrackHandle`]: struct.TrackHandle.html
|
/// [`TrackHandle`]: TrackHandle
|
||||||
pub fn state(&self) -> TrackState {
|
pub fn state(&self) -> TrackState {
|
||||||
TrackState {
|
TrackState {
|
||||||
playing: self.playing,
|
playing: self.playing,
|
||||||
@@ -338,15 +338,18 @@ impl Track {
|
|||||||
|
|
||||||
/// Seek to a specific point in the track.
|
/// Seek to a specific point in the track.
|
||||||
///
|
///
|
||||||
/// Returns `None` if unsupported.
|
/// If the underlying [`Input`] does not support seeking,
|
||||||
pub fn seek_time(&mut self, pos: Duration) -> Option<Duration> {
|
/// then all calls will fail with [`TrackError::SeekUnsupported`].
|
||||||
let out = self.source.seek_time(pos);
|
///
|
||||||
|
/// [`Input`]: crate::input::Input
|
||||||
if let Some(t) = out {
|
/// [`TrackError::SeekUnsupported`]: TrackError::SeekUnsupported
|
||||||
|
pub fn seek_time(&mut self, pos: Duration) -> TrackResult<Duration> {
|
||||||
|
if let Some(t) = self.source.seek_time(pos) {
|
||||||
self.position = t;
|
self.position = t;
|
||||||
|
Ok(t)
|
||||||
|
} else {
|
||||||
|
Err(TrackError::SeekUnsupported)
|
||||||
}
|
}
|
||||||
|
|
||||||
out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns this track's unique identifier.
|
/// Returns this track's unique identifier.
|
||||||
@@ -373,22 +376,3 @@ pub fn create_player(source: Input) -> (Track, TrackHandle) {
|
|||||||
|
|
||||||
(player, handle)
|
(player, handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Alias for most result-free calls to a [`TrackHandle`].
|
|
||||||
///
|
|
||||||
/// Failure indicates that the accessed audio object has been
|
|
||||||
/// removed or deleted by the audio context.
|
|
||||||
///
|
|
||||||
/// [`TrackHandle`]: TrackHandle
|
|
||||||
pub type TrackResult = Result<(), SendError<TrackCommand>>;
|
|
||||||
|
|
||||||
/// Alias for return value from calls to [`TrackHandle::get_info`].
|
|
||||||
///
|
|
||||||
/// Crucially, the audio thread will respond *at a later time*:
|
|
||||||
/// It is up to the user when or how this should be read from the returned channel.
|
|
||||||
///
|
|
||||||
/// Failure indicates that the accessed audio object has been
|
|
||||||
/// removed or deleted by the audio context.
|
|
||||||
///
|
|
||||||
/// [`TrackHandle::get_info`]: TrackHandle::get_info
|
|
||||||
pub type TrackQueryResult = Result<OneshotReceiver<Box<TrackState>>, SendError<TrackCommand>>;
|
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ impl TrackQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Pause the track at the head of the queue.
|
/// Pause the track at the head of the queue.
|
||||||
pub fn pause(&self) -> TrackResult {
|
pub fn pause(&self) -> TrackResult<()> {
|
||||||
let inner = self.inner.lock();
|
let inner = self.inner.lock();
|
||||||
|
|
||||||
if let Some(handle) = inner.tracks.front() {
|
if let Some(handle) = inner.tracks.front() {
|
||||||
@@ -251,7 +251,7 @@ impl TrackQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Resume the track at the head of the queue.
|
/// Resume the track at the head of the queue.
|
||||||
pub fn resume(&self) -> TrackResult {
|
pub fn resume(&self) -> TrackResult<()> {
|
||||||
let inner = self.inner.lock();
|
let inner = self.inner.lock();
|
||||||
|
|
||||||
if let Some(handle) = inner.tracks.front() {
|
if let Some(handle) = inner.tracks.front() {
|
||||||
@@ -273,7 +273,7 @@ impl TrackQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Skip to the next track in the queue, if it exists.
|
/// Skip to the next track in the queue, if it exists.
|
||||||
pub fn skip(&self) -> TrackResult {
|
pub fn skip(&self) -> TrackResult<()> {
|
||||||
let inner = self.inner.lock();
|
let inner = self.inner.lock();
|
||||||
|
|
||||||
inner.stop_current()
|
inner.stop_current()
|
||||||
@@ -295,7 +295,7 @@ impl TrackQueue {
|
|||||||
|
|
||||||
impl TrackQueueCore {
|
impl TrackQueueCore {
|
||||||
/// Skip to the next track in the queue, if it exists.
|
/// Skip to the next track in the queue, if it exists.
|
||||||
fn stop_current(&self) -> TrackResult {
|
fn stop_current(&self) -> TrackResult<()> {
|
||||||
if let Some(handle) = self.tracks.front() {
|
if let Some(handle) = self.tracks.front() {
|
||||||
handle.stop()
|
handle.stop()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user