Selaa lähdekoodia

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

release-8443
Andrei Gavrilescu 5 vuotta sitten
vanhempi
commit
a7950f8ebb
No account linked to committer's email address

+ 11
- 0
JitsiMeetJS.js Näytä tiedosto

1
 /* global __filename */
1
 /* global __filename */
2
 
2
 
3
 import getActiveAudioDevice from './modules/detection/ActiveDeviceDetector';
3
 import getActiveAudioDevice from './modules/detection/ActiveDeviceDetector';
4
+import AudioMixer from './modules/webaudio/AudioMixer';
4
 import * as DetectionEvents from './modules/detection/DetectionEvents';
5
 import * as DetectionEvents from './modules/detection/DetectionEvents';
5
 import TrackVADEmitter from './modules/detection/TrackVADEmitter';
6
 import TrackVADEmitter from './modules/detection/TrackVADEmitter';
6
 import { createGetUserMediaEvent } from './service/statistics/AnalyticsEvents';
7
 import { createGetUserMediaEvent } from './service/statistics/AnalyticsEvents';
516
         return TrackVADEmitter.create(localAudioDeviceId, sampleRate, vadProcessor);
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
      * Go through all audio devices on the system and return one that is active, i.e. has audio signal.
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 Näytä tiedosto

375
             return Promise.reject(new Error('setEffect already in progress!'));
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
             this._streamEffect = effect;
381
             this._streamEffect = effect;
380
 
382
 
381
             return Promise.resolve();
383
             return Promise.resolve();

+ 26
- 7
modules/RTC/RTCUtils.js Näytä tiedosto

1287
 
1287
 
1288
             const { stream, sourceId, sourceType } = desktopStream;
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 Näytä tiedosto

312
             getDisplayMedia = navigator.mediaDevices.getDisplayMedia.bind(navigator.mediaDevices);
312
             getDisplayMedia = navigator.mediaDevices.getDisplayMedia.bind(navigator.mediaDevices);
313
         }
313
         }
314
 
314
 
315
-        getDisplayMedia({ video: true })
315
+        getDisplayMedia({ video: true,
316
+            audio: true })
316
             .then(stream => {
317
             .then(stream => {
317
                 let applyConstraintsPromise;
318
                 let applyConstraintsPromise;
318
 
319
 
319
                 if (stream
320
                 if (stream
320
                     && stream.getTracks()
321
                     && stream.getTracks()
321
                     && stream.getTracks().length > 0) {
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
                 } else {
329
                 } else {
325
                     applyConstraintsPromise = Promise.resolve();
330
                     applyConstraintsPromise = Promise.resolve();
326
                 }
331
                 }

+ 1
- 1
modules/detection/TrackVADEmitter.js Näytä tiedosto

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

+ 91
- 0
modules/webaudio/AudioMixer.js Näytä tiedosto

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 Näytä tiedosto


Loading…
Peruuta
Tallenna