mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-20 14:52:13 +00:00
Merge branch 'development' into add-install-link-to-warning
This commit is contained in:
commit
136e0ab024
10 changed files with 202 additions and 47 deletions
26
package-lock.json
generated
26
package-lock.json
generated
|
|
@ -12,7 +12,7 @@
|
|||
"@babel/runtime": "7.16.0",
|
||||
"@sentry/browser": "6.13.3",
|
||||
"@stremio/stremio-colors": "5.0.1",
|
||||
"@stremio/stremio-core-web": "0.44.14",
|
||||
"@stremio/stremio-core-web": "0.44.17",
|
||||
"@stremio/stremio-icons": "4.0.0",
|
||||
"@stremio/stremio-video": "0.0.24",
|
||||
"a-color-picker": "1.2.1",
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
"react-i18next": "^12.1.1",
|
||||
"react-is": "18.2.0",
|
||||
"spatial-navigation-polyfill": "git+https://git@github.com/Stremio/spatial-navigation.git#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
|
||||
"stremio-translations": "git+https://git@github.com/Stremio/stremio-translations.git#f55fe0d7dfe33bd6be461b4430cba2319afd1d8e",
|
||||
"stremio-translations": "git+https://git@github.com/Stremio/stremio-translations.git#eb2d0dbcf959d99fad448d4b41aca11c0ae29449",
|
||||
"url": "0.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -2702,9 +2702,9 @@
|
|||
"integrity": "sha512-Dt3PYmy1DZ473QNs99KYXVWQPHtpIl37VUY0+gCEvvuCqE1fRrZIJtZ9KbysUKonvO7WwdQDztgcW0iGoc1dEA=="
|
||||
},
|
||||
"node_modules/@stremio/stremio-core-web": {
|
||||
"version": "0.44.14",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.14.tgz",
|
||||
"integrity": "sha512-v2m6EoR49Qohbrv7PxwX1yS1XjNxCkTiEe4KOEAHsCydvjtryJOpBZCXFWDstSx2o57uYwfJq1DNJRvEUVpeCA==",
|
||||
"version": "0.44.17",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.17.tgz",
|
||||
"integrity": "sha512-F5mnx2zWTV5w1h9VX89qk/Wbv6DbKKM8KCIW2i/ZqlAqYGIjKHyjh1p7/g2yxGBV4dkm7DI1nQHwtao7lyCgww==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.16.0"
|
||||
}
|
||||
|
|
@ -12848,8 +12848,8 @@
|
|||
},
|
||||
"node_modules/stremio-translations": {
|
||||
"version": "1.43.16",
|
||||
"resolved": "git+https://git@github.com/Stremio/stremio-translations.git#f55fe0d7dfe33bd6be461b4430cba2319afd1d8e",
|
||||
"integrity": "sha512-yCFQSjZ1R/xjbHiPb7UZnWUFNu5YQq6vsdzSQZ1DGizwGnERxUvRwyzh8a1tlNOEyjZoZyI4cqdbv/LHRGCqbA==",
|
||||
"resolved": "git+https://git@github.com/Stremio/stremio-translations.git#eb2d0dbcf959d99fad448d4b41aca11c0ae29449",
|
||||
"integrity": "sha512-SV3Foxdr1nYJZGhq0TX4rZaZiXtOTMX59dAUcOMT/EwrtDqCdg6fSbNuNAlO/adlaSyyFcpEZKC+U4kRRFANkA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
|
|
@ -16795,9 +16795,9 @@
|
|||
"integrity": "sha512-Dt3PYmy1DZ473QNs99KYXVWQPHtpIl37VUY0+gCEvvuCqE1fRrZIJtZ9KbysUKonvO7WwdQDztgcW0iGoc1dEA=="
|
||||
},
|
||||
"@stremio/stremio-core-web": {
|
||||
"version": "0.44.14",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.14.tgz",
|
||||
"integrity": "sha512-v2m6EoR49Qohbrv7PxwX1yS1XjNxCkTiEe4KOEAHsCydvjtryJOpBZCXFWDstSx2o57uYwfJq1DNJRvEUVpeCA==",
|
||||
"version": "0.44.17",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.17.tgz",
|
||||
"integrity": "sha512-F5mnx2zWTV5w1h9VX89qk/Wbv6DbKKM8KCIW2i/ZqlAqYGIjKHyjh1p7/g2yxGBV4dkm7DI1nQHwtao7lyCgww==",
|
||||
"requires": {
|
||||
"@babel/runtime": "7.16.0"
|
||||
}
|
||||
|
|
@ -24508,9 +24508,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"stremio-translations": {
|
||||
"version": "git+https://git@github.com/Stremio/stremio-translations.git#f55fe0d7dfe33bd6be461b4430cba2319afd1d8e",
|
||||
"integrity": "sha512-yCFQSjZ1R/xjbHiPb7UZnWUFNu5YQq6vsdzSQZ1DGizwGnERxUvRwyzh8a1tlNOEyjZoZyI4cqdbv/LHRGCqbA==",
|
||||
"from": "stremio-translations@git+https://git@github.com/Stremio/stremio-translations.git#f55fe0d7dfe33bd6be461b4430cba2319afd1d8e"
|
||||
"version": "git+https://git@github.com/Stremio/stremio-translations.git#eb2d0dbcf959d99fad448d4b41aca11c0ae29449",
|
||||
"integrity": "sha512-SV3Foxdr1nYJZGhq0TX4rZaZiXtOTMX59dAUcOMT/EwrtDqCdg6fSbNuNAlO/adlaSyyFcpEZKC+U4kRRFANkA==",
|
||||
"from": "stremio-translations@git+https://git@github.com/Stremio/stremio-translations.git#eb2d0dbcf959d99fad448d4b41aca11c0ae29449"
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
"@babel/runtime": "7.16.0",
|
||||
"@sentry/browser": "6.13.3",
|
||||
"@stremio/stremio-colors": "5.0.1",
|
||||
"@stremio/stremio-core-web": "0.44.14",
|
||||
"@stremio/stremio-core-web": "0.44.17",
|
||||
"@stremio/stremio-icons": "4.0.0",
|
||||
"@stremio/stremio-video": "0.0.24",
|
||||
"a-color-picker": "1.2.1",
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
"react-i18next": "^12.1.1",
|
||||
"react-is": "18.2.0",
|
||||
"spatial-navigation-polyfill": "git+https://git@github.com/Stremio/spatial-navigation.git#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
|
||||
"stremio-translations": "git+https://git@github.com/Stremio/stremio-translations.git#f55fe0d7dfe33bd6be461b4430cba2319afd1d8e",
|
||||
"stremio-translations": "git+https://git@github.com/Stremio/stremio-translations.git#eb2d0dbcf959d99fad448d4b41aca11c0ae29449",
|
||||
"url": "0.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
30
src/common/externalPlayerOptions.js
Normal file
30
src/common/externalPlayerOptions.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (C) 2017-2022 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' },
|
||||
{ label: 'Infuse', value: 'infuse' }
|
||||
]);
|
||||
} 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;
|
||||
|
|
@ -40,6 +40,8 @@ const useOnScrollToBottom = require('./useOnScrollToBottom');
|
|||
const useProfile = require('./useProfile');
|
||||
const useStreamingServer = require('./useStreamingServer');
|
||||
const useTorrent = require('./useTorrent');
|
||||
const platform = require('./platform');
|
||||
const externalPlayerOptions = require('./externalPlayerOptions');
|
||||
|
||||
module.exports = {
|
||||
AddonDetailsModal,
|
||||
|
|
@ -84,5 +86,7 @@ module.exports = {
|
|||
useOnScrollToBottom,
|
||||
useProfile,
|
||||
useStreamingServer,
|
||||
useTorrent
|
||||
useTorrent,
|
||||
platform,
|
||||
externalPlayerOptions,
|
||||
};
|
||||
|
|
|
|||
14
src/common/platform.js
Normal file
14
src/common/platform.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (C) 2017-2022 Smart code 203358507
|
||||
|
||||
const Bowser = require('bowser');
|
||||
|
||||
const browser = Bowser.parse(window.navigator?.userAgent || '');
|
||||
|
||||
const name = (browser?.os?.name || 'unknown').toLowerCase();
|
||||
|
||||
module.exports = {
|
||||
name,
|
||||
isMobile: () => {
|
||||
return name === 'ios' || name === 'android';
|
||||
}
|
||||
};
|
||||
|
|
@ -4,25 +4,64 @@ const React = require('react');
|
|||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { Button, Image, PlayIconCircleCentered } = require('stremio/common');
|
||||
const { Button, Image, PlayIconCircleCentered, useProfile, platform, useStreamingServer, useToast } = require('stremio/common');
|
||||
const { useServices } = require('stremio/services');
|
||||
const StreamPlaceholder = require('./StreamPlaceholder');
|
||||
const styles = require('./styles');
|
||||
|
||||
const Stream = ({ className, addonName, name, description, thumbnail, progress, deepLinks, ...props }) => {
|
||||
const profile = useProfile();
|
||||
const streamingServer = useStreamingServer();
|
||||
const { core } = useServices();
|
||||
const toast = useToast();
|
||||
const href = React.useMemo(() => {
|
||||
const haveStreamingServer = streamingServer.settings !== null && streamingServer.settings.type === 'Ready';
|
||||
return deepLinks ?
|
||||
typeof deepLinks.player === 'string' ?
|
||||
deepLinks.player
|
||||
profile.settings.playerType && profile.settings.playerType !== 'internal' ?
|
||||
platform.isMobile() || !haveStreamingServer ?
|
||||
(deepLinks.externalPlayer.openPlayer || {})[platform.name] || deepLinks.externalPlayer.href
|
||||
: null
|
||||
:
|
||||
null
|
||||
typeof deepLinks.player === 'string' ?
|
||||
deepLinks.player
|
||||
:
|
||||
null
|
||||
:
|
||||
null;
|
||||
}, [deepLinks]);
|
||||
}, [deepLinks, profile, streamingServer]);
|
||||
const onClick = React.useCallback((e) => {
|
||||
if (href === null) {
|
||||
// link does not lead to the player, it is expected to
|
||||
// open with local video player through the streaming server
|
||||
core.transport.dispatch({
|
||||
action: 'StreamingServer',
|
||||
args: {
|
||||
action: 'PlayOnDevice',
|
||||
args: {
|
||||
device: 'vlc',
|
||||
source: deepLinks.externalPlayer.streaming
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (profile.settings.playerType === 'external') {
|
||||
toast.show({
|
||||
type: 'success',
|
||||
title: 'Stream opened in external player',
|
||||
timeout: 4000
|
||||
});
|
||||
}
|
||||
props.onClick(e);
|
||||
}, [href, deepLinks, props.onClick, profile, toast]);
|
||||
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]);
|
||||
const renderThumbnailFallback = React.useCallback(() => (
|
||||
<Icon className={styles['placeholder-icon']} icon={'ic_broken_link'} />
|
||||
), []);
|
||||
return (
|
||||
<Button href={href} {...props} className={classnames(className, styles['stream-container'])} title={addonName}>
|
||||
<Button href={href} download={forceDownload} {...props} onClick={onClick} className={classnames(className, styles['stream-container'])} title={addonName}>
|
||||
{
|
||||
typeof thumbnail === 'string' && thumbnail.length > 0 ?
|
||||
<div className={styles['thumbnail-container']} title={name || addonName}>
|
||||
|
|
@ -62,8 +101,58 @@ Stream.propTypes = {
|
|||
thumbnail: PropTypes.string,
|
||||
progress: PropTypes.number,
|
||||
deepLinks: PropTypes.shape({
|
||||
player: PropTypes.string
|
||||
})
|
||||
player: PropTypes.string,
|
||||
externalPlayer: PropTypes.shape({
|
||||
href: PropTypes.string,
|
||||
fileName: PropTypes.string,
|
||||
streaming: PropTypes.string,
|
||||
openPlayer: PropTypes.shape({
|
||||
choose: {
|
||||
ios: PropTypes.string,
|
||||
android: PropTypes.string,
|
||||
windows: PropTypes.string,
|
||||
macos: PropTypes.string,
|
||||
linux: PropTypes.string
|
||||
},
|
||||
vlc: {
|
||||
ios: PropTypes.string,
|
||||
android: PropTypes.string,
|
||||
windows: PropTypes.string,
|
||||
macos: PropTypes.string,
|
||||
linux: PropTypes.string
|
||||
},
|
||||
outplayer: {
|
||||
ios: PropTypes.string,
|
||||
android: PropTypes.string,
|
||||
windows: PropTypes.string,
|
||||
macos: PropTypes.string,
|
||||
linux: PropTypes.string
|
||||
},
|
||||
infuse: {
|
||||
ios: PropTypes.string,
|
||||
android: PropTypes.string,
|
||||
windows: PropTypes.string,
|
||||
macos: PropTypes.string,
|
||||
linux: PropTypes.string
|
||||
},
|
||||
justplayer: {
|
||||
ios: PropTypes.string,
|
||||
android: PropTypes.string,
|
||||
windows: PropTypes.string,
|
||||
macos: PropTypes.string,
|
||||
linux: PropTypes.string
|
||||
},
|
||||
mxplayer: {
|
||||
ios: PropTypes.string,
|
||||
android: PropTypes.string,
|
||||
windows: PropTypes.string,
|
||||
macos: PropTypes.string,
|
||||
linux: PropTypes.string
|
||||
},
|
||||
})
|
||||
})
|
||||
}),
|
||||
onClick: PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = Stream;
|
||||
|
|
|
|||
|
|
@ -588,6 +588,12 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
error !== null ?
|
||||
<div className={classnames(styles['layer'], styles['error-layer'])}>
|
||||
<div className={styles['error-label']} title={error.message}>{error.message}</div>
|
||||
{
|
||||
error.code === 2 ?
|
||||
<div className={styles['error-sub']} title={t('EXTERNAL_PLAYER_HINT')}>{t('EXTERNAL_PLAYER_HINT')}</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
player.selected !== null ?
|
||||
<Button className={styles['playlist-button']} title={t('PLAYER_OPEN_IN_EXTERNAL')} href={player.selected.stream.deepLinks.externalPlayer.href} download={player.selected.stream.deepLinks.externalPlayer.fileName} target={'_blank'}>
|
||||
|
|
|
|||
|
|
@ -54,6 +54,16 @@ html:not(.active-slider-within) {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.error-sub {
|
||||
flex: 0 1 auto;
|
||||
padding: 0 2rem;
|
||||
max-height: 4.8em;
|
||||
font-size: 1.3rem;
|
||||
margin-top: 0.8rem;
|
||||
color: @color-surface-light5-90;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.playlist-button {
|
||||
flex: none;
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -35,10 +35,10 @@ const Settings = () => {
|
|||
subtitlesOutlineColorInput,
|
||||
audioLanguageSelect,
|
||||
seekTimeDurationSelect,
|
||||
playInExternalPlayerSelect,
|
||||
nextVideoPopupDurationSelect,
|
||||
bingeWatchingCheckbox,
|
||||
playInBackgroundCheckbox,
|
||||
playInExternalPlayerCheckbox,
|
||||
hardwareDecodingCheckbox,
|
||||
streamingServerUrlInput
|
||||
} = useProfileSettingsInputs(profile);
|
||||
|
|
@ -399,11 +399,9 @@ const Settings = () => {
|
|||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>{ t('SETTINGS_PLAY_IN_EXTERNAL_PLAYER') }</div>
|
||||
</div>
|
||||
<Checkbox
|
||||
className={classnames(styles['option-input-container'], styles['checkbox-container'])}
|
||||
disabled={true}
|
||||
tabIndex={-1}
|
||||
{...playInExternalPlayerCheckbox}
|
||||
<Multiselect
|
||||
className={classnames(styles['option-input-container'], styles['multiselect-container'])}
|
||||
{...playInExternalPlayerSelect}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
const React = require('react');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const { useServices } = require('stremio/services');
|
||||
const { CONSTANTS, interfaceLanguages, languageNames } = require('stremio/common');
|
||||
const { CONSTANTS, interfaceLanguages, languageNames, externalPlayerOptions } = require('stremio/common');
|
||||
|
||||
const useProfileSettingsInputs = (profile) => {
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -157,6 +157,25 @@ 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'}`],
|
||||
onSelect: (event) => {
|
||||
core.transport.dispatch({
|
||||
action: 'Ctx',
|
||||
args: {
|
||||
action: 'UpdateSettings',
|
||||
args: {
|
||||
...profile.settings,
|
||||
playerType: event.value
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}), [profile.settings]);
|
||||
const nextVideoPopupDurationSelect = React.useMemo(() => ({
|
||||
options: CONSTANTS.NEXT_VIDEO_POPUP_DURATIONS.map((duration) => ({
|
||||
value: `${duration}`,
|
||||
|
|
@ -212,21 +231,6 @@ const useProfileSettingsInputs = (profile) => {
|
|||
});
|
||||
}
|
||||
}), [profile.settings]);
|
||||
const playInExternalPlayerCheckbox = React.useMemo(() => ({
|
||||
checked: profile.settings.playInExternalPlayer,
|
||||
onClick: () => {
|
||||
core.transport.dispatch({
|
||||
action: 'Ctx',
|
||||
args: {
|
||||
action: 'UpdateSettings',
|
||||
args: {
|
||||
...profile.settings,
|
||||
playInExternalPlayer: !profile.settings.playInExternalPlayer
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}), [profile.settings]);
|
||||
const hardwareDecodingCheckbox = React.useMemo(() => ({
|
||||
checked: profile.settings.hardwareDecoding,
|
||||
onClick: () => {
|
||||
|
|
@ -266,10 +270,10 @@ const useProfileSettingsInputs = (profile) => {
|
|||
subtitlesOutlineColorInput,
|
||||
audioLanguageSelect,
|
||||
seekTimeDurationSelect,
|
||||
playInExternalPlayerSelect,
|
||||
nextVideoPopupDurationSelect,
|
||||
bingeWatchingCheckbox,
|
||||
playInBackgroundCheckbox,
|
||||
playInExternalPlayerCheckbox,
|
||||
hardwareDecodingCheckbox,
|
||||
streamingServerUrlInput
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue