mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-14 05:30:24 +00:00
sub bg fix
This commit is contained in:
parent
2ba7c3c057
commit
5fe1db24c1
4 changed files with 55 additions and 17 deletions
|
|
@ -56,6 +56,32 @@ const HeroCarousel: React.FC<HeroCarouselProps> = ({ 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;
|
||||
|
|
|
|||
|
|
@ -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<CustomSubtitlesProps> = ({
|
|||
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<CustomSubtitlesProps> = ({
|
|||
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)
|
||||
<Svg
|
||||
width={estimatedContentWidth}
|
||||
width={svgWidth}
|
||||
height={svgHeight}
|
||||
viewBox={`0 0 ${estimatedContentWidth} ${svgHeight}`}
|
||||
viewBox={`0 0 ${svgWidth} ${svgHeight}`}
|
||||
preserveAspectRatio="xMidYMax meet"
|
||||
>
|
||||
{(() => {
|
||||
|
|
@ -168,10 +169,10 @@ export const CustomSubtitles: React.FC<CustomSubtitlesProps> = ({
|
|||
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<CustomSubtitlesProps> = ({
|
|||
letterSpacing: effectiveLetterSpacing,
|
||||
fontSize: subtitleSize * inverseScale,
|
||||
lineHeight: subtitleSize * lineHeightMultiplier * inverseScale,
|
||||
transform: [{ scale: inverseScale }],
|
||||
},
|
||||
shadowStyle,
|
||||
]}>
|
||||
|
|
|
|||
|
|
@ -668,6 +668,8 @@ export const styles = StyleSheet.create({
|
|||
customSubtitleWrapper: {
|
||||
padding: 10,
|
||||
borderRadius: 5,
|
||||
alignSelf: 'center',
|
||||
maxWidth: '90%',
|
||||
},
|
||||
customSubtitleText: {
|
||||
color: 'white',
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue