mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +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",
|
"ua-parser-js": "^1.0.34",
|
||||||
"universal-cookie": "^4.0.4",
|
"universal-cookie": "^4.0.4",
|
||||||
"video.js": "^7.21.3",
|
"video.js": "^7.21.3",
|
||||||
|
"videojs-abloop": "^1.2.0",
|
||||||
"videojs-contrib-dash": "^5.1.1",
|
"videojs-contrib-dash": "^5.1.1",
|
||||||
"videojs-mobile-ui": "^0.8.0",
|
"videojs-mobile-ui": "^0.8.0",
|
||||||
"videojs-seek-buttons": "^3.0.1",
|
"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,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from "video.js";
|
import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from "video.js";
|
||||||
|
import abLoopPlugin from "videojs-abloop";
|
||||||
import useScript from "src/hooks/useScript";
|
import useScript from "src/hooks/useScript";
|
||||||
import "videojs-contrib-dash";
|
import "videojs-contrib-dash";
|
||||||
import "videojs-mobile-ui";
|
import "videojs-mobile-ui";
|
||||||
@@ -73,6 +74,21 @@ function handleHotkeys(player: VideoJsPlayer, event: videojs.KeyboardEvent) {
|
|||||||
player.currentTime(time);
|
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;
|
let seekFactor = 10;
|
||||||
if (event.shiftKey) {
|
if (event.shiftKey) {
|
||||||
seekFactor = 5;
|
seekFactor = 5;
|
||||||
@@ -111,6 +127,9 @@ function handleHotkeys(player: VideoJsPlayer, event: videojs.KeyboardEvent) {
|
|||||||
if (player.isFullscreen()) player.exitFullscreen();
|
if (player.isFullscreen()) player.exitFullscreen();
|
||||||
else player.requestFullscreen();
|
else player.requestFullscreen();
|
||||||
break;
|
break;
|
||||||
|
case 76: // l
|
||||||
|
toggleABLooping();
|
||||||
|
break;
|
||||||
case 38: // up arrow
|
case 38: // up arrow
|
||||||
player.volume(player.volume() + 0.1);
|
player.volume(player.volume() + 0.1);
|
||||||
break;
|
break;
|
||||||
@@ -340,6 +359,16 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
|||||||
skipButtons: {},
|
skipButtons: {},
|
||||||
trackActivity: {},
|
trackActivity: {},
|
||||||
vrMenu: {},
|
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");
|
videoEl.classList.add("vjs-big-play-centered");
|
||||||
videoRef.current!.appendChild(videoEl);
|
videoRef.current!.appendChild(videoEl);
|
||||||
|
|
||||||
|
abLoopPlugin(window, videojs);
|
||||||
|
|
||||||
const vjs = videojs(videoEl, options);
|
const vjs = videojs(videoEl, options);
|
||||||
|
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||||
@@ -372,7 +403,8 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
|||||||
sceneId.current = undefined;
|
sceneId.current = undefined;
|
||||||
};
|
};
|
||||||
// empty deps - only init once
|
// empty deps - only init once
|
||||||
}, []);
|
// showAbLoopControls is necessary to re-init the player when the config changes
|
||||||
|
}, [uiConfig?.showAbLoopControls]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const player = getPlayer();
|
const player = getPlayer();
|
||||||
|
|||||||
@@ -364,6 +364,13 @@ export const SettingsInterfacePanel: React.FC = () => {
|
|||||||
return <span>{DurationUtils.secondsToString(v ?? 0)}</span>;
|
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>
|
||||||
<SettingSection headingID="config.ui.tag_panel.heading">
|
<SettingSection headingID="config.ui.tag_panel.heading">
|
||||||
<BooleanSetting
|
<BooleanSetting
|
||||||
|
|||||||
@@ -55,8 +55,10 @@ export interface IUIConfig {
|
|||||||
compactExpandedDetails?: boolean;
|
compactExpandedDetails?: boolean;
|
||||||
// if true show all content details by default
|
// if true show all content details by default
|
||||||
showAllDetails?: boolean;
|
showAllDetails?: boolean;
|
||||||
|
|
||||||
// if true the chromecast option will enabled
|
// if true the chromecast option will enabled
|
||||||
enableChromecast?: boolean;
|
enableChromecast?: boolean;
|
||||||
|
|
||||||
// if true continue scene will always play from the beginning
|
// if true continue scene will always play from the beginning
|
||||||
alwaysStartFromBeginning?: boolean;
|
alwaysStartFromBeginning?: boolean;
|
||||||
// if true enable activity tracking
|
// if true enable activity tracking
|
||||||
@@ -65,6 +67,8 @@ export interface IUIConfig {
|
|||||||
// before the play count is incremented
|
// before the play count is incremented
|
||||||
minimumPlayPercent?: number;
|
minimumPlayPercent?: number;
|
||||||
|
|
||||||
|
showAbLoopControls?: boolean;
|
||||||
|
|
||||||
// maximum number of items to shown in the dropdown list - defaults to 200
|
// maximum number of items to shown in the dropdown list - defaults to 200
|
||||||
// upper limit of 1000
|
// upper limit of 1000
|
||||||
maxOptionsShown?: number;
|
maxOptionsShown?: number;
|
||||||
|
|||||||
@@ -78,7 +78,8 @@
|
|||||||
| `↑` | Increase volume 10% |
|
| `↑` | Increase volume 10% |
|
||||||
| `↓` | Decrease volume 10% |
|
| `↓` | Decrease volume 10% |
|
||||||
| `m` | Toggle mute |
|
| `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
|
### Scene Markers tab shortcuts
|
||||||
|
|
||||||
|
|||||||
@@ -690,6 +690,7 @@
|
|||||||
"heading": "Continue playlist by default"
|
"heading": "Continue playlist by default"
|
||||||
},
|
},
|
||||||
"enable_chromecast": "Enable Chromecast",
|
"enable_chromecast": "Enable Chromecast",
|
||||||
|
"show_ab_loop_controls": "Show AB Loop plugin controls",
|
||||||
"show_scrubber": "Show Scrubber",
|
"show_scrubber": "Show Scrubber",
|
||||||
"track_activity": "Track Activity",
|
"track_activity": "Track Activity",
|
||||||
"vr_tag": {
|
"vr_tag": {
|
||||||
|
|||||||
@@ -8104,6 +8104,11 @@ vfile@^4.0.0:
|
|||||||
videojs-font "3.2.0"
|
videojs-font "3.2.0"
|
||||||
videojs-vtt.js "^0.15.4"
|
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:
|
videojs-contrib-dash@^5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/videojs-contrib-dash/-/videojs-contrib-dash-5.1.1.tgz#9f50191677815a7d816c500977811a926aee0643"
|
resolved "https://registry.yarnpkg.com/videojs-contrib-dash/-/videojs-contrib-dash-5.1.1.tgz#9f50191677815a7d816c500977811a926aee0643"
|
||||||
|
|||||||
Reference in New Issue
Block a user