mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-05-24 04:22:12 +00:00
Merge c136b5043d into 87ccc591df
This commit is contained in:
commit
4f896311be
5 changed files with 156 additions and 4 deletions
56
src/routes/Player/CastDevicesMenu/CastDevicesMenu.tsx
Normal file
56
src/routes/Player/CastDevicesMenu/CastDevicesMenu.tsx
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright (C) 2017-2026 Smart code 203358507
|
||||
|
||||
import React, { forwardRef, memo, MouseEvent } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import classNames from 'classnames';
|
||||
import Option from '../OptionsMenu/Option';
|
||||
import styles from './styles.less';
|
||||
|
||||
type CastDevice = {
|
||||
id: string,
|
||||
name: string,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
className: string,
|
||||
devices: CastDevice[],
|
||||
loading: boolean,
|
||||
onDeviceSelected: (deviceId: string) => void,
|
||||
};
|
||||
|
||||
const CastDevicesMenu = memo(forwardRef<HTMLDivElement, Props>(({ className, devices, loading, onDeviceSelected }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onMouseDown = (event: MouseEvent) => {
|
||||
// @ts-expect-error: Property 'castDevicesMenuClosePrevented' does not exist on type 'MouseEvent'.
|
||||
event.nativeEvent.castDevicesMenuClosePrevented = true;
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={ref} className={classNames(className, styles['cast-devices-menu-container'])} onMouseDown={onMouseDown}>
|
||||
{
|
||||
devices.length > 0 ?
|
||||
devices.map(({ id, name }) => (
|
||||
<Option
|
||||
key={id}
|
||||
icon={'cast'}
|
||||
label={t('PLAYER_PLAY_IN', { device: name })}
|
||||
deviceId={id}
|
||||
onClick={onDeviceSelected}
|
||||
/>
|
||||
))
|
||||
:
|
||||
<div className={styles['message']}>
|
||||
{
|
||||
loading ?
|
||||
t('STREAM_LOADING')
|
||||
:
|
||||
t('PLAYER_NO_CAST_DEVICES_FOUND', { defaultValue: 'No cast devices found' })
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}));
|
||||
|
||||
export default CastDevicesMenu;
|
||||
5
src/routes/Player/CastDevicesMenu/index.ts
Normal file
5
src/routes/Player/CastDevicesMenu/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (C) 2017-2026 Smart code 203358507
|
||||
|
||||
import CastDevicesMenu from './CastDevicesMenu';
|
||||
|
||||
export default CastDevicesMenu;
|
||||
13
src/routes/Player/CastDevicesMenu/styles.less
Normal file
13
src/routes/Player/CastDevicesMenu/styles.less
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (C) 2017-2026 Smart code 203358507
|
||||
|
||||
.cast-devices-menu-container {
|
||||
width: 16rem;
|
||||
padding: 1rem;
|
||||
|
||||
.message {
|
||||
padding: 1rem;
|
||||
color: var(--primary-foreground-color);
|
||||
opacity: 0.7;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
|
@ -39,6 +39,9 @@ const ControlBar = React.forwardRef(({
|
|||
onToggleSpeedMenu,
|
||||
onToggleSideDrawer,
|
||||
onToggleOptionsMenu,
|
||||
shellCastSupported,
|
||||
onRefreshCastDevices,
|
||||
onToggleCastDevicesMenu,
|
||||
videoScale,
|
||||
videoScaleLabel,
|
||||
onVideoScaleChanged,
|
||||
|
|
@ -68,6 +71,9 @@ const ControlBar = React.forwardRef(({
|
|||
const onStatisticsButtonMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.statisticsMenuClosePrevented = true;
|
||||
}, []);
|
||||
const onCastDevicesButtonMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.castDevicesMenuClosePrevented = true;
|
||||
}, []);
|
||||
const onPlayPauseButtonClick = React.useCallback(() => {
|
||||
if (paused) {
|
||||
if (typeof onPlayRequested === 'function') {
|
||||
|
|
@ -95,9 +101,22 @@ const ControlBar = React.forwardRef(({
|
|||
}
|
||||
}
|
||||
}, [muted, onMuteRequested, onUnmuteRequested]);
|
||||
const castButtonDisabled = platform.shell.active ? !shellCastSupported : !chromecastServiceActive;
|
||||
const onChromecastButtonClick = React.useCallback(() => {
|
||||
if (platform.shell.active) {
|
||||
if (typeof onRefreshCastDevices === 'function') {
|
||||
onRefreshCastDevices();
|
||||
}
|
||||
if (shellCastSupported && typeof onToggleCastDevicesMenu === 'function') {
|
||||
onToggleCastDevicesMenu();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (castButtonDisabled) {
|
||||
return;
|
||||
}
|
||||
chromecast.transport.requestSession();
|
||||
}, []);
|
||||
}, [castButtonDisabled, platform.shell.active, shellCastSupported, onRefreshCastDevices, onToggleCastDevicesMenu]);
|
||||
React.useEffect(() => {
|
||||
const onStateChanged = () => {
|
||||
setChromecastServiceActive(chromecast.active);
|
||||
|
|
@ -162,7 +181,7 @@ const ControlBar = React.forwardRef(({
|
|||
<Button className={classnames(styles['control-bar-button'], { 'disabled': playbackSpeed === null })} tabIndex={-1} onMouseDown={onSpeedButtonMouseDown} onClick={onToggleSpeedMenu}>
|
||||
<Icon className={styles['icon']} name={'speed'} />
|
||||
</Button>
|
||||
<Button className={classnames(styles['control-bar-button'], { 'disabled': !chromecastServiceActive })} tabIndex={-1} onClick={onChromecastButtonClick}>
|
||||
<Button className={classnames(styles['control-bar-button'], { 'disabled': castButtonDisabled })} tabIndex={-1} onMouseDown={onCastDevicesButtonMouseDown} onClick={onChromecastButtonClick}>
|
||||
<Icon className={styles['icon']} name={'cast'} />
|
||||
</Button>
|
||||
<Button className={classnames(styles['control-bar-button'], { 'disabled': !Array.isArray(subtitlesTracks) || subtitlesTracks.length === 0 })} tabIndex={-1} onMouseDown={onSubtitlesButtonMouseDown} onClick={onToggleSubtitlesMenu}>
|
||||
|
|
@ -221,6 +240,9 @@ ControlBar.propTypes = {
|
|||
onToggleSpeedMenu: PropTypes.func,
|
||||
onToggleSideDrawer: PropTypes.func,
|
||||
onToggleOptionsMenu: PropTypes.func,
|
||||
shellCastSupported: PropTypes.bool,
|
||||
onRefreshCastDevices: PropTypes.func,
|
||||
onToggleCastDevicesMenu: PropTypes.func,
|
||||
onToggleStatisticsMenu: PropTypes.func,
|
||||
onMouseOver: PropTypes.func,
|
||||
onMouseMove: PropTypes.func,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ const ControlBar = require('./ControlBar');
|
|||
const NextVideoPopup = require('./NextVideoPopup');
|
||||
const StatisticsMenu = require('./StatisticsMenu');
|
||||
const OptionsMenu = require('./OptionsMenu');
|
||||
const { default: CastDevicesMenu } = require('./CastDevicesMenu');
|
||||
const SubtitlesMenu = require('./SubtitlesMenu');
|
||||
const { default: AudioMenu } = require('./AudioMenu');
|
||||
const SpeedMenu = require('./SpeedMenu');
|
||||
|
|
@ -82,12 +83,13 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
const [audioMenuOpen, , closeAudioMenu, toggleAudioMenu] = useBinaryState(false);
|
||||
const [speedMenuOpen, , closeSpeedMenu, toggleSpeedMenu] = useBinaryState(false);
|
||||
const [statisticsMenuOpen, , closeStatisticsMenu, toggleStatisticsMenu] = useBinaryState(false);
|
||||
const [castDevicesMenuOpen, , closeCastDevicesMenu, toggleCastDevicesMenu] = useBinaryState(false);
|
||||
const [nextVideoPopupOpen, openNextVideoPopup, closeNextVideoPopup] = useBinaryState(false);
|
||||
const [sideDrawerOpen, , closeSideDrawer, toggleSideDrawer] = useBinaryState(false);
|
||||
|
||||
const menusOpen = React.useMemo(() => {
|
||||
return optionsMenuOpen || subtitlesMenuOpen || audioMenuOpen || speedMenuOpen || statisticsMenuOpen || sideDrawerOpen || nextVideoPopupOpen;
|
||||
}, [optionsMenuOpen, subtitlesMenuOpen, audioMenuOpen, speedMenuOpen, statisticsMenuOpen, sideDrawerOpen, nextVideoPopupOpen]);
|
||||
return optionsMenuOpen || subtitlesMenuOpen || audioMenuOpen || speedMenuOpen || statisticsMenuOpen || castDevicesMenuOpen || sideDrawerOpen || nextVideoPopupOpen;
|
||||
}, [optionsMenuOpen, subtitlesMenuOpen, audioMenuOpen, speedMenuOpen, statisticsMenuOpen, castDevicesMenuOpen, sideDrawerOpen, nextVideoPopupOpen]);
|
||||
|
||||
const closeMenus = React.useCallback(() => {
|
||||
closeOptionsMenu();
|
||||
|
|
@ -95,9 +97,49 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
closeAudioMenu();
|
||||
closeSpeedMenu();
|
||||
closeStatisticsMenu();
|
||||
closeCastDevicesMenu();
|
||||
closeSideDrawer();
|
||||
}, []);
|
||||
|
||||
const castDevices = React.useMemo(() => {
|
||||
return playbackDevices.filter(({ type }) => type === 'chromecast' || type === 'tv');
|
||||
}, [playbackDevices]);
|
||||
const castDevicesLoading = platform.shell.active && streamingServer.playbackDevices !== null && streamingServer.playbackDevices.type === 'Loading';
|
||||
const castStreamingUrl = React.useMemo(() => {
|
||||
return player.selected?.stream?.deepLinks?.externalPlayer?.streaming || null;
|
||||
}, [player.selected]);
|
||||
const shellCastSupported = platform.shell.active && castStreamingUrl !== null;
|
||||
const refreshCastDevices = React.useCallback(() => {
|
||||
if (platform.shell.active) {
|
||||
core.transport.dispatch({
|
||||
action: 'StreamingServer',
|
||||
args: {
|
||||
action: 'RefreshPlaybackDevices',
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [platform.shell.active]);
|
||||
const onCastDeviceSelected = React.useCallback((deviceId) => {
|
||||
if (castStreamingUrl) {
|
||||
core.transport.dispatch({
|
||||
action: 'StreamingServer',
|
||||
args: {
|
||||
action: 'PlayOnDevice',
|
||||
args: {
|
||||
device: deviceId,
|
||||
source: castStreamingUrl,
|
||||
}
|
||||
}
|
||||
});
|
||||
closeCastDevicesMenu();
|
||||
}
|
||||
}, [castStreamingUrl]);
|
||||
React.useEffect(() => {
|
||||
if (castDevicesMenuOpen) {
|
||||
refreshCastDevices();
|
||||
}
|
||||
}, [castDevicesMenuOpen, refreshCastDevices]);
|
||||
|
||||
const {
|
||||
streamSubtitles,
|
||||
allSubtitleTracks,
|
||||
|
|
@ -294,6 +336,9 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
if (!event.nativeEvent.statisticsMenuClosePrevented) {
|
||||
closeStatisticsMenu();
|
||||
}
|
||||
if (!event.nativeEvent.castDevicesMenuClosePrevented) {
|
||||
closeCastDevicesMenu();
|
||||
}
|
||||
|
||||
closeSideDrawer();
|
||||
}, []);
|
||||
|
|
@ -878,6 +923,9 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
onVolumeChangeRequested={onVolumeChangeRequested}
|
||||
onSeekRequested={onSeekRequested}
|
||||
onToggleOptionsMenu={toggleOptionsMenu}
|
||||
shellCastSupported={shellCastSupported}
|
||||
onRefreshCastDevices={refreshCastDevices}
|
||||
onToggleCastDevicesMenu={toggleCastDevicesMenu}
|
||||
onToggleSubtitlesMenu={toggleSubtitlesMenu}
|
||||
onToggleAudioMenu={toggleAudioMenu}
|
||||
onToggleSpeedMenu={toggleSpeedMenu}
|
||||
|
|
@ -913,6 +961,14 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
{...statistics}
|
||||
/>
|
||||
</Transition>
|
||||
<Transition when={castDevicesMenuOpen} name={'fade'}>
|
||||
<CastDevicesMenu
|
||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||
devices={castDevices}
|
||||
loading={castDevicesLoading}
|
||||
onDeviceSelected={onCastDeviceSelected}
|
||||
/>
|
||||
</Transition>
|
||||
<Transition when={sideDrawerOpen} name={'slide-left'}>
|
||||
<SideDrawer
|
||||
className={classnames(styles['layer'], styles['side-drawer-layer'])}
|
||||
|
|
|
|||
Loading…
Reference in a new issue