twilight_lavalink/
player.rs

1//! Players containing information about active playing state within guilds and
2//! allowing you to send events to connected nodes.
3//!
4//! Use the [`PlayerManager`] to retrieve existing [players] for guilds and
5//! use those players to do things like [send events] or [read the position] of
6//! the active audio.
7//!
8//! [players]: Player
9//! [send events]: Player::send
10//! [read the position]: Player::position
11
12use crate::{
13    model::{Destroy, OutgoingEvent},
14    node::{Node, NodeSenderError},
15};
16use dashmap::DashMap;
17use std::{
18    fmt::Debug,
19    sync::{
20        atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering},
21        Arc,
22    },
23};
24use twilight_model::id::{
25    marker::{ChannelMarker, GuildMarker},
26    Id,
27};
28
29/// Retrieve and create players for guilds.
30///
31/// The player manager contains all of the players for all guilds over all
32/// nodes, and can be used to read player information and send events to nodes.
33#[derive(Clone, Debug, Default)]
34pub struct PlayerManager {
35    pub(crate) players: Arc<DashMap<Id<GuildMarker>, Arc<Player>>>,
36}
37
38impl PlayerManager {
39    /// Create a new player manager.
40    pub(crate) fn new() -> Self {
41        Self::default()
42    }
43
44    /// Return an immutable reference to a player by guild ID.
45    pub fn get(&self, guild_id: &Id<GuildMarker>) -> Option<Arc<Player>> {
46        self.players.get(guild_id).map(|r| Arc::clone(r.value()))
47    }
48
49    /// Return a mutable reference to a player by guild ID or insert a new
50    /// player linked to a given node.
51    pub fn get_or_insert(&self, guild_id: Id<GuildMarker>, node: Arc<Node>) -> Arc<Player> {
52        let player = self
53            .players
54            .entry(guild_id)
55            .or_insert_with(|| Arc::new(Player::new(guild_id, node)));
56
57        Arc::clone(&player)
58    }
59
60    /// Destroy a player on the remote node and remove it from the [`PlayerManager`].
61    ///
62    /// # Errors
63    ///
64    /// Returns a [`NodeSenderErrorType::Sending`] error type if node is no
65    /// longer connected.
66    ///
67    /// [`NodeSenderErrorType::Sending`]: crate::node::NodeSenderErrorType::Sending
68    pub fn destroy(&self, guild_id: Id<GuildMarker>) -> Result<(), NodeSenderError> {
69        if let Some(player) = self.get(&guild_id) {
70            player
71                .node()
72                .send(OutgoingEvent::from(Destroy::new(guild_id)))?;
73            self.players.remove(&guild_id);
74        }
75
76        Ok(())
77    }
78}
79
80/// A player for a guild connected to a node.
81///
82/// This can be used to send events over a node and to read the details of a
83/// player for a guild.
84#[derive(Debug)]
85pub struct Player {
86    channel_id: AtomicU64,
87    guild_id: Id<GuildMarker>,
88    node: Arc<Node>,
89    paused: AtomicBool,
90    position: AtomicI64,
91    time: AtomicI64,
92    volume: AtomicI64,
93}
94
95impl Player {
96    pub(crate) const fn new(guild_id: Id<GuildMarker>, node: Arc<Node>) -> Self {
97        Self {
98            channel_id: AtomicU64::new(0),
99            guild_id,
100            node,
101            paused: AtomicBool::new(false),
102            position: AtomicI64::new(0),
103            time: AtomicI64::new(0),
104            volume: AtomicI64::new(100),
105        }
106    }
107
108    /// Send an event to the player's node.
109    ///
110    /// Returns a `futures_channel` `TrySendError` if the node has been removed.
111    ///
112    /// # Examples
113    ///
114    /// Send a [`Play`] and [`Pause`] event:
115    ///
116    /// ```
117    /// use twilight_lavalink::{
118    ///     model::{Pause, Play},
119    ///     Lavalink,
120    /// };
121    /// # use twilight_model::id::Id;
122    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
123    /// # let (guild_id, user_id) = (Id::new(1), Id::new(2));
124    /// # let track = String::new();
125    ///
126    /// let lavalink = Lavalink::new(user_id, 10);
127    /// let players = lavalink.players();
128    ///
129    /// if let Some(player) = players.get(&guild_id) {
130    ///     player.send(Play::from((guild_id, track)))?;
131    ///     player.send(Pause::from((guild_id, true)))?;
132    /// }
133    /// # Ok(()) }
134    /// ```
135    ///
136    /// # Errors
137    ///
138    /// Returns a [`NodeSenderErrorType::Sending`] error type if node is no
139    /// longer connected.
140    ///
141    /// [`NodeSenderErrorType::Sending`]: crate::node::NodeSenderErrorType::Sending
142    /// [`Pause`]: crate::model::outgoing::Pause
143    /// [`Play`]: crate::model::outgoing::Play
144    pub fn send(&self, event: impl Into<OutgoingEvent>) -> Result<(), NodeSenderError> {
145        self._send(event.into())
146    }
147
148    fn _send(&self, event: OutgoingEvent) -> Result<(), NodeSenderError> {
149        tracing::debug!("sending event on guild player {}: {event:?}", self.guild_id);
150
151        match &event {
152            OutgoingEvent::Pause(event) => self.paused.store(event.pause, Ordering::Release),
153            OutgoingEvent::Volume(event) => {
154                #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
155                self.volume.store(event.volume, Ordering::Release);
156            }
157            _ => {}
158        }
159
160        self.node.send(event)
161    }
162
163    /// Return an immutable reference to the node linked to the player.
164    pub const fn node(&self) -> &Arc<Node> {
165        &self.node
166    }
167
168    /// Return the player's channel ID.
169    pub fn channel_id(&self) -> Option<Id<ChannelMarker>> {
170        let channel_id = self.channel_id.load(Ordering::Acquire);
171
172        if channel_id == 0 {
173            None
174        } else {
175            Some(Id::new(channel_id))
176        }
177    }
178
179    /// Sets the channel ID the player is currently connected to.
180    pub(crate) fn set_channel_id(&self, channel_id: Option<Id<ChannelMarker>>) {
181        self.channel_id
182            .store(channel_id.map_or(0_u64, Id::get), Ordering::Release);
183    }
184
185    /// Return the player's guild ID.
186    pub const fn guild_id(&self) -> Id<GuildMarker> {
187        self.guild_id
188    }
189
190    /// Return whether the player is paused.
191    pub fn paused(&self) -> bool {
192        self.paused.load(Ordering::Acquire)
193    }
194
195    /// Return the player's position.
196    pub fn position(&self) -> i64 {
197        self.position.load(Ordering::Relaxed)
198    }
199
200    /// Set the player's position.
201    pub(crate) fn set_position(&self, position: i64) {
202        self.position.store(position, Ordering::Release);
203    }
204
205    /// Return the player's time.
206    pub fn time(&mut self) -> i64 {
207        self.time.load(Ordering::Relaxed)
208    }
209
210    /// Set the player's time.
211    pub(crate) fn set_time(&self, time: i64) {
212        self.time.store(time, Ordering::Release);
213    }
214
215    /// Return the player's volume.
216    pub fn volume(&self) -> i64 {
217        self.volume.load(Ordering::Relaxed)
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use super::{Player, PlayerManager};
224    use static_assertions::assert_impl_all;
225    use std::fmt::Debug;
226
227    assert_impl_all!(PlayerManager: Debug, Default, Send, Sync);
228    assert_impl_all!(Player: Debug, Send, Sync);
229}