diff --git a/.dockerignore b/.dockerignore
deleted file mode 100644
index c1da1247e..000000000
--- a/.dockerignore
+++ /dev/null
@@ -1,2 +0,0 @@
-screenshots/*
-screenshots*
diff --git a/package-lock.json b/package-lock.json
index 50ef9df6f..12ca92c87 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "stremio",
- "version": "5.0.0-beta.0",
+ "version": "5.0.0-beta.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "stremio",
- "version": "5.0.0-beta.0",
+ "version": "5.0.0-beta.4",
"license": "gpl-2.0",
"dependencies": {
"@babel/runtime": "7.16.0",
@@ -35,8 +35,8 @@
"react-focus-lock": "2.9.1",
"react-i18next": "^12.1.1",
"react-is": "18.2.0",
- "spatial-navigation-polyfill": "git+ssh://git@github.com/Stremio/spatial-navigation.git#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
- "stremio-translations": "git+ssh://git@github.com/Stremio/stremio-translations.git#8e30d9961402a7389f1a6209d9dab5592f32c9d7",
+ "spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
+ "stremio-translations": "github:Stremio/stremio-translations#13c8241ca262541813ce0e2df4ff3e289fbd391b",
"url": "0.11.0",
"use-long-press": "^3.1.5"
},
@@ -13523,7 +13523,7 @@
},
"node_modules/spatial-navigation-polyfill": {
"version": "1.2.0",
- "resolved": "git+https://git@github.com/Stremio/spatial-navigation.git#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
+ "resolved": "git+ssh://git@github.com/Stremio/spatial-navigation.git#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
"integrity": "sha512-zREQWdiN2VSugD90lq0uBsSBjT/YtytCijN83V6LEJTRmAe/fMYUtjUtYr7UvBrSPxfnSM54BNq9r2kKFTJvlQ==",
"license": "MIT"
},
@@ -13600,9 +13600,8 @@
}
},
"node_modules/stremio-translations": {
- "version": "1.44.3",
- "resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#8e30d9961402a7389f1a6209d9dab5592f32c9d7",
- "integrity": "sha512-E8UqbRzVQzDFQvKLTztrtA2V8xL8JeYfi+hI9IWx9kwIMQ34no2hxuualXH7A4U0s8xlM6BQW4bcQlVKwflhEg==",
+ "version": "1.44.4",
+ "resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#13c8241ca262541813ce0e2df4ff3e289fbd391b",
"license": "MIT"
},
"node_modules/string_decoder": {
@@ -25940,9 +25939,9 @@
"dev": true
},
"spatial-navigation-polyfill": {
- "version": "git+https://git@github.com/Stremio/spatial-navigation.git#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
+ "version": "git+ssh://git@github.com/Stremio/spatial-navigation.git#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
"integrity": "sha512-zREQWdiN2VSugD90lq0uBsSBjT/YtytCijN83V6LEJTRmAe/fMYUtjUtYr7UvBrSPxfnSM54BNq9r2kKFTJvlQ==",
- "from": "spatial-navigation-polyfill@git+ssh://git@github.com/Stremio/spatial-navigation.git#64871b1422466f5f45d24ebc8bbd315b2ebab6a6"
+ "from": "spatial-navigation-polyfill@github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6"
},
"spdy": {
"version": "4.0.2",
@@ -26007,9 +26006,8 @@
"dev": true
},
"stremio-translations": {
- "version": "git+ssh://git@github.com/Stremio/stremio-translations.git#8e30d9961402a7389f1a6209d9dab5592f32c9d7",
- "integrity": "sha512-E8UqbRzVQzDFQvKLTztrtA2V8xL8JeYfi+hI9IWx9kwIMQ34no2hxuualXH7A4U0s8xlM6BQW4bcQlVKwflhEg==",
- "from": "stremio-translations@git+ssh://git@github.com/Stremio/stremio-translations.git#8e30d9961402a7389f1a6209d9dab5592f32c9d7"
+ "version": "git+ssh://git@github.com/Stremio/stremio-translations.git#13c8241ca262541813ce0e2df4ff3e289fbd391b",
+ "from": "stremio-translations@github:Stremio/stremio-translations#13c8241ca262541813ce0e2df4ff3e289fbd391b"
},
"string_decoder": {
"version": "1.1.1",
diff --git a/package.json b/package.json
index 2ab99c401..d4b310ada 100755
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "stremio",
"displayName": "Stremio",
- "version": "5.0.0-beta.0",
+ "version": "5.0.0-beta.4",
"author": "Smart Code OOD",
"private": true,
"license": "gpl-2.0",
@@ -38,8 +38,8 @@
"react-focus-lock": "2.9.1",
"react-i18next": "^12.1.1",
"react-is": "18.2.0",
- "spatial-navigation-polyfill": "git+ssh://git@github.com/Stremio/spatial-navigation.git#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
- "stremio-translations": "git+ssh://git@github.com/Stremio/stremio-translations.git#8e30d9961402a7389f1a6209d9dab5592f32c9d7",
+ "spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
+ "stremio-translations": "github:Stremio/stremio-translations#13c8241ca262541813ce0e2df4ff3e289fbd391b",
"url": "0.11.0",
"use-long-press": "^3.1.5"
},
diff --git a/src/App/App.js b/src/App/App.js
index de443d9d4..d6e92fbbb 100644
--- a/src/App/App.js
+++ b/src/App/App.js
@@ -146,8 +146,10 @@ const App = () => {
.catch((e) => console.error(e));
}
return () => {
- window.removeEventListener('focus', onWindowFocus);
- services.core.transport.off('CoreEvent', onCoreEvent);
+ if (services.core.active) {
+ window.removeEventListener('focus', onWindowFocus);
+ services.core.transport.off('CoreEvent', onCoreEvent);
+ }
};
}, [initialized]);
return (
diff --git a/src/App/ErrorDialog/ErrorDialog.js b/src/App/ErrorDialog/ErrorDialog.js
index eb0335d9f..8429bb340 100644
--- a/src/App/ErrorDialog/ErrorDialog.js
+++ b/src/App/ErrorDialog/ErrorDialog.js
@@ -1,12 +1,15 @@
// Copyright (C) 2017-2023 Smart code 203358507
const React = require('react');
+const { useTranslation } = require('react-i18next');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { Button, Image } = require('stremio/common');
const styles = require('./styles');
const ErrorDialog = ({ className }) => {
+ const { t } = useTranslation();
+
const [dataCleared, setDataCleared] = React.useState(false);
const reload = React.useCallback(() => {
window.location.reload();
@@ -22,13 +25,19 @@ const ErrorDialog = ({ className }) => {
src={require('/images/empty.png')}
alt={' '}
/>
-
Something went wrong!
+
+ { t('GENERIC_ERROR_MESSAGE') }
+
-
diff --git a/src/App/ErrorDialog/styles.less b/src/App/ErrorDialog/styles.less
index 01f6634b2..c3f7813d4 100644
--- a/src/App/ErrorDialog/styles.less
+++ b/src/App/ErrorDialog/styles.less
@@ -7,12 +7,12 @@
flex-direction: column;
align-items: center;
justify-content: center;
+ gap: 1rem;
.error-image {
flex: none;
width: 12rem;
height: 12rem;
- margin-bottom: 1rem;
object-fit: contain;
object-position: center;
opacity: 0.9;
@@ -24,7 +24,7 @@
font-size: 2rem;
max-height: 3.6em;
text-align: center;
- color: @color-surface-light5-90;
+ color: var(--primary-foreground-color);
}
.buttons-container {
@@ -36,6 +36,8 @@
flex-wrap: wrap;
align-items: center;
justify-content: center;
+ gap: 1.5rem;
+ margin-top: 1rem;
.button-container {
flex-grow: 0;
@@ -45,18 +47,23 @@
flex-direction: row;
align-items: center;
justify-content: center;
- margin: 2rem 1rem 0;
- padding: 0 1rem;
+ padding: 0 2.5rem;
min-width: 8rem;
- height: 3rem;
- background-color: @color-accent3;
+ height: 3.5rem;
+ border-radius: 3.5rem;
+ background-color: var(--overlay-color);
&:hover {
- background-color: @color-accent3-light1;
+ outline: var(--focus-outline-size) solid var(--primary-foreground-color);
+ background-color: transparent;
+ }
+
+ &:active {
+ outline: none;
}
&:global(.disabled) {
- background-color: @color-surface-dark5;
+ opacity: 0.3;
}
.label {
@@ -67,7 +74,7 @@
font-size: 1.1rem;
font-weight: 500;
text-align: center;
- color: @color-surface-light5-90;
+ color: var(--primary-foreground-color);
}
}
}
diff --git a/src/App/styles.less b/src/App/styles.less
index 64177ad65..95566c9a8 100644
--- a/src/App/styles.less
+++ b/src/App/styles.less
@@ -144,7 +144,6 @@ html {
.loader-container, .error-container {
width: 100%;
height: 100%;
- background-color: @color-background-dark2;
}
}
}
diff --git a/src/common/CONSTANTS.js b/src/common/CONSTANTS.js
index 4cc5a474f..50bc770c9 100644
--- a/src/common/CONSTANTS.js
+++ b/src/common/CONSTANTS.js
@@ -40,6 +40,49 @@ const ICON_FOR_TYPE = new Map([
['other', 'movies'],
]);
+const EXTERNAL_PLAYERS = [
+ {
+ label: 'EXTERNAL_PLAYER_DISABLED',
+ value: null,
+ platforms: ['ios', 'android', 'windows', 'linux', 'macos'],
+ },
+ {
+ label: 'EXTERNAL_PLAYER_ALLOW_CHOOSING',
+ value: 'choose',
+ platforms: ['android'],
+ },
+ {
+ label: 'VLC',
+ value: 'vlc',
+ platforms: ['ios', 'android'],
+ },
+ {
+ label: 'MPV',
+ value: 'mpv',
+ platforms: ['macos'],
+ },
+ {
+ label: 'IINA',
+ value: 'iina',
+ platforms: ['macos'],
+ },
+ {
+ label: 'MX Player',
+ value: 'mxplayer',
+ platforms: ['android'],
+ },
+ {
+ label: 'Just Player',
+ value: 'justplayer',
+ platforms: ['android'],
+ },
+ {
+ label: 'Outplayer',
+ value: 'outplayer',
+ platforms: ['ios'],
+ },
+];
+
module.exports = {
CHROMECAST_RECEIVER_APP_ID,
SUBTITLES_SIZES,
@@ -55,5 +98,6 @@ module.exports = {
SHARE_LINK_CATEGORY,
WRITERS_LINK_CATEGORY,
TYPE_PRIORITIES,
- ICON_FOR_TYPE
+ ICON_FOR_TYPE,
+ EXTERNAL_PLAYERS,
};
diff --git a/src/common/EventModal/EventModal.js b/src/common/EventModal/EventModal.js
new file mode 100644
index 000000000..dcd9ca626
--- /dev/null
+++ b/src/common/EventModal/EventModal.js
@@ -0,0 +1,90 @@
+// Copyright (C) 2017-2023 Smart code 203358507
+
+const React = require('react');
+const { useTranslation } = require('react-i18next');
+const Button = require('stremio/common/Button');
+const ModalDialog = require('stremio/common/ModalDialog');
+const useEvents = require('./useEvents');
+const styles = require('./styles');
+const { default: Icon } = require('@stremio/stremio-icons/react');
+
+const EventModal = () => {
+ const { t } = useTranslation();
+
+ const { events, pullEvents, dismissEvent } = useEvents();
+
+ const modal = React.useMemo(() => {
+ return events?.modal?.type === 'Ready' ?
+ events.modal.content
+ :
+ null;
+ }, [events]);
+
+ const onClose = React.useCallback(() => {
+ modal?.id && dismissEvent(modal.id);
+ }, [modal]);
+
+ React.useEffect(() => {
+ pullEvents();
+ }, []);
+
+ return (
+ modal !== null ?
+
+ {
+ modal.imageUrl ?
+
+ :
+ null
+ }
+
+
+ {
+ modal.title ?
+
{modal.title}
+ :
+ null
+ }
+ {
+ modal.message ?
+
{modal.message}
+ :
+ null
+ }
+
+ {
+ modal?.addon?.name ?
+
+
+
+ { modal.addon.name }
+
+
+ :
+ null
+ }
+ {
+ modal?.addon?.manifestUrl ?
+
+
+ { t('INSTALL_ADDON') }
+
+
+ :
+ modal.externalUrl ?
+
+
+ { t('LEARN_MORE') }
+
+
+ :
+ null
+ }
+
+
+ :
+ null
+ );
+};
+
+module.exports = EventModal;
diff --git a/src/common/EventModal/index.js b/src/common/EventModal/index.js
new file mode 100644
index 000000000..ac88c0210
--- /dev/null
+++ b/src/common/EventModal/index.js
@@ -0,0 +1,5 @@
+// Copyright (C) 2017-2023 Smart code 203358507
+
+const EventModal = require('./EventModal');
+
+module.exports = EventModal;
diff --git a/src/common/EventModal/styles.less b/src/common/EventModal/styles.less
new file mode 100644
index 000000000..0fa9919b8
--- /dev/null
+++ b/src/common/EventModal/styles.less
@@ -0,0 +1,119 @@
+// Copyright (C) 2017-2023 Smart code 203358507
+
+@import (reference) '~stremio/common/screen-sizes.less';
+
+:import('~stremio/common/ModalDialog/styles.less') {
+ modal-dialog-content: modal-dialog-content;
+ modal-dialog-container: modal-dialog-container;
+}
+
+.event-modal {
+ backdrop-filter: blur(10px);
+
+ .modal-dialog-container {
+ overflow: visible;
+ max-width: 45rem;
+
+ .modal-dialog-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ overflow: visible;
+
+ .image {
+ width: 100%;
+ height: 100%;
+ margin-top: -10rem;
+ }
+
+ .info-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 2.5rem;
+ padding: 1rem 4rem;
+ margin-top: -7rem;
+
+ .title-container {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+
+ .title {
+ color: var(--primary-foreground-color);
+ font-size: 1.325rem;
+ text-align: center;
+ padding: 0 6rem;
+ }
+
+ .label {
+ color: var(--primary-foreground-color);
+ font-size: 1rem;
+ text-align: center;
+ opacity: 0.5;
+ }
+ }
+
+ .addon-container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ gap: 0.5rem;
+
+ .icon {
+ height: 2rem;
+ width: 2rem;
+ color: var(--primary-accent-color);
+ }
+
+ .name {
+ color: var(--primary-foreground-color);
+ }
+ }
+
+ .action-button {
+ background-color: var(--primary-foreground-color);
+ border: 2px solid var(--primary-foreground-color);
+ padding: 0.8rem 2rem;
+ border-radius: 2rem;
+
+ .button-label {
+ color: var(--primary-accent-color);
+ font-size: 1rem;
+ font-weight: 700;
+ }
+
+ &:hover {
+ background-color: transparent;
+ }
+ }
+ }
+ }
+ }
+
+ @media only screen and (max-width: @minimum) {
+ .modal-dialog-container {
+ .modal-dialog-content {
+ .image {
+ height: 125%;
+ width: 125%;
+ }
+
+ .info-container {
+ .title-container {
+ .title {
+ padding: 0rem;
+ font-size: 1rem;
+ }
+
+ .label {
+ font-size: 0.875rem;
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/common/EventModal/useEvents.js b/src/common/EventModal/useEvents.js
new file mode 100644
index 000000000..f5efaa79e
--- /dev/null
+++ b/src/common/EventModal/useEvents.js
@@ -0,0 +1,36 @@
+// Copyright (C) 2017-2023 Smart code 203358507
+
+const useModelState = require('stremio/common/useModelState');
+const { useServices } = require('stremio/services');
+
+const map = (ctx) => ({
+ ...ctx.events,
+});
+
+const useEvents = () => {
+ const { core } = useServices();
+
+ const pullEvents = () => {
+ core.transport.dispatch({
+ action: 'Ctx',
+ args: {
+ action: 'GetEvents',
+ },
+ });
+ };
+
+ const dismissEvent = (id) => {
+ core.transport.dispatch({
+ action: 'Ctx',
+ args: {
+ action: 'DismissEvent',
+ args: id,
+ },
+ });
+ };
+
+ const events = useModelState({ model: 'ctx', map });
+ return { events, pullEvents, dismissEvent };
+};
+
+module.exports = useEvents;
diff --git a/src/common/Multiselect/Multiselect.js b/src/common/Multiselect/Multiselect.js
index a308393e3..73638eaa0 100644
--- a/src/common/Multiselect/Multiselect.js
+++ b/src/common/Multiselect/Multiselect.js
@@ -14,13 +14,17 @@ const Multiselect = ({ className, mode, direction, title, disabled, dataset, ren
const [menuOpen, , closeMenu, toggleMenu] = useBinaryState(false);
const options = React.useMemo(() => {
return Array.isArray(props.options) ?
- props.options
+ props.options.filter((option) => {
+ return option && (typeof option.value === 'string' || option.value === null);
+ })
:
[];
}, [props.options]);
const selected = React.useMemo(() => {
return Array.isArray(props.selected) ?
- props.selected
+ props.selected.filter((value) => {
+ return typeof value === 'string' || value === null;
+ })
:
[];
}, [props.selected]);
diff --git a/src/common/NavBar/HorizontalNavBar/NavMenu/NavMenu.js b/src/common/NavBar/HorizontalNavBar/NavMenu/NavMenu.js
index 7af959d75..8c4477b42 100644
--- a/src/common/NavBar/HorizontalNavBar/NavMenu/NavMenu.js
+++ b/src/common/NavBar/HorizontalNavBar/NavMenu/NavMenu.js
@@ -7,6 +7,7 @@ const { useRouteFocused } = require('stremio-router');
const Popup = require('stremio/common/Popup');
const useBinaryState = require('stremio/common/useBinaryState');
const NavMenuContent = require('./NavMenuContent');
+const styles = require('./styles.less');
const NavMenu = (props) => {
const routeFocused = useRouteFocused();
@@ -42,6 +43,7 @@ const NavMenu = (props) => {
onCloseRequest={closeMenu}
renderLabel={renderLabel}
renderMenu={renderMenu}
+ className={styles['nav-menu-popup-label']}
/>
);
};
diff --git a/src/common/NavBar/HorizontalNavBar/NavMenu/styles.less b/src/common/NavBar/HorizontalNavBar/NavMenu/styles.less
index 8b58f3796..aa9d175a0 100644
--- a/src/common/NavBar/HorizontalNavBar/NavMenu/styles.less
+++ b/src/common/NavBar/HorizontalNavBar/NavMenu/styles.less
@@ -3,6 +3,15 @@
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
@import (reference) '~stremio/common/screen-sizes.less';
+:import('~stremio/common/Popup/styles.less') {
+ popup-menu-container: menu-container;
+}
+
+.nav-menu-popup-label {
+ .popup-menu-container {
+ margin-top: 1rem;
+ }
+}
.nav-menu-container {
width: 22rem;
max-height: calc(100vh - var(--horizontal-nav-bar-size));
diff --git a/src/common/NavBar/VerticalNavBar/NavTabButton/styles.less b/src/common/NavBar/VerticalNavBar/NavTabButton/styles.less
index 95ed91c35..01fd993e3 100644
--- a/src/common/NavBar/VerticalNavBar/NavTabButton/styles.less
+++ b/src/common/NavBar/VerticalNavBar/NavTabButton/styles.less
@@ -40,14 +40,19 @@
.label {
flex: none;
+ position: relative;
+ max-width: 100%;
max-height: 2.4em;
- padding: 0 0.2rem;
+ padding: 0 0.5rem;
font-size: 0.8rem;
font-weight: 500;
letter-spacing: 0.01rem;
text-align: center;
+ white-space: nowrap;
+ text-overflow: ellipsis;
color: var(--primary-foreground-color);
opacity: 0;
+ overflow: hidden;
}
}
diff --git a/src/common/Popup/styles.less b/src/common/Popup/styles.less
index e81de9033..e74c2cfa0 100644
--- a/src/common/Popup/styles.less
+++ b/src/common/Popup/styles.less
@@ -17,8 +17,7 @@
visibility: hidden;
border-radius: var(--border-radius);
background-color: var(--modal-background-color);
- box-shadow: 0 1.35rem 2.7rem @color-background-dark5-40,
- 0 1.1rem 0.85rem @color-background-dark5-20;
+ box-shadow: var(--outer-glow);
cursor: auto;
&.menu-direction-top-left {
diff --git a/src/common/externalPlayerOptions.js b/src/common/externalPlayerOptions.js
deleted file mode 100644
index 36f530a0b..000000000
--- a/src/common/externalPlayerOptions.js
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) 2017-2023 Smart code 203358507
-
-const platform = require('./platform');
-
-let options = [{ label: 'EXTERNAL_PLAYER_DISABLED', value: 'internal' }];
-
-if (platform.name === 'ios') {
- options = options.concat([
- { label: 'VLC', value: 'vlc' },
- { label: 'Outplayer', value: 'outplayer' }
- ]);
-} else if (platform.name === 'android') {
- options = options.concat([
- { label: 'EXTERNAL_PLAYER_ALLOW_CHOOSING', value: 'choose' },
- { label: 'VLC', value: 'vlc' },
- { label: 'Just Player', value: 'justplayer' },
- { label: 'MX Player', value: 'mxplayer' }
- ]);
-} else if (['windows', 'macos', 'linux'].includes(platform.name)) {
- options = options.concat([
- { label: 'VLC', value: 'vlc' }
- ]);
-} else {
- options = options.concat([
- { label: 'M3U Playlist', value: 'm3u' }
- ]);
-}
-
-module.exports = options;
diff --git a/src/common/index.js b/src/common/index.js
index 8d399d9a0..5d8d65cbe 100644
--- a/src/common/index.js
+++ b/src/common/index.js
@@ -44,7 +44,7 @@ const useProfile = require('./useProfile');
const useStreamingServer = require('./useStreamingServer');
const useTorrent = require('./useTorrent');
const platform = require('./platform');
-const externalPlayerOptions = require('./externalPlayerOptions');
+const EventModal = require('./EventModal');
module.exports = {
AddonDetailsModal,
@@ -95,5 +95,5 @@ module.exports = {
useStreamingServer,
useTorrent,
platform,
- externalPlayerOptions,
+ EventModal,
};
diff --git a/src/routes/Board/Board.js b/src/routes/Board/Board.js
index 6e92beea5..4c0702eda 100644
--- a/src/routes/Board/Board.js
+++ b/src/routes/Board/Board.js
@@ -4,7 +4,7 @@ const React = require('react');
const classnames = require('classnames');
const debounce = require('lodash.debounce');
const { useTranslation } = require('react-i18next');
-const { MainNavBars, MetaRow, ContinueWatchingItem, MetaItem, StreamingServerWarning, useStreamingServer, withCoreSuspender, getVisibleChildrenRange } = require('stremio/common');
+const { MainNavBars, MetaRow, ContinueWatchingItem, MetaItem, StreamingServerWarning, useStreamingServer, withCoreSuspender, getVisibleChildrenRange, EventModal } = require('stremio/common');
const useBoard = require('./useBoard');
const useContinueWatchingPreview = require('./useContinueWatchingPreview');
const styles = require('./styles');
@@ -38,6 +38,7 @@ const Board = () => {
}, [board.catalogs, onVisibleRangeChange]);
return (
+
{
diff --git a/src/routes/MetaDetails/StreamsList/Stream/Stream.js b/src/routes/MetaDetails/StreamsList/Stream/Stream.js
index be46486a4..6587c72d0 100644
--- a/src/routes/MetaDetails/StreamsList/Stream/Stream.js
+++ b/src/routes/MetaDetails/StreamsList/Stream/Stream.js
@@ -4,31 +4,49 @@ const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { default: Icon } = require('@stremio/stremio-icons/react');
-const { Button, Image, useProfile, platform, useStreamingServer, useToast } = require('stremio/common');
+const { Button, Image, useProfile, platform, useToast } = require('stremio/common');
const { useServices } = require('stremio/services');
const StreamPlaceholder = require('./StreamPlaceholder');
const styles = require('./styles');
const Stream = ({ className, videoId, videoReleased, addonName, name, description, thumbnail, progress, deepLinks, ...props }) => {
const profile = useProfile();
- const streamingServer = useStreamingServer();
- const { core } = useServices();
const toast = useToast();
+ const { core } = useServices();
+
const href = React.useMemo(() => {
- const haveStreamingServer = streamingServer.settings !== null && streamingServer.settings.type === 'Ready';
return deepLinks ?
- profile.settings.playerType && profile.settings.playerType !== 'internal' ?
- platform.isMobile() || !haveStreamingServer ?
- (deepLinks.externalPlayer.openPlayer || {})[platform.name] || deepLinks.externalPlayer.href
- : null
- :
- typeof deepLinks.player === 'string' ?
- deepLinks.player
+ deepLinks.externalPlayer ?
+ deepLinks.externalPlayer.web ?
+ deepLinks.externalPlayer.web
:
- null
+ deepLinks.externalPlayer.openPlayer ?
+ deepLinks.externalPlayer.openPlayer[platform.name] ?
+ deepLinks.externalPlayer.openPlayer[platform.name]
+ :
+ deepLinks.externalPlayer.playlist
+ :
+ deepLinks.player
+ :
+ deepLinks.player
:
null;
- }, [deepLinks, profile, streamingServer]);
+ }, [deepLinks]);
+
+ const download = React.useMemo(() => {
+ return href === deepLinks?.externalPlayer?.playlist ?
+ deepLinks.externalPlayer.fileName
+ :
+ null;
+ }, [href, deepLinks]);
+
+ const target = React.useMemo(() => {
+ return href === deepLinks?.externalPlayer?.web ?
+ '_blank'
+ :
+ null;
+ }, [href, deepLinks]);
+
const markVideoAsWatched = React.useCallback(() => {
if (typeof videoId === 'string') {
core.transport.dispatch({
@@ -40,22 +58,9 @@ const Stream = ({ className, videoId, videoReleased, addonName, name, descriptio
});
}
}, [videoId, videoReleased]);
+
const onClick = React.useCallback((event) => {
- if (href === null) {
- // link does not lead to the player, it is expected to
- // open with local video player through the streaming server
- markVideoAsWatched();
- core.transport.dispatch({
- action: 'StreamingServer',
- args: {
- action: 'PlayOnDevice',
- args: {
- device: 'vlc',
- source: deepLinks.externalPlayer.streaming
- }
- }
- });
- } else if (profile.settings.playerType && profile.settings.playerType !== 'internal') {
+ if (profile.settings.playerType !== null) {
markVideoAsWatched();
toast.show({
type: 'success',
@@ -63,20 +68,18 @@ const Stream = ({ className, videoId, videoReleased, addonName, name, descriptio
timeout: 4000
});
}
+
if (typeof props.onClick === 'function') {
props.onClick(event);
}
- }, [href, deepLinks, props.onClick, profile, toast, markVideoAsWatched]);
- const forceDownload = React.useMemo(() => {
- // we only do this in one case to force the download
- // of a M3U playlist generated in the browser
- return href === deepLinks.externalPlayer.href ? deepLinks.externalPlayer.fileName : false;
- }, [href]);
+ }, [props.onClick, profile.settings, markVideoAsWatched]);
+
const renderThumbnailFallback = React.useCallback(() => (
), []);
+
return (
-
+
{
typeof thumbnail === 'string' && thumbnail.length > 0 ?
@@ -123,52 +126,17 @@ Stream.propTypes = {
deepLinks: PropTypes.shape({
player: PropTypes.string,
externalPlayer: PropTypes.shape({
- href: PropTypes.string,
- fileName: PropTypes.string,
+ download: PropTypes.string,
streaming: PropTypes.string,
+ playlist: PropTypes.string,
+ fileName: PropTypes.string,
+ web: PropTypes.string,
openPlayer: PropTypes.shape({
- choose: PropTypes.shape({
- ios: PropTypes.string,
- android: PropTypes.string,
- windows: PropTypes.string,
- macos: PropTypes.string,
- linux: PropTypes.string
- }),
- vlc: PropTypes.shape({
- ios: PropTypes.string,
- android: PropTypes.string,
- windows: PropTypes.string,
- macos: PropTypes.string,
- linux: PropTypes.string
- }),
- outplayer: PropTypes.shape({
- ios: PropTypes.string,
- android: PropTypes.string,
- windows: PropTypes.string,
- macos: PropTypes.string,
- linux: PropTypes.string
- }),
- infuse: PropTypes.shape({
- ios: PropTypes.string,
- android: PropTypes.string,
- windows: PropTypes.string,
- macos: PropTypes.string,
- linux: PropTypes.string
- }),
- justplayer: PropTypes.shape({
- ios: PropTypes.string,
- android: PropTypes.string,
- windows: PropTypes.string,
- macos: PropTypes.string,
- linux: PropTypes.string
- }),
- mxplayer: PropTypes.shape({
- ios: PropTypes.string,
- android: PropTypes.string,
- windows: PropTypes.string,
- macos: PropTypes.string,
- linux: PropTypes.string
- }),
+ ios: PropTypes.string,
+ android: PropTypes.string,
+ windows: PropTypes.string,
+ macos: PropTypes.string,
+ linux: PropTypes.string,
})
})
}),
diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js
index ce72b7d8b..9a40b4cd7 100644
--- a/src/routes/Player/Player.js
+++ b/src/routes/Player/Player.js
@@ -260,7 +260,7 @@ const Player = ({ urlParams, queryParams }) => {
setError(null);
if (player.selected === null) {
dispatch({ type: 'command', commandName: 'unload' });
- } else if (streamingServer.baseUrl !== null && (player.selected.metaRequest === null || (player.metaItem !== null && player.metaItem.type !== 'Loading'))) {
+ } else if ((player.selected.metaRequest === null || (player.metaItem !== null && player.metaItem.type !== 'Loading'))) {
dispatch({
type: 'command',
commandName: 'load',
@@ -647,8 +647,14 @@ const Player = ({ urlParams, queryParams }) => {
null
}
{
- player.selected !== null ?
-
+ player.selected?.stream?.deepLinks?.externalPlayer?.playlist !== null ?
+
{t('PLAYER_OPEN_IN_EXTERNAL')}
diff --git a/src/routes/Settings/useProfileSettingsInputs.js b/src/routes/Settings/useProfileSettingsInputs.js
index 64b1beb17..939e5e7cf 100644
--- a/src/routes/Settings/useProfileSettingsInputs.js
+++ b/src/routes/Settings/useProfileSettingsInputs.js
@@ -3,7 +3,7 @@
const React = require('react');
const { useTranslation } = require('react-i18next');
const { useServices } = require('stremio/services');
-const { CONSTANTS, interfaceLanguages, languageNames, externalPlayerOptions } = require('stremio/common');
+const { CONSTANTS, interfaceLanguages, languageNames, platform } = require('stremio/common');
const useProfileSettingsInputs = (profile) => {
const { t } = useTranslation();
@@ -211,11 +211,17 @@ const useProfileSettingsInputs = (profile) => {
}
}), [profile.settings]);
const playInExternalPlayerSelect = React.useMemo(() => ({
- options: externalPlayerOptions.map((opt) => {
- opt.label = t(opt.label);
- return opt;
- }),
- selected: [`${profile.settings.playerType || 'internal'}`],
+ options: CONSTANTS.EXTERNAL_PLAYERS
+ .filter(({ platforms }) => platforms.includes(platform.name))
+ .map(({ label, value }) => ({
+ value,
+ label: t(label),
+ })),
+ selected: [profile.settings.playerType],
+ renderLabelText: () => {
+ const selectedOption = CONSTANTS.EXTERNAL_PLAYERS.find(({ value }) => value === profile.settings.playerType);
+ return selectedOption ? t(selectedOption.label, { defaultValue: selectedOption.label }) : profile.settings.playerType;
+ },
onSelect: (event) => {
core.transport.dispatch({
action: 'Ctx',