Files
siren/crates/siren-bot/src/commands/audio/queue.rs
2026-04-08 09:15:01 -04:00

147 lines
4.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}