/* 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 crate::{stream_info, SMediaInfo}; use anyhow::{anyhow, Result}; use jellystream_types::{FormatNum, SegmentNum, StreamContainer, StreamSpec, TrackKind, TrackNum}; use std::{fmt::Write, ops::Range, sync::Arc}; use tokio::{ io::{AsyncWriteExt, DuplexStream}, task::spawn_blocking, }; pub async fn hls_supermultivariant_stream( mut b: DuplexStream, info: Arc, container: StreamContainer, ) -> Result<()> { let (_iinfo, info) = stream_info(info).await?; let mut out = String::new(); writeln!(out, "#EXTM3U")?; writeln!(out, "#EXT-X-VERSION:4")?; for (i, _seg) in info.segments.iter().enumerate() { let uri = format!( "stream{}", StreamSpec::HlsMultiVariant { segment: i, container, } .to_query() ); writeln!(out, "{uri}")?; } tokio::spawn(async move { b.write_all(out.as_bytes()).await }); Ok(()) } pub async fn hls_multivariant_stream( mut b: DuplexStream, info: Arc, segment: SegmentNum, container: StreamContainer, ) -> Result<()> { let (_iinfo, info) = stream_info(info).await?; let seg = info .segments .get(segment) .ok_or(anyhow!("segment not found"))?; let mut out = String::new(); writeln!(out, "#EXTM3U")?; writeln!(out, "#EXT-X-VERSION:4")?; // writeln!(out, "#EXT-X-INDEPENDENT-SEGMENTS")?; for (i, t) in seg.tracks.iter().enumerate() { let uri = format!( "stream{}", StreamSpec::HlsVariant { segment, track: i, container, format: 0 } .to_query() ); let r#type = match t.kind { TrackKind::Video => "VIDEO", TrackKind::Audio => "AUDIO", TrackKind::Subtitle => "SUBTITLES", }; // TODO bw 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( mut b: DuplexStream, info: Arc, segment: SegmentNum, track: TrackNum, format: FormatNum, container: StreamContainer, ) -> Result<()> { let (iinfo, info) = stream_info(info).await?; let (file_index, track_num) = *iinfo .track_to_file .get(track) .ok_or(anyhow!("track not found"))?; let seg = info .segments .get(segment) .ok_or(anyhow!("segment not found"))?; let frags = spawn_blocking(move || { jellyremuxer::fragment::fragment_index(&iinfo.paths[file_index], track_num) }) .await??; let mut out = String::new(); writeln!(out, "#EXTM3U")?; writeln!(out, "#EXT-X-PLAYLIST-TYPE:VOD")?; writeln!(out, "#EXT-X-TARGETDURATION:{}", seg.duration)?; writeln!(out, "#EXT-X-VERSION:4")?; writeln!(out, "#EXT-X-MEDIA-SEQUENCE:0")?; for (index, Range { start, end }) in frags.iter().enumerate() { writeln!(out, "#EXTINF:{:},", end - start)?; writeln!( out, "stream{}", StreamSpec::Fragment { segment, track, index, container, format, } .to_query() )?; } writeln!(out, "#EXT-X-ENDLIST")?; tokio::spawn(async move { b.write_all(out.as_bytes()).await }); Ok(()) }