@aidenlx/player
Version:
Headless web components that make integrating media on the a web a breeze.
150 lines (128 loc) • 4.12 kB
text/typescript
import { ifNonEmpty, redispatchEvent, storeRecordSubscription } from '@vidstack/foundation';
import {
css,
type CSSResultGroup,
html,
LitElement,
type PropertyValues,
type TemplateResult,
} from 'lit';
import { property, state } from 'lit/decorators.js';
import { createRef, type Ref, ref } from 'lit/directives/ref.js';
import { sliderStoreContext } from './store';
/**
* Used to load a low-resolution video to be displayed when the user is hovering or dragging
* the slider. The point at which they're hovering or dragging (`pointerValue`) is the preview
* time position. The video will automatically be updated to match, so ensure it's of the same
* length as the original.
*
* 💡 The following attributes are updated for your styling needs:
*
* - `video-can-play`: Applied when the video is ready for playback.
* - `video-error`: Applied when a media error has been encountered.
*
* 💡 The `canplay` and `error` events are re-dispatched by this element for you to listen to if
* needed.
*
* @tagname vds-slider-video
* @csspart video - The video element.
* @example
* ```html
* <vds-time-slider>
* <vds-slider-video
* src="/low-res-video.mp4"
* ></vds-slider-video>
* </vds-time-slider>
* ```
*/
export class SliderVideoElement extends LitElement {
static override get styles(): CSSResultGroup {
return [
css`
:host {
display: inline-block;
contain: content;
}
:host([hidden]) {
display: none;
}
video {
display: block;
width: 100%;
height: auto;
}
`,
];
}
constructor() {
super();
storeRecordSubscription(this, sliderStoreContext, 'pointerValue', ($previewTime) => {
this._updateCurrentTime($previewTime);
});
}
// -------------------------------------------------------------------------------------------
// Properties
// -------------------------------------------------------------------------------------------
/**
* The URL of a media resource to use.
*
* @link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/src
*/
() src: string | undefined;
protected readonly _videoRef: Ref<HTMLVideoElement> = createRef();
/**
* The underlying `<video>` element.
*/
get videoElement() {
return this._videoRef.value;
}
protected _updateCurrentTime(seconds: number) {
if (!this.__hasError && this.__canPlay && this.videoElement!.currentTime !== seconds) {
this.videoElement!.currentTime = seconds;
}
}
// -------------------------------------------------------------------------------------------
// Lifecycle
// -------------------------------------------------------------------------------------------
override willUpdate(changedProperties: PropertyValues) {
if (changedProperties.has('src')) {
this.__canPlay = false;
this.__hasError = false;
this.removeAttribute('video-can-play');
this.removeAttribute('video-error');
}
super.willUpdate(changedProperties);
}
protected override render(): TemplateResult {
return this._renderVideo();
}
protected _renderVideo(): TemplateResult {
return html`
<video
part="video"
muted
playsinline
preload="auto"
src=${ifNonEmpty(this.src)}
@canplay=${this._handleCanPlay}
@error=${this._handleError}
${ref(this._videoRef)}
></video>
`;
}
// -------------------------------------------------------------------------------------------
// Events
// -------------------------------------------------------------------------------------------
() protected __canPlay = false;
protected async _handleCanPlay(event: Event) {
this.__canPlay = true;
this.setAttribute('video-can-play', '');
redispatchEvent(this, event);
}
() protected __hasError = false;
protected _handleError(event: Event) {
this.__hasError = true;
this.setAttribute('video-error', '');
redispatchEvent(this, event);
}
}