mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-05-11 08:10:40 +00:00
Merge branch 'development' into feat/player-add-video-scale-property
This commit is contained in:
commit
cdffeb4fdf
10 changed files with 103 additions and 57 deletions
|
|
@ -18,7 +18,7 @@
|
||||||
"@sentry/browser": "8.42.0",
|
"@sentry/browser": "8.42.0",
|
||||||
"@stremio/stremio-colors": "5.2.0",
|
"@stremio/stremio-colors": "5.2.0",
|
||||||
"@stremio/stremio-core-web": "0.56.4",
|
"@stremio/stremio-core-web": "0.56.4",
|
||||||
"@stremio/stremio-icons": "5.8.0",
|
"@stremio/stremio-icons": "5.10.0",
|
||||||
"@stremio/stremio-video": "0.0.77",
|
"@stremio/stremio-video": "0.0.77",
|
||||||
"a-color-picker": "1.2.1",
|
"a-color-picker": "1.2.1",
|
||||||
"bowser": "2.11.0",
|
"bowser": "2.11.0",
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ importers:
|
||||||
specifier: 0.56.4
|
specifier: 0.56.4
|
||||||
version: 0.56.4
|
version: 0.56.4
|
||||||
'@stremio/stremio-icons':
|
'@stremio/stremio-icons':
|
||||||
specifier: 5.8.0
|
specifier: 5.10.0
|
||||||
version: 5.8.0
|
version: 5.10.0
|
||||||
'@stremio/stremio-video':
|
'@stremio/stremio-video':
|
||||||
specifier: 0.0.77
|
specifier: 0.0.77
|
||||||
version: 0.0.77
|
version: 0.0.77
|
||||||
|
|
@ -1123,8 +1123,8 @@ packages:
|
||||||
'@stremio/stremio-core-web@0.56.4':
|
'@stremio/stremio-core-web@0.56.4':
|
||||||
resolution: {integrity: sha512-tFAMYgKrJ1bkvHRMpxDykM/844sDjgRPFk6FLhjQiwh01OHIyEgDqGo/NgwFM+CuMR4mW676SDvwNHkK0Xqg3w==}
|
resolution: {integrity: sha512-tFAMYgKrJ1bkvHRMpxDykM/844sDjgRPFk6FLhjQiwh01OHIyEgDqGo/NgwFM+CuMR4mW676SDvwNHkK0Xqg3w==}
|
||||||
|
|
||||||
'@stremio/stremio-icons@5.8.0':
|
'@stremio/stremio-icons@5.10.0':
|
||||||
resolution: {integrity: sha512-IVUvQbIWfA4YEHCTed7v/sdQJCJ+OOCf84LTWpkE2W6GLQ+15WHcMEJrVkE1X3ekYJnGg3GjT0KLO6tKSU0P4w==}
|
resolution: {integrity: sha512-Zw/vGC3D2yeQfk8xv/tfMJTDvbCPOI91tBg4XpR2+EgbZSX8Xvm7Vz457PIhFPhTAwdOPHp0VX0M3gzjbt0zOg==}
|
||||||
|
|
||||||
'@stremio/stremio-video@0.0.77':
|
'@stremio/stremio-video@0.0.77':
|
||||||
resolution: {integrity: sha512-bnKBS5a9R3+M0zx95YpDUiPs1gXcKCsybgdxfZmpWuQaN0RE9bTBAUlIfBSrcEjVhufMOvg+cfXScT+0fBzTTw==}
|
resolution: {integrity: sha512-bnKBS5a9R3+M0zx95YpDUiPs1gXcKCsybgdxfZmpWuQaN0RE9bTBAUlIfBSrcEjVhufMOvg+cfXScT+0fBzTTw==}
|
||||||
|
|
@ -5874,7 +5874,7 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.24.1
|
'@babel/runtime': 7.24.1
|
||||||
|
|
||||||
'@stremio/stremio-icons@5.8.0': {}
|
'@stremio/stremio-icons@5.10.0': {}
|
||||||
|
|
||||||
'@stremio/stremio-video@0.0.77':
|
'@stremio/stremio-video@0.0.77':
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ const NavMenu = require('./NavMenu');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
const { t } = require('i18next');
|
const { t } = require('i18next');
|
||||||
|
|
||||||
const HorizontalNavBar = React.memo(({ className, route, query, title, backButton, searchBar, fullscreenButton, navMenu, ...props }) => {
|
const HorizontalNavBar = React.memo(({ className, route, query, title, backButton, searchBar, fullscreenButton, navMenu, hdrInfo, ...props }) => {
|
||||||
const backButtonOnClick = React.useCallback(() => {
|
const backButtonOnClick = React.useCallback(() => {
|
||||||
window.history.back();
|
window.history.back();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -53,6 +53,14 @@ const HorizontalNavBar = React.memo(({ className, route, query, title, backButto
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
<div className={styles['buttons-container']}>
|
<div className={styles['buttons-container']}>
|
||||||
|
{
|
||||||
|
hdrInfo && (hdrInfo.gamma === 'pq' || hdrInfo.gamma === 'hlg') ?
|
||||||
|
<div className={styles['hdr-indicator']} title={hdrInfo.gamma === 'pq' ? 'HDR10' : 'HLG'}>
|
||||||
|
<Icon className={styles['icon']} name={'hdr'} />
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
{
|
{
|
||||||
!isIOSPWA && fullscreenButton ?
|
!isIOSPWA && 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}>
|
||||||
|
|
@ -82,7 +90,10 @@ HorizontalNavBar.propTypes = {
|
||||||
backButton: PropTypes.bool,
|
backButton: PropTypes.bool,
|
||||||
searchBar: PropTypes.bool,
|
searchBar: PropTypes.bool,
|
||||||
fullscreenButton: PropTypes.bool,
|
fullscreenButton: PropTypes.bool,
|
||||||
navMenu: PropTypes.bool
|
navMenu: PropTypes.bool,
|
||||||
|
hdrInfo: PropTypes.shape({
|
||||||
|
gamma: PropTypes.string,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = HorizontalNavBar;
|
module.exports = HorizontalNavBar;
|
||||||
|
|
|
||||||
|
|
@ -57,10 +57,30 @@
|
||||||
.buttons-container {
|
.buttons-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hdr-indicator {
|
||||||
|
flex: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 3.5rem;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
opacity: 0.6;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
flex: none;
|
||||||
|
width: 3rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
color: var(--primary-foreground-color);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.button-container {
|
.button-container {
|
||||||
flex: none;
|
flex: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ const useVideo = require('./useVideo');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
const Video = require('./Video');
|
const Video = require('./Video');
|
||||||
const { default: Indicator } = require('./Indicator/Indicator');
|
const { default: Indicator } = require('./Indicator/Indicator');
|
||||||
|
const { default: useMediaSession } = require('./useMediaSession');
|
||||||
|
|
||||||
const findTrackByLang = (tracks, lang) => tracks.find((track) => track.lang === lang || langs.where('1', track.lang)?.[2] === lang);
|
const findTrackByLang = (tracks, lang) => tracks.find((track) => track.lang === lang || langs.where('1', track.lang)?.[2] === lang);
|
||||||
const findTrackById = (tracks, id) => tracks.find((track) => track.id === id);
|
const findTrackById = (tracks, id) => tracks.find((track) => track.id === id);
|
||||||
|
|
@ -606,52 +607,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
}
|
}
|
||||||
}, [settings.pauseOnMinimize, shell.windowClosed, shell.windowHidden]);
|
}, [settings.pauseOnMinimize, shell.windowClosed, shell.windowHidden]);
|
||||||
|
|
||||||
// Media Session PlaybackState
|
useMediaSession(video.state, player, onPlayRequested, onPauseRequested, onNextVideoRequested);
|
||||||
React.useEffect(() => {
|
|
||||||
if (!navigator.mediaSession) return;
|
|
||||||
|
|
||||||
const playbackState = !video.state.paused ? 'playing' : 'paused';
|
|
||||||
navigator.mediaSession.playbackState = playbackState;
|
|
||||||
|
|
||||||
return () => navigator.mediaSession.playbackState = 'none';
|
|
||||||
}, [video.state.paused]);
|
|
||||||
|
|
||||||
// Media Session Metadata
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (!navigator.mediaSession) return;
|
|
||||||
|
|
||||||
const metaItem = player.metaItem && player.metaItem?.type === 'Ready' ? player.metaItem.content : null;
|
|
||||||
const videoId = player.selected ? player.selected?.streamRequest?.path?.id : null;
|
|
||||||
const video = metaItem ? metaItem.videos.find(({ id }) => id === videoId) : null;
|
|
||||||
|
|
||||||
const videoInfo = video && video.season && video.episode ? ` (${video.season}x${video.episode})` : null;
|
|
||||||
const videoTitle = video ? `${video.title}${videoInfo}` : null;
|
|
||||||
const metaTitle = metaItem ? metaItem.name : null;
|
|
||||||
const imageUrl = metaItem ? metaItem.logo : null;
|
|
||||||
|
|
||||||
const title = videoTitle ?? metaTitle;
|
|
||||||
const artist = videoTitle ? metaTitle : undefined;
|
|
||||||
const artwork = imageUrl ? [{ src: imageUrl }] : undefined;
|
|
||||||
|
|
||||||
if (title) {
|
|
||||||
navigator.mediaSession.metadata = new MediaMetadata({
|
|
||||||
title,
|
|
||||||
artist,
|
|
||||||
artwork,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [player.metaItem, player.selected]);
|
|
||||||
|
|
||||||
// Media Session Actions
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (!navigator.mediaSession) return;
|
|
||||||
|
|
||||||
navigator.mediaSession.setActionHandler('play', onPlayRequested);
|
|
||||||
navigator.mediaSession.setActionHandler('pause', onPauseRequested);
|
|
||||||
|
|
||||||
const nexVideoCallback = player.nextVideo ? onNextVideoRequested : null;
|
|
||||||
navigator.mediaSession.setActionHandler('nexttrack', nexVideoCallback);
|
|
||||||
}, [player.nextVideo, onPlayRequested, onPauseRequested, onNextVideoRequested]);
|
|
||||||
|
|
||||||
onShortcut('seekForward', (combo) => {
|
onShortcut('seekForward', (combo) => {
|
||||||
if (video.state.time !== null) {
|
if (video.state.time !== null) {
|
||||||
|
|
@ -970,6 +926,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
title={player.title !== null ? player.title : ''}
|
title={player.title !== null ? player.title : ''}
|
||||||
backButton={true}
|
backButton={true}
|
||||||
fullscreenButton={true}
|
fullscreenButton={true}
|
||||||
|
hdrInfo={video.state.hdrInfo}
|
||||||
onMouseMove={onBarMouseMove}
|
onMouseMove={onBarMouseMove}
|
||||||
onMouseOver={onBarMouseMove}
|
onMouseOver={onBarMouseMove}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,6 @@ html:not(.active-slider-within) {
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
box-shadow: 0 0 8rem 6rem @color-background-dark5;
|
|
||||||
content: "";
|
content: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,7 +101,6 @@ html:not(.active-slider-within) {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
box-shadow: 0 0 8rem 8rem @color-background-dark5;
|
|
||||||
content: "";
|
content: "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
57
src/routes/Player/useMediaSession.ts
Normal file
57
src/routes/Player/useMediaSession.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
const useMediaSession = (
|
||||||
|
videoState: VideoState,
|
||||||
|
player: Player,
|
||||||
|
onPlayRequested: () => void,
|
||||||
|
onPauseRequested: () => void,
|
||||||
|
onNextVideoRequested: () => void,
|
||||||
|
) => {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!navigator.mediaSession) return;
|
||||||
|
|
||||||
|
const playbackState = !videoState.paused ? 'playing' : 'paused';
|
||||||
|
navigator.mediaSession.playbackState = playbackState;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
navigator.mediaSession.playbackState = 'none';
|
||||||
|
};
|
||||||
|
}, [videoState.paused]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!navigator.mediaSession) return;
|
||||||
|
|
||||||
|
const metaItem = player.metaItem && player.metaItem?.type === 'Ready' ? player.metaItem.content as MetaItemPlayer : null;
|
||||||
|
const videoId = player.selected ? player.selected?.streamRequest?.path?.id : null;
|
||||||
|
const video = metaItem?.videos.find(({ id }) => id === videoId);
|
||||||
|
|
||||||
|
const videoInfo = video?.season && video?.episode ? ` (${video.season}x${video.episode})` : null;
|
||||||
|
const videoTitle = video ? `${video.title}${videoInfo}` : null;
|
||||||
|
const metaTitle = metaItem ? metaItem.name : null;
|
||||||
|
const imageUrl = metaItem ? metaItem.logo : null;
|
||||||
|
|
||||||
|
const title = videoTitle ?? metaTitle;
|
||||||
|
const artist = (videoTitle && metaTitle) ?? undefined;
|
||||||
|
const artwork = imageUrl ? [{ src: imageUrl }] : undefined;
|
||||||
|
|
||||||
|
if (title) {
|
||||||
|
navigator.mediaSession.metadata = new MediaMetadata({
|
||||||
|
title,
|
||||||
|
artist,
|
||||||
|
artwork,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [player.metaItem, player.selected]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!navigator.mediaSession) return;
|
||||||
|
|
||||||
|
navigator.mediaSession.setActionHandler('play', onPlayRequested);
|
||||||
|
navigator.mediaSession.setActionHandler('pause', onPauseRequested);
|
||||||
|
|
||||||
|
const nexVideoCallback = player.nextVideo ? onNextVideoRequested : null;
|
||||||
|
navigator.mediaSession.setActionHandler('nexttrack', nexVideoCallback);
|
||||||
|
}, [player.nextVideo, onPlayRequested, onPauseRequested, onNextVideoRequested]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useMediaSession;
|
||||||
|
|
@ -22,6 +22,7 @@ const useVideo = () => {
|
||||||
muted: null,
|
muted: null,
|
||||||
playbackSpeed: null,
|
playbackSpeed: null,
|
||||||
videoParams: null,
|
videoParams: null,
|
||||||
|
hdrInfo: null,
|
||||||
audioTracks: [],
|
audioTracks: [],
|
||||||
selectedAudioTrackId: null,
|
selectedAudioTrackId: null,
|
||||||
subtitlesTracks: [],
|
subtitlesTracks: [],
|
||||||
|
|
|
||||||
3
src/routes/Player/videoState.d.ts
vendored
Normal file
3
src/routes/Player/videoState.d.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
type VideoState = {
|
||||||
|
paused?: boolean;
|
||||||
|
};
|
||||||
1
src/types/models/Player.d.ts
vendored
1
src/types/models/Player.d.ts
vendored
|
|
@ -5,7 +5,6 @@ type LibraryItemPlayer = Pick<LibraryItem, '_id'> & {
|
||||||
type VideoPlayer = Video & {
|
type VideoPlayer = Video & {
|
||||||
upcoming: boolean,
|
upcoming: boolean,
|
||||||
watched: boolean,
|
watched: boolean,
|
||||||
progress: boolean | null,
|
|
||||||
scheduled: boolean,
|
scheduled: boolean,
|
||||||
deepLinks: VideoDeepLinks,
|
deepLinks: VideoDeepLinks,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue