feat(packages): add live-video and live-audio presets (#1399)

This commit is contained in:
Wesley Luyten
2026-04-22 12:43:27 -07:00
committed by GitHub
parent 3a66c4d25c
commit d9c00491e7
49 changed files with 2552 additions and 51 deletions
@@ -0,0 +1,3 @@
@import "../base.css";
@import "../shared.css";
@import "@videojs/skins/minimal/css/audio.css";
@@ -0,0 +1,86 @@
import { renderIcon } from '@videojs/icons/render/minimal';
import {
button,
buttonGroup,
controls,
error,
icon,
iconState,
popup,
root,
slider,
} from '@videojs/skins/minimal/tailwind/audio.tailwind';
import { createTemplate } from '@videojs/utils/dom';
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';
function getTemplateHTML() {
return /*html*/ `
<media-container class="${root}">
<!-- @deprecated slot="media" is no longer required, use the default slot instead -->
<slot name="media"></slot>
<slot></slot>
<media-error-dialog class="${error.root}">
<div class="${error.dialog}">
<div class="${error.content}">
<media-alert-dialog-title class="${error.title}">Something went wrong.</media-alert-dialog-title>
<media-alert-dialog-description class="${error.description}"></media-alert-dialog-description>
</div>
<div class="${error.actions}">
<media-alert-dialog-close class="${cn(button.base, button.subtle)}">OK</media-alert-dialog-close>
</div>
</div>
</media-error-dialog>
<div class="${controls}">
<media-tooltip-group>
<div class="${buttonGroup}">
<media-play-button commandfor="play-tooltip" class="${cn(button.base, button.subtle, button.icon, iconState.play.button)}">
${renderIcon('restart', { class: cn(icon, iconState.play.restart) })}
${renderIcon('play', { class: cn(icon, iconState.play.play) })}
${renderIcon('pause', { class: cn(icon, iconState.play.pause) })}
</media-play-button>
<media-tooltip id="play-tooltip" side="top" class="${cn(popup.tooltip)}"></media-tooltip>
</div>
<div class="grow" aria-hidden="true"></div>
<div class="${buttonGroup}">
<media-mute-button commandfor="live-audio-volume-popover" class="${cn(button.base, button.subtle, button.icon, iconState.mute.button)}">
${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })}
${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })}
${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })}
</media-mute-button>
<media-popover id="live-audio-volume-popover" open-on-hover delay="200" close-delay="100" side="left" class="${cn(popup.volume)}">
<media-volume-slider class="${slider.root}" orientation="horizontal" thumb-alignment="edge">
<media-slider-track class="${slider.track}">
<media-slider-fill class="${cn(slider.fill.base, slider.fill.fill)}"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="${slider.thumb.base}"></media-slider-thumb>
</media-volume-slider>
</media-popover>
</div>
</media-tooltip-group>
</div>
</media-container>
`;
}
export class MinimalLiveAudioSkinTailwindElement extends SkinElement {
static readonly tagName = 'live-audio-minimal-skin-tailwind';
static template = createTemplate(getTemplateHTML());
}
safeDefine(MinimalLiveAudioSkinTailwindElement);
declare global {
interface HTMLElementTagNameMap {
[MinimalLiveAudioSkinTailwindElement.tagName]: MinimalLiveAudioSkinTailwindElement;
}
}
@@ -0,0 +1,76 @@
import { renderIcon } from '@videojs/icons/render/minimal';
import { createShadowStyle, createTemplate } from '@videojs/utils/dom';
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';
function getTemplateHTML() {
return /*html*/ `
<media-container class="media-minimal-skin media-minimal-skin--audio">
<!-- @deprecated slot="media" is no longer required, use the default slot instead -->
<slot name="media"></slot>
<slot></slot>
<media-error-dialog class="media-error">
<div class="media-error__dialog">
<div class="media-error__content">
<media-alert-dialog-title class="media-error__title">Something went wrong.</media-alert-dialog-title>
<media-alert-dialog-description class="media-error__description"></media-alert-dialog-description>
</div>
<div class="media-error__actions">
<media-alert-dialog-close class="media-button media-button--subtle">OK</media-alert-dialog-close>
</div>
</div>
</media-error-dialog>
<div class="media-controls">
<media-tooltip-group>
<div class="media-button-group">
<media-play-button commandfor="play-tooltip" class="media-button media-button--subtle media-button--icon media-button--play">
${renderIcon('restart', { class: 'media-icon media-icon--restart' })}
${renderIcon('play', { class: 'media-icon media-icon--play' })}
${renderIcon('pause', { class: 'media-icon media-icon--pause' })}
</media-play-button>
<media-tooltip id="play-tooltip" side="top" class="media-tooltip"></media-tooltip>
</div>
<div class="media-time-controls" aria-hidden="true"></div>
<div class="media-button-group">
<media-mute-button commandfor="live-audio-volume-popover" class="media-button media-button--subtle media-button--icon media-button--mute">
${renderIcon('volume-off', { class: 'media-icon media-icon--volume-off' })}
${renderIcon('volume-low', { class: 'media-icon media-icon--volume-low' })}
${renderIcon('volume-high', { class: 'media-icon media-icon--volume-high' })}
</media-mute-button>
<media-popover id="live-audio-volume-popover" open-on-hover delay="200" close-delay="100" side="left" class="media-popover media-popover--volume">
<media-volume-slider class="media-slider" orientation="horizontal" thumb-alignment="edge">
<media-slider-track class="media-slider__track">
<media-slider-fill class="media-slider__fill"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="media-slider__thumb media-slider__thumb--persistent"></media-slider-thumb>
</media-volume-slider>
</media-popover>
</div>
</media-tooltip-group>
</div>
</media-container>
`;
}
export class MinimalLiveAudioSkinElement extends SkinElement {
static readonly tagName = 'live-audio-minimal-skin';
static styles = createShadowStyle(styles);
static template = createTemplate(getTemplateHTML());
}
safeDefine(MinimalLiveAudioSkinElement);
declare global {
interface HTMLElementTagNameMap {
[MinimalLiveAudioSkinElement.tagName]: MinimalLiveAudioSkinElement;
}
}
@@ -0,0 +1,3 @@
@import "../base.css";
@import "../shared.css";
@import "@videojs/skins/default/css/audio.css";
@@ -0,0 +1,86 @@
import { renderIcon } from '@videojs/icons/render';
import {
button,
buttonGroup,
controls,
error,
icon,
iconState,
popup,
root,
slider,
} from '@videojs/skins/default/tailwind/audio.tailwind';
import { createTemplate } from '@videojs/utils/dom';
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';
function getTemplateHTML() {
return /*html*/ `
<media-container class="${root}">
<!-- @deprecated slot="media" is no longer required, use the default slot instead -->
<slot name="media"></slot>
<slot></slot>
<media-error-dialog class="${error.root}">
<div class="${error.dialog}">
<div class="${error.content}">
<media-alert-dialog-title class="${error.title}">Something went wrong.</media-alert-dialog-title>
<media-alert-dialog-description class="${error.description}"></media-alert-dialog-description>
</div>
<div class="${error.actions}">
<media-alert-dialog-close class="${cn(button.base, button.subtle)}">OK</media-alert-dialog-close>
</div>
</div>
</media-error-dialog>
<div class="${controls}">
<media-tooltip-group>
<div class="${buttonGroup}">
<media-play-button commandfor="play-tooltip" class="${cn(button.base, button.subtle, button.icon, iconState.play.button)}">
${renderIcon('restart', { class: cn(icon, iconState.play.restart) })}
${renderIcon('play', { class: cn(icon, iconState.play.play) })}
${renderIcon('pause', { class: cn(icon, iconState.play.pause) })}
</media-play-button>
<media-tooltip id="play-tooltip" side="top" class="${cn(popup.tooltip)}"></media-tooltip>
</div>
<div class="grow" aria-hidden="true"></div>
<div class="${buttonGroup}">
<media-mute-button commandfor="live-audio-volume-popover" class="${cn(button.base, button.subtle, button.icon, iconState.mute.button)}">
${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })}
${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })}
${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })}
</media-mute-button>
<media-popover id="live-audio-volume-popover" open-on-hover delay="200" close-delay="100" side="top" class="${cn(popup.popover, popup.volume)}">
<media-volume-slider class="${slider.root}" orientation="vertical" thumb-alignment="edge">
<media-slider-track class="${slider.track}">
<media-slider-fill class="${cn(slider.fill.base, slider.fill.fill)}"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="${cn(slider.thumb.base, slider.thumb.persistent)}"></media-slider-thumb>
</media-volume-slider>
</media-popover>
</div>
</media-tooltip-group>
</div>
</media-container>
`;
}
export class LiveAudioSkinTailwindElement extends SkinElement {
static readonly tagName = 'live-audio-skin-tailwind';
static template = createTemplate(getTemplateHTML());
}
safeDefine(LiveAudioSkinTailwindElement);
declare global {
interface HTMLElementTagNameMap {
[LiveAudioSkinTailwindElement.tagName]: LiveAudioSkinTailwindElement;
}
}
@@ -0,0 +1,83 @@
import { renderIcon } from '@videojs/icons/render';
import { createShadowStyle, createTemplate } from '@videojs/utils/dom';
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';
function getTemplateHTML() {
return /*html*/ `
<media-container class="media-default-skin media-default-skin--audio">
<!-- @deprecated slot="media" is no longer required, use the default slot instead -->
<slot name="media"></slot>
<slot></slot>
<media-error-dialog class="media-error">
<div class="media-error__dialog">
<div class="media-error__content">
<media-alert-dialog-title class="media-error__title">Something went wrong.</media-alert-dialog-title>
<media-alert-dialog-description class="media-error__description"></media-alert-dialog-description>
</div>
<div class="media-error__actions">
<media-alert-dialog-close class="media-button media-button--subtle">OK</media-alert-dialog-close>
</div>
</div>
</media-error-dialog>
<div class="media-surface media-controls">
<media-tooltip-group>
<div class="media-button-group">
<media-play-button commandfor="play-tooltip" class="media-button media-button--subtle media-button--icon media-button--play">
${renderIcon('restart', { class: 'media-icon media-icon--restart' })}
${renderIcon('play', { class: 'media-icon media-icon--play' })}
${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>
</div>
<div class="media-time-controls" aria-hidden="true"></div>
<div class="media-button-group">
<media-mute-button commandfor="live-audio-volume-popover" class="media-button media-button--subtle media-button--icon media-button--mute">
${renderIcon('volume-off', { class: 'media-icon media-icon--volume-off' })}
${renderIcon('volume-low', { class: 'media-icon media-icon--volume-low' })}
${renderIcon('volume-high', { class: 'media-icon media-icon--volume-high' })}
</media-mute-button>
<media-popover id="live-audio-volume-popover" open-on-hover delay="200" close-delay="100" side="top" class="media-surface media-popover media-popover--volume">
<media-volume-slider class="media-slider" orientation="vertical" thumb-alignment="edge">
<media-slider-track class="media-slider__track">
<media-slider-fill class="media-slider__fill"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="media-slider__thumb media-slider__thumb--persistent"></media-slider-thumb>
</media-volume-slider>
</media-popover>
</div>
</media-tooltip-group>
</div>
<!-- Hotkeys -->
<media-hotkey keys="Space" action="togglePaused"></media-hotkey>
<media-hotkey keys="k" action="togglePaused"></media-hotkey>
<media-hotkey keys="m" action="toggleMuted"></media-hotkey>
<media-hotkey keys="ArrowUp" action="volumeStep" value="0.05"></media-hotkey>
<media-hotkey keys="ArrowDown" action="volumeStep" value="-0.05"></media-hotkey>
</media-container>
`;
}
export class LiveAudioSkinElement extends SkinElement {
static readonly tagName = 'live-audio-skin';
static styles = createShadowStyle(styles);
static template = createTemplate(getTemplateHTML());
}
safeDefine(LiveAudioSkinElement);
declare global {
interface HTMLElementTagNameMap {
[LiveAudioSkinElement.tagName]: LiveAudioSkinElement;
}
}
@@ -0,0 +1,3 @@
@import "../base.css";
@import "../shared.css";
@import "@videojs/skins/minimal/css/video.css";
@@ -0,0 +1,120 @@
import { renderIcon } from '@videojs/icons/render/minimal';
import {
bufferingIndicator,
button,
buttonGroupEnd,
buttonGroupStart,
controls,
error,
icon,
iconState,
overlay,
popup,
poster,
root,
slider,
} from '@videojs/skins/minimal/tailwind/video.tailwind';
import { createTemplate } from '@videojs/utils/dom';
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';
function getTemplateHTML() {
return /*html*/ `
<media-container class="${root(true)}">
<!-- @deprecated slot="media" is no longer required, use the default slot instead -->
<slot name="media"></slot>
<slot></slot>
<media-poster class="${poster(true)}">
<slot name="poster"></slot>
</media-poster>
<media-buffering-indicator class="${bufferingIndicator}">
${renderIcon('spinner')}
</media-buffering-indicator>
<media-error-dialog class="${error.root}">
<div class="${error.dialog}">
<div class="${error.content}">
<media-alert-dialog-title class="${error.title}">Something went wrong.</media-alert-dialog-title>
<media-alert-dialog-description class="${error.description}"></media-alert-dialog-description>
</div>
<div class="${error.actions}">
<media-alert-dialog-close class="${cn(button.base, button.primary)}">OK</media-alert-dialog-close>
</div>
</div>
</media-error-dialog>
<media-controls data-controls="" class="${controls}">
<media-tooltip-group>
<div class="${buttonGroupStart}">
<media-play-button commandfor="play-tooltip" class="${cn(button.base, button.subtle, button.icon, iconState.play.button)}">
${renderIcon('restart', { class: cn(icon, iconState.play.restart) })}
${renderIcon('play', { class: cn(icon, iconState.play.play) })}
${renderIcon('pause', { class: cn(icon, iconState.play.pause) })}
</media-play-button>
<media-tooltip id="play-tooltip" side="top" class="${cn(popup.tooltip)}"></media-tooltip>
</div>
<div class="grow" aria-hidden="true"></div>
<div class="${buttonGroupEnd}">
<media-mute-button commandfor="live-video-volume-popover" class="${cn(button.base, button.subtle, button.icon, iconState.mute.button)}">
${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })}
${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })}
${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })}
</media-mute-button>
<media-popover id="live-video-volume-popover" open-on-hover delay="200" close-delay="100" side="top" class="${cn(popup.volume)}">
<media-volume-slider class="${slider.root}" orientation="vertical" thumb-alignment="edge">
<media-slider-track class="${slider.track}">
<media-slider-fill class="${cn(slider.fill.base, slider.fill.fill)}"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="${slider.thumb.base}"></media-slider-thumb>
</media-volume-slider>
</media-popover>
<media-captions-button commandfor="captions-tooltip" class="${cn(button.base, button.subtle, button.icon, iconState.captions.button)}">
${renderIcon('captions-off', { class: cn(icon, iconState.captions.off) })}
${renderIcon('captions-on', { class: cn(icon, iconState.captions.on) })}
</media-captions-button>
<media-tooltip id="captions-tooltip" side="top" class="${cn(popup.tooltip)}"></media-tooltip>
<media-cast-button commandfor="cast-tooltip" class="${cn(button.base, button.subtle, button.icon, iconState.cast.button)}">
${renderIcon('cast-enter', { class: cn(icon, iconState.cast.enter) })}
${renderIcon('cast-exit', { class: cn(icon, iconState.cast.exit) })}
</media-cast-button>
<media-tooltip id="cast-tooltip" side="top" class="${cn(popup.tooltip)}"></media-tooltip>
<media-pip-button commandfor="pip-tooltip" class="${cn(button.base, button.subtle, button.icon, iconState.pip.button)}">
${renderIcon('pip-enter', { class: cn(icon, iconState.pip.off) })}
${renderIcon('pip-exit', { class: cn(icon, iconState.pip.on) })}
</media-pip-button>
<media-tooltip id="pip-tooltip" side="top" class="${cn(popup.tooltip)}"></media-tooltip>
<media-fullscreen-button commandfor="fullscreen-tooltip" class="${cn(button.base, button.subtle, button.icon, iconState.fullscreen.button)}">
${renderIcon('fullscreen-enter', { class: cn(icon, iconState.fullscreen.enter) })}
${renderIcon('fullscreen-exit', { class: cn(icon, iconState.fullscreen.exit) })}
</media-fullscreen-button>
<media-tooltip id="fullscreen-tooltip" side="top" class="${cn(popup.tooltip)}"></media-tooltip>
</div>
</media-tooltip-group>
</media-controls>
<div class="${overlay}"></div>
</media-container>
`;
}
export class MinimalLiveVideoSkinTailwindElement extends SkinElement {
static readonly tagName = 'live-video-minimal-skin-tailwind';
static template = createTemplate(getTemplateHTML());
}
safeDefine(MinimalLiveVideoSkinTailwindElement);
declare global {
interface HTMLElementTagNameMap {
[MinimalLiveVideoSkinTailwindElement.tagName]: MinimalLiveVideoSkinTailwindElement;
}
}
@@ -0,0 +1,112 @@
import { renderIcon } from '@videojs/icons/render/minimal';
import { createShadowStyle, createTemplate } from '@videojs/utils/dom';
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';
function getTemplateHTML() {
return /*html*/ `
<media-container class="media-minimal-skin media-minimal-skin--video">
<!-- @deprecated slot="media" is no longer required, use the default slot instead -->
<slot name="media"></slot>
<slot></slot>
<media-poster>
<slot name="poster"></slot>
</media-poster>
<media-buffering-indicator class="media-buffering-indicator">
${renderIcon('spinner', { class: 'media-icon' })}
</media-buffering-indicator>
<media-error-dialog class="media-error">
<div class="media-error__dialog">
<div class="media-error__content">
<media-alert-dialog-title class="media-error__title">Something went wrong.</media-alert-dialog-title>
<media-alert-dialog-description class="media-error__description"></media-alert-dialog-description>
</div>
<div class="media-error__actions">
<media-alert-dialog-close class="media-button media-button--primary">OK</media-alert-dialog-close>
</div>
</div>
</media-error-dialog>
<media-controls class="media-controls">
<media-tooltip-group>
<div class="media-button-group">
<media-play-button commandfor="play-tooltip" class="media-button media-button--subtle media-button--icon media-button--play">
${renderIcon('restart', { class: 'media-icon media-icon--restart' })}
${renderIcon('play', { class: 'media-icon media-icon--play' })}
${renderIcon('pause', { class: 'media-icon media-icon--pause' })}
</media-play-button>
<media-tooltip id="play-tooltip" side="top" class="media-tooltip"></media-tooltip>
</div>
<div class="media-time-controls" aria-hidden="true"></div>
<div class="media-button-group">
<media-mute-button commandfor="live-video-volume-popover" class="media-button media-button--subtle media-button--icon media-button--mute">
${renderIcon('volume-off', { class: 'media-icon media-icon--volume-off' })}
${renderIcon('volume-low', { class: 'media-icon media-icon--volume-low' })}
${renderIcon('volume-high', { class: 'media-icon media-icon--volume-high' })}
</media-mute-button>
<media-popover id="live-video-volume-popover" open-on-hover delay="200" close-delay="100" side="top" class="media-popover media-popover--volume">
<media-volume-slider class="media-slider" orientation="vertical" thumb-alignment="edge">
<media-slider-track class="media-slider__track">
<media-slider-fill class="media-slider__fill"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="media-slider__thumb media-slider__thumb--persistent"></media-slider-thumb>
</media-volume-slider>
</media-popover>
<media-captions-button commandfor="captions-tooltip" class="media-button media-button--subtle media-button--icon media-button--captions">
${renderIcon('captions-off', { class: 'media-icon media-icon--captions-off' })}
${renderIcon('captions-on', { class: 'media-icon media-icon--captions-on' })}
</media-captions-button>
<media-tooltip id="captions-tooltip" side="top" class="media-tooltip">
Toggle captions
</media-tooltip>
<media-cast-button commandfor="cast-tooltip" class="media-button media-button--subtle media-button--icon media-button--cast">
${renderIcon('cast-enter', { class: 'media-icon media-icon--cast-enter' })}
${renderIcon('cast-exit', { class: 'media-icon media-icon--cast-exit' })}
</media-cast-button>
<media-tooltip id="cast-tooltip" side="top" class="media-tooltip"></media-tooltip>
<media-pip-button commandfor="pip-tooltip" class="media-button media-button--subtle media-button--icon media-button--pip">
${renderIcon('pip-enter', { class: 'media-icon media-icon--pip-enter' })}
${renderIcon('pip-exit', { class: 'media-icon media-icon--pip-exit' })}
</media-pip-button>
<media-tooltip id="pip-tooltip" side="top" class="media-tooltip"></media-tooltip>
<media-fullscreen-button commandfor="fullscreen-tooltip" class="media-button media-button--subtle media-button--icon media-button--fullscreen">
${renderIcon('fullscreen-enter', { class: 'media-icon media-icon--fullscreen-enter' })}
${renderIcon('fullscreen-exit', { class: 'media-icon media-icon--fullscreen-exit' })}
</media-fullscreen-button>
<media-tooltip id="fullscreen-tooltip" side="top" class="media-tooltip"></media-tooltip>
</div>
</media-tooltip-group>
</media-controls>
<div class="media-overlay"></div>
</media-container>
`;
}
export class MinimalLiveVideoSkinElement extends SkinElement {
static readonly tagName = 'live-video-minimal-skin';
static styles = createShadowStyle(styles);
static template = createTemplate(getTemplateHTML());
}
safeDefine(MinimalLiveVideoSkinElement);
declare global {
interface HTMLElementTagNameMap {
[MinimalLiveVideoSkinElement.tagName]: MinimalLiveVideoSkinElement;
}
}
@@ -0,0 +1,3 @@
@import "../base.css";
@import "../shared.css";
@import "@videojs/skins/default/css/video.css";
@@ -0,0 +1,122 @@
import { renderIcon } from '@videojs/icons/render';
import {
bufferingIndicator,
button,
buttonGroupEnd,
buttonGroupStart,
controls,
error,
icon,
iconState,
overlay,
popup,
poster,
root,
slider,
} from '@videojs/skins/default/tailwind/video.tailwind';
import { createTemplate } from '@videojs/utils/dom';
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';
function getTemplateHTML() {
return /*html*/ `
<media-container class="${root(true)}">
<!-- @deprecated slot="media" is no longer required, use the default slot instead -->
<slot name="media"></slot>
<slot></slot>
<media-poster class="${poster(true)}">
<slot name="poster"></slot>
</media-poster>
<media-buffering-indicator class="${bufferingIndicator.root}">
<div class="${bufferingIndicator.container}">
${renderIcon('spinner')}
</div>
</media-buffering-indicator>
<media-error-dialog class="${error.root}">
<div class="${error.dialog}">
<div class="${error.content}">
<media-alert-dialog-title class="${error.title}">Something went wrong.</media-alert-dialog-title>
<media-alert-dialog-description class="${error.description}"></media-alert-dialog-description>
</div>
<div class="${error.actions}">
<media-alert-dialog-close class="${cn(button.base, button.primary)}">OK</media-alert-dialog-close>
</div>
</div>
</media-error-dialog>
<media-controls data-controls="" class="${controls}">
<media-tooltip-group>
<div class="${buttonGroupStart}">
<media-play-button commandfor="play-tooltip" class="${cn(button.base, button.subtle, button.icon, iconState.play.button)}">
${renderIcon('restart', { class: cn(icon, iconState.play.restart) })}
${renderIcon('play', { class: cn(icon, iconState.play.play) })}
${renderIcon('pause', { class: cn(icon, iconState.play.pause) })}
</media-play-button>
<media-tooltip id="play-tooltip" side="top" class="${cn(popup.tooltip)}"></media-tooltip>
</div>
<div class="grow" aria-hidden="true"></div>
<div class="${buttonGroupEnd}">
<media-mute-button commandfor="live-video-volume-popover" class="${cn(button.base, button.subtle, button.icon, iconState.mute.button)}">
${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })}
${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })}
${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })}
</media-mute-button>
<media-popover id="live-video-volume-popover" open-on-hover delay="200" close-delay="100" side="top" class="${cn(popup.popover, popup.volume)}">
<media-volume-slider class="${slider.root}" orientation="vertical" thumb-alignment="edge">
<media-slider-track class="${slider.track}">
<media-slider-fill class="${cn(slider.fill.base, slider.fill.fill)}"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="${cn(slider.thumb.base, slider.thumb.persistent)}"></media-slider-thumb>
</media-volume-slider>
</media-popover>
<media-captions-button commandfor="captions-tooltip" class="${cn(button.base, button.subtle, button.icon, iconState.captions.button)}">
${renderIcon('captions-off', { class: cn(icon, iconState.captions.off) })}
${renderIcon('captions-on', { class: cn(icon, iconState.captions.on) })}
</media-captions-button>
<media-tooltip id="captions-tooltip" side="top" class="${cn(popup.tooltip)}"></media-tooltip>
<media-cast-button commandfor="cast-tooltip" class="${cn(button.base, button.subtle, button.icon, iconState.cast.button)}">
${renderIcon('cast-enter', { class: cn(icon, iconState.cast.enter) })}
${renderIcon('cast-exit', { class: cn(icon, iconState.cast.exit) })}
</media-cast-button>
<media-tooltip id="cast-tooltip" side="top" class="${cn(popup.tooltip)}"></media-tooltip>
<media-pip-button commandfor="pip-tooltip" class="${cn(button.base, button.subtle, button.icon, iconState.pip.button)}">
${renderIcon('pip-enter', { class: cn(icon, iconState.pip.off) })}
${renderIcon('pip-exit', { class: cn(icon, iconState.pip.on) })}
</media-pip-button>
<media-tooltip id="pip-tooltip" side="top" class="${cn(popup.tooltip)}"></media-tooltip>
<media-fullscreen-button commandfor="fullscreen-tooltip" class="${cn(button.base, button.subtle, button.icon, iconState.fullscreen.button)}">
${renderIcon('fullscreen-enter', { class: cn(icon, iconState.fullscreen.enter) })}
${renderIcon('fullscreen-exit', { class: cn(icon, iconState.fullscreen.exit) })}
</media-fullscreen-button>
<media-tooltip id="fullscreen-tooltip" side="top" class="${cn(popup.tooltip)}"></media-tooltip>
</div>
</media-tooltip-group>
</media-controls>
<div class="${overlay}"></div>
</media-container>
`;
}
export class LiveVideoSkinTailwindElement extends SkinElement {
static readonly tagName = 'live-video-skin-tailwind';
static template = createTemplate(getTemplateHTML());
}
safeDefine(LiveVideoSkinTailwindElement);
declare global {
interface HTMLElementTagNameMap {
[LiveVideoSkinTailwindElement.tagName]: LiveVideoSkinTailwindElement;
}
}
+128
View File
@@ -0,0 +1,128 @@
import { renderIcon } from '@videojs/icons/render';
import { createShadowStyle, createTemplate } from '@videojs/utils/dom';
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';
function getTemplateHTML() {
return /*html*/ `
<media-container class="media-default-skin media-default-skin--video">
<!-- @deprecated slot="media" is no longer required, use the default slot instead -->
<slot name="media"></slot>
<slot></slot>
<media-poster>
<slot name="poster"></slot>
</media-poster>
<media-buffering-indicator class="media-buffering-indicator">
<div class="media-surface">
${renderIcon('spinner', { class: 'media-icon' })}
</div>
</media-buffering-indicator>
<media-error-dialog class="media-error">
<div class="media-error__dialog media-surface">
<div class="media-error__content">
<media-alert-dialog-title class="media-error__title">Something went wrong.</media-alert-dialog-title>
<media-alert-dialog-description class="media-error__description"></media-alert-dialog-description>
</div>
<div class="media-error__actions">
<media-alert-dialog-close class="media-button media-button--primary">OK</media-alert-dialog-close>
</div>
</div>
</media-error-dialog>
<media-controls class="media-surface media-controls">
<media-tooltip-group>
<div class="media-button-group">
<media-play-button commandfor="play-tooltip" class="media-button media-button--subtle media-button--icon media-button--play">
${renderIcon('restart', { class: 'media-icon media-icon--restart' })}
${renderIcon('play', { class: 'media-icon media-icon--play' })}
${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>
</div>
<div class="media-time-controls" aria-hidden="true"></div>
<div class="media-button-group">
<media-mute-button commandfor="live-video-volume-popover" class="media-button media-button--subtle media-button--icon media-button--mute">
${renderIcon('volume-off', { class: 'media-icon media-icon--volume-off' })}
${renderIcon('volume-low', { class: 'media-icon media-icon--volume-low' })}
${renderIcon('volume-high', { class: 'media-icon media-icon--volume-high' })}
</media-mute-button>
<media-popover id="live-video-volume-popover" open-on-hover delay="200" close-delay="100" side="top" class="media-surface media-popover media-popover--volume">
<media-volume-slider class="media-slider" orientation="vertical" thumb-alignment="edge">
<media-slider-track class="media-slider__track">
<media-slider-fill class="media-slider__fill"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="media-slider__thumb media-slider__thumb--persistent"></media-slider-thumb>
</media-volume-slider>
</media-popover>
<media-captions-button commandfor="captions-tooltip" class="media-button media-button--subtle media-button--icon media-button--captions">
${renderIcon('captions-off', { class: 'media-icon media-icon--captions-off' })}
${renderIcon('captions-on', { class: 'media-icon media-icon--captions-on' })}
</media-captions-button>
<media-tooltip id="captions-tooltip" side="top" class="media-surface media-tooltip"></media-tooltip>
<media-cast-button commandfor="cast-tooltip" class="media-button media-button--subtle media-button--icon media-button--cast">
${renderIcon('cast-enter', { class: 'media-icon media-icon--cast-enter' })}
${renderIcon('cast-exit', { class: 'media-icon media-icon--cast-exit' })}
</media-cast-button>
<media-tooltip id="cast-tooltip" side="top" class="media-surface media-tooltip"></media-tooltip>
<media-pip-button commandfor="pip-tooltip" class="media-button media-button--subtle media-button--icon media-button--pip">
${renderIcon('pip-enter', { class: 'media-icon media-icon--pip-enter' })}
${renderIcon('pip-exit', { class: 'media-icon media-icon--pip-exit' })}
</media-pip-button>
<media-tooltip id="pip-tooltip" side="top" class="media-surface media-tooltip"></media-tooltip>
<media-fullscreen-button commandfor="fullscreen-tooltip" class="media-button media-button--subtle media-button--icon media-button--fullscreen">
${renderIcon('fullscreen-enter', { class: 'media-icon media-icon--fullscreen-enter' })}
${renderIcon('fullscreen-exit', { class: 'media-icon media-icon--fullscreen-exit' })}
</media-fullscreen-button>
<media-tooltip id="fullscreen-tooltip" side="top" class="media-surface media-tooltip"></media-tooltip>
</div>
</media-tooltip-group>
</media-controls>
<div class="media-overlay"></div>
<!-- Hotkeys -->
<media-hotkey keys="Space" action="togglePaused"></media-hotkey>
<media-hotkey keys="k" action="togglePaused"></media-hotkey>
<media-hotkey keys="m" action="toggleMuted"></media-hotkey>
<media-hotkey keys="f" action="toggleFullscreen"></media-hotkey>
<media-hotkey keys="c" action="toggleSubtitles"></media-hotkey>
<media-hotkey keys="i" action="togglePictureInPicture"></media-hotkey>
<media-hotkey keys="ArrowUp" action="volumeStep" value="0.05"></media-hotkey>
<media-hotkey keys="ArrowDown" action="volumeStep" value="-0.05"></media-hotkey>
<!-- Gestures -->
<media-gesture type="tap" action="togglePaused" pointer="mouse" region="center"></media-gesture>
<media-gesture type="tap" action="toggleControls" pointer="touch"></media-gesture>
<media-gesture type="doubletap" action="toggleFullscreen" region="center"></media-gesture>
</media-container>
`;
}
export class LiveVideoSkinElement extends SkinElement {
static readonly tagName = 'live-video-skin';
static styles = createShadowStyle(styles);
static template = createTemplate(getTemplateHTML());
}
safeDefine(LiveVideoSkinElement);
declare global {
interface HTMLElementTagNameMap {
[LiveVideoSkinElement.tagName]: LiveVideoSkinElement;
}
}