https fixes
This commit is contained in:
parent
fcef5e54a9
commit
2b3f3810d4
@ -28,6 +28,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class XtreamPlayerApplication {
|
||||
private static final int DEFAULT_PORT = 8080;
|
||||
@ -35,6 +37,7 @@ public final class XtreamPlayerApplication {
|
||||
private static final String ATTR_REQ_START_NANOS = "reqStartNanos";
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
private static final Logger LOGGER = LogManager.getLogger(XtreamPlayerApplication.class);
|
||||
private static final Pattern URI_ATTR_PATTERN = Pattern.compile("URI=\"([^\"]+)\"");
|
||||
private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(20))
|
||||
.followRedirects(HttpClient.Redirect.NORMAL)
|
||||
@ -60,6 +63,7 @@ public final class XtreamPlayerApplication {
|
||||
server.createContext("/api/test-login", new TestLoginHandler(configStore));
|
||||
server.createContext("/api/xtream", new XtreamProxyHandler(configStore));
|
||||
server.createContext("/api/stream-url", new StreamUrlHandler(configStore));
|
||||
server.createContext("/api/stream-proxy", new StreamProxyHandler());
|
||||
server.createContext("/api/open-in-player", new OpenInPlayerHandler(configStore));
|
||||
server.createContext("/api/library/load", new LibraryLoadHandler(libraryService));
|
||||
server.createContext("/api/library/status", new LibraryStatusHandler(libraryService));
|
||||
@ -203,6 +207,66 @@ public final class XtreamPlayerApplication {
|
||||
}
|
||||
}
|
||||
|
||||
private record StreamProxyHandler() implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
Map<String, String> query = parseKeyValue(exchange.getRequestURI().getRawQuery());
|
||||
logApiRequest(exchange, "/api/stream-proxy", query);
|
||||
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
|
||||
methodNotAllowed(exchange, "GET");
|
||||
return;
|
||||
}
|
||||
|
||||
String rawUrl = query.getOrDefault("url", "").trim();
|
||||
if (rawUrl.isBlank()) {
|
||||
writeJson(exchange, 400, errorJson("Missing url parameter."));
|
||||
return;
|
||||
}
|
||||
|
||||
URI target;
|
||||
try {
|
||||
target = URI.create(rawUrl);
|
||||
} catch (Exception exception) {
|
||||
writeJson(exchange, 400, errorJson("Invalid url parameter."));
|
||||
return;
|
||||
}
|
||||
String scheme = target.getScheme() == null ? "" : target.getScheme().toLowerCase(Locale.ROOT);
|
||||
if (!"http".equals(scheme) && !"https".equals(scheme)) {
|
||||
writeJson(exchange, 400, errorJson("Unsupported URL protocol."));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
HttpRequest request = HttpRequest.newBuilder(target)
|
||||
.GET()
|
||||
.timeout(Duration.ofSeconds(60))
|
||||
.header("User-Agent", "XtreamPlayer/1.0")
|
||||
.header("Accept", "*/*")
|
||||
.build();
|
||||
HttpResponse<byte[]> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofByteArray());
|
||||
String contentType = response.headers().firstValue("Content-Type").orElse("application/octet-stream");
|
||||
byte[] body = response.body() == null ? new byte[0] : response.body();
|
||||
|
||||
if (isHlsPlaylist(target, contentType)) {
|
||||
String rewritten = rewritePlaylistForProxy(target, body);
|
||||
exchange.getResponseHeaders().set("Content-Type", "application/vnd.apple.mpegurl; charset=utf-8");
|
||||
writeBytes(exchange, response.statusCode(), rewritten.getBytes(StandardCharsets.UTF_8),
|
||||
"application/vnd.apple.mpegurl; charset=utf-8");
|
||||
return;
|
||||
}
|
||||
|
||||
exchange.getResponseHeaders().set("Content-Type", contentType);
|
||||
writeBytes(exchange, response.statusCode(), body, contentType);
|
||||
} catch (InterruptedException interruptedException) {
|
||||
Thread.currentThread().interrupt();
|
||||
writeJson(exchange, 500, errorJson("Stream proxy interrupted."));
|
||||
} catch (Exception exception) {
|
||||
LOGGER.error("Stream proxy failed for {}", maskUri(target), exception);
|
||||
writeJson(exchange, 502, errorJson("Unable to proxy stream: " + exception.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record OpenInPlayerHandler(ConfigStore configStore) implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
@ -641,6 +705,50 @@ public final class XtreamPlayerApplication {
|
||||
return URI.create(config.serverUrl() + "/player_api.php?" + query);
|
||||
}
|
||||
|
||||
private static boolean isHlsPlaylist(URI target, String contentType) {
|
||||
String path = target.getPath() == null ? "" : target.getPath().toLowerCase(Locale.ROOT);
|
||||
String ct = contentType == null ? "" : contentType.toLowerCase(Locale.ROOT);
|
||||
return path.endsWith(".m3u8")
|
||||
|| ct.contains("application/vnd.apple.mpegurl")
|
||||
|| ct.contains("application/x-mpegurl");
|
||||
}
|
||||
|
||||
private static String rewritePlaylistForProxy(URI baseUri, byte[] bodyBytes) {
|
||||
String raw = new String(bodyBytes, StandardCharsets.UTF_8);
|
||||
String[] lines = raw.split("\\r?\\n", -1);
|
||||
StringBuilder out = new StringBuilder(raw.length() + 256);
|
||||
for (String line : lines) {
|
||||
String trimmed = line.trim();
|
||||
if (trimmed.isEmpty()) {
|
||||
out.append('\n');
|
||||
continue;
|
||||
}
|
||||
if (trimmed.startsWith("#")) {
|
||||
out.append(rewriteTagUris(line, baseUri)).append('\n');
|
||||
continue;
|
||||
}
|
||||
URI absolute = baseUri.resolve(trimmed);
|
||||
out.append(proxyStreamUrl(absolute.toString())).append('\n');
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
private static String rewriteTagUris(String line, URI baseUri) {
|
||||
Matcher matcher = URI_ATTR_PATTERN.matcher(line);
|
||||
StringBuilder out = new StringBuilder(line.length() + 64);
|
||||
while (matcher.find()) {
|
||||
String current = matcher.group(1);
|
||||
String rewritten = proxyStreamUrl(baseUri.resolve(current).toString());
|
||||
matcher.appendReplacement(out, "URI=\"" + Matcher.quoteReplacement(rewritten) + "\"");
|
||||
}
|
||||
matcher.appendTail(out);
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
private static String proxyStreamUrl(String absoluteUrl) {
|
||||
return "/api/stream-proxy?url=" + urlEncode(absoluteUrl);
|
||||
}
|
||||
|
||||
private static Map<String, String> parseKeyValue(String raw) {
|
||||
Map<String, String> result = new LinkedHashMap<>();
|
||||
if (raw == null || raw.isBlank()) {
|
||||
|
||||
@ -1493,6 +1493,7 @@
|
||||
function setPlayer(title, url, info = {}) {
|
||||
el.playerTitle.textContent = title || "Player";
|
||||
const systemPlayerUrl = buildSystemPlayerHref(title, url, info);
|
||||
const playbackUrl = buildBrowserPlaybackUrl(url);
|
||||
el.openDirect.dataset.url = url;
|
||||
el.openDirect.disabled = !url;
|
||||
el.openSystemPlayer.dataset.url = systemPlayerUrl;
|
||||
@ -1512,7 +1513,7 @@
|
||||
setSubtitleStatus("No subtitle loaded.", false);
|
||||
scheduleEmbeddedSubtitleScan();
|
||||
|
||||
if (isLikelyHls(url) && shouldUseHlsJs()) {
|
||||
if (isLikelyHls(playbackUrl) && shouldUseHlsJs()) {
|
||||
state.currentStreamInfo.playbackEngine = "hls.js";
|
||||
renderStreamInfo();
|
||||
hlsInstance = new window.Hls({
|
||||
@ -1531,7 +1532,7 @@
|
||||
: (Array.isArray(hlsInstance?.subtitleTracks) ? hlsInstance.subtitleTracks : []);
|
||||
refreshEmbeddedSubtitleTracks();
|
||||
});
|
||||
hlsInstance.loadSource(url);
|
||||
hlsInstance.loadSource(playbackUrl);
|
||||
hlsInstance.attachMedia(el.player);
|
||||
hlsInstance.on(window.Hls.Events.ERROR, (_event, data) => {
|
||||
if (!data?.fatal) {
|
||||
@ -1545,7 +1546,7 @@
|
||||
}
|
||||
});
|
||||
} else {
|
||||
el.player.src = url;
|
||||
el.player.src = playbackUrl;
|
||||
}
|
||||
|
||||
const playbackPromise = el.player.play();
|
||||
@ -1579,6 +1580,23 @@
|
||||
return `/api/open-in-player?${params.toString()}`;
|
||||
}
|
||||
|
||||
function buildBrowserPlaybackUrl(urlRaw) {
|
||||
const url = String(urlRaw || "").trim();
|
||||
if (!url) {
|
||||
return url;
|
||||
}
|
||||
try {
|
||||
const pageIsHttps = window.location.protocol === "https:";
|
||||
const target = new URL(url, window.location.href);
|
||||
if (pageIsHttps && target.protocol === "http:") {
|
||||
return `/api/stream-proxy?url=${encodeURIComponent(target.toString())}`;
|
||||
}
|
||||
} catch (error) {
|
||||
return url;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
function resetPlayerElement() {
|
||||
disposeHls();
|
||||
el.player.pause();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user