Skip to content

Fix SEEK events dropped in idle/loading track states - #28

Closed
switz wants to merge 4 commits into
masterfrom
fix/seek-in-idle-loading
Closed

switz wants to merge 4 commits into
masterfrom
fix/seek-in-idle-loading

Conversation

@switz

@switz switz commented Sep 19, 2026

Copy link
Copy Markdown
Member

Summary

  • Root cause: Track machine's idle and loading states had no SEEK handler, so when seekCurrentToStartTime fires during gotoTrack, the SEEK event was silently dropped — pausedAtTrackTime was set correctly but audio.currentTime stayed at 0.
  • Fix 1: Add SEEK → seekHtml5 handler to idle and loading states so the HTML5 element actually seeks during the GOTO phase before PLAY fires.
  • Fix 2: Defense-in-depth in _playHtml5() — if audio.currentTime diverges from pausedAtTrackTime by >0.5s, seek the element before calling play().

Test plan

  • gotoTrack(n, { startTime: 142.6 }) starts playback at 142.6s, not 0
  • Seek while paused on an idle track, then play — resumes from seeked position
  • Normal play/pause/seek cycle unaffected
  • Gapless transitions still work correctly

🤖 Generated with Claude Code

The track machine's idle and loading states had no SEEK handler, so when
seekCurrentToStartTime fired during gotoTrack, the SEEK event was silently
dropped — pausedAtTrackTime was set correctly but audio.currentTime stayed
at 0, causing playback to start from the beginning instead of the target.

- Add SEEK → seekHtml5 handler to idle and loading states so the HTML5
  element actually seeks during the GOTO phase before PLAY fires.
- Defense-in-depth: _playHtml5() now checks if audio.currentTime diverges
  from pausedAtTrackTime by >0.5s and seeks before calling play(), handling
  both metadata-ready and not-yet-loaded cases.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

Deploying gapless with  Cloudflare Pages  Cloudflare Pages

Latest commit: 49be3df
Status: ✅  Deploy successful!
Preview URL: https://d4244077.gapless-e1l.pages.dev
Branch Preview URL: https://fix-seek-in-idle-loading.gapless-e1l.pages.dev

View logs

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-19T19:20:50.009411Z 49be3df New commits
🔒 Security Review ✅ Completed 2026-09-19T19:02:09.349171Z 335bc39 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 335bc39b5b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Track.ts Outdated
Comment on lines +580 to +582
if (isFinite(this.pausedAtTrackTime) && Math.abs(this.audio.currentTime - this.pausedAtTrackTime) > 0.5) {
if (this.audio.readyState >= HTMLMediaElement.HAVE_METADATA) {
this.audio.currentTime = this.pausedAtTrackTime;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid restoring a stale anchor when resuming HTML5 playback

When HTML5 playback is paused normally, pausedAtTrackTime is not updated—the HTML5 PAUSE transition only pauses the element—so it commonly remains 0 or the position of an earlier seek. Resuming after the element has advanced by more than 0.5 seconds now assigns that stale value to audio.currentTime, causing HTML5-only playback, or hybrid playback while decoding is still pending, to jump backward instead of resuming from the paused position.

Useful? React with 👍 / 👎.

switz and others added 2 commits September 19, 2026 15:02
Matches the html5 and webaudio states so UI consumers get notified of
the new seek position immediately.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pausedAtTrackTime is not updated on HTML5 pause, so the previous
Math.abs check would seek backward on normal resume (e.g. pausedAt=0
after 30s of playback). Changed to only trigger when pausedAtTrackTime
is ahead of audio.currentTime — the actual GOTO scenario where the
SEEK event was dropped.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@switz

switz commented Sep 19, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6dc74d3303

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/machines/track.machine.ts Outdated
idle: {
on: {
SEEK: {
actions: ['seekHtml5', 'reportProgress'],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep idle seeks off the HTML5 path in WebAudio-only mode

When playbackMethod is WEBAUDIO_ONLY and a seek arrives before decoding completes—for example, gotoTrack(..., startTime) while the track is idle—this new handler invokes _seekHtml5(). Without loaded metadata, that method sets preload = 'auto' and calls audio.load(), initiating an unnecessary HTML5 media request and potentially reporting an HTML5 onerror even if the Web Audio fetch succeeds, despite this mode promising Web Audio exclusively. Gate this action by playback method or use an anchor-only seek for WebAudio-only tracks.

Useful? React with 👍 / 👎.

In WEBAUDIO_ONLY mode, calling seekHtml5 triggers audio.load() which
initiates an unnecessary HTML5 request and can fire spurious onerror.
pausedAtTrackTime is already set by seek(), and startSourceNode reads
it on PLAY, so the HTML5 element doesn't need to be touched.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 49be3df008

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +185 to +186
{
actions: ['seekHtml5', 'reportProgress'],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve the requested start time across activation

When gotoTrack(index, true, startTime) selects a destination with decoded audio or loaded HTML metadata, the queue runs seekCurrentToStartTime before activateAndPlayCurrent; track.activate() then sends ACTIVATE, whose idle/loading actions reset both pausedAtTrackTime and audio.currentTime. This erases the seek performed here before play() reads it, so preloaded Web Audio tracks and metadata-ready HTML tracks start at zero instead of startTime. Activate before seeking, or preserve the requested anchor during this activation.

Useful? React with 👍 / 👎.

Comment on lines +357 to +358
{
actions: ['seekHtml5', 'reportProgress'],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Invalidate deferred seeks when the track is reset

If this loading-state seek occurs before metadata is available, _seekHtml5() installs a one-shot loadedmetadata listener capturing the requested offset. Navigating away before loading completes resets the track's timing but does not remove that listener, so it can later restore the abandoned offset into audio.currentTime; because _playHtml5() only corrects when the anchor is ahead of the element, a subsequent HTML5 play can then begin at this stale position. Cancel or generation-check deferred seeks when deactivating/resetting the track.

Useful? React with 👍 / 👎.

Comment on lines +182 to +183
guard: 'isWebAudioOnly',
actions: ['reportProgress'],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Report the Web Audio seek anchor instead of HTML time

In WEBAUDIO_ONLY mode while the track is idle, this branch intentionally skips seekHtml5, but reportProgress calls toInfo(), whose currentTime getter returns audio.currentTime outside the webaudio state rather than the newly assigned pausedAtTrackTime. A seek before decoding therefore emits the old position—typically zero—to progress consumers and media-session state even though playback will eventually use the requested anchor. Report the requested anchor directly or make idle/loading Web Audio-only tracks expose it through currentTime.

Useful? React with 👍 / 👎.

@switz

switz commented Sep 19, 2026

Copy link
Copy Markdown
Member Author

Closing in favor of a broader refactor: moving pausedAtTrackTime into machine context as seekTarget to eliminate the dual-authority problem.

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.

1 participant