@@ -1,3 +1,7 @@
import ' dart:async ' ;
import ' dart:collection ' ;
import ' dart:convert ' ;
import ' package:flutter/foundation.dart ' ;
import ' package:flutter_inappwebview/flutter_inappwebview.dart ' ;
abstract class ILinkExtractionService {
@@ -5,39 +9,664 @@ abstract class ILinkExtractionService {
}
class LinkExtractionService implements ILinkExtractionService {
HeadlessInAppWebView ? _headlessWebView ;
@ override
Future < String ? > extractVideoUrl ( String pageUrl ) async {
// 1. Check if it's already a video link
// 1. Zaten direkt bir video linkiyse hemen döndür
if ( _isVideoUrl ( pageUrl ) ) return pageUrl ;
// 2. Use Headless WebView to find the video
String ? foundUrl ;
_headlessWebView = HeadlessInAppWebView (
// 2. hdfilmizle.so gibi bilinen siteleri özel olarak işle
if ( _isHdFilmIzle ( pageUrl ) ) {
debugPrint ( " EXTRACTION: Detected hdfilmizle.so — using specialized extractor " ) ;
return _extractFromHdFilmIzle ( pageUrl ) ;
}
// 3. Genel amaçlı sniffer (diğer siteler için)
return _extractGeneric ( pageUrl ) ;
}
// ============================================================
// hdfilmizle.so Özel Extractor
// ============================================================
bool _isHdFilmIzle ( String url ) {
final lower = url . toLowerCase ( ) ;
return lower . contains ( ' hdfilmizle.so ' ) | |
lower . contains ( ' hdfilmizle.co ' ) | |
lower . contains ( ' hdfilmizle.org ' ) | |
lower . contains ( ' hdfilmizle.net ' ) ;
}
/// hdfilmizle.so için 2 aşamalı extraction:
/// Aşama 1: Ana sayfadan "parts" array'ini çıkar → iframe URL al
/// Aşama 2: iframe (vidrame.pro) sayfası ndan EE.dd() ile m3u8 URL decode et
Future < String ? > _extractFromHdFilmIzle ( String pageUrl ) async {
// AŞAMA 1: Ana sayfadan iframe URL'lerini çıkar
debugPrint ( " EXTRACTION [PHASE 1]: Loading main page to extract iframe URLs... " ) ;
final iframeUrl = await _extractIframeUrl ( pageUrl ) ;
if ( iframeUrl = = null ) {
debugPrint ( " EXTRACTION [PHASE 1]: No iframe URL found, falling back to generic extractor " ) ;
return _extractGeneric ( pageUrl ) ;
}
debugPrint ( " EXTRACTION [PHASE 1]: Found iframe URL: $ iframeUrl " ) ;
// AŞAMA 2: iframe sayfası ndan video URL'yi decode et
debugPrint ( " EXTRACTION [PHASE 2]: Loading iframe to decode video URL... " ) ;
final videoUrl = await _extractVideoFromIframe ( iframeUrl ) ;
if ( videoUrl ! = null ) {
debugPrint ( " EXTRACTION [PHASE 2]: SUCCESS! Video URL: $ videoUrl " ) ;
return videoUrl ;
}
debugPrint ( " EXTRACTION [PHASE 2]: Failed, falling back to generic extractor " ) ;
return _extractGeneric ( pageUrl ) ;
}
/// Aşama 1: hdfilmizle.so ana sayfası ndan "parts" JavaScript değişkenini parse eder
/// ve ilk iframe URL'sini döndürür.
Future < String ? > _extractIframeUrl ( String pageUrl ) async {
final completer = Completer < String ? > ( ) ;
HeadlessInAppWebView ? webView ;
Timer ? timeout ;
void cleanup ( ) {
timeout ? . cancel ( ) ;
webView ? . dispose ( ) ;
webView = null ;
}
timeout = Timer ( const Duration ( seconds: 12 ) , ( ) {
if ( ! completer . isCompleted ) {
debugPrint ( " EXTRACTION [PHASE 1]: Timeout " ) ;
cleanup ( ) ;
completer . complete ( null ) ;
}
} ) ;
// JS: "parts" array'ini bul ve iframe src'lerini çıkar
final extractScript = UserScript (
source : '''
(function() {
if (window.__phase1Injected) return;
window.__phase1Injected = true;
// parts array ' inden iframe URL çıkar
window.__extractIframeUrls = function() {
try {
// 1. Sayfadaki tüm script tagları nı tara
var scripts = document.querySelectorAll( ' script ' );
for (var i = 0; i < scripts.length; i++) {
var text = scripts[i].textContent || scripts[i].innerText || '' ;
// "let parts" veya "var parts" değişkenini bul
var partsMatch = text.match(/(?:let|var|const) \\ s+parts \\ s*= \\ s*( \\ [.*? \\ ]);/s);
if (partsMatch) {
try {
var partsData = eval( ' ( ' + partsMatch[1] + ' ) ' );
var iframeUrls = [];
for (var j = 0; j < partsData.length; j++) {
var part = partsData[j];
var data = part.data || part.html || '' ;
// iframe src ' yi çıkar
var iframeMatch = data.match(/src=[" ' ]([^" ' ]+)[" ' ]/i);
if (iframeMatch) {
iframeUrls.push({
title: part.title || part.name || ( ' Part ' + j),
url: iframeMatch[1]
});
}
}
if (iframeUrls.length > 0) {
window.flutter_inappwebview.callHandler( ' iframeUrlsFound ' , JSON.stringify(iframeUrls));
return;
}
} catch(e) {
console.log( ' PHASE1: Error parsing parts: ' + e);
}
}
}
// 2. Fallback: Sayfadaki mevcut iframe ' leri doğrudan kontrol et
var iframes = document.querySelectorAll( ' iframe ' );
var directUrls = [];
for (var k = 0; k < iframes.length; k++) {
var src = iframes[k].src || iframes[k].getAttribute( ' data-src ' ) || '' ;
if (src && src.indexOf( ' vidrame ' ) > -1 || src.indexOf( ' vidmoly ' ) > -1 ||
src.indexOf( ' closeload ' ) > -1 || src.indexOf( ' rapidezvous ' ) > -1 ||
src.indexOf( ' player ' ) > -1) {
directUrls.push({ title: ' iframe- ' + k, url: src });
}
}
if (directUrls.length > 0) {
window.flutter_inappwebview.callHandler( ' iframeUrlsFound ' , JSON.stringify(directUrls));
return;
}
// 3. Fallback: data-url veya benzeri attribute ' lerden
var playerElements = document.querySelectorAll( ' [data-url], [data-src], [data-iframe] ' );
var attrUrls = [];
for (var m = 0; m < playerElements.length; m++) {
var u = playerElements[m].getAttribute( ' data-url ' ) ||
playerElements[m].getAttribute( ' data-src ' ) ||
playerElements[m].getAttribute( ' data-iframe ' ) || '' ;
if (u && u.startsWith( ' http ' )) {
attrUrls.push({ title: ' data- ' + m, url: u });
}
}
if (attrUrls.length > 0) {
window.flutter_inappwebview.callHandler( ' iframeUrlsFound ' , JSON.stringify(attrUrls));
}
} catch(e) {
console.log( ' PHASE1: Top-level error: ' + e);
}
};
})();
''' ,
injectionTime: UserScriptInjectionTime . AT_DOCUMENT_START ,
forMainFrameOnly: true ,
) ;
webView = HeadlessInAppWebView (
initialUrlRequest: URLRequest ( url: WebUri ( pageUrl ) ) ,
onLoadResource: ( controller , resource ) {
final url = resource . url . toString ( ) ;
if ( _isVideoUrl ( url ) ) {
foundUrl = url ;
_headlessWebView ? . dispose ( ) ;
initialUserScripts: UnmodifiableListView < UserScript > ( [ extractScript ] ) ,
initialSettings: InAppWebViewSettings (
javaScriptEnabled: true ,
mediaPlaybackRequiresUserGesture: true ,
mixedContentMode: MixedContentMode . MIXED_CONTENT_ALWAYS_ALLOW ,
supportMultipleWindows: false ,
javaScriptCanOpenWindowsAutomatically: false ,
userAgent: " Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 " ,
) ,
onWebViewCreated: ( controller ) {
controller . addJavaScriptHandler (
handlerName: ' iframeUrlsFound ' ,
callback: ( args ) {
if ( args . isNotEmpty & & ! completer . isCompleted ) {
try {
final List < dynamic > urls = jsonDecode ( args [ 0 ] . toString ( ) ) ;
if ( urls . isNotEmpty ) {
final firstUrl = urls [ 0 ] [ ' url ' ] ? . toString ( ) ;
debugPrint ( " EXTRACTION [PHASE 1]: Found ${ urls . length } iframe(s), first: $ firstUrl " ) ;
cleanup ( ) ;
completer . complete ( firstUrl ) ;
}
} catch ( e ) {
debugPrint ( " EXTRACTION [PHASE 1]: JSON parse error: $ e " ) ;
}
}
} ,
) ;
} ,
onReceivedServerTrustAuthRequest: ( controller , challenge ) async {
return ServerTrustAuthResponse ( action: ServerTrustAuthResponseAction . PROCEED ) ;
} ,
onLoadStop: ( controller , url ) async {
debugPrint ( " EXTRACTION [PHASE 1]: Page loaded: $ url " ) ;
// Sayfa yüklendikten sonra extraction fonksiyonunu çalıştı r
for ( int i = 0 ; i < 5 ; i + + ) {
if ( completer . isCompleted ) break ;
await Future . delayed ( const Duration ( milliseconds: 800 ) ) ;
if ( completer . isCompleted ) break ;
try {
await controller . evaluateJavascript (
source : " if(window.__extractIframeUrls) window.__extractIframeUrls(); " ,
) ;
} catch ( e ) {
debugPrint ( " EXTRACTION [PHASE 1]: JS execution error: $ e " ) ;
}
}
} ,
) ;
await _headlessWebView ? . run ( ) ;
// Wait for some time to catch resources
await Future . delayed ( const Duration ( seconds: 5 ) ) ;
return foundUrl ;
await webView ? . run ( ) ;
return completer . future ;
}
/// Aşama 2: vidrame.pro iframe sayfası ndan video URL'yi decode eder.
/// Sayfa JWPlayer kullanı r ve video kaynağı EE.dd() fonksiyonu ile şifrelenir.
/// Strateji: Sayfanı n kendi JS'ini çalıştı rarak decode edilmiş URL'yi alı yoruz.
Future < String ? > _extractVideoFromIframe ( String iframeUrl ) async {
final completer = Completer < String ? > ( ) ;
HeadlessInAppWebView ? webView ;
Timer ? timeout ;
void cleanup ( ) {
timeout ? . cancel ( ) ;
webView ? . dispose ( ) ;
webView = null ;
}
void handleFoundUrl ( String url ) {
if ( ! completer . isCompleted ) {
cleanup ( ) ;
completer . complete ( url ) ;
}
}
timeout = Timer ( const Duration ( seconds: 15 ) , ( ) {
if ( ! completer . isCompleted ) {
debugPrint ( " EXTRACTION [PHASE 2]: Timeout " ) ;
cleanup ( ) ;
completer . complete ( null ) ;
}
} ) ;
// JS: vidrame.pro sayfası ndan video URL'yi çıkar
// EE.dd() fonksiyonu sayfada zaten tanı mlı , biz sadece configs.sources[0].file değerini alı yoruz
final decodeScript = UserScript (
source : '''
(function() {
if (window.__phase2Injected) return;
window.__phase2Injected = true;
console.log("PHASE2: Injected into: " + window.location.href);
// Yöntem 1: Network request ' leri yakala (en güvenilir)
function reportUrl(url) {
if (url && (url.includes( ' .m3u8 ' ) || url.includes( ' .mp4 ' ))) {
console.log("PHASE2: Found video URL via intercept: " + url);
window.flutter_inappwebview.callHandler( ' videoUrlFound ' , url);
}
}
var oldOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
reportUrl(url);
return oldOpen.apply(this, arguments);
};
var oldFetch = window.fetch;
window.fetch = function() {
var url = (arguments[0] instanceof Request) ? arguments[0].url : arguments[0];
if (url) reportUrl(url.toString());
return oldFetch.apply(this, arguments);
};
// Yöntem 2: JWPlayer configs ' ten doğrudan çıkar
window.__extractFromJwPlayer = function() {
try {
// jwplayer instance ' ı nı kontrol et
if (typeof jwplayer !== ' undefined ' ) {
var pl = jwplayer();
if (pl && pl.getPlaylistItem) {
var item = pl.getPlaylistItem();
if (item && item.file) {
reportUrl(item.file);
return true;
}
if (item && item.sources) {
for (var i = 0; i < item.sources.length; i++) {
if (item.sources[i].file) {
reportUrl(item.sources[i].file);
return true;
}
}
}
}
}
} catch(e) {
console.log("PHASE2: JWPlayer check error: " + e);
}
try {
// configs variable ' ı nı doğrudan kontrol et
if (typeof configs !== ' undefined ' && configs.sources) {
for (var j = 0; j < configs.sources.length; j++) {
if (configs.sources[j].file) {
reportUrl(configs.sources[j].file);
return true;
}
}
}
} catch(e) {
console.log("PHASE2: configs check error: " + e);
}
try {
// EE.dd decode fonksiyonunu doğrudan dene
if (typeof EE !== ' undefined ' && EE.dd) {
// Sayfadaki tüm scriptleri tara ve EE.dd(...) çağrı ları nı bul
var scripts = document.querySelectorAll( ' script ' );
for (var k = 0; k < scripts.length; k++) {
var text = scripts[k].textContent || '' ;
var matches = text.match(/EE \\ .dd \\ ([" ' ]([^" ' ]+)[" ' ] \\ )/g);
if (matches) {
for (var m = 0; m < matches.length; m++) {
var encoded = matches[m].match(/EE \\ .dd \\ ([" ' ]([^" ' ]+)[" ' ] \\ )/);
if (encoded && encoded[1]) {
try {
var decoded = EE.dd(encoded[1]);
if (decoded) reportUrl(decoded);
} catch(e2) {}
}
}
}
}
}
} catch(e) {
console.log("PHASE2: EE.dd scan error: " + e);
}
try {
// HTML içinde doğrudan m3u8/mp4 regex tara
var html = document.documentElement.innerHTML;
var match = html.match(/https?: \\ / \\ /[^" ' \\ s<>]+ \\ .(m3u8|mp4)[^" ' \\ s<>]*/i);
if (match) {
reportUrl(match[0]);
return true;
}
} catch(e) {}
try {
// Video elementlerini kontrol et
document.querySelectorAll( ' video ' ).forEach(function(v) {
if (v.src && v.src.length > 5) reportUrl(v.src);
v.querySelectorAll( ' source ' ).forEach(function(s) {
if (s.src) reportUrl(s.src);
});
});
} catch(e) {}
return false;
};
})();
''' ,
injectionTime: UserScriptInjectionTime . AT_DOCUMENT_START ,
forMainFrameOnly: false ,
) ;
webView = HeadlessInAppWebView (
initialUrlRequest: URLRequest ( url: WebUri ( iframeUrl ) ) ,
initialUserScripts: UnmodifiableListView < UserScript > ( [ decodeScript ] ) ,
initialSettings: InAppWebViewSettings (
javaScriptEnabled: true ,
mediaPlaybackRequiresUserGesture: false ,
mixedContentMode: MixedContentMode . MIXED_CONTENT_ALWAYS_ALLOW ,
supportMultipleWindows: false ,
javaScriptCanOpenWindowsAutomatically: false ,
useShouldInterceptRequest: true ,
userAgent: " Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 " ,
) ,
onWebViewCreated: ( controller ) {
controller . addJavaScriptHandler (
handlerName: ' videoUrlFound ' ,
callback: ( args ) {
if ( args . isNotEmpty ) {
handleFoundUrl ( args [ 0 ] . toString ( ) ) ;
}
} ,
) ;
} ,
onReceivedServerTrustAuthRequest: ( controller , challenge ) async {
return ServerTrustAuthResponse ( action: ServerTrustAuthResponseAction . PROCEED ) ;
} ,
shouldInterceptRequest: ( controller , request ) async {
final url = request . url . toString ( ) ;
if ( _isVideoUrl ( url ) ) {
handleFoundUrl ( url ) ;
}
return null ;
} ,
onLoadStop: ( controller , url ) async {
debugPrint ( " EXTRACTION [PHASE 2]: iframe loaded: $ url " ) ;
// JWPlayer'ı n yüklenmesini bekle ve video URL'yi çıkar
for ( int i = 0 ; i < 10 ; i + + ) {
if ( completer . isCompleted ) break ;
await Future . delayed ( const Duration ( seconds: 1 ) ) ;
if ( completer . isCompleted ) break ;
try {
await controller . evaluateJavascript (
source : " if(window.__extractFromJwPlayer) window.__extractFromJwPlayer(); " ,
) ;
} catch ( e ) {
debugPrint ( " EXTRACTION [PHASE 2]: JS execution error: $ e " ) ;
}
}
} ,
) ;
await webView ? . run ( ) ;
return completer . future ;
}
// ============================================================
// Dart tarafı nda EE.dd() decode (fallback olarak)
// ============================================================
/// vidrame.pro'nun EE.dd() şifre çözme algoritması :
/// 1. URL-safe base64 → standart base64 dönüştür
/// 2. Base64 decode
/// 3. ROT13 uygula
/// 4. String reverse
static String ? decodEEdd ( String encoded ) {
try {
// URL-safe base64 → standart base64
String s = encoded . replaceAll ( ' - ' , ' + ' ) . replaceAll ( ' _ ' , ' / ' ) ;
while ( s . length % 4 ! = 0 ) {
s + = ' = ' ;
}
// Base64 decode
final decoded = utf8 . decode ( base64Decode ( s ) ) ;
// ROT13
final rot13 = _rot13 ( decoded ) ;
// String reverse
return rot13 . split ( ' ' ) . reversed . join ( ' ' ) ;
} catch ( e ) {
debugPrint ( " decodEEdd error: $ e " ) ;
return null ;
}
}
/// ROT13 dönüşümü (harfler ve rakamlar dahil, vidrame'nin özel versiyonu)
static String _rot13 ( String input ) {
final buffer = StringBuffer ( ) ;
for ( int i = 0 ; i < input . length ; i + + ) {
final c = input . codeUnitAt ( i ) ;
if ( c > = 65 & & c < = 90 ) {
// A-Z
buffer . writeCharCode ( ( ( c - 65 + 13 ) % 26 ) + 65 ) ;
} else if ( c > = 97 & & c < = 122 ) {
// a-z
buffer . writeCharCode ( ( ( c - 97 + 13 ) % 26 ) + 97 ) ;
} else if ( c > = 48 & & c < = 57 ) {
// 0-9
buffer . writeCharCode ( ( ( c - 48 + 13 ) % 10 ) + 48 ) ;
} else {
buffer . writeCharCode ( c ) ;
}
}
return buffer . toString ( ) ;
}
// ============================================================
// Genel Amaçlı Sniffer (Fallback)
// ============================================================
Future < String ? > _extractGeneric ( String pageUrl ) async {
final completer = Completer < String ? > ( ) ;
Timer ? timeoutTimer ;
HeadlessInAppWebView ? headlessWebView ;
void cleanup ( ) {
timeoutTimer ? . cancel ( ) ;
headlessWebView ? . dispose ( ) ;
headlessWebView = null ;
}
void handleFoundUrl ( String url ) {
if ( ! completer . isCompleted ) {
debugPrint ( " EXTRACTION: SUCCESS! Found: $ url " ) ;
cleanup ( ) ;
completer . complete ( url ) ;
}
}
// Zaman aşımı (15 saniye)
timeoutTimer = Timer ( const Duration ( seconds: 15 ) , ( ) {
if ( ! completer . isCompleted ) {
debugPrint ( " EXTRACTION: Timeout - no video URL found " ) ;
cleanup ( ) ;
completer . complete ( null ) ;
}
} ) ;
// UserScript: JS Kodunu Iframe'ler dahil tüm sayfalara DOM yüklenmeden ENJEKTE EDİYORUZ
final snifferScript = UserScript (
source : '''
(function() {
if (window.snifferInjected) return;
window.snifferInjected = true;
console.log("SNIFFER: Injected into frame: " + window.location.href);
function reportUrl(url) {
if (url && (url.includes( ' .m3u8 ' ) || url.includes( ' .mp4 ' ))) {
window.flutter_inappwebview.callHandler( ' foundVideoUrl ' , url);
}
}
// 1. Override XMLHttpRequest
var oldOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
reportUrl(url);
return oldOpen.apply(this, arguments);
};
// 2. Override Fetch
var oldFetch = window.fetch;
window.fetch = function() {
var url = (arguments[0] instanceof Request) ? arguments[0].url : arguments[0];
if (url) reportUrl(url.toString());
return oldFetch.apply(this, arguments);
};
// 3. Oynatma Butonları na Tı klama (Tüm iframe ' lerde çalışacak)
window.clickPlayButtons = function() {
var selectors = [
' .play-that-video ' , ' .play-button ' , ' .vjs-big-play-button ' ,
' [aria-label="Play"] ' , ' .ytp-large-play-button ' , ' .play-icon ' ,
' #play ' , ' .player-play ' , ' .jw-display-icon-container ' ,
' div[class*="play"] ' , ' button[class*="play"] ' , ' svg[class*="play"] '
];
selectors.forEach(function(sel) {
try {
document.querySelectorAll(sel).forEach(function(el) {
if (el.offsetWidth > 0 || el.offsetHeight > 0) {
el.click();
}
});
} catch(e) {}
});
// Video etiketlerini direkt kontrol et
document.querySelectorAll( ' video ' ).forEach(function(v) {
if (v.src && v.src.length > 5) reportUrl(v.src);
v.querySelectorAll( ' source ' ).forEach(function(s) {
if (s.src) reportUrl(s.src);
});
});
};
// HTML içindeki linkleri Regex ile tara
var scanInterval = setInterval(function() {
var html = document.documentElement.innerHTML;
var match = html.match(/https?: \\ / \\ /[^" ' \\ s<>]+ \\ .(m3u8|mp4)[^" ' \\ s<>]*/i);
if (match) reportUrl(match[0]);
}, 1500);
})();
''' ,
injectionTime: UserScriptInjectionTime . AT_DOCUMENT_START , // Sayfa açılmadan hemen önce
forMainFrameOnly: false , // ÇOK ÖNEMLİ: Iframe'lerin içine girmesi için false olmalı !
) ;
headlessWebView = HeadlessInAppWebView (
initialUrlRequest: URLRequest ( url: WebUri ( pageUrl ) ) ,
initialUserScripts: UnmodifiableListView < UserScript > ( [ snifferScript ] ) ,
initialSettings: InAppWebViewSettings (
javaScriptEnabled: true ,
isInspectable: true ,
mediaPlaybackRequiresUserGesture: false , // ÇOK ÖNEMLİ: Videoları n tı klanmadan yüklenmesine izin ver
mixedContentMode: MixedContentMode . MIXED_CONTENT_ALWAYS_ALLOW ,
supportMultipleWindows: false ,
javaScriptCanOpenWindowsAutomatically: false ,
useShouldInterceptRequest: true ,
userAgent: " Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 " ,
) ,
onWebViewCreated: ( controller ) {
// JS'den gelen sinyali dinle
controller . addJavaScriptHandler ( handlerName: ' foundVideoUrl ' , callback: ( args ) {
if ( args . isNotEmpty ) {
handleFoundUrl ( args [ 0 ] . toString ( ) ) ;
}
} ) ;
} ,
onReceivedServerTrustAuthRequest: ( controller , challenge ) async {
return ServerTrustAuthResponse ( action: ServerTrustAuthResponseAction . PROCEED ) ;
} ,
shouldInterceptRequest: ( controller , request ) async {
final url = request . url . toString ( ) ;
if ( _isVideoUrl ( url ) ) {
handleFoundUrl ( url ) ;
}
return null ;
} ,
onLoadStop: ( controller , url ) async {
debugPrint ( " EXTRACTION: Page loaded: $ url " ) ;
// JS yüklenmiş mi diye kontrol et ve butonlara tı kla
for ( int i = 0 ; i < 8 ; i + + ) {
if ( completer . isCompleted ) break ;
await Future . delayed ( const Duration ( seconds: 1 ) ) ;
if ( completer . isCompleted ) break ;
try {
// WebView kapanmışsa hata fı rlatması nı engellemek için try-catch
await controller . evaluateJavascript ( source : " if(window.clickPlayButtons) window.clickPlayButtons(); " ) ;
} catch ( e ) {
debugPrint ( " WebView disposed before javascript execution. " ) ;
}
}
} ,
) ;
await headlessWebView ? . run ( ) ;
return completer . future ;
}
// ============================================================
// Yardı mcı Fonksiyonlar
// ============================================================
bool _isVideoUrl ( String url ) {
final lower = url . toLowerCase ( ) ;
return lower . contains ( ' .m3u8 ' ) | |
lower . contains ( ' .mp4 ' ) | |
lower . contains ( ' .mkv ' ) ;
if ( lower . startsWith ( ' data: ' ) | | lower . length < 10 ) return false ;
// Analitik veya reklam linklerini es geç
if ( lower . contains ( ' google.com ' ) | | lower . contains ( ' gstatic.com ' ) | | lower . contains ( ' analytics ' ) ) return false ;
return lower . contains ( ' .m3u8 ' ) | |
lower . contains ( ' .mp4 ' ) | |
lower . contains ( ' .mkv ' ) | |
lower . contains ( ' /hls/ ' ) | |
lower . contains ( ' master.m3u8 ' ) | |
lower . contains ( ' index.m3u8 ' ) | |
lower . contains ( ' playlist.m3u8 ' ) ;
}
}
}