You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SimulcastReceiver.js 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. var SimulcastLogger = require("./SimulcastLogger");
  2. var SimulcastUtils = require("./SimulcastUtils");
  3. function SimulcastReceiver() {
  4. this.simulcastUtils = new SimulcastUtils();
  5. this.logger = new SimulcastLogger('SimulcastReceiver', 1);
  6. }
  7. SimulcastReceiver.prototype._remoteVideoSourceCache = '';
  8. SimulcastReceiver.prototype._remoteMaps = {
  9. msid2Quality: {},
  10. ssrc2Msid: {},
  11. msid2ssrc: {},
  12. receivingVideoStreams: {}
  13. };
  14. SimulcastReceiver.prototype._cacheRemoteVideoSources = function (lines) {
  15. this._remoteVideoSourceCache = this.simulcastUtils._getVideoSources(lines);
  16. };
  17. SimulcastReceiver.prototype._restoreRemoteVideoSources = function (lines) {
  18. this.simulcastUtils._replaceVideoSources(lines, this._remoteVideoSourceCache);
  19. };
  20. SimulcastReceiver.prototype._ensureGoogConference = function (lines) {
  21. var sb;
  22. this.logger.info('Ensuring x-google-conference flag...')
  23. if (this.simulcastUtils._indexOfArray('a=x-google-flag:conference', lines) === this.simulcastUtils._emptyCompoundIndex) {
  24. // TODO(gp) do that for the audio as well as suggested by fippo.
  25. // Add the google conference flag
  26. sb = this.simulcastUtils._getVideoSources(lines);
  27. sb = ['a=x-google-flag:conference'].concat(sb);
  28. this.simulcastUtils._replaceVideoSources(lines, sb);
  29. }
  30. };
  31. SimulcastReceiver.prototype._restoreSimulcastGroups = function (sb) {
  32. this._restoreRemoteVideoSources(sb);
  33. };
  34. /**
  35. * Restores the simulcast groups of the remote description. In
  36. * transformRemoteDescription we remove those in order for the set remote
  37. * description to succeed. The focus needs the signal the groups to new
  38. * participants.
  39. *
  40. * @param desc
  41. * @returns {*}
  42. */
  43. SimulcastReceiver.prototype.reverseTransformRemoteDescription = function (desc) {
  44. var sb;
  45. if (!this.simulcastUtils.isValidDescription(desc)) {
  46. return desc;
  47. }
  48. if (config.enableSimulcast) {
  49. sb = desc.sdp.split('\r\n');
  50. this._restoreSimulcastGroups(sb);
  51. desc = new RTCSessionDescription({
  52. type: desc.type,
  53. sdp: sb.join('\r\n')
  54. });
  55. }
  56. return desc;
  57. };
  58. SimulcastUtils.prototype._ensureOrder = function (lines) {
  59. var videoSources, sb;
  60. videoSources = this.parseMedia(lines, ['video'])[0];
  61. sb = this._compileVideoSources(videoSources);
  62. this._replaceVideoSources(lines, sb);
  63. };
  64. SimulcastReceiver.prototype._updateRemoteMaps = function (lines) {
  65. var remoteVideoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0],
  66. videoSource, quality;
  67. // (re) initialize the remote maps.
  68. this._remoteMaps.msid2Quality = {};
  69. this._remoteMaps.ssrc2Msid = {};
  70. this._remoteMaps.msid2ssrc = {};
  71. var self = this;
  72. if (remoteVideoSources.groups && remoteVideoSources.groups.length !== 0) {
  73. remoteVideoSources.groups.forEach(function (group) {
  74. if (group.semantics === 'SIM' && group.ssrcs && group.ssrcs.length !== 0) {
  75. quality = 0;
  76. group.ssrcs.forEach(function (ssrc) {
  77. videoSource = remoteVideoSources.sources[ssrc];
  78. self._remoteMaps.msid2Quality[videoSource.msid] = quality++;
  79. self._remoteMaps.ssrc2Msid[videoSource.ssrc] = videoSource.msid;
  80. self._remoteMaps.msid2ssrc[videoSource.msid] = videoSource.ssrc;
  81. });
  82. }
  83. });
  84. }
  85. };
  86. SimulcastReceiver.prototype._setReceivingVideoStream = function (resource, ssrc) {
  87. this._remoteMaps.receivingVideoStreams[resource] = ssrc;
  88. };
  89. /**
  90. * Returns a stream with single video track, the one currently being
  91. * received by this endpoint.
  92. *
  93. * @param stream the remote simulcast stream.
  94. * @returns {webkitMediaStream}
  95. */
  96. SimulcastReceiver.prototype.getReceivingVideoStream = function (stream) {
  97. var tracks, i, electedTrack, msid, quality = 0, receivingTrackId;
  98. var self = this;
  99. if (config.enableSimulcast) {
  100. stream.getVideoTracks().some(function (track) {
  101. return Object.keys(self._remoteMaps.receivingVideoStreams).some(function (resource) {
  102. var ssrc = self._remoteMaps.receivingVideoStreams[resource];
  103. var msid = self._remoteMaps.ssrc2Msid[ssrc];
  104. if (msid == [stream.id, track.id].join(' ')) {
  105. electedTrack = track;
  106. return true;
  107. }
  108. });
  109. });
  110. if (!electedTrack) {
  111. // we don't have an elected track, choose by initial quality.
  112. tracks = stream.getVideoTracks();
  113. for (i = 0; i < tracks.length; i++) {
  114. msid = [stream.id, tracks[i].id].join(' ');
  115. if (this._remoteMaps.msid2Quality[msid] === quality) {
  116. electedTrack = tracks[i];
  117. break;
  118. }
  119. }
  120. // TODO(gp) if the initialQuality could not be satisfied, lower
  121. // the requirement and try again.
  122. }
  123. }
  124. return (electedTrack)
  125. ? new webkitMediaStream([electedTrack])
  126. : stream;
  127. };
  128. SimulcastReceiver.prototype.getReceivingSSRC = function (jid) {
  129. var resource = Strophe.getResourceFromJid(jid);
  130. var ssrc = this._remoteMaps.receivingVideoStreams[resource];
  131. // If we haven't receiving a "changed" event yet, then we must be receiving
  132. // low quality (that the sender always streams).
  133. if(!ssrc)
  134. {
  135. var remoteStreamObject = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
  136. var remoteStream = remoteStreamObject.getOriginalStream();
  137. var tracks = remoteStream.getVideoTracks();
  138. if (tracks) {
  139. for (var k = 0; k < tracks.length; k++) {
  140. var track = tracks[k];
  141. var msid = [remoteStream.id, track.id].join(' ');
  142. var _ssrc = this._remoteMaps.msid2ssrc[msid];
  143. var quality = this._remoteMaps.msid2Quality[msid];
  144. if (quality == 0) {
  145. ssrc = _ssrc;
  146. }
  147. }
  148. }
  149. }
  150. return ssrc;
  151. };
  152. SimulcastReceiver.prototype.getReceivingVideoStreamBySSRC = function (ssrc)
  153. {
  154. var sid, electedStream;
  155. var i, j, k;
  156. var jid = ssrc2jid[ssrc];
  157. if(jid && RTC.remoteStreams[jid])
  158. {
  159. var remoteStreamObject = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
  160. var remoteStream = remoteStreamObject.getOriginalStream();
  161. var tracks = remoteStream.getVideoTracks();
  162. if (tracks) {
  163. for (k = 0; k < tracks.length; k++) {
  164. var track = tracks[k];
  165. var msid = [remoteStream.id, track.id].join(' ');
  166. var tmp = this._remoteMaps.msid2ssrc[msid];
  167. if (tmp == ssrc) {
  168. electedStream = new webkitMediaStream([track]);
  169. sid = remoteStreamObject.sid;
  170. // stream found, stop.
  171. break;
  172. }
  173. }
  174. }
  175. }
  176. else
  177. {
  178. console.debug(RTC.remoteStreams, jid, ssrc);
  179. }
  180. return {
  181. sid: sid,
  182. stream: electedStream
  183. };
  184. };
  185. /**
  186. * Gets the fully qualified msid (stream.id + track.id) associated to the
  187. * SSRC.
  188. *
  189. * @param ssrc
  190. * @returns {*}
  191. */
  192. SimulcastReceiver.prototype.getRemoteVideoStreamIdBySSRC = function (ssrc) {
  193. return this._remoteMaps.ssrc2Msid[ssrc];
  194. };
  195. /**
  196. * Removes the ssrc-group:SIM from the remote description bacause Chrome
  197. * either gets confused and thinks this is an FID group or, if an FID group
  198. * is already present, it fails to set the remote description.
  199. *
  200. * @param desc
  201. * @returns {*}
  202. */
  203. SimulcastReceiver.prototype.transformRemoteDescription = function (desc) {
  204. if (desc && desc.sdp) {
  205. var sb = desc.sdp.split('\r\n');
  206. this._updateRemoteMaps(sb);
  207. this._cacheRemoteVideoSources(sb);
  208. // NOTE(gp) this needs to be called after updateRemoteMaps because we
  209. // need the simulcast group in the _updateRemoteMaps() method.
  210. this.simulcastUtils._removeSimulcastGroup(sb);
  211. if (desc.sdp.indexOf('a=ssrc-group:SIM') !== -1) {
  212. // We don't need the goog conference flag if we're not doing
  213. // simulcast.
  214. this._ensureGoogConference(sb);
  215. }
  216. desc = new RTCSessionDescription({
  217. type: desc.type,
  218. sdp: sb.join('\r\n')
  219. });
  220. this.logger.fine(['Transformed remote description', desc.sdp].join(' '));
  221. }
  222. return desc;
  223. };
  224. module.exports = SimulcastReceiver;