feat(packages): add live button component (#1473)

This commit is contained in:
Wesley Luyten
2026-04-30 18:00:15 -07:00
committed by GitHub
parent c5b06cf948
commit e37d5df873
70 changed files with 2207 additions and 118 deletions
+2
View File
@@ -4,6 +4,7 @@
import { MediaContainerElement } from '../../media/container-element';
import { GestureElement } from '../../ui/gesture/gesture-element';
import { HotkeyElement } from '../../ui/hotkey/hotkey-element';
import { LiveButtonElement } from '../../ui/live-button/live-button-element';
import { MuteButtonElement } from '../../ui/mute-button/mute-button-element';
import { PlayButtonElement } from '../../ui/play-button/play-button-element';
import { PlaybackRateButtonElement } from '../../ui/playback-rate-button/playback-rate-button-element';
@@ -31,6 +32,7 @@ defineTime();
// Standalone elements.
safeDefine(GestureElement);
safeDefine(HotkeyElement);
safeDefine(LiveButtonElement);
safeDefine(MuteButtonElement);
safeDefine(PlayButtonElement);
safeDefine(PlaybackRateButtonElement);
+7 -3
View File
@@ -2,7 +2,8 @@
/* Base */
/* -------------------------------------------------------------------------- */
video-player {
video-player,
live-video-player {
display: contents;
}
@@ -11,13 +12,16 @@ Required to override any default video and image styles (such as
Tailwind's CSS reset) and ensure they fill the container as expected.
*/
video-player video,
video-player [slot="poster"] {
video-player [slot="poster"],
live-video-player video,
live-video-player [slot="poster"] {
display: block;
width: 100%;
height: 100%;
}
video-player video::-webkit-media-text-track-container {
video-player video::-webkit-media-text-track-container,
live-video-player video::-webkit-media-text-track-container {
z-index: 1;
font-family: inherit;
scale: 0.98;
@@ -15,8 +15,8 @@ import { cn } from '@videojs/utils/style';
import { safeDefine } from '../safe-define';
import { SkinElement } from '../skin-element';
// Reuse the audio preset's minimal UI element registrations.
import '../audio/minimal-ui';
// Register the live audio player, container, and minimal UI custom elements.
import './minimal-ui';
function getTemplateHTML() {
return /*html*/ `
@@ -46,6 +46,8 @@ function getTemplateHTML() {
${renderIcon('pause', { class: cn(icon, iconState.play.pause) })}
</media-play-button>
<media-tooltip id="play-tooltip" side="top" class="${cn(popup.tooltip)}"></media-tooltip>
<media-live-button class="${cn(button.base, button.subtle, button.live)}"></media-live-button>
</div>
<div class="grow" aria-hidden="true"></div>
@@ -4,8 +4,8 @@ import { safeDefine } from '../safe-define';
import { SkinElement } from '../skin-element';
import styles from './minimal-skin.css?inline';
// Reuse the audio preset's minimal UI element registrations.
import '../audio/minimal-ui';
// Register the live audio player, container, and minimal UI custom elements.
import './minimal-ui';
function getTemplateHTML() {
return /*html*/ `
@@ -35,6 +35,8 @@ function getTemplateHTML() {
${renderIcon('pause', { class: 'media-icon media-icon--pause' })}
</media-play-button>
<media-tooltip id="play-tooltip" side="top" class="media-tooltip"></media-tooltip>
<media-live-button class="media-button media-button--subtle media-button--live"></media-live-button>
</div>
<div class="media-time-controls" aria-hidden="true"></div>
@@ -0,0 +1,35 @@
// Registers the live audio player, container, and all audio UI custom
// elements used by the minimal skin without creating a skin element. Use
// this entry when building an ejected (light DOM) player layout for live
// HLS / DASH streams.
import { MediaContainerElement } from '../../media/container-element';
import { LiveButtonElement } from '../../ui/live-button/live-button-element';
import { MuteButtonElement } from '../../ui/mute-button/mute-button-element';
import { PlayButtonElement } from '../../ui/play-button/play-button-element';
import { PopoverElement } from '../../ui/popover/popover-element';
import { TooltipElement } from '../../ui/tooltip/tooltip-element';
import { TooltipGroupElement } from '../../ui/tooltip/tooltip-group-element';
import { safeDefine } from '../safe-define';
import { defineErrorDialog, defineTime, defineTimeSlider, defineVolumeSlider } from '../ui/compounds';
// Value import — player.ts body runs before this module's body.
import { LiveAudioPlayerElement } from './player';
// ── Registration (providers / parents first) ────────────────────────────
safeDefine(LiveAudioPlayerElement);
safeDefine(MediaContainerElement);
// Compound groups.
defineErrorDialog();
defineTimeSlider();
defineVolumeSlider();
defineTime();
// Standalone elements.
safeDefine(LiveButtonElement);
safeDefine(MuteButtonElement);
safeDefine(PlayButtonElement);
safeDefine(PopoverElement);
safeDefine(TooltipElement);
safeDefine(TooltipGroupElement);
@@ -0,0 +1,23 @@
import { liveAudioFeatures } from '@videojs/core/dom';
import { MediaContainerElement } from '../../media/container-element';
import { createPlayer } from '../../player/create-player';
import { MediaElement } from '../../ui/media-element';
import { safeDefine } from '../safe-define';
const { ProviderMixin } = createPlayer({
features: liveAudioFeatures,
});
export class LiveAudioPlayerElement extends ProviderMixin(MediaElement) {
static readonly tagName = 'live-audio-player';
}
// Provider must be defined before consumer for context handshake during upgrade.
safeDefine(LiveAudioPlayerElement);
safeDefine(MediaContainerElement);
declare global {
interface HTMLElementTagNameMap {
[LiveAudioPlayerElement.tagName]: LiveAudioPlayerElement;
}
}
@@ -15,8 +15,8 @@ import { cn } from '@videojs/utils/style';
import { safeDefine } from '../safe-define';
import { SkinElement } from '../skin-element';
// Reuse the audio preset's UI element registrations.
import '../audio/ui';
// Register the live audio player, container, and all UI custom elements.
import './ui';
function getTemplateHTML() {
return /*html*/ `
@@ -46,6 +46,8 @@ function getTemplateHTML() {
${renderIcon('pause', { class: cn(icon, iconState.play.pause) })}
</media-play-button>
<media-tooltip id="play-tooltip" side="top" class="${cn(popup.tooltip)}"></media-tooltip>
<media-live-button class="${cn(button.base, button.subtle, button.live)}"></media-live-button>
</div>
<div class="grow" aria-hidden="true"></div>
+4 -2
View File
@@ -4,8 +4,8 @@ import { safeDefine } from '../safe-define';
import { SkinElement } from '../skin-element';
import styles from './skin.css?inline';
// Reuse the audio preset's UI element registrations.
import '../audio/ui';
// Register the live audio player, container, and all UI custom elements.
import './ui';
function getTemplateHTML() {
return /*html*/ `
@@ -35,6 +35,8 @@ function getTemplateHTML() {
${renderIcon('pause', { class: 'media-icon media-icon--pause' })}
</media-play-button>
<media-tooltip id="play-tooltip" side="top" class="media-surface media-tooltip"></media-tooltip>
<media-live-button class="media-button media-button--subtle media-button--live"></media-live-button>
</div>
<div class="media-time-controls" aria-hidden="true"></div>
+38
View File
@@ -0,0 +1,38 @@
// Registers the live audio player, container, and all audio UI custom
// elements without creating a skin element. Use this entry when building an
// ejected (light DOM) player layout for live HLS / DASH streams.
import { MediaContainerElement } from '../../media/container-element';
import { GestureElement } from '../../ui/gesture/gesture-element';
import { HotkeyElement } from '../../ui/hotkey/hotkey-element';
import { LiveButtonElement } from '../../ui/live-button/live-button-element';
import { MuteButtonElement } from '../../ui/mute-button/mute-button-element';
import { PlayButtonElement } from '../../ui/play-button/play-button-element';
import { PopoverElement } from '../../ui/popover/popover-element';
import { TooltipElement } from '../../ui/tooltip/tooltip-element';
import { TooltipGroupElement } from '../../ui/tooltip/tooltip-group-element';
import { safeDefine } from '../safe-define';
import { defineErrorDialog, defineTime, defineTimeSlider, defineVolumeSlider } from '../ui/compounds';
// Value import — player.ts body runs before this module's body.
import { LiveAudioPlayerElement } from './player';
// ── Registration (providers / parents first) ────────────────────────────
safeDefine(LiveAudioPlayerElement);
safeDefine(MediaContainerElement);
// Compound groups.
defineErrorDialog();
defineTimeSlider();
defineVolumeSlider();
defineTime();
// Standalone elements.
safeDefine(GestureElement);
safeDefine(HotkeyElement);
safeDefine(LiveButtonElement);
safeDefine(MuteButtonElement);
safeDefine(PlayButtonElement);
safeDefine(PopoverElement);
safeDefine(TooltipElement);
safeDefine(TooltipGroupElement);
@@ -19,8 +19,8 @@ import { cn } from '@videojs/utils/style';
import { safeDefine } from '../safe-define';
import { SkinElement } from '../skin-element';
// Reuse the video preset's minimal UI element registrations.
import '../video/minimal-ui';
// Register the live video player, container, and minimal UI custom elements.
import './minimal-ui';
function getTemplateHTML() {
return /*html*/ `
@@ -58,6 +58,8 @@ function getTemplateHTML() {
${renderIcon('pause', { class: cn(icon, iconState.play.pause) })}
</media-play-button>
<media-tooltip id="play-tooltip" side="top" class="${cn(popup.tooltip)}"></media-tooltip>
<media-live-button class="${cn(button.base, button.subtle, button.live)}"></media-live-button>
</div>
<div class="grow" aria-hidden="true"></div>
@@ -4,8 +4,8 @@ import { safeDefine } from '../safe-define';
import { SkinElement } from '../skin-element';
import styles from './minimal-skin.css?inline';
// Reuse the video preset's minimal UI element registrations.
import '../video/minimal-ui';
// Register the live video player, container, and minimal UI custom elements.
import './minimal-ui';
function getTemplateHTML() {
return /*html*/ `
@@ -43,6 +43,8 @@ function getTemplateHTML() {
${renderIcon('pause', { class: 'media-icon media-icon--pause' })}
</media-play-button>
<media-tooltip id="play-tooltip" side="top" class="media-tooltip"></media-tooltip>
<media-live-button class="media-button media-button--subtle media-button--live"></media-live-button>
</div>
<div class="media-time-controls" aria-hidden="true"></div>
@@ -0,0 +1,48 @@
// Registers the live video player, container, and all video UI custom
// elements used by the minimal skin without creating a skin element. Use
// this entry when building an ejected (light DOM) player layout for live
// HLS / DASH streams.
import { MediaContainerElement } from '../../media/container-element';
import { BufferingIndicatorElement } from '../../ui/buffering-indicator/buffering-indicator-element';
import { CaptionsButtonElement } from '../../ui/captions-button/captions-button-element';
import { CastButtonElement } from '../../ui/cast-button/cast-button-element';
import { FullscreenButtonElement } from '../../ui/fullscreen-button/fullscreen-button-element';
import { LiveButtonElement } from '../../ui/live-button/live-button-element';
import { MuteButtonElement } from '../../ui/mute-button/mute-button-element';
import { PiPButtonElement } from '../../ui/pip-button/pip-button-element';
import { PlayButtonElement } from '../../ui/play-button/play-button-element';
import { PopoverElement } from '../../ui/popover/popover-element';
import { PosterElement } from '../../ui/poster/poster-element';
import { TooltipElement } from '../../ui/tooltip/tooltip-element';
import { TooltipGroupElement } from '../../ui/tooltip/tooltip-group-element';
import { safeDefine } from '../safe-define';
import { defineControls, defineErrorDialog, defineTime, defineTimeSlider, defineVolumeSlider } from '../ui/compounds';
// Value import — player.ts body runs before this module's body.
import { LiveVideoPlayerElement } from './player';
// ── Registration (providers / parents first) ────────────────────────────
safeDefine(LiveVideoPlayerElement);
safeDefine(MediaContainerElement);
// Compound groups.
defineControls();
defineErrorDialog();
defineTimeSlider();
defineVolumeSlider();
defineTime();
// Standalone elements.
safeDefine(BufferingIndicatorElement);
safeDefine(CaptionsButtonElement);
safeDefine(CastButtonElement);
safeDefine(FullscreenButtonElement);
safeDefine(LiveButtonElement);
safeDefine(MuteButtonElement);
safeDefine(PiPButtonElement);
safeDefine(PlayButtonElement);
safeDefine(PopoverElement);
safeDefine(PosterElement);
safeDefine(TooltipElement);
safeDefine(TooltipGroupElement);
@@ -0,0 +1,23 @@
import { liveVideoFeatures } from '@videojs/core/dom';
import { MediaContainerElement } from '../../media/container-element';
import { createPlayer } from '../../player/create-player';
import { MediaElement } from '../../ui/media-element';
import { safeDefine } from '../safe-define';
const { ProviderMixin } = createPlayer({
features: liveVideoFeatures,
});
export class LiveVideoPlayerElement extends ProviderMixin(MediaElement) {
static readonly tagName = 'live-video-player';
}
// Provider must be defined before consumer for context handshake during upgrade.
safeDefine(LiveVideoPlayerElement);
safeDefine(MediaContainerElement);
declare global {
interface HTMLElementTagNameMap {
[LiveVideoPlayerElement.tagName]: LiveVideoPlayerElement;
}
}
@@ -19,8 +19,8 @@ import { cn } from '@videojs/utils/style';
import { safeDefine } from '../safe-define';
import { SkinElement } from '../skin-element';
// Reuse the video preset's UI element registrations.
import '../video/ui';
// Register the live video player, container, and all UI custom elements.
import './ui';
function getTemplateHTML() {
return /*html*/ `
@@ -60,6 +60,8 @@ function getTemplateHTML() {
${renderIcon('pause', { class: cn(icon, iconState.play.pause) })}
</media-play-button>
<media-tooltip id="play-tooltip" side="top" class="${cn(popup.tooltip)}"></media-tooltip>
<media-live-button class="${cn(button.base, button.subtle, button.live)}"></media-live-button>
</div>
<div class="grow" aria-hidden="true"></div>
+4 -3
View File
@@ -4,9 +4,8 @@ import { safeDefine } from '../safe-define';
import { SkinElement } from '../skin-element';
import styles from './skin.css?inline';
// Reuse the video preset's UI element registrations (player, container,
// controls, buttons, etc.) — the live variant only differs in its template.
import '../video/ui';
// Register the live video player, container, and all UI custom elements.
import './ui';
function getTemplateHTML() {
return /*html*/ `
@@ -46,6 +45,8 @@ function getTemplateHTML() {
${renderIcon('pause', { class: 'media-icon media-icon--pause' })}
</media-play-button>
<media-tooltip id="play-tooltip" side="top" class="media-surface media-tooltip"></media-tooltip>
<media-live-button class="media-button media-button--subtle media-button--live"></media-live-button>
</div>
<div class="media-time-controls" aria-hidden="true"></div>
+51
View File
@@ -0,0 +1,51 @@
// Registers the live video player, container, and all video UI custom
// elements without creating a skin element. Use this entry when building an
// ejected (light DOM) player layout for live HLS / DASH streams.
import { MediaContainerElement } from '../../media/container-element';
import { BufferingIndicatorElement } from '../../ui/buffering-indicator/buffering-indicator-element';
import { CaptionsButtonElement } from '../../ui/captions-button/captions-button-element';
import { CastButtonElement } from '../../ui/cast-button/cast-button-element';
import { FullscreenButtonElement } from '../../ui/fullscreen-button/fullscreen-button-element';
import { GestureElement } from '../../ui/gesture/gesture-element';
import { HotkeyElement } from '../../ui/hotkey/hotkey-element';
import { LiveButtonElement } from '../../ui/live-button/live-button-element';
import { MuteButtonElement } from '../../ui/mute-button/mute-button-element';
import { PiPButtonElement } from '../../ui/pip-button/pip-button-element';
import { PlayButtonElement } from '../../ui/play-button/play-button-element';
import { PopoverElement } from '../../ui/popover/popover-element';
import { PosterElement } from '../../ui/poster/poster-element';
import { TooltipElement } from '../../ui/tooltip/tooltip-element';
import { TooltipGroupElement } from '../../ui/tooltip/tooltip-group-element';
import { safeDefine } from '../safe-define';
import { defineControls, defineErrorDialog, defineTime, defineTimeSlider, defineVolumeSlider } from '../ui/compounds';
// Value import — player.ts body runs before this module's body.
import { LiveVideoPlayerElement } from './player';
// ── Registration (providers / parents first) ────────────────────────────
safeDefine(LiveVideoPlayerElement);
safeDefine(MediaContainerElement);
// Compound groups.
defineControls();
defineErrorDialog();
defineTimeSlider();
defineVolumeSlider();
defineTime();
// Standalone elements.
safeDefine(BufferingIndicatorElement);
safeDefine(CaptionsButtonElement);
safeDefine(CastButtonElement);
safeDefine(FullscreenButtonElement);
safeDefine(GestureElement);
safeDefine(HotkeyElement);
safeDefine(LiveButtonElement);
safeDefine(MuteButtonElement);
safeDefine(PiPButtonElement);
safeDefine(PlayButtonElement);
safeDefine(PopoverElement);
safeDefine(PosterElement);
safeDefine(TooltipElement);
safeDefine(TooltipGroupElement);
+2
View File
@@ -8,6 +8,7 @@ import { CastButtonElement } from '../../ui/cast-button/cast-button-element';
import { FullscreenButtonElement } from '../../ui/fullscreen-button/fullscreen-button-element';
import { GestureElement } from '../../ui/gesture/gesture-element';
import { HotkeyElement } from '../../ui/hotkey/hotkey-element';
import { LiveButtonElement } from '../../ui/live-button/live-button-element';
import { MuteButtonElement } from '../../ui/mute-button/mute-button-element';
import { PiPButtonElement } from '../../ui/pip-button/pip-button-element';
import { PlayButtonElement } from '../../ui/play-button/play-button-element';
@@ -42,6 +43,7 @@ safeDefine(CastButtonElement);
safeDefine(FullscreenButtonElement);
safeDefine(GestureElement);
safeDefine(HotkeyElement);
safeDefine(LiveButtonElement);
safeDefine(MuteButtonElement);
safeDefine(PiPButtonElement);
safeDefine(PlayButtonElement);