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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. import { getLogger } from '@jitsi/logger';
  2. import transform from 'sdp-transform';
  3. import { MediaDirection } from '../../service/RTC/MediaDirection';
  4. import { MediaType } from '../../service/RTC/MediaType';
  5. import { getSourceIndexFromSourceName } from '../../service/RTC/SignalingLayer';
  6. import { VideoType } from '../../service/RTC/VideoType';
  7. import browser from '../browser';
  8. import FeatureFlags from '../flags/FeatureFlags';
  9. const logger = getLogger(__filename);
  10. const DESKTOP_SHARE_RATE = 500000;
  11. const LD_BITRATE = 200000;
  12. const SD_BITRATE = 700000;
  13. const SIM_LAYER_1_RID = '1';
  14. const SIM_LAYER_2_RID = '2';
  15. const SIM_LAYER_3_RID = '3';
  16. export const HD_BITRATE = 2500000;
  17. export const HD_SCALE_FACTOR = 1;
  18. export const LD_SCALE_FACTOR = 4;
  19. export const SD_SCALE_FACTOR = 2;
  20. export const SIM_LAYER_RIDS = [ SIM_LAYER_1_RID, SIM_LAYER_2_RID, SIM_LAYER_3_RID ];
  21. /**
  22. * Handles track related operations on TraceablePeerConnection when browser is
  23. * running in unified plan mode.
  24. */
  25. export class TPCUtils {
  26. /**
  27. * Creates a new instance for a given TraceablePeerConnection
  28. *
  29. * @param peerconnection - the tpc instance for which we have utility functions.
  30. */
  31. constructor(peerconnection) {
  32. this.pc = peerconnection;
  33. const bitrateSettings = this.pc.options?.videoQuality?.maxBitratesVideo;
  34. const standardBitrates = {
  35. low: LD_BITRATE,
  36. standard: SD_BITRATE,
  37. high: HD_BITRATE,
  38. ssHigh: HD_BITRATE
  39. };
  40. // Check if the max. bitrates for video are specified through config.js videoQuality settings.
  41. // Right now only VP8 bitrates are configured on the simulcast encodings, VP9 bitrates have to be
  42. // configured on the SDP using b:AS line.
  43. this.videoBitrates = bitrateSettings ?? standardBitrates;
  44. this.encodingBitrates = this.videoBitrates.VP8 ?? this.videoBitrates;
  45. }
  46. /**
  47. * Obtains stream encodings that need to be configured on the given track based
  48. * on the track media type and the simulcast setting.
  49. * @param {JitsiLocalTrack} localTrack
  50. */
  51. _getStreamEncodings(localTrack) {
  52. if (this.pc.isSimulcastOn() && localTrack.isVideoTrack()) {
  53. return this._getVideoStreamEncodings(localTrack.getVideoType());
  54. }
  55. return localTrack.isVideoTrack()
  56. ? [ {
  57. active: this.pc.videoTransferActive,
  58. maxBitrate: this.videoBitrates.high
  59. } ]
  60. : [ { active: this.pc.audioTransferActive } ];
  61. }
  62. /**
  63. * The startup configuration for the stream encodings that are applicable to
  64. * the video stream when a new sender is created on the peerconnection. The initial
  65. * config takes into account the differences in browser's simulcast implementation.
  66. *
  67. * Encoding parameters:
  68. * active - determine the on/off state of a particular encoding.
  69. * maxBitrate - max. bitrate value to be applied to that particular encoding
  70. * based on the encoding's resolution and config.js videoQuality settings if applicable.
  71. * rid - Rtp Stream ID that is configured for a particular simulcast stream.
  72. * scaleResolutionDownBy - the factor by which the encoding is scaled down from the
  73. * original resolution of the captured video.
  74. *
  75. * @param {VideoType} videoType
  76. */
  77. _getVideoStreamEncodings(videoType) {
  78. const maxVideoBitrate = videoType === VideoType.DESKTOP && this.encodingBitrates.ssHigh
  79. ? this.encodingBitrates.ssHigh : this.encodingBitrates.high;
  80. // The SSRCs on older versions of Firefox are reversed in SDP, i.e., they have resolution order of 1:2:4 as
  81. // opposed to Chromium and other browsers. This has been reverted in Firefox 117 as part of the below commit.
  82. // https://hg.mozilla.org/mozilla-central/rev/b0348f1f8d7197fb87158ba74542d28d46133997
  83. const reversedEncodings = browser.isFirefox() && browser.isVersionLessThan(117);
  84. return [
  85. {
  86. active: this.pc.videoTransferActive,
  87. maxBitrate: reversedEncodings ? maxVideoBitrate : this.encodingBitrates.low,
  88. rid: SIM_LAYER_1_RID,
  89. scaleResolutionDownBy: reversedEncodings ? HD_SCALE_FACTOR : LD_SCALE_FACTOR
  90. },
  91. {
  92. active: this.pc.videoTransferActive,
  93. maxBitrate: this.encodingBitrates.standard,
  94. rid: SIM_LAYER_2_RID,
  95. scaleResolutionDownBy: SD_SCALE_FACTOR
  96. },
  97. {
  98. active: this.pc.videoTransferActive,
  99. maxBitrate: reversedEncodings ? this.encodingBitrates.low : maxVideoBitrate,
  100. rid: SIM_LAYER_3_RID,
  101. scaleResolutionDownBy: reversedEncodings ? LD_SCALE_FACTOR : HD_SCALE_FACTOR
  102. }
  103. ];
  104. }
  105. /**
  106. * Updates the sender parameters in the stream encodings.
  107. *
  108. * @param {RTCRtpSender} sender - the sender associated with a MediaStreamTrack.
  109. * @param {boolean} enable - whether the streams needs to be enabled or disabled.
  110. * @returns {Promise} - A promise that resolves when the operation is successful, rejected otherwise.
  111. */
  112. _updateSenderEncodings(sender, enable) {
  113. const parameters = sender.getParameters();
  114. if (parameters?.encodings?.length) {
  115. for (const encoding of parameters.encodings) {
  116. encoding.active = enable;
  117. }
  118. }
  119. return sender.setParameters(parameters);
  120. }
  121. /**
  122. * Ensures that the ssrcs associated with a FID ssrc-group appear in the correct order, i.e.,
  123. * the primary ssrc first and the secondary rtx ssrc later. This is important for unified
  124. * plan since we have only one FID group per media description.
  125. * @param {Object} description the webRTC session description instance for the remote
  126. * description.
  127. * @private
  128. */
  129. ensureCorrectOrderOfSsrcs(description) {
  130. const parsedSdp = transform.parse(description.sdp);
  131. parsedSdp.media.forEach(mLine => {
  132. if (mLine.type === MediaType.AUDIO) {
  133. return;
  134. }
  135. if (!mLine.ssrcGroups || !mLine.ssrcGroups.length) {
  136. return;
  137. }
  138. let reorderedSsrcs = [];
  139. const ssrcs = new Set();
  140. mLine.ssrcGroups.map(group =>
  141. group.ssrcs
  142. .split(' ')
  143. .filter(Boolean)
  144. .forEach(ssrc => ssrcs.add(ssrc))
  145. );
  146. ssrcs.forEach(ssrc => {
  147. const sources = mLine.ssrcs.filter(source => source.id.toString() === ssrc);
  148. reorderedSsrcs = reorderedSsrcs.concat(sources);
  149. });
  150. mLine.ssrcs = reorderedSsrcs;
  151. });
  152. return new RTCSessionDescription({
  153. type: description.type,
  154. sdp: transform.write(parsedSdp)
  155. });
  156. }
  157. /**
  158. * Returns the transceiver associated with a given RTCRtpSender/RTCRtpReceiver.
  159. *
  160. * @param {string} mediaType - type of track associated with the transceiver 'audio' or 'video'.
  161. * @param {JitsiLocalTrack} localTrack - local track to be used for lookup.
  162. * @returns {RTCRtpTransceiver}
  163. */
  164. findTransceiver(mediaType, localTrack = null) {
  165. const transceiver = localTrack?.track && localTrack.getOriginalStream()
  166. ? this.pc.peerconnection.getTransceivers().find(t => t.sender?.track?.id === localTrack.getTrackId())
  167. : this.pc.peerconnection.getTransceivers().find(t => t.receiver?.track?.kind === mediaType);
  168. return transceiver;
  169. }
  170. /**
  171. * Takes in a *unified plan* offer and inserts the appropriate
  172. * parameters for adding simulcast receive support.
  173. * @param {Object} desc - A session description object
  174. * @param {String} desc.type - the type (offer/answer)
  175. * @param {String} desc.sdp - the sdp content
  176. *
  177. * @return {Object} A session description (same format as above) object
  178. * with its sdp field modified to advertise simulcast receive support
  179. */
  180. insertUnifiedPlanSimulcastReceive(desc) {
  181. // a=simulcast line is not needed on browsers where we SDP munging is used for enabling on simulcast.
  182. // Remove this check when the client switches to RID/MID based simulcast on all browsers.
  183. if (browser.usesSdpMungingForSimulcast()) {
  184. return desc;
  185. }
  186. const sdp = transform.parse(desc.sdp);
  187. const idx = sdp.media.findIndex(mline => mline.type === MediaType.VIDEO);
  188. if (sdp.media[idx].rids && (sdp.media[idx].simulcast_03 || sdp.media[idx].simulcast)) {
  189. // Make sure we don't have the simulcast recv line on video descriptions other than
  190. // the first video description.
  191. sdp.media.forEach((mline, i) => {
  192. if (mline.type === MediaType.VIDEO && i !== idx) {
  193. sdp.media[i].rids = undefined;
  194. sdp.media[i].simulcast = undefined;
  195. // eslint-disable-next-line camelcase
  196. sdp.media[i].simulcast_03 = undefined;
  197. }
  198. });
  199. return new RTCSessionDescription({
  200. type: desc.type,
  201. sdp: transform.write(sdp)
  202. });
  203. }
  204. // In order of highest to lowest spatial quality
  205. sdp.media[idx].rids = [
  206. {
  207. id: SIM_LAYER_1_RID,
  208. direction: 'recv'
  209. },
  210. {
  211. id: SIM_LAYER_2_RID,
  212. direction: 'recv'
  213. },
  214. {
  215. id: SIM_LAYER_3_RID,
  216. direction: 'recv'
  217. }
  218. ];
  219. // Firefox 72 has stopped parsing the legacy rid= parameters in simulcast attributes.
  220. // eslint-disable-next-line max-len
  221. // https://www.fxsitecompat.dev/en-CA/docs/2019/pt-and-rid-in-webrtc-simulcast-attributes-are-no-longer-supported/
  222. const simulcastLine = browser.isFirefox() && browser.isVersionGreaterThan(71)
  223. ? `recv ${SIM_LAYER_RIDS.join(';')}`
  224. : `recv rid=${SIM_LAYER_RIDS.join(';')}`;
  225. // eslint-disable-next-line camelcase
  226. sdp.media[idx].simulcast_03 = {
  227. value: simulcastLine
  228. };
  229. return new RTCSessionDescription({
  230. type: desc.type,
  231. sdp: transform.write(sdp)
  232. });
  233. }
  234. /**
  235. * Adds {@link JitsiLocalTrack} to the WebRTC peerconnection for the first time.
  236. * @param {JitsiLocalTrack} track - track to be added to the peerconnection.
  237. * @param {boolean} isInitiator - boolean that indicates if the endpoint is offerer in a p2p connection.
  238. * @returns {void}
  239. */
  240. addTrack(localTrack, isInitiator) {
  241. const track = localTrack.getTrack();
  242. if (isInitiator) {
  243. const streams = [];
  244. if (localTrack.getOriginalStream()) {
  245. streams.push(localTrack.getOriginalStream());
  246. }
  247. // Use pc.addTransceiver() for the initiator case when local tracks are getting added
  248. // to the peerconnection before a session-initiate is sent over to the peer.
  249. const transceiverInit = {
  250. direction: MediaDirection.SENDRECV,
  251. streams,
  252. sendEncodings: []
  253. };
  254. if (!browser.isFirefox()) {
  255. transceiverInit.sendEncodings = this._getStreamEncodings(localTrack);
  256. }
  257. this.pc.peerconnection.addTransceiver(track, transceiverInit);
  258. } else {
  259. // Use pc.addTrack() for responder case so that we can re-use the m-lines that were created
  260. // when setRemoteDescription was called. pc.addTrack() automatically attaches to any existing
  261. // unused "recv-only" transceiver.
  262. this.pc.peerconnection.addTrack(track);
  263. }
  264. }
  265. /**
  266. * Returns the calculated active state of the simulcast encodings based on the frame height requested for the send
  267. * stream. All the encodings that have a resolution lower than the frame height requested will be enabled.
  268. *
  269. * @param {JitsiLocalTrack} localVideoTrack The local video track.
  270. * @param {number} newHeight The resolution requested for the video track.
  271. * @returns {Array<boolean>}
  272. */
  273. calculateEncodingsActiveState(localVideoTrack, newHeight) {
  274. const height = localVideoTrack.getHeight();
  275. const videoStreamEncodings = this._getVideoStreamEncodings(localVideoTrack.getVideoType());
  276. const encodingsState = videoStreamEncodings
  277. .map(encoding => height / encoding.scaleResolutionDownBy)
  278. .map((frameHeight, idx) => {
  279. let active = localVideoTrack.getVideoType() === VideoType.CAMERA
  280. // Keep the LD stream enabled even when the LD stream's resolution is higher than of the requested
  281. // resolution. This can happen when camera is captured at resolutions higher than 720p but the
  282. // requested resolution is 180. Since getParameters doesn't give us information about the resolutions
  283. // of the simulcast encodings, we have to rely on our initial config for the simulcast streams.
  284. ? newHeight > 0 && videoStreamEncodings[idx]?.scaleResolutionDownBy === LD_SCALE_FACTOR
  285. ? true
  286. : frameHeight <= newHeight
  287. // Keep all the encodings for desktop track active.
  288. : true;
  289. // Disable the lower spatial layers for screensharing in Unified plan when low fps screensharing is in
  290. // progress. Sending all three streams often results in the browser suspending the high resolution in low
  291. // b/w and cpu cases, especially on the low end machines. Suspending the low resolution streams ensures
  292. // that the highest resolution stream is available always. Safari is an exception here since it does not
  293. // send the desktop stream at all if only the high resolution stream is enabled.
  294. if (localVideoTrack.getVideoType() === VideoType.DESKTOP
  295. && this.pc._capScreenshareBitrate
  296. && this.pc.usesUnifiedPlan()
  297. && !browser.isWebKitBased()
  298. && videoStreamEncodings[idx].scaleResolutionDownBy !== HD_SCALE_FACTOR) {
  299. active = false;
  300. }
  301. return active;
  302. });
  303. return encodingsState;
  304. }
  305. /**
  306. * Returns the calculates max bitrates that need to be configured on the simulcast encodings based on the video
  307. * type and other considerations associated with screenshare.
  308. *
  309. * @param {JitsiLocalTrack} localVideoTrack The local video track.
  310. * @returns {Array<number>}
  311. */
  312. calculateEncodingsBitrates(localVideoTrack) {
  313. const videoType = localVideoTrack.getVideoType();
  314. const desktopShareBitrate = this.pc.options?.videoQuality?.desktopBitrate || DESKTOP_SHARE_RATE;
  315. const lowFpsScreenshare = localVideoTrack.getVideoType() === VideoType.DESKTOP
  316. && this.pc._capScreenshareBitrate
  317. && !browser.isWebKitBased();
  318. const encodingsBitrates = this._getVideoStreamEncodings(localVideoTrack.getVideoType())
  319. .map(encoding => {
  320. const bitrate = lowFpsScreenshare
  321. ? desktopShareBitrate
  322. // For high fps screenshare, 'maxBitrate' setting must be cleared on Chrome in plan-b, because
  323. // if simulcast is enabled for screen and maxBitrates are set then Chrome will not send the
  324. // desktop stream.
  325. : videoType === VideoType.DESKTOP && browser.isChromiumBased() && !this.pc.usesUnifiedPlan()
  326. ? undefined
  327. : encoding.maxBitrate;
  328. return bitrate;
  329. });
  330. return encodingsBitrates;
  331. }
  332. /**
  333. * Returns the max resolution that the client is configured to encode for a given local video track. The actual
  334. * send resolution might be downscaled based on cpu and bandwidth constraints.
  335. *
  336. * @param {JitsiLocalTrack} localVideoTrack - The local video track.
  337. * @returns {number|null} The max encoded resolution for the given video track.
  338. */
  339. getConfiguredEncodeResolution(localVideoTrack) {
  340. const height = localVideoTrack.getHeight();
  341. const videoSender = this.pc.findSenderForTrack(localVideoTrack.getTrack());
  342. let maxHeight = 0;
  343. if (!videoSender) {
  344. return null;
  345. }
  346. const parameters = videoSender.getParameters();
  347. if (!parameters?.encodings?.length) {
  348. return null;
  349. }
  350. const hasIncorrectConfig = this.pc._capScreenshareBitrate
  351. ? parameters.encodings.every(encoding => encoding.active)
  352. : parameters.encodings.some(encoding => !encoding.active);
  353. // Check if every encoding is active for screenshare track when low fps screenshare is configured or some
  354. // of the encodings are disabled when high fps screenshare is configured. In both these cases, the track
  355. // encodings need to be reconfigured. This is needed when p2p->jvb switch happens and new sender constraints
  356. // are not received by the client.
  357. if (localVideoTrack.getVideoType() === VideoType.DESKTOP && hasIncorrectConfig) {
  358. return null;
  359. }
  360. for (const encoding in parameters.encodings) {
  361. if (parameters.encodings[encoding].active) {
  362. const scaleResolutionDownBy
  363. = this.pc.isSimulcastOn()
  364. ? this._getVideoStreamEncodings(localVideoTrack.getVideoType())[encoding].scaleResolutionDownBy
  365. : parameters.encodings[encoding].scaleResolutionDownBy;
  366. maxHeight = Math.max(maxHeight, height / scaleResolutionDownBy);
  367. }
  368. }
  369. return maxHeight;
  370. }
  371. /**
  372. * Replaces the existing track on a RTCRtpSender with the given track.
  373. *
  374. * @param {JitsiLocalTrack} oldTrack - existing track on the sender that needs to be removed.
  375. * @param {JitsiLocalTrack} newTrack - new track that needs to be added to the sender.
  376. * @returns {Promise<RTCRtpTransceiver>} - resolved with the associated transceiver when done, rejected otherwise.
  377. */
  378. replaceTrack(oldTrack, newTrack) {
  379. const mediaType = newTrack?.getType() ?? oldTrack?.getType();
  380. const localTracks = this.pc.getLocalTracks(mediaType);
  381. const track = newTrack?.getTrack() ?? null;
  382. const isNewLocalSource = FeatureFlags.isMultiStreamSendSupportEnabled()
  383. && localTracks?.length
  384. && !oldTrack
  385. && newTrack
  386. && !localTracks.find(t => t === newTrack);
  387. let transceiver;
  388. // If old track exists, replace the track on the corresponding sender.
  389. if (oldTrack && !oldTrack.isMuted()) {
  390. transceiver = this.pc.peerconnection.getTransceivers().find(t => t.sender.track === oldTrack.getTrack());
  391. // Find the first recvonly transceiver when more than one track of the same media type is being added to the pc.
  392. // As part of the track addition, a new m-line was added to the remote description with direction set to
  393. // recvonly.
  394. } else if (isNewLocalSource) {
  395. transceiver = this.pc.peerconnection.getTransceivers().find(
  396. t => t.receiver.track.kind === mediaType
  397. && t.direction === MediaDirection.RECVONLY
  398. // Re-use any existing recvonly transceiver (if available) for p2p case.
  399. && ((this.pc.isP2P && t.currentDirection === MediaDirection.RECVONLY)
  400. || (t.currentDirection === MediaDirection.INACTIVE && !t.stopped)));
  401. // For mute/unmute operations, find the transceiver based on the track index in the source name if present,
  402. // otherwise it is assumed to be the first local track that was added to the peerconnection.
  403. } else {
  404. transceiver = this.pc.peerconnection.getTransceivers().find(t => t.receiver.track.kind === mediaType);
  405. const sourceName = newTrack?.getSourceName() ?? oldTrack?.getSourceName();
  406. if (sourceName) {
  407. const trackIndex = getSourceIndexFromSourceName(sourceName);
  408. if (this.pc.isP2P) {
  409. transceiver = this.pc.peerconnection.getTransceivers()
  410. .filter(t => t.receiver.track.kind === mediaType)[trackIndex];
  411. } else if (oldTrack) {
  412. const transceiverMid = this.pc._localTrackTransceiverMids.get(oldTrack.rtcId);
  413. transceiver = this.pc.peerconnection.getTransceivers().find(t => t.mid === transceiverMid);
  414. } else if (trackIndex) {
  415. transceiver = this.pc.peerconnection.getTransceivers()
  416. .filter(t => t.receiver.track.kind === mediaType
  417. && t.direction !== MediaDirection.RECVONLY)[trackIndex];
  418. }
  419. }
  420. }
  421. if (!transceiver) {
  422. return Promise.reject(
  423. new Error(`Replace track failed - no transceiver for old: ${oldTrack}, new: ${newTrack}`));
  424. }
  425. logger.debug(`${this.pc} Replacing ${oldTrack} with ${newTrack}`);
  426. return transceiver.sender.replaceTrack(track)
  427. .then(() => Promise.resolve(transceiver));
  428. }
  429. /**
  430. * Set the simulcast stream encoding properties on the RTCRtpSender.
  431. * @param {JitsiLocalTrack} track - the current track in use for which
  432. * the encodings are to be set.
  433. * @returns {Promise<void>} - resolved when done.
  434. */
  435. setEncodings(track) {
  436. const mediaType = track.getType();
  437. const transceiver = this.findTransceiver(mediaType, track);
  438. const parameters = transceiver?.sender?.getParameters();
  439. // Resolve if the encodings are not available yet. This happens immediately after the track is added to the
  440. // peerconnection on chrome in unified-plan. It is ok to ignore and not report the error here since the
  441. // action that triggers 'addTrack' (like unmute) will also configure the encodings and set bitrates after that.
  442. if (!parameters?.encodings?.length) {
  443. return Promise.resolve();
  444. }
  445. parameters.encodings = this._getStreamEncodings(track);
  446. if (mediaType === MediaType.VIDEO) {
  447. return this.pc._updateVideoSenderParameters(() => transceiver.sender.setParameters(parameters));
  448. }
  449. return transceiver.sender.setParameters(parameters);
  450. }
  451. /**
  452. * Resumes or suspends media on the peerconnection by setting the active state on RTCRtpEncodingParameters
  453. * associated with all the senders that have a track attached to it.
  454. *
  455. * @param {boolean} enable - whether outgoing media needs to be enabled or disabled.
  456. * @param {string} mediaType - media type, 'audio' or 'video', if neither is passed, all outgoing media will either
  457. * be enabled or disabled.
  458. * @returns {Promise} - A promise that is resolved when the change is succesful on all the senders, rejected
  459. * otherwise.
  460. */
  461. setMediaTransferActive(enable, mediaType) {
  462. logger.info(`${this.pc} ${enable ? 'Resuming' : 'Suspending'} media transfer.`);
  463. const senders = this.pc.peerconnection.getSenders()
  464. .filter(s => Boolean(s.track) && (!mediaType || s.track.kind === mediaType));
  465. const promises = [];
  466. for (const sender of senders) {
  467. if (sender.track.kind === MediaType.VIDEO) {
  468. promises.push(this.pc._updateVideoSenderParameters(() => this._updateSenderEncodings(sender, enable)));
  469. } else {
  470. promises.push(this._updateSenderEncodings(sender, enable));
  471. }
  472. }
  473. return Promise.allSettled(promises)
  474. .then(settledResult => {
  475. const errors = settledResult
  476. .filter(result => result.status === 'rejected')
  477. .map(result => result.reason);
  478. if (errors.length) {
  479. return Promise.reject(new Error('Failed to change encodings on the RTCRtpSenders'
  480. + `${errors.join(' ')}`));
  481. }
  482. return Promise.resolve();
  483. });
  484. }
  485. /**
  486. * Ensures that the resolution of the stream encodings are consistent with the values
  487. * that were configured on the RTCRtpSender when the source was added to the peerconnection.
  488. * This should prevent us from overriding the default values if the browser returns
  489. * erroneous values when RTCRtpSender.getParameters is used for getting the encodings info.
  490. * @param {JitsiLocalTrack} localVideoTrack The local video track.
  491. * @param {Object} parameters - the RTCRtpEncodingParameters obtained from the browser.
  492. * @returns {void}
  493. */
  494. updateEncodingsResolution(localVideoTrack, parameters) {
  495. if (!(browser.isWebKitBased() && parameters.encodings && Array.isArray(parameters.encodings))) {
  496. return;
  497. }
  498. const allEqualEncodings
  499. = encodings => encodings.every(encoding => typeof encoding.scaleResolutionDownBy !== 'undefined'
  500. && encoding.scaleResolutionDownBy === encodings[0].scaleResolutionDownBy);
  501. // Implement the workaround only when all the encodings report the same resolution.
  502. if (allEqualEncodings(parameters.encodings)) {
  503. const videoStreamEncodings = this._getVideoStreamEncodings(localVideoTrack.getVideoType());
  504. parameters.encodings.forEach((encoding, idx) => {
  505. encoding.scaleResolutionDownBy = videoStreamEncodings[idx].scaleResolutionDownBy;
  506. });
  507. }
  508. }
  509. }