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> <button type="button" class="btn-close" aria-label="Close" (click)="closeSettingsModal()"></button>
</div> </div>
<div class="modal-body"> <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="row g-3">
<div class="col-12"> <div class="col-12">
<div class="input-group"> <div class="input-group">
<span class="input-group-text">Focused Refresh (ms)</span> <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}"> [ngModelOptions]="{standalone: true}">
</div> </div>
</div> </div>
<div class="col-12"> <div class="col-12">
<div class="input-group"> <div class="input-group">
<span class="input-group-text">Background Refresh (ms)</span> <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}"> [ngModelOptions]="{standalone: true}">
</div> </div>
</div> </div>

View file

@ -1,7 +1,7 @@
import { AsyncPipe, DatePipe, KeyValuePipe, NgTemplateOutlet } from '@angular/common'; import { AsyncPipe, DatePipe, KeyValuePipe, NgTemplateOutlet } from '@angular/common';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, ElementRef, viewChild, inject, OnDestroy, OnInit } from '@angular/core'; 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 { FormsModule } from '@angular/forms';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
@ -120,8 +120,9 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
webhookTestResponsePretty = ''; webhookTestResponsePretty = '';
webhookTestResponseRaw = ''; webhookTestResponseRaw = '';
settingsModalOpen = false; settingsModalOpen = false;
focusedRefreshMs = 3000; /** 0 = stock mainline: refresh on every progress event. */
backgroundRefreshMs = 30000; focusedRefreshMs = 0;
backgroundRefreshMs = 0;
sortAscending = false; sortAscending = false;
expandedErrors: Set<string> = new Set<string>(); expandedErrors: Set<string> = new Set<string>();
cachedSortedDone: [string, Download][] = []; cachedSortedDone: [string, Download][] = [];
@ -278,11 +279,11 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
} }
const focusedCookie = parseInt(this.cookieService.get(this.focusedRefreshCookie) || '', 10); const focusedCookie = parseInt(this.cookieService.get(this.focusedRefreshCookie) || '', 10);
if (!Number.isNaN(focusedCookie)) { 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); const backgroundCookie = parseInt(this.cookieService.get(this.backgroundRefreshCookie) || '', 10);
if (!Number.isNaN(backgroundCookie)) { 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.downloads.setRefreshCadence(this.focusedRefreshMs, this.backgroundRefreshMs);
this.activeTheme = this.getPreferredTheme(this.cookieService); this.activeTheme = this.getPreferredTheme(this.cookieService);
@ -297,13 +298,7 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
this.rebuildSortedDone(); this.rebuildSortedDone();
this.cdr.markForCheck(); this.cdr.markForCheck();
}); });
// Subscribe to real-time updates (throttled to reduce CPU on large queues). this.downloads.updated.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
this.downloads.updated
.pipe(
auditTime(200),
takeUntilDestroyed(this.destroyRef)
)
.subscribe(() => {
this.updateMetrics(); this.updateMetrics();
this.cdr.markForCheck(); this.cdr.markForCheck();
}); });
@ -471,8 +466,8 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
} }
saveRefreshSettings(): void { saveRefreshSettings(): void {
this.focusedRefreshMs = this.clampRefreshMs(this.focusedRefreshMs, 500, 10000); this.focusedRefreshMs = this.clampRefreshMs(this.focusedRefreshMs, 0, 10000);
this.backgroundRefreshMs = this.clampRefreshMs(this.backgroundRefreshMs, 2000, 120000); this.backgroundRefreshMs = this.clampRefreshMs(this.backgroundRefreshMs, 0, 120000);
this.downloads.setRefreshCadence(this.focusedRefreshMs, this.backgroundRefreshMs); this.downloads.setRefreshCadence(this.focusedRefreshMs, this.backgroundRefreshMs);
this.cookieService.set(this.focusedRefreshCookie, String(this.focusedRefreshMs), { expires: this.settingsCookieExpiryDays }); this.cookieService.set(this.focusedRefreshCookie, String(this.focusedRefreshMs), { expires: this.settingsCookieExpiryDays });
this.cookieService.set(this.backgroundRefreshCookie, String(this.backgroundRefreshMs), { 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>>(); configurationChanged = new Subject<Record<string, unknown>>();
updated = new Subject<void>(); updated = new Subject<void>();
private updateRefreshScheduled = false; private updateRefreshScheduled = false;
private foregroundRefreshMs = 3000; /** 0 = emit on every socket progress event (stock mainline behavior). */
private backgroundRefreshMs = 30000; private foregroundRefreshMs = 0;
/** 0 = emit on every socket progress event when the tab is in the background. */
private backgroundRefreshMs = 0;
configuration: Record<string, unknown> = {}; configuration: Record<string, unknown> = {};
customDirs: Record<string, string[]> = {}; customDirs: Record<string, string[]> = {};
@ -136,25 +138,24 @@ export class DownloadsService {
} }
private scheduleUpdatedRefresh() { private scheduleUpdatedRefresh() {
// Coalesce high-frequency download progress events into a capped refresh rate. const delay = document.hidden
// requestAnimationFrame (~60fps) is smooth but expensive remotely and on large queues. ? this.backgroundRefreshMs
// We trade a little smoothness for much lower CPU: : this.foregroundRefreshMs;
// - foreground: 1 per 3 seconds // Match stock mainline when interval is 0: notify on every socket `updated` event.
// - background: 1 per 30 seconds if (delay <= 0) {
this.updated.next();
return;
}
// Optional coalesce: merge bursty progress events into at most one UI refresh per interval.
if (this.updateRefreshScheduled) { if (this.updateRefreshScheduled) {
return; return;
} }
this.updateRefreshScheduled = true; this.updateRefreshScheduled = true;
const flush = () => { const flush = () => {
this.updateRefreshScheduled = false; this.updateRefreshScheduled = false;
this.updated.next(); this.updated.next();
}; };
setTimeout(flush, delay);
const delay = document.hidden
? this.backgroundRefreshMs
: this.foregroundRefreshMs;
setTimeout(() => flush(), delay);
} }
setRefreshCadence(focusedMs: number, backgroundMs: number) { setRefreshCadence(focusedMs: number, backgroundMs: number) {