Skip to content

fix(playback): restore V3 subtitle timing and stability - #598

Merged
Quick104 merged 6 commits into
mainfrom
t3code/fix-v3-subtitle-sync
Aug 11, 2026
Merged

fix(playback): restore V3 subtitle timing and stability#598
Quick104 merged 6 commits into
mainfrom
t3code/fix-v3-subtitle-sync

Conversation

@Quick104

@Quick104 Quick104 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Problem

Part of #135
Follow-up to #567

The neutral V3 migration stopped calling the existing copy-seek anchor resolver. FFmpeg copy remuxes begin on the preceding keyframe, but V3 advertised the requested seek as byte-zero. The web player also used stream_origin_seconds for source/player conversion instead of the contract's timeline_offset_seconds.

On the isolated real-media reproduction, a request for 1086.2 s emitted its first copied video packet from 1085.501 s. The old V3 response advertised both origin and player start as 1086.2/0, producing a measurable 699 ms subtitle error.

Follow-up runtime testing exposed the reported flashing. The merged web capability incorrectly advertised embedded_text: false, even though Silo exposes embedded text tracks to the web player through session-scoped VTT/ASS sidecars that it already renders. Selecting an embedded SRT therefore forced an unnecessary burn-in video transcode. Shared dev could not initialize its QSV device, so that replan was refused; rollback then cleared the automatic-selection guard and immediately selected the same rejected track again. One session issued 483 replans in 155 seconds, repeatedly toggling subtitle state and flashing the cues. Any refused automatically selected subtitle replan could trigger the feedback loop.

Approach

  • Resolve the actual copy keyframe before choosing progressive, local-HLS, or remote-HLS transport.
  • Skip video-keyframe probing for audio-only remuxes, anchor their zero-based chunked timeline at the requested source position, and select the same non-attached V:0 stream that FFmpeg maps for video remuxes.
  • Keep the requested position as FFmpeg's input seek, but advertise the resolved origin, player pre-roll offset, seek window, and timeline offset.
  • Carry the anchor through local transcode options, remote node requests, and reconstruction recipe cards.
  • Return a retryable transport failure when the anchor cannot be established instead of publishing a false timeline.
  • Make the video and audiobook players use timeline_offset_seconds for source/player conversions.
  • Advertise embedded text support for every web delivery class so the V3 planner keeps embedded text on the existing session-scoped sidecar/rendering path instead of forcing burn-in.
  • After a refused subtitle replan, pin the last server-accepted selection so automatic selection cannot request the same rejected track in a loop; explicit user selection can still retry it.
  • Scope the refusal pin to its playback session so persisted automatic subtitle preferences apply normally to the next title.
  • Update protocol documentation and add regression coverage for copy-remux timing, transport propagation, subtitle capability negotiation, player clock conversion, and refused subtitle replans.

Impact

Resumed and reanchored copy remuxes now keep video, text/ASS subtitles, progress, and seeks on the same source clock. Embedded text subtitles remain client-rendered and no longer depend on a video encoder. A failed subtitle selection cannot cause a replan storm or flashing cues. Bitmap subtitles still correctly require burn-in, and direct/encoded route timeline shapes are unchanged.

The shared-dev QSV startup failure remains an operational issue for routes that genuinely require video encoding; this change removes that dependency from embedded text playback rather than masking encoder failures.

Validation

  • Isolated dev-builder frontend/backend production build: passed.
  • Sandbox doctor: API, frontend, container, and database healthy.
  • Real authenticated V3 route on the reproducing file:
    • requested/source start: 1086.2 s
    • resolved stream origin and timeline offset: 1085.501 s
    • player start: 0.699 s
    • subtitle artifact timing origin: 1085.501 s
    • delivery: server_remux_progressive
  • Shared-dev flashing diagnosis:
    • 483 completed replans in 155 seconds for one session
    • the selected English track was embedded SubRip text
    • each forced-burn-in replan failed with transcode_start_failed after QSV/VAAPI initialization failed
    • rollback and automatic selection then repeated the same request
  • Focused subtitle, capability, and audiobook timeline regression tests: 23 passed.
  • Focused copy-anchor tests plus full internal/api/handlers and internal/playback package tests: passed.
  • Current make test-web: passed 273 web test files / 1,876 tests.
  • Current pnpm run build: passed.
  • Original make test: passed all Go packages and 273 web test files / 1,873 tests.
  • go test ./internal/api/handlers ./internal/playback -count=1: passed.
  • go vet ./...: passed.
  • Pinned CI-equivalent changed-line golangci-lint v2.12.2: 0 issues.
  • Current pnpm run lint: 0 errors (151 existing warnings).
  • Current pnpm run format:check: passed.
  • make verify-local-paths and git diff --check: passed.

Coverage gap

The collaborative browser reached the shared-dev sign-in screen but had no authenticated session, so the post-fix playback path was not visually replayed there and this branch was not deployed to the shared environment. The timing path was exercised through an authenticated isolated sandbox against real scanned media; the flashing cause was independently confirmed from shared-dev session, route-event, replan, and FFmpeg evidence; and both regressions are covered by focused tests plus the full web suite.

AI Disclosure

  • Tool(s): T3 Code; OpenAI Codex
  • Model(s): gpt-5.6-sol
  • Involvement: fully AI-generated
  • Adversarial review: The final review corrected stale copy-remux contract prose, centralized a repeated failure reason, and rechecked local, remote, and reconstruction propagation. Follow-up review traced flashing through the full capability-to-planner-to-runtime path: the web client falsely denied embedded text support, forcing burn-in, while a refused replan fed back into automatic selection. The capability now matches the actual VTT/ASS renderer and the rollback guard independently prevents refusal loops without suppressing explicit user retries. The final automated review also caught signed audiobook timeline offsets being passed through a non-negative helper; the fix preserves signed offsets, clamps only final absolute values, and adds a negative-offset regression. Later review found and fixed audio-only remux probing and timestamp anchoring, attached-cover stream selection, and the refusal pin carrying into the next session; each now has regression coverage.

