From 5fe1db24c16f0d9411e47804f9d7545243c46693 Mon Sep 17 00:00:00 2001 From: tapframe Date: Tue, 4 Nov 2025 20:47:03 +0530 Subject: [PATCH] sub bg fix --- src/components/home/HeroCarousel.tsx | 26 +++++++++++++++++ .../player/subtitles/CustomSubtitles.tsx | 28 +++++++++---------- src/components/player/utils/playerStyles.ts | 2 ++ src/services/localScraperService.ts | 16 +++++++++-- 4 files changed, 55 insertions(+), 17 deletions(-) diff --git a/src/components/home/HeroCarousel.tsx b/src/components/home/HeroCarousel.tsx index e3a5dbea..44a82108 100644 --- a/src/components/home/HeroCarousel.tsx +++ b/src/components/home/HeroCarousel.tsx @@ -56,6 +56,32 @@ const HeroCarousel: React.FC = ({ items, loading = false }) = const scrollX = useSharedValue(0); const interval = CARD_WIDTH + 16; + // Parallel image prefetch: start fetching banners and logos as soon as data arrives + const itemsToPreload = useMemo(() => data.slice(0, 8), [data]); + useEffect(() => { + if (!itemsToPreload.length) return; + try { + const sources = itemsToPreload.flatMap((it) => { + const result: { uri: string; priority?: any }[] = []; + const bannerOrPoster = it.banner || it.poster; + if (bannerOrPoster) { + result.push({ uri: bannerOrPoster, priority: (FastImage as any).priority?.low }); + } + if (it.logo) { + result.push({ uri: it.logo, priority: (FastImage as any).priority?.normal }); + } + return result; + }); + // de-duplicate by uri + const uniqueSources = Array.from(new Map(sources.map((s) => [s.uri, s])).values()); + if (uniqueSources.length && (FastImage as any).preload) { + (FastImage as any).preload(uniqueSources); + } + } catch { + // no-op: prefetch is best-effort + } + }, [itemsToPreload]); + // Comprehensive reset when component mounts/remounts to prevent glitching useEffect(() => { scrollX.value = 0; diff --git a/src/components/player/subtitles/CustomSubtitles.tsx b/src/components/player/subtitles/CustomSubtitles.tsx index b4f79ca1..44d38ef5 100644 --- a/src/components/player/subtitles/CustomSubtitles.tsx +++ b/src/components/player/subtitles/CustomSubtitles.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { View, Text, Dimensions } from 'react-native'; +import { View, Text } from 'react-native'; import Svg, { Text as SvgText, TSpan } from 'react-native-svg'; import { styles } from '../utils/playerStyles'; import { SubtitleSegment } from '../utils/playerTypes'; @@ -85,13 +85,11 @@ export const CustomSubtitles: React.FC = ({ const displayFontSize = subtitleSize * inverseScale; const displayLineHeight = subtitleSize * lineHeightMultiplier * inverseScale; const svgHeight = lines.length * displayLineHeight; - // Estimate text width to keep background from spanning full screen when using SVG - const windowWidth = Math.min(Dimensions.get('window').width, Dimensions.get('window').height); - const longestLineChars = Math.max(1, ...lines.map(l => l.length)); - const estimatedContentWidth = Math.min( - Math.max(100, Math.ceil(longestLineChars * displayFontSize * 0.6)), - Math.max(140, windowWidth - 40) - ); + // Roughly estimate text width to size SVG snugly (avoids overly wide background) + const charWidthFactor = 0.48; // even tighter average width per character + const estimatedLineWidths = lines.map(line => Math.max(1, line.length * displayFontSize * charWidthFactor)); + const maxEstimatedLineWidth = estimatedLineWidths.length > 0 ? Math.max(...estimatedLineWidths) : displayFontSize * 2; + const svgWidth = Math.max(displayFontSize * 2, Math.ceil(maxEstimatedLineWidth + displayFontSize * 0.25)); // Helper to render formatted segments const renderFormattedText = (segments: SubtitleSegment[], lineIndex: number, keyPrefix: string, isRTL?: boolean, customLetterSpacing?: number) => { @@ -148,15 +146,18 @@ export const CustomSubtitles: React.FC = ({ position: 'relative', alignItems: 'center', alignSelf: 'center', - maxWidth: windowWidth - 40, + maxWidth: '90%', + paddingHorizontal: 4, + paddingVertical: 4, + transform: [{ scale: inverseScale }], } ]}> {useCrispSvgOutline ? ( // Crisp outline using react-native-svg (stroke under, fill on top) {(() => { @@ -168,10 +169,10 @@ export const CustomSubtitles: React.FC = ({ if (isRTL) { // For RTL, always use 'end' anchor to position from right edge anchor = 'end'; - x = estimatedContentWidth; + x = svgWidth; } else { anchor = align === 'center' ? 'middle' : align === 'left' ? 'start' : 'end'; - x = align === 'center' ? (estimatedContentWidth / 2) : (align === 'left' ? 0 : estimatedContentWidth); + x = align === 'center' ? svgWidth / 2 : (align === 'left' ? 0 : svgWidth); } const baseFontSize = displayFontSize; @@ -257,7 +258,6 @@ export const CustomSubtitles: React.FC = ({ letterSpacing: effectiveLetterSpacing, fontSize: subtitleSize * inverseScale, lineHeight: subtitleSize * lineHeightMultiplier * inverseScale, - transform: [{ scale: inverseScale }], }, shadowStyle, ]}> diff --git a/src/components/player/utils/playerStyles.ts b/src/components/player/utils/playerStyles.ts index 7eed60f8..61804247 100644 --- a/src/components/player/utils/playerStyles.ts +++ b/src/components/player/utils/playerStyles.ts @@ -668,6 +668,8 @@ export const styles = StyleSheet.create({ customSubtitleWrapper: { padding: 10, borderRadius: 5, + alignSelf: 'center', + maxWidth: '90%', }, customSubtitleText: { color: 'white', diff --git a/src/services/localScraperService.ts b/src/services/localScraperService.ts index 47683e5d..1588e03f 100644 --- a/src/services/localScraperService.ts +++ b/src/services/localScraperService.ts @@ -904,22 +904,32 @@ class LocalScraperService { } } + // Normalize media type for plugin compatibility (treat 'series'/'other' as 'tv') + const media: 'movie' | 'tv' = (type === 'series' || type === 'other') ? 'tv' : (type as 'movie' | 'tv'); + // Get available scrapers from manifest (respects manifestEnabled) const availableScrapers = await this.getAvailableScrapers(); const enabledScrapers = availableScrapers .filter(scraper => scraper.enabled && scraper.manifestEnabled !== false && - scraper.supportedTypes.includes(type as 'movie' | 'tv') + scraper.supportedTypes.includes(media) ); + logger.log(`[LocalScraperService] Media normalized '${type}' -> '${media}'. Enabled scrapers for this media: ${enabledScrapers.length}`); + if (enabledScrapers.length > 0) { + try { + logger.log('[LocalScraperService] Enabled scrapers:', enabledScrapers.map(s => s.name).join(', ')); + } catch {} + } + if (enabledScrapers.length === 0) { logger.log('[LocalScraperService] No enabled scrapers found for type:', type); // No callback needed here since this is after filtering - scrapers weren't added to UI yet return; } - logger.log(`[LocalScraperService] Executing ${enabledScrapers.length} scrapers for ${type}:${tmdbId}`, { + logger.log(`[LocalScraperService] Executing ${enabledScrapers.length} scrapers for ${media}:${tmdbId}`, { scrapers: enabledScrapers.map(s => s.name) }); @@ -928,7 +938,7 @@ class LocalScraperService { // Execute all enabled scrapers for (const scraper of enabledScrapers) { - this.executeScraper(scraper, type, tmdbId, season, episode, callback, requestId); + this.executeScraper(scraper, media, tmdbId, season, episode, callback, requestId); } }