|
@@ -138,12 +138,10 @@ export default function TraceablePeerConnection(
|
138
|
138
|
*/
|
139
|
139
|
this.isP2P = isP2P;
|
140
|
140
|
|
141
|
|
- // FIXME: We should support multiple streams per jid.
|
142
|
141
|
/**
|
143
|
|
- * The map holds remote tracks associated with this peer connection.
|
144
|
|
- * It maps user's JID to media type and remote track
|
145
|
|
- * (one track per media type per user's JID).
|
146
|
|
- * @type {Map<string, Map<MediaType, JitsiRemoteTrack>>}
|
|
142
|
+ * The map holds remote tracks associated with this peer connection. It maps user's JID to media type and a set of
|
|
143
|
+ * remote tracks.
|
|
144
|
+ * @type {Map<string, Map<MediaType, Set<JitsiRemoteTrack>>>}
|
147
|
145
|
*/
|
148
|
146
|
this.remoteTracks = new Map();
|
149
|
147
|
|
|
@@ -606,18 +604,19 @@ TraceablePeerConnection.prototype.getLocalTracks = function(mediaType) {
|
606
|
604
|
};
|
607
|
605
|
|
608
|
606
|
/**
|
609
|
|
- * Retrieves the local video track.
|
|
607
|
+ * Retrieves the local video tracks.
|
610
|
608
|
*
|
611
|
|
- * @returns {JitsiLocalTrack|undefined} - local video track.
|
|
609
|
+ * @returns {JitsiLocalTrack|undefined} - local video tracks.
|
612
|
610
|
*/
|
613
|
|
-TraceablePeerConnection.prototype.getLocalVideoTrack = function() {
|
614
|
|
- return this.getLocalTracks(MediaType.VIDEO)[0];
|
|
611
|
+TraceablePeerConnection.prototype.getLocalVideoTracks = function() {
|
|
612
|
+ return this.getLocalTracks(MediaType.VIDEO);
|
615
|
613
|
};
|
616
|
614
|
|
617
|
615
|
/**
|
618
|
|
- * Checks whether or not this {@link TraceablePeerConnection} instance contains
|
619
|
|
- * any local tracks for given <tt>mediaType</tt>.
|
620
|
|
- * @param {MediaType} mediaType
|
|
616
|
+ * Checks whether or not this {@link TraceablePeerConnection} instance contains any local tracks for given
|
|
617
|
+ * <tt>mediaType</tt>.
|
|
618
|
+ *
|
|
619
|
+ * @param {MediaType} mediaType - The media type.
|
621
|
620
|
* @return {boolean}
|
622
|
621
|
*/
|
623
|
622
|
TraceablePeerConnection.prototype.hasAnyTracksOfType = function(mediaType) {
|
|
@@ -630,35 +629,24 @@ TraceablePeerConnection.prototype.hasAnyTracksOfType = function(mediaType) {
|
630
|
629
|
|
631
|
630
|
/**
|
632
|
631
|
* Obtains all remote tracks currently known to this PeerConnection instance.
|
633
|
|
- * @param {string} [endpointId] the track owner's identifier (MUC nickname)
|
634
|
|
- * @param {MediaType} [mediaType] the remote tracks will be filtered
|
635
|
|
- * by their media type if this argument is specified.
|
|
632
|
+ *
|
|
633
|
+ * @param {string} [endpointId] - The track owner's identifier (MUC nickname)
|
|
634
|
+ * @param {MediaType} [mediaType] - The remote tracks will be filtered by their media type if this argument is
|
|
635
|
+ * specified.
|
636
|
636
|
* @return {Array<JitsiRemoteTrack>}
|
637
|
637
|
*/
|
638
|
|
-TraceablePeerConnection.prototype.getRemoteTracks = function(
|
639
|
|
- endpointId,
|
640
|
|
- mediaType) {
|
641
|
|
- const remoteTracks = [];
|
642
|
|
- const endpoints
|
643
|
|
- = endpointId ? [ endpointId ] : this.remoteTracks.keys();
|
|
638
|
+TraceablePeerConnection.prototype.getRemoteTracks = function(endpointId, mediaType) {
|
|
639
|
+ let remoteTracks = [];
|
|
640
|
+ const endpoints = endpointId ? [ endpointId ] : this.remoteTracks.keys();
|
644
|
641
|
|
645
|
642
|
for (const endpoint of endpoints) {
|
646
|
|
- const endpointTrackMap = this.remoteTracks.get(endpoint);
|
|
643
|
+ const endpointTracksByMediaType = this.remoteTracks.get(endpoint);
|
647
|
644
|
|
648
|
|
- if (!endpointTrackMap) {
|
649
|
|
-
|
650
|
|
- // Otherwise an empty Map() would have to be allocated above
|
651
|
|
- // eslint-disable-next-line no-continue
|
652
|
|
- continue;
|
653
|
|
- }
|
654
|
|
-
|
655
|
|
- for (const trackMediaType of endpointTrackMap.keys()) {
|
656
|
|
- // per media type filtering
|
657
|
|
- if (!mediaType || mediaType === trackMediaType) {
|
658
|
|
- const mediaTrack = endpointTrackMap.get(trackMediaType);
|
659
|
|
-
|
660
|
|
- if (mediaTrack) {
|
661
|
|
- remoteTracks.push(mediaTrack);
|
|
645
|
+ if (endpointTracksByMediaType) {
|
|
646
|
+ for (const trackMediaType of endpointTracksByMediaType.keys()) {
|
|
647
|
+ // per media type filtering
|
|
648
|
+ if (!mediaType || mediaType === trackMediaType) {
|
|
649
|
+ remoteTracks = remoteTracks.concat(Array.from(endpointTracksByMediaType.get(trackMediaType)));
|
662
|
650
|
}
|
663
|
651
|
}
|
664
|
652
|
}
|
|
@@ -969,27 +957,26 @@ TraceablePeerConnection.prototype._createRemoteTrack = function(
|
969
|
957
|
|
970
|
958
|
if (!remoteTracksMap) {
|
971
|
959
|
remoteTracksMap = new Map();
|
|
960
|
+ remoteTracksMap.set(MediaType.AUDIO, new Set());
|
|
961
|
+ remoteTracksMap.set(MediaType.VIDEO, new Set());
|
972
|
962
|
this.remoteTracks.set(ownerEndpointId, remoteTracksMap);
|
973
|
963
|
}
|
974
|
964
|
|
975
|
|
- const existingTrack = remoteTracksMap.get(mediaType);
|
|
965
|
+ const userTracksByMediaType = remoteTracksMap.get(mediaType);
|
976
|
966
|
|
977
|
|
- if (existingTrack && existingTrack.getTrack() === track) {
|
|
967
|
+ if (userTracksByMediaType?.size
|
|
968
|
+ && Array.from(userTracksByMediaType).find(jitsiTrack => jitsiTrack.getTrack() === track)) {
|
978
|
969
|
// Ignore duplicated event which can originate either from 'onStreamAdded' or 'onTrackAdded'.
|
979
|
970
|
logger.info(`${this} ignored duplicated track event for track[endpoint=${ownerEndpointId},type=${mediaType}]`);
|
980
|
971
|
|
981
|
972
|
return;
|
982
|
|
- } else if (existingTrack) {
|
|
973
|
+ } else if (userTracksByMediaType?.size && !FeatureFlags.isSourceNameSignalingEnabled()) {
|
983
|
974
|
logger.error(`${this} received a second remote track for track[endpoint=${ownerEndpointId},type=${mediaType}]`
|
984
|
975
|
+ 'deleting the existing track');
|
|
976
|
+ const existingTrack = Array.from(userTracksByMediaType)[0];
|
985
|
977
|
|
986
|
|
- // The exisiting track needs to be removed here. We can get here when Jicofo reverses the order of source-add
|
987
|
|
- // and source-remove messages. Ideally, when a remote endpoint changes source, like switching devices, it sends
|
988
|
|
- // a source-remove (for old ssrc) followed by a source-add (for new ssrc) and Jicofo then should forward these
|
989
|
|
- // two messages to all the other endpoints in the conference in the same order. However, sometimes, these
|
990
|
|
- // messages arrive at the client in the reverse order resulting in two remote tracks (of same media type) being
|
991
|
|
- // created and in case of video, a black strip (that of the first track which has ended) appears over the live
|
992
|
|
- // track obscuring it. Removing the existing track when that happens will fix this issue.
|
|
978
|
+ // The exisiting track needs to be removed here. This happens on Safari sometimes when a SSRC is removed from
|
|
979
|
+ // the remote description and the browser doesn't fire a 'removetrack' event on the associated MediaStream.
|
993
|
980
|
this._remoteTrackRemoved(existingTrack.getOriginalStream(), existingTrack.getTrack());
|
994
|
981
|
}
|
995
|
982
|
|
|
@@ -1007,8 +994,7 @@ TraceablePeerConnection.prototype._createRemoteTrack = function(
|
1007
|
994
|
this.isP2P,
|
1008
|
995
|
sourceName);
|
1009
|
996
|
|
1010
|
|
- remoteTracksMap.set(mediaType, remoteTrack);
|
1011
|
|
-
|
|
997
|
+ userTracksByMediaType.add(remoteTrack);
|
1012
|
998
|
this.eventEmitter.emit(RTCEvents.REMOTE_TRACK_ADDED, remoteTrack, this);
|
1013
|
999
|
};
|
1014
|
1000
|
|
|
@@ -1043,14 +1029,12 @@ TraceablePeerConnection.prototype._remoteStreamRemoved = function(stream) {
|
1043
|
1029
|
|
1044
|
1030
|
/**
|
1045
|
1031
|
* Handles remote media track removal.
|
1046
|
|
- * @param {MediaStream} stream WebRTC MediaStream instance which is the parent
|
1047
|
|
- * of the track.
|
1048
|
|
- * @param {MediaStreamTrack} track the WebRTC MediaStreamTrack which has been
|
1049
|
|
- * removed from the PeerConnection.
|
|
1032
|
+ *
|
|
1033
|
+ * @param {MediaStream} stream - WebRTC MediaStream instance which is the parent of the track.
|
|
1034
|
+ * @param {MediaStreamTrack} track - WebRTC MediaStreamTrack which has been removed from the PeerConnection.
|
|
1035
|
+ * @returns {void}
|
1050
|
1036
|
*/
|
1051
|
|
-TraceablePeerConnection.prototype._remoteTrackRemoved = function(
|
1052
|
|
- stream,
|
1053
|
|
- track) {
|
|
1037
|
+TraceablePeerConnection.prototype._remoteTrackRemoved = function(stream, track) {
|
1054
|
1038
|
const streamId = RTC.getStreamID(stream);
|
1055
|
1039
|
const trackId = track && RTC.getTrackID(track);
|
1056
|
1040
|
|
|
@@ -1059,7 +1043,6 @@ TraceablePeerConnection.prototype._remoteTrackRemoved = function(
|
1059
|
1043
|
|
1060
|
1044
|
return;
|
1061
|
1045
|
}
|
1062
|
|
- logger.info(`${this} remote track removed stream[id=${streamId},trackId=${trackId}]`);
|
1063
|
1046
|
|
1064
|
1047
|
if (!streamId) {
|
1065
|
1048
|
GlobalOnErrorHandler.callErrorHandler(new Error(`${this} remote track removal failed - no stream ID`));
|
|
@@ -1073,67 +1056,33 @@ TraceablePeerConnection.prototype._remoteTrackRemoved = function(
|
1073
|
1056
|
return;
|
1074
|
1057
|
}
|
1075
|
1058
|
|
1076
|
|
- if (!this._removeRemoteTrackById(streamId, trackId)) {
|
1077
|
|
- // NOTE this warning is always printed when user leaves the room,
|
1078
|
|
- // because we remove remote tracks manually on MUC member left event,
|
1079
|
|
- // before the SSRCs are removed by Jicofo. In most cases it is fine to
|
1080
|
|
- // ignore this warning, but still it's better to keep it printed for
|
1081
|
|
- // debugging purposes.
|
1082
|
|
- //
|
1083
|
|
- // We could change the behaviour to emit track removed only from here,
|
1084
|
|
- // but the order of the events will change and consuming apps could
|
1085
|
|
- // behave unexpectedly (the "user left" event would come before "track
|
1086
|
|
- // removed" events).
|
1087
|
|
- logger.warn(`${this} Removed track not found for stream[id=${streamId},trackId=${trackId}]`);
|
1088
|
|
- }
|
1089
|
|
-};
|
|
1059
|
+ const toBeRemoved = this.getRemoteTracks().find(
|
|
1060
|
+ remoteTrack => remoteTrack.getStreamId() === streamId
|
|
1061
|
+ && remoteTrack.getTrackId() === trackId);
|
1090
|
1062
|
|
1091
|
|
-/**
|
1092
|
|
- * Finds remote track by it's stream and track ids.
|
1093
|
|
- * @param {string} streamId the media stream id as defined by the WebRTC
|
1094
|
|
- * @param {string} trackId the media track id as defined by the WebRTC
|
1095
|
|
- * @return {JitsiRemoteTrack|undefined} the track's instance or
|
1096
|
|
- * <tt>undefined</tt> if not found.
|
1097
|
|
- * @private
|
1098
|
|
- */
|
1099
|
|
-TraceablePeerConnection.prototype._getRemoteTrackById = function(
|
1100
|
|
- streamId,
|
1101
|
|
- trackId) {
|
1102
|
|
- // .find will break the loop once the first match is found
|
1103
|
|
- for (const endpointTrackMap of this.remoteTracks.values()) {
|
1104
|
|
- for (const mediaTrack of endpointTrackMap.values()) {
|
1105
|
|
- // FIXME verify and try to use ===
|
1106
|
|
- /* eslint-disable eqeqeq */
|
1107
|
|
- if (mediaTrack.getStreamId() == streamId
|
1108
|
|
- && mediaTrack.getTrackId() == trackId) {
|
1109
|
|
- return mediaTrack;
|
1110
|
|
- }
|
|
1063
|
+ if (!toBeRemoved) {
|
|
1064
|
+ GlobalOnErrorHandler.callErrorHandler(new Error(`${this} remote track removal failed - track not found`));
|
1111
|
1065
|
|
1112
|
|
- /* eslint-enable eqeqeq */
|
1113
|
|
- }
|
|
1066
|
+ return;
|
1114
|
1067
|
}
|
1115
|
1068
|
|
1116
|
|
- return undefined;
|
|
1069
|
+ logger.info(`${this} remote track removed stream[id=${streamId},trackId=${trackId}]`);
|
|
1070
|
+ this._removeRemoteTrack(toBeRemoved);
|
1117
|
1071
|
};
|
1118
|
1072
|
|
1119
|
1073
|
/**
|
1120
|
|
- * Removes all JitsiRemoteTracks associated with given MUC nickname
|
1121
|
|
- * (resource part of the JID). Returns array of removed tracks.
|
|
1074
|
+ * Removes all JitsiRemoteTracks associated with given MUC nickname (resource part of the JID).
|
1122
|
1075
|
*
|
1123
|
1076
|
* @param {string} owner - The resource part of the MUC JID.
|
1124
|
|
- * @returns {JitsiRemoteTrack[]}
|
|
1077
|
+ * @returns {JitsiRemoteTrack[]} - The array of removed tracks.
|
1125
|
1078
|
*/
|
1126
|
1079
|
TraceablePeerConnection.prototype.removeRemoteTracks = function(owner) {
|
1127
|
|
- const removedTracks = [];
|
1128
|
|
- const remoteTracksMap = this.remoteTracks.get(owner);
|
1129
|
|
-
|
1130
|
|
- if (remoteTracksMap) {
|
1131
|
|
- const removedAudioTrack = remoteTracksMap.get(MediaType.AUDIO);
|
1132
|
|
- const removedVideoTrack = remoteTracksMap.get(MediaType.VIDEO);
|
1133
|
|
-
|
1134
|
|
- removedAudioTrack && removedTracks.push(removedAudioTrack);
|
1135
|
|
- removedVideoTrack && removedTracks.push(removedVideoTrack);
|
|
1080
|
+ let removedTracks = [];
|
|
1081
|
+ const remoteTracksByMedia = this.remoteTracks.get(owner);
|
1136
|
1082
|
|
|
1083
|
+ if (remoteTracksByMedia) {
|
|
1084
|
+ removedTracks = removedTracks.concat(Array.from(remoteTracksByMedia.get(MediaType.AUDIO)));
|
|
1085
|
+ removedTracks = removedTracks.concat(Array.from(remoteTracksByMedia.get(MediaType.VIDEO)));
|
1137
|
1086
|
this.remoteTracks.delete(owner);
|
1138
|
1087
|
}
|
1139
|
1088
|
logger.debug(`${this} removed remote tracks[endpoint=${owner},count=${removedTracks.length}`);
|
|
@@ -1142,45 +1091,24 @@ TraceablePeerConnection.prototype.removeRemoteTracks = function(owner) {
|
1142
|
1091
|
};
|
1143
|
1092
|
|
1144
|
1093
|
/**
|
1145
|
|
- * Removes and disposes given <tt>JitsiRemoteTrack</tt> instance. Emits
|
1146
|
|
- * {@link RTCEvents.REMOTE_TRACK_REMOVED}.
|
1147
|
|
- * @param {JitsiRemoteTrack} toBeRemoved
|
|
1094
|
+ * Removes and disposes given <tt>JitsiRemoteTrack</tt> instance. Emits {@link RTCEvents.REMOTE_TRACK_REMOVED}.
|
|
1095
|
+ *
|
|
1096
|
+ * @param {JitsiRemoteTrack} toBeRemoved - The remote track to be removed.
|
|
1097
|
+ * @returns {void}
|
1148
|
1098
|
*/
|
1149
|
1099
|
TraceablePeerConnection.prototype._removeRemoteTrack = function(toBeRemoved) {
|
1150
|
1100
|
toBeRemoved.dispose();
|
1151
|
1101
|
const participantId = toBeRemoved.getParticipantId();
|
1152
|
|
- const remoteTracksMap = this.remoteTracks.get(participantId);
|
|
1102
|
+ const userTracksByMediaType = this.remoteTracks.get(participantId);
|
1153
|
1103
|
|
1154
|
|
- if (!remoteTracksMap) {
|
|
1104
|
+ if (!userTracksByMediaType) {
|
1155
|
1105
|
logger.error(`${this} removeRemoteTrack: no remote tracks map for endpoint=${participantId}`);
|
1156
|
|
- } else if (!remoteTracksMap.delete(toBeRemoved.getType())) {
|
|
1106
|
+ } else if (!userTracksByMediaType.get(toBeRemoved.getType())?.delete(toBeRemoved)) {
|
1157
|
1107
|
logger.error(`${this} Failed to remove ${toBeRemoved} - type mapping messed up ?`);
|
1158
|
1108
|
}
|
1159
|
1109
|
this.eventEmitter.emit(RTCEvents.REMOTE_TRACK_REMOVED, toBeRemoved);
|
1160
|
1110
|
};
|
1161
|
1111
|
|
1162
|
|
-/**
|
1163
|
|
- * Removes and disposes <tt>JitsiRemoteTrack</tt> identified by given stream and
|
1164
|
|
- * track ids.
|
1165
|
|
- *
|
1166
|
|
- * @param {string} streamId the media stream id as defined by the WebRTC
|
1167
|
|
- * @param {string} trackId the media track id as defined by the WebRTC
|
1168
|
|
- * @returns {JitsiRemoteTrack|undefined} the track which has been removed or
|
1169
|
|
- * <tt>undefined</tt> if no track matching given stream and track ids was
|
1170
|
|
- * found.
|
1171
|
|
- */
|
1172
|
|
-TraceablePeerConnection.prototype._removeRemoteTrackById = function(
|
1173
|
|
- streamId,
|
1174
|
|
- trackId) {
|
1175
|
|
- const toBeRemoved = this._getRemoteTrackById(streamId, trackId);
|
1176
|
|
-
|
1177
|
|
- if (toBeRemoved) {
|
1178
|
|
- this._removeRemoteTrack(toBeRemoved);
|
1179
|
|
- }
|
1180
|
|
-
|
1181
|
|
- return toBeRemoved;
|
1182
|
|
-};
|
1183
|
|
-
|
1184
|
1112
|
/**
|
1185
|
1113
|
* Returns a map with keys msid/mediaType and <tt>TrackSSRCInfo</tt> values.
|
1186
|
1114
|
* @param {RTCSessionDescription} desc the local description.
|
|
@@ -1614,13 +1542,12 @@ TraceablePeerConnection.prototype.isSharingLowFpsScreen = function() {
|
1614
|
1542
|
/**
|
1615
|
1543
|
* Checks if screensharing is in progress.
|
1616
|
1544
|
*
|
1617
|
|
- * @returns {boolean} Returns true if a desktop track has been added to the
|
1618
|
|
- * peerconnection, false otherwise.
|
|
1545
|
+ * @returns {boolean} Returns true if a desktop track has been added to the peerconnection, false otherwise.
|
1619
|
1546
|
*/
|
1620
|
1547
|
TraceablePeerConnection.prototype._isSharingScreen = function() {
|
1621
|
|
- const track = this.getLocalVideoTrack();
|
|
1548
|
+ const tracks = this.getLocalVideoTracks();
|
1622
|
1549
|
|
1623
|
|
- return track && track.videoType === VideoType.DESKTOP;
|
|
1550
|
+ return Boolean(tracks.find(track => track.videoType === VideoType.DESKTOP));
|
1624
|
1551
|
};
|
1625
|
1552
|
|
1626
|
1553
|
/**
|
|
@@ -2475,7 +2402,7 @@ TraceablePeerConnection.prototype.setSenderVideoConstraints = function(frameHeig
|
2475
|
2402
|
}
|
2476
|
2403
|
|
2477
|
2404
|
this._senderVideoMaxHeight = frameHeight;
|
2478
|
|
- const localVideoTrack = this.getLocalVideoTrack();
|
|
2405
|
+ const localVideoTrack = this.getLocalVideoTracks()[0];
|
2479
|
2406
|
|
2480
|
2407
|
if (!localVideoTrack || localVideoTrack.isMuted()) {
|
2481
|
2408
|
return Promise.resolve();
|
|
@@ -2692,8 +2619,10 @@ TraceablePeerConnection.prototype.close = function() {
|
2692
|
2619
|
this._usesUnifiedPlan && this.peerconnection.removeEventListener('track', this.onTrack);
|
2693
|
2620
|
|
2694
|
2621
|
for (const peerTracks of this.remoteTracks.values()) {
|
2695
|
|
- for (const remoteTrack of peerTracks.values()) {
|
2696
|
|
- this._removeRemoteTrack(remoteTrack);
|
|
2622
|
+ for (const remoteTracks of peerTracks.values()) {
|
|
2623
|
+ for (const remoteTrack of remoteTracks) {
|
|
2624
|
+ this._removeRemoteTrack(remoteTrack);
|
|
2625
|
+ }
|
2697
|
2626
|
}
|
2698
|
2627
|
}
|
2699
|
2628
|
this.remoteTracks.clear();
|
|
@@ -2755,7 +2684,7 @@ TraceablePeerConnection.prototype._createOfferOrAnswer = function(
|
2755
|
2684
|
dumpSDP(resultSdp));
|
2756
|
2685
|
}
|
2757
|
2686
|
|
2758
|
|
- const localVideoTrack = this.getLocalVideoTrack();
|
|
2687
|
+ const localVideoTrack = this.getLocalVideoTracks()[0];
|
2759
|
2688
|
|
2760
|
2689
|
// Configure simulcast for camera tracks and for desktop tracks that need simulcast.
|
2761
|
2690
|
if (this.isSimulcastOn() && browser.usesSdpMungingForSimulcast()
|
|
@@ -2764,10 +2693,7 @@ TraceablePeerConnection.prototype._createOfferOrAnswer = function(
|
2764
|
2693
|
|| !this.isSharingLowFpsScreen())) {
|
2765
|
2694
|
// eslint-disable-next-line no-param-reassign
|
2766
|
2695
|
resultSdp = this.simulcast.mungeLocalDescription(resultSdp);
|
2767
|
|
- this.trace(
|
2768
|
|
- `create${logName}`
|
2769
|
|
- + 'OnSuccess::postTransform (simulcast)',
|
2770
|
|
- dumpSDP(resultSdp));
|
|
2696
|
+ this.trace(`create${logName} OnSuccess::postTransform (simulcast)`, dumpSDP(resultSdp));
|
2771
|
2697
|
}
|
2772
|
2698
|
|
2773
|
2699
|
if (!this.options.disableRtx && browser.usesSdpMungingForSimulcast()) {
|