Default UI refresh cadence to mainline (every progress tick).

Use 0 ms focused/background intervals by default and when delay <= 0 emit
updated on every socket event like stock MeTube. Remove auditTime(200) so
defaults match upstream. Gear modal allows 0 and explains opt-in coalescing.

Made-with: Cursor
This commit is contained in:
KennyG 2026-04-03 09:58:27 -04:00
parent d278c653da
commit c324d544d5
3 changed files with 29 additions and 29 deletions

View file

@ -645,18 +645,22 @@
<button type="button" class="btn-close" aria-label="Close" (click)="closeSettingsModal()"></button>
</div>
<div class="modal-body">
<p class="small text-muted mb-3">
<strong>0 ms</strong> matches stock MeTube: the queue repaints on every download progress event.
Set higher values to coalesce updates and reduce CPU (helpful for large queues or slow devices).
</p>
<div class="row g-3">
<div class="col-12">
<div class="input-group">
<span class="input-group-text">Focused Refresh (ms)</span>
<input type="number" min="500" max="10000" class="form-control" [(ngModel)]="focusedRefreshMs"
<input type="number" min="0" max="10000" class="form-control" [(ngModel)]="focusedRefreshMs"
[ngModelOptions]="{standalone: true}">
</div>
</div>
<div class="col-12">
<div class="input-group">
<span class="input-group-text">Background Refresh (ms)</span>
<input type="number" min="2000" max="120000" class="form-control" [(ngModel)]="backgroundRefreshMs"
<input type="number" min="0" max="120000" class="form-control" [(ngModel)]="backgroundRefreshMs"
[ngModelOptions]="{standalone: true}">
</div>
</div>

View file

@ -1,7 +1,7 @@
import { AsyncPipe, DatePipe, KeyValuePipe, NgTemplateOutlet } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, ElementRef, viewChild, inject, OnDestroy, OnInit } from '@angular/core';
import { Observable, Subscription, map, distinctUntilChanged, finalize, auditTime } from 'rxjs';
import { Observable, Subscription, map, distinctUntilChanged, finalize } from 'rxjs';
import { FormsModule } from '@angular/forms';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
@ -120,8 +120,9 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
webhookTestResponsePretty = '';
webhookTestResponseRaw = '';
settingsModalOpen = false;
focusedRefreshMs = 3000;
backgroundRefreshMs = 30000;
/** 0 = stock mainline: refresh on every progress event. */
focusedRefreshMs = 0;
backgroundRefreshMs = 0;
sortAscending = false;
expandedErrors: Set<string> = new Set<string>();
cachedSortedDone: [string, Download][] = [];
@ -278,11 +279,11 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
}
const focusedCookie = parseInt(this.cookieService.get(this.focusedRefreshCookie) || '', 10);
if (!Number.isNaN(focusedCookie)) {
this.focusedRefreshMs = this.clampRefreshMs(focusedCookie, 500, 10000);
this.focusedRefreshMs = this.clampRefreshMs(focusedCookie, 0, 10000);
}
const backgroundCookie = parseInt(this.cookieService.get(this.backgroundRefreshCookie) || '', 10);
if (!Number.isNaN(backgroundCookie)) {
this.backgroundRefreshMs = this.clampRefreshMs(backgroundCookie, 2000, 120000);
this.backgroundRefreshMs = this.clampRefreshMs(backgroundCookie, 0, 120000);
}
this.downloads.setRefreshCadence(this.focusedRefreshMs, this.backgroundRefreshMs);
this.activeTheme = this.getPreferredTheme(this.cookieService);
@ -297,13 +298,7 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
this.rebuildSortedDone();
this.cdr.markForCheck();
});
// Subscribe to real-time updates (throttled to reduce CPU on large queues).
this.downloads.updated
.pipe(
auditTime(200),
takeUntilDestroyed(this.destroyRef)
)
.subscribe(() => {
this.downloads.updated.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
this.updateMetrics();
this.cdr.markForCheck();
});
@ -471,8 +466,8 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
}
saveRefreshSettings(): void {
this.focusedRefreshMs = this.clampRefreshMs(this.focusedRefreshMs, 500, 10000);
this.backgroundRefreshMs = this.clampRefreshMs(this.backgroundRefreshMs, 2000, 120000);
this.focusedRefreshMs = this.clampRefreshMs(this.focusedRefreshMs, 0, 10000);
this.backgroundRefreshMs = this.clampRefreshMs(this.backgroundRefreshMs, 0, 120000);
this.downloads.setRefreshCadence(this.focusedRefreshMs, this.backgroundRefreshMs);
this.cookieService.set(this.focusedRefreshCookie, String(this.focusedRefreshMs), { expires: this.settingsCookieExpiryDays });
this.cookieService.set(this.backgroundRefreshCookie, String(this.backgroundRefreshMs), { expires: this.settingsCookieExpiryDays });

View file

@ -52,8 +52,10 @@ export class DownloadsService {
configurationChanged = new Subject<Record<string, unknown>>();
updated = new Subject<void>();
private updateRefreshScheduled = false;
private foregroundRefreshMs = 3000;
private backgroundRefreshMs = 30000;
/** 0 = emit on every socket progress event (stock mainline behavior). */
private foregroundRefreshMs = 0;
/** 0 = emit on every socket progress event when the tab is in the background. */
private backgroundRefreshMs = 0;
configuration: Record<string, unknown> = {};
customDirs: Record<string, string[]> = {};
@ -136,25 +138,24 @@ export class DownloadsService {
}
private scheduleUpdatedRefresh() {
// Coalesce high-frequency download progress events into a capped refresh rate.
// requestAnimationFrame (~60fps) is smooth but expensive remotely and on large queues.
// We trade a little smoothness for much lower CPU:
// - foreground: 1 per 3 seconds
// - background: 1 per 30 seconds
const delay = document.hidden
? this.backgroundRefreshMs
: this.foregroundRefreshMs;
// Match stock mainline when interval is 0: notify on every socket `updated` event.
if (delay <= 0) {
this.updated.next();
return;
}
// Optional coalesce: merge bursty progress events into at most one UI refresh per interval.
if (this.updateRefreshScheduled) {
return;
}
this.updateRefreshScheduled = true;
const flush = () => {
this.updateRefreshScheduled = false;
this.updated.next();
};
const delay = document.hidden
? this.backgroundRefreshMs
: this.foregroundRefreshMs;
setTimeout(() => flush(), delay);
setTimeout(flush, delay);
}
setRefreshCadence(focusedMs: number, backgroundMs: number) {