aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2024-01-16 22:58:23 +0100
committermetamuffin <metamuffin@disroot.org>2024-01-16 22:58:23 +0100
commit1c37d32a0985ff7390313833345b9299f9f0b196 (patch)
treecd46bcbe7df154bf506c083cf0325d44b3f4e0fb
parent98b379afb31e455b529d443dcfc5797b8dd6723e (diff)
downloadjellything-1c37d32a0985ff7390313833345b9299f9f0b196.tar
jellything-1c37d32a0985ff7390313833345b9299f9f0b196.tar.bz2
jellything-1c37d32a0985ff7390313833345b9299f9f0b196.tar.zst
chapter thumbnail display and player seek via url
-rw-r--r--server/src/routes/ui/assets.rs1
-rw-r--r--server/src/routes/ui/home.rs10
-rw-r--r--server/src/routes/ui/node.rs47
-rw-r--r--server/src/routes/ui/player.rs10
-rw-r--r--web/script/player/player.ts14
-rw-r--r--web/style/layout.css8
-rw-r--r--web/style/nodecard.css44
7 files changed, 85 insertions, 49 deletions
diff --git a/server/src/routes/ui/assets.rs b/server/src/routes/ui/assets.rs
index 7df7cf0..ddbc2ee 100644
--- a/server/src/routes/ui/assets.rs
+++ b/server/src/routes/ui/assets.rs
@@ -51,6 +51,7 @@ pub async fn r_item_assets(
Ok(asset_with_res(asset, width).await?)
}
+// TODO this can create "federation recursion" because track selection cannot be relied on.
#[get("/n/<id>/thumbnail?<t>&<width>")]
pub async fn r_node_thumbnail(
session: Session,
diff --git a/server/src/routes/ui/home.rs b/server/src/routes/ui/home.rs
index 6a3b9a4..d332447 100644
--- a/server/src/routes/ui/home.rs
+++ b/server/src/routes/ui/home.rs
@@ -84,27 +84,27 @@ pub fn r_home(sess: Session, db: &State<Database>) -> MyResult<DynLayoutPage> {
title: "Home".to_string(),
content: markup::new! {
h2 { "Explore " @CONF.brand }
- .homelist { ul {@for (id, node, udata) in &toplevel {
+ .hlist { ul {@for (id, node, udata) in &toplevel {
li { @NodeCard { id, node, udata } }
}}}
@if !continue_watching.is_empty() {
h2 { "Continue Watching" }
- .homelist { ul {@for (id, node, udata) in &continue_watching {
+ .hlist { ul {@for (id, node, udata) in &continue_watching {
li { @NodeCard { id, node, udata } }
}}}
}
@if !watchlist.is_empty() {
h2 { "Watchlist" }
- .homelist { ul {@for (id, node, udata) in &watchlist {
+ .hlist { ul {@for (id, node, udata) in &watchlist {
li { @NodeCard { id, node, udata } }
}}}
}
h2 { "Latest Releases" }
- .homelist { ul {@for (id, node, udata) in &latest {
+ .hlist { ul {@for (id, node, udata) in &latest {
li { @NodeCard { id, node, udata } }
}}}
h2 { "Today's Picks" }
- .homelist { ul {@for (id, node, udata) in &random {
+ .hlist { ul {@for (id, node, udata) in &random {
li { @NodeCard { id, node, udata } }
}}}
p.error { "TODO: recently added" }
diff --git a/server/src/routes/ui/node.rs b/server/src/routes/ui/node.rs
index adbe5b1..0dbb027 100644
--- a/server/src/routes/ui/node.rs
+++ b/server/src/routes/ui/node.rs
@@ -4,7 +4,7 @@
Copyright (C) 2023 metamuffin <metamuffin.org>
*/
use super::{
- assets::rocket_uri_macro_r_item_assets,
+ assets::{rocket_uri_macro_r_item_assets, rocket_uri_macro_r_node_thumbnail},
error::MyError,
sort::{filter_and_sort_nodes, NodeFilterSort, NodeFilterSortForm},
};
@@ -12,13 +12,13 @@ use crate::{
database::Database,
routes::{
api::AcceptJson,
- userdata::{rocket_uri_macro_r_player_watched, UrlWatchedState},
ui::{
account::session::Session,
assets::AssetRole,
layout::{DynLayoutPage, LayoutPage},
player::{rocket_uri_macro_r_player, PlayerConfig},
},
+ userdata::{rocket_uri_macro_r_player_watched, UrlWatchedState},
},
uri,
};
@@ -143,20 +143,27 @@ markup::define! {
p { @for line in description.lines() { @line br; } }
}
@if let Some(media) = &node.media {
+ @if !media.chapters.is_empty() {
+ h2 { "Chapters" }
+ .hlist { ul.children { @for chap in &media.chapters {
+ @let (inl, sub) = format_chapter(chap);
+ li { .card."aspect-thumb" {
+ .poster {
+ a[href=&uri!(r_player(id, PlayerConfig::seek(chap.time_start.unwrap_or(0.))))] {
+ img[src=&uri!(r_node_thumbnail(id, chapter_key_time(chap, media.duration), Some(1024)))];
+ }
+ .cardhover { .props { p { @inl } } }
+ }
+ .title { @sub }
+ }}
+ }}}
+ }
details {
summary { "Tracks" }
ol { @for track in &media.tracks {
li { @format!("{track}") }
}}
}
- @if !media.chapters.is_empty() {
- details {
- summary { "Chapters" }
- ol { @for chap in &media.chapters {
- li { @format_chapter(chap) }
- }}
- }
- }
}
}
@if matches!(node.kind.unwrap_or_default(), NodeKind::Collection | NodeKind::Channel) {
@@ -297,11 +304,19 @@ fn format_count(n: impl Into<usize>) -> String {
}
}
-fn format_chapter(c: &Chapter) -> String {
- format!(
- "{}-{}: {}",
- c.time_start.map(format_duration).unwrap_or_default(),
- c.time_end.map(format_duration).unwrap_or_default(),
- c.labels.first().map(|l| l.1.clone()).unwrap_or_default()
+fn format_chapter(c: &Chapter) -> (String, String) {
+ (
+ format!(
+ "{}-{}",
+ c.time_start.map(format_duration).unwrap_or_default(),
+ c.time_end.map(format_duration).unwrap_or_default(),
+ ),
+ c.labels.first().map(|l| l.1.clone()).unwrap_or_default(),
)
}
+
+fn chapter_key_time(c: &Chapter, dur: f64) -> f64 {
+ let start = c.time_start.unwrap_or(0.);
+ let end = c.time_end.unwrap_or(dur);
+ start * 0.8 + end * 0.2
+}
diff --git a/server/src/routes/ui/player.rs b/server/src/routes/ui/player.rs
index 8b8adf6..e3c5cb2 100644
--- a/server/src/routes/ui/player.rs
+++ b/server/src/routes/ui/player.rs
@@ -29,6 +29,16 @@ pub struct PlayerConfig {
pub a: Option<TrackID>,
pub v: Option<TrackID>,
pub s: Option<TrackID>,
+ pub t: Option<f64>,
+}
+
+impl PlayerConfig {
+ pub fn seek(t: f64) -> Self {
+ Self {
+ t: Some(t),
+ ..Default::default()
+ }
+ }
}
#[get("/n/<id>/player?<conf..>", rank = 4)]
diff --git a/web/script/player/player.ts b/web/script/player/player.ts
index a92ab4b..ea45e9a 100644
--- a/web/script/player/player.ts
+++ b/web/script/player/player.ts
@@ -107,9 +107,10 @@ export class Player {
this.set_pers("Downloading track indecies...")
await this.set_track_enabled(0, true, false)
await this.set_track_enabled(1, true, false)
-
+
this.set_pers("Downloading initial segments...")
- const start_time = get_continue_time(userdata.watched);
+ const start_time = get_query_start_time() ?? get_continue_time(userdata.watched);
+
this.update(start_time)
this.video.currentTime = start_time
@@ -155,3 +156,12 @@ function get_continue_time(w: WatchedState): number {
if (typeof w == "string") return 0
else return w.progress
}
+
+function get_query_start_time() {
+ const u = new URL(window.location.href)
+ const p = u.searchParams.get("t")
+ if (!p) return
+ const x = parseFloat(p)
+ if (Number.isNaN(x)) return
+ return x
+}
diff --git a/web/style/layout.css b/web/style/layout.css
index 16ae668..3ad5236 100644
--- a/web/style/layout.css
+++ b/web/style/layout.css
@@ -106,19 +106,19 @@ footer p {
margin-right: 3px;
}
-.homelist {
+.hlist {
overflow-x: scroll;
max-width: 100%;
}
-.homelist ul {
+.hlist ul {
display: table;
}
-.homelist ul li {
+.hlist ul li {
display: table-cell;
}
@media (max-width: 750px) {
- .homelist ul {
+ .hlist ul {
padding-left: 0;
}
}
diff --git a/web/style/nodecard.css b/web/style/nodecard.css
index 7554649..b0dd3e2 100644
--- a/web/style/nodecard.css
+++ b/web/style/nodecard.css
@@ -18,52 +18,52 @@
display: block;
}
-.node.card {
+.card {
padding: 1em;
}
-.node.card .poster,
-.node.card .poster img {
+.card .poster,
+.card .poster img {
height: var(--card-size);
}
-.node.card.aspect-port {
+.card.aspect-port {
width: calc(var(--card-size) / var(--port-poster-aspect));
}
-.node.card.aspect-land {
+.card.aspect-land {
width: calc(var(--card-size) / var(--land-poster-aspect));
}
-.node.card.aspect-thumb {
+.card.aspect-thumb {
width: calc(var(--card-size) / var(--land-thumb-aspect));
}
-.node.card.aspect-square {
+.card.aspect-square {
width: calc(var(--card-size));
}
-.node.card .poster a img {
+.card .poster a img {
object-fit: cover;
object-position: center;
width: 100%;
}
-.node.card .title {
+.card .title {
text-align: center;
margin-top: 0.5em;
text-align: center;
text-overflow: ellipsis;
}
-.node.card .title a {
+.card .title a {
text-decoration-line: none;
}
-.node.card .title a:hover {
+.card .title a:hover {
text-decoration-line: underline;
}
-.node.card .poster {
+.card .poster {
display: grid;
}
-.node.card .poster a {
+.card .poster a {
grid-area: 1 / 1;
}
-.node.card.poster .poster .cardhover.item {
+.card .poster .cardhover {
position: relative;
pointer-events: none;
grid-area: 1 / 1;
@@ -74,23 +74,23 @@
justify-content: center;
align-items: center;
}
-.node.card.poster .poster:hover .cardhover.item {
+.card .poster:hover .cardhover {
opacity: 1;
}
-.node.card.poster .poster a {
+.card .poster a {
overflow: hidden;
}
-.node.card.poster .poster a img {
+.card .poster a img {
transition: transform 0.3s;
}
-.node.card.poster .poster:hover a img {
+.card .poster:hover a img {
transform: scale(1.1);
}
-.node.card.poster .poster .cardhover.item a.play {
+.card .poster .cardhover a.play {
text-decoration: none;
width: 1em;
height: 1em;
@@ -103,11 +103,11 @@
background-color: var(--overlay);
transition: background-color 0.3s, font-size 0.3s;
}
-.node.card.poster .poster .cardhover.item a.play:hover {
+.card .poster .cardhover a.play:hover {
background-color: var(--overlay-hover);
font-size: 3em;
}
-.node.card.poster .poster .cardhover.item .props {
+.card .poster .cardhover .props {
position: absolute;
bottom: 0px;
left: 0px;
@@ -120,4 +120,4 @@
.children {
justify-content: center;
}
-} \ No newline at end of file
+}