mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
Region-based Looping (a.k.a. A/B looping) utilizing videojs-abloop plugin (#3904)
* yarn add videojs-abloop * add abLoop plugin to video player * adding player keyboard shortcut 'l' for toggling a/b looping copies mpv behavior: if a/b loop start not yet set, sets start to current player time elif a/b loop stop not yet set, sets end to current player time and enables loop else, disables a/b loop relates to #3264 (https://github.com/stashapp/stash/issues/3264) * update help with keyboard shortcut * Add plugin type definitions * Make UI elements optional --------- Co-authored-by: chickenwingavalanche <chickenwingavalanche@example.com> Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
922aef3e5a
commit
1f3ed07188
@@ -68,6 +68,7 @@
|
||||
"ua-parser-js": "^1.0.34",
|
||||
"universal-cookie": "^4.0.4",
|
||||
"video.js": "^7.21.3",
|
||||
"videojs-abloop": "^1.2.0",
|
||||
"videojs-contrib-dash": "^5.1.1",
|
||||
"videojs-mobile-ui": "^0.8.0",
|
||||
"videojs-seek-buttons": "^3.0.1",
|
||||
|
||||
35
ui/v2.5/src/@types/videojs-abloop.d.ts
vendored
Normal file
35
ui/v2.5/src/@types/videojs-abloop.d.ts
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
declare module "videojs-abloop" {
|
||||
import videojs from "video.js";
|
||||
|
||||
declare function abLoopPlugin(
|
||||
window: Window & typeof globalThis,
|
||||
player: videojs
|
||||
): abLoopPlugin.Plugin;
|
||||
|
||||
declare namespace abLoopPlugin {
|
||||
interface Options {
|
||||
start: number | boolean;
|
||||
end: number | boolean;
|
||||
enabled: boolean;
|
||||
loopIfBeforeStart: boolean;
|
||||
loopIfAfterEnd: boolean;
|
||||
pauseBeforeLooping: boolean;
|
||||
pauseAfterLooping: boolean;
|
||||
}
|
||||
|
||||
class Plugin extends videojs.Plugin {
|
||||
getOptions(): Options;
|
||||
setOptions(o: Options): void;
|
||||
}
|
||||
}
|
||||
|
||||
export = abLoopPlugin;
|
||||
|
||||
declare module "video.js" {
|
||||
interface VideoJsPlayer {
|
||||
abLoopPlugin: abLoopPlugin.Plugin;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import React, {
|
||||
useState,
|
||||
} from "react";
|
||||
import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from "video.js";
|
||||
import abLoopPlugin from "videojs-abloop";
|
||||
import useScript from "src/hooks/useScript";
|
||||
import "videojs-contrib-dash";
|
||||
import "videojs-mobile-ui";
|
||||
@@ -73,6 +74,21 @@ function handleHotkeys(player: VideoJsPlayer, event: videojs.KeyboardEvent) {
|
||||
player.currentTime(time);
|
||||
}
|
||||
|
||||
function toggleABLooping() {
|
||||
const opts = player.abLoopPlugin.getOptions();
|
||||
if (!opts.start) {
|
||||
opts.start = player.currentTime();
|
||||
} else if (!opts.end) {
|
||||
opts.end = player.currentTime();
|
||||
opts.enabled = true;
|
||||
} else {
|
||||
opts.start = 0;
|
||||
opts.end = 0;
|
||||
opts.enabled = false;
|
||||
}
|
||||
player.abLoopPlugin.setOptions(opts);
|
||||
}
|
||||
|
||||
let seekFactor = 10;
|
||||
if (event.shiftKey) {
|
||||
seekFactor = 5;
|
||||
@@ -111,6 +127,9 @@ function handleHotkeys(player: VideoJsPlayer, event: videojs.KeyboardEvent) {
|
||||
if (player.isFullscreen()) player.exitFullscreen();
|
||||
else player.requestFullscreen();
|
||||
break;
|
||||
case 76: // l
|
||||
toggleABLooping();
|
||||
break;
|
||||
case 38: // up arrow
|
||||
player.volume(player.volume() + 0.1);
|
||||
break;
|
||||
@@ -340,6 +359,16 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
||||
skipButtons: {},
|
||||
trackActivity: {},
|
||||
vrMenu: {},
|
||||
abLoopPlugin: {
|
||||
start: 0,
|
||||
end: false,
|
||||
enabled: false,
|
||||
loopIfBeforeStart: true,
|
||||
loopIfAfterEnd: true,
|
||||
pauseAfterLooping: false,
|
||||
pauseBeforeLooping: false,
|
||||
createButtons: uiConfig?.showAbLoopControls ?? false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -349,6 +378,8 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
||||
videoEl.classList.add("vjs-big-play-centered");
|
||||
videoRef.current!.appendChild(videoEl);
|
||||
|
||||
abLoopPlugin(window, videojs);
|
||||
|
||||
const vjs = videojs(videoEl, options);
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
@@ -372,7 +403,8 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
||||
sceneId.current = undefined;
|
||||
};
|
||||
// empty deps - only init once
|
||||
}, []);
|
||||
// showAbLoopControls is necessary to re-init the player when the config changes
|
||||
}, [uiConfig?.showAbLoopControls]);
|
||||
|
||||
useEffect(() => {
|
||||
const player = getPlayer();
|
||||
|
||||
@@ -364,6 +364,13 @@ export const SettingsInterfacePanel: React.FC = () => {
|
||||
return <span>{DurationUtils.secondsToString(v ?? 0)}</span>;
|
||||
}}
|
||||
/>
|
||||
|
||||
<BooleanSetting
|
||||
id="show-ab-loop"
|
||||
headingID="config.ui.scene_player.options.show_ab_loop_controls"
|
||||
checked={ui.showAbLoopControls ?? undefined}
|
||||
onChange={(v) => saveUI({ showAbLoopControls: v })}
|
||||
/>
|
||||
</SettingSection>
|
||||
<SettingSection headingID="config.ui.tag_panel.heading">
|
||||
<BooleanSetting
|
||||
|
||||
@@ -55,8 +55,10 @@ export interface IUIConfig {
|
||||
compactExpandedDetails?: boolean;
|
||||
// if true show all content details by default
|
||||
showAllDetails?: boolean;
|
||||
|
||||
// if true the chromecast option will enabled
|
||||
enableChromecast?: boolean;
|
||||
|
||||
// if true continue scene will always play from the beginning
|
||||
alwaysStartFromBeginning?: boolean;
|
||||
// if true enable activity tracking
|
||||
@@ -65,6 +67,8 @@ export interface IUIConfig {
|
||||
// before the play count is incremented
|
||||
minimumPlayPercent?: number;
|
||||
|
||||
showAbLoopControls?: boolean;
|
||||
|
||||
// maximum number of items to shown in the dropdown list - defaults to 200
|
||||
// upper limit of 1000
|
||||
maxOptionsShown?: number;
|
||||
|
||||
@@ -78,7 +78,8 @@
|
||||
| `↑` | Increase volume 10% |
|
||||
| `↓` | Decrease volume 10% |
|
||||
| `m` | Toggle mute |
|
||||
| `Shift + l` | Toggle player looping |
|
||||
| `l` | A/B looping toggle. Press once to set start point. Press again to set end point. Press again to disable loop. |
|
||||
| `Shift + l` | Toggle looping of scene when it's over |
|
||||
|
||||
### Scene Markers tab shortcuts
|
||||
|
||||
|
||||
@@ -690,6 +690,7 @@
|
||||
"heading": "Continue playlist by default"
|
||||
},
|
||||
"enable_chromecast": "Enable Chromecast",
|
||||
"show_ab_loop_controls": "Show AB Loop plugin controls",
|
||||
"show_scrubber": "Show Scrubber",
|
||||
"track_activity": "Track Activity",
|
||||
"vr_tag": {
|
||||
|
||||
@@ -8104,6 +8104,11 @@ vfile@^4.0.0:
|
||||
videojs-font "3.2.0"
|
||||
videojs-vtt.js "^0.15.4"
|
||||
|
||||
videojs-abloop@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/videojs-abloop/-/videojs-abloop-1.2.0.tgz#ead4054400e6107d6512553ddff2a97260decf3e"
|
||||
integrity sha512-6/hvtB5gNQUr5FJ969UhXVg5H+3wxhOzh9AVftlezOXlhzzaWfNfiOJYqNKo01Gc/eSQOvfttrOX7jH+aHpwrw==
|
||||
|
||||
videojs-contrib-dash@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/videojs-contrib-dash/-/videojs-contrib-dash-5.1.1.tgz#9f50191677815a7d816c500977811a926aee0643"
|
||||
|
||||
Reference in New Issue
Block a user