mirror of https://github.com/jetkvm/kvm.git
[WIP] Improvements: Improve audio resume mechanism after Hardware Settings deactivation & reactivation
This commit is contained in:
parent
a84f63c0c4
commit
f2ad918dfd
|
@ -217,44 +217,17 @@ export default function AudioControlPopover({ microphone }: AudioControlPopoverP
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use RPC for device communication - works for both local and cloud
|
|
||||||
if (rpcDataChannel?.readyState !== "open") {
|
|
||||||
throw new Error("Device connection not available");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isMicrophoneActiveFromHook) {
|
if (isMicrophoneActiveFromHook) {
|
||||||
// Disable: Stop microphone subprocess via RPC AND remove WebRTC tracks locally
|
// Disable: Use the hook's stopMicrophone which handles both RPC and local cleanup
|
||||||
await new Promise<void>((resolve, reject) => {
|
|
||||||
send("microphoneStop", {}, (resp: JsonRpcResponse) => {
|
|
||||||
if ("error" in resp) {
|
|
||||||
reject(new Error(resp.error.message));
|
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Also stop local WebRTC stream
|
|
||||||
const result = await stopMicrophone();
|
const result = await stopMicrophone();
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
console.warn("Local microphone stop failed:", result.error?.message);
|
throw new Error(result.error?.message || "Failed to stop microphone");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Enable: Start microphone subprocess via RPC AND add WebRTC tracks locally
|
// Enable: Use the hook's startMicrophone which handles both RPC and local setup
|
||||||
await new Promise<void>((resolve, reject) => {
|
|
||||||
send("microphoneStart", {}, (resp: JsonRpcResponse) => {
|
|
||||||
if ("error" in resp) {
|
|
||||||
reject(new Error(resp.error.message));
|
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Also start local WebRTC stream
|
|
||||||
const result = await startMicrophone();
|
const result = await startMicrophone();
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error?.message || "Failed to start local microphone");
|
throw new Error(result.error?.message || "Failed to start microphone");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -2,6 +2,8 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
import { useRTCStore, useSettingsStore } from "@/hooks/stores";
|
import { useRTCStore, useSettingsStore } from "@/hooks/stores";
|
||||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
|
import { useUsbDeviceConfig } from "@/hooks/useUsbDeviceConfig";
|
||||||
|
import { useAudioEvents, AudioDeviceChangedData } from "@/hooks/useAudioEvents";
|
||||||
import { devLog, devInfo, devWarn, devError, devOnly } from "@/utils/debug";
|
import { devLog, devInfo, devWarn, devError, devOnly } from "@/utils/debug";
|
||||||
import { AUDIO_CONFIG } from "@/config/constants";
|
import { AUDIO_CONFIG } from "@/config/constants";
|
||||||
|
|
||||||
|
@ -27,6 +29,16 @@ export function useMicrophone() {
|
||||||
const { microphoneWasEnabled, setMicrophoneWasEnabled } = useSettingsStore();
|
const { microphoneWasEnabled, setMicrophoneWasEnabled } = useSettingsStore();
|
||||||
const { send } = useJsonRpc();
|
const { send } = useJsonRpc();
|
||||||
|
|
||||||
|
// Check USB audio status and handle microphone restoration when USB audio is re-enabled
|
||||||
|
const { usbDeviceConfig } = useUsbDeviceConfig();
|
||||||
|
const isUsbAudioEnabled = usbDeviceConfig?.audio ?? true;
|
||||||
|
|
||||||
|
// Track microphone state when USB audio gets disabled, so we can restore it when re-enabled
|
||||||
|
const [microphoneWasActiveBeforeUsbDisable, setMicrophoneWasActiveBeforeUsbDisable] = useState<boolean>(false);
|
||||||
|
|
||||||
|
// Track previous USB audio state to detect changes
|
||||||
|
const prevUsbAudioEnabled = useRef<boolean | null>(null);
|
||||||
|
|
||||||
// RPC helper functions to replace HTTP API calls
|
// RPC helper functions to replace HTTP API calls
|
||||||
const rpcMicrophoneStart = useCallback((): Promise<void> => {
|
const rpcMicrophoneStart = useCallback((): Promise<void> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -261,9 +273,19 @@ export function useMicrophone() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify backend that microphone is started
|
// Notify backend that microphone is started - only if USB audio is enabled
|
||||||
|
if (!isUsbAudioEnabled) {
|
||||||
|
devInfo("USB audio is disabled, skipping backend microphone start");
|
||||||
|
// Still set frontend state as active since the stream was successfully created
|
||||||
|
setMicrophoneActive(true);
|
||||||
|
setMicrophoneMuted(false);
|
||||||
|
setMicrophoneWasEnabled(true);
|
||||||
|
isStartingRef.current = false;
|
||||||
|
setIsStarting(false);
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
|
||||||
// Retry logic for backend failures
|
// Retry logic for backend failures
|
||||||
let backendSuccess = false;
|
let backendSuccess = false;
|
||||||
let lastError: Error | string | null = null;
|
let lastError: Error | string | null = null;
|
||||||
|
|
||||||
|
@ -372,7 +394,7 @@ export function useMicrophone() {
|
||||||
setIsStarting(false);
|
setIsStarting(false);
|
||||||
return { success: false, error: micError };
|
return { success: false, error: micError };
|
||||||
}
|
}
|
||||||
}, [peerConnection, setMicrophoneStream, setMicrophoneSender, setMicrophoneActive, setMicrophoneMuted, setMicrophoneWasEnabled, stopMicrophoneStream, isStarting, isStopping, isToggling, rpcMicrophoneStart, rpcDataChannel?.readyState, send]);
|
}, [peerConnection, setMicrophoneStream, setMicrophoneSender, setMicrophoneActive, setMicrophoneMuted, setMicrophoneWasEnabled, stopMicrophoneStream, isStarting, isStopping, isToggling, rpcMicrophoneStart, rpcDataChannel?.readyState, send, isUsbAudioEnabled]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -575,6 +597,53 @@ export function useMicrophone() {
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [syncMicrophoneState, microphoneWasEnabled, isMicrophoneActive, peerConnection, startMicrophone, rpcDataChannel?.readyState]);
|
}, [syncMicrophoneState, microphoneWasEnabled, isMicrophoneActive, peerConnection, startMicrophone, rpcDataChannel?.readyState]);
|
||||||
|
|
||||||
|
// Handle USB audio enable/disable changes
|
||||||
|
useEffect(() => {
|
||||||
|
// If this is the first run, just store the current state
|
||||||
|
if (prevUsbAudioEnabled.current === null) {
|
||||||
|
prevUsbAudioEnabled.current = isUsbAudioEnabled;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// USB audio was just disabled
|
||||||
|
if (prevUsbAudioEnabled.current && !isUsbAudioEnabled) {
|
||||||
|
devInfo("USB audio disabled - storing microphone state");
|
||||||
|
setMicrophoneWasActiveBeforeUsbDisable(isMicrophoneActive);
|
||||||
|
// Clear the enabled flag to prevent auto-restore attempts
|
||||||
|
if (isMicrophoneActive) {
|
||||||
|
setMicrophoneWasEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// USB audio was just re-enabled
|
||||||
|
else if (!prevUsbAudioEnabled.current && isUsbAudioEnabled) {
|
||||||
|
devInfo("USB audio re-enabled - checking if microphone should be restored");
|
||||||
|
|
||||||
|
// If microphone was active before USB was disabled, restore it
|
||||||
|
if (microphoneWasActiveBeforeUsbDisable && !isMicrophoneActive && rpcDataChannel?.readyState === "open") {
|
||||||
|
devInfo("Restoring microphone after USB audio re-enabled");
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
const result = await startMicrophone();
|
||||||
|
if (result.success) {
|
||||||
|
devInfo("Microphone successfully restored after USB audio re-enable");
|
||||||
|
setMicrophoneWasEnabled(true);
|
||||||
|
} else {
|
||||||
|
devWarn("Failed to restore microphone after USB audio re-enable:", result.error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
devWarn("Error restoring microphone after USB audio re-enable:", error);
|
||||||
|
}
|
||||||
|
}, 500); // Small delay to ensure USB device reconfiguration is complete
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the stored state
|
||||||
|
setMicrophoneWasActiveBeforeUsbDisable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
prevUsbAudioEnabled.current = isUsbAudioEnabled;
|
||||||
|
}, [isUsbAudioEnabled, isMicrophoneActive, microphoneWasActiveBeforeUsbDisable, startMicrophone, setMicrophoneWasEnabled, rpcDataChannel?.readyState]);
|
||||||
|
|
||||||
// Cleanup on unmount - use ref to avoid dependency on stopMicrophoneStream
|
// Cleanup on unmount - use ref to avoid dependency on stopMicrophoneStream
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
|
Loading…
Reference in New Issue