mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-10 03:50:52 +00:00
some ui changes
This commit is contained in:
parent
4f1b8103d0
commit
ca52a81141
7 changed files with 192 additions and 30 deletions
|
|
@ -124,8 +124,9 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const shouldFetchMeta = stremioService.isValidContentId(type, id);
|
||||||
const [metadata, basicContent] = await Promise.all([
|
const [metadata, basicContent] = await Promise.all([
|
||||||
stremioService.getMetaDetails(type, id),
|
shouldFetchMeta ? stremioService.getMetaDetails(type, id) : Promise.resolve(null),
|
||||||
catalogService.getBasicContentDetails(type, id)
|
catalogService.getBasicContentDetails(type, id)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -656,7 +656,7 @@ const WatchProgressDisplay = memo(({
|
||||||
<Text style={[isTablet ? styles.tabletWatchProgressSubText : styles.watchProgressSubText, {
|
<Text style={[isTablet ? styles.tabletWatchProgressSubText : styles.watchProgressSubText, {
|
||||||
color: isCompleted ? 'rgba(0,255,136,0.7)' : currentTheme.colors.textMuted,
|
color: isCompleted ? 'rgba(0,255,136,0.7)' : currentTheme.colors.textMuted,
|
||||||
}]}>
|
}]}>
|
||||||
{progressData.episodeInfo} • {progressData.formattedTime}
|
{progressData.episodeInfo}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{/* Trakt sync status with enhanced styling */}
|
{/* Trakt sync status with enhanced styling */}
|
||||||
|
|
|
||||||
|
|
@ -521,6 +521,9 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
// Volume and brightness controls
|
// Volume and brightness controls
|
||||||
const [volume, setVolume] = useState(1.0);
|
const [volume, setVolume] = useState(1.0);
|
||||||
const [brightness, setBrightness] = useState(1.0);
|
const [brightness, setBrightness] = useState(1.0);
|
||||||
|
// Store Android system brightness state to restore on exit/unmount
|
||||||
|
const originalSystemBrightnessRef = useRef<number | null>(null);
|
||||||
|
const originalSystemBrightnessModeRef = useRef<number | null>(null);
|
||||||
const [showVolumeOverlay, setShowVolumeOverlay] = useState(false);
|
const [showVolumeOverlay, setShowVolumeOverlay] = useState(false);
|
||||||
const [showBrightnessOverlay, setShowBrightnessOverlay] = useState(false);
|
const [showBrightnessOverlay, setShowBrightnessOverlay] = useState(false);
|
||||||
const [subtitleSettingsLoaded, setSubtitleSettingsLoaded] = useState(false);
|
const [subtitleSettingsLoaded, setSubtitleSettingsLoaded] = useState(false);
|
||||||
|
|
@ -848,6 +851,22 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Capture Android system brightness and mode to restore later
|
||||||
|
if (Platform.OS === 'android') {
|
||||||
|
try {
|
||||||
|
const [sysBright, sysMode] = await Promise.all([
|
||||||
|
(Brightness as any).getSystemBrightnessAsync?.(),
|
||||||
|
(Brightness as any).getSystemBrightnessModeAsync?.()
|
||||||
|
]);
|
||||||
|
originalSystemBrightnessRef.current = typeof sysBright === 'number' ? sysBright : null;
|
||||||
|
originalSystemBrightnessModeRef.current = typeof sysMode === 'number' ? sysMode : null;
|
||||||
|
if (DEBUG_MODE) {
|
||||||
|
logger.log(`[AndroidVideoPlayer] Captured system brightness=${originalSystemBrightnessRef.current}, mode=${originalSystemBrightnessModeRef.current}`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (__DEV__) logger.warn('[AndroidVideoPlayer] Failed to capture system brightness state:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
const currentBrightness = await Brightness.getBrightnessAsync();
|
const currentBrightness = await Brightness.getBrightnessAsync();
|
||||||
setBrightness(currentBrightness);
|
setBrightness(currentBrightness);
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
|
|
@ -1694,6 +1713,27 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
|
|
||||||
logger.log(`[AndroidVideoPlayer] Current progress: ${actualCurrentTime}/${duration} (${progressPercent.toFixed(1)}%)`);
|
logger.log(`[AndroidVideoPlayer] Current progress: ${actualCurrentTime}/${duration} (${progressPercent.toFixed(1)}%)`);
|
||||||
|
|
||||||
|
// Restore Android system brightness state so app does not lock brightness
|
||||||
|
const restoreSystemBrightness = async () => {
|
||||||
|
if (Platform.OS !== 'android') return;
|
||||||
|
try {
|
||||||
|
// Restore mode first (if available), then brightness value
|
||||||
|
if (originalSystemBrightnessModeRef.current !== null && typeof (Brightness as any).setSystemBrightnessModeAsync === 'function') {
|
||||||
|
await (Brightness as any).setSystemBrightnessModeAsync(originalSystemBrightnessModeRef.current);
|
||||||
|
}
|
||||||
|
if (originalSystemBrightnessRef.current !== null && typeof (Brightness as any).setSystemBrightnessAsync === 'function') {
|
||||||
|
await (Brightness as any).setSystemBrightnessAsync(originalSystemBrightnessRef.current);
|
||||||
|
}
|
||||||
|
if (DEBUG_MODE) {
|
||||||
|
logger.log('[AndroidVideoPlayer] Restored Android system brightness and mode');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn('[AndroidVideoPlayer] Failed to restore system brightness state:', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await restoreSystemBrightness();
|
||||||
|
|
||||||
// Navigate immediately without delay
|
// Navigate immediately without delay
|
||||||
ScreenOrientation.unlockAsync().then(() => {
|
ScreenOrientation.unlockAsync().then(() => {
|
||||||
// On tablets keep rotation unlocked; on phones, return to portrait
|
// On tablets keep rotation unlocked; on phones, return to portrait
|
||||||
|
|
@ -2227,6 +2267,16 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
}
|
}
|
||||||
}, [useVLC, selectVlcSubtitleTrack]);
|
}, [useVLC, selectVlcSubtitleTrack]);
|
||||||
|
|
||||||
|
const disableCustomSubtitles = useCallback(() => {
|
||||||
|
setUseCustomSubtitles(false);
|
||||||
|
setCustomSubtitles([]);
|
||||||
|
// Reset to first available built-in track or disable all tracks
|
||||||
|
if (useVLC) {
|
||||||
|
selectVlcSubtitleTrack(ksTextTracks.length > 0 ? 0 : null);
|
||||||
|
}
|
||||||
|
setSelectedTextTrack(ksTextTracks.length > 0 ? 0 : -1);
|
||||||
|
}, [useVLC, selectVlcSubtitleTrack, ksTextTracks.length]);
|
||||||
|
|
||||||
const loadSubtitleSize = async () => {
|
const loadSubtitleSize = async () => {
|
||||||
try {
|
try {
|
||||||
// Prefer scoped subtitle settings
|
// Prefer scoped subtitle settings
|
||||||
|
|
@ -2760,6 +2810,19 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
clearInterval(progressSaveInterval);
|
clearInterval(progressSaveInterval);
|
||||||
setProgressSaveInterval(null);
|
setProgressSaveInterval(null);
|
||||||
}
|
}
|
||||||
|
// Best-effort restore of Android system brightness state on unmount
|
||||||
|
if (Platform.OS === 'android') {
|
||||||
|
try {
|
||||||
|
if (originalSystemBrightnessModeRef.current !== null && typeof (Brightness as any).setSystemBrightnessModeAsync === 'function') {
|
||||||
|
(Brightness as any).setSystemBrightnessModeAsync(originalSystemBrightnessModeRef.current);
|
||||||
|
}
|
||||||
|
if (originalSystemBrightnessRef.current !== null && typeof (Brightness as any).setSystemBrightnessAsync === 'function') {
|
||||||
|
(Brightness as any).setSystemBrightnessAsync(originalSystemBrightnessRef.current);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn('[AndroidVideoPlayer] Failed to restore system brightness on unmount:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
@ -4018,6 +4081,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
fetchAvailableSubtitles={fetchAvailableSubtitles}
|
fetchAvailableSubtitles={fetchAvailableSubtitles}
|
||||||
loadWyzieSubtitle={loadWyzieSubtitle}
|
loadWyzieSubtitle={loadWyzieSubtitle}
|
||||||
selectTextTrack={selectTextTrack}
|
selectTextTrack={selectTextTrack}
|
||||||
|
disableCustomSubtitles={disableCustomSubtitles}
|
||||||
increaseSubtitleSize={increaseSubtitleSize}
|
increaseSubtitleSize={increaseSubtitleSize}
|
||||||
decreaseSubtitleSize={decreaseSubtitleSize}
|
decreaseSubtitleSize={decreaseSubtitleSize}
|
||||||
toggleSubtitleBackground={toggleSubtitleBackground}
|
toggleSubtitleBackground={toggleSubtitleBackground}
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,14 @@ const KSPlayerCore: React.FC = () => {
|
||||||
const [isLoadingSubtitleList, setIsLoadingSubtitleList] = useState<boolean>(false);
|
const [isLoadingSubtitleList, setIsLoadingSubtitleList] = useState<boolean>(false);
|
||||||
const [showSourcesModal, setShowSourcesModal] = useState<boolean>(false);
|
const [showSourcesModal, setShowSourcesModal] = useState<boolean>(false);
|
||||||
const [availableStreams, setAvailableStreams] = useState<{ [providerId: string]: { streams: any[]; addonName: string } }>(passedAvailableStreams || {});
|
const [availableStreams, setAvailableStreams] = useState<{ [providerId: string]: { streams: any[]; addonName: string } }>(passedAvailableStreams || {});
|
||||||
|
// Playback speed controls required by PlayerControls
|
||||||
|
const speedOptions = [0.5, 1.0, 1.25, 1.5, 2.0, 2.5, 3.0];
|
||||||
|
const [playbackSpeed, setPlaybackSpeed] = useState<number>(1.0);
|
||||||
|
const cyclePlaybackSpeed = useCallback(() => {
|
||||||
|
const idx = speedOptions.indexOf(playbackSpeed);
|
||||||
|
const nextIdx = (idx + 1) % speedOptions.length;
|
||||||
|
setPlaybackSpeed(speedOptions[nextIdx]);
|
||||||
|
}, [playbackSpeed, speedOptions]);
|
||||||
// Smart URL processing for KSPlayer compatibility
|
// Smart URL processing for KSPlayer compatibility
|
||||||
const processUrlForKsPlayer = (url: string): string => {
|
const processUrlForKsPlayer = (url: string): string => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -1640,6 +1648,13 @@ const KSPlayerCore: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const disableCustomSubtitles = () => {
|
||||||
|
setUseCustomSubtitles(false);
|
||||||
|
setCustomSubtitles([]);
|
||||||
|
// Reset to first available built-in track or disable all tracks
|
||||||
|
setSelectedTextTrack(ksTextTracks.length > 0 ? 0 : -1);
|
||||||
|
};
|
||||||
|
|
||||||
// Ensure native KSPlayer text tracks are disabled when using custom (addon) subtitles
|
// Ensure native KSPlayer text tracks are disabled when using custom (addon) subtitles
|
||||||
// and re-applied when switching back to built-in tracks. This prevents double-rendering.
|
// and re-applied when switching back to built-in tracks. This prevents double-rendering.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -2687,6 +2702,8 @@ const KSPlayerCore: React.FC = () => {
|
||||||
onSlidingComplete={handleSlidingComplete}
|
onSlidingComplete={handleSlidingComplete}
|
||||||
buffered={buffered}
|
buffered={buffered}
|
||||||
formatTime={formatTime}
|
formatTime={formatTime}
|
||||||
|
cyclePlaybackSpeed={cyclePlaybackSpeed}
|
||||||
|
currentPlaybackSpeed={playbackSpeed}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{showPauseOverlay && (
|
{showPauseOverlay && (
|
||||||
|
|
@ -3290,6 +3307,7 @@ const KSPlayerCore: React.FC = () => {
|
||||||
fetchAvailableSubtitles={fetchAvailableSubtitles}
|
fetchAvailableSubtitles={fetchAvailableSubtitles}
|
||||||
loadWyzieSubtitle={loadWyzieSubtitle}
|
loadWyzieSubtitle={loadWyzieSubtitle}
|
||||||
selectTextTrack={selectTextTrack}
|
selectTextTrack={selectTextTrack}
|
||||||
|
disableCustomSubtitles={disableCustomSubtitles}
|
||||||
increaseSubtitleSize={increaseSubtitleSize}
|
increaseSubtitleSize={increaseSubtitleSize}
|
||||||
decreaseSubtitleSize={decreaseSubtitleSize}
|
decreaseSubtitleSize={decreaseSubtitleSize}
|
||||||
toggleSubtitleBackground={toggleSubtitleBackground}
|
toggleSubtitleBackground={toggleSubtitleBackground}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ interface SubtitleModalsProps {
|
||||||
fetchAvailableSubtitles: () => void;
|
fetchAvailableSubtitles: () => void;
|
||||||
loadWyzieSubtitle: (subtitle: WyzieSubtitle) => void;
|
loadWyzieSubtitle: (subtitle: WyzieSubtitle) => void;
|
||||||
selectTextTrack: (trackId: number) => void;
|
selectTextTrack: (trackId: number) => void;
|
||||||
|
disableCustomSubtitles: () => void;
|
||||||
increaseSubtitleSize: () => void;
|
increaseSubtitleSize: () => void;
|
||||||
decreaseSubtitleSize: () => void;
|
decreaseSubtitleSize: () => void;
|
||||||
toggleSubtitleBackground: () => void;
|
toggleSubtitleBackground: () => void;
|
||||||
|
|
@ -79,6 +80,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
||||||
fetchAvailableSubtitles,
|
fetchAvailableSubtitles,
|
||||||
loadWyzieSubtitle,
|
loadWyzieSubtitle,
|
||||||
selectTextTrack,
|
selectTextTrack,
|
||||||
|
disableCustomSubtitles,
|
||||||
increaseSubtitleSize,
|
increaseSubtitleSize,
|
||||||
decreaseSubtitleSize,
|
decreaseSubtitleSize,
|
||||||
toggleSubtitleBackground,
|
toggleSubtitleBackground,
|
||||||
|
|
@ -162,6 +164,22 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
||||||
loadWyzieSubtitle(subtitle);
|
loadWyzieSubtitle(subtitle);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getFileNameFromUrl = (url?: string): string | null => {
|
||||||
|
if (!url || typeof url !== 'string') return null;
|
||||||
|
try {
|
||||||
|
// Prefer URL parsing to safely strip query/hash
|
||||||
|
const u = new URL(url);
|
||||||
|
const raw = u.pathname.split('/').pop() || '';
|
||||||
|
const decoded = decodeURIComponent(raw);
|
||||||
|
return decoded || null;
|
||||||
|
} catch {
|
||||||
|
// Fallback for non-standard URLs
|
||||||
|
const path = url.split('?')[0].split('#')[0];
|
||||||
|
const raw = path.split('/').pop() || '';
|
||||||
|
try { return decodeURIComponent(raw) || null; } catch { return raw || null; }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Main subtitle menu
|
// Main subtitle menu
|
||||||
const renderSubtitleMenu = () => {
|
const renderSubtitleMenu = () => {
|
||||||
if (!showSubtitleModal) return null;
|
if (!showSubtitleModal) return null;
|
||||||
|
|
@ -383,32 +401,61 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
||||||
}}>
|
}}>
|
||||||
Addon Subtitles
|
Addon Subtitles
|
||||||
</Text>
|
</Text>
|
||||||
<TouchableOpacity
|
<View style={{ flexDirection: 'row', gap: 8 }}>
|
||||||
style={{
|
{useCustomSubtitles && (
|
||||||
backgroundColor: 'rgba(34, 197, 94, 0.15)',
|
<TouchableOpacity
|
||||||
borderRadius: 12,
|
style={{
|
||||||
paddingHorizontal: chipPadH,
|
backgroundColor: 'rgba(239, 68, 68, 0.15)',
|
||||||
paddingVertical: chipPadV-2,
|
borderRadius: 12,
|
||||||
flexDirection: 'row',
|
paddingHorizontal: chipPadH,
|
||||||
alignItems: 'center',
|
paddingVertical: chipPadV-2,
|
||||||
}}
|
flexDirection: 'row',
|
||||||
onPress={() => fetchAvailableSubtitles()}
|
alignItems: 'center',
|
||||||
disabled={isLoadingSubtitleList}
|
}}
|
||||||
>
|
onPress={() => {
|
||||||
{isLoadingSubtitleList ? (
|
disableCustomSubtitles();
|
||||||
<ActivityIndicator size="small" color="#22C55E" />
|
setSelectedOnlineSubtitleId(null);
|
||||||
) : (
|
}}
|
||||||
<MaterialIcons name="refresh" size={16} color="#22C55E" />
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<MaterialIcons name="close" size={16} color="#EF4444" />
|
||||||
|
<Text style={{
|
||||||
|
color: '#EF4444',
|
||||||
|
fontSize: isCompact ? 11 : 12,
|
||||||
|
fontWeight: '600',
|
||||||
|
marginLeft: 6,
|
||||||
|
}}>
|
||||||
|
Disable
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
<Text style={{
|
<TouchableOpacity
|
||||||
color: '#22C55E',
|
style={{
|
||||||
fontSize: isCompact ? 11 : 12,
|
backgroundColor: 'rgba(34, 197, 94, 0.15)',
|
||||||
fontWeight: '600',
|
borderRadius: 12,
|
||||||
marginLeft: 6,
|
paddingHorizontal: chipPadH,
|
||||||
}}>
|
paddingVertical: chipPadV-2,
|
||||||
{isLoadingSubtitleList ? 'Searching' : 'Refresh'}
|
flexDirection: 'row',
|
||||||
</Text>
|
alignItems: 'center',
|
||||||
</TouchableOpacity>
|
}}
|
||||||
|
onPress={() => fetchAvailableSubtitles()}
|
||||||
|
disabled={isLoadingSubtitleList}
|
||||||
|
>
|
||||||
|
{isLoadingSubtitleList ? (
|
||||||
|
<ActivityIndicator size="small" color="#22C55E" />
|
||||||
|
) : (
|
||||||
|
<MaterialIcons name="refresh" size={16} color="#22C55E" />
|
||||||
|
)}
|
||||||
|
<Text style={{
|
||||||
|
color: '#22C55E',
|
||||||
|
fontSize: isCompact ? 11 : 12,
|
||||||
|
fontWeight: '600',
|
||||||
|
marginLeft: 6,
|
||||||
|
}}>
|
||||||
|
{isLoadingSubtitleList ? 'Searching' : 'Refresh'}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{(availableSubtitles.length === 0) && !isLoadingSubtitleList ? (
|
{(availableSubtitles.length === 0) && !isLoadingSubtitleList ? (
|
||||||
|
|
@ -473,10 +520,19 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
||||||
>
|
>
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
<Text style={{ color: '#FFFFFF', fontSize: 15, fontWeight: '500', marginBottom: 4 }}>
|
<Text style={{ color: '#FFFFFF', fontSize: 15, fontWeight: '500' }}>
|
||||||
{sub.display}
|
{sub.display}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={{ color: 'rgba(255, 255, 255, 0.6)', fontSize: 13 }}>
|
{(() => {
|
||||||
|
const filename = getFileNameFromUrl(sub.url);
|
||||||
|
if (!filename) return null;
|
||||||
|
return (
|
||||||
|
<Text style={{ color: 'rgba(255,255,255,0.75)', fontSize: 12, marginTop: 2 }} numberOfLines={1}>
|
||||||
|
{filename}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
<Text style={{ color: 'rgba(255, 255, 255, 0.6)', fontSize: 13, marginTop: 2 }}>
|
||||||
{formatLanguage(sub.language)}{sub.source ? ` · ${sub.source}` : ''}
|
{formatLanguage(sub.language)}{sub.source ? ` · ${sub.source}` : ''}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
||||||
|
|
@ -489,6 +489,10 @@ class CatalogService {
|
||||||
|
|
||||||
for (let i = 0; i < 2; i++) {
|
for (let i = 0; i < 2; i++) {
|
||||||
try {
|
try {
|
||||||
|
// Skip meta requests for non-content ids (e.g., provider slugs)
|
||||||
|
if (!stremioService.isValidContentId(type, id)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
meta = await stremioService.getMetaDetails(type, id, preferredAddonId);
|
meta = await stremioService.getMetaDetails(type, id, preferredAddonId);
|
||||||
if (meta) break;
|
if (meta) break;
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
|
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
|
||||||
|
|
@ -536,6 +540,10 @@ class CatalogService {
|
||||||
|
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
try {
|
try {
|
||||||
|
// Skip meta requests for non-content ids (e.g., provider slugs)
|
||||||
|
if (!stremioService.isValidContentId(type, id)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
meta = await stremioService.getMetaDetails(type, id, preferredAddonId);
|
meta = await stremioService.getMetaDetails(type, id, preferredAddonId);
|
||||||
if (meta) break;
|
if (meta) break;
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
|
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,21 @@ class StremioService {
|
||||||
this.initializationPromise = this.initialize();
|
this.initializationPromise = this.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shared validator for content IDs eligible for metadata requests
|
||||||
|
public isValidContentId(type: string, id: string | null | undefined): boolean {
|
||||||
|
const isValidType = type === 'movie' || type === 'series';
|
||||||
|
const lowerId = (id || '').toLowerCase();
|
||||||
|
const looksLikeImdb = /^tt\d+/.test(lowerId);
|
||||||
|
const looksLikeKitsu = lowerId.startsWith('kitsu:') || lowerId === 'kitsu';
|
||||||
|
const looksLikeSeriesId = lowerId.startsWith('series:');
|
||||||
|
const isNullishId = !id || lowerId === 'null' || lowerId === 'undefined';
|
||||||
|
const providerLikeIds = new Set<string>(['moviebox', 'torbox']);
|
||||||
|
const isProviderSlug = providerLikeIds.has(lowerId);
|
||||||
|
|
||||||
|
if (!isValidType || isNullishId || isProviderSlug) return false;
|
||||||
|
return looksLikeImdb || looksLikeKitsu || looksLikeSeriesId;
|
||||||
|
}
|
||||||
|
|
||||||
static getInstance(): StremioService {
|
static getInstance(): StremioService {
|
||||||
if (!StremioService.instance) {
|
if (!StremioService.instance) {
|
||||||
StremioService.instance = new StremioService();
|
StremioService.instance = new StremioService();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue