live stream support

This commit is contained in:
Simon
2026-06-22 12:34:47 +00:00
parent a97f7e7b0f
commit b5b3e13dd0
9 changed files with 271 additions and 35 deletions

View File

@@ -110,7 +110,7 @@ App.videos = App.videos || {};
App.videos.buildImageProxyUrl = function(imageUrl) {
if (!imageUrl) return '';
try {
return `/api/image?url=${encodeURIComponent(imageUrl)}&ts=${Date.now()}`;
return `/api/image?url=${encodeURIComponent(imageUrl)}`;
} catch (err) {
return '';
}
@@ -290,14 +290,16 @@ App.videos = App.videos || {};
const tagsMarkup = tags.length
? `<div class="video-tags">${tags.map(tag => `<button class="video-tag" type="button" data-tag="${tag}">${tag}</button>`).join('')}</div>`
: '';
const liveBadge = v.isLive ? '<span class="live-badge">● LIVE</span>' : '';
card.innerHTML = `
${liveBadge}
<button class="favorite-btn" type="button" aria-pressed="false" aria-label="Add to favorites" data-fav-key="${favoriteKey || ''}">♡</button>
<button class="video-menu-btn" type="button" aria-haspopup="true" aria-expanded="false" aria-label="More options">⋯</button>
<div class="video-menu" role="menu">
<button class="video-menu-item" type="button" data-action="info" role="menuitem">Show info</button>
<button class="video-menu-item" type="button" data-action="download" role="menuitem">Download</button>
</div>
<img src="${v.thumb}" alt="${v.title}">
<img src="${v.thumb}" alt="${v.title}" loading="lazy" decoding="async">
<div class="video-loading" aria-hidden="true">
<div class="video-loading-spinner"></div>
</div>
@@ -432,11 +434,16 @@ App.videos = App.videos || {};
App.videos.handleSearch = function(value) {
if (typeof value === 'string') {
const searchInput = document.getElementById('search-input');
if (searchInput) {
if (searchInput.value !== value) {
searchInput.value = value;
}
searchInput.dispatchEvent(new Event('input', { bubbles: true }));
if (searchInput && searchInput.value !== value) {
searchInput.value = value;
}
// Keep the clear button in sync without re-dispatching an `input`
// event (which would re-trigger the debounced reload listener).
const clearBtn = document.getElementById('search-clear-btn');
if (searchInput && clearBtn) {
const hasValue = searchInput.value.trim().length > 0;
clearBtn.classList.toggle('is-visible', hasValue);
clearBtn.disabled = !hasValue;
}
}
state.currentPage = 1;
@@ -600,6 +607,8 @@ App.videos = App.videos || {};
let sourceUrl = '';
let referer = '';
let userAgent = '';
const isLive = !!(videoOrUrl && typeof videoOrUrl === 'object' &&
(videoOrUrl.isLive || (videoOrUrl.meta && videoOrUrl.meta.isLive)));
if (typeof videoOrUrl === 'string') {
sourceUrl = videoOrUrl;
} else if (videoOrUrl && typeof videoOrUrl === 'object') {
@@ -634,7 +643,7 @@ App.videos = App.videos || {};
referer = '';
}
}
return { url: sourceUrl, referer, userAgent };
return { url: sourceUrl, referer, userAgent, isLive };
};
// Builds a proxied stream URL. Extra params other than `url` are forwarded
@@ -644,7 +653,8 @@ App.videos = App.videos || {};
if (!resolved.url) return '';
const refererParam = resolved.referer ? `&referer=${encodeURIComponent(resolved.referer)}` : '';
const userAgentParam = resolved.userAgent ? `&User-Agent=${encodeURIComponent(resolved.userAgent)}` : '';
return `/api/stream?url=${encodeURIComponent(resolved.url)}${refererParam}${userAgentParam}`;
const liveParam = resolved.isLive ? '&live=1' : '';
return `/api/stream?url=${encodeURIComponent(resolved.url)}${refererParam}${userAgentParam}${liveParam}`;
};
App.videos.downloadVideo = function(video) {