Browse Source

feat(screenSharing): Add system audio screen sharing via audio mixer

release-8443
Andrei Gavrilescu 5 years ago
parent
commit
a7950f8ebb
No account linked to committer's email address

+ 11
- 0
JitsiMeetJS.js View File

@@ -1,6 +1,7 @@
1 1
 /* global __filename */
2 2
 
3 3
 import getActiveAudioDevice from './modules/detection/ActiveDeviceDetector';
4
+import AudioMixer from './modules/webaudio/AudioMixer';
4 5
 import * as DetectionEvents from './modules/detection/DetectionEvents';
5 6
 import TrackVADEmitter from './modules/detection/TrackVADEmitter';
6 7
 import { createGetUserMediaEvent } from './service/statistics/AnalyticsEvents';
@@ -516,6 +517,16 @@ export default _mergeNamespaceAndModule({
516 517
         return TrackVADEmitter.create(localAudioDeviceId, sampleRate, vadProcessor);
517 518
     },
518 519
 
520
+    /**
521
+     * Create AudioMixer, which is essentially a wrapper over web audio ChannelMergerNode. It essentially allows the
522
+     * user to mix multiple MediaStreams into a single one.
523
+     *
524
+     * @returns {AudioMixer}
525
+     */
526
+    createAudioMixer() {
527
+        return new AudioMixer();
528
+    },
529
+
519 530
     /**
520 531
      * Go through all audio devices on the system and return one that is active, i.e. has audio signal.
521 532
      *

+ 3
- 1
modules/RTC/JitsiLocalTrack.js View File

@@ -375,7 +375,9 @@ export default class JitsiLocalTrack extends JitsiTrack {
375 375
             return Promise.reject(new Error('setEffect already in progress!'));
376 376
         }
377 377
 
378
-        if (this.isMuted()) {
378
+        // In case we have an audio track that is being enhanced with an effect, we still want it to be applied,
379
+        // even if the track is muted. Where as for video the actual track doesn't exists if it's muted.
380
+        if (this.isMuted() && !this.isAudioTrack()) {
379 381
             this._streamEffect = effect;
380 382
 
381 383
             return Promise.resolve();

+ 26
- 7
modules/RTC/RTCUtils.js View File

@@ -1287,13 +1287,32 @@ class RTCUtils extends Listenable {
1287 1287
 
1288 1288
             const { stream, sourceId, sourceType } = desktopStream;
1289 1289
 
1290
-            mediaStreamsMetaData.push({
1291
-                stream,
1292
-                sourceId,
1293
-                sourceType,
1294
-                track: stream.getVideoTracks()[0],
1295
-                videoType: VideoType.DESKTOP
1296
-            });
1290
+            const desktopAudioTracks = stream.getAudioTracks();
1291
+
1292
+            if (desktopAudioTracks.length) {
1293
+                const desktopAudioStream = new MediaStream(desktopAudioTracks);
1294
+
1295
+                mediaStreamsMetaData.push({
1296
+                    stream: desktopAudioStream,
1297
+                    sourceId,
1298
+                    sourceType,
1299
+                    track: desktopAudioStream.getAudioTracks()[0]
1300
+                });
1301
+            }
1302
+
1303
+            const desktopVideoTracks = stream.getVideoTracks();
1304
+
1305
+            if (desktopVideoTracks.length) {
1306
+                const desktopVideoStream = new MediaStream(desktopVideoTracks);
1307
+
1308
+                mediaStreamsMetaData.push({
1309
+                    stream: desktopVideoStream,
1310
+                    sourceId,
1311
+                    sourceType,
1312
+                    track: desktopVideoStream.getVideoTracks()[0],
1313
+                    videoType: VideoType.DESKTOP
1314
+                });
1315
+            }
1297 1316
         };
1298 1317
 
1299 1318
         /**

+ 8
- 3
modules/RTC/ScreenObtainer.js View File

@@ -312,15 +312,20 @@ const ScreenObtainer = {
312 312
             getDisplayMedia = navigator.mediaDevices.getDisplayMedia.bind(navigator.mediaDevices);
313 313
         }
314 314
 
315
-        getDisplayMedia({ video: true })
315
+        getDisplayMedia({ video: true,
316
+            audio: true })
316 317
             .then(stream => {
317 318
                 let applyConstraintsPromise;
318 319
 
319 320
                 if (stream
320 321
                     && stream.getTracks()
321 322
                     && stream.getTracks().length > 0) {
322
-                    applyConstraintsPromise = stream.getTracks()[0]
323
-                        .applyConstraints(options.trackOptions);
323
+                    const videoTrack = stream.getVideoTracks()[0];
324
+
325
+                    // Apply video track constraint.
326
+                    if (videoTrack) {
327
+                        applyConstraintsPromise = videoTrack.applyConstraints(options.trackOptions);
328
+                    }
324 329
                 } else {
325 330
                     applyConstraintsPromise = Promise.resolve();
326 331
                 }

+ 1
- 1
modules/detection/TrackVADEmitter.js View File

@@ -2,7 +2,7 @@ import EventEmitter from 'events';
2 2
 
3 3
 import RTC from '../RTC/RTC';
4 4
 
5
-import { createAudioContext } from './webaudio/WebAudioUtils';
5
+import { createAudioContext } from '../webaudio/WebAudioUtils';
6 6
 import { VAD_SCORE_PUBLISHED } from './DetectionEvents';
7 7
 
8 8
 /**

+ 91
- 0
modules/webaudio/AudioMixer.js View File

@@ -0,0 +1,91 @@
1
+/* global
2
+    __filename
3
+*/
4
+
5
+import { getLogger } from 'jitsi-meet-logger';
6
+import { createAudioContext } from './WebAudioUtils';
7
+
8
+const logger = getLogger(__filename);
9
+
10
+/**
11
+ * The AudioMixer, as the name implies, mixes a number of MediaStreams containing audio tracks into a single
12
+ * MediaStream.
13
+ */
14
+export default class AudioMixer {
15
+    /**
16
+     * Create AudioMixer instance.
17
+     */
18
+    constructor() {
19
+        this._started = false;
20
+        this._streamsToMix = [];
21
+    }
22
+
23
+    /**
24
+     * Add audio MediaStream to be mixed, if the stream doesn't contain any audio tracks it will be ignored.
25
+     *
26
+     * @param {MediaStream} stream - MediaStream to be mixed.
27
+     */
28
+    addMediaStream(stream) {
29
+        if (!stream.getAudioTracks()) {
30
+            logger.warn('Added MediaStream doesn\'t contain audio tracks.');
31
+        }
32
+
33
+        this._streamsToMix.push(stream);
34
+    }
35
+
36
+    /**
37
+     * At this point a WebAudio ChannelMergerNode is created and and the two associated MediaStreams are connected to
38
+     * it; the resulting mixed MediaStream is returned.
39
+     *
40
+     * @returns {MediaStream} - MediaStream containing added streams mixed together, or null if no MediaStream
41
+     * is added.
42
+     */
43
+    start() {
44
+        // If the mixer was already started just return the existing mixed stream.
45
+        if (this._started) {
46
+            return this._mixedMSD.stream;
47
+        }
48
+
49
+        this._audioContext = createAudioContext();
50
+
51
+        if (!this._streamsToMix.length) {
52
+            logger.warn('No MediaStream\'s added to AudioMixer, nothing will happen.');
53
+
54
+            return null;
55
+        }
56
+
57
+        this._started = true;
58
+
59
+        // Create ChannelMergerNode and connect all MediaStreams to it.
60
+        this._channelMerger = this._audioContext.createChannelMerger(this._streamsToMix.length);
61
+
62
+        for (const stream of this._streamsToMix) {
63
+            const streamMSS = this._audioContext.createMediaStreamSource(stream);
64
+
65
+            streamMSS.connect(this._channelMerger);
66
+        }
67
+
68
+        this._mixedMSD = this._audioContext.createMediaStreamDestination();
69
+        this._channelMerger.connect(this._mixedMSD);
70
+
71
+        return this._mixedMSD.stream;
72
+    }
73
+
74
+    /**
75
+     * Disconnect the ChannelMergerNode stopping the audio mix process.References to MediaStreams are also cleared.
76
+     *
77
+     * @returns {void}
78
+     */
79
+    reset() {
80
+        this._started = false;
81
+        this._streamsToMix = [];
82
+
83
+        if (this._channelMerger) {
84
+            this._channelMerger.disconnect();
85
+        }
86
+
87
+        if (this._audioContext) {
88
+            this._audioContext = undefined;
89
+        }
90
+    }
91
+}

modules/detection/webaudio/WebAudioUtils.js → modules/webaudio/WebAudioUtils.js View File


Loading…
Cancel
Save