Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

ParticipantConnectionStatus.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. /* global __filename, module, require */
  2. var logger = require("jitsi-meet-logger").getLogger(__filename);
  3. var MediaType = require("../../service/RTC/MediaType");
  4. var RTCBrowserType = require("../RTC/RTCBrowserType");
  5. var RTCEvents = require("../../service/RTC/RTCEvents");
  6. import * as JitsiConferenceEvents from "../../JitsiConferenceEvents";
  7. import * as JitsiTrackEvents from "../../JitsiTrackEvents";
  8. /**
  9. * How long we're going to wait after the RTC video track muted event for
  10. * the corresponding signalling mute event, before the connection interrupted
  11. * is fired.
  12. *
  13. * @type {number} amount of time in milliseconds
  14. */
  15. const RTC_MUTE_TIMEOUT = 1000;
  16. /**
  17. * Class is responsible for emitting
  18. * JitsiConferenceEvents.PARTICIPANT_CONN_STATUS_CHANGED events.
  19. *
  20. * @constructor
  21. * @param rtc {RTC} the RTC service instance
  22. * @param conference {JitsiConference} parent conference instance
  23. */
  24. function ParticipantConnectionStatus(rtc, conference) {
  25. this.rtc = rtc;
  26. this.conference = conference;
  27. /**
  28. * A map of the "endpoint ID"(which corresponds to the resource part of MUC
  29. * JID(nickname)) to the timeout callback IDs scheduled using
  30. * window.setTimeout.
  31. * @type {Object.<string, number>}
  32. */
  33. this.trackTimers = {};
  34. }
  35. /**
  36. * Initializes <tt>ParticipantConnectionStatus</tt> and bind required event
  37. * listeners.
  38. */
  39. ParticipantConnectionStatus.prototype.init = function() {
  40. this._onEndpointConnStatusChanged
  41. = this.onEndpointConnStatusChanged.bind(this);
  42. this.rtc.addListener(
  43. RTCEvents.ENDPOINT_CONN_STATUS_CHANGED,
  44. this._onEndpointConnStatusChanged);
  45. // On some browsers MediaStreamTrack trigger "onmute"/"onunmute"
  46. // events for video type tracks when they stop receiving data which is
  47. // often a sign that remote user is having connectivity issues
  48. if (RTCBrowserType.isVideoMuteOnConnInterruptedSupported()) {
  49. this._onTrackRtcMuted = this.onTrackRtcMuted.bind(this);
  50. this.rtc.addListener(
  51. RTCEvents.REMOTE_TRACK_MUTE, this._onTrackRtcMuted);
  52. this._onTrackRtcUnmuted = this.onTrackRtcUnmuted.bind(this);
  53. this.rtc.addListener(
  54. RTCEvents.REMOTE_TRACK_UNMUTE, this._onTrackRtcUnmuted);
  55. // Track added/removed listeners are used to bind "mute"/"unmute"
  56. // event handlers
  57. this._onRemoteTrackAdded = this.onRemoteTrackAdded.bind(this);
  58. this.conference.on(
  59. JitsiConferenceEvents.TRACK_ADDED, this._onRemoteTrackAdded);
  60. this._onRemoteTrackRemoved = this.onRemoteTrackRemoved.bind(this);
  61. this.conference.on(
  62. JitsiConferenceEvents.TRACK_REMOVED, this._onRemoteTrackRemoved);
  63. // Listened which will be bound to JitsiRemoteTrack to listen for
  64. // signalling mute/unmute events.
  65. this._onSignallingMuteChanged = this.onSignallingMuteChanged.bind(this);
  66. }
  67. };
  68. /**
  69. * Removes all event listeners and disposes of all resources held by this
  70. * instance.
  71. */
  72. ParticipantConnectionStatus.prototype.dispose = function () {
  73. this.rtc.removeListener(
  74. RTCEvents.ENDPOINT_CONN_STATUS_CHANGED,
  75. this._onEndpointConnStatusChanged);
  76. if (RTCBrowserType.isVideoMuteOnConnInterruptedSupported()) {
  77. this.rtc.removeListener(
  78. RTCEvents.REMOTE_TRACK_MUTE, this._onTrackRtcMuted);
  79. this.rtc.removeListener(
  80. RTCEvents.REMOTE_TRACK_UNMUTE, this._onTrackRtcUnmuted);
  81. this.conference.off(
  82. JitsiConferenceEvents.TRACK_ADDED, this._onRemoteTrackAdded);
  83. this.conference.off(
  84. JitsiConferenceEvents.TRACK_REMOVED, this._onRemoteTrackRemoved);
  85. }
  86. Object.keys(this.trackTimers).forEach(function (participantId) {
  87. this.clearTimeout(participantId);
  88. }.bind(this));
  89. };
  90. /**
  91. * Checks whether given <tt>JitsiParticipant</tt> has any muted video
  92. * <tt>MediaStreamTrack</tt>s.
  93. *
  94. * @param {JitsiParticipant} participant to be checked for muted video tracks
  95. *
  96. * @return {boolean} <tt>true</tt> if given <tt>participant</tt> contains any
  97. * video <tt>MediaStreamTrack</tt>s muted according to their 'muted' field.
  98. */
  99. var hasRtcMutedVideoTrack = function (participant) {
  100. return participant.getTracks().some(function(jitsiTrack) {
  101. var rtcTrack = jitsiTrack.getTrack();
  102. return jitsiTrack.getType() === MediaType.VIDEO
  103. && rtcTrack && rtcTrack.muted === true;
  104. });
  105. };
  106. /**
  107. * Handles RTCEvents.ENDPOINT_CONN_STATUS_CHANGED triggered when we receive
  108. * notification over the data channel from the bridge about endpoint's
  109. * connection status update.
  110. * @param endpointId {string} the endpoint ID(MUC nickname/resource JID)
  111. * @param isActive {boolean} true if the connection is OK or false otherwise
  112. */
  113. ParticipantConnectionStatus.prototype.onEndpointConnStatusChanged
  114. = function(endpointId, isActive) {
  115. logger.debug(
  116. 'Detector RTCEvents.ENDPOINT_CONN_STATUS_CHANGED('
  117. + Date.now() +'): ' + endpointId + ": " + isActive);
  118. // Filter out events for the local JID for now
  119. if (endpointId !== this.conference.myUserId()) {
  120. var participant = this.conference.getParticipantById(endpointId);
  121. // Delay the 'active' event until the video track gets RTC unmuted event
  122. if (isActive
  123. && RTCBrowserType.isVideoMuteOnConnInterruptedSupported()
  124. && participant
  125. && hasRtcMutedVideoTrack(participant)
  126. && !participant.isVideoMuted()) {
  127. logger.debug(
  128. "Ignoring RTCEvents.ENDPOINT_CONN_STATUS_CHANGED -"
  129. + " will wait for unmute event");
  130. } else {
  131. this._changeConnectionStatus(endpointId, isActive);
  132. }
  133. }
  134. };
  135. ParticipantConnectionStatus.prototype._changeConnectionStatus
  136. = function (endpointId, newStatus) {
  137. var participant = this.conference.getParticipantById(endpointId);
  138. if (!participant) {
  139. // This will happen when participant exits the conference with broken
  140. // ICE connection and we join after that. The bridge keeps sending
  141. // that notification until the conference does not expire.
  142. logger.warn(
  143. 'Missed participant connection status update - ' +
  144. 'no participant for endpoint: ' + endpointId);
  145. return;
  146. }
  147. if (participant.isConnectionActive() !== newStatus) {
  148. participant._setIsConnectionActive(newStatus);
  149. logger.debug(
  150. 'Emit endpoint conn status(' + Date.now() + '): ',
  151. endpointId, newStatus);
  152. this.conference.eventEmitter.emit(
  153. JitsiConferenceEvents.PARTICIPANT_CONN_STATUS_CHANGED,
  154. endpointId, newStatus);
  155. }
  156. };
  157. /**
  158. * Reset the postponed "connection interrupted" event which was previously
  159. * scheduled as a timeout on RTC 'onmute' event.
  160. *
  161. * @param participantId the participant for which the "connection interrupted"
  162. * timeout was scheduled
  163. */
  164. ParticipantConnectionStatus.prototype.clearTimeout = function (participantId) {
  165. if (this.trackTimers[participantId]) {
  166. window.clearTimeout(this.trackTimers[participantId]);
  167. this.trackTimers[participantId] = null;
  168. }
  169. };
  170. /**
  171. * Bind signalling mute event listeners for video {JitsiRemoteTrack} when
  172. * a new one is added to the conference.
  173. *
  174. * @param {JitsiTrack} remoteTrack the {JitsiTrack} which is being added to
  175. * the conference.
  176. */
  177. ParticipantConnectionStatus.prototype.onRemoteTrackAdded
  178. = function(remoteTrack) {
  179. if (!remoteTrack.isLocal() && remoteTrack.getType() === MediaType.VIDEO) {
  180. logger.debug(
  181. "Detector on remote track added: ", remoteTrack.getParticipantId());
  182. remoteTrack.on(
  183. JitsiTrackEvents.TRACK_MUTE_CHANGED,
  184. this._onSignallingMuteChanged);
  185. }
  186. };
  187. /**
  188. * Removes all event listeners bound to the remote video track and clears any
  189. * related timeouts.
  190. *
  191. * @param {JitsiRemoteTrack} remoteTrack the remote track which is being removed
  192. * from the conference.
  193. */
  194. ParticipantConnectionStatus.prototype.onRemoteTrackRemoved
  195. = function(remoteTrack) {
  196. if (!remoteTrack.isLocal() && remoteTrack.getType() === MediaType.VIDEO) {
  197. logger.debug(
  198. "Detector on remote track removed: ",
  199. remoteTrack.getParticipantId());
  200. remoteTrack.off(
  201. JitsiTrackEvents.TRACK_MUTE_CHANGED,
  202. this._onSignallingMuteChanged);
  203. this.clearTimeout(remoteTrack.getParticipantId());
  204. }
  205. };
  206. /**
  207. * Handles RTC 'onmute' event for the video track.
  208. *
  209. * @param track {JitsiRemoteTrack} the video track for which 'onmute' event will
  210. * be processed.
  211. */
  212. ParticipantConnectionStatus.prototype.onTrackRtcMuted = function(track) {
  213. var participantId = track.getParticipantId();
  214. var participant = this.conference.getParticipantById(participantId);
  215. logger.debug("Detector track RTC muted: ", participantId);
  216. if (!participant) {
  217. logger.error("No participant for id: " + participantId);
  218. return;
  219. }
  220. if (!participant.isVideoMuted()) {
  221. // If the user is not muted according to the signalling we'll give it
  222. // some time, before the connection interrupted event is triggered.
  223. this.trackTimers[participantId] = window.setTimeout(function () {
  224. if (!track.isMuted() && participant.isConnectionActive()) {
  225. logger.info(
  226. "Connection interrupted through the RTC mute: "
  227. + participantId, Date.now());
  228. this._changeConnectionStatus(participantId, false);
  229. }
  230. this.clearTimeout(participantId);
  231. }.bind(this), RTC_MUTE_TIMEOUT);
  232. }
  233. };
  234. /**
  235. * Handles RTC 'onunmute' event for the video track.
  236. *
  237. * @param track {JitsiRemoteTrack} the video track for which 'onunmute' event
  238. * will be processed.
  239. */
  240. ParticipantConnectionStatus.prototype.onTrackRtcUnmuted = function(track) {
  241. logger.debug("Detector track RTC unmuted: ", track);
  242. var participantId = track.getParticipantId();
  243. if (!track.isMuted() &&
  244. !this.conference.getParticipantById(participantId)
  245. .isConnectionActive()) {
  246. logger.info(
  247. "Detector connection restored through the RTC unmute: "
  248. + participantId, Date.now());
  249. this._changeConnectionStatus(participantId, true);
  250. }
  251. this.clearTimeout(participantId);
  252. };
  253. /**
  254. * Here the signalling "mute"/"unmute" events are processed.
  255. *
  256. * @param track {JitsiRemoteTrack} the remote video track for which
  257. * the signalling mute/unmute event will be processed.
  258. */
  259. ParticipantConnectionStatus.prototype.onSignallingMuteChanged
  260. = function (track) {
  261. logger.debug(
  262. "Detector on track signalling mute changed: ", track, track.isMuted());
  263. var isMuted = track.isMuted();
  264. var participantId = track.getParticipantId();
  265. var participant = this.conference.getParticipantById(participantId);
  266. if (!participant) {
  267. logger.error("No participant for id: " + participantId);
  268. return;
  269. }
  270. var isConnectionActive = participant.isConnectionActive();
  271. if (isMuted && isConnectionActive && this.trackTimers[participantId]) {
  272. logger.debug(
  273. "Signalling got in sync - cancelling task for: " + participantId);
  274. this.clearTimeout(participantId);
  275. }
  276. };
  277. module.exports = ParticipantConnectionStatus;