Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,23 +1,41 @@
package com.lagradost.cloudstream3.network

import okhttp3.Dns
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.dnsoverhttps.DnsOverHttps
import java.net.Inet4Address
import java.net.InetAddress

/**
* Based on https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/network/DohProviders.kt
*/

/**
* Strips IPv6 addresses from any DNS result, returning IPv4-only.
* Android TV firmware often has a broken IPv6 stack — looks available but
* connections silently fail or hang, causing ExoPlayer errors 2004/2001
* while the same stream works fine on mobile over the same Wi-Fi.
*/
class IPv4OnlyDns(private val delegate: Dns = Dns.SYSTEM) : Dns {
override fun lookup(hostname: String): List<InetAddress> {
val addresses = delegate.lookup(hostname)
val ipv4Only = addresses.filterIsInstance<Inet4Address>()
return ipv4Only.ifEmpty { addresses }
}
}

fun OkHttpClient.Builder.addGenericDns(url: String, ips: List<String>) = dns(
DnsOverHttps
.Builder()
.client(build())
.url(url.toHttpUrl())
.bootstrapDnsHosts(
ips.map { InetAddress.getByName(it) }
)
.build()
IPv4OnlyDns(
DnsOverHttps
.Builder()
.client(build())
.url(url.toHttpUrl())
.bootstrapDnsHosts(
ips.map { InetAddress.getByName(it) }
)
.build()
)
)

fun OkHttpClient.Builder.addGoogleDns() = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ fun buildDefaultClient(context: Context, ignoreSSL: Boolean = false): OkHttpClie
val baseClient = OkHttpClient.Builder()
.followRedirects(true)
.followSslRedirects(true)
// Explicit timeouts — OkHttp's 10s default is too short for slower
// TV network stacks, a known contributor to ExoPlayer error 2004/2001.
.connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
.readTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
.writeTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
.apply {
if (ignoreSSL) {
ignoreAllSSLErrors()
Expand All @@ -63,6 +68,9 @@ fun buildDefaultClient(context: Context, ignoreSSL: Boolean = false): OkHttpClie
6 -> addQuad9Dns()
7 -> addDnsSbDns()
8 -> addCanadianShieldDns()
// Default (system DNS): still wrap with IPv4OnlyDns so Android TV's
// often-broken IPv6 stack doesn't get tried even with no DoH selected.
else -> dns(IPv4OnlyDns())
}
}
// Needs to be build as otherwise the other builders will change this object
Expand All @@ -87,4 +95,4 @@ fun getHeaders(
}) else mapOf()
val tempHeaders = (DEFAULT_HEADERS + headers + cookieMap)
return tempHeaders.toHeaders()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ import com.lagradost.cloudstream3.ui.player.live.LiveHelper
import com.lagradost.cloudstream3.ui.player.live.PREFERRED_LIVE_OFFSET
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.applyStyle
Expand Down Expand Up @@ -669,7 +670,9 @@ class CS3IPlayer : IPlayer {
}

companion object {
private const val CRONET_TIMEOUT_MS = 15_000
// 30s instead of 15s — TV network stacks are slower than phones
// and were timing out before the stream could even start.
private const val CRONET_TIMEOUT_MS = 30_000

/**
* Single shared engine, to minimize the overhead of maintaining many as:
Expand Down Expand Up @@ -765,6 +768,18 @@ class CS3IPlayer : IPlayer {
}

fun tryCreateEngine(context: Context, diskCacheSize: Long): CronetEngine? {
// Android TV: skip Cronet entirely and fall back to OkHttp instead.
// Cronet manages its own native networking stack (its own DNS resolution,
// its own IPv6 handling, its own QUIC/UDP transport) completely independent
// of OkHttp — none of OkHttp's IPv4-only DNS fix or timeout settings reach
// Cronet at all. Many Android TV boxes have broken IPv6 and/or block UDP,
// which Cronet has no public API to work around, but OkHttp (via
// IPv4OnlyDns in RequestsHelper/DohProviders) already handles correctly.
// This is the root cause behind ExoPlayer errors 2004/2001 on TV.
if (isLayout(TV)) {
return null
}

// Fast case, no need to recreate it
cronetEngine?.let {
return it
Expand All @@ -780,7 +795,10 @@ class CS3IPlayer : IPlayer {
CronetEngine.Builder(context)
.enableBrotli(true)
.enableHttp2(true)
.enableQuic(true)
// QUIC runs over UDP, which many routers/ISPs throttle or block,
// and which Cronet handles with no app-level override. Disabled as
// defense-in-depth in case TV detection above ever misses a device.
.enableQuic(false)
.setStoragePath(cacheDirectory.absolutePath)
.setLibraryLoader(null)
.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK, diskCacheSize)
Expand Down
Loading