Checklist

  • I ran an adversarial AI review of the diff and summarized findings above.
  • I ran the relevant repository verify commands and documented the browser coverage gap.

Summary by CodeRabbit

  • Improvements
    • Improved playback start points and seeking for remuxed HLS and progressive video with more accurate timeline positioning.
    • Preserved requested player positions when playback begins from an earlier keyframe.
    • Improved buffering, watch-progress tracking, keyboard seeking, and subtitle timing across offset playback timelines.
    • Web playback now supports embedded text subtitles alongside sidecar subtitles.
    • Subtitle selections are less likely to be repeatedly retried after an unsuccessful playback replan.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Playback V3 now resolves remux seeks to preceding keyframes before transport selection. Local, remote, audiobook, video, and subtitle playback use timeline_offset_seconds. Subtitle capability and recovery tests were expanded.

Changes

Playback timeline and subtitle handling

Layer / File(s) Summary
Timeline contract and anchor model
internal/api/handlers/playback.go, internal/api/handlers/playback_v3.go, internal/playback/copy_seek_anchor.go, internal/playback/copy_seek_anchor_test.go, docs/architecture/playback-protocol-v3.md
Defines copy-seek anchor resolution, excludes attached pictures from video selection, and documents resolved remux timeline fields.
Timeline preparation and transport propagation
internal/api/handlers/playback_v3.go
Prepares timeline metadata before transport selection and passes seek, origin, anchor, and segment values to local and remote transports.
Backend anchor and transport tests
internal/api/handlers/playback_v3_test.go
Tests resolved anchors, retryable probe failures, timeline fields, generated seek URLs, and remote executor requests.
Web timeline offset mapping
web/src/player/utils/mediaTimeline.ts, web/src/player/components/VideoPlayer.tsx, web/src/pages/audiobooks/player/useAudiobookPlayback.ts, web/src/pages/audiobooks/player/useAudiobookPlayback.test.ts
Uses timeline_offset_seconds for time conversion, seeking, progress, exit state, subtitle matching, and audiobook playback.
Subtitle capabilities and recovery behavior
web/src/player/client-context-v3.ts, web/src/player/client-context-v3.test.ts, web/src/player/components/VideoPlayer.test.tsx, web/src/player/components/VideoPlayer.tsx
Advertises embedded text subtitles, propagates subtitle offsets, validates native HLS offsets, and prevents automatic subtitle reselection after refused replans.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant PlaybackHandler
  participant FFprobe
  participant Transport
  Client->>PlaybackHandler: request playback at target position
  PlaybackHandler->>FFprobe: resolve preceding keyframe
  FFprobe-->>PlaybackHandler: return seek anchor and segment
  PlaybackHandler->>Transport: start with prepared timeline metadata
  Transport-->>Client: return playback plan
Loading

Possibly related PRs

Suggested labels: v1

Suggested reviewers: rhainland

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary changes to V3 subtitle timing and playback stability.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/fix-v3-subtitle-sync

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

@Quick104 Quick104 changed the title fix(playback): restore copy-remux subtitle timing fix(playback): restore V3 subtitle timing and stability Aug 11, 2026
@Quick104
Quick104 marked this pull request as ready for review August 11, 2026 18:18
@coderabbitai coderabbitai Bot added the v1 Silo v1 scope - auto-adds to the Silo v1 project label Aug 11, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 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 `@web/src/pages/audiobooks/player/useAudiobookPlayback.ts`:
- Line 329: Update useAudiobookPlayback and the audiobook time-conversion
helpers to rename streamOriginSecondsRef to timelineOffsetSecondsRef, preserve
finite signed timeline_offset_seconds values through audiobookAbsoluteTime and
absoluteBufferedRanges, and clamp only the resulting absolute position and
buffered range boundaries to zero. Add a regression test covering a negative
timeline offset.
🪄 Autofix

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 63df1b97-dcb8-41a5-ad5b-d594d858dab0

📥 Commits

Reviewing files that changed from the base of the PR and between 6dd3f1a and 55b1747.

📒 Files selected for processing (11)
  • docs/architecture/playback-protocol-v3.md
  • internal/api/handlers/playback.go
  • internal/api/handlers/playback_v3.go
  • internal/api/handlers/playback_v3_test.go
  • web/src/pages/audiobooks/player/useAudiobookPlayback.test.ts
  • web/src/pages/audiobooks/player/useAudiobookPlayback.ts
  • web/src/player/client-context-v3.test.ts
  • web/src/player/client-context-v3.ts
  • web/src/player/components/VideoPlayer.test.tsx
  • web/src/player/components/VideoPlayer.tsx
  • web/src/player/utils/mediaTimeline.ts

Comment thread web/src/pages/audiobooks/player/useAudiobookPlayback.ts Outdated

@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: 55b17470e4

ℹ️ 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 internal/api/handlers/playback_v3.go
Comment thread internal/api/handlers/playback_v3.go

@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: 271ee33022

ℹ️ 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 web/src/player/components/VideoPlayer.tsx

@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: 8f9486f1ed

ℹ️ 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 internal/api/handlers/playback_v3.go Outdated
@Quick104
Quick104 merged commit 461b51c into main Aug 11, 2026
6 checks passed
@Quick104
Quick104 deleted the t3code/fix-v3-subtitle-sync branch August 11, 2026 19:26
@github-project-automation github-project-automation Bot moved this to Done in Silo v1 Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v1 Silo v1 scope - auto-adds to the Silo v1 project

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant