aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/src/stream.rs39
-rw-r--r--server/src/routes/stream.rs103
-rw-r--r--server/src/routes/ui/player.rs60
3 files changed, 119 insertions, 83 deletions
diff --git a/common/src/stream.rs b/common/src/stream.rs
index 0e8f810..9a00ce0 100644
--- a/common/src/stream.rs
+++ b/common/src/stream.rs
@@ -4,7 +4,7 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
use serde::{Deserialize, Serialize};
-use std::fmt::Display;
+use std::{collections::BTreeMap, fmt::Display, str::FromStr};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum StreamSpec {
@@ -106,6 +106,31 @@ impl StreamSpec {
} => format!("?fragment&segment={segment}&track={track}&index={index}&container={container}&format={format}"),
}
}
+ pub fn from_query_kv(query: &BTreeMap<String, String>) -> Result<Self, &'static str> {
+ let get_num = |k: &'static str| {
+ query
+ .get(k)
+ .ok_or(k)
+ .and_then(|a| a.parse().map_err(|_| "invalid number"))
+ };
+ let get_container = || {
+ query
+ .get("container")
+ .ok_or("container")
+ .and_then(|s| s.parse().map_err(|()| "unknown container"))
+ };
+ if query.contains_key("fragment") {
+ Ok(Self::Fragment {
+ segment: get_num("segment")?,
+ track: get_num("track")? as usize,
+ index: get_num("index")?,
+ container: get_container()?,
+ format: get_num("format")? as usize,
+ })
+ } else {
+ Err("invalid stream spec")
+ }
+ }
}
impl Display for StreamContainer {
@@ -118,3 +143,15 @@ impl Display for StreamContainer {
})
}
}
+impl FromStr for StreamContainer {
+ type Err = ();
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Ok(match s {
+ "webm" => StreamContainer::WebM,
+ "matroska" => StreamContainer::Matroska,
+ "webvtt" => StreamContainer::WebVTT,
+ "jvtt" => StreamContainer::JVTT,
+ _ => return Err(()),
+ })
+ }
+}
diff --git a/server/src/routes/stream.rs b/server/src/routes/stream.rs
index 1fb136c..d65b346 100644
--- a/server/src/routes/stream.rs
+++ b/server/src/routes/stream.rs
@@ -21,7 +21,11 @@ use rocket::{
response::{self, Redirect, Responder},
Either, Request, Response, State,
};
-use std::{collections::HashSet, ops::Range};
+use std::{
+ collections::{BTreeMap, HashSet},
+ ops::Range,
+ sync::Arc,
+};
use tokio::io::{duplex, DuplexStream};
#[head("/n/<_id>/stream?<spec>")]
@@ -46,65 +50,66 @@ pub async fn r_stream(
db: &State<Database>,
id: &str,
range: Option<RequestRange>,
- spec: StreamSpec,
+ spec: BTreeMap<String, String>,
) -> Result<Either<StreamResponse, RedirectResponse>, MyError> {
// TODO perm
let node = db
.get_node_slug(id)?
.ok_or(anyhow!("node does not exist"))?;
- let media = node
- .media
- .as_ref()
- .ok_or(anyhow!("item does not contain media"))?;
+ let media = Arc::new(
+ node.media
+ .clone()
+ .ok_or(anyhow!("item does not contain media"))?,
+ );
// TODO its unclear how requests with multiple tracks should be handled.
- if spec.track.len() == 1 {
- let ti = spec.track[0];
- if let TrackSource::Remote(remote_index) = media.tracks[ti].source {
- session
- .user
- .permissions
- .assert(&UserPermission::FederatedContent)?;
+ // if spec.track.len() == 1 {
+ // let ti = spec.track[0];
+ // if let TrackSource::Remote(remote_index) = media.tracks[ti].source {
+ // session
+ // .user
+ // .permissions
+ // .assert(&UserPermission::FederatedContent)?;
- let track = &node.media.as_ref().ok_or(anyhow!("no media"))?.tracks[ti];
- let host = track
- .federated
- .last()
- .ok_or(anyhow!("federation inconsistent"))?;
+ // let track = &node.media.as_ref().ok_or(anyhow!("no media"))?.tracks[ti];
+ // let host = track
+ // .federated
+ // .last()
+ // .ok_or(anyhow!("federation inconsistent"))?;
- let FederationAccount {
- password, username, ..
- } = SECRETS
- .federation
- .get(host)
- .ok_or(anyhow!("no credentials on the server-side"))?;
+ // let FederationAccount {
+ // password, username, ..
+ // } = SECRETS
+ // .federation
+ // .get(host)
+ // .ok_or(anyhow!("no credentials on the server-side"))?;
- info!("creating session on {host}");
- let instance = federation.get_instance(host)?.to_owned();
- let session = instance
- .login(CreateSessionParams {
- username: username.to_owned(),
- password: password.to_owned(),
- expire: Some(60),
- drop_permissions: Some(HashSet::from_iter([
- UserPermission::ManageSelf,
- UserPermission::Admin, // in case somebody federated the admin :)))
- ])),
- })
- .await?;
+ // info!("creating session on {host}");
+ // let instance = federation.get_instance(host)?.to_owned();
+ // let session = instance
+ // .login(CreateSessionParams {
+ // username: username.to_owned(),
+ // password: password.to_owned(),
+ // expire: Some(60),
+ // drop_permissions: Some(HashSet::from_iter([
+ // UserPermission::ManageSelf,
+ // UserPermission::Admin, // in case somebody federated the admin :)))
+ // ])),
+ // })
+ // .await?;
- let uri = session.stream_url(
- node.slug.clone().into(),
- &StreamSpec {
- track: vec![remote_index],
- ..spec
- },
- );
- info!("federation redirect");
- return Ok(Either::Right(RedirectResponse(uri)));
- }
- }
+ // let uri = session.stream_url(
+ // node.slug.clone().into(),
+ // &StreamSpec {
+ // track: vec![remote_index],
+ // ..spec
+ // },
+ // );
+ // info!("federation redirect");
+ // return Ok(Either::Right(RedirectResponse(uri)));
+ // }
+ // }
info!(
"stream request (range={})",
@@ -124,7 +129,7 @@ pub async fn r_stream(
let head = jellystream::stream_head(&spec);
- match jellystream::stream(node, spec, urange, &session.user.permissions).await {
+ match jellystream::stream(media, spec, urange, &session.user.permissions).await {
Ok(stream) => Ok(Either::Left(StreamResponse {
stream,
range,
diff --git a/server/src/routes/ui/player.rs b/server/src/routes/ui/player.rs
index 2f28f74..aa567ab 100644
--- a/server/src/routes/ui/player.rs
+++ b/server/src/routes/ui/player.rs
@@ -9,16 +9,14 @@ use super::{
};
use crate::{
database::Database,
- routes::{
- stream::rocket_uri_macro_r_stream,
- ui::{assets::rocket_uri_macro_r_item_backdrop, error::MyResult, layout::DynLayoutPage},
+ routes::ui::{
+ assets::rocket_uri_macro_r_item_backdrop, error::MyResult, layout::DynLayoutPage,
},
uri,
};
use anyhow::anyhow;
use jellybase::{permission::PermissionSetExt, CONF};
use jellycommon::{
- stream::{StreamFormat, StreamSpec},
user::{PermissionSet, PlayerKind, UserPermission},
Node, NodeID, SourceTrackKind, TrackID,
};
@@ -47,13 +45,15 @@ impl PlayerConfig {
fn jellynative_url(action: &str, seek: f64, secret: &str, node: &str, session: &str) -> String {
let protocol = if CONF.tls { "https" } else { "http" };
let host = &CONF.hostname;
- let stream_url = uri!(r_stream(
- node,
- StreamSpec {
- format: StreamFormat::HlsMaster,
- ..Default::default()
- }
- ));
+ let stream_url = "";
+ // TODO
+ // uri!(r_stream(
+ // node,
+ // StreamSpec {
+ // format: StreamFormat::HlsMaster,
+ // ..Default::default()
+ // }
+ // ));
format!("jellynative://{action}/{secret}/{session}/{seek}/{protocol}://{host}{stream_url}",)
}
@@ -67,14 +67,6 @@ pub fn r_player(
let node = db.get_node(id)?.ok_or(anyhow!("node does not exist"))?;
let native_session = |action: &str| {
- let perm = [
- UserPermission::StreamFormat(StreamFormat::HlsMaster),
- UserPermission::StreamFormat(StreamFormat::HlsVariant),
- UserPermission::StreamFormat(StreamFormat::Fragment),
- ];
- for perm in &perm {
- sess.user.permissions.assert(perm)?;
- }
Ok(Either::Right(Redirect::temporary(jellynative_url(
action,
conf.t.unwrap_or(0.),
@@ -82,7 +74,7 @@ pub fn r_player(
&id.to_string(),
&token::create(
sess.user.name,
- PermissionSet(perm.map(|e| (e, true)).into()),
+ PermissionSet::default(), // TODO
chrono::Duration::hours(24),
),
))))
@@ -98,19 +90,20 @@ pub fn r_player(
}
}
- let spec = StreamSpec {
- track: None
- .into_iter()
- .chain(conf.v)
- .chain(conf.a)
- .chain(conf.s)
- .collect::<Vec<_>>(),
- format: StreamFormat::Matroska,
- webm: Some(true),
- ..Default::default()
- };
+ // TODO
+ // let spec = StreamSpec {
+ // track: None
+ // .into_iter()
+ // .chain(conf.v)
+ // .chain(conf.a)
+ // .chain(conf.s)
+ // .collect::<Vec<_>>(),
+ // format: StreamFormat::Matroska,
+ // webm: Some(true),
+ // ..Default::default()
+ // };
- let playing = !spec.track.is_empty();
+ let playing = false; // !spec.track.is_empty();
let conf = player_conf(node.clone(), playing)?;
Ok(Either::Left(LayoutPage {
@@ -118,7 +111,8 @@ pub fn r_player(
class: Some("player"),
content: markup::new! {
@if playing {
- video[src=uri!(r_stream(&node.slug, &spec)), controls, preload="auto"]{}
+ // TODO
+ // video[src=uri!(r_stream(&node.slug, &spec)), controls, preload="auto"]{}
} else {
img.backdrop[src=uri!(r_item_backdrop(&node.slug, Some(2048))).to_string()];
}