aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZeyla Hellyer <[email protected]>2017-02-11 12:10:03 -0800
committerZeyla Hellyer <[email protected]>2017-02-11 12:10:03 -0800
commit3388a13d06993cae8647fe2720660ad33594284d (patch)
treeb32435c6aba22e92918c6507a8faf0dfd8220d5c
parentUpdate examples for cache rewrite (diff)
downloadserenity-3388a13d06993cae8647fe2720660ad33594284d.tar.xz
serenity-3388a13d06993cae8647fe2720660ad33594284d.zip
Allow standalone voice connections
Allow voice connections be built standalone from the primary websocket connection to the gateway. This has the side-effect of reduced functionality (e.g. muting or deafening only update the internal handler state), but allows voice connections to be handled independently from the rest of the lib.
-rw-r--r--src/ext/voice/handler.rs394
-rw-r--r--src/ext/voice/mod.rs2
2 files changed, 245 insertions, 151 deletions
diff --git a/src/ext/voice/handler.rs b/src/ext/voice/handler.rs
index 949e725..0ef518a 100644
--- a/src/ext/voice/handler.rs
+++ b/src/ext/voice/handler.rs
@@ -11,9 +11,15 @@ use super::threading;
/// The handler is responsible for "handling" a single voice connection, acting
/// as a clean API above the inner connection.
///
+/// Look into the [`Manager`] for a slightly higher-level interface for managing
+/// the existence of handlers.
+///
+/// **Note**: You should _not_ manually mutate any struct fields. You should
+/// _only_ read them. Use methods to mutate them.
+///
/// # Examples
///
-/// Assuming that you already have a [`Manager`], most likely retrieved via a
+/// Assuming that you already have a `Manager`, most likely retrieved via a
/// [WebSocket connection], you can join a guild's voice channel and deafen
/// yourself like so:
///
@@ -32,15 +38,69 @@ use super::threading;
/// [`Manager`]: struct.Manager.html
/// [WebSocket connection]: ../../client/struct.Connection.html
pub struct Handler {
- channel_id: Option<ChannelId>,
- endpoint_token: Option<(String, String)>,
- guild_id: Option<GuildId>,
- self_deaf: bool,
- self_mute: bool,
+ /// The ChannelId to be connected to, if any.
+ ///
+ /// Note that when connected to a voice channel, while the `ChannelId` will
+ /// not be `None`, the [`guild_id`] can, in the event of [`Group`] or
+ /// 1-on-1 [`Call`]s.
+ ///
+ /// **Note**: This _must not_ be manually mutated. Call [`switch_to`] to
+ /// mutate this value.
+ ///
+ /// [`Call`]: ../../model/struct.Call.html
+ /// [`Group`]: ../../model/struct.Group.html
+ /// [`guild`]: #structfield.guild
+ /// [`switch_to`]: #method.switch_to
+ pub channel_id: Option<ChannelId>,
+ /// The voice server endpoint.
+ pub endpoint: Option<String>,
+ /// The GuildId to be connected to, if any. Can be normally `None` in the
+ /// event of playing audio to a one-on-one [`Call`] or [`Group`].
+ ///
+ /// [`Call`]: ../../model/struct.Call.html
+ /// [`Group`]: ../../model/struct.Group.html
+ pub guild_id: Option<GuildId>,
+ /// Whether the current handler is set to deafen voice connections.
+ ///
+ /// **Note**: This _must not_ be manually mutated. Call [`deafen`] to
+ /// mutate this value.
+ ///
+ /// [`deafen`]: #method.deafen
+ pub self_deaf: bool,
+ /// Whether the current handler is set to mute voice connections.
+ ///
+ /// **Note**: This _must not_ be manually mutated. Call [`mute`] to mutate
+ /// this value.
+ ///
+ /// [`mute`]: #method.mute
+ pub self_mute: bool,
+ /// The internal sender to the voice connection monitor thread.
sender: MpscSender<VoiceStatus>,
- session_id: Option<String>,
- user_id: UserId,
- ws: MpscSender<GatewayStatus>,
+ /// The session Id of the current voice connection, if any.
+ ///
+ /// **Note**: This _should_ be set through an [`update_state`] call.
+ ///
+ /// [`update_state`]: #method.update_state
+ pub session_id: Option<String>,
+ /// The token of the current voice connection, if any.
+ ///
+ /// **Note**: This _should_ be set through an [`update_server`] call.
+ ///
+ /// [`update_server`]: #method.update_server
+ pub token: Option<String>,
+ /// The Id of the current user.
+ ///
+ /// This is configured via [`new`] or [`standalone`].
+ ///
+ /// [`new`]: #method.new
+ /// [`standalone`]: #method.standalone
+ pub user_id: UserId,
+ /// Will be set when a `Handler` is made via the [`new`][`Handler::new`]
+ /// method.
+ ///
+ /// When set via [`standalone`][`Handler::standalone`], it will not be
+ /// present.
+ ws: Option<MpscSender<GatewayStatus>>,
}
impl Handler {
@@ -53,43 +113,73 @@ impl Handler {
///
/// [`Manager::join`]: struct.Manager.html#method.join
#[doc(hidden)]
- pub fn new(target: Target, ws: MpscSender<GatewayStatus>, user_id: UserId)
- -> Self {
- let (tx, rx) = mpsc::channel();
+ #[inline]
+ pub fn new(target: Target, ws: MpscSender<GatewayStatus>, user_id: UserId) -> Self {
+ Self::new_raw(target, Some(ws), user_id)
+ }
- let (channel_id, guild_id) = match target {
- Target::Channel(channel_id) => (Some(channel_id), None),
- Target::Guild(guild_id) => (None, Some(guild_id)),
+ /// Creates a new, standalone Handler which is not connected to the primary
+ /// WebSocket to the Gateway.
+ ///
+ /// Actions such as muting, deafening, and switching channels will not
+ /// function through this Handler and must be done through some other
+ /// method, as the values will only be internally updated.
+ ///
+ /// For most use cases you do not want this. Only use it if you are using
+ /// the voice component standalone from the rest of the library.
+ #[inline]
+ pub fn standalone(target: Target, user_id: UserId) -> Self {
+ Self::new_raw(target, None, user_id)
+ }
+
+ /// Connects to the voice channel if the following are present:
+ ///
+ /// - [`endpoint`]
+ /// - [`session_id`]
+ /// - [`token`]
+ ///
+ /// If they _are_ all present, then `true` is returned. Otherwise, `false`
+ /// is.
+ ///
+ /// This will automatically be called by [`update_server`] or
+ /// [`update_state`] when all three values become present.
+ ///
+ /// [`endpoint`]: #structfield.endpoint
+ /// [`session_id`]: #structfield.session_id
+ /// [`token`]: #structfield.token
+ /// [`update_server`]: #method.update_server
+ /// [`update_state`]: #method.update_state
+ pub fn connect(&mut self) -> bool {
+ if self.endpoint.is_none() || self.session_id.is_none() || self.token.is_none() {
+ return false;
+ }
+
+ let target_id = if let Some(guild_id) = self.guild_id {
+ guild_id.0
+ } else if let Some(channel_id) = self.channel_id {
+ channel_id.0
+ } else {
+ // Theoretically never happens? This needs to be researched more.
+ error!("(╯°□°)╯︵ ┻━┻ No guild/channel ID when connecting");
+
+ return false;
};
- threading::start(target, rx);
+ // Safe as all of these being present was already checked.
+ let endpoint = self.endpoint.clone().unwrap();
+ let session_id = self.session_id.clone().unwrap();
+ let token = self.token.clone().unwrap();
+ let user_id = self.user_id;
- Handler {
- channel_id: channel_id,
- endpoint_token: None,
- guild_id: guild_id,
- self_deaf: false,
- self_mute: false,
- sender: tx,
- session_id: None,
+ self.send(VoiceStatus::Connect(ConnectionInfo {
+ endpoint: endpoint,
+ session_id: session_id,
+ target_id: target_id,
+ token: token,
user_id: user_id,
- ws: ws,
- }
- }
+ }));
- /// Retrieves the current connected voice channel's `ChannelId`, if connected
- /// to one.
- ///
- /// Note that when connected to a voice channel, while the `ChannelId` will
- /// not be `None`, the [`GuildId`] retrieved via [`guild`] can, in the event
- /// of [`Group`] or 1-on-1 [`Call`]s.
- ///
- /// [`Call`]: ../../model/struct.Call.html
- /// [`Group`]: ../../model/struct.Group.html
- /// [`GuildId`]: ../../model/struct.GuildId.html
- /// [`guild`]: #method.guild
- pub fn channel(&self) -> Option<ChannelId> {
- self.channel_id
+ true
}
/// Sets whether the current connection to be deafened.
@@ -99,6 +189,11 @@ impl Handler {
///
/// **Note**: Unlike in the official client, you _can_ be deafened while
/// not being muted.
+ ///
+ /// **Note**: If the `Handler` was created via [`standalone`], then this
+ /// will _only_ update whether the connection is internally deafened.
+ ///
+ /// [`standalone`]: #method.standalone
pub fn deafen(&mut self, deaf: bool) {
self.self_deaf = deaf;
@@ -111,39 +206,6 @@ impl Handler {
}
}
- /// Retrieves the current connected voice channel's `GuildId`, if connected
- /// to one.
- ///
- /// Note that the `GuildId` can be `None` in the event of [`Group`] or
- /// 1-on-1 [`Call`]s, although when connected to a voice channel, the
- /// [`ChannelId`] retrieved via [`channel`] will be `Some`.
- ///
- /// [`Call`]: ../../model/struct.Call.html
- /// [`ChannelId`]: ../../model/struct.ChannelId.html
- /// [`Group`]: ../../model/struct.Group.html
- /// [`channel`]: #method.channel
- pub fn guild(&self) -> Option<GuildId> {
- self.guild_id
- }
-
- /// Whether the current handler is set to deafen voice connections.
- ///
- /// Use [`deafen`] to modify this configuration.
- ///
- /// [`deafen`]: #method.deafen
- pub fn is_deafened(&self) -> bool {
- self.self_deaf
- }
-
- /// Whether the current handler is set to mute voice connections.
- ///
- /// Use [`mute`] to modify this configuration.
- ///
- /// [`mute`]: #method.mute
- pub fn is_muted(&self) -> bool {
- self.self_mute
- }
-
/// Connect - or switch - to the given voice channel by its Id.
///
/// **Note**: This is not necessary for [`Group`] or direct [call][`Call`]s.
@@ -153,14 +215,21 @@ impl Handler {
pub fn join(&mut self, channel_id: ChannelId) {
self.channel_id = Some(channel_id);
- self.connect();
+ self.send_join();
}
/// Leaves the current voice channel, disconnecting from it.
///
/// This does _not_ forget settings, like whether to be self-deafened or
/// self-muted.
+ ///
+ /// **Note**: If the `Handler` was created via [`standalone`], then this
+ /// will _only_ update whether the connection is internally connected to a
+ /// voice channel.
+ ///
+ /// [`standalone`]: #method.standalone
pub fn leave(&mut self) {
+ // Only send an update if we were in a voice channel.
if let Some(_) = self.channel_id {
self.channel_id = None;
@@ -183,6 +252,11 @@ impl Handler {
///
/// If there is no live voice connection, then this only acts as a settings
/// update for future connections.
+ ///
+ /// **Note**: If the `Handler` was created via [`standalone`], then this
+ /// will _only_ update whether the connection is internally muted.
+ ///
+ /// [`standalone`]: #method.standalone
pub fn mute(&mut self, mute: bool) {
self.self_mute = mute;
@@ -200,6 +274,7 @@ impl Handler {
self.send(VoiceStatus::SetSender(Some(source)))
}
+ /// Stops playing audio from a source, if one is set.
pub fn stop(&mut self) {
self.send(VoiceStatus::SetSender(None))
}
@@ -213,81 +288,111 @@ impl Handler {
/// - if the given `channel_id` is _not_ equivalent to the current connected
/// `channel_id`, then switch to the given `channel_id`;
/// - if not currently connected to a voice channel, connect to the given
- /// one.
+ /// one.
+ ///
+ /// If you are dealing with switching from one group to another, then open
+ /// another handler, and optionally drop this one via [`Manager::remove`].
///
/// **Note**: The given `channel_id`, if in a guild, _must_ be in the
/// current handler's associated guild.
///
- /// If you are dealing with switching from one group to another, then open
- /// another handler, and optionally drop this one via [`Manager::remove`].
+ /// **Note**: If the `Handler` was created via [`standalone`], then this
+ /// will _only_ update whether the connection is internally switched to a
+ /// different channel.
///
/// [`Manager::remove`]: struct.Manager.html#method.remove
+ /// [`standalone`]: #method.standalone
pub fn switch_to(&mut self, channel_id: ChannelId) {
match self.channel_id {
Some(current_id) if current_id == channel_id => {
// If already connected to the given channel, do nothing.
return;
},
- Some(_) => {
+ _ => {
self.channel_id = Some(channel_id);
self.update();
},
- None => {
- self.channel_id = Some(channel_id);
+ }
+ }
+
+ /// Updates the voice server data.
+ ///
+ /// You should only need to use this if you initialized the `Handler` via
+ /// [`standalone`].
+ ///
+ /// Refer to the documentation for [`connect`] for when this will
+ /// automatically connect to a voice channel.
+ ///
+ /// [`connect`]: #method.connect
+ /// [`standalone`]: #method.standalone
+ pub fn update_server(&mut self, endpoint: &Option<String>, token: &str) {
+ self.token = Some(token.to_owned());
+
+ if let Some(endpoint) = endpoint.clone() {
+ self.endpoint = Some(endpoint);
+ if self.session_id.is_some() {
self.connect();
- },
+ }
+ } else {
+ self.leave();
}
}
- fn connect(&self) {
- // Do _not_ try connecting if there is not at least a channel. There
- // does not _necessarily_ need to be a guild.
- if self.channel_id.is_none() {
+ /// Updates the internal voice state of the current user.
+ ///
+ /// You should only need to use this if you initialized the `Handler` via
+ /// [`standalone`].
+ ///
+ /// refer to the documentation for [`connect`] for when this will
+ /// automatically connect to a voice channel.
+ ///
+ /// [`connect`]: #method.connect
+ /// [`standalone`]: #method.standalone
+ pub fn update_state(&mut self, voice_state: &VoiceState) {
+ if self.user_id != voice_state.user_id.0 {
return;
}
- self.update();
- }
+ self.channel_id = voice_state.channel_id;
- fn connect_with_data(&mut self, session_id: String, endpoint: String, token: String) {
- let target_id = if let Some(guild_id) = self.guild_id {
- guild_id.0
- } else if let Some(channel_id) = self.channel_id {
- channel_id.0
+ if voice_state.channel_id.is_some() {
+ self.session_id = Some(voice_state.session_id.clone());
+
+ if self.endpoint.is_some() && self.token.is_some() {
+ self.connect();
+ }
} else {
- // Theoretically never happens? This needs to be researched more.
- error!("(╯°□°)╯︵ ┻━┻ No guild/channel ID when connecting");
+ self.leave();
+ }
+ }
- return;
+ fn new_raw(target: Target, ws: Option<MpscSender<GatewayStatus>>, user_id: UserId) -> Self {
+ let (tx, rx) = mpsc::channel();
+
+ let (channel_id, guild_id) = match target {
+ Target::Channel(channel_id) => (Some(channel_id), None),
+ Target::Guild(guild_id) => (None, Some(guild_id)),
};
- let user_id = self.user_id;
+ threading::start(target, rx);
- self.send(VoiceStatus::Connect(ConnectionInfo {
- endpoint: endpoint,
- session_id: session_id,
- target_id: target_id,
- token: token,
+ Handler {
+ channel_id: channel_id,
+ endpoint: None,
+ guild_id: guild_id,
+ self_deaf: false,
+ self_mute: false,
+ sender: tx,
+ session_id: None,
+ token: None,
user_id: user_id,
- }))
- }
-
- // Send an update for the current session.
- fn update(&self) {
- let map = ObjectBuilder::new()
- .insert("op", VoiceOpCode::SessionDescription.num())
- .insert_object("d", |o| o
- .insert("channel_id", self.channel_id.map(|c| c.0))
- .insert("guild_id", self.guild_id.map(|g| g.0))
- .insert("self_deaf", self.self_deaf)
- .insert("self_mute", self.self_mute))
- .build();
-
- let _ = self.ws.send(GatewayStatus::SendMessage(map));
+ ws: ws,
+ }
}
+ /// Sends a message to the thread.
fn send(&mut self, status: VoiceStatus) {
// Restart thread if it errored.
if let Err(mpsc::SendError(status)) = self.sender.send(status) {
@@ -302,44 +407,33 @@ impl Handler {
}
}
- /// You probably shouldn't use this if you're reading the source code.
- #[doc(hidden)]
- pub fn update_server(&mut self, endpoint: &Option<String>, token: &str) {
- if let Some(endpoint) = endpoint.clone() {
- let token = token.to_owned();
-
- if let Some(session_id) = self.session_id.clone() {
- self.connect_with_data(session_id, endpoint, token);
- } else {
- self.endpoint_token = Some((endpoint, token));
- }
- } else {
- self.leave();
- }
- }
-
- /// You probably shouldn't use this if you're reading the source code.
- #[doc(hidden)]
- pub fn update_state(&mut self, voice_state: &VoiceState) {
- if self.user_id != voice_state.user_id.0 {
+ fn send_join(&self) {
+ // Do _not_ try connecting if there is not at least a channel. There
+ // does not _necessarily_ need to be a guild.
+ if self.channel_id.is_none() {
return;
}
- self.channel_id = voice_state.channel_id;
+ self.update();
+ }
- if voice_state.channel_id.is_some() {
- let session_id = voice_state.session_id.clone();
-
- match self.endpoint_token.take() {
- Some((endpoint, token)) => {
- self.connect_with_data(session_id, endpoint, token);
- },
- None => {
- self.session_id = Some(session_id);
- },
- }
- } else {
- self.leave();
+ /// Send an update for the current session over WS.
+ ///
+ /// Does nothing if initialized via [`standalone`].
+ ///
+ /// [`standalone`]: #method.standalone
+ fn update(&self) {
+ if let Some(ref ws) = self.ws {
+ let map = ObjectBuilder::new()
+ .insert("op", VoiceOpCode::SessionDescription.num())
+ .insert_object("d", |o| o
+ .insert("channel_id", self.channel_id.map(|c| c.0))
+ .insert("guild_id", self.guild_id.map(|g| g.0))
+ .insert("self_deaf", self.self_deaf)
+ .insert("self_mute", self.self_mute))
+ .build();
+
+ let _ = ws.send(GatewayStatus::SendMessage(map));
}
}
}
diff --git a/src/ext/voice/mod.rs b/src/ext/voice/mod.rs
index eb63f27..38ea3a1 100644
--- a/src/ext/voice/mod.rs
+++ b/src/ext/voice/mod.rs
@@ -12,7 +12,7 @@ pub use self::audio::{AudioReceiver, AudioSource};
pub use self::error::VoiceError;
pub use self::handler::Handler;
pub use self::manager::Manager;
-pub use self::streamer::{ffmpeg, ytdl};
+pub use self::streamer::{ffmpeg, pcm, ytdl};
use self::connection_info::ConnectionInfo;
use ::model::{ChannelId, GuildId};