Merge branch 'development' into feat/manage-streaming-urls

This commit is contained in:
Timothy Z. 2024-11-28 15:01:40 +02:00
commit 93db08678d
13 changed files with 95 additions and 31 deletions

9
package-lock.json generated
View file

@ -12,8 +12,8 @@
"@babel/runtime": "7.16.0",
"@sentry/browser": "6.13.3",
"@stremio/stremio-colors": "5.0.1",
"@stremio/stremio-core-web": "https://stremio.github.io/stremio-core/stremio-core-web/feat/streming-server-urls-bucket/dev/stremio-stremio-core-web-0.47.8.tgz",
"@stremio/stremio-icons": "5.4.0",
"@stremio/stremio-core-web": "0.47.8",
"@stremio/stremio-icons": "5.2.0",
"@stremio/stremio-video": "0.0.46",
"a-color-picker": "1.2.1",
"bowser": "2.11.0",
@ -3132,9 +3132,8 @@
},
"node_modules/@stremio/stremio-core-web": {
"version": "0.47.8",
"resolved": "https://stremio.github.io/stremio-core/stremio-core-web/feat/streming-server-urls-bucket/dev/stremio-stremio-core-web-0.47.8.tgz",
"license": "MIT",
"integrity": "sha512-Y9o1ax6TJuYB9GYeXuRjMgyt9bpORLhzgzU2+ie2b1nIa9+5IS+jCWhgJFud35Io479GZJNVUeP83KxVktbuNg==",
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.47.8.tgz",
"integrity": "sha512-X5yKSCm5DXR7U6oIO+2kaI1q3TnaWP6df/HFa1RBi/uw+8IYk+FB8GWpryxXyisJTFiUfQgcJDIlHROauaBQkg==",
"dependencies": {
"@babel/runtime": "7.24.1"
}

View file

@ -94,7 +94,7 @@ const EXTERNAL_PLAYERS = [
},
];
const WHITELISTED_HOSTS = ['stremio.com', 'strem.io', 'stremio.zendesk.com', 'google.com', 'youtube.com', 'twitch.tv', 'twitter.com', 'netflix.com', 'adex.network', 'amazon.com', 'forms.gle'];
const WHITELISTED_HOSTS = ['stremio.com', 'strem.io', 'stremio.zendesk.com', 'google.com', 'youtube.com', 'twitch.tv', 'twitter.com', 'x.com', 'netflix.com', 'adex.network', 'amazon.com', 'forms.gle'];
module.exports = {
CHROMECAST_RECEIVER_APP_ID,

View file

@ -70,7 +70,7 @@ const ModalDialog = ({ className, title, buttons, children, dataset, onCloseRequ
:
null
}
<div className={styles['modal-dialog-content']}>
<div className={styles['body-container']}>
{children}
</div>
{

View file

@ -67,6 +67,7 @@
.modal-dialog-content {
z-index: 1;
position: relative;
overflow-y: auto;
.title-container {
flex: 1 0 auto;
@ -78,7 +79,7 @@
color: var(--primary-foreground-color);
}
.modal-dialog-content {
.body-container {
flex: 1;
align-self: stretch;
overflow-y: auto;
@ -157,9 +158,11 @@
z-index: 0;
padding: 0 1.5rem;
.buttons-container {
flex-direction: column;
gap: 1rem;
.modal-dialog-content {
.buttons-container {
flex-direction: column;
gap: 1rem;
}
}
}

View file

@ -27,6 +27,9 @@
max-height: 2.4em;
font-weight: 500;
color: var(--primary-foreground-color);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.icon {

View file

@ -58,7 +58,6 @@
.addon-name {
width: 7rem;
max-height: 3.6em;
font-size: 1.1rem;
text-align: left;
color: var(--primary-foreground-color);

View file

@ -108,7 +108,6 @@
.select-input-container {
min-width: 40%;
flex: 0 0 auto;
flex-grow: 1;
background: none;

View file

@ -47,6 +47,7 @@ const Video = ({ className, id, title, thumbnail, episode, released, upcoming, w
}, []);
const toggleWatchedOnClick = React.useCallback((event) => {
event.preventDefault();
event.stopPropagation();
closeMenu();
core.transport.dispatch({
action: 'MetaDetails',

View file

@ -34,7 +34,7 @@ const Player = ({ urlParams, queryParams }) => {
return queryParams.has('forceTranscoding');
}, [queryParams]);
const [player, videoParamsChanged, timeChanged, pausedChanged, ended, nextVideo] = usePlayer(urlParams);
const [player, videoParamsChanged, timeChanged, seek, pausedChanged, ended, nextVideo] = usePlayer(urlParams);
const [settings, updateSettings] = useSettings();
const streamingServer = useStreamingServer();
const statistics = useStatistics(player, streamingServer);
@ -42,6 +42,8 @@ const Player = ({ urlParams, queryParams }) => {
const routeFocused = useRouteFocused();
const toast = useToast();
const [seeking, setSeeking] = React.useState(false);
const [casting, setCasting] = React.useState(() => {
return chromecast.active && chromecast.transport.getCastState() === cast.framework.CastState.CONNECTED;
});
@ -136,6 +138,7 @@ const Player = ({ urlParams, queryParams }) => {
const onPlayRequested = React.useCallback(() => {
video.setProp('paused', false);
setSeeking(false);
}, []);
const onPlayRequestedDebounced = React.useCallback(debounce(onPlayRequested, 200), []);
@ -159,7 +162,8 @@ const Player = ({ urlParams, queryParams }) => {
const onSeekRequested = React.useCallback((time) => {
video.setProp('time', time);
}, []);
seek(time, video.state.duration, video.state.manifest?.name);
}, [video.state.duration, video.state.manifest]);
const onPlaybackSpeedChanged = React.useCallback((rate) => {
video.setProp('playbackSpeed', rate);
@ -342,12 +346,8 @@ const Player = ({ urlParams, queryParams }) => {
}, [settings.subtitlesOutlineColor]);
React.useEffect(() => {
if (video.state.time !== null && !isNaN(video.state.time) &&
video.state.duration !== null && !isNaN(video.state.duration) &&
video.state.manifest !== null && typeof video.state.manifest.name === 'string') {
timeChanged(video.state.time, video.state.duration, video.state.manifest.name);
}
}, [video.state.time, video.state.duration, video.state.manifest]);
!seeking && timeChanged(video.state.time, video.state.duration, video.state.manifest?.name);
}, [video.state.time, video.state.duration, video.state.manifest, seeking]);
React.useEffect(() => {
if (video.state.paused !== null) {
@ -468,6 +468,7 @@ const Player = ({ urlParams, queryParams }) => {
if (!menusOpen && !nextVideoPopupOpen && video.state.paused !== null) {
if (video.state.paused) {
onPlayRequested();
setSeeking(false);
} else {
onPauseRequested();
}
@ -478,6 +479,7 @@ const Player = ({ urlParams, queryParams }) => {
case 'ArrowRight': {
if (!menusOpen && !nextVideoPopupOpen && video.state.time !== null) {
const seekDuration = event.shiftKey ? settings.seekShortTimeDuration : settings.seekTimeDuration;
setSeeking(true);
onSeekRequested(video.state.time + seekDuration);
}
@ -486,6 +488,7 @@ const Player = ({ urlParams, queryParams }) => {
case 'ArrowLeft': {
if (!menusOpen && !nextVideoPopupOpen && video.state.time !== null) {
const seekDuration = event.shiftKey ? settings.seekShortTimeDuration : settings.seekTimeDuration;
setSeeking(true);
onSeekRequested(video.state.time - seekDuration);
}
@ -553,6 +556,11 @@ const Player = ({ urlParams, queryParams }) => {
}
}
};
const onKeyUp = (event) => {
if (event.code === 'ArrowRight' || event.code === 'ArrowLeft') {
setSeeking(false);
}
};
const onWheel = ({ deltaY }) => {
if (deltaY > 0) {
if (!menusOpen && video.state.volume !== null) {
@ -566,10 +574,12 @@ const Player = ({ urlParams, queryParams }) => {
};
if (routeFocused) {
window.addEventListener('keydown', onKeyDown);
window.addEventListener('keyup', onKeyUp);
window.addEventListener('wheel', onWheel);
}
return () => {
window.removeEventListener('keydown', onKeyDown);
window.removeEventListener('keyup', onKeyUp);
window.removeEventListener('wheel', onWheel);
};
}, [player.metaItem, player.selected, streamingServer.statistics, settings.seekTimeDuration, settings.seekShortTimeDuration, routeFocused, menusOpen, nextVideoPopupOpen, video.state.paused, video.state.time, video.state.volume, video.state.audioTracks, video.state.subtitlesTracks, video.state.extraSubtitlesTracks, video.state.playbackSpeed, toggleSubtitlesMenu, toggleInfoMenu, toggleVideosMenu, toggleStatisticsMenu]);

View file

@ -204,7 +204,15 @@ const SubtitlesMenu = React.memo((props) => {
<div className={styles['variants-list']}>
{subtitlesTracksForLanguage.map((track, index) => (
<Button key={index} title={track.label} className={classnames(styles['variant-option'], { 'selected': props.selectedSubtitlesTrackId === track.id || props.selectedExtraSubtitlesTrackId === track.id })} data-id={track.id} data-origin={track.origin} data-embedded={track.embedded} onClick={subtitlesTrackOnClick}>
<div className={styles['variant-label']}>{track.origin}</div>
<div className={styles['variant-label']}>
{
typeof track.label === 'string' && !track.label.startsWith('http') ?
track.label
:
track.lang
}
<div className={styles['variant-origin']}>{t(track.origin)}</div>
</div>
{
props.selectedSubtitlesTrackId === track.id || props.selectedExtraSubtitlesTrackId === track.id ?
<div className={styles['icon']} />

View file

@ -47,6 +47,15 @@
color: var(--primary-foreground-color);
}
.language-label, .variant-label, .variant-origin {
text-wrap: nowrap;
text-overflow: ellipsis;
}
.variant-label .variant-origin {
color: var(--color-placeholder-text);
}
.icon {
flex: none;
width: 0.5rem;

View file

@ -1,2 +1,11 @@
declare const usePlayer: (urlParams: UrlParams, videoParams: any) => [Player, (time: number, duration: number, device: string) => void, (paused: boolean) => void, () => void, () => void];
declare const usePlayer: (urlParams: UrlParams) => [
Player,
videoParamsChanged: (videoParams: { hash: string | null, size: number | null, filename: string | null }) => void,
timeChanged: (time: number, duration: number, device: string) => void,
seek: (time: number, duration: number, device: string) => void,
pausedChanged: (paused: boolean) => void, () => void, () => void,
ended: () => void,
nextVideo: () => void,
];
export = usePlayer;

View file

@ -96,14 +96,37 @@ const usePlayer = (urlParams) => {
}, 'player');
}, []);
const timeChanged = React.useCallback((time, duration, device) => {
core.transport.dispatch({
action: 'Player',
args: {
action: 'TimeChanged',
args: { time, duration, device }
}
}, 'player');
if (typeof time === 'number' && typeof duration === 'number' && typeof device === 'string') {
core.transport.dispatch({
action: 'Player',
args: {
action: 'TimeChanged',
args: {
time: Math.round(time),
duration,
device,
}
}
}, 'player');
}
}, []);
const seek = React.useCallback((time, duration, device) => {
if (typeof time === 'number' && typeof duration === 'number' && typeof device === 'string') {
core.transport.dispatch({
action: 'Player',
args: {
action: 'Seek',
args: {
time: Math.round(time),
duration,
device,
}
}
}, 'player');
}
}, []);
const ended = React.useCallback(() => {
core.transport.dispatch({
action: 'Player',
@ -129,8 +152,9 @@ const usePlayer = (urlParams) => {
}
}, 'player');
}, []);
const player = useModelState({ model: 'player', action, map });
return [player, videoParamsChanged, timeChanged, pausedChanged, ended, nextVideo];
return [player, videoParamsChanged, timeChanged, seek, pausedChanged, ended, nextVideo];
};
module.exports = usePlayer;