diff --git a/.eslintrc b/.eslintrc index 0f7f26836..c2d000b22 100644 --- a/.eslintrc +++ b/.eslintrc @@ -10,7 +10,9 @@ }, "globals": { "YT": "readonly", - "FB": "readonly" + "FB": "readonly", + "cast": "readonly", + "chrome": "readonly" }, "env": { "node": true, diff --git a/.npmrc b/.npmrc deleted file mode 100644 index ab890481c..000000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -@stremio:registry=https://npm.pkg.github.com diff --git a/package.json b/package.json index 0eb032d12..11bafbdde 100755 --- a/package.json +++ b/package.json @@ -15,11 +15,11 @@ }, "dependencies": { "@sentry/browser": "5.11.1", - "@stremio/stremio-core-web": "0.12.0", + "@stremio/stremio-core-web": "0.16.0", + "@stremio/stremio-video": "0.0.4", "a-color-picker": "1.2.1", "classnames": "2.2.6", "events": "1.1.1", - "hat": "0.0.3", "lodash.debounce": "4.0.8", "lodash.isequal": "4.5.0", "lodash.throttle": "4.1.1", @@ -30,8 +30,7 @@ "react-focus-lock": "2.2.1", "spatial-navigation-polyfill": "git+https://git@github.com/Stremio/spatial-navigation.git#40204ad9942fe786794c62f99ea5ab2b52b24096", "stremio-colors": "git+https://git@github.com/Stremio/stremio-colors.git#v3.0.0", - "stremio-icons": "git+https://git@github.com/Stremio/stremio-icons.git#v2.0.2", - "vtt.js": "0.13.0" + "stremio-icons": "git+https://git@github.com/Stremio/stremio-icons.git#v2.0.2" }, "devDependencies": { "@babel/core": "7.8.7", @@ -68,4 +67,4 @@ "webpack-cli": "3.3.11", "webpack-dev-server": "3.10.3" } -} \ No newline at end of file +} diff --git a/src/App/App.js b/src/App/App.js index fc38da179..53459cbd5 100644 --- a/src/App/App.js +++ b/src/App/App.js @@ -3,9 +3,9 @@ require('spatial-navigation-polyfill'); const React = require('react'); const { Router } = require('stremio-router'); -const { Core, KeyboardNavigation, ServicesProvider, Shell } = require('stremio/services'); +const { Core, Shell, Chromecast, KeyboardShortcuts, ServicesProvider } = require('stremio/services'); const { NotFound } = require('stremio/routes'); -const { ToastProvider } = require('stremio/common'); +const { ToastProvider, CONSTANTS } = require('stremio/common'); const CoreEventsToaster = require('./CoreEventsToaster'); const routerViewsConfig = require('./routerViewsConfig'); const styles = require('./styles'); @@ -15,47 +15,62 @@ const App = () => { return NotFound; }, []); const services = React.useMemo(() => ({ - keyboardNavigation: new KeyboardNavigation(), + core: new Core(), shell: new Shell(), - core: new Core() + chromecast: new Chromecast(), + keyboardShortcuts: new KeyboardShortcuts() }), []); - const [shellInitialized, setShellInitialized] = React.useState(false); const [coreInitialized, setCoreInitialized] = React.useState(false); + const [shellInitialized, setShellInitialized] = React.useState(false); React.useEffect(() => { - const onShellStateChanged = () => { - setShellInitialized(services.shell.active || services.shell.error instanceof Error); - }; const onCoreStateChanged = () => { if (services.core.active) { - services.core.dispatch({ + services.core.transport.dispatch({ action: 'Load', args: { model: 'Ctx' } }); } + setCoreInitialized(services.core.active); }; - services.shell.on('stateChanged', onShellStateChanged); + const onShellStateChanged = () => { + setShellInitialized(services.shell.active || services.shell.error instanceof Error); + }; + const onChromecastStateChange = () => { + if (services.chromecast.active) { + services.chromecast.transport.setOptions({ + receiverApplicationId: CONSTANTS.CHROMECAST_RECEIVER_APP_ID, + autoJoinPolicy: chrome.cast.AutoJoinPolicy.PAGE_SCOPED, + resumeSavedSession: false, + language: null + }); + } + }; services.core.on('stateChanged', onCoreStateChanged); - services.keyboardNavigation.start(); - services.shell.start(); + services.shell.on('stateChanged', onShellStateChanged); + services.chromecast.on('stateChanged', onChromecastStateChange); services.core.start(); - window.shell = services.shell; - window.core = services.core; + services.shell.start(); + services.chromecast.start(); + services.keyboardShortcuts.start(); + window.services = services; return () => { - services.keyboardNavigation.stop(); - services.shell.stop(); services.core.stop(); - services.shell.off('stateChanged', onShellStateChanged); + services.shell.stop(); + services.chromecast.stop(); + services.keyboardShortcuts.stop(); services.core.off('stateChanged', onCoreStateChanged); + services.shell.off('stateChanged', onShellStateChanged); + services.chromecast.off('stateChanged', onChromecastStateChange); }; }, []); return ( { - shellInitialized && coreInitialized ? + coreInitialized && shellInitialized ? { }); } }; - core.on('Event', onEvent); + core.transport.on('Event', onEvent); return () => { - core.off('Event', onEvent); + core.transport.off('Event', onEvent); }; }, []); return null; diff --git a/src/common/AddonDetailsModal/AddonDetailsModal.js b/src/common/AddonDetailsModal/AddonDetailsModal.js index bf452b9fb..3d03d7819 100644 --- a/src/common/AddonDetailsModal/AddonDetailsModal.js +++ b/src/common/AddonDetailsModal/AddonDetailsModal.js @@ -26,7 +26,7 @@ const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => { } }; const installOnClick = (event) => { - core.dispatch({ + core.transport.dispatch({ action: 'Ctx', args: { action: 'InstallAddon', @@ -42,7 +42,7 @@ const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => { } }; const uninstallOnClick = (event) => { - core.dispatch({ + core.transport.dispatch({ action: 'Ctx', args: { action: 'UninstallAddon', diff --git a/src/common/CONSTANTS.js b/src/common/CONSTANTS.js index 42a7e9f7f..f5702fd4e 100644 --- a/src/common/CONSTANTS.js +++ b/src/common/CONSTANTS.js @@ -1,5 +1,6 @@ // Copyright (C) 2017-2020 Smart code 203358507 +const CHROMECAST_RECEIVER_APP_ID = '1634F54B'; const SUBTITLES_SIZES = [75, 100, 125, 150, 175, 200, 250]; const SUBTITLES_FONTS = ['Roboto', 'Arial', 'Halvetica', 'Times New Roman', 'Verdana', 'Courier', 'Lucida Console', 'sans-serif', 'serif', 'monospace']; const CATALOG_PREVIEW_SIZE = 10; @@ -24,6 +25,7 @@ const TYPE_PRIORITIES = { }; module.exports = { + CHROMECAST_RECEIVER_APP_ID, SUBTITLES_SIZES, SUBTITLES_FONTS, CATALOG_PREVIEW_SIZE, diff --git a/src/common/LibItem/LibItem.js b/src/common/LibItem/LibItem.js index 8b14ca317..687f6e6a2 100644 --- a/src/common/LibItem/LibItem.js +++ b/src/common/LibItem/LibItem.js @@ -52,7 +52,7 @@ const LibItem = ({ id, ...props }) => { } case 'dismiss': { if (typeof id === 'string') { - core.dispatch({ + core.transport.dispatch({ action: 'Ctx', args: { action: 'RewindLibraryItem', diff --git a/src/common/NavBar/HorizontalNavBar/NavMenu/NavMenu.js b/src/common/NavBar/HorizontalNavBar/NavMenu/NavMenu.js index 0333f1afa..6f32a2d09 100644 --- a/src/common/NavBar/HorizontalNavBar/NavMenu/NavMenu.js +++ b/src/common/NavBar/HorizontalNavBar/NavMenu/NavMenu.js @@ -26,7 +26,7 @@ const NavMenu = (props) => { event.nativeEvent.togglePopupPrevented = true; }, []); const logoutButtonOnClick = React.useCallback(() => { - core.dispatch({ + core.transport.dispatch({ action: 'Ctx', args: { action: 'Logout' diff --git a/src/common/NavBar/HorizontalNavBar/NotificationsMenu/useNotifications.js b/src/common/NavBar/HorizontalNavBar/NotificationsMenu/useNotifications.js index 3304befca..ed194af63 100644 --- a/src/common/NavBar/HorizontalNavBar/NotificationsMenu/useNotifications.js +++ b/src/common/NavBar/HorizontalNavBar/NotificationsMenu/useNotifications.js @@ -8,18 +8,18 @@ const useNotifications = () => { const { core } = useServices(); React.useEffect(() => { const onNewState = () => { - const state = core.getState(); + const state = core.transport.getState(); setNotifications(state.notifications.groups); }; - core.on('NewModel', onNewState); - core.dispatch({ + core.transport.on('NewModel', onNewState); + core.transport.dispatch({ action: 'Load', args: { load: 'Notifications' } }); return () => { - core.off('NewModel', onNewState); + core.transport.off('NewModel', onNewState); }; }, []); return notifications; diff --git a/src/common/index.js b/src/common/index.js index 3def79626..82e46f1e1 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -36,6 +36,7 @@ const useInLibrary = require('./useInLibrary'); const useLiveRef = require('./useLiveRef'); const useModelState = require('./useModelState'); const useProfile = require('./useProfile'); +const useStreamingServer = require('./useStreamingServer'); module.exports = { AddonDetailsModal, @@ -76,4 +77,5 @@ module.exports = { useLiveRef, useModelState, useProfile, + useStreamingServer, }; diff --git a/src/common/useInLibrary.js b/src/common/useInLibrary.js index 4b1b467b4..74912339c 100644 --- a/src/common/useInLibrary.js +++ b/src/common/useInLibrary.js @@ -7,14 +7,14 @@ const useModelState = require('stremio/common/useModelState'); const useInLibrary = (metaItem) => { const { core } = useServices(); const initLibraryItemsState = React.useCallback(() => { - return core.getState('library_items'); + return core.transport.getState('library_items'); }, []); const libraryItems = useModelState({ model: 'library_items', init: initLibraryItemsState }); const addToLibrary = React.useCallback((metaItem) => { - core.dispatch({ + core.transport.dispatch({ action: 'Ctx', args: { action: 'AddToLibrary', @@ -23,7 +23,7 @@ const useInLibrary = (metaItem) => { }); }, []); const removeFromLibrary = React.useCallback((metaItem) => { - core.dispatch({ + core.transport.dispatch({ action: 'Ctx', args: { action: 'RemoveFromLibrary', diff --git a/src/common/useModelState.js b/src/common/useModelState.js index 19c670bbf..d28a9e0ab 100644 --- a/src/common/useModelState.js +++ b/src/common/useModelState.js @@ -13,26 +13,26 @@ const useModelState = ({ model, init, action, timeout, onNewState, map, mapWithC const routeFocused = useRouteFocused(); const [state, setState] = useDeepEqualState(init); React.useLayoutEffect(() => { - core.dispatch(action, modelRef.current); + core.transport.dispatch(action, modelRef.current); }, [action]); React.useLayoutEffect(() => { return () => { - core.dispatch({ action: 'Unload' }, modelRef.current); + core.transport.dispatch({ action: 'Unload' }, modelRef.current); }; }, []); React.useLayoutEffect(() => { const onNewStateThrottled = throttle(() => { - const state = core.getState(modelRef.current); + const state = core.transport.getState(modelRef.current); if (typeof onNewState === 'function') { const action = onNewState(state); - const handled = core.dispatch(action, modelRef.current); + const handled = core.transport.dispatch(action, modelRef.current); if (handled) { return; } } if (typeof mapWithCtx === 'function') { - const ctx = core.getState('ctx'); + const ctx = core.transport.getState('ctx'); setState(mapWithCtx(state, ctx)); } else if (typeof map === 'function') { setState(map(state)); @@ -41,14 +41,14 @@ const useModelState = ({ model, init, action, timeout, onNewState, map, mapWithC } }, timeout); if (routeFocused) { - core.on('NewState', onNewStateThrottled); + core.transport.on('NewState', onNewStateThrottled); if (mountedRef.current) { onNewStateThrottled.call(); } } return () => { onNewStateThrottled.cancel(); - core.off('NewState', onNewStateThrottled); + core.transport.off('NewState', onNewStateThrottled); }; }, [routeFocused, timeout, onNewState, map, mapWithCtx]); React.useLayoutEffect(() => { diff --git a/src/common/useProfile.js b/src/common/useProfile.js index 6a520ae98..ded6b9e64 100644 --- a/src/common/useProfile.js +++ b/src/common/useProfile.js @@ -11,7 +11,7 @@ const mapProfileState = (ctx) => { const useProfile = () => { const { core } = useServices(); const initProfileState = React.useCallback(() => { - const ctx = core.getState('ctx'); + const ctx = core.transport.getState('ctx'); return mapProfileState(ctx); }, []); const profile = useModelState({ diff --git a/src/routes/Settings/useStreamingServer.js b/src/common/useStreamingServer.js similarity index 79% rename from src/routes/Settings/useStreamingServer.js rename to src/common/useStreamingServer.js index fffbf5980..20268e639 100644 --- a/src/routes/Settings/useStreamingServer.js +++ b/src/common/useStreamingServer.js @@ -2,15 +2,15 @@ const React = require('react'); const { useServices } = require('stremio/services'); -const { useModelState } = require('stremio/common'); +const useModelState = require('stremio/common/useModelState'); const useStreamingServer = () => { const { core } = useServices(); const initStreamingServer = React.useCallback(() => { - return core.getState('streaming_server'); + return core.transport.getState('streaming_server'); }, []); const loadStreamingServerAction = React.useMemo(() => { - const streamingServer = core.getState('streaming_server'); + const streamingServer = core.transport.getState('streaming_server'); if (streamingServer.selected === null) { return { action: 'StreamingServer', diff --git a/src/index.html b/src/index.html index 67b70ad11..2df264a6f 100755 --- a/src/index.html +++ b/src/index.html @@ -16,6 +16,7 @@ + \ No newline at end of file diff --git a/src/routes/Addons/useAddons.js b/src/routes/Addons/useAddons.js index 7a1098f17..11f3d600c 100644 --- a/src/routes/Addons/useAddons.js +++ b/src/routes/Addons/useAddons.js @@ -126,7 +126,7 @@ const useAddons = (urlParams) => { } }; } else { - const addons = core.getState('addons'); + const addons = core.transport.getState('addons'); if (addons.selectable.catalogs.length > 0) { return { action: 'Load', diff --git a/src/routes/Board/useContinueWatchingPreview.js b/src/routes/Board/useContinueWatchingPreview.js index 544cb20e3..5a47f60fa 100644 --- a/src/routes/Board/useContinueWatchingPreview.js +++ b/src/routes/Board/useContinueWatchingPreview.js @@ -24,7 +24,7 @@ const mapContinueWatchingPreviewState = (continue_watching_preview) => { const useContinueWatchingPreview = () => { const { core } = useServices(); const initContinueWatchingPreviewState = React.useMemo(() => { - return mapContinueWatchingPreviewState(core.getState('continue_watching_preview')); + return mapContinueWatchingPreviewState(core.transport.getState('continue_watching_preview')); }, []); return useModelState({ model: 'continue_watching_preview', diff --git a/src/routes/Discover/useDiscover.js b/src/routes/Discover/useDiscover.js index 3cc96d617..67db7419e 100644 --- a/src/routes/Discover/useDiscover.js +++ b/src/routes/Discover/useDiscover.js @@ -90,7 +90,7 @@ const useDiscover = (urlParams, queryParams) => { } }; } else { - const discover = core.getState('discover'); + const discover = core.transport.getState('discover'); if (discover.selectable.types.length > 0) { return { action: 'Load', diff --git a/src/routes/Intro/Intro.js b/src/routes/Intro/Intro.js index 3296b6a97..23d058142 100644 --- a/src/routes/Intro/Intro.js +++ b/src/routes/Intro/Intro.js @@ -92,7 +92,7 @@ const Intro = ({ queryParams }) => { if (!user || typeof user.fbLoginToken !== 'string' || typeof user.email !== 'string') { throw new Error('Login failed at getting token from Stremio'); } - core.dispatch({ + core.transport.dispatch({ action: 'Ctx', args: { action: 'Authenticate', @@ -123,7 +123,7 @@ const Intro = ({ queryParams }) => { return; } openLoaderModal(); - core.dispatch({ + core.transport.dispatch({ action: 'Ctx', args: { action: 'Authenticate', @@ -140,7 +140,7 @@ const Intro = ({ queryParams }) => { dispatch({ type: 'error', error: 'You must accept the Terms of Service' }); return; } - core.dispatch({ + core.transport.dispatch({ action: 'Ctx', args: { action: 'Logout' @@ -170,7 +170,7 @@ const Intro = ({ queryParams }) => { return; } openLoaderModal(); - core.dispatch({ + core.transport.dispatch({ action: 'Ctx', args: { action: 'Authenticate', @@ -270,10 +270,10 @@ const Intro = ({ queryParams }) => { } }; if (routeFocused) { - core.on('Event', onEvent); + core.transport.on('Event', onEvent); } return () => { - core.off('Event', onEvent); + core.transport.off('Event', onEvent); }; }, [routeFocused]); React.useEffect(() => { diff --git a/src/routes/Player/ControlBar/ControlBar.js b/src/routes/Player/ControlBar/ControlBar.js index cf38fa70b..324f5b41e 100644 --- a/src/routes/Player/ControlBar/ControlBar.js +++ b/src/routes/Player/ControlBar/ControlBar.js @@ -5,6 +5,7 @@ const PropTypes = require('prop-types'); const classnames = require('classnames'); const Icon = require('stremio-icons/dom'); const { Button } = require('stremio/common'); +const { useServices } = require('stremio/services'); const SeekBar = require('./SeekBar'); const VolumeSlider = require('./VolumeSlider'); const styles = require('./styles'); @@ -28,6 +29,8 @@ const ControlBar = ({ onToggleInfoMenu, ...props }) => { + const { chromecast } = useServices(); + const [chromecastServiceActive, setChromecastServiceActive] = React.useState(() => chromecast.active); const onSubtitlesButtonMouseDown = React.useCallback((event) => { event.nativeEvent.subtitlesMenuClosePrevented = true; }, []); @@ -66,6 +69,18 @@ const ControlBar = ({ onToggleInfoMenu(); } }, [onToggleInfoMenu]); + const onChromecastButtonClick = React.useCallback(() => { + chromecast.transport.requestSession(); + }, []); + React.useEffect(() => { + const onStateChanged = () => { + setChromecastServiceActive(chromecast.active); + }; + chromecast.on('stateChanged', onStateChanged); + return () => { + chromecast.off('stateChanged', onStateChanged); + }; + }, []); return (
-