added sub customize support

This commit is contained in:
tapframe 2025-08-08 16:41:33 +05:30
parent 7d9f8fba86
commit 51550316ec
4 changed files with 394 additions and 11 deletions

View file

@ -149,6 +149,18 @@ const AndroidVideoPlayer: React.FC = () => {
const [customSubtitleVersion, setCustomSubtitleVersion] = useState<number>(0);
const [subtitleSize, setSubtitleSize] = useState<number>(DEFAULT_SUBTITLE_SIZE);
const [subtitleBackground, setSubtitleBackground] = useState<boolean>(true);
// External subtitle customization
const [subtitleFontFamily, setSubtitleFontFamily] = useState<string | undefined>(undefined);
const [subtitleTextColor, setSubtitleTextColor] = useState<string>('#FFFFFF');
const [subtitleBgOpacity, setSubtitleBgOpacity] = useState<number>(0.7);
const [subtitleTextShadow, setSubtitleTextShadow] = useState<boolean>(true);
const [subtitleOutline, setSubtitleOutline] = useState<boolean>(false);
const [subtitleOutlineColor, setSubtitleOutlineColor] = useState<string>('#000000');
const [subtitleOutlineWidth, setSubtitleOutlineWidth] = useState<number>(2);
const [subtitleAlign, setSubtitleAlign] = useState<'center' | 'left' | 'right'>('center');
const [subtitleBottomOffset, setSubtitleBottomOffset] = useState<number>(20);
const [subtitleLetterSpacing, setSubtitleLetterSpacing] = useState<number>(0);
const [subtitleLineHeightMultiplier, setSubtitleLineHeightMultiplier] = useState<number>(1.2);
const [useCustomSubtitles, setUseCustomSubtitles] = useState<boolean>(false);
const [isLoadingSubtitles, setIsLoadingSubtitles] = useState<boolean>(false);
const [availableSubtitles, setAvailableSubtitles] = useState<WyzieSubtitle[]>([]);
@ -1450,6 +1462,17 @@ const AndroidVideoPlayer: React.FC = () => {
subtitleSize={subtitleSize}
subtitleBackground={subtitleBackground}
zoomScale={zoomScale}
fontFamily={subtitleFontFamily}
textColor={subtitleTextColor}
backgroundOpacity={subtitleBgOpacity}
textShadow={subtitleTextShadow}
outline={subtitleOutline}
outlineColor={subtitleOutlineColor}
outlineWidth={subtitleOutlineWidth}
align={subtitleAlign}
bottomOffset={subtitleBottomOffset}
letterSpacing={subtitleLetterSpacing}
lineHeightMultiplier={subtitleLineHeightMultiplier}
/>
<ResumeOverlay
@ -1492,6 +1515,28 @@ const AndroidVideoPlayer: React.FC = () => {
increaseSubtitleSize={increaseSubtitleSize}
decreaseSubtitleSize={decreaseSubtitleSize}
toggleSubtitleBackground={toggleSubtitleBackground}
subtitleFontFamily={subtitleFontFamily}
setSubtitleFontFamily={setSubtitleFontFamily}
subtitleTextColor={subtitleTextColor}
setSubtitleTextColor={setSubtitleTextColor}
subtitleBgOpacity={subtitleBgOpacity}
setSubtitleBgOpacity={setSubtitleBgOpacity}
subtitleTextShadow={subtitleTextShadow}
setSubtitleTextShadow={setSubtitleTextShadow}
subtitleOutline={subtitleOutline}
setSubtitleOutline={setSubtitleOutline}
subtitleOutlineColor={subtitleOutlineColor}
setSubtitleOutlineColor={setSubtitleOutlineColor}
subtitleOutlineWidth={subtitleOutlineWidth}
setSubtitleOutlineWidth={setSubtitleOutlineWidth}
subtitleAlign={subtitleAlign}
setSubtitleAlign={setSubtitleAlign}
subtitleBottomOffset={subtitleBottomOffset}
setSubtitleBottomOffset={setSubtitleBottomOffset}
subtitleLetterSpacing={subtitleLetterSpacing}
setSubtitleLetterSpacing={setSubtitleLetterSpacing}
subtitleLineHeightMultiplier={subtitleLineHeightMultiplier}
setSubtitleLineHeightMultiplier={setSubtitleLineHeightMultiplier}
/>
<SourcesModal

View file

@ -37,6 +37,7 @@ import ResumeOverlay from './modals/ResumeOverlay';
import PlayerControls from './controls/PlayerControls';
import CustomSubtitles from './subtitles/CustomSubtitles';
import { SourcesModal } from './modals/SourcesModal';
import axios from 'axios';
import { stremioService } from '../../services/stremioService';
const VideoPlayer: React.FC = () => {
@ -159,6 +160,18 @@ const VideoPlayer: React.FC = () => {
const [currentSubtitle, setCurrentSubtitle] = useState<string>('');
const [subtitleSize, setSubtitleSize] = useState<number>(DEFAULT_SUBTITLE_SIZE);
const [subtitleBackground, setSubtitleBackground] = useState<boolean>(true);
// External subtitle customization
const [subtitleFontFamily, setSubtitleFontFamily] = useState<string | undefined>(undefined);
const [subtitleTextColor, setSubtitleTextColor] = useState<string>('#FFFFFF');
const [subtitleBgOpacity, setSubtitleBgOpacity] = useState<number>(0.7);
const [subtitleTextShadow, setSubtitleTextShadow] = useState<boolean>(true);
const [subtitleOutline, setSubtitleOutline] = useState<boolean>(false);
const [subtitleOutlineColor, setSubtitleOutlineColor] = useState<string>('#000000');
const [subtitleOutlineWidth, setSubtitleOutlineWidth] = useState<number>(2);
const [subtitleAlign, setSubtitleAlign] = useState<'center' | 'left' | 'right'>('center');
const [subtitleBottomOffset, setSubtitleBottomOffset] = useState<number>(20);
const [subtitleLetterSpacing, setSubtitleLetterSpacing] = useState<number>(0);
const [subtitleLineHeightMultiplier, setSubtitleLineHeightMultiplier] = useState<number>(1.2);
const [useCustomSubtitles, setUseCustomSubtitles] = useState<boolean>(false);
const [isLoadingSubtitles, setIsLoadingSubtitles] = useState<boolean>(false);
const [availableSubtitles, setAvailableSubtitles] = useState<WyzieSubtitle[]>([]);
@ -953,18 +966,100 @@ const VideoPlayer: React.FC = () => {
};
const loadWyzieSubtitle = async (subtitle: WyzieSubtitle) => {
logger.log(`[VideoPlayer] Subtitle click received: id=${subtitle.id}, lang=${subtitle.language}, url=${subtitle.url}`);
setShowSubtitleLanguageModal(false);
setIsLoadingSubtitles(true);
try {
const response = await fetch(subtitle.url);
const srtContent = await response.text();
logger.log('[VideoPlayer] Fetching subtitle SRT start');
let srtContent = '';
try {
const axiosResp = await axios.get(subtitle.url, {
timeout: 10000,
headers: {
'Accept': 'text/plain, */*',
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Nuvio/1.0'
},
responseType: 'text',
transitional: { clarifyTimeoutError: true }
});
srtContent = typeof axiosResp.data === 'string' ? axiosResp.data : String(axiosResp.data || '');
} catch (axiosErr: any) {
logger.warn('[VideoPlayer] Axios subtitle fetch failed, falling back to fetch()', {
message: axiosErr?.message,
code: axiosErr?.code
});
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
try {
const resp = await fetch(subtitle.url, { signal: controller.signal });
srtContent = await resp.text();
} finally {
clearTimeout(timeoutId);
}
}
logger.log(`[VideoPlayer] Fetching subtitle SRT done, size=${srtContent.length}`);
const parsedCues = parseSRT(srtContent);
setCustomSubtitles(parsedCues);
setUseCustomSubtitles(true);
logger.log(`[VideoPlayer] Parsed cues count=${parsedCues.length}`);
// For VLC on iOS: stop spinner early, then clear-apply and micro-seek nudge
setIsLoadingSubtitles(false);
logger.log('[VideoPlayer] isLoadingSubtitles -> false (early)');
// Clear existing state
setUseCustomSubtitles(false);
logger.log('[VideoPlayer] useCustomSubtitles -> false');
setCustomSubtitles([]);
logger.log('[VideoPlayer] customSubtitles -> []');
setSelectedTextTrack(-1);
logger.log('[VideoPlayer] selectedTextTrack -> -1');
// Apply immediately
setCustomSubtitles(parsedCues);
logger.log('[VideoPlayer] customSubtitles <- parsedCues');
setUseCustomSubtitles(true);
logger.log('[VideoPlayer] useCustomSubtitles -> true');
setSelectedTextTrack(-1);
logger.log('[VideoPlayer] selectedTextTrack -> -1 (disable native while using custom)');
// Immediately set current subtitle text
try {
const cueNow = parsedCues.find(cue => currentTime >= cue.start && currentTime <= cue.end);
const textNow = cueNow ? cueNow.text : '';
setCurrentSubtitle(textNow);
logger.log('[VideoPlayer] currentSubtitle set immediately after apply');
} catch (e) {
logger.error('[VideoPlayer] Error setting immediate subtitle', e);
}
// VLC micro-seek nudge
try {
if (vlcRef.current && duration > 0) {
const wasPaused = paused;
const original = currentTime;
const forward = Math.min(original + 0.05, Math.max(duration - 0.1, 0));
logger.log('[VideoPlayer] Performing micro-seek nudge', { original, forward });
if (wasPaused) setPaused(false);
setTimeout(() => {
try {
// @ts-ignore - VLCPlayer seek method
vlcRef.current?.seek(forward);
setTimeout(() => {
// @ts-ignore
vlcRef.current?.seek(original);
if (wasPaused) setPaused(true);
logger.log('[VideoPlayer] Micro-seek nudge complete');
}, 150);
} catch (e) {
logger.warn('[VideoPlayer] Micro-seek nudge failed', e);
if (wasPaused) setPaused(true);
}
}, 50);
}
} catch(e) {
logger.warn('[VideoPlayer] Outer micro-seek failed', e);
}
} catch (error) {
logger.error('[VideoPlayer] Error loading Wyzie subtitle:', error);
} finally {
setIsLoadingSubtitles(false);
}
};
@ -1275,7 +1370,7 @@ const VideoPlayer: React.FC = () => {
<VLCPlayer
ref={vlcRef}
style={[styles.video, customVideoStyles, { transform: [{ scale: zoomScale }] }]}
source={(() => {
source={(() => {
// FORCEFULLY use headers from route params if available - no filtering or modification
const sourceWithHeaders = headers ? {
uri: currentStreamUrl,
@ -1339,6 +1434,17 @@ const VideoPlayer: React.FC = () => {
subtitleSize={subtitleSize}
subtitleBackground={subtitleBackground}
zoomScale={zoomScale}
fontFamily={subtitleFontFamily}
textColor={subtitleTextColor}
backgroundOpacity={subtitleBgOpacity}
textShadow={subtitleTextShadow}
outline={subtitleOutline}
outlineColor={subtitleOutlineColor}
outlineWidth={subtitleOutlineWidth}
align={subtitleAlign}
bottomOffset={subtitleBottomOffset}
letterSpacing={subtitleLetterSpacing}
lineHeightMultiplier={subtitleLineHeightMultiplier}
/>
<ResumeOverlay
@ -1381,6 +1487,28 @@ const VideoPlayer: React.FC = () => {
increaseSubtitleSize={increaseSubtitleSize}
decreaseSubtitleSize={decreaseSubtitleSize}
toggleSubtitleBackground={toggleSubtitleBackground}
subtitleFontFamily={subtitleFontFamily}
setSubtitleFontFamily={setSubtitleFontFamily}
subtitleTextColor={subtitleTextColor}
setSubtitleTextColor={setSubtitleTextColor}
subtitleBgOpacity={subtitleBgOpacity}
setSubtitleBgOpacity={setSubtitleBgOpacity}
subtitleTextShadow={subtitleTextShadow}
setSubtitleTextShadow={setSubtitleTextShadow}
subtitleOutline={subtitleOutline}
setSubtitleOutline={setSubtitleOutline}
subtitleOutlineColor={subtitleOutlineColor}
setSubtitleOutlineColor={setSubtitleOutlineColor}
subtitleOutlineWidth={subtitleOutlineWidth}
setSubtitleOutlineWidth={setSubtitleOutlineWidth}
subtitleAlign={subtitleAlign}
setSubtitleAlign={setSubtitleAlign}
subtitleBottomOffset={subtitleBottomOffset}
setSubtitleBottomOffset={setSubtitleBottomOffset}
subtitleLetterSpacing={subtitleLetterSpacing}
setSubtitleLetterSpacing={setSubtitleLetterSpacing}
subtitleLineHeightMultiplier={subtitleLineHeightMultiplier}
setSubtitleLineHeightMultiplier={setSubtitleLineHeightMultiplier}
/>
<SourcesModal

View file

@ -31,6 +31,29 @@ interface SubtitleModalsProps {
increaseSubtitleSize: () => void;
decreaseSubtitleSize: () => void;
toggleSubtitleBackground: () => void;
// Customization props
subtitleFontFamily?: string;
setSubtitleFontFamily: (f?: string) => void;
subtitleTextColor: string;
setSubtitleTextColor: (c: string) => void;
subtitleBgOpacity: number;
setSubtitleBgOpacity: (o: number) => void;
subtitleTextShadow: boolean;
setSubtitleTextShadow: (b: boolean) => void;
subtitleOutline: boolean;
setSubtitleOutline: (b: boolean) => void;
subtitleOutlineColor: string;
setSubtitleOutlineColor: (c: string) => void;
subtitleOutlineWidth: number;
setSubtitleOutlineWidth: (n: number) => void;
subtitleAlign: 'center' | 'left' | 'right';
setSubtitleAlign: (a: 'center' | 'left' | 'right') => void;
subtitleBottomOffset: number;
setSubtitleBottomOffset: (n: number) => void;
subtitleLetterSpacing: number;
setSubtitleLetterSpacing: (n: number) => void;
subtitleLineHeightMultiplier: number;
setSubtitleLineHeightMultiplier: (n: number) => void;
}
const { width, height } = Dimensions.get('window');
@ -56,6 +79,28 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
increaseSubtitleSize,
decreaseSubtitleSize,
toggleSubtitleBackground,
subtitleFontFamily,
setSubtitleFontFamily,
subtitleTextColor,
setSubtitleTextColor,
subtitleBgOpacity,
setSubtitleBgOpacity,
subtitleTextShadow,
setSubtitleTextShadow,
subtitleOutline,
setSubtitleOutline,
subtitleOutlineColor,
setSubtitleOutlineColor,
subtitleOutlineWidth,
setSubtitleOutlineWidth,
subtitleAlign,
setSubtitleAlign,
subtitleBottomOffset,
setSubtitleBottomOffset,
subtitleLetterSpacing,
setSubtitleLetterSpacing,
subtitleLineHeightMultiplier,
setSubtitleLineHeightMultiplier,
}) => {
// Track which specific online subtitle is currently loaded
const [selectedOnlineSubtitleId, setSelectedOnlineSubtitleId] = React.useState<string | null>(null);
@ -295,6 +340,118 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
</View>
)}
{/* Customization Section - Only for custom subtitles */}
{useCustomSubtitles && (
<View style={{ marginBottom: 30, gap: 10 }}>
<Text style={{ color: 'rgba(255,255,255,0.7)', fontSize: 14, fontWeight: '600', marginBottom: 10, textTransform: 'uppercase' }}>Appearance</Text>
<View style={{ backgroundColor: 'rgba(255,255,255,0.05)', borderRadius: 16, padding: 16, gap: 12 }}>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
<Text style={{ color: 'white' }}>Text Color</Text>
<View style={{ flexDirection: 'row', gap: 8 }}>
{['#FFFFFF', '#FFD700', '#00E5FF', '#FF5C5C', '#00FF88'].map(c => (
<TouchableOpacity key={c} onPress={() => setSubtitleTextColor(c)} style={{ width: 22, height: 22, borderRadius: 11, backgroundColor: c, borderWidth: 1, borderColor: 'rgba(255,255,255,0.3)' }} />
))}
</View>
</View>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
<Text style={{ color: 'white' }}>Align</Text>
<View style={{ flexDirection: 'row', gap: 8 }}>
{(['left','center','right'] as const).map(a => (
<TouchableOpacity key={a} onPress={() => setSubtitleAlign(a)} style={{ paddingHorizontal: 10, paddingVertical: 6, borderRadius: 6, backgroundColor: subtitleAlign === a ? 'rgba(255,255,255,0.2)' : 'transparent', borderWidth: 1, borderColor: 'rgba(255,255,255,0.2)' }}>
<Text style={{ color: 'white', textTransform: 'capitalize' }}>{a}</Text>
</TouchableOpacity>
))}
</View>
</View>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
<Text style={{ color: 'white' }}>Bottom Offset</Text>
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
<TouchableOpacity onPress={() => setSubtitleBottomOffset(Math.max(0, subtitleBottomOffset - 5))} style={{ width: 30, height: 30, borderRadius: 15, backgroundColor: 'rgba(255,255,255,0.2)', alignItems: 'center', justifyContent: 'center' }}>
<MaterialIcons name="keyboard-arrow-down" color="#fff" size={20} />
</TouchableOpacity>
<Text style={{ color: 'white', width: 40, textAlign: 'center' }}>{subtitleBottomOffset}</Text>
<TouchableOpacity onPress={() => setSubtitleBottomOffset(subtitleBottomOffset + 5)} style={{ width: 30, height: 30, borderRadius: 15, backgroundColor: 'rgba(255,255,255,0.2)', alignItems: 'center', justifyContent: 'center' }}>
<MaterialIcons name="keyboard-arrow-up" color="#fff" size={20} />
</TouchableOpacity>
</View>
</View>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
<Text style={{ color: 'white' }}>Background Opacity</Text>
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
<TouchableOpacity onPress={() => setSubtitleBgOpacity(Math.max(0, +(subtitleBgOpacity - 0.1).toFixed(1)))} style={{ width: 30, height: 30, borderRadius: 15, backgroundColor: 'rgba(255,255,255,0.2)', alignItems: 'center', justifyContent: 'center' }}>
<MaterialIcons name="remove" color="#fff" size={18} />
</TouchableOpacity>
<Text style={{ color: 'white', width: 40, textAlign: 'center' }}>{subtitleBgOpacity.toFixed(1)}</Text>
<TouchableOpacity onPress={() => setSubtitleBgOpacity(Math.min(1, +(subtitleBgOpacity + 0.1).toFixed(1)))} style={{ width: 30, height: 30, borderRadius: 15, backgroundColor: 'rgba(255,255,255,0.2)', alignItems: 'center', justifyContent: 'center' }}>
<MaterialIcons name="add" color="#fff" size={18} />
</TouchableOpacity>
</View>
</View>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
<Text style={{ color: 'white' }}>Text Shadow</Text>
<TouchableOpacity onPress={() => setSubtitleTextShadow(!subtitleTextShadow)} style={{ paddingHorizontal: 10, paddingVertical: 6, borderRadius: 6, backgroundColor: subtitleTextShadow ? 'rgba(255,255,255,0.2)' : 'transparent', borderWidth: 1, borderColor: 'rgba(255,255,255,0.2)' }}>
<Text style={{ color: 'white' }}>{subtitleTextShadow ? 'On' : 'Off'}</Text>
</TouchableOpacity>
</View>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
<Text style={{ color: 'white' }}>Outline</Text>
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
<TouchableOpacity onPress={() => setSubtitleOutline(!subtitleOutline)} style={{ paddingHorizontal: 10, paddingVertical: 6, borderRadius: 6, backgroundColor: subtitleOutline ? 'rgba(255,255,255,0.2)' : 'transparent', borderWidth: 1, borderColor: 'rgba(255,255,255,0.2)' }}>
<Text style={{ color: 'white' }}>{subtitleOutline ? 'On' : 'Off'}</Text>
</TouchableOpacity>
{['#000000', '#FFFFFF', '#00E5FF', '#FF5C5C'].map(c => (
<TouchableOpacity key={c} onPress={() => setSubtitleOutlineColor(c)} style={{ width: 22, height: 22, borderRadius: 11, backgroundColor: c, borderWidth: 1, borderColor: 'rgba(255,255,255,0.3)' }} />
))}
</View>
</View>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
<Text style={{ color: 'white' }}>Outline Width</Text>
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
<TouchableOpacity onPress={() => setSubtitleOutlineWidth(Math.max(0, subtitleOutlineWidth - 1))} style={{ width: 30, height: 30, borderRadius: 15, backgroundColor: 'rgba(255,255,255,0.2)', alignItems: 'center', justifyContent: 'center' }}>
<MaterialIcons name="remove" color="#fff" size={18} />
</TouchableOpacity>
<Text style={{ color: 'white', width: 40, textAlign: 'center' }}>{subtitleOutlineWidth}</Text>
<TouchableOpacity onPress={() => setSubtitleOutlineWidth(subtitleOutlineWidth + 1)} style={{ width: 30, height: 30, borderRadius: 15, backgroundColor: 'rgba(255,255,255,0.2)', alignItems: 'center', justifyContent: 'center' }}>
<MaterialIcons name="add" color="#fff" size={18} />
</TouchableOpacity>
</View>
</View>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
<Text style={{ color: 'white' }}>Letter Spacing</Text>
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
<TouchableOpacity onPress={() => setSubtitleLetterSpacing(Math.max(0, +(subtitleLetterSpacing - 0.5).toFixed(1)))} style={{ width: 30, height: 30, borderRadius: 15, backgroundColor: 'rgba(255,255,255,0.2)', alignItems: 'center', justifyContent: 'center' }}>
<MaterialIcons name="remove" color="#fff" size={18} />
</TouchableOpacity>
<Text style={{ color: 'white', width: 40, textAlign: 'center' }}>{subtitleLetterSpacing.toFixed(1)}</Text>
<TouchableOpacity onPress={() => setSubtitleLetterSpacing(+(subtitleLetterSpacing + 0.5).toFixed(1))} style={{ width: 30, height: 30, borderRadius: 15, backgroundColor: 'rgba(255,255,255,0.2)', alignItems: 'center', justifyContent: 'center' }}>
<MaterialIcons name="add" color="#fff" size={18} />
</TouchableOpacity>
</View>
</View>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
<Text style={{ color: 'white' }}>Line Height</Text>
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
<TouchableOpacity onPress={() => setSubtitleLineHeightMultiplier(Math.max(1, +(subtitleLineHeightMultiplier - 0.1).toFixed(1)))} style={{ width: 30, height: 30, borderRadius: 15, backgroundColor: 'rgba(255,255,255,0.2)', alignItems: 'center', justifyContent: 'center' }}>
<MaterialIcons name="remove" color="#fff" size={18} />
</TouchableOpacity>
<Text style={{ color: 'white', width: 40, textAlign: 'center' }}>{subtitleLineHeightMultiplier.toFixed(1)}</Text>
<TouchableOpacity onPress={() => setSubtitleLineHeightMultiplier(+(subtitleLineHeightMultiplier + 0.1).toFixed(1))} style={{ width: 30, height: 30, borderRadius: 15, backgroundColor: 'rgba(255,255,255,0.2)', alignItems: 'center', justifyContent: 'center' }}>
<MaterialIcons name="add" color="#fff" size={18} />
</TouchableOpacity>
</View>
</View>
</View>
</View>
)}
{/* Built-in Subtitles */}
{vlcTextTracks.length > 0 && (
<View style={{ marginBottom: 30 }}>

View file

@ -8,6 +8,18 @@ interface CustomSubtitlesProps {
subtitleSize: number;
subtitleBackground: boolean;
zoomScale?: number; // current video zoom scale; defaults to 1
// New customization props
fontFamily?: string;
textColor?: string;
backgroundOpacity?: number; // 0..1
textShadow?: boolean;
outline?: boolean;
outlineColor?: string;
outlineWidth?: number; // px
align?: 'center' | 'left' | 'right';
bottomOffset?: number; // px from bottom
letterSpacing?: number;
lineHeightMultiplier?: number; // multiplies subtitleSize
}
export const CustomSubtitles: React.FC<CustomSubtitlesProps> = ({
@ -16,27 +28,68 @@ export const CustomSubtitles: React.FC<CustomSubtitlesProps> = ({
subtitleSize,
subtitleBackground,
zoomScale = 1,
fontFamily,
textColor = '#FFFFFF',
backgroundOpacity = 0.7,
textShadow = true,
outline = false,
outlineColor = '#000000',
outlineWidth = 2,
align = 'center',
bottomOffset = 20,
letterSpacing = 0,
lineHeightMultiplier = 1.2,
}) => {
if (!useCustomSubtitles || !currentSubtitle) return null;
const inverseScale = 1 / zoomScale;
const bgColor = subtitleBackground ? `rgba(0, 0, 0, ${Math.min(Math.max(backgroundOpacity, 0), 1)})` : 'transparent';
// Outline via textShadow for multi-direction pass
const outlineStyle = outline
? {
textShadowColor: outlineColor,
textShadowOffset: { width: 0, height: 0 },
textShadowRadius: outlineWidth,
}
: {};
const shadowStyle = textShadow
? {
textShadowColor: 'rgba(0, 0, 0, 0.9)',
textShadowOffset: { width: 2, height: 2 },
textShadowRadius: 4,
}
: {};
return (
<View
style={styles.customSubtitleContainer}
style={[
styles.customSubtitleContainer,
{ bottom: bottomOffset },
]}
pointerEvents="none"
>
<View style={[
styles.customSubtitleWrapper,
{
backgroundColor: subtitleBackground ? 'rgba(0, 0, 0, 0.7)' : 'transparent',
backgroundColor: bgColor,
alignSelf: align === 'center' ? 'center' : align === 'left' ? 'flex-start' : 'flex-end',
}
]}>
<Text style={[
styles.customSubtitleText,
{
styles.customSubtitleText,
{
color: textColor,
fontFamily,
textAlign: align,
letterSpacing,
fontSize: subtitleSize * inverseScale,
lineHeight: subtitleSize * lineHeightMultiplier * inverseScale,
transform: [{ scale: inverseScale }],
}
},
shadowStyle,
outlineStyle,
]}>
{currentSubtitle}
</Text>