mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
removed vlc lib
This commit is contained in:
parent
f0f71afd67
commit
767fd2ff87
15 changed files with 124 additions and 715 deletions
|
|
@ -51,7 +51,6 @@
|
|||
"expo-glass-effect": "~0.1.4",
|
||||
"expo-haptics": "~15.0.7",
|
||||
"expo-intent-launcher": "~13.0.7",
|
||||
"expo-libvlc-player": "^2.2.3",
|
||||
"expo-linear-gradient": "~15.0.7",
|
||||
"expo-localization": "~17.0.7",
|
||||
"expo-navigation-bar": "~5.0.10",
|
||||
|
|
@ -104,4 +103,4 @@
|
|||
"xcode": "^3.0.1"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
}
|
||||
|
|
@ -649,7 +649,6 @@ const AppleTVHero: React.FC<AppleTVHeroProps> = ({
|
|||
streamProvider: cachedStream.stream.addonId || cachedStream.stream.addonName || cachedStream.stream.name,
|
||||
streamName: cachedStream.stream.name || cachedStream.stream.title || 'Unnamed Stream',
|
||||
headers: cachedStream.stream.headers || undefined,
|
||||
forceVlc: false,
|
||||
id: currentItem.id,
|
||||
type: currentItem.type,
|
||||
episodeId: episodeId,
|
||||
|
|
|
|||
|
|
@ -977,7 +977,6 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
streamProvider: cachedStream.stream.addonId || cachedStream.stream.addonName || cachedStream.stream.name,
|
||||
streamName: cachedStream.stream.name || cachedStream.stream.title || 'Unnamed Stream',
|
||||
headers: cachedStream.stream.headers || undefined,
|
||||
forceVlc: false,
|
||||
id: item.id,
|
||||
type: item.type,
|
||||
episodeId: episodeId,
|
||||
|
|
|
|||
|
|
@ -13,9 +13,8 @@ import {
|
|||
useOpeningAnimation
|
||||
} from './hooks';
|
||||
|
||||
// Android-specific hooks (VLC integration, dual player support)
|
||||
// Android-specific hooks
|
||||
import { usePlayerSetup } from './android/hooks/usePlayerSetup';
|
||||
import { useVlcPlayer } from './android/hooks/useVlcPlayer';
|
||||
import { usePlayerTracks } from './android/hooks/usePlayerTracks';
|
||||
import { useWatchProgress } from './android/hooks/useWatchProgress';
|
||||
import { usePlayerControls } from './android/hooks/usePlayerControls';
|
||||
|
|
@ -46,7 +45,7 @@ import { MpvPlayerRef } from './android/MpvPlayer';
|
|||
// Utils
|
||||
import { logger } from '../../utils/logger';
|
||||
import { styles } from './utils/playerStyles';
|
||||
import { formatTime, isHlsStream, processUrlForVLC, getHlsHeaders, defaultAndroidHeaders, parseSRT } from './utils/playerUtils';
|
||||
import { formatTime, isHlsStream, getHlsHeaders, defaultAndroidHeaders, parseSRT } from './utils/playerUtils';
|
||||
import { storageService } from '../../services/storageService';
|
||||
import stremioService from '../../services/stremioService';
|
||||
import { WyzieSubtitle, SubtitleCue } from './utils/playerTypes';
|
||||
|
|
@ -71,28 +70,12 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
const modals = usePlayerModals();
|
||||
const speedControl = useSpeedControl();
|
||||
|
||||
const forceVlc = useMemo(() => {
|
||||
const rp: any = route.params || {};
|
||||
const v = rp.forceVlc !== undefined ? rp.forceVlc : rp.forceVLC;
|
||||
return typeof v === 'string' ? v.toLowerCase() === 'true' : Boolean(v);
|
||||
}, [route.params]);
|
||||
|
||||
const useVLC = (Platform.OS === 'android' && forceVlc);
|
||||
|
||||
const videoRef = useRef<any>(null);
|
||||
const mpvPlayerRef = useRef<MpvPlayerRef>(null);
|
||||
const vlcHook = useVlcPlayer(useVLC, playerState.paused, playerState.currentTime);
|
||||
const tracksHook = usePlayerTracks(
|
||||
useVLC,
|
||||
vlcHook.vlcAudioTracks,
|
||||
vlcHook.vlcSubtitleTracks,
|
||||
vlcHook.vlcSelectedAudioTrack,
|
||||
vlcHook.vlcSelectedSubtitleTrack
|
||||
);
|
||||
const tracksHook = usePlayerTracks();
|
||||
|
||||
const [currentStreamUrl, setCurrentStreamUrl] = useState<string>(uri);
|
||||
const [currentVideoType, setCurrentVideoType] = useState<string | undefined>((route.params as any).videoType);
|
||||
const processedStreamUrl = useMemo(() => useVLC ? processUrlForVLC(currentStreamUrl) : currentStreamUrl, [currentStreamUrl, useVLC]);
|
||||
|
||||
const [availableStreams, setAvailableStreams] = useState<any>(passedAvailableStreams || {});
|
||||
const [currentQuality, setCurrentQuality] = useState(quality);
|
||||
|
|
@ -132,9 +115,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
const setupHook = usePlayerSetup(playerState.setScreenDimensions, setVolume, setBrightness, playerState.paused);
|
||||
|
||||
const controlsHook = usePlayerControls(
|
||||
mpvPlayerRef, // Use mpvPlayerRef for MPV player
|
||||
vlcHook.vlcPlayerRef,
|
||||
useVLC,
|
||||
mpvPlayerRef,
|
||||
playerState.paused,
|
||||
playerState.setPaused,
|
||||
playerState.currentTime,
|
||||
|
|
@ -265,23 +246,21 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
playerState.setVideoAspectRatio(16 / 9);
|
||||
}
|
||||
|
||||
if (!useVLC) {
|
||||
if (data.audioTracks) {
|
||||
const formatted = data.audioTracks.map((t: any, i: number) => ({
|
||||
id: t.index !== undefined ? t.index : i,
|
||||
name: t.title || t.name || `Track ${i + 1}`,
|
||||
language: t.language
|
||||
}));
|
||||
tracksHook.setRnVideoAudioTracks(formatted);
|
||||
}
|
||||
if (data.textTracks) {
|
||||
const formatted = data.textTracks.map((t: any, i: number) => ({
|
||||
id: t.index !== undefined ? t.index : i,
|
||||
name: t.title || t.name || `Track ${i + 1}`,
|
||||
language: t.language
|
||||
}));
|
||||
tracksHook.setRnVideoTextTracks(formatted);
|
||||
}
|
||||
if (data.audioTracks) {
|
||||
const formatted = data.audioTracks.map((t: any, i: number) => ({
|
||||
id: t.index !== undefined ? t.index : i,
|
||||
name: t.title || t.name || `Track ${i + 1}`,
|
||||
language: t.language
|
||||
}));
|
||||
tracksHook.setRnVideoAudioTracks(formatted);
|
||||
}
|
||||
if (data.textTracks) {
|
||||
const formatted = data.textTracks.map((t: any, i: number) => ({
|
||||
id: t.index !== undefined ? t.index : i,
|
||||
name: t.title || t.name || `Track ${i + 1}`,
|
||||
language: t.language
|
||||
}));
|
||||
tracksHook.setRnVideoTextTracks(formatted);
|
||||
}
|
||||
|
||||
playerState.setIsVideoLoaded(true);
|
||||
|
|
@ -299,7 +278,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
}
|
||||
}, 200);
|
||||
}
|
||||
}, [id, type, episodeId, useVLC, playerState.isMounted, watchProgress.initialPosition]);
|
||||
}, [id, type, episodeId, playerState.isMounted, watchProgress.initialPosition]);
|
||||
|
||||
const handleProgress = useCallback((data: any) => {
|
||||
if (playerState.isDragging.current || playerState.isSeeking.current || !playerState.isMounted.current || setupHook.isAppBackgrounded.current) return;
|
||||
|
|
@ -385,7 +364,6 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
streamProvider: newProvider,
|
||||
streamName: newStreamName,
|
||||
headers: stream.headers || undefined,
|
||||
forceVlc: false,
|
||||
id,
|
||||
type: 'series',
|
||||
episodeId: ep.stremioId || `${id}:${ep.season_number}:${ep.episode_number}`,
|
||||
|
|
@ -508,24 +486,12 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
|
||||
<View style={{ flex: 1, backgroundColor: 'black' }}>
|
||||
<VideoSurface
|
||||
useVLC={!!useVLC}
|
||||
forceVlcRemount={vlcHook.forceVlcRemount}
|
||||
processedStreamUrl={processedStreamUrl}
|
||||
processedStreamUrl={currentStreamUrl}
|
||||
volume={volume}
|
||||
playbackSpeed={speedControl.playbackSpeed}
|
||||
zoomScale={1.0}
|
||||
resizeMode={playerState.resizeMode}
|
||||
paused={playerState.paused}
|
||||
currentStreamUrl={currentStreamUrl}
|
||||
headers={headers || (isHlsStream(currentStreamUrl) ? getHlsHeaders() : defaultAndroidHeaders)}
|
||||
videoType={currentVideoType}
|
||||
vlcSelectedAudioTrack={vlcHook.vlcSelectedAudioTrack}
|
||||
vlcSelectedSubtitleTrack={vlcHook.vlcSelectedSubtitleTrack}
|
||||
vlcRestoreTime={vlcHook.vlcRestoreTime}
|
||||
vlcKey={vlcHook.vlcKey}
|
||||
selectedAudioTrack={tracksHook.selectedAudioTrack}
|
||||
selectedTextTrack={tracksHook.selectedTextTrack}
|
||||
useCustomSubtitles={false}
|
||||
toggleControls={toggleControls}
|
||||
onLoad={handleLoad}
|
||||
onProgress={handleProgress}
|
||||
|
|
@ -540,32 +506,6 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
onError={(err: any) => {
|
||||
logger.error('Video Error', err);
|
||||
|
||||
// Check for decoding errors to switch to VLC
|
||||
const errorString = err?.errorString || err?.error?.errorString;
|
||||
const errorCode = err?.errorCode || err?.error?.errorCode;
|
||||
const causeMessage = err?.error?.cause?.message;
|
||||
|
||||
const isDecodingError =
|
||||
(errorString && errorString.includes('ERROR_CODE_DECODING_FAILED')) ||
|
||||
errorCode === '24003' ||
|
||||
(causeMessage && causeMessage.includes('MediaCodecVideoRenderer error'));
|
||||
|
||||
if (!useVLC && isDecodingError) {
|
||||
const toastId = toast.loading('Decoding error. Switching to VLC Player...');
|
||||
setTimeout(() => toast.dismiss(toastId), 3000);
|
||||
|
||||
// We can just show a normal toast or use the existing modal system if we want,
|
||||
// but checking the file imports, I don't see Toast imported.
|
||||
// Let's implement the navigation replace.
|
||||
|
||||
// Using a simple navigation replace to force VLC
|
||||
(navigation as any).replace('PlayerAndroid', {
|
||||
...route.params,
|
||||
forceVlc: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the actual error message
|
||||
let displayError = 'An unknown error occurred';
|
||||
|
||||
|
|
@ -585,7 +525,6 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
modals.setShowErrorModal(true);
|
||||
}}
|
||||
onBuffer={(buf) => playerState.setIsBuffering(buf.isBuffering)}
|
||||
onTracksUpdate={vlcHook.handleVlcTracksUpdate}
|
||||
onTracksChanged={(data) => {
|
||||
console.log('[AndroidVideoPlayer] onTracksChanged:', data);
|
||||
if (data?.audioTracks) {
|
||||
|
|
@ -605,17 +544,11 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
tracksHook.setRnVideoTextTracks(formatted);
|
||||
}
|
||||
}}
|
||||
vlcPlayerRef={vlcHook.vlcPlayerRef}
|
||||
mpvPlayerRef={mpvPlayerRef}
|
||||
videoRef={videoRef}
|
||||
pinchRef={useRef(null)}
|
||||
onPinchGestureEvent={() => { }}
|
||||
onPinchHandlerStateChange={() => { }}
|
||||
vlcLoadedRef={vlcHook.vlcLoadedRef}
|
||||
screenDimensions={playerState.screenDimensions}
|
||||
customVideoStyles={{}}
|
||||
loadStartAtRef={loadStartAtRef}
|
||||
firstFrameAtRef={firstFrameAtRef}
|
||||
/>
|
||||
|
||||
{/* Custom Subtitles for addon subtitles */}
|
||||
|
|
@ -698,7 +631,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
}}
|
||||
buffered={playerState.buffered}
|
||||
formatTime={formatTime}
|
||||
playerBackend={useVLC ? 'VLC' : 'ExoPlayer'}
|
||||
playerBackend={'MPV'}
|
||||
/>
|
||||
|
||||
<SpeedActivatedOverlay
|
||||
|
|
@ -728,14 +661,10 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
ksAudioTracks={tracksHook.ksAudioTracks}
|
||||
selectedAudioTrack={tracksHook.computedSelectedAudioTrack}
|
||||
selectAudioTrack={(trackId) => {
|
||||
if (useVLC) {
|
||||
vlcHook.selectVlcAudioTrack(trackId);
|
||||
} else {
|
||||
tracksHook.setSelectedAudioTrack(trackId === null ? null : { type: 'index', value: trackId });
|
||||
// Actually tell MPV to switch the audio track
|
||||
if (trackId !== null && mpvPlayerRef.current) {
|
||||
mpvPlayerRef.current.setAudioTrack(trackId);
|
||||
}
|
||||
tracksHook.setSelectedAudioTrack(trackId === null ? null : { type: 'index', value: trackId });
|
||||
// Actually tell MPV to switch the audio track
|
||||
if (trackId !== null && mpvPlayerRef.current) {
|
||||
mpvPlayerRef.current.setAudioTrack(trackId);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
@ -752,20 +681,16 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
ksTextTracks={tracksHook.ksTextTracks}
|
||||
selectedTextTrack={tracksHook.computedSelectedTextTrack}
|
||||
useCustomSubtitles={useCustomSubtitles}
|
||||
isKsPlayerActive={!useVLC}
|
||||
isKsPlayerActive={true}
|
||||
subtitleSize={subtitleSize}
|
||||
subtitleBackground={subtitleBackground}
|
||||
fetchAvailableSubtitles={fetchAvailableSubtitles}
|
||||
loadWyzieSubtitle={loadWyzieSubtitle}
|
||||
selectTextTrack={(trackId) => {
|
||||
if (useVLC) {
|
||||
vlcHook.selectVlcSubtitleTrack(trackId);
|
||||
} else {
|
||||
tracksHook.setSelectedTextTrack(trackId);
|
||||
// Actually tell MPV to switch the subtitle track
|
||||
if (mpvPlayerRef.current) {
|
||||
mpvPlayerRef.current.setSubtitleTrack(trackId);
|
||||
}
|
||||
tracksHook.setSelectedTextTrack(trackId);
|
||||
// Actually tell MPV to switch the subtitle track
|
||||
if (mpvPlayerRef.current) {
|
||||
mpvPlayerRef.current.setSubtitleTrack(trackId);
|
||||
}
|
||||
// Disable custom subtitles when selecting built-in track
|
||||
setUseCustomSubtitles(false);
|
||||
|
|
|
|||
|
|
@ -302,6 +302,17 @@ const KSPlayerCore: React.FC = () => {
|
|||
}
|
||||
}, [imdbId]);
|
||||
|
||||
// Sync custom subtitle text with current playback time
|
||||
useEffect(() => {
|
||||
if (!customSubs.useCustomSubtitles || customSubs.customSubtitles.length === 0) return;
|
||||
|
||||
const adjustedTime = currentTime + (customSubs.subtitleOffsetSec || 0);
|
||||
const cueNow = customSubs.customSubtitles.find(
|
||||
cue => adjustedTime >= cue.start && adjustedTime <= cue.end
|
||||
);
|
||||
customSubs.setCurrentSubtitle(cueNow ? cueNow.text : '');
|
||||
}, [currentTime, customSubs.useCustomSubtitles, customSubs.customSubtitles, customSubs.subtitleOffsetSec]);
|
||||
|
||||
// Handlers
|
||||
const onLoad = (data: any) => {
|
||||
setDuration(data.duration);
|
||||
|
|
@ -416,7 +427,7 @@ const KSPlayerCore: React.FC = () => {
|
|||
headers: stream.headers || undefined,
|
||||
id,
|
||||
type: 'series',
|
||||
episodeId: ep.stremioId || `${id}:${ep.season_number}:${ep.episode_number}`,
|
||||
episodeId: ep.stremioId || `${id}:${ep.season_number}:${ep.episode_number} `,
|
||||
imdbId: imdbId ?? undefined,
|
||||
backdrop: backdrop || undefined,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,364 +0,0 @@
|
|||
import React, { useState, useRef, useEffect, useCallback, useMemo, forwardRef, useImperativeHandle } from 'react';
|
||||
import { View, Dimensions } from 'react-native';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
// Dynamic import to avoid iOS loading Android native module
|
||||
let LibVlcPlayerViewComponent: any = null;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const mod = require('expo-libvlc-player');
|
||||
LibVlcPlayerViewComponent = mod?.LibVlcPlayerView || null;
|
||||
} catch {
|
||||
LibVlcPlayerViewComponent = null;
|
||||
}
|
||||
|
||||
interface VlcVideoPlayerProps {
|
||||
source: string;
|
||||
volume: number;
|
||||
playbackSpeed: number;
|
||||
zoomScale: number;
|
||||
resizeMode: 'contain' | 'cover' | 'none';
|
||||
onLoad: (data: any) => void;
|
||||
onProgress: (data: any) => void;
|
||||
onSeek: (data: any) => void;
|
||||
onEnd: () => void;
|
||||
onError: (error: any) => void;
|
||||
onTracksUpdate: (tracks: { audio: any[], subtitle: any[] }) => void;
|
||||
selectedAudioTrack?: number | null;
|
||||
selectedSubtitleTrack?: number | null;
|
||||
restoreTime?: number | null;
|
||||
forceRemount?: boolean;
|
||||
key?: string;
|
||||
}
|
||||
|
||||
interface VlcTrack {
|
||||
id: number;
|
||||
name: string;
|
||||
language?: string;
|
||||
}
|
||||
|
||||
export interface VlcPlayerRef {
|
||||
seek: (timeInSeconds: number) => void;
|
||||
pause: () => void;
|
||||
play: () => void;
|
||||
}
|
||||
|
||||
const VlcVideoPlayer = forwardRef<VlcPlayerRef, VlcVideoPlayerProps>(({
|
||||
source,
|
||||
volume,
|
||||
playbackSpeed,
|
||||
zoomScale,
|
||||
resizeMode,
|
||||
onLoad,
|
||||
onProgress,
|
||||
onSeek,
|
||||
onEnd,
|
||||
onError,
|
||||
onTracksUpdate,
|
||||
selectedAudioTrack,
|
||||
selectedSubtitleTrack,
|
||||
restoreTime,
|
||||
forceRemount,
|
||||
key,
|
||||
}, ref) => {
|
||||
const vlcRef = useRef<any>(null);
|
||||
const [vlcActive, setVlcActive] = useState(true);
|
||||
const [duration, setDuration] = useState<number>(0);
|
||||
const [videoAspectRatio, setVideoAspectRatio] = useState<number | null>(null);
|
||||
|
||||
// Expose imperative methods to parent component
|
||||
useImperativeHandle(ref, () => ({
|
||||
seek: (timeInSeconds: number) => {
|
||||
if (vlcRef.current && typeof vlcRef.current.seek === 'function') {
|
||||
const fraction = Math.min(Math.max(timeInSeconds / (duration || 1), 0), 0.999);
|
||||
vlcRef.current.seek(fraction);
|
||||
logger.log(`[VLC] Seeked to ${timeInSeconds}s (${fraction.toFixed(3)})`);
|
||||
}
|
||||
},
|
||||
pause: () => {
|
||||
if (vlcRef.current && typeof vlcRef.current.pause === 'function') {
|
||||
vlcRef.current.pause();
|
||||
logger.log('[VLC] Paused');
|
||||
}
|
||||
},
|
||||
play: () => {
|
||||
if (vlcRef.current && typeof vlcRef.current.play === 'function') {
|
||||
vlcRef.current.play();
|
||||
logger.log('[VLC] Played');
|
||||
}
|
||||
}
|
||||
}), [duration]);
|
||||
|
||||
// Compute aspect ratio string for VLC
|
||||
const toVlcRatio = useCallback((w: number, h: number): string => {
|
||||
const a = Math.max(1, Math.round(w));
|
||||
const b = Math.max(1, Math.round(h));
|
||||
const gcd = (x: number, y: number): number => (y === 0 ? x : gcd(y, x % y));
|
||||
const g = gcd(a, b);
|
||||
return `${Math.floor(a / g)}:${Math.floor(b / g)}`;
|
||||
}, []);
|
||||
|
||||
const screenDimensions = Dimensions.get('screen');
|
||||
|
||||
const vlcAspectRatio = useMemo(() => {
|
||||
// For VLC, no forced aspect ratio - let it preserve natural aspect
|
||||
return undefined;
|
||||
}, [resizeMode, screenDimensions.width, screenDimensions.height, toVlcRatio]);
|
||||
|
||||
const clientScale = useMemo(() => {
|
||||
if (!videoAspectRatio || screenDimensions.width <= 0 || screenDimensions.height <= 0) {
|
||||
return 1;
|
||||
}
|
||||
if (resizeMode === 'cover') {
|
||||
const screenAR = screenDimensions.width / screenDimensions.height;
|
||||
return Math.max(screenAR / videoAspectRatio, videoAspectRatio / screenAR);
|
||||
}
|
||||
return 1;
|
||||
}, [resizeMode, videoAspectRatio, screenDimensions.width, screenDimensions.height]);
|
||||
|
||||
// VLC options for better playback
|
||||
const vlcOptions = useMemo(() => {
|
||||
return [
|
||||
'--network-caching=2000',
|
||||
'--clock-jitter=0',
|
||||
'--http-reconnect',
|
||||
'--sout-mux-caching=2000'
|
||||
];
|
||||
}, []);
|
||||
|
||||
// VLC tracks prop
|
||||
const vlcTracks = useMemo(() => ({
|
||||
audio: selectedAudioTrack,
|
||||
video: 0, // Use first video track
|
||||
subtitle: selectedSubtitleTrack
|
||||
}), [selectedAudioTrack, selectedSubtitleTrack]);
|
||||
|
||||
const handleFirstPlay = useCallback((info: any) => {
|
||||
try {
|
||||
logger.log('[VLC] Video loaded, extracting tracks...');
|
||||
logger.log('[AndroidVideoPlayer][VLC] Video loaded successfully');
|
||||
|
||||
// Process VLC tracks using optimized function
|
||||
if (info?.tracks) {
|
||||
processVlcTracks(info.tracks);
|
||||
}
|
||||
|
||||
const lenSec = (info?.length ?? 0) / 1000;
|
||||
const width = info?.width || 0;
|
||||
const height = info?.height || 0;
|
||||
setDuration(lenSec);
|
||||
onLoad({ duration: lenSec, naturalSize: width && height ? { width, height } : undefined });
|
||||
|
||||
if (width > 0 && height > 0) {
|
||||
setVideoAspectRatio(width / height);
|
||||
}
|
||||
|
||||
// Restore playback position after remount (workaround for surface detach)
|
||||
if (restoreTime !== undefined && restoreTime !== null && restoreTime > 0) {
|
||||
setTimeout(() => {
|
||||
if (vlcRef.current && typeof vlcRef.current.seek === 'function') {
|
||||
const seekPosition = Math.min(restoreTime / lenSec, 0.999); // Convert to fraction
|
||||
vlcRef.current.seek(seekPosition);
|
||||
logger.log('[VLC] Seeked to restore position');
|
||||
}
|
||||
}, 500); // Small delay to ensure player is ready
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('[VLC] onFirstPlay error:', e);
|
||||
logger.warn('[AndroidVideoPlayer][VLC] onFirstPlay parse error', e);
|
||||
}
|
||||
}, [onLoad, restoreTime]);
|
||||
|
||||
const handlePositionChanged = useCallback((ev: any) => {
|
||||
const pos = typeof ev?.position === 'number' ? ev.position : 0;
|
||||
// We need duration to calculate current time, but it's not available here
|
||||
// The parent component should handle this calculation
|
||||
onProgress({ position: pos });
|
||||
}, [onProgress]);
|
||||
|
||||
const handlePlaying = useCallback(() => {
|
||||
setVlcActive(true);
|
||||
}, []);
|
||||
|
||||
const handlePaused = useCallback(() => {
|
||||
setVlcActive(false);
|
||||
}, []);
|
||||
|
||||
const handleEndReached = useCallback(() => {
|
||||
onEnd();
|
||||
}, [onEnd]);
|
||||
|
||||
const handleEncounteredError = useCallback((e: any) => {
|
||||
logger.error('[AndroidVideoPlayer][VLC] Encountered error:', e);
|
||||
onError(e);
|
||||
}, [onError]);
|
||||
|
||||
const handleBackground = useCallback(() => {
|
||||
logger.log('[VLC] App went to background');
|
||||
}, []);
|
||||
|
||||
const handleESAdded = useCallback((tracks: any) => {
|
||||
try {
|
||||
logger.log('[VLC] ES Added - processing tracks...');
|
||||
processVlcTracks(tracks);
|
||||
} catch (e) {
|
||||
logger.error('[VLC] onESAdded error:', e);
|
||||
logger.warn('[AndroidVideoPlayer][VLC] onESAdded parse error', e);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Format VLC tracks to match RN Video format - raw version
|
||||
const formatVlcTracks = useCallback((vlcTracks: Array<{id: number, name: string}>): VlcTrack[] => {
|
||||
if (!Array.isArray(vlcTracks)) return [];
|
||||
return vlcTracks.map(track => {
|
||||
// Just extract basic language info if available, but keep the full name
|
||||
let language = undefined;
|
||||
let displayName = track.name || `Track ${track.id + 1}`;
|
||||
|
||||
// Log the raw track data for debugging
|
||||
if (__DEV__) {
|
||||
logger.log(`[VLC] Raw track data:`, { id: track.id, name: track.name });
|
||||
}
|
||||
|
||||
// Only extract language from brackets if present, but keep full name
|
||||
const languageMatch = track.name?.match(/\[([^\]]+)\]/);
|
||||
if (languageMatch && languageMatch[1]) {
|
||||
language = languageMatch[1].trim();
|
||||
}
|
||||
|
||||
return {
|
||||
id: track.id,
|
||||
name: displayName, // Show exactly what VLC provides
|
||||
language: language
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Optimized VLC track processing function with reduced JSON operations
|
||||
const processVlcTracks = useCallback((tracks: any) => {
|
||||
if (!tracks) return;
|
||||
|
||||
// Log raw VLC tracks data for debugging
|
||||
if (__DEV__) {
|
||||
logger.log(`[VLC] Raw tracks data:`, tracks);
|
||||
}
|
||||
|
||||
const { audio = [], subtitle = [] } = tracks;
|
||||
|
||||
// Process audio tracks
|
||||
if (Array.isArray(audio) && audio.length > 0) {
|
||||
const formattedAudio = formatVlcTracks(audio);
|
||||
if (__DEV__) {
|
||||
logger.log(`[VLC] Audio tracks updated:`, formattedAudio.length);
|
||||
}
|
||||
}
|
||||
|
||||
// Process subtitle tracks
|
||||
if (Array.isArray(subtitle) && subtitle.length > 0) {
|
||||
const formattedSubs = formatVlcTracks(subtitle);
|
||||
if (__DEV__) {
|
||||
logger.log(`[VLC] Subtitle tracks updated:`, formattedSubs.length);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify parent of track updates
|
||||
onTracksUpdate({ audio, subtitle });
|
||||
}, [formatVlcTracks, onTracksUpdate]);
|
||||
|
||||
// Process URL for VLC compatibility
|
||||
const processUrlForVLC = useCallback((url: string): string => {
|
||||
if (!url || typeof url !== 'string') {
|
||||
logger.warn('[AndroidVideoPlayer][VLC] Invalid URL provided:', url);
|
||||
return url || '';
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if URL is already properly formatted
|
||||
const urlObj = new URL(url);
|
||||
|
||||
// Handle special characters in the pathname that might cause issues
|
||||
const pathname = urlObj.pathname;
|
||||
const search = urlObj.search;
|
||||
const hash = urlObj.hash;
|
||||
|
||||
// Decode and re-encode the pathname to handle double-encoding
|
||||
const decodedPathname = decodeURIComponent(pathname);
|
||||
const encodedPathname = encodeURI(decodedPathname);
|
||||
|
||||
// Reconstruct the URL
|
||||
const processedUrl = `${urlObj.protocol}//${urlObj.host}${encodedPathname}${search}${hash}`;
|
||||
|
||||
logger.log(`[AndroidVideoPlayer][VLC] URL processed: ${url} -> ${processedUrl}`);
|
||||
return processedUrl;
|
||||
} catch (error) {
|
||||
logger.warn(`[AndroidVideoPlayer][VLC] URL processing failed, using original: ${error}`);
|
||||
return url;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const processedSource = useMemo(() => processUrlForVLC(source), [source, processUrlForVLC]);
|
||||
|
||||
if (!LibVlcPlayerViewComponent) {
|
||||
return (
|
||||
<View style={{
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#000'
|
||||
}}>
|
||||
{/* VLC not available fallback */}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: screenDimensions.width,
|
||||
height: screenDimensions.height,
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
<LibVlcPlayerViewComponent
|
||||
ref={vlcRef}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: screenDimensions.width,
|
||||
height: screenDimensions.height,
|
||||
transform: [{ scale: clientScale }]
|
||||
}}
|
||||
// Force remount when surfaces are recreated
|
||||
key={key || 'vlc-default'}
|
||||
source={processedSource}
|
||||
aspectRatio={vlcAspectRatio}
|
||||
// Let VLC auto-fit the video to the view to prevent flicker on mode changes
|
||||
scale={0}
|
||||
options={vlcOptions}
|
||||
tracks={vlcTracks}
|
||||
volume={Math.round(Math.max(0, Math.min(1, volume)) * 100)}
|
||||
mute={false}
|
||||
repeat={false}
|
||||
rate={playbackSpeed}
|
||||
autoplay={false}
|
||||
onFirstPlay={handleFirstPlay}
|
||||
onPositionChanged={handlePositionChanged}
|
||||
onPlaying={handlePlaying}
|
||||
onPaused={handlePaused}
|
||||
onEndReached={handleEndReached}
|
||||
onEncounteredError={handleEncounteredError}
|
||||
onBackground={handleBackground}
|
||||
onESAdded={handleESAdded}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
VlcVideoPlayer.displayName = 'VlcVideoPlayer';
|
||||
|
||||
export default VlcVideoPlayer;
|
||||
|
|
@ -30,27 +30,6 @@ interface VideoSurfaceProps {
|
|||
onPinchGestureEvent: any;
|
||||
onPinchHandlerStateChange: any;
|
||||
screenDimensions: { width: number, height: number };
|
||||
|
||||
// Legacy props (kept for compatibility but unused with MPV)
|
||||
useVLC?: boolean;
|
||||
forceVlcRemount?: boolean;
|
||||
headers?: any;
|
||||
videoType?: any;
|
||||
vlcSelectedAudioTrack?: number;
|
||||
vlcSelectedSubtitleTrack?: number;
|
||||
vlcRestoreTime?: number;
|
||||
vlcKey?: string;
|
||||
selectedAudioTrack?: any;
|
||||
selectedTextTrack?: any;
|
||||
useCustomSubtitles?: boolean;
|
||||
onTracksUpdate?: (tracks: any) => void;
|
||||
vlcPlayerRef?: any;
|
||||
videoRef?: any;
|
||||
vlcLoadedRef?: any;
|
||||
customVideoStyles?: any;
|
||||
loadStartAtRef?: any;
|
||||
firstFrameAtRef?: any;
|
||||
zoomScale?: number;
|
||||
onTracksChanged?: (data: { audioTracks: any[]; subtitleTracks: any[] }) => void;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ const END_EPSILON = 0.3;
|
|||
|
||||
export const usePlayerControls = (
|
||||
mpvPlayerRef: any,
|
||||
vlcPlayerRef: any,
|
||||
useVLC: boolean,
|
||||
paused: boolean,
|
||||
setPaused: (paused: boolean) => void,
|
||||
currentTime: number,
|
||||
|
|
@ -29,40 +27,31 @@ export const usePlayerControls = (
|
|||
console.log('[usePlayerControls] seekToTime called:', {
|
||||
rawSeconds,
|
||||
timeInSeconds,
|
||||
useVLC,
|
||||
hasMpvRef: !!mpvPlayerRef?.current,
|
||||
hasVlcRef: !!vlcPlayerRef?.current,
|
||||
duration,
|
||||
isSeeking: isSeeking.current
|
||||
});
|
||||
|
||||
if (useVLC) {
|
||||
if (vlcPlayerRef.current && duration > 0) {
|
||||
logger.log(`[usePlayerControls][VLC] Seeking to ${timeInSeconds}`);
|
||||
vlcPlayerRef.current.seek(timeInSeconds);
|
||||
}
|
||||
// MPV Player
|
||||
if (mpvPlayerRef.current && duration > 0) {
|
||||
console.log(`[usePlayerControls][MPV] Seeking to ${timeInSeconds}`);
|
||||
|
||||
isSeeking.current = true;
|
||||
mpvPlayerRef.current.seek(timeInSeconds);
|
||||
|
||||
// Reset seeking flag after a delay
|
||||
setTimeout(() => {
|
||||
if (isMounted.current) {
|
||||
isSeeking.current = false;
|
||||
}
|
||||
}, 500);
|
||||
} else {
|
||||
// MPV Player
|
||||
if (mpvPlayerRef.current && duration > 0) {
|
||||
console.log(`[usePlayerControls][MPV] Seeking to ${timeInSeconds}`);
|
||||
|
||||
isSeeking.current = true;
|
||||
mpvPlayerRef.current.seek(timeInSeconds);
|
||||
|
||||
// Reset seeking flag after a delay
|
||||
setTimeout(() => {
|
||||
if (isMounted.current) {
|
||||
isSeeking.current = false;
|
||||
}
|
||||
}, 500);
|
||||
} else {
|
||||
console.log('[usePlayerControls][MPV] Cannot seek - ref or duration invalid:', {
|
||||
hasRef: !!mpvPlayerRef?.current,
|
||||
duration
|
||||
});
|
||||
}
|
||||
console.log('[usePlayerControls][MPV] Cannot seek - ref or duration invalid:', {
|
||||
hasRef: !!mpvPlayerRef?.current,
|
||||
duration
|
||||
});
|
||||
}
|
||||
}, [useVLC, duration, paused, setPaused, mpvPlayerRef, vlcPlayerRef, isSeeking, isMounted]);
|
||||
}, [duration, paused, setPaused, mpvPlayerRef, isSeeking, isMounted]);
|
||||
|
||||
const skip = useCallback((seconds: number) => {
|
||||
console.log('[usePlayerControls] skip called:', { seconds, currentTime, newTime: currentTime + seconds });
|
||||
|
|
|
|||
|
|
@ -7,14 +7,8 @@ interface Track {
|
|||
language?: string;
|
||||
}
|
||||
|
||||
export const usePlayerTracks = (
|
||||
useVLC: boolean,
|
||||
vlcAudioTracks: Track[],
|
||||
vlcSubtitleTracks: Track[],
|
||||
vlcSelectedAudioTrack: number | undefined,
|
||||
vlcSelectedSubtitleTrack: number | undefined
|
||||
) => {
|
||||
// React Native Video Tracks
|
||||
export const usePlayerTracks = () => {
|
||||
// Tracks from native player (MPV/RN-Video)
|
||||
const [rnVideoAudioTracks, setRnVideoAudioTracks] = useState<Track[]>([]);
|
||||
const [rnVideoTextTracks, setRnVideoTextTracks] = useState<Track[]>([]);
|
||||
|
||||
|
|
@ -22,31 +16,19 @@ export const usePlayerTracks = (
|
|||
const [selectedAudioTrack, setSelectedAudioTrack] = useState<SelectedTrack | null>({ type: 'system' });
|
||||
const [selectedTextTrack, setSelectedTextTrack] = useState<number>(-1);
|
||||
|
||||
// Unified Tracks
|
||||
const ksAudioTracks = useMemo(() =>
|
||||
useVLC ? vlcAudioTracks : rnVideoAudioTracks,
|
||||
[useVLC, vlcAudioTracks, rnVideoAudioTracks]
|
||||
);
|
||||
|
||||
const ksTextTracks = useMemo(() =>
|
||||
useVLC ? vlcSubtitleTracks : rnVideoTextTracks,
|
||||
[useVLC, vlcSubtitleTracks, rnVideoTextTracks]
|
||||
);
|
||||
// Unified Tracks (now just returns native tracks)
|
||||
const ksAudioTracks = useMemo(() => rnVideoAudioTracks, [rnVideoAudioTracks]);
|
||||
const ksTextTracks = useMemo(() => rnVideoTextTracks, [rnVideoTextTracks]);
|
||||
|
||||
// Unified Selection
|
||||
const computedSelectedAudioTrack = useMemo(() =>
|
||||
useVLC
|
||||
? (vlcSelectedAudioTrack ?? null)
|
||||
: (selectedAudioTrack?.type === 'index' && selectedAudioTrack?.value !== undefined
|
||||
? Number(selectedAudioTrack?.value)
|
||||
: null),
|
||||
[useVLC, vlcSelectedAudioTrack, selectedAudioTrack]
|
||||
selectedAudioTrack?.type === 'index' && selectedAudioTrack?.value !== undefined
|
||||
? Number(selectedAudioTrack?.value)
|
||||
: null,
|
||||
[selectedAudioTrack]
|
||||
);
|
||||
|
||||
const computedSelectedTextTrack = useMemo(() =>
|
||||
useVLC ? (vlcSelectedSubtitleTrack ?? -1) : selectedTextTrack,
|
||||
[useVLC, vlcSelectedSubtitleTrack, selectedTextTrack]
|
||||
);
|
||||
const computedSelectedTextTrack = useMemo(() => selectedTextTrack, [selectedTextTrack]);
|
||||
|
||||
return {
|
||||
rnVideoAudioTracks, setRnVideoAudioTracks,
|
||||
|
|
|
|||
|
|
@ -1,148 +0,0 @@
|
|||
import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
||||
import { logger } from '../../../../utils/logger';
|
||||
import { VlcPlayerRef } from '../../VlcVideoPlayer';
|
||||
|
||||
interface Track {
|
||||
id: number;
|
||||
name: string;
|
||||
language?: string;
|
||||
}
|
||||
|
||||
const DEBUG_MODE = false;
|
||||
|
||||
export const useVlcPlayer = (useVLC: boolean, paused: boolean, currentTime: number) => {
|
||||
const [vlcAudioTracks, setVlcAudioTracks] = useState<Track[]>([]);
|
||||
const [vlcSubtitleTracks, setVlcSubtitleTracks] = useState<Track[]>([]);
|
||||
const [vlcSelectedAudioTrack, setVlcSelectedAudioTrack] = useState<number | undefined>(undefined);
|
||||
const [vlcSelectedSubtitleTrack, setVlcSelectedSubtitleTrack] = useState<number | undefined>(undefined);
|
||||
const [vlcRestoreTime, setVlcRestoreTime] = useState<number | undefined>(undefined);
|
||||
const [forceVlcRemount, setForceVlcRemount] = useState(false);
|
||||
const [vlcKey, setVlcKey] = useState('vlc-initial');
|
||||
|
||||
const vlcPlayerRef = useRef<VlcPlayerRef>(null);
|
||||
const vlcLoadedRef = useRef<boolean>(false);
|
||||
const trackUpdateTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
// Handle VLC pause/play interactions
|
||||
useEffect(() => {
|
||||
if (useVLC && vlcLoadedRef.current && vlcPlayerRef.current) {
|
||||
if (paused) {
|
||||
vlcPlayerRef.current.pause();
|
||||
} else {
|
||||
vlcPlayerRef.current.play();
|
||||
}
|
||||
}
|
||||
}, [useVLC, paused]);
|
||||
|
||||
// Reset forceVlcRemount when VLC becomes inactive
|
||||
useEffect(() => {
|
||||
if (!useVLC && forceVlcRemount) {
|
||||
setForceVlcRemount(false);
|
||||
}
|
||||
}, [useVLC, forceVlcRemount]);
|
||||
|
||||
// Track selection
|
||||
const selectVlcAudioTrack = useCallback((trackId: number | null) => {
|
||||
setVlcSelectedAudioTrack(trackId ?? undefined);
|
||||
logger.log('[AndroidVideoPlayer][VLC] Audio track selected:', trackId);
|
||||
}, []);
|
||||
|
||||
const selectVlcSubtitleTrack = useCallback((trackId: number | null) => {
|
||||
setVlcSelectedSubtitleTrack(trackId ?? undefined);
|
||||
logger.log('[AndroidVideoPlayer][VLC] Subtitle track selected:', trackId);
|
||||
}, []);
|
||||
|
||||
// Track updates handler
|
||||
const handleVlcTracksUpdate = useCallback((tracks: { audio: any[], subtitle: any[] }) => {
|
||||
if (!tracks) return;
|
||||
|
||||
if (trackUpdateTimeoutRef.current) {
|
||||
clearTimeout(trackUpdateTimeoutRef.current);
|
||||
}
|
||||
|
||||
trackUpdateTimeoutRef.current = setTimeout(() => {
|
||||
const { audio = [], subtitle = [] } = tracks;
|
||||
let hasUpdates = false;
|
||||
|
||||
// Process Audio
|
||||
if (Array.isArray(audio) && audio.length > 0) {
|
||||
const formattedAudio = audio.map(track => ({
|
||||
id: track.id,
|
||||
name: track.name || `Track ${track.id + 1}`,
|
||||
language: track.language
|
||||
}));
|
||||
|
||||
const audioChanged = formattedAudio.length !== vlcAudioTracks.length ||
|
||||
formattedAudio.some((track, index) => {
|
||||
const existing = vlcAudioTracks[index];
|
||||
return !existing || track.id !== existing.id || track.name !== existing.name;
|
||||
});
|
||||
|
||||
if (audioChanged) {
|
||||
setVlcAudioTracks(formattedAudio);
|
||||
hasUpdates = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Process Subtitles
|
||||
if (Array.isArray(subtitle) && subtitle.length > 0) {
|
||||
const formattedSubs = subtitle.map(track => ({
|
||||
id: track.id,
|
||||
name: track.name || `Track ${track.id + 1}`,
|
||||
language: track.language
|
||||
}));
|
||||
|
||||
const subsChanged = formattedSubs.length !== vlcSubtitleTracks.length ||
|
||||
formattedSubs.some((track, index) => {
|
||||
const existing = vlcSubtitleTracks[index];
|
||||
return !existing || track.id !== existing.id || track.name !== existing.name;
|
||||
});
|
||||
|
||||
if (subsChanged) {
|
||||
setVlcSubtitleTracks(formattedSubs);
|
||||
hasUpdates = true;
|
||||
}
|
||||
}
|
||||
|
||||
trackUpdateTimeoutRef.current = null;
|
||||
}, 100);
|
||||
}, [vlcAudioTracks, vlcSubtitleTracks]);
|
||||
|
||||
// Cleanup
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (trackUpdateTimeoutRef.current) {
|
||||
clearTimeout(trackUpdateTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const remountVlc = useCallback((reason: string) => {
|
||||
if (useVLC) {
|
||||
logger.log(`[VLC] Forcing complete remount: ${reason}`);
|
||||
setVlcRestoreTime(currentTime);
|
||||
setForceVlcRemount(true);
|
||||
vlcLoadedRef.current = false;
|
||||
setTimeout(() => {
|
||||
setForceVlcRemount(false);
|
||||
setVlcKey(`vlc-${reason}-${Date.now()}`);
|
||||
}, 100);
|
||||
}
|
||||
}, [useVLC, currentTime]);
|
||||
|
||||
return {
|
||||
vlcAudioTracks,
|
||||
vlcSubtitleTracks,
|
||||
vlcSelectedAudioTrack,
|
||||
vlcSelectedSubtitleTrack,
|
||||
selectVlcAudioTrack,
|
||||
selectVlcSubtitleTrack,
|
||||
vlcPlayerRef,
|
||||
vlcLoadedRef,
|
||||
forceVlcRemount,
|
||||
vlcRestoreTime,
|
||||
vlcKey,
|
||||
handleVlcTracksUpdate,
|
||||
remountVlc,
|
||||
};
|
||||
};
|
||||
|
|
@ -2,13 +2,14 @@
|
|||
* Shared Custom Subtitles Hook
|
||||
* Used by both Android (VLC) and iOS (KSPlayer) players
|
||||
*/
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
DEFAULT_SUBTITLE_SIZE,
|
||||
SubtitleCue,
|
||||
SubtitleSegment,
|
||||
WyzieSubtitle
|
||||
} from '../utils/playerTypes';
|
||||
import { storageService } from '../../../services/storageService';
|
||||
|
||||
export const useCustomSubtitles = () => {
|
||||
// Data State
|
||||
|
|
@ -32,11 +33,58 @@ export const useCustomSubtitles = () => {
|
|||
const [subtitleOutlineColor, setSubtitleOutlineColor] = useState<string>('#000000');
|
||||
const [subtitleOutlineWidth, setSubtitleOutlineWidth] = useState<number>(4);
|
||||
const [subtitleAlign, setSubtitleAlign] = useState<'center' | 'left' | 'right'>('center');
|
||||
const [subtitleBottomOffset, setSubtitleBottomOffset] = useState<number>(10);
|
||||
const [subtitleBottomOffset, setSubtitleBottomOffset] = useState<number>(20);
|
||||
const [subtitleLetterSpacing, setSubtitleLetterSpacing] = useState<number>(0);
|
||||
const [subtitleLineHeightMultiplier, setSubtitleLineHeightMultiplier] = useState<number>(1.2);
|
||||
const [subtitleOffsetSec, setSubtitleOffsetSec] = useState<number>(0);
|
||||
|
||||
// Load subtitle settings on mount
|
||||
useEffect(() => {
|
||||
const loadSettings = async () => {
|
||||
const settings = await storageService.getSubtitleSettings();
|
||||
if (settings) {
|
||||
if (settings.subtitleSize !== undefined) setSubtitleSize(settings.subtitleSize);
|
||||
if (settings.subtitleBackground !== undefined) setSubtitleBackground(settings.subtitleBackground);
|
||||
if (settings.subtitleTextColor !== undefined) setSubtitleTextColor(settings.subtitleTextColor);
|
||||
if (settings.subtitleBgOpacity !== undefined) setSubtitleBgOpacity(settings.subtitleBgOpacity);
|
||||
if (settings.subtitleTextShadow !== undefined) setSubtitleTextShadow(settings.subtitleTextShadow);
|
||||
if (settings.subtitleOutline !== undefined) setSubtitleOutline(settings.subtitleOutline);
|
||||
if (settings.subtitleOutlineColor !== undefined) setSubtitleOutlineColor(settings.subtitleOutlineColor);
|
||||
if (settings.subtitleOutlineWidth !== undefined) setSubtitleOutlineWidth(settings.subtitleOutlineWidth);
|
||||
if (settings.subtitleAlign !== undefined) setSubtitleAlign(settings.subtitleAlign);
|
||||
if (settings.subtitleBottomOffset !== undefined) setSubtitleBottomOffset(settings.subtitleBottomOffset);
|
||||
if (settings.subtitleLetterSpacing !== undefined) setSubtitleLetterSpacing(settings.subtitleLetterSpacing);
|
||||
if (settings.subtitleLineHeightMultiplier !== undefined) setSubtitleLineHeightMultiplier(settings.subtitleLineHeightMultiplier);
|
||||
}
|
||||
};
|
||||
loadSettings();
|
||||
}, []);
|
||||
|
||||
// Save subtitle settings when they change
|
||||
useEffect(() => {
|
||||
const saveSettings = async () => {
|
||||
await storageService.saveSubtitleSettings({
|
||||
subtitleSize,
|
||||
subtitleBackground,
|
||||
subtitleTextColor,
|
||||
subtitleBgOpacity,
|
||||
subtitleTextShadow,
|
||||
subtitleOutline,
|
||||
subtitleOutlineColor,
|
||||
subtitleOutlineWidth,
|
||||
subtitleAlign,
|
||||
subtitleBottomOffset,
|
||||
subtitleLetterSpacing,
|
||||
subtitleLineHeightMultiplier,
|
||||
});
|
||||
};
|
||||
saveSettings();
|
||||
}, [
|
||||
subtitleSize, subtitleBackground, subtitleTextColor, subtitleBgOpacity,
|
||||
subtitleTextShadow, subtitleOutline, subtitleOutlineColor, subtitleOutlineWidth,
|
||||
subtitleAlign, subtitleBottomOffset, subtitleLetterSpacing, subtitleLineHeightMultiplier
|
||||
]);
|
||||
|
||||
return {
|
||||
customSubtitles, setCustomSubtitles,
|
||||
currentSubtitle, setCurrentSubtitle,
|
||||
|
|
|
|||
|
|
@ -125,7 +125,6 @@ export type RootStackParamList = {
|
|||
streamProvider?: string;
|
||||
streamName?: string;
|
||||
headers?: { [key: string]: string };
|
||||
forceVlc?: boolean;
|
||||
id?: string;
|
||||
type?: string;
|
||||
episodeId?: string;
|
||||
|
|
@ -146,7 +145,6 @@ export type RootStackParamList = {
|
|||
streamProvider?: string;
|
||||
streamName?: string;
|
||||
headers?: { [key: string]: string };
|
||||
forceVlc?: boolean;
|
||||
id?: string;
|
||||
type?: string;
|
||||
episodeId?: string;
|
||||
|
|
|
|||
|
|
@ -525,7 +525,6 @@ const DownloadsScreen: React.FC = () => {
|
|||
streamProvider: 'Downloads',
|
||||
streamName: item.providerName || 'Offline',
|
||||
headers: undefined,
|
||||
forceVlc: Platform.OS === 'android' ? isMkv : false,
|
||||
id: item.contentId, // Use contentId (base ID) instead of compound id for progress tracking
|
||||
type: item.type,
|
||||
episodeId: episodeId, // Pass episodeId for series progress tracking
|
||||
|
|
|
|||
|
|
@ -821,7 +821,7 @@ export const StreamsScreen = () => {
|
|||
fetchIMDbRatings();
|
||||
}, [type, id, currentEpisode?.season_number, currentEpisode?.episode_number]);
|
||||
|
||||
const navigateToPlayer = useCallback(async (stream: Stream, options?: { forceVlc?: boolean; headers?: Record<string, string> }) => {
|
||||
const navigateToPlayer = useCallback(async (stream: Stream, options?: { headers?: Record<string, string> }) => {
|
||||
// Filter headers for Vidrock - only send essential headers
|
||||
// Filter headers for Vidrock - only send essential headers
|
||||
// Filter headers for Vidrock - only send essential headers
|
||||
|
|
@ -859,9 +859,6 @@ export const StreamsScreen = () => {
|
|||
const streamName = stream.name || stream.title || 'Unnamed Stream';
|
||||
const streamProvider = stream.addonId || stream.addonName || stream.name;
|
||||
|
||||
// Do NOT pre-force VLC. Let ExoPlayer try first; fallback occurs on decoder error in the player.
|
||||
let forceVlc = !!options?.forceVlc;
|
||||
|
||||
// Save stream to cache for future use
|
||||
try {
|
||||
const episodeId = (type === 'series' || type === 'other') && selectedEpisode ? selectedEpisode : undefined;
|
||||
|
|
@ -922,8 +919,6 @@ export const StreamsScreen = () => {
|
|||
streamName: streamName,
|
||||
// Use filtered headers for Vidrock compatibility
|
||||
headers: finalHeaders,
|
||||
// Android will use this to choose VLC path; iOS ignores
|
||||
forceVlc,
|
||||
id,
|
||||
type,
|
||||
episodeId: (type === 'series' || type === 'other') && selectedEpisode ? selectedEpisode : undefined,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import { isMkvStream } from './mkvDetection';
|
|||
export interface PlayerSelectionOptions {
|
||||
uri: string;
|
||||
headers?: Record<string, string>;
|
||||
forceVlc?: boolean;
|
||||
platform?: typeof Platform.OS;
|
||||
}
|
||||
|
||||
|
|
@ -19,10 +18,9 @@ export interface PlayerSelectionOptions {
|
|||
export const shouldUseKSPlayer = ({
|
||||
uri,
|
||||
headers,
|
||||
forceVlc = false,
|
||||
platform = Platform.OS
|
||||
}: PlayerSelectionOptions): boolean => {
|
||||
// Android always uses AndroidVideoPlayer (react-native-video)
|
||||
// Android always uses AndroidVideoPlayer (MPV)
|
||||
if (platform === 'android') {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue