@aidenlx/player
Version:
Headless web components that make integrating media on the a web a breeze.
207 lines (180 loc) • 7.37 kB
text/typescript
import { ifNonEmpty, ifNumber, vdsEvent } from '@vidstack/foundation';
import { CSSResultGroup, html, TemplateResult } from 'lit';
import { property } from 'lit/decorators.js';
import { ref } from 'lit/directives/ref.js';
import { ViewType } from '../../media';
import { Html5MediaElement } from '../html5';
import { VideoFullscreenController } from './fullscreen';
import { VideoPresentationController } from './presentation';
import { videoElementStyles } from './styles';
/**
* Embeds video content into documents via the native `<video>` element. It may contain
* one or more video sources, represented using the `src` attribute or the `<source>` element: the
* browser will choose the most suitable one.
*
* The list of [supported media formats](https://developer.mozilla.org/en-US/docs/Web/Media/Formats)
* varies from one browser to the other. You should either provide your video in a single format
* that all the relevant browsers support, or provide multiple video sources in enough different
* formats that all the browsers you need to support are covered.
*
* 💡 This element contains the exact same interface as the `<video>` element. It redispatches
* all the native events if needed, but prefer the `vds-*` variants (eg: `vds-play`) as they
* iron out any browser issues.
*
* @tagname vds-video
* @slot - Used to pass in `<source>` and `<track>` elements to the underlying HTML5 media player.
* @csspart media - The video element (`<video>`).
* @csspart video - Alias for `media` part.
* @events ./presentation/events.ts
* @example
* ```html
* <vds-video src="/media/video.mp4" poster="/media/poster.png">
* <!-- Additional media resources here. -->
* </vds-video>
* ```
* @example
* ```html
* <vds-video poster="/media/poster.png">
* <source src="/media/video.mp4" type="video/mp4" />
* <track default kind="subtitles" src="/media/subs/en.vtt" srclang="en" label="English" />
* </vds-video>
* ```
*/
export class VideoElement extends Html5MediaElement {
static override get styles(): CSSResultGroup {
return [videoElementStyles];
}
static get parts(): string[] {
return ['root', 'video'];
}
// -------------------------------------------------------------------------------------------
// Properties
// -------------------------------------------------------------------------------------------
/**
* 🧑🔬 **EXPERIMENTAL:** Whether the browser should automatically toggle picture-in-picture mode as
* the user switches back and forth between this document and another document or application.
*/
autoPiP: boolean | undefined;
/**
* 🧑🔬 **EXPERIMENTAL:** Prevents the browser from suggesting a picture-in-picture context menu or
* to request picture-in-picture automatically in some cases.
*
* @link https://w3c.github.io/picture-in-picture/#disable-pip
*/
disablePiP: boolean | undefined;
/**
* A URL for an image to be shown while the video is downloading. If this attribute isn't
* specified, nothing is displayed until the first frame is available, then the first frame is
* shown as the poster frame.
*/
get poster() {
return this.mediaState.currentPoster;
}
set poster(newPoster) {
this._connectedQueue.queue('current-poster', () => {
this.dispatchEvent(vdsEvent('vds-poster-change', { detail: newPoster }));
this.requestUpdate();
});
}
override get mediaElement() {
return this._mediaRef.value as HTMLVideoElement | undefined;
}
get videoElement() {
return this.mediaElement;
}
override get engine() {
return this.mediaElement;
}
get videoEngine() {
return this.videoElement;
}
// -------------------------------------------------------------------------------------------
// Lifecycle
// -------------------------------------------------------------------------------------------
override connectedCallback() {
super.connectedCallback();
this.dispatchEvent(
vdsEvent('vds-view-type-change', {
detail: ViewType.Video,
}),
);
}
// -------------------------------------------------------------------------------------------
// Render
// -------------------------------------------------------------------------------------------
protected override render(): TemplateResult {
return this._renderVideo();
}
protected _renderVideo(): TemplateResult {
return html`
<video
part="${this._getVideoPartAttr()}"
src="${ifNonEmpty(this._shouldSetVideoSrcAttr() ? this.src : '')}"
width="${ifNumber(this.width)}"
height="${ifNumber(this.height)}"
poster="${ifNonEmpty(this.__canLoadPoster ? this.poster : '')}"
preload="${ifNonEmpty(this.preload)}"
crossorigin="${ifNonEmpty(this.crossOrigin)}"
controlslist="${ifNonEmpty(this.controlsList)}"
?playsinline="${this.playsinline}"
?controls="${this.controls}"
?autopictureinpicture="${this.autoPiP}"
?disablepictureinpicture="${this.disablePiP}"
?disableremoteplayback="${this.disableRemotePlayback}"
.defaultMuted="${this.defaultMuted ?? this.muted}"
.defaultPlaybackRate="${this.defaultPlaybackRate ?? 1}"
${ref(this._mediaRef)}
>
${this._renderMediaChildren()}
</video>
`;
}
/**
* Override this to modify video CSS Parts.
*/
protected _getVideoPartAttr(): string {
return 'media video';
}
/**
* Can be used by attaching engine such as `hls.js` to prevent src attr being set on
* `<video>` element.
*/
protected _shouldSetVideoSrcAttr(): boolean {
return this.canLoad;
}
override async handleMediaCanLoad(): Promise<void> {
await super.handleMediaCanLoad();
if (this._shouldSetVideoSrcAttr()) {
this.requestUpdate();
await this.updateComplete;
this.load();
}
}
// -------------------------------------------------------------------------------------------
// Methods
// -------------------------------------------------------------------------------------------
/**
* Issues an asynchronous request to display the video in picture-in-picture mode.
*
* It's not guaranteed that the video will be put into picture-in-picture. If permission to enter
* that mode is granted, the returned `Promise` will resolve and the video will receive a
* `enterpictureinpicture` event to let it know that it's now in picture-in-picture.
*
* @link https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/requestPictureInPicture
*/
async requestPictureInPicture(): Promise<PictureInPictureWindow | undefined> {
return this.videoElement?.requestPictureInPicture();
}
// -------------------------------------------------------------------------------------------
// Fullscreen
// -------------------------------------------------------------------------------------------
readonly presentationController = new VideoPresentationController(this);
override readonly fullscreenController = new VideoFullscreenController(
this,
this.screenOrientationController,
this.presentationController,
);
}