Timed comments on previews too

pl8list gives where a preview cuts in (previewStart, a fraction of the full
track). The server keeps it and hands previews their start in seconds; the
card moves each comment by it and keeps the ones inside the clip. Tracks
crawled before this show none until their artist comes round again.
This commit is contained in:
nvrl committed 2026-11-03 11:38:50 +01:00
1 parent 028700881d
commit 92d531dc10
5 files changed
+27 -6

No files matched your search

+14 -2
View File
@@ -133,6 +133,11 @@ CREATE TABLE IF NOT EXISTS meta (
); );
"#; "#;
// preview_start: where a preview cuts in, as a fraction of the full track.
const SCHEMA_V5: &str = r#"
ALTER TABLE tracks ADD COLUMN preview_start REAL;
"#;
pub fn now() -> f64 { pub fn now() -> f64 {
SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs_f64() SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs_f64()
} }
@@ -229,6 +234,9 @@ fn migrate(conn: &Connection) -> rusqlite::Result<()> {
if version < 4 { if version < 4 {
conn.execute_batch(&format!("BEGIN; {SCHEMA_V4} PRAGMA user_version = 4; COMMIT;"))?; conn.execute_batch(&format!("BEGIN; {SCHEMA_V4} PRAGMA user_version = 4; COMMIT;"))?;
} }
if version < 5 {
conn.execute_batch(&format!("BEGIN; {SCHEMA_V5} PRAGMA user_version = 5; COMMIT;"))?;
}
Ok(()) Ok(())
} }
@@ -379,6 +387,8 @@ pub struct Track {
pub key: Option<String>, pub key: Option<String>,
pub duration: Option<f64>, pub duration: Option<f64>,
pub preview_duration: Option<f64>, pub preview_duration: Option<f64>,
/// Where the preview cuts in, 0..1 of the full track.
pub preview_start: Option<f64>,
pub price: f64, pub price: f64,
pub created_at: Option<String>, pub created_at: Option<String>,
pub upload_id: Option<String>, pub upload_id: Option<String>,
@@ -393,9 +403,9 @@ pub struct Track {
pub related_of: Option<i64>, pub related_of: Option<i64>,
} }
const TRACK_COLS: [&str; 23] = [ const TRACK_COLS: [&str; 24] = [
"id", "handle", "artist_name", "artist_img", "artist_verified", "slug", "title", "description", "id", "handle", "artist_name", "artist_img", "artist_verified", "slug", "title", "description",
"bpm", "key", "duration", "preview_duration", "price", "created_at", "upload_id", "bpm", "key", "duration", "preview_duration", "preview_start", "price", "created_at", "upload_id",
"preview_upload_id", "waveform", "preview_waveform", "full_access", "saves", "downloads", "preview_upload_id", "waveform", "preview_waveform", "full_access", "saves", "downloads",
"purchases", "comments", "purchases", "comments",
]; ];
@@ -415,6 +425,7 @@ impl Track {
key: r.get("key")?, key: r.get("key")?,
duration: r.get("duration")?, duration: r.get("duration")?,
preview_duration: r.get("preview_duration")?, preview_duration: r.get("preview_duration")?,
preview_start: r.get("preview_start")?,
price: r.get::<_, Option<f64>>("price")?.unwrap_or(0.0), price: r.get::<_, Option<f64>>("price")?.unwrap_or(0.0),
created_at: r.get("created_at")?, created_at: r.get("created_at")?,
upload_id: r.get("upload_id")?, upload_id: r.get("upload_id")?,
@@ -449,6 +460,7 @@ impl Track {
opt(&self.key), opt(&self.key),
optf(self.duration), optf(self.duration),
optf(self.preview_duration), optf(self.preview_duration),
optf(self.preview_start),
price, price,
opt(&self.created_at), opt(&self.created_at),
opt(&self.upload_id), opt(&self.upload_id),
+1
View File
@@ -82,6 +82,7 @@ fn track(t: &Value, extra: Extra) -> Option<Track> {
key: text(t, "key"), key: text(t, "key"),
duration: num(t, "duration"), duration: num(t, "duration"),
preview_duration: num(t, "previewDuration"), preview_duration: num(t, "previewDuration"),
preview_start: num(t, "previewStart"),
price: num(t, "price").unwrap_or(0.0), price: num(t, "price").unwrap_or(0.0),
created_at: text(t, "createdAt"), created_at: text(t, "createdAt"),
upload_id: text(t, "uploadId"), upload_id: text(t, "uploadId"),
+2
View File
@@ -479,6 +479,8 @@ pub fn serialize(r: &Track, score: Option<f64>) -> Value {
"purchases": r.purchases, "purchases": r.purchases,
"comments": r.comments, "comments": r.comments,
"preview": preview, "preview": preview,
// Seconds into the full track where the preview cuts in.
"preview_start": if preview { r.preview_start.zip(r.duration).map(|(f, d)| f * d) } else { None },
"upload_id": if preview { &r.preview_upload_id } else { &r.upload_id }, "upload_id": if preview { &r.preview_upload_id } else { &r.upload_id },
"waveform": wave.as_deref().and_then(|w| serde_json::from_str::<Value>(w).ok()), "waveform": wave.as_deref().and_then(|w| serde_json::from_str::<Value>(w).ok()),
"score": score.map(|s| (s * 1000.0).round() / 1000.0), "score": score.map(|s| (s * 1000.0).round() / 1000.0),
+2
View File
@@ -19,6 +19,8 @@ export interface Track {
purchases: number; purchases: number;
comments: number; comments: number;
preview: boolean; preview: boolean;
/// For a preview: seconds into the full track where it cuts in.
preview_start?: number | null;
upload_id: string | null; upload_id: string | null;
waveform: number[] | null; waveform: number[] | null;
followed?: boolean; followed?: boolean;
+8 -4
View File
@@ -93,12 +93,16 @@ export class Card {
private comments: WaveComments | null = null; private comments: WaveComments | null = null;
private commentsAsked = false; private commentsAsked = false;
/// Fetch the timed comments, once. Previews have none: their moments are those /// Fetch the timed comments, once. Their moments are those of the full track, so
/// of the full track. /// a preview shows only the ones inside its clip, moved to where the clip starts.
loadComments(): void { loadComments(): void {
if (this.commentsAsked || this.track.preview || !this.track.comments) return; const t = this.track;
const start = t.preview ? t.preview_start : 0;
if (this.commentsAsked || start == null || !t.comments) return;
this.commentsAsked = true; this.commentsAsked = true;
comments(this.track.id).then(({ comments: list }) => { comments(t.id).then(({ comments: all }) => {
const end = t.preview ? (t.duration ?? 0) : Infinity;
const list = start ? all.map((c) => ({ ...c, at: c.at - start })).filter((c) => c.at >= 0 && c.at <= end) : all;
if (!list.length) return; if (!list.length) return;
this.comments = new WaveComments(list, () => this.actions.duration(this)); this.comments = new WaveComments(list, () => this.actions.duration(this));
const wrap = this.canvas?.parentElement; const wrap = this.canvas?.parentElement;