mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-17 23:11:41 +00:00
some changes on ios player
This commit is contained in:
parent
53b439f1fd
commit
30b148d783
6 changed files with 78 additions and 139 deletions
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
45
src/utils/playerSelection.ts
Normal file
45
src/utils/playerSelection.ts
Normal 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';
|
||||
};
|
||||
Loading…
Reference in a new issue