Skip to content

feat(packages): add live button component#1473

Merged
luwes merged 11 commits intomainfrom
feat/live-button
May 1, 2026
Merged

feat(packages): add live button component#1473
luwes merged 11 commits intomainfrom
feat/live-button

Conversation

@luwes
Copy link
Copy Markdown
Collaborator

@luwes luwes commented Apr 28, 2026

Closes #1390
Closes #1419

Test: https://v10-sandbox-git-feat-live-button-mux.vercel.app/?platform=html&styling=css&preset=hls-video&skin=default&source=hls-live&autoplay=1&muted=1&loop=0&preload=metadata

Summary

Adds a Live button for live and DVR streams that signals "at the live edge" and seeks to the Seekable Live Edge when activated.

Changes

  • LiveButtonCore derives live and timeIsLive from the media state, with a tolerance window before liveEdgeStart so autoplay reliably reports live and a conservative seekable-end fallback when liveEdgeStart is unavailable
  • HTML media-live-button element and React LiveButton component sharing the same core
  • data-live, data-edge, data-hidden attributes for skin styling (red-dot at edge, grey-dot behind, hidden off-air)
  • Default and minimal skins render the live button next to PlayButton in the live-audio and live-video presets
  • Sandbox: navbar toggles for autoplay, muted, loop, and preload propagate to every template (kept in a separate commit)
Implementation notes

Detection follows the media-ui-extensions Live Edge proposal (#0007):

  • "At live edge" when currentTime >= liveEdgeStart - liveEdgeTolerance (default 5s)
  • Fallback uses seekable.end() - liveEdgeOffset (default 10s) when liveEdgeStart is undefined or NaN
  • Seek target is the constrained Seekable Live Edge — seekable.end(seekable.length - 1) accounting for HLS HOLD-BACK / DASH suggestedPresentationDelay

Testing

  • pnpm -F @videojs/core test src/core/ui/live-button covers state transitions, edge detection, and seek behavior
  • Manual: load live-video / live-audio presets in the sandbox, verify data-edge flips when seeking back, and that the button hides on VOD sources

Made with Cursor


Note

Medium Risk
Medium risk because it introduces new live-playback UI/state logic and modifies HLS live behavior (seek-to-live and hls.js config tweaks), which could affect live stream startup/latency and seeking across presets.

Overview
Adds a new Live button across core/HTML/React that tracks whether playback is at the live edge and seeks to the seekable live edge when activated (via new LiveButtonCore, media-live-button, and React LiveButton), exposing data-live/data-live-edge for skin styling.

Updates live audio/video presets and skins to register/ship dedicated live player elements (live-video-player, live-audio-player) and render the Live button in default/minimal (CSS + Tailwind) layouts; exports are wired through @videojs/core, @videojs/html, and @videojs/react entrypoints.

Enhances HLS live handling by deriving stream type from playlist type, applying live/LL-HLS default hls.js config (with user overrides), and adding a one-shot seek-to-live on first user-initiated play (including deferral for preload="none").

Extends the sandbox to control autoplay/muted/loop/preload via URL + postMessage, adds a settings menu and React hooks for these values, and propagates them into all HTML/CDN/React templates (including switching to live providers/tags when sources are live).

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

luwes added 2 commits April 27, 2026 17:47
Adds a Live button for live/DVR streams that indicates whether playback is at
the live edge and seeks to the Seekable Live Edge when activated. Hidden via
aria-hidden when the stream is not live. Follows the media-ui-extensions Live
Edge proposal (#7).

- LiveButtonCore with at-edge detection (currentTime >= liveEdgeStart, with
  conservative fallback when liveEdgeStart is unavailable) and a tolerance
  window so autoplay reliably reports live
- HTML media-live-button element and React LiveButton component
- data-live, data-edge, data-hidden attrs for skin styling
- Default and minimal skin chrome includes the live button next to PlayButton
  in the live-audio and live-video presets

Refs #1390

Made-with: Cursor
Adds navbar toggles and a preload selector that propagate to every HTML and
React template via the sandbox listener, so live and on-demand demos can be
exercised across attribute combinations without editing each template.

Made-with: Cursor
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 28, 2026

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

Project Deployment Actions Updated (UTC)
v10-sandbox Ready Ready Preview, Comment Apr 30, 2026 9:17pm

Request Review

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 28, 2026

Deploy Preview for vjs10-site ready!

Name Link
🔨 Latest commit 4b33370
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-site/deploys/69f3c6c20a1953000880e0ef
😎 Deploy Preview https://deploy-preview-1473--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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 28, 2026

📦 Bundle Size Report

🎨 @videojs/html

Path Base PR Diff %
/video/skin 28.81 kB 29.31 kB +510 B +1.7% 🔺
/video/skin.tailwind 28.88 kB 29.38 kB +515 B +1.7% 🔺
/audio/skin 26.67 kB 27.14 kB +480 B +1.8% 🔺
/audio/skin.tailwind 26.79 kB 27.29 kB +514 B +1.9% 🔺
/live-video/player 7.01 kB 🆕
/live-audio/player 5.13 kB 🆕
/video (default) 28.83 kB 29.25 kB +435 B +1.5% 🔺
/video (default + hls) 162.13 kB 163.05 kB +949 B +0.6% 🔺
/video (minimal + hls) 159.65 kB 160.00 kB +359 B +0.2% 🔺
/audio (default) 26.68 kB 27.14 kB +473 B +1.7% 🔺
Presets (7)
Entry Size
/video (default) 29.25 kB
/video (default + hls) 163.05 kB
/video (minimal) 26.42 kB
/video (minimal + hls) 160.00 kB
/audio (default) 27.14 kB
/audio (minimal) 24.39 kB
/background 4.16 kB
Media (8)
Entry Size
/media/background-video 1.04 kB
/media/container 1.72 kB
/media/dash-video 236.58 kB
/media/hls-video 134.87 kB
/media/mux-audio 160.89 kB
/media/mux-video 161.01 kB
/media/native-hls-video 4.62 kB
/media/simple-hls-video 16.01 kB
Players (5)
Entry Size
/video/player 7.00 kB
/audio/player 5.12 kB
/background/player 3.86 kB
/live-video/player 7.01 kB
/live-audio/player 5.13 kB
Skins (29)
Entry Type Size
/video/minimal-skin.css css 3.66 kB
/video/skin.css css 3.69 kB
/video/minimal-skin js 26.39 kB
/video/minimal-skin.tailwind js 26.61 kB
/video/skin js 29.31 kB
/video/skin.tailwind js 29.38 kB
/audio/minimal-skin.css css 2.67 kB
/audio/skin.css css 2.64 kB
/audio/minimal-skin js 24.35 kB
/audio/minimal-skin.tailwind js 24.51 kB
/audio/skin js 27.14 kB
/audio/skin.tailwind js 27.29 kB
/background/skin.css css 115 B
/background/skin js 1.15 kB
/live-video/minimal-skin.css css 3.66 kB
/live-video/skin.css css 3.69 kB
/live-video/minimal-skin js 26.24 kB
/live-video/minimal-skin.tailwind js 26.32 kB
/live-video/skin js 28.75 kB
/live-video/skin.tailwind js 28.76 kB
/live-audio/minimal-skin.css css 2.67 kB
/live-audio/skin.css css 2.64 kB
/live-audio/minimal-skin js 24.23 kB
/live-audio/minimal-skin.tailwind js 24.21 kB
/live-audio/skin js 26.64 kB
/live-audio/skin.tailwind js 26.66 kB
/base.css css 176 B
/shared.css css 88 B
/skin-element js 1.37 kB
UI Components (25)
Entry Size
/ui/alert-dialog 1003 B
/ui/alert-dialog-close 473 B
/ui/alert-dialog-description 404 B
/ui/alert-dialog-title 368 B
/ui/buffering-indicator 2.50 kB
/ui/captions-button 2.60 kB
/ui/cast-button 2.59 kB
/ui/compounds 4.20 kB
/ui/controls 1.99 kB
/ui/error-dialog 3.04 kB
/ui/fullscreen-button 2.56 kB
/ui/hotkey 1.92 kB
/ui/mute-button 2.59 kB
/ui/pip-button 2.59 kB
/ui/play-button 2.60 kB
/ui/playback-rate-button 2.72 kB
/ui/popover 1.85 kB
/ui/poster 2.28 kB
/ui/seek-button 2.60 kB
/ui/slider 1.48 kB
/ui/thumbnail 2.94 kB
/ui/time 2.51 kB
/ui/time-slider 3.92 kB
/ui/tooltip 1.99 kB
/ui/volume-slider 2.75 kB

Sizes are marginal over the root entry point.

⚛️ @videojs/react

Path Base PR Diff %
/media/mux-audio 159.31 kB 159.70 kB +396 B +0.2% 🔺
/live-video/minimal-skin 17.79 kB 18.26 kB +483 B +2.7% 🔺
/live-video/minimal-skin.tailwind 21.18 kB 21.76 kB +596 B +2.7% 🔺
/live-video/skin 20.18 kB 20.65 kB +481 B +2.3% 🔺
/live-video/skin.tailwind 21.32 kB 21.93 kB +620 B +2.8% 🔺
/live-audio/minimal-skin 15.79 kB 16.24 kB +462 B +2.9% 🔺
/live-audio/minimal-skin.tailwind 18.06 kB 18.63 kB +589 B +3.2% 🔺
/live-audio/skin 17.26 kB 17.76 kB +514 B +2.9% 🔺
/live-audio/skin.tailwind 18.19 kB 18.76 kB +586 B +3.1% 🔺
/ui/live-button 2.06 kB 🆕
/video (minimal + hls) 153.27 kB 153.65 kB +393 B +0.3% 🔺
Presets (7)
Entry Size
/video (default) 23.53 kB
/video (default + hls) 155.87 kB
/video (minimal) 21.14 kB
/video (minimal + hls) 153.65 kB
/audio (default) 19.10 kB
/audio (minimal) 17.62 kB
/background 756 B
Media (7)
Entry Size
/media/background-video 575 B
/media/dash-video 235.21 kB
/media/hls-video 133.39 kB
/media/mux-audio 159.70 kB
/media/mux-video 159.68 kB
/media/native-hls-video 3.13 kB
/media/simple-hls-video 14.55 kB
Skins (26)
Entry Type Size
/video/minimal-skin.css css 3.58 kB
/video/skin.css css 3.60 kB
/video/minimal-skin js 21.07 kB
/video/minimal-skin.tailwind js 24.69 kB
/video/skin js 23.45 kB
/video/skin.tailwind js 24.81 kB
/audio/minimal-skin.css css 2.54 kB
/audio/skin.css css 2.50 kB
/audio/minimal-skin js 17.57 kB
/audio/minimal-skin.tailwind js 20.18 kB
/audio/skin js 19.03 kB
/audio/skin.tailwind js 20.19 kB
/background/skin.css css 90 B
/background/skin js 272 B
/live-video/minimal-skin.css css 3.58 kB
/live-video/skin.css css 3.60 kB
/live-video/minimal-skin js 18.26 kB
/live-video/minimal-skin.tailwind js 21.76 kB
/live-video/skin js 20.65 kB
/live-video/skin.tailwind js 21.93 kB
/live-audio/minimal-skin.css css 2.54 kB
/live-audio/skin.css css 2.50 kB
/live-audio/minimal-skin js 16.24 kB
/live-audio/minimal-skin.tailwind js 18.63 kB
/live-audio/skin js 17.76 kB
/live-audio/skin.tailwind js 18.76 kB
UI Components (21)
Entry Size
/ui/alert-dialog 1.12 kB
/ui/buffering-indicator 1.83 kB
/ui/captions-button 2.07 kB
/ui/cast-button 2.03 kB
/ui/controls 1.83 kB
/ui/error-dialog 2.29 kB
/ui/fullscreen-button 2.03 kB
/ui/live-button 2.06 kB
/ui/mute-button 2.04 kB
/ui/pip-button 2.02 kB
/ui/play-button 2.03 kB
/ui/playback-rate-button 2.05 kB
/ui/popover 1.89 kB
/ui/poster 1.66 kB
/ui/seek-button 2.11 kB
/ui/slider 3.36 kB
/ui/thumbnail 2.02 kB
/ui/time 2.48 kB
/ui/time-slider 2.97 kB
/ui/tooltip 2.22 kB
/ui/volume-slider 2.40 kB

Sizes are marginal over the root entry point.

🧩 @videojs/core — no changes
Entries (9)
Entry Size
. 5.21 kB
/dom 11.93 kB
/dom/media/custom-media-element 1.90 kB
/dom/media/dash 234.36 kB
/dom/media/google-cast 4.07 kB
/dom/media/hls 132.98 kB
/dom/media/mux 159.10 kB
/dom/media/native-hls 2.52 kB
/dom/media/simple-hls 13.89 kB
🏷️ @videojs/element — no changes
Entries (2)
Entry Size
. 996 B
/context 943 B
📦 @videojs/store — no changes
Entries (3)
Entry Size
. 1.39 kB
/html 695 B
/react 360 B
🔧 @videojs/utils — no changes
Entries (10)
Entry Size
/array 104 B
/dom 1.92 kB
/events 319 B
/function 327 B
/object 275 B
/predicate 265 B
/string 148 B
/style 190 B
/time 478 B
/number 158 B
📦 @videojs/spf — no changes
Entries (3)
Entry Size
. 4.29 kB
/dom 13.40 kB
/playback-engine 13.26 kB

ℹ️ How to interpret

All sizes are standalone totals (minified + brotli).

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

Run pnpm size locally to check current sizes.

Comment thread packages/core/src/core/ui/live-button/live-button-core.ts
Comment thread apps/sandbox/templates/cdn/main.ts
Comment thread packages/core/src/core/ui/live-button/live-button-core.ts Outdated
Comment thread packages/core/src/core/ui/live-button/live-button-data-attrs.ts Outdated
Comment thread packages/core/src/dom/ui/live-button.ts Outdated
Comment thread packages/html/src/define/live-video/minimal-skin.ts Outdated
Comment thread packages/skins/src/minimal/css/components/live-button.css Outdated
luwes added 3 commits April 28, 2026 09:12
Renames the LiveButton state field from timeIsLive to liveEdge and the
corresponding data attribute from data-edge to data-live-edge for
consistency with liveEdgeStart and the existing data-live attribute.

Made-with: Cursor
…o-insert text

Drops the .media-live-indicator and .media-live-label spans from every live
skin and renders the status dot via .media-button--live::before instead. The
LiveButton element/component now auto-inserts LiveButtonCore.defaultText
('Live') when no children are provided, giving a single i18n hook, while CSS
text-transform: uppercase keeps the visual "LIVE" treatment.

Made-with: Cursor
Move `live-button.css` into `button.css` and `live-button.ts` (Tailwind) into
`button.ts` as a `live` variant, alongside `primary`, `subtle`, and `icon`.
Removes the dedicated files in both default and minimal skins, drops the
`@import` lines and `liveButton` re-exports, and updates all 8 live
audio/video skin templates to use `button.live` instead of `liveButton.button`.

Made-with: Cursor
Comment thread packages/core/src/dom/ui/live-button.ts Outdated
Drop the `selectLiveButton` selector and have the HTML and React adapters
select `live`, `time`, and `buffer` directly, composing `LiveButtonMediaState`
inline. The HTML element no longer extends `MediaButtonElement` and the React
component no longer wraps `createMediaButton` — both bases assume a single
feature selector, but the LiveButton needs three slices to detect the live
edge and seek.

`LiveButtonCore` keeps `LiveButtonMediaState` as its public media-state
contract; only the wiring changes.

Made-with: Cursor
Copy link
Copy Markdown

@cursor cursor Bot left a comment

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 and found 1 potential issue.

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 5b1dc68. Configure here.

Comment thread packages/core/src/core/ui/live-button/live-button-core.ts
mihar-22
mihar-22 previously approved these changes Apr 29, 2026
Drop the customizable `liveEdgeOffset` and `liveEdgeTolerance` props from
`LiveButtonCore` and the React `<LiveButton>` component, replacing them
with module-level constants. Simplifies the public API while preserving
the existing default behavior (10s offset fallback, 5s tolerance).

Made-with: Cursor
mihar-22
mihar-22 previously approved these changes Apr 30, 2026
…sjsLevelDetails

Consolidate `targetLiveWindow` and `liveEdgeStartOffset` computation into
the shared `getStreamInfoFromHlsjsLevelDetails` helper so the HLS live
mixin stays in sync with stream-type detection. Preserves the existing
HOLD-BACK / PART-HOLD-BACK preference over the spec-multiple fallbacks.

Fixes #1419

Made-with: Cursor
Extend the test engine mock with `config`/`userConfig` and add coverage
for the low-latency vs standard live config defaults, user-supplied
overrides, and the non-live no-op.

Made-with: Cursor
@luwes luwes merged commit e37d5df into main May 1, 2026
26 checks passed
@luwes luwes deleted the feat/live-button branch May 1, 2026 01:00
@luwes luwes mentioned this pull request Apr 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Feature: Low-Latency HLS Support (LL-HLS) Feature: Add Live Button Component

3 participants