147 lines
4.3 KiB
Rust
147 lines
4.3 KiB
Rust
use crate::handler::get_songbird;
|
||
use dashmap::DashMap;
|
||
use serde::Serialize;
|
||
use serenity::model::prelude::GuildId;
|
||
use songbird::tracks::PlayMode;
|
||
use std::{
|
||
collections::VecDeque,
|
||
sync::{Arc, OnceLock},
|
||
};
|
||
|
||
/// Metadata for a single track stored in our queue.
|
||
#[derive(Debug, Clone, Serialize)]
|
||
pub struct TrackInfo {
|
||
pub title: String,
|
||
pub url: String,
|
||
/// Total duration in seconds, if known (from yt-dlp metadata).
|
||
pub duration_secs: Option<f64>,
|
||
/// Whether this track should loop indefinitely.
|
||
pub loop_enabled: bool,
|
||
}
|
||
|
||
/// Global map of guild_id → ordered queue of TrackInfo.
|
||
/// Initialised once by the bot handler's `ready` event.
|
||
static TRACK_QUEUES: OnceLock<Arc<DashMap<u64, VecDeque<TrackInfo>>>> = OnceLock::new();
|
||
|
||
/// Call once from the `ready` event handler to initialise the store.
|
||
pub fn init_track_queues() {
|
||
TRACK_QUEUES.set(Arc::new(DashMap::new())).ok();
|
||
}
|
||
|
||
/// Returns a reference to the global TRACK_QUEUES map.
|
||
fn queues() -> &'static Arc<DashMap<u64, VecDeque<TrackInfo>>> {
|
||
TRACK_QUEUES
|
||
.get()
|
||
.expect("TRACK_QUEUES not initialised – call init_track_queues() in the ready handler")
|
||
}
|
||
|
||
/// Append one or more tracks to the end of a guild's queue.
|
||
pub fn enqueue_tracks(guild_id: u64, tracks: Vec<TrackInfo>) {
|
||
let mut entry = queues().entry(guild_id).or_default();
|
||
for t in tracks {
|
||
entry.push_back(t);
|
||
}
|
||
}
|
||
|
||
/// Remove and return the front track (called when a track finishes).
|
||
pub fn pop_front(guild_id: u64) -> Option<TrackInfo> {
|
||
queues()
|
||
.get_mut(&guild_id)
|
||
.and_then(|mut q: dashmap::mapref::one::RefMut<u64, VecDeque<TrackInfo>>| q.pop_front())
|
||
}
|
||
|
||
/// Clear the entire queue for a guild (called on stop).
|
||
pub fn clear_queue(guild_id: u64) {
|
||
if let Some(mut q) = queues().get_mut(&guild_id) {
|
||
let q: &mut VecDeque<TrackInfo> = q.value_mut();
|
||
q.clear();
|
||
}
|
||
}
|
||
|
||
/// Return a snapshot of the current queue for a guild.
|
||
/// Index 0 is the currently-playing track, index 1+ are upcoming.
|
||
pub fn get_queue(guild_id: u64) -> Vec<TrackInfo> {
|
||
queues()
|
||
.get(&guild_id)
|
||
.map(|q: dashmap::mapref::one::Ref<u64, VecDeque<TrackInfo>>| q.iter().cloned().collect())
|
||
.unwrap_or_default()
|
||
}
|
||
|
||
/// Returns `true` if the bot is currently paused in the given guild.
|
||
/// Encapsulates the songbird dependency so `siren-api` doesn't need it directly.
|
||
pub async fn get_is_paused(guild_id: u64) -> bool {
|
||
let manager = get_songbird();
|
||
let serenity_guild_id = GuildId::from(guild_id);
|
||
if let Some(handler_lock) = manager.get(serenity_guild_id) {
|
||
let handler = handler_lock.lock().await;
|
||
let current = handler.queue().current();
|
||
drop(handler);
|
||
if let Some(track) = current {
|
||
return track
|
||
.get_info()
|
||
.await
|
||
.map(|info| info.playing == PlayMode::Pause)
|
||
.unwrap_or(false);
|
||
}
|
||
}
|
||
false
|
||
}
|
||
|
||
/// Toggle or set loop on the currently playing track
|
||
pub async fn set_loop_current(guild_id: u64, enabled: bool) -> bool {
|
||
// Update our metadata store first
|
||
let updated = {
|
||
if let Some(mut q) = queues().get_mut(&guild_id) {
|
||
if let Some(front) = q.front_mut() {
|
||
front.loop_enabled = enabled;
|
||
true
|
||
} else {
|
||
false
|
||
}
|
||
} else {
|
||
false
|
||
}
|
||
};
|
||
if !updated {
|
||
return false;
|
||
}
|
||
|
||
// Tell songbird to loop / unloop the live track handle
|
||
let manager = get_songbird();
|
||
let serenity_guild_id = GuildId::from(guild_id);
|
||
if let Some(handler_lock) = manager.get(serenity_guild_id) {
|
||
let handler = handler_lock.lock().await;
|
||
let current = handler.queue().current();
|
||
drop(handler);
|
||
if let Some(track) = current {
|
||
if enabled {
|
||
let _ = track.enable_loop();
|
||
} else {
|
||
let _ = track.disable_loop();
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
false
|
||
}
|
||
|
||
/// Returns the current playback position (in seconds) for the active track in the
|
||
/// given guild, or `0.0` if nothing is playing or the info is unavailable.
|
||
pub async fn get_current_position(guild_id: u64) -> f64 {
|
||
let manager = get_songbird();
|
||
let serenity_guild_id = GuildId::from(guild_id);
|
||
if let Some(handler_lock) = manager.get(serenity_guild_id) {
|
||
let handler = handler_lock.lock().await;
|
||
let current = handler.queue().current();
|
||
drop(handler);
|
||
if let Some(track) = current {
|
||
return track
|
||
.get_info()
|
||
.await
|
||
.map(|info| info.position.as_secs_f64())
|
||
.unwrap_or(0.0);
|
||
}
|
||
}
|
||
0.0
|
||
}
|