import React, { useRef, useImperativeHandle, forwardRef, useEffect, useState } from 'react'; import { View, requireNativeComponent, UIManager, findNodeHandle, NativeModules } from 'react-native'; export interface KSPlayerSource { uri: string; headers?: Record; } interface KSPlayerViewProps { source?: KSPlayerSource; paused?: boolean; volume?: number; rate?: number; audioTrack?: number; textTrack?: number; allowsExternalPlayback?: boolean; usesExternalPlaybackWhileExternalScreenIsActive?: boolean; subtitleBottomOffset?: number; subtitleFontSize?: number; subtitleTextColor?: string; subtitleBackgroundColor?: string; subtitleOutlineEnabled?: boolean; resizeMode?: 'contain' | 'cover' | 'stretch'; onLoad?: (data: any) => void; onProgress?: (data: any) => void; onBuffering?: (data: any) => void; onEnd?: () => void; onError?: (error: any) => void; onBufferingProgress?: (data: any) => void; style?: any; } const KSPlayerViewManager = requireNativeComponent('KSPlayerView'); const KSPlayerModule = NativeModules.KSPlayerModule; export interface KSPlayerRef { seek: (time: number) => void; setSource: (source: KSPlayerSource) => void; setPaused: (paused: boolean) => void; setVolume: (volume: number) => void; setPlaybackRate: (rate: number) => void; setAudioTrack: (trackId: number) => void; setTextTrack: (trackId: number) => void; getTracks: () => Promise<{ audioTracks: any[]; textTracks: any[] }>; setAllowsExternalPlayback: (allows: boolean) => void; setUsesExternalPlaybackWhileExternalScreenIsActive: (uses: boolean) => void; getAirPlayState: () => Promise<{ allowsExternalPlayback: boolean; usesExternalPlaybackWhileExternalScreenIsActive: boolean; isExternalPlaybackActive: boolean }>; showAirPlayPicker: () => void; } export interface KSPlayerProps { source?: KSPlayerSource; paused?: boolean; volume?: number; rate?: number; audioTrack?: number; textTrack?: number; allowsExternalPlayback?: boolean; usesExternalPlaybackWhileExternalScreenIsActive?: boolean; subtitleBottomOffset?: number; subtitleFontSize?: number; subtitleTextColor?: string; subtitleBackgroundColor?: string; subtitleOutlineEnabled?: boolean; resizeMode?: 'contain' | 'cover' | 'stretch'; onLoad?: (data: any) => void; onProgress?: (data: any) => void; onBuffering?: (data: any) => void; onEnd?: () => void; onError?: (error: any) => void; onBufferingProgress?: (data: any) => void; style?: any; } const KSPlayer = forwardRef((props, ref) => { const nativeRef = useRef(null); const [key, setKey] = useState(0); // Force re-render when source changes useImperativeHandle(ref, () => ({ seek: (time: number) => { if (nativeRef.current) { const node = findNodeHandle(nativeRef.current); // @ts-ignore legacy UIManager commands path for Paper const commandId = UIManager.getViewManagerConfig('KSPlayerView').Commands.seek; UIManager.dispatchViewManagerCommand(node, commandId, [time]); } }, setSource: (source: KSPlayerSource) => { if (nativeRef.current) { const node = findNodeHandle(nativeRef.current); // @ts-ignore legacy UIManager commands path for Paper const commandId = UIManager.getViewManagerConfig('KSPlayerView').Commands.setSource; UIManager.dispatchViewManagerCommand(node, commandId, [source]); } }, setPaused: (paused: boolean) => { if (nativeRef.current) { const node = findNodeHandle(nativeRef.current); // @ts-ignore legacy UIManager commands path for Paper const commandId = UIManager.getViewManagerConfig('KSPlayerView').Commands.setPaused; UIManager.dispatchViewManagerCommand(node, commandId, [paused]); } }, setVolume: (volume: number) => { if (nativeRef.current) { const node = findNodeHandle(nativeRef.current); // @ts-ignore legacy UIManager commands path for Paper const commandId = UIManager.getViewManagerConfig('KSPlayerView').Commands.setVolume; UIManager.dispatchViewManagerCommand(node, commandId, [volume]); } }, setPlaybackRate: (rate: number) => { if (nativeRef.current) { const node = findNodeHandle(nativeRef.current); // @ts-ignore legacy UIManager commands path for Paper const commandId = UIManager.getViewManagerConfig('KSPlayerView').Commands.setPlaybackRate; UIManager.dispatchViewManagerCommand(node, commandId, [rate]); } }, setAudioTrack: (trackId: number) => { if (nativeRef.current) { const node = findNodeHandle(nativeRef.current); // @ts-ignore legacy UIManager commands path for Paper const commandId = UIManager.getViewManagerConfig('KSPlayerView').Commands.setAudioTrack; UIManager.dispatchViewManagerCommand(node, commandId, [trackId]); } }, setTextTrack: (trackId: number) => { console.log('[KSPlayerComponent] setTextTrack called with trackId:', trackId); if (nativeRef.current) { const node = findNodeHandle(nativeRef.current); console.log('[KSPlayerComponent] setTextTrack dispatching command to node:', node); // @ts-ignore legacy UIManager commands path for Paper const commandId = UIManager.getViewManagerConfig('KSPlayerView').Commands.setTextTrack; console.log('[KSPlayerComponent] setTextTrack commandId:', commandId); UIManager.dispatchViewManagerCommand(node, commandId, [trackId]); } else { console.warn('[KSPlayerComponent] setTextTrack: nativeRef.current is null'); } }, getTracks: async () => { if (nativeRef.current) { const node = findNodeHandle(nativeRef.current); if (node) { return await KSPlayerModule.getTracks(node); } } return { audioTracks: [], textTracks: [] }; }, setAllowsExternalPlayback: (allows: boolean) => { if (nativeRef.current) { const node = findNodeHandle(nativeRef.current); // @ts-ignore legacy UIManager commands path for Paper const commandId = UIManager.getViewManagerConfig('KSPlayerView').Commands.setAllowsExternalPlayback; UIManager.dispatchViewManagerCommand(node, commandId, [allows]); } }, setUsesExternalPlaybackWhileExternalScreenIsActive: (uses: boolean) => { if (nativeRef.current) { const node = findNodeHandle(nativeRef.current); // @ts-ignore legacy UIManager commands path for Paper const commandId = UIManager.getViewManagerConfig('KSPlayerView').Commands.setUsesExternalPlaybackWhileExternalScreenIsActive; UIManager.dispatchViewManagerCommand(node, commandId, [uses]); } }, getAirPlayState: async () => { if (nativeRef.current) { const node = findNodeHandle(nativeRef.current); if (node) { return await KSPlayerModule.getAirPlayState(node); } } return { allowsExternalPlayback: false, usesExternalPlaybackWhileExternalScreenIsActive: false, isExternalPlaybackActive: false }; }, showAirPlayPicker: () => { if (nativeRef.current) { const node = findNodeHandle(nativeRef.current); if (node) { console.log('[KSPlayerComponent] Calling showAirPlayPicker with node:', node); KSPlayerModule.showAirPlayPicker(node); } else { console.warn('[KSPlayerComponent] Cannot call showAirPlayPicker: node is null'); } } else { console.log('[KSPlayerComponent] nativeRef.current is null'); } }, })); // No need for event listeners - events are handled through props // Force re-render when source changes to ensure proper reloading useEffect(() => { if (props.source) { setKey(prev => prev + 1); } }, [props.source?.uri]); return ( props.onLoad?.(e?.nativeEvent ?? e)} onProgress={(e: any) => props.onProgress?.(e?.nativeEvent ?? e)} onBuffering={(e: any) => props.onBuffering?.(e?.nativeEvent ?? e)} onEnd={() => props.onEnd?.()} onError={(e: any) => props.onError?.(e?.nativeEvent ?? e)} onBufferingProgress={(e: any) => props.onBufferingProgress?.(e?.nativeEvent ?? e)} style={props.style} /> ); }); KSPlayer.displayName = 'KSPlayer'; export default KSPlayer;