diff --git a/ui/v2.5/package.json b/ui/v2.5/package.json index d5f21e3d3..21c25ab58 100644 --- a/ui/v2.5/package.json +++ b/ui/v2.5/package.json @@ -74,8 +74,8 @@ "thehandy": "^1.0.3", "universal-cookie": "^4.0.4", "video.js": "^7.20.3", - "videojs-landscape-fullscreen": "^11.33.0", - "videojs-seek-buttons": "^2.2.0", + "videojs-mobile-ui": "^0.8.0", + "videojs-seek-buttons": "^3.0.1", "videojs-vtt.js": "^0.15.4", "vite": "^2.9.13", "vite-plugin-compression": "^0.3.5", @@ -103,7 +103,8 @@ "@types/react-router-dom": "5.1.7", "@types/react-router-hash-link": "^1.2.1", "@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", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", diff --git a/ui/v2.5/src/@types/landscape-fullscreen.d.ts b/ui/v2.5/src/@types/landscape-fullscreen.d.ts deleted file mode 100644 index 5cafaceef..000000000 --- a/ui/v2.5/src/@types/landscape-fullscreen.d.ts +++ /dev/null @@ -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 }; - } - } -} diff --git a/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx b/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx index 09d4cf39c..032b41efc 100644 --- a/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx +++ b/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx @@ -7,15 +7,14 @@ import React, { useState, } from "react"; import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from "video.js"; +import "videojs-mobile-ui"; import "videojs-seek-buttons"; -import "videojs-landscape-fullscreen"; import "./live"; import "./PlaylistButtons"; import "./source-selector"; import "./persist-volume"; import "./markers"; import "./vtt-thumbnails"; -import "./big-buttons"; import "./track-activity"; import cx from "classnames"; import { @@ -36,6 +35,18 @@ import { VIDEO_PLAYER_ID } from "./util"; import { IUIConfig } from "src/core/config"; 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) { const duration = player.duration(); const time = duration * percent; @@ -50,6 +61,21 @@ function handleHotkeys(player: VideoJsPlayer, event: videojs.KeyboardEvent) { 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) { return; } @@ -67,12 +93,6 @@ function handleHotkeys(player: VideoJsPlayer, event: videojs.KeyboardEvent) { if (player.isFullscreen()) player.exitFullscreen(); else player.requestFullscreen(); 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 player.volume(player.volume() + 0.1); break; @@ -263,7 +283,6 @@ export const ScenePlayer: React.FC = ({ markers: {}, sourceSelector: {}, persistVolume: {}, - bigButtons: {}, seekButtons: { forward: 10, back: 10, @@ -421,20 +440,21 @@ export const ScenePlayer: React.FC = ({ interactiveClient.pause(); interactiveReady.current = false; - const alwaysStartFromBeginning = - uiConfig?.alwaysStartFromBeginning ?? false; const isLandscape = file.height && file.width && file.width > file.height; - - if (isLandscape) { - player.landscapeFullscreen({ - fullscreen: { - enterOnRotate: true, - exitOnRotate: true, - alwaysInLandscapeMode: true, - iOS: false, - }, - }); - } + const mobileUiOptions = { + fullscreen: { + enterOnRotate: true, + exitOnRotate: true, + lockOnRotate: true, + lockToLandscapeOnEnter: isLandscape, + }, + touchControls: { + seekSeconds: 10, + tapTimeout: 500, + disableOnEnd: false, + }, + }; + player.mobileUi(mobileUiOptions); const { duration } = file; const sourceSelector = player.sourceSelector(); @@ -455,15 +475,6 @@ export const ScenePlayer: React.FC = ({ }) ); - const markers = player.markers(); - markers.clearMarkers(); - for (const marker of scene.scene_markers) { - markers.addMarker({ - title: getMarkerTitle(marker), - time: marker.seconds, - }); - } - function getDefaultLanguageCode() { let languageCode = window.navigator.language; @@ -507,28 +518,25 @@ export const ScenePlayer: React.FC = ({ } } - if (scene.paths.screenshot) { - player.poster(scene.paths.screenshot); - } else { - player.poster(""); - } - auto.current = autoplay || (interfaceConfig?.autostartVideo ?? false) || _initialTimestamp > 0; - let startPositition = _initialTimestamp; + const alwaysStartFromBeginning = + uiConfig?.alwaysStartFromBeginning ?? false; + + let startPosition = _initialTimestamp; if ( - !startPositition && + !startPosition && !(alwaysStartFromBeginning || sessionInitialised) && file.duration > scene.resume_time! ) { - startPositition = scene.resume_time!; + startPosition = scene.resume_time!; } - initialTimestamp.current = startPositition; - setTime(startPositition); + initialTimestamp.current = startPosition; + setTime(startPosition); setSessionInitialised(true); player.load(); @@ -556,6 +564,26 @@ export const ScenePlayer: React.FC = ({ _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(() => { const player = playerRef.current; if (!player) return; diff --git a/ui/v2.5/src/components/ScenePlayer/ScenePlayerScrubber.tsx b/ui/v2.5/src/components/ScenePlayer/ScenePlayerScrubber.tsx index fd992f03f..07a5417a8 100644 --- a/ui/v2.5/src/components/ScenePlayer/ScenePlayerScrubber.tsx +++ b/ui/v2.5/src/components/ScenePlayer/ScenePlayerScrubber.tsx @@ -211,6 +211,8 @@ export const ScenePlayerScrubber: React.FC = ({ mouseDown.current = false; + contentEl.current!.classList.remove("dragging"); + let newPosition = position.current; const midpointOffset = slider.clientWidth / 2; const delta = Math.abs(event.clientX - startMouseEvent.current!.clientX); @@ -256,6 +258,8 @@ export const ScenePlayerScrubber: React.FC = ({ onScroll(); } + contentEl.current!.classList.add("dragging"); + // negative dragging right (past), positive left (future) const delta = event.clientX - lastMouseEvent.current!.clientX; @@ -338,7 +342,6 @@ export const ScenePlayerScrubber: React.FC = ({ return (