@@ -31,70 +31,110 @@ export async function createLocalTracks(
3131 options ?: CreateLocalTracksOptions ,
3232) : Promise < Array < LocalTrack > > {
3333 // set default options to true
34- options ??= { } ;
35- options . audio ??= { deviceId : 'default' } ;
36- options . video ??= { deviceId : 'default' } ;
34+ const internalOptions = { ...( options ?? { } ) } ;
35+ let attemptExactMatch = false ;
36+ let retryAudioOptions : AudioCaptureOptions | undefined | boolean = options ?. audio ;
37+ let retryVideoOptions : VideoCaptureOptions | undefined | boolean = options ?. video ;
38+ // if the user passes a device id as a string, we default to exact match
39+ if (
40+ internalOptions . audio &&
41+ typeof internalOptions . audio === 'object' &&
42+ typeof internalOptions . audio . deviceId === 'string'
43+ ) {
44+ const deviceId : string = internalOptions . audio . deviceId ;
45+ internalOptions . audio . deviceId = { exact : deviceId } ;
46+ attemptExactMatch = true ;
47+ retryAudioOptions = {
48+ ...internalOptions . audio ,
49+ deviceId : { ideal : deviceId } ,
50+ } ;
51+ }
52+ if (
53+ internalOptions . video &&
54+ typeof internalOptions . video === 'object' &&
55+ typeof internalOptions . video . deviceId === 'string'
56+ ) {
57+ const deviceId : string = internalOptions . video . deviceId ;
58+ internalOptions . video . deviceId = { exact : deviceId } ;
59+ attemptExactMatch = true ;
60+ retryVideoOptions = {
61+ ...internalOptions . video ,
62+ deviceId : { ideal : deviceId } ,
63+ } ;
64+ }
65+ internalOptions . audio ??= { deviceId : 'default' } ;
66+ internalOptions . video ??= { deviceId : 'default' } ;
3767
38- const { audioProcessor, videoProcessor } = extractProcessorsFromOptions ( options ) ;
39- const opts = mergeDefaultOptions ( options , audioDefaults , videoDefaults ) ;
68+ const { audioProcessor, videoProcessor } = extractProcessorsFromOptions ( internalOptions ) ;
69+ const opts = mergeDefaultOptions ( internalOptions , audioDefaults , videoDefaults ) ;
4070 const constraints = constraintsForOptions ( opts ) ;
4171
4272 // Keep a reference to the promise on DeviceManager and await it in getLocalDevices()
4373 // works around iOS Safari Bug https://bugs.webkit.org/show_bug.cgi?id=179363
4474 const mediaPromise = navigator . mediaDevices . getUserMedia ( constraints ) ;
4575
46- if ( options . audio ) {
76+ if ( internalOptions . audio ) {
4777 DeviceManager . userMediaPromiseMap . set ( 'audioinput' , mediaPromise ) ;
4878 mediaPromise . catch ( ( ) => DeviceManager . userMediaPromiseMap . delete ( 'audioinput' ) ) ;
4979 }
50- if ( options . video ) {
80+ if ( internalOptions . video ) {
5181 DeviceManager . userMediaPromiseMap . set ( 'videoinput' , mediaPromise ) ;
5282 mediaPromise . catch ( ( ) => DeviceManager . userMediaPromiseMap . delete ( 'videoinput' ) ) ;
5383 }
84+ try {
85+ const stream = await mediaPromise ;
86+ return await Promise . all (
87+ stream . getTracks ( ) . map ( async ( mediaStreamTrack ) => {
88+ const isAudio = mediaStreamTrack . kind === 'audio' ;
89+ let trackOptions = isAudio ? opts ! . audio : opts ! . video ;
90+ if ( typeof trackOptions === 'boolean' || ! trackOptions ) {
91+ trackOptions = { } ;
92+ }
93+ let trackConstraints : MediaTrackConstraints | undefined ;
94+ const conOrBool = isAudio ? constraints . audio : constraints . video ;
95+ if ( typeof conOrBool !== 'boolean' ) {
96+ trackConstraints = conOrBool ;
97+ }
5498
55- const stream = await mediaPromise ;
56- return Promise . all (
57- stream . getTracks ( ) . map ( async ( mediaStreamTrack ) => {
58- const isAudio = mediaStreamTrack . kind === 'audio' ;
59- let trackOptions = isAudio ? opts ! . audio : opts ! . video ;
60- if ( typeof trackOptions === 'boolean' || ! trackOptions ) {
61- trackOptions = { } ;
62- }
63- let trackConstraints : MediaTrackConstraints | undefined ;
64- const conOrBool = isAudio ? constraints . audio : constraints . video ;
65- if ( typeof conOrBool !== 'boolean' ) {
66- trackConstraints = conOrBool ;
67- }
99+ // update the constraints with the device id the user gave permissions to in the permission prompt
100+ // otherwise each track restart (e.g. mute - unmute) will try to initialize the device again -> causing additional permission prompts
101+ const newDeviceId = mediaStreamTrack . getSettings ( ) . deviceId ;
102+ if (
103+ trackConstraints ?. deviceId &&
104+ unwrapConstraint ( trackConstraints . deviceId ) !== newDeviceId
105+ ) {
106+ trackConstraints . deviceId = newDeviceId ;
107+ } else if ( ! trackConstraints ) {
108+ trackConstraints = { deviceId : newDeviceId } ;
109+ }
68110
69- // update the constraints with the device id the user gave permissions to in the permission prompt
70- // otherwise each track restart (e.g. mute - unmute) will try to initialize the device again -> causing additional permission prompts
71- const newDeviceId = mediaStreamTrack . getSettings ( ) . deviceId ;
72- if (
73- trackConstraints ?. deviceId &&
74- unwrapConstraint ( trackConstraints . deviceId ) !== newDeviceId
75- ) {
76- trackConstraints . deviceId = newDeviceId ;
77- } else if ( ! trackConstraints ) {
78- trackConstraints = { deviceId : newDeviceId } ;
79- }
111+ const track = mediaTrackToLocalTrack ( mediaStreamTrack , trackConstraints ) ;
112+ if ( track . kind === Track . Kind . Video ) {
113+ track . source = Track . Source . Camera ;
114+ } else if ( track . kind === Track . Kind . Audio ) {
115+ track . source = Track . Source . Microphone ;
116+ }
117+ track . mediaStream = stream ;
80118
81- const track = mediaTrackToLocalTrack ( mediaStreamTrack , trackConstraints ) ;
82- if ( track . kind === Track . Kind . Video ) {
83- track . source = Track . Source . Camera ;
84- } else if ( track . kind === Track . Kind . Audio ) {
85- track . source = Track . Source . Microphone ;
86- }
87- track . mediaStream = stream ;
119+ if ( isAudioTrack ( track ) && audioProcessor ) {
120+ await track . setProcessor ( audioProcessor ) ;
121+ } else if ( isVideoTrack ( track ) && videoProcessor ) {
122+ await track . setProcessor ( videoProcessor ) ;
123+ }
88124
89- if ( isAudioTrack ( track ) && audioProcessor ) {
90- await track . setProcessor ( audioProcessor ) ;
91- } else if ( isVideoTrack ( track ) && videoProcessor ) {
92- await track . setProcessor ( videoProcessor ) ;
93- }
94-
95- return track ;
96- } ) ,
97- ) ;
125+ return track ;
126+ } ) ,
127+ ) ;
128+ } catch ( e ) {
129+ if ( ! attemptExactMatch ) {
130+ throw e ;
131+ }
132+ return createLocalTracks ( {
133+ ...options ,
134+ audio : retryAudioOptions ,
135+ video : retryVideoOptions ,
136+ } ) ;
137+ }
98138}
99139
100140/**
0 commit comments