@@ -36,7 +36,7 @@ export function useDeviceAlignment(
3636 handleSetupFailure, setPendingGestureOperations, setPendingOperationDetails,
3737 } = gestureHandlers ;
3838 const { cameraError, micError, speakerError } = errorValues ;
39- const { addDeviceBanner, clearBannersForDevice } = bannerCallbacks ;
39+ const { addDeviceBanner, clearBannersForDevice, logEvent } = bannerCallbacks ;
4040
4141 const preferredCameraId = player ?. get ( "cameraId" ) ?? "waiting" ;
4242 const preferredMicId = player ?. get ( "micId" ) ?? "waiting" ;
@@ -72,23 +72,29 @@ export function useDeviceAlignment(
7272 try {
7373 if ( deviceType === "camera" ) {
7474 await callObject . setInputDevicesAsync ( { videoDeviceId : deviceId } ) ;
75+ const toLabel = devices ?. cameras ?. find ( ( c ) => c . device . deviceId === deviceId ) ?. device ?. label || null ;
7576 player ?. set ( "cameraId" , deviceId ) ;
7677 loggedUnavailableCameraRef . current = null ;
78+ if ( logEvent ) logEvent ( "device-changed" , { deviceType, reason : "user-select" , toId : deviceId , toLabel } ) ;
7779 } else if ( deviceType === "microphone" ) {
7880 await callObject . setInputDevicesAsync ( { audioDeviceId : deviceId } ) ;
81+ const toLabel = devices ?. microphones ?. find ( ( m ) => m . device . deviceId === deviceId ) ?. device ?. label || null ;
7982 player ?. set ( "micId" , deviceId ) ;
8083 loggedUnavailableMicRef . current = null ;
84+ if ( logEvent ) logEvent ( "device-changed" , { deviceType, reason : "user-select" , toId : deviceId , toLabel } ) ;
8185 } else if ( deviceType === "speaker" ) {
8286 await devices . setSpeaker ( deviceId ) ;
83- player ?. set ( "speakerId" , deviceId ) ;
84- // Also save the label so future Safari ID-rotation fallback works
8587 const selectedSpeaker = devices ?. speakers ?. find (
8688 ( s ) => s . device . deviceId === deviceId
8789 ) ;
90+ const toLabel = selectedSpeaker ?. device ?. label || null ;
91+ player ?. set ( "speakerId" , deviceId ) ;
92+ // Also save the label so future Safari ID-rotation fallback works
8893 if ( selectedSpeaker ) {
89- player ?. set ( "speakerLabel" , selectedSpeaker . device . label ) ;
94+ player ?. set ( "speakerLabel" , toLabel ) ;
9095 }
9196 loggedUnavailableSpeakerRef . current = null ;
97+ if ( logEvent ) logEvent ( "device-changed" , { deviceType, reason : "user-select" , toId : deviceId , toLabel } ) ;
9298 }
9399 // Clear only the error for the device that was just switched
94100 if ( deviceType === "camera" ) setCameraError ( null ) ;
@@ -111,7 +117,7 @@ export function useDeviceAlignment(
111117 }
112118 }
113119 } ,
114- [ callObject , player , devices , handleSetupFailure , setCameraError , setMicError , setSpeakerError ]
120+ [ callObject , player , devices , handleSetupFailure , setCameraError , setMicError , setSpeakerError , logEvent ]
115121 ) ;
116122
117123 // ------------------- alignment effect ---------------------
@@ -183,6 +189,13 @@ export function useDeviceAlignment(
183189 message : `"${ prefName } " disconnected — switched to "${ fallbackLabel } "` ,
184190 } ) ;
185191 }
192+ if ( logEvent ) {
193+ logEvent ( "device-changed" , {
194+ deviceType, reason : "auto-fallback" ,
195+ toId : targetId , toLabel : fallbackLabel ,
196+ preferredId, preferredLabel,
197+ } ) ;
198+ }
186199 } catch ( err ) {
187200 console . error (
188201 `Failed to auto-switch ${ deviceType } to fallback` ,
@@ -197,19 +210,25 @@ export function useDeviceAlignment(
197210
198211 // Skip if we're already using this device
199212 if ( currentDeviceId === targetId ) {
200- // Clear any stale banner if the device is now matched
201- if ( clearBannersForDevice ) clearBannersForDevice ( deviceType ) ;
202- // If we previously fell back, clear the ref and show recovery banner
213+ // If we previously fell back, show recovery banner.
214+ // addDeviceBanner internally clears existing banners for this device
215+ // type first, so no separate clearBannersForDevice call is needed.
203216 if ( loggedUnavailableRef . current === preferredId ) {
204217 // eslint-disable-next-line no-param-reassign
205218 loggedUnavailableRef . current = null ;
219+ const deviceName = targetDevice . device . label || preferredLabel || preferredId ;
206220 if ( addDeviceBanner ) {
207- const deviceName = targetDevice . device . label || preferredLabel || preferredId ;
208221 addDeviceBanner ( {
209222 deviceType,
210223 message : `"${ deviceName } " reconnected — switched back` ,
211224 } ) ;
212225 }
226+ if ( logEvent ) {
227+ logEvent ( "device-changed" , {
228+ deviceType, reason : "recovery-already-active" ,
229+ toId : targetId , toLabel : deviceName , preferredId, preferredLabel,
230+ } ) ;
231+ }
213232 }
214233 return ;
215234 }
@@ -224,9 +243,6 @@ export function useDeviceAlignment(
224243 } ,
225244 } ) ;
226245
227- console . log ( `Setting ${ deviceType } via ${ matchType } match` , {
228- preferredId, preferredLabel, targetId, targetLabel : targetDevice . device . label , matchType,
229- } ) ;
230246 // Check if this is a recovery (we previously fell back for this device)
231247 const isRecovery = loggedUnavailableRef . current === preferredId ;
232248 // eslint-disable-next-line no-param-reassign
@@ -249,6 +265,16 @@ export function useDeviceAlignment(
249265 message : `"${ deviceName } " reconnected — switched back` ,
250266 } ) ;
251267 }
268+ if ( logEvent ) {
269+ logEvent ( "device-changed" , {
270+ deviceType,
271+ reason : isRecovery ? "recovery" : "alignment" ,
272+ matchType,
273+ fromId : currentDeviceId ,
274+ toId : targetId ,
275+ toLabel : targetDevice . device . label ,
276+ } ) ;
277+ }
252278 }
253279 } catch ( err ) {
254280 console . error ( `Failed to set ${ deviceType } via ${ matchType } match` , err ) ;
@@ -327,6 +353,13 @@ export function useDeviceAlignment(
327353 message : `"${ prefName } " disconnected — switched to "${ fallbackLabel } "` ,
328354 } ) ;
329355 }
356+ if ( logEvent ) {
357+ logEvent ( "device-changed" , {
358+ deviceType : "speaker" , reason : "auto-fallback" ,
359+ toId : targetId , toLabel : fallbackLabel ,
360+ preferredId : preferredSpeakerId , preferredLabel : preferredSpeakerLabel ,
361+ } ) ;
362+ }
330363 } catch ( err ) {
331364 console . error (
332365 "Failed to auto-switch speaker to fallback" ,
@@ -351,17 +384,24 @@ export function useDeviceAlignment(
351384 }
352385
353386 if ( devices ?. currentSpeaker ?. device ?. deviceId === targetId ) {
354- if ( clearBannersForDevice ) clearBannersForDevice ( "speaker" ) ;
355- // If we previously fell back, clear the ref and show recovery banner
387+ // If we previously fell back, show recovery banner.
388+ // addDeviceBanner internally clears existing banners for speaker first.
356389 if ( loggedUnavailableSpeakerRef . current === preferredSpeakerId ) {
357390 loggedUnavailableSpeakerRef . current = null ;
391+ const deviceName = targetSpeaker . device . label || preferredSpeakerLabel || preferredSpeakerId ;
358392 if ( addDeviceBanner ) {
359- const deviceName = targetSpeaker . device . label || preferredSpeakerLabel || preferredSpeakerId ;
360393 addDeviceBanner ( {
361394 deviceType : "speaker" ,
362395 message : `"${ deviceName } " reconnected — switched back` ,
363396 } ) ;
364397 }
398+ if ( logEvent ) {
399+ logEvent ( "device-changed" , {
400+ deviceType : "speaker" , reason : "recovery-already-active" ,
401+ toId : targetId , toLabel : deviceName ,
402+ preferredId : preferredSpeakerId , preferredLabel : preferredSpeakerLabel ,
403+ } ) ;
404+ }
365405 }
366406 return ;
367407 }
@@ -379,10 +419,6 @@ export function useDeviceAlignment(
379419 } ,
380420 } ) ;
381421
382- console . log ( `Setting speaker via ${ matchType } match` , {
383- preferredSpeakerId, preferredSpeakerLabel,
384- targetId, targetLabel : targetSpeaker . device . label , matchType,
385- } ) ;
386422 const isSpeakerRecovery = loggedUnavailableSpeakerRef . current === preferredSpeakerId ;
387423 loggedUnavailableSpeakerRef . current = null ;
388424 updatingSpeakerRef . current = true ;
@@ -402,6 +438,15 @@ export function useDeviceAlignment(
402438 message : `"${ deviceName } " reconnected — switched back` ,
403439 } ) ;
404440 }
441+ if ( logEvent ) {
442+ logEvent ( "device-changed" , {
443+ deviceType : "speaker" ,
444+ reason : isSpeakerRecovery ? "recovery" : "alignment" ,
445+ matchType,
446+ toId : targetId ,
447+ toLabel : targetSpeaker . device . label ,
448+ } ) ;
449+ }
405450 setPendingGestureOperations ( ( prev ) => ( { ...prev , speaker : false } ) ) ;
406451 setPendingOperationDetails ( ( prev ) => ( { ...prev , speaker : null } ) ) ;
407452 }
@@ -515,6 +560,7 @@ export function useDeviceAlignment(
515560 setPendingOperationDetails ,
516561 addDeviceBanner ,
517562 clearBannersForDevice ,
563+ logEvent ,
518564 ] ) ;
519565
520566 return { handleSwitchDevice } ;
0 commit comments