Bladeren bron

ref(conference) Simplify track creation. (#13209)

* ref(conference) Simplify track creation.
If gUM fails, we do not have to retry gUM with mic only and camera only constraints. gUM has come a long way and this is not needed anymore.

* ref(conference) Filter tracks that are added to conference.

* squash: Address review comments

* fix(prejoin): Display the exact gUM error in prejoin.

* squash: Address review comments
factor2
Jaya Allamsetty 2 jaren geleden
bovenliggende
commit
4c37ef7a2c
No account linked to committer's email address
3 gewijzigde bestanden met toevoegingen van 154 en 165 verwijderingen
  1. 75
    98
      conference.js
  2. 63
    51
      react/features/base/tracks/functions.web.ts
  3. 16
    16
      react/features/prejoin/reducer.ts

+ 75
- 98
conference.js Bestand weergeven

@@ -559,7 +559,7 @@ export default {
559 559
             );
560 560
         }
561 561
 
562
-        let tryCreateLocalTracks;
562
+        let tryCreateLocalTracks = Promise.resolve([]);
563 563
 
564 564
         // On Electron there is no permission prompt for granting permissions. That's why we don't need to
565 565
         // spend much time displaying the overlay screen. If GUM is not resolved within 15 seconds it will
@@ -600,76 +600,65 @@ export default {
600 600
 
601 601
                     return [];
602 602
                 });
