This document details all events, triggers, and recovery mechanisms implemented in the Cantina WebRTC application for device resilience and video recovery. The system handles device disconnections, reconnections, focus changes, and various edge cases to provide a seamless user experience.
Location: deviceMonitorSaga.ts
setInterval(checkDevices, 3000)
navigator.mediaDevices.addEventListener('devicechange', checkDevices)
Triggers:
Device physical connection/disconnection
const devicesChanged = devices.length !== previousDevices.length ||
devices.some((device, index) => {
const prevDevice = previousDevices[index]
return !prevDevice ||
device.deviceId !== prevDevice.deviceId ||
device.label !== prevDevice.label
})
Location: visibilitySaga.ts
document.addEventListener('visibilitychange', handleVisibilityChange)
States:
document.hidden === true
: Tab/window is hidden
document.hidden === false
: Tab/window is visibleTriggers:
User switches browser tabs
window.addEventListener('focus', handleFocus)
Triggers:
User clicks on browser window
window.addEventListener('blur', handleBlur)
Triggers:
User clicks outside browser window
Location: callSaga.ts
restoreLocalVideoTrack
Pre-flight checks:
Validate camera device still exists
restoreLocalAudioTrack
Pre-flight checks:
Validate microphone device still exists
Location: deviceSlice.ts
cameraChanged
{ deviceId: string, label: string }
Side effects:
Creates/updates camera preference
microphoneChanged
{ deviceId: string, label: string }
Side effects:
Creates/updates microphone preference
speakerChanged
{ deviceId: string, label: string }
Side effects:
Creates/updates speaker preference
Purpose: Allow devices to stabilize after wake-from-sleep or reconnection
Device Validation
Compare current device ID against available devices
Recovery Attempt (if device missing)
Priority 3: OS default fallback
State Updates
Apply to active WebRTC call
User Notification
Toast for fallback: "Camera disconnected. Using default camera."
Video Recovery
Only proceed if video is currently unmuted and sending
Auto-mute Video
wasAutoMuted = true
previousVideoState
Purpose: Let browser stabilize after focus change
Video Restoration (Mobile Only)
If wasAutoMuted && previousVideoState
:
Device Re-enumeration
Force enumerate speakers
Video Stream Recovery
Step 3: Trigger re-invite if still failing (wait 1500ms)
Local Video Recovery
.local-video
elementsDevice Validation
javascript
const validatedDevice = yield call(
checkMicrophone,
devices.microphoneId,
devices.microphoneLabel,
microphonePreference
)
Device Update (if changed)
Apply to active call
Video Recovery
checkRemoteVideo
(non-blocking)Fork checkLocalVideo
(non-blocking)
Complete Unmute
Purpose | Duration | Location |
---|---|---|
Device polling interval | 3000ms | deviceMonitorSaga |
Wake-from-sleep stabilization | 2000ms | deviceMonitorSaga |
Focus gain stabilization | 200ms | visibilitySaga |
Video play attempt wait | 500ms | checkRemoteVideo |
Keyframe request wait | 1500ms | checkRemoteVideo |
Immediate Response (0ms delay):
User device selection
Unmute actions validation
Quick Response (200ms delay):
Focus gain recovery
Stabilization Required (2000ms delay):
Device change after wake
isMobile === true
)Command | Purpose | When Sent |
---|---|---|
'*0' | Toggle video mute state | On mobile auto-mute/unmute |
'0' | Toggle audio mute state | On device update if muted |
try {
const devices = await navigator.mediaDevices.enumerateDevices()
} catch (error) {
console.error('Failed to enumerate devices:', error)
}
videoEl.play().catch((error) => {
console.error('Failed to play video:', error)
})