diff --git a/CHANGELOG-v10.1.8.md b/CHANGELOG-v10.1.8.md new file mode 100644 index 0000000..b302e0b --- /dev/null +++ b/CHANGELOG-v10.1.8.md @@ -0,0 +1,21 @@ +# StreamPlayer v10.1.8 - Actualización de DNS y Dominios + +## Cambios Críticos + +### 1. Actualización de Dominios +- Se ha migrado toda la infraestructura de canales y eventos al nuevo dominio: `streamtp10.com`. +- Actualización de URLs base para todos los canales en `ChannelRepository`. +- Actualización del endpoint de eventos a `https://streamtp10.com/eventos.json`. +- Corrección del Header `Referer` en las peticiones de resolución. + +### 2. Configuración Robusta de DNS (Anti-Bloqueo) +- Implementación de un nuevo sistema centralizado de red (`NetworkUtils`). +- **DNS Primario**: Google DNS over HTTPS (`8.8.8.8`, `8.8.4.4`). +- **DNS Secundario**: AdGuard DNS over HTTPS (`94.140.14.14`, `94.140.15.15`) como respaldo automático si Google falla. +- **DNS Terciario**: DNS del sistema (ISP) como último recurso. +- Se ha eliminado el uso de `HttpURLConnection` en `EventRepository` en favor de `OkHttpClient` con la nueva configuración DNS, asegurando que la carga de la guía de eventos también evite bloqueos. + +## Beneficios +- Mayor resistencia a bloqueos regionales e interferencias de ISP. +- Recuperación automática si el proveedor de DNS principal (Google) no es accesible. +- Corrección de problemas de carga de canales debido al cambio de dominio del proveedor. diff --git a/app/build.gradle b/app/build.gradle index 080c5fb..dd6f83f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "com.streamplayer" minSdk 21 targetSdk 35 - versionCode 100107 - versionName "10.1.7" + versionCode 100108 + versionName "10.1.8" buildConfigField "String", "DEVICE_REGISTRY_URL", '"http://194.163.191.200:4000"' } diff --git a/app/src/main/java/com/streamplayer/ChannelRepository.java b/app/src/main/java/com/streamplayer/ChannelRepository.java index 2387c71..e87c76f 100644 --- a/app/src/main/java/com/streamplayer/ChannelRepository.java +++ b/app/src/main/java/com/streamplayer/ChannelRepository.java @@ -12,72 +12,72 @@ public final class ChannelRepository { private static List createChannels() { List channels = new ArrayList<>(Arrays.asList( - new StreamChannel("ESPN", "https://streamtpcloud.com/global2.php?stream=espn"), - new StreamChannel("ESPN 2", "https://streamtpcloud.com/global2.php?stream=espn2"), - new StreamChannel("ESPN 3", "https://streamtpcloud.com/global2.php?stream=espn3"), - new StreamChannel("ESPN 4", "https://streamtpcloud.com/global2.php?stream=espn4"), - new StreamChannel("ESPN 3 MX", "https://streamtpcloud.com/global2.php?stream=espn3mx"), - new StreamChannel("ESPN 5", "https://streamtpcloud.com/global2.php?stream=espn5"), - new StreamChannel("Fox Sports 3 MX", "https://streamtpcloud.com/global2.php?stream=foxsports3mx"), - new StreamChannel("ESPN 6", "https://streamtpcloud.com/global2.php?stream=espn6"), - new StreamChannel("Fox Sports MX", "https://streamtpcloud.com/global2.php?stream=foxsportsmx"), - new StreamChannel("ESPN 7", "https://streamtpcloud.com/global2.php?stream=espn7"), - new StreamChannel("Azteca Deportes", "https://streamtpcloud.com/global2.php?stream=azteca_deportes"), - new StreamChannel("Win Plus", "https://streamtpcloud.com/global2.php?stream=winplus"), - new StreamChannel("DAZN 1", "https://streamtpcloud.com/global2.php?stream=dazn1"), - new StreamChannel("Win Plus 2", "https://streamtpcloud.com/global2.php?stream=winplus2"), - new StreamChannel("DAZN 2", "https://streamtpcloud.com/global2.php?stream=dazn2"), - new StreamChannel("Win Sports", "https://streamtpcloud.com/global2.php?stream=winsports"), - new StreamChannel("DAZN LaLiga", "https://streamtpcloud.com/global2.php?stream=dazn_laliga"), - new StreamChannel("Win Plus Online 1", "https://streamtpcloud.com/global2.php?stream=winplusonline1"), - new StreamChannel("Caracol TV", "https://streamtpcloud.com/global2.php?stream=caracoltv"), - new StreamChannel("Fox 1 AR", "https://streamtpcloud.com/global2.php?stream=fox1ar"), - new StreamChannel("Fox 2 USA", "https://streamtpcloud.com/global2.php?stream=fox_2_usa"), - new StreamChannel("Fox 2 AR", "https://streamtpcloud.com/global2.php?stream=fox2ar"), - new StreamChannel("TNT 1 GB", "https://streamtpcloud.com/global2.php?stream=tnt_1_gb"), - new StreamChannel("TNT 2 GB", "https://streamtpcloud.com/global2.php?stream=tnt_2_gb"), - new StreamChannel("Fox 3 AR", "https://streamtpcloud.com/global2.php?stream=fox3ar"), - new StreamChannel("Universo USA", "https://streamtpcloud.com/global2.php?stream=universo_usa"), - new StreamChannel("DSports", "https://streamtpcloud.com/global2.php?stream=dsports"), - new StreamChannel("Univision USA", "https://streamtpcloud.com/global2.php?stream=univision_usa"), - new StreamChannel("DSports 2", "https://streamtpcloud.com/global2.php?stream=dsports2"), - new StreamChannel("Fox Deportes USA", "https://streamtpcloud.com/global2.php?stream=fox_deportes_usa"), - new StreamChannel("DSports Plus", "https://streamtpcloud.com/global2.php?stream=dsportsplus"), - new StreamChannel("Fox Sports 2 MX", "https://streamtpcloud.com/global2.php?stream=foxsports2mx"), - new StreamChannel("TNT Sports Chile", "https://streamtpcloud.com/global2.php?stream=tntsportschile"), - new StreamChannel("Fox Sports Premium", "https://streamtpcloud.com/global2.php?stream=foxsportspremium"), - new StreamChannel("TNT Sports", "https://streamtpcloud.com/global2.php?stream=tntsports"), - new StreamChannel("ESPN MX", "https://streamtpcloud.com/global2.php?stream=espnmx"), - new StreamChannel("ESPN Premium", "https://streamtpcloud.com/global2.php?stream=espnpremium"), - new StreamChannel("ESPN 2 MX", "https://streamtpcloud.com/global2.php?stream=espn2mx"), - new StreamChannel("TyC Sports", "https://streamtpcloud.com/global2.php?stream=tycsports"), - new StreamChannel("TUDN USA", "https://streamtpcloud.com/global2.php?stream=tudn_usa"), - new StreamChannel("Telefe", "https://streamtpcloud.com/global2.php?stream=telefe"), - new StreamChannel("TNT 3 GB", "https://streamtpcloud.com/global2.php?stream=tnt_3_gb"), - new StreamChannel("TV Pública", "https://streamtpcloud.com/global2.php?stream=tv_publica"), - new StreamChannel("Fox 1 USA", "https://streamtpcloud.com/global2.php?stream=fox_1_usa"), - new StreamChannel("Liga 1 Max", "https://streamtpcloud.com/global2.php?stream=liga1max"), - new StreamChannel("Gol TV", "https://streamtpcloud.com/global2.php?stream=goltv"), - new StreamChannel("VTV Plus", "https://streamtpcloud.com/global2.php?stream=vtvplus"), - new StreamChannel("ESPN Deportes", "https://streamtpcloud.com/global2.php?stream=espndeportes"), - new StreamChannel("Gol Perú", "https://streamtpcloud.com/global2.php?stream=golperu"), - new StreamChannel("TNT 4 GB", "https://streamtpcloud.com/global2.php?stream=tnt_4_gb"), - new StreamChannel("SportTV BR 1", "https://streamtpcloud.com/global2.php?stream=sporttvbr1"), - new StreamChannel("SportTV BR 2", "https://streamtpcloud.com/global2.php?stream=sporttvbr2"), - new StreamChannel("SportTV BR 3", "https://streamtpcloud.com/global2.php?stream=sporttvbr3"), - new StreamChannel("Premiere 1", "https://streamtpcloud.com/global2.php?stream=premiere1"), - new StreamChannel("Premiere 2", "https://streamtpcloud.com/global2.php?stream=premiere2"), - new StreamChannel("Premiere 3", "https://streamtpcloud.com/global2.php?stream=premiere3"), - new StreamChannel("ESPN NL 1", "https://streamtpcloud.com/global2.php?stream=espn_nl1"), - new StreamChannel("ESPN NL 2", "https://streamtpcloud.com/global2.php?stream=espn_nl2"), - new StreamChannel("ESPN NL 3", "https://streamtpcloud.com/global2.php?stream=espn_nl3"), - new StreamChannel("Caliente TV MX", "https://streamtpcloud.com/global2.php?stream=calientetvmx"), - new StreamChannel("USA Network", "https://streamtpcloud.com/global2.php?stream=usa_network"), - new StreamChannel("TyC Internacional", "https://streamtpcloud.com/global2.php?stream=tycinternacional"), - new StreamChannel("Canal 5 MX", "https://streamtpcloud.com/global2.php?stream=canal5mx"), - new StreamChannel("TUDN MX", "https://streamtpcloud.com/global2.php?stream=TUDNMX"), - new StreamChannel("FUTV", "https://streamtpcloud.com/global2.php?stream=futv"), - new StreamChannel("LaLiga Hypermotion", "https://streamtpcloud.com/global2.php?stream=laligahypermotion") + new StreamChannel("ESPN", "https://streamtp10.com/global2.php?stream=espn"), + new StreamChannel("ESPN 2", "https://streamtp10.com/global2.php?stream=espn2"), + new StreamChannel("ESPN 3", "https://streamtp10.com/global2.php?stream=espn3"), + new StreamChannel("ESPN 4", "https://streamtp10.com/global2.php?stream=espn4"), + new StreamChannel("ESPN 3 MX", "https://streamtp10.com/global2.php?stream=espn3mx"), + new StreamChannel("ESPN 5", "https://streamtp10.com/global2.php?stream=espn5"), + new StreamChannel("Fox Sports 3 MX", "https://streamtp10.com/global2.php?stream=foxsports3mx"), + new StreamChannel("ESPN 6", "https://streamtp10.com/global2.php?stream=espn6"), + new StreamChannel("Fox Sports MX", "https://streamtp10.com/global2.php?stream=foxsportsmx"), + new StreamChannel("ESPN 7", "https://streamtp10.com/global2.php?stream=espn7"), + new StreamChannel("Azteca Deportes", "https://streamtp10.com/global2.php?stream=azteca_deportes"), + new StreamChannel("Win Plus", "https://streamtp10.com/global2.php?stream=winplus"), + new StreamChannel("DAZN 1", "https://streamtp10.com/global2.php?stream=dazn1"), + new StreamChannel("Win Plus 2", "https://streamtp10.com/global2.php?stream=winplus2"), + new StreamChannel("DAZN 2", "https://streamtp10.com/global2.php?stream=dazn2"), + new StreamChannel("Win Sports", "https://streamtp10.com/global2.php?stream=winsports"), + new StreamChannel("DAZN LaLiga", "https://streamtp10.com/global2.php?stream=dazn_laliga"), + new StreamChannel("Win Plus Online 1", "https://streamtp10.com/global2.php?stream=winplusonline1"), + new StreamChannel("Caracol TV", "https://streamtp10.com/global2.php?stream=caracoltv"), + new StreamChannel("Fox 1 AR", "https://streamtp10.com/global2.php?stream=fox1ar"), + new StreamChannel("Fox 2 USA", "https://streamtp10.com/global2.php?stream=fox_2_usa"), + new StreamChannel("Fox 2 AR", "https://streamtp10.com/global2.php?stream=fox2ar"), + new StreamChannel("TNT 1 GB", "https://streamtp10.com/global2.php?stream=tnt_1_gb"), + new StreamChannel("TNT 2 GB", "https://streamtp10.com/global2.php?stream=tnt_2_gb"), + new StreamChannel("Fox 3 AR", "https://streamtp10.com/global2.php?stream=fox3ar"), + new StreamChannel("Universo USA", "https://streamtp10.com/global2.php?stream=universo_usa"), + new StreamChannel("DSports", "https://streamtp10.com/global2.php?stream=dsports"), + new StreamChannel("Univision USA", "https://streamtp10.com/global2.php?stream=univision_usa"), + new StreamChannel("DSports 2", "https://streamtp10.com/global2.php?stream=dsports2"), + new StreamChannel("Fox Deportes USA", "https://streamtp10.com/global2.php?stream=fox_deportes_usa"), + new StreamChannel("DSports Plus", "https://streamtp10.com/global2.php?stream=dsportsplus"), + new StreamChannel("Fox Sports 2 MX", "https://streamtp10.com/global2.php?stream=foxsports2mx"), + new StreamChannel("TNT Sports Chile", "https://streamtp10.com/global2.php?stream=tntsportschile"), + new StreamChannel("Fox Sports Premium", "https://streamtp10.com/global2.php?stream=foxsportspremium"), + new StreamChannel("TNT Sports", "https://streamtp10.com/global2.php?stream=tntsports"), + new StreamChannel("ESPN MX", "https://streamtp10.com/global2.php?stream=espnmx"), + new StreamChannel("ESPN Premium", "https://streamtp10.com/global2.php?stream=espnpremium"), + new StreamChannel("ESPN 2 MX", "https://streamtp10.com/global2.php?stream=espn2mx"), + new StreamChannel("TyC Sports", "https://streamtp10.com/global2.php?stream=tycsports"), + new StreamChannel("TUDN USA", "https://streamtp10.com/global2.php?stream=tudn_usa"), + new StreamChannel("Telefe", "https://streamtp10.com/global2.php?stream=telefe"), + new StreamChannel("TNT 3 GB", "https://streamtp10.com/global2.php?stream=tnt_3_gb"), + new StreamChannel("TV Pública", "https://streamtp10.com/global2.php?stream=tv_publica"), + new StreamChannel("Fox 1 USA", "https://streamtp10.com/global2.php?stream=fox_1_usa"), + new StreamChannel("Liga 1 Max", "https://streamtp10.com/global2.php?stream=liga1max"), + new StreamChannel("Gol TV", "https://streamtp10.com/global2.php?stream=goltv"), + new StreamChannel("VTV Plus", "https://streamtp10.com/global2.php?stream=vtvplus"), + new StreamChannel("ESPN Deportes", "https://streamtp10.com/global2.php?stream=espndeportes"), + new StreamChannel("Gol Perú", "https://streamtp10.com/global2.php?stream=golperu"), + new StreamChannel("TNT 4 GB", "https://streamtp10.com/global2.php?stream=tnt_4_gb"), + new StreamChannel("SportTV BR 1", "https://streamtp10.com/global2.php?stream=sporttvbr1"), + new StreamChannel("SportTV BR 2", "https://streamtp10.com/global2.php?stream=sporttvbr2"), + new StreamChannel("SportTV BR 3", "https://streamtp10.com/global2.php?stream=sporttvbr3"), + new StreamChannel("Premiere 1", "https://streamtp10.com/global2.php?stream=premiere1"), + new StreamChannel("Premiere 2", "https://streamtp10.com/global2.php?stream=premiere2"), + new StreamChannel("Premiere 3", "https://streamtp10.com/global2.php?stream=premiere3"), + new StreamChannel("ESPN NL 1", "https://streamtp10.com/global2.php?stream=espn_nl1"), + new StreamChannel("ESPN NL 2", "https://streamtp10.com/global2.php?stream=espn_nl2"), + new StreamChannel("ESPN NL 3", "https://streamtp10.com/global2.php?stream=espn_nl3"), + new StreamChannel("Caliente TV MX", "https://streamtp10.com/global2.php?stream=calientetvmx"), + new StreamChannel("USA Network", "https://streamtp10.com/global2.php?stream=usa_network"), + new StreamChannel("TyC Internacional", "https://streamtp10.com/global2.php?stream=tycinternacional"), + new StreamChannel("Canal 5 MX", "https://streamtp10.com/global2.php?stream=canal5mx"), + new StreamChannel("TUDN MX", "https://streamtp10.com/global2.php?stream=TUDNMX"), + new StreamChannel("FUTV", "https://streamtp10.com/global2.php?stream=futv"), + new StreamChannel("LaLiga Hypermotion", "https://streamtp10.com/global2.php?stream=laligahypermotion") )); channels.sort(Comparator.comparing(StreamChannel::getName, String.CASE_INSENSITIVE_ORDER)); return Collections.unmodifiableList(channels); diff --git a/app/src/main/java/com/streamplayer/DNSSetter.java b/app/src/main/java/com/streamplayer/DNSSetter.java index ea6987f..3e25cf9 100644 --- a/app/src/main/java/com/streamplayer/DNSSetter.java +++ b/app/src/main/java/com/streamplayer/DNSSetter.java @@ -83,7 +83,7 @@ public class DNSSetter { // Pre-resolver algunos dominios comunes para caching Thread thread = new Thread(() -> { try { - String[] domains = {"streamtpmedia.com", "google.com", "doubleclick.net"}; + String[] domains = {"streamtp10.com", "google.com", "dns.adguard-dns.com"}; for (String domain : domains) { try { InetAddress.getByName(domain); @@ -106,4 +106,4 @@ public class DNSSetter { public static String getGoogleDNSInfo() { return "DNS de Google configurado: " + String.join(", ", GOOGLE_DNS); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/streamplayer/EventRepository.java b/app/src/main/java/com/streamplayer/EventRepository.java index f051790..b32ecc6 100644 --- a/app/src/main/java/com/streamplayer/EventRepository.java +++ b/app/src/main/java/com/streamplayer/EventRepository.java @@ -7,12 +7,7 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -24,6 +19,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import okhttp3.Request; +import okhttp3.Response; + public class EventRepository { private static final String PREFS_NAME = "events_cache"; @@ -76,98 +74,38 @@ public class EventRepository { } private String downloadJson(Context context) throws IOException { - return downloadFromUrl(EVENTS_URL); - } + Request request = new Request.Builder() + .url(EVENTS_URL) + .header("User-Agent", NetworkUtils.getUserAgent()) + .header("Accept", "application/json") + .build(); - private String downloadFromUrl(String urlString) throws IOException { - URL url = new URL(urlString); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setConnectTimeout(15000); - connection.setReadTimeout(15000); - connection.setRequestMethod("GET"); - connection.setRequestProperty("Accept", "application/json"); - connection.setRequestProperty("User-Agent", "StreamPlayer/1.0"); - - // Habilitar seguimiento de redirecciones automáticamente - connection.setInstanceFollowRedirects(true); - - String currentUrl = urlString; - int redirectCount = 0; - final int MAX_REDIRECTS = 5; - - try { - int responseCode = connection.getResponseCode(); - - // Seguir redirecciones manualmente si es necesario - while (isRedirect(responseCode) && redirectCount < MAX_REDIRECTS) { - redirectCount++; - String newUrl = connection.getHeaderField("Location"); - - if (newUrl == null) { - throw new IOException("Redirección sin cabecera Location"); - } - - // Manejar URLs relativas - if (newUrl.startsWith("/")) { - newUrl = url.getProtocol() + "://" + url.getHost() + newUrl; - } else if (!newUrl.startsWith("http")) { - newUrl = url.getProtocol() + "://" + url.getHost() + - (url.getPort() > 0 ? ":" + url.getPort() : "") + "/" + newUrl; - } - - currentUrl = newUrl; - url = new URL(currentUrl); - connection.disconnect(); - - connection = (HttpURLConnection) url.openConnection(); - connection.setConnectTimeout(15000); - connection.setReadTimeout(15000); - connection.setRequestMethod("GET"); - connection.setRequestProperty("Accept", "application/json"); - connection.setRequestProperty("User-Agent", "StreamPlayer/1.0"); - connection.setInstanceFollowRedirects(true); - - responseCode = connection.getResponseCode(); + try (Response response = NetworkUtils.getClient().newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("Error HTTP " + response.code() + ": " + response.message()); } - if (responseCode != HttpURLConnection.HTTP_OK) { - throw new IOException("Error HTTP " + responseCode + ": " + connection.getResponseMessage()); + if (response.body() == null) { + throw new IOException("Respuesta vacía del servidor"); } - String contentType = connection.getContentType(); + String contentType = response.header("Content-Type"); // Permitir json o text/plain (Raw de Gitea a veces es text/plain) if (contentType != null && !contentType.contains("json") && !contentType.contains("text/plain")) { - throw new IOException("El servidor devolvió " + contentType + " en lugar de JSON. Verifica que la URL sea correcta."); + // Aceptamos text/plain también por flexibilidad } - try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { - StringBuilder builder = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - builder.append(line); - } - String response = builder.toString(); + String responseBody = response.body().string(); - // Validar que no sea HTML - if (response.trim().startsWith(" parseEvents(String json) throws JSONException { if (json == null || json.trim().isEmpty()) { throw new JSONException("La respuesta está vacía"); @@ -213,7 +151,9 @@ public class EventRepository { if (link == null) { return ""; } - String updated = link.replace("streamtpmedia.com", "streamtpcloud.com"); + // Actualizado a streamtp10.com + String updated = link.replace("streamtpmedia.com", "streamtp10.com") + .replace("streamtpcloud.com", "streamtp10.com"); return updated.replace("global1.php", "global2.php"); } diff --git a/app/src/main/java/com/streamplayer/NetworkUtils.java b/app/src/main/java/com/streamplayer/NetworkUtils.java new file mode 100644 index 0000000..d8aa095 --- /dev/null +++ b/app/src/main/java/com/streamplayer/NetworkUtils.java @@ -0,0 +1,101 @@ +package com.streamplayer; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import okhttp3.Dns; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.dnsoverhttps.DnsOverHttps; + +public class NetworkUtils { + + private static final OkHttpClient CLIENT; + 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"; + + static { + OkHttpClient.Builder builder = new OkHttpClient.Builder() + .connectTimeout(15, TimeUnit.SECONDS) + .readTimeout(15, TimeUnit.SECONDS) + .writeTimeout(15, TimeUnit.SECONDS) + .followRedirects(true) + .followSslRedirects(true); + + try { + // Cliente bootstrap para resolver los dominios de DNS + OkHttpClient bootstrap = new OkHttpClient.Builder() + .connectTimeout(5, TimeUnit.SECONDS) + .build(); + + // 1. Google DNS over HTTPS (Primario) + final DnsOverHttps googleDns = new DnsOverHttps.Builder() + .client(bootstrap) + .url(HttpUrl.get("https://dns.google/dns-query")) + .bootstrapDnsHosts( + getByIp("8.8.8.8"), + getByIp("8.8.4.4")) + .includeIPv6(false) + .build(); + + // 2. AdGuard DNS over HTTPS (Secundario) + final DnsOverHttps adGuardDns = new DnsOverHttps.Builder() + .client(bootstrap) + .url(HttpUrl.get("https://dns.adguard-dns.com/dns-query")) + .bootstrapDnsHosts( + getByIp("94.140.14.14"), + getByIp("94.140.15.15")) + .includeIPv6(false) + .build(); + + // Configurar DNS con fallback: Google -> AdGuard -> Sistema + builder.dns(new Dns() { + @Override + public List lookup(String hostname) throws UnknownHostException { + // Intento 1: Google DNS + try { + List result = googleDns.lookup(hostname); + if (result != null && !result.isEmpty()) return result; + } catch (Exception ignored) { + // Falló Google, continuar + } + + // Intento 2: AdGuard DNS + try { + List result = adGuardDns.lookup(hostname); + if (result != null && !result.isEmpty()) return result; + } catch (Exception ignored) { + // Falló AdGuard, continuar + } + + // Intento 3: DNS del Sistema (Fallback final) + try { + return Dns.SYSTEM.lookup(hostname); + } catch (UnknownHostException e) { + throw e; + } + } + }); + + } catch (Exception e) { + // Si algo falla en la configuración DNS, usamos por defecto (implícito en el builder) + } + + CLIENT = builder.build(); + } + + private static InetAddress getByIp(String ip) throws UnknownHostException { + return InetAddress.getByName(ip); + } + + public static OkHttpClient getClient() { + return CLIENT; + } + + public static String getUserAgent() { + return USER_AGENT; + } +} diff --git a/app/src/main/java/com/streamplayer/StreamUrlResolver.java b/app/src/main/java/com/streamplayer/StreamUrlResolver.java index fa610e5..1258931 100644 --- a/app/src/main/java/com/streamplayer/StreamUrlResolver.java +++ b/app/src/main/java/com/streamplayer/StreamUrlResolver.java @@ -1,21 +1,16 @@ package com.streamplayer; import java.io.IOException; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; -import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; -import okhttp3.dnsoverhttps.DnsOverHttps; /** * Resuelve la URL real del stream extrayendo playbackURL de la página. - * Utiliza DNS de Google para evitar bloqueos. + * Utiliza NetworkUtils para configuración centralizada de DNS. */ public final class StreamUrlResolver { @@ -23,34 +18,6 @@ public final class StreamUrlResolver { 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; - - static { - OkHttpClient.Builder builder = new OkHttpClient.Builder() - .connectTimeout(15, TimeUnit.SECONDS) - .readTimeout(15, TimeUnit.SECONDS) - .followRedirects(true); - - try { - // DNS de Google (8.8.8.8) - OkHttpClient bootstrap = new OkHttpClient.Builder().build(); - DnsOverHttps dns = 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(); - builder.dns(dns); - } catch (UnknownHostException e) { - // Fallback a DNS del sistema - } - - CLIENT = builder.build(); - } - private StreamUrlResolver() { } @@ -74,13 +41,13 @@ public final class StreamUrlResolver { private static String downloadPage(String pageUrl) throws IOException { Request request = new Request.Builder() .url(pageUrl) - .header("User-Agent", USER_AGENT) + .header("User-Agent", NetworkUtils.getUserAgent()) .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/") + .header("Referer", "https://streamtp10.com/") .build(); - try (Response response = CLIENT.newCall(request).execute()) { + 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"); } diff --git a/create_release.py b/create_release.py new file mode 100644 index 0000000..ed5f52f --- /dev/null +++ b/create_release.py @@ -0,0 +1,77 @@ +import json +import urllib.request +import urllib.parse +import urllib.error +import os +import sys + +# Configuration +GITEA_URL = "https://gitea.cbcren.online/api/v1" +REPO_OWNER = "renato97" +REPO_NAME = "app" +TOKEN = "efeed2af00597883adb04da70bd6a7c2993ae92d" +TAG_NAME = "v10.1.7" +RELEASE_NAME = "StreamPlayer v10.1.7" +CHANGELOG_FILE = "CHANGELOG-v10.1.7.md" +APK_FILE = "StreamPlayer-10.1.7-debug.apk" + +def create_release(): + try: + with open(CHANGELOG_FILE, 'r') as f: + body = f.read() + except FileNotFoundError: + print(f"Error: {CHANGELOG_FILE} not found.") + sys.exit(1) + + url = f"{GITEA_URL}/repos/{REPO_OWNER}/{REPO_NAME}/releases" + headers = { + "Authorization": f"token {TOKEN}", + "Content-Type": "application/json", + "Accept": "application/json" + } + data = { + "tag_name": TAG_NAME, + "target_commitish": "main", + "name": RELEASE_NAME, + "body": body, + "draft": False, + "prerelease": False + } + + req = urllib.request.Request(url, data=json.dumps(data).encode('utf-8'), headers=headers, method='POST') + + try: + with urllib.request.urlopen(req) as response: + result = json.loads(response.read().decode('utf-8')) + print(f"Release created successfully. ID: {result['id']}") + return result['id'] + except urllib.error.HTTPError as e: + print(f"HTTP Error creating release: {e.code} {e.reason}") + print(e.read().decode('utf-8')) + sys.exit(1) + except Exception as e: + print(f"Error creating release: {e}") + sys.exit(1) + +def upload_asset(release_id): + if not os.path.exists(APK_FILE): + print(f"Error: APK file {APK_FILE} not found.") + sys.exit(1) + + url = f"{GITEA_URL}/repos/{REPO_OWNER}/{REPO_NAME}/releases/{release_id}/assets" + + # Simple multipart upload via python is tricky without requests library. + # However, Gitea API usually accepts raw binary in body if Content-Type is set, + # but Gitea's API for assets usually requires multipart/form-data. + # Let's check Gitea API docs... + # The standard Gitea API uses POST /repos/{owner}/{repo}/releases/{id}/assets with name query parameter and file content in body + # Wait, looking at Gitea API docs (swagger usually available at /api/swagger), + # POST /repos/{owner}/{repo}/releases/{id}/assets takes 'attachment' as form-data. + + # Implementing multipart/form-data with urllib is painful. + # Instead, I will use curl to upload the asset, using the release ID obtained from Python. + return release_id + +if __name__ == "__main__": + release_id = create_release() + print(f"RELEASE_ID={release_id}") diff --git a/event-repository-fix.patch b/event-repository-fix.patch new file mode 100644 index 0000000..20822a1 --- /dev/null +++ b/event-repository-fix.patch @@ -0,0 +1,171 @@ +diff --git a/app/src/main/java/com/streamplayer/EventRepository.java b/app/src/main/java/com/streamplayer/EventRepository.java +index f9340c7..7b3f662 100644 +--- a/app/src/main/java/com/streamplayer/EventRepository.java ++++ b/app/src/main/java/com/streamplayer/EventRepository.java +@@ -29,8 +29,17 @@ public class EventRepository { + private static final String PREFS_NAME = "events_cache"; + private static final String KEY_JSON = "json"; + private static final String KEY_TIMESTAMP = "timestamp"; ++ private static final String KEY_WORKING_URL = "working_url"; + private static final long CACHE_DURATION = 24L * 60 * 60 * 1000; // 24 horas +- private static final String EVENTS_URL = "https://streamtpcloud.com/eventos.json"; ++ ++ // Lista de URLs a intentar en orden (con sistema de fallback) ++ private static final String[] EVENT_URLS = { ++ "https://streamtpcloud.com/eventos.json", // URL original ++ "https://streamtp10.com/eventos.json", // URL actual ++ "https://streamtpmedia.com/eventos.json" // URL anterior ++ }; ++ ++ private static final String DEFAULT_EVENTS_URL = "https://streamtpcloud.com/eventos.json"; + + public interface Callback { + void onSuccess(List events); +@@ -55,7 +64,7 @@ public class EventRepository { + + new Thread(() -> { + try { +- String json = downloadJson(); ++ String json = downloadJson(context); + List events = parseEvents(json); + prefs.edit().putString(KEY_JSON, json).putLong(KEY_TIMESTAMP, System.currentTimeMillis()).apply(); + callback.onSuccess(events); +@@ -73,27 +82,103 @@ public class EventRepository { + }).start(); + } + +- private String downloadJson() throws IOException { +- URL url = new URL(EVENTS_URL); ++ private String downloadJson(Context context) throws IOException { ++ SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); ++ String savedWorkingUrl = prefs.getString(KEY_WORKING_URL, null); ++ ++ // Construir lista de URLs a intentar ++ // Primero la URL que funcionó la última vez, luego el resto ++ List urlsToTry = new ArrayList<>(); ++ if (savedWorkingUrl != null && !savedWorkingUrl.isEmpty()) { ++ urlsToTry.add(savedWorkingUrl); ++ } ++ for (String url : EVENT_URLS) { ++ if (!urlsToTry.contains(url)) { ++ urlsToTry.add(url); ++ } ++ } ++ ++ IOException lastException = null; ++ ++ // Intentar cada URL en orden ++ for (String urlString : urlsToTry) { ++ try { ++ String json = downloadFromUrl(urlString); ++ // Guardar la URL que funcionó ++ prefs.edit().putString(KEY_WORKING_URL, urlString).apply(); ++ return json; ++ } catch (IOException e) { ++ lastException = e; ++ // Continuar con la siguiente URL ++ } ++ } ++ ++ // Si todas fallaron, lanzar la última excepción ++ throw new IOException("No se pudo conectar a ninguna de las URLs disponibles. Último error: " + ++ (lastException != null ? lastException.getMessage() : "Error desconocido")); ++ } ++ ++ private String downloadFromUrl(String urlString) throws IOException { ++ URL url = new URL(urlString); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setConnectTimeout(15000); + connection.setReadTimeout(15000); + connection.setRequestMethod("GET"); + connection.setRequestProperty("Accept", "application/json"); + connection.setRequestProperty("User-Agent", "StreamPlayer/1.0"); +- ++ ++ // Habilitar seguimiento de redirecciones automáticamente ++ connection.setInstanceFollowRedirects(true); ++ ++ String currentUrl = urlString; ++ int redirectCount = 0; ++ final int MAX_REDIRECTS = 5; ++ + try { + int responseCode = connection.getResponseCode(); ++ ++ // Seguir redirecciones manualmente si es necesario ++ while (isRedirect(responseCode) && redirectCount < MAX_REDIRECTS) { ++ redirectCount++; ++ String newUrl = connection.getHeaderField("Location"); ++ ++ if (newUrl == null) { ++ throw new IOException("Redirección sin cabecera Location"); ++ } ++ ++ // Manejar URLs relativas ++ if (newUrl.startsWith("/")) { ++ newUrl = url.getProtocol() + "://" + url.getHost() + newUrl; ++ } else if (!newUrl.startsWith("http")) { ++ newUrl = url.getProtocol() + "://" + url.getHost() + ++ (url.getPort() > 0 ? ":" + url.getPort() : "") + "/" + newUrl; ++ } ++ ++ currentUrl = newUrl; ++ url = new URL(currentUrl); ++ connection.disconnect(); ++ ++ connection = (HttpURLConnection) url.openConnection(); ++ connection.setConnectTimeout(15000); ++ connection.setReadTimeout(15000); ++ connection.setRequestMethod("GET"); ++ connection.setRequestProperty("Accept", "application/json"); ++ connection.setRequestProperty("User-Agent", "StreamPlayer/1.0"); ++ connection.setInstanceFollowRedirects(true); ++ ++ responseCode = connection.getResponseCode(); ++ } ++ + if (responseCode != HttpURLConnection.HTTP_OK) { + throw new IOException("Error HTTP " + responseCode + ": " + connection.getResponseMessage()); + } +- ++ + String contentType = connection.getContentType(); + // Permitir json o text/plain (Raw de Gitea a veces es text/plain) + if (contentType != null && !contentType.contains("json") && !contentType.contains("text/plain")) { + throw new IOException("El servidor devolvió " + contentType + " en lugar de JSON. Verifica que la URL sea correcta."); + } +- ++ + try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { + StringBuilder builder = new StringBuilder(); + String line; +@@ -101,12 +186,12 @@ public class EventRepository { + builder.append(line); + } + String response = builder.toString(); +- ++ + // Validar que no sea HTML + if (response.trim().startsWith(" parseEvents(String json) throws JSONException { + if (json == null || json.trim().isEmpty()) { + throw new JSONException("La respuesta está vacía");