From b77d681b417e885d59f58027353ce1d7085ee03d Mon Sep 17 00:00:00 2001 From: paregi12 Date: Sat, 17 Jan 2026 21:16:08 +0530 Subject: [PATCH] chore: remove experimental WebViewExtractor feature and dependency --- App.tsx | 2 - package.json | 1 - src/components/WebViewExtractor.tsx | 148 ------------------- src/components/player/AndroidVideoPlayer.tsx | 83 ----------- src/services/StreamExtractorService.ts | 91 ------------ 5 files changed, 325 deletions(-) delete mode 100644 src/components/WebViewExtractor.tsx delete mode 100644 src/services/StreamExtractorService.ts diff --git a/App.tsx b/App.tsx index 6d9e55fd..102d672c 100644 --- a/App.tsx +++ b/App.tsx @@ -44,7 +44,6 @@ import { AccountProvider, useAccount } from './src/contexts/AccountContext'; import { ToastProvider } from './src/contexts/ToastContext'; import { mmkvStorage } from './src/services/mmkvStorage'; import { CampaignManager } from './src/components/promotions/CampaignManager'; -import { WebViewExtractor } from './src/components/WebViewExtractor'; Sentry.init({ dsn: 'https://1a58bf436454d346e5852b7bfd3c95e8@o4509536317276160.ingest.de.sentry.io/4509536317734992', @@ -203,7 +202,6 @@ const ThemedApp = () => { onDismiss={githubUpdate.onDismiss} onLater={githubUpdate.onLater} /> - diff --git a/package.json b/package.json index 1c1cf6f1..64512dcd 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,6 @@ "react-native-vector-icons": "^10.3.0", "react-native-video": "6.18.0", "react-native-web": "^0.21.0", - "react-native-webview": "^13.15.0", "react-native-wheel-color-picker": "^1.3.1", "react-native-worklets": "^0.7.1" }, diff --git a/src/components/WebViewExtractor.tsx b/src/components/WebViewExtractor.tsx deleted file mode 100644 index d5ee4528..00000000 --- a/src/components/WebViewExtractor.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import React, { useEffect, useState, useRef } from 'react'; -import { View, StyleSheet } from 'react-native'; -import { WebView } from 'react-native-webview'; -import { streamExtractorService, EXTRACTOR_EVENTS } from '../services/StreamExtractorService'; -import { logger } from '../utils/logger'; - -export const WebViewExtractor: React.FC = () => { - const [currentRequest, setCurrentRequest] = useState<{ id: string; url: string; script?: string } | null>(null); - const webViewRef = useRef(null); - - useEffect(() => { - const startListener = (request: { id: string; url: string; script?: string }) => { - logger.log(`[WebViewExtractor] Received request: ${request.url}`); - setCurrentRequest(request); - }; - - streamExtractorService.events.on(EXTRACTOR_EVENTS.START_EXTRACTION, startListener); - - return () => { - streamExtractorService.events.off(EXTRACTOR_EVENTS.START_EXTRACTION, startListener); - }; - }, []); - - const handleMessage = (event: any) => { - if (!currentRequest) return; - - try { - const data = JSON.parse(event.nativeEvent.data); - if (data.type === 'found_stream') { - streamExtractorService.events.emit(EXTRACTOR_EVENTS.EXTRACTION_SUCCESS, { - id: currentRequest.id, - streamUrl: data.url, - headers: data.headers - }); - setCurrentRequest(null); // Reset after success - } else if (data.type === 'error') { - // Optional: Retry logic or just log - logger.warn(`[WebViewExtractor] Error from page: ${data.message}`); - } - } catch (e) { - logger.error('[WebViewExtractor] Failed to parse message:', e); - } - }; - - const handleError = (syntheticEvent: any) => { - const { nativeEvent } = syntheticEvent; - logger.warn('[WebViewExtractor] WebView error: ', nativeEvent); - if (currentRequest) { - streamExtractorService.events.emit(EXTRACTOR_EVENTS.EXTRACTION_FAILURE, { - id: currentRequest.id, - error: `WebView Error: ${nativeEvent.description}` - }); - setCurrentRequest(null); - } - }; - - // Default extraction script: looks for video tags and intercepts network traffic - const DEFAULT_INJECTED_JS = ` - (function() { - function sendStream(url, headers) { - // Broad regex to catch HLS, DASH, and common video containers - const videoRegex = /\.(m3u8|mp4|mpd|mkv|webm|mov|avi)(\?|$)/i; - if (!videoRegex.test(url)) return; - - window.ReactNativeWebView.postMessage(JSON.stringify({ - type: 'found_stream', - url: url, - headers: headers - })); - } - - // 1. Intercept Video Elements - function checkVideoElements() { - var videos = document.getElementsByTagName('video'); - for (var i = 0; i < videos.length; i++) { - if (videos[i].src && !videos[i].src.startsWith('blob:')) { - sendStream(videos[i].src); - return true; - } - // Check for source children - var sources = videos[i].getElementsByTagName('source'); - for (var j = 0; j < sources.length; j++) { - if (sources[j].src) { - sendStream(sources[j].src); - return true; - } - } - } - return false; - } - - // Check periodically - setInterval(checkVideoElements, 1000); - - // 2. Intercept XHR (optional, for m3u8/mp4 fetches) - var originalOpen = XMLHttpRequest.prototype.open; - XMLHttpRequest.prototype.open = function(method, url) { - sendStream(url); - originalOpen.apply(this, arguments); - }; - - // 3. Intercept Fetch - var originalFetch = window.fetch; - window.fetch = function(input, init) { - var url = typeof input === 'string' ? input : (input instanceof Request ? input.url : ''); - sendStream(url); - return originalFetch.apply(this, arguments); - }; - - // 4. Check for specific common player variables (optional) - // e.g., jwplayer, etc. - })(); - `; - - if (!currentRequest) { - return null; - } - - return ( - - - - ); -}; - -const styles = StyleSheet.create({ - hiddenContainer: { - position: 'absolute', - top: -1000, // Move off-screen - left: 0, - width: 1, - height: 1, - opacity: 0.01, // Almost invisible but technically rendered - }, -}); diff --git a/src/components/player/AndroidVideoPlayer.tsx b/src/components/player/AndroidVideoPlayer.tsx index d829b04a..a975b86e 100644 --- a/src/components/player/AndroidVideoPlayer.tsx +++ b/src/components/player/AndroidVideoPlayer.tsx @@ -62,7 +62,6 @@ import { WyzieSubtitle, SubtitleCue } from './utils/playerTypes'; import { findBestSubtitleTrack, findBestAudioTrack } from './utils/trackSelectionUtils'; import { useTheme } from '../../contexts/ThemeContext'; import axios from 'axios'; -import { streamExtractorService } from '../../services/StreamExtractorService'; const DEBUG_MODE = false; @@ -93,56 +92,6 @@ const AndroidVideoPlayer: React.FC = () => { const [currentStreamUrl, setCurrentStreamUrl] = useState(uri); const [currentVideoType, setCurrentVideoType] = useState((route.params as any).videoType); - - // Stream Resolution State - const [isResolving, setIsResolving] = useState(false); - const [resolutionError, setResolutionError] = useState(null); - - // Auto-resolve stream URL if it's an embed - useEffect(() => { - const resolveStream = async () => { - // Simple heuristic: If it doesn't look like a direct video file, try to resolve it - // Valid video extensions: .mp4, .mkv, .avi, .m3u8, .mpd, .mov, .flv, .webm - // Also check for magnet links (which don't need resolution) - const lowerUrl = uri.toLowerCase(); - const isDirectFile = /\.(mp4|mkv|avi|m3u8|mpd|mov|flv|webm)(\?|$)/.test(lowerUrl); - const isMagnet = lowerUrl.startsWith('magnet:'); - - // If it looks like a direct link or magnet, skip resolution - if (isDirectFile || isMagnet) { - setCurrentStreamUrl(uri); - return; - } - - logger.log(`[AndroidVideoPlayer] URL ${uri} does not look like a direct file. Attempting resolution...`); - setIsResolving(true); - setResolutionError(null); - - try { - const result = await streamExtractorService.extractStream(uri); - - if (result && result.streamUrl) { - logger.log(`[AndroidVideoPlayer] Resolved stream: ${result.streamUrl}`); - setCurrentStreamUrl(result.streamUrl); - - // If headers were returned, we might want to use them (though current player prop structure is simple) - // For now we just use the URL - } else { - logger.warn(`[AndroidVideoPlayer] Resolution returned no URL, using original.`); - setCurrentStreamUrl(uri); // Fallback to original - } - } catch (error) { - logger.error(`[AndroidVideoPlayer] Stream resolution failed:`, error); - // Fallback to original URL on error, maybe it works anyway? - setCurrentStreamUrl(uri); - // Optional: setResolutionError(error.message); - } finally { - setIsResolving(false); - } - }; - - resolveStream(); - }, [uri]); const [availableStreams, setAvailableStreams] = useState(passedAvailableStreams || {}); const [currentQuality, setCurrentQuality] = useState(quality); @@ -834,38 +783,6 @@ const AndroidVideoPlayer: React.FC = () => { height={playerState.screenDimensions.height} /> - {/* Stream Resolution Overlay */} - {isResolving && ( - - - - Resolving Stream Source... - - - Extracting video from embed link - - - )} - {!isTransitioningStream && ( ; -} - -interface ExtractionResult { - id: string; - streamUrl?: string; - headers?: Record; - error?: string; -} - -class StreamExtractorService { - private static instance: StreamExtractorService; - public events = new EventEmitter(); - private pendingRequests = new Map void; reject: (err: any) => void; timeout: NodeJS.Timeout }>(); - - private constructor() { - // Listen for results from the component - this.events.on(EXTRACTOR_EVENTS.EXTRACTION_SUCCESS, this.handleSuccess.bind(this)); - this.events.on(EXTRACTOR_EVENTS.EXTRACTION_FAILURE, this.handleFailure.bind(this)); - } - - static getInstance(): StreamExtractorService { - if (!StreamExtractorService.instance) { - StreamExtractorService.instance = new StreamExtractorService(); - } - return StreamExtractorService.instance; - } - - /** - * Extracts a direct stream URL from an embed URL using a hidden WebView. - * @param url The embed URL to load. - * @param script Optional custom JavaScript to run in the WebView. - * @param timeoutMs Timeout in milliseconds (default 15000). - * @returns Promise resolving to the stream URL or object with url and headers. - */ - public async extractStream(url: string, script?: string, timeoutMs = 15000): Promise<{ streamUrl: string; headers?: Record } | null> { - const id = Math.random().toString(36).substring(7); - logger.log(`[StreamExtractor] Starting extraction for ${url} (ID: ${id})`); - - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - this.finishRequest(id, undefined, 'Timeout waiting for stream extraction'); - }, timeoutMs); - - this.pendingRequests.set(id, { resolve, reject, timeout }); - - // Emit event for the component to pick up - this.events.emit(EXTRACTOR_EVENTS.START_EXTRACTION, { id, url, script }); - }); - } - - private handleSuccess(result: ExtractionResult) { - logger.log(`[StreamExtractor] Extraction success for ID: ${result.id}`); - this.finishRequest(result.id, { streamUrl: result.streamUrl, headers: result.headers }); - } - - private handleFailure(result: ExtractionResult) { - logger.log(`[StreamExtractor] Extraction failed for ID: ${result.id}: ${result.error}`); - this.finishRequest(result.id, undefined, result.error); - } - - private finishRequest(id: string, data?: any, error?: string) { - const pending = this.pendingRequests.get(id); - if (pending) { - clearTimeout(pending.timeout); - this.pendingRequests.delete(id); - if (error) { - pending.reject(new Error(error)); - } else { - pending.resolve(data); - } - } - } -} - -export const streamExtractorService = StreamExtractorService.getInstance(); -export default streamExtractorService;