some changes on ios player

This commit is contained in:
tapframe 2025-09-21 22:02:58 +05:30
parent 53b439f1fd
commit 30b148d783
6 changed files with 78 additions and 139 deletions

View file

@ -428,7 +428,7 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
PRODUCT_NAME = "Nuvio";
PRODUCT_NAME = Nuvio;
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -462,7 +462,7 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
PRODUCT_NAME = "Nuvio";
PRODUCT_NAME = Nuvio;
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";

View file

@ -16,7 +16,6 @@ class KSPlayerView: UIView {
private var isPaused = false
private var currentVolume: Float = 1.0
weak var viewManager: KSPlayerViewManager?
private var loadTimeoutWorkItem: DispatchWorkItem?
// Event blocks for Fabric
@objc var onLoad: RCTDirectEventBlock?
@ -149,18 +148,6 @@ class KSPlayerView: UIView {
print("KSPlayerView: Setting source: \(uri)")
print("KSPlayerView: URL scheme: \(url.scheme ?? "unknown"), host: \(url.host ?? "unknown")")
// Add timeout for source loading
loadTimeoutWorkItem?.cancel()
let work = DispatchWorkItem { [weak self] in
guard let self = self else { return }
let dur = self.playerView.playerLayer?.player.duration ?? 0
if dur <= 0 {
self.sendEvent("onError", ["error": "Stream timeout: unable to open input after 8 seconds"])
}
}
loadTimeoutWorkItem = work
DispatchQueue.main.asyncAfter(deadline: .now() + 8, execute: work)
playerView.set(resource: resource)
// Set up delegate after setting the resource
@ -427,8 +414,6 @@ extension KSPlayerView: KSPlayerLayerDelegate {
func player(layer: KSPlayerLayer, state: KSPlayerState) {
switch state {
case .readyToPlay:
// Cancel timeout when ready
loadTimeoutWorkItem?.cancel()
// Send onLoad event to React Native with track information
let p = layer.player
let tracks = getAvailableTracks()

View file

@ -37,7 +37,7 @@ import PlayerControls from './controls/PlayerControls';
import CustomSubtitles from './subtitles/CustomSubtitles';
import { SourcesModal } from './modals/SourcesModal';
import { stremioService } from '../../services/stremioService';
import { isMkvStream } from '../../utils/mkvDetection';
import { shouldUseKSPlayer } from '../../utils/playerSelection';
import axios from 'axios';
import * as Brightness from 'expo-brightness';
@ -2332,39 +2332,8 @@ const AndroidVideoPlayer: React.FC = () => {
return;
}
// On iOS: if the selected stream is MKV, switch to KSPlayer screen by replacing route
if (Platform.OS === 'ios') {
const targetIsMkv = isMkvStream(newStream.url, newStream.headers || {});
if (targetIsMkv) {
// Ensure current player stops immediately before switching screens
setPaused(true);
setShowSourcesModal(false);
// Small delay to guarantee audio halt before navigation switch
setTimeout(() => {
(navigation as any).replace('Player', {
uri: newStream.url,
title,
episodeTitle,
season,
episode,
quality: (newStream.title?.match(/(\d+)p/) || [])[1] || newStream.quality,
year,
streamProvider: newStream.addonName || newStream.name || newStream.addon || 'Unknown',
streamName: newStream.name || newStream.title || 'Unknown Stream',
headers: newStream.headers || undefined,
id,
type,
episodeId,
imdbId,
backdrop,
availableStreams,
// Ensure KSPlayer is chosen even if URL/headers do not reveal MKV
forceVlc: true,
});
}, 50);
return;
}
}
// Note: iOS now always uses KSPlayer, so this AndroidVideoPlayer should never be used on iOS
// This logic is kept for safety in case routing changes
setIsChangingSource(true);
setShowSourcesModal(false);

View file

@ -32,7 +32,7 @@ import {
} from './utils/playerTypes';
import { safeDebugLog, parseSRT, DEBUG_MODE, formatTime } from './utils/playerUtils';
import { styles } from './utils/playerStyles';
import { isMkvStream } from '../../utils/mkvDetection';
import { shouldUseKSPlayer } from '../../utils/playerSelection';
import { SubtitleModals } from './modals/SubtitleModals';
import { AudioTrackModal } from './modals/AudioTrackModal';
// Removed ResumeOverlay usage when alwaysResume is enabled
@ -43,32 +43,32 @@ import axios from 'axios';
import { stremioService } from '../../services/stremioService';
import * as Brightness from 'expo-brightness';
// KSPlayerRouter component handles platform selection without conditional hook calls
// KSPlayerRouter component handles platform selection
const KSPlayerRouter: React.FC = () => {
const route = useRoute<RouteProp<RootStackParamList, 'Player'>>();
const { uri, headers } = route.params as any;
const { uri, headers, forceVlc } = route.params as any;
// Detect if stream is MKV format
const isMkvFile = isMkvStream(uri, headers);
// Honor forceVlc from navigation params for iOS, or fallback to MKV detection
const forceVlc = ((route.params as any)?.forceVlc === true);
// Use AndroidVideoPlayer for Android devices. On iOS, use KSPlayer when MKV or forced.
const shouldUseAndroidPlayer = Platform.OS === 'android' || (Platform.OS === 'ios' && !(isMkvFile || forceVlc));
// Use centralized player selection logic
const shouldUseKSPlayerComponent = shouldUseKSPlayer({
uri,
headers,
forceVlc
});
safeDebugLog("Player selection logic", {
platform: Platform.OS,
isMkvFile,
uri,
forceVlc,
shouldUseAndroidPlayer
shouldUseKSPlayer: shouldUseKSPlayerComponent
});
if (shouldUseAndroidPlayer) {
return <AndroidVideoPlayer />;
// iOS: Always use KSPlayer (handles all formats with AVPlayer → FFmpeg fallback)
// Android: Always use AndroidVideoPlayer (react-native-video/ExoPlayer)
if (shouldUseKSPlayerComponent) {
return <KSPlayerCore />;
}
return <KSPlayerCore />;
return <AndroidVideoPlayer />;
};
const KSPlayer: React.FC = () => {
@ -2318,36 +2318,8 @@ const KSPlayerCore: React.FC = () => {
return;
}
// On iOS: if the selected stream is NOT MKV, switch to AndroidVideoPlayer screen by replacing route
if (Platform.OS === 'ios') {
const targetIsMkv = isMkvStream(newStream.url, newStream.headers || {});
if (!targetIsMkv) {
// Ensure KSPlayer stops immediately before switching screens
setPaused(true);
setShowSourcesModal(false);
setTimeout(() => {
navigation.replace('Player', {
uri: newStream.url,
title,
episodeTitle,
season,
episode,
quality: (newStream.title?.match(/(\d+)p/) || [])[1] || newStream.quality,
year,
streamProvider: newStream.addonName || newStream.name || newStream.addon || 'Unknown',
streamName: newStream.name || newStream.title || 'Unknown Stream',
headers: newStream.headers || undefined,
id,
type,
episodeId,
imdbId,
backdrop,
availableStreams,
} as any);
}, 50);
return;
}
}
// On iOS: All streams use KSPlayer, no need to switch players
// Stream switching is handled internally by KSPlayerCore
setIsChangingSource(true);
setShowSourcesModal(false);

View file

@ -874,41 +874,9 @@ export const StreamsScreen = () => {
const streamName = stream.name || stream.title || 'Unnamed Stream';
const streamProvider = stream.addonId || stream.addonName || stream.name;
// Determine if we should force VLC on iOS based on actual stream format and provider capability
let forceVlc = !!options?.forceVlc;
try {
if (Platform.OS === 'ios' && !forceVlc) {
const isMkvFile = isMkvStream(stream.url, stream.headers);
// Special case: moviebox should always use AndroidVideoPlayer
if (streamProvider === 'moviebox') {
forceVlc = false;
logger.log(`[StreamsScreen] Provider ${streamProvider} -> always using AndroidVideoPlayer`);
} else {
// Also check if the provider declares MKV format support
let providerSupportsMkv = false;
try {
const availableScrapers = await localScraperService.getAvailableScrapers();
const provider = availableScrapers.find(scraper => scraper.id === streamProvider);
if (provider && provider.formats) {
providerSupportsMkv = provider.formats.includes('mkv');
logger.log(`[StreamsScreen] Provider ${streamProvider} formats:`, provider.formats, 'supports MKV:', providerSupportsMkv);
}
} catch (providerError) {
logger.warn('[StreamsScreen] Failed to check provider formats:', providerError);
}
if (isMkvFile || providerSupportsMkv) {
forceVlc = true;
logger.log(`[StreamsScreen] Stream is MKV format (detected: ${isMkvFile}, provider supports: ${providerSupportsMkv}) -> forcing VLC`);
} else {
logger.log(`[StreamsScreen] Stream is NOT MKV format (detected: ${isMkvFile}, provider supports: ${providerSupportsMkv}) -> using AndroidVideoPlayer`);
}
}
}
} catch (e) {
logger.warn('[StreamsScreen] Stream format detection failed:', e);
}
// iOS now always uses KSPlayer, no need for player selection logic
// Keep forceVlc for backward compatibility but it's ignored by player selection
const forceVlc = !!options?.forceVlc;
// Show a quick full-screen black overlay to mask rotation flicker
@ -944,7 +912,7 @@ export const StreamsScreen = () => {
streamName: streamName,
// Always prefer stream.headers; player will use these for requests
headers: options?.headers || stream.headers || undefined,
// Force VLC for providers that declare MKV format support on iOS
// iOS now always uses KSPlayer, forceVlc kept for backward compatibility
forceVlc,
id,
type,
@ -974,15 +942,15 @@ export const StreamsScreen = () => {
if (Platform.OS === 'ios' && settings.preferredPlayer === 'internal') {
// Check if the actual stream is an MKV file
const lowerUri = (stream.url || '').toLowerCase();
// iOS now always uses KSPlayer, no need for format-specific logic
// Keep this for logging purposes only
const contentType = (stream.headers && ((stream.headers as any)['Content-Type'] || (stream.headers as any)['content-type'])) || '';
const isMkvByHeader = typeof contentType === 'string' && contentType.includes('matroska');
const isMkvByPath = lowerUri.includes('.mkv') || /[?&]ext=mkv\b/.test(lowerUri) || /format=mkv\b/.test(lowerUri) || /container=mkv\b/.test(lowerUri);
const isMkvFile = Boolean(isMkvByHeader || isMkvByPath);
if (isMkvFile) {
logger.log(`[StreamsScreen] Stream is MKV format -> forcing VLC on iOS (internal preferred)`);
navigateToPlayer(stream, { forceVlc: true });
return;
logger.log(`[StreamsScreen] Stream is MKV format - will play in KSPlayer on iOS`);
}
}
} catch (err) {
@ -1005,8 +973,8 @@ export const StreamsScreen = () => {
...(stream.headers || {}),
'Content-Type': 'video/x-matroska',
} as Record<string, string>;
logger.log('[StreamsScreen] HEAD detected MKV via Content-Type quickly, forcing in-app VLC on iOS (internal preferred)');
navigateToPlayer(stream, { forceVlc: true, headers: mergedHeaders });
logger.log('[StreamsScreen] HEAD detected MKV via Content-Type - will play in KSPlayer on iOS');
navigateToPlayer(stream, { headers: mergedHeaders });
return;
}
} catch (e) {

View file

@ -0,0 +1,45 @@
/**
* Centralized player selection logic
* Used by both StreamsScreen and KSPlayer routing
*/
import { Platform } from 'react-native';
import { isMkvStream } from './mkvDetection';
export interface PlayerSelectionOptions {
uri: string;
headers?: Record<string, string>;
forceVlc?: boolean;
platform?: typeof Platform.OS;
}
/**
* Determines which player should be used for a given stream
*/
export const shouldUseKSPlayer = ({
uri,
headers,
forceVlc = false,
platform = Platform.OS
}: PlayerSelectionOptions): boolean => {
// Android always uses AndroidVideoPlayer (react-native-video)
if (platform === 'android') {
return false;
}
// iOS: Always use KSPlayer for all formats
// KSPlayer handles automatic fallback (AVPlayer → FFmpeg)
if (platform === 'ios') {
return true;
}
// Default fallback
return false;
};
/**
* Get the appropriate player component name
*/
export const getPlayerComponent = (options: PlayerSelectionOptions): 'AndroidVideoPlayer' | 'KSPlayerCore' => {
return shouldUseKSPlayer(options) ? 'KSPlayerCore' : 'AndroidVideoPlayer';
};