From d0d4ef25ebf11639432a9d1de9cf54ff52610c95 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 20 Dec 2023 04:28:04 +0100 Subject: [PATCH] fix: external player stream logic --- src/common/CONSTANTS.js | 46 ++++++- src/common/Multiselect/Multiselect.js | 6 +- src/common/externalPlayerOptions.js | 35 ----- src/common/index.js | 2 - .../MetaDetails/StreamsList/Stream/Stream.js | 124 +++++++----------- src/routes/Player/Player.js | 12 +- .../Settings/useProfileSettingsInputs.js | 14 +- 7 files changed, 114 insertions(+), 125 deletions(-) delete mode 100644 src/common/externalPlayerOptions.js 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/Multiselect/Multiselect.js b/src/common/Multiselect/Multiselect.js index df2488a47..73638eaa0 100644 --- a/src/common/Multiselect/Multiselect.js +++ b/src/common/Multiselect/Multiselect.js @@ -15,7 +15,7 @@ const Multiselect = ({ className, mode, direction, title, disabled, dataset, ren const options = React.useMemo(() => { return Array.isArray(props.options) ? props.options.filter((option) => { - return option && typeof option.value === 'string'; + return option && (typeof option.value === 'string' || option.value === null); }) : []; @@ -23,7 +23,7 @@ const Multiselect = ({ className, mode, direction, title, disabled, dataset, ren const selected = React.useMemo(() => { return Array.isArray(props.selected) ? props.selected.filter((value) => { - return typeof value === 'string'; + return typeof value === 'string' || value === null; }) : []; @@ -161,7 +161,7 @@ Multiselect.propTypes = { direction: PropTypes.any, title: PropTypes.string, options: PropTypes.arrayOf(PropTypes.shape({ - value: PropTypes.string.isRequired, + value: PropTypes.string, title: PropTypes.string, label: PropTypes.string })), diff --git a/src/common/externalPlayerOptions.js b/src/common/externalPlayerOptions.js deleted file mode 100644 index e1b3938d0..000000000 --- a/src/common/externalPlayerOptions.js +++ /dev/null @@ -1,35 +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 (platform.name === 'macos') { - options = options.concat([ - { label: 'IINA', value: 'iina' }, - { label: 'mpv', value: 'mpv' }, - { label: 'VLC', value: 'vlc' } - ]); -} else if (['windows', '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 a26418eca..5d8d65cbe 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -44,7 +44,6 @@ 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 = { @@ -96,6 +95,5 @@ module.exports = { useStreamingServer, useTorrent, platform, - externalPlayerOptions, EventModal, }; diff --git a/src/routes/MetaDetails/StreamsList/Stream/Stream.js b/src/routes/MetaDetails/StreamsList/Stream/Stream.js index a8961cfe0..6587c72d0 100644 --- a/src/routes/MetaDetails/StreamsList/Stream/Stream.js +++ b/src/routes/MetaDetails/StreamsList/Stream/Stream.js @@ -4,25 +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(() => { - if (!deepLinks) return null; + return deepLinks ? + deepLinks.externalPlayer ? + deepLinks.externalPlayer.web ? + deepLinks.externalPlayer.web + : + deepLinks.externalPlayer.openPlayer ? + deepLinks.externalPlayer.openPlayer[platform.name] ? + deepLinks.externalPlayer.openPlayer[platform.name] + : + deepLinks.externalPlayer.playlist + : + deepLinks.player + : + deepLinks.player + : + null; + }, [deepLinks]); - if (profile.settings.playerType && profile.settings.playerType !== 'internal') { - return (deepLinks.externalPlayer.openPlayer || {})[platform.name] || deepLinks.externalPlayer.href; - } + 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]); - return typeof deepLinks.player === 'string' ? deepLinks.player : null; - }, [deepLinks, profile, streamingServer]); const markVideoAsWatched = React.useCallback(() => { if (typeof videoId === 'string') { core.transport.dispatch({ @@ -34,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', @@ -57,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 ( - diff --git a/src/routes/Settings/useProfileSettingsInputs.js b/src/routes/Settings/useProfileSettingsInputs.js index 5d7d5b87f..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,13 +211,15 @@ const useProfileSettingsInputs = (profile) => { } }), [profile.settings]); const playInExternalPlayerSelect = React.useMemo(() => ({ - options: externalPlayerOptions.map((opt) => ({ - value: opt.value, - label: t(opt.label), - })), + options: CONSTANTS.EXTERNAL_PLAYERS + .filter(({ platforms }) => platforms.includes(platform.name)) + .map(({ label, value }) => ({ + value, + label: t(label), + })), selected: [profile.settings.playerType], renderLabelText: () => { - const selectedOption = externalPlayerOptions.find(({ value }) => value === profile.settings.playerType); + const selectedOption = CONSTANTS.EXTERNAL_PLAYERS.find(({ value }) => value === profile.settings.playerType); return selectedOption ? t(selectedOption.label, { defaultValue: selectedOption.label }) : profile.settings.playerType; }, onSelect: (event) => {