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:
parent
d278c653da
commit
c324d544d5
3 changed files with 29 additions and 29 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 });
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue