@videojs/core
Version:
Core components and utilities for Video.js
335 lines (331 loc) • 8.88 kB
JavaScript
import { shallowEqual } from "@videojs/utils";
import { map } from "nanostores";
//#region src/components/slider.ts
var Slider = class {
#element = null;
#abortController = null;
#state = map({
_trackElement: null,
_pointerRatio: 0,
_hovering: false,
_dragging: false,
_keying: false,
_fillWidth: 0,
_pointerWidth: 0,
_stepSize: .01
});
attach(target) {
this.#element = target;
this.#abortController = new AbortController();
const { signal } = this.#abortController;
this.#element.addEventListener("pointerdown", this, { signal });
this.#element.addEventListener("pointermove", this, { signal });
this.#element.addEventListener("pointerup", this, { signal });
this.#element.addEventListener("pointerenter", this, { signal });
this.#element.addEventListener("pointerleave", this, { signal });
this.#element.addEventListener("keydown", this, { signal });
this.#element.addEventListener("keyup", this, { signal });
}
detach() {
this.#element = null;
this.#abortController?.abort();
this.#abortController = null;
}
subscribe(callback) {
return this.#state.subscribe(callback);
}
setState(state) {
if (shallowEqual(state, this.#state.get())) return;
this.#state.set({
...this.#state.get(),
...state
});
}
getState() {
const state = this.#state.get();
let _pointerWidth = 0;
if (state._hovering) _pointerWidth = state._pointerRatio;
return {
...state,
_pointerWidth
};
}
handleEvent(event) {
const { type } = event;
switch (type) {
case "pointerdown":
this.#handlePointerDown(event);
break;
case "pointermove":
this.#handlePointerMove(event);
break;
case "pointerup":
this.#handlePointerUp(event);
break;
case "pointerenter":
this.#handlePointerEnter(event);
break;
case "pointerleave":
this.#handlePointerLeave(event);
break;
case "keydown":
this.#handleKeyDown(event);
break;
case "keyup":
this.#handleKeyUp(event);
break;
}
}
getPointerRatio(evt) {
const { _trackElement } = this.#state.get();
if (!_trackElement) return 0;
const rect = _trackElement.getBoundingClientRect();
return getPointProgressOnLine(evt.clientX, evt.clientY, {
x: rect.left,
y: rect.bottom
}, {
x: rect.right,
y: rect.top
});
}
#handlePointerDown(event) {
this.#element?.setPointerCapture(event.pointerId);
this.setState({
_pointerRatio: this.getPointerRatio(event),
_dragging: true
});
}
#handlePointerMove(event) {
this.setState({ _pointerRatio: this.getPointerRatio(event) });
}
#handlePointerUp(event) {
this.setState({
_pointerRatio: this.getPointerRatio(event),
_dragging: false
});
this.#element?.releasePointerCapture(event.pointerId);
}
#handlePointerEnter(_event) {
this.setState({ _hovering: true });
}
#handlePointerLeave(_event) {
this.setState({ _hovering: false });
}
#handleKeyDown(event) {
const { key } = event;
const { _pointerRatio, _stepSize } = this.#state.get();
let newRatio = _pointerRatio;
switch (key) {
case "ArrowLeft":
case "ArrowDown":
event.preventDefault();
newRatio = Math.max(0, _pointerRatio - _stepSize);
break;
case "ArrowRight":
case "ArrowUp":
event.preventDefault();
newRatio = Math.min(1, _pointerRatio + _stepSize);
break;
case "Home":
event.preventDefault();
newRatio = 0;
break;
case "End":
event.preventDefault();
newRatio = 1;
break;
default: return;
}
this.setState({
_pointerRatio: newRatio,
_keying: true
});
}
#handleKeyUp(_event) {
this.setState({ _keying: false });
}
setStepSize(stepSize) {
this.setState({ _stepSize: Math.max(.001, Math.min(1, stepSize)) });
}
};
/**
* Get progress ratio of a point on a line segment.
* @param x - The x coordinate of the point.
* @param y - The y coordinate of the point.
* @param p1 - The first point of the line segment.
* @param p2 - The second point of the line segment.
*/
function getPointProgressOnLine(x, y, p1, p2) {
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const lengthSquared = dx * dx + dy * dy;
if (lengthSquared === 0) return 0;
const projection = ((x - p1.x) * dx + (y - p1.y) * dy) / lengthSquared;
return Math.max(0, Math.min(1, projection));
}
//#endregion
//#region src/components/time-slider.ts
var TimeSlider = class extends Slider {
#seekingTime = null;
#oldCurrentTime = null;
getState() {
const state = super.getState();
let _fillWidth = 0;
if (state._dragging || state._keying) _fillWidth = state._pointerRatio * 100;
else if (state.duration > 0) if (this.#seekingTime !== null && this.#oldCurrentTime === state.currentTime) _fillWidth = this.#seekingTime / state.duration * 100;
else {
_fillWidth = state.currentTime / state.duration * 100;
this.#seekingTime = null;
}
this.#oldCurrentTime = state.currentTime;
const _currentTimeText = formatTime(state.currentTime);
const _durationText = formatTime(state.duration);
return {
...state,
_fillWidth,
_currentTimeText,
_durationText
};
}
setState(newState) {
const state = this.getState();
if (!state._dragging && !state._keying && newState.currentTime && newState.duration) {
super.setState({
...newState,
_pointerRatio: newState.currentTime / newState.duration
});
return;
}
super.setState(newState);
}
handleEvent(event) {
const { type } = event;
switch (type) {
case "pointerdown":
this.#handlePointerDown(event);
break;
case "pointermove":
this.#handlePointerMove(event);
break;
case "pointerup":
this.#handlePointerUp(event);
break;
case "keydown":
this.#handleKeyDown(event);
break;
default:
super.handleEvent(event);
break;
}
}
#handlePointerDown(event) {
super.handleEvent(event);
const { _pointerRatio, duration, requestSeek } = super.getState();
this.#seekingTime = _pointerRatio * duration;
requestSeek(this.#seekingTime);
}
#handlePointerMove(event) {
super.handleEvent(event);
const { _dragging, _pointerRatio, duration, requestSeek, requestPreview } = super.getState();
const previewTime = _pointerRatio * duration;
requestPreview(previewTime);
if (_dragging) {
this.#seekingTime = previewTime;
requestSeek(this.#seekingTime);
}
}
#handlePointerUp(event) {
const { _dragging, _pointerRatio, duration, requestSeek } = super.getState();
if (_dragging) {
this.#seekingTime = _pointerRatio * duration;
requestSeek(this.#seekingTime);
}
super.handleEvent(event);
}
#handleKeyDown(event) {
super.handleEvent(event);
const { _pointerRatio, duration, requestSeek } = super.getState();
this.#seekingTime = _pointerRatio * duration;
requestSeek(this.#seekingTime);
}
};
function formatTime(time) {
return `${Math.floor(time / 60)}:${Math.floor(time % 60).toString().padStart(2, "0")}`;
}
//#endregion
//#region src/components/volume-slider.ts
var VolumeSlider = class extends Slider {
constructor() {
super();
this.setStepSize(.1);
}
getState() {
const state = super.getState();
let _fillWidth = 0;
if (state._dragging || state._keying) _fillWidth = state._pointerRatio * 100;
else _fillWidth = state.muted ? 0 : (state.volume || 0) * 100;
const _volumeText = formatVolume(state.muted ? 0 : state.volume || 0);
return {
...state,
_fillWidth,
_volumeText
};
}
setState(newState) {
const state = this.getState();
if (!state._dragging && !state._keying && newState.volume) {
super.setState({
...newState,
_pointerRatio: newState.volume
});
return;
}
super.setState(newState);
}
handleEvent(event) {
const { type } = event;
switch (type) {
case "pointerdown":
this.#handlePointerDown(event);
break;
case "pointermove":
this.#handlePointerMove(event);
break;
case "pointerup":
this.#handlePointerUp(event);
break;
case "keydown":
this.#handleKeyDown(event);
break;
default:
super.handleEvent(event);
break;
}
}
#handlePointerDown(event) {
super.handleEvent(event);
const { _pointerRatio, requestVolumeChange } = super.getState();
requestVolumeChange(_pointerRatio);
}
#handlePointerMove(event) {
super.handleEvent(event);
const { _dragging, _pointerRatio, requestVolumeChange } = super.getState();
if (_dragging) requestVolumeChange(_pointerRatio);
}
#handlePointerUp(event) {
const { _dragging, _pointerRatio, requestVolumeChange } = super.getState();
if (_dragging) requestVolumeChange(_pointerRatio);
super.handleEvent(event);
}
#handleKeyDown(event) {
super.handleEvent(event);
const { _pointerRatio, requestVolumeChange } = super.getState();
requestVolumeChange(_pointerRatio);
}
};
function formatVolume(volume) {
return `${Math.round(volume * 100)}%`;
}
//#endregion
export { TimeSlider, VolumeSlider };
//# sourceMappingURL=index.js.map