aboutsummaryrefslogtreecommitdiff
path: root/server/src
diff options
context:
space:
mode:
Diffstat (limited to 'server/src')
-rw-r--r--server/src/routes/compat/jellyfin/mod.rs45
-rw-r--r--server/src/routes/stream.rs136
-rw-r--r--server/src/routes/ui/player.rs72
3 files changed, 129 insertions, 124 deletions
diff --git a/server/src/routes/compat/jellyfin/mod.rs b/server/src/routes/compat/jellyfin/mod.rs
index ab36a8c..7393c5f 100644
--- a/server/src/routes/compat/jellyfin/mod.rs
+++ b/server/src/routes/compat/jellyfin/mod.rs
@@ -5,23 +5,19 @@
*/
pub mod models;
-use crate::routes::{
- stream::rocket_uri_macro_r_stream,
- ui::{
- account::{login_logic, session::Session},
- assets::{
- rocket_uri_macro_r_asset, rocket_uri_macro_r_item_backdrop,
- rocket_uri_macro_r_item_poster,
- },
- error::MyResult,
- node::{aspect_class, DatabaseNodeUserDataExt},
- sort::{filter_and_sort_nodes, FilterProperty, NodeFilterSort, SortOrder, SortProperty},
+use crate::routes::ui::{
+ account::{login_logic, session::Session},
+ assets::{
+ rocket_uri_macro_r_asset, rocket_uri_macro_r_item_backdrop, rocket_uri_macro_r_item_poster,
},
+ error::MyResult,
+ node::{aspect_class, DatabaseNodeUserDataExt},
+ sort::{filter_and_sort_nodes, FilterProperty, NodeFilterSort, SortOrder, SortProperty},
};
use anyhow::{anyhow, Context};
use jellybase::{database::Database, CONF};
use jellycommon::{
- stream::{StreamFormat, StreamSpec},
+ stream::{StreamContainer, StreamSpec},
user::{NodeUserData, WatchedState},
MediaInfo, Node, NodeID, NodeKind, SourceTrack, SourceTrackKind, Visibility,
};
@@ -446,16 +442,12 @@ pub fn r_jellyfin_video_stream(
.get_node_slug(id)?
.ok_or(anyhow!("node does not exist"))?;
let media = node.media.as_ref().ok_or(anyhow!("node has no media"))?;
- Ok(Redirect::temporary(rocket::uri!(r_stream(
- id,
- StreamSpec {
- format: StreamFormat::Matroska,
- webm: Some(true),
- track: (0..media.tracks.len()).collect(),
- index: None,
- profile: None,
- }
- ))))
+ let params = StreamSpec::Remux {
+ tracks: (0..media.tracks.len()).collect(),
+ container: StreamContainer::WebM,
+ }
+ .to_query();
+ Ok(Redirect::temporary(format!("/n/{id}/stream{params}")))
}
#[derive(Deserialize)]
@@ -498,9 +490,7 @@ pub fn r_jellyfin_playback_bitratetest(_session: Session, Size: usize) -> Vec<u8
}
#[post("/Sessions/Capabilities/Full")]
-pub fn r_jellyfin_sessions_capabilities_full(_session: Session) {
-
-}
+pub fn r_jellyfin_sessions_capabilities_full(_session: Session) {}
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
@@ -796,7 +786,10 @@ fn item_object(node: &Node, userdata: &NodeUserData) -> JellyfinItem {
location_type: node.media.as_ref().map(|_| "FileSystem".to_owned()),
play_access: node.media.as_ref().map(|_| "Full".to_owned()),
container: node.media.as_ref().map(|_| "webm".to_owned()),
- run_time_ticks: node.media.as_ref().map(|m| (m.duration * 10_000_000.) as i64),
+ run_time_ticks: node
+ .media
+ .as_ref()
+ .map(|m| (m.duration * 10_000_000.) as i64),
media_sources: media_source.as_ref().map(|s| vec![s.clone()]),
media_streams: media_source.as_ref().map(|s| s.media_streams.clone()),
path: node
diff --git a/server/src/routes/stream.rs b/server/src/routes/stream.rs
index 1fb136c..0fbeb3a 100644
--- a/server/src/routes/stream.rs
+++ b/server/src/routes/stream.rs
@@ -6,13 +6,9 @@
use super::ui::{account::session::Session, error::MyError};
use crate::database::Database;
use anyhow::{anyhow, Result};
-use jellybase::{federation::Federation, permission::PermissionSetExt, SECRETS};
-use jellycommon::{
- config::FederationAccount,
- stream::StreamSpec,
- user::{CreateSessionParams, UserPermission},
- TrackSource,
-};
+use jellybase::{assetfed::AssetInner, federation::Federation};
+use jellycommon::{stream::StreamSpec, TrackSource};
+use jellystream::SMediaInfo;
use log::{info, warn};
use rocket::{
get, head,
@@ -21,15 +17,20 @@ use rocket::{
response::{self, Redirect, Responder},
Either, Request, Response, State,
};
-use std::{collections::HashSet, ops::Range};
+use std::{
+ collections::{BTreeMap, BTreeSet},
+ ops::Range,
+ sync::Arc,
+};
use tokio::io::{duplex, DuplexStream};
-#[head("/n/<_id>/stream?<spec>")]
+#[head("/n/<_id>/stream?<spec..>")]
pub async fn r_stream_head(
_sess: Session,
_id: &str,
- spec: StreamSpec,
+ spec: BTreeMap<String, String>,
) -> Result<Either<StreamResponse, Redirect>, MyError> {
+ let spec = StreamSpec::from_query_kv(&spec).map_err(|x| anyhow!("spec invalid: {x}"))?;
let head = jellystream::stream_head(&spec);
Ok(Either::Left(StreamResponse {
stream: duplex(0).0,
@@ -41,70 +42,72 @@ pub async fn r_stream_head(
#[get("/n/<id>/stream?<spec..>")]
pub async fn r_stream(
- session: Session,
- federation: &State<Federation>,
+ _session: Session,
+ _federation: &State<Federation>,
db: &State<Database>,
id: &str,
range: Option<RequestRange>,
- spec: StreamSpec,
+ spec: BTreeMap<String, String>,
) -> Result<Either<StreamResponse, RedirectResponse>, MyError> {
+ let spec = StreamSpec::from_query_kv(&spec).map_err(|x| anyhow!("spec invalid: {x}"))?;
// 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 +127,20 @@ pub async fn r_stream(
let head = jellystream::stream_head(&spec);
- match jellystream::stream(node, spec, urange, &session.user.permissions).await {
+ let mut sources = BTreeSet::new();
+ for t in &media.tracks {
+ if let TrackSource::Local(x) = &t.source {
+ if let AssetInner::LocalTrack(m) = AssetInner::deser(&x.0)? {
+ sources.insert(m.path);
+ }
+ }
+ }
+ let media = Arc::new(SMediaInfo {
+ files: sources,
+ info: node,
+ });
+
+ match jellystream::stream(media, spec, urange).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 c2188a8..d2a8236 100644
--- a/server/src/routes/ui/player.rs
+++ b/server/src/routes/ui/player.rs
@@ -6,22 +6,20 @@
use super::{
account::session::{token, Session},
layout::LayoutPage,
- node::{get_similar_media, DatabaseNodeUserDataExt, NodePage},
- sort::NodeFilterSort,
+ node::{get_similar_media, DatabaseNodeUserDataExt},
};
use crate::{
database::Database,
- routes::{
- stream::rocket_uri_macro_r_stream,
- ui::{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 jellybase::CONF;
use jellycommon::{
- stream::{StreamFormat, StreamSpec},
- user::{PermissionSet, PlayerKind, UserPermission},
+ stream::{StreamContainer, StreamSpec},
+ user::{PermissionSet, PlayerKind},
Node, NodeID, SourceTrackKind, TrackID, Visibility,
};
use markup::DynRender;
@@ -49,13 +47,14 @@ 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 = format!(
+ "/n/{node}/stream{}",
+ StreamSpec::HlsMultiVariant {
+ segment: 0,
+ container: StreamContainer::Matroska
}
- ));
+ .to_query()
+ );
format!("jellynative://{action}/{secret}/{session}/{seek}/{protocol}://{host}{stream_url}",)
}
@@ -66,7 +65,7 @@ pub fn r_player(
id: NodeID,
conf: PlayerConfig,
) -> MyResult<Either<DynLayoutPage<'_>, Redirect>> {
- let (node, udata) = db.get_node_with_userdata(id, &session)?;
+ let (node, _udata) = db.get_node_with_userdata(id, &session)?;
let mut parents = node
.parents
@@ -80,14 +79,6 @@ pub fn r_player(
parents.retain(|(n, _)| n.visibility >= Visibility::Reduced);
let native_session = |action: &str| {
- let perm = [
- UserPermission::StreamFormat(StreamFormat::HlsMaster),
- UserPermission::StreamFormat(StreamFormat::HlsVariant),
- UserPermission::StreamFormat(StreamFormat::Fragment),
- ];
- for perm in &perm {
- session.user.permissions.assert(perm)?;
- }
Ok(Either::Right(Redirect::temporary(jellynative_url(
action,
conf.t.unwrap_or(0.),
@@ -95,7 +86,7 @@ pub fn r_player(
&id.to_string(),
&token::create(
session.user.name,
- PermissionSet(perm.map(|e| (e, true)).into()),
+ PermissionSet::default(), // TODO
chrono::Duration::hours(24),
),
))))
@@ -111,27 +102,32 @@ 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 {
title: node.title.to_owned().unwrap_or_default(),
class: Some("player"),
content: markup::new! {
- video[id="player", src=uri!(r_stream(&node.slug, &spec)), controls, preload="auto"]{}
- @NodePage { children: &[], parents: &parents, filter: &NodeFilterSort::default(), node: &node, udata: &udata, player: true, similar: &similar }
+ @if playing {
+ // 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()];
+ }
@conf
},
}))