diff --git a/app/src/main/java/com/nuvio/tv/core/player/StreamAutoPlaySelector.kt b/app/src/main/java/com/nuvio/tv/core/player/StreamAutoPlaySelector.kt index 92a20c2f..52f5a4c1 100644 --- a/app/src/main/java/com/nuvio/tv/core/player/StreamAutoPlaySelector.kt +++ b/app/src/main/java/com/nuvio/tv/core/player/StreamAutoPlaySelector.kt @@ -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, 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 regex‑matching 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 } + } } } diff --git a/app/src/main/java/com/nuvio/tv/ui/screens/settings/PlaybackAutoPlaySettings.kt b/app/src/main/java/com/nuvio/tv/ui/screens/settings/PlaybackAutoPlaySettings.kt index 0dcfde2c..e320535a 100644 --- a/app/src/main/java/com/nuvio/tv/ui/screens/settings/PlaybackAutoPlaySettings.kt +++ b/app/src/main/java/com/nuvio/tv/ui/screens/settings/PlaybackAutoPlaySettings.kt @@ -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).+$" ) }