diff options
author | metamuffin <metamuffin@disroot.org> | 2024-01-16 22:58:23 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-01-16 22:58:23 +0100 |
commit | 1c37d32a0985ff7390313833345b9299f9f0b196 (patch) | |
tree | cd46bcbe7df154bf506c083cf0325d44b3f4e0fb | |
parent | 98b379afb31e455b529d443dcfc5797b8dd6723e (diff) | |
download | jellything-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.rs | 1 | ||||
-rw-r--r-- | server/src/routes/ui/home.rs | 10 | ||||
-rw-r--r-- | server/src/routes/ui/node.rs | 47 | ||||
-rw-r--r-- | server/src/routes/ui/player.rs | 10 | ||||
-rw-r--r-- | web/script/player/player.ts | 14 | ||||
-rw-r--r-- | web/style/layout.css | 8 | ||||
-rw-r--r-- | web/style/nodecard.css | 44 |
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 +} |