603
-        } else if (!requestedAudio && !requestedVideo) {
604
-            // Resolve with no tracks
605
-            tryCreateLocalTracks = Promise.resolve([]);
606
-        } else {
603
+        } else if (requestedAudio || requestedVideo) {
607 604
             tryCreateLocalTracks = createLocalTracksF({
608 605
                 devices: initialDevices,
609 606
                 timeout,
610 607
                 firePermissionPromptIsShownEvent: true
611 608
             })
612
-                .catch(err => {
613
-                    if (requestedAudio && requestedVideo) {
614
-
615
-                        // Try audio only...
616
-                        errors.audioAndVideoError = err;
609
+            .catch(async error => {
610
+                if (error.name === JitsiTrackErrors.TIMEOUT && !browser.isElectron()) {
611
+                    errors.audioAndVideoError = error;
617 612
 
618
-                        if (err.name === JitsiTrackErrors.TIMEOUT && !browser.isElectron()) {
619
-                            // In this case we expect that the permission prompt is still visible. There is no point of
620
-                            // executing GUM with different source. Also at the time of writing the following
621
-                            // inconsistency have been noticed in some browsers - if the permissions prompt is visible
622
-                            // and another GUM is executed the prompt does not change its content but if the user
623
-                            // clicks allow the user action isassociated with the latest GUM call.
624
-                            errors.audioOnlyError = err;
625
-                            errors.videoOnlyError = err;
613
+                    return [];
614
+                }
626 615
 
627
-                            return [];
628
-                        }
616
+                // Retry with separate gUM calls.
617
+                const gUMPromises = [];
618
+                const tracks = [];
629 619
 
630
-                        return createLocalTracksF(audioOptions);
631
-                    } else if (requestedAudio && !requestedVideo) {
632
-                        errors.audioOnlyError = err;
620
+                if (requestedAudio) {
621
+                    gUMPromises.push(createLocalTracksF(audioOptions));
622
+                }
633 623
 
634
-                        return [];
635
-                    } else if (requestedVideo && !requestedAudio) {
636
-                        errors.videoOnlyError = err;
624
+                if (requestedVideo) {
625
+                    gUMPromises.push(createLocalTracksF({
626
+                        devices: [ MEDIA_TYPE.VIDEO ],
627
+                        timeout,
628
+                        firePermissionPromptIsShownEvent: true
629
+                    }));
630
+                }
637 631
 
638
-                        return [];
639
-                    }
640
-                    logger.error('Should never happen');
641
-                })
642
-                .catch(err => {
643
-                    // Log this just in case...
644
-                    if (!requestedAudio) {
645
-                        logger.error('The impossible just happened', err);
646
-                    }
647
-                    errors.audioOnlyError = err;
648
-
649
-                    // Try video only...
650
-                    return requestedVideo
651
-                        ? createLocalTracksF({
652
-                            devices: [ MEDIA_TYPE.VIDEO ],
653
-                            firePermissionPromptIsShownEvent: true
654
-                        })
655
-                        : [];
656
-                })
657
-                .catch(err => {
658
-                    // Log this just in case...
659
-                    if (!requestedVideo) {
660
-                        logger.error('The impossible just happened', err);
632
+                const results = await Promise.allSettled(gUMPromises);
633
+                let errorMsg;
634
+
635
+                results.forEach((result, idx) => {
636
+                    if (result.status === 'fulfilled') {
637
+                        tracks.push(result.value[0]);
638
+                    } else {
639
+                        errorMsg = result.reason;
640
+                        const isAudio = idx === 0;
641
+
642
+                        logger.error(`${isAudio ? 'Audio' : 'Video'} track creation failed with error ${errorMsg}`);
643
+                        if (isAudio) {
644
+                            errors.audioOnlyError = errorMsg;
645
+                        } else {
646
+                            errors.videoOnlyError = errorMsg;
647
+                        }
661 648
                     }
662
-                    errors.videoOnlyError = err;
663
-
664
-                    return [];
665 649
                 });
650
+
651
+                if (errors.audioOnlyError && errors.videoOnlyError) {
652
+                    errors.audioAndVideoError = errorMsg;
653
+                }
654
+
655
+                return tracks;
656
+            });
666 657
         }
667 658
 
668
-        // Hide the permissions prompt/overlay as soon as the tracks are
669
-        // created. Don't wait for the connection to be made, since in some
670
-        // cases, when auth is required, for instance, that won't happen until
671
-        // the user inputs their credentials, but the dialog would be
672
-        // overshadowed by the overlay.
659
+        // Hide the permissions prompt/overlay as soon as the tracks are created. Don't wait for the connection to
660
+        // be established, as in some cases like when auth is required, connection won't be established until the user
661
+        // inputs their credentials, but the dialog would be overshadowed by the overlay.
673 662
         tryCreateLocalTracks.then(tracks => {
674 663
             APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
675 664
 
@@ -810,43 +799,51 @@ export default {
810 799
         const initialOptions = {
811 800
             startAudioOnly: config.startAudioOnly,
812 801
             startScreenSharing: config.startScreenSharing,
813
-            startWithAudioMuted: getStartWithAudioMuted(state)
814
-                || isUserInteractionRequiredForUnmute(state),
815
-            startWithVideoMuted: getStartWithVideoMuted(state)
816
-                || isUserInteractionRequiredForUnmute(state)
802
+            startWithAudioMuted: getStartWithAudioMuted(state) || isUserInteractionRequiredForUnmute(state),
803
+            startWithVideoMuted: getStartWithVideoMuted(state) || isUserInteractionRequiredForUnmute(state)
817 804
         };
818 805
 
819 806
         this.roomName = roomName;
820 807
 
821 808
         try {
822
-            // Initialize the device list first. This way, when creating tracks
823
-            // based on preferred devices, loose label matching can be done in
824
-            // cases where the exact ID match is no longer available, such as
825
-            // when the camera device has switched USB ports.
826
-            // when in startSilent mode we want to start with audio muted
809
+            // Initialize the device list first. This way, when creating tracks based on preferred devices, loose label
810
+            // matching can be done in cases where the exact ID match is no longer available, such as -
811
+            // 1. When the camera device has switched USB ports.
812
+            // 2. When in startSilent mode we want to start with audio muted
827 813
             await this._initDeviceList();
828 814
         } catch (error) {
829 815
             logger.warn('initial device list initialization failed', error);
830 816
         }
831 817
 
832
-        const handleStartAudioMuted = (options, tracks) => {
833
-            if (options.startWithAudioMuted) {
818
+        // Filter out the local tracks based on various config options, i.e., when user joins muted or is muted by
819
+        // focus. However, audio track will always be created even though it is not added to the conference since we
820
+        // want audio related features (noisy mic, talk while muted, etc.) to work even if the mic is muted.
821
+        const handleInitialTracks = (options, tracks) => {
822
+            let localTracks = tracks;
823
+
824
+            // No local tracks are added when user joins as a visitor.
825
+            if (iAmVisitor(state)) {
826
+                return [];
827
+            }
828
+            if (options.startWithAudioMuted || room?.isStartAudioMuted()) {
834 829
                 // Always add the track on Safari because of a known issue where audio playout doesn't happen
835 830
                 // if the user joins audio and video muted, i.e., if there is no local media capture.
836 831
                 if (browser.isWebKitBased()) {
837 832
                     this.muteAudio(true, true);
838 833
                 } else {
839
-                    return tracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
834
+                    localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
840 835
                 }
841 836
             }
837
+            if (room?.isStartVideoMuted()) {
838
+                localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.VIDEO);
839
+            }
842 840
 
843
-            return tracks;
841
+            return localTracks;
844 842
         };
845 843
 
846 844
         if (isPrejoinPageVisible(state)) {
847 845
             _connectionPromise = connect(roomName).then(c => {
848
-                // we want to initialize it early, in case of errors to be able
849
-                // to gather logs
846
+                // We want to initialize it early, in case of errors to be able to gather logs.
850 847
                 APP.connection = c;
851 848
 
852 849
                 return c;
@@ -859,48 +856,28 @@ export default {
859 856
             APP.store.dispatch(makePrecallTest(this._getConferenceOptions()));
860 857
 
861 858
             const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
862
-            const tracks = await tryCreateLocalTracks;
859
+            const localTracks = await tryCreateLocalTracks;
863 860
 
864
-            // Initialize device list a second time to ensure device labels
865
-            // get populated in case of an initial gUM acceptance; otherwise
866
-            // they may remain as empty strings.
861
+            // Initialize device list a second time to ensure device labels get populated in case of an initial gUM
862
+            // acceptance; otherwise they may remain as empty strings.
867 863
             this._initDeviceList(true);
868 864
 
869 865
             if (isPrejoinPageVisible(state)) {
870
-                return APP.store.dispatch(initPrejoin(tracks, errors));
866
+                return APP.store.dispatch(initPrejoin(localTracks, errors));
871 867
             }
872 868
 
873 869
             logger.debug('Prejoin screen no longer displayed at the time when tracks were created');
874 870
 
875 871
             this._displayErrorsForCreateInitialLocalTracks(errors);
876 872
 
877
-            let localTracks = handleStartAudioMuted(initialOptions, tracks);
878
-
879
-            // In case where gUM is slow and resolves after the startAudio/VideoMuted coming from jicofo, we can be
880
-            // join unmuted even though jicofo had instruct us to mute, so let's respect that before passing the tracks
881
-            if (!browser.isWebKitBased()) {
882
-                if (room?.isStartAudioMuted()) {
883
-                    localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
884
-                }
885
-            }
886
-
887
-            if (room?.isStartVideoMuted()) {
888
-                localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.VIDEO);
889
-            }
890
-
891
-            // Do not add the tracks if the user has joined the call as a visitor.
892
-            if (iAmVisitor(state)) {
893
-                return Promise.resolve();
894
-            }
895
-
896
-            return this._setLocalAudioVideoStreams(localTracks);
873
+            return this._setLocalAudioVideoStreams(handleInitialTracks(initialOptions, localTracks));
897 874
         }
898 875
 
899 876
         const [ tracks, con ] = await this.createInitialLocalTracksAndConnect(roomName, initialOptions);
900 877
 
901 878
         this._initDeviceList(true);
902 879
 
903
-        return this.startConference(con, handleStartAudioMuted(initialOptions, tracks));
880
+        return this.startConference(con, handleInitialTracks(initialOptions, tracks));
904 881
     },
905 882
 
906 883
     /**

+ 63
- 51
react/features/base/tracks/functions.web.ts Bestand weergeven

@@ -1,8 +1,10 @@
1 1
 import { IStore } from '../../app/types';
2 2
 import { IStateful } from '../app/types';
3 3
 import { isMobileBrowser } from '../environment/utils';
4
-import JitsiMeetJS from '../lib-jitsi-meet';
4
+import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
5 5
 import { setAudioMuted } from '../media/actions';
6
+import { MEDIA_TYPE } from '../media/constants';
7
+import { getStartWithAudioMuted } from '../media/functions';
6 8
 import { toState } from '../redux/functions';
7 9
 import {
8 10
     getUserSelectedCameraDeviceId,
@@ -94,10 +96,10 @@ export function createLocalTracksF(options: ITrackOptions = {}, store?: IStore)
94 96
 }
95 97
 
96 98
 /**
97
- * Returns an object containing a promise which resolves with the created tracks &
98
- * the errors resulting from that process.
99
+ * Returns an object containing a promise which resolves with the created tracks and the errors resulting from that
100
+ * process.
99 101
  *
100
- * @returns {Promise<JitsiLocalTrack>}
102
+ * @returns {Promise<JitsiLocalTrack[]>}
101 103
  *
102 104
  * @todo Refactor to not use APP.
103 105
  */
@@ -106,7 +108,13 @@ export function createPrejoinTracks() {
106 108
     const initialDevices = [ 'audio' ];
107 109
     const requestedAudio = true;
108 110
     let requestedVideo = false;
109
-    const { startAudioOnly, startWithAudioMuted, startWithVideoMuted } = APP.store.getState()['features/base/settings'];
111
+    const { startAudioOnly, startWithVideoMuted } = APP.store.getState()['features/base/settings'];
112
+    const startWithAudioMuted = getStartWithAudioMuted(APP.store.getState());
113
+
114
+    // On Electron there is no permission prompt for granting permissions. That's why we don't need to
115
+    // spend much time displaying the overlay screen. If GUM is not resolved within 15 seconds it will
116
+    // probably never resolve.
117
+    const timeout = browser.isElectron() ? 15000 : 60000;
110 118
 
111 119
     // Always get a handle on the audio input device so that we have statistics even if the user joins the
112 120
     // conference muted. Previous implementation would only acquire the handle when the user first unmuted,
@@ -121,62 +129,66 @@ export function createPrejoinTracks() {
121 129
         requestedVideo = true;
122 130
     }
123 131
 
124
-    let tryCreateLocalTracks;
132
+    let tryCreateLocalTracks: any = Promise.resolve([]);
125 133
 
126
-    if (!requestedAudio && !requestedVideo) {
127
-        // Resolve with no tracks
128
-        tryCreateLocalTracks = Promise.resolve([]);
129
-    } else {
134
+    if (requestedAudio || requestedVideo) {
130 135
         tryCreateLocalTracks = createLocalTracksF({
131 136
             devices: initialDevices,
132
-            firePermissionPromptIsShownEvent: true
137
+            firePermissionPromptIsShownEvent: true,
138
+            timeout
133 139
         }, APP.store)
134
-                .catch((err: Error) => {
135
-                    if (requestedAudio && requestedVideo) {
140
+        .catch(async (err: Error) => {
141
+            if (err.name === JitsiTrackErrors.TIMEOUT && !browser.isElectron()) {
142
+                errors.audioAndVideoError = err;
136 143
 
137
-                        // Try audio only...
138
-                        errors.audioAndVideoError = err;
144
+                return [];
145
+            }
139 146
 
140
-                        return (
141
-                            createLocalTracksF({
142
-                                devices: [ 'audio' ],
143
-                                firePermissionPromptIsShownEvent: true
144
-                            }));
145
-                    } else if (requestedAudio && !requestedVideo) {
146
-                        errors.audioOnlyError = err;
147
+            // Retry with separate gUM calls.
148
+            const gUMPromises: any = [];
149
+            const tracks: any = [];
147 150
 
148
-                        return [];
149
-                    } else if (requestedVideo && !requestedAudio) {
150
-                        errors.videoOnlyError = err;
151
+            if (requestedAudio) {
152
+                gUMPromises.push(createLocalTracksF({
153
+                    devices: [ MEDIA_TYPE.AUDIO ],
154
+                    firePermissionPromptIsShownEvent: true,
155
+                    timeout
156
+                }));
157
+            }
151 158
 
152
-                        return [];
153
-                    }
154
-                    logger.error('Should never happen');
155
-                })
156
-                .catch((err: Error) => {
157
-                    // Log this just in case...
158
-                    if (!requestedAudio) {
159
-                        logger.error('The impossible just happened', err);
160
-                    }
161
-                    errors.audioOnlyError = err;
162
-
163
-                    // Try video only...
164
-                    return requestedVideo
165
-                        ? createLocalTracksF({
166
-                            devices: [ 'video' ],
167
-                            firePermissionPromptIsShownEvent: true
168
-                        })
169
-                        : [];
170
-                })
171
-                .catch((err: Error) => {
172
-                    // Log this just in case...
173
-                    if (!requestedVideo) {
174
-                        logger.error('The impossible just happened', err);
159
+            if (requestedVideo) {
160
+                gUMPromises.push(createLocalTracksF({
161
+                    devices: [ MEDIA_TYPE.VIDEO ],
162
+                    firePermissionPromptIsShownEvent: true,
163
+                    timeout
164
+                }));
165
+            }
166
+
167
+            const results = await Promise.allSettled(gUMPromises);
168
+            let errorMsg;
169
+
170
+            results.forEach((result, idx) => {
171
+                if (result.status === 'fulfilled') {
172
+                    tracks.push(result.value[0]);
173
+                } else {
174
+                    errorMsg = result.reason;
175
+                    const isAudio = idx === 0;
176
+
177
+                    logger.error(`${isAudio ? 'Audio' : 'Video'} track creation failed with error ${errorMsg}`);
178
+                    if (isAudio) {
179
+                        errors.audioOnlyError = errorMsg;
180
+                    } else {
181
+                        errors.videoOnlyError = errorMsg;
175 182
                     }
176
-                    errors.videoOnlyError = err;
183
+                }
184
+            });
185
+
186
+            if (errors.audioOnlyError && errors.videoOnlyError) {
187
+                errors.audioAndVideoError = errorMsg;
188
+            }
177 189
 
178
-                    return [];
179
-                });
190
+            return tracks;
191
+        });
180 192
     }
181 193
 
182 194
     return {

+ 16
- 16
react/features/prejoin/reducer.ts Bestand weergeven

@@ -165,31 +165,31 @@ ReducerRegistry.register<IPrejoinState>(
165 165
 function getStatusFromErrors(errors: {
166 166
     audioAndVideoError?: { message: string; };
167 167
     audioOnlyError?: { message: string; };
168
-    videoOnlyError?: Object; }
168
+    videoOnlyError?: { message: string; }; }
169 169
 ) {
170 170
     const { audioOnlyError, videoOnlyError, audioAndVideoError } = errors;
171 171
 
172 172
     if (audioAndVideoError) {
173
-        if (audioOnlyError) {
174
-            if (videoOnlyError) {
175
-                return {
176
-                    deviceStatusType: 'warning',
177
-                    deviceStatusText: 'prejoin.audioAndVideoError',
178
-                    rawError: audioAndVideoError.message
179
-                };
180
-            }
173
+        return {
174
+            deviceStatusType: 'warning',
175
+            deviceStatusText: 'prejoin.audioAndVideoError',
176
+            rawError: audioAndVideoError.message
177
+        };
178
+    }
181 179
 
182
-            return {
183
-                deviceStatusType: 'warning',
184
-                deviceStatusText: 'prejoin.audioOnlyError',
185
-                rawError: audioOnlyError.message
186
-            };
187
-        }
180
+    if (audioOnlyError) {
181
+        return {
182
+            deviceStatusType: 'warning',
183
+            deviceStatusText: 'prejoin.audioOnlyError',
184
+            rawError: audioOnlyError.message
185
+        };
186
+    }
188 187
 
188
+    if (videoOnlyError) {
189 189
         return {
190 190
             deviceStatusType: 'warning',
191 191
             deviceStatusText: 'prejoin.videoOnlyError',
192
-            rawError: audioAndVideoError.message
192
+            rawError: videoOnlyError.message
193 193
         };
194 194
     }
195 195
 

Laden…
Annuleren
Opslaan