Add eslint

This commit is contained in:
2026-03-03 22:56:12 +01:00
parent d641229f9d
commit ad0e28bf84
92 changed files with 2656 additions and 1127 deletions

View File

@@ -19,13 +19,12 @@
import { Injectable, signal, computed, inject, OnDestroy, Signal } from '@angular/core';
import { Subscription } from 'rxjs';
import { WebRTCService } from './webrtc.service';
/* eslint-disable @typescript-eslint/member-ordering, @typescript-eslint/prefer-for-of, id-length, max-statements-per-line */
/** RMS volume threshold (01) above which a user counts as "speaking". */
const SPEAKING_THRESHOLD = 0.015;
/** How many consecutive silent frames before we flip speaking → false. */
const SILENT_FRAME_GRACE = 8;
/** FFT size for the AnalyserNode (smaller = cheaper). */
const FFT_SIZE = 256;
@@ -73,13 +72,13 @@ export class VoiceActivityService implements OnDestroy {
this.subs.push(
this.webrtc.onRemoteStream.subscribe(({ peerId, stream }) => {
this.trackStream(peerId, stream);
}),
})
);
this.subs.push(
this.webrtc.onPeerDisconnected.subscribe((peerId) => {
this.untrackStream(peerId);
}),
})
);
}
@@ -114,7 +113,9 @@ export class VoiceActivityService implements OnDestroy {
*/
isSpeaking(userId: string): Signal<boolean> {
const entry = this.tracked.get(userId);
if (entry) return entry.speakingSignal.asReadonly();
if (entry)
return entry.speakingSignal.asReadonly();
// Return a computed that re-checks the map so it becomes live
// once the stream is tracked.
@@ -127,7 +128,10 @@ export class VoiceActivityService implements OnDestroy {
*/
volume(userId: string): Signal<number> {
const entry = this.tracked.get(userId);
if (entry) return entry.volumeSignal.asReadonly();
if (entry)
return entry.volumeSignal.asReadonly();
return computed(() => 0);
}
@@ -141,14 +145,18 @@ export class VoiceActivityService implements OnDestroy {
trackStream(id: string, stream: MediaStream): void {
// If we already track this exact stream, skip.
const existing = this.tracked.get(id);
if (existing && existing.stream === stream) return;
if (existing && existing.stream === stream)
return;
// Clean up any previous entry for this id.
if (existing) this.disposeEntry(existing);
if (existing)
this.disposeEntry(existing);
const ctx = new AudioContext();
const source = ctx.createMediaStreamSource(stream);
const analyser = ctx.createAnalyser();
analyser.fftSize = FFT_SIZE;
source.connect(analyser);
@@ -167,7 +175,7 @@ export class VoiceActivityService implements OnDestroy {
volumeSignal,
speakingSignal,
silentFrames: 0,
stream,
stream
});
// Ensure the poll loop is running.
@@ -177,19 +185,25 @@ export class VoiceActivityService implements OnDestroy {
/** Stop tracking and dispose resources for a given ID. */
untrackStream(id: string): void {
const entry = this.tracked.get(id);
if (!entry) return;
if (!entry)
return;
this.disposeEntry(entry);
this.tracked.delete(id);
this.publishSpeakingMap();
// Stop polling when nothing is tracked.
if (this.tracked.size === 0) this.stopPolling();
if (this.tracked.size === 0)
this.stopPolling();
}
// ── Polling loop ────────────────────────────────────────────────
private ensurePolling(): void {
if (this.animFrameId !== null) return;
if (this.animFrameId !== null)
return;
this.poll();
}
@@ -214,23 +228,29 @@ export class VoiceActivityService implements OnDestroy {
// Compute RMS volume from time-domain data (values 0255, centred at 128).
let sumSquares = 0;
for (let i = 0; i < dataArray.length; i++) {
const normalised = (dataArray[i] - 128) / 128;
sumSquares += normalised * normalised;
}
const rms = Math.sqrt(sumSquares / dataArray.length);
volumeSignal.set(rms);
const wasSpeaking = speakingSignal();
if (rms >= SPEAKING_THRESHOLD) {
entry.silentFrames = 0;
if (!wasSpeaking) {
speakingSignal.set(true);
mapDirty = true;
}
} else {
entry.silentFrames++;
if (wasSpeaking && entry.silentFrames >= SILENT_FRAME_GRACE) {
speakingSignal.set(false);
mapDirty = true;
@@ -238,7 +258,8 @@ export class VoiceActivityService implements OnDestroy {
}
});
if (mapDirty) this.publishSpeakingMap();
if (mapDirty)
this.publishSpeakingMap();
this.animFrameId = requestAnimationFrame(this.poll);
};
@@ -246,6 +267,7 @@ export class VoiceActivityService implements OnDestroy {
/** Rebuild the public speaking-map signal from current entries. */
private publishSpeakingMap(): void {
const map = new Map<string, boolean>();
this.tracked.forEach((entry, id) => {
map.set(id, entry.speakingSignal());
});
@@ -256,6 +278,7 @@ export class VoiceActivityService implements OnDestroy {
private disposeEntry(entry: TrackedStream): void {
try { entry.source.disconnect(); } catch { /* already disconnected */ }
try { entry.ctx.close(); } catch { /* already closed */ }
}