您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

CodecSelection.js 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  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. // Keep a list of participants that join the call with a non-preferred codec.
  48. // The call is upgraded to the preferred codec once that list is empty.
  49. this.nonPreferredParticipants = [];
  50. this.conference.on(
  51. JitsiConferenceEvents.USER_JOINED,
  52. this._onParticipantJoined.bind(this));
  53. this.conference.on(
  54. JitsiConferenceEvents.USER_LEFT,
  55. this._onParticipantLeft.bind(this));
  56. this.conference.on(
  57. JitsiConferenceEvents._MEDIA_SESSION_STARTED,
  58. session => this._onMediaSessionStared(session));
  59. }
  60. /**
  61. * Checks if a given string is a valid video codec mime type.
  62. *
  63. * @param {string} codec the codec string that needs to be validated.
  64. * @returns {CodecMimeType|null} mime type if valid, null otherwise.
  65. * @private
  66. */
  67. _getCodecMimeType(codec) {
  68. if (typeof codec === 'string') {
  69. return Object.values(CodecMimeType).find(value => value === codec.toLowerCase());
  70. }
  71. return null;
  72. }
  73. /**
  74. * Checks if the given codec is supported by the browser.
  75. *
  76. * @param {CodecMimeType} preferredCodec codec to be checked.
  77. * @returns {boolean} true if the given codec is supported, false otherwise.
  78. * @private
  79. */
  80. _isCodecSupported(preferredCodec) {
  81. // Skip the check on FF and RN because they do not support the getCapabilities API.
  82. // It is safe to assume both of them support all the codecs supported by Chrome.
  83. if (browser.isFirefox() || browser.isReactNative()) {
  84. return true;
  85. }
  86. return window.RTCRtpReceiver
  87. && window.RTCRtpReceiver.getCapabilities('video').codecs
  88. .some(codec => codec.mimeType.toLowerCase() === `video/${preferredCodec}`);
  89. }
  90. /**
  91. * Handles the {@link JitsiConferenceEvents._MEDIA_SESSION_STARTED} event. Codecs need to be
  92. * configured on the media session that is newly created.
  93. *
  94. * @param {JingleSessionPC} mediaSession media session that started.
  95. * @returns {void}
  96. * @private
  97. */
  98. _onMediaSessionStared(mediaSession) {
  99. const preferredCodec = mediaSession.isP2P ? this.p2pPreferredCodec : this.jvbPreferredCodec;
  100. const disabledCodec = this.disabledCodec && this._isCodecSupported(this.disabledCodec)
  101. ? this.disabledCodec
  102. : null;
  103. let codec = preferredCodec;
  104. // For a new endpoint joining the call, JitsiConferenceEvents.USER_JOINED event is received before the
  105. // media session is created, the supported codecs for all the remote endpoints in the call need to be
  106. // compared here before setting the codec on the peerconnection.
  107. if (!mediaSession.isP2P) {
  108. const remoteParticipants = this.conference.getParticipants().map(participant => participant.getId());
  109. for (const remote of remoteParticipants) {
  110. const peerMediaInfo = mediaSession.signalingLayer.getPeerMediaInfo(remote, MediaType.VIDEO);
  111. if (peerMediaInfo && peerMediaInfo.codecType && peerMediaInfo.codecType !== preferredCodec) {
  112. this.nonPreferredParticipants.push(remote);
  113. codec = peerMediaInfo.codecType;
  114. }
  115. }
  116. }
  117. mediaSession.setVideoCodecs(codec, disabledCodec);
  118. }
  119. /**
  120. * Handles the {@link JitsiConferenceEvents.USER_JOINED} event. When a new user joins the call,
  121. * the codec types are compared and the codec configued on the peerconnection is updated when
  122. * needed.
  123. *
  124. * @param {string} id endpoint id of the newly joined user.
  125. * @returns {void}
  126. * @private
  127. */
  128. _onParticipantJoined(id) {
  129. const session = this.conference.jvbJingleSession;
  130. if (session && !this.options.enforcePreferredCodec) {
  131. const peerMediaInfo = session.signalingLayer.getPeerMediaInfo(id, MediaType.VIDEO);
  132. if (!peerMediaInfo) {
  133. return;
  134. }
  135. const newCodec = peerMediaInfo.codecType;
  136. const currentCodec = session.getConfiguredVideoCodec();
  137. if (newCodec
  138. && newCodec !== this.jvbPreferredCodec
  139. && newCodec !== currentCodec
  140. && this._isCodecSupported(newCodec)) {
  141. // Add the participant to the list of participants that don't support the preferred codec.
  142. this.nonPreferredParticipants.push(id);
  143. session.setVideoCodecs(newCodec);
  144. }
  145. }
  146. }
  147. /**
  148. * Handles the {@link JitsiConferenceEvents.USER_LEFT} event. When a user leaves the call,
  149. * the codec configured on the peerconnection is updated to the preferred codec if all the
  150. * users that do not support the preferred codec have left the call.
  151. *
  152. * @param {string} id endpoint id of the user that has left the call.
  153. * @returns {void}
  154. * @private
  155. */
  156. _onParticipantLeft(id) {
  157. const session = this.conference.jvbJingleSession;
  158. if (session && !this.options.enforcePreferredCodec) {
  159. const index = this.nonPreferredParticipants.findIndex(participantId => participantId === id);
  160. if (index > -1) {
  161. this.nonPreferredParticipants.splice(index, 1);
  162. }
  163. // If all the participants that have joined the conference with a
  164. // non-preferred codec have left, switch to the preferred codec.
  165. if (!this.nonPreferredParticipants.length) {
  166. session.setVideoCodecs(this.jvbPreferredCodec);
  167. }
  168. }
  169. }
  170. /**
  171. * Returns the preferred codec for the conference.
  172. *
  173. * @returns {CodecMimeType} preferred codec.
  174. */
  175. getPreferredCodec() {
  176. return this.conference.isP2PActive() ? this.p2pPreferredCodec : this.jvbPreferredCodec;
  177. }
  178. }