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
|