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:
@@ -30,6 +30,7 @@ import {
|
||||
import { useAuth } from './AuthContext';
|
||||
import { useConversationsContext } from './ConversationsContext';
|
||||
import { playEndBeep, playJoinBeep, playLeaveBeep } from '../lib/callSounds';
|
||||
import { setCallWakeLock } from '../lib/wakeLock';
|
||||
import { notify } from '../lib/osNotify';
|
||||
import {
|
||||
isTauriRuntime,
|
||||
@@ -40,8 +41,13 @@ import { getPttSettings, subscribePttSettings } from '../lib/pttSettings';
|
||||
import {
|
||||
getAudioQualityParams,
|
||||
getAudioSettings,
|
||||
subscribeAudioSettings,
|
||||
updateAudioSettings,
|
||||
} from '../lib/audioSettings';
|
||||
import {
|
||||
applyBackgroundBlurToLocal,
|
||||
removeBackgroundBlurFromLocal,
|
||||
} from '../lib/videoBlur';
|
||||
import {
|
||||
createCallE2EE,
|
||||
getCallE2EESettings,
|
||||
@@ -492,14 +498,14 @@ export function CallProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
r.on(RoomEvent.ParticipantConnected, () => {
|
||||
setRemoteParticipants(Array.from(r.remoteParticipants.values()));
|
||||
void playJoinBeep();
|
||||
if (presenceRef.current !== 'dnd') void playJoinBeep();
|
||||
markConnectedIfReady(r, conversationId, mediaKind, callId);
|
||||
});
|
||||
|
||||
r.on(RoomEvent.ParticipantDisconnected, () => {
|
||||
const remaining = Array.from(r.remoteParticipants.values());
|
||||
setRemoteParticipants(remaining);
|
||||
void playLeaveBeep();
|
||||
if (presenceRef.current !== 'dnd') void playLeaveBeep();
|
||||
// Alone in the room while connected — start the solo-timeout.
|
||||
if (
|
||||
stateRef.current.kind === 'connected' &&
|
||||
@@ -627,7 +633,11 @@ export function CallProvider({ children }: { children: ReactNode }) {
|
||||
setIsE2EEActive(false);
|
||||
}
|
||||
try {
|
||||
const inputId = getAudioSettings().inputDeviceId;
|
||||
const audioPrefs = getAudioSettings();
|
||||
const inputId = audioPrefs.inputDeviceId;
|
||||
// Noise suppression: user-preference wins over the quality preset so
|
||||
// hifi-mode users can still enable NS when they need to cut room hum.
|
||||
const nsEffective = audioPrefs.noiseSuppression && aParams.noiseSuppression;
|
||||
// Grab the raw mic ourselves instead of going through LiveKit's
|
||||
// setMicrophoneEnabled. The resulting MediaStreamTrack is routed
|
||||
// through createMicPipeline, which mixes in soundboard buffers and
|
||||
@@ -636,7 +646,7 @@ export function CallProvider({ children }: { children: ReactNode }) {
|
||||
const rawStream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
echoCancellation: aParams.echoCancellation,
|
||||
noiseSuppression: aParams.noiseSuppression,
|
||||
noiseSuppression: nsEffective,
|
||||
autoGainControl: aParams.autoGainControl,
|
||||
channelCount: aParams.stereo ? 2 : 1,
|
||||
sampleRate: aParams.sampleRateHz,
|
||||
@@ -947,7 +957,10 @@ export function CallProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
try {
|
||||
await lp.setScreenShareEnabled(true, {
|
||||
audio: false,
|
||||
// "Go live" mode — capture system audio alongside the screen when
|
||||
// the user opted in. On hosts that can't fulfil the request the
|
||||
// browser quietly drops it; peers just get video-only, no error.
|
||||
audio: settings.includeSystemAudio,
|
||||
...(ssParams.dims
|
||||
? {
|
||||
resolution: {
|
||||
@@ -1033,6 +1046,9 @@ export function CallProvider({ children }: { children: ReactNode }) {
|
||||
try {
|
||||
await lp.setCameraEnabled(nextOn);
|
||||
setIsCameraEnabled(nextOn);
|
||||
if (nextOn && getAudioSettings().videoBackgroundBlur) {
|
||||
void applyBackgroundBlurToLocal(lp);
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
console.error('setCameraEnabled failed', err);
|
||||
// Permission denied / no camera — keep state in sync with actual
|
||||
@@ -1041,6 +1057,23 @@ export function CallProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Live-toggle the background-blur processor when the settings flag flips.
|
||||
// Acquiring the MediaPipe model is deferred until first activation to
|
||||
// avoid the 1.5MB download on users who never enable blur.
|
||||
useEffect(() => {
|
||||
return subscribeAudioSettings((s) => {
|
||||
const r = roomRef.current;
|
||||
if (!r) return;
|
||||
const lp = r.localParticipant;
|
||||
if (!lp.isCameraEnabled) return;
|
||||
if (s.videoBackgroundBlur) {
|
||||
void applyBackgroundBlurToLocal(lp);
|
||||
} else {
|
||||
void removeBackgroundBlurFromLocal(lp);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
// --- Push-to-talk ------------------------------------------------------
|
||||
// While PTT is active + we're in a connected call, the mic is held off
|
||||
// except while the configured key is pressed. Under Tauri we also register
|
||||
@@ -1337,11 +1370,13 @@ export function CallProvider({ children }: { children: ReactNode }) {
|
||||
// to the pipeline. It disconnects the old source, stops the old track,
|
||||
// and rewires micGain onto the new source — the published track stays
|
||||
// stable so peers don't see a republish.
|
||||
const aParams = getAudioQualityParams(getAudioSettings().quality);
|
||||
const audioPrefs = getAudioSettings();
|
||||
const aParams = getAudioQualityParams(audioPrefs.quality);
|
||||
const nsEffective = audioPrefs.noiseSuppression && aParams.noiseSuppression;
|
||||
const newStream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
echoCancellation: aParams.echoCancellation,
|
||||
noiseSuppression: aParams.noiseSuppression,
|
||||
noiseSuppression: nsEffective,
|
||||
autoGainControl: aParams.autoGainControl,
|
||||
channelCount: aParams.stereo ? 2 : 1,
|
||||
sampleRate: aParams.sampleRateHz,
|
||||
@@ -1393,6 +1428,17 @@ export function CallProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
}, [state.kind]);
|
||||
|
||||
// Hold the screen awake while a call is live so long sessions don't get
|
||||
// dropped by display-sleep / OS power-save. Released the moment the call
|
||||
// ends or errors out.
|
||||
useEffect(() => {
|
||||
const callActive =
|
||||
state.kind === 'connected' ||
|
||||
state.kind === 'connecting' ||
|
||||
state.kind === 'outgoing';
|
||||
void setCallWakeLock(callActive);
|
||||
}, [state.kind]);
|
||||
|
||||
// Global Esc: drop out of fullscreen cinema back to grid while in an active
|
||||
// call. Doesn't hangup and doesn't fire when other dialogs would consume it.
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user