diff options
author | metamuffin <metamuffin@disroot.org> | 2023-09-30 09:44:23 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2023-09-30 09:44:23 +0200 |
commit | 9e205e33a61d0268c35362740aa91ca459dbf428 (patch) | |
tree | 2277d78a5215c9da5c8090b988c0e9d595cbda96 | |
parent | c180123e07c31ff45194e1645a4b90c3fd039650 (diff) | |
download | jellything-9e205e33a61d0268c35362740aa91ca459dbf428.tar jellything-9e205e33a61d0268c35362740aa91ca459dbf428.tar.bz2 jellything-9e205e33a61d0268c35362740aa91ca459dbf428.tar.zst |
stream head + webm not a format anymore
-rw-r--r-- | api.md | 2 | ||||
-rw-r--r-- | common/src/stream.rs | 17 | ||||
-rw-r--r-- | server/src/main.rs | 10 | ||||
-rw-r--r-- | server/src/routes/mod.rs | 6 | ||||
-rw-r--r-- | server/src/routes/stream.rs | 43 | ||||
-rw-r--r-- | server/src/routes/ui/player.rs | 16 | ||||
-rw-r--r-- | stream/src/lib.rs | 23 |
7 files changed, 80 insertions, 37 deletions
@@ -34,7 +34,7 @@ Where `role` is one of `backdrop` or `poster` and `width` is the width of the resolution you want to image to be in. The actual returned resolution must not be exactly what you requested. Returns assets for a node. -## GET* `/stream/<id>?<format>&<abr>&<vbr>&<index>&<tracks>` +## GET* `/stream/<id>?<format>&<abr>&<vbr>&<index>&<tracks>&<webm>` Responds with the stream directly or a redirect to the actual source in case of federation. diff --git a/common/src/stream.rs b/common/src/stream.rs index 3fb1008..ca09999 100644 --- a/common/src/stream.rs +++ b/common/src/stream.rs @@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize}; pub struct StreamSpec { pub tracks: Vec<usize>, pub format: StreamFormat, + pub webm: Option<bool>, pub abr: Option<usize>, pub vbr: Option<usize>, pub index: Option<usize>, @@ -19,7 +20,6 @@ pub struct StreamSpec { pub enum StreamFormat { #[cfg_attr(feature = "rocket", field(value = "original"))] Original, #[cfg_attr(feature = "rocket", field(value = "matroska"))] Matroska, - #[cfg_attr(feature = "rocket", field(value = "webm"))] Webm, #[cfg_attr(feature = "rocket", field(value = "hls"))] Hls, #[cfg_attr(feature = "rocket", field(value = "jhls"))] Jhls, #[cfg_attr(feature = "rocket", field(value = "hlsseg"))] Segment, @@ -28,11 +28,12 @@ pub enum StreamFormat { impl Default for StreamSpec { fn default() -> Self { Self { - tracks: Default::default(), - format: StreamFormat::Webm, - abr: Default::default(), - vbr: Default::default(), - index: Default::default(), + tracks: Vec::new(), + format: StreamFormat::Matroska, + webm: Some(true), + abr: None, + vbr: None, + index: None, } } } @@ -64,6 +65,9 @@ impl StreamSpec { if let Some(index) = self.index { writeln!(u, "&index={index}").unwrap(); } + if let Some(webm) = self.webm { + writeln!(u, "&webmm={webm}").unwrap(); + } u } } @@ -73,7 +77,6 @@ impl StreamFormat { match self { StreamFormat::Original => "original", StreamFormat::Matroska => "matroska", - StreamFormat::Webm => "webm", StreamFormat::Hls => "hls", StreamFormat::Jhls => "jhls", StreamFormat::Segment => "hlsseg", diff --git a/server/src/main.rs b/server/src/main.rs index f2c1440..2b2d2c0 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -19,18 +19,12 @@ pub mod federation; pub mod import; pub mod routes; -fn main() { +#[rocket::main] +async fn main() { enable_logging(); #[cfg(feature = "bypass-auth")] log::warn!("authentification bypass enabled"); - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() - .block_on(async_main()) -} -async fn async_main() { create_dir_all(&CONF.cache_path).await.unwrap(); let database = Database::open(&CONF.database_path).unwrap(); let federation = Federation::initialize(); diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs index 6fe5018..94a7547 100644 --- a/server/src/routes/mod.rs +++ b/server/src/routes/mod.rs @@ -45,10 +45,7 @@ macro_rules! uri { }; } -pub fn build_rocket( - database: Database, - federation: Federation, -) -> Rocket<Build> { +pub fn build_rocket(database: Database, federation: Federation) -> Rocket<Build> { rocket::build() .configure(Config { address: std::env::var("BIND_ADDR") @@ -66,6 +63,7 @@ pub fn build_rocket( }) .as_bytes(), ), + ip_header: Some("x-forwarded-for".into()), ..Default::default() }) .manage(database) diff --git a/server/src/routes/stream.rs b/server/src/routes/stream.rs index c47f589..018fae5 100644 --- a/server/src/routes/stream.rs +++ b/server/src/routes/stream.rs @@ -10,14 +10,29 @@ use jellybase::CONF; use jellycommon::{stream::StreamSpec, MediaSource}; use log::{info, warn}; use rocket::{ - get, - http::{ContentType, Header, Status}, + get, head, + http::{Header, Status}, request::{self, FromRequest}, response::{self, Redirect, Responder}, Either, Request, Response, State, }; use std::{ops::Range, time::Duration}; -use tokio::io::DuplexStream; +use tokio::io::{duplex, DuplexStream}; + +#[head("/n/<_id>/stream?<spec>")] +pub async fn r_stream_head( + _sess: Session, + _id: &str, + spec: StreamSpec, +) -> Result<Either<StreamResponse, Redirect>, MyError> { + let head = jellystream::stream_head(&spec); + Ok(Either::Left(StreamResponse { + stream: duplex(0).0, + advertise_range: head.range_supported, + content_type: head.content_type, + range: None, + })) +} #[get("/n/<id>/stream?<spec>")] pub async fn r_stream( @@ -28,7 +43,10 @@ pub async fn r_stream( range: Option<RequestRange>, spec: StreamSpec, ) -> Result<Either<StreamResponse, Redirect>, MyError> { - let node = db.node.get(&id.to_string())?.ok_or(anyhow!("node does not exist"))?; + let node = db + .node + .get(&id.to_string())? + .ok_or(anyhow!("node does not exist"))?; let source = node .private .source @@ -70,8 +88,15 @@ pub async fn r_stream( None => 0..(isize::MAX as usize), }; + let head = jellystream::stream_head(&spec); + match jellystream::stream(node, spec, urange).await { - Ok(stream) => Ok(Either::Left(StreamResponse { stream, range })), + Ok(stream) => Ok(Either::Left(StreamResponse { + stream, + range, + advertise_range: head.range_supported, + content_type: head.content_type, + })), Err(e) => { warn!("stream error: {e}"); Err(MyError(e)) @@ -81,6 +106,8 @@ pub async fn r_stream( pub struct StreamResponse { stream: DuplexStream, + advertise_range: bool, + content_type: &'static str, range: Option<RequestRange>, } @@ -92,8 +119,10 @@ impl<'r> Responder<'r, 'static> for StreamResponse { b.status(Status::PartialContent); b.header(Header::new("content-range", range.to_cr_hv())); } - b.header(Header::new("accept-ranges", "bytes")) - .header(ContentType::WEBM) + if self.advertise_range { + b.header(Header::new("accept-ranges", "bytes")); + } + b.header(Header::new("content-type", self.content_type)) .streamed_body(self.stream) .ok() } diff --git a/server/src/routes/ui/player.rs b/server/src/routes/ui/player.rs index fa9657f..4a636e6 100644 --- a/server/src/routes/ui/player.rs +++ b/server/src/routes/ui/player.rs @@ -33,13 +33,16 @@ pub struct PlayerConfig { } #[get("/n/<id>/player?<conf..>", rank = 4)] -pub fn r_player( +pub fn r_player<'a>( _sess: Session, - db: &State<Database>, - id: String, + db: &'a State<Database>, + id: &'a str, conf: PlayerConfig, -) -> MyResult<DynLayoutPage<'_>> { - let item = db.node.get(&id)?.ok_or(anyhow!("node does not exist"))?; +) -> MyResult<DynLayoutPage<'a>> { + let item = db + .node + .get(&id.to_string())? + .ok_or(anyhow!("node does not exist"))?; let spec = StreamSpec { tracks: None @@ -48,7 +51,8 @@ pub fn r_player( .chain(conf.a.into_iter()) .chain(conf.s.into_iter()) .collect::<Vec<_>>(), - format: StreamFormat::Webm, + format: StreamFormat::Matroska, + webm: Some(true), ..Default::default() }; diff --git a/stream/src/lib.rs b/stream/src/lib.rs index ccb424a..5051b18 100644 --- a/stream/src/lib.rs +++ b/stream/src/lib.rs @@ -11,6 +11,23 @@ use tokio::{ }; use tokio_util::io::SyncIoBridge; +pub struct StreamHead { + pub content_type: &'static str, + pub range_supported: bool, +} + +#[rustfmt::skip] +pub fn stream_head(spec: &StreamSpec) -> StreamHead { + let webm_or_mkv = if spec.webm.unwrap_or(false) { "video/webm" } else { "video/x-matroska" }; + match spec.format { + StreamFormat::Original => StreamHead { content_type: "video/x-matroska", range_supported: true }, + StreamFormat::Matroska => StreamHead { content_type: webm_or_mkv, range_supported: true }, + StreamFormat::Hls => StreamHead { content_type: "application/vnd.apple.mpegurl", range_supported: false }, + StreamFormat::Jhls => StreamHead { content_type: "application/jellything-jhls+json", range_supported: false }, + StreamFormat::Segment => StreamHead { content_type: webm_or_mkv, range_supported: false }, + } +} + pub async fn stream(node: Node, spec: StreamSpec, range: Range<usize>) -> Result<DuplexStream> { let (a, b) = duplex(4096); @@ -26,9 +43,7 @@ pub async fn stream(node: Node, spec: StreamSpec, range: Range<usize>) -> Result match spec.format { StreamFormat::Original => original_stream(track_sources, spec, range, b).await?, - StreamFormat::Matroska | StreamFormat::Webm => { - remux_stream(node, track_sources, spec, range, b).await? - } + StreamFormat::Matroska => remux_stream(node, track_sources, spec, range, b).await?, StreamFormat::Hls => todo!(), StreamFormat::Jhls => todo!(), StreamFormat::Segment => todo!(), @@ -54,7 +69,7 @@ async fn remux_stream( node.public, track_sources, spec.tracks, - spec.format == StreamFormat::Webm, + spec.webm.unwrap_or(false), ) }); |