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);