From 7a439fa81424a0fdf9a9870cb04b33fc5520a886 Mon Sep 17 00:00:00 2001 From: tapframe Date: Sat, 10 Jan 2026 02:22:45 +0530 Subject: [PATCH] added mpv switch toggle --- ios/KSPlayerManager.m | 23 ++++++++++++-- src/components/player/KSPlayerCore.tsx | 31 ++++++++++++++++--- .../player/controls/PlayerControls.tsx | 12 ++++++- .../player/ios/components/AVPlayerSurface.tsx | 1 + .../ios/components/AirPlayRoutePicker.tsx | 19 ++++++++++++ 5 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 src/components/player/ios/components/AirPlayRoutePicker.tsx diff --git a/ios/KSPlayerManager.m b/ios/KSPlayerManager.m index ea83566..b0ba942 100644 --- a/ios/KSPlayerManager.m +++ b/ios/KSPlayerManager.m @@ -1,6 +1,23 @@ #import -// Dummy -@interface KSPlayerManager : NSObject +#import + +@interface AirPlayRoutePickerViewManager : RCTViewManager @end -@implementation KSPlayerManager + +@implementation AirPlayRoutePickerViewManager + +// Expose as `AirPlayRoutePickerView` for `requireNativeComponent` +RCT_EXPORT_MODULE(AirPlayRoutePickerView) + +- (UIView *)view +{ + AVRoutePickerView *picker = [AVRoutePickerView new]; + picker.backgroundColor = UIColor.clearColor; + if (@available(iOS 11.0, *)) { + picker.tintColor = UIColor.whiteColor; + picker.activeTintColor = UIColor.whiteColor; + } + return picker; +} + @end diff --git a/src/components/player/KSPlayerCore.tsx b/src/components/player/KSPlayerCore.tsx index 42cedb8..84809eb 100644 --- a/src/components/player/KSPlayerCore.tsx +++ b/src/components/player/KSPlayerCore.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useState, useCallback } from 'react'; -import { View, StatusBar, StyleSheet, Animated, Dimensions, Platform } from 'react-native'; +import { View, StatusBar, StyleSheet, Animated, Dimensions, Platform, Alert } from 'react-native'; import { useNavigation, useRoute } from '@react-navigation/native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import axios from 'axios'; @@ -502,6 +502,13 @@ const KSPlayerCore: React.FC = () => { modals.setShowErrorModal(true); }; + // Confirm and execute the switch to MPV (iOS manual switch) + const confirmSwitchToMPV = useCallback(() => { + lastPlaybackTimeRef.current = currentTime || lastPlaybackTimeRef.current || 0; + hasAviOSFailed.current = true; // reuse the resume-on-load path + setUseMpvFallback(true); + }, [currentTime]); + const handleClose = useCallback(() => { if (isSyncingBeforeClose.current) return; isSyncingBeforeClose.current = true; @@ -816,10 +823,23 @@ const KSPlayerCore: React.FC = () => { onSwitchToMPV={ (!shouldUseMpvOnly && !useMpvFallback) ? () => { - // Manual switch (like Android): go to MPV for this session and resume at current time. - lastPlaybackTimeRef.current = currentTime || lastPlaybackTimeRef.current || 0; - hasAviOSFailed.current = true; // reuse the resume-on-load path - setUseMpvFallback(true); + // Manual switch should confirm (like Android) + Alert.alert( + 'Switch to MPV Player?', + 'This will switch from AVPlayer to MPV player. Use this if you\'re facing playback issues. The switch cannot be undone during this playback session.', + [ + { + text: 'Cancel', + style: 'cancel', + }, + { + text: 'Switch to MPV', + onPress: confirmSwitchToMPV, + style: 'default', + }, + ], + { cancelable: true } + ); } : undefined } @@ -1028,6 +1048,7 @@ const KSPlayerCore: React.FC = () => { onSelectStream={handleEpisodeStreamSelect} metadata={{ id: id, name: title }} /> + ); }; diff --git a/src/components/player/controls/PlayerControls.tsx b/src/components/player/controls/PlayerControls.tsx index 29d884b..3ed342f 100644 --- a/src/components/player/controls/PlayerControls.tsx +++ b/src/components/player/controls/PlayerControls.tsx @@ -8,6 +8,7 @@ import { useTranslation } from 'react-i18next'; import { styles } from '../utils/playerStyles'; // Updated styles import { getTrackDisplayName } from '../utils/playerUtils'; import { useTheme } from '../../../contexts/ThemeContext'; +import AirPlayRoutePicker from '../ios/components/AirPlayRoutePicker'; interface PlayerControlsProps { showControls: boolean; @@ -350,8 +351,10 @@ export const PlayerControls: React.FC = ({ (Platform.OS === 'ios' && onSwitchToMPV && playerBackend === 'AVPlayer') ) && ( = ({ /> )} + + {/* AirPlay picker (iOS + AVPlayer) */} + {Platform.OS === 'ios' && playerBackend === 'AVPlayer' && ( + + + + )} diff --git a/src/components/player/ios/components/AVPlayerSurface.tsx b/src/components/player/ios/components/AVPlayerSurface.tsx index 130c031..b4301ab 100644 --- a/src/components/player/ios/components/AVPlayerSurface.tsx +++ b/src/components/player/ios/components/AVPlayerSurface.tsx @@ -102,6 +102,7 @@ export const AVPlayerSurface: React.FC = ({ volume={volume} rate={playbackSpeed} resizeMode={resizeMode as any} + allowsExternalPlayback={true} selectedAudioTrack={selectedAudioTrack} selectedTextTrack={selectedTextTrack} onLoad={handleLoad} diff --git a/src/components/player/ios/components/AirPlayRoutePicker.tsx b/src/components/player/ios/components/AirPlayRoutePicker.tsx new file mode 100644 index 0000000..e4e7607 --- /dev/null +++ b/src/components/player/ios/components/AirPlayRoutePicker.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Platform, requireNativeComponent, ViewStyle, View } from 'react-native'; + +type Props = { + style?: ViewStyle; +}; + +const NativeAirPlayRoutePickerView = + Platform.OS === 'ios' + ? requireNativeComponent('AirPlayRoutePickerView') + : null; + +export const AirPlayRoutePicker: React.FC = (props) => { + if (!NativeAirPlayRoutePickerView) return ; + return ; +}; + +export default AirPlayRoutePicker; +