Skip to content

feat: Vimeo provider#18

Merged
karngyan merged 21 commits into
mainfrom
feat/vimeo-provider
Jun 28, 2026
Merged

feat: Vimeo provider#18
karngyan merged 21 commits into
mainfrom
feat/vimeo-provider

Conversation

@karngyan

Copy link
Copy Markdown
Collaborator

Adds a fourth kino provider — Vimeo — under the same glass chrome as Mux/native/YouTube, plus two demo-site improvements for LLM consumption.

Vimeo provider (@karnstack/kino/vimeo)

Built task-by-task (TDD, per-task subagent review) from the design spec + plan on this branch:

  • createVimeoProvider over the Vimeo Player SDK (player.js) — module-singleton loader, host <div>, destroyed-flag teardown, event-driven state sync (no polling ticker).
  • Source parsing (vimeo.com/ID, /ID/HASH, player.vimeo.com?h=), unlisted-hash embed URLs.
  • loaded handler: duration, qualities (height parsed from the rendition id, not the label), synthesized text-track ids, capability flips derived at runtime.
  • Full action set; styled captions via kino's own overlay (enableTextTrack(…, showing=false)); swapSource with the hash channel.
  • <VimeoPlayer> React wrapper (reactive videoId/hash/metadata.videoTitle through swapSource); ./vimeo build entry + export map; README/demo/changeset (minor).
  • No runtime dep on the SDK, no @types/* — hand-rolled structural types.

Bug fixes (found in manual testing)

  • Quality menu showed a bogus 0p row + double checkmark — Vimeo's getQualities() returns an auto pseudo-entry; it's now filtered out (kino renders its own Auto row).
  • Playback rate desync — Vimeo emits no playbackratechange for programmatic rate changes, so the control stuck at the old rate; now patches on setPlaybackRate resolve.
  • Seek past the buffered region looked stuck/laggy — the seeking event carries the target seconds (like seeked) but was ignored, and timeupdate is silent while buffering; now advances currentTime from seeking (matching native/mux) and guards bufferstart during a paused seek.

Demo site

  • "Copy as Markdown" button on every page (co-located per-page markdown → clipboard).
  • /llms.txt index (served as text/plain at the site root, ahead of the SPA fallback) per the llms.txt standard, with a footer link.
  • Studio: provider order is now Mux · Vimeo · Native · YouTube; the Source picker shows only for Mux.

Test plan

  • pnpm test — 122 passing (19 files), incl. the Vimeo provider suite and regression tests for all three bug fixes.
  • pnpm typecheck, pnpm lint, pnpm build — all clean; ./vimeo entry emits dist/vimeo.{js,d.ts}; demo build emits demo/dist/llms.txt.
  • Headless screenshots confirm the Copy-as-Markdown button renders on the doc pages; /llms.txt returns 200 text/plain.

🤖 Generated with Claude Code

karngyan and others added 17 commits June 28, 2026 16:14
Event-driven provider over the Vimeo Player SDK — quality, captions,
PiP, and rate. Mirrors youtube/ shape (async iframe SDK, destroyed
flag) but syncs on SDK events instead of a ticker, renders cues in
kino's own overlay (enableTextTrack showing:false + cuechange), and
derives plan-gated capabilities at runtime. Supports unlisted videos
via hash parsing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
10 TDD tasks from source parsing through docs/changeset. Shared
FakeVimeoPlayer fixture, event-driven sync, runtime-derived
capabilities, hash channel through swapSource.

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

The Vimeo SDK's getQualities() returns an "auto" pseudo-entry; mapping it
surfaced a bogus "0p" rendition that also matched activeQualityId === "auto",
double-checking the quality menu. Filter it out — kino renders its own Auto row.

setRate relied on a playbackratechange echo that Vimeo does not emit for
programmatic rate changes, leaving the control stuck at the old rate while the
video played at the new one. Patch rate when setPlaybackRate resolves instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Provider tabs now read Mux, Vimeo, Native, YouTube. The Source picker swaps the
Mux playback id and has no effect on the embed providers, so it's hidden unless
the Mux tab is active.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…uring seek

Seeking past the buffered region appeared stuck/laggy: the seeking event
carries the target seconds (like seeked), but the handler ignored it. While
Vimeo buffers the new position, timeupdate is suppressed, so currentTime — and
the scrubber thumb — held at the pre-seek value. Native/mux read el.currentTime,
which returns the target instantly; mirror that by patching currentTime from the
seeking event.

bufferstart also fired during a paused seek and flipped paused to false with no
later event to correct it, stranding the wrong paused state. Guard it on
!state.seeking so it only reflects an active-playback buffer stall.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each docs page gets a 'Copy as Markdown' button (top-right of the header) that
copies a co-located markdown rendering of the page — handy for pasting into an
LLM. Adds /llms.txt (served as text/plain at the site root, ahead of the SPA
fallback) following the llms.txt standard: a summary plus curated links to the
four pages, the npm entry points, GitHub, and the README. A footer link makes it
discoverable.

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

cloudflare-workers-and-pages Bot commented Jun 28, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
kino 889c5e1 Commit Preview URL

Branch Preview URL
Jun 28 2026, 03:10 PM

karngyan and others added 4 commits June 28, 2026 18:46
The Vimeo SDK builds its cross-origin player.vimeo.com iframe with only
allow="autoplay; encrypted-media". Permissions Policy is enforced at the iframe
boundary, so requestPictureInPicture() inside the frame was silently rejected
(and swallowed by the action's .catch) even though the top document had PiP
enabled — the button did nothing.

Patch the iframe's allow attribute to include picture-in-picture the moment the
SDK inserts it, via a MutationObserver on the host (set before navigation so the
policy is evaluated with the grant present). The observer self-disconnects on
the first iframe and is torn down on destroy(). The test fake now mirrors the
SDK's real default allow list so the patch path is exercised.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Supersedes the previous allow-attribute patch, which was misguided: inspecting
the live embed shows the Vimeo SDK already grants the iframe
allow="…; picture-in-picture; …", so Permissions Policy was never the blocker.

The real cause is cross-frame user-gesture loss. The SDK's
requestPictureInPicture() is a plain postMessage to the cross-origin
player.vimeo.com iframe, and postMessage does not transfer transient user
activation. The iframe's video.requestPictureInPicture() therefore runs without
a gesture and the browser rejects it (NotAllowedError), which the action's
.catch swallowed — so the button silently did nothing. Vimeo's SDK exposes no
capability-delegation path (vimeo/player.js#696, #734, both closed not-planned).

Same limitation as YouTube: set canPiP: false so the dead button is hidden, and
stub enterPiP/exitPiP. Removes the now-pointless MutationObserver allow-patch,
its tests, and the fake's iframe injection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PiP is disabled for Vimeo (can't be driven from the parent frame), so remove it
from the providers card, the page markdown, and the changeset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI's format:check (prettier --check) flagged the Vimeo source and the
docs/superpowers markdown — eslint passed locally but prettier is a separate
gate. Run prettier --write.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@karngyan karngyan merged commit 4e01744 into main Jun 28, 2026
2 checks passed
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