mkv chunking
This commit is contained in:
parent
4ae128f35d
commit
b8df5d0997
@ -244,6 +244,11 @@ public final class XtreamPlayerApplication {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isLikelyLargeVodFile(target, sourceUrl)) {
|
||||||
|
proxyLargeMediaStream(exchange, target, sourceUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
List<URI> attempts = candidateUris(target);
|
List<URI> attempts = candidateUris(target);
|
||||||
HttpResponse<byte[]> response = null;
|
HttpResponse<byte[]> response = null;
|
||||||
@ -798,10 +803,103 @@ public final class XtreamPlayerApplication {
|
|||||||
return out.toString();
|
return out.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isLikelyLargeVodFile(URI uri, String sourceUrl) {
|
||||||
|
if (sourceUrl != null && !sourceUrl.isBlank()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String path = uri == null || uri.getPath() == null ? "" : uri.getPath().toLowerCase(Locale.ROOT);
|
||||||
|
return path.endsWith(".mkv") || path.contains(".mkv?")
|
||||||
|
|| path.endsWith(".mp4") || path.contains(".mp4?");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void proxyLargeMediaStream(HttpExchange exchange, URI target, String sourceUrl) throws IOException {
|
||||||
|
List<URI> attempts = candidateUris(target);
|
||||||
|
List<String> attemptErrors = new ArrayList<>();
|
||||||
|
for (URI candidate : attempts) {
|
||||||
|
try {
|
||||||
|
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(candidate)
|
||||||
|
.GET()
|
||||||
|
.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");
|
||||||
|
copyRequestHeaderIfPresent(exchange, requestBuilder, "Accept-Encoding");
|
||||||
|
copyRequestHeaderIfPresent(exchange, requestBuilder, "Cache-Control");
|
||||||
|
copyRequestHeaderIfPresent(exchange, requestBuilder, "Pragma");
|
||||||
|
String referer = resolveRefererForCandidate(exchange, candidate, sourceUrl);
|
||||||
|
if (!referer.isBlank()) {
|
||||||
|
requestBuilder.header("Referer", referer);
|
||||||
|
}
|
||||||
|
HttpResponse<InputStream> response = HTTP_CLIENT.send(
|
||||||
|
requestBuilder.build(),
|
||||||
|
HttpResponse.BodyHandlers.ofInputStream()
|
||||||
|
);
|
||||||
|
int status = response.statusCode();
|
||||||
|
String contentType = response.headers().firstValue("Content-Type").orElse("application/octet-stream");
|
||||||
|
if (status >= 400) {
|
||||||
|
attemptErrors.add(maskUri(candidate) + " -> HTTP " + status);
|
||||||
|
try (InputStream ignored = response.body()) {
|
||||||
|
// close upstream body
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
copyResponseHeaderIfPresent(response, exchange, "Accept-Ranges");
|
||||||
|
copyResponseHeaderIfPresent(response, exchange, "Content-Range");
|
||||||
|
copyResponseHeaderIfPresent(response, exchange, "Cache-Control");
|
||||||
|
copyResponseHeaderIfPresent(response, exchange, "Expires");
|
||||||
|
exchange.getResponseHeaders().set("Content-Type", contentType);
|
||||||
|
response.headers().firstValue("Content-Length")
|
||||||
|
.ifPresent(value -> exchange.getResponseHeaders().set("Content-Length", value));
|
||||||
|
|
||||||
|
long responseLength = parseContentLength(response.headers().firstValue("Content-Length").orElse(""));
|
||||||
|
exchange.sendResponseHeaders(status, responseLength >= 0 ? responseLength : 0);
|
||||||
|
long sent = 0;
|
||||||
|
try (InputStream inputStream = response.body()) {
|
||||||
|
byte[] buffer = new byte[64 * 1024];
|
||||||
|
int read;
|
||||||
|
while ((read = inputStream.read(buffer)) >= 0) {
|
||||||
|
exchange.getResponseBody().write(buffer, 0, read);
|
||||||
|
sent += read;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
logApiResponse(exchange, status, (int) Math.min(Integer.MAX_VALUE, sent), contentType);
|
||||||
|
exchange.close();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} catch (InterruptedException interruptedException) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
writeJson(exchange, 500, errorJson("Stream proxy interrupted."));
|
||||||
|
return;
|
||||||
|
} catch (Exception exception) {
|
||||||
|
attemptErrors.add(maskUri(candidate) + " -> " + compactError(exception));
|
||||||
|
LOGGER.warn("Large media proxy candidate failed uri={} reason={}", maskUri(candidate), compactError(exception));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeJson(exchange, 502, errorJson("Unable to proxy large media stream. Attempts: " + String.join(" | ", attemptErrors)));
|
||||||
|
}
|
||||||
|
|
||||||
private static String proxyStreamUrl(String absoluteUrl, String sourceUrl) {
|
private static String proxyStreamUrl(String absoluteUrl, String sourceUrl) {
|
||||||
return "/api/stream-proxy?url=" + urlEncode(absoluteUrl) + "&src=" + urlEncode(sourceUrl);
|
return "/api/stream-proxy?url=" + urlEncode(absoluteUrl) + "&src=" + urlEncode(sourceUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static long parseContentLength(String value) {
|
||||||
|
if (value == null || value.isBlank()) {
|
||||||
|
return -1L;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Long.parseLong(value.trim());
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
return -1L;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static UpstreamResult retrySegmentUsingFreshPlaylist(HttpExchange exchange, URI originalSegmentUri, String sourceUrl)
|
private static UpstreamResult retrySegmentUsingFreshPlaylist(HttpExchange exchange, URI originalSegmentUri, String sourceUrl)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
URI sourceUri = URI.create(sourceUrl);
|
URI sourceUri = URI.create(sourceUrl);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user