mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 21:27:05 +00:00
Merge branch 'development' of https://github.com/Stremio/stremio-web into refactor/settings
This commit is contained in:
commit
409267cb44
8 changed files with 173 additions and 5 deletions
6
package-lock.json
generated
6
package-lock.json
generated
|
|
@ -36,7 +36,7 @@
|
||||||
"react-i18next": "^15.1.3",
|
"react-i18next": "^15.1.3",
|
||||||
"react-is": "18.3.1",
|
"react-is": "18.3.1",
|
||||||
"spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
|
"spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
|
||||||
"stremio-translations": "github:Stremio/stremio-translations#8efdffbcf6eeadf01ab658e54adcc6a236b7b10f",
|
"stremio-translations": "github:Stremio/stremio-translations#8212fa77c4febd22ddb611590e9fb574dc845416",
|
||||||
"url": "0.11.4",
|
"url": "0.11.4",
|
||||||
"use-long-press": "^3.2.0"
|
"use-long-press": "^3.2.0"
|
||||||
},
|
},
|
||||||
|
|
@ -13434,8 +13434,8 @@
|
||||||
},
|
},
|
||||||
"node_modules/stremio-translations": {
|
"node_modules/stremio-translations": {
|
||||||
"version": "1.44.12",
|
"version": "1.44.12",
|
||||||
"resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#8efdffbcf6eeadf01ab658e54adcc6a236b7b10f",
|
"resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#8212fa77c4febd22ddb611590e9fb574dc845416",
|
||||||
"integrity": "sha512-b38OjGwlsvFm/aNn/ia18mPxPjZvnI/GaToppn1XaQqCuZuSHxQlYDddwOYTztskWo4VO/IZmCi3UFewqpsqCQ==",
|
"integrity": "sha512-5DladLUsghLlVRsZh2bBnb7UMqU8NEYMHc+YbzBvb1llgMk9elXFSHtAjInepZlC5zWx2pJYOQ8lQzzqogQdFw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/string_decoder": {
|
"node_modules/string_decoder": {
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
"react-i18next": "^15.1.3",
|
"react-i18next": "^15.1.3",
|
||||||
"react-is": "18.3.1",
|
"react-is": "18.3.1",
|
||||||
"spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
|
"spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
|
||||||
"stremio-translations": "github:Stremio/stremio-translations#8efdffbcf6eeadf01ab658e54adcc6a236b7b10f",
|
"stremio-translations": "github:Stremio/stremio-translations#8212fa77c4febd22ddb611590e9fb574dc845416",
|
||||||
"url": "0.11.4",
|
"url": "0.11.4",
|
||||||
"use-long-press": "^3.2.0"
|
"use-long-press": "^3.2.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,19 @@
|
||||||
transform: translateY(100%);
|
transform: translateY(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fade-enter {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-active {
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 0.3s cubic-bezier(0.32, 0, 0.67, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-exit {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes fade-in-no-motion {
|
@keyframes fade-in-no-motion {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
|
||||||
23
src/routes/Player/Indicator/Indicator.less
Normal file
23
src/routes/Player/Indicator/Indicator.less
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
.indicator-container {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 4rem;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
.indicator {
|
||||||
|
flex: none;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 2rem;
|
||||||
|
border-radius: 4rem;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--primary-foreground-color);
|
||||||
|
background-color: var(--modal-background-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
73
src/routes/Player/Indicator/Indicator.tsx
Normal file
73
src/routes/Player/Indicator/Indicator.tsx
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
import { Transition } from 'stremio/components';
|
||||||
|
import { useBinaryState } from 'stremio/common';
|
||||||
|
import styles from './Indicator.less';
|
||||||
|
|
||||||
|
type Property = {
|
||||||
|
label: string,
|
||||||
|
format: (value: number) => string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PROPERTIES: Record<string, Property> = {
|
||||||
|
'extraSubtitlesDelay': {
|
||||||
|
label: 'SUBTITLES_DELAY',
|
||||||
|
format: (value) => `${(value / 1000).toFixed(2)}s`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type VideoState = Record<string, number>;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className: string,
|
||||||
|
videoState: VideoState,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Indicator = ({ className, videoState }: Props) => {
|
||||||
|
const timeout = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const prevVideoState = useRef<VideoState>(videoState);
|
||||||
|
|
||||||
|
const [shown, show, hide] = useBinaryState(false);
|
||||||
|
const [current, setCurrent] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const label = useMemo(() => {
|
||||||
|
const property = current && PROPERTIES[current];
|
||||||
|
return property && t(property.label);
|
||||||
|
}, [current]);
|
||||||
|
|
||||||
|
const value = useMemo(() => {
|
||||||
|
const property = current && PROPERTIES[current];
|
||||||
|
const value = current && videoState[current];
|
||||||
|
return property && value && property.format(value);
|
||||||
|
}, [current, videoState]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
for (const property of Object.keys(PROPERTIES)) {
|
||||||
|
const prev = prevVideoState.current[property];
|
||||||
|
const next = videoState[property];
|
||||||
|
|
||||||
|
if (next && next !== prev) {
|
||||||
|
setCurrent(property);
|
||||||
|
show();
|
||||||
|
|
||||||
|
timeout.current && clearTimeout(timeout.current);
|
||||||
|
timeout.current = setTimeout(hide, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prevVideoState.current = videoState;
|
||||||
|
}, [videoState]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Transition when={shown} name={'fade'}>
|
||||||
|
<div className={classNames(className, styles['indicator-container'])}>
|
||||||
|
<div className={styles['indicator']}>
|
||||||
|
<div>{label} {value}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Indicator;
|
||||||
|
|
@ -27,6 +27,7 @@ const useStatistics = require('./useStatistics');
|
||||||
const useVideo = require('./useVideo');
|
const useVideo = require('./useVideo');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
const Video = require('./Video');
|
const Video = require('./Video');
|
||||||
|
const { default: Indicator } = require('./Indicator/Indicator');
|
||||||
|
|
||||||
const Player = ({ urlParams, queryParams }) => {
|
const Player = ({ urlParams, queryParams }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
@ -216,6 +217,16 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
video.setProp('extraSubtitlesDelay', delay);
|
video.setProp('extraSubtitlesDelay', delay);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onIncreaseSubtitlesDelay = React.useCallback(() => {
|
||||||
|
const delay = video.state.extraSubtitlesDelay + 250;
|
||||||
|
onExtraSubtitlesDelayChanged(delay);
|
||||||
|
}, [video.state.extraSubtitlesDelay, onExtraSubtitlesDelayChanged]);
|
||||||
|
|
||||||
|
const onDecreaseSubtitlesDelay = React.useCallback(() => {
|
||||||
|
const delay = video.state.extraSubtitlesDelay - 250;
|
||||||
|
onExtraSubtitlesDelayChanged(delay);
|
||||||
|
}, [video.state.extraSubtitlesDelay, onExtraSubtitlesDelayChanged]);
|
||||||
|
|
||||||
const onSubtitlesSizeChanged = React.useCallback((size) => {
|
const onSubtitlesSizeChanged = React.useCallback((size) => {
|
||||||
updateSettings({ subtitlesSize: size });
|
updateSettings({ subtitlesSize: size });
|
||||||
}, [updateSettings]);
|
}, [updateSettings]);
|
||||||
|
|
@ -587,6 +598,14 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'KeyG': {
|
||||||
|
onDecreaseSubtitlesDelay();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'KeyH': {
|
||||||
|
onIncreaseSubtitlesDelay();
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'Escape': {
|
case 'Escape': {
|
||||||
closeMenus();
|
closeMenus();
|
||||||
!settings.escExitFullscreen && window.history.back();
|
!settings.escExitFullscreen && window.history.back();
|
||||||
|
|
@ -620,7 +639,29 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
window.removeEventListener('keyup', onKeyUp);
|
window.removeEventListener('keyup', onKeyUp);
|
||||||
window.removeEventListener('wheel', onWheel);
|
window.removeEventListener('wheel', onWheel);
|
||||||
};
|
};
|
||||||
}, [player.metaItem, player.selected, streamingServer.statistics, settings.seekTimeDuration, settings.seekShortTimeDuration, settings.escExitFullscreen, routeFocused, menusOpen, nextVideoPopupOpen, video.state.paused, video.state.time, video.state.volume, video.state.audioTracks, video.state.subtitlesTracks, video.state.extraSubtitlesTracks, video.state.playbackSpeed, toggleSubtitlesMenu, toggleStatisticsMenu, toggleSideDrawer]);
|
}, [
|
||||||
|
player.metaItem,
|
||||||
|
player.selected,
|
||||||
|
streamingServer.statistics,
|
||||||
|
settings.seekTimeDuration,
|
||||||
|
settings.seekShortTimeDuration,
|
||||||
|
settings.escExitFullscreen,
|
||||||
|
routeFocused,
|
||||||
|
menusOpen,
|
||||||
|
nextVideoPopupOpen,
|
||||||
|
video.state.paused,
|
||||||
|
video.state.time,
|
||||||
|
video.state.volume,
|
||||||
|
video.state.audioTracks,
|
||||||
|
video.state.subtitlesTracks,
|
||||||
|
video.state.extraSubtitlesTracks,
|
||||||
|
video.state.playbackSpeed,
|
||||||
|
toggleSubtitlesMenu,
|
||||||
|
toggleStatisticsMenu,
|
||||||
|
toggleSideDrawer,
|
||||||
|
onDecreaseSubtitlesDelay,
|
||||||
|
onIncreaseSubtitlesDelay,
|
||||||
|
]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
video.events.on('error', onError);
|
video.events.on('error', onError);
|
||||||
|
|
@ -760,6 +801,10 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
onMouseOver={onBarMouseMove}
|
onMouseOver={onBarMouseMove}
|
||||||
onTouchEnd={onContainerMouseLeave}
|
onTouchEnd={onContainerMouseLeave}
|
||||||
/>
|
/>
|
||||||
|
<Indicator
|
||||||
|
className={classnames(styles['layer'], styles['indicator-layer'])}
|
||||||
|
videoState={video.state}
|
||||||
|
/>
|
||||||
{
|
{
|
||||||
nextVideoPopupOpen ?
|
nextVideoPopupOpen ?
|
||||||
<NextVideoPopup
|
<NextVideoPopup
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,13 @@ html:not(.active-slider-within) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.indicator-layer {
|
||||||
|
top: initial;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 10rem;
|
||||||
|
}
|
||||||
|
|
||||||
&.menu-layer {
|
&.menu-layer {
|
||||||
top: initial;
|
top: initial;
|
||||||
left: initial;
|
left: initial;
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,13 @@ const Shortcuts = forwardRef<HTMLDivElement>((_, ref) => {
|
||||||
<kbd>F</kbd>
|
<kbd>F</kbd>
|
||||||
</div>
|
</div>
|
||||||
</Option>
|
</Option>
|
||||||
|
<Option label={'SETTINGS_SHORTCUT_SUBTITLES_DELAY'}>
|
||||||
|
<div className={styles['shortcut-container']}>
|
||||||
|
<kbd>G</kbd>
|
||||||
|
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_AND') }</div>
|
||||||
|
<kbd>H</kbd>
|
||||||
|
</div>
|
||||||
|
</Option>
|
||||||
<Option label={'SETTINGS_SHORTCUT_NAVIGATE_MENUS'}>
|
<Option label={'SETTINGS_SHORTCUT_NAVIGATE_MENUS'}>
|
||||||
<div className={styles['shortcut-container']}>
|
<div className={styles['shortcut-container']}>
|
||||||
<kbd>1</kbd>
|
<kbd>1</kbd>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue