diff --git a/ui/src/components/ActionBar.tsx b/ui/src/components/ActionBar.tsx index 62df18a..d2fd1ea 100644 --- a/ui/src/components/ActionBar.tsx +++ b/ui/src/components/ActionBar.tsx @@ -22,14 +22,20 @@ import AudioControlPopover from "@/components/popovers/AudioControlPopover"; import { useDeviceUiNavigation } from "@/hooks/useAppNavigation"; import api from "@/api"; +// Type for microphone error +interface MicrophoneError { + type: 'permission' | 'device' | 'network' | 'unknown'; + message: string; +} + // Type for microphone hook return value interface MicrophoneHookReturn { isMicrophoneActive: boolean; isMicrophoneMuted: boolean; microphoneStream: MediaStream | null; - startMicrophone: (deviceId?: string) => Promise<{ success: boolean; error?: any }>; - stopMicrophone: () => Promise<{ success: boolean; error?: any }>; - toggleMicrophoneMute: () => Promise<{ success: boolean; error?: any }>; + startMicrophone: (deviceId?: string) => Promise<{ success: boolean; error?: MicrophoneError }>; + stopMicrophone: () => Promise<{ success: boolean; error?: MicrophoneError }>; + toggleMicrophoneMute: () => Promise<{ success: boolean; error?: MicrophoneError }>; syncMicrophoneState: () => Promise; } diff --git a/ui/src/components/WebRTCVideo.tsx b/ui/src/components/WebRTCVideo.tsx index 9364f05..0c83065 100644 --- a/ui/src/components/WebRTCVideo.tsx +++ b/ui/src/components/WebRTCVideo.tsx @@ -25,14 +25,20 @@ import { PointerLockBar, } from "./VideoOverlay"; +// Type for microphone error +interface MicrophoneError { + type: 'permission' | 'device' | 'network' | 'unknown'; + message: string; +} + // Interface for microphone hook return type interface MicrophoneHookReturn { isMicrophoneActive: boolean; isMicrophoneMuted: boolean; microphoneStream: MediaStream | null; - startMicrophone: (deviceId?: string) => Promise<{ success: boolean; error?: any }>; - stopMicrophone: () => Promise<{ success: boolean; error?: any }>; - toggleMicrophoneMute: () => Promise<{ success: boolean; error?: any }>; + startMicrophone: (deviceId?: string) => Promise<{ success: boolean; error?: MicrophoneError }>; + stopMicrophone: () => Promise<{ success: boolean; error?: MicrophoneError }>; + toggleMicrophoneMute: () => Promise<{ success: boolean; error?: MicrophoneError }>; syncMicrophoneState: () => Promise; } diff --git a/ui/src/components/popovers/AudioControlPopover.tsx b/ui/src/components/popovers/AudioControlPopover.tsx index fed714e..b8bcdca 100644 --- a/ui/src/components/popovers/AudioControlPopover.tsx +++ b/ui/src/components/popovers/AudioControlPopover.tsx @@ -11,14 +11,20 @@ import { useAudioLevel } from "@/hooks/useAudioLevel"; import api from "@/api"; import notifications from "@/notifications"; +// Type for microphone error +interface MicrophoneError { + type: 'permission' | 'device' | 'network' | 'unknown'; + message: string; +} + // Type for microphone hook return value interface MicrophoneHookReturn { isMicrophoneActive: boolean; isMicrophoneMuted: boolean; microphoneStream: MediaStream | null; - startMicrophone: (deviceId?: string) => Promise<{ success: boolean; error?: any }>; - stopMicrophone: () => Promise<{ success: boolean; error?: any }>; - toggleMicrophoneMute: () => Promise<{ success: boolean; error?: any }>; + startMicrophone: (deviceId?: string) => Promise<{ success: boolean; error?: MicrophoneError }>; + stopMicrophone: () => Promise<{ success: boolean; error?: MicrophoneError }>; + toggleMicrophoneMute: () => Promise<{ success: boolean; error?: MicrophoneError }>; syncMicrophoneState: () => Promise; } @@ -276,9 +282,9 @@ export default function AudioControlPopover({ microphone }: AudioControlPopoverP const videoElement = document.querySelector('video'); if (videoElement && 'setSinkId' in videoElement) { try { - await (videoElement as any).setSinkId(deviceId); + await (videoElement as HTMLVideoElement & { setSinkId: (deviceId: string) => Promise }).setSinkId(deviceId); console.log('Audio output device changed to:', deviceId); - } catch (error) { + } catch (error: unknown) { console.error('Failed to change audio output device:', error); } } else { diff --git a/ui/src/hooks/useAudioLevel.ts b/ui/src/hooks/useAudioLevel.ts index 0e2038e..5b16623 100644 --- a/ui/src/hooks/useAudioLevel.ts +++ b/ui/src/hooks/useAudioLevel.ts @@ -43,7 +43,7 @@ export const useAudioLevel = (stream: MediaStream | null): AudioLevelHookResult try { // Create audio context and analyser - const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)(); + const audioContext = new (window.AudioContext || (window as Window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext)(); const analyser = audioContext.createAnalyser(); const source = audioContext.createMediaStreamSource(stream); @@ -68,8 +68,8 @@ export const useAudioLevel = (stream: MediaStream | null): AudioLevelHookResult // Calculate RMS (Root Mean Square) for more accurate level representation let sum = 0; - for (let i = 0; i < dataArray.length; i++) { - sum += dataArray[i] * dataArray[i]; + for (const value of dataArray) { + sum += value * value; } const rms = Math.sqrt(sum / dataArray.length); diff --git a/ui/src/hooks/useMicrophone.ts b/ui/src/hooks/useMicrophone.ts index 9472b6e..4e3ac2d 100644 --- a/ui/src/hooks/useMicrophone.ts +++ b/ui/src/hooks/useMicrophone.ts @@ -1,4 +1,5 @@ import { useCallback, useEffect, useRef } from "react"; + import { useRTCStore } from "@/hooks/stores"; import api from "@/api"; @@ -97,9 +98,9 @@ export function useMicrophone() { // Make debug function available globally for console access useEffect(() => { - (window as any).debugMicrophoneState = debugMicrophoneState; + (window as Window & { debugMicrophoneState?: () => unknown }).debugMicrophoneState = debugMicrophoneState; return () => { - delete (window as any).debugMicrophoneState; + delete (window as Window & { debugMicrophoneState?: () => unknown }).debugMicrophoneState; }; }, [debugMicrophoneState]); @@ -396,7 +397,7 @@ export function useMicrophone() { isStartingRef.current = false; return { success: false, error: micError }; } - }, [peerConnection, setMicrophoneStream, setMicrophoneSender, setMicrophoneActive, setMicrophoneMuted, syncMicrophoneState, stopMicrophoneStream]); + }, [peerConnection, setMicrophoneStream, setMicrophoneSender, setMicrophoneActive, setMicrophoneMuted, stopMicrophoneStream, isMicrophoneActive, isMicrophoneMuted, microphoneStream]); // Stop microphone const stopMicrophone = useCallback(async (): Promise<{ success: boolean; error?: MicrophoneError }> => { @@ -519,7 +520,15 @@ export function useMicrophone() { try { const stats = await microphoneSender.getStats(); - const audioStats: any[] = []; + const audioStats: { + id: string; + type: string; + kind: string; + packetsSent?: number; + bytesSent?: number; + timestamp?: number; + ssrc?: number; + }[] = []; stats.forEach((report, id) => { if (report.type === 'outbound-rtp' && report.kind === 'audio') { @@ -576,7 +585,7 @@ export function useMicrophone() { // 3. Test audio level detection manually try { - const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)(); + const audioContext = new (window.AudioContext || (window as Window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext)(); const analyser = audioContext.createAnalyser(); const source = audioContext.createMediaStreamSource(stream); @@ -595,8 +604,8 @@ export function useMicrophone() { analyser.getByteFrequencyData(dataArray); let sum = 0; - for (let i = 0; i < dataArray.length; i++) { - sum += dataArray[i] * dataArray[i]; + for (const value of dataArray) { + sum += value * value; } const rms = Math.sqrt(sum / dataArray.length); const level = Math.min(100, (rms / 255) * 100); @@ -672,13 +681,37 @@ export function useMicrophone() { // Make debug functions available globally for console access useEffect(() => { - (window as any).debugMicrophone = debugMicrophoneState; - (window as any).checkAudioStats = checkAudioTransmissionStats; - (window as any).testMicrophoneAudio = testMicrophoneAudio; + (window as Window & { + debugMicrophone?: () => unknown; + checkAudioStats?: () => unknown; + testMicrophoneAudio?: () => unknown; + }).debugMicrophone = debugMicrophoneState; + (window as Window & { + debugMicrophone?: () => unknown; + checkAudioStats?: () => unknown; + testMicrophoneAudio?: () => unknown; + }).checkAudioStats = checkAudioTransmissionStats; + (window as Window & { + debugMicrophone?: () => unknown; + checkAudioStats?: () => unknown; + testMicrophoneAudio?: () => unknown; + }).testMicrophoneAudio = testMicrophoneAudio; return () => { - delete (window as any).debugMicrophone; - delete (window as any).checkAudioStats; - delete (window as any).testMicrophoneAudio; + delete (window as Window & { + debugMicrophone?: () => unknown; + checkAudioStats?: () => unknown; + testMicrophoneAudio?: () => unknown; + }).debugMicrophone; + delete (window as Window & { + debugMicrophone?: () => unknown; + checkAudioStats?: () => unknown; + testMicrophoneAudio?: () => unknown; + }).checkAudioStats; + delete (window as Window & { + debugMicrophone?: () => unknown; + checkAudioStats?: () => unknown; + testMicrophoneAudio?: () => unknown; + }).testMicrophoneAudio; }; }, [debugMicrophoneState, checkAudioTransmissionStats, testMicrophoneAudio]);