honoring formats and http headers

This commit is contained in:
Simon
2026-02-09 19:16:44 +00:00
parent c2872c1883
commit d2e1e3adea
3 changed files with 106 additions and 22 deletions

View File

@@ -120,7 +120,7 @@ App.favorites = App.favorites || {};
if (App.videos && typeof App.videos.attachNoReferrerRetry === 'function') {
App.videos.attachNoReferrerRetry(thumb);
}
card.onclick = () => App.player.open(item.url);
card.onclick = () => App.player.open(item.meta || item);
const favoriteBtn = card.querySelector('.favorite-btn');
if (favoriteBtn) {
favoriteBtn.onclick = (event) => {

View File

@@ -29,7 +29,7 @@ App.player = App.player || {};
return host;
}
App.player.open = async function(url) {
App.player.open = async function(source) {
const modal = document.getElementById('video-modal');
const video = document.getElementById('player');
if (!modal || !video) return;
@@ -41,15 +41,30 @@ App.player = App.player || {};
}
// Normalize stream URL + optional referer forwarding.
let refererParam = '';
try {
const origin = new URL(url).origin;
refererParam = `&referer=${encodeURIComponent(origin + '/')}`;
} catch (err) {
refererParam = '';
let resolved = { url: '', referer: '' };
if (App.videos && typeof App.videos.resolveStreamSource === 'function') {
resolved = App.videos.resolveStreamSource(source);
} else if (typeof source === 'string') {
resolved.url = source;
} else if (source && typeof source === 'object') {
resolved.url = source.url || '';
}
const streamUrl = `/api/stream?url=${encodeURIComponent(url)}${refererParam}`;
let isHls = /\.m3u8($|\?)/i.test(url);
if (!resolved.referer && resolved.url) {
try {
resolved.referer = `${new URL(resolved.url).origin}/`;
} catch (err) {
resolved.referer = '';
}
}
if (!resolved.url) {
if (App.ui && App.ui.showError) {
App.ui.showError('Unable to play this stream.');
}
return;
}
const refererParam = resolved.referer ? `&referer=${encodeURIComponent(resolved.referer)}` : '';
const streamUrl = `/api/stream?url=${encodeURIComponent(resolved.url)}${refererParam}`;
let isHls = /\.m3u8($|\?)/i.test(resolved.url);
// Cleanup existing player instance to prevent aborted bindings.
if (state.hlsPlayer) {

View File

@@ -180,7 +180,7 @@ App.videos = App.videos || {};
App.videos.closeAllMenus();
};
}
card.onclick = () => App.player.open(v.url);
card.onclick = () => App.player.open(v);
grid.appendChild(card);
state.renderedVideoIds.add(v.id);
});
@@ -257,22 +257,91 @@ App.videos = App.videos || {};
}
};
// Builds a proxied stream URL with an optional referer parameter.
App.videos.buildStreamUrl = function(videoUrl) {
let refererParam = '';
try {
const origin = new URL(videoUrl).origin;
refererParam = `&referer=${encodeURIComponent(origin + '/')}`;
} catch (err) {
refererParam = '';
App.videos.coerceNumber = function(value) {
if (value === null || value === undefined) return 0;
if (typeof value === 'number') return Number.isFinite(value) ? value : 0;
if (typeof value === 'string') {
const parsed = parseFloat(value);
return Number.isFinite(parsed) ? parsed : 0;
}
return `/api/stream?url=${encodeURIComponent(videoUrl)}${refererParam}`;
return 0;
};
App.videos.pickBestFormat = function(formats) {
if (!Array.isArray(formats) || formats.length === 0) return null;
const candidates = formats.filter((fmt) => fmt && fmt.url);
if (!candidates.length) return null;
const videoCandidates = candidates.filter((fmt) => {
const videoExt = String(fmt.video_ext || '').toLowerCase();
const vcodec = String(fmt.vcodec || '').toLowerCase();
if (videoExt && videoExt !== 'none') return true;
if (vcodec && vcodec !== 'none') return true;
return false;
});
const pool = videoCandidates.length ? videoCandidates : candidates;
const score = (fmt) => {
const height = App.videos.coerceNumber(fmt.height || fmt.quality);
const width = App.videos.coerceNumber(fmt.width);
const size = height || width;
const bitrate = App.videos.coerceNumber(fmt.tbr || fmt.bitrate);
const fps = App.videos.coerceNumber(fmt.fps);
return [size, bitrate, fps];
};
return pool.reduce((best, fmt) => {
if (!best) return fmt;
const bestScore = score(best);
const curScore = score(fmt);
for (let i = 0; i < curScore.length; i++) {
if (curScore[i] > bestScore[i]) return fmt;
if (curScore[i] < bestScore[i]) return best;
}
return best;
}, null);
};
App.videos.resolveStreamSource = function(videoOrUrl) {
let sourceUrl = '';
let referer = '';
if (typeof videoOrUrl === 'string') {
sourceUrl = videoOrUrl;
} else if (videoOrUrl && typeof videoOrUrl === 'object') {
const meta = videoOrUrl.meta || videoOrUrl;
sourceUrl = meta.url || videoOrUrl.url || '';
const best = App.videos.pickBestFormat(meta.formats);
if (best && best.url) {
sourceUrl = best.url;
if (best.http_headers && (best.http_headers.Referer || best.http_headers.referer)) {
referer = best.http_headers.Referer || best.http_headers.referer;
}
}
if (!referer && meta.http_headers && (meta.http_headers.Referer || meta.http_headers.referer)) {
referer = meta.http_headers.Referer || meta.http_headers.referer;
}
}
if (!referer && sourceUrl) {
try {
referer = `${new URL(sourceUrl).origin}/`;
} catch (err) {
referer = '';
}
}
return { url: sourceUrl, referer };
};
// Builds a proxied stream URL with an optional referer parameter.
App.videos.buildStreamUrl = function(videoOrUrl) {
const resolved = App.videos.resolveStreamSource(videoOrUrl);
if (!resolved.url) return '';
const refererParam = resolved.referer ? `&referer=${encodeURIComponent(resolved.referer)}` : '';
return `/api/stream?url=${encodeURIComponent(resolved.url)}${refererParam}`;
};
App.videos.downloadVideo = function(video) {
if (!video || !video.url) return;
if (!video) return;
const streamUrl = App.videos.buildStreamUrl(video);
if (!streamUrl) return;
const link = document.createElement('a');
link.href = App.videos.buildStreamUrl(video.url);
link.href = streamUrl;
const rawName = (video.title || video.id || 'video').toString();
const safeName = rawName.replace(/[^a-z0-9]+/gi, '_').replace(/^_+|_+$/g, '').slice(0, 80);
link.download = safeName ? `${safeName}.mp4` : 'video.mp4';