Skip to content

fix: stop fudging playback rate; let video drive the timeline#595

Open
sshane wants to merge 1 commit into
masterfrom
ios-video-sync
Open

fix: stop fudging playback rate; let video drive the timeline#595
sshane wants to merge 1 commit into
masterfrom
ios-video-sync

Conversation

@sshane
Copy link
Copy Markdown
Contributor

@sshane sshane commented Apr 30, 2026

Summary

`syncVideo` was keeping the `

Layers of workarounds had grown around it:

  • iOS hid the playback speed UI entirely.
  • iOS skipped the rate-fudge (so iOS audio was OK, but iOS video drifted from the timeline and `readyState` got pumped via a force-pause hack to "unstick" the buffer).
  • Firefox capped at 8x because it mutes audio above 8x.
  • HLS.js had custom pause/play orchestration to work around the rate writes interfering with playback.

This PR inverts the model. The video plays at its declared rate; the timeline follows it. `syncVideo` only acts on real divergence:

  • `|diff| > 0.5s` → push to video (user seek, loop wrap, initial sync).
  • `|diff| > 0.05s` while playing → pull the timeline to the video via a Redux `seek` (no media-element touch).

Buffering state moves to the browser's own `waiting`/`playing` events via ReactPlayer's `onBuffer`/`onBufferEnd`, replacing the `readyState` polling and the iOS-specific force-pause "fix". Drops the 16x cap and the Firefox 8x branch — playback rate is now whatever the user selected, not a moving target.

Restores the speed UI on iOS. Drops the now-unused `isFirefox` helper. `isIos` is kept for the legitimate platform difference in `onPlayerReady` (iOS Safari plays HLS natively, so audio-track detection has to read `videoElement.audioTracks` instead of hls.js's `hlsBufferCodecs` event).

Test plan

  • iOS Safari: video plays smoothly with audio, no crackle on playback rate changes
  • CarPlay: audio doesn't stutter every second
  • macOS with audio routed via AirPlay/Bluetooth: smooth audio
  • Speed buttons (0.1x→8x) appear on iOS and work
  • Spinner doesn't pump on/off during normal playback
  • Desktop Chrome/Firefox unchanged
  • `pnpm lint` clean
  • `pnpm test` passes (30/30)

🤖 Generated with Claude Code

The video player was kept in sync with a virtual timeline clock by
nudging videoElement.playbackRate by ±0.1 every 200-500ms whenever
they drifted. Each playbackRate write hits the audio decoder, so on
audio-sensitive paths (iOS Safari, CarPlay, AirPlay/Bluetooth, macOS
audio routes) the audio crackled, stuttered, and dropped.

Several layers of workarounds had grown around it:

- iOS hid the playback speed UI entirely.
- iOS skipped the rate-fudge step (so iOS audio was OK, but iOS video
  drifted from the timeline and `readyState` got pumped via a
  force-pause hack to "unstick" the buffer).
- Firefox capped at 8x because it mutes audio above 8x.
- HLS.js had a custom pause/play orchestration to work around the
  rate writes interfering with playback.

This change inverts the model: the video plays at its declared rate
and the timeline follows it, instead of the timeline driving the
video. syncVideo now only acts on real divergence:

- |diff| > 0.5s → push to video (user seek, loop wrap, initial sync).
- |diff| > 0.05s while playing → pull the timeline to the video by
  dispatching a seek action (Redux only, no media-element touch).

Buffering state switches to the browser's own waiting/playing events
via ReactPlayer's onBuffer/onBufferEnd, replacing the readyState
polling and the iOS-specific force-pause "fix". Drops the 16x cap and
the Firefox 8x branch — playback rate is now whatever the user
actually selected, not a moving target.

Restores the speed UI on iOS, drops the now-unused isFirefox helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

Welcome to connect! Make sure to:

  • read the contributing guidelines
  • mark your PR as a draft until it's ready to review
  • post the preview on Discord; feedback from users will speedup the PR review

deployed preview: https://595.connect-d5y.pages.dev

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