support live streams (closes #302, closes #752, closes #978)

This commit is contained in:
Alex Shnitman
2026-06-13 17:39:14 +03:00
parent 72d60ea55a
commit 5429200fba
6 changed files with 524 additions and 14 deletions
+27 -1
View File
@@ -132,6 +132,7 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
lastCopiedErrorId: string | null = null;
private previousDownloadType = 'video';
private addRequestSub?: Subscription;
private liveCountdownTimer?: ReturnType<typeof setInterval>;
private selectionsByType: Record<string, {
codec: string;
format: string;
@@ -285,6 +286,7 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
// Subscribe to download updates
this.downloads.queueChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
this.updateMetrics();
this.syncLiveCountdownTimer();
this.cdr.markForCheck();
});
this.downloads.doneChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
@@ -295,6 +297,7 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
// Subscribe to real-time updates
this.downloads.updated.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
this.updateMetrics();
this.syncLiveCountdownTimer();
this.cdr.markForCheck();
});
@@ -337,6 +340,9 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
ngOnDestroy() {
this.addRequestSub?.unsubscribe();
if (this.liveCountdownTimer) {
clearInterval(this.liveCountdownTimer);
}
this.colorSchemeMediaQuery.removeEventListener('change', this.onColorSchemeChanged);
}
@@ -1106,6 +1112,26 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
this.downloads.startById([id]).subscribe();
}
liveCountdownSeconds(download: Download): number | null {
const ts = download.live_release_timestamp;
if (ts == null || download.status !== 'scheduled') {
return null;
}
return Math.max(0, ts - Date.now() / 1000);
}
private syncLiveCountdownTimer() {
const hasScheduled = Array.from(this.downloads.queue.values()).some(
(download) => download.status === 'scheduled',
);
if (hasScheduled && !this.liveCountdownTimer) {
this.liveCountdownTimer = setInterval(() => this.cdr.markForCheck(), 1000);
} else if (!hasScheduled && this.liveCountdownTimer) {
clearInterval(this.liveCountdownTimer);
this.liveCountdownTimer = undefined;
}
}
retryDownload(key: string, download: Download) {
this.addDownload({
url: download.url,
@@ -1631,7 +1657,7 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
speed += download.speed || 0;
} else if (download.status === 'preparing') {
active++;
} else if (download.status === 'pending') {
} else if (download.status === 'pending' || download.status === 'scheduled') {
queued++;
}
});