diff --git a/app/src/main/java/com/streamplayer/NetworkUtils.java b/app/src/main/java/com/streamplayer/NetworkUtils.java index cd8a994..43efa85 100644 --- a/app/src/main/java/com/streamplayer/NetworkUtils.java +++ b/app/src/main/java/com/streamplayer/NetworkUtils.java @@ -1,11 +1,15 @@ package com.streamplayer; +import java.security.cert.X509Certificate; import java.net.InetAddress; import java.net.UnknownHostException; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + import okhttp3.Dns; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; @@ -28,17 +32,38 @@ public class NetworkUtils { static { OkHttpClient.Builder builder = new OkHttpClient.Builder() - .connectTimeout(15, TimeUnit.SECONDS) - .readTimeout(15, TimeUnit.SECONDS) - .writeTimeout(15, TimeUnit.SECONDS) + .connectTimeout(20, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .writeTimeout(20, TimeUnit.SECONDS) .followRedirects(true) .followSslRedirects(true) .retryOnConnectionFailure(true); try { + // Configurar para aceptar todos los certificados SSL (útil para diagnosticar problemas de ISP) + // NOTA: Esto es temporal para diagnosticar si hay problemas de certificados MITM + final TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) {} + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) {} + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[]{}; + } + } + }; + + final SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); + + builder.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustAllCerts[0]); + builder.hostnameVerifier((hostname, session) -> true); + // Cliente bootstrap para resolver los dominios de DNS OkHttpClient bootstrap = new OkHttpClient.Builder() - .connectTimeout(5, TimeUnit.SECONDS) + .connectTimeout(10, TimeUnit.SECONDS) .retryOnConnectionFailure(true) .build(); diff --git a/app/src/main/java/com/streamplayer/PlayerActivity.java b/app/src/main/java/com/streamplayer/PlayerActivity.java index c06a9eb..2edec8a 100644 --- a/app/src/main/java/com/streamplayer/PlayerActivity.java +++ b/app/src/main/java/com/streamplayer/PlayerActivity.java @@ -25,16 +25,10 @@ import androidx.media3.common.util.Util; import androidx.annotation.OptIn; import androidx.media3.common.util.UnstableApi; -import java.io.IOException; -import java.net.InetAddress; -import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.TimeUnit; -import okhttp3.HttpUrl; import okhttp3.OkHttpClient; -import okhttp3.dnsoverhttps.DnsOverHttps; @OptIn(markerClass = UnstableApi.class) public class PlayerActivity extends AppCompatActivity { @@ -54,7 +48,6 @@ public class PlayerActivity extends AppCompatActivity { private String channelName; private String channelUrl; private boolean overlayVisible = true; - private OkHttpClient okHttpClient; private int retryCount = 0; private static final int MAX_RETRIES = 3; private String lastStreamUrl; @@ -110,10 +103,8 @@ public class PlayerActivity extends AppCompatActivity { try { String resolvedUrl = StreamUrlResolver.resolve(channelUrl); runOnUiThread(() -> startPlayback(resolvedUrl)); - } catch (IOException e) { - runOnUiThread(() -> showError("No se pudo conectar con el canal: " + e.getMessage())); } catch (Exception e) { - runOnUiThread(() -> showError("Error inesperado: " + e.getMessage())); + runOnUiThread(() -> showError("No se pudo conectar con el canal: " + e.getMessage())); } }).start(); } @@ -164,13 +155,17 @@ public class PlayerActivity extends AppCompatActivity { boolean isRetryableError = fullError.contains("404") || fullError.contains("403") || + fullError.contains("401") || fullError.contains("timeout") || fullError.contains("Unable to connect") || fullError.contains("Network") || fullError.contains("source error") || + fullError.contains("SSL") || + fullError.contains("Certificate") || error.errorCode == PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED || error.errorCode == PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT || - error.errorCode == PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS; + error.errorCode == PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS || + error.errorCode == PlaybackException.ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE; if (isRetryableError && retryCount < MAX_RETRIES) { retryCount++; @@ -179,14 +174,12 @@ public class PlayerActivity extends AppCompatActivity { showError("Error de conexión. Reintentando... (" + retryCount + "/" + MAX_RETRIES + ")"); }); - // Reintentar después de 2 segundos + // Reintentar después de 3 segundos new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed(() -> { if (lastStreamUrl != null) { - startPlayback(lastStreamUrl); - } else { - loadChannel(); + loadChannel(); // Recargar desde la página para obtener URL fresca } - }, 2000); + }, 3000); } else { // Mostrar error final después de agotar reintentos String finalMessage = "Error al reproducir: " + fullError; @@ -235,53 +228,27 @@ public class PlayerActivity extends AppCompatActivity { private MediaSource buildMediaSource(MediaItem mediaItem) { Map headers = new HashMap<>(); - headers.put("Referer", channelUrl); - headers.put("Origin", "https://streamtpcloud.com"); + + // Headers críticos para que el servidor acepte la petición + headers.put("Referer", "https://streamtp10.com/"); + headers.put("Origin", "https://streamtp10.com"); headers.put("Accept", "*/*"); - headers.put("Connection", "keep-alive"); + headers.put("Accept-Language", "es-ES,es;q=0.9"); + headers.put("Accept-Encoding", "gzip, deflate, br"); + + // Usar el User-Agent estándar de NetworkUtils que funciona mejor + String userAgent = NetworkUtils.getUserAgent(); - String userAgent = Util.getUserAgent(this, "StreamPlayer"); + // Usar NetworkUtils.getClient() que tiene los 4 servidores DNS configurados + OkHttpClient client = NetworkUtils.getClient(); - OkHttpDataSource.Factory factory = new OkHttpDataSource.Factory(provideOkHttpClient()) + OkHttpDataSource.Factory factory = new OkHttpDataSource.Factory(client) .setUserAgent(userAgent) .setDefaultRequestProperties(headers); + return new HlsMediaSource.Factory(factory).createMediaSource(mediaItem); } - private OkHttpClient provideOkHttpClient() { - if (okHttpClient != null) { - return okHttpClient; - } - - try { - OkHttpClient bootstrap = new OkHttpClient.Builder() - .connectTimeout(20, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .retryOnConnectionFailure(true) - .build(); - - DnsOverHttps dohDns = new DnsOverHttps.Builder() - .client(bootstrap) - .url(HttpUrl.get("https://dns.google/dns-query")) - .bootstrapDnsHosts( - InetAddress.getByName("8.8.8.8"), - InetAddress.getByName("8.8.4.4")) - .build(); - - okHttpClient = bootstrap.newBuilder() - .dns(dohDns) - .build(); - } catch (UnknownHostException e) { - okHttpClient = new OkHttpClient.Builder() - .connectTimeout(20, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .retryOnConnectionFailure(true) - .build(); - } - - return okHttpClient; - } - @Override protected void onStart() { super.onStart(); diff --git a/app/src/main/java/com/streamplayer/StreamUrlResolver.java b/app/src/main/java/com/streamplayer/StreamUrlResolver.java index 1258931..17a576b 100644 --- a/app/src/main/java/com/streamplayer/StreamUrlResolver.java +++ b/app/src/main/java/com/streamplayer/StreamUrlResolver.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.util.regex.Matcher; import java.util.regex.Pattern; -import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; @@ -14,9 +13,18 @@ import okhttp3.Response; */ public final class StreamUrlResolver { - // Patrón para extraer la URL del stream directamente - private static final Pattern PLAYBACK_URL_PATTERN = - Pattern.compile("var\\s+playbackURL\\s*=\\s*[\"']([^\"']+)[\"']"); + // Múltiples patrones para extraer la URL del stream + // El proveedor cambia frecuentemente el formato + private static final Pattern[] URL_PATTERNS = { + // Patrón original + Pattern.compile("var\\s+playbackURL\\s*=\\s*[\"']([^\"']+)[\"']"), + // Alternativas comunes + Pattern.compile("source\\s*:\\s*[\"']([^\"']+\\.m3u8[^\"']*)[\"']"), + Pattern.compile("src\\s*:\\s*[\"']([^\"']+\\.m3u8[^\"']*)[\"']"), + Pattern.compile("file\\s*:\\s*[\"']([^\"']+\\.m3u8[^\"']*)[\"']"), + Pattern.compile("[\"'](https?://[^\"']+\\.m3u8[^\"']*)[\"']"), + Pattern.compile("url\\s*:\\s*[\"']([^\"']+)[\"']"), + }; private StreamUrlResolver() { } @@ -24,32 +32,54 @@ public final class StreamUrlResolver { public static String resolve(String pageUrl) throws IOException { String html = downloadPage(pageUrl); - // 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; + // Intentar todos los patrones + for (Pattern pattern : URL_PATTERNS) { + Matcher matcher = pattern.matcher(html); + if (matcher.find()) { + String url = matcher.group(1); + if (url != null && !url.isEmpty() && url.startsWith("http")) { + // Limpiar la URL si tiene caracteres de escape + url = url.replace("\\", "").replace("&", "&"); + 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); + // Si no encontramos la URL, verificar si hay un mensaje de geobloqueo o similar + if (html.contains("Access Denied") || html.contains("403 Forbidden")) { + throw new IOException("Acceso bloqueado por el servidor. Posible geobloqueo o restricción de IP."); + } + + if (html.contains("404") || html.contains("Not Found")) { + throw new IOException("Canal no encontrado (404). El canal puede estar temporalmente fuera de servicio."); + } + + // Mostrar un fragmento del HTML para debug + String preview = html.length() > 800 ? html.substring(0, 800) : html; + throw new IOException("No se encontró la URL del stream. Vista previa: " + preview); } private static String downloadPage(String pageUrl) throws IOException { Request request = new Request.Builder() .url(pageUrl) .header("User-Agent", NetworkUtils.getUserAgent()) - .header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") + .header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8") .header("Accept-Language", "es-ES,es;q=0.9,en;q=0.8") + .header("Accept-Encoding", "gzip, deflate, br") .header("Referer", "https://streamtp10.com/") + .header("Connection", "keep-alive") + .header("Upgrade-Insecure-Requests", "1") .build(); try (Response response = NetworkUtils.getClient().newCall(request).execute()) { if (!response.isSuccessful()) { - throw new IOException("Error HTTP " + response.code() + " al cargar la página del stream"); + if (response.code() == 403) { + throw new IOException("Acceso denegado (403). El servidor puede estar bloqueando tu conexión."); + } else if (response.code() == 404) { + throw new IOException("Canal no encontrado (404)."); + } else { + throw new IOException("Error HTTP " + response.code() + " al cargar la página del stream"); + } } if (response.body() == null) { throw new IOException("Respuesta vacía del servidor");