diff --git a/src/App/ServicesToaster.js b/src/App/ServicesToaster.js index d3fdd3461..9ceff4bb7 100644 --- a/src/App/ServicesToaster.js +++ b/src/App/ServicesToaster.js @@ -50,6 +50,14 @@ const ServicesToaster = () => { }); break; } + case 'PlayingOnDevice': { + toast.show({ + type: 'success', + title: `Stream opened in ${args.device}`, + timeout: 4000 + }); + break; + } } }; const onDragAndDropError = (error) => { diff --git a/src/routes/Player/ControlBar/styles.less b/src/routes/Player/ControlBar/styles.less index 052622400..67b0979ad 100644 --- a/src/routes/Player/ControlBar/styles.less +++ b/src/routes/Player/ControlBar/styles.less @@ -91,6 +91,10 @@ padding: 0 0.5rem; overflow: visible; + .volume-slider { + display: none; + } + .control-bar-buttons-menu-button { display: flex; } diff --git a/src/routes/Player/OptionsMenu/Option/Option.js b/src/routes/Player/OptionsMenu/Option/Option.js new file mode 100644 index 000000000..317cd0a15 --- /dev/null +++ b/src/routes/Player/OptionsMenu/Option/Option.js @@ -0,0 +1,32 @@ +// Copyright (C) 2017-2022 Smart code 203358507 + +const React = require('react'); +const PropTypes = require('prop-types'); +const classnames = require('classnames'); +const Icon = require('@stremio/stremio-icons/dom'); +const { Button } = require('stremio/common'); +const styles = require('./styles'); + +const Option = ({ icon, label, deviceId, disabled, onClick }) => { + const onButtonClick = React.useCallback(() => { + if (typeof onClick === 'function') { + onClick(deviceId); + } + }, [onClick, deviceId]); + return ( + + ); +}; + +Option.propTypes = { + icon: PropTypes.string, + label: PropTypes.string, + deviceId: PropTypes.string, + disabled: PropTypes.bool, + onClick: PropTypes.func, +}; + +module.exports = Option; diff --git a/src/routes/Player/OptionsMenu/Option/index.js b/src/routes/Player/OptionsMenu/Option/index.js new file mode 100644 index 000000000..2bf2d108d --- /dev/null +++ b/src/routes/Player/OptionsMenu/Option/index.js @@ -0,0 +1,5 @@ +// Copyright (C) 2017-2022 Smart code 203358507 + +const Option = require('./Option'); + +module.exports = Option; diff --git a/src/routes/Player/OptionsMenu/Option/styles.less b/src/routes/Player/OptionsMenu/Option/styles.less new file mode 100644 index 000000000..a3f54650d --- /dev/null +++ b/src/routes/Player/OptionsMenu/Option/styles.less @@ -0,0 +1,33 @@ +// Copyright (C) 2017-2022 Smart code 203358507 + +@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less'; + +.option-container { + display: flex; + flex-direction: row; + align-items: center; + height: 4rem; + + .icon { + flex: none; + width: 1.4rem; + height: 1.4rem; + margin: 1.3rem; + fill: @color-surface-light5-90; + } + + .label { + flex: 1; + max-height: 2.4em; + font-weight: 400; + color: @color-surface-light5-90; + } + + &:hover { + background-color: @color-background-light2; + } + + &:global(.disabled) { + opacity: 0.5; + } +} \ No newline at end of file diff --git a/src/routes/Player/OptionsMenu/OptionsMenu.js b/src/routes/Player/OptionsMenu/OptionsMenu.js index a310dc66c..6e3736691 100644 --- a/src/routes/Player/OptionsMenu/OptionsMenu.js +++ b/src/routes/Player/OptionsMenu/OptionsMenu.js @@ -3,28 +3,28 @@ const React = require('react'); const PropTypes = require('prop-types'); const classnames = require('classnames'); -const Icon = require('@stremio/stremio-icons/dom'); -const { Button, useToast } = require('stremio/common'); -// const { useServices } = require('stremio/services'); +const { useToast } = require('stremio/common'); +const { useServices } = require('stremio/services'); +const Option = require('./Option'); const styles = require('./styles'); -const OptionsMenu = ({ className, stream }) => { - // const { core } = useServices(); +const OptionsMenu = ({ className, stream, playbackDevices }) => { + const { core } = useServices(); const toast = useToast(); - const streamUrl = React.useMemo(() => { + const [streamingUrl, downloadUrl] = React.useMemo(() => { return stream !== null ? stream.deepLinks && stream.deepLinks.externalPlayer && - typeof stream.deepLinks.externalPlayer.download === 'string' ? - stream.deepLinks.externalPlayer.download - : - null + [stream.deepLinks.externalPlayer.streaming, stream.deepLinks.externalPlayer.download] : - null; + [null, null]; }, [stream]); + const externalDevices = React.useMemo(() => { + return playbackDevices.filter(({ type }) => type === 'external'); + }, [playbackDevices]); const onCopyStreamButtonClick = React.useCallback(() => { - if (streamUrl !== null) { - navigator.clipboard.writeText(streamUrl) + if (streamingUrl || downloadUrl) { + navigator.clipboard.writeText(streamingUrl || downloadUrl) .then(() => { toast.show({ type: 'success', @@ -38,55 +38,78 @@ const OptionsMenu = ({ className, stream }) => { toast.show({ type: 'error', title: 'Error', - message: `Failed to copy stream link: ${streamUrl}`, + message: `Failed to copy stream link: ${streamingUrl || downloadUrl}`, timeout: 3000 }); }); } - }, [streamUrl]); + }, [streamingUrl, downloadUrl]); const onDownloadVideoButtonClick = React.useCallback(() => { - if (streamUrl !== null) { - window.open(streamUrl); + if (streamingUrl || downloadUrl) { + window.open(streamingUrl || downloadUrl); } - }, [streamUrl]); - // const onExternalPlayerButtonClick = React.useCallback(() => { - // if (streamUrl !== null) { - // core.transport.dispatch({ - // action: 'StreamingServer', - // args: { - // action: 'PlayOnDevice', - // args: { - // device: 'vlc', - // source: streamUrl, - // } - // } - // }); - // } - // }, [streamUrl]); + }, [streamingUrl, downloadUrl]); + const onExternalDeviceRequested = React.useCallback((deviceId) => { + if (streamingUrl) { + core.transport.dispatch({ + action: 'StreamingServer', + args: { + action: 'PlayOnDevice', + args: { + device: deviceId, + source: streamingUrl, + } + } + }); + } + }, [streamingUrl]); const onMouseDown = React.useCallback((event) => { event.nativeEvent.optionsMenuClosePrevented = true; }, []); return (
- - - {/* */} + { + streamingUrl || downloadUrl ? +
); }; OptionsMenu.propTypes = { className: PropTypes.string, - stream: PropTypes.object + stream: PropTypes.object, + playbackDevices: PropTypes.array }; module.exports = OptionsMenu; diff --git a/src/routes/Player/OptionsMenu/styles.less b/src/routes/Player/OptionsMenu/styles.less index 54ebd0368..bbebd5616 100644 --- a/src/routes/Player/OptionsMenu/styles.less +++ b/src/routes/Player/OptionsMenu/styles.less @@ -1,37 +1,5 @@ // Copyright (C) 2017-2022 Smart code 203358507 -@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less'; - .options-menu-container { width: 15rem; - - .option-container { - display: flex; - flex-direction: row; - align-items: center; - height: 4rem; - - .icon { - flex: none; - width: 1.4rem; - height: 1.4rem; - margin: 1.3rem; - fill: @color-surface-light5-90; - } - - .label { - flex: 1; - max-height: 2.4em; - font-weight: 400; - color: @color-surface-light5-90; - } - - &:hover { - background-color: @color-background-light2; - } - - &:global(.disabled) { - opacity: 0.5; - } - } } \ No newline at end of file diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js index 6f27e0b11..5c4907f76 100644 --- a/src/routes/Player/Player.js +++ b/src/routes/Player/Player.js @@ -23,7 +23,7 @@ const useSettings = require('./useSettings'); const styles = require('./styles'); const Player = ({ urlParams, queryParams }) => { - const { chromecast, shell } = useServices(); + const { chromecast, shell, core } = useServices(); const [forceTranscoding, maxAudioChannels] = React.useMemo(() => { return [ queryParams.has('forceTranscoding'), @@ -428,11 +428,18 @@ const Player = ({ urlParams, queryParams }) => { ); } }; + const onCoreEvent = ({ event }) => { + if (event === 'PlayingOnDevice') { + onPauseRequested(); + } + }; chromecast.on('stateChanged', onChromecastServiceStateChange); + core.transport.on('CoreEvent', onCoreEvent); onChromecastServiceStateChange(); return () => { toast.removeFilter(toastFilter); chromecast.off('stateChanged', onChromecastServiceStateChange); + core.transport.off('CoreEvent', onCoreEvent); if (chromecast.active) { chromecast.transport.off( cast.framework.CastContextEventType.CAST_STATE_CHANGED, @@ -716,6 +723,7 @@ const Player = ({ urlParams, queryParams }) => { : null