mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 08:41:57 +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 scrollX = useSharedValue(0);
|
||||||
const interval = CARD_WIDTH + 16;
|
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
|
// Comprehensive reset when component mounts/remounts to prevent glitching
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
scrollX.value = 0;
|
scrollX.value = 0;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
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 Svg, { Text as SvgText, TSpan } from 'react-native-svg';
|
||||||
import { styles } from '../utils/playerStyles';
|
import { styles } from '../utils/playerStyles';
|
||||||
import { SubtitleSegment } from '../utils/playerTypes';
|
import { SubtitleSegment } from '../utils/playerTypes';
|
||||||
|
|
@ -85,13 +85,11 @@ export const CustomSubtitles: React.FC<CustomSubtitlesProps> = ({
|
||||||
const displayFontSize = subtitleSize * inverseScale;
|
const displayFontSize = subtitleSize * inverseScale;
|
||||||
const displayLineHeight = subtitleSize * lineHeightMultiplier * inverseScale;
|
const displayLineHeight = subtitleSize * lineHeightMultiplier * inverseScale;
|
||||||
const svgHeight = lines.length * displayLineHeight;
|
const svgHeight = lines.length * displayLineHeight;
|
||||||
// Estimate text width to keep background from spanning full screen when using SVG
|
// Roughly estimate text width to size SVG snugly (avoids overly wide background)
|
||||||
const windowWidth = Math.min(Dimensions.get('window').width, Dimensions.get('window').height);
|
const charWidthFactor = 0.48; // even tighter average width per character
|
||||||
const longestLineChars = Math.max(1, ...lines.map(l => l.length));
|
const estimatedLineWidths = lines.map(line => Math.max(1, line.length * displayFontSize * charWidthFactor));
|
||||||
const estimatedContentWidth = Math.min(
|
const maxEstimatedLineWidth = estimatedLineWidths.length > 0 ? Math.max(...estimatedLineWidths) : displayFontSize * 2;
|
||||||
Math.max(100, Math.ceil(longestLineChars * displayFontSize * 0.6)),
|
const svgWidth = Math.max(displayFontSize * 2, Math.ceil(maxEstimatedLineWidth + displayFontSize * 0.25));
|
||||||
Math.max(140, windowWidth - 40)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Helper to render formatted segments
|
// Helper to render formatted segments
|
||||||
const renderFormattedText = (segments: SubtitleSegment[], lineIndex: number, keyPrefix: string, isRTL?: boolean, customLetterSpacing?: number) => {
|
const renderFormattedText = (segments: SubtitleSegment[], lineIndex: number, keyPrefix: string, isRTL?: boolean, customLetterSpacing?: number) => {
|
||||||
|
|
@ -148,15 +146,18 @@ export const CustomSubtitles: React.FC<CustomSubtitlesProps> = ({
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
maxWidth: windowWidth - 40,
|
maxWidth: '90%',
|
||||||
|
paddingHorizontal: 4,
|
||||||
|
paddingVertical: 4,
|
||||||
|
transform: [{ scale: inverseScale }],
|
||||||
}
|
}
|
||||||
]}>
|
]}>
|
||||||
{useCrispSvgOutline ? (
|
{useCrispSvgOutline ? (
|
||||||
// Crisp outline using react-native-svg (stroke under, fill on top)
|
// Crisp outline using react-native-svg (stroke under, fill on top)
|
||||||
<Svg
|
<Svg
|
||||||
width={estimatedContentWidth}
|
width={svgWidth}
|
||||||
height={svgHeight}
|
height={svgHeight}
|
||||||
viewBox={`0 0 ${estimatedContentWidth} ${svgHeight}`}
|
viewBox={`0 0 ${svgWidth} ${svgHeight}`}
|
||||||
preserveAspectRatio="xMidYMax meet"
|
preserveAspectRatio="xMidYMax meet"
|
||||||
>
|
>
|
||||||
{(() => {
|
{(() => {
|
||||||
|
|
@ -168,10 +169,10 @@ export const CustomSubtitles: React.FC<CustomSubtitlesProps> = ({
|
||||||
if (isRTL) {
|
if (isRTL) {
|
||||||
// For RTL, always use 'end' anchor to position from right edge
|
// For RTL, always use 'end' anchor to position from right edge
|
||||||
anchor = 'end';
|
anchor = 'end';
|
||||||
x = estimatedContentWidth;
|
x = svgWidth;
|
||||||
} else {
|
} else {
|
||||||
anchor = align === 'center' ? 'middle' : align === 'left' ? 'start' : 'end';
|
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;
|
const baseFontSize = displayFontSize;
|
||||||
|
|
@ -257,7 +258,6 @@ export const CustomSubtitles: React.FC<CustomSubtitlesProps> = ({
|
||||||
letterSpacing: effectiveLetterSpacing,
|
letterSpacing: effectiveLetterSpacing,
|
||||||
fontSize: subtitleSize * inverseScale,
|
fontSize: subtitleSize * inverseScale,
|
||||||
lineHeight: subtitleSize * lineHeightMultiplier * inverseScale,
|
lineHeight: subtitleSize * lineHeightMultiplier * inverseScale,
|
||||||
transform: [{ scale: inverseScale }],
|
|
||||||
},
|
},
|
||||||
shadowStyle,
|
shadowStyle,
|
||||||
]}>
|
]}>
|
||||||
|
|
|
||||||
|
|
@ -668,6 +668,8 @@ export const styles = StyleSheet.create({
|
||||||
customSubtitleWrapper: {
|
customSubtitleWrapper: {
|
||||||
padding: 10,
|
padding: 10,
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
|
alignSelf: 'center',
|
||||||
|
maxWidth: '90%',
|
||||||
},
|
},
|
||||||
customSubtitleText: {
|
customSubtitleText: {
|
||||||
color: 'white',
|
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)
|
// Get available scrapers from manifest (respects manifestEnabled)
|
||||||
const availableScrapers = await this.getAvailableScrapers();
|
const availableScrapers = await this.getAvailableScrapers();
|
||||||
const enabledScrapers = availableScrapers
|
const enabledScrapers = availableScrapers
|
||||||
.filter(scraper =>
|
.filter(scraper =>
|
||||||
scraper.enabled &&
|
scraper.enabled &&
|
||||||
scraper.manifestEnabled !== false &&
|
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) {
|
if (enabledScrapers.length === 0) {
|
||||||
logger.log('[LocalScraperService] No enabled scrapers found for type:', type);
|
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
|
// No callback needed here since this is after filtering - scrapers weren't added to UI yet
|
||||||
return;
|
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)
|
scrapers: enabledScrapers.map(s => s.name)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -928,7 +938,7 @@ class LocalScraperService {
|
||||||
|
|
||||||
// Execute all enabled scrapers
|
// Execute all enabled scrapers
|
||||||
for (const scraper of enabledScrapers) {
|
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