mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-05-24 08:32:10 +00:00
refactor: move shell logic to provider
This commit is contained in:
parent
5727fc0322
commit
4cc9761307
23 changed files with 226 additions and 391 deletions
|
|
@ -5,9 +5,9 @@ const React = require('react');
|
|||
const { useTranslation } = require('react-i18next');
|
||||
const { useCore } = require('stremio/core');
|
||||
const { Router } = require('stremio-router');
|
||||
const { Shell, Chromecast, ServicesProvider, GamepadProvider } = require('stremio/services');
|
||||
const { Chromecast, ServicesProvider, GamepadProvider } = require('stremio/services');
|
||||
const { NotFound } = require('stremio/routes');
|
||||
const { FullscreenProvider, PlatformProvider, ToastProvider, TooltipProvider, ShortcutsProvider, CONSTANTS, useShell, useBinaryState, useProfile, withCoreSuspender, onFileDrop } = require('stremio/common');
|
||||
const { FullscreenProvider, ToastProvider, TooltipProvider, ShortcutsProvider, CONSTANTS, useBinaryState, useProfile, withCoreSuspender, onFileDrop, usePlatform } = require('stremio/common');
|
||||
const ServicesToaster = require('./ServicesToaster');
|
||||
const DeepLinkHandler = require('./DeepLinkHandler');
|
||||
const SearchParamsHandler = require('./SearchParamsHandler');
|
||||
|
|
@ -24,14 +24,13 @@ const App = () => {
|
|||
const core = useCore();
|
||||
const profile = useProfile();
|
||||
const { i18n } = useTranslation();
|
||||
const shell = useShell();
|
||||
const { shell } = usePlatform();
|
||||
const [gamepadSupportEnabled, setGamepadSupportEnabled] = React.useState(false);
|
||||
const onPathNotMatch = React.useCallback(() => {
|
||||
return NotFound;
|
||||
}, []);
|
||||
const services = React.useMemo(() => {
|
||||
return {
|
||||
shell: new Shell(),
|
||||
chromecast: new Chromecast(),
|
||||
};
|
||||
}, []);
|
||||
|
|
@ -99,11 +98,9 @@ const App = () => {
|
|||
}
|
||||
};
|
||||
services.chromecast.on('stateChanged', onChromecastStateChange);
|
||||
services.shell.start();
|
||||
services.chromecast.start();
|
||||
window.services = services;
|
||||
return () => {
|
||||
services.shell.stop();
|
||||
services.chromecast.stop();
|
||||
services.chromecast.off('stateChanged', onChromecastStateChange);
|
||||
};
|
||||
|
|
@ -143,10 +140,10 @@ const App = () => {
|
|||
setGamepadSupportEnabled(profile.settings.gamepadSupport);
|
||||
}
|
||||
|
||||
if (profile.settings?.quitOnClose && shell.windowClosed) {
|
||||
if (profile.settings?.quitOnClose && shell.state.windowClosed) {
|
||||
shell.send('quit');
|
||||
}
|
||||
}, [profile.settings, shell.windowClosed]);
|
||||
}, [profile.settings, shell.state.windowClosed]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const onWindowFocus = () => {
|
||||
|
|
@ -187,33 +184,31 @@ const App = () => {
|
|||
|
||||
return (
|
||||
<ServicesProvider services={services}>
|
||||
<PlatformProvider>
|
||||
<ToastProvider className={styles['toasts-container']}>
|
||||
<TooltipProvider className={styles['tooltip-container']}>
|
||||
<GamepadProvider enabled={gamepadSupportEnabled} onGuide={toggleGamepadModal}>
|
||||
<ShortcutsProvider onShortcut={onShortcut}>
|
||||
<FullscreenProvider>
|
||||
{
|
||||
shortcutModalOpen && <ShortcutsModal onClose={closeShortcutsModal}/>
|
||||
}
|
||||
{
|
||||
gamepadModalOpen && <GamepadModal onClose={closeGamepadModal}/>
|
||||
}
|
||||
<ServicesToaster />
|
||||
<DeepLinkHandler />
|
||||
<SearchParamsHandler />
|
||||
<UpdaterBanner className={styles['updater-banner-container']} />
|
||||
<RouterWithProtectedRoutes
|
||||
className={styles['router']}
|
||||
viewsConfig={routerViewsConfig}
|
||||
onPathNotMatch={onPathNotMatch}
|
||||
/>
|
||||
</FullscreenProvider>
|
||||
</ShortcutsProvider>
|
||||
</GamepadProvider>
|
||||
</TooltipProvider>
|
||||
</ToastProvider>
|
||||
</PlatformProvider>
|
||||
<ToastProvider className={styles['toasts-container']}>
|
||||
<TooltipProvider className={styles['tooltip-container']}>
|
||||
<GamepadProvider enabled={gamepadSupportEnabled} onGuide={toggleGamepadModal}>
|
||||
<ShortcutsProvider onShortcut={onShortcut}>
|
||||
<FullscreenProvider>
|
||||
{
|
||||
shortcutModalOpen && <ShortcutsModal onClose={closeShortcutsModal}/>
|
||||
}
|
||||
{
|
||||
gamepadModalOpen && <GamepadModal onClose={closeGamepadModal}/>
|
||||
}
|
||||
<ServicesToaster />
|
||||
<DeepLinkHandler />
|
||||
<SearchParamsHandler />
|
||||
<UpdaterBanner className={styles['updater-banner-container']} />
|
||||
<RouterWithProtectedRoutes
|
||||
className={styles['router']}
|
||||
viewsConfig={routerViewsConfig}
|
||||
onPathNotMatch={onPathNotMatch}
|
||||
/>
|
||||
</FullscreenProvider>
|
||||
</ShortcutsProvider>
|
||||
</GamepadProvider>
|
||||
</TooltipProvider>
|
||||
</ToastProvider>
|
||||
</ServicesProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import Icon from '@stremio/stremio-icons/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useServices } from 'stremio/services';
|
||||
import { useBinaryState, useShell } from 'stremio/common';
|
||||
import { useBinaryState, usePlatform } from 'stremio/common';
|
||||
import { Button, Transition } from 'stremio/components';
|
||||
import styles from './UpdaterBanner.less';
|
||||
|
||||
|
|
@ -12,19 +11,18 @@ type Props = {
|
|||
|
||||
const UpdaterBanner = ({ className }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { shell } = useServices();
|
||||
const shellTransport = useShell();
|
||||
const { shell } = usePlatform();
|
||||
const [visible, show, hide] = useBinaryState(false);
|
||||
|
||||
const onInstallClick = () => {
|
||||
shellTransport.send('autoupdater-notif-clicked');
|
||||
shell.send('autoupdater-notif-clicked');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
shell.transport && shell.transport.on('autoupdater-show-notif', show);
|
||||
shell.on('autoupdater-show-notif', show);
|
||||
|
||||
return () => {
|
||||
shell.transport && shell.transport.off('autoupdater-show-notif', show);
|
||||
shell.off('autoupdater-show-notif', show);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,15 +4,15 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|||
import { withCoreSuspender } from '../CoreSuspender';
|
||||
import onShortcut from '../Shortcuts/onShortcut';
|
||||
import useSettings from '../useSettings';
|
||||
import useShell, { type WindowVisibility } from '../useShell';
|
||||
import FullscreenContext, { type FullscreenContextValue } from './FullscreenContext';
|
||||
import { usePlatform } from '../Platform';
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode,
|
||||
};
|
||||
|
||||
const FullscreenProvider = ({ children }: Props) => {
|
||||
const shell = useShell();
|
||||
const { shell } = usePlatform();
|
||||
const [settings] = useSettings();
|
||||
const escExitFullscreen = settings.escExitFullscreen;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import React, { createContext, useContext } from 'react';
|
||||
import { WHITELISTED_HOSTS } from 'stremio/common/CONSTANTS';
|
||||
import { name, isMobile } from './device';
|
||||
import useShell from './shell/useShell';
|
||||
|
||||
interface PlatformContext {
|
||||
name: string;
|
||||
isMobile: boolean;
|
||||
shell: Shell;
|
||||
openExternal: (url: string) => void;
|
||||
}
|
||||
|
||||
|
|
@ -15,6 +17,8 @@ type Props = {
|
|||
};
|
||||
|
||||
const PlatformProvider = ({ children }: Props) => {
|
||||
const shell = useShell();
|
||||
|
||||
const openExternal = (url: string) => {
|
||||
try {
|
||||
const { hostname } = new URL(url);
|
||||
|
|
@ -30,7 +34,7 @@ const PlatformProvider = ({ children }: Props) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<PlatformContext.Provider value={{ openExternal, name, isMobile }}>
|
||||
<PlatformContext.Provider value={{ openExternal, shell, name, isMobile }}>
|
||||
{children}
|
||||
</PlatformContext.Provider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ type QtTransportMessage = {
|
|||
};
|
||||
|
||||
interface QtTransport {
|
||||
send: (message: string) => void,
|
||||
onmessage: (message: QtTransportMessage) => void,
|
||||
}
|
||||
|
||||
|
|
@ -22,8 +21,8 @@ interface Chrome {
|
|||
}
|
||||
|
||||
declare global {
|
||||
var qt: Qt | undefined;
|
||||
var qt: Qt;
|
||||
var chrome: Chrome | undefined;
|
||||
}
|
||||
|
||||
export { };
|
||||
export {};
|
||||
27
src/common/Platform/shell/shell.d.ts
vendored
Normal file
27
src/common/Platform/shell/shell.d.ts
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
type WindowVisibility = {
|
||||
visible: boolean;
|
||||
visibility: number;
|
||||
isFullscreen: boolean;
|
||||
};
|
||||
|
||||
type WindowState = {
|
||||
state: number;
|
||||
};
|
||||
|
||||
type MediaStatus = {
|
||||
paused: boolean;
|
||||
};
|
||||
|
||||
interface Shell {
|
||||
active: boolean,
|
||||
state: ShellState,
|
||||
on: (name: string, listener: (arg: any) => void) => void;
|
||||
off: (name: string, listener: (arg: any) => void) => void;
|
||||
send: (method: string, ...args: (string | number | object)[]) => void;
|
||||
}
|
||||
|
||||
type ShellState = {
|
||||
version: string | null;
|
||||
windowClosed: boolean;
|
||||
windowHidden: boolean;
|
||||
};
|
||||
124
src/common/Platform/shell/useShell.ts
Normal file
124
src/common/Platform/shell/useShell.ts
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
||||
const IPC = globalThis?.chrome?.webview;
|
||||
const LEGACY_IPC = globalThis?.qt?.webChannelTransport;
|
||||
if (LEGACY_IPC) LEGACY_IPC.onmessage = () => { /* empty */ };
|
||||
|
||||
const events = new EventEmitter();
|
||||
|
||||
enum ShellEventType {
|
||||
SIGNAL = 1,
|
||||
INIT = 3,
|
||||
INVOKE_METHOD = 6,
|
||||
}
|
||||
|
||||
type ShellEvent = {
|
||||
id: number;
|
||||
type: ShellEventType;
|
||||
};
|
||||
|
||||
type ShellEventInit = ShellEvent & {
|
||||
data: {
|
||||
transport: {
|
||||
properties: string[][],
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
type ShellEventSignal = ShellEvent & {
|
||||
args: string[];
|
||||
};
|
||||
|
||||
type ShellMessage = {
|
||||
data: string;
|
||||
};
|
||||
|
||||
const useShell = (): Shell => {
|
||||
const [state, setState] = useState<ShellState>({
|
||||
version: null,
|
||||
windowClosed: false,
|
||||
windowHidden: false,
|
||||
});
|
||||
|
||||
const on = (name: string, listener: (arg: any) => void) => events.on(name, listener);
|
||||
const off = (name: string, listener: (arg: any) => void) => events.off(name, listener);
|
||||
|
||||
const send = (method: string, ...args: (string | number | object)[]) => {
|
||||
try {
|
||||
IPC?.postMessage(JSON.stringify({
|
||||
id: 0,
|
||||
type: ShellEventType.INVOKE_METHOD,
|
||||
args: [method, ...args],
|
||||
}));
|
||||
} catch (e) {
|
||||
console.error('Shell', 'Failed to send event', e);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const onWindowVisibilityChanged = (data: WindowVisibility) => {
|
||||
setState((state) => ({
|
||||
...state,
|
||||
windowClosed: data.visible === false && data.visibility === 0,
|
||||
}));
|
||||
};
|
||||
|
||||
const onWindowStateChanged = (data: WindowState) => {
|
||||
setState((state) => ({
|
||||
...state,
|
||||
windowHidden: 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(() => {
|
||||
IPC?.postMessage(JSON.stringify({
|
||||
id: 0,
|
||||
type: ShellEventType.INIT,
|
||||
}));
|
||||
|
||||
const onMessage = (message: ShellMessage) => {
|
||||
try {
|
||||
const event = JSON.parse(message.data) as ShellEvent;
|
||||
|
||||
if (event.type === ShellEventType.INIT) {
|
||||
const { data } = event as ShellEventInit;
|
||||
const [, [,,, version]] = data.transport.properties;
|
||||
|
||||
setState((state) => ({ ...state, version }));
|
||||
send('app-ready');
|
||||
}
|
||||
|
||||
if (event.type === ShellEventType.SIGNAL) {
|
||||
const { args } = event as ShellEventSignal;
|
||||
const [methodName, methodArg] = args;
|
||||
events.emit(methodName, methodArg);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Shell', 'Failed to handle event', e);
|
||||
}
|
||||
};
|
||||
|
||||
IPC?.addEventListener('message', onMessage);
|
||||
return () => IPC?.removeEventListener('message', onMessage);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
active: !!IPC,
|
||||
send,
|
||||
on,
|
||||
off,
|
||||
state,
|
||||
};
|
||||
};
|
||||
|
||||
export default useShell;
|
||||
|
|
@ -22,7 +22,6 @@ 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 { default: useTimeout } = require('./useTimeout');
|
||||
const { default: usePlayUrl } = require('./usePlayUrl');
|
||||
|
|
@ -63,7 +62,6 @@ module.exports = {
|
|||
useOnScrollToBottom,
|
||||
useProfile,
|
||||
useSettings,
|
||||
useShell,
|
||||
useStreamingServer,
|
||||
useTimeout,
|
||||
usePlayUrl,
|
||||
|
|
|
|||
|
|
@ -1,109 +0,0 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
||||
const SHELL_EVENT_OBJECT = 'transport';
|
||||
const transport = globalThis?.chrome?.webview;
|
||||
const events = new EventEmitter();
|
||||
|
||||
enum ShellEventType {
|
||||
SIGNAL = 1,
|
||||
INVOKE_METHOD = 6,
|
||||
}
|
||||
|
||||
type ShellEvent = {
|
||||
id: number;
|
||||
type: ShellEventType;
|
||||
object: string;
|
||||
args: string[];
|
||||
};
|
||||
|
||||
export type WindowVisibility = {
|
||||
visible: boolean;
|
||||
visibility: number;
|
||||
isFullscreen: boolean;
|
||||
};
|
||||
|
||||
export type WindowState = {
|
||||
state: number;
|
||||
};
|
||||
|
||||
export type MediaStatus = {
|
||||
paused: boolean;
|
||||
};
|
||||
|
||||
const createId = () => Math.floor(Math.random() * 9999) + 1;
|
||||
|
||||
const useShell = () => {
|
||||
const [windowClosed, setWindowClosed] = useState(false);
|
||||
const [windowHidden, setWindowHidden] = useState(false);
|
||||
|
||||
const on = (name: string, listener: (arg: any) => void) => {
|
||||
events.on(name, listener);
|
||||
};
|
||||
|
||||
const off = (name: string, listener: (arg: any) => void) => {
|
||||
events.off(name, listener);
|
||||
};
|
||||
|
||||
const send = (method: string, ...args: (string | number | object)[]) => {
|
||||
try {
|
||||
transport?.postMessage(JSON.stringify({
|
||||
id: createId(),
|
||||
type: ShellEventType.INVOKE_METHOD,
|
||||
object: SHELL_EVENT_OBJECT,
|
||||
method: 'onEvent',
|
||||
args: [method, ...args],
|
||||
}));
|
||||
} catch (e) {
|
||||
console.error('Shell', 'Failed to send event', e);
|
||||
}
|
||||
};
|
||||
|
||||
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(() => {
|
||||
if (!transport) return;
|
||||
|
||||
const onMessage = ({ data }: { data: string }) => {
|
||||
try {
|
||||
const { type, args } = JSON.parse(data) as ShellEvent;
|
||||
if (type === ShellEventType.SIGNAL) {
|
||||
const [methodName, methodArg] = args;
|
||||
events.emit(methodName, methodArg);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Shell', 'Failed to handle event', e);
|
||||
}
|
||||
};
|
||||
|
||||
transport.addEventListener('message', onMessage);
|
||||
return () => transport.removeEventListener('message', onMessage);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
active: !!transport,
|
||||
send,
|
||||
on,
|
||||
off,
|
||||
windowClosed,
|
||||
windowHidden,
|
||||
};
|
||||
};
|
||||
|
||||
export default useShell;
|
||||
14
src/index.js
14
src/index.js
|
|
@ -18,7 +18,7 @@ const { initReactI18next } = require('react-i18next');
|
|||
const stremioTranslations = require('stremio-translations');
|
||||
const App = require('./App');
|
||||
const { CoreProvider } = require('./core');
|
||||
const { FileDropProvider } = require('./common');
|
||||
const { FileDropProvider, PlatformProvider } = require('./common');
|
||||
|
||||
const translations = Object.fromEntries(Object.entries(stremioTranslations()).map(([key, value]) => [key, {
|
||||
translation: value
|
||||
|
|
@ -42,11 +42,13 @@ const appInfo = {
|
|||
|
||||
const root = ReactDOM.createRoot(document.getElementById('app'));
|
||||
root.render(
|
||||
<CoreProvider appInfo={appInfo}>
|
||||
<FileDropProvider>
|
||||
<App />
|
||||
</FileDropProvider>
|
||||
</CoreProvider>
|
||||
<PlatformProvider>
|
||||
<CoreProvider appInfo={appInfo}>
|
||||
<FileDropProvider>
|
||||
<App />
|
||||
</FileDropProvider>
|
||||
</CoreProvider>
|
||||
</PlatformProvider>
|
||||
);
|
||||
|
||||
if (process.env.NODE_ENV === 'production' && process.env.SERVICE_WORKER_DISABLED !== 'true' && process.env.SERVICE_WORKER_DISABLED !== true && 'serviceWorker' in navigator) {
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ const PropTypes = require('prop-types');
|
|||
const classnames = require('classnames');
|
||||
const debounce = require('lodash.debounce');
|
||||
const { useRouteFocused } = require('stremio-router');
|
||||
const { useServices } = require('stremio/services');
|
||||
const { usePlatform } = require('stremio/common');
|
||||
const { Slider } = require('stremio/components');
|
||||
const styles = require('./styles');
|
||||
|
||||
const VolumeSlider = ({ className, volume, onVolumeChangeRequested, muted }) => {
|
||||
const { shell } = useServices();
|
||||
const { shell } = usePlatform();
|
||||
const disabled = volume === null || isNaN(volume);
|
||||
const routeFocused = useRouteFocused();
|
||||
const [slidingVolume, setSlidingVolume] = React.useState(null);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const { useRouteFocused } = require('stremio-router');
|
|||
const { useCore } = require('stremio/core');
|
||||
const { useServices, useGamepad } = require('stremio/services');
|
||||
const { useContentGamepadNavigation } = require('stremio/services/GamepadNavigation');
|
||||
const { useSettings, useProfile, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, useShell, usePlatform, onShortcut } = require('stremio/common');
|
||||
const { useSettings, useProfile, useFullscreen, useBinaryState, useToast, useStreamingServer, withCoreSuspender, usePlatform, onShortcut } = require('stremio/common');
|
||||
const { HorizontalNavBar, Transition, ContextMenu } = require('stremio/components');
|
||||
const BufferingLoader = require('./BufferingLoader');
|
||||
const VolumeChangeIndicator = require('./VolumeChangeIndicator');
|
||||
|
|
@ -42,7 +42,6 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
const { t } = useTranslation();
|
||||
const services = useServices();
|
||||
const core = useCore();
|
||||
const shell = useShell();
|
||||
const gamepad = useGamepad();
|
||||
const forceTranscoding = React.useMemo(() => {
|
||||
return queryParams.has('forceTranscoding');
|
||||
|
|
@ -418,7 +417,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
seriesInfo: player.seriesInfo,
|
||||
}, {
|
||||
chromecastTransport: services.chromecast.active ? services.chromecast.transport : null,
|
||||
shellTransport: services.shell.active ? services.shell.transport : null,
|
||||
shellTransport: platform.shell.active ? platform.shell : null,
|
||||
});
|
||||
}
|
||||
}, [streamingServer.baseUrl, player.selected, player.stream, streamSubtitles, forceTranscoding, casting]);
|
||||
|
|
@ -531,10 +530,10 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (settings.pauseOnMinimize && (shell.windowClosed || shell.windowHidden)) {
|
||||
if (settings.pauseOnMinimize && (platform.shell.state.windowClosed || platform.shell.state.windowHidden)) {
|
||||
onPauseRequested();
|
||||
}
|
||||
}, [settings.pauseOnMinimize, shell.windowClosed, shell.windowHidden]);
|
||||
}, [settings.pauseOnMinimize, platform.shell.state.windowClosed, platform.shell.state.windowHidden]);
|
||||
|
||||
useMediaSession(video.state, player, onPlayRequested, onPauseRequested, onNextVideoRequested);
|
||||
|
||||
|
|
@ -560,8 +559,8 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
break;
|
||||
}
|
||||
};
|
||||
shell.on('media-key', onMediaKey);
|
||||
return () => shell.off('media-key', onMediaKey);
|
||||
platform.shell.on('media-key', onMediaKey);
|
||||
return () => platform.shell.off('media-key', onMediaKey);
|
||||
}, [video.state.paused, player.nextVideo, onPlayRequested, onPauseRequested, onNextVideoRequested]);
|
||||
|
||||
onShortcut('seekForward', (combo) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useShell } from 'stremio/common';
|
||||
import { MediaStatus } from 'stremio/common/useShell';
|
||||
import { usePlatform } from 'stremio/common';
|
||||
|
||||
const useMediaSession = (
|
||||
videoState: VideoState,
|
||||
|
|
@ -9,7 +8,7 @@ const useMediaSession = (
|
|||
onPauseRequested: () => void,
|
||||
onNextVideoRequested: () => void,
|
||||
) => {
|
||||
const shell = useShell();
|
||||
const { shell } = usePlatform();
|
||||
|
||||
// Playback state
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useServices } from 'stremio/services';
|
||||
import { usePlatform } from 'stremio/common';
|
||||
import { Option, Section } from '../components';
|
||||
import styles from './Info.less';
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ type Props = {
|
|||
};
|
||||
|
||||
const Info = ({ streamingServer }: Props) => {
|
||||
const { shell } = useServices();
|
||||
const { shell } = usePlatform();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const settings = useMemo(() => (
|
||||
|
|
@ -38,10 +38,10 @@ const Info = ({ streamingServer }: Props) => {
|
|||
</Option>
|
||||
}
|
||||
{
|
||||
typeof shell?.transport?.props?.shellVersion === 'string' &&
|
||||
typeof shell.state.version === 'string' &&
|
||||
<Option label={t('SETTINGS_SHELL_VERSION')}>
|
||||
<div className={styles['label']}>
|
||||
{shell.transport.props.shellVersion}
|
||||
{shell.state.version}
|
||||
</div>
|
||||
</Option>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { forwardRef } from 'react';
|
||||
import { useServices } from 'stremio/services';
|
||||
import { usePlatform } from 'stremio/common';
|
||||
import { MultiselectMenu, Toggle } from 'stremio/components';
|
||||
import { Section, Option } from '../components';
|
||||
import useInterfaceOptions from './useInterfaceOptions';
|
||||
|
|
@ -9,7 +9,7 @@ type Props = {
|
|||
};
|
||||
|
||||
const Interface = forwardRef<HTMLDivElement, Props>(({ profile }: Props, ref) => {
|
||||
const { shell } = useServices();
|
||||
const { shell } = usePlatform();
|
||||
|
||||
const {
|
||||
interfaceLanguageSelect,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useServices } from 'stremio/services';
|
||||
import { usePlatform } from 'stremio/common';
|
||||
import { Button } from 'stremio/components';
|
||||
import { SECTIONS } from '../constants';
|
||||
import { usePlatform } from 'stremio/common';
|
||||
import styles from './Menu.less';
|
||||
|
||||
type Props = {
|
||||
|
|
@ -15,7 +14,7 @@ type Props = {
|
|||
|
||||
const Menu = ({ selected, streamingServer, onSelect }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { shell } = useServices();
|
||||
const { shell } = usePlatform();
|
||||
const platform = usePlatform();
|
||||
|
||||
const settings = useMemo(() => (
|
||||
|
|
@ -55,9 +54,9 @@ const Menu = ({ selected, streamingServer, onSelect }: Props) => {
|
|||
</div>
|
||||
}
|
||||
{
|
||||
typeof shell?.transport?.props?.shellVersion === 'string' &&
|
||||
<div className={styles['version-info-label']} title={shell.transport.props.shellVersion}>
|
||||
{t('SETTINGS_SHELL_VERSION')}: {shell.transport.props.shellVersion}
|
||||
typeof shell.state.version === 'string' &&
|
||||
<div className={styles['version-info-label']} title={shell.state.version}>
|
||||
{t('SETTINGS_SHELL_VERSION')}: {shell.state.version}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import React, { forwardRef } from 'react';
|
||||
import { ColorInput, MultiselectMenu, Toggle } from 'stremio/components';
|
||||
import { useServices } from 'stremio/services';
|
||||
import { usePlatform } from 'stremio/common';
|
||||
import { Category, Option, Section } from '../components';
|
||||
import usePlayerOptions from './usePlayerOptions';
|
||||
import { usePlatform } from 'stremio/common';
|
||||
|
||||
type Props = {
|
||||
profile: Profile,
|
||||
};
|
||||
|
||||
const Player = forwardRef<HTMLDivElement, Props>(({ profile }: Props, ref) => {
|
||||
const { shell } = useServices();
|
||||
const { shell } = usePlatform();
|
||||
const platform = usePlatform();
|
||||
|
||||
const {
|
||||
|
|
|
|||
1
src/services/ServicesContext/types.d.ts
vendored
1
src/services/ServicesContext/types.d.ts
vendored
|
|
@ -1,4 +1,3 @@
|
|||
type ServicesContext = {
|
||||
shell: any,
|
||||
chromecast: any,
|
||||
};
|
||||
|
|
|
|||
11
src/services/Shell/Shell.d.ts
vendored
11
src/services/Shell/Shell.d.ts
vendored
|
|
@ -1,11 +0,0 @@
|
|||
type ShellTransportProps = {
|
||||
shellVersion: string,
|
||||
};
|
||||
|
||||
type ShellTransport = {
|
||||
props: ShellTransportProps,
|
||||
};
|
||||
|
||||
interface ShellService {
|
||||
transport: ShellTransport,
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const EventEmitter = require('eventemitter3');
|
||||
const ShellTransport = require('./ShellTransport');
|
||||
|
||||
function Shell() {
|
||||
let active = false;
|
||||
let error = null;
|
||||
let starting = false;
|
||||
let transport = null;
|
||||
|
||||
const events = new EventEmitter();
|
||||
|
||||
function onStateChanged() {
|
||||
events.emit('stateChanged');
|
||||
}
|
||||
|
||||
Object.defineProperties(this, {
|
||||
active: {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return active;
|
||||
}
|
||||
},
|
||||
error: {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return error;
|
||||
}
|
||||
},
|
||||
starting: {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return starting;
|
||||
}
|
||||
},
|
||||
transport: {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return transport;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.start = function() {
|
||||
if (active || error instanceof Error || starting) {
|
||||
return;
|
||||
}
|
||||
|
||||
active = false;
|
||||
starting = true;
|
||||
|
||||
try {
|
||||
transport = new ShellTransport();
|
||||
active = true;
|
||||
error = null;
|
||||
starting = false;
|
||||
onStateChanged();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
active = false;
|
||||
error = new Error('Failed to initialize shell transport', { cause: e });
|
||||
starting = false;
|
||||
onStateChanged();
|
||||
transport = null;
|
||||
}
|
||||
|
||||
onStateChanged();
|
||||
};
|
||||
this.stop = function() {
|
||||
active = false;
|
||||
error = null;
|
||||
starting = false;
|
||||
onStateChanged();
|
||||
};
|
||||
this.on = function(name, listener) {
|
||||
events.on(name, listener);
|
||||
};
|
||||
this.off = function(name, listener) {
|
||||
events.off(name, listener);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = Shell;
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const EventEmitter = require('eventemitter3');
|
||||
|
||||
const QtMsgTypes = {
|
||||
signal: 1,
|
||||
propertyUpdate: 2,
|
||||
init: 3,
|
||||
idle: 4,
|
||||
debug: 5,
|
||||
invokeMethod: 6,
|
||||
connectToSignal: 7,
|
||||
disconnectFromSignal: 8,
|
||||
setProperty: 9,
|
||||
response: 10,
|
||||
};
|
||||
const QtObjId = 'transport'; // the ID of our transport object
|
||||
|
||||
function ShellTransport() {
|
||||
const events = new EventEmitter();
|
||||
|
||||
this.props = {};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const shell = this;
|
||||
const transport = window.qt && window.qt.webChannelTransport;
|
||||
if (!transport) throw 'no viable transport found (qt.webChannelTransport)';
|
||||
|
||||
let id = 0;
|
||||
function send(msg) {
|
||||
msg.id = id++;
|
||||
transport.send(JSON.stringify(msg));
|
||||
}
|
||||
|
||||
transport.onmessage = function (message) {
|
||||
const msg = JSON.parse(message.data);
|
||||
if (msg.id === 0) {
|
||||
const obj = msg.data[QtObjId];
|
||||
|
||||
obj.properties.slice(1).forEach(function (prop) {
|
||||
shell.props[prop[1]] = prop[3];
|
||||
});
|
||||
if (typeof shell.props.shellVersion === 'string') {
|
||||
shell.shellVersionArr = (
|
||||
shell.props.shellVersion.match(/(\d+)\.(\d+)\.(\d+)/) || []
|
||||
)
|
||||
.slice(1, 4)
|
||||
.map(Number);
|
||||
}
|
||||
events.emit('received-props', shell.props);
|
||||
|
||||
obj.signals.forEach(function (sig) {
|
||||
send({
|
||||
type: QtMsgTypes.connectToSignal,
|
||||
object: QtObjId,
|
||||
signal: sig[1],
|
||||
});
|
||||
});
|
||||
|
||||
const onEvent = obj.methods.filter(function (x) {
|
||||
return x[0] === 'onEvent';
|
||||
})[0];
|
||||
|
||||
shell.send = function (ev, args) {
|
||||
send({
|
||||
type: QtMsgTypes.invokeMethod,
|
||||
object: QtObjId,
|
||||
method: onEvent[1],
|
||||
args: [ev, args || {}],
|
||||
});
|
||||
};
|
||||
|
||||
shell.send('app-ready', {}); // signal that we're ready to take events
|
||||
}
|
||||
|
||||
if (msg.object === QtObjId && msg.type === QtMsgTypes.signal)
|
||||
events.emit(msg.args[0], msg.args[1]);
|
||||
};
|
||||
send({ type: QtMsgTypes.init });
|
||||
|
||||
this.on = function(name, listener) {
|
||||
events.on(name, listener);
|
||||
};
|
||||
this.off = function(name, listener) {
|
||||
events.off(name, listener);
|
||||
};
|
||||
this.removeAllListeners = function() {
|
||||
events.removeAllListeners();
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = ShellTransport;
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const Shell = require('./Shell');
|
||||
|
||||
module.exports = Shell;
|
||||
|
|
@ -3,13 +3,11 @@
|
|||
const Chromecast = require('./Chromecast');
|
||||
const { ServicesProvider, useServices } = require('./ServicesContext');
|
||||
const { GamepadProvider, useGamepad } = require('./GamepadContext');
|
||||
const Shell = require('./Shell');
|
||||
|
||||
module.exports = {
|
||||
Chromecast,
|
||||
ServicesProvider,
|
||||
useServices,
|
||||
Shell,
|
||||
GamepadProvider,
|
||||
useGamepad,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue