From 1d11a72af2bb64acbf217eae671dcb78ac569c2b Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 13 Mar 2026 08:42:35 +0100 Subject: [PATCH] add logs --- src/controls.ts | 63 +++++++++++++++++++++++++++++++++++++-- src/state/MediaDevices.ts | 27 ++++++++++++++--- 2 files changed, 83 insertions(+), 7 deletions(-) diff --git a/src/controls.ts b/src/controls.ts index 6a050cb0..86de0ace 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -33,12 +33,36 @@ export interface Controls { showNativeOutputDevicePicker?: () => void; } +/** + * Output Audio device when using the controlled audio output mode (mobile). + */ export interface OutputDevice { id: string; name: string; + /** + * Reverse engineered: + * - on iOS always true if output is routed to speaker. In other case iOS on declare a `dummy` id device. + * In that case then ElementCalls manually append a earpiece device with id `EARPIECE_CONFIG_ID` anb `{ type: "earpiece" }` + * - on Android this is unused. + */ forEarpiece?: boolean; + /** + * Reverse engineered: + * - on iOS always undefined + * - on Android true for the `TYPE_BUILTIN_EARPIECE` + */ isEarpiece?: boolean; + /** + * Reverse engineered: + * - on iOS always true if output is routed to speaker. In other case iOS on declare a `dummy` id device. + * - on Android true for the `TYPE_BUILTIN_SPEAKER` + */ isSpeaker?: boolean; + /** + * Reverse engineered: + * - on iOS always undefined. + * - on Android true for the `TYPE_BLUETOOTH_SCO` + */ isExternalHeadset?: boolean; } @@ -47,8 +71,16 @@ export interface OutputDevice { */ export const setPipEnabled$ = new Subject(); +/** + * Stores the list of available controlled audio output devices. + * This is set when the native code calls `setAvailableAudioDevices` with the list of available audio output devices. + */ export const availableOutputDevices$ = new Subject(); +/** + * Stores the current audio output device id. + * This is set when the native code calls `setAudioDevice` + */ export const outputDevice$ = new Subject(); /** @@ -80,16 +112,41 @@ window.controls = { setPipEnabled$.next(false); }, + /** + * Reverse engineered: + * + * - on iOS: + * This always a list of one thing. If current route output is speaker it returns + * the single `{"id":"Speaker","name":"Speaker","forEarpiece":true,"isSpeaker":true}` Notice that EC will + * also manually add a virtual earpiece device with id `EARPIECE_CONFIG_ID` and `{ type: "earpiece" }`. + * If the route output is not speaker then it will be `{id: 'dummy', name: 'dummy'}` + * + * + * - on Android: + * This is a list of all available output audio devices. The `id` is the Android AudioDeviceInfo.getId() + * and the `name` is based the Android AudioDeviceInfo.productName (mapped to static strings for known types) + * The `isEarpiece`, `isSpeaker` and `isExternalHeadset` are set based on the Android AudioDeviceInfo.type + * matching the corresponding types for earpiece, speaker and bluetooth headset. + */ setAvailableAudioDevices(devices: OutputDevice[]): void { - logger.info("setAvailableAudioDevices called from native:", devices); + logger.info( + "[MediaDevices controls] setAvailableAudioDevices called from native:", + devices, + ); availableOutputDevices$.next(devices); }, setAudioDevice(id: string): void { - logger.info("setAudioDevice called from native", id); + logger.info( + "[MediaDevices controls] setAudioDevice called from native", + id, + ); outputDevice$.next(id); }, setAudioEnabled(enabled: boolean): void { - logger.info("setAudioEnabled called from native:", enabled); + logger.info( + "[MediaDevices controls] setAudioEnabled called from native:", + enabled, + ); if (!setAudioEnabled$.observed) throw new Error( "Output controls are disabled. No setAudioEnabled$ observer", diff --git a/src/state/MediaDevices.ts b/src/state/MediaDevices.ts index 613aa3f4..7c2544a8 100644 --- a/src/state/MediaDevices.ts +++ b/src/state/MediaDevices.ts @@ -375,8 +375,12 @@ class ControlledAudioOutput implements MediaDevice< // Create a virtual earpiece device in case a non-earpiece device is // designated for this purpose - if (iosDeviceMenu && availableRaw.some((d) => d.forEarpiece)) + if (iosDeviceMenu && availableRaw.some((d) => d.forEarpiece)) { + this.logger.info( + `[MediaDevices ControlledAudioOutput] IOS Add virtual earpiece device with id ${EARPIECE_CONFIG_ID}`, + ); available.set(EARPIECE_CONFIG_ID, { type: "earpiece" }); + } return available; }, @@ -386,6 +390,9 @@ class ControlledAudioOutput implements MediaDevice< private readonly deviceSelection$ = new Subject(); public select(id: string): void { + this.logger.info( + `[MediaDevices ControlledAudioOutput] select device: ${id}`, + ); this.deviceSelection$.next(id); } @@ -399,11 +406,20 @@ class ControlledAudioOutput implements MediaDevice< ), ], (available, preferredId) => { + this.logger.debug( + `[MediaDevices ControlledAudioOutput] selecting device. Preferred: ${preferredId}, Available: ${Array.from(available.keys()).join(",")}`, + ); const id = preferredId ?? available.keys().next().value; return id === undefined ? undefined : { id, virtualEarpiece: id === EARPIECE_CONFIG_ID }; }, + ).pipe( + tap((selected) => { + this.logger.debug( + `[MediaDevices ControlledAudioOutput] selected device: ${selected?.id}`, + ); + }), ), ); @@ -419,7 +435,7 @@ class ControlledAudioOutput implements MediaDevice< // whether it should enable the proximity sensor. if (device !== undefined) { this.logger.info( - "[controlled-output] onAudioDeviceSelect called:", + "[MediaDevices controlled-output] onAudioDeviceSelect called:", device, ); window.controls.onAudioDeviceSelect?.(device.id); @@ -428,11 +444,14 @@ class ControlledAudioOutput implements MediaDevice< } }); this.available$.subscribe((available) => { - this.logger.info("[controlled-output] available devices:", available); + this.logger.info( + "[MediaDevices controlled-output] available devices:", + available, + ); }); this.availableRaw$.subscribe((availableRaw) => { this.logger.info( - "[controlled-output] available raw devices:", + "[MediaDevices controlled-output] available raw devices:", availableRaw, ); });