diff --git a/app/build.gradle b/app/build.gradle index bb23e1e..dac99d7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "com.streamplayer" minSdk 21 targetSdk 33 - versionCode 100300 - versionName "10.0.3" + versionCode 100400 + versionName "10.0.4" buildConfigField "String", "DEVICE_REGISTRY_URL", '"http://194.163.191.200:4000"' } diff --git a/app/src/main/java/com/streamplayer/StreamUrlResolver.java b/app/src/main/java/com/streamplayer/StreamUrlResolver.java index 95dfdb0..fa610e5 100644 --- a/app/src/main/java/com/streamplayer/StreamUrlResolver.java +++ b/app/src/main/java/com/streamplayer/StreamUrlResolver.java @@ -1,16 +1,8 @@ package com.streamplayer; -import android.util.Base64; -import android.util.Log; - import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -22,17 +14,16 @@ import okhttp3.Response; import okhttp3.dnsoverhttps.DnsOverHttps; /** - * Resuelve la URL real del stream analizando el JavaScript ofuscado de streamtpcloud. + * Resuelve la URL real del stream extrayendo playbackURL de la página. * Utiliza DNS de Google para evitar bloqueos. */ public final class StreamUrlResolver { - private static final Pattern ARRAY_NAME_PATTERN = - Pattern.compile("var\\s+playbackURL\\s*=\\s*\"\"\\s*,\\s*([A-Za-z0-9]+)\\s*=\\s*\\[\\]"); - private static final Pattern ENTRY_PATTERN = Pattern.compile("\\[(\\d+),\"([A-Za-z0-9+/=]+)\"\\]"); - private static final Pattern KEY_FUNCTIONS_PATTERN = Pattern.compile("var\\s+k=(\\w+)\\(\\)\\+(\\w+)\\(\\);"); - private static final String FUNCTION_TEMPLATE = "function\\s+%s\\(\\)\\s*\\{\\s*return\\s+(\\d+);\\s*\\}"; - private static final String USER_AGENT = "Mozilla/5.0 (Linux; Android 13) ExoPlayerResolver/1.0"; + // Patrón para extraer la URL del stream directamente + private static final Pattern PLAYBACK_URL_PATTERN = + Pattern.compile("var\\s+playbackURL\\s*=\\s*[\"']([^\"']+)[\"']"); + + private static final String USER_AGENT = "Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36"; private static final OkHttpClient CLIENT; @@ -54,7 +45,7 @@ public final class StreamUrlResolver { .build(); builder.dns(dns); } catch (UnknownHostException e) { - // Fallback a DNS del sistema si falla la inicialización (raro con IPs hardcoded) + // Fallback a DNS del sistema } CLIENT = builder.build(); @@ -65,34 +56,28 @@ public final class StreamUrlResolver { public static String resolve(String pageUrl) throws IOException { String html = downloadPage(pageUrl); - long keyOffset = extractKeyOffset(html); - List entries = extractEntries(html); - if (entries.isEmpty()) { - throw new IOException("No se pudieron obtener los fragmentos del stream"); - } - - StringBuilder builder = new StringBuilder(); - for (Entry entry : entries) { - String decoded = new String(Base64.decode(entry.encoded, Base64.DEFAULT), StandardCharsets.UTF_8); - String numeric = decoded.replaceAll("\\D+", ""); - if (numeric.isEmpty()) { - continue; + + // Buscar playbackURL directamente en el HTML + Matcher matcher = PLAYBACK_URL_PATTERN.matcher(html); + if (matcher.find()) { + String url = matcher.group(1); + if (url != null && !url.isEmpty() && url.startsWith("http")) { + return url; } - long value = Long.parseLong(numeric) - keyOffset; - builder.append((char) value); } - String url = builder.toString(); - if (url.isEmpty()) { - throw new IOException("No se pudo reconstruir la URL del stream"); - } - return url; + + // Si no encontramos la URL, mostrar un fragmento del HTML para debug + String preview = html.length() > 500 ? html.substring(0, 500) : html; + throw new IOException("No se encontró la URL del stream en la página. Vista previa: " + preview); } private static String downloadPage(String pageUrl) throws IOException { Request request = new Request.Builder() .url(pageUrl) .header("User-Agent", USER_AGENT) - .header("Accept", "text/html,application/xhtml+xml") + .header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") + .header("Accept-Language", "es-ES,es;q=0.9,en;q=0.8") + .header("Referer", "https://streamtpcloud.com/") .build(); try (Response response = CLIENT.newCall(request).execute()) { @@ -103,71 +88,7 @@ public final class StreamUrlResolver { throw new IOException("Respuesta vacía del servidor"); } - // Validar Content-Type - String contentType = response.header("Content-Type"); - if (contentType != null && !contentType.contains("html") && !contentType.contains("text")) { - if (contentType.startsWith("image/") || contentType.startsWith("video/") || contentType.startsWith("audio/")) { - throw new IOException("El servidor devolvió " + contentType + " en lugar de HTML"); - } - } - return response.body().string(); } } - - private static long extractKeyOffset(String html) throws IOException { - Matcher matcher = KEY_FUNCTIONS_PATTERN.matcher(html); - if (!matcher.find()) { - // Logging para debug si es necesario, pero lanzamos excepción limpia - throw new IOException("No se encontró la clave del stream (posible bloqueo o cambio en la página)"); - } - String first = matcher.group(1); - String second = matcher.group(2); - long firstVal = extractReturnValue(html, first); - long secondVal = extractReturnValue(html, second); - return firstVal + secondVal; - } - - private static long extractReturnValue(String html, String functionName) throws IOException { - Pattern functionPattern = Pattern.compile( - String.format(FUNCTION_TEMPLATE, Pattern.quote(functionName))); - Matcher matcher = functionPattern.matcher(html); - if (!matcher.find()) { - throw new IOException("No se encontró el valor de la función " + functionName); - } - return Long.parseLong(matcher.group(1)); - } - - private static List extractEntries(String html) throws IOException { - Matcher arrayNameMatcher = ARRAY_NAME_PATTERN.matcher(html); - if (!arrayNameMatcher.find()) { - throw new IOException("No se detectó la variable del arreglo de fragmentos"); - } - String arrayName = arrayNameMatcher.group(1); - Pattern arrayPattern = Pattern.compile(Pattern.quote(arrayName) + "=\\[(.*?)\\];", Pattern.DOTALL); - Matcher matcher = arrayPattern.matcher(html); - if (!matcher.find()) { - throw new IOException("No se encontró el arreglo de fragmentos"); - } - String rawEntries = matcher.group(1); - Matcher entryMatcher = ENTRY_PATTERN.matcher(rawEntries); - List entries = new ArrayList<>(); - while (entryMatcher.find()) { - int index = Integer.parseInt(entryMatcher.group(1)); - String encoded = entryMatcher.group(2); - entries.add(new Entry(index, encoded)); - } - Collections.sort(entries, Comparator.comparingInt(e -> e.index)); - return entries; - } - - private static final class Entry { - final int index; - final String encoded; - - Entry(int index, String encoded) { - this.index = index; - this.encoded = encoded; - } - } }