diff options
| author | Zeyla Hellyer <[email protected]> | 2017-02-11 12:10:03 -0800 |
|---|---|---|
| committer | Zeyla Hellyer <[email protected]> | 2017-02-11 12:10:03 -0800 |
| commit | 3388a13d06993cae8647fe2720660ad33594284d (patch) | |
| tree | b32435c6aba22e92918c6507a8faf0dfd8220d5c | |
| parent | Update examples for cache rewrite (diff) | |
| download | serenity-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.rs | 394 | ||||
| -rw-r--r-- | src/ext/voice/mod.rs | 2 |
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}; |