mirror of https://github.com/jetkvm/kvm.git
refactor(audio): replace mute functionality with start/stop for microphone
- Replace MuteMicrophone calls with StartMicrophone/StopMicrophone for clearer behavior - Update microphone state broadcasting to reflect actual subprocess status - Modify UI to use enable/disable terminology instead of mute/unmute - Ensure microphone device changes properly restart the active microphone
This commit is contained in:
parent
7d39a2741e
commit
e3b4bb2002
|
@ -42,13 +42,13 @@ func StartAudioOutputAndAddTracks() error {
|
||||||
// StopMicrophoneAndRemoveTracks is a global helper to stop microphone subprocess and remove WebRTC tracks
|
// StopMicrophoneAndRemoveTracks is a global helper to stop microphone subprocess and remove WebRTC tracks
|
||||||
func StopMicrophoneAndRemoveTracks() error {
|
func StopMicrophoneAndRemoveTracks() error {
|
||||||
initAudioControlService()
|
initAudioControlService()
|
||||||
return audioControlService.MuteMicrophone(true)
|
return audioControlService.StopMicrophone()
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartMicrophoneAndAddTracks is a global helper to start microphone subprocess and add WebRTC tracks
|
// StartMicrophoneAndAddTracks is a global helper to start microphone subprocess and add WebRTC tracks
|
||||||
func StartMicrophoneAndAddTracks() error {
|
func StartMicrophoneAndAddTracks() error {
|
||||||
initAudioControlService()
|
initAudioControlService()
|
||||||
return audioControlService.MuteMicrophone(false)
|
return audioControlService.StartMicrophone()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAudioOutputActive is a global helper to check if audio output subprocess is running
|
// IsAudioOutputActive is a global helper to check if audio output subprocess is running
|
||||||
|
|
|
@ -95,6 +95,12 @@ func (s *AudioControlService) StartMicrophone() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
s.logger.Info().Msg("microphone started successfully")
|
s.logger.Info().Msg("microphone started successfully")
|
||||||
|
|
||||||
|
// Broadcast microphone state change via WebSocket
|
||||||
|
broadcaster := GetAudioEventBroadcaster()
|
||||||
|
sessionActive := s.sessionProvider.IsSessionActive()
|
||||||
|
broadcaster.BroadcastMicrophoneStateChanged(true, sessionActive)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,6 +122,12 @@ func (s *AudioControlService) StopMicrophone() error {
|
||||||
|
|
||||||
audioInputManager.Stop()
|
audioInputManager.Stop()
|
||||||
s.logger.Info().Msg("microphone stopped successfully")
|
s.logger.Info().Msg("microphone stopped successfully")
|
||||||
|
|
||||||
|
// Broadcast microphone state change via WebSocket
|
||||||
|
broadcaster := GetAudioEventBroadcaster()
|
||||||
|
sessionActive := s.sessionProvider.IsSessionActive()
|
||||||
|
broadcaster.BroadcastMicrophoneStateChanged(false, sessionActive)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,8 +165,17 @@ func (s *AudioControlService) MuteMicrophone(muted bool) error {
|
||||||
// Broadcast microphone state change via WebSocket
|
// Broadcast microphone state change via WebSocket
|
||||||
broadcaster := GetAudioEventBroadcaster()
|
broadcaster := GetAudioEventBroadcaster()
|
||||||
sessionActive := s.sessionProvider.IsSessionActive()
|
sessionActive := s.sessionProvider.IsSessionActive()
|
||||||
// With the new approach, "running" means "not muted"
|
|
||||||
broadcaster.BroadcastMicrophoneStateChanged(!muted, sessionActive)
|
// Get actual subprocess running status (not mute status)
|
||||||
|
var subprocessRunning bool
|
||||||
|
if sessionActive {
|
||||||
|
audioInputManager := s.sessionProvider.GetAudioInputManager()
|
||||||
|
if audioInputManager != nil {
|
||||||
|
subprocessRunning = audioInputManager.IsRunning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcaster.BroadcastMicrophoneStateChanged(subprocessRunning, sessionActive)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -267,13 +288,17 @@ func (s *AudioControlService) IsAudioOutputActive() bool {
|
||||||
return !IsAudioMuted() && IsAudioRelayRunning()
|
return !IsAudioMuted() && IsAudioRelayRunning()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsMicrophoneActive returns whether the microphone is active (not muted)
|
// IsMicrophoneActive returns whether the microphone subprocess is running
|
||||||
func (s *AudioControlService) IsMicrophoneActive() bool {
|
func (s *AudioControlService) IsMicrophoneActive() bool {
|
||||||
if !s.sessionProvider.IsSessionActive() {
|
if !s.sessionProvider.IsSessionActive() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// With the new unified approach, microphone "active" means "not muted"
|
audioInputManager := s.sessionProvider.GetAudioInputManager()
|
||||||
// This matches how audio output works - active means not muted
|
if audioInputManager == nil {
|
||||||
return !IsMicrophoneMuted()
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Enable/Disable buttons, we check subprocess status
|
||||||
|
return audioInputManager.IsRunning()
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ export default function AudioControlPopover({ microphone }: AudioControlPopoverP
|
||||||
// Use WebSocket-based audio events for real-time updates
|
// Use WebSocket-based audio events for real-time updates
|
||||||
const {
|
const {
|
||||||
audioMuted,
|
audioMuted,
|
||||||
microphoneState,
|
// microphoneState - now using hook state instead
|
||||||
isConnected: wsConnected
|
isConnected: wsConnected
|
||||||
} = useAudioEvents();
|
} = useAudioEvents();
|
||||||
|
|
||||||
|
@ -69,6 +69,7 @@ export default function AudioControlPopover({ microphone }: AudioControlPopoverP
|
||||||
|
|
||||||
// Microphone state from props (keeping hook for legacy device operations)
|
// Microphone state from props (keeping hook for legacy device operations)
|
||||||
const {
|
const {
|
||||||
|
isMicrophoneActive: isMicrophoneActiveFromHook,
|
||||||
startMicrophone,
|
startMicrophone,
|
||||||
stopMicrophone,
|
stopMicrophone,
|
||||||
syncMicrophoneState,
|
syncMicrophoneState,
|
||||||
|
@ -82,8 +83,8 @@ export default function AudioControlPopover({ microphone }: AudioControlPopoverP
|
||||||
const isMuted = audioMuted ?? false;
|
const isMuted = audioMuted ?? false;
|
||||||
const isConnected = wsConnected;
|
const isConnected = wsConnected;
|
||||||
|
|
||||||
// Use WebSocket microphone state instead of hook state for real-time updates
|
// Note: We now use hook state instead of WebSocket state for microphone Enable/Disable
|
||||||
const isMicrophoneActiveFromWS = microphoneState?.running ?? false;
|
// const isMicrophoneActiveFromWS = microphoneState?.running ?? false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -200,7 +201,7 @@ export default function AudioControlPopover({ microphone }: AudioControlPopoverP
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggleMicrophoneMute = async () => {
|
const handleToggleMicrophoneEnable = async () => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
// Prevent rapid clicking - if any operation is in progress or within cooldown, ignore the click
|
// Prevent rapid clicking - if any operation is in progress or within cooldown, ignore the click
|
||||||
|
@ -212,20 +213,18 @@ export default function AudioControlPopover({ microphone }: AudioControlPopoverP
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isMicrophoneActiveFromWS) {
|
if (isMicrophoneActiveFromHook) {
|
||||||
// Mute: Use unified microphone mute API (like audio output)
|
// Disable: Stop microphone subprocess AND remove WebRTC tracks
|
||||||
const resp = await api.POST("/microphone/mute", { muted: true });
|
const result = await stopMicrophone();
|
||||||
if (!resp.ok) {
|
if (!result.success) {
|
||||||
throw new Error(`Failed to mute microphone: ${resp.status}`);
|
throw new Error(result.error?.message || "Failed to stop microphone");
|
||||||
}
|
}
|
||||||
// WebSocket will handle the state update automatically
|
|
||||||
} else {
|
} else {
|
||||||
// Unmute: Use unified microphone mute API (like audio output)
|
// Enable: Start microphone subprocess AND add WebRTC tracks
|
||||||
const resp = await api.POST("/microphone/mute", { muted: false });
|
const result = await startMicrophone();
|
||||||
if (!resp.ok) {
|
if (!result.success) {
|
||||||
throw new Error(`Failed to unmute microphone: ${resp.status}`);
|
throw new Error(result.error?.message || "Failed to start microphone");
|
||||||
}
|
}
|
||||||
// WebSocket will handle the state update automatically
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : "Failed to toggle microphone";
|
const errorMessage = error instanceof Error ? error.message : "Failed to toggle microphone";
|
||||||
|
@ -239,8 +238,8 @@ export default function AudioControlPopover({ microphone }: AudioControlPopoverP
|
||||||
const handleMicrophoneDeviceChange = async (deviceId: string) => {
|
const handleMicrophoneDeviceChange = async (deviceId: string) => {
|
||||||
setSelectedInputDevice(deviceId);
|
setSelectedInputDevice(deviceId);
|
||||||
|
|
||||||
// If microphone is currently active (unmuted), restart it with the new device
|
// If microphone is currently active, restart it with the new device
|
||||||
if (isMicrophoneActiveFromWS) {
|
if (isMicrophoneActiveFromHook) {
|
||||||
try {
|
try {
|
||||||
// Stop current microphone
|
// Stop current microphone
|
||||||
await stopMicrophone();
|
await stopMicrophone();
|
||||||
|
@ -325,20 +324,20 @@ export default function AudioControlPopover({ microphone }: AudioControlPopoverP
|
||||||
|
|
||||||
<div className="flex items-center justify-between rounded-lg bg-slate-50 p-3 dark:bg-slate-700">
|
<div className="flex items-center justify-between rounded-lg bg-slate-50 p-3 dark:bg-slate-700">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{isMicrophoneActiveFromWS ? (
|
{isMicrophoneActiveFromHook ? (
|
||||||
<MdMic className="h-5 w-5 text-green-500" />
|
<MdMic className="h-5 w-5 text-green-500" />
|
||||||
) : (
|
) : (
|
||||||
<MdMicOff className="h-5 w-5 text-red-500" />
|
<MdMicOff className="h-5 w-5 text-red-500" />
|
||||||
)}
|
)}
|
||||||
<span className="font-medium text-slate-900 dark:text-slate-100">
|
<span className="font-medium text-slate-900 dark:text-slate-100">
|
||||||
{isMicrophoneActiveFromWS ? "Unmuted" : "Muted"}
|
{isMicrophoneActiveFromHook ? "Enabled" : "Disabled"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
size="SM"
|
size="SM"
|
||||||
theme={isMicrophoneActiveFromWS ? "danger" : "primary"}
|
theme={isMicrophoneActiveFromHook ? "danger" : "primary"}
|
||||||
text={isMicrophoneActiveFromWS ? "Disable" : "Enable"}
|
text={isMicrophoneActiveFromHook ? "Disable" : "Enable"}
|
||||||
onClick={handleToggleMicrophoneMute}
|
onClick={handleToggleMicrophoneEnable}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -381,7 +380,7 @@ export default function AudioControlPopover({ microphone }: AudioControlPopoverP
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
{isMicrophoneActiveFromWS && (
|
{isMicrophoneActiveFromHook && (
|
||||||
<p className="text-xs text-slate-500 dark:text-slate-400">
|
<p className="text-xs text-slate-500 dark:text-slate-400">
|
||||||
Changing device will restart the microphone
|
Changing device will restart the microphone
|
||||||
</p>
|
</p>
|
||||||
|
@ -418,7 +417,7 @@ export default function AudioControlPopover({ microphone }: AudioControlPopoverP
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Microphone Quality Settings */}
|
{/* Microphone Quality Settings */}
|
||||||
{isMicrophoneActiveFromWS && (
|
{isMicrophoneActiveFromHook && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<MdMic className="h-4 w-4 text-slate-600 dark:text-slate-400" />
|
<MdMic className="h-4 w-4 text-slate-600 dark:text-slate-400" />
|
||||||
|
|
Loading…
Reference in New Issue