mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-21 11:42:05 +00:00
Merge pull request #883 from Stremio/feat/shell-pause-on-minimize
Some checks are pending
Build / build (push) Waiting to run
Some checks are pending
Build / build (push) Waiting to run
Player(Shell): add pause on window minimize setting
This commit is contained in:
commit
68f044f7b5
6 changed files with 86 additions and 28 deletions
|
|
@ -21,7 +21,6 @@ const RouterWithProtectedRoutes = withCoreSuspender(withProtectedRoutes(Router))
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
const shell = useShell();
|
const shell = useShell();
|
||||||
const [windowHidden, setWindowHidden] = React.useState(false);
|
|
||||||
const onPathNotMatch = React.useCallback(() => {
|
const onPathNotMatch = React.useCallback(() => {
|
||||||
return NotFound;
|
return NotFound;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -102,10 +101,6 @@ const App = () => {
|
||||||
|
|
||||||
// Handle shell events
|
// Handle shell events
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const onWindowVisibilityChanged = (state) => {
|
|
||||||
setWindowHidden(state.visible === false && state.visibility === 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onOpenMedia = (data) => {
|
const onOpenMedia = (data) => {
|
||||||
if (data.startsWith('stremio:///')) return;
|
if (data.startsWith('stremio:///')) return;
|
||||||
if (data.startsWith('stremio://')) {
|
if (data.startsWith('stremio://')) {
|
||||||
|
|
@ -116,11 +111,9 @@ const App = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
shell.on('win-visibility-changed', onWindowVisibilityChanged);
|
|
||||||
shell.on('open-media', onOpenMedia);
|
shell.on('open-media', onOpenMedia);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
shell.off('win-visibility-changed', onWindowVisibilityChanged);
|
|
||||||
shell.off('open-media', onOpenMedia);
|
shell.off('open-media', onOpenMedia);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -133,7 +126,7 @@ const App = () => {
|
||||||
i18n.changeLanguage(args.settings.interfaceLanguage);
|
i18n.changeLanguage(args.settings.interfaceLanguage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args?.settings?.quitOnClose && windowHidden) {
|
if (args?.settings?.quitOnClose && shell.windowClosed) {
|
||||||
shell.send('quit');
|
shell.send('quit');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,7 +139,7 @@ const App = () => {
|
||||||
i18n.changeLanguage(state.profile.settings.interfaceLanguage);
|
i18n.changeLanguage(state.profile.settings.interfaceLanguage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state?.profile?.settings?.quitOnClose && windowHidden) {
|
if (state?.profile?.settings?.quitOnClose && shell.windowClosed) {
|
||||||
shell.send('quit');
|
shell.send('quit');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -191,7 +184,7 @@ const App = () => {
|
||||||
services.core.transport.off('CoreEvent', onCoreEvent);
|
services.core.transport.off('CoreEvent', onCoreEvent);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [initialized, windowHidden]);
|
}, [initialized, shell.windowClosed]);
|
||||||
return (
|
return (
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ServicesProvider services={services}>
|
<ServicesProvider services={services}>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (C) 2017-2023 Smart code 203358507
|
// Copyright (C) 2017-2023 Smart code 203358507
|
||||||
|
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import useShell, { type WindowVisibilityState } from './useShell';
|
import useShell, { type WindowVisibility } from './useShell';
|
||||||
import useSettings from './useSettings';
|
import useSettings from './useSettings';
|
||||||
|
|
||||||
const useFullscreen = () => {
|
const useFullscreen = () => {
|
||||||
|
|
@ -31,7 +31,7 @@ const useFullscreen = () => {
|
||||||
}, [fullscreen]);
|
}, [fullscreen]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onWindowVisibilityChanged = (state: WindowVisibilityState) => {
|
const onWindowVisibilityChanged = (state: WindowVisibility) => {
|
||||||
setFullscreen(state.isFullscreen === true);
|
setFullscreen(state.isFullscreen === true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useEffect } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import EventEmitter from 'eventemitter3';
|
import EventEmitter from 'eventemitter3';
|
||||||
|
|
||||||
const SHELL_EVENT_OBJECT = 'transport';
|
const SHELL_EVENT_OBJECT = 'transport';
|
||||||
|
|
@ -17,13 +17,22 @@ type ShellEvent = {
|
||||||
args: string[];
|
args: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WindowVisibilityState = {
|
export type WindowVisibility = {
|
||||||
|
visible: boolean;
|
||||||
|
visibility: number;
|
||||||
isFullscreen: boolean;
|
isFullscreen: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type WindowState = {
|
||||||
|
state: number;
|
||||||
|
};
|
||||||
|
|
||||||
const createId = () => Math.floor(Math.random() * 9999) + 1;
|
const createId = () => Math.floor(Math.random() * 9999) + 1;
|
||||||
|
|
||||||
const useShell = () => {
|
const useShell = () => {
|
||||||
|
const [windowClosed, setWindowClosed] = useState(false);
|
||||||
|
const [windowHidden, setWindowHidden] = useState(false);
|
||||||
|
|
||||||
const on = (name: string, listener: (arg: any) => void) => {
|
const on = (name: string, listener: (arg: any) => void) => {
|
||||||
events.on(name, listener);
|
events.on(name, listener);
|
||||||
};
|
};
|
||||||
|
|
@ -46,6 +55,24 @@ const useShell = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onWindowVisibilityChanged = (data: WindowVisibility) => {
|
||||||
|
setWindowClosed(data.visible === false && data.visibility === 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onWindowStateChanged = (data: WindowState) => {
|
||||||
|
setWindowHidden(data.state === 9);
|
||||||
|
};
|
||||||
|
|
||||||
|
on('win-visibility-changed', onWindowVisibilityChanged);
|
||||||
|
on('win-state-changed', onWindowStateChanged);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
off('win-visibility-changed', onWindowVisibilityChanged);
|
||||||
|
off('win-state-changed', onWindowStateChanged);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!transport) return;
|
if (!transport) return;
|
||||||
|
|
||||||
|
|
@ -70,6 +97,8 @@ const useShell = () => {
|
||||||
send,
|
send,
|
||||||
on,
|
on,
|
||||||
off,
|
off,
|
||||||
|
windowClosed,
|
||||||
|
windowHidden,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const langs = require('langs');
|
||||||
const { useTranslation } = require('react-i18next');
|
const { useTranslation } = require('react-i18next');
|
||||||
const { useRouteFocused } = require('stremio-router');
|
const { useRouteFocused } = require('stremio-router');
|
||||||
const { useServices } = require('stremio/services');
|
const { useServices } = require('stremio/services');
|
||||||
const { onFileDrop, useSettings, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, CONSTANTS } = require('stremio/common');
|
const { onFileDrop, useSettings, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, CONSTANTS, useShell } = require('stremio/common');
|
||||||
const { HorizontalNavBar, Transition, ContextMenu } = require('stremio/components');
|
const { HorizontalNavBar, Transition, ContextMenu } = require('stremio/components');
|
||||||
const BufferingLoader = require('./BufferingLoader');
|
const BufferingLoader = require('./BufferingLoader');
|
||||||
const VolumeChangeIndicator = require('./VolumeChangeIndicator');
|
const VolumeChangeIndicator = require('./VolumeChangeIndicator');
|
||||||
|
|
@ -30,7 +30,8 @@ const Video = require('./Video');
|
||||||
|
|
||||||
const Player = ({ urlParams, queryParams }) => {
|
const Player = ({ urlParams, queryParams }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { chromecast, shell, core } = useServices();
|
const services = useServices();
|
||||||
|
const shell = useShell();
|
||||||
const forceTranscoding = React.useMemo(() => {
|
const forceTranscoding = React.useMemo(() => {
|
||||||
return queryParams.has('forceTranscoding');
|
return queryParams.has('forceTranscoding');
|
||||||
}, [queryParams]);
|
}, [queryParams]);
|
||||||
|
|
@ -46,7 +47,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
const [seeking, setSeeking] = React.useState(false);
|
const [seeking, setSeeking] = React.useState(false);
|
||||||
|
|
||||||
const [casting, setCasting] = React.useState(() => {
|
const [casting, setCasting] = React.useState(() => {
|
||||||
return chromecast.active && chromecast.transport.getCastState() === cast.framework.CastState.CONNECTED;
|
return services.chromecast.active && services.chromecast.transport.getCastState() === cast.framework.CastState.CONNECTED;
|
||||||
});
|
});
|
||||||
const playbackDevices = React.useMemo(() => streamingServer.playbackDevices !== null && streamingServer.playbackDevices.type === 'Ready' ? streamingServer.playbackDevices.content : [], [streamingServer]);
|
const playbackDevices = React.useMemo(() => streamingServer.playbackDevices !== null && streamingServer.playbackDevices.type === 'Ready' ? streamingServer.playbackDevices.content : [], [streamingServer]);
|
||||||
|
|
||||||
|
|
@ -320,8 +321,8 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
null,
|
null,
|
||||||
seriesInfo: player.seriesInfo,
|
seriesInfo: player.seriesInfo,
|
||||||
}, {
|
}, {
|
||||||
chromecastTransport: chromecast.active ? chromecast.transport : null,
|
chromecastTransport: services.chromecast.active ? services.chromecast.transport : null,
|
||||||
shellTransport: shell.active ? shell.transport : null,
|
shellTransport: services.shell.active ? services.shell.transport : null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [streamingServer.baseUrl, player.selected, forceTranscoding, casting]);
|
}, [streamingServer.baseUrl, player.selected, forceTranscoding, casting]);
|
||||||
|
|
@ -442,12 +443,12 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
const toastFilter = (item) => item?.dataset?.type === 'CoreEvent';
|
const toastFilter = (item) => item?.dataset?.type === 'CoreEvent';
|
||||||
toast.addFilter(toastFilter);
|
toast.addFilter(toastFilter);
|
||||||
const onCastStateChange = () => {
|
const onCastStateChange = () => {
|
||||||
setCasting(chromecast.active && chromecast.transport.getCastState() === cast.framework.CastState.CONNECTED);
|
setCasting(services.chromecast.active && services.chromecast.transport.getCastState() === cast.framework.CastState.CONNECTED);
|
||||||
};
|
};
|
||||||
const onChromecastServiceStateChange = () => {
|
const onChromecastServiceStateChange = () => {
|
||||||
onCastStateChange();
|
onCastStateChange();
|
||||||
if (chromecast.active) {
|
if (services.chromecast.active) {
|
||||||
chromecast.transport.on(
|
services.chromecast.transport.on(
|
||||||
cast.framework.CastContextEventType.CAST_STATE_CHANGED,
|
cast.framework.CastContextEventType.CAST_STATE_CHANGED,
|
||||||
onCastStateChange
|
onCastStateChange
|
||||||
);
|
);
|
||||||
|
|
@ -458,15 +459,15 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
onPauseRequested();
|
onPauseRequested();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
chromecast.on('stateChanged', onChromecastServiceStateChange);
|
services.chromecast.on('stateChanged', onChromecastServiceStateChange);
|
||||||
core.transport.on('CoreEvent', onCoreEvent);
|
services.core.transport.on('CoreEvent', onCoreEvent);
|
||||||
onChromecastServiceStateChange();
|
onChromecastServiceStateChange();
|
||||||
return () => {
|
return () => {
|
||||||
toast.removeFilter(toastFilter);
|
toast.removeFilter(toastFilter);
|
||||||
chromecast.off('stateChanged', onChromecastServiceStateChange);
|
services.chromecast.off('stateChanged', onChromecastServiceStateChange);
|
||||||
core.transport.off('CoreEvent', onCoreEvent);
|
services.core.transport.off('CoreEvent', onCoreEvent);
|
||||||
if (chromecast.active) {
|
if (services.chromecast.active) {
|
||||||
chromecast.transport.off(
|
services.chromecast.transport.off(
|
||||||
cast.framework.CastContextEventType.CAST_STATE_CHANGED,
|
cast.framework.CastContextEventType.CAST_STATE_CHANGED,
|
||||||
onCastStateChange
|
onCastStateChange
|
||||||
);
|
);
|
||||||
|
|
@ -474,6 +475,12 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (settings.pauseOnMinimize && (shell.windowClosed || shell.windowHidden)) {
|
||||||
|
onPauseRequested();
|
||||||
|
}
|
||||||
|
}, [settings.pauseOnMinimize, shell.windowClosed, shell.windowHidden]);
|
||||||
|
|
||||||
React.useLayoutEffect(() => {
|
React.useLayoutEffect(() => {
|
||||||
const onKeyDown = (event) => {
|
const onKeyDown = (event) => {
|
||||||
switch (event.code) {
|
switch (event.code) {
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ const Settings = () => {
|
||||||
bingeWatchingToggle,
|
bingeWatchingToggle,
|
||||||
playInBackgroundToggle,
|
playInBackgroundToggle,
|
||||||
hardwareDecodingToggle,
|
hardwareDecodingToggle,
|
||||||
|
pauseOnMinimizeToggle,
|
||||||
} = useProfileSettingsInputs(profile);
|
} = useProfileSettingsInputs(profile);
|
||||||
const {
|
const {
|
||||||
streamingServerRemoteUrlInput,
|
streamingServerRemoteUrlInput,
|
||||||
|
|
@ -529,6 +530,18 @@ const Settings = () => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
shell.active &&
|
||||||
|
<div className={styles['option-container']}>
|
||||||
|
<div className={styles['option-name-container']}>
|
||||||
|
<div className={styles['label']}>{ t('SETTINGS_PAUSE_MINIMIZED') }</div>
|
||||||
|
</div>
|
||||||
|
<Toggle
|
||||||
|
className={classnames(styles['option-input-container'], styles['toggle-container'])}
|
||||||
|
{...pauseOnMinimizeToggle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div ref={streamingServerSectionRef} className={styles['section-container']}>
|
<div ref={streamingServerSectionRef} className={styles['section-container']}>
|
||||||
<div className={styles['section-title']}>{ t('SETTINGS_NAV_STREAMING') }</div>
|
<div className={styles['section-title']}>{ t('SETTINGS_NAV_STREAMING') }</div>
|
||||||
|
|
|
||||||
|
|
@ -339,6 +339,21 @@ const useProfileSettingsInputs = (profile) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}), [profile.settings]);
|
}), [profile.settings]);
|
||||||
|
const pauseOnMinimizeToggle = React.useMemo(() => ({
|
||||||
|
checked: profile.settings.pauseOnMinimize,
|
||||||
|
onClick: () => {
|
||||||
|
core.transport.dispatch({
|
||||||
|
action: 'Ctx',
|
||||||
|
args: {
|
||||||
|
action: 'UpdateSettings',
|
||||||
|
args: {
|
||||||
|
...profile.settings,
|
||||||
|
pauseOnMinimize: !profile.settings.pauseOnMinimize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}), [profile.settings]);
|
||||||
return {
|
return {
|
||||||
interfaceLanguageSelect,
|
interfaceLanguageSelect,
|
||||||
hideSpoilersToggle,
|
hideSpoilersToggle,
|
||||||
|
|
@ -358,6 +373,7 @@ const useProfileSettingsInputs = (profile) => {
|
||||||
bingeWatchingToggle,
|
bingeWatchingToggle,
|
||||||
playInBackgroundToggle,
|
playInBackgroundToggle,
|
||||||
hardwareDecodingToggle,
|
hardwareDecodingToggle,
|
||||||
|
pauseOnMinimizeToggle,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue