mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-28 13:28:48 +00:00
many major fixes
This commit is contained in:
parent
6bb4d927ed
commit
c92dfb149c
14 changed files with 130 additions and 51 deletions
|
|
@ -129,14 +129,16 @@ const UpdatePopup: React.FC<UpdatePopupProps> = ({
|
|||
]}>
|
||||
Version:
|
||||
</Text>
|
||||
<Text style={[
|
||||
styles.infoValue,
|
||||
{ color: currentTheme.colors.highEmphasis }
|
||||
]}>
|
||||
{updateInfo.manifest?.id ?
|
||||
`${updateInfo.manifest.id.substring(0, 8)}...` :
|
||||
'Latest'
|
||||
}
|
||||
<Text
|
||||
style={[
|
||||
styles.infoValue,
|
||||
{ color: currentTheme.colors.highEmphasis }
|
||||
]}
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="middle"
|
||||
selectable
|
||||
>
|
||||
{updateInfo.manifest?.id || 'Latest'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ const OptimizedImage: React.FC<OptimizedImageProps> = ({
|
|||
if (mountedRef.current) {
|
||||
setIsVisible(true);
|
||||
}
|
||||
}, priority === 'high' ? 100 : priority === 'normal' ? 300 : 500);
|
||||
}, priority === 'high' ? 200 : priority === 'normal' ? 500 : 1000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
|
|
@ -124,8 +124,8 @@ const OptimizedImage: React.FC<OptimizedImageProps> = ({
|
|||
}
|
||||
}, 10000); // 10 second timeout
|
||||
|
||||
// Prefetch the image
|
||||
await ExpoImage.prefetch(cachedUrl);
|
||||
// Skip prefetch to reduce memory pressure and heating
|
||||
// await ExpoImage.prefetch(cachedUrl);
|
||||
|
||||
if (mountedRef.current) {
|
||||
setIsLoaded(true);
|
||||
|
|
|
|||
|
|
@ -1080,19 +1080,19 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
|
|||
};
|
||||
}, [imageOpacity, imageLoadOpacity, shimmerOpacity, trailerOpacity, thumbnailOpacity, actionButtonsOpacity, titleCardTranslateY, genreOpacity, watchProgressOpacity, buttonsOpacity, buttonsTranslateY, logoOpacity, heroOpacity, heroHeight]);
|
||||
|
||||
// Development-only performance monitoring
|
||||
useEffect(() => {
|
||||
if (__DEV__) {
|
||||
const startTime = Date.now();
|
||||
const timer = setTimeout(() => {
|
||||
const renderTime = Date.now() - startTime;
|
||||
if (renderTime > 100) {
|
||||
console.warn(`[HeroSection] Slow render detected: ${renderTime}ms`);
|
||||
}
|
||||
}, 0);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
});
|
||||
// Disabled performance monitoring to reduce CPU overhead in production
|
||||
// useEffect(() => {
|
||||
// if (__DEV__) {
|
||||
// const startTime = Date.now();
|
||||
// const timer = setTimeout(() => {
|
||||
// const renderTime = Date.now() - startTime;
|
||||
// if (renderTime > 100) {
|
||||
// console.warn(`[HeroSection] Slow render detected: ${renderTime}ms`);
|
||||
// }
|
||||
// }, 0);
|
||||
// return () => clearTimeout(timer);
|
||||
// }
|
||||
// });
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -557,8 +557,8 @@ export function useFeaturedContent() {
|
|||
}
|
||||
};
|
||||
|
||||
// Increased rotation interval from 15s to 45s to reduce heating
|
||||
const intervalId = setInterval(rotateContent, 45000);
|
||||
// Further increased rotation interval to 90s to reduce CPU cycles
|
||||
const intervalId = setInterval(rotateContent, 90000);
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
}, [allFeaturedContent]);
|
||||
|
|
|
|||
|
|
@ -608,6 +608,28 @@ const AddonsScreen = () => {
|
|||
const [communityLoading, setCommunityLoading] = useState(true);
|
||||
const [communityError, setCommunityError] = useState<string | null>(null);
|
||||
|
||||
// Promotional addon: Nuvio Streams
|
||||
const PROMO_ADDON_URL = 'https://nuviostreams.hayd.uk/manifest.json';
|
||||
const promoAddon: ExtendedManifest = {
|
||||
id: 'org.nuvio.streams',
|
||||
name: 'Nuvio Streams | Elfhosted',
|
||||
version: '0.5.0',
|
||||
description: 'Stremio addon for high-quality streaming links.',
|
||||
// @ts-ignore - logo not in base manifest type
|
||||
logo: 'https://raw.githubusercontent.com/tapframe/NuvioStreaming/refs/heads/appstore/assets/titlelogo.png',
|
||||
types: ['movie', 'series'],
|
||||
catalogs: [],
|
||||
behaviorHints: { configurable: true },
|
||||
// help handleConfigureAddon derive configure URL from the transport
|
||||
transport: PROMO_ADDON_URL,
|
||||
} as ExtendedManifest;
|
||||
const isPromoInstalled = addons.some(a =>
|
||||
a.id === 'org.nuvio.streams' ||
|
||||
(typeof a.id === 'string' && a.id.includes('nuviostreams.hayd.uk')) ||
|
||||
(typeof a.transport === 'string' && a.transport.includes('nuviostreams.hayd.uk')) ||
|
||||
(typeof (a as any).url === 'string' && (a as any).url.includes('nuviostreams.hayd.uk'))
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
loadAddons();
|
||||
loadCommunityAddons();
|
||||
|
|
@ -1129,6 +1151,7 @@ const AddonsScreen = () => {
|
|||
showsVerticalScrollIndicator={false}
|
||||
contentInsetAdjustmentBehavior="automatic"
|
||||
>
|
||||
|
||||
{/* Overview Section */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>OVERVIEW</Text>
|
||||
|
|
@ -1195,6 +1218,65 @@ const AddonsScreen = () => {
|
|||
{/* Separator */}
|
||||
<View style={styles.sectionSeparator} />
|
||||
|
||||
{/* Promotional Addon Section (hidden if installed) */}
|
||||
{!isPromoInstalled && (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>OFFICIAL ADDON</Text>
|
||||
<View style={styles.addonList}>
|
||||
<View style={styles.addonItem}>
|
||||
<View style={styles.addonHeader}>
|
||||
{promoAddon.logo ? (
|
||||
<ExpoImage
|
||||
source={{ uri: promoAddon.logo }}
|
||||
style={styles.addonIcon}
|
||||
contentFit="contain"
|
||||
/>
|
||||
) : (
|
||||
<View style={styles.addonIconPlaceholder}>
|
||||
<MaterialIcons name="extension" size={22} color={colors.mediumGray} />
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.addonTitleContainer}>
|
||||
<Text style={styles.addonName}>{promoAddon.name}</Text>
|
||||
<View style={styles.addonMetaContainer}>
|
||||
<Text style={styles.addonVersion}>v{promoAddon.version}</Text>
|
||||
<Text style={styles.addonDot}>•</Text>
|
||||
<Text style={styles.addonCategory}>{promoAddon.types?.map(t => t.charAt(0).toUpperCase() + t.slice(1)).join(' • ')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.addonActions}>
|
||||
{promoAddon.behaviorHints?.configurable && (
|
||||
<TouchableOpacity
|
||||
style={styles.configButton}
|
||||
onPress={() => handleConfigureAddon(promoAddon, PROMO_ADDON_URL)}
|
||||
>
|
||||
<MaterialIcons name="settings" size={20} color={colors.primary} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<TouchableOpacity
|
||||
style={styles.installButton}
|
||||
onPress={() => handleAddAddon(PROMO_ADDON_URL)}
|
||||
disabled={installing}
|
||||
>
|
||||
{installing ? (
|
||||
<ActivityIndicator size="small" color={colors.white} />
|
||||
) : (
|
||||
<MaterialIcons name="add" size={20} color={colors.white} />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
<Text style={styles.addonDescription}>
|
||||
{promoAddon.description}
|
||||
</Text>
|
||||
<Text style={[styles.addonDescription, { marginTop: 4, opacity: 0.9 }]}>
|
||||
Configure and install for full functionality.
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Community Addons Section */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>COMMUNITY ADDONS</Text>
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ const UpdateScreen: React.FC = () => {
|
|||
|
||||
if (info.isAvailable) {
|
||||
setUpdateStatus('available');
|
||||
setLastOperation(`Update available: ${info.manifest?.id?.substring(0, 8) || 'unknown'}...`);
|
||||
setLastOperation(`Update available: ${info.manifest?.id || 'unknown'}`);
|
||||
} else {
|
||||
setUpdateStatus('idle');
|
||||
setLastOperation('No updates available');
|
||||
|
|
@ -484,8 +484,9 @@ const UpdateScreen: React.FC = () => {
|
|||
<MaterialIcons name="verified" size={14} color={currentTheme.colors.primary} />
|
||||
</View>
|
||||
<Text style={[styles.infoLabel, { color: currentTheme.colors.mediumEmphasis }]}>Current version:</Text>
|
||||
<Text style={[styles.infoValue, { color: currentTheme.colors.highEmphasis }]}>
|
||||
{currentInfo?.manifest?.id ? `${currentInfo.manifest.id.substring(0, 8)}...` : (currentInfo?.isEmbeddedLaunch === false ? 'Unknown' : 'Embedded')}
|
||||
<Text style={[styles.infoValue, { color: currentTheme.colors.highEmphasis }]}
|
||||
selectable>
|
||||
{currentInfo?.manifest?.id || (currentInfo?.isEmbeddedLaunch === false ? 'Unknown' : 'Embedded')}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ class SyncService {
|
|||
} catch (e) {
|
||||
// silent
|
||||
}
|
||||
}, 14400000);
|
||||
}, 21600000); // Increased from 4 hours to 6 hours to reduce background CPU
|
||||
};
|
||||
|
||||
unsubscribeRealtime = (): void => {
|
||||
|
|
|
|||
|
|
@ -482,11 +482,11 @@ class CatalogService {
|
|||
|
||||
async getContentDetails(type: string, id: string, preferredAddonId?: string): Promise<StreamingContent | null> {
|
||||
try {
|
||||
// Try up to 3 times with increasing delays
|
||||
// Try up to 2 times with increasing delays to reduce CPU load
|
||||
let meta = null;
|
||||
let lastError = null;
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
try {
|
||||
meta = await stremioService.getMetaDetails(type, id, preferredAddonId);
|
||||
if (meta) break;
|
||||
|
|
|
|||
|
|
@ -189,9 +189,7 @@ class ImageCacheService {
|
|||
removedCount++;
|
||||
}
|
||||
|
||||
if (removedCount > 0) {
|
||||
logger.log(`[ImageCache] Evicted ${removedCount} entries to free memory. Current usage: ${(this.currentMemoryUsage / 1024 / 1024).toFixed(1)}MB`);
|
||||
}
|
||||
// Skip verbose memory eviction logging to reduce CPU load
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -236,9 +234,7 @@ class ImageCacheService {
|
|||
const finalSize = this.cache.size;
|
||||
const finalMemory = this.currentMemoryUsage;
|
||||
|
||||
if (initialSize !== finalSize || Math.abs(initialMemory - finalMemory) > 1024 * 1024) {
|
||||
logger.log(`[ImageCache] Cleanup completed: ${initialSize}→${finalSize} entries, ${(initialMemory / 1024 / 1024).toFixed(1)}→${(finalMemory / 1024 / 1024).toFixed(1)}MB`);
|
||||
}
|
||||
// Skip verbose cleanup logging to reduce CPU load
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -977,7 +977,7 @@ class LocalScraperService {
|
|||
throw new Error(`No code found for scraper ${scraper.id}`);
|
||||
}
|
||||
|
||||
logger.log('[LocalScraperService] Executing scraper:', scraper.name);
|
||||
// Skip verbose logging to reduce CPU load
|
||||
|
||||
// Load per-scraper settings
|
||||
const scraperSettings = await this.getScraperSettings(scraper.id);
|
||||
|
|
@ -999,7 +999,7 @@ class LocalScraperService {
|
|||
callback(streams, scraper.id, scraper.name, null);
|
||||
}
|
||||
|
||||
logger.log('[LocalScraperService] Scraper', scraper.name, 'returned', streams.length, 'streams');
|
||||
// Skip verbose logging to reduce CPU load
|
||||
|
||||
} catch (error) {
|
||||
logger.error('[LocalScraperService] Scraper', scraper.name, 'failed:', error);
|
||||
|
|
|
|||
|
|
@ -285,14 +285,14 @@ class NotificationService {
|
|||
|
||||
// Setup background sync for notifications
|
||||
private setupBackgroundSync(): void {
|
||||
// Sync notifications every 6 hours
|
||||
// Sync notifications every 12 hours to reduce background CPU usage
|
||||
this.backgroundSyncInterval = setInterval(async () => {
|
||||
if (this.settings.enabled) {
|
||||
// Reduced logging verbosity
|
||||
// logger.log('[NotificationService] Running background notification sync');
|
||||
await this.performBackgroundSync();
|
||||
}
|
||||
}, 6 * 60 * 60 * 1000); // 6 hours
|
||||
}, 12 * 60 * 60 * 1000); // 12 hours
|
||||
}
|
||||
|
||||
// Setup app state handling for foreground sync
|
||||
|
|
@ -326,8 +326,8 @@ class NotificationService {
|
|||
|
||||
for (const series of seriesItems) {
|
||||
await this.updateNotificationsForSeries(series.id);
|
||||
// Small delay to prevent overwhelming the API
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
// Longer delay to prevent overwhelming the API and reduce heating
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
|
||||
// Reduced logging verbosity
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ interface TraktCollections {
|
|||
|
||||
const THIS_WEEK_CACHE_KEY = 'this_week_episodes_cache';
|
||||
const CALENDAR_CACHE_KEY = 'calendar_data_cache';
|
||||
const CACHE_DURATION_MS = 15 * 60 * 1000; // 15 minutes
|
||||
const CACHE_DURATION_MS = 30 * 60 * 1000; // 30 minutes (increased to reduce API calls)
|
||||
const ERROR_CACHE_DURATION_MS = 5 * 60 * 1000; // 5 minutes for error recovery
|
||||
|
||||
class RobustCalendarCache {
|
||||
|
|
|
|||
0
src/services/seriesGraphService.ts
Normal file
0
src/services/seriesGraphService.ts
Normal file
|
|
@ -260,7 +260,7 @@ export class TraktService {
|
|||
|
||||
// Rate limiting
|
||||
private lastApiCall: number = 0;
|
||||
private readonly MIN_API_INTERVAL = 1000; // Minimum 1 second between API calls
|
||||
private readonly MIN_API_INTERVAL = 2000; // Minimum 2 seconds between API calls (reduce heating)
|
||||
private requestQueue: Array<() => Promise<any>> = [];
|
||||
private isProcessingQueue: boolean = false;
|
||||
|
||||
|
|
@ -272,11 +272,11 @@ export class TraktService {
|
|||
// Track currently watching sessions to avoid duplicate starts// Sync debouncing
|
||||
private currentlyWatching: Set<string> = new Set();
|
||||
private lastSyncTimes: Map<string, number> = new Map();
|
||||
private readonly SYNC_DEBOUNCE_MS = 1000; // 1 second for immediate sync
|
||||
private readonly SYNC_DEBOUNCE_MS = 15000; // 15 seconds to align with player save interval
|
||||
|
||||
// Debounce for stop calls
|
||||
private lastStopCalls: Map<string, number> = new Map();
|
||||
private readonly STOP_DEBOUNCE_MS = 1000; // 1 second debounce for immediate stop calls
|
||||
private readonly STOP_DEBOUNCE_MS = 3000; // 3 seconds to avoid duplicate stop calls
|
||||
|
||||
// Default completion threshold (overridden by user settings)
|
||||
private readonly DEFAULT_COMPLETION_THRESHOLD = 80; // 80%
|
||||
|
|
@ -356,9 +356,7 @@ export class TraktService {
|
|||
}
|
||||
}
|
||||
|
||||
if (cleanupCount > 0) {
|
||||
logger.log(`[TraktService] Cleaned up ${cleanupCount} old tracking entries`);
|
||||
}
|
||||
// Skip verbose cleanup logging to reduce CPU load
|
||||
}
|
||||
|
||||
public static getInstance(): TraktService {
|
||||
|
|
|
|||
Loading…
Reference in a new issue