/* 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 */ use anyhow::{anyhow, Result}; use jellybase::{ common::{ stream::{StreamContainer, StreamSpec}, LocalTrack, Node, SourceTrackKind, }, CONF, }; use std::{fmt::Write, ops::Range, sync::Arc}; use tokio::{ io::{AsyncWriteExt, DuplexStream}, task::spawn_blocking, }; pub async fn hls_master_stream( node: Arc, _local_tracks: Vec, segment: u64, container: StreamContainer, mut b: DuplexStream, ) -> Result<()> { let media = node.media.as_ref().ok_or(anyhow!("no media"))?; let mut out = String::new(); writeln!(out, "#EXTM3U")?; writeln!(out, "#EXT-X-VERSION:4")?; // writeln!(out, "#EXT-X-INDEPENDENT-SEGMENTS")?; for (i, t) in media.tracks.iter().enumerate() { let uri = format!( "stream?{}", StreamSpec::HlsVariant { segment, track: i, container, format: 0 } .to_query() ); let r#type = match t.kind { SourceTrackKind::Video { .. } => "VIDEO", SourceTrackKind::Audio { .. } => "AUDIO", SourceTrackKind::Subtitles => "SUBTITLES", }; writeln!(out, "#EXT-X-STREAM-INF:BANDWIDTH=5000000,TYPE={type}")?; writeln!(out, "{uri}")?; } tokio::spawn(async move { b.write_all(out.as_bytes()).await }); Ok(()) } pub async fn hls_variant_stream( node: Arc, local_tracks: Vec, segment: u64, track: usize, format: usize, container: StreamContainer, mut b: DuplexStream, ) -> Result<()> { let local_track = local_tracks.first().ok_or(anyhow!("no track"))?.to_owned(); let media_info = node.media.to_owned().ok_or(anyhow!("no media?"))?; let frags = spawn_blocking(move || { jellyremuxer::fragment::fragment_index( &CONF.media_path, &node, &local_track, track as usize, ) }) .await??; let mut out = String::new(); writeln!(out, "#EXTM3U")?; writeln!(out, "#EXT-X-PLAYLIST-TYPE:VOD")?; writeln!(out, "#EXT-X-TARGETDURATION:{}", media_info.duration)?; writeln!(out, "#EXT-X-VERSION:4")?; writeln!(out, "#EXT-X-MEDIA-SEQUENCE:0")?; for (i, Range { start, end }) in frags.iter().enumerate() { writeln!(out, "#EXTINF:{:},", end - start)?; writeln!( out, "stream?{}", StreamSpec::Fragment { segment, track, index: i as u64, container, format, } .to_query() )?; } writeln!(out, "#EXT-X-ENDLIST")?; tokio::spawn(async move { b.write_all(out.as_bytes()).await }); Ok(()) }