- Update all channel URLs and event endpoint to streamtp10.com - Create NetworkUtils for centralized OkHttpClient configuration - Implement DNS fallback: Google (Primary) -> AdGuard (Secondary) -> System (Tertiary) - Migrate EventRepository to use NetworkUtils client instead of HttpURLConnection - Fix Referer header in StreamUrlResolver
193 lines
7.4 KiB
Java
193 lines
7.4 KiB
Java
package com.streamplayer;
|
|
|
|
import android.content.Context;
|
|
import android.content.SharedPreferences;
|
|
|
|
import org.json.JSONArray;
|
|
import org.json.JSONException;
|
|
import org.json.JSONObject;
|
|
|
|
import java.io.IOException;
|
|
import java.time.LocalDate;
|
|
import java.time.LocalDateTime;
|
|
import java.time.LocalTime;
|
|
import java.time.ZoneId;
|
|
import java.time.ZonedDateTime;
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.time.format.DateTimeParseException;
|
|
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";
|
|
private static final String KEY_JSON = "json";
|
|
private static final String KEY_TIMESTAMP = "timestamp";
|
|
private static final long CACHE_DURATION = 24L * 60 * 60 * 1000; // 24 horas
|
|
|
|
// URL única para eventos (actualizado para evitar bloqueos de ISP)
|
|
private static final String EVENTS_URL = "https://streamtp10.com/eventos.json";
|
|
|
|
public interface Callback {
|
|
void onSuccess(List<EventItem> events);
|
|
|
|
void onError(String message);
|
|
}
|
|
|
|
public void loadEvents(Context context, boolean forceRefresh, Callback callback) {
|
|
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
|
long last = prefs.getLong(KEY_TIMESTAMP, 0);
|
|
long now = System.currentTimeMillis();
|
|
if (!forceRefresh && now - last < CACHE_DURATION) {
|
|
String cachedJson = prefs.getString(KEY_JSON, null);
|
|
if (cachedJson != null) {
|
|
try {
|
|
callback.onSuccess(parseEvents(cachedJson));
|
|
return;
|
|
} catch (JSONException ignored) {
|
|
}
|
|
}
|
|
}
|
|
|
|
new Thread(() -> {
|
|
try {
|
|
String json = downloadJson(context);
|
|
List<EventItem> events = parseEvents(json);
|
|
prefs.edit().putString(KEY_JSON, json).putLong(KEY_TIMESTAMP, System.currentTimeMillis()).apply();
|
|
callback.onSuccess(events);
|
|
} catch (IOException | JSONException e) {
|
|
String cachedJson = prefs.getString(KEY_JSON, null);
|
|
if (cachedJson != null) {
|
|
try {
|
|
callback.onSuccess(parseEvents(cachedJson));
|
|
return;
|
|
} catch (JSONException ignored) {
|
|
}
|
|
}
|
|
callback.onError(e.getMessage() != null ? e.getMessage() : "Error desconocido");
|
|
}
|
|
}).start();
|
|
}
|
|
|
|
private String downloadJson(Context context) throws IOException {
|
|
Request request = new Request.Builder()
|
|
.url(EVENTS_URL)
|
|
.header("User-Agent", NetworkUtils.getUserAgent())
|
|
.header("Accept", "application/json")
|
|
.build();
|
|
|
|
try (Response response = NetworkUtils.getClient().newCall(request).execute()) {
|
|
if (!response.isSuccessful()) {
|
|
throw new IOException("Error HTTP " + response.code() + ": " + response.message());
|
|
}
|
|
|
|
if (response.body() == null) {
|
|
throw new IOException("Respuesta vacía del servidor");
|
|
}
|
|
|
|
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")) {
|
|
// Aceptamos text/plain también por flexibilidad
|
|
}
|
|
|
|
String responseBody = response.body().string();
|
|
|
|
// Validar que no sea HTML
|
|
if (responseBody.trim().startsWith("<!") || responseBody.trim().startsWith("<html")) {
|
|
throw new IOException("El servidor devolvió HTML en lugar de JSON. La URL del endpoint puede estar incorrecta o el servidor tiene problemas.");
|
|
}
|
|
|
|
return responseBody;
|
|
}
|
|
}
|
|
|
|
private List<EventItem> parseEvents(String json) throws JSONException {
|
|
if (json == null || json.trim().isEmpty()) {
|
|
throw new JSONException("La respuesta está vacía");
|
|
}
|
|
|
|
// Validar que no sea HTML antes de parsear
|
|
String trimmed = json.trim();
|
|
if (trimmed.startsWith("<!") || trimmed.startsWith("<html")) {
|
|
throw new JSONException("Se recibió HTML en lugar de JSON");
|
|
}
|
|
|
|
JSONArray array = new JSONArray(json);
|
|
List<EventItem> events = new ArrayList<>();
|
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
|
|
|
|
for (int i = 0; i < array.length(); i++) {
|
|
JSONObject obj = array.getJSONObject(i);
|
|
String title = obj.optString("title");
|
|
String time = obj.optString("time");
|
|
String category = obj.optString("category");
|
|
String status = obj.optString("status");
|
|
String link = obj.optString("link");
|
|
String normalized = normalizeLink(link);
|
|
|
|
// Ajustar hora: la web muestra hora de España, Argentina es +2 horas
|
|
String displayTime = time;
|
|
try {
|
|
if (time != null && !time.isEmpty()) {
|
|
LocalTime localTime = LocalTime.parse(time.trim(), formatter);
|
|
LocalTime adjustedTime = localTime.plusHours(2);
|
|
displayTime = adjustedTime.format(formatter);
|
|
}
|
|
} catch (DateTimeParseException ignored) {
|
|
}
|
|
|
|
long startMillis = parseEventTime(time);
|
|
events.add(new EventItem(title, displayTime, category, status, normalized, extractChannelName(link), startMillis));
|
|
}
|
|
return Collections.unmodifiableList(events);
|
|
}
|
|
|
|
private String normalizeLink(String link) {
|
|
if (link == null) {
|
|
return "";
|
|
}
|
|
// Actualizado a streamtp10.com
|
|
String updated = link.replace("streamtpmedia.com", "streamtp10.com")
|
|
.replace("streamtpcloud.com", "streamtp10.com");
|
|
return updated.replace("global1.php", "global2.php");
|
|
}
|
|
|
|
private String extractChannelName(String link) {
|
|
if (link == null) {
|
|
return "";
|
|
}
|
|
int idx = link.indexOf("stream=");
|
|
if (idx == -1) {
|
|
return "";
|
|
}
|
|
return link.substring(idx + 7).replace("_", " ").toUpperCase();
|
|
}
|
|
|
|
private long parseEventTime(String time) {
|
|
if (time == null || time.isEmpty()) {
|
|
return -1;
|
|
}
|
|
try {
|
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
|
|
LocalTime localTime = LocalTime.parse(time.trim(), formatter);
|
|
// Ajustar hora: la web muestra hora de España, Argentina es +2 horas
|
|
LocalTime adjustedTime = localTime.plusHours(2);
|
|
ZoneId zone = ZoneId.of("America/Argentina/Buenos_Aires");
|
|
LocalDate today = LocalDate.now(zone);
|
|
ZonedDateTime start = ZonedDateTime.of(LocalDateTime.of(today, adjustedTime), zone);
|
|
ZonedDateTime now = ZonedDateTime.now(zone);
|
|
if (start.isBefore(now.minusHours(12))) {
|
|
start = start.plusDays(1);
|
|
}
|
|
return start.toInstant().toEpochMilli();
|
|
} catch (DateTimeParseException e) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|