145 lines
6.5 KiB
JavaScript
145 lines
6.5 KiB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||
// skipButtonsPlugin.tsx
|
||
import React from "react";
|
||
import { createRoot } from "react-dom/client";
|
||
import videojs from "video.js";
|
||
import BackwardSvg from "./skip-backward.svg";
|
||
import ForwardSvg from "./skip-forward.svg";
|
||
import "./skip-buttons.css";
|
||
import { numWord } from "../../../../shared/math";
|
||
const renderSvg = (asset) => {
|
||
if (typeof asset === "string") {
|
||
return _jsx("img", { src: asset, alt: "", "aria-hidden": "true" });
|
||
}
|
||
const SvgComponent = asset;
|
||
return _jsx(SvgComponent, {});
|
||
};
|
||
const SkipButton = ({ player, skip, direction }) => {
|
||
// Накопленная сумма перемотки
|
||
const [accumulated, setAccumulated] = React.useState(0);
|
||
// Храним ID таймера debounce
|
||
const timerRef = React.useRef(null);
|
||
// Используем ref для мгновенного доступа к накопленному значению
|
||
const accumulatedRef = React.useRef(0);
|
||
const handleClick = () => {
|
||
// Вычисляем новое накопленное значение
|
||
const newAccumulated = direction === "forward"
|
||
? accumulatedRef.current + skip
|
||
: accumulatedRef.current - skip;
|
||
accumulatedRef.current = newAccumulated;
|
||
setAccumulated(newAccumulated);
|
||
// Сбрасываем предыдущий таймер
|
||
if (timerRef.current) {
|
||
clearTimeout(timerRef.current);
|
||
}
|
||
// Устанавливаем debounce (500 мс)
|
||
timerRef.current = setTimeout(() => {
|
||
const currentTime = player.currentTime() || 0;
|
||
const newTime = currentTime + accumulatedRef.current;
|
||
player.currentTime(newTime);
|
||
// Сбрасываем накопленное значение
|
||
accumulatedRef.current = 0;
|
||
setAccumulated(0);
|
||
}, 500);
|
||
};
|
||
return (_jsxs("button", { onClick: handleClick, className: `vjs-skip-button vjs-skip-button-${direction}`, children: [_jsx("span", { className: "icon-placeholder", children: skip }), accumulated ? (_jsxs("div", { className: "scroll-info", children: [direction === "backward"
|
||
? renderSvg(BackwardSvg)
|
||
: renderSvg(ForwardSvg), `${Math.abs(accumulated)} ${numWord(accumulated, ["секунду", "секунды", "секунд"])}`] })) : null] }));
|
||
};
|
||
// Базовый Video.js компонент, обёртывающий React-компонент
|
||
class SkipButtonComponent extends videojs.getComponent("Component") {
|
||
constructor() {
|
||
super(...arguments);
|
||
this.reactRoot = null;
|
||
}
|
||
createEl() {
|
||
const direction = this.options_.direction;
|
||
const el = super.createEl("div", {
|
||
className: `vjs-skip-button-component vjs-skip-${direction}`,
|
||
});
|
||
// Рендерим React-компонент сразу внутри созданного элемента
|
||
this.reactRoot = createRoot(el);
|
||
this.reactRoot.render(_jsx(SkipButton, { player: this.player(), skip: this.options_.skip, direction: direction }));
|
||
return el;
|
||
}
|
||
dispose() {
|
||
if (this.reactRoot) {
|
||
this.reactRoot.unmount();
|
||
this.reactRoot = null;
|
||
}
|
||
super.dispose();
|
||
}
|
||
}
|
||
// Компонент для кнопки перемотки назад – задаём direction через options
|
||
class SkipBackwardButtonComponent extends SkipButtonComponent {
|
||
constructor(player, options) {
|
||
options.direction = "backward";
|
||
super(player, options);
|
||
}
|
||
}
|
||
// Компонент для кнопки перемотки вперёд – задаём direction через options
|
||
class SkipForwardButtonComponent extends SkipButtonComponent {
|
||
constructor(player, options) {
|
||
options.direction = "forward";
|
||
super(player, options);
|
||
}
|
||
}
|
||
// Регистрируем компоненты в Video.js
|
||
videojs.registerComponent("SkipBackwardButtonComponent", SkipBackwardButtonComponent);
|
||
videojs.registerComponent("SkipForwardButtonComponent", SkipForwardButtonComponent);
|
||
// Плагин, который добавляет два отдельных компонента через player.addChild
|
||
const skipButtonsPlugin = function (options) {
|
||
const player = this;
|
||
player.ready(() => {
|
||
// 1) Добавляем кнопки
|
||
player.addChild("SkipBackwardButtonComponent", { skip: options.skip });
|
||
player.addChild("SkipForwardButtonComponent", { skip: options.skip });
|
||
// 2) Вспомогательная функция, которая находит кнопку и вызывает click()
|
||
const triggerSkipClick = (direction) => {
|
||
// Ищем именно <button class="vjs-skip-button vjs-skip-button-{direction}">
|
||
const selector = `.vjs-skip-button-${direction}`;
|
||
const btn = player.el().querySelector(selector);
|
||
if (btn)
|
||
btn.click();
|
||
};
|
||
// 3) Обработка стрелок
|
||
const onKeydown = (e) => {
|
||
// Если фокус в инпуте/textarea/select или contenteditable — выходим
|
||
const active = document.activeElement;
|
||
const tag = active?.tagName;
|
||
const isEditable = tag === "INPUT" ||
|
||
tag === "TEXTAREA" ||
|
||
tag === "SELECT" ||
|
||
active?.getAttribute("contenteditable") === "true";
|
||
if (isEditable)
|
||
return;
|
||
if (e.key === " " || e.code === "Space") {
|
||
player.el()?.focus();
|
||
e.preventDefault();
|
||
if (player.paused())
|
||
player.play();
|
||
else
|
||
player.pause();
|
||
return;
|
||
}
|
||
if (e.key === "ArrowRight") {
|
||
player.el()?.focus();
|
||
e.preventDefault();
|
||
triggerSkipClick("forward");
|
||
}
|
||
else if (e.key === "ArrowLeft") {
|
||
player.el()?.focus();
|
||
e.preventDefault();
|
||
triggerSkipClick("backward");
|
||
}
|
||
};
|
||
document.addEventListener("keydown", onKeydown);
|
||
// 4) Убираем слушатель при dispose
|
||
player.on("dispose", () => {
|
||
document.removeEventListener("keydown", onKeydown);
|
||
});
|
||
});
|
||
};
|
||
videojs.registerPlugin("skipButtons", skipButtonsPlugin);
|
||
export default skipButtonsPlugin;
|
||
//# sourceMappingURL=index.js.map
|