Benchmarked live in this browser

What changed in Fideo,
and what it actually bought

Every comparison below runs two real builds at once: the released v0.6.0 bundle, pinned and committed to the repo, and the current working build. Same markup, same state, same harness code — only the library differs.

The player

Current build, no stylesheet link

This player is mounted by <script src="fideo.global.js"> alone. The released build could not do that: its page-scope CSS positioned the shadow host, so a missing <link> left the controls stacked in the top-left corner. Hover for the controls.

16-second procedurally generated clip, 101 KB, bundled with the repo — no external requests.

BenchmarkRunning both builds.

Fix 01

Durations past an hour were wrong

formatTime divided seconds by 60 once and stopped, so an hour-long video reported inflated minutes. Both frames are driven with the same state: 3,904 seconds total, 2,704 elapsed.

v0.6.0reads as 65 minutes
Currentreads as 1:05:04

Any video over 60 minutes displayed a duration that did not exist. Now covered by unit tests.

Fix 02

The buffered range was computed but never drawn

The HTML5 provider had always populated state.buffered. Nothing consumed it. The timeline now paints it as a third band behind the played portion. Both frames are driven with buffered: 0.62 at 23% elapsed.

v0.6.0flat track, no load state
Currentplayed · buffered · remaining

Data the library already had, surfaced for zero runtime cost — one extra gradient stop.

Fix 03

The speed menu never showed the active rate

Menu items were plain menuitem buttons with no selected state — nothing visual, nothing for assistive tech. They are now menuitemradio with a live aria-checked state and a tick. Both frames are driven at 1.5× with the menu open.

v0.6.0which one is active?
Current1.5× ticked and announced

A screen reader now announces the rate as selected instead of reading four identical buttons.

Fix 04

Keyboard focus landed on an invisible button

A transparent button covers the whole video to catch play/pause clicks, and it is first in tab order. It had no focus style, so tabbing into a player moved focus nowhere visible. Click inside either frame and press Tab.

v0.6.0no visible focus
Currentinset focus ring

Required for WCAG 2.4.7. The ring uses the player's own accent variable.

Fix 05

The timeline rewrote the DOM 60 times a second

Progress is interpolated on requestAnimationFrame so the bar moves smoothly. The released build rewrote both time labels, the aria-valuetext attribute, the input value and the progress property on every frame — even though the visible clock only changes once a second, and screen readers were re-announced 60 times a second.

The current build keeps the per-frame property update and drops every write whose value has not changed. The harness below mounts six players in each build and pushes one state update per animation frame for three seconds, with playback time advancing in real time.

DOM writes · 6 players · 3 seconds
Operationv0.6.0CurrentDelta
Waiting for both builds…

DOM writes are a proxy for style and layout work, not a direct measure of frame time. The reduction is real; what it saves depends on the page.

Fix 06

Every player cloned the entire stylesheet

Controls live in a shadow root. The released build copied all 7.6 KB of CSS into a fresh <style> node per player, and that CSS carried every page-scope rule (.fideo, .fideo__poster, .fideo__click-target) which can never match inside a shadow root. The sheet is now split in two, and the controls half is adopted once and shared by every instance.

Stylesheet cost · 6 players
Measurementv0.6.0CurrentDelta
Waiting for both builds…

Fix 07

One bad element took down every player on the page

initFideo() mapped over its matches, so the first element that threw — a disabled provider, an iframe a provider rejects — aborted every remaining mount. It now warns and continues. The harness mounts a Vimeo iframe with disabledProviders: ['vimeo'] next to one healthy video.

Two elements, one of them unmountable
BuildOutcomePlayers mounted
Waiting for both builds…

Fix 08

Setup lost a required step

Because the page-scope CSS is what positions the shadow host, forgetting the stylesheet produced a broken player rather than an unstyled one. That half is now injected once per document, prepended to <head> so author CSS still wins the cascade. Opt out with injectStyles: false.

What the page has to provide
Measurementv0.6.0CurrentDelta
Waiting for both builds…
Transfer size · gzip · measured from the builds on disk
Filev0.6.0CurrentDelta
fideo.global.js14,011 B14,839 B+828 B
fideo.css1,818 B919 B−899 B
Script + stylesheet15,829 B15,758 B−71 B
Script only — not possible before15,829 B14,839 B−990 B · −6.3%
Requests for a styled player21−1
fideo.css, uncompressed7,661 B2,904 B−62.1%
The script got bigger. Moving the page CSS into JavaScript costs 828 gzipped bytes. If you already link the stylesheet and never wanted injection, this trade nets you 71 bytes — it buys a removed setup step, not raw bytes. The 6.3% saving applies to the script-only path the released build could not offer.

Also shipped

Smaller corrections in the same pass

Not in these numbers

Identified, not yet built

Memory held by duplicated stylesheets is reported as attached CSS text, not heap usage — the real cost is larger than the text and engine-dependent.