Skip to content

#508 Allow seeking audio to next/previous heading#989

Open
benpaj-cps wants to merge 12 commits into
sillsdev:mainfrom
benpaj-cps:fix/audio-seek-headings/GH-508
Open

#508 Allow seeking audio to next/previous heading#989
benpaj-cps wants to merge 12 commits into
sillsdev:mainfrom
benpaj-cps:fix/audio-seek-headings/GH-508

Conversation

@benpaj-cps

@benpaj-cps benpaj-cps commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Fixes #508.

I tested this on Mark 1 in the CUK Bible with a generated timing file from aeneas. The heading search works even with several nested HTML elements between the heading and the next verse.

image

Summary by CodeRabbit

  • Improvements
    • Smarter skip/seek behavior near chapter and verse boundaries for more accurate navigation.
    • Seeking now respects progress and timing availability, delegating when appropriate for smoother jumps.
    • Lazy detection and caching of heading/chapter markers to speed repeated navigation.
    • Playback now records first-play state so highlights and progress updates occur at the correct times.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds heading-marker-based skip navigation: AudioPlayer gets hasPlayed and optional headingMarkers; skip(direction) now pauses, preserves play state, lazily computes/caches DOM-derived heading markers, uses a 2s AUDIO_SEEK_THRESHOLD to decide local seeks vs refs.skip, and resumes playback. updateTime/play use hasPlayed.

Changes

Heading-marker audio navigation

Layer / File(s) Summary
AudioPlayer contract updates
src/lib/data/stores/audio.ts
AudioPlayer adds hasPlayed: boolean and optional headingMarkers?: number[]; audioPlayerDefault initializes hasPlayed: false.
Skip() and heading marker discovery
src/lib/data/audio.ts
Adds AUDIO_SEEK_THRESHOLD and reimplements skip(direction) to pause, preserve playing state, compute/cache headingMarkers via getHeadingMarkers() and nextElementDFS(), decide between local seek() and refs.skip(direction) using marker positions and threshold, then resume and call updateTime().
Playback / time update gating
src/lib/data/audio.ts
updateTime() now gates store sync/highlight updates on currentAudioPlayer.hasPlayed; play() sets hasPlayed = true on first transition to playing.

Sequence Diagram

sequenceDiagram
  participant User
  participant Skip as skip(direction)
  participant Markers as getHeadingMarkers()
  participant DOM as DOM(div.s / data-verse)
  participant Timing as currentAudioPlayer.timing
  participant Audio as currentAudioPlayer.seek()
  User->>Skip: click prev/next
  Skip->>Skip: pause, record wasPlaying
  Skip->>Timing: check timing & loaded
  alt timing missing or audio not ready
    Skip->>Skip: decide threshold -> local seek or refs.skip
  else timing available
    Skip->>Markers: compute or read cached headingMarkers
    Markers->>DOM: scan div.s and DFS to next data-verse
    DOM->>Timing: map verse tags to timing start times
    Markers->>Skip: return markers[]
    Skip->>Skip: compare progress vs AUDIO_SEEK_THRESHOLD and markers
    alt seek within chapter to marker
      Skip->>Audio: seek(targetMarker)
    else delegate to chapter skip
      Skip->>Skip: call refs.skip(direction)
    end
  end
  Skip->>Skip: updateTime()
  Skip->>User: resume playback if wasPlaying
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • FyreByrd

Poem

