File: source/stream-sdp.js

  1. /**
  2. * Stores the preferred sending Peer connection streaming audio codec.
  3. * @attribute _selectedAudioCodec
  4. * @type String
  5. * @default "auto"
  6. * @private
  7. * @for Skylink
  8. * @since 0.5.10
  9. */
  10. Skylink.prototype._selectedAudioCodec = 'auto';
  11.  
  12. /**
  13. * Stores the preferred sending Peer connection streaming video codec.
  14. * @attribute _selectedVideoCodec
  15. * @type String
  16. * @default "auto"
  17. * @private
  18. * @for Skylink
  19. * @since 0.5.10
  20. */
  21. Skylink.prototype._selectedVideoCodec = 'auto';
  22.  
  23. /**
  24. * Function that modifies the SessionDescription string to enable OPUS stereo.
  25. * @method _addSDPStereo
  26. * @private
  27. * @for Skylink
  28. * @since 0.5.10
  29. */
  30. Skylink.prototype._addSDPStereo = function(sdpLines) {
  31. var opusRtmpLineIndex = 0;
  32. var opusLineFound = false;
  33. var opusPayload = 0;
  34. var fmtpLineFound = false;
  35.  
  36. var i, j;
  37. var line;
  38.  
  39. for (i = 0; i < sdpLines.length; i += 1) {
  40. line = sdpLines[i];
  41.  
  42. if (line.indexOf('a=rtpmap:') === 0) {
  43. var parts = line.split(' ');
  44.  
  45. if (parts[1].indexOf('opus/48000/') === 0) {
  46. opusLineFound = true;
  47. opusPayload = parts[0].split(':')[1];
  48. opusRtmpLineIndex = i;
  49. break;
  50. }
  51. }
  52. }
  53.  
  54. // if found
  55. if (opusLineFound) {
  56. log.debug([null, 'SDP', null, 'OPUS line is found. Enabling stereo']);
  57.  
  58. // loop for fmtp payload
  59. for (j = 0; j < sdpLines.length; j += 1) {
  60. line = sdpLines[j];
  61.  
  62. if (line.indexOf('a=fmtp:' + opusPayload) === 0) {
  63. fmtpLineFound = true;
  64. sdpLines[j] += '; stereo=1';
  65. break;
  66. }
  67. }
  68.  
  69. // if line doesn't exists for an instance firefox
  70. if (!fmtpLineFound) {
  71. sdpLines.splice(opusRtmpLineIndex, 0, 'a=fmtp:' + opusPayload + ' stereo=1');
  72. }
  73. }
  74.  
  75. return sdpLines;
  76. };
  77.  
  78. /**
  79. * Function that modifies the SessionDescription string to set the video resolution.
  80. * This is not even supported in the specs, and we should re-evalute it to be removed.
  81. * @method _setSDPVideoResolution
  82. * @private
  83. * @for Skylink
  84. * @since 0.5.10
  85. */
  86. Skylink.prototype._setSDPVideoResolution = function(sdpLines){
  87. var video = this._streams.userMedia && this._streams.userMedia.settings.video;
  88. var frameRate = video.frameRate || 50;
  89. var resolution = {
  90. width: 320,
  91. height: 50
  92. }; //video.resolution || {};
  93.  
  94. var videoLineFound = false;
  95. var videoLineIndex = 0;
  96. var fmtpPayloads = [];
  97.  
  98. var i, j, k;
  99. var line;
  100.  
  101. var sdpLineData = 'max-fr=' + frameRate +
  102. '; max-recv-width=320' + //(resolution.width ? resolution.width : 640) +
  103. '; max-recv-height=160'; //+ (resolution.height ? resolution.height : 480);
  104.  
  105. for (i = 0; i < sdpLines.length; i += 1) {
  106. line = sdpLines[i];
  107.  
  108. if (line.indexOf('a=video') === 0 || line.indexOf('m=video') === 0) {
  109. videoLineFound = true;
  110. videoLineIndex = i;
  111. fmtpPayloads = line.split(' ');
  112. fmtpPayloads.splice(0, 3);
  113. break;
  114. }
  115. }
  116.  
  117. if (videoLineFound) {
  118. // loop for every video codec
  119. // ignore if not vp8 or h264
  120. for (j = 0; j < fmtpPayloads.length; j += 1) {
  121. var payload = fmtpPayloads[j];
  122. var rtpmapLineIndex = 0;
  123. var fmtpLineIndex = 0;
  124. var fmtpLineFound = false;
  125. var ignore = false;
  126.  
  127. for (k = 0; k < sdpLines.length; k += 1) {
  128. line = sdpLines[k];
  129.  
  130. if (line.indexOf('a=rtpmap:' + payload) === 0) {
  131. // for non h264 or vp8 codec, ignore. these are experimental codecs
  132. // that may not exists afterwards
  133. if (!(line.indexOf('VP8') > 0 || line.indexOf('H264') > 0)) {
  134. ignore = true;
  135. break;
  136. }
  137. rtpmapLineIndex = k;
  138. }
  139.  
  140. if (line.indexOf('a=fmtp:' + payload) === 0) {
  141. fmtpLineFound = true;
  142. fmtpLineIndex = k;
  143. }
  144. }
  145.  
  146. if (ignore) {
  147. continue;
  148. }
  149.  
  150. if (fmtpLineFound) {
  151. sdpLines[fmtpLineIndex] += ';' + sdpLineData;
  152.  
  153. } else {
  154. sdpLines.splice(rtpmapLineIndex + 1, 0, 'a=fmtp:' + payload + ' ' + sdpLineData);
  155. }
  156. }
  157.  
  158. log.debug([null, 'SDP', null, 'Setting video resolution (broken)']);
  159. }
  160. return sdpLines;
  161. };
  162.  
  163. /**
  164. * Function that modifies the SessionDescription string to set the sending bandwidth.
  165. * Setting this may not necessarily work in Firefox.
  166. * @method _setSDPBitrate
  167. * @private
  168. * @for Skylink
  169. * @since 0.5.10
  170. */
  171. Skylink.prototype._setSDPBitrate = function(sdpLines, settings) {
  172. // Find if user has audioStream
  173. var bandwidth = this._streamsBandwidthSettings;
  174.  
  175. // Prevent setting of bandwidth audio if not configured
  176. if (typeof bandwidth.audio === 'number' && bandwidth.audio > 0) {
  177. var hasSetAudio = false;
  178.  
  179. for (var i = 0; i < sdpLines.length; i += 1) {
  180. // set the audio bandwidth
  181. if (sdpLines[i].indexOf('m=audio') === 0) {
  182. //if (sdpLines[i].indexOf('a=audio') === 0 || sdpLines[i].indexOf('m=audio') === 0) {
  183. sdpLines.splice(i + 1, 0, window.webrtcDetectedBrowser === 'firefox' ?
  184. 'b=TIAS:' + (bandwidth.audio * 1024) : 'b=AS:' + bandwidth.audio);
  185.  
  186. log.info([null, 'SDP', null, 'Setting maximum sending audio bandwidth bitrate @(index:' + i + ') -> '], bandwidth.audio);
  187. hasSetAudio = true;
  188. break;
  189. }
  190. }
  191.  
  192. if (!hasSetAudio) {
  193. log.warn([null, 'SDP', null, 'Not setting maximum sending audio bandwidth bitrate as m=audio line is not found']);
  194. }
  195. } else {
  196. log.warn([null, 'SDP', null, 'Not setting maximum sending audio bandwidth bitrate and leaving to browser\'s defaults']);
  197. }
  198.  
  199. // Prevent setting of bandwidth video if not configured
  200. if (typeof bandwidth.video === 'number' && bandwidth.video > 0) {
  201. var hasSetVideo = false;
  202.  
  203. for (var j = 0; j < sdpLines.length; j += 1) {
  204. // set the video bandwidth
  205. if (sdpLines[j].indexOf('m=video') === 0) {
  206. //if (sdpLines[j].indexOf('a=video') === 0 || sdpLines[j].indexOf('m=video') === 0) {
  207. sdpLines.splice(j + 1, 0, window.webrtcDetectedBrowser === 'firefox' ?
  208. 'b=TIAS:' + (bandwidth.video * 1024) : 'b=AS:' + bandwidth.video);
  209.  
  210. log.info([null, 'SDP', null, 'Setting maximum sending video bandwidth bitrate @(index:' + j + ') -> '], bandwidth.video);
  211. hasSetVideo = true;
  212. break;
  213. }
  214. }
  215.  
  216. if (!hasSetVideo) {
  217. log.warn([null, 'SDP', null, 'Not setting maximum sending video bandwidth bitrate as m=video line is not found']);
  218. }
  219. } else {
  220. log.warn([null, 'SDP', null, 'Not setting maximum sending video bandwidth bitrate and leaving to browser\'s defaults']);
  221. }
  222.  
  223. // Prevent setting of bandwidth data if not configured
  224. if (typeof bandwidth.data === 'number' && bandwidth.data > 0) {
  225. var hasSetData = false;
  226.  
  227. for (var k = 0; k < sdpLines.length; k += 1) {
  228. // set the data bandwidth
  229. if (sdpLines[k].indexOf('m=application') === 0) {
  230. //if (sdpLines[k].indexOf('a=application') === 0 || sdpLines[k].indexOf('m=application') === 0) {
  231. sdpLines.splice(k + 1, 0, window.webrtcDetectedBrowser === 'firefox' ?
  232. 'b=TIAS:' + (bandwidth.data * 1024) : 'b=AS:' + bandwidth.data);
  233.  
  234. log.info([null, 'SDP', null, 'Setting maximum sending data bandwidth bitrate @(index:' + k + ') -> '], bandwidth.data);
  235. hasSetData = true;
  236. break;
  237. }
  238. }
  239.  
  240. if (!hasSetData) {
  241. log.warn([null, 'SDP', null, 'Not setting maximum sending data bandwidth bitrate as m=application line is not found']);
  242. }
  243. } else {
  244. log.warn([null, 'SDP', null, 'Not setting maximum sending data bandwidth bitrate and leaving to browser\'s defaults']);
  245. }
  246.  
  247. return sdpLines;
  248. };
  249.  
  250. /**
  251. * Function that modifies the SessionDescription string to set the preferred sending video codec.
  252. * @method _setSDPVideoCodec
  253. * @private
  254. * @for Skylink
  255. * @since 0.5.2
  256. */
  257. Skylink.prototype._setSDPVideoCodec = function(sdpLines) {
  258. log.log('Setting video codec', this._selectedVideoCodec);
  259. var codecFound = false;
  260. var payload = 0;
  261.  
  262. var i, j;
  263. var line;
  264.  
  265. for (i = 0; i < sdpLines.length; i += 1) {
  266. line = sdpLines[i];
  267.  
  268. if (line.indexOf('a=rtpmap:') === 0) {
  269. if (line.indexOf(this._selectedVideoCodec) > 0) {
  270. codecFound = true;
  271. payload = line.split(':')[1].split(' ')[0];
  272. break;
  273. }
  274. }
  275. }
  276.  
  277. if (codecFound) {
  278. for (j = 0; j < sdpLines.length; j += 1) {
  279. line = sdpLines[j];
  280.  
  281. if (line.indexOf('m=video') === 0 || line.indexOf('a=video') === 0) {
  282. var parts = line.split(' ');
  283. var payloads = line.split(' ');
  284. payloads.splice(0, 3);
  285.  
  286. var selectedPayloadIndex = payloads.indexOf(payload);
  287.  
  288. if (selectedPayloadIndex === -1) {
  289. payloads.splice(0, 0, payload);
  290. } else {
  291. var first = payloads[0];
  292. payloads[0] = payload;
  293. payloads[selectedPayloadIndex] = first;
  294. }
  295. sdpLines[j] = parts[0] + ' ' + parts[1] + ' ' + parts[2] + ' ' + payloads.join(' ');
  296. break;
  297. }
  298. }
  299. }
  300. return sdpLines;
  301. };
  302.  
  303. /**
  304. * Function that modifies the SessionDescription string to set the preferred sending audio codec.
  305. * @method _setSDPAudioCodec
  306. * @private
  307. * @for Skylink
  308. * @since 0.5.2
  309. */
  310. Skylink.prototype._setSDPAudioCodec = function(sdpLines) {
  311. log.log('Setting audio codec', this._selectedAudioCodec);
  312. var codecFound = false;
  313. var payload = 0;
  314.  
  315. var i, j;
  316. var line;
  317.  
  318. for (i = 0; i < sdpLines.length; i += 1) {
  319. line = sdpLines[i];
  320.  
  321. if (line.indexOf('a=rtpmap:') === 0) {
  322. if (line.indexOf(this._selectedAudioCodec) > 0) {
  323. codecFound = true;
  324. payload = line.split(':')[1].split(' ')[0];
  325. }
  326. }
  327. }
  328.  
  329. if (codecFound) {
  330. for (j = 0; j < sdpLines.length; j += 1) {
  331. line = sdpLines[j];
  332.  
  333. if (line.indexOf('m=audio') === 0 || line.indexOf('a=audio') === 0) {
  334. var parts = line.split(' ');
  335. var payloads = line.split(' ');
  336. payloads.splice(0, 3);
  337.  
  338. var selectedPayloadIndex = payloads.indexOf(payload);
  339.  
  340. if (selectedPayloadIndex === -1) {
  341. payloads.splice(0, 0, payload);
  342. } else {
  343. var first = payloads[0];
  344. payloads[0] = payload;
  345. payloads[selectedPayloadIndex] = first;
  346. }
  347. sdpLines[j] = parts[0] + ' ' + parts[1] + ' ' + parts[2] + ' ' + payloads.join(' ');
  348. break;
  349. }
  350. }
  351. }
  352. return sdpLines;
  353. };
  354.  
  355. /**
  356. * Function that modifies the SessionDescription string to remove the experimental H264 Firefox flag
  357. * that is breaking connections.
  358. * To evaluate removal of this change once we roll out H264 codec interop.
  359. * @method _removeSDPFirefoxH264Pref
  360. * @private
  361. * @for Skylink
  362. * @since 0.5.2
  363. */
  364. Skylink.prototype._removeSDPFirefoxH264Pref = function(sdpLines) {
  365. var invalidLineIndex = sdpLines.indexOf(
  366. 'a=fmtp:0 profile-level-id=0x42e00c;packetization-mode=1');
  367. if (invalidLineIndex > -1) {
  368. log.debug([null, 'SDP', null, 'Firefox H264 invalid pref found:'], invalidLineIndex);
  369. sdpLines.splice(invalidLineIndex, 1);
  370. }
  371. return sdpLines;
  372. };
  373.  
  374. /**
  375. * Function that modifies the SessionDescription string to set with the correct MediaStream ID and
  376. * MediaStreamTrack IDs that is not provided from Firefox connection to Chromium connection.
  377. * @method _addSDPSsrcFirefoxAnswer
  378. * @private
  379. * @for Skylink
  380. * @since 0.6.6
  381. */
  382. Skylink.prototype._addSDPSsrcFirefoxAnswer = function (targetMid, sdp) {
  383. var self = this;
  384. var agent = self.getPeerInfo(targetMid).agent;
  385.  
  386. var pc = self._peerConnections[targetMid];
  387.  
  388. if (!pc) {
  389. log.error([targetMid, 'RTCSessionDesription', 'answer', 'Peer connection object ' +
  390. 'not found. Unable to parse answer session description for peer']);
  391. return;
  392. }
  393.  
  394. var updatedSdp = sdp;
  395.  
  396. // for this case, this is because firefox uses Unified Plan and Chrome uses
  397. // Plan B. we have to remodify this a bit to let the non-ff detect as new mediastream
  398. // as chrome/opera/safari detects it as default due to missing ssrc specified as used in plan B.
  399. if (window.webrtcDetectedBrowser === 'firefox' && agent.name !== 'firefox' &&
  400. //pc.remoteDescription.sdp.indexOf('a=msid-semantic: WMS *') === -1 &&
  401. updatedSdp.indexOf('a=msid-semantic:WMS *') > 0) {
  402. // start parsing
  403. var sdpLines = updatedSdp.split('\r\n');
  404. var streamId = '';
  405. var replaceSSRCSemantic = -1;
  406. var i;
  407. var trackId = '';
  408.  
  409. var parseTracksSSRC = function (track) {
  410. for (i = 0, trackId = ''; i < sdpLines.length; i++) {
  411. if (!!trackId) {
  412. if (sdpLines[i].indexOf('a=ssrc:') === 0) {
  413. var ssrcId = sdpLines[i].split(':')[1].split(' ')[0];
  414. sdpLines.splice(i+1, 0, 'a=ssrc:' + ssrcId + ' msid:' + streamId + ' ' + trackId,
  415. 'a=ssrc:' + ssrcId + ' mslabel:default',
  416. 'a=ssrc:' + ssrcId + ' label:' + trackId);
  417. break;
  418. } else if (sdpLines[i].indexOf('a=mid:') === 0) {
  419. break;
  420. }
  421. } else if (sdpLines[i].indexOf('a=msid:') === 0) {
  422. if (i > 0 && sdpLines[i-1].indexOf('a=mid:' + track) === 0) {
  423. var parts = sdpLines[i].split(':')[1].split(' ');
  424.  
  425. streamId = parts[0];
  426. trackId = parts[1];
  427. replaceSSRCSemantic = true;
  428. }
  429. }
  430. }
  431. };
  432.  
  433. parseTracksSSRC('video');
  434. parseTracksSSRC('audio');
  435.  
  436. /*if (replaceSSRCSemantic) {
  437. for (i = 0; i < sdpLines.length; i++) {
  438. if (sdpLines[i].indexOf('a=msid-semantic:WMS ') === 0) {
  439. var parts = sdpLines[i].split(' ');
  440. parts[parts.length - 1] = streamId;
  441. sdpLines[i] = parts.join(' ');
  442. break;
  443. }
  444. }
  445.  
  446. }*/
  447. updatedSdp = sdpLines.join('\r\n');
  448.  
  449. log.debug([targetMid, 'RTCSessionDesription', 'answer', 'Parsed remote description from firefox'], sdpLines);
  450. }
  451.  
  452. return updatedSdp;
  453. };