Device management
The logic for device management is handled in the Devices
class. This class
uses @signalwire/webrtc
to enumerate devices and switch between them.
The UI for device management is handled in the controls.ui.ts
file. It creates new
DeviceMenu
objects when user clicks the device menu button.
notes mar 26
The device management and control buttons rendering has to be redone.
The current implementation has logic and state spread between Devices
class, Call
class,
controls.ui.ts
, and C2CWidget
class. It is riddled with callbacks and a messy flow
of commands, events and state. In general, the code has become the bad kind of complex,
and I can't be sure there aren't subtle bugs or that it'll handle all cases gratefully.
I underestimated the dependency between Device Management and the FabricRoomSession object. As of now, FabricRoomSession's handling of device management is incomplete so can't be fully relied upon. However, it is also important that I first try the official SDK device management before falling back to native methods.
Every new feature I add contributes to the mess exponentially, and I don't think it's good to keep on piling on to this mess. My next steps are as follows:
-
Devices
class becomes a singleton. Instead ofawait
ing it's creation (to make sure we get permissions), it has callbacks that are called when the devices are ready. (do we even need to wait for permissions? does SDK does this anyway?) -
Devices
class holds a reference to the current callFabricRoomSession
object to pull it's local audio and video tracks.Devices
class will be considered fully coupled with theFabricRoomSession
object. Trying to keep them separate was a mistake. -
Devices
class has aDevicesState
object which holds all the state for the devices. Rough shape:type DevicesState = {
// list of all devices available to us
audioinputDeviceList: MediaDeviceInfo[];
videoinputDeviceList: MediaDeviceInfo[];
audiooutputDeviceList: MediaDeviceInfo[];
// what device are we currently using?
currentAudioInputDevice: MediaDeviceInfo | null;
currentVideoInputDevice: MediaDeviceInfo | null;
currentAudioOutputDevice: MediaDeviceInfo | null;
// this holds information about whether user *wants* the device muted
// we should honor user's command in every case, whether the SDK has permissions or not.
isAudioMuted: boolean;
isVideoMuted: boolean;
isSpeakerMuted: boolean;
currentCall: FabricRoomSession | null;
} -
Devices
class responsibilities are limited to:a. keeping the state up to date by hooking into the various events
b. handling the logic of switching devices (using
FabricRoomSession
's methods)c. handling the logic for muting/unmuting devices (using
FabricRoomSession
's methods; then falling back to native methods)d. emitting events when a. devices are ready b. UI needs updating
-
controls.ui.ts
only triggers whenDevices
emits the UI needs updating event. It builds up the UI using theDevicesState
object. -
controls.ui.ts
passes commands from the user to theDevices
class. If changes happen,Devices
emits an event andcontrols.ui.ts
updates the UI. -
My current implementation for
DeviceMenu
is needlessly JS-heavy. I can use simple CSS for menus, using JS to simply toggle the menu open/close.
Rough flow:
The 5 second timeout issue
There's a problem. If the user doesn't allow or deny permissions within 5 seconds, a timeout error is thrown by the SDK. It seems to be hardcoded in the signalwire webrtc wrapper for getUserMedia. This is rather annoying for the user, so if it can be removed, it should.
export const getUserMedia = async (
constraints: MediaStreamConstraints = { audio: true, video: true }
) => {
try {
const promise = getMediaDevicesApi().getUserMedia(constraints)
const useTimeout = await _useTimeoutForGUM(constraints)
if (useTimeout) {
const exception = new Error('Timeout reading from your devices')
return await timeoutPromise<MediaStream>(promise, GUM_TIMEOUT, exception)
}
//...
}
}