aboutsummaryrefslogtreecommitdiff
path: root/src
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 /src
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.
Diffstat (limited to 'src')
-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};