mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-05-18 19:21:48 +00:00
feat: fullscreen support on safari
This commit is contained in:
parent
6065d8bf15
commit
671170a9da
7 changed files with 223 additions and 190 deletions
|
|
@ -7,6 +7,8 @@ export type FullscreenContextValue = readonly [
|
||||||
requestFullscreen: () => Promise<void> | void,
|
requestFullscreen: () => Promise<void> | void,
|
||||||
exitFullscreen: () => void,
|
exitFullscreen: () => void,
|
||||||
toggleFullscreen: () => void,
|
toggleFullscreen: () => void,
|
||||||
|
supported: boolean,
|
||||||
|
setVideoElement: (el: HTMLVideoElement | null) => void,
|
||||||
];
|
];
|
||||||
|
|
||||||
const FullscreenContext = createContext<FullscreenContextValue | null>(null);
|
const FullscreenContext = createContext<FullscreenContextValue | null>(null);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Copyright (C) 2017-2026 Smart code 203358507
|
// Copyright (C) 2017-2026 Smart code 203358507
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { withCoreSuspender } from '../CoreSuspender';
|
import { withCoreSuspender } from '../CoreSuspender';
|
||||||
import onShortcut from '../Shortcuts/onShortcut';
|
import onShortcut from '../Shortcuts/onShortcut';
|
||||||
import useSettings from '../useSettings';
|
import useSettings from '../useSettings';
|
||||||
|
|
@ -21,35 +21,46 @@ const isTextInputFocused = () => {
|
||||||
activeElement.isContentEditable);
|
activeElement.isContentEditable);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasWebkitFullscreen = typeof HTMLVideoElement !== 'undefined' &&
|
||||||
|
typeof HTMLVideoElement.prototype.webkitEnterFullscreen === 'function';
|
||||||
|
|
||||||
const FullscreenProvider = ({ children }: Props) => {
|
const FullscreenProvider = ({ children }: Props) => {
|
||||||
const shell = useShell();
|
const shell = useShell();
|
||||||
const [settings] = useSettings();
|
const [settings] = useSettings();
|
||||||
const escExitFullscreen = settings.escExitFullscreen;
|
const escExitFullscreen = settings.escExitFullscreen;
|
||||||
|
|
||||||
|
const videoElementRef = useRef<HTMLVideoElement | null>(null);
|
||||||
|
const [hasVideoElement, setHasVideoElement] = useState(false);
|
||||||
|
|
||||||
const [fullscreen, setFullscreen] = useState<boolean>(() => {
|
const [fullscreen, setFullscreen] = useState<boolean>(() => {
|
||||||
if (typeof document === 'undefined') return false;
|
if (typeof document === 'undefined') return false;
|
||||||
return document.fullscreenElement === document.documentElement;
|
return document.fullscreenElement === document.documentElement;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const setVideoElement = useCallback((el: HTMLVideoElement | null) => {
|
||||||
|
videoElementRef.current = el;
|
||||||
|
setHasVideoElement(el !== null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const supported = shell.active || document.fullscreenEnabled === true || (hasVideoElement && hasWebkitFullscreen);
|
||||||
|
|
||||||
const requestFullscreen = useCallback(async () => {
|
const requestFullscreen = useCallback(async () => {
|
||||||
if (shell.active) {
|
if (shell.active) {
|
||||||
shell.send('win-set-visibility', { fullscreen: true });
|
shell.send('win-set-visibility', { fullscreen: true });
|
||||||
} else {
|
} else if (document.fullscreenEnabled) {
|
||||||
try {
|
await document.documentElement.requestFullscreen();
|
||||||
await document.documentElement.requestFullscreen();
|
} else if (videoElementRef.current && hasWebkitFullscreen) {
|
||||||
} catch (err) {
|
(videoElementRef.current as any).webkitEnterFullscreen();
|
||||||
console.error('Error enabling fullscreen', err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [shell]);
|
}, [shell]);
|
||||||
|
|
||||||
const exitFullscreen = useCallback(() => {
|
const exitFullscreen = useCallback(() => {
|
||||||
if (shell.active) {
|
if (shell.active) {
|
||||||
shell.send('win-set-visibility', { fullscreen: false });
|
shell.send('win-set-visibility', { fullscreen: false });
|
||||||
} else {
|
} else if (document.fullscreenElement === document.documentElement) {
|
||||||
if (document.fullscreenElement === document.documentElement) {
|
document.exitFullscreen();
|
||||||
document.exitFullscreen();
|
} else if (videoElementRef.current && (videoElementRef.current as any).webkitDisplayingFullscreen) {
|
||||||
}
|
(videoElementRef.current as any).webkitExitFullscreen();
|
||||||
}
|
}
|
||||||
}, [shell]);
|
}, [shell]);
|
||||||
|
|
||||||
|
|
@ -95,8 +106,8 @@ const FullscreenProvider = ({ children }: Props) => {
|
||||||
}, [shell, toggleFullscreen, exitFullscreen, escExitFullscreen]);
|
}, [shell, toggleFullscreen, exitFullscreen, escExitFullscreen]);
|
||||||
|
|
||||||
const value = useMemo<FullscreenContextValue>(
|
const value = useMemo<FullscreenContextValue>(
|
||||||
() => [fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen],
|
() => [fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen, supported, setVideoElement],
|
||||||
[fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen]
|
[fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen, supported, setVideoElement]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ const classnames = require('classnames');
|
||||||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||||
const { Button, Image } = require('stremio/components');
|
const { Button, Image } = require('stremio/components');
|
||||||
const { useFullscreen } = require('stremio/common/Fullscreen');
|
const { useFullscreen } = require('stremio/common/Fullscreen');
|
||||||
const usePWA = require('stremio/common/usePWA');
|
|
||||||
const { useHorizontalNavGamepadNavigation } = require('stremio/services/GamepadNavigation');
|
const { useHorizontalNavGamepadNavigation } = require('stremio/services/GamepadNavigation');
|
||||||
const SearchBar = require('./SearchBar');
|
const SearchBar = require('./SearchBar');
|
||||||
const NavMenu = require('./NavMenu');
|
const NavMenu = require('./NavMenu');
|
||||||
|
|
@ -17,8 +16,7 @@ const HorizontalNavBar = React.memo(({ className, route, query, title, backButto
|
||||||
const backButtonOnClick = React.useCallback(() => {
|
const backButtonOnClick = React.useCallback(() => {
|
||||||
window.history.back();
|
window.history.back();
|
||||||
}, []);
|
}, []);
|
||||||
const [fullscreen, requestFullscreen, exitFullscreen] = useFullscreen();
|
const [fullscreen, requestFullscreen, exitFullscreen, , supported] = useFullscreen();
|
||||||
const [isIOSPWA] = usePWA();
|
|
||||||
const renderNavMenuLabel = React.useCallback(({ ref, className, onClick, children, }) => (
|
const renderNavMenuLabel = React.useCallback(({ ref, className, onClick, children, }) => (
|
||||||
<Button ref={ref} className={classnames(className, styles['button-container'], styles['menu-button-container'])} tabIndex={-1} onClick={onClick}>
|
<Button ref={ref} className={classnames(className, styles['button-container'], styles['menu-button-container'])} tabIndex={-1} onClick={onClick}>
|
||||||
<Icon className={styles['icon']} name={'person-outline'} />
|
<Icon className={styles['icon']} name={'person-outline'} />
|
||||||
|
|
@ -64,7 +62,7 @@ const HorizontalNavBar = React.memo(({ className, route, query, title, backButto
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
!isIOSPWA && fullscreenButton ?
|
supported && fullscreenButton ?
|
||||||
<Button className={styles['button-container']} title={fullscreen ? t('EXIT_FULLSCREEN') : t('ENTER_FULLSCREEN')} tabIndex={-1} onClick={fullscreen ? exitFullscreen : requestFullscreen}>
|
<Button className={styles['button-container']} title={fullscreen ? t('EXIT_FULLSCREEN') : t('ENTER_FULLSCREEN')} tabIndex={-1} onClick={fullscreen ? exitFullscreen : requestFullscreen}>
|
||||||
<Icon className={styles['icon']} name={fullscreen ? 'minimize' : 'maximize'} />
|
<Icon className={styles['icon']} name={fullscreen ? 'minimize' : 'maximize'} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ const NavMenuContent = ({ onClick }) => {
|
||||||
const streamingServer = useStreamingServer();
|
const streamingServer = useStreamingServer();
|
||||||
const { handlePlayUrl } = usePlayUrl();
|
const { handlePlayUrl } = usePlayUrl();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const [fullscreen, requestFullscreen, exitFullscreen] = useFullscreen();
|
const [fullscreen, requestFullscreen, exitFullscreen, , supported] = useFullscreen();
|
||||||
const [isIOSPWA, isAndroidPWA] = usePWA();
|
const [, isAndroidPWA] = usePWA();
|
||||||
const streamingServerWarningDismissed = React.useMemo(() => {
|
const streamingServerWarningDismissed = React.useMemo(() => {
|
||||||
return streamingServer.settings !== null && streamingServer.settings.type === 'Ready' || (
|
return streamingServer.settings !== null && streamingServer.settings.type === 'Ready' || (
|
||||||
!isNaN(profile.settings.streamingServerWarningDismissed.getTime()) &&
|
!isNaN(profile.settings.streamingServerWarningDismissed.getTime()) &&
|
||||||
|
|
@ -79,7 +79,7 @@ const NavMenuContent = ({ onClick }) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
!isIOSPWA && !isAndroidPWA ?
|
supported && !isAndroidPWA ?
|
||||||
<div className={styles['nav-menu-section']}>
|
<div className={styles['nav-menu-section']}>
|
||||||
<Button className={styles['nav-menu-option-container']} title={fullscreen ? t('EXIT_FULLSCREEN') : t('ENTER_FULLSCREEN')} onClick={fullscreen ? exitFullscreen : requestFullscreen}>
|
<Button className={styles['nav-menu-option-container']} title={fullscreen ? t('EXIT_FULLSCREEN') : t('ENTER_FULLSCREEN')} onClick={fullscreen ? exitFullscreen : requestFullscreen}>
|
||||||
<Icon className={styles['icon']} name={fullscreen ? 'minimize' : 'maximize'} />
|
<Icon className={styles['icon']} name={fullscreen ? 'minimize' : 'maximize'} />
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,13 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
|
|
||||||
const [immersed, setImmersed] = React.useState(true);
|
const [immersed, setImmersed] = React.useState(true);
|
||||||
const setImmersedDebounced = React.useCallback(debounce(setImmersed, 3000), []);
|
const setImmersedDebounced = React.useCallback(debounce(setImmersed, 3000), []);
|
||||||
const [, , , toggleFullscreen] = useFullscreen();
|
const [fullscreen, , , toggleFullscreen, , setVideoElement] = useFullscreen();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const el = video.containerRef.current?.querySelector('video');
|
||||||
|
setVideoElement(el || null);
|
||||||
|
return () => setVideoElement(null);
|
||||||
|
}, [video.state.manifest]);
|
||||||
|
|
||||||
const [optionsMenuOpen, , closeOptionsMenu, toggleOptionsMenu] = useBinaryState(false);
|
const [optionsMenuOpen, , closeOptionsMenu, toggleOptionsMenu] = useBinaryState(false);
|
||||||
const [subtitlesMenuOpen, , closeSubtitlesMenu, toggleSubtitlesMenu] = useBinaryState(false);
|
const [subtitlesMenuOpen, , closeSubtitlesMenu, toggleSubtitlesMenu] = useBinaryState(false);
|
||||||
|
|
@ -534,7 +540,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
}
|
}
|
||||||
}, [settings.pauseOnMinimize, shell.windowClosed, shell.windowHidden]);
|
}, [settings.pauseOnMinimize, shell.windowClosed, shell.windowHidden]);
|
||||||
|
|
||||||
useMediaSession(video.state, player, onPlayRequested, onPauseRequested, onNextVideoRequested);
|
useMediaSession(video.state, player, fullscreen, onPlayRequested, onPauseRequested, onNextVideoRequested);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const onMediaKey = (action) => {
|
const onMediaKey = (action) => {
|
||||||
|
|
@ -780,181 +786,181 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
onMouseMove={onContainerMouseMove}
|
onMouseMove={onContainerMouseMove}
|
||||||
onMouseOver={onContainerMouseMove}
|
onMouseOver={onContainerMouseMove}
|
||||||
onMouseLeave={onContainerMouseLeave}>
|
onMouseLeave={onContainerMouseLeave}>
|
||||||
<Video
|
<Video
|
||||||
ref={video.containerRef}
|
ref={video.containerRef}
|
||||||
className={styles['layer']}
|
className={styles['layer']}
|
||||||
onClick={onVideoClick}
|
onClick={onVideoClick}
|
||||||
onDoubleClick={onVideoDoubleClick}
|
onDoubleClick={onVideoDoubleClick}
|
||||||
/>
|
|
||||||
{
|
|
||||||
!video.state.loaded ?
|
|
||||||
<div className={classnames(styles['layer'], styles['background-layer'])}>
|
|
||||||
<img className={styles['image']} src={player?.metaItem?.content?.background} />
|
|
||||||
</div>
|
|
||||||
:
|
|
||||||
null
|
|
||||||
}
|
|
||||||
{
|
|
||||||
(video.state.buffering || !video.state.loaded) && !error ?
|
|
||||||
<BufferingLoader
|
|
||||||
ref={bufferingRef}
|
|
||||||
className={classnames(styles['layer'], styles['buffering-layer'])}
|
|
||||||
logo={player?.metaItem?.content?.logo}
|
|
||||||
/>
|
|
||||||
:
|
|
||||||
null
|
|
||||||
}
|
|
||||||
{
|
|
||||||
error !== null ?
|
|
||||||
<Error
|
|
||||||
ref={errorRef}
|
|
||||||
className={classnames(styles['layer'], styles['error-layer'])}
|
|
||||||
stream={video.state.stream}
|
|
||||||
{...error}
|
|
||||||
/>
|
|
||||||
:
|
|
||||||
null
|
|
||||||
}
|
|
||||||
{
|
|
||||||
menusOpen ?
|
|
||||||
<div className={styles['layer']} />
|
|
||||||
:
|
|
||||||
null
|
|
||||||
}
|
|
||||||
{
|
|
||||||
video.state.volume !== null && overlayHidden ?
|
|
||||||
<VolumeChangeIndicator
|
|
||||||
muted={video.state.muted}
|
|
||||||
volume={video.state.volume}
|
|
||||||
/>
|
|
||||||
:
|
|
||||||
null
|
|
||||||
}
|
|
||||||
<ContextMenu on={[video.containerRef, bufferingRef, errorRef]} autoClose>
|
|
||||||
<OptionsMenu
|
|
||||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
|
||||||
stream={player?.selected?.stream}
|
|
||||||
playbackDevices={playbackDevices}
|
|
||||||
extraSubtitlesTracks={extraSubtitleTracks}
|
|
||||||
selectedExtraSubtitlesTrackId={selectedExtraSubtitleTrackId}
|
|
||||||
/>
|
/>
|
||||||
</ContextMenu>
|
{
|
||||||
<HorizontalNavBar
|
!video.state.loaded ?
|
||||||
className={classnames(styles['layer'], styles['nav-bar-layer'])}
|
<div className={classnames(styles['layer'], styles['background-layer'])}>
|
||||||
title={player.title !== null ? player.title : ''}
|
<img className={styles['image']} src={player?.metaItem?.content?.background} />
|
||||||
backButton={true}
|
</div>
|
||||||
fullscreenButton={true}
|
:
|
||||||
hdrInfo={video.state.hdrInfo}
|
null
|
||||||
onMouseMove={onBarMouseMove}
|
}
|
||||||
onMouseOver={onBarMouseMove}
|
{
|
||||||
/>
|
(video.state.buffering || !video.state.loaded) && !error ?
|
||||||
{
|
<BufferingLoader
|
||||||
player.metaItem?.type === 'Ready' ?
|
ref={bufferingRef}
|
||||||
<SideDrawerButton
|
className={classnames(styles['layer'], styles['buffering-layer'])}
|
||||||
className={classnames(styles['layer'], styles['side-drawer-button-layer'])}
|
logo={player?.metaItem?.content?.logo}
|
||||||
onClick={toggleSideDrawer}
|
/>
|
||||||
/>
|
:
|
||||||
:
|
null
|
||||||
null
|
}
|
||||||
}
|
{
|
||||||
<ControlBar
|
error !== null ?
|
||||||
ref={controlBarRef}
|
<Error
|
||||||
className={classnames(styles['layer'], styles['control-bar-layer'])}
|
ref={errorRef}
|
||||||
paused={video.state.paused}
|
className={classnames(styles['layer'], styles['error-layer'])}
|
||||||
time={video.state.time}
|
stream={video.state.stream}
|
||||||
duration={video.state.duration}
|
{...error}
|
||||||
buffered={video.state.buffered}
|
/>
|
||||||
volume={video.state.volume}
|
:
|
||||||
muted={video.state.muted}
|
null
|
||||||
playbackSpeed={video.state.playbackSpeed}
|
}
|
||||||
subtitlesTracks={allSubtitleTracks}
|
{
|
||||||
audioTracks={video.state.audioTracks}
|
menusOpen ?
|
||||||
metaItem={player.metaItem}
|
<div className={styles['layer']} />
|
||||||
nextVideo={player.nextVideo}
|
:
|
||||||
stream={player.selected !== null ? player.selected.stream : null}
|
null
|
||||||
statistics={statistics}
|
}
|
||||||
onPlayRequested={onPlayRequested}
|
{
|
||||||
onPauseRequested={onPauseRequested}
|
video.state.volume !== null && overlayHidden ?
|
||||||
onNextVideoRequested={onNextVideoRequested}
|
<VolumeChangeIndicator
|
||||||
onMuteRequested={onMuteRequested}
|
muted={video.state.muted}
|
||||||
onUnmuteRequested={onUnmuteRequested}
|
volume={video.state.volume}
|
||||||
onVolumeChangeRequested={onVolumeChangeRequested}
|
/>
|
||||||
onSeekRequested={onSeekRequested}
|
:
|
||||||
onToggleOptionsMenu={toggleOptionsMenu}
|
null
|
||||||
onToggleSubtitlesMenu={toggleSubtitlesMenu}
|
}
|
||||||
onToggleAudioMenu={toggleAudioMenu}
|
<ContextMenu on={[video.containerRef, bufferingRef, errorRef]} autoClose>
|
||||||
onToggleSpeedMenu={toggleSpeedMenu}
|
<OptionsMenu
|
||||||
videoScale={video.state.videoScale}
|
|
||||||
videoScaleLabel={VIDEO_SCALE_LABELS[video.state.videoScale || 'contain']}
|
|
||||||
onVideoScaleChanged={onVideoScaleChanged}
|
|
||||||
onToggleStatisticsMenu={toggleStatisticsMenu}
|
|
||||||
onToggleSideDrawer={toggleSideDrawer}
|
|
||||||
onMouseMove={onBarMouseMove}
|
|
||||||
onMouseOver={onBarMouseMove}
|
|
||||||
onTouchEnd={onContainerMouseLeave}
|
|
||||||
/>
|
|
||||||
<Indicator
|
|
||||||
className={classnames(styles['layer'], styles['indicator-layer'])}
|
|
||||||
videoState={video.state}
|
|
||||||
disabled={subtitlesMenuOpen}
|
|
||||||
/>
|
|
||||||
{
|
|
||||||
nextVideoPopupOpen ?
|
|
||||||
<NextVideoPopup
|
|
||||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||||
metaItem={player.metaItem !== null && player.metaItem.type === 'Ready' ? player.metaItem.content : null}
|
stream={player?.selected?.stream}
|
||||||
nextVideo={player.nextVideo}
|
playbackDevices={playbackDevices}
|
||||||
onDismiss={onDismissNextVideoPopup}
|
extraSubtitlesTracks={extraSubtitleTracks}
|
||||||
onNextVideoRequested={onNextVideoRequested}
|
selectedExtraSubtitlesTrackId={selectedExtraSubtitleTrackId}
|
||||||
/>
|
/>
|
||||||
:
|
</ContextMenu>
|
||||||
null
|
<HorizontalNavBar
|
||||||
}
|
className={classnames(styles['layer'], styles['nav-bar-layer'])}
|
||||||
<Transition when={statisticsMenuOpen} name={'fade'}>
|
title={player.title !== null ? player.title : ''}
|
||||||
<StatisticsMenu
|
backButton={true}
|
||||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
fullscreenButton={true}
|
||||||
{...statistics}
|
hdrInfo={video.state.hdrInfo}
|
||||||
|
onMouseMove={onBarMouseMove}
|
||||||
|
onMouseOver={onBarMouseMove}
|
||||||
/>
|
/>
|
||||||
</Transition>
|
{
|
||||||
<Transition when={sideDrawerOpen} name={'slide-left'}>
|
player.metaItem?.type === 'Ready' ?
|
||||||
<SideDrawer
|
<SideDrawerButton
|
||||||
className={classnames(styles['layer'], styles['side-drawer-layer'])}
|
className={classnames(styles['layer'], styles['side-drawer-button-layer'])}
|
||||||
metaItem={player.metaItem?.content}
|
onClick={toggleSideDrawer}
|
||||||
seriesInfo={player.seriesInfo}
|
/>
|
||||||
closeSideDrawer={closeSideDrawer}
|
:
|
||||||
selected={player.selected?.streamRequest?.path.id}
|
null
|
||||||
/>
|
}
|
||||||
</Transition>
|
<ControlBar
|
||||||
<Transition when={subtitlesMenuOpen} name={'fade'}>
|
ref={controlBarRef}
|
||||||
<SubtitlesMenu
|
className={classnames(styles['layer'], styles['control-bar-layer'])}
|
||||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
paused={video.state.paused}
|
||||||
{...subtitlesMenuProps}
|
time={video.state.time}
|
||||||
/>
|
duration={video.state.duration}
|
||||||
</Transition>
|
buffered={video.state.buffered}
|
||||||
<Transition when={audioMenuOpen} name={'fade'}>
|
volume={video.state.volume}
|
||||||
<AudioMenu
|
muted={video.state.muted}
|
||||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
|
||||||
audioTracks={video.state.audioTracks}
|
|
||||||
selectedAudioTrackId={video.state.selectedAudioTrackId}
|
|
||||||
onAudioTrackSelected={onAudioTrackSelected}
|
|
||||||
/>
|
|
||||||
</Transition>
|
|
||||||
<Transition when={speedMenuOpen} name={'fade'}>
|
|
||||||
<SpeedMenu
|
|
||||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
|
||||||
playbackSpeed={video.state.playbackSpeed}
|
playbackSpeed={video.state.playbackSpeed}
|
||||||
onPlaybackSpeedChanged={onPlaybackSpeedChanged}
|
subtitlesTracks={allSubtitleTracks}
|
||||||
|
audioTracks={video.state.audioTracks}
|
||||||
|
metaItem={player.metaItem}
|
||||||
|
nextVideo={player.nextVideo}
|
||||||
|
stream={player.selected !== null ? player.selected.stream : null}
|
||||||
|
statistics={statistics}
|
||||||
|
onPlayRequested={onPlayRequested}
|
||||||
|
onPauseRequested={onPauseRequested}
|
||||||
|
onNextVideoRequested={onNextVideoRequested}
|
||||||
|
onMuteRequested={onMuteRequested}
|
||||||
|
onUnmuteRequested={onUnmuteRequested}
|
||||||
|
onVolumeChangeRequested={onVolumeChangeRequested}
|
||||||
|
onSeekRequested={onSeekRequested}
|
||||||
|
onToggleOptionsMenu={toggleOptionsMenu}
|
||||||
|
onToggleSubtitlesMenu={toggleSubtitlesMenu}
|
||||||
|
onToggleAudioMenu={toggleAudioMenu}
|
||||||
|
onToggleSpeedMenu={toggleSpeedMenu}
|
||||||
|
videoScale={video.state.videoScale}
|
||||||
|
videoScaleLabel={VIDEO_SCALE_LABELS[video.state.videoScale || 'contain']}
|
||||||
|
onVideoScaleChanged={onVideoScaleChanged}
|
||||||
|
onToggleStatisticsMenu={toggleStatisticsMenu}
|
||||||
|
onToggleSideDrawer={toggleSideDrawer}
|
||||||
|
onMouseMove={onBarMouseMove}
|
||||||
|
onMouseOver={onBarMouseMove}
|
||||||
|
onTouchEnd={onContainerMouseLeave}
|
||||||
/>
|
/>
|
||||||
</Transition>
|
<Indicator
|
||||||
<Transition when={optionsMenuOpen} name={'fade'}>
|
className={classnames(styles['layer'], styles['indicator-layer'])}
|
||||||
<OptionsMenu
|
videoState={video.state}
|
||||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
disabled={subtitlesMenuOpen}
|
||||||
stream={player.selected?.stream}
|
|
||||||
playbackDevices={playbackDevices}
|
|
||||||
extraSubtitlesTracks={extraSubtitleTracks}
|
|
||||||
selectedExtraSubtitlesTrackId={selectedExtraSubtitleTrackId}
|
|
||||||
/>
|
/>
|
||||||
</Transition>
|
{
|
||||||
|
nextVideoPopupOpen ?
|
||||||
|
<NextVideoPopup
|
||||||
|
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||||
|
metaItem={player.metaItem !== null && player.metaItem.type === 'Ready' ? player.metaItem.content : null}
|
||||||
|
nextVideo={player.nextVideo}
|
||||||
|
onDismiss={onDismissNextVideoPopup}
|
||||||
|
onNextVideoRequested={onNextVideoRequested}
|
||||||
|
/>
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
|
<Transition when={statisticsMenuOpen} name={'fade'}>
|
||||||
|
<StatisticsMenu
|
||||||
|
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||||
|
{...statistics}
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
|
<Transition when={sideDrawerOpen} name={'slide-left'}>
|
||||||
|
<SideDrawer
|
||||||
|
className={classnames(styles['layer'], styles['side-drawer-layer'])}
|
||||||
|
metaItem={player.metaItem?.content}
|
||||||
|
seriesInfo={player.seriesInfo}
|
||||||
|
closeSideDrawer={closeSideDrawer}
|
||||||
|
selected={player.selected?.streamRequest?.path.id}
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
|
<Transition when={subtitlesMenuOpen} name={'fade'}>
|
||||||
|
<SubtitlesMenu
|
||||||
|
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||||
|
{...subtitlesMenuProps}
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
|
<Transition when={audioMenuOpen} name={'fade'}>
|
||||||
|
<AudioMenu
|
||||||
|
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||||
|
audioTracks={video.state.audioTracks}
|
||||||
|
selectedAudioTrackId={video.state.selectedAudioTrackId}
|
||||||
|
onAudioTrackSelected={onAudioTrackSelected}
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
|
<Transition when={speedMenuOpen} name={'fade'}>
|
||||||
|
<SpeedMenu
|
||||||
|
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||||
|
playbackSpeed={video.state.playbackSpeed}
|
||||||
|
onPlaybackSpeedChanged={onPlaybackSpeedChanged}
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
|
<Transition when={optionsMenuOpen} name={'fade'}>
|
||||||
|
<OptionsMenu
|
||||||
|
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||||
|
stream={player.selected?.stream}
|
||||||
|
playbackDevices={playbackDevices}
|
||||||
|
extraSubtitlesTracks={extraSubtitleTracks}
|
||||||
|
selectedExtraSubtitlesTrackId={selectedExtraSubtitleTrackId}
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,22 @@ import { MediaStatus } from 'stremio/common/useShell';
|
||||||
const useMediaSession = (
|
const useMediaSession = (
|
||||||
videoState: VideoState,
|
videoState: VideoState,
|
||||||
player: Player,
|
player: Player,
|
||||||
|
fullscreen: boolean,
|
||||||
onPlayRequested: () => void,
|
onPlayRequested: () => void,
|
||||||
onPauseRequested: () => void,
|
onPauseRequested: () => void,
|
||||||
onNextVideoRequested: () => void,
|
onNextVideoRequested: () => void,
|
||||||
) => {
|
) => {
|
||||||
const shell = useShell();
|
const shell = useShell();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!('audioSession' in navigator)) return;
|
||||||
|
const audioSession = (navigator as any).audioSession;
|
||||||
|
audioSession.type = fullscreen ? 'ambient' : 'playback';
|
||||||
|
return () => {
|
||||||
|
audioSession.type = 'playback';
|
||||||
|
};
|
||||||
|
}, [fullscreen]);
|
||||||
|
|
||||||
// Playback state
|
// Playback state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (navigator.mediaSession) {
|
if (navigator.mediaSession) {
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ const useVideo = () => {
|
||||||
extraSubtitlesTextColor: null,
|
extraSubtitlesTextColor: null,
|
||||||
extraSubtitlesBackgroundColor: null,
|
extraSubtitlesBackgroundColor: null,
|
||||||
extraSubtitlesOutlineColor: null,
|
extraSubtitlesOutlineColor: null,
|
||||||
|
fullscreen: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const dispatch = (action, options) => {
|
const dispatch = (action, options) => {
|
||||||
|
|
@ -147,6 +148,10 @@ const useVideo = () => {
|
||||||
setProp('videoScale', scale);
|
setProp('videoScale', scale);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setFullscreen = (state) => {
|
||||||
|
setProp('fullscreen', state);
|
||||||
|
};
|
||||||
|
|
||||||
const setSubtitlesTextColor = (color) => {
|
const setSubtitlesTextColor = (color) => {
|
||||||
setProp('subtitlesTextColor', color);
|
setProp('subtitlesTextColor', color);
|
||||||
setProp('extraSubtitlesTextColor', color);
|
setProp('extraSubtitlesTextColor', color);
|
||||||
|
|
@ -244,6 +249,7 @@ const useVideo = () => {
|
||||||
setSubtitlesOutlineColor,
|
setSubtitlesOutlineColor,
|
||||||
setExtraSubtitlesTrack,
|
setExtraSubtitlesTrack,
|
||||||
setVideoScale,
|
setVideoScale,
|
||||||
|
setFullscreen,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue