|
@@ -1,6 +1,5 @@
|
1
|
|
-/* global $,
|
|
1
|
+/* global
|
2
|
2
|
__filename,
|
3
|
|
- attachMediaStream,
|
4
|
3
|
MediaStreamTrack,
|
5
|
4
|
RTCIceCandidate: true,
|
6
|
5
|
RTCPeerConnection,
|
|
@@ -28,13 +27,6 @@ import VideoType from '../../service/RTC/VideoType';
|
28
|
27
|
|
29
|
28
|
const logger = getLogger(__filename);
|
30
|
29
|
|
31
|
|
-// XXX Don't require Temasys unless it's to be used because it doesn't run on
|
32
|
|
-// React Native, for example.
|
33
|
|
-const AdapterJS
|
34
|
|
- = browser.isTemasysPluginUsed()
|
35
|
|
- ? require('./adapter.screenshare')
|
36
|
|
- : undefined;
|
37
|
|
-
|
38
|
30
|
// Require adapter only for certain browsers. This is being done for
|
39
|
31
|
// react-native, which has its own shims, and while browsers are being migrated
|
40
|
32
|
// over to use adapter's shims.
|
|
@@ -113,7 +105,7 @@ const featureDetectionAudioEl = document.createElement('audio');
|
113
|
105
|
const isAudioOutputDeviceChangeAvailable
|
114
|
106
|
= typeof featureDetectionAudioEl.setSinkId !== 'undefined';
|
115
|
107
|
|
116
|
|
-let currentlyAvailableMediaDevices;
|
|
108
|
+let availableDevices;
|
117
|
109
|
|
118
|
110
|
/**
|
119
|
111
|
* "rawEnumerateDevicesWithCallback" will be initialized only after WebRTC is
|
|
@@ -123,7 +115,7 @@ let currentlyAvailableMediaDevices;
|
123
|
115
|
let rawEnumerateDevicesWithCallback;
|
124
|
116
|
|
125
|
117
|
/**
|
126
|
|
- *
|
|
118
|
+ * Initialize {@link rawEnumerateDevicesWithCallback}.
|
127
|
119
|
*/
|
128
|
120
|
function initRawEnumerateDevicesWithCallback() {
|
129
|
121
|
rawEnumerateDevicesWithCallback
|
|
@@ -134,19 +126,13 @@ function initRawEnumerateDevicesWithCallback() {
|
134
|
126
|
() => callback([]));
|
135
|
127
|
}
|
136
|
128
|
|
137
|
|
- // Safari:
|
138
|
|
- // "ReferenceError: Can't find variable: MediaStreamTrack" when
|
139
|
|
- // Temasys plugin is not installed yet, have to delay this call
|
140
|
|
- // until WebRTC is ready.
|
141
|
|
- : typeof MediaStreamTrack !== 'undefined'
|
142
|
|
- && MediaStreamTrack.getSources
|
143
|
|
- ? function(callback) {
|
144
|
|
- MediaStreamTrack.getSources(
|
145
|
|
- sources =>
|
146
|
|
- callback(
|
147
|
|
- sources.map(convertMediaStreamTrackSource)));
|
148
|
|
- }
|
149
|
|
- : undefined;
|
|
129
|
+ // react-native-webrtc
|
|
130
|
+ : function(callback) {
|
|
131
|
+ MediaStreamTrack.getSources(
|
|
132
|
+ sources =>
|
|
133
|
+ callback(
|
|
134
|
+ sources.map(convertMediaStreamTrackSource)));
|
|
135
|
+ };
|
150
|
136
|
}
|
151
|
137
|
|
152
|
138
|
// TODO: currently no browser supports 'devicechange' event even in nightly
|
|
@@ -228,9 +214,8 @@ function getConstraints(um, options = {}) {
|
228
|
214
|
// @see https://github.com/jitsi/lib-jitsi-meet/pull/136
|
229
|
215
|
const isNewStyleConstraintsSupported
|
230
|
216
|
= browser.isFirefox()
|
231
|
|
- || browser.isEdge()
|
232
|
|
- || browser.isReactNative()
|
233
|
|
- || browser.isTemasysPluginUsed();
|
|
217
|
+ || browser.isEdge()
|
|
218
|
+ || browser.isReactNative();
|
234
|
219
|
|
235
|
220
|
if (um.indexOf('video') >= 0) {
|
236
|
221
|
// same behaviour as true
|
|
@@ -332,14 +317,6 @@ function getConstraints(um, options = {}) {
|
332
|
317
|
optional: []
|
333
|
318
|
};
|
334
|
319
|
|
335
|
|
- } else if (browser.isTemasysPluginUsed()) {
|
336
|
|
- constraints.video = {
|
337
|
|
- optional: [
|
338
|
|
- {
|
339
|
|
- sourceId: AdapterJS.WebRTCPlugin.plugin.screensharingKey
|
340
|
|
- }
|
341
|
|
- ]
|
342
|
|
- };
|
343
|
320
|
} else if (browser.isFirefox()) {
|
344
|
321
|
constraints.video = {
|
345
|
322
|
mozMediaSource: 'window',
|
|
@@ -353,7 +330,7 @@ function getConstraints(um, options = {}) {
|
353
|
330
|
} else {
|
354
|
331
|
const errmsg
|
355
|
332
|
= '\'screen\' WebRTC media source is supported only in Chrome'
|
356
|
|
- + ' and with Temasys plugin';
|
|
333
|
+ + ' and Firefox';
|
357
|
334
|
|
358
|
335
|
GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));
|
359
|
336
|
logger.error(errmsg);
|
|
@@ -549,7 +526,7 @@ function setAvailableDevices(um, stream) {
|
549
|
526
|
* @returns {boolean} - true if list is different, false otherwise.
|
550
|
527
|
*/
|
551
|
528
|
function compareAvailableMediaDevices(newDevices) {
|
552
|
|
- if (newDevices.length !== currentlyAvailableMediaDevices.length) {
|
|
529
|
+ if (newDevices.length !== availableDevices.length) {
|
553
|
530
|
return true;
|
554
|
531
|
}
|
555
|
532
|
|
|
@@ -557,7 +534,7 @@ function compareAvailableMediaDevices(newDevices) {
|
557
|
534
|
|
558
|
535
|
return (
|
559
|
536
|
newDevices.map(mediaDeviceInfoToJSON).sort().join('')
|
560
|
|
- !== currentlyAvailableMediaDevices
|
|
537
|
+ !== availableDevices
|
561
|
538
|
.map(mediaDeviceInfoToJSON).sort().join(''));
|
562
|
539
|
|
563
|
540
|
/* eslint-enable newline-per-chained-call */
|
|
@@ -591,8 +568,8 @@ function pollForAvailableMediaDevices() {
|
591
|
568
|
rawEnumerateDevicesWithCallback(ds => {
|
592
|
569
|
// We don't fire RTCEvents.DEVICE_LIST_CHANGED for the first time
|
593
|
570
|
// we call enumerateDevices(). This is the initial step.
|
594
|
|
- if (typeof currentlyAvailableMediaDevices === 'undefined') {
|
595
|
|
- currentlyAvailableMediaDevices = ds.slice(0);
|
|
571
|
+ if (typeof availableDevices === 'undefined') {
|
|
572
|
+ availableDevices = ds.slice(0);
|
596
|
573
|
} else if (compareAvailableMediaDevices(ds)) {
|
597
|
574
|
onMediaDevicesListChanged(ds);
|
598
|
575
|
}
|
|
@@ -643,17 +620,17 @@ function sendDeviceListToAnalytics(deviceList) {
|
643
|
620
|
* @emits RTCEvents.DEVICE_LIST_CHANGED
|
644
|
621
|
*/
|
645
|
622
|
function onMediaDevicesListChanged(devicesReceived) {
|
646
|
|
- currentlyAvailableMediaDevices = devicesReceived.slice(0);
|
|
623
|
+ availableDevices = devicesReceived.slice(0);
|
647
|
624
|
logger.info(
|
648
|
625
|
'list of media devices has changed:',
|
649
|
|
- currentlyAvailableMediaDevices);
|
|
626
|
+ availableDevices);
|
650
|
627
|
|
651
|
|
- sendDeviceListToAnalytics(currentlyAvailableMediaDevices);
|
|
628
|
+ sendDeviceListToAnalytics(availableDevices);
|
652
|
629
|
|
653
|
630
|
const videoInputDevices
|
654
|
|
- = currentlyAvailableMediaDevices.filter(d => d.kind === 'videoinput');
|
|
631
|
+ = availableDevices.filter(d => d.kind === 'videoinput');
|
655
|
632
|
const audioInputDevices
|
656
|
|
- = currentlyAvailableMediaDevices.filter(d => d.kind === 'audioinput');
|
|
633
|
+ = availableDevices.filter(d => d.kind === 'audioinput');
|
657
|
634
|
const videoInputDevicesWithEmptyLabels
|
658
|
635
|
= videoInputDevices.filter(d => d.label === '');
|
659
|
636
|
const audioInputDevicesWithEmptyLabels
|
|
@@ -720,16 +697,6 @@ function wrapGetUserMedia(getUserMedia, usePromises = false) {
|
720
|
697
|
return gUM;
|
721
|
698
|
}
|
722
|
699
|
|
723
|
|
-/**
|
724
|
|
- * Use old MediaStreamTrack to get devices list and
|
725
|
|
- * convert it to enumerateDevices format.
|
726
|
|
- * @param {Function} callback function to call when received devices list.
|
727
|
|
- */
|
728
|
|
-function enumerateDevicesThroughMediaStreamTrack(callback) {
|
729
|
|
- MediaStreamTrack.getSources(
|
730
|
|
- sources => callback(sources.map(convertMediaStreamTrackSource)));
|
731
|
|
-}
|
732
|
|
-
|
733
|
700
|
/**
|
734
|
701
|
* Converts MediaStreamTrack Source to enumerateDevices format.
|
735
|
702
|
* @param {Object} source
|
|
@@ -976,8 +943,6 @@ class RTCUtils extends Listenable {
|
976
|
943
|
if (element) {
|
977
|
944
|
element.srcObject = stream;
|
978
|
945
|
}
|
979
|
|
-
|
980
|
|
- return element;
|
981
|
946
|
});
|
982
|
947
|
|
983
|
948
|
this.getStreamID = stream => stream.id;
|
|
@@ -1043,8 +1008,6 @@ class RTCUtils extends Listenable {
|
1043
|
1008
|
this.attachMediaStream
|
1044
|
1009
|
= wrapAttachMediaStream((element, stream) => {
|
1045
|
1010
|
defaultSetVideoSrc(element, stream);
|
1046
|
|
-
|
1047
|
|
- return element;
|
1048
|
1011
|
});
|
1049
|
1012
|
|
1050
|
1013
|
// ORTC does not generate remote MediaStreams so those are
|
|
@@ -1065,75 +1028,6 @@ class RTCUtils extends Listenable {
|
1065
|
1028
|
this.getTrackID = function(track) {
|
1066
|
1029
|
return track.jitsiRemoteId || track.id;
|
1067
|
1030
|
};
|
1068
|
|
- } else if (browser.isTemasysPluginUsed()) {
|
1069
|
|
- // Detect IE/Safari
|
1070
|
|
- const webRTCReadyCb = () => {
|
1071
|
|
- this.RTCPeerConnectionType = RTCPeerConnection;
|
1072
|
|
- this.getUserMedia = window.getUserMedia;
|
1073
|
|
- this.enumerateDevices
|
1074
|
|
- = enumerateDevicesThroughMediaStreamTrack;
|
1075
|
|
- this.attachMediaStream
|
1076
|
|
- = wrapAttachMediaStream((element, stream) => {
|
1077
|
|
- if (stream) {
|
1078
|
|
- if (stream.id === 'dummyAudio'
|
1079
|
|
- || stream.id === 'dummyVideo') {
|
1080
|
|
- return;
|
1081
|
|
- }
|
1082
|
|
-
|
1083
|
|
- // The container must be visible in order to
|
1084
|
|
- // play or attach the stream when Temasys plugin
|
1085
|
|
- // is in use
|
1086
|
|
- const containerSel = $(element);
|
1087
|
|
-
|
1088
|
|
- if (browser.isTemasysPluginUsed()
|
1089
|
|
- && !containerSel.is(':visible')) {
|
1090
|
|
- containerSel.show();
|
1091
|
|
- }
|
1092
|
|
- const video
|
1093
|
|
- = stream.getVideoTracks().length > 0;
|
1094
|
|
-
|
1095
|
|
- if (video && !$(element).is(':visible')) {
|
1096
|
|
- throw new Error(
|
1097
|
|
- 'video element must be visible to'
|
1098
|
|
- + ' attach video stream');
|
1099
|
|
- }
|
1100
|
|
- }
|
1101
|
|
-
|
1102
|
|
- return attachMediaStream(element, stream);
|
1103
|
|
- });
|
1104
|
|
- this.getStreamID
|
1105
|
|
- = stream => SDPUtil.filterSpecialChars(stream.label);
|
1106
|
|
- this.getTrackID
|
1107
|
|
- = track => track.id;
|
1108
|
|
-
|
1109
|
|
- onReady(
|
1110
|
|
- options,
|
1111
|
|
- this.getUserMediaWithConstraints.bind(this));
|
1112
|
|
- };
|
1113
|
|
- const webRTCReadyPromise
|
1114
|
|
- = new Promise(r => AdapterJS.webRTCReady(r));
|
1115
|
|
-
|
1116
|
|
- // Resolve or reject depending on whether the Temasys plugin is
|
1117
|
|
- // installed.
|
1118
|
|
- AdapterJS.WebRTCPlugin.isPluginInstalled(
|
1119
|
|
- AdapterJS.WebRTCPlugin.pluginInfo.prefix,
|
1120
|
|
- AdapterJS.WebRTCPlugin.pluginInfo.plugName,
|
1121
|
|
- AdapterJS.WebRTCPlugin.pluginInfo.type,
|
1122
|
|
- /* installed */ () => {
|
1123
|
|
- webRTCReadyPromise.then(() => {
|
1124
|
|
- webRTCReadyCb();
|
1125
|
|
- resolve();
|
1126
|
|
- });
|
1127
|
|
- },
|
1128
|
|
- /* not installed */ () => {
|
1129
|
|
- const error
|
1130
|
|
- = new Error('Temasys plugin is not installed');
|
1131
|
|
-
|
1132
|
|
- error.name = 'WEBRTC_NOT_READY';
|
1133
|
|
- error.webRTCReadyPromise = webRTCReadyPromise;
|
1134
|
|
-
|
1135
|
|
- reject(error);
|
1136
|
|
- });
|
1137
|
1031
|
} else {
|
1138
|
1032
|
rejectWithWebRTCNotSupported(
|
1139
|
1033
|
'Browser does not appear to be WebRTC-capable',
|
|
@@ -1144,11 +1038,34 @@ class RTCUtils extends Listenable {
|
1144
|
1038
|
|
1145
|
1039
|
this._initPCConstraints(options);
|
1146
|
1040
|
|
1147
|
|
- // Call onReady() if Temasys plugin is not used
|
1148
|
|
- if (!browser.isTemasysPluginUsed()) {
|
1149
|
|
- onReady(options, this.getUserMediaWithConstraints.bind(this));
|
1150
|
|
- resolve();
|
|
1041
|
+ rtcReady = true;
|
|
1042
|
+ eventEmitter.emit(RTCEvents.RTC_READY, true);
|
|
1043
|
+ screenObtainer.init(
|
|
1044
|
+ options, this.getUserMediaWithConstraints.bind(this));
|
|
1045
|
+
|
|
1046
|
+ if (this.isDeviceListAvailable()
|
|
1047
|
+ && rawEnumerateDevicesWithCallback) {
|
|
1048
|
+ rawEnumerateDevicesWithCallback(ds => {
|
|
1049
|
+ availableDevices = ds.splice(0);
|
|
1050
|
+
|
|
1051
|
+ logger.debug('Available devices: ', availableDevices);
|
|
1052
|
+ sendDeviceListToAnalytics(availableDevices);
|
|
1053
|
+
|
|
1054
|
+ eventEmitter.emit(RTCEvents.DEVICE_LIST_AVAILABLE,
|
|
1055
|
+ availableDevices);
|
|
1056
|
+
|
|
1057
|
+ if (isDeviceChangeEventSupported) {
|
|
1058
|
+ navigator.mediaDevices.addEventListener(
|
|
1059
|
+ 'devicechange',
|
|
1060
|
+ () => this.enumerateDevices(
|
|
1061
|
+ onMediaDevicesListChanged));
|
|
1062
|
+ } else {
|
|
1063
|
+ pollForAvailableMediaDevices();
|
|
1064
|
+ }
|
|
1065
|
+ });
|
1151
|
1066
|
}
|
|
1067
|
+
|
|
1068
|
+ resolve();
|
1152
|
1069
|
});
|
1153
|
1070
|
}
|
1154
|
1071
|
|
|
@@ -1782,7 +1699,6 @@ class RTCUtils extends Listenable {
|
1782
|
1699
|
: browser.isChrome()
|
1783
|
1700
|
|| browser.isFirefox()
|
1784
|
1701
|
|| browser.isOpera()
|
1785
|
|
- || browser.isTemasysPluginUsed()
|
1786
|
1702
|
|| browser.isNWJS()
|
1787
|
1703
|
|| browser.isElectron()
|
1788
|
1704
|
|| browser.isEdge();
|
|
@@ -1795,8 +1711,7 @@ class RTCUtils extends Listenable {
|
1795
|
1711
|
*/
|
1796
|
1712
|
stopMediaStream(mediaStream) {
|
1797
|
1713
|
mediaStream.getTracks().forEach(track => {
|
1798
|
|
- // stop() not supported with IE
|
1799
|
|
- if (!browser.isTemasysPluginUsed() && track.stop) {
|
|
1714
|
+ if (track.stop) {
|
1800
|
1715
|
track.stop();
|
1801
|
1716
|
}
|
1802
|
1717
|
});
|
|
@@ -1871,7 +1786,7 @@ class RTCUtils extends Listenable {
|
1871
|
1786
|
* @returns {Array} list of available media devices.
|
1872
|
1787
|
*/
|
1873
|
1788
|
getCurrentlyAvailableMediaDevices() {
|
1874
|
|
- return currentlyAvailableMediaDevices;
|
|
1789
|
+ return availableDevices;
|
1875
|
1790
|
}
|
1876
|
1791
|
|
1877
|
1792
|
/**
|
|
@@ -1928,8 +1843,8 @@ class RTCUtils extends Listenable {
|
1928
|
1843
|
function rejectWithWebRTCNotSupported(errorMessage, reject) {
|
1929
|
1844
|
const error = new Error(errorMessage);
|
1930
|
1845
|
|
1931
|
|
- // WebRTC is not supported either natively or via a known plugin such as
|
1932
|
|
- // Temasys.
|
|
1846
|
+ // WebRTC is not supported.
|
|
1847
|
+ //
|
1933
|
1848
|
// XXX The Error class already has a property name which is commonly used to
|
1934
|
1849
|
// detail the represented error in a non-human-readable way (in contrast to
|
1935
|
1850
|
// the human-readable property message). I explicitly did not want to
|
|
@@ -1972,39 +1887,6 @@ function obtainDevices(options) {
|
1972
|
1887
|
});
|
1973
|
1888
|
}
|
1974
|
1889
|
|
1975
|
|
-/**
|
1976
|
|
- * In case of IE we continue from 'onReady' callback passed to RTCUtils
|
1977
|
|
- * constructor. It will be invoked by Temasys plugin once it is initialized.
|
1978
|
|
- *
|
1979
|
|
- * @param options
|
1980
|
|
- * @param GUM
|
1981
|
|
- */
|
1982
|
|
-function onReady(options, GUM) {
|
1983
|
|
- rtcReady = true;
|
1984
|
|
- eventEmitter.emit(RTCEvents.RTC_READY, true);
|
1985
|
|
- screenObtainer.init(options, GUM);
|
1986
|
|
-
|
1987
|
|
- if (rtcUtils.isDeviceListAvailable() && rawEnumerateDevicesWithCallback) {
|
1988
|
|
- rawEnumerateDevicesWithCallback(ds => {
|
1989
|
|
- currentlyAvailableMediaDevices = ds.splice(0);
|
1990
|
|
-
|
1991
|
|
- logger.info('Available devices: ', currentlyAvailableMediaDevices);
|
1992
|
|
- sendDeviceListToAnalytics(currentlyAvailableMediaDevices);
|
1993
|
|
-
|
1994
|
|
- eventEmitter.emit(RTCEvents.DEVICE_LIST_AVAILABLE,
|
1995
|
|
- currentlyAvailableMediaDevices);
|
1996
|
|
-
|
1997
|
|
- if (isDeviceChangeEventSupported) {
|
1998
|
|
- navigator.mediaDevices.addEventListener(
|
1999
|
|
- 'devicechange',
|
2000
|
|
- () => rtcUtils.enumerateDevices(onMediaDevicesListChanged));
|
2001
|
|
- } else {
|
2002
|
|
- pollForAvailableMediaDevices();
|
2003
|
|
- }
|
2004
|
|
- });
|
2005
|
|
- }
|
2006
|
|
-}
|
2007
|
|
-
|
2008
|
1890
|
/**
|
2009
|
1891
|
* Wraps original attachMediaStream function to set current audio output device
|
2010
|
1892
|
* if this is supported.
|