feat(shell): implement escape key fullscreen behavior

This commit is contained in:
Tim 2025-04-01 12:23:42 +02:00
parent 2da5a0c6d1
commit e0d1662f86
9 changed files with 99 additions and 61 deletions

View file

@ -14,12 +14,13 @@ const languages = require('./languages');
const routesRegexp = require('./routesRegexp');
const useAnimationFrame = require('./useAnimationFrame');
const useBinaryState = require('./useBinaryState');
const useFullscreen = require('./useFullscreen');
const { default: useFullscreen } = require('./useFullscreen');
const useLiveRef = require('./useLiveRef');
const useModelState = require('./useModelState');
const useNotifications = require('./useNotifications');
const useOnScrollToBottom = require('./useOnScrollToBottom');
const useProfile = require('./useProfile');
const { default: useSettings } = require('./useSettings');
const { default: useShell } = require('./useShell');
const useStreamingServer = require('./useStreamingServer');
const useTorrent = require('./useTorrent');
@ -52,6 +53,7 @@ module.exports = {
useNotifications,
useOnScrollToBottom,
useProfile,
useSettings,
useShell,
useStreamingServer,
useTorrent,

View file

@ -1,32 +0,0 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
const useFullscreen = () => {
const [fullscreen, setFullscreen] = React.useState(document.fullscreenElement === document.documentElement);
const requestFullscreen = React.useCallback(() => {
document.documentElement.requestFullscreen();
}, []);
const exitFullscreen = React.useCallback(() => {
document.exitFullscreen();
}, []);
const toggleFullscreen = React.useCallback(() => {
if (fullscreen) {
exitFullscreen();
} else {
requestFullscreen();
}
}, [fullscreen]);
React.useEffect(() => {
const onFullscreenChange = () => {
setFullscreen(document.fullscreenElement === document.documentElement);
};
document.addEventListener('fullscreenchange', onFullscreenChange);
return () => {
document.removeEventListener('fullscreenchange', onFullscreenChange);
};
}, []);
return [fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen];
};
module.exports = useFullscreen;

View file

@ -0,0 +1,66 @@
// Copyright (C) 2017-2023 Smart code 203358507
import { useCallback, useEffect, useState } from 'react';
import useShell, { type WindowVisibilityState } from './useShell';
import useSettings from './useSettings';
const useFullscreen = () => {
const shell = useShell();
const [settings] = useSettings();
const [fullscreen, setFullscreen] = useState(false);
const requestFullscreen = useCallback(() => {
if (shell.active) {
shell.send('win-set-visibility', { fullscreen: true });
} else {
document.documentElement.requestFullscreen();
}
}, []);
const exitFullscreen = useCallback(() => {
if (shell.active) {
shell.send('win-set-visibility', { fullscreen: false });
} else {
document.exitFullscreen();
}
}, []);
const toggleFullscreen = useCallback(() => {
fullscreen ? exitFullscreen() : requestFullscreen();
}, [fullscreen]);
useEffect(() => {
const onWindowVisibilityChanged = (state: WindowVisibilityState) => {
setFullscreen(state.isFullscreen === true);
};
const onFullscreenChange = () => {
setFullscreen(document.fullscreenElement === document.documentElement);
};
const onKeyDown = (event: KeyboardEvent) => {
if (event.code === 'Escape' && settings.escExitFullscreen) {
exitFullscreen();
}
if ((event.code === 'F11' || event.code === 'KeyF') && shell.active) {
toggleFullscreen();
}
};
shell.on('win-visibility-changed', onWindowVisibilityChanged);
document.addEventListener('keydown', onKeyDown);
document.addEventListener('fullscreenchange', onFullscreenChange);
return () => {
shell.off('win-visibility-changed', onWindowVisibilityChanged);
document.removeEventListener('keydown', onKeyDown);
document.removeEventListener('fullscreenchange', onFullscreenChange);
};
}, [settings.escExitFullscreen]);
return [fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen];
};
export default useFullscreen;

View file

@ -1,13 +1,14 @@
// Copyright (C) 2017-2023 Smart code 203358507
// Copyright (C) 2017-2025 Smart code 203358507
const React = require('react');
const { useServices } = require('stremio/services');
const { useProfile } = require('stremio/common');
import { useCallback } from 'react';
import { useServices } from 'stremio/services';
import useProfile from './useProfile';
const useSettings = () => {
const useSettings = (): [Settings, (settings: Settings) => void] => {
const { core } = useServices();
const profile = useProfile();
const updateSettings = React.useCallback((settings) => {
const updateSettings = useCallback((settings: Settings) => {
core.transport.dispatch({
action: 'Ctx',
args: {
@ -19,7 +20,8 @@ const useSettings = () => {
}
});
}, [profile]);
return [profile.settings, updateSettings];
};
module.exports = useSettings;
export default useSettings;

View file

@ -17,6 +17,10 @@ type ShellEvent = {
args: string[];
};
export type WindowVisibilityState = {
isFullscreen: boolean;
};
const createId = () => Math.floor(Math.random() * 9999) + 1;
const useShell = () => {
@ -28,7 +32,7 @@ const useShell = () => {
events.off(name, listener);
};
const send = (method: string, ...args: (string | number)[]) => {
const send = (method: string, ...args: (string | number | object)[]) => {
try {
transport?.postMessage(JSON.stringify({
id: createId(),

View file

@ -5,7 +5,7 @@ const PropTypes = require('prop-types');
const classnames = require('classnames');
const { default: Icon } = require('@stremio/stremio-icons/react');
const { Button, Image } = require('stremio/components');
const useFullscreen = require('stremio/common/useFullscreen');
const { default: useFullscreen } = require('stremio/common/useFullscreen');
const usePWA = require('stremio/common/usePWA');
const SearchBar = require('./SearchBar');
const NavMenu = require('./NavMenu');

View file

@ -8,7 +8,7 @@ const langs = require('langs');
const { useTranslation } = require('react-i18next');
const { useRouteFocused } = require('stremio-router');
const { useServices } = require('stremio/services');
const { onFileDrop, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, CONSTANTS } = require('stremio/common');
const { onFileDrop, useSettings, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, CONSTANTS } = require('stremio/common');
const { HorizontalNavBar, Transition, ContextMenu } = require('stremio/components');
const BufferingLoader = require('./BufferingLoader');
const VolumeChangeIndicator = require('./VolumeChangeIndicator');
@ -23,7 +23,6 @@ const SpeedMenu = require('./SpeedMenu');
const { default: SideDrawerButton } = require('./SideDrawerButton');
const { default: SideDrawer } = require('./SideDrawer');
const usePlayer = require('./usePlayer');
const useSettings = require('./useSettings');
const useStatistics = require('./useStatistics');
const useVideo = require('./useVideo');
const styles = require('./styles');
@ -565,6 +564,7 @@ const Player = ({ urlParams, queryParams }) => {
}
case 'Escape': {
closeMenus();
!settings.escExitFullscreen && window.history.back();
break;
}
}
@ -595,7 +595,7 @@ const Player = ({ urlParams, queryParams }) => {
window.removeEventListener('keyup', onKeyUp);
window.removeEventListener('wheel', onWheel);
};
}, [player.metaItem, player.selected, streamingServer.statistics, settings.seekTimeDuration, settings.seekShortTimeDuration, 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]);
React.useEffect(() => {
video.events.on('error', onError);

View file

@ -1,2 +0,0 @@
declare const useSettings: () => [Settings, (settings: any) => void];
export = useSettings;

View file

@ -342,6 +342,18 @@ const Settings = () => {
/>
</div>
}
{
shell.active &&
<div className={styles['option-container']}>
<div className={styles['option-name-container']}>
<div className={styles['label']}>{ t('SETTINGS_FULLSCREEN_EXIT') }</div>
</div>
<Toggle
className={classnames(styles['option-input-container'], styles['toggle-container'])}
{...escExitFullscreenToggle}
/>
</div>
}
<div className={styles['option-container']}>
<div className={styles['option-name-container']}>
<div className={styles['label']}>{ t('SETTINGS_BLUR_UNWATCHED_IMAGE') }</div>
@ -368,20 +380,6 @@ const Settings = () => {
{...subtitlesLanguageSelect}
/>
</div>
{
shell.active ?
<div className={styles['option-container']}>
<div className={styles['option-name-container']}>
<div className={styles['label']}>{ t('SETTINGS_FULLSCREEN_EXIT') }</div>
</div>
<Toggle
className={classnames(styles['option-input-container'], styles['toggle-container'])}
{...escExitFullscreenToggle}
/>
</div>
:
null
}
<div className={styles['option-container']}>
<div className={styles['option-name-container']}>
<div className={styles['label']}>{ t('SETTINGS_SUBTITLES_SIZE') }</div>