Player mobile improvements (#3120)

* Add videojs-mobile-ui
* Prevent marker wrapping and fix alignment
* Fix marker update on delete
* Change hotkey modifier behaviour
* Update KeyboardShortcuts.md
This commit is contained in:
DingDongSoLong4
2022-11-23 06:55:24 +02:00
committed by GitHub
parent b175f1865f
commit 821587b166
12 changed files with 268 additions and 290 deletions

View File

@@ -74,8 +74,8 @@
"thehandy": "^1.0.3", "thehandy": "^1.0.3",
"universal-cookie": "^4.0.4", "universal-cookie": "^4.0.4",
"video.js": "^7.20.3", "video.js": "^7.20.3",
"videojs-landscape-fullscreen": "^11.33.0", "videojs-mobile-ui": "^0.8.0",
"videojs-seek-buttons": "^2.2.0", "videojs-seek-buttons": "^3.0.1",
"videojs-vtt.js": "^0.15.4", "videojs-vtt.js": "^0.15.4",
"vite": "^2.9.13", "vite": "^2.9.13",
"vite-plugin-compression": "^0.3.5", "vite-plugin-compression": "^0.3.5",
@@ -103,7 +103,8 @@
"@types/react-router-dom": "5.1.7", "@types/react-router-dom": "5.1.7",
"@types/react-router-hash-link": "^1.2.1", "@types/react-router-hash-link": "^1.2.1",
"@types/react-slick": "^0.23.8", "@types/react-slick": "^0.23.8",
"@types/video.js": "^7.3.28", "@types/video.js": "^7.3.49",
"@types/videojs-mobile-ui": "^0.5.0",
"@types/videojs-seek-buttons": "^2.1.0", "@types/videojs-seek-buttons": "^2.1.0",
"@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0", "@typescript-eslint/parser": "^4.33.0",

View File

@@ -1,47 +0,0 @@
/* eslint-disable @typescript-eslint/naming-convention */
declare module "videojs-landscape-fullscreen" {
import videojs from "video.js";
function landscapeFullscreen(options?: {
fullscreen: landscapeFullscreen.Options;
}): void;
namespace landscapeFullscreen {
const VERSION: typeof videojs.VERSION;
interface Options {
/**
* Enter fullscreen mode on rotating the device to landscape.
* @default true
*/
enterOnRotate?: boolean;
/**
* Exit fullscreen mode on rotating the device to portrait.
* @default true
*/
exitOnRotate?: boolean;
/**
* Always enter fullscreen in landscape mode even when device is in portrait mode (works on Chromium, Firefox, and IE >= 11).
* @default true
*/
alwaysInLandscapeMode?: boolean;
/**
* Whether to use fake fullscreen on iOS (needed for displaying player controls instead of system controls).
* @default true
*/
iOS?: boolean;
}
}
export = landscapeFullscreen;
declare module "video.js" {
interface VideoJsPlayer {
landscapeFullscreen: typeof landscapeFullscreen;
}
interface VideoJsPlayerPluginOptions {
landscapeFullscreen?: { fullscreen: landscapeFullscreen.Options };
}
}
}

View File

@@ -7,15 +7,14 @@ import React, {
useState, useState,
} from "react"; } from "react";
import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from "video.js"; import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from "video.js";
import "videojs-mobile-ui";
import "videojs-seek-buttons"; import "videojs-seek-buttons";
import "videojs-landscape-fullscreen";
import "./live"; import "./live";
import "./PlaylistButtons"; import "./PlaylistButtons";
import "./source-selector"; import "./source-selector";
import "./persist-volume"; import "./persist-volume";
import "./markers"; import "./markers";
import "./vtt-thumbnails"; import "./vtt-thumbnails";
import "./big-buttons";
import "./track-activity"; import "./track-activity";
import cx from "classnames"; import cx from "classnames";
import { import {
@@ -36,6 +35,18 @@ import { VIDEO_PLAYER_ID } from "./util";
import { IUIConfig } from "src/core/config"; import { IUIConfig } from "src/core/config";
function handleHotkeys(player: VideoJsPlayer, event: videojs.KeyboardEvent) { function handleHotkeys(player: VideoJsPlayer, event: videojs.KeyboardEvent) {
function seekStep(step: number) {
const time = player.currentTime() + step;
const duration = player.duration();
if (time < 0) {
player.currentTime(0);
} else if (time < duration) {
player.currentTime(time);
} else {
player.currentTime(duration);
}
}
function seekPercent(percent: number) { function seekPercent(percent: number) {
const duration = player.duration(); const duration = player.duration();
const time = duration * percent; const time = duration * percent;
@@ -50,6 +61,21 @@ function handleHotkeys(player: VideoJsPlayer, event: videojs.KeyboardEvent) {
player.currentTime(time); player.currentTime(time);
} }
let seekFactor = 10;
if (event.shiftKey) {
seekFactor = 5;
} else if (event.ctrlKey || event.altKey) {
seekFactor = 60;
}
switch (event.which) {
case 39: // right arrow
seekStep(seekFactor);
break;
case 37: // left arrow
seekStep(-seekFactor);
break;
}
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) { if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
return; return;
} }
@@ -67,12 +93,6 @@ 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 39: // right arrow
player.currentTime(Math.min(player.duration(), player.currentTime() + 5));
break;
case 37: // left arrow
player.currentTime(Math.max(0, player.currentTime() - 5));
break;
case 38: // up arrow case 38: // up arrow
player.volume(player.volume() + 0.1); player.volume(player.volume() + 0.1);
break; break;
@@ -263,7 +283,6 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
markers: {}, markers: {},
sourceSelector: {}, sourceSelector: {},
persistVolume: {}, persistVolume: {},
bigButtons: {},
seekButtons: { seekButtons: {
forward: 10, forward: 10,
back: 10, back: 10,
@@ -421,20 +440,21 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
interactiveClient.pause(); interactiveClient.pause();
interactiveReady.current = false; interactiveReady.current = false;
const alwaysStartFromBeginning =
uiConfig?.alwaysStartFromBeginning ?? false;
const isLandscape = file.height && file.width && file.width > file.height; const isLandscape = file.height && file.width && file.width > file.height;
const mobileUiOptions = {
if (isLandscape) {
player.landscapeFullscreen({
fullscreen: { fullscreen: {
enterOnRotate: true, enterOnRotate: true,
exitOnRotate: true, exitOnRotate: true,
alwaysInLandscapeMode: true, lockOnRotate: true,
iOS: false, lockToLandscapeOnEnter: isLandscape,
}, },
}); touchControls: {
} seekSeconds: 10,
tapTimeout: 500,
disableOnEnd: false,
},
};
player.mobileUi(mobileUiOptions);
const { duration } = file; const { duration } = file;
const sourceSelector = player.sourceSelector(); const sourceSelector = player.sourceSelector();
@@ -455,15 +475,6 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
}) })
); );
const markers = player.markers();
markers.clearMarkers();
for (const marker of scene.scene_markers) {
markers.addMarker({
title: getMarkerTitle(marker),
time: marker.seconds,
});
}
function getDefaultLanguageCode() { function getDefaultLanguageCode() {
let languageCode = window.navigator.language; let languageCode = window.navigator.language;
@@ -507,28 +518,25 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
} }
} }
if (scene.paths.screenshot) {
player.poster(scene.paths.screenshot);
} else {
player.poster("");
}
auto.current = auto.current =
autoplay || autoplay ||
(interfaceConfig?.autostartVideo ?? false) || (interfaceConfig?.autostartVideo ?? false) ||
_initialTimestamp > 0; _initialTimestamp > 0;
let startPositition = _initialTimestamp; const alwaysStartFromBeginning =
uiConfig?.alwaysStartFromBeginning ?? false;
let startPosition = _initialTimestamp;
if ( if (
!startPositition && !startPosition &&
!(alwaysStartFromBeginning || sessionInitialised) && !(alwaysStartFromBeginning || sessionInitialised) &&
file.duration > scene.resume_time! file.duration > scene.resume_time!
) { ) {
startPositition = scene.resume_time!; startPosition = scene.resume_time!;
} }
initialTimestamp.current = startPositition; initialTimestamp.current = startPosition;
setTime(startPositition); setTime(startPosition);
setSessionInitialised(true); setSessionInitialised(true);
player.load(); player.load();
@@ -556,6 +564,26 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
_initialTimestamp, _initialTimestamp,
]); ]);
useEffect(() => {
const player = playerRef.current;
if (!player || !scene) return;
const markers = player.markers();
markers.clearMarkers();
for (const marker of scene.scene_markers) {
markers.addMarker({
title: getMarkerTitle(marker),
time: marker.seconds,
});
}
if (scene.paths.screenshot) {
player.poster(scene.paths.screenshot);
} else {
player.poster("");
}
}, [scene]);
useEffect(() => { useEffect(() => {
const player = playerRef.current; const player = playerRef.current;
if (!player) return; if (!player) return;

View File

@@ -211,6 +211,8 @@ export const ScenePlayerScrubber: React.FC<IScenePlayerScrubberProps> = ({
mouseDown.current = false; mouseDown.current = false;
contentEl.current!.classList.remove("dragging");
let newPosition = position.current; let newPosition = position.current;
const midpointOffset = slider.clientWidth / 2; const midpointOffset = slider.clientWidth / 2;
const delta = Math.abs(event.clientX - startMouseEvent.current!.clientX); const delta = Math.abs(event.clientX - startMouseEvent.current!.clientX);
@@ -256,6 +258,8 @@ export const ScenePlayerScrubber: React.FC<IScenePlayerScrubberProps> = ({
onScroll(); onScroll();
} }
contentEl.current!.classList.add("dragging");
// negative dragging right (past), positive left (future) // negative dragging right (past), positive left (future)
const delta = event.clientX - lastMouseEvent.current!.clientX; const delta = event.clientX - lastMouseEvent.current!.clientX;
@@ -338,7 +342,6 @@ export const ScenePlayerScrubber: React.FC<IScenePlayerScrubberProps> = ({
return ( return (
<div className="scrubber-wrapper"> <div className="scrubber-wrapper">
<Button <Button
variant="link"
className="scrubber-button" className="scrubber-button"
id="scrubber-back" id="scrubber-back"
onClick={() => goBack()} onClick={() => goBack()}

View File

@@ -1,69 +0,0 @@
import videojs, { VideoJsPlayer } from "video.js";
// prettier-ignore
const BigPlayButton = videojs.getComponent("BigPlayButton") as unknown as typeof videojs.BigPlayButton;
class BigPlayPauseButton extends BigPlayButton {
handleClick(event: videojs.EventTarget.Event) {
if (this.player().paused()) {
super.handleClick(event);
} else {
this.player().pause();
}
}
buildCSSClass() {
return "vjs-control vjs-button vjs-big-play-pause-button";
}
}
class BigButtonGroup extends videojs.getComponent("Component") {
constructor(player: VideoJsPlayer) {
super(player);
this.addChild("seekButton", {
direction: "back",
seconds: 10,
});
this.addChild("BigPlayPauseButton");
this.addChild("seekButton", {
direction: "forward",
seconds: 10,
});
}
createEl() {
return super.createEl("div", {
className: "vjs-big-button-group",
});
}
}
class BigButtonsPlugin extends videojs.getPlugin("plugin") {
constructor(player: VideoJsPlayer) {
super(player);
player.ready(() => {
player.addChild("BigButtonGroup");
});
}
}
// Register the plugin with video.js.
videojs.registerComponent("BigButtonGroup", BigButtonGroup);
videojs.registerComponent("BigPlayPauseButton", BigPlayPauseButton);
videojs.registerPlugin("bigButtons", BigButtonsPlugin);
/* eslint-disable @typescript-eslint/naming-convention */
declare module "video.js" {
interface VideoJsPlayer {
bigButtons: () => BigButtonsPlugin;
}
interface VideoJsPlayerPluginOptions {
bigButtons?: {};
}
}
export default BigButtonsPlugin;

View File

@@ -48,7 +48,13 @@ class MarkersPlugin extends videojs.getPlugin("plugin") {
const marker = this.markers[i]; const marker = this.markers[i];
const markerDiv = this.markerDivs[i]; const markerDiv = this.markerDivs[i];
markerDiv.style.left = `${(marker.time / duration) * 100}%`; if (duration) {
// marker is 6px wide - adjust by 3px to align to center not left side
markerDiv.style.left = `calc(${
(marker.time / duration) * 100
}% - 3px)`;
markerDiv.style.visibility = "visible";
}
if (seekBar) seekBar.appendChild(markerDiv); if (seekBar) seekBar.appendChild(markerDiv);
} }
}); });
@@ -58,6 +64,7 @@ class MarkersPlugin extends videojs.getPlugin("plugin") {
if (!this.markerTooltip) return; if (!this.markerTooltip) return;
this.markerTooltip.innerText = title; this.markerTooltip.innerText = title;
this.markerTooltip.style.right = `${-this.markerTooltip.clientWidth / 2}px`;
this.markerTooltip.style.visibility = "visible"; this.markerTooltip.style.visibility = "visible";
// hide default tooltip // hide default tooltip
@@ -76,7 +83,11 @@ class MarkersPlugin extends videojs.getPlugin("plugin") {
markerDiv.className = "vjs-marker"; markerDiv.className = "vjs-marker";
const duration = this.player.duration(); const duration = this.player.duration();
markerDiv.style.position = `${(marker.time / duration) * 100}%`; if (duration) {
// marker is 6px wide - adjust by 3px to align to center not left side
markerDiv.style.left = `calc(${(marker.time / duration) * 100}% - 3px)`;
markerDiv.style.visibility = "visible";
}
// bind click event to seek to marker time // bind click event to seek to marker time
markerDiv.addEventListener("click", () => markerDiv.addEventListener("click", () =>
@@ -122,7 +133,7 @@ class MarkersPlugin extends videojs.getPlugin("plugin") {
} }
clearMarkers() { clearMarkers() {
this.removeMarkers(this.markers); this.removeMarkers([...this.markers]);
} }
} }

View File

@@ -1,3 +1,7 @@
@import "video.js/dist/video-js.css";
@import "videojs-mobile-ui/dist/videojs-mobile-ui.css";
@import "videojs-seek-buttons/dist/videojs-seek-buttons.css";
$scrubberHeight: 120px; $scrubberHeight: 120px;
$menuHeight: 4rem; $menuHeight: 4rem;
$sceneTabWidth: 450px; $sceneTabWidth: 450px;
@@ -52,6 +56,10 @@ $sceneTabWidth: 450px;
} }
} }
.vjs-touch-overlay .vjs-play-control {
z-index: 1;
}
.vjs-control-bar { .vjs-control-bar {
background: none; background: none;
@@ -67,13 +75,16 @@ $sceneTabWidth: 450px;
bottom: 0; bottom: 0;
content: ""; content: "";
height: 10rem; height: 10rem;
pointer-events: none;
position: absolute; position: absolute;
width: 100%; width: 100%;
} }
} }
.vjs-time-control { .vjs-time-control {
display: block; align-items: center;
display: flex;
justify-content: center;
min-width: 0; min-width: 0;
padding: 0 4px; padding: 0 4px;
pointer-events: none; pointer-events: none;
@@ -92,17 +103,22 @@ $sceneTabWidth: 450px;
} }
.vjs-progress-control { .vjs-progress-control {
bottom: 3rem; bottom: 2.5em;
margin-left: 1%; height: 3em;
position: absolute; position: absolute;
width: 98%; width: 100%;
.vjs-play-progress .vjs-time-tooltip, .vjs-progress-holder {
&:hover .vjs-play-progress .vjs-time-tooltip { margin: 0 1rem;
visibility: hidden;
} }
} }
/* stylelint-disable declaration-no-important */
.vjs-play-progress .vjs-time-tooltip {
display: none !important;
}
/* stylelint-enable declaration-no-important */
.vjs-volume-control { .vjs-volume-control {
z-index: 1; z-index: 1;
} }
@@ -117,7 +133,7 @@ $sceneTabWidth: 450px;
.vjs-vtt-thumbnail-display { .vjs-vtt-thumbnail-display {
border: 2px solid white; border: 2px solid white;
border-radius: 2px; border-radius: 2px;
bottom: 90px; bottom: 6em;
box-shadow: 0 0 7px rgba(0, 0, 0, 0.6); box-shadow: 0 0 7px rgba(0, 0, 0, 0.6);
opacity: 0; opacity: 0;
pointer-events: none; pointer-events: none;
@@ -155,7 +171,7 @@ $sceneTabWidth: 450px;
.vjs-source-selector { .vjs-source-selector {
.vjs-menu li { .vjs-menu li {
font-size: 12px; font-size: 0.8em;
} }
.vjs-button > .vjs-icon-placeholder::before { .vjs-button > .vjs-icon-placeholder::before {
@@ -171,36 +187,31 @@ $sceneTabWidth: 450px;
left: 0; left: 0;
opacity: 1; opacity: 1;
position: absolute; position: absolute;
-webkit-transition: opacity 0.2s ease;
-moz-transition: opacity 0.2s ease;
transition: opacity 0.2s ease; transition: opacity 0.2s ease;
visibility: hidden;
width: 6px; width: 6px;
z-index: 100; z-index: 100;
&:hover { &:hover {
cursor: pointer; cursor: pointer;
-webkit-transform: scale(1.3, 1.3);
-moz-transform: scale(1.3, 1.3);
-o-transform: scale(1.3, 1.3);
-ms-transform: scale(1.3, 1.3);
transform: scale(1.3, 1.3); transform: scale(1.3, 1.3);
} }
} }
.vjs-marker-tooltip { .vjs-marker-tooltip {
background-color: #fff;
background-color: rgba(255, 255, 255, 0.8);
border-radius: 0.3em; border-radius: 0.3em;
color: white; color: #000;
display: block;
float: right; float: right;
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
font-size: 10px; font-size: 0.6em;
height: 50px;
padding: 6px 8px 8px 8px; padding: 6px 8px 8px 8px;
pointer-events: none; pointer-events: none;
position: absolute; position: absolute;
right: -80px; top: -3.4em;
top: -5.4em; visibility: hidden;
width: 160px; white-space: nowrap;
z-index: 1; z-index: 1;
} }
@@ -244,9 +255,8 @@ $sceneTabWidth: 450px;
content: "\f103"; content: "\f103";
} }
// hide the regular play/pause button on touch screens .vjs-vtt-thumbnail-display {
.vjs-play-control { bottom: 2.8em;
display: none;
} }
// hide the regular seek buttons on touch screens // hide the regular seek buttons on touch screens
@@ -259,10 +269,19 @@ $sceneTabWidth: 450px;
// make controls a little more compact on smaller screens // make controls a little more compact on smaller screens
@media (max-width: 576px) { @media (max-width: 576px) {
.vjs-control-bar { .vjs-control-bar {
.vjs-control:not(.vjs-progress-control) { .vjs-control {
width: 2.5em; width: 2.5em;
} }
.vjs-progress-control {
height: 2em;
width: 100%;
}
.vjs-playback-rate {
width: 3em;
}
.vjs-button > .vjs-icon-placeholder::before, .vjs-button > .vjs-icon-placeholder::before,
.vjs-skip-button::before { .vjs-skip-button::before {
font-size: 1.5em; font-size: 1.5em;
@@ -270,9 +289,27 @@ $sceneTabWidth: 450px;
} }
} }
.vjs-menu-button-popup .vjs-menu {
width: 8em;
.vjs-menu-content {
max-height: 10em;
}
}
.vjs-playback-rate .vjs-playback-rate-value {
font-size: 1em;
line-height: 2.97;
}
.vjs-source-selector {
.vjs-menu li {
font-size: 10px;
}
}
.vjs-time-control { .vjs-time-control {
font-size: 12px; font-size: 12px;
line-height: 4em;
} }
.vjs-big-button-group .vjs-button { .vjs-big-button-group .vjs-button {
@@ -283,10 +320,6 @@ $sceneTabWidth: 450px;
.vjs-current-time { .vjs-current-time {
margin-left: 1em; margin-left: 1em;
} }
.vjs-vtt-thumbnail-display {
bottom: 40px;
}
} }
} }
@@ -301,73 +334,8 @@ $sceneTabWidth: 450px;
padding-right: 15px; padding-right: 15px;
} }
@media (max-width: 1199px) {
.scene-tabs {
padding-right: 15px;
}
.scene-player-container {
padding-left: 0;
padding-right: 0;
}
}
@media (min-width: 1200px) {
.scene-tabs {
flex: 0 0 $sceneTabWidth;
max-width: $sceneTabWidth;
overflow: auto;
&.collapsed {
display: none;
}
.tab-content {
flex: 1 1 auto;
min-height: 15rem;
overflow-x: hidden;
overflow-y: auto;
}
}
.scene-divider {
flex: 0 0 15px;
max-width: 15px;
button {
background-color: transparent;
border: 0;
color: $link-color;
cursor: pointer;
font-size: 10px;
font-weight: 800;
height: 100%;
line-height: 100%;
padding: 0;
text-align: center;
width: 100%;
&:active:not(:hover),
&:focus:not(:hover) {
background-color: transparent;
border: 0;
box-shadow: none;
}
}
}
.scene-player-container {
flex: 0 0 calc(100% - #{$sceneTabWidth} - 15px);
max-width: calc(100% - #{$sceneTabWidth} - 15px);
padding-left: 0;
&.expanded {
flex: 0 0 calc(100% - 15px);
max-width: calc(100% - 15px);
}
}
}
.scrubber-wrapper { .scrubber-wrapper {
display: flex;
flex-shrink: 0; flex-shrink: 0;
margin: 5px 0; margin: 5px 0;
overflow: hidden; overflow: hidden;
@@ -387,26 +355,26 @@ $sceneTabWidth: 450px;
border: 1px solid #555; border: 1px solid #555;
color: $link-color; color: $link-color;
cursor: pointer; cursor: pointer;
font-size: 20px; font-size: 1.3rem;
font-weight: 800; font-weight: 800;
height: 100%; height: 100%;
line-height: $scrubberHeight; line-height: $scrubberHeight;
padding: 0; padding: 0;
text-align: center; text-align: center;
width: 1.5%; width: 1.3rem;
} }
.scrubber-content { .scrubber-content {
cursor: grab; cursor: pointer;
display: inline-block; display: inline-block;
flex-grow: 1;
height: $scrubberHeight; height: $scrubberHeight;
margin: 0 0.5%; margin: 0 7px;
overflow: hidden; overflow: hidden;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
position: relative; position: relative;
-webkit-user-select: none; -webkit-user-select: none;
user-select: none; user-select: none;
width: 96%;
&.dragging { &.dragging {
cursor: grabbing; cursor: grabbing;
@@ -492,7 +460,6 @@ $sceneTabWidth: 450px;
.scrubber-item { .scrubber-item {
color: white; color: white;
cursor: pointer;
display: flex; display: flex;
font-size: 10px; font-size: 10px;
margin: 0 auto; margin: 0 auto;
@@ -506,3 +473,74 @@ $sceneTabWidth: 450px;
width: 100%; width: 100%;
} }
} }
@media (max-width: 1199px) {
.scene-tabs {
padding-right: 15px;
}
.scene-player-container {
padding-left: 0;
padding-right: 0;
}
.scrubber-wrapper {
margin-left: 5px;
margin-right: 5px;
}
}
@media (min-width: 1200px) {
.scene-tabs {
flex: 0 0 $sceneTabWidth;
max-width: $sceneTabWidth;
overflow: auto;
&.collapsed {
display: none;
}
.tab-content {
flex: 1 1 auto;
min-height: 15rem;
overflow-x: hidden;
overflow-y: auto;
}
}
.scene-divider {
flex: 0 0 15px;
max-width: 15px;
button {
background-color: transparent;
border: 0;
color: $link-color;
cursor: pointer;
font-size: 10px;
font-weight: 800;
height: 100%;
line-height: 100%;
padding: 0;
text-align: center;
width: 100%;
&:active:not(:hover),
&:focus:not(:hover) {
background-color: transparent;
border: 0;
box-shadow: none;
}
}
}
.scene-player-container {
flex: 0 0 calc(100% - #{$sceneTabWidth} - 15px);
max-width: calc(100% - #{$sceneTabWidth} - 15px);
padding-left: 0;
&.expanded {
flex: 0 0 calc(100% - 15px);
max-width: calc(100% - 15px);
}
}
}

View File

@@ -68,7 +68,13 @@ const typePolicies: TypePolicies = {
}, },
}, },
}, },
Scene: {
fields: {
scene_markers: {
merge: false,
},
},
},
Tag: { Tag: {
fields: { fields: {
parents: { parents: {

View File

@@ -13,6 +13,8 @@
* Added tag description filter criterion. ([#3011](https://github.com/stashapp/stash/pull/3011)) * Added tag description filter criterion. ([#3011](https://github.com/stashapp/stash/pull/3011))
### 🎨 Improvements ### 🎨 Improvements
* Jump back/forward buttons on mobile have been replaced with double-tap gestures on mobile. ([#3120](https://github.com/stashapp/stash/pull/3120))
* Added shift- and ctrl-keybinds for seeking for shorter and longer intervals, respectively. ([#3120](https://github.com/stashapp/stash/pull/3120))
* Added Estonian and Russian Language translations. Added in-progress Languages for Persian, Ukrainian, Bengali, Thai, Romainian, Hungarian, and Czech.([#3024] (https://github.com/stashapp/stash/pull/3024)) * Added Estonian and Russian Language translations. Added in-progress Languages for Persian, Ukrainian, Bengali, Thai, Romainian, Hungarian, and Czech.([#3024] (https://github.com/stashapp/stash/pull/3024))
* Also show imperial units for performer height and weight. ([#3097](https://github.com/stashapp/stash/pull/3097)) * Also show imperial units for performer height and weight. ([#3097](https://github.com/stashapp/stash/pull/3097))
* Limit number of items in selector drop-downs to 200. ([#3062](https://github.com/stashapp/stash/pull/3062)) * Limit number of items in selector drop-downs to 200. ([#3062](https://github.com/stashapp/stash/pull/3062))

View File

@@ -64,6 +64,12 @@
| `p n` | Play next scene in queue | | `p n` | Play next scene in queue |
| `p p` | Play previous scene in queue | | `p p` | Play previous scene in queue |
| `p r` | Play random scene in queue | | `p r` | Play random scene in queue |
| `←` | Seek backwards by 10 seconds |
| `→` | Seek forwards by 10 seconds |
| `Shift + ←` | Seek backwards by 5 seconds |
| `Shift + →` | Seek forwards by 5 seconds |
| `Ctrl/Alt + ←` | Seek backwards by 1 minute |
| `Ctrl/Alt + →` | Seek forwards by 1 minute |
| `{1-9}` | Seek to 10-90% duration | | `{1-9}` | Seek to 10-90% duration |
| `[` | Scrub backwards 10% duration | | `[` | Scrub backwards 10% duration |
| `]` | Scrub forwards 10% duration | | `]` | Scrub forwards 10% duration |

View File

@@ -26,8 +26,6 @@
@import "src/components/Dialogs/IdentifyDialog/styles.scss"; @import "src/components/Dialogs/IdentifyDialog/styles.scss";
@import "src/components/Dialogs/styles.scss"; @import "src/components/Dialogs/styles.scss";
@import "../node_modules/flag-icon-css/css/flag-icon.min.css"; @import "../node_modules/flag-icon-css/css/flag-icon.min.css";
@import "video.js/dist/video-js.css";
@import "videojs-seek-buttons/dist/videojs-seek-buttons.css";
/* stylelint-disable */ /* stylelint-disable */
#root { #root {

View File

@@ -1576,10 +1576,17 @@
resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz" resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz"
integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==
"@types/video.js@*", "@types/video.js@^7.3.28": "@types/video.js@*", "@types/video.js@^7.3.49":
version "7.3.28" version "7.3.49"
resolved "https://registry.yarnpkg.com/@types/video.js/-/video.js-7.3.28.tgz#9acb8c46db3984d556ee7321483ef834bf0a43a2" resolved "https://registry.yarnpkg.com/@types/video.js/-/video.js-7.3.49.tgz#33fbc421a02827c90935afbf7dcaac77170b6cda"
integrity sha512-vUxgGAQN+tAOx6OVu8wiSKGfvXJXvX++B5xKJTRpWfPHOV8y1VcJxqhIMb9nLfhIz0Pw3R69pWq2DzlOoxOn8Q== integrity sha512-GtBMH+rm7yyw5DAK7ycQeEd35x/EYoLK/49op+CqDDoNUm9XJEVOfb+EARKKe4TwP5jkaikjWqf5RFjmw8yHoQ==
"@types/videojs-mobile-ui@^0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@types/videojs-mobile-ui/-/videojs-mobile-ui-0.5.0.tgz#66934b140fd437fda361986f8e7e87b01dc39138"
integrity sha512-wqeapTB35qpLfERxvL5mZGoexf5bA2TreDpFgc3zyCdr7Acf86VItvo9oTclFeUc11wOo7W7/4ueZZAEYmlTaA==
dependencies:
"@types/video.js" "*"
"@types/videojs-seek-buttons@^2.1.0": "@types/videojs-seek-buttons@^2.1.0":
version "2.1.0" version "2.1.0"
@@ -6673,16 +6680,11 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
resize-observer-polyfill@^1.5.0: resize-observer-polyfill@^1.5.0, resize-observer-polyfill@^1.5.1:
version "1.5.1" version "1.5.1"
resolved "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz" resolved "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz"
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
resize-observer-polyfill@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
resolve-from@5.0.0, resolve-from@^5.0.0: resolve-from@5.0.0, resolve-from@^5.0.0:
version "5.0.0" version "5.0.0"
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz"
@@ -7910,20 +7912,19 @@ videojs-font@3.2.0:
resolved "https://registry.yarnpkg.com/videojs-font/-/videojs-font-3.2.0.tgz#212c9d3f4e4ec3fa7345167d64316add35e92232" resolved "https://registry.yarnpkg.com/videojs-font/-/videojs-font-3.2.0.tgz#212c9d3f4e4ec3fa7345167d64316add35e92232"
integrity sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA== integrity sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA==
videojs-landscape-fullscreen@^11.33.0: videojs-mobile-ui@^0.8.0:
version "11.33.0" version "0.8.0"
resolved "https://registry.yarnpkg.com/videojs-landscape-fullscreen/-/videojs-landscape-fullscreen-11.33.0.tgz#4033100b3a97399c994426e825662860dfc232e0" resolved "https://registry.yarnpkg.com/videojs-mobile-ui/-/videojs-mobile-ui-0.8.0.tgz#40a1c6f9302071b9bbe95937c934114600916ac5"
integrity sha512-Eex5ovlvIipHHif9LEhVL63zxxmOEQQi6Bt1P+EaA0QpjJAPF+CocWfhMItfGF1GcFQeP1ZFGCPTGw60n7vyUg== integrity sha512-Jd+u/ctjUkbZlT1cAA0umTu0LQwSZSFG+02cJxShuwq27B6rfrRALETK/gsuTc7U27lB9fbwcF7HBMaNxW62nA==
dependencies: dependencies:
global "^4.4.0" global "^4.4.0"
videojs-seek-buttons@^2.2.0: videojs-seek-buttons@^3.0.1:
version "2.2.0" version "3.0.1"
resolved "https://registry.yarnpkg.com/videojs-seek-buttons/-/videojs-seek-buttons-2.2.0.tgz#50b8da1178a5718ee5a7649fbb90a64a518103f7" resolved "https://registry.yarnpkg.com/videojs-seek-buttons/-/videojs-seek-buttons-3.0.1.tgz#cc2adc23a6372e8aa6c2e9fd0fe7e7831a46747f"
integrity sha512-yjCA6ntq+8fRKgZi/H6QJlghQWgA1x9oSRl6wfLODAcujhynDXetwMgRKGgl4NlV5af2bKY6erNtJ0kOBko/nQ== integrity sha512-scVWOqCMqHajlbwYZIzJ5nBYkDXTAhEpWjfcdCu8ykksA1barrKnEKdQvS84TtDWOx6UXDD/e/x0acYEZCDMEQ==
dependencies: dependencies:
global "^4.4.0" global "^4.4.0"
video.js "^6 || ^7"
videojs-vtt.js@^0.15.4: videojs-vtt.js@^0.15.4:
version "0.15.4" version "0.15.4"