Conversation
Demonstrates Video.js 10's architectural versatility by building an animated GIF renderer that plugs into the existing feature/slice system without modifying any feature or UI component. Key components: - GifMedia: EventTarget subclass that fetches and decodes GIFs via gifuct-js, drives a canvas with requestAnimationFrame, and emits the play/pause/playing events that playbackFeature listens to - AnimatedGif: React component that renders <canvas> and registers GifMedia as the player's media target via useMediaRegistration() - GifMediaElement: HTMLElement custom element with EventTarget delegation to its internal GifMedia; sets data-media-element so the container mixin discovers it automatically Demos at /react-gif/ and /html-gif/ show the Play/Pause button working with a GIF using only playbackFeature — no changes to features or UI. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Wire up PlayButton icons via render prop (React) and icon spans with data-attribute CSS (HTML) — both patterns from the spf demos - Add native <img> alongside the canvas for side-by-side comparison, making it easy to see the GifMedia play/pause against the always-on browser-native GIF rendering Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…n ms gifuct-js converts GCE centiseconds → milliseconds internally: resultImage.delay = (frame.gce.delay || 10) * 10; // convert to ms Our extra `* 10` multiplier was making every frame display 10× too long (a 40ms frame appeared as 400ms). Also note the `|| 10` fallback: 0-delay frames become 100ms, matching the browser clamping behavior. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Demonstrate that a media renderer needs almost nothing from HTMLMediaElement to integrate with the feature system. gif-media.ts: - Replace #lastTime + #elapsed with a single #nextFrameAt timestamp, eliminating two private fields and simplifying #tick - Replace AbortController with a generation counter (#loadGen), removing #loadAbort and the redundant abort at the top of #load() - Store the ParsedGif object directly (#gif) so lsd.width/height come from the library rather than extra #gifWidth/#gifHeight fields - Fix play() before src: defer play/playing events until frames are available; #load() emits them when it starts playback - Silence error events from superseded loads in the catch block html.ts: - Remove delegating stubs for ended, currentTime, duration, readyState, and load() — none are needed; playbackFeature only requires paused, play(), pause(), and EventTarget dispatch Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Collapse #canvas + #tempCanvas into single #render object - Guard play() for already-unpaused and no-src cases - Remove redundant guards from #tick and #start - Drop optional chaining on frame index access Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Add 'gif' preset to PRESETS and app shell (constants, navbar, app.tsx) - Auto-select gif source when switching to gif preset; revert on switch away - Disable styling/skin/source selectors for gif preset (no tailwind variant) - Add Mux animated GIF sources (gif-1: Dancing Dude, gif-2: Big Buck Bunny) - Rename gif-media element to gif-video for container-mixin discovery - Update html-gif to new createPlayer API: ProviderMixin + ContainerMixin - Use ProviderMixin(ContainerMixin(MediaElement)) so #contextStore initializes before ProviderMixin constructor reads this.store - Drop old static src/index.html superseded by the app shell from main Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove templates/index.html (superseded by app shell) - Drop data-media-element attribute setting: container-mixin discovers gif-video by element name convention (*-video), not by attribute Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
✅ Deploy Preview for vjs10-site ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
📦 Bundle Size Report🎨 @videojs/html(no changes) Presets (7)
Media (4)
Players (3)
Skins (16)
UI Components (21)
Sizes are marginal over the root entry point. ⚛️ @videojs/react(no changes) Presets (7)
Media (3)
Skins (14)
UI Components (17)
Sizes are marginal over the root entry point. 🧩 @videojs/core(no changes) Entries (5)
🏷️ @videojs/element(no changes) Entries (2)
📦 @videojs/store(no changes) Entries (3)
🔧 @videojs/utils(no changes) Entries (10)
📦 @videojs/spf(no changes) Entries (3)
ℹ️ How to interpretAll sizes are standalone totals (minified + brotli).
Run |
| #nextFrameAt = 0; | ||
| #loadGen = 0; | ||
|
|
||
| get paused(): boolean { |
There was a problem hiding this comment.
The TL;DR here - as long as you make this delegate "look like" an HTMLMediaElement, and only for the things you need (e.g. in this simple implementation, I don't have duration/currentTime/playbackRate/etc), you can do whatever else you want with the "innards"
| return this.#paused; | ||
| } | ||
|
|
||
| get src(): string { |
There was a problem hiding this comment.
NOTE: src isn't as strict as other HTMLMediaElement API conformance. That said, we encourage you to use a separate property (e.g. source, or, like we did for <mux-video> pre-VJSv10, playbackid for our Mux Video specific shorthand) if you want to provide something other than a string!
| this.dispatchEvent(new Event('pause')); | ||
| } | ||
|
|
||
| attach(canvas: HTMLCanvasElement): void { |
There was a problem hiding this comment.
attach(), detach() are the VJS-specific integration/extension points. They're used indirectly by the html/web component + react component integrations. note that this example accepts an HTMLCanvasElement, unlike others in our core library, which expect an HTMLMediaElement
| @@ -0,0 +1,68 @@ | |||
| import { GifMedia } from './gif-media'; | |||
|
|
|||
| export class GifMediaElement extends HTMLElement { | |||
There was a problem hiding this comment.
NOTE: You only have to implement this if you're planning on supporting web components!
Hopefully we can also improve boilerplate here so you can write less code going forward! Feedback welcome!
| @@ -0,0 +1,48 @@ | |||
| import type { Media } from '@videojs/react'; | |||
There was a problem hiding this comment.
NOTE: You only have to implement this if you're planning on supporting react!
Hopefully we can also improve boilerplate here so you can write less code going forward! Feedback welcome!
Summary
An animated GIF renderer built on Video.js 10's core infrastructure — no changes to any feature, store, or UI component. The GIF plays/pauses/restarts through `playbackFeature` exactly like a native `
The goal is twofold: demonstrate how to build a custom media renderer that plugs into the existing architecture, and surface any points of friction in our convenience abstractions (delegates, mixins, container discovery) for real external-media use cases.
Code walkthrough
Core delegate — `packages/sandbox/templates/gif-media/`
Framework demos
App shell integration — `packages/sandbox/app/`
Try it locally
Open http://localhost:5173 and select Preset → GIF in the navbar. Switch between HTML and React platforms. The play/pause button and the native `
` comparison render side-by-side.
Direct links (after `pnpm dev`):
What this surfaces
A few things became apparent building this against our existing abstractions: