@aidenlx/player
Version:
Headless web components that make integrating media on the a web a breeze.
197 lines (165 loc) • 5.87 kB
text/typescript
import {
eventListener,
formatSpokenTime,
isKeyboardEvent,
setAttributeIfEmpty,
throttle,
} from '@vidstack/foundation';
import { type CSSResultGroup, type PropertyValues } from 'lit';
import { property, state } from 'lit/decorators.js';
import { mediaStoreSubscription } from '../../media';
import { SliderElement } from '../slider';
import { timeSliderElementStyles } from './styles';
/**
* A slider that lets the user control the current media playback time.
*
* @tagname vds-time-slider
* @example
* ```html
* <vds-time-slider>
* <div class="thumb"></div>
* </vds-time-slider>
* ```
*/
export class TimeSliderElement extends SliderElement {
static override get styles(): CSSResultGroup {
return [super.styles, timeSliderElementStyles];
}
constructor() {
super();
mediaStoreSubscription(this, 'currentTime', ($currentTime) => {
this.value = $currentTime;
});
mediaStoreSubscription(this, 'duration', ($duration) => {
this.__mediaDuration = $duration;
this.requestUpdate('max');
});
mediaStoreSubscription(this, 'paused', ($paused) => {
this.__mediaPaused = $paused;
});
}
override connectedCallback(): void {
super.connectedCallback();
setAttributeIfEmpty(this, 'aria-label', 'Media time');
}
// -------------------------------------------------------------------------------------------
// Properties
// -------------------------------------------------------------------------------------------
protected override _step = 0.1;
/**
* Represents the current media playback time.
*
* @internal
*/
override value = 0;
/** @internal */
override get min() {
return 0;
}
override set min(_) {}
/** @internal */
override get max() {
return this.__mediaDuration;
}
override set max(_) {}
/**
* ♿ **ARIA:** Human-readable text alternative for the current slider value. If you pass
* in a string containing `{currentTime}` or `{duration}` templates they'll be replaced with
* the spoken form such as `1 hour 30 minutes`.
*/
valueText = '{currentTime} out of {duration}';
/**
* Whether it should request playback to pause while the user is dragging the
* thumb. If the media was playing before the dragging starts, the state will be restored by
* dispatching a user play request once the dragging ends.
*/
pauseWhileDragging = false;
/**
* The amount of milliseconds to throttle media seeking request events being dispatched.
*/
seekingRequestThrottle = 100;
protected __mediaDuration = 0;
protected __mediaPaused = true;
// -------------------------------------------------------------------------------------------
// Lifecycle
// -------------------------------------------------------------------------------------------
protected override update(changedProperties: PropertyValues) {
if (changedProperties.has('disabled') && this.disabled) {
this._dispatchSeekingRequest.cancel();
}
super.update(changedProperties);
}
override disconnectedCallback() {
this._dispatchSeekingRequest.cancel();
super.disconnectedCallback();
}
// -------------------------------------------------------------------------------------------
// ARIA
// -------------------------------------------------------------------------------------------
protected override _getValueMin(): string {
return '0%';
}
protected override _getValueNow(): string {
return `${Math.round(this.fillPercent)}%`;
}
protected override _getValueText(): string {
return this.valueText
.replace('{currentTime}', formatSpokenTime(this.value))
.replace('{duration}', formatSpokenTime(this.__mediaDuration));
}
protected override _getValueMax(): string {
return '100%';
}
// -------------------------------------------------------------------------------------------
// Events
// -------------------------------------------------------------------------------------------
protected readonly _handleSliderDragStart = eventListener(
this,
'vds-slider-drag-start',
(event) => {
this._togglePlaybackWhileDragging(event);
},
);
protected readonly _handleSliderValueChange = eventListener(
this,
'vds-slider-value-change',
(event) => {
if (isKeyboardEvent(event.originEvent)) {
this._dispatchSeekingRequest.cancel();
this._mediaRemote.seek(this.value, event);
}
},
);
protected readonly _handleSliderDragValueChange = eventListener(
this,
'vds-slider-drag-value-change',
(event) => {
this._dispatchSeekingRequest(event);
},
);
protected readonly _handleSliderDragEnd = eventListener(this, 'vds-slider-drag-end', (event) => {
this._dispatchSeekingRequest.cancel();
this._mediaRemote.seek(this.value, event);
this._togglePlaybackWhileDragging(event);
});
protected readonly _dispatchSeekingRequest = throttle((event: Event) => {
this._mediaRemote.seeking(this.value, event);
}, this.seekingRequestThrottle);
protected _wasPlayingBeforeDragStart = false;
protected _togglePlaybackWhileDragging(event: Event) {
if (!this.pauseWhileDragging) return;
if (this.isDragging && !this.__mediaPaused) {
this._wasPlayingBeforeDragStart = true;
this._mediaRemote.pause(event);
} else if (this._wasPlayingBeforeDragStart && !this.isDragging && this.__mediaPaused) {
this._wasPlayingBeforeDragStart = false;
this._mediaRemote.play(event);
}
}
}