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}