aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api.md2
-rw-r--r--common/src/stream.rs17
-rw-r--r--server/src/main.rs10
-rw-r--r--server/src/routes/mod.rs6
-rw-r--r--server/src/routes/stream.rs43
-rw-r--r--server/src/routes/ui/player.rs16
-rw-r--r--stream/src/lib.rs23
7 files changed, 80 insertions, 37 deletions
diff --git a/api.md b/api.md
index 1dcdb41..854f9d3 100644
--- a/api.md
+++ b/api.md
@@ -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),
)
});