import type { PlayerEvents } from '@react-native-youtube-bridge/core'; import { type WebView } from 'react-native-webview'; class WebviewYoutubePlayerController { private webViewRef: React.RefObject; private commandId = 0; private pendingCommands: Map void> = new Map(); constructor(webViewRef: React.RefObject) { this.webViewRef = webViewRef; } static createInstance( webViewRef: React.RefObject, ): WebviewYoutubePlayerController { return new WebviewYoutubePlayerController(webViewRef); } getPendingCommands(): Map void> { return this.pendingCommands; } async play(): Promise { await this.executeCommand('play'); } async pause(): Promise { await this.executeCommand('pause'); } async stop(): Promise { await this.executeCommand('stop'); } async seekTo(seconds: number, allowSeekAhead = true): Promise { await this.executeCommand('seekTo', [seconds, allowSeekAhead]); } async setVolume(volume: number): Promise { await this.executeCommand('setVolume', [volume]); } async getVolume(): Promise { return await this.executeCommand('getVolume', [], true); } async mute(): Promise { await this.executeCommand('mute'); } async unMute(): Promise { await this.executeCommand('unMute'); } async isMuted(): Promise { return await this.executeCommand('isMuted', [], true); } async getCurrentTime(): Promise { return await this.executeCommand('getCurrentTime', [], true); } async getDuration(): Promise { return await this.executeCommand('getDuration', [], true); } async getVideoUrl(): Promise { return await this.executeCommand('getVideoUrl', [], true); } async getVideoEmbedCode(): Promise { return await this.executeCommand('getVideoEmbedCode', [], true); } async getPlaybackRate(): Promise { return await this.executeCommand('getPlaybackRate', [], true); } async getAvailablePlaybackRates(): Promise { return await this.executeCommand('getAvailablePlaybackRates', [], true); } async getPlayerState(): Promise { return await this.executeCommand('getPlayerState', [], true); } async setPlaybackRate(suggestedRate: number): Promise { await this.executeCommand('setPlaybackRate', [suggestedRate]); } async getVideoLoadedFraction(): Promise { return await this.executeCommand('getVideoLoadedFraction', [], true); } async loadVideoById(videoId: string, startSeconds?: number, endSeconds?: number): Promise { await this.executeCommand('loadVideoById', [videoId, startSeconds, endSeconds]); } async cueVideoById(videoId: string, startSeconds?: number, endSeconds?: number): Promise { await this.executeCommand('cueVideoById', [videoId, startSeconds, endSeconds]); } async setSize(width: number, height: number): Promise { await this.executeCommand('setSize', [width, height]); } async cleanup(): Promise { await this.executeCommand('cleanup'); } async updateProgressInterval(interval: number): Promise { await this.executeCommand('updateProgressInterval', [interval]); } async setMutedTrackingEnabled(enabled: boolean): Promise { await this.executeCommand('setMutedTrackingEnabled', [enabled]); } private executeCommand( command: string, args: (string | number | boolean | undefined)[] = [], needsResult = false, ): Promise { return new Promise((resolve) => { if (!this.webViewRef.current) { resolve(null); return; } const messageId = needsResult ? (++this.commandId).toString() : undefined; if (needsResult && messageId) { const timeout = setTimeout(() => { this.pendingCommands.delete(messageId); console.warn('Command timeout:', command, messageId); resolve(null); }, 5000); this.pendingCommands.set(messageId, (result) => { clearTimeout(timeout); resolve(result); }); } const commandData = { command, args, ...(messageId && { id: messageId }), }; const injectScript = /* js */ ` window.__execCommand && window.__execCommand(${JSON.stringify(commandData)}); true; `; this.webViewRef.current.injectJavaScript(injectScript); if (!needsResult) { resolve(null); } }); } /** * Updates player event callbacks. No-op in WebView implementation. * This method exists for interface compatibility with web implementation. * @param _newCallbacks - Event callbacks (ignored in WebView) */ updateCallbacks(_newCallbacks: Partial): void { // no-op only for web } async destroy(): Promise { this.pendingCommands.clear(); await this.cleanup(); } } export default WebviewYoutubePlayerController;