From ec360cf30390831a9f3ac1e22ab65709a53b81e6 Mon Sep 17 00:00:00 2001 From: Apple Date: Mon, 9 Feb 2026 21:14:35 -0300 Subject: [PATCH] fix: implementar sistema de fallback y redirecciones HTTP para carga de eventos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Agregar sistema de fallback con múltiples URLs (streamtpcloud.com, streamtp10.com, streamtpmedia.com) - Implementar seguimiento automático de redirecciones HTTP (301, 302, 303, 307, 308) - Guardar última URL exitosa en SharedPreferences para optimizar futuras peticiones - Corregir error "Unable to resolve host 'streamtpcloud.com'" cuando el dominio cambia Resuelve issue donde los eventos no cargaban debido a cambios en el dominio del servidor. La app ahora se adapta automáticamente sin necesidad de actualización. --- CHANGELOG-v10.1.3.md | 26 ++++ .../com/streamplayer/EventRepository.java | 111 ++++++++++++++++-- 2 files changed, 128 insertions(+), 9 deletions(-) create mode 100644 CHANGELOG-v10.1.3.md diff --git a/CHANGELOG-v10.1.3.md b/CHANGELOG-v10.1.3.md new file mode 100644 index 0000000..ab507fd --- /dev/null +++ b/CHANGELOG-v10.1.3.md @@ -0,0 +1,26 @@ +# StreamPlayer v10.1.3 + +## Cambios en esta versión + +### Corrección de Carga de Eventos + +- **Sistema de fallback con múltiples URLs**: Implementado sistema inteligente que intenta múltiples URLs de eventos cuando la principal no está disponible: + - `https://streamtpcloud.com/eventos.json` (URL original) + - `https://streamtp10.com/eventos.json` (URL actual) + - `https://streamtpmedia.com/eventos.json` (URL anterior) + +- **Seguimiento automático de redirecciones HTTP**: El cliente ahora sigue automáticamente las redirecciones HTTP (códigos 301, 302, 303, 307, 308), lo que permite adaptarse a cambios de URL del servidor sin necesidad de actualizar la app. + +- **Memoria de URL exitosa**: La app recuerda cuál fue la última URL que funcionó correctamente y la intenta primero en futuras peticiones, mejorando el rendimiento y la fiabilidad. + +### Detalles Técnicos + +- Modificado `EventRepository.java` para implementar: + - Lógica de reintento secuencial con múltiples URLs + - Seguimiento manual de redirecciones (hasta 5 consecutivas) + - Persistencia de la última URL exitosa en SharedPreferences + - Manejo mejorado de errores con mensajes descriptivos + +### Problema Resuelto + +Esta versión corrige el error: *"Unable to resolve host 'streamtpcloud.com': No address associated with hostname"* que ocurría cuando el servidor de eventos cambió su dominio. La app ahora se adapta automáticamente a estos cambios sin intervención del usuario. 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");