Skip to content

Commit 800bc90

Browse files
errorcodeQQerrorcodeQQ
authored andcommitted
Fix BinTV sports and PPV streams loading by implementing native WebView API loader and capturing Android context
1 parent e6923f5 commit 800bc90

4 files changed

Lines changed: 170 additions & 33 deletions

File tree

BinTV/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ cloudstream {
77
tvTypes = listOf("Live")
88
language = "en"
99
iconUrl = "https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsQsag_KQPokaomZsgDV4fiXQ_fi494N6wV8cwKyQQuhSnOh1pAl4lV2Ur-yHCG6IFBimoeWaZKiOTQyyEmfYLetghJRbhyoTHZuzfbZ9VOWZV5aNE4L4akyYmk5D1sB-QLzVQLy200JxziBg0Wwetdxb0Ybf7oqv4R1W8t49rsYLsFkLHZuNPL42I8Q/s512/letter-b.png"
10+
requiresResources = false
1011
}
1112

1213
android {
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package com.bintv
22

3-
import com.lagradost.cloudstream3.plugins.BasePlugin
3+
import android.content.Context
4+
import com.lagradost.cloudstream3.plugins.Plugin
45
import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
56

67
@CloudstreamPlugin
7-
class BinTVPlugin : BasePlugin() {
8-
override fun load() {
9-
// Register provider
8+
class BinTVPlugin : Plugin() {
9+
override fun load(context: Context) {
10+
BinTVProvider.context = context
1011
registerMainAPI(BinTVProvider())
1112
}
1213
}

BinTV/src/main/kotlin/com/bintv/BinTVProvider.kt

Lines changed: 93 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@ import com.lagradost.cloudstream3.utils.*
55
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
66
import com.lagradost.cloudstream3.utils.AppUtils.toJson
77
import com.lagradost.cloudstream3.network.CloudflareKiller
8+
import com.lagradost.cloudstream3.network.WebViewResolver
89
import com.fasterxml.jackson.annotation.JsonProperty
910
import java.net.ServerSocket
1011
import java.net.Socket
1112
import kotlin.concurrent.thread
1213
import kotlinx.coroutines.runBlocking
14+
import kotlinx.coroutines.withContext
15+
import kotlinx.coroutines.Dispatchers
16+
import kotlin.coroutines.suspendCoroutine
17+
import kotlin.coroutines.resume
1318

1419
class BinTVProvider : MainAPI() {
1520

@@ -123,6 +128,86 @@ class BinTVProvider : MainAPI() {
123128
}
124129
}
125130

131+
private suspend fun loadUrlViaWebView(url: String): String? {
132+
val ctx = context ?: return null
133+
return withContext(Dispatchers.Main) {
134+
suspendCoroutine { continuation ->
135+
try {
136+
val webView = android.webkit.WebView(ctx)
137+
val settings = webView.settings
138+
settings.javaScriptEnabled = true
139+
settings.domStorageEnabled = true
140+
settings.databaseEnabled = true
141+
settings.userAgentString = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36"
142+
143+
var resumed = false
144+
145+
webView.webViewClient = object : android.webkit.WebViewClient() {
146+
override fun onPageFinished(view: android.webkit.WebView?, pageUrl: String?) {
147+
super.onPageFinished(view, pageUrl)
148+
if (resumed) return
149+
150+
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
151+
if (!resumed) {
152+
webView.evaluateJavascript("(function() { return document.body ? document.body.innerText : document.documentElement.outerHTML; })()") { result ->
153+
if (!resumed) {
154+
resumed = true
155+
val cleanedResult = if (result != null && result.startsWith("\"") && result.endsWith("\"")) {
156+
try {
157+
com.fasterxml.jackson.databind.ObjectMapper().readValue(result, String::class.java)
158+
} catch (e: Exception) {
159+
result
160+
}
161+
} else {
162+
result
163+
}
164+
165+
try {
166+
webView.destroy()
167+
} catch (e: Exception) {}
168+
continuation.resume(cleanedResult)
169+
}
170+
}
171+
}
172+
}, 500)
173+
}
174+
175+
override fun onReceivedError(
176+
view: android.webkit.WebView?,
177+
request: android.webkit.WebResourceRequest?,
178+
error: android.webkit.WebResourceError?
179+
) {
180+
super.onReceivedError(view, request, error)
181+
if (resumed) return
182+
resumed = true
183+
try {
184+
webView.destroy()
185+
} catch (e: Exception) {}
186+
continuation.resume(null)
187+
}
188+
}
189+
190+
webView.loadUrl(url)
191+
192+
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
193+
if (!resumed) {
194+
resumed = true
195+
try {
196+
webView.destroy()
197+
} catch (e: Exception) {}
198+
continuation.resume(null)
199+
}
200+
}, 8000)
201+
} catch (e: Exception) {
202+
println("BinTV: loadUrlViaWebView exception: ${e.message}")
203+
try {
204+
continuation.resume(null)
205+
} catch (ex: Exception) {}
206+
}
207+
}
208+
}
209+
}
210+
126211
override suspend fun getMainPage(
127212
page: Int,
128213
request: MainPageRequest
@@ -204,36 +289,19 @@ class BinTVProvider : MainAPI() {
204289

205290
// Fetch PPV matches
206291
val ppvMatches = mutableListOf<EventLoadData>()
207-
val ppvUrls = listOf(
208-
"https://api.ppv.cx/api/streams",
209-
"https://old.ppv.to/api/streams",
210-
"https://api.ppv.to/api/streams"
211-
)
212292
val ppvHeaders = mapOf(
213293
"User-Agent" to "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36",
214294
"Referer" to "https://www.bintv.net/",
215295
"Origin" to "https://www.bintv.net"
216296
)
217297

218-
var ppvText = ""
219-
for (url in ppvUrls) {
220-
try {
221-
val res = if (url.contains("api.ppv.cx")) {
222-
app.get(url, headers = ppvHeaders, interceptor = cfInterceptor, timeout = 20L)
223-
} else {
224-
try {
225-
app.get(url, headers = ppvHeaders, timeout = 15L)
226-
} catch (e: Exception) {
227-
app.get(url, headers = ppvHeaders, interceptor = cfInterceptor, timeout = 20L)
228-
}
229-
}
230-
if (res.code == 200 && res.text.isNotBlank()) {
231-
ppvText = res.text
232-
break
233-
}
234-
} catch (e: Exception) {
235-
println("BinTV: failed to load PPV matches from $url - ${e.message}")
236-
}
298+
println("BinTV: Fetching PPV matches via loadUrlViaWebView...")
299+
val ppvText = loadUrlViaWebView("https://api.ppv.to/api/streams") ?: ""
300+
println("BinTV: loadUrlViaWebView response length: ${ppvText.length}")
301+
if (ppvText.length > 500) {
302+
println("BinTV: loadUrlViaWebView response snippet: ${ppvText.substring(0, 500)}")
303+
} else {
304+
println("BinTV: loadUrlViaWebView response snippet: $ppvText")
237305
}
238306

239307
if (ppvText.isNotBlank()) {
@@ -626,6 +694,7 @@ class BinTVProvider : MainAPI() {
626694
}
627695

628696
companion object {
697+
var context: android.content.Context? = null
629698
private val cfInterceptor = CloudflareKiller()
630699
private var serverSocket: ServerSocket? = null
631700
private var port: Int = 0

BinTV/src/main/kotlin/com/bintv/EmbedIndiaExtractor.kt

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,22 +62,88 @@ class EmbedIndiaExtractor : ExtractorApi() {
6262

6363
// WebView resolver fallback
6464
try {
65+
val playScript = """
66+
(function() {
67+
try {
68+
var attempts = 0;
69+
var interval = setInterval(function() {
70+
attempts++;
71+
var results = [];
72+
73+
// 1. Try JWPlayer click
74+
var jwButton = document.querySelector('.jw-icon-display') ||
75+
document.querySelector('.jw-display-icon-container');
76+
if (jwButton) {
77+
jwButton.click();
78+
results.push('jw_clicked');
79+
}
80+
81+
// 2. Try jwplayer JS API
82+
if (typeof jwplayer !== 'undefined') {
83+
try {
84+
jwplayer().play();
85+
results.push('jw_api_played');
86+
} catch(e) {
87+
results.push('jw_api_err_' + e.message);
88+
}
89+
}
90+
91+
// 3. Try HTML5 video elements play
92+
var videos = document.querySelectorAll('video');
93+
if (videos && videos.length > 0) {
94+
for (var i = 0; i < videos.length; i++) {
95+
try {
96+
videos[i].play();
97+
results.push('video_played_' + i);
98+
} catch(e) {
99+
results.push('video_played_err_' + i + '_' + e.message);
100+
}
101+
}
102+
}
103+
104+
// 4. Try any play overlay
105+
var playOverlay = document.querySelector('[class*="play"]') ||
106+
document.querySelector('[id*="play"]');
107+
if (playOverlay && playOverlay !== document.body) {
108+
try {
109+
playOverlay.click();
110+
results.push('overlay_clicked');
111+
} catch(e) {}
112+
}
113+
114+
if (results.length > 0) {
115+
console.log('Play triggered: ' + results.join(', '));
116+
clearInterval(interval);
117+
}
118+
if (attempts > 30) {
119+
clearInterval(interval);
120+
}
121+
}, 500);
122+
return 'loop_started';
123+
} catch(e) {
124+
return 'error: ' + e.message;
125+
}
126+
})()
127+
""".trimIndent()
128+
65129
val resolver = WebViewResolver(
66-
interceptUrl = Regex("""(?i)\.m3u8(?:\?|$)"""),
67-
additionalUrls = listOf(Regex("""(?i)\.m3u8(?:\?|$)""")),
68-
script = null,
130+
interceptUrl = Regex("""(?i)(m3u8|master\.txt)"""),
131+
additionalUrls = listOf(Regex("""(?i)(m3u8|master\.txt)""")),
132+
script = playScript,
133+
scriptCallback = { result -> Log.d("EmbedIndiaJS", "Script Result: ${"$"}{result}") },
69134
useOkhttp = false,
70135
timeout = 30_000L
71136
)
72137
val resolved = app.get(
73138
url,
139+
headers = fetchHeaders,
74140
referer = referer ?: "$embedHost/",
75141
interceptor = resolver
76142
).url
77-
if (resolved.contains(".m3u8", ignoreCase = true)) {
143+
if (resolved.contains("m3u8", ignoreCase = true) || resolved.contains("master.txt", ignoreCase = true)) {
78144
emitLink(callback, name, resolved, embedHost, 0)
79145
} else {
80-
Log.w(name, "WebViewResolver returned non-m3u8 URL for $url: $resolved")
146+
Log.w(name, "WebViewResolver returned non-stream URL for $url: $resolved")
81147
}
82148
} catch (e: Exception) {
83149
Log.w(name, "WebViewResolver failed for $url: ${e.message}")

0 commit comments

Comments
 (0)