feat: backup/restore, user profile popover, image compress, video blur, wake lock

- backup/restore dialog + user profile popover components
- image compression, video blur, wake lock utilities
- message cache + conversation messages hook refinements
- call context, active speakers, screen share dialog tweaks
- audio + screen share settings persistence
- refreshed app icons (smaller sizes) across all platforms
This commit is contained in:
2026-04-21 12:11:09 +02:00
parent 48ac9d2922
commit 1303c8e26f
71 changed files with 1077 additions and 114 deletions
+28
View File
@@ -16,12 +16,27 @@ export interface AudioSettings {
// Preferred output (speaker/headphone) deviceId. null = system default.
// Applied to every <audio> element we attach via HTMLMediaElement.setSinkId.
outputDeviceId: string | null;
// RMS threshold (0..1) for the "is this participant talking" ring. Lower =
// more sensitive. Default 0.03 catches soft speech without lighting up on
// keyboard noise. Users on quiet mics can lower; users in noisy rooms bump up.
voiceThreshold: number;
// Per-publish DSP toggle. Off lets hifi-style music go through unmodified;
// on cleans up voice when the quality preset doesn't already imply it.
// Defaults to "follow the quality preset".
noiseSuppression: boolean;
// Background blur on the local camera track. Lazy — requires
// @livekit/track-processors + its MediaPipe selfie-segmentation model
// (~1.5MB) which downloads on first activation.
videoBackgroundBlur: boolean;
}
const DEFAULTS: AudioSettings = {
quality: 'voice',
inputDeviceId: null,
outputDeviceId: null,
voiceThreshold: 0.03,
noiseSuppression: true,
videoBackgroundBlur: false,
};
export interface AudioQualityParams {
@@ -80,6 +95,7 @@ function read(): AudioSettings {
return cached;
}
const parsed = JSON.parse(raw) as Partial<AudioSettings>;
const rawThreshold = typeof parsed.voiceThreshold === 'number' ? parsed.voiceThreshold : NaN;
cached = {
quality: isQuality(parsed.quality) ? parsed.quality : DEFAULTS.quality,
inputDeviceId:
@@ -90,6 +106,18 @@ function read(): AudioSettings {
typeof parsed.outputDeviceId === 'string' && parsed.outputDeviceId.length > 0
? parsed.outputDeviceId
: DEFAULTS.outputDeviceId,
voiceThreshold:
Number.isFinite(rawThreshold) && rawThreshold >= 0.005 && rawThreshold <= 0.2
? rawThreshold
: DEFAULTS.voiceThreshold,
noiseSuppression:
typeof parsed.noiseSuppression === 'boolean'
? parsed.noiseSuppression
: DEFAULTS.noiseSuppression,
videoBackgroundBlur:
typeof parsed.videoBackgroundBlur === 'boolean'
? parsed.videoBackgroundBlur
: DEFAULTS.videoBackgroundBlur,
};
return cached;
} catch {