Something went wrong. Try again.
experiments of a tiny cytube-like player with yt-dlp
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216//! Shared types used across moqbox modules.
use std::fmt;
use chrono::{DateTime, Utc};use serde::Serialize;use tokio::sync::{broadcast, mpsc, watch};
/// Typed identifier for a queued / active / history track.#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]#[serde(transparent)]pub(crate) struct TrackId(pub(crate) uuid::Uuid);
impl TrackId { pub(crate) fn new() -> Self { Self(uuid::Uuid::now_v7()) }
/// Sentinel value: no specific track. Used in PersistMetadata effects /// generated by URL-keyed operations where the effect targets media_cache, /// not a particular track row. pub(crate) fn nil() -> Self { Self(uuid::Uuid::nil()) }
pub(crate) fn as_str_buf(&self) -> String { self.0.to_string() }}
impl fmt::Display for TrackId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) }}
/// Typed identifier for a perpetual playlist entry.#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]#[serde(transparent)]pub(crate) struct PlaylistEntryId(pub(crate) uuid::Uuid);
impl PlaylistEntryId { pub(crate) fn new() -> Self { Self(uuid::Uuid::now_v7()) }
pub(crate) fn parse(s: &str) -> Option<Self> { uuid::Uuid::parse_str(s).ok().map(Self) }
pub(crate) fn as_str_buf(&self) -> String { self.0.to_string() }}
impl fmt::Display for PlaylistEntryId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) }}
/// Typed identifier for a chat message.#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]#[serde(transparent)]pub(crate) struct ChatMessageId(pub(crate) uuid::Uuid);
impl ChatMessageId { pub(crate) fn new() -> Self { Self(uuid::Uuid::now_v7()) }
pub(crate) fn as_str_buf(&self) -> String { self.0.to_string() }}
impl fmt::Display for ChatMessageId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) }}
/// Opaque room identifier backed by a UUIDv7.#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]#[serde(transparent)]pub(crate) struct RoomId(pub(crate) uuid::Uuid);
impl RoomId { pub(crate) fn new() -> Self { Self(uuid::Uuid::now_v7()) }
pub(crate) fn as_str_buf(&self) -> String { self.0.to_string() }
/// Parse a UUID string into a `RoomId`. Returns `None` if invalid. pub(crate) fn parse(s: &str) -> Option<Self> { uuid::Uuid::parse_str(s).ok().map(Self) }}
impl fmt::Display for RoomId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) }}
/// Snapshot of the currently playing track for state publishing.#[derive(Debug, Clone, Serialize)]pub(crate) struct TrackState { pub(crate) id: TrackId, /// Stable hash of the media URL, used for file serving. pub(crate) hash: String, pub(crate) title: String, pub(crate) url: String, pub(crate) duration: String, pub(crate) thumbnail: Option<String>, pub(crate) started_at: i64,}
/// Summary of a queued track for state publishing.#[derive(Debug, Clone, Serialize)]pub(crate) struct QueueSummary { pub(crate) id: TrackId, /// Stable hash of the media URL, used for file serving. pub(crate) hash: String, pub(crate) title: String, pub(crate) url: String, pub(crate) duration: String, pub(crate) thumbnail: Option<String>, /// True while metadata is being extracted / download in progress. #[serde(skip_serializing_if = "is_false")] pub(crate) pending: bool,}
fn is_false(b: &bool) -> bool { !*b}
#[derive(Debug, Clone, Serialize)]pub(crate) struct HistoryEntry { pub(crate) id: TrackId, pub(crate) hash: String, pub(crate) title: String, pub(crate) url: String, pub(crate) duration: String, pub(crate) thumbnail: Option<String>, pub(crate) played_at: String,}
/// A chat message returned from send/recent operations.#[derive(Debug, Clone, Serialize)]pub(crate) struct ChatMessage { pub(crate) id: ChatMessageId, pub(crate) user_name: String, pub(crate) content: String, pub(crate) msg_type: String, pub(crate) created_at: String,}
/// WebSocket publishers for a room.////// Replaces the old MoQ `TrackPublishers`. Provides a `watch` channel for/// state snapshots and a `broadcast` channel for chat messages.#[derive(Clone)]pub(crate) struct RoomPublishers { pub(crate) state_tx: watch::Sender<serde_json::Value>, pub(crate) chat_tx: broadcast::Sender<ChatMessage>,}
impl RoomPublishers { pub(crate) fn new() -> Self { let (state_tx, _) = watch::channel(serde_json::Value::Null); let (chat_tx, _) = broadcast::channel(256); Self { state_tx, chat_tx } }}
/// Re-exported from [`crate::room`] for convenience.pub(crate) use crate::room::RoomCommand;
/// Ephemeral info about the active track, stored in-memory only.#[derive(Debug, Clone, PartialEq, Eq)]pub(crate) struct ActiveTrackInfo { pub(crate) id: TrackId, pub(crate) title: String, pub(crate) url: String, pub(crate) duration: String, pub(crate) thumbnail: Option<String>, /// Epoch ms when the track started, for client-side elapsed computation. pub(crate) started_at_wall: i64,}
/// Public handle to a room: allows sending commands and reading publishers.#[derive(Clone)]pub(crate) struct RoomHandle { pub(crate) cmd_tx: mpsc::Sender<RoomCommand>, pub(crate) publishers: RoomPublishers, pub(crate) last_active: watch::Receiver<DateTime<Utc>>,}
#[cfg(test)]mod tests { use super::*;
#[test] fn test_room_id_display() { let id = RoomId::new(); assert!( id.to_string().contains('-'), "RoomId should be UUID with hyphens" ); }}