mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-08-17 20:56:02 +00:00
Merge 509cad645b into 94a7be7c11
This commit is contained in:
commit
40bc07e2f1
8 changed files with 136 additions and 12 deletions
|
|
@ -3,9 +3,9 @@
|
|||
require('spatial-navigation-polyfill');
|
||||
const React = require('react');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const { useNavigate } = require('react-router');
|
||||
const { useLocation, useNavigate } = require('react-router');
|
||||
const { useCore } = require('stremio/core');
|
||||
const { Routes } = require('stremio-router');
|
||||
const { Routes, navigateToRoute } = require('stremio-router');
|
||||
const { Chromecast, ServicesProvider, GamepadProvider } = require('stremio/services');
|
||||
const { FullscreenProvider, ToastProvider, TooltipProvider, ShortcutsProvider, DiscordProvider, CONSTANTS, useBinaryState, useProfile, withCoreSuspender, onFileDrop, usePlatform } = require('stremio/common');
|
||||
const ServicesToaster = require('./ServicesToaster');
|
||||
|
|
@ -24,7 +24,10 @@ const App = () => {
|
|||
const profile = useProfile();
|
||||
const { i18n } = useTranslation();
|
||||
const { shell } = usePlatform();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const locationRef = React.useRef(location);
|
||||
locationRef.current = location;
|
||||
const [gamepadSupportEnabled, setGamepadSupportEnabled] = React.useState(false);
|
||||
const services = React.useMemo(() => {
|
||||
return {
|
||||
|
|
@ -113,7 +116,9 @@ const App = () => {
|
|||
const transportUrl = `https://${hostname}${pathname}`;
|
||||
navigate(`/addons?addon=${encodeURIComponent(transportUrl)}`);
|
||||
} else {
|
||||
navigate(`${pathname}?${searchParams.toString()}`);
|
||||
const search = searchParams.toString();
|
||||
const path = search ? `${pathname}?${search}` : pathname;
|
||||
navigateToRoute(navigate, locationRef.current, path);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
@ -127,7 +132,7 @@ const App = () => {
|
|||
}
|
||||
|
||||
return () => shell.off('open-media', onOpenMedia);
|
||||
}, [shell.state.initialized]);
|
||||
}, [shell.state.initialized, navigate]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (typeof profile.settings?.interfaceLanguage === 'string') {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const { useNavigate } = require('react-router');
|
||||
const { useLocation, useNavigate } = require('react-router');
|
||||
const { withCoreSuspender, useStreamingServer } = require('stremio/common');
|
||||
const { default: toPath } = require('stremio-router/toPath');
|
||||
const { navigateToRoute, toPath } = require('stremio-router');
|
||||
|
||||
const DeepLinkHandler = () => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const streamingServer = useStreamingServer();
|
||||
React.useEffect(() => {
|
||||
|
|
@ -14,11 +15,12 @@ const DeepLinkHandler = () => {
|
|||
if (type === 'Ready') {
|
||||
const [, deepLinks] = content;
|
||||
if (typeof deepLinks.metaDetailsVideos === 'string') {
|
||||
navigate(toPath(deepLinks.metaDetailsVideos));
|
||||
const path = toPath(deepLinks.metaDetailsVideos);
|
||||
navigateToRoute(navigate, location, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [streamingServer.torrent]);
|
||||
}, [streamingServer.torrent, location.pathname, location.search, navigate]);
|
||||
return null;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import Modal from './Modal';
|
|||
import Routes from './Routes';
|
||||
import Route from './Route';
|
||||
import toPath from './toPath';
|
||||
import navigateToRoute from './routePath';
|
||||
|
||||
export {
|
||||
useModalsContainer,
|
||||
|
|
@ -14,4 +15,5 @@ export {
|
|||
Routes,
|
||||
Route,
|
||||
toPath,
|
||||
navigateToRoute,
|
||||
};
|
||||
|
|
|
|||
41
src/router/routePath.ts
Normal file
41
src/router/routePath.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (C) 2017-2026 Smart code 203358507
|
||||
|
||||
import type { Location, NavigateFunction } from 'react-router-dom';
|
||||
|
||||
const decodePathname = (pathname: string): string => {
|
||||
try {
|
||||
return decodeURIComponent(pathname);
|
||||
} catch {
|
||||
return pathname;
|
||||
}
|
||||
};
|
||||
|
||||
const normalizeRoutePathname = (pathname: string): string => {
|
||||
return decodePathname(pathname)
|
||||
.replace(/^\/metadetails(?=\/|$)/, '/detail');
|
||||
};
|
||||
|
||||
const getPathname = (path: string): string => path.split(/[?#]/)[0];
|
||||
|
||||
const getSearch = (path: string): string => {
|
||||
const searchIndex = path.indexOf('?');
|
||||
return searchIndex !== -1 ? path.slice(searchIndex) : '';
|
||||
};
|
||||
|
||||
const sameRoutePathname = (location: Location, path: string): boolean => {
|
||||
return normalizeRoutePathname(location.pathname) === normalizeRoutePathname(getPathname(path));
|
||||
};
|
||||
|
||||
const sameRouteLocation = (location: Location, path: string): boolean => {
|
||||
return sameRoutePathname(location, path) && location.search === getSearch(path);
|
||||
};
|
||||
|
||||
const navigateToRoute = (navigate: NavigateFunction, location: Location, path: string): void => {
|
||||
if (!sameRouteLocation(location, path)) {
|
||||
navigate(path, {
|
||||
replace: sameRoutePathname(location, path)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default navigateToRoute;
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
const React = require('react');
|
||||
const { useParams, useLocation, useNavigate } = require('react-router');
|
||||
const { useSearchParams } = require('react-router-dom');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const classnames = require('classnames');
|
||||
const { useCore } = require('stremio/core');
|
||||
|
|
@ -14,6 +15,7 @@ const VideosList = require('./VideosList');
|
|||
const useMetaDetails = require('./useMetaDetails');
|
||||
const useSeason = require('./useSeason');
|
||||
const useMetaExtensionTabs = require('./useMetaExtensionTabs');
|
||||
const { default: useExternalPlayerCallback } = require('./useExternalPlayerCallback');
|
||||
const styles = require('./styles');
|
||||
|
||||
const GAMEPAD_HANDLER_ID = 'metadetails';
|
||||
|
|
@ -22,6 +24,7 @@ const MetaDetails = () => {
|
|||
const { type, id, videoId } = useParams();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const [queryParams] = useSearchParams();
|
||||
const { getStoredOrigin } = useNavigateWithOrigin();
|
||||
const contentRef = React.useRef(null);
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -32,6 +35,7 @@ const MetaDetails = () => {
|
|||
videoId
|
||||
}), [type, id, videoId]);
|
||||
const metaDetails = useMetaDetails(urlParams);
|
||||
useExternalPlayerCallback(urlParams, queryParams, metaDetails);
|
||||
const [season, setSeason] = useSeason(urlParams);
|
||||
const [tabs, metaExtension, clearMetaExtension] = useMetaExtensionTabs(metaDetails.metaExtensions);
|
||||
const [metaPath, streamPath] = React.useMemo(() => {
|
||||
|
|
@ -52,6 +56,11 @@ const MetaDetails = () => {
|
|||
:
|
||||
null;
|
||||
}, [metaDetails.metaItem, streamPath]);
|
||||
const externalPlayerCallbackCanMarkWatched = React.useMemo(() => {
|
||||
return typeof video?.id === 'string' &&
|
||||
metaDetails.libraryItem?.state?.video_id === video.id &&
|
||||
metaDetails.libraryItem?.state?.duration > 0;
|
||||
}, [metaDetails.libraryItem, video]);
|
||||
const addToLibrary = React.useCallback(() => {
|
||||
if (metaDetails.metaItem === null || metaDetails.metaItem.content.type !== 'Ready') {
|
||||
return;
|
||||
|
|
@ -215,6 +224,7 @@ const MetaDetails = () => {
|
|||
streams={metaDetails.streams}
|
||||
video={video}
|
||||
type={streamPath.type}
|
||||
externalPlayerCallbackCanMarkWatched={externalPlayerCallbackCanMarkWatched}
|
||||
onEpisodeSearch={handleEpisodeSearch}
|
||||
/>
|
||||
:
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const { default: useRouteFocused } = require('stremio/common/useRouteFocused');
|
|||
const StreamPlaceholder = require('./StreamPlaceholder');
|
||||
const styles = require('./styles');
|
||||
|
||||
const Stream = ({ className, videoId, videoReleased, addonName, name, description, thumbnail, progress, deepLinks, ...props }) => {
|
||||
const Stream = ({ className, videoId, videoReleased, addonName, name, description, thumbnail, progress, deepLinks, externalPlayerCallbackCanMarkWatched, ...props }) => {
|
||||
const profile = useProfile();
|
||||
const toast = useToast();
|
||||
const platform = usePlatform();
|
||||
|
|
@ -118,7 +118,9 @@ const Stream = ({ className, videoId, videoReleased, addonName, name, descriptio
|
|||
}
|
||||
|
||||
if (profile.settings.playerType !== null) {
|
||||
markVideoAsWatched();
|
||||
if (profile.settings.playerType !== 'infuse' || !externalPlayerCallbackCanMarkWatched) {
|
||||
markVideoAsWatched();
|
||||
}
|
||||
toast.show({
|
||||
type: 'success',
|
||||
title: 'Stream opened in external player',
|
||||
|
|
@ -129,7 +131,7 @@ const Stream = ({ className, videoId, videoReleased, addonName, name, descriptio
|
|||
if (typeof props.onClick === 'function') {
|
||||
props.onClick(event);
|
||||
}
|
||||
}, [props.onClick, profile.settings, markVideoAsWatched]);
|
||||
}, [props.onClick, profile.settings.playerType, externalPlayerCallbackCanMarkWatched, markVideoAsWatched]);
|
||||
|
||||
const copyMagnetLink = React.useCallback((event) => {
|
||||
event.preventDefault();
|
||||
|
|
@ -321,6 +323,7 @@ Stream.propTypes = {
|
|||
})
|
||||
})
|
||||
}),
|
||||
externalPlayerCallbackCanMarkWatched: PropTypes.bool,
|
||||
onClick: PropTypes.func
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const { default: SeasonEpisodePicker } = require('../EpisodePicker');
|
|||
|
||||
const ALL_ADDONS_KEY = 'ALL';
|
||||
|
||||
const StreamsList = ({ className, video, type, onEpisodeSearch, ...props }) => {
|
||||
const StreamsList = ({ className, video, type, externalPlayerCallbackCanMarkWatched, onEpisodeSearch, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
const core = useCore();
|
||||
const platform = usePlatform();
|
||||
|
|
@ -184,6 +184,7 @@ const StreamsList = ({ className, video, type, onEpisodeSearch, ...props }) => {
|
|||
thumbnail={stream.thumbnail}
|
||||
progress={stream.progress}
|
||||
deepLinks={stream.deepLinks}
|
||||
externalPlayerCallbackCanMarkWatched={externalPlayerCallbackCanMarkWatched}
|
||||
onClick={stream.onClick}
|
||||
/>
|
||||
))}
|
||||
|
|
@ -219,6 +220,7 @@ StreamsList.propTypes = {
|
|||
streams: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
video: PropTypes.object,
|
||||
type: PropTypes.string,
|
||||
externalPlayerCallbackCanMarkWatched: PropTypes.bool,
|
||||
onEpisodeSearch: PropTypes.func
|
||||
};
|
||||
|
||||
|
|
|
|||
59
src/routes/MetaDetails/useExternalPlayerCallback.ts
Normal file
59
src/routes/MetaDetails/useExternalPlayerCallback.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright (C) 2017-2026 Smart code 203358507
|
||||
|
||||
import React from 'react';
|
||||
import { useCore } from 'stremio/core';
|
||||
|
||||
type UrlParams = {
|
||||
type?: string,
|
||||
id?: string,
|
||||
videoId?: string,
|
||||
};
|
||||
|
||||
const useExternalPlayerCallback = (
|
||||
urlParams: UrlParams,
|
||||
searchParams: URLSearchParams,
|
||||
metaDetails: MetaDetails,
|
||||
) => {
|
||||
const core = useCore();
|
||||
const callbackRef = React.useRef<string | null>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
const position = searchParams.get('position');
|
||||
if (position === null || position === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
const positionNumber = Number(position);
|
||||
const time = Math.round(positionNumber * 1000);
|
||||
const streamPath = metaDetails.selected?.streamPath ?? null;
|
||||
const callbackKey = [
|
||||
urlParams.type,
|
||||
urlParams.id,
|
||||
urlParams.videoId,
|
||||
time,
|
||||
searchParams.get('lastPlayedUrl')
|
||||
].join(':');
|
||||
if (
|
||||
!Number.isFinite(positionNumber) ||
|
||||
positionNumber < 0 ||
|
||||
streamPath === null ||
|
||||
metaDetails.libraryItem === null ||
|
||||
callbackRef.current === callbackKey
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
callbackRef.current = callbackKey;
|
||||
core.transport.dispatch({
|
||||
action: 'MetaDetails',
|
||||
args: {
|
||||
action: 'ExternalPlayerProgressChanged',
|
||||
args: {
|
||||
time
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [urlParams, searchParams, metaDetails]);
|
||||
};
|
||||
|
||||
export default useExternalPlayerCallback;
|
||||
Loading…
Reference in a new issue