This commit is contained in:
tapframe 2026-03-02 06:19:16 +05:30
commit ec754b73bd
2 changed files with 68 additions and 8 deletions

View file

@ -4,6 +4,8 @@ import com.nuvio.tv.data.local.StreamAutoPlayMode
import com.nuvio.tv.data.local.StreamAutoPlaySource
import com.nuvio.tv.domain.model.AddonStreams
import com.nuvio.tv.domain.model.Stream
import java.net.HttpURLConnection
import java.net.URL
object StreamAutoPlaySelector {
fun orderAddonStreams(
@ -17,6 +19,52 @@ object StreamAutoPlaySelector {
return orderedAddons + pluginEntries
}
private fun resolvePlayableUrl(stream: Stream): String? {
val url = stream.getStreamUrl() ?: return null
// Pixeldrain is the ONLY host that needs special resolving
if ("pixeldrain" in url) {
val id = url.substringAfterLast("/")
val infoUrl = "https://pixeldrain.dev/api/file/$id/info"
val infoJson = runCatching { URL(infoUrl).readText() }.getOrNull() ?: return null
if (!infoJson.contains("\"success\":true")) return null
if (!infoJson.contains("\"mime_type\":\"video")) return null
return "https://pixeldrain.dev/api/file/$id?download"
}
// Everything else: return as-is
return url
}
private fun urlWorks(url: String): Boolean {
val lower = url.lowercase()
// Skip probing for signed or tokened URLs
if (listOf("token=", "expires=", "signature=", "sig=", "auth=", "key=", "hash=", "x-amz-", "hdnts=", "cf_")
.any { lower.contains(it) }) {
return true
}
// Safe HEAD probe for everything else
return runCatching {
val connection = URL(url).openConnection() as HttpURLConnection
connection.requestMethod = "HEAD"
connection.connectTimeout = 3000
connection.readTimeout = 3000
connection.instanceFollowRedirects = true
connection.connect()
connection.responseCode in 200..399
}.getOrElse { false }
}
fun selectAutoPlayStream(
streams: List<Stream>,
mode: StreamAutoPlayMode,
@ -80,9 +128,9 @@ object StreamAutoPlaySelector {
Regex("\\b(${exclusionWords.joinToString("|")})\\b", RegexOption.IGNORE_CASE)
} else null
candidateStreams.firstOrNull { stream ->
val url = stream.getStreamUrl() ?: return@firstOrNull false
// 1. Build list of ALL regexmatching streams
val matchingStreams = candidateStreams.filter { stream ->
val url = stream.getStreamUrl() ?: return@filter false
val searchableText = buildString {
append(stream.addonName).append(' ')
@ -91,20 +139,31 @@ object StreamAutoPlaySelector {
append(stream.description.orEmpty()).append(' ')
append(url)
}
// Must match user include pattern
if (!userRegex.containsMatchIn(searchableText)) return@firstOrNull false
// Must NOT match user exclusion pattern (if any)
// Must match include pattern
if (!userRegex.containsMatchIn(searchableText)) return@filter false
// Must NOT match exclusion pattern
if (excludeRegex != null && excludeRegex.containsMatchIn(searchableText)) {
return@firstOrNull false
return@filter false
}
true
}
if (matchingStreams.isEmpty()) return null
// 2. Try each matching stream until one works
for (stream in matchingStreams) {
val resolved = resolvePlayableUrl(stream) ?: continue
println("Trying resolved stream: $resolved")
if (urlWorks(resolved)) return stream
}
// None worked
null
}
}
}
}

View file

@ -911,6 +911,7 @@ private fun StreamRegexDialog(
"Dolby Atmos / DTS" to "(atmos|truehd|dts[-\\s]?hd|dtsx?)",
"English" to "(\\beng\\b|english)",
"No CAM/TS" to "^(?!.*\\b(cam|hdcam|ts|telesync)\\b).*$"
"No REMUX/HDR" to "(?is)^(?!.*\\b(hdr|hdr10|dv|dolby|vision|hevc|remux|2160p)\\b).+$"
)
}