This commit is contained in:
Radek Davidek 2026-03-04 14:36:10 +01:00
parent 52f3af0758
commit 87925a57de

View File

@ -251,28 +251,7 @@ public final class XtreamPlayerApplication {
List<String> attemptErrors = new ArrayList<>();
for (URI candidate : attempts) {
try {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(candidate)
.GET()
.timeout(Duration.ofSeconds(60))
.header("User-Agent", firstNonBlank(
exchange.getRequestHeaders().getFirst("User-Agent"),
DEFAULT_BROWSER_UA
))
.header("Accept", firstNonBlank(
exchange.getRequestHeaders().getFirst("Accept"),
"*/*"
));
copyRequestHeaderIfPresent(exchange, requestBuilder, "Range");
copyRequestHeaderIfPresent(exchange, requestBuilder, "If-Range");
if (!sourceUrl.isBlank()) {
requestBuilder.header("Referer", sourceUrl);
String origin = originFromUrl(sourceUrl);
if (!origin.isBlank()) {
requestBuilder.header("Origin", origin);
}
}
HttpRequest request = requestBuilder.build();
HttpResponse<byte[]> candidateResponse = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofByteArray());
HttpResponse<byte[]> candidateResponse = sendStreamRequest(exchange, candidate, sourceUrl);
if (response == null || response.statusCode() >= 400) {
response = candidateResponse;
usedTarget = candidate;
@ -298,6 +277,17 @@ public final class XtreamPlayerApplication {
}
String contentType = response.headers().firstValue("Content-Type").orElse("application/octet-stream");
byte[] body = response.body() == null ? new byte[0] : response.body();
if (response.statusCode() == 403 && !sourceUrl.isBlank() && !isHlsPlaylist(usedTarget, contentType)) {
UpstreamResult retried = retrySegmentUsingFreshPlaylist(exchange, target, sourceUrl);
if (retried != null) {
response = retried.response();
usedTarget = retried.uri();
contentType = response.headers().firstValue("Content-Type").orElse("application/octet-stream");
body = response.body() == null ? new byte[0] : response.body();
}
}
if (response.statusCode() >= 400) {
LOGGER.warn(
"Stream proxy upstream returned status={} uri={} bytes={} contentType={}",
@ -811,6 +801,86 @@ public final class XtreamPlayerApplication {
return "/api/stream-proxy?url=" + urlEncode(absoluteUrl) + "&src=" + urlEncode(sourceUrl);
}
private static UpstreamResult retrySegmentUsingFreshPlaylist(HttpExchange exchange, URI originalSegmentUri, String sourceUrl)
throws IOException, InterruptedException {
URI sourceUri = URI.create(sourceUrl);
HttpResponse<byte[]> playlistResponse = sendStreamRequest(exchange, sourceUri, sourceUrl);
if (playlistResponse.statusCode() >= 400) {
return null;
}
String playlistType = playlistResponse.headers().firstValue("Content-Type").orElse("");
if (!isHlsPlaylist(sourceUri, playlistType)) {
return null;
}
String segmentName = pathBasename(originalSegmentUri);
if (segmentName.isBlank()) {
return null;
}
String playlist = new String(
playlistResponse.body() == null ? new byte[0] : playlistResponse.body(),
StandardCharsets.UTF_8
);
String[] lines = playlist.split("\\r?\\n");
UpstreamResult fallback = null;
for (String line : lines) {
String trimmed = line.trim();
if (trimmed.isBlank() || trimmed.startsWith("#")) {
continue;
}
URI candidate = sourceUri.resolve(trimmed);
if (!segmentName.equals(pathBasename(candidate))) {
continue;
}
HttpResponse<byte[]> response = sendStreamRequest(exchange, candidate, sourceUrl);
UpstreamResult result = new UpstreamResult(response, candidate);
fallback = result;
if (response.statusCode() < 400) {
LOGGER.info("Segment retry via fresh playlist succeeded uri={}", maskUri(candidate));
return result;
}
}
return fallback;
}
private static HttpResponse<byte[]> sendStreamRequest(HttpExchange exchange, URI candidate, String sourceUrl)
throws IOException, InterruptedException {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(candidate)
.GET()
.timeout(Duration.ofSeconds(60))
.header("User-Agent", firstNonBlank(
exchange.getRequestHeaders().getFirst("User-Agent"),
DEFAULT_BROWSER_UA
))
.header("Accept", firstNonBlank(
exchange.getRequestHeaders().getFirst("Accept"),
"*/*"
));
copyRequestHeaderIfPresent(exchange, requestBuilder, "Range");
copyRequestHeaderIfPresent(exchange, requestBuilder, "If-Range");
if (sourceUrl != null && !sourceUrl.isBlank()) {
requestBuilder.header("Referer", sourceUrl);
String origin = originFromUrl(sourceUrl);
if (!origin.isBlank()) {
requestBuilder.header("Origin", origin);
}
}
HttpRequest request = requestBuilder.build();
return HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofByteArray());
}
private static String pathBasename(URI uri) {
String path = uri == null || uri.getPath() == null ? "" : uri.getPath();
int index = path.lastIndexOf('/');
if (index < 0 || index == path.length() - 1) {
return path;
}
return path.substring(index + 1);
}
private record UpstreamResult(HttpResponse<byte[]> response, URI uri) {
}
private static String originFromUrl(String url) {
try {
URI uri = URI.create(url);