/* This file is part of jellything (https://codeberg.org/metamuffin/jellything) which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin */ #![feature(iterator_try_collect)] pub mod fragment; pub mod hls; pub mod jhls; pub mod webvtt; use anyhow::Result; use ebml_struct::matroska::{Info, Tracks}; use jellybase::common::{ stream::{StreamContainer, StreamSpec}, LocalTrack, MediaInfo, Node, }; use jellymatroska::block::LacingType; use std::{ops::Range, sync::Arc}; use tokio::{ fs::File, io::{duplex, AsyncReadExt, AsyncWriteExt, DuplexStream}, }; use tokio_util::io::SyncIoBridge; pub struct StreamHead { pub content_type: &'static str, pub range_supported: bool, } pub fn stream_head(spec: &StreamSpec) -> StreamHead { let cons = |ct: &'static str, rs: bool| StreamHead { content_type: ct, range_supported: rs, }; let container_ct = |x: StreamContainer| match x { StreamContainer::WebM => "video/webm", StreamContainer::Matroska => "video/x-matroska", StreamContainer::WebVTT => "text/vtt", StreamContainer::JVTT => "application/jellything-vtt+json", }; match spec { StreamSpec::Whep { .. } => cons("application/x-todo", false), StreamSpec::WhepControl { .. } => cons("application/x-todo", false), StreamSpec::Remux { container, .. } => cons(container_ct(*container), true), StreamSpec::Original { .. } => cons("video/x-matroska", true), StreamSpec::HlsSuperMultiVariant { .. } => cons("application/vnd.apple.mpegurl", false), StreamSpec::HlsMultiVariant { .. } => cons("application/vnd.apple.mpegurl", false), StreamSpec::HlsVariant { .. } => cons("application/vnd.apple.mpegurl", false), StreamSpec::Info { .. } => cons("application/jellything-stream-info+json", false), StreamSpec::FragmentIndex { .. } => cons("application/jellything-frag-index+json", false), StreamSpec::Fragment { container, .. } => cons(container_ct(*container), false), } } pub async fn stream( info: Arc, spec: StreamSpec, range: Range, ) -> Result { let (a, b) = duplex(4096); match spec { StreamSpec::Whep { track, seek } => todo!(), StreamSpec::WhepControl { token } => todo!(), StreamSpec::Remux { tracks, container } => todo!(), StreamSpec::Original { track } => todo!(), StreamSpec::HlsSuperMultiVariant { container } => todo!(), StreamSpec::HlsMultiVariant { segment, container } => todo!(), StreamSpec::HlsVariant { segment, track, container, format, } => todo!(), StreamSpec::Info { segment } => todo!(), StreamSpec::FragmentIndex { segment, track } => todo!(), StreamSpec::Fragment { segment, track, index, container, format, } => todo!(), } Ok(a) } async fn remux_stream( node: Arc, local_tracks: Vec, spec: StreamSpec, range: Range, b: DuplexStream, ) -> Result<()> { let b = SyncIoBridge::new(b); // tokio::task::spawn_blocking(move || { // jellyremuxer::remux_stream_into( // b, // range, // CONF.media_path.to_owned(), // &node, // local_tracks, // spec.track, // spec.webm.unwrap_or(false), // ) // }); Ok(()) } async fn original_stream( local_tracks: Vec, spec: StreamSpec, range: Range, b: DuplexStream, ) -> Result<()> { // if spec.track.len() != 1 { // bail!("invalid amout of source \"tracks\". original only allows for exactly one.") // } // let source = local_tracks[spec.track[0]].clone(); // let mut file = File::open(CONF.media_path.join(source.path)) // .await // .context("opening source")?; // file.seek(SeekFrom::Start(range.start as u64)) // .await // .context("seek source")?; // tokio::task::spawn(copy_stream(file, b, range.end - range.start)); Ok(()) } async fn copy_stream(mut inp: File, mut out: DuplexStream, mut amount: usize) -> Result<()> { let mut buf = [0u8; 4096]; loop { let size = inp.read(&mut buf[..amount.min(4096)]).await?; if size == 0 { break Ok(()); } out.write_all(&buf[..size]).await?; amount -= size; } } // TODO functions to test seekability, get live status and enumate segments trait MediaSource { /// Seeks to some position close to, but before, `time` ticks. fn seek(&mut self, time: u64) -> Result<()>; /// Retrieve headers (info and tracks) for some segment. fn segment_headers(&mut self, seg: u64) -> Result<(Info, Tracks)>; /// Returns the next block and the current segment index fn next(&mut self) -> Result>; } pub struct AbsBlock { track: u64, pts: u64, keyframe: bool, lacing: Option, data: Vec, }