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.

CodecSelection.js 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import { getLogger } from 'jitsi-meet-logger';
  2. import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
  3. import CodecMimeType from '../../service/RTC/CodecMimeType';
  4. import * as MediaType from '../../service/RTC/MediaType';
  5. import browser from '../browser';
  6. const logger = getLogger(__filename);
  7. /**
  8. * This class handles the codec selection mechanism for the conference based on the config.js settings.
  9. * The preferred codec is selected based on the settings and the list of codecs supported by the browser.
  10. * The preferred codec is published in presence which is then used by the other endpoints in the
  11. * conference to pick a supported codec at join time and when the call transitions between p2p and jvb
  12. * connections.
  13. */
  14. export class CodecSelection {
  15. /**
  16. * Creates a new instance for a given conference.
  17. *
  18. * @param {JitsiConference} conference the conference instance
  19. * @param {*} options
  20. * @param {string} options.disabledCodec the codec that needs to be disabled.
  21. * @param {boolean} options.enforcePreferredCodec whether codec preference has to be
  22. * enforced even when an endpoints that doesn't support the preferred codec joins the call.
  23. * Falling back to the standard codec will be skipped when this option is true, endpoints
  24. * that do not support the preferred codec may not be able to encode/decode video when this happens.
  25. * @param {string} options.jvbCodec the codec that is preferred on jvb connection.
  26. * @param {string} options.p2pCodec the codec that is preferred on p2p connection.
  27. */
  28. constructor(conference, options) {
  29. this.conference = conference;
  30. this.options = options;
  31. // VP8 cannot be disabled and it will be the default codec when no preference is set.
  32. this.disabledCodec = options.disabledCodec === CodecMimeType.VP8
  33. ? undefined
  34. : this._getCodecMimeType(options.disabledCodec);
  35. // Check if the codec values passed are valid.
  36. const jvbCodec = this._getCodecMimeType(options.jvbCodec);
  37. const p2pCodec = this._getCodecMimeType(options.p2pCodec);
  38. this.jvbPreferredCodec = jvbCodec && this._isCodecSupported(jvbCodec) ? jvbCodec : CodecMimeType.VP8;
  39. this.p2pPreferredCodec = p2pCodec && this._isCodecSupported(p2pCodec) ? p2pCodec : CodecMimeType.VP8;
  40. logger.debug(`Codec preferences for the conference are JVB: ${this.jvbPreferredCodec},
  41. P2P: ${this.p2pPreferredCodec}`);
  42. // Do not prefer VP9 on Firefox because of the following bug.
  43. // https://bugzilla.mozilla.org/show_bug.cgi?id=1633876
  44. if (browser.isFirefox() && this.jvbPreferredCodec === CodecMimeType.VP9) {
  45. this.jvbPreferredCodec = CodecMimeType.VP8;
  46. }
  47. this.conference.on(
  48. JitsiConferenceEvents.USER_JOINED,
  49. () => this._selectPreferredCodec());
  50. this.conference.on(
  51. JitsiConferenceEvents.USER_LEFT,
  52. () => this._selectPreferredCodec());
  53. this.conference.on(
  54. JitsiConferenceEvents._MEDIA_SESSION_STARTED,
  55. session => this._onMediaSessionStared(session));
  56. }
  57. /**
  58. * Checks if a given string is a valid video codec mime type.
  59. *
  60. * @param {string} codec the codec string that needs to be validated.
  61. * @returns {CodecMimeType|null} mime type if valid, null otherwise.
  62. * @private
  63. */
  64. _getCodecMimeType(codec) {
  65. if (typeof codec === 'string') {
  66. return Object.values(CodecMimeType).find(value => value === codec.toLowerCase());
  67. }
  68. return null;
  69. }
  70. /**
  71. * Checks if the given codec is supported by the browser.
  72. *
  73. * @param {CodecMimeType} preferredCodec codec to be checked.
  74. * @returns {boolean} true if the given codec is supported, false otherwise.
  75. * @private
  76. */
  77. _isCodecSupported(preferredCodec) {
  78. // Skip the check on FF and RN because they do not support the getCapabilities API.
  79. // It is safe to assume both of them support all the codecs supported by Chrome.
  80. if (browser.isFirefox() || browser.isReactNative()) {
  81. return true;
  82. }
  83. return window.RTCRtpReceiver
  84. && window.RTCRtpReceiver.getCapabilities
  85. && window.RTCRtpReceiver.getCapabilities('video').codecs
  86. .some(codec => codec.mimeType.toLowerCase() === `video/${preferredCodec}`);
  87. }
  88. /**
  89. * Handles the {@link JitsiConferenceEvents._MEDIA_SESSION_STARTED} event. Codecs need to be
  90. * configured on the media session that is newly created.
  91. *
  92. * @param {JingleSessionPC} mediaSession media session that started.
  93. * @returns {void}
  94. * @private
  95. */
  96. _onMediaSessionStared(mediaSession) {
  97. const preferredCodec = mediaSession.isP2P ? this.p2pPreferredCodec : this.jvbPreferredCodec;
  98. const disabledCodec = this.disabledCodec && this._isCodecSupported(this.disabledCodec)
  99. ? this.disabledCodec
  100. : null;
  101. this._selectPreferredCodec(mediaSession, preferredCodec, disabledCodec);
  102. }
  103. /**
  104. * Sets the codec on the media session based on the preferred codec setting and the supported codecs
  105. * published by the remote participants in their presence.
  106. *
  107. * @param {JingleSessionPC} mediaSession session for which the codec selection has to be made.
  108. * @param {CodecMimeType} preferredCodec preferred codec.
  109. * @param {CodecMimeType} disabledCodec codec that needs to be disabled.
  110. */
  111. _selectPreferredCodec(mediaSession = null, preferredCodec = null, disabledCodec = null) {
  112. const session = mediaSession ? mediaSession : this.conference.jvbJingleSession;
  113. const codec = preferredCodec ? preferredCodec : this.jvbPreferredCodec;
  114. let selectedCodec = codec;
  115. if (session && !session.isP2P && !this.options.enforcePreferredCodec) {
  116. const remoteParticipants = this.conference.getParticipants().map(participant => participant.getId());
  117. for (const remote of remoteParticipants) {
  118. const peerMediaInfo = session.signalingLayer.getPeerMediaInfo(remote, MediaType.VIDEO);
  119. if (peerMediaInfo && peerMediaInfo.codecType && peerMediaInfo.codecType !== codec) {
  120. selectedCodec = peerMediaInfo.codecType;
  121. }
  122. }
  123. }
  124. session && session.setVideoCodecs(selectedCodec, disabledCodec);
  125. }
  126. /**
  127. * Returns the preferred codec for the conference. The preferred codec for the JVB media session
  128. * is the one that gets published in presence and a comparision is made whenever a participant joins
  129. * or leaves the call.
  130. *
  131. * @returns {CodecMimeType} preferred codec.
  132. */
  133. getPreferredCodec() {
  134. return this.jvbPreferredCodec;
  135. }
  136. }