Cancel stale playback probes and verify Linux QSV support
This commit is contained in:
@ -1107,8 +1107,9 @@
|
||||
<script src="/js/components/VodDuration.js?v=1"></script>
|
||||
<script src="/js/components/CaptionTracks.js?v=2"></script>
|
||||
<script src="/js/components/HlsAudioRecovery.js?v=1"></script>
|
||||
<script src="/js/components/PlaybackProbe.js?v=1"></script>
|
||||
<script src="/js/components/PlayerFullscreen.js?v=1"></script>
|
||||
<script src="/js/components/VideoPlayer.js?v=7"></script>
|
||||
<script src="/js/components/VideoPlayer.js?v=8"></script>
|
||||
<script src="/js/components/ChannelList.js?v=4"></script>
|
||||
<script src="/js/components/SourceManager.js?v=3"></script>
|
||||
<script src="/js/components/EpgGuide.js?v=2"></script>
|
||||
@ -1118,7 +1119,7 @@
|
||||
<script src="/js/pages/MoviesPage.js?v=4"></script>
|
||||
<script src="/js/pages/SeriesPage.js?v=1"></script>
|
||||
<script src="/js/pages/Settings.js?v=2"></script>
|
||||
<script src="/js/pages/WatchPage.js?v=7"></script>
|
||||
<script src="/js/pages/WatchPage.js?v=8"></script>
|
||||
<script src="/js/app.js?v=5"></script>
|
||||
|
||||
</body>
|
||||
|
||||
28
public/js/components/PlaybackProbe.js
Normal file
28
public/js/components/PlaybackProbe.js
Normal file
@ -0,0 +1,28 @@
|
||||
/** Metadata probes belong to the active playback, and share an ordered lease across replacements. */
|
||||
class PlaybackProbe {
|
||||
constructor(fetchImpl = fetch) {
|
||||
this.fetch = fetchImpl;
|
||||
this.sequence = 0;
|
||||
this.leaseId = Array.from(globalThis.crypto.getRandomValues(new Uint8Array(16)),
|
||||
byte => byte.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
async read(url, { program = null, userAgent } = {}) {
|
||||
this.cancel();
|
||||
const controller = this.controller = new AbortController();
|
||||
const query = new URLSearchParams({ url, probeLeaseId: this.leaseId, requestSequence: String(++this.sequence) });
|
||||
if (program != null) query.set('program', program);
|
||||
if (userAgent !== undefined) query.set('ua', userAgent);
|
||||
try {
|
||||
const response = await this.fetch('/api/probe?' + query, { signal: controller.signal, credentials: 'same-origin' });
|
||||
const info = await response.json();
|
||||
if (controller.signal.aborted || this.controller !== controller || response.status === 409) {
|
||||
throw Object.assign(new Error('Playback probe was superseded'), { name: 'AbortError' });
|
||||
}
|
||||
if (response.ok === false) throw new Error(info.error || 'Unable to probe stream');
|
||||
return info;
|
||||
} finally { if (this.controller === controller) this.controller = null; }
|
||||
}
|
||||
cancel() { this.controller?.abort(); this.controller = null; }
|
||||
}
|
||||
if (typeof window !== 'undefined') window.PlaybackProbe = PlaybackProbe;
|
||||
if (typeof module !== 'undefined') module.exports = PlaybackProbe;
|
||||
@ -643,7 +643,6 @@ class VideoPlayer {
|
||||
}
|
||||
if (this.playbackGeneration !== playbackGeneration) return;
|
||||
this.currentChannel = channel;
|
||||
const programQuery = channel?.program == null ? '' : '&program=' + encodeURIComponent(channel.program);
|
||||
let probeInfo = null;
|
||||
this.updateTranscodeStatus('hidden');
|
||||
|
||||
@ -661,8 +660,7 @@ class VideoPlayer {
|
||||
if (this.settings.autoTranscode) {
|
||||
console.log('[Player] Auto Transcode enabled. Probing stream...');
|
||||
try {
|
||||
const probeRes = await fetch(`/api/probe?url=${encodeURIComponent(streamUrl)}${programQuery}`);
|
||||
const info = await probeRes.json();
|
||||
const info = await (this.playbackProbe ||= new PlaybackProbe(fetch)).read(streamUrl, { program: channel?.program });
|
||||
if (this.playbackGeneration !== playbackGeneration) return;
|
||||
probeInfo = info;
|
||||
console.log(`[Player] Probe result: video=${info.video}, audio=${info.audio}, ${info.width}x${info.height}, compatible=${info.compatible}`);
|
||||
@ -724,6 +722,7 @@ class VideoPlayer {
|
||||
// Compatible - fall through to normal HLS.js path
|
||||
console.log('[Player] Auto: Using HLS.js (compatible)');
|
||||
} catch (err) {
|
||||
if (err.name === 'AbortError') return;
|
||||
console.warn('[Player] Probe failed, using normal playback:', err.message);
|
||||
// Continue with normal playback on probe failure
|
||||
}
|
||||
@ -753,11 +752,10 @@ class VideoPlayer {
|
||||
// Probe to get video codec for HEVC tag handling
|
||||
let videoCodec = 'unknown';
|
||||
try {
|
||||
const probeRes = await fetch(`/api/probe?url=${encodeURIComponent(streamUrl)}${programQuery}`);
|
||||
const info = await probeRes.json();
|
||||
const info = await (this.playbackProbe ||= new PlaybackProbe(fetch)).read(streamUrl, { program: channel?.program });
|
||||
if (this.playbackGeneration !== playbackGeneration) return;
|
||||
videoCodec = info.video;
|
||||
} catch (e) { console.warn('Probe failed for force audio, assuming h264'); }
|
||||
} catch (e) { if (e.name === 'AbortError') return; console.warn('Probe failed for force audio, assuming h264'); }
|
||||
|
||||
if (this.playbackGeneration !== playbackGeneration) return;
|
||||
const playlistUrl = await this.startTranscodeSession(streamUrl, { program: channel?.program || null, videoMode: 'copy', videoCodec });
|
||||
@ -805,9 +803,9 @@ class VideoPlayer {
|
||||
this.updateTranscodeStatus('remuxing', this.settings.forceRemux ? 'Remux (Force)' : 'Remux (Program)');
|
||||
if (!probeInfo) {
|
||||
try {
|
||||
const response = await fetch(`/api/probe?url=${encodeURIComponent(streamUrl)}${programQuery}`);
|
||||
probeInfo = await response.json();
|
||||
probeInfo = await (this.playbackProbe ||= new PlaybackProbe(fetch)).read(streamUrl, { program: channel?.program });
|
||||
} catch (error) {
|
||||
if (error.name === 'AbortError') return;
|
||||
console.warn('[Player] Remux probe failed:', error.message);
|
||||
}
|
||||
}
|
||||
@ -1115,6 +1113,7 @@ class VideoPlayer {
|
||||
* Stop playback
|
||||
*/
|
||||
stop() {
|
||||
this.playbackProbe?.cancel();
|
||||
this.zapController?.abort();
|
||||
this.zapController = null;
|
||||
this.playbackGeneration = (this.playbackGeneration || 0) + 1;
|
||||
|
||||
@ -421,7 +421,6 @@ class WatchPage {
|
||||
this.setSourceDuration(VodDuration.firstValid(VodDuration.fromContent(this.content || {}), knownDuration));
|
||||
const playbackGeneration = this.playbackGeneration;
|
||||
const program = this.content?.program ?? this.content?.data?.program ?? null;
|
||||
const programQuery = program == null ? '' : '&program=' + encodeURIComponent(program);
|
||||
let probeInfo = null;
|
||||
|
||||
// Show loading spinner
|
||||
@ -446,8 +445,7 @@ class WatchPage {
|
||||
console.log('[WatchPage] Auto Transcode enabled. Probing stream...');
|
||||
try {
|
||||
const ua = settings.userAgentPreset === 'custom' ? settings.userAgentCustom : settings.userAgentPreset;
|
||||
const probeRes = await fetch(`/api/probe?url=${encodeURIComponent(url)}&ua=${encodeURIComponent(ua || '')}${programQuery}`);
|
||||
const info = await probeRes.json();
|
||||
const info = await (this.playbackProbe ||= new PlaybackProbe(fetch)).read(url, { program, userAgent: ua || '' });
|
||||
if (this.playbackGeneration !== playbackGeneration) return;
|
||||
probeInfo = info;
|
||||
this.setSourceDuration(info.duration);
|
||||
@ -497,6 +495,7 @@ class WatchPage {
|
||||
// Compatible - fall through to normal playback
|
||||
console.log('[WatchPage] Auto: Using normal playback (compatible)');
|
||||
} catch (err) {
|
||||
if (err.name === 'AbortError') return;
|
||||
console.warn('[WatchPage] Probe failed, using normal playback:', err.message);
|
||||
// Continue with normal playback on probe failure
|
||||
}
|
||||
@ -529,11 +528,10 @@ class WatchPage {
|
||||
let videoCodec = 'unknown';
|
||||
try {
|
||||
const ua = settings.userAgentPreset === 'custom' ? settings.userAgentCustom : settings.userAgentPreset;
|
||||
const probeRes = await fetch(`/api/probe?url=${encodeURIComponent(url)}&ua=${encodeURIComponent(ua || '')}${programQuery}`);
|
||||
const info = await probeRes.json();
|
||||
const info = await (this.playbackProbe ||= new PlaybackProbe(fetch)).read(url, { program, userAgent: ua || '' });
|
||||
if (this.playbackGeneration !== playbackGeneration) return;
|
||||
videoCodec = info.video;
|
||||
} catch (e) { console.warn('Probe failed for force audio, assuming h264'); }
|
||||
} catch (e) { if (e.name === 'AbortError') return; console.warn('Probe failed for force audio, assuming h264'); }
|
||||
|
||||
if (this.playbackGeneration !== playbackGeneration) return;
|
||||
const playlistUrl = await this.startTranscodeSession(url, {
|
||||
@ -555,9 +553,9 @@ class WatchPage {
|
||||
if (!probeInfo) {
|
||||
try {
|
||||
const ua = settings.userAgentPreset === 'custom' ? settings.userAgentCustom : settings.userAgentPreset;
|
||||
const response = await fetch(`/api/probe?url=${encodeURIComponent(url)}&ua=${encodeURIComponent(ua || '')}${programQuery}`);
|
||||
probeInfo = await response.json();
|
||||
probeInfo = await (this.playbackProbe ||= new PlaybackProbe(fetch)).read(url, { program, userAgent: ua || '' });
|
||||
} catch (error) {
|
||||
if (error.name === 'AbortError') return;
|
||||
console.warn('[WatchPage] Remux probe failed:', error.message);
|
||||
}
|
||||
}
|
||||
@ -658,6 +656,7 @@ class WatchPage {
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.playbackProbe?.cancel();
|
||||
this.playbackGeneration = (this.playbackGeneration || 0) + 1;
|
||||
this.audioRecovery?.dispose();
|
||||
this.audioRecovery = null;
|
||||
|
||||
Reference in New Issue
Block a user