Merge branch 'development' into Web-app-post-redesign-fixes
2
.dockerignore
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
screenshots/*
|
||||
screenshots*
|
||||
55
Dockerfile
|
|
@ -1,24 +1,31 @@
|
|||
# Stremio Node 14.x
|
||||
FROM stremio/node-base:fermium
|
||||
|
||||
# Meta
|
||||
LABEL Description="Stremio Web" Vendor="Smart Code OOD" Version="1.0.0"
|
||||
|
||||
# Update GitHub remote host key
|
||||
RUN echo "github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=" >> ~/.ssh/known_hosts
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /var/www/stremio-web
|
||||
|
||||
# Install app dependencies
|
||||
WORKDIR /var/www/stremio-web
|
||||
COPY . /var/www/stremio-web
|
||||
RUN npm install
|
||||
|
||||
# Bundle app source
|
||||
WORKDIR /var/www/stremio-web
|
||||
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 8080
|
||||
CMD ["node", "http_server.js"]
|
||||
# Stremio Node 14.x
|
||||
# the node version for running Stremio Web
|
||||
ARG NODE_VERSION=15-alpine
|
||||
FROM node:$NODE_VERSION AS base
|
||||
|
||||
# Meta
|
||||
LABEL Description="Stremio Web" Vendor="Smart Code OOD" Version="1.0.0"
|
||||
|
||||
RUN mkdir -p /var/www/stremio-web
|
||||
WORKDIR /var/www/stremio-web
|
||||
|
||||
# Install app dependencies
|
||||
FROM base AS prebuild
|
||||
|
||||
RUN apk update && apk upgrade && \
|
||||
apk add --no-cache git
|
||||
WORKDIR /var/www/stremio-web
|
||||
COPY . .
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
|
||||
# Bundle app source
|
||||
FROM base AS final
|
||||
|
||||
WORKDIR /var/www/stremio-web
|
||||
COPY . .
|
||||
COPY --from=prebuild /var/www/stremio-web/node_modules ./node_modules
|
||||
COPY --from=prebuild /var/www/stremio-web/build ./build
|
||||
|
||||
EXPOSE 8080
|
||||
CMD ["node", "http_server.js"]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Stremio - Freedom to Stream
|
||||
|
||||

|
||||
[](https://stremio.github.io/stremio-web/)
|
||||
[](https://stremio.github.io/stremio-web/development)
|
||||
|
||||
Stremio is a modern media center that's a one-stop solution for your video entertainment. You discover, watch and organize video content from easy to install addons.
|
||||
|
||||
|
|
@ -46,4 +46,4 @@ npm run build
|
|||
|
||||
## License
|
||||
|
||||
Stremio is copyright 2017-2022 Smart code and available under GPLv2 license. See the [LICENSE](/LICENSE.md) file in the project for more information.
|
||||
Stremio is copyright 2017-2023 Smart code and available under GPLv2 license. See the [LICENSE](/LICENSE.md) file in the project for more information.
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 19 KiB |
28
package-lock.json
generated
|
|
@ -12,9 +12,9 @@
|
|||
"@babel/runtime": "7.16.0",
|
||||
"@sentry/browser": "6.13.3",
|
||||
"@stremio/stremio-colors": "5.0.1",
|
||||
"@stremio/stremio-core-web": "0.44.23",
|
||||
"@stremio/stremio-core-web": "0.44.25",
|
||||
"@stremio/stremio-icons": "5.0.0-beta.3",
|
||||
"@stremio/stremio-video": "0.0.24",
|
||||
"@stremio/stremio-video": "0.0.25-rc.2",
|
||||
"a-color-picker": "1.2.1",
|
||||
"bowser": "2.11.0",
|
||||
"buffer": "6.0.3",
|
||||
|
|
@ -2704,9 +2704,9 @@
|
|||
"integrity": "sha512-Dt3PYmy1DZ473QNs99KYXVWQPHtpIl37VUY0+gCEvvuCqE1fRrZIJtZ9KbysUKonvO7WwdQDztgcW0iGoc1dEA=="
|
||||
},
|
||||
"node_modules/@stremio/stremio-core-web": {
|
||||
"version": "0.44.23",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.23.tgz",
|
||||
"integrity": "sha512-I3t0jxZdyNTLIE0w2O1fJIQ562BJc2iN26xL056KiD5FIuGfTAJ/vdHTiPAbcmEXsO3TPxX9fOGsntNEhnO2cA==",
|
||||
"version": "0.44.25",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.25.tgz",
|
||||
"integrity": "sha512-kW/AAh+c1qnfV5xFbfM+VFFvuRXp4M1pQuUj94O6my2kC39zKkZnFMQRDNbzoodoUQY0fusDM1K7rIw3DmlqEg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.16.0"
|
||||
}
|
||||
|
|
@ -2717,9 +2717,9 @@
|
|||
"integrity": "sha512-K+jDsizEgxpBC+b0HExCUg+bnsWPZnx96qUfkdQ9nBDVAN/kzcP24Jq/4KwkiEyqDEcvC5l+xBzOLzkhdLwTMw=="
|
||||
},
|
||||
"node_modules/@stremio/stremio-video": {
|
||||
"version": "0.0.24",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-video/-/stremio-video-0.0.24.tgz",
|
||||
"integrity": "sha512-k9bSyQBbZQnLll62P/enc/gy2eILZrW0xuU/MRxxTm9gtjo+8JLp/77H0a4tQFFHjJ9WPuNj4K6Lo2UnGem3kg==",
|
||||
"version": "0.0.25-rc.2",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-video/-/stremio-video-0.0.25-rc.2.tgz",
|
||||
"integrity": "sha512-OXNimBgPpkin5gX39Bsx/w6M+5ifP71amZEYlvsZz3CDKxGHkQTPLzl4z6RrbVr8wz8nt6eiWIj8BKxa4/u6nQ==",
|
||||
"dependencies": {
|
||||
"buffer": "6.0.3",
|
||||
"color": "4.2.3",
|
||||
|
|
@ -16834,9 +16834,9 @@
|
|||
"integrity": "sha512-Dt3PYmy1DZ473QNs99KYXVWQPHtpIl37VUY0+gCEvvuCqE1fRrZIJtZ9KbysUKonvO7WwdQDztgcW0iGoc1dEA=="
|
||||
},
|
||||
"@stremio/stremio-core-web": {
|
||||
"version": "0.44.23",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.23.tgz",
|
||||
"integrity": "sha512-I3t0jxZdyNTLIE0w2O1fJIQ562BJc2iN26xL056KiD5FIuGfTAJ/vdHTiPAbcmEXsO3TPxX9fOGsntNEhnO2cA==",
|
||||
"version": "0.44.25",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.25.tgz",
|
||||
"integrity": "sha512-kW/AAh+c1qnfV5xFbfM+VFFvuRXp4M1pQuUj94O6my2kC39zKkZnFMQRDNbzoodoUQY0fusDM1K7rIw3DmlqEg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "7.16.0"
|
||||
}
|
||||
|
|
@ -16847,9 +16847,9 @@
|
|||
"integrity": "sha512-K+jDsizEgxpBC+b0HExCUg+bnsWPZnx96qUfkdQ9nBDVAN/kzcP24Jq/4KwkiEyqDEcvC5l+xBzOLzkhdLwTMw=="
|
||||
},
|
||||
"@stremio/stremio-video": {
|
||||
"version": "0.0.24",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-video/-/stremio-video-0.0.24.tgz",
|
||||
"integrity": "sha512-k9bSyQBbZQnLll62P/enc/gy2eILZrW0xuU/MRxxTm9gtjo+8JLp/77H0a4tQFFHjJ9WPuNj4K6Lo2UnGem3kg==",
|
||||
"version": "0.0.25-rc.2",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-video/-/stremio-video-0.0.25-rc.2.tgz",
|
||||
"integrity": "sha512-OXNimBgPpkin5gX39Bsx/w6M+5ifP71amZEYlvsZz3CDKxGHkQTPLzl4z6RrbVr8wz8nt6eiWIj8BKxa4/u6nQ==",
|
||||
"requires": {
|
||||
"buffer": "6.0.3",
|
||||
"color": "4.2.3",
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@
|
|||
"@babel/runtime": "7.16.0",
|
||||
"@sentry/browser": "6.13.3",
|
||||
"@stremio/stremio-colors": "5.0.1",
|
||||
"@stremio/stremio-core-web": "0.44.23",
|
||||
"@stremio/stremio-core-web": "0.44.25",
|
||||
"@stremio/stremio-icons": "5.0.0-beta.3",
|
||||
"@stremio/stremio-video": "0.0.24",
|
||||
"@stremio/stremio-video": "0.0.25-rc.2",
|
||||
"a-color-picker": "1.2.1",
|
||||
"bowser": "2.11.0",
|
||||
"buffer": "6.0.3",
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 976 KiB After Width: | Height: | Size: 1 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 947 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.2 MiB |
|
|
@ -6,7 +6,7 @@ const { useTranslation } = require('react-i18next');
|
|||
const { Router } = require('stremio-router');
|
||||
const { Core, Shell, Chromecast, DragAndDrop, KeyboardShortcuts, ServicesProvider } = require('stremio/services');
|
||||
const { NotFound } = require('stremio/routes');
|
||||
const { ToastProvider, CONSTANTS, withCoreSuspender } = require('stremio/common');
|
||||
const { ToastProvider, TooltipProvider, CONSTANTS, withCoreSuspender } = require('stremio/common');
|
||||
const ServicesToaster = require('./ServicesToaster');
|
||||
const DeepLinkHandler = require('./DeepLinkHandler');
|
||||
const ErrorDialog = require('./ErrorDialog');
|
||||
|
|
@ -159,13 +159,15 @@ const App = () => {
|
|||
<ErrorDialog className={styles['error-container']} />
|
||||
:
|
||||
<ToastProvider className={styles['toasts-container']}>
|
||||
<ServicesToaster />
|
||||
<DeepLinkHandler />
|
||||
<RouterWithProtectedRoutes
|
||||
className={styles['router']}
|
||||
viewsConfig={routerViewsConfig}
|
||||
onPathNotMatch={onPathNotMatch}
|
||||
/>
|
||||
<TooltipProvider className={styles['tooltip-container']}>
|
||||
<ServicesToaster />
|
||||
<DeepLinkHandler />
|
||||
<RouterWithProtectedRoutes
|
||||
className={styles['router']}
|
||||
viewsConfig={routerViewsConfig}
|
||||
onPathNotMatch={onPathNotMatch}
|
||||
/>
|
||||
</TooltipProvider>
|
||||
</ToastProvider>
|
||||
:
|
||||
<div className={styles['loader-container']} />
|
||||
|
|
|
|||
|
|
@ -117,6 +117,19 @@ html {
|
|||
}
|
||||
}
|
||||
|
||||
.tooltip-container {
|
||||
height: 2.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 1.5rem;
|
||||
font-size: 1rem;
|
||||
color: var(--primary-foreground-color);
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--modal-background-color);
|
||||
transition: opacity 0.25s ease-out;
|
||||
}
|
||||
|
||||
.router {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
|
|
|||
|
|
@ -90,9 +90,14 @@ const NavMenuContent = ({ onClick }) => {
|
|||
<Button className={styles['nav-menu-option-container']} title={ t('PRIVACY_POLICY') } href={'https://www.stremio.com/privacy'} target={'_blank'}>
|
||||
<div className={styles['nav-menu-option-label']}>{ t('PRIVACY_POLICY') }</div>
|
||||
</Button>
|
||||
<Button className={styles['nav-menu-option-container']} title={ t('ABOUT_STREMIO') } href={'https://www.stremio.com/'} target={'_blank'}>
|
||||
<div className={styles['nav-menu-option-label']}>{ t('ABOUT_STREMIO') }</div>
|
||||
</Button>
|
||||
{
|
||||
profile.auth !== null ?
|
||||
<Button className={styles['nav-menu-option-container']} title={ t('USER_PANEL') } href={'https://www.stremio.com/acc-settings'} target={'_blank'}>
|
||||
<div className={styles['nav-menu-option-label']}>{ t('USER_PANEL') }</div>
|
||||
</Button>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
63
src/common/Tooltip/Tooltip.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const useTooltip = require('./useTooltip');
|
||||
const styles = require('./styles');
|
||||
|
||||
const createId = () => (Math.random() + 1).toString(36).substring(7);
|
||||
|
||||
const Tooltip = ({ label, position, margin }) => {
|
||||
const tooltip = useTooltip();
|
||||
|
||||
const id = React.useRef(createId());
|
||||
const element = React.useRef(null);
|
||||
|
||||
const onMouseEnter = () => {
|
||||
tooltip.toggle(id.current, true);
|
||||
};
|
||||
|
||||
const onMouseLeave = () => {
|
||||
tooltip.toggle(id.current, false);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (element.current && element.current.parentElement) {
|
||||
const parentElement = element.current.parentElement;
|
||||
tooltip.add({
|
||||
id: id.current,
|
||||
label,
|
||||
position,
|
||||
margin,
|
||||
parent: parentElement,
|
||||
});
|
||||
|
||||
parentElement.addEventListener('mouseenter', onMouseEnter);
|
||||
parentElement.addEventListener('mouseleave', onMouseLeave);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (element.current && element.current.parentElement) {
|
||||
const parentElement = element.current.parentElement;
|
||||
parentElement.removeEventListener('mouseenter', onMouseEnter);
|
||||
parentElement.removeEventListener('mouseleave', onMouseLeave);
|
||||
|
||||
tooltip.remove(id.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={element} className={styles['tooltip']} />
|
||||
);
|
||||
};
|
||||
|
||||
Tooltip.displayName = 'Tooltip';
|
||||
|
||||
Tooltip.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
position: PropTypes.string.isRequired,
|
||||
margin: PropTypes.number,
|
||||
};
|
||||
|
||||
module.exports = Tooltip;
|
||||
8
src/common/Tooltip/TooltipContext.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const { createContext } = require('react');
|
||||
|
||||
const TooltipContext = createContext(null);
|
||||
|
||||
module.exports = TooltipContext;
|
||||
|
||||
106
src/common/Tooltip/TooltipProvider.js
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const { useState, createRef } = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classNames = require('classnames');
|
||||
const TooltipContext = require('./TooltipContext');
|
||||
const styles = require('./styles');
|
||||
|
||||
const TooltipProvider = ({ children, className }) => {
|
||||
const [tooltips, setTooltips] = useState([]);
|
||||
|
||||
const add = ({ id, label, position, margin = 15, parent }) => {
|
||||
const ref = createRef(null);
|
||||
|
||||
const tooltip = {
|
||||
ref,
|
||||
id,
|
||||
label,
|
||||
position,
|
||||
margin: margin,
|
||||
active: false,
|
||||
parent,
|
||||
};
|
||||
|
||||
setTooltips((tooltips) => ([
|
||||
...tooltips,
|
||||
tooltip,
|
||||
]));
|
||||
};
|
||||
|
||||
const remove = (id) => {
|
||||
setTooltips((tooltips) => (
|
||||
tooltips.filter((tooltip) => tooltip.id !== id)
|
||||
));
|
||||
};
|
||||
|
||||
const toggle = (id, state) => {
|
||||
setTooltips((tooltips) => (
|
||||
tooltips.map((tooltip) => {
|
||||
if (tooltip.id === id) {
|
||||
tooltip.active = state;
|
||||
}
|
||||
return tooltip;
|
||||
})
|
||||
));
|
||||
};
|
||||
|
||||
const style = (ref, position, margin, active, parent) => {
|
||||
if (!active) return {};
|
||||
|
||||
const tooltipHeight = ref.current?.offsetHeight ?? 0;
|
||||
const tooltipWidth = ref.current?.offsetWidth ?? 0;
|
||||
const parentBounds = parent.getBoundingClientRect();
|
||||
|
||||
switch (position) {
|
||||
case 'top':
|
||||
return {
|
||||
top: `${parentBounds.top - tooltipHeight - margin}px`,
|
||||
left: `${(parentBounds.left + (parentBounds.width / 2)) - (tooltipWidth / 2)}px`,
|
||||
};
|
||||
case 'bottom':
|
||||
return {
|
||||
top: `${parentBounds.top + parentBounds.height + margin}px`,
|
||||
left: `${(parentBounds.left + (parentBounds.width / 2)) - (tooltipWidth / 2)}px`,
|
||||
};
|
||||
case 'left':
|
||||
return {
|
||||
top: `${parentBounds.top + (parentBounds.height / 2) - (tooltipHeight / 2)}px`,
|
||||
left: `${(parentBounds.left - tooltipWidth - margin)}px`,
|
||||
};
|
||||
case 'right':
|
||||
return {
|
||||
top: `${parentBounds.top + (parentBounds.height / 2) - (tooltipHeight / 2)}px`,
|
||||
left: `${(parentBounds.left + parentBounds.width + margin)}px`,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipContext.Provider value={{ add, remove, toggle }}>
|
||||
{ children }
|
||||
<div className={'tooltips-container'}>
|
||||
{
|
||||
tooltips.map(({ ref, id, label, position, margin, active, parent }) => (
|
||||
<div
|
||||
key={id}
|
||||
ref={ref}
|
||||
className={classNames(className, styles['tooltip-container'], { 'active': active })}
|
||||
style={style(ref, position, margin, active, parent)}
|
||||
>
|
||||
{ label }
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</TooltipContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
TooltipProvider.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
module.exports = TooltipProvider;
|
||||
9
src/common/Tooltip/index.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const TooltipProvider = require('./TooltipProvider');
|
||||
const Tooltip = require('./Tooltip');
|
||||
|
||||
module.exports = {
|
||||
TooltipProvider,
|
||||
Tooltip,
|
||||
};
|
||||
22
src/common/Tooltip/styles.less
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
.tooltip {
|
||||
z-index: -1;
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.tooltip-container {
|
||||
position: fixed;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
|
||||
&:global(.active) {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
10
src/common/Tooltip/useTooltip.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const TooltipContext = require('./TooltipContext');
|
||||
|
||||
const useTooltip = () => {
|
||||
return React.useContext(TooltipContext);
|
||||
};
|
||||
|
||||
module.exports = useTooltip;
|
||||
|
|
@ -23,6 +23,7 @@ const SharePrompt = require('./SharePrompt');
|
|||
const Slider = require('./Slider');
|
||||
const TextInput = require('./TextInput');
|
||||
const { ToastProvider, useToast } = require('./Toast');
|
||||
const { TooltipProvider, Tooltip } = require('./Tooltip');
|
||||
const comparatorWithPriorities = require('./comparatorWithPriorities');
|
||||
const CONSTANTS = require('./CONSTANTS');
|
||||
const { withCoreSuspender, useCoreSuspender } = require('./CoreSuspender');
|
||||
|
|
@ -70,6 +71,8 @@ module.exports = {
|
|||
TextInput,
|
||||
ToastProvider,
|
||||
useToast,
|
||||
TooltipProvider,
|
||||
Tooltip,
|
||||
comparatorWithPriorities,
|
||||
CONSTANTS,
|
||||
withCoreSuspender,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,28 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
queryParams.has('maxAudioChannels') ? parseInt(queryParams.get('maxAudioChannels'), 10) : null
|
||||
];
|
||||
}, [queryParams]);
|
||||
const [player, videoParamsChanged, timeChanged, pausedChanged, ended] = usePlayer(urlParams);
|
||||
const [settings, updateSettings] = useSettings();
|
||||
const streamingServer = useStreamingServer();
|
||||
const routeFocused = useRouteFocused();
|
||||
const toast = useToast();
|
||||
const [, , , toggleFullscreen] = useFullscreen();
|
||||
const [casting, setCasting] = React.useState(() => {
|
||||
return chromecast.active && chromecast.transport.getCastState() === cast.framework.CastState.CONNECTED;
|
||||
});
|
||||
const [immersed, setImmersed] = React.useState(true);
|
||||
const setImmersedDebounced = React.useCallback(debounce(setImmersed, 3000), []);
|
||||
const [optionsMenuOpen, , closeOptionsMenu, toggleOptionsMenu] = useBinaryState(false);
|
||||
const [subtitlesMenuOpen, , closeSubtitlesMenu, toggleSubtitlesMenu] = useBinaryState(false);
|
||||
const [infoMenuOpen, , closeInfoMenu, toggleInfoMenu] = useBinaryState(false);
|
||||
const [speedMenuOpen, , closeSpeedMenu, toggleSpeedMenu] = useBinaryState(false);
|
||||
const [videosMenuOpen, , closeVideosMenu, toggleVideosMenu] = useBinaryState(false);
|
||||
const [nextVideoPopupOpen, openNextVideoPopup, closeNextVideoPopup] = useBinaryState(false);
|
||||
const [statisticsMenuOpen, , closeStatisticsMenu, toggleStatisticsMenu] = useBinaryState(false);
|
||||
const nextVideoPopupDismissed = React.useRef(false);
|
||||
const defaultSubtitlesSelected = React.useRef(false);
|
||||
const defaultAudioTrackSelected = React.useRef(false);
|
||||
const [error, setError] = React.useState(null);
|
||||
const [videoState, setVideoState] = React.useReducer(
|
||||
(videoState, nextVideoState) => ({ ...videoState, ...nextVideoState }),
|
||||
{
|
||||
|
|
@ -66,28 +88,6 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
extraSubtitlesOutlineColor: null
|
||||
}
|
||||
);
|
||||
const [player, timeChanged, pausedChanged, ended] = usePlayer(urlParams, videoState.videoParams);
|
||||
const [settings, updateSettings] = useSettings();
|
||||
const streamingServer = useStreamingServer();
|
||||
const routeFocused = useRouteFocused();
|
||||
const toast = useToast();
|
||||
const [, , , toggleFullscreen] = useFullscreen();
|
||||
const [casting, setCasting] = React.useState(() => {
|
||||
return chromecast.active && chromecast.transport.getCastState() === cast.framework.CastState.CONNECTED;
|
||||
});
|
||||
const [immersed, setImmersed] = React.useState(true);
|
||||
const setImmersedDebounced = React.useCallback(debounce(setImmersed, 3000), []);
|
||||
const [optionsMenuOpen, , closeOptionsMenu, toggleOptionsMenu] = useBinaryState(false);
|
||||
const [subtitlesMenuOpen, , closeSubtitlesMenu, toggleSubtitlesMenu] = useBinaryState(false);
|
||||
const [infoMenuOpen, , closeInfoMenu, toggleInfoMenu] = useBinaryState(false);
|
||||
const [speedMenuOpen, , closeSpeedMenu, toggleSpeedMenu] = useBinaryState(false);
|
||||
const [videosMenuOpen, , closeVideosMenu, toggleVideosMenu] = useBinaryState(false);
|
||||
const [nextVideoPopupOpen, openNextVideoPopup, closeNextVideoPopup] = useBinaryState(false);
|
||||
const [statisticsMenuOpen, , closeStatisticsMenu, toggleStatisticsMenu] = useBinaryState(false);
|
||||
const nextVideoPopupDismissed = React.useRef(false);
|
||||
const defaultSubtitlesSelected = React.useRef(false);
|
||||
const defaultAudioTrackSelected = React.useRef(false);
|
||||
const [error, setError] = React.useState(null);
|
||||
const videoRef = React.useRef(null);
|
||||
const dispatch = React.useCallback((action, options) => {
|
||||
if (videoRef.current !== null) {
|
||||
|
|
@ -353,6 +353,9 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
pausedChanged(videoState.paused);
|
||||
}
|
||||
}, [videoState.paused]);
|
||||
React.useEffect(() => {
|
||||
videoParamsChanged(videoState.videoParams);
|
||||
}, [videoState.videoParams]);
|
||||
React.useEffect(() => {
|
||||
if (!!settings.bingeWatching && player.nextVideo !== null && !nextVideoPopupDismissed.current) {
|
||||
if (videoState.time !== null && videoState.duration !== null && videoState.time < videoState.duration && (videoState.duration - videoState.time) <= settings.nextVideoNotificationDuration) {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ const map = (player) => ({
|
|||
player.metaItem,
|
||||
});
|
||||
|
||||
const usePlayer = (urlParams, videoParams) => {
|
||||
const usePlayer = (urlParams) => {
|
||||
const { core } = useServices();
|
||||
const { decodeStream } = useCoreSuspender();
|
||||
const stream = decodeStream(urlParams.stream);
|
||||
|
|
@ -44,7 +44,6 @@ const usePlayer = (urlParams, videoParams) => {
|
|||
model: 'Player',
|
||||
args: {
|
||||
stream,
|
||||
videoParams,
|
||||
streamRequest: typeof urlParams.streamTransportUrl === 'string' && typeof urlParams.type === 'string' && typeof urlParams.videoId === 'string' ?
|
||||
{
|
||||
base: urlParams.streamTransportUrl,
|
||||
|
|
@ -86,7 +85,16 @@ const usePlayer = (urlParams, videoParams) => {
|
|||
action: 'Unload'
|
||||
};
|
||||
}
|
||||
}, [urlParams, videoParams]);
|
||||
}, [urlParams]);
|
||||
const videoParamsChanged = React.useCallback((videoParams) => {
|
||||
core.transport.dispatch({
|
||||
action: 'Player',
|
||||
args: {
|
||||
action: 'VideoParamsChanged',
|
||||
args: { videoParams }
|
||||
}
|
||||
}, 'player');
|
||||
}, []);
|
||||
const timeChanged = React.useCallback((time, duration, device) => {
|
||||
core.transport.dispatch({
|
||||
action: 'Player',
|
||||
|
|
@ -114,7 +122,7 @@ const usePlayer = (urlParams, videoParams) => {
|
|||
}, 'player');
|
||||
}, []);
|
||||
const player = useModelState({ model: 'player', action, map });
|
||||
return [player, timeChanged, pausedChanged, ended];
|
||||
return [player, videoParamsChanged, timeChanged, pausedChanged, ended];
|
||||
};
|
||||
|
||||
module.exports = usePlayer;
|
||||
|
|
|
|||
|
|
@ -300,6 +300,26 @@ const Settings = () => {
|
|||
<div className={styles['label']}>{ t('PRIVACY_POLICY') }</div>
|
||||
</Button>
|
||||
</div>
|
||||
{
|
||||
profile.auth !== null && profile.auth.user !== null && typeof profile.auth.user.email === 'string' ?
|
||||
<div className={styles['option-container']}>
|
||||
<Button className={classnames(styles['option-input-container'], styles['link-container'])} title={t('RESET_PASSWORD')} target={'_blank'} href={`https://www.strem.io/reset-password/${profile.auth.user.email}`}>
|
||||
<div className={styles['label']}>{ t('RESET_PASSWORD') }</div>
|
||||
</Button>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
profile.auth !== null && profile.auth.user !== null ?
|
||||
<div className={styles['option-container']}>
|
||||
<Button className={classnames(styles['option-input-container'], styles['link-container'])} title={t('SETTINGS_ACC_DELETE')} target={'_blank'} href={'https://stremio.zendesk.com/hc/en-us/articles/360021428911-How-to-delete-my-account'}>
|
||||
<div className={styles['label']}>{ t('SETTINGS_ACC_DELETE') }</div>
|
||||
</Button>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
<div ref={playerSectionRef} className={styles['section-container']}>
|
||||
<div className={styles['section-title']}>{ t('SETTINGS_NAV_PLAYER') }</div>
|
||||
|
|
@ -437,8 +457,8 @@ const Settings = () => {
|
|||
streamingServer.settings.type === 'Ready' ?
|
||||
t('SETTINGS_SERVER_STATUS_ONLINE')
|
||||
:
|
||||
streamingServer.settings.type === 'Error' ?
|
||||
`${t('SETTINGS_SERVER_STATUS_ERROR')}: (${streamingServer.settings.content})`
|
||||
streamingServer.settings.type === 'Err' ?
|
||||
t('SETTINGS_SERVER_STATUS_ERROR')
|
||||
:
|
||||
streamingServer.settings.type
|
||||
}
|
||||
|
|
|
|||