Skip to content

feat(packages): remove native controls after mounting our controls#1727

Closed
sampotts wants to merge 1 commit into
mainfrom
feat/native-controls
Closed

feat(packages): remove native controls after mounting our controls#1727
sampotts wants to merge 1 commit into
mainfrom
feat/native-controls

Conversation

@sampotts

@sampotts sampotts commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Remove the native controls after mounting our custom controls component. This allows for progressive enhancement and has been noted in the migration docs.

Given it affects skins, I won't merge until @mihar-22 gives it the ok.

Closes #1160


Note

Medium Risk
Touches core player attach lifecycle and mutates native controls on media elements across HTML and React; behavior is covered by new tests but regressions could affect fallback playback without custom UI.

Overview
Enables progressive enhancement: media can ship with native controls until a custom controls UI mounts, then native controls are turned off and restored if all custom controls unmount.

A new controls registration path tracks mounted control roots (HTML media-controls via player context; React Controls.Root via registerControls / controlsMounted). The provider mixin and React provider use isMediaControlsCapable to set media.controls = false only while at least one controls instance is registered, with refcounting when multiple roots exist.

Skins and presets wrap control chrome in media-controls / Controls.Root instead of plain divs, and defineControls() is wired into audio/live-audio UI registration. React Video / Audio omit the controls attribute when controlsMounted is true.

Reviewed by Cursor Bugbot for commit 1415071. Bugbot is set up for automated code reviews on this repo. Configure here.

@netlify

netlify Bot commented Jun 23, 2026

Copy link
Copy Markdown

Deploy Preview for vjs10-site ready!

Name Link
🔨 Latest commit 1415071
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-site/deploys/6a3b22de9195d9000875d99b
😎 Deploy Preview https://deploy-preview-1727--vjs10-site.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@vercel

vercel Bot commented Jun 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
v10-sandbox Ready Ready Preview, Comment Jun 24, 2026 12:21am

Request Review

@sampotts sampotts enabled auto-merge (squash) June 23, 2026 06:51
Comment thread packages/react/src/player/create-player.tsx Outdated
@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Size Report

🎨 @videojs/html

Path Base initial PR initial Diff % Lazy
/audio/minimal-skin 36.62 kB 36.99 kB +378 B +1.0% 🔺
/audio/minimal-skin.tailwind 37.04 kB 37.38 kB +348 B +0.9% 🔺
/audio/skin 37.80 kB 38.29 kB +504 B +1.3% 🔺
/audio/skin.tailwind 38.25 kB 38.63 kB +385 B +1.0% 🔺
/live-audio/minimal-skin 29.73 kB 30.11 kB +394 B +1.3% 🔺
/live-audio/minimal-skin.tailwind 29.20 kB 29.61 kB +422 B +1.4% 🔺
/live-audio/skin 31.04 kB 31.47 kB +437 B +1.4% 🔺
/live-audio/skin.tailwind 30.66 kB 31.10 kB +458 B +1.5% 🔺
/video (default + hls) 183.79 kB 184.14 kB +362 B +0.2% 🔺
/audio (default) 37.83 kB 38.24 kB +420 B +1.1% 🔺
/audio (minimal) 36.61 kB 37.04 kB +441 B +1.2% 🔺
Presets (7)
Entry Initial
/video (default) 44.54 kB
/video (default + hls) 184.14 kB
/video (minimal) 44.21 kB
/video (minimal + hls) 183.89 kB
/audio (default) 38.24 kB
/audio (minimal) 37.04 kB
/background 4.35 kB
Media (10)
Entry Initial
/media/background-video 1.15 kB
/media/container 1.75 kB
/media/dash-video 214.82 kB
/media/hls-video 141.27 kB
/media/mux-audio 163.77 kB
/media/mux-video 163.73 kB
/media/native-hls-video 9.06 kB
/media/simple-hls-audio-only 17.16 kB
/media/simple-hls-video 18.72 kB
/media/vimeo-video 12.33 kB
Players (5)
Entry Initial
/video/player 8.23 kB
/audio/player 5.53 kB
/background/player 4.09 kB
/live-video/player 7.78 kB
/live-audio/player 5.54 kB
Skins (30)
Entry Type Initial
/video/minimal-skin.css css 5.45 kB
/video/skin.css css 5.43 kB
/video/minimal-skin js 44.25 kB
/video/minimal-skin.tailwind js 44.83 kB
/video/skin js 44.54 kB
/video/skin.tailwind js 45.19 kB
/audio/minimal-skin.css css 3.60 kB
/audio/skin.css css 3.53 kB
/audio/minimal-skin js 36.99 kB
/audio/minimal-skin.tailwind js 37.38 kB
/audio/skin js 38.29 kB
/audio/skin.tailwind js 38.63 kB
/background/skin.css css 133 B
/background/skin js 1.14 kB
/live-video/minimal-skin.css css 5.45 kB
/live-video/skin.css css 5.43 kB
/live-video/minimal-skin js 43.32 kB
/live-video/minimal-skin.tailwind js 43.75 kB
/live-video/skin js 43.27 kB
/live-video/skin.tailwind js 43.84 kB
/live-audio/minimal-skin.css css 3.60 kB
/live-audio/skin.css css 3.53 kB
/live-audio/minimal-skin js 30.11 kB
/live-audio/minimal-skin.tailwind js 29.61 kB
/live-audio/skin js 31.47 kB
/live-audio/skin.tailwind js 31.10 kB
/global.css css 176 B
/shared.css css 88 B
/tailwind.css css 228 B
/skin-element js 1.44 kB
UI Components (38)
Entry Initial
/ui/airplay-button 2.40 kB
/ui/alert-dialog 2.56 kB
/ui/alert-dialog-close 2.30 kB
/ui/alert-dialog-description 2.28 kB
/ui/alert-dialog-title 2.28 kB
/ui/buffering-indicator 2.27 kB
/ui/captions-button 2.42 kB
/ui/captions-radio-group 2.79 kB
/ui/cast-button 2.44 kB
/ui/compounds 2.98 kB
/ui/controls 2.72 kB
/ui/error-dialog 2.74 kB
/ui/fullscreen-button 2.41 kB
/ui/hotkey 2.40 kB
/ui/menu 2.76 kB
/ui/mute-button 2.45 kB
/ui/pip-button 2.41 kB
/ui/play-button 2.38 kB
/ui/playback-rate-button 2.43 kB
/ui/playback-rate-radio-group 2.90 kB
/ui/popover 2.77 kB
/ui/poster 2.36 kB
/ui/quality-radio-group 2.81 kB
/ui/seek-button 2.39 kB
/ui/seek-indicator 2.43 kB
/ui/seek-indicator-value 546 B
/ui/slider 2.75 kB
/ui/status-announcer 2.31 kB
/ui/status-indicator 2.46 kB
/ui/status-indicator-value 561 B
/ui/thumbnail 2.20 kB
/ui/time 2.72 kB
/ui/time-slider 2.77 kB
/ui/tooltip 2.72 kB
/ui/volume-indicator 2.43 kB
/ui/volume-indicator-fill 554 B
/ui/volume-indicator-value 483 B
/ui/volume-slider 2.75 kB

Sizes are marginal over the root entry point.

⚛️ @videojs/react — no changes
Presets (7)
Entry Initial
/video (default) 36.93 kB
/video (default + hls) 175.21 kB
/video (minimal) 36.95 kB
/video (minimal + hls) 175.27 kB
/audio (default) 30.16 kB
/audio (minimal) 30.22 kB
/background 752 B
Media (9)
Entry Initial
/media/background-video 575 B
/media/dash-video 213.24 kB
/media/hls-video 139.80 kB
/media/mux-audio 162.26 kB
/media/mux-video 162.26 kB
/media/native-hls-video 7.39 kB
/media/simple-hls-audio-only 15.56 kB
/media/simple-hls-video 17.15 kB
/media/vimeo-video 10.58 kB
Skins (27)
Entry Type Initial
/tailwind.css css 228 B
/video/minimal-skin.css css 5.37 kB
/video/skin.css css 5.34 kB
/video/minimal-skin js 36.87 kB
/video/minimal-skin.tailwind js 42.61 kB
/video/skin js 36.78 kB
/video/skin.tailwind js 42.55 kB
/audio/minimal-skin.css css 3.47 kB
/audio/skin.css css 3.39 kB
/audio/minimal-skin js 30.13 kB
/audio/minimal-skin.tailwind js 31.90 kB
/audio/skin js 30.06 kB
/audio/skin.tailwind js 33.88 kB
/background/skin.css css 90 B
/background/skin js 272 B
/live-video/minimal-skin.css css 5.37 kB
/live-video/skin.css css 5.34 kB
/live-video/minimal-skin js 32.63 kB
/live-video/minimal-skin.tailwind js 38.26 kB
/live-video/skin js 32.64 kB
/live-video/skin.tailwind js 38.34 kB
/live-audio/minimal-skin.css css 3.47 kB
/live-audio/skin.css css 3.39 kB
/live-audio/minimal-skin js 21.92 kB
/live-audio/minimal-skin.tailwind js 24.82 kB
/live-audio/skin js 21.97 kB
/live-audio/skin.tailwind js 24.97 kB
UI Components (32)
Entry Initial
/ui/airplay-button 2.34 kB
/ui/alert-dialog 2.27 kB
/ui/buffering-indicator 2.17 kB
/ui/captions-button 2.29 kB
/ui/captions-radio-group 2.22 kB
/ui/cast-button 2.31 kB
/ui/controls 2.11 kB
/ui/error-dialog 2.33 kB
/ui/fullscreen-button 2.31 kB
/ui/gesture 2.33 kB
/ui/hotkey 2.29 kB
/ui/live-button 2.18 kB
/ui/menu 2.48 kB
/ui/mute-button 2.34 kB
/ui/pip-button 2.32 kB
/ui/play-button 2.34 kB
/ui/playback-rate 2.17 kB
/ui/playback-rate-button 2.34 kB
/ui/popover 2.67 kB
/ui/poster 2.10 kB
/ui/quality 2.16 kB
/ui/seek-button 2.33 kB
/ui/seek-indicator 2.24 kB
/ui/slider 2.34 kB
/ui/status-announcer 2.19 kB
/ui/status-indicator 2.19 kB
/ui/thumbnail 2.11 kB
/ui/time 2.10 kB
/ui/time-slider 2.32 kB
/ui/tooltip 2.72 kB
/ui/volume-indicator 2.14 kB
/ui/volume-slider 2.38 kB

Sizes are marginal over the root entry point.

🧩 @videojs/core — no changes
Entries (14)
Entry Initial
. 9.10 kB
/dom 17.02 kB
/dom/media/custom-media-element 2.09 kB
/dom/media/dash 209.07 kB
/dom/media/google-cast 4.04 kB
/dom/media/hls 135.63 kB
/dom/media/media-host 1.25 kB
/dom/media/media-played-ranges 576 B
/dom/media/mux 151.26 kB
/dom/media/native-hls 3.05 kB
/dom/media/simple-hls 16.47 kB
/dom/media/simple-hls-audio-only 14.92 kB
/dom/media/vimeo 9.86 kB
/media/predicate 561 B
🏷️ @videojs/element — no changes
Entries (2)
Entry Initial
. 996 B
/context 943 B
📦 @videojs/store — no changes
Entries (3)
Entry Initial
. 1.39 kB
/html 696 B
/react 360 B
🔧 @videojs/utils — no changes
Entries (10)
Entry Initial
/array 104 B
/dom 2.26 kB
/events 319 B
/function 327 B
/object 275 B
/predicate 265 B
/string 231 B
/style 190 B
/time 478 B
/number 158 B
📦 @videojs/spf — no changes
Entries (4)
Entry Initial
. 4.45 kB
/dom 6.33 kB
/hls 15.37 kB
/background-looping-video 12.90 kB

ℹ️ How to interpret

JS sizes are initial static graph totals (minified + brotli). Lazy dynamic chunks are shown separately when present.

Icon Meaning
No change
🔺 Increased ≤ 10%
🔴 Increased > 10%
🔽 Decreased
🆕 New (no baseline)

Run pnpm size locally to check current initial sizes.

if (hasMediaChanged || hasContainerChanged) {
this.#detachStore();
this.#detach = store.attach(target);
if (isMediaControlsCapable(target.media) && target.media.controls) target.media.controls = false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I read the issue but I find it slightly presumptuous. what if the user does want to use the native controls for some reason.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I can see that but at the same time I think it'd be unlikely someone has both provided custom controls and wants native controls?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I'd have preferred to add the native controls removal logic to our controls component but we don't actually have one (it's just a <div>) so we'd have to add a <media-controls> / <Controls> component, which would obviously add a tiny bit more to the bundle.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We do have controls components?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

"You're absolutely right!"

I don't know why we're not using them currently in the skins (probably an oversight on my part way back) but I'll update and put the logic there instead.

@mihar-22 mihar-22 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't believe we have a progressive enhancement story? Is there an example where this would come up?

Edit: Ah, if someone passes in <video> on the HTML side, makes sense to me now 👍

@sampotts

Copy link
Copy Markdown
Collaborator Author

I've changed it to be based on mounting of our custom controls instead of the container. This would allow consumers to use native controls if they wanted to.

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

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c6eceee. Configure here.

Comment thread packages/html/src/store/provider-mixin.ts
Comment thread packages/html/src/ui/controls/controls-element.ts
@sampotts sampotts force-pushed the feat/native-controls branch from c6eceee to 1415071 Compare June 24, 2026 00:20
@sampotts sampotts changed the title feat(packages): remove native controls after attaching media feat(packages): remove native controls after mounting our controls Jun 25, 2026
@mihar-22

Copy link
Copy Markdown
Member

Going to close this out as Controls have been migrated over separately and I'd love for a simpler solution here for toggling native controls, preferably without a separate controls context.

@mihar-22 mihar-22 closed this Jun 30, 2026
auto-merge was automatically disabled June 30, 2026 18:09

Pull request was closed

@sampotts sampotts deleted the feat/native-controls branch July 1, 2026 05:23
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.

Feature: Remove Native Controls When Custom Control Is Loaded

4 participants