aboutsummaryrefslogtreecommitdiff
path: root/server/src
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2023-01-18 19:13:36 +0100
committermetamuffin <metamuffin@disroot.org>2023-01-18 19:13:36 +0100
commite24a026c58c07a2800662b9f5f4fd3f61d53c1d1 (patch)
tree91c158ff294508f903f2489f204e313700d42828 /server/src
parent46ef75431e1d34c63a690726a8ef584d175ddd30 (diff)
downloadjellything-e24a026c58c07a2800662b9f5f4fd3f61d53c1d1.tar
jellything-e24a026c58c07a2800662b9f5f4fd3f61d53c1d1.tar.bz2
jellything-e24a026c58c07a2800662b9f5f4fd3f61d53c1d1.tar.zst
player config works
Diffstat (limited to 'server/src')
-rw-r--r--server/src/library.rs16
-rw-r--r--server/src/routes/mod.rs4
-rw-r--r--server/src/routes/stream.rs28
-rw-r--r--server/src/routes/ui/error.rs2
-rw-r--r--server/src/routes/ui/mod.rs1
-rw-r--r--server/src/routes/ui/node.rs11
-rw-r--r--server/src/routes/ui/player.rs95
-rw-r--r--server/src/routes/ui/style/layout.css23
8 files changed, 155 insertions, 25 deletions
diff --git a/server/src/library.rs b/server/src/library.rs
index 37d71df..17adf3c 100644
--- a/server/src/library.rs
+++ b/server/src/library.rs
@@ -1,7 +1,12 @@
use anyhow::{anyhow, bail, Context, Ok};
use jellycommon::{DirectoryInfo, ItemInfo};
use log::info;
-use std::{ffi::OsStr, fs::File, path::PathBuf, sync::Arc};
+use std::{
+ ffi::OsStr,
+ fs::File,
+ path::{Path, PathBuf},
+ sync::Arc,
+};
pub struct Library {
pub root: Arc<Node>,
@@ -26,7 +31,7 @@ pub struct Item {
pub fs_path: PathBuf,
pub lib_path: PathBuf,
pub identifier: String,
- pub data: ItemInfo,
+ pub info: ItemInfo,
}
impl Library {
@@ -35,6 +40,9 @@ impl Library {
root: Node::from_path(path.into(), PathBuf::new(), true).context("indexing root")?,
})
}
+ pub fn nested_path(&self, path: &Path) -> anyhow::Result<Arc<Node>> {
+ self.nested(path.to_str().unwrap())
+ }
pub fn nested(&self, path: &str) -> anyhow::Result<Arc<Node>> {
let mut n = self.root.clone();
if path == "" {
@@ -66,7 +74,7 @@ impl Node {
pub fn title(&self) -> &str {
match self {
Node::Directory(d) => &d.data.name,
- Node::Item(i) => &i.data.title,
+ Node::Item(i) => &i.info.title,
}
}
pub fn identifier(&self) -> &str {
@@ -133,7 +141,7 @@ impl Node {
Ok(Node::Item(Arc::new(Item {
fs_path: path.clone(),
lib_path: lib_path.join(identifier.clone()),
- data,
+ info: data,
identifier,
}))
.into())
diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs
index 4491e81..87d3051 100644
--- a/server/src/routes/mod.rs
+++ b/server/src/routes/mod.rs
@@ -4,6 +4,7 @@ use stream::r_stream;
use ui::error::r_not_found;
use ui::home::r_home;
use ui::node::r_library_node;
+use ui::player::r_player;
use ui::style::{r_assets_font, r_assets_style};
pub mod stream;
@@ -20,7 +21,8 @@ pub fn build_rocket(state: AppState) -> Rocket<Build> {
r_library_node,
r_assets_style,
r_assets_font,
- r_stream
+ r_stream,
+ r_player,
],
)
}
diff --git a/server/src/routes/stream.rs b/server/src/routes/stream.rs
index 6ff6982..c49f51d 100644
--- a/server/src/routes/stream.rs
+++ b/server/src/routes/stream.rs
@@ -1,7 +1,6 @@
use super::ui::error::MyError;
use crate::AppState;
-use anyhow::anyhow;
-use anyhow::Context;
+use anyhow::{anyhow, Context};
use log::debug;
use log::warn;
use rocket::{get, http::ContentType, response::stream::ReaderStream, State};
@@ -9,10 +8,23 @@ use std::path::PathBuf;
use tokio::io::{duplex, DuplexStream};
use tokio_util::io::SyncIoBridge;
-#[get("/stream/<path..>?<selection>")]
+pub fn stream_uri(path: &PathBuf, tracks: &Vec<u64>) -> String {
+ format!(
+ "/stream/{}?tracks={}",
+ path.to_str().unwrap().to_string(),
+ tracks
+ .iter()
+ .map(|v| format!("{v}"))
+ .collect::<Vec<_>>()
+ .join(",")
+ )
+}
+
+#[get("/stream/<path..>?<tracks>&<webm>")]
pub fn r_stream(
path: PathBuf,
- selection: String,
+ webm: Option<bool>,
+ tracks: String,
state: &State<AppState>,
) -> Result<(ContentType, ReaderStream![DuplexStream]), MyError> {
let (a, b) = duplex(1024);
@@ -23,20 +35,22 @@ pub fn r_stream(
.context("retrieving library node")?
.get_item()?;
let remuxer = state.remuxer.clone();
- let selection = selection
+ let tracks = tracks
.split(",")
.map(|e| e.parse().map_err(|_| anyhow!("invalid number")))
.into_iter()
.collect::<Result<Vec<_>, _>>()?;
let b = SyncIoBridge::new(b);
+
tokio::task::spawn_blocking(move || {
if let Err(e) = remuxer.generate_into(
b,
0,
item.fs_path.parent().unwrap().to_path_buf(),
- item.data.clone(),
- selection,
+ item.info.clone(),
+ tracks,
+ webm.unwrap_or(false),
) {
warn!("stream stopped: {e}")
}
diff --git a/server/src/routes/ui/error.rs b/server/src/routes/ui/error.rs
index 2a1f8e6..0b24b62 100644
--- a/server/src/routes/ui/error.rs
+++ b/server/src/routes/ui/error.rs
@@ -20,6 +20,8 @@ pub fn r_not_found<'a>(status: Status, _request: &Request) -> HtmlTemplate<marku
)
}
+pub type MyResult<T> = Result<T, MyError>;
+
#[derive(Debug)]
pub struct MyError(anyhow::Error);
diff --git a/server/src/routes/ui/mod.rs b/server/src/routes/ui/mod.rs
index aa0259d..1cad72c 100644
--- a/server/src/routes/ui/mod.rs
+++ b/server/src/routes/ui/mod.rs
@@ -12,6 +12,7 @@ pub mod home;
pub mod layout;
pub mod node;
pub mod style;
+pub mod player;
pub struct HtmlTemplate<T>(pub String, pub T);
diff --git a/server/src/routes/ui/node.rs b/server/src/routes/ui/node.rs
index b6d0d30..ea23291 100644
--- a/server/src/routes/ui/node.rs
+++ b/server/src/routes/ui/node.rs
@@ -1,5 +1,5 @@
-use super::super::stream::rocket_uri_macro_r_stream;
use super::error::MyError;
+use super::player::player_uri;
use crate::{
library::{Directory, Item, Node},
routes::ui::HtmlTemplate,
@@ -14,10 +14,9 @@ pub async fn r_library_node(
path: PathBuf,
state: &State<AppState>,
) -> Result<HtmlTemplate<markup::DynRender>, MyError> {
- let path = path.to_str().unwrap().to_string();
let node = state
.library
- .nested(&path)
+ .nested_path(&path)
.context("retrieving library node")?
.clone();
Ok(HtmlTemplate(
@@ -52,10 +51,10 @@ markup::define! {
}
}
ItemCard(item: Arc<Item>) {
- span { a[href=&uri!(r_library_node(&item.lib_path)).to_string()] { @item.data.title } }
+ span { a[href=&uri!(r_library_node(&item.lib_path)).to_string()] { @item.info.title } }
}
ItemPage(item: Arc<Item>) {
- h1 { @item.data.title }
- video[src=&uri!(r_stream(&item.lib_path, "1,2")).to_string(), controls] {}
+ h1 { @item.info.title }
+ a[href=&player_uri(&item.lib_path)] { "Watch now" }
}
}
diff --git a/server/src/routes/ui/player.rs b/server/src/routes/ui/player.rs
new file mode 100644
index 0000000..69f6e7f
--- /dev/null
+++ b/server/src/routes/ui/player.rs
@@ -0,0 +1,95 @@
+use super::HtmlTemplate;
+use crate::routes::stream::stream_uri;
+use crate::{library::Item, routes::ui::error::MyResult, AppState};
+use jellycommon::SourceTrackKind;
+use log::warn;
+use rocket::{get, FromForm, State};
+use std::{path::PathBuf, sync::Arc};
+
+pub fn player_uri(path: &PathBuf) -> String {
+ format!("/player/{}", path.to_str().unwrap())
+}
+
+#[derive(FromForm, Default, Clone, Debug)]
+pub struct PlayerConfig {
+ pub a: Option<u64>,
+ pub v: Option<u64>,
+ pub s: Option<u64>,
+ pub webm: bool,
+}
+
+#[get("/player/<path..>?<conf..>", rank = 4)]
+pub fn r_player(
+ state: &State<AppState>,
+ path: PathBuf,
+ conf: PlayerConfig,
+) -> MyResult<HtmlTemplate<markup::DynRender<'_>>> {
+ warn!("{conf:?}");
+ let item = state.library.nested_path(&path)?.get_item()?;
+ if conf.a.is_none() && conf.v.is_none() && conf.s.is_none() {
+ return player_conf(item.clone());
+ }
+
+ let tracks = conf
+ .a
+ .into_iter()
+ .chain(conf.v.into_iter())
+ .chain(conf.s.into_iter())
+ .collect::<Vec<_>>();
+
+ Ok(HtmlTemplate(
+ "Configure Player".to_string(),
+ markup::new! {
+ video[src=stream_uri(&item.lib_path, &tracks), controls];
+ },
+ ))
+}
+
+pub fn player_conf<'a>(item: Arc<Item>) -> MyResult<HtmlTemplate<markup::DynRender<'a>>> {
+ let mut audio_tracks = vec![];
+ let mut video_tracks = vec![];
+ let mut sub_tracks = vec![];
+ for (tid, track) in item.info.tracks.clone() {
+ match &track.kind {
+ SourceTrackKind::Audio { .. } => audio_tracks.push((tid, track)),
+ SourceTrackKind::Video { .. } => video_tracks.push((tid, track)),
+ SourceTrackKind::Subtitles { .. } => sub_tracks.push((tid, track)),
+ }
+ }
+
+ Ok(HtmlTemplate(
+ "Configure Player".to_string(),
+ markup::new! {
+ h2 { "Watch: " @item.info.title }
+ form[method = "GET", action = ""] {
+ h3 { "Select tracks" }
+
+ label[for="select-v"] { "Video: " }
+ select[name="v", id="select-v"] {
+ @for (tid, track) in &video_tracks {
+ option[value=tid] { @format!("{track}") }
+ }
+ }
+ br;
+
+ label[for="select-a"] { "Audio: " }
+ select[name="a", id="select-a"] {
+ @for (tid, track) in &audio_tracks {
+ option[value=tid] { @format!("{track}") }
+ }
+ }
+ br;
+
+ label[for="select-s"] { "Subtitles: " }
+ select[name="s", id="select-s"] {
+ @for (tid, track) in &sub_tracks {
+ option[value=tid] { @format!("{track}") }
+ }
+ }
+ br;
+
+ input[type="submit", value="Start playback"];
+ }
+ },
+ ))
+}
diff --git a/server/src/routes/ui/style/layout.css b/server/src/routes/ui/style/layout.css
index 0612a7e..2e54b1b 100644
--- a/server/src/routes/ui/style/layout.css
+++ b/server/src/routes/ui/style/layout.css
@@ -1,18 +1,18 @@
@font-face {
- font-family: 'Cantarell';
- src: url(/assets/cantarell.woff2) format('woff2');
+ font-family: "Cantarell";
+ src: url(/assets/cantarell.woff2) format("woff2");
}
* {
- color: white;
+ color: rgb(218, 218, 218);
font-family: "Cantarell", sans-serif;
- font-weight: 300;
+ font-weight: 500;
margin: 0px;
padding: 0px;
}
body {
- background-color: #1a1a1a;
+ background-color: #0f0f0f;
width: 100vw;
}
@@ -23,7 +23,7 @@ nav {
padding: 1em;
width: calc(100vw - 2em);
height: 2em;
- background-color: #41414144;
+ background-color: #27272744;
}
nav h1 {
@@ -42,4 +42,13 @@ nav h1 {
padding: 1em;
color: rgb(255, 117, 117);
font-family: monospace;
-} \ No newline at end of file
+}
+
+select,
+input {
+ color: white;
+ background-color: black;
+}
+option {
+ font-family: "Cantarell", sans-serif;
+}