diff --git a/src/components/StreamCard.tsx b/src/components/StreamCard.tsx index 2040c4fc..74308d06 100644 --- a/src/components/StreamCard.tsx +++ b/src/components/StreamCard.tsx @@ -38,36 +38,36 @@ interface StreamCardProps { parentImdbId?: string; } -const StreamCard = memo(({ - stream, - onPress, - index, - isLoading, - statusMessage, - theme, - showLogos, - scraperLogo, - showAlert, - parentTitle, - parentType, - parentSeason, - parentEpisode, - parentEpisodeTitle, - parentPosterUrl, - providerName, - parentId, - parentImdbId +const StreamCard = memo(({ + stream, + onPress, + index, + isLoading, + statusMessage, + theme, + showLogos, + scraperLogo, + showAlert, + parentTitle, + parentType, + parentSeason, + parentEpisode, + parentEpisodeTitle, + parentPosterUrl, + providerName, + parentId, + parentImdbId }: StreamCardProps) => { const { settings } = useSettings(); const { startDownload } = useDownloads(); const { showSuccess, showInfo } = useToast(); - + // Handle long press to copy stream URL to clipboard const handleLongPress = useCallback(async () => { if (stream.url) { try { await Clipboard.setString(stream.url); - + // Use toast for Android, custom alert for iOS if (Platform.OS === 'android') { showSuccess('URL Copied', 'Stream URL copied to clipboard!'); @@ -85,13 +85,13 @@ const StreamCard = memo(({ } } }, [stream.url, showAlert, showSuccess, showInfo]); - + const styles = React.useMemo(() => createStyles(theme.colors), [theme.colors]); - + const streamInfo = useMemo(() => { const title = stream.title || ''; const name = stream.name || ''; - + // Helper function to format size from bytes const formatSize = (bytes: number): string => { if (bytes === 0) return '0 Bytes'; @@ -100,16 +100,16 @@ const StreamCard = memo(({ const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; - + // Get size from title (legacy format) or from stream.size field let sizeDisplay = title.match(/💾\s*([\d.]+\s*[GM]B)/)?.[1]; if (!sizeDisplay && stream.size && typeof stream.size === 'number' && stream.size > 0) { sizeDisplay = formatSize(stream.size); } - + // Extract quality for badge display const basicQuality = title.match(/(\d+)p/)?.[1] || null; - + return { quality: basicQuality, isHDR: title.toLowerCase().includes('hdr'), @@ -120,7 +120,7 @@ const StreamCard = memo(({ subTitle: title && title !== name ? title : null }; }, [stream.name, stream.title, stream.behaviorHints, stream.size]); - + const handleDownload = useCallback(async () => { try { const url = stream.url; @@ -132,9 +132,10 @@ const StreamCard = memo(({ showAlert('Already Downloading', 'This download has already started for this exact link.'); return; } - } catch {} + } catch { } // Show immediate feedback on both platforms - showAlert('Starting Download', 'Download will be started.'); + // Show immediate feedback on both platforms + // showAlert('Starting Download', 'Download will be started.'); const parent: any = stream as any; const inferredTitle = parentTitle || stream.name || stream.title || parent.metaName || 'Content'; const inferredType: 'movie' | 'series' = parentType || (parent.kind === 'series' || parent.type === 'series' ? 'series' : 'movie'); @@ -143,10 +144,10 @@ const StreamCard = memo(({ const episodeTitle = parentEpisodeTitle || parent.episodeTitle || parent.episode_name; // Prefer the stream's display name (often includes provider + resolution) const provider = (stream.name as any) || (stream.title as any) || providerName || parent.addonName || parent.addonId || (stream.addonName as any) || (stream.addonId as any) || 'Provider'; - + // Use parentId first (from route params), fallback to stream metadata const idForContent = parentId || parent.imdbId || parent.tmdbId || parent.addonId || inferredTitle; - + // Extract tmdbId if available (from parentId or parent metadata) let tmdbId: number | undefined = undefined; if (parentId && parentId.startsWith('tmdb:')) { @@ -172,99 +173,101 @@ const StreamCard = memo(({ tmdbId: tmdbId, }); showAlert('Download Started', 'Your download has been added to the queue.'); - } catch {} + } catch (e: any) { + showAlert('Download Failed', e.message || 'Could not start download.'); + } }, [startDownload, stream.url, stream.headers, streamInfo.quality, showAlert, stream.name, stream.title, parentId, parentImdbId, parentTitle, parentType, parentSeason, parentEpisode, parentEpisodeTitle, parentPosterUrl, providerName]); const isDebrid = streamInfo.isDebrid; return ( - {/* Scraper Logo */} - {showLogos && scraperLogo && ( - - {scraperLogo.toLowerCase().endsWith('.svg') || scraperLogo.toLowerCase().includes('.svg?') ? ( - - ) : ( - - )} - - )} - - - - - - {streamInfo.displayName} - - {streamInfo.subTitle && ( - - {streamInfo.subTitle} - - )} - - - {/* Show loading indicator if stream is loading */} - {isLoading && ( - - - - {statusMessage || "Loading..."} - - - )} - - - - {streamInfo.isDolby && ( - - )} - - {streamInfo.size && ( - - 💾 {streamInfo.size} - - )} - - {streamInfo.isDebrid && ( - - DEBRID - - )} - - - - - {settings?.enableDownloads !== false && ( - - + {/* Scraper Logo */} + {showLogos && scraperLogo && ( + + {scraperLogo.toLowerCase().endsWith('.svg') || scraperLogo.toLowerCase().includes('.svg?') ? ( + - - )} - + ) : ( + + )} + + )} + + + + + + {streamInfo.displayName} + + {streamInfo.subTitle && ( + + {streamInfo.subTitle} + + )} + + + {/* Show loading indicator if stream is loading */} + {isLoading && ( + + + + {statusMessage || "Loading..."} + + + )} + + + + {streamInfo.isDolby && ( + + )} + + {streamInfo.size && ( + + 💾 {streamInfo.size} + + )} + + {streamInfo.isDebrid && ( + + DEBRID + + )} + + + + + {settings?.enableDownloads !== false && ( + + + + )} + ); }); diff --git a/src/components/home/AppleTVHero.tsx b/src/components/home/AppleTVHero.tsx index 019b1152..1f257307 100644 --- a/src/components/home/AppleTVHero.tsx +++ b/src/components/home/AppleTVHero.tsx @@ -446,7 +446,7 @@ const AppleTVHero: React.FC = ({ if (url) { const bestUrl = TrailerService.getBestFormatUrl(url); setTrailerUrl(bestUrl); - logger.info('[AppleTVHero] Trailer URL loaded:', bestUrl); + // logger.info('[AppleTVHero] Trailer URL loaded:', bestUrl); } else { logger.info('[AppleTVHero] No trailer found for:', currentItem.name); setTrailerUrl(null); @@ -997,7 +997,7 @@ const AppleTVHero: React.FC = ({ {/* Background Images with Crossfade */} {/* Current Image - Always visible as base */} - + setIsPressed(true)} onPressOut={() => setIsPressed(false)} onPress={() => { - console.log('CompactCommentCard: TouchableOpacity pressed for comment:', comment.id); onPress(); }} activeOpacity={1} @@ -789,26 +788,21 @@ export const CommentsSection: React.FC = ({ }, [loading]); // Debug logging - console.log('CommentsSection: Comments data:', comments); - console.log('CommentsSection: Comments length:', comments?.length); - console.log('CommentsSection: Loading:', loading); - console.log('CommentsSection: Error:', error); + // Debug logging removed per user request const renderComment = useCallback(({ item }: { item: TraktContentComment }) => { // Safety check for null/undefined items if (!item || !item.id) { - console.log('CommentsSection: Invalid comment item:', item); return null; } - console.log('CommentsSection: Rendering comment:', item.id); + return ( { - console.log('CommentsSection: Comment pressed:', item.id); onCommentPress?.(item); }} isSpoilerRevealed={true} diff --git a/src/components/metadata/HeroSection.tsx b/src/components/metadata/HeroSection.tsx index c53d020f..911986c1 100644 --- a/src/components/metadata/HeroSection.tsx +++ b/src/components/metadata/HeroSection.tsx @@ -925,7 +925,7 @@ const HeroSection: React.FC = memo(({ // Handle trailer preload completion const handleTrailerPreloaded = useCallback(() => { setTrailerPreloaded(true); - logger.info('HeroSection', 'Trailer preloaded successfully'); + // logger.info('HeroSection', 'Trailer preloaded successfully'); }, []); // Handle smooth transition when trailer is ready to play diff --git a/src/components/video/TrailerPlayer.tsx b/src/components/video/TrailerPlayer.tsx index c39b5858..de8a7b91 100644 --- a/src/components/video/TrailerPlayer.tsx +++ b/src/components/video/TrailerPlayer.tsx @@ -64,7 +64,7 @@ const TrailerPlayer = React.forwardRef(({ const { currentTheme } = useTheme(); const { isTrailerPlaying: globalTrailerPlaying } = useTrailer(); const videoRef = useRef(null); - + const [isLoading, setIsLoading] = useState(true); const [isPlaying, setIsPlaying] = useState(autoPlay); const [isMuted, setIsMuted] = useState(muted); @@ -90,16 +90,16 @@ const TrailerPlayer = React.forwardRef(({ if (videoRef.current) { // Pause the video setIsPlaying(false); - + // Seek to beginning to stop any background processing videoRef.current.seek(0); - + // Clear any pending timeouts if (hideControlsTimeout.current) { clearTimeout(hideControlsTimeout.current); hideControlsTimeout.current = null; } - + logger.info('TrailerPlayer', 'Video cleanup completed'); } } catch (error) { @@ -138,7 +138,7 @@ const TrailerPlayer = React.forwardRef(({ // Component mount/unmount tracking useEffect(() => { setIsComponentMounted(true); - + return () => { setIsComponentMounted(false); cleanupVideo(); @@ -185,15 +185,15 @@ const TrailerPlayer = React.forwardRef(({ const showControlsWithTimeout = useCallback(() => { if (!isComponentMounted) return; - + setShowControls(true); controlsOpacity.value = withTiming(1, { duration: 200 }); - + // Clear existing timeout if (hideControlsTimeout.current) { clearTimeout(hideControlsTimeout.current); } - + // Set new timeout to hide controls hideControlsTimeout.current = setTimeout(() => { if (isComponentMounted) { @@ -205,7 +205,7 @@ const TrailerPlayer = React.forwardRef(({ const handleVideoPress = useCallback(() => { if (!isComponentMounted) return; - + if (showControls) { // If controls are visible, toggle play/pause handlePlayPause(); @@ -218,7 +218,7 @@ const TrailerPlayer = React.forwardRef(({ const handlePlayPause = useCallback(async () => { try { if (!videoRef.current || !isComponentMounted) return; - + playButtonScale.value = withTiming(0.8, { duration: 100 }, () => { if (isComponentMounted) { playButtonScale.value = withTiming(1, { duration: 100 }); @@ -226,7 +226,7 @@ const TrailerPlayer = React.forwardRef(({ }); setIsPlaying(!isPlaying); - + showControlsWithTimeout(); } catch (error) { logger.error('TrailerPlayer', 'Error toggling playback:', error); @@ -236,7 +236,7 @@ const TrailerPlayer = React.forwardRef(({ const handleMuteToggle = useCallback(async () => { try { if (!videoRef.current || !isComponentMounted) return; - + setIsMuted(!isMuted); showControlsWithTimeout(); } catch (error) { @@ -246,28 +246,28 @@ const TrailerPlayer = React.forwardRef(({ const handleLoadStart = useCallback(() => { if (!isComponentMounted) return; - + setIsLoading(true); setHasError(false); // Only show loading spinner if not hidden loadingOpacity.value = hideLoadingSpinner ? 0 : 1; onLoadStart?.(); - logger.info('TrailerPlayer', 'Video load started'); + // logger.info('TrailerPlayer', 'Video load started'); }, [loadingOpacity, onLoadStart, hideLoadingSpinner, isComponentMounted]); const handleLoad = useCallback((data: OnLoadData) => { if (!isComponentMounted) return; - + setIsLoading(false); loadingOpacity.value = withTiming(0, { duration: 300 }); setDuration(data.duration * 1000); // Convert to milliseconds onLoad?.(); - logger.info('TrailerPlayer', 'Video loaded successfully'); + // logger.info('TrailerPlayer', 'Video loaded successfully'); }, [loadingOpacity, onLoad, isComponentMounted]); const handleError = useCallback((error: any) => { if (!isComponentMounted) return; - + setIsLoading(false); setHasError(true); loadingOpacity.value = withTiming(0, { duration: 300 }); @@ -278,10 +278,10 @@ const TrailerPlayer = React.forwardRef(({ const handleProgress = useCallback((data: OnProgressData) => { if (!isComponentMounted) return; - + setPosition(data.currentTime * 1000); // Convert to milliseconds onProgress?.(data); - + if (onPlaybackStatusUpdate) { onPlaybackStatusUpdate({ isLoaded: data.currentTime > 0, @@ -304,7 +304,7 @@ const TrailerPlayer = React.forwardRef(({ clearTimeout(hideControlsTimeout.current); hideControlsTimeout.current = null; } - + // Reset all animated values to prevent memory leaks try { controlsOpacity.value = 0; @@ -313,7 +313,7 @@ const TrailerPlayer = React.forwardRef(({ } catch (error) { logger.error('TrailerPlayer', 'Error cleaning up animation values:', error); } - + // Ensure video is stopped cleanupVideo(); }; @@ -420,9 +420,9 @@ const TrailerPlayer = React.forwardRef(({ )} - {/* Video controls overlay */} + {/* Video controls overlay */} {!hideControls && ( - (({ - @@ -457,8 +457,8 @@ const TrailerPlayer = React.forwardRef(({ {/* Progress bar */} - @@ -466,27 +466,27 @@ const TrailerPlayer = React.forwardRef(({ {/* Control buttons */} - - + - - + {onFullscreenToggle && ( - )} diff --git a/src/contexts/DownloadsContext.tsx b/src/contexts/DownloadsContext.tsx index d6afb2aa..76fb59da 100644 --- a/src/contexts/DownloadsContext.tsx +++ b/src/contexts/DownloadsContext.tsx @@ -75,7 +75,7 @@ async function getExtensionFromHeaders(url: string, headers?: Record - lower.includes(format) || + const isStreamingFormat = streamingFormats.some(format => + lower.includes(format) || lower.includes(`ext=${format}`) || lower.includes(`format=${format}`) || lower.includes(`container=${format}`) ); - + // Return true if it's NOT a streaming format (m3u8 or DASH) return !isStreamingFormat; } @@ -183,7 +183,7 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi }); setDownloads(restored); } - } catch {} + } catch { } })(); }, []); @@ -209,18 +209,18 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi if (d.progress <= prev || d.progress - prev < 2) return; // notify every 2% lastNotifyRef.current.set(d.id, d.progress); await notificationService.notifyDownloadProgress(d.title, d.progress, d.downloadedBytes, d.totalBytes); - } catch {} + } catch { } }, []); const notifyCompleted = useCallback(async (d: DownloadItem) => { try { if (appStateRef.current === 'active') return; await notificationService.notifyDownloadComplete(d.title); - } catch {} + } catch { } }, []); useEffect(() => { - mmkvStorage.setItem(STORAGE_KEY, JSON.stringify(downloads)).catch(() => {}); + mmkvStorage.setItem(STORAGE_KEY, JSON.stringify(downloads)).catch(() => { }); }, [downloads]); const updateDownload = useCallback((id: string, updater: (d: DownloadItem) => DownloadItem) => { @@ -247,7 +247,7 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi // No need to recreate it } else { console.log(`[DownloadsContext] Creating new resumable for download: ${id}`); - + // Use the exact same file URI that was used initially const fileUri = item.fileUri; if (!fileUri) { @@ -322,13 +322,13 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi if (fileInfo.size === 0) { throw new Error('Downloaded file is empty (0 bytes)'); } - + // CRITICAL FIX: Check if file size matches expected size (if known) const currentItem = downloadsRef.current.find(d => d.id === id); if (currentItem && currentItem.totalBytes > 0) { const sizeDifference = Math.abs(fileInfo.size - currentItem.totalBytes); const percentDifference = (sizeDifference / currentItem.totalBytes) * 100; - + // Allow up to 1% difference to account for potential header/metadata variations if (percentDifference > 1) { throw new Error( @@ -336,7 +336,7 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi ); } } - + console.log(`[DownloadsContext] File validation passed: ${result.uri} (${fileInfo.size} bytes)`); } catch (validationError) { console.error(`[DownloadsContext] File validation failed: ${validationError}`); @@ -373,15 +373,15 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi // Only mark as error and clean up if it's a real error (not pause-related) console.log(`[DownloadsContext] Marking download as error: ${id}`); - + // For validation errors, clear resumeData and allow fresh restart if (e.message && e.message.includes('validation failed')) { console.log(`[DownloadsContext] Validation error - clearing resume data for fresh start: ${id}`); - updateDownload(id, (d) => ({ - ...d, - status: 'error', + updateDownload(id, (d) => ({ + ...d, + status: 'error', resumeData: undefined, // Clear corrupted resume data - updatedAt: Date.now() + updatedAt: Date.now() })); // Clean up resumable to force fresh download on retry resumablesRef.current.delete(id); @@ -389,13 +389,13 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi } else if (e.message && (e.message.includes('size mismatch') || e.message.includes('empty'))) { // File corruption detected - clear everything for fresh start console.log(`[DownloadsContext] File corruption detected - clearing for fresh start: ${id}`); - updateDownload(id, (d) => ({ - ...d, + updateDownload(id, (d) => ({ + ...d, status: 'error', downloadedBytes: 0, progress: 0, resumeData: undefined, // Clear corrupted resume data - updatedAt: Date.now() + updatedAt: Date.now() })); resumablesRef.current.delete(id); lastBytesRef.current.delete(id); @@ -443,7 +443,7 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi const fileUri = extension ? `${baseDir}downloads/${uniqueId}.${extension}` : `${baseDir}downloads/${uniqueId}`; // Ensure directory exists - await FileSystem.makeDirectoryAsync(`${baseDir}downloads`, { intermediates: true }).catch(() => {}); + await FileSystem.makeDirectoryAsync(`${baseDir}downloads`, { intermediates: true }).catch(() => { }); const createdAt = Date.now(); const newItem: DownloadItem = { @@ -515,9 +515,9 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi resumablesRef.current.set(compoundId, resumable); lastBytesRef.current.set(compoundId, { bytes: 0, time: Date.now() }); - try { - const result = await resumable.downloadAsync(); - + // Start download in background (non-blocking) to allow UI success alert + resumable.downloadAsync().then(async (result) => { + // Check if download was paused during download const currentItem = downloadsRef.current.find(d => d.id === compoundId); if (currentItem && currentItem.status === 'paused') { @@ -536,7 +536,7 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi // Don't delete resumable - keep it for resume return; } - + if (!result) throw new Error('Download failed'); // Validate the downloaded file @@ -548,13 +548,13 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi if (fileInfo.size === 0) { throw new Error('Downloaded file is empty (0 bytes)'); } - + // CRITICAL FIX: Check if file size matches expected size (if known) const currentItem = downloadsRef.current.find(d => d.id === compoundId); if (currentItem && currentItem.totalBytes > 0) { const sizeDifference = Math.abs(fileInfo.size - currentItem.totalBytes); const percentDifference = (sizeDifference / currentItem.totalBytes) * 100; - + // Allow up to 1% difference to account for potential header/metadata variations if (percentDifference > 1) { throw new Error( @@ -562,7 +562,7 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi ); } } - + console.log(`[DownloadsContext] File validation passed: ${result.uri} (${fileInfo.size} bytes)`); } catch (validationError) { console.error(`[DownloadsContext] File validation failed: ${validationError}`); @@ -581,7 +581,7 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi if (done) notifyCompleted({ ...done, status: 'completed', progress: 100, fileUri: result.uri } as DownloadItem); resumablesRef.current.delete(compoundId); lastBytesRef.current.delete(compoundId); - } catch (e: any) { + }).catch(async (e: any) => { // If user paused, keep paused state, else error const current = downloadsRef.current.find(d => d.id === compoundId); if (current && current.status === 'paused') { @@ -602,15 +602,15 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi } console.log(`[DownloadsContext] Marking initial download as error: ${compoundId}`); - + // For validation errors, clear resumeData and allow fresh restart if (e.message && e.message.includes('validation failed')) { console.log(`[DownloadsContext] Validation error - clearing resume data for fresh start: ${compoundId}`); - updateDownload(compoundId, (d) => ({ - ...d, - status: 'error', + updateDownload(compoundId, (d) => ({ + ...d, + status: 'error', resumeData: undefined, // Clear corrupted resume data - updatedAt: Date.now() + updatedAt: Date.now() })); // Clean up resumable to force fresh download on retry resumablesRef.current.delete(compoundId); @@ -618,13 +618,13 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi } else if (e.message && (e.message.includes('size mismatch') || e.message.includes('empty'))) { // File corruption detected - clear everything for fresh start console.log(`[DownloadsContext] File corruption detected - clearing for fresh start: ${compoundId}`); - updateDownload(compoundId, (d) => ({ - ...d, + updateDownload(compoundId, (d) => ({ + ...d, status: 'error', downloadedBytes: 0, progress: 0, resumeData: undefined, // Clear corrupted resume data - updatedAt: Date.now() + updatedAt: Date.now() })); resumablesRef.current.delete(compoundId); lastBytesRef.current.delete(compoundId); @@ -634,27 +634,27 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi updateDownload(compoundId, (d) => ({ ...d, status: 'error', updatedAt: Date.now() })); // Keep resumable for potential retry } - } + }); }, [updateDownload, resumeDownload]); const pauseDownload = useCallback(async (id: string) => { console.log(`[DownloadsContext] Pausing download: ${id}`); - + // First, update the status to 'paused' immediately // This will cause any ongoing download/resume operations to check status and exit gracefully updateDownload(id, (d) => ({ ...d, status: 'paused', updatedAt: Date.now() })); - + const resumable = resumablesRef.current.get(id); if (resumable) { try { // CRITICAL FIX: Get the pause state which contains resumeData const pauseResult = await resumable.pauseAsync(); console.log(`[DownloadsContext] Successfully paused download: ${id}`); - + // CRITICAL FIX: Save the resumeData from pauseAsync result or savable() // The pauseAsync returns a DownloadPauseState object with resumeData const savableState = resumable.savable(); - + // Update the download item with the critical resumeData for future resume updateDownload(id, (d) => ({ ...d, @@ -662,9 +662,9 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi resumeData: savableState.resumeData || pauseResult.resumeData, // Store resume data updatedAt: Date.now(), })); - + console.log(`[DownloadsContext] Saved resume data for download: ${id}`); - + // Keep the resumable in memory for resume - DO NOT DELETE } catch (error) { console.log(`[DownloadsContext] Pause async failed (this is normal if already paused): ${id}`, error); @@ -691,7 +691,7 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi const resumable = resumablesRef.current.get(id); try { if (resumable) { - try { await resumable.pauseAsync(); } catch {} + try { await resumable.pauseAsync(); } catch { } } } finally { resumablesRef.current.delete(id); @@ -700,7 +700,7 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi const item = downloadsRef.current.find(d => d.id === id); if (item?.fileUri) { - await FileSystem.deleteAsync(item.fileUri, { idempotent: true }).catch(() => {}); + await FileSystem.deleteAsync(item.fileUri, { idempotent: true }).catch(() => { }); } setDownloads(prev => prev.filter(d => d.id !== id)); }, []); @@ -708,7 +708,7 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi const removeDownload = useCallback(async (id: string) => { const item = downloadsRef.current.find(d => d.id === id); if (item?.fileUri && item.status === 'completed') { - await FileSystem.deleteAsync(item.fileUri, { idempotent: true }).catch(() => {}); + await FileSystem.deleteAsync(item.fileUri, { idempotent: true }).catch(() => { }); } setDownloads(prev => prev.filter(d => d.id !== id)); }, []); diff --git a/src/hooks/useTraktComments.ts b/src/hooks/useTraktComments.ts index 3b87ad23..2fd184f3 100644 --- a/src/hooks/useTraktComments.ts +++ b/src/hooks/useTraktComments.ts @@ -60,7 +60,7 @@ export const useTraktComments = ({ const traktService = TraktService.getInstance(); let fetchedComments: TraktContentComment[] = []; - console.log(`[useTraktComments] Loading comments for ${type} - IMDb: ${imdbId}, TMDB: ${tmdbId}, page: ${pageNum}`); + switch (type) { case 'movie': @@ -87,10 +87,10 @@ export const useTraktComments = ({ setComments(prevComments => { if (append) { const newComments = [...prevComments, ...fetchedComments]; - console.log(`[useTraktComments] Appended ${fetchedComments.length} comments, total: ${newComments.length}`); + return newComments; } else { - console.log(`[useTraktComments] Loaded ${fetchedComments.length} comments`); + return fetchedComments; } }); diff --git a/src/services/trailerService.ts b/src/services/trailerService.ts index ad6b4050..8e73667c 100644 --- a/src/services/trailerService.ts +++ b/src/services/trailerService.ts @@ -33,10 +33,10 @@ export class TrailerService { // Try local server first, fallback to XPrime if it fails const localResult = await this.getTrailerFromLocalServer(title, year, tmdbId, type); if (localResult) { - logger.info('TrailerService', 'Returning trailer URL from local server'); + // logger.info('TrailerService', 'Returning trailer URL from local server'); return localResult; } - + logger.info('TrailerService', `Local server failed, falling back to XPrime for: ${title} (${year})`); return this.getTrailerFromXPrime(title, year); } else { @@ -59,11 +59,11 @@ export class TrailerService { // Build URL with parameters const params = new URLSearchParams(); - + // Always send title and year for logging and fallback params.append('title', title); params.append('year', year.toString()); - + if (tmdbId) { params.append('tmdbId', tmdbId); params.append('type', type || 'movie'); @@ -76,9 +76,9 @@ export class TrailerService { logger.info('TrailerService', `Local server request URL: ${url}`); logger.info('TrailerService', `Local server timeout set to ${this.TIMEOUT}ms`); logger.info('TrailerService', `Making fetch request to: ${url}`); - + try { - + const response = await fetch(url, { method: 'GET', headers: { @@ -87,24 +87,26 @@ export class TrailerService { }, signal: controller.signal, }); - - logger.info('TrailerService', `Fetch request completed. Response status: ${response.status}`); + + // logger.info('TrailerService', `Fetch request completed. Response status: ${response.status}`); clearTimeout(timeoutId); const elapsed = Date.now() - startTime; const contentType = response.headers.get('content-type') || 'unknown'; - logger.info('TrailerService', `Local server response: status=${response.status} ok=${response.ok} content-type=${contentType} elapsedMs=${elapsed}`); + // logger.info('TrailerService', `Local server response: status=${response.status} ok=${response.ok} content-type=${contentType} elapsedMs=${elapsed}`); // Read body as text first so we can log it even on non-200s let rawText = ''; try { rawText = await response.text(); if (rawText) { + /* const preview = rawText.length > 200 ? `${rawText.slice(0, 200)}...` : rawText; logger.info('TrailerService', `Local server body preview: ${preview}`); + */ } else { - logger.info('TrailerService', 'Local server body is empty'); + // logger.info('TrailerService', 'Local server body is empty'); } } catch (e) { const msg = e instanceof Error ? `${e.name}: ${e.message}` : String(e); @@ -120,20 +122,20 @@ export class TrailerService { let data: any = null; try { data = rawText ? JSON.parse(rawText) : null; - const keys = typeof data === 'object' && data !== null ? Object.keys(data).join(',') : typeof data; - logger.info('TrailerService', `Local server JSON parsed. Keys/Type: ${keys}`); + // const keys = typeof data === 'object' && data !== null ? Object.keys(data).join(',') : typeof data; + // logger.info('TrailerService', `Local server JSON parsed. Keys/Type: ${keys}`); } catch (e) { const msg = e instanceof Error ? `${e.name}: ${e.message}` : String(e); logger.warn('TrailerService', `Failed to parse local server JSON: ${msg}`); return null; } - + if (!data.url || !this.isValidTrailerUrl(data.url)) { logger.warn('TrailerService', `Invalid trailer URL from auto-search: ${data.url}`); return null; } - logger.info('TrailerService', `Successfully found trailer: ${String(data.url).substring(0, 80)}...`); + // logger.info('TrailerService', `Successfully found trailer: ${String(data.url).substring(0, 80)}...`); return data.url; } catch (error) { if (error instanceof Error && error.name === 'AbortError') { @@ -164,11 +166,11 @@ export class TrailerService { const timeoutId = setTimeout(() => controller.abort(), this.TIMEOUT); const url = `${this.XPRIME_URL}?title=${encodeURIComponent(title)}&year=${year}`; - + logger.info('TrailerService', `Fetching trailer from XPrime for: ${title} (${year})`); logger.info('TrailerService', `XPrime request URL: ${url}`); logger.info('TrailerService', `XPrime timeout set to ${this.TIMEOUT}ms`); - + const response = await fetch(url, { method: 'GET', headers: { @@ -188,7 +190,7 @@ export class TrailerService { const trailerUrl = await response.text(); logger.info('TrailerService', `XPrime raw URL length: ${trailerUrl ? trailerUrl.length : 0}`); - + if (!trailerUrl || !this.isValidTrailerUrl(trailerUrl.trim())) { logger.warn('TrailerService', `Invalid trailer URL from XPrime: ${trailerUrl}`); return null; @@ -196,7 +198,7 @@ export class TrailerService { const cleanUrl = trailerUrl.trim(); logger.info('TrailerService', `Successfully fetched trailer from XPrime: ${cleanUrl}`); - + return cleanUrl; } catch (error) { if (error instanceof Error && error.name === 'AbortError') { @@ -218,7 +220,7 @@ export class TrailerService { private static isValidTrailerUrl(url: string): boolean { try { const urlObj = new URL(url); - + // Check if it's a valid HTTP/HTTPS URL if (!['http:', 'https:'].includes(urlObj.protocol)) { return false; @@ -242,19 +244,19 @@ export class TrailerService { ]; const hostname = urlObj.hostname.toLowerCase(); - const isValidDomain = validDomains.some(domain => + const isValidDomain = validDomains.some(domain => hostname.includes(domain) || hostname.endsWith(domain) ); // Special check for Google Video CDN (YouTube direct streaming URLs) - const isGoogleVideoCDN = hostname.includes('googlevideo.com') || - hostname.includes('sn-') && hostname.includes('.googlevideo.com'); + const isGoogleVideoCDN = hostname.includes('googlevideo.com') || + hostname.includes('sn-') && hostname.includes('.googlevideo.com'); // Check for video file extensions or streaming formats const hasVideoFormat = /\.(mp4|m3u8|mpd|webm|mov|avi|mkv)$/i.test(urlObj.pathname) || - url.includes('formats=') || - url.includes('manifest') || - url.includes('playlist'); + url.includes('formats=') || + url.includes('manifest') || + url.includes('playlist'); return isValidDomain || hasVideoFormat || isGoogleVideoCDN; } catch { @@ -286,9 +288,9 @@ export class TrailerService { return best; } } - + // Return the original URL if no format optimization is needed - logger.info('TrailerService', 'No format optimization applied'); + // logger.info('TrailerService', 'No format optimization applied'); return url; } @@ -314,7 +316,7 @@ export class TrailerService { static async getTrailerData(title: string, year: number): Promise { logger.info('TrailerService', `getTrailerData for: ${title} (${year})`); const url = await this.getTrailerUrl(title, year); - + if (!url) { logger.info('TrailerService', 'No trailer URL found for getTrailerData'); return null; @@ -433,9 +435,9 @@ export class TrailerService { signal: AbortSignal.timeout(5000) // 5 second timeout }); if (response.ok || response.status === 404) { // 404 is ok, means server is running - results.localServer = { - status: 'online', - responseTime: Date.now() - startTime + results.localServer = { + status: 'online', + responseTime: Date.now() - startTime }; logger.info('TrailerService', `Local server online. Response time: ${results.localServer.responseTime}ms`); } @@ -452,9 +454,9 @@ export class TrailerService { signal: AbortSignal.timeout(5000) // 5 second timeout }); if (response.ok || response.status === 404) { // 404 is ok, means server is running - results.xprimeServer = { - status: 'online', - responseTime: Date.now() - startTime + results.xprimeServer = { + status: 'online', + responseTime: Date.now() - startTime }; logger.info('TrailerService', `XPrime server online. Response time: ${results.xprimeServer.responseTime}ms`); } diff --git a/src/services/traktService.ts b/src/services/traktService.ts index bb5de6f9..64c12120 100644 --- a/src/services/traktService.ts +++ b/src/services/traktService.ts @@ -1463,7 +1463,7 @@ export class TraktService { if (matchingResult) { const traktId = matchingResult[type]?.ids?.trakt; if (traktId) { - logger.log(`[TraktService] Found Trakt ID: ${traktId} for IMDb ID: ${fullImdbId}`); + // logger.log(`[TraktService] Found Trakt ID: ${traktId} for IMDb ID: ${fullImdbId}`); return traktId; } } @@ -1471,7 +1471,7 @@ export class TraktService { // Fallback: try the first result if type filtering didn't work const traktId = data[0][type]?.ids?.trakt; if (traktId) { - logger.log(`[TraktService] Found Trakt ID (fallback): ${traktId} for IMDb ID: ${fullImdbId}`); + // logger.log(`[TraktService] Found Trakt ID (fallback): ${traktId} for IMDb ID: ${fullImdbId}`); return traktId; } } @@ -2860,7 +2860,7 @@ export class TraktService { if (data && data.length > 0) { const traktId = data[0][type === 'show' ? 'show' : type]?.ids?.trakt; if (traktId) { - logger.log(`[TraktService] Found Trakt ID via TMDB: ${traktId} for TMDB ID: ${tmdbId}`); + // logger.log(`[TraktService] Found Trakt ID via TMDB: ${traktId} for TMDB ID: ${tmdbId}`); return traktId; } } @@ -2893,7 +2893,7 @@ export class TraktService { const endpoint = `/movies/${traktId}/comments?page=${page}&limit=${limit}`; const result = await this.apiRequest(endpoint, 'GET'); - console.log(`[TraktService] Movie comments response:`, result); + // console.log(`[TraktService] Movie comments response:`, result); return result; } catch (error) { logger.error('[TraktService] Failed to get movie comments:', error);