ソースを参照

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

dev1
Horatiu Muresan 3年前
コミット
0947d98944
コミッターのメールアドレスに関連付けられたアカウントが存在しません

+ 1
- 7
JitsiMeetJS.js ファイルの表示

330
                 if (!RTC.options.disableAudioLevels) {
330
                 if (!RTC.options.disableAudioLevels) {
331
                     for (let i = 0; i < tracks.length; i++) {
331
                     for (let i = 0; i < tracks.length; i++) {
332
                         const track = tracks[i];
332
                         const track = tracks[i];
333
-                        const mStream = track.getOriginalStream();
334
 
333
 
335
                         if (track.getType() === MediaType.AUDIO) {
334
                         if (track.getType() === MediaType.AUDIO) {
336
-                            Statistics.startLocalStats(mStream,
335
+                            Statistics.startLocalStats(track,
337
                                 track.setAudioLevel.bind(track));
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 ファイルの表示

32
                     // We expect a single device to be available when obtained from obtainAudioAndVideoPermissions
32
                     // We expect a single device to be available when obtained from obtainAudioAndVideoPermissions
33
                     // that's  why only take p.value[0].
33
                     // that's  why only take p.value[0].
34
                     const track = tracks[0];
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
                     return track;
38
                     return track;
43
                 });
39
                 });

+ 52
- 21
modules/statistics/LocalStatsCollector.js ファイルの表示

2
  * Provides statistics for the local stream.
2
  * Provides statistics for the local stream.
3
  */
3
  */
4
 
4
 
5
+const logger = require('@jitsi/logger').getLogger(__filename);
6
+
5
 /**
7
 /**
6
  * Size of the webaudio analyzer buffer.
8
  * Size of the webaudio analyzer buffer.
7
  * @type {number}
9
  * @type {number}
16
 
18
 
17
 window.AudioContext = window.AudioContext || window.webkitAudioContext;
19
 window.AudioContext = window.AudioContext || window.webkitAudioContext;
18
 
20
 
21
+/**
22
+ * The audio context.
23
+ * @type {AudioContext}
24
+ */
19
 let context = null;
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
  * Converts time domain data array to audio level.
29
  * Converts time domain data array to audio level.
88
     this.intervalMilis = interval;
81
     this.intervalMilis = interval;
89
     this.audioLevel = 0;
82
     this.audioLevel = 0;
90
     this.callback = callback;
83
     this.callback = callback;
84
+    this.source = null;
85
+    this.analyser = null;
91
 }
86
 }
92
 
87
 
93
 /**
88
 /**
97
     if (!LocalStatsCollector.isLocalStatsSupported()) {
92
     if (!LocalStatsCollector.isLocalStatsSupported()) {
98
         return;
93
         return;
99
     }
94
     }
95
+
100
     context.resume();
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
     this.intervalId = setInterval(
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
             const audioLevel = timeDomainDataToAudioLevel(array);
111
             const audioLevel = timeDomainDataToAudioLevel(array);
116
 
112
 
117
             // Set the audio levels always as NoAudioSignalDetection now
113
             // Set the audio levels always as NoAudioSignalDetection now
133
         clearInterval(this.intervalId);
129
         clearInterval(this.intervalId);
134
         this.intervalId = null;
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
  * @returns {boolean}
143
  * @returns {boolean}
143
  */
144
  */
144
 LocalStatsCollector.isLocalStatsSupported = function() {
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 ファイルの表示

2
 
2
 
3
 import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
3
 import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
4
 import JitsiTrackError from '../../JitsiTrackError';
4
 import JitsiTrackError from '../../JitsiTrackError';
5
+import { JitsiTrackEvents } from '../../JitsiTrackEvents';
5
 import { FEEDBACK } from '../../service/statistics/AnalyticsEvents';
6
 import { FEEDBACK } from '../../service/statistics/AnalyticsEvents';
6
 import * as StatisticsEvents from '../../service/statistics/Events';
7
 import * as StatisticsEvents from '../../service/statistics/Events';
7
 import browser from '../browser';
8
 import browser from '../browser';
238
 
239
 
239
 Statistics.localStats = [];
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
     if (!Statistics.audioLevelsEnabled) {
272
     if (!Statistics.audioLevelsEnabled) {
243
         return;
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
     const localStats = new LocalStats(stream, Statistics.audioLevelsInterval,
283
     const localStats = new LocalStats(stream, Statistics.audioLevelsInterval,
246
         callback);
284
         callback);
247
 
285
 
388
     }
426
     }
389
 };
427
 };
390
 
428
 
391
-Statistics.stopLocalStats = function(stream) {
429
+Statistics.stopLocalStats = function(track) {
392
     if (!Statistics.audioLevelsEnabled) {
430
     if (!Statistics.audioLevelsEnabled) {
393
         return;
431
         return;
394
     }
432
     }
395
 
433
 
434
+    const stream = track.getOriginalStream();
435
+
396
     for (let i = 0; i < Statistics.localStats.length; i++) {
436
     for (let i = 0; i < Statistics.localStats.length; i++) {
397
         if (Statistics.localStats[i].stream === stream) {
437
         if (Statistics.localStats[i].stream === stream) {
398
             const localStats = Statistics.localStats.splice(i, 1);
438
             const localStats = Statistics.localStats.splice(i, 1);

+ 10
- 0
types/auto/modules/statistics/LocalStatsCollector.d.ts ファイルの表示

22
     intervalMilis: any;
22
     intervalMilis: any;
23
     audioLevel: number;
23
     audioLevel: number;
24
     callback: any;
24
     callback: any;
25
+    source: MediaStreamAudioSourceNode;
26
+    analyser: AnalyserNode;
25
     /**
27
     /**
26
      * Starts the collecting the statistics.
28
      * Starts the collecting the statistics.
27
      */
29
      */
39
      * @returns {boolean}
41
      * @returns {boolean}
40
      */
42
      */
41
     function isLocalStatsSupported(): boolean;
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
 export default LocalStatsCollector;
53
 export default LocalStatsCollector;

+ 2
- 2
types/auto/modules/statistics/statistics.d.ts ファイルの表示

244
     export { analytics };
244
     export { analytics };
245
     export const instances: any;
245
     export const instances: any;
246
     export const localStats: any[];
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
      * Obtains the list of *all* {@link CallStats} instances collected from every
250
      * Obtains the list of *all* {@link CallStats} instances collected from every
251
      * valid {@link Statistics} instance.
251
      * valid {@link Statistics} instance.

読み込み中…
キャンセル
保存