Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

utils.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. /* global RTCRtpReceiver */
  2. import sdpTransform from 'sdp-transform';
  3. /**
  4. * Extract RTP capabilities from remote description.
  5. * @param {Object} sdpObject - Remote SDP object generated by sdp-transform.
  6. * @return {RTCRtpCapabilities}
  7. */
  8. export function extractCapabilities(sdpObject) {
  9. // Map of RtpCodecParameters indexed by payload type.
  10. const codecsMap = new Map();
  11. // Array of RtpHeaderExtensions.
  12. const headerExtensions = [];
  13. for (const m of sdpObject.media) {
  14. // Media kind.
  15. const kind = m.type;
  16. if (kind !== 'audio' && kind !== 'video') {
  17. continue; // eslint-disable-line no-continue
  18. }
  19. // Get codecs.
  20. for (const rtp of m.rtp) {
  21. const codec = {
  22. clockRate: rtp.rate,
  23. kind,
  24. mimeType: `${kind}/${rtp.codec}`,
  25. name: rtp.codec,
  26. numChannels: rtp.encoding || 1,
  27. parameters: {},
  28. preferredPayloadType: rtp.payload,
  29. rtcpFeedback: []
  30. };
  31. codecsMap.set(codec.preferredPayloadType, codec);
  32. }
  33. // Get codec parameters.
  34. for (const fmtp of m.fmtp || []) {
  35. const parameters = sdpTransform.parseFmtpConfig(fmtp.config);
  36. const codec = codecsMap.get(fmtp.payload);
  37. if (!codec) {
  38. continue; // eslint-disable-line no-continue
  39. }
  40. codec.parameters = parameters;
  41. }
  42. // Get RTCP feedback for each codec.
  43. for (const fb of m.rtcpFb || []) {
  44. const codec = codecsMap.get(fb.payload);
  45. if (!codec) {
  46. continue; // eslint-disable-line no-continue
  47. }
  48. codec.rtcpFeedback.push({
  49. parameter: fb.subtype || '',
  50. type: fb.type
  51. });
  52. }
  53. // Get RTP header extensions.
  54. for (const ext of m.ext || []) {
  55. const preferredId = ext.value;
  56. const uri = ext.uri;
  57. const headerExtension = {
  58. kind,
  59. uri,
  60. preferredId
  61. };
  62. // Check if already present.
  63. const duplicated = headerExtensions.find(savedHeaderExtension =>
  64. headerExtension.kind === savedHeaderExtension.kind
  65. && headerExtension.uri === savedHeaderExtension.uri
  66. );
  67. if (!duplicated) {
  68. headerExtensions.push(headerExtension);
  69. }
  70. }
  71. }
  72. return {
  73. codecs: Array.from(codecsMap.values()),
  74. fecMechanisms: [], // TODO
  75. headerExtensions
  76. };
  77. }
  78. /**
  79. * Extract DTLS parameters from remote description.
  80. * @param {Object} sdpObject - Remote SDP object generated by sdp-transform.
  81. * @return {RTCDtlsParameters}
  82. */
  83. export function extractDtlsParameters(sdpObject) {
  84. const media = getFirstActiveMediaSection(sdpObject);
  85. const fingerprint = media.fingerprint || sdpObject.fingerprint;
  86. let role;
  87. switch (media.setup) {
  88. case 'active':
  89. role = 'client';
  90. break;
  91. case 'passive':
  92. role = 'server';
  93. break;
  94. case 'actpass':
  95. role = 'auto';
  96. break;
  97. }
  98. return {
  99. role,
  100. fingerprints: [
  101. {
  102. algorithm: fingerprint.type,
  103. value: fingerprint.hash
  104. }
  105. ]
  106. };
  107. }
  108. /**
  109. * Extract ICE candidates from remote description.
  110. * NOTE: This implementation assumes a single BUNDLEd transport and rtcp-mux.
  111. * @param {Object} sdpObject - Remote SDP object generated by sdp-transform.
  112. * @return {sequence<RTCIceCandidate>}
  113. */
  114. export function extractIceCandidates(sdpObject) {
  115. const media = getFirstActiveMediaSection(sdpObject);
  116. const candidates = [];
  117. for (const c of media.candidates) {
  118. // Ignore RTCP candidates (we assume rtcp-mux).
  119. if (c.component !== 1) {
  120. continue; // eslint-disable-line no-continue
  121. }
  122. const candidate = {
  123. foundation: c.foundation,
  124. ip: c.ip,
  125. port: c.port,
  126. priority: c.priority,
  127. protocol: c.transport.toLowerCase(),
  128. type: c.type
  129. };
  130. candidates.push(candidate);
  131. }
  132. return candidates;
  133. }
  134. /**
  135. * Extract ICE parameters from remote description.
  136. * NOTE: This implementation assumes a single BUNDLEd transport.
  137. * @param {Object} sdpObject - Remote SDP object generated by sdp-transform.
  138. * @return {RTCIceParameters}
  139. */
  140. export function extractIceParameters(sdpObject) {
  141. const media = getFirstActiveMediaSection(sdpObject);
  142. const usernameFragment = media.iceUfrag;
  143. const password = media.icePwd;
  144. const icelite = sdpObject.icelite === 'ice-lite';
  145. return {
  146. icelite,
  147. password,
  148. usernameFragment
  149. };
  150. }
  151. /**
  152. * Extract MID values from remote description.
  153. * @param {Object} sdpObject - Remote SDP object generated by sdp-transform.
  154. * @return {map<String, String>} Ordered Map with MID as key and kind as value.
  155. */
  156. export function extractMids(sdpObject) {
  157. const midToKind = new Map();
  158. // Ignore disabled media sections.
  159. for (const m of sdpObject.media) {
  160. midToKind.set(m.mid, m.type);
  161. }
  162. return midToKind;
  163. }
  164. /**
  165. * Extract tracks information.
  166. * @param {Object} sdpObject - Remote SDP object generated by sdp-transform.
  167. * @return {Map}
  168. */
  169. export function extractTrackInfos(sdpObject) {
  170. // Map with info about receiving media.
  171. // - index: Media SSRC
  172. // - value: Object
  173. // - kind: 'audio' / 'video'
  174. // - ssrc: Media SSRC
  175. // - rtxSsrc: RTX SSRC (may be unset)
  176. // - streamId: MediaStream.jitsiRemoteId
  177. // - trackId: MediaStreamTrack.jitsiRemoteId
  178. // - cname: CNAME
  179. // @type {map<Number, Object>}
  180. const infos = new Map();
  181. // Map with stream SSRC as index and associated RTX SSRC as value.
  182. // @type {map<Number, Number>}
  183. const rtxMap = new Map();
  184. // Set of RTX SSRC values.
  185. const rtxSet = new Set();
  186. for (const m of sdpObject.media) {
  187. const kind = m.type;
  188. if (kind !== 'audio' && kind !== 'video') {
  189. continue; // eslint-disable-line no-continue
  190. }
  191. // Get RTX information.
  192. for (const ssrcGroup of m.ssrcGroups || []) {
  193. // Just consider FID.
  194. if (ssrcGroup.semantics !== 'FID') {
  195. continue; // eslint-disable-line no-continue
  196. }
  197. const ssrcs
  198. = ssrcGroup.ssrcs.split(' ').map(ssrc => Number(ssrc));
  199. const ssrc = ssrcs[0];
  200. const rtxSsrc = ssrcs[1];
  201. rtxMap.set(ssrc, rtxSsrc);
  202. rtxSet.add(rtxSsrc);
  203. }
  204. for (const ssrcObject of m.ssrcs || []) {
  205. const ssrc = ssrcObject.id;
  206. // Ignore RTX.
  207. if (rtxSet.has(ssrc)) {
  208. continue; // eslint-disable-line no-continue
  209. }
  210. let info = infos.get(ssrc);
  211. if (!info) {
  212. info = {
  213. kind,
  214. rtxSsrc: rtxMap.get(ssrc),
  215. ssrc
  216. };
  217. infos.set(ssrc, info);
  218. }
  219. switch (ssrcObject.attribute) {
  220. case 'cname': {
  221. info.cname = ssrcObject.value;
  222. break;
  223. }
  224. case 'msid': {
  225. const values = ssrcObject.value.split(' ');
  226. const streamId = values[0];
  227. const trackId = values[1];
  228. info.streamId = streamId;
  229. info.trackId = trackId;
  230. break;
  231. }
  232. case 'mslabel': {
  233. const streamId = ssrcObject.value;
  234. info.streamId = streamId;
  235. break;
  236. }
  237. case 'label': {
  238. const trackId = ssrcObject.value;
  239. info.trackId = trackId;
  240. break;
  241. }
  242. }
  243. }
  244. }
  245. return infos;
  246. }
  247. /**
  248. * Get local ORTC RTP capabilities filtered and adapted to the given remote RTP
  249. * capabilities.
  250. * @param {RTCRtpCapabilities} filterWithCapabilities - RTP capabilities to
  251. * filter with.
  252. * @return {RTCRtpCapabilities}
  253. */
  254. export function getLocalCapabilities(filterWithCapabilities) {
  255. const localFullCapabilities = RTCRtpReceiver.getCapabilities();
  256. const localCapabilities = {
  257. codecs: [],
  258. fecMechanisms: [],
  259. headerExtensions: []
  260. };
  261. // Map of RTX and codec payloads.
  262. // - index: Codec payloadType
  263. // - value: Associated RTX payloadType
  264. // @type {map<Number, Number>}
  265. const remoteRtxMap = new Map();
  266. // Set codecs.
  267. for (const remoteCodec of filterWithCapabilities.codecs) {
  268. const remoteCodecName = remoteCodec.name.toLowerCase();
  269. if (remoteCodecName === 'rtx') {
  270. remoteRtxMap.set(
  271. remoteCodec.parameters.apt, remoteCodec.preferredPayloadType);
  272. continue; // eslint-disable-line no-continue
  273. }
  274. const localCodec = localFullCapabilities.codecs.find(codec =>
  275. codec.name.toLowerCase() === remoteCodecName
  276. && codec.kind === remoteCodec.kind
  277. && codec.clockRate === remoteCodec.clockRate
  278. );
  279. if (!localCodec) {
  280. continue; // eslint-disable-line no-continue
  281. }
  282. const codec = {
  283. clockRate: localCodec.clockRate,
  284. kind: localCodec.kind,
  285. mimeType: `${localCodec.kind}/${localCodec.name}`,
  286. name: localCodec.name,
  287. numChannels: localCodec.numChannels || 1,
  288. parameters: {},
  289. preferredPayloadType: remoteCodec.preferredPayloadType,
  290. rtcpFeedback: []
  291. };
  292. for (const remoteParamName of Object.keys(remoteCodec.parameters)) {
  293. const remoteParamValue
  294. = remoteCodec.parameters[remoteParamName];
  295. for (const localParamName of Object.keys(localCodec.parameters)) {
  296. const localParamValue
  297. = localCodec.parameters[localParamName];
  298. if (localParamName !== remoteParamName) {
  299. continue; // eslint-disable-line no-continue
  300. }
  301. // TODO: We should consider much more cases here, but Edge
  302. // does not support many codec parameters.
  303. if (localParamValue === remoteParamValue) {
  304. // Use this RTP parameter.
  305. codec.parameters[localParamName] = localParamValue;
  306. break;
  307. }
  308. }
  309. }
  310. for (const remoteFb of remoteCodec.rtcpFeedback) {
  311. const localFb = localCodec.rtcpFeedback.find(fb =>
  312. fb.type === remoteFb.type
  313. && fb.parameter === remoteFb.parameter
  314. );
  315. if (localFb) {
  316. // Use this RTCP feedback.
  317. codec.rtcpFeedback.push(localFb);
  318. }
  319. }
  320. // Use this codec.
  321. localCapabilities.codecs.push(codec);
  322. }
  323. // Add RTX for video codecs.
  324. for (const codec of localCapabilities.codecs) {
  325. const payloadType = codec.preferredPayloadType;
  326. if (!remoteRtxMap.has(payloadType)) {
  327. continue; // eslint-disable-line no-continue
  328. }
  329. const rtxCodec = {
  330. clockRate: codec.clockRate,
  331. kind: codec.kind,
  332. mimeType: `${codec.kind}/rtx`,
  333. name: 'rtx',
  334. parameters: {
  335. apt: payloadType
  336. },
  337. preferredPayloadType: remoteRtxMap.get(payloadType),
  338. rtcpFeedback: []
  339. };
  340. // Add RTX codec.
  341. localCapabilities.codecs.push(rtxCodec);
  342. }
  343. // Add RTP header extensions.
  344. for (const remoteExtension of filterWithCapabilities.headerExtensions) {
  345. const localExtension
  346. = localFullCapabilities.headerExtensions.find(extension =>
  347. extension.kind === remoteExtension.kind
  348. && extension.uri === remoteExtension.uri
  349. );
  350. if (localExtension) {
  351. const extension = {
  352. kind: localExtension.kind,
  353. preferredEncrypt: Boolean(remoteExtension.preferredEncrypt),
  354. preferredId: remoteExtension.preferredId,
  355. uri: localExtension.uri
  356. };
  357. // Use this RTP header extension.
  358. localCapabilities.headerExtensions.push(extension);
  359. }
  360. }
  361. // Add FEC mechanisms.
  362. // NOTE: We don't support FEC yet and, in fact, neither does Edge.
  363. for (const remoteFecMechanism of filterWithCapabilities.fecMechanisms) {
  364. const localFecMechanism
  365. = localFullCapabilities.fecMechanisms.find(fec =>
  366. fec === remoteFecMechanism
  367. );
  368. if (localFecMechanism) {
  369. // Use this FEC mechanism.
  370. localCapabilities.fecMechanisms.push(localFecMechanism);
  371. }
  372. }
  373. return localCapabilities;
  374. }
  375. /**
  376. * Get the first acive media section.
  377. * @param {Object} sdpObject - SDP object generated by sdp-transform.
  378. * @return {Object} SDP media section as parsed by sdp-transform.
  379. */
  380. function getFirstActiveMediaSection(sdpObject) {
  381. return sdpObject.media.find(m =>
  382. m.iceUfrag && m.port !== 0
  383. );
  384. }