Procházet zdrojové kódy

fix(safari-ios) Fix audio being lost on safari ios after phone call

dev1
Horatiu Muresan před 2 roky
rodič
revize
0947d98944
Žádný účet není propojen s e-mailovou adresou tvůrce revize

+ 1
- 7
JitsiMeetJS.js Zobrazit soubor

@@ -330,16 +330,10 @@ export default _mergeNamespaceAndModule({
330 330
                 if (!RTC.options.disableAudioLevels) {
331 331
                     for (let i = 0; i < tracks.length; i++) {
332 332
                         const track = tracks[i];
333
-                        const mStream = track.getOriginalStream();
334 333
 
335 334
                         if (track.getType() === MediaType.AUDIO) {
336
-                            Statistics.startLocalStats(mStream,
335
+                            Statistics.startLocalStats(track,
337 336
                                 track.setAudioLevel.bind(track));
338
-                            track.addEventListener(
339
-                                JitsiTrackEvents.LOCAL_TRACK_STOPPED,
340
-                                () => {
341
-                                    Statistics.stopLocalStats(mStream);
342
-                                });
343 337
                         }
344 338
                     }
345 339
                 }

+ 1
- 5
modules/detection/ActiveDeviceDetector.js Zobrazit soubor

@@ -32,12 +32,8 @@ export default function getActiveAudioDevice() {
32 32
                     // We expect a single device to be available when obtained from obtainAudioAndVideoPermissions
33 33
                     // that's  why only take p.value[0].
34 34
                     const track = tracks[0];
35
-                    const originalStream = track.getOriginalStream();
36 35
 
37
-                    Statistics.startLocalStats(originalStream, track.setAudioLevel.bind(track));
38
-                    track.addEventListener(JitsiTrackEvents.LOCAL_TRACK_STOPPED, () => {
39
-                        Statistics.stopLocalStats(originalStream);
40
-                    });
36
+                    Statistics.startLocalStats(track, track.setAudioLevel.bind(track));
41 37
 
42 38
                     return track;
43 39
                 });

+ 52
- 21
modules/statistics/LocalStatsCollector.js Zobrazit soubor

@@ -2,6 +2,8 @@
2 2
  * Provides statistics for the local stream.
3 3
  */
4 4
 
5
+const logger = require('@jitsi/logger').getLogger(__filename);
6
+
5 7
 /**
6 8
  * Size of the webaudio analyzer buffer.
7 9
  * @type {number}
@@ -16,21 +18,12 @@ const WEBAUDIO_ANALYZER_SMOOTING_TIME = 0.8;
16 18
 
17 19
 window.AudioContext = window.AudioContext || window.webkitAudioContext;
18 20
 
21
+/**
22
+ * The audio context.
23
+ * @type {AudioContext}
24
+ */
19 25
 let context = null;
20 26
 
21
-if (window.AudioContext) {
22
-    context = new AudioContext();
23
-
24
-    // XXX Not all browsers define a suspend method on AudioContext. As the
25
-    // invocation is at the (ES6 module) global execution level, it breaks the
26
-    // loading of the lib-jitsi-meet library in such browsers and, consequently,
27
-    // the loading of the very Web app that uses the lib-jitsi-meet library. For
28
-    // example, Google Chrome 40 on Android does not define the method but we
29
-    // still want to be able to load the lib-jitsi-meet library there and
30
-    // display a page which notifies the user that the Web app is not supported
31
-    // there.
32
-    context.suspend && context.suspend();
33
-}
34 27
 
35 28
 /**
36 29
  * Converts time domain data array to audio level.
@@ -88,6 +81,8 @@ export default function LocalStatsCollector(stream, interval, callback) {
88 81
     this.intervalMilis = interval;
89 82
     this.audioLevel = 0;
90 83
     this.callback = callback;
84
+    this.source = null;
85
+    this.analyser = null;
91 86
 }
92 87
 
93 88
 /**
@@ -97,21 +92,22 @@ LocalStatsCollector.prototype.start = function() {
97 92
     if (!LocalStatsCollector.isLocalStatsSupported()) {
98 93
         return;
99 94
     }
95
+
100 96
     context.resume();
101
-    const analyser = context.createAnalyser();
97
+    this.analyser = context.createAnalyser();
102 98
 
103
-    analyser.smoothingTimeConstant = WEBAUDIO_ANALYZER_SMOOTING_TIME;
104
-    analyser.fftSize = WEBAUDIO_ANALYZER_FFT_SIZE;
99
+    this.analyser.smoothingTimeConstant = WEBAUDIO_ANALYZER_SMOOTING_TIME;
100
+    this.analyser.fftSize = WEBAUDIO_ANALYZER_FFT_SIZE;
105 101
 
106
-    const source = context.createMediaStreamSource(this.stream);
102
+    this.source = context.createMediaStreamSource(this.stream);
107 103
 
108
-    source.connect(analyser);
104
+    this.source.connect(this.analyser);
109 105
 
110 106
     this.intervalId = setInterval(
111 107
         () => {
112
-            const array = new Uint8Array(analyser.frequencyBinCount);
108
+            const array = new Uint8Array(this.analyser.frequencyBinCount);
113 109
 
114
-            analyser.getByteTimeDomainData(array);
110
+            this.analyser.getByteTimeDomainData(array);
115 111
             const audioLevel = timeDomainDataToAudioLevel(array);
116 112
 
117 113
             // Set the audio levels always as NoAudioSignalDetection now
@@ -133,6 +129,11 @@ LocalStatsCollector.prototype.stop = function() {
133 129
         clearInterval(this.intervalId);
134 130
         this.intervalId = null;
135 131
     }
132
+
133
+    this.analyser?.disconnect();
134
+    this.analyser = null;
135
+    this.source?.disconnect();
136
+    this.source = null;
136 137
 };
137 138
 
138 139
 /**
@@ -142,5 +143,35 @@ LocalStatsCollector.prototype.stop = function() {
142 143
  * @returns {boolean}
143 144
  */
144 145
 LocalStatsCollector.isLocalStatsSupported = function() {
145
-    return Boolean(context);
146
+    return Boolean(window.AudioContext);
147
+};
148
+
149
+/**
150
+ * Disconnects the audio context.
151
+ */
152
+LocalStatsCollector.disconnectAudioContext = async function() {
153
+    if (context) {
154
+        logger.info('Disconnecting audio context');
155
+        await context.close();
156
+        context = null;
157
+    }
146 158
 };
159
+
160
+/**
161
+ * Connects the audio context.
162
+ */
163
+LocalStatsCollector.connectAudioContext = function() {
164
+    if (!LocalStatsCollector.isLocalStatsSupported()) {
165
+        return;
166
+    }
167
+
168
+    logger.info('Connecting audio context');
169
+    context = new AudioContext();
170
+
171
+    context.suspend();
172
+};
173
+
174
+/**
175
+ * Initialize the audio context on startup.
176
+ */
177
+LocalStatsCollector.connectAudioContext();

+ 42
- 2
modules/statistics/statistics.js Zobrazit soubor

@@ -2,6 +2,7 @@ import EventEmitter from 'events';
2 2
 
3 3
 import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
4 4
 import JitsiTrackError from '../../JitsiTrackError';
5
+import { JitsiTrackEvents } from '../../JitsiTrackEvents';
5 6
 import { FEEDBACK } from '../../service/statistics/AnalyticsEvents';
6 7
 import * as StatisticsEvents from '../../service/statistics/Events';
7 8
 import browser from '../browser';
@@ -238,10 +239,47 @@ Statistics.prototype.startRemoteStats = function(peerconnection) {
238 239
 
239 240
 Statistics.localStats = [];
240 241
 
241
-Statistics.startLocalStats = function(stream, callback) {
242
+Statistics.startLocalStats = function(track, callback) {
243
+    if (browser.isIosBrowser()) {
244
+        // On iOS browsers audio is lost if the audio input device is in use by another app
245
+        // https://bugs.webkit.org/show_bug.cgi?id=233473
246
+        // The culprit was using the AudioContext, so now we close the AudioContext during
247
+        // the track being muted, and re-instantiate it afterwards.
248
+        track.addEventListener(
249
+        JitsiTrackEvents.NO_DATA_FROM_SOURCE,
250
+
251
+        /**
252
+         * Closes AudioContext on no audio data, and enables it on data received again.
253
+         *
254
+         * @param {boolean} value - Whether we receive audio data or not.
255
+         */
256
+        async value => {
257
+            if (value) {
258
+                for (const localStat of Statistics.localStats) {
259
+                    localStat.stop();
260
+                }
261
+
262
+                await LocalStats.disconnectAudioContext();
263
+            } else {
264
+                LocalStats.connectAudioContext();
265
+                for (const localStat of Statistics.localStats) {
266
+                    localStat.start();
267
+                }
268
+            }
269
+        });
270
+    }
271
+
242 272
     if (!Statistics.audioLevelsEnabled) {
243 273
         return;
244 274
     }
275
+
276
+    track.addEventListener(
277
+        JitsiTrackEvents.LOCAL_TRACK_STOPPED,
278
+        () => {
279
+            Statistics.stopLocalStats(track);
280
+        });
281
+
282
+    const stream = track.getOriginalStream();
245 283
     const localStats = new LocalStats(stream, Statistics.audioLevelsInterval,
246 284
         callback);
247 285
 
@@ -388,11 +426,13 @@ Statistics.prototype.dispose = function() {
388 426
     }
389 427
 };
390 428
 
391
-Statistics.stopLocalStats = function(stream) {
429
+Statistics.stopLocalStats = function(track) {
392 430
     if (!Statistics.audioLevelsEnabled) {
393 431
         return;
394 432
     }
395 433
 
434
+    const stream = track.getOriginalStream();
435
+
396 436
     for (let i = 0; i < Statistics.localStats.length; i++) {
397 437
         if (Statistics.localStats[i].stream === stream) {
398 438
             const localStats = Statistics.localStats.splice(i, 1);

+ 10
- 0
types/auto/modules/statistics/LocalStatsCollector.d.ts Zobrazit soubor

@@ -22,6 +22,8 @@ declare class LocalStatsCollector {
22 22
     intervalMilis: any;
23 23
     audioLevel: number;
24 24
     callback: any;
25
+    source: MediaStreamAudioSourceNode;
26
+    analyser: AnalyserNode;
25 27
     /**
26 28
      * Starts the collecting the statistics.
27 29
      */
@@ -39,5 +41,13 @@ declare namespace LocalStatsCollector {
39 41
      * @returns {boolean}
40 42
      */
41 43
     function isLocalStatsSupported(): boolean;
44
+    /**
45
+     * Disconnects the audio context.
46
+     */
47
+    function disconnectAudioContext(): Promise<void>;
48
+    /**
49
+     * Connects the audio context.
50
+     */
51
+    function connectAudioContext(): void;
42 52
 }
43 53
 export default LocalStatsCollector;

+ 2
- 2
types/auto/modules/statistics/statistics.d.ts Zobrazit soubor

@@ -244,8 +244,8 @@ declare namespace Statistics {
244 244
     export { analytics };
245 245
     export const instances: any;
246 246
     export const localStats: any[];
247
-    export function startLocalStats(stream: any, callback: any): void;
248
-    export function stopLocalStats(stream: any): void;
247
+    export function startLocalStats(track: any, callback: any): void;
248
+    export function stopLocalStats(track: any): void;
249 249
     /**
250 250
      * Obtains the list of *all* {@link CallStats} instances collected from every
251 251
      * valid {@link Statistics} instance.

Načítá se…
Zrušit
Uložit