@@ -9,37 +9,28 @@ import {
99 combineLatest ,
1010 filter ,
1111 map ,
12- merge ,
12+ type Observable ,
1313 pairwise ,
14- startWith ,
1514 Subject ,
1615 switchMap ,
17- type Observable ,
18- tap ,
1916} from "rxjs" ;
2017import { createMediaDeviceObserver } from "@livekit/components-core" ;
2118import { type Logger , logger as rootLogger } from "matrix-js-sdk/lib/logger" ;
2219
2320import {
21+ alwaysShowIphoneEarpiece as alwaysShowIphoneEarpieceSetting ,
2422 audioInput as audioInputSetting ,
2523 audioOutput as audioOutputSetting ,
2624 videoInput as videoInputSetting ,
27- alwaysShowIphoneEarpiece as alwaysShowIphoneEarpieceSetting ,
2825} from "../settings/settings" ;
2926import { type ObservableScope } from "./ObservableScope" ;
30- import {
31- outputDevice$ as controlledOutputSelection$ ,
32- availableOutputDevices$ as controlledAvailableOutputDevices$ ,
33- } from "../controls" ;
27+ import { availableOutputDevices$ as controlledAvailableOutputDevices$ } from "../controls" ;
3428import { getUrlParams } from "../UrlParams" ;
3529import { platform } from "../Platform" ;
3630import { switchWhen } from "../utils/observable" ;
3731import { type Behavior , constant } from "./Behavior" ;
3832import { AndroidControlledAudioOutput } from "./AndroidControlledAudioOutput.ts" ;
39-
40- // This hardcoded id is used in EX ios! It can only be changed in coordination with
41- // the ios swift team.
42- const EARPIECE_CONFIG_ID = "earpiece-id" ;
33+ import { ControlledAudioOutput } from "./ControlledAudioOutput.ts" ;
4334
4435export type DeviceLabel =
4536 | { type : "name" ; name : string }
@@ -127,7 +118,7 @@ export interface MediaDevice<Label, Selected> {
127118export const iosDeviceMenu$ =
128119 platform === "ios" ? constant ( true ) : alwaysShowIphoneEarpieceSetting . value$ ;
129120
130- function availableRawDevices$ (
121+ export function availableRawDevices$ (
131122 kind : MediaDeviceKind ,
132123 usingNames$ : Behavior < boolean > ,
133124 scope : ObservableScope ,
@@ -175,9 +166,6 @@ function buildDeviceMap(
175166function selectDevice$ < Label > (
176167 available$ : Observable < Map < string , Label > > ,
177168 preferredId$ : Observable < string | undefined > ,
178- defaultPicker : ( available : Map < string , Label > ) => string | undefined = (
179- available ,
180- ) => available . keys ( ) . next ( ) . value ,
181169) : Observable < string | undefined > {
182170 return combineLatest ( [ available$ , preferredId$ ] , ( available , preferredId ) => {
183171 if ( available . size ) {
@@ -196,7 +184,7 @@ function selectDevice$<Label>(
196184 return preferredId ;
197185 } else {
198186 // No preferred, so pick a default.
199- return defaultPicker ( available ) ;
187+ return available . keys ( ) . next ( ) . value ;
200188 }
201189 }
202190 return undefined ;
@@ -319,111 +307,6 @@ export class AudioOutput implements MediaDevice<
319307 }
320308}
321309
322- /**
323- * A special implementation of audio output that allows the hosting application
324- * to have more control over the device selection process. This is used when the
325- * `controlledAudioDevices` URL parameter is set, which is currently only true on mobile.
326- */
327- class ControlledAudioOutput implements MediaDevice <
328- AudioOutputDeviceLabel ,
329- SelectedAudioOutputDevice
330- > {
331- private logger = rootLogger . getChild ( "[MediaDevices ControlledAudioOutput]" ) ;
332- // We need to subscribe to the raw devices so that the OS does update the input
333- // back to what it was before. otherwise we will switch back to the default
334- // whenever we allocate a new stream.
335- public readonly availableRaw$ = availableRawDevices$ (
336- "audiooutput" ,
337- this . usingNames$ ,
338- this . scope ,
339- this . logger ,
340- ) ;
341-
342- public readonly available$ = this . scope . behavior (
343- combineLatest (
344- [ controlledAvailableOutputDevices$ . pipe ( startWith ( [ ] ) ) , iosDeviceMenu$ ] ,
345- ( availableRaw , iosDeviceMenu ) => {
346- const available = new Map < string , AudioOutputDeviceLabel > (
347- availableRaw . map (
348- ( { id, name, isEarpiece, isSpeaker /*,isExternalHeadset*/ } ) => {
349- let deviceLabel : AudioOutputDeviceLabel ;
350- // if (isExternalHeadset) // Do we want this?
351- if ( isEarpiece ) deviceLabel = { type : "earpiece" } ;
352- else if ( isSpeaker ) deviceLabel = { type : "speaker" } ;
353- else deviceLabel = { type : "name" , name } ;
354- return [ id , deviceLabel ] ;
355- } ,
356- ) ,
357- ) ;
358-
359- // Create a virtual earpiece device in case a non-earpiece device is
360- // designated for this purpose
361- if ( iosDeviceMenu && availableRaw . some ( ( d ) => d . forEarpiece ) ) {
362- this . logger . info (
363- `IOS Add virtual earpiece device with id ${ EARPIECE_CONFIG_ID } ` ,
364- ) ;
365- available . set ( EARPIECE_CONFIG_ID , { type : "earpiece" } ) ;
366- }
367-
368- return available ;
369- } ,
370- ) ,
371- ) ;
372-
373- private readonly deviceSelection$ = new Subject < string > ( ) ;
374-
375- public select ( id : string ) : void {
376- this . logger . info ( `select device: ${ id } ` ) ;
377- this . deviceSelection$ . next ( id ) ;
378- }
379-
380- public readonly selected$ = this . scope . behavior (
381- combineLatest (
382- [
383- this . available$ ,
384- merge (
385- controlledOutputSelection$ . pipe ( startWith ( undefined ) ) ,
386- this . deviceSelection$ ,
387- ) ,
388- ] ,
389- ( available , preferredId ) => {
390- const id = preferredId ?? available . keys ( ) . next ( ) . value ;
391- return id === undefined
392- ? undefined
393- : { id, virtualEarpiece : id === EARPIECE_CONFIG_ID } ;
394- } ,
395- ) . pipe (
396- tap ( ( selected ) => {
397- this . logger . debug ( `selected device: ${ selected ?. id } ` ) ;
398- } ) ,
399- ) ,
400- ) ;
401-
402- public constructor (
403- private readonly usingNames$ : Behavior < boolean > ,
404- private readonly scope : ObservableScope ,
405- ) {
406- this . selected$ . subscribe ( ( device ) => {
407- // Let the hosting application know which output device has been selected.
408- // This information is probably only of interest if the earpiece mode has
409- // been selected - for example, Element X iOS listens to this to determine
410- // whether it should enable the proximity sensor.
411- if ( device !== undefined ) {
412- this . logger . info ( "onAudioDeviceSelect called:" , device ) ;
413- window . controls . onAudioDeviceSelect ?.( device . id ) ;
414- // Also invoke the deprecated callback for backward compatibility
415- window . controls . onOutputDeviceSelect ?.( device . id ) ;
416- }
417- } ) ;
418- this . available$ . subscribe ( ( available ) => {
419- this . logger . debug ( "available devices:" , available ) ;
420- } ) ;
421- this . availableRaw$ . subscribe ( ( availableRaw ) => {
422- this . logger . debug ( "available raw devices:" , availableRaw ) ;
423- } ) ;
424- }
425- }
426-
427310class VideoInput implements MediaDevice < DeviceLabel , SelectedDevice > {
428311 private logger = rootLogger . getChild ( "[MediaDevices VideoInput]" ) ;
429312
0 commit comments