🐰 I nudged the player, soft and spry,
Through headings hidden, low and high,
DOM-carrots show each verse's start,
I hop and seek with ear and heart—
Playback resumes; the verses sigh.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The code changes implement the core requirement from #508: seeking to next/previous headings instead of chapters, handling edge cases (no headings, chapter boundaries), and resuming playback state.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing heading-based seeking functionality. No unrelated modifications to other features or systems were detected.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the main change: enabling audio seek functionality to move playback to next/previous section headings, which matches the PR's core objective to fix issue #508.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@benpaj-cps

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/data/audio.ts`:
- Line 221: Remove the leftover debug console.log calls in src/lib/data/audio.ts
— specifically the lines that call console.log with template strings like `found
heading ${h.getHTML()}` and the other console.log statements around the
heading/paragraph processing (the debug prints at lines referenced in the
review). Locate the block that iterates headings/paragraphs (references to
h.getHTML(), p.getText(), etc.) and delete those console.log(...) calls or
replace them with the project's sanctioned logger if persistent logging is
required.
- Line 154: Remove the debug console.log calls in src/lib/data/audio.ts (e.g.,
the "console.log('skip')" and the other console.log occurrences) before merge;
delete these console.log lines or replace them with the project's structured
logger (e.g., processLogger.debug or logger.debug) if the information is needed,
ensuring you reference and update the surrounding functions in the file where
they appear so behavior and return flow remains correct (remove the
console.log(...) statements inside the relevant functions and use the existing
logging utility or nothing).
- Line 217: The variable headingMarkers is declared with let but never
reassigned; change its declaration to const so it remains immutable while still
allowing mutation via push()—update the headingMarkers declaration (where it is
initialized to [0.0]) to use const instead of let in src/lib/data/audio.ts to
reflect that it is only mutated, not reassigned.
- Around line 190-206: The loop that decides where to seek can perform an
out-of-bounds access when calling seek(currentAudioPlayer.headingMarkers[i +
1]); update the logic in the loop that iterates
currentAudioPlayer.headingMarkers to guard any i+1 access: before calling
seek(currentAudioPlayer.headingMarkers[i + 1]) verify i + 1 <
currentAudioPlayer.headingMarkers.length (and handle the case by seeking to the
last marker or current marker instead), and ensure play() is still resumed when
wasPlaying is true; adjust the conditions around direction,
currentAudioPlayer.progress, and AUDIO_SEEK_THRESHOLD accordingly to use the
safe alternative when i+1 would be out of range.
- Around line 241-243: The push to headingMarkers uses an unsafe non-null
assertion on currentAudioPlayer.duration; replace this with a safe fallback and
validation: compute a candidate value from
currentAudioPlayer?.timing?.at(-1)?.endtime ?? currentAudioPlayer?.duration ??
null, ensure it's a finite number (or >= 0) before pushing, and skip or push a
defined default (e.g. 0) if the candidate is null/invalid; update the code
around headingMarkers.push(...) and reference currentAudioPlayer, timing, and
duration when implementing this guard.
- Around line 179-188: The code uses an unsafe non-null assertion on
headingMarkers?.at(-2)! which can be undefined and breaks the pipeline; compute
a safe value first (e.g., const lastButOne = headingMarkers &&
headingMarkers.length >= 2 ? headingMarkers.at(-2) : undefined) and then change
the second part of the if to check lastButOne !== undefined before comparing
progress (e.g., direction >= 0 && lastButOne !== undefined &&
currentAudioPlayer.progress >= lastButOne), removing the non-null assertion and
guarding against headingMarkers being undefined or too short; keep the existing
refs.skip(...) call and adjust the log to reference lastButOne if needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7f9da34d-40fa-4eda-962f-aa70bc5dd8ee

📥 Commits

Reviewing files that changed from the base of the PR and between c4812d5 and 185a5b3.

📒 Files selected for processing (2)
  • src/lib/data/audio.ts
  • src/lib/data/stores/audio.ts

Comment thread src/lib/data/audio.ts Outdated
Comment thread src/lib/data/audio.ts
Comment thread src/lib/data/audio.ts
Comment thread src/lib/data/audio.ts Outdated
Comment thread src/lib/data/audio.ts Outdated
Comment thread src/lib/data/audio.ts Outdated
@benpaj-cps benpaj-cps force-pushed the fix/audio-seek-headings/GH-508 branch 2 times, most recently from d4c1975 to 3c1b5a3 Compare June 10, 2026 22:30
@benpaj-cps benpaj-cps force-pushed the fix/audio-seek-headings/GH-508 branch from 9223462 to 5bcee7c Compare June 12, 2026 15:19
@benpaj-cps benpaj-cps marked this pull request as ready for review June 12, 2026 17:29

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/data/audio.ts`:
- Around line 194-213: When replaying from a local heading seek the store can
remain stale because pause() writes a paused state, seek(...) writes that paused
object to audioPlayerStore, and play() changes playback without updating the
store; modify the local heading-seek path in the loop (the block that calls
seek(...) then play()) to re-sync the store after play() returns by writing the
up-to-date currentAudioPlayer back into audioPlayerStore (or call the existing
function that updates the store for the active player), ensuring
$audioPlayer.playing reflects the resumed state; reference the functions
pause(), seek(), play(), the currentAudioPlayer object, and audioPlayerStore
when making this change.
- Around line 177-186: The code treats headingMarkers.at(-2) === 0 (only
sentinels) as falsy and incorrectly falls through for forward skips; change the
logic in the block that computes and checks finalIntermediateMarker so it only
considers a real intermediate heading when headingMarkers.length > 2 (or
finalIntermediateMarker > 0). Concretely, in the computation of
finalIntermediateMarker and the subsequent if that decides whether to seek to a
marker vs. call skip(1), ensure finalIntermediateMarker is undefined/null when
headingMarkers contains only the two sentinels (e.g., set
finalIntermediateMarker only if headingMarkers.length > 2 or explicitly check
finalIntermediateMarker > 0), so next() will call skip(1) to advance to the next
chapter when there are no further headings in the current chapter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aa36fd9b-f2b1-40ed-a990-f39d0bf09e95

📥 Commits

Reviewing files that changed from the base of the PR and between 185a5b3 and fff279c.

📒 Files selected for processing (2)
  • src/lib/data/audio.ts
  • src/lib/data/stores/audio.ts

Comment thread src/lib/data/audio.ts Outdated
Comment thread src/lib/data/audio.ts
@benpaj-cps benpaj-cps force-pushed the fix/audio-seek-headings/GH-508 branch 3 times, most recently from 710d8a5 to b5949c3 Compare June 12, 2026 18:09
@benpaj-cps benpaj-cps force-pushed the fix/audio-seek-headings/GH-508 branch from b5949c3 to 3d2e0b8 Compare June 12, 2026 18:17
@benpaj-cps

Copy link
Copy Markdown
Contributor Author

@chrisvire This is ready for review.

@benpaj-cps benpaj-cps requested a review from chrisvire June 12, 2026 19:53
@benpaj-cps benpaj-cps changed the title Allow seeking audio to next/previous heading #508 Allow seeking audio to next/previous heading Jun 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Advance button in chapter with timing should go to next section heading

1 participant