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.

TPCUtils.js 24KB

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