Skip to content

E3 — Playback & Watch Experience#112

Open
aloewright wants to merge 3 commits into
mainfrom
conductor/alo-122-e3-playback-watch-experience
Open

E3 — Playback & Watch Experience#112
aloewright wants to merge 3 commits into
mainfrom
conductor/alo-122-e3-playback-watch-experience

Conversation

@aloewright
Copy link
Copy Markdown
Owner

@aloewright aloewright commented May 8, 2026

Closes ALO-122.

Summary

E3 — Playback & Watch Experience. Most of the surface (HLS player via hls.js, view counts + heartbeat watch-time analytics, signed-in watch history, resume from last position, ?t= deep-linked timestamp, share-at-current-time, keyboard shortcuts, auto-advance) shipped in earlier work. This PR closes the remaining gaps:

  • WebVTT captions: a new video_captions table, owner CRUD endpoints (GET /api/videos/:id/captions, PUT /api/videos/:id/captions/:lang, DELETE /api/videos/:id/captions/:lang, public GET /api/videos/:id/captions/:lang.vtt), R2-backed VTT bodies with a leading-WEBVTT header validator and a 2 MiB cap. The Watch page fetches the track list and renders <track kind="subtitles"> children — same-origin so no crossOrigin flip is needed on the <video>.
  • ABR introspection: NativePlayer exposes read-only levels() / currentLevel() so we can surface the manifest's variant ladder + the rendition currently being played for telemetry. Returns an empty list on native HLS / direct mp4 where the browser hides ABR state.
  • Mobile player polish: .native-player gets a 16/9 aspect-ratio cap with object-fit: contain so non-16:9 sources letterbox cleanly, plus a max-height: 80vh on desktop. A ≤720px breakpoint tightens padding, drops heading sizes, and removes the height cap so the player stays edge-to-edge on phones.

Files

  • src/db/migrations/0019_video_captions.sql + schema.sql mirror
  • src/workers/captions.ts + src/workers/captions.test.ts
  • src/frontend/pages/Watch.tsx (caption fetch + <track> rendering)
  • src/frontend/lib/native-player.ts + native-player.test.ts (ABR levels)
  • src/frontend/styles/strand.css (mobile player + responsive)

Test plan

  • npm test — 521/521 passing (incl. 21 new captions worker tests + 2 ABR tests + 2 migration tests)
  • npm run lint — 0 warnings, AI Gateway guard clean
  • npm run type-check — clean
  • Smoke a Watch page after wrangler d1 migrations apply + PUT /api/videos/:id/captions/en of a sample VTT; confirm the captions toggle appears in the native player UI and the track auto-enables when is_default = 1.
  • Verify on Safari (native HLS) and Chrome (hls.js) that levels() / currentLevel() return what we expect for telemetry.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added video caption and subtitle support with WebVTT file uploads and playback.
    • Native player now displays available quality levels and bitrate information.
    • Watch page automatically loads and displays available captions for each video.
  • Style

    • Enhanced video player styling with responsive mobile layout.

Review Change Stack

Add a video_captions table (one row per language, optional default flag)
and join it onto GET /api/videos/:id so the watch page can render <track>
elements alongside the native player. Tracks side-load via WebVTT URLs,
which works for both the Cloudflare Stream HLS path and the R2 fallback.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 8, 2026 15:01
@aloewright aloewright added the conductor Conductor-managed PR label May 8, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@ecc-tools
Copy link
Copy Markdown
Contributor

ecc-tools Bot commented May 8, 2026

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

Warning

Rate limit exceeded

@aloewright has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 52 minutes and 4 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bbf1133e-338c-43b4-9ce2-49383f79010a

📥 Commits

Reviewing files that changed from the base of the PR and between cc2e75f and 9f62f96.

📒 Files selected for processing (2)
  • src/workers/captions.test.ts
  • src/workers/captions.ts

Walkthrough

This PR adds complete WebVTT subtitle support: database schema for per-video caption metadata, a Hono worker API for managing captions with R2 storage and D1 persistence, HLS rendition introspection in the player layer, and frontend UI to fetch and display caption tracks in the native video player.

Changes

WebVTT Captions Feature

Layer / File(s) Summary
Database Schema & Migrations
src/db/migrations/0019_video_captions.sql, src/db/schema.sql, src/db/migrations.test.ts
New video_captions table with composite primary key (video_id, language) stores track metadata. Cascading foreign key to videos(id) ensures cleanup. Index on video_id optimizes queries. Migration and schema tests verify structure.
Caption Worker API & Validation
src/workers/captions.ts, src/workers/captions.test.ts
Hono routes implement REST API: GET /api/videos/:id/captions lists tracks; GET/HEAD :lang.vtt serves VTT files; PUT :lang uploads with WebVTT validation and R2 storage; DELETE :lang removes idempotently. Exports validation helpers (captionR2Key, normalizeLanguage, isValidWebVtt) and types (CaptionsEnv, CaptionRow). Tests cover auth, language validation, storage, and edge cases.
Player HLS Rendition Introspection
src/frontend/lib/native-player.ts, src/frontend/lib/native-player.test.ts
New RenditionInfo interface and NativePlayer methods levels() and currentLevel() expose hls.js adaptive bitrate ladder metadata. Returns empty ladder and -1 for non-HLS playback. Internal HlsInstance typing updated to include levels and currentLevel fields.
Caption Track Fetching & Rendering
src/frontend/pages/Watch.tsx
Adds CaptionTrack type and captions state. Effect fetches /api/videos/:id/captions when video ID changes. Renders HTML5 track elements within video with language, label, and default flag bindings.
Native Player Styling
src/frontend/styles/strand.css
New .native-player class enforces 16:9 aspect ratio, object-fit: contain, black background, and 80vh height cap. Mobile responsive block (max-width: 720px) adjusts padding, font scales, removes height cap, and sets border-radius: 0.
Worker Route Wiring
src/workers/index.ts
Imports captionsRoutes and registers it on the worker router via app.route('/', captionsRoutes).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • aloewright/spooool#39: Both PRs register new modular sub-app routes with the main worker router; this PR adds captionsRoutes while the related PR introduced videoRoutes registration pattern.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is broad and vague, referring to 'Playback & Watch Experience' without clearly identifying the main change. While the PR objectives mention captions/subtitles support, the title does not specifically reflect this primary feature. Consider a more specific title like 'Add WebVTT captions support to watch experience' to clearly indicate the main change.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch conductor/alo-122-e3-playback-watch-experience

Warning

Review ran into problems

🔥 Problems

These MCP integrations need to be re-authenticated in the Integrations settings: Sentry


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.

@aloewright
Copy link
Copy Markdown
Owner Author

CodeRabbit hit a rate limit and did not produce review comments on the latest push — nothing actionable to address. Will re-request review later if needed.

Copy link
Copy Markdown

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

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: 00359a8de3

ℹ️ 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".

lang TEXT NOT NULL,
label TEXT NOT NULL,
url TEXT NOT NULL,
is_default INTEGER NOT NULL DEFAULT 0,
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 Enforce single default caption track per video

The migration comment says only one caption row per video should have is_default = 1, but the schema does not enforce that invariant, so bulk imports/admin edits can mark multiple tracks as default for the same video. In that case /api/videos/:id returns several default: true tracks and the watch page renders multiple <track default> entries, leaving the initial subtitle selection browser-dependent rather than deterministic. Add a DB-level constraint (for example a partial unique index on video_id where is_default = 1) to prevent inconsistent data.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

…sh (ALO-122)

E3 wraps up the watch surface: viewers get caption tracks, owners can
upload them, the native-player adapter surfaces hls.js variant info for
future ABR telemetry, and the watch page survives narrow viewports.

- 0019_video_captions migration + matching schema.sql block (multi-track,
  CASCADE on video delete, R2 keys hold the VTT bodies)
- src/workers/captions.ts: GET list, public GET /:lang.vtt, owner PUT/DELETE,
  WEBVTT header validation, 2 MiB cap, default-track sync
- Watch.tsx fetches tracks and renders <track kind="subtitles"> children;
  same-origin src means no crossOrigin churn
- native-player exposes levels() / currentLevel() read-only for HLS ABR
  introspection (returns empty list on native HLS / mp4)
- strand.css: 16:9 aspect-ratio cap on .native-player + a 720px breakpoint
  that tightens padding and edge-to-edge player on phones

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@ecc-tools
Copy link
Copy Markdown
Contributor

ecc-tools Bot commented May 9, 2026

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 `@src/workers/captions.ts`:
- Around line 218-237: The separate UPDATE and UPSERT cause a race; wrap both
statements into a single atomic D1 batch/transaction so clearing other defaults
and inserting/updating the row happen together. Replace the two separate
c.env.DB.prepare calls with a single D1 batch/transaction that executes: 1) the
UPDATE 'UPDATE video_captions SET is_default = 0 WHERE video_id = ? AND language
!= ?' bound with (id, language), then 2) the INSERT ... ON CONFLICT(...) DO
UPDATE ... bound with (id, language, label, r2Key, isDefault ? 1 : 0, bytes,
isDefault ? 1 : 0) (or the parameter ordering required by D1.batch), and run the
batch via c.env.DB.batch()/transaction API so both statements are executed
atomically against the video_captions table to enforce a single is_default flag;
keep references to id, language, label, r2Key, bytes and isDefault when binding
parameters.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f12a47af-f6eb-4f6f-9174-c45122c854a2

📥 Commits

Reviewing files that changed from the base of the PR and between 4d3c13f and cc2e75f.

📒 Files selected for processing (10)
  • src/db/migrations.test.ts
  • src/db/migrations/0019_video_captions.sql
  • src/db/schema.sql
  • src/frontend/lib/native-player.test.ts
  • src/frontend/lib/native-player.ts
  • src/frontend/pages/Watch.tsx
  • src/frontend/styles/strand.css
  • src/workers/captions.test.ts
  • src/workers/captions.ts
  • src/workers/index.ts

Comment thread src/workers/captions.ts Outdated
Two concurrent PUT requests marking different languages as default could
both clear each other's pending row and then both upsert is_default=1,
leaving multiple tracks flagged. Wrap the clear-others UPDATE and the
upsert in a single D1 batch so the operations execute atomically.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@ecc-tools
Copy link
Copy Markdown
Contributor

ecc-tools Bot commented May 9, 2026

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@aloewright
Copy link
Copy Markdown
Owner Author

Re: ecc-tools 2026-05-09T07:30:47Z — non-actionable. The bot reports it skipped its own bundle generation because ECC bundle files are already tracked in the repo. No code change required on this branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conductor Conductor-managed PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants