Retire cancelled playback sessions and preserve VOD content time
Adapted from MikaelKW/nodecast-tv-plus playback lease work beginning at 43a58c7685f541498663b827c4b9d6b3753e7202. Reject superseded requests, release on cancellation, bound live restart attempts and await FFmpeg close before deleting cache. Co-authored-by: Mikael <mikaelkwessel@gmail.com>
This commit is contained in:
52
public/js/components/PlaybackSessions.js
Normal file
52
public/js/components/PlaybackSessions.js
Normal file
@ -0,0 +1,52 @@
|
||||
(function (root) {
|
||||
const cancelled = () => Object.assign(new Error('Playback was superseded'), { name: 'AbortError' });
|
||||
class PlaybackSessions {
|
||||
constructor(isLive) {
|
||||
this.isLive = isLive;
|
||||
this.leaseId = null;
|
||||
this.sequence = 0;
|
||||
this.controller = null;
|
||||
this.timer = null;
|
||||
root.addEventListener?.('pagehide', () => this.stop());
|
||||
}
|
||||
async create(url, options) {
|
||||
this.leaseId ||= root.crypto.randomUUID?.() || Array.from(root.crypto.getRandomValues(new Uint8Array(16)),
|
||||
byte => byte.toString(16).padStart(2, '0')).join('');
|
||||
const leaseId = this.leaseId, sequence = ++this.sequence;
|
||||
this.controller?.abort();
|
||||
const controller = this.controller = new AbortController();
|
||||
const response = await fetch('/api/transcode/session', {
|
||||
method: 'POST', credentials: 'same-origin', signal: controller.signal,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ...options, url, isLive: this.isLive, playbackLeaseId: leaseId, requestSequence: sequence })
|
||||
});
|
||||
const session = await response.json();
|
||||
if (controller.signal.aborted || leaseId !== this.leaseId || sequence !== this.sequence) {
|
||||
if (session.sessionId) fetch(`/api/transcode/${session.sessionId}`, { method: 'DELETE', keepalive: true }).catch(() => {});
|
||||
throw cancelled();
|
||||
}
|
||||
if (!response.ok) throw new Error(session.error || 'Unable to start playback');
|
||||
this.timer ||= setInterval(() => {
|
||||
if (this.leaseId) fetch('/api/transcode/lease/heartbeat', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ playbackLeaseId: this.leaseId })
|
||||
}).catch(() => {});
|
||||
}, 30000);
|
||||
return session;
|
||||
}
|
||||
stop() {
|
||||
const leaseId = this.leaseId;
|
||||
this.leaseId = null;
|
||||
this.sequence++;
|
||||
this.controller?.abort();
|
||||
this.controller = null;
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
if (leaseId) fetch('/api/transcode/lease', {
|
||||
method: 'DELETE', keepalive: true, headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ playbackLeaseId: leaseId })
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
root.PlaybackSessions = PlaybackSessions;
|
||||
}(typeof window === 'object' ? window : globalThis));
|
||||
Reference in New Issue
Block a user