diff --git a/templates/audio_reader.templ b/templates/audio_reader.templ index a90ddd10..ad68e26c 100644 --- a/templates/audio_reader.templ +++ b/templates/audio_reader.templ @@ -255,6 +255,25 @@ templ AudioReader(user User, metadata ReaderMetadata) { } + +
+ Resume rewind + +
diff --git a/templates/audio_reader_templ.go b/templates/audio_reader_templ.go index 23438900..a24c79f5 100644 --- a/templates/audio_reader_templ.go +++ b/templates/audio_reader_templ.go @@ -394,67 +394,116 @@ func AudioReader(user User, metadata ReaderMetadata) templ.Component { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
Resume rewind
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if metadata.CoverImagePath != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "\"")") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "🎧") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "🎧") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var22 string - templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(metadata.Title) + var templ_7745c5c3_Var25 string + templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(metadata.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `audio_reader.templ`, Line: 278, Col: 117} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `audio_reader.templ`, Line: 297, Col: 117} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "

—

0:00
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "

—

0:00
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if metadata.PodcastSequence != nil { if metadata.PodcastSequence.PrevURL != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -475,13 +524,13 @@ func AudioReader(user User, metadata ReaderMetadata) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if metadata.PodcastSequence != nil { if metadata.PodcastSequence.NextURL != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -547,13 +596,13 @@ func AudioReader(user User, metadata ReaderMetadata) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "
Chapters

No chapters detected for this audiobook.

Bookmarks

No bookmarks yet — tap the button to save your position.

Bookmark at 0:00

Shortcuts & interactions
Mouse
Touch
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "
Chapters

No chapters detected for this audiobook.

Bookmarks

No bookmarks yet — tap the button to save your position.

Bookmark at 0:00

Shortcuts & interactions
Mouse
Touch
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/templates/types.go b/templates/types.go index 93a1fc0a..96c71c72 100644 --- a/templates/types.go +++ b/templates/types.go @@ -1,6 +1,7 @@ package templates import ( + "fmt" "time" "github.com/a-h/templ" @@ -35,6 +36,18 @@ type User struct { // server-side mirror of the web player's SPEEDS list (plan §5/§8). var SpeedOptions = []float64{0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3} +// ResumeRewindOptions mirrors the server's strict allow-list for +// audio_resume_rewind_seconds (0 = exact resume). Shared by the +// profile form and the audio player's settings popover. +var ResumeRewindOptions = []int{0, 5, 10} + +func ResumeRewindLabel(seconds int) string { + if seconds == 0 { + return "Off" + } + return fmt.Sprintf("%d s before", seconds) +} + type PageData struct { User User CurrentPage string diff --git a/web/src/audio.ts b/web/src/audio.ts index afe90811..c43bbe36 100644 --- a/web/src/audio.ts +++ b/web/src/audio.ts @@ -789,6 +789,27 @@ Alpine.data("audioReader", () => ({ applyDefaultSpeedFromSelect(e: Event) { this.setDefaultSpeed(Number((e.target as HTMLSelectElement).value)); }, + // Player-side control of the account setting: applies LIVE (the + // next open/unpause uses it — no reload) and rides the profile + // endpoint like the profile-page select. String wire shape — the + // server binds audio_resume_rewind_seconds keep_screen_awake-style. + applyResumeRewindFromSelect(e: Event) { + const seconds = normalizeResumeRewind((e.target as HTMLSelectElement).value); + this.resumeRewind = seconds; + fetch("/api/auth/profile", { + method: "PUT", + headers: { ...getAuthHeaders(), "content-type": "application/json" }, + body: JSON.stringify({ audio_resume_rewind_seconds: String(seconds) }), + }) + .then((r) => { + if (!r.ok) throw new Error(String(r.status)); + showToast( + seconds === 0 ? "Resume rewind off" : `Resume rewind: ${seconds}s before saved position`, + "success", + ); + }) + .catch(() => showToast("Resume rewind not saved", "error")); + }, isDefaultSpeed(rate: number): boolean { return this.defaultSpeed === rate; },