Something went wrong. Try again.
experiments of a tiny cytube-like player with yt-dlp
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116//! moqbox — Shared media rooms with HTTP media + WebSocket state.
#![deny(rust_2018_idioms, unsafe_code)]#![cfg_attr(not(test), deny(clippy::unwrap_used))]
mod ingest;mod media;mod names;mod room;mod state;mod store;mod transport;mod types;mod util;mod web;
use clap::Parser;use std::net::SocketAddr;use std::path::PathBuf;use std::sync::Arc;use std::time::Duration;use tracing_subscriber::EnvFilter;
#[derive(Parser)]#[command(name = "moqbox", version, about = "Shared media rooms over MoQ")]struct Args { /// HTTP listen address. #[arg(long, default_value = "0.0.0.0:3000", env = "MOQBOX_HTTP_ADDR")] http_addr: SocketAddr,
/// Directory for downloaded media files. #[arg(long, default_value = "/tmp/moqbox", env = "MOQBOX_CACHE_DIR")] cache_dir: PathBuf,
/// Path to SQLite database file. #[arg(long, default_value = "moqbox.db", env = "MOQBOX_DB_PATH")] db_path: PathBuf,}
#[tokio::main]async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) .init();
let args = Args::parse();
tokio::fs::create_dir_all(&args.cache_dir).await?;
// Ensure database parent directory exists. if let Some(parent) = args.db_path.parent() { if !parent.as_os_str().is_empty() { tokio::fs::create_dir_all(parent).await?; } }
let store: Arc<dyn crate::store::RoomStore> = Arc::new(crate::store::SqliteStore::connect(&args.db_path).await?); tracing::info!("database ready at {}", args.db_path.display());
let rooms = room::Registry::new(store, args.cache_dir.clone());
// Idle room sweeper: every 60s, remove rooms idle for 600s (10 min). let registry_clone = rooms.clone(); tokio::spawn(async move { let mut interval = tokio::time::interval(Duration::from_secs(60)); loop { interval.tick().await; let swept = registry_clone.sweep_idle(Duration::from_secs(600)).await; if !swept.is_empty() { tracing::info!("swept {} idle rooms", swept.len()); } } });
let app = web::router(rooms, args.cache_dir); let listener = tokio::net::TcpListener::bind(args.http_addr).await?;
tracing::info!("HTTP server listening on {}", args.http_addr);
axum::serve( listener, app.into_make_service_with_connect_info::<SocketAddr>(), ) .with_graceful_shutdown(shutdown_signal()) .await?;
Ok(())}
async fn shutdown_signal() { let ctrl_c = async { tokio::signal::ctrl_c() .await .expect("failed to install Ctrl+C handler"); };
#[cfg(unix)] let terminate = async { tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) .expect("failed to install SIGTERM handler") .recv() .await; };
#[cfg(not(unix))] let terminate = std::future::pending::<()>();
tokio::select! { _ = ctrl_c => {}, _ = terminate => {}, }
tracing::info!("shutting down");}