mp4, rtsp

This commit is contained in:
Radek Davidek 2026-03-04 15:33:04 +01:00
parent 28c5cb8c0c
commit c5d5685734
3 changed files with 46 additions and 4 deletions

View File

@ -338,7 +338,11 @@ public final class XtreamPlayerApplication {
String streamUrl; String streamUrl;
String directUrl = query.getOrDefault("url", "").trim(); String directUrl = query.getOrDefault("url", "").trim();
if (!directUrl.isBlank()) { if (!directUrl.isBlank()) {
if (!directUrl.startsWith("http://") && !directUrl.startsWith("https://")) { String lowerUrl = directUrl.toLowerCase(Locale.ROOT);
if (!lowerUrl.startsWith("http://")
&& !lowerUrl.startsWith("https://")
&& !lowerUrl.startsWith("rtsp://")
&& !lowerUrl.startsWith("rtsps://")) {
writeJson(exchange, 400, errorJson("Unsupported URL protocol.")); writeJson(exchange, 400, errorJson("Unsupported URL protocol."));
return; return;
} }
@ -817,8 +821,13 @@ public final class XtreamPlayerApplication {
List<String> attemptErrors = new ArrayList<>(); List<String> attemptErrors = new ArrayList<>();
for (URI candidate : attempts) { for (URI candidate : attempts) {
try { try {
String incomingRange = firstNonBlank(
exchange.getRequestHeaders().getFirst("Range"),
"bytes=0-"
);
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(candidate) HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(candidate)
.GET() .GET()
.timeout(Duration.ofSeconds(60))
.header("User-Agent", firstNonBlank( .header("User-Agent", firstNonBlank(
exchange.getRequestHeaders().getFirst("User-Agent"), exchange.getRequestHeaders().getFirst("User-Agent"),
DEFAULT_BROWSER_UA DEFAULT_BROWSER_UA
@ -826,12 +835,16 @@ public final class XtreamPlayerApplication {
.header("Accept", firstNonBlank( .header("Accept", firstNonBlank(
exchange.getRequestHeaders().getFirst("Accept"), exchange.getRequestHeaders().getFirst("Accept"),
"*/*" "*/*"
))
.header("Range", incomingRange)
.header("Accept-Encoding", firstNonBlank(
exchange.getRequestHeaders().getFirst("Accept-Encoding"),
"identity"
)); ));
copyRequestHeaderIfPresent(exchange, requestBuilder, "Range");
copyRequestHeaderIfPresent(exchange, requestBuilder, "If-Range"); copyRequestHeaderIfPresent(exchange, requestBuilder, "If-Range");
copyRequestHeaderIfPresent(exchange, requestBuilder, "Accept-Encoding");
copyRequestHeaderIfPresent(exchange, requestBuilder, "Cache-Control"); copyRequestHeaderIfPresent(exchange, requestBuilder, "Cache-Control");
copyRequestHeaderIfPresent(exchange, requestBuilder, "Pragma"); copyRequestHeaderIfPresent(exchange, requestBuilder, "Pragma");
copyRequestHeaderIfPresent(exchange, requestBuilder, "Origin");
String referer = resolveRefererForCandidate(exchange, candidate, sourceUrl); String referer = resolveRefererForCandidate(exchange, candidate, sourceUrl);
if (!referer.isBlank()) { if (!referer.isBlank()) {
requestBuilder.header("Referer", referer); requestBuilder.header("Referer", referer);

View File

@ -310,6 +310,10 @@
if (!name || !url) { if (!name || !url) {
return; return;
} }
if (!isSupportedCustomUrl(url)) {
setSettingsMessage("Custom stream URL must start with http://, https://, rtsp://, or rtsps://", "err");
return;
}
state.customStreams.push({ state.customStreams.push({
id: String(Date.now()), id: String(Date.now()),
name, name,
@ -1748,6 +1752,15 @@
setSubtitleStatus("No subtitle loaded.", false); setSubtitleStatus("No subtitle loaded.", false);
scheduleEmbeddedSubtitleScan(); scheduleEmbeddedSubtitleScan();
if (isRtspUrl(playbackUrl)) {
state.currentStreamInfo.playbackEngine = "external player (RTSP)";
state.currentStreamInfo.resolution = "n/a";
state.currentStreamInfo.duration = "n/a";
renderStreamInfo();
setSettingsMessage("RTSP is not supported in browser player. Use Open in system player.", "err");
return;
}
if (isLikelyHls(playbackUrl) && shouldUseHlsJs()) { if (isLikelyHls(playbackUrl) && shouldUseHlsJs()) {
state.currentStreamInfo.playbackEngine = "hls.js"; state.currentStreamInfo.playbackEngine = "hls.js";
renderStreamInfo(); renderStreamInfo();
@ -1820,6 +1833,9 @@
if (!url) { if (!url) {
return url; return url;
} }
if (isRtspUrl(url)) {
return url;
}
try { try {
const pageIsHttps = window.location.protocol === "https:"; const pageIsHttps = window.location.protocol === "https:";
const target = new URL(url, window.location.href); const target = new URL(url, window.location.href);
@ -1832,6 +1848,19 @@
return url; return url;
} }
function isRtspUrl(urlRaw) {
const value = String(urlRaw || "").trim().toLowerCase();
return value.startsWith("rtsp://") || value.startsWith("rtsps://");
}
function isSupportedCustomUrl(urlRaw) {
const value = String(urlRaw || "").trim().toLowerCase();
return value.startsWith("http://")
|| value.startsWith("https://")
|| value.startsWith("rtsp://")
|| value.startsWith("rtsps://");
}
function resetPlayerElement() { function resetPlayerElement() {
disposeHls(); disposeHls();
el.player.pause(); el.player.pause();

View File

@ -129,7 +129,7 @@
</label> </label>
<label> <label>
URL URL
<input id="custom-url" required placeholder="https://...m3u8"> <input id="custom-url" required placeholder="https://...m3u8 or rtsp://...">
</label> </label>
<div class="actions"> <div class="actions">
<button type="submit">Add</button> <button type="submit">Add</button>