UNPKG

@videojs/core

Version:

Core components and utilities for Video.js

335 lines (331 loc) 8.88 kB
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