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ů.

CodecSelection.js 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import { getLogger } from '@jitsi/logger';
  2. import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
  3. import { CodecMimeType } from '../../service/RTC/CodecMimeType';
  4. import { MediaType } from '../../service/RTC/MediaType';
  5. import browser from '../browser';
  6. const logger = getLogger(__filename);
  7. // Default video codec preferences on mobile and desktop endpoints.
  8. const DESKTOP_VIDEO_CODEC_ORDER = [ CodecMimeType.VP9, CodecMimeType.VP8, CodecMimeType.H264 ];
  9. const MOBILE_P2P_VIDEO_CODEC_ORDER = [ CodecMimeType.H264, CodecMimeType.VP8, CodecMimeType.VP9 ];
  10. const MOBILE_VIDEO_CODEC_ORDER = [ CodecMimeType.VP8, CodecMimeType.VP9, CodecMimeType.H264 ];
  11. /**
  12. * This class handles the codec selection mechanism for the conference based on the config.js settings.
  13. * The preferred codec is selected based on the settings and the list of codecs supported by the browser.
  14. * The preferred codec is published in presence which is then used by the other endpoints in the
  15. * conference to pick a supported codec at join time and when the call transitions between p2p and jvb
  16. * connections.
  17. */
  18. export class CodecSelection {
  19. /**
  20. * Creates a new instance for a given conference.
  21. *
  22. * @param {JitsiConference} conference the conference instance
  23. * @param {*} options
  24. * @param {string} options.jvb settings (codec list, preferred and disabled) for the jvb connection.
  25. * @param {string} options.p2p settings (codec list, preferred and disabled) for the p2p connection.
  26. */
  27. constructor(conference, options) {
  28. this.conference = conference;
  29. this.options = options;
  30. this.codecPreferenceOrder = {};
  31. for (const connectionType of Object.keys(options)) {
  32. // eslint-disable-next-line prefer-const
  33. let { disabledCodec, preferredCodec, preferenceOrder } = options[connectionType];
  34. const supportedCodecs = new Set(this._getSupportedVideoCodecs(connectionType));
  35. // Default preference codec order when no codec preferences are set in config.js
  36. let selectedOrder = Array.from(supportedCodecs);
  37. if (preferenceOrder) {
  38. preferenceOrder = preferenceOrder.map(codec => codec.toLowerCase());
  39. // Select all codecs that are supported by the browser.
  40. selectedOrder = preferenceOrder.filter(codec => supportedCodecs.has(codec));
  41. // Generate the codec list based on the supported codecs and the preferred/disabled (deprecated) settings
  42. } else if (preferredCodec || disabledCodec) {
  43. disabledCodec = disabledCodec?.toLowerCase();
  44. preferredCodec = preferredCodec?.toLowerCase();
  45. // VP8 cannot be disabled since it the default codec.
  46. if (disabledCodec && disabledCodec !== CodecMimeType.VP8) {
  47. selectedOrder = selectedOrder.filter(codec => codec !== disabledCodec);
  48. }
  49. const index = selectedOrder.findIndex(codec => codec === preferredCodec);
  50. // Move the preferred codec to the top of the list.
  51. if (preferredCodec && index !== -1) {
  52. selectedOrder.splice(index, 1);
  53. selectedOrder.unshift(preferredCodec);
  54. }
  55. }
  56. // Push VP9 to the end of the list so that the client continues to decode VP9 even if its not
  57. // preferable to encode VP9 (because of browser bugs on the encoding side or added complexity on mobile
  58. // devices). Currently, VP9 encode is supported on Chrome and on Safari (only for p2p).
  59. const isVp9EncodeSupported = browser.supportsVP9() || (browser.isWebKitBased() && connectionType === 'p2p');
  60. if (!isVp9EncodeSupported || this.conference.isE2EEEnabled()) {
  61. const index = selectedOrder.findIndex(codec => codec === CodecMimeType.VP9);
  62. if (index !== -1) {
  63. selectedOrder.splice(index, 1);
  64. // Remove VP9 from the list when E2EE is enabled since it is not supported.
  65. // TODO - remove this check when support for VP9-E2EE is introduced.
  66. if (!this.conference.isE2EEEnabled()) {
  67. selectedOrder.push(CodecMimeType.VP9);
  68. }
  69. }
  70. }
  71. logger.info(`Codec preference order for ${connectionType} connection is ${selectedOrder}`);
  72. this.codecPreferenceOrder[connectionType] = selectedOrder;
  73. }
  74. this.conference.on(
  75. JitsiConferenceEvents._MEDIA_SESSION_STARTED,
  76. session => this._selectPreferredCodec(session));
  77. this.conference.on(
  78. JitsiConferenceEvents.USER_JOINED,
  79. () => this._selectPreferredCodec());
  80. this.conference.on(
  81. JitsiConferenceEvents.USER_LEFT,
  82. () => this._selectPreferredCodec());
  83. }
  84. /**
  85. * Returns a list of video codecs that are supported by the browser.
  86. *
  87. * @param {string} connectionType - media connection type, p2p or jvb.
  88. * @returns {Array}
  89. */
  90. _getSupportedVideoCodecs(connectionType) {
  91. const videoCodecMimeTypes = browser.isMobileDevice() && connectionType === 'p2p'
  92. ? MOBILE_P2P_VIDEO_CODEC_ORDER
  93. : browser.isMobileDevice() ? MOBILE_VIDEO_CODEC_ORDER : DESKTOP_VIDEO_CODEC_ORDER;
  94. if (connectionType === 'p2p' || this.options.jvb.supportsAv1) {
  95. videoCodecMimeTypes.push(CodecMimeType.AV1);
  96. }
  97. const supportedCodecs = videoCodecMimeTypes.filter(codec =>
  98. (window.RTCRtpReceiver?.getCapabilities?.(MediaType.VIDEO)?.codecs ?? [])
  99. .some(supportedCodec => supportedCodec.mimeType.toLowerCase() === `${MediaType.VIDEO}/${codec}`));
  100. // Select VP8 as the default codec if RTCRtpReceiver.getCapabilities() is not supported by the browser or if it
  101. // returns an empty set.
  102. !supportedCodecs.length && supportedCodecs.push(CodecMimeType.VP8);
  103. return supportedCodecs;
  104. }
  105. /**
  106. * Sets the codec on the media session based on the codec preference order configured in config.js and the supported
  107. * codecs published by the remote participants in their presence.
  108. *
  109. * @param {JingleSessionPC} mediaSession session for which the codec selection has to be made.
  110. */
  111. _selectPreferredCodec(mediaSession) {
  112. const session = mediaSession ? mediaSession : this.conference.jvbJingleSession;
  113. if (!session) {
  114. return;
  115. }
  116. const currentCodecOrder = session.peerconnection.getConfiguredVideoCodecs();
  117. const isJvbSession = session === this.conference.jvbJingleSession;
  118. let localPreferredCodecOrder = isJvbSession ? this.codecPreferenceOrder.jvb : this.codecPreferenceOrder.p2p;
  119. // E2EE is curently supported only for VP8 codec.
  120. if (this.conference.isE2EEEnabled() && isJvbSession) {
  121. localPreferredCodecOrder = [ CodecMimeType.VP8 ];
  122. }
  123. const remoteParticipants = this.conference.getParticipants().map(participant => participant.getId());
  124. const remoteCodecsPerParticipant = remoteParticipants?.map(remote => {
  125. const peerMediaInfo = session._signalingLayer.getPeerMediaInfo(remote, MediaType.VIDEO);
  126. if (peerMediaInfo?.codecList) {
  127. return peerMediaInfo.codecList;
  128. } else if (peerMediaInfo?.codecType) {
  129. return [ peerMediaInfo.codecType ];
  130. }
  131. return [];
  132. });
  133. const selectedCodecOrder = localPreferredCodecOrder.reduce((acc, localCodec) => {
  134. let codecNotSupportedByRemote = false;
  135. // Ignore remote codecs for p2p since only the JVB codec preferences are published in presence.
  136. // For p2p, we rely on the codec order present in the remote offer/answer.
  137. if (!session.isP2P) {
  138. // Remove any codecs that are not supported by any of the remote endpoints. The order of the supported
  139. // codecs locally however will remain the same since we want to support asymmetric codecs.
  140. for (const remoteCodecs of remoteCodecsPerParticipant) {
  141. // Ignore remote participants that do not publish codec preference in presence (transcriber).
  142. if (remoteCodecs.length) {
  143. codecNotSupportedByRemote = codecNotSupportedByRemote
  144. || !remoteCodecs.find(participantCodec => participantCodec === localCodec);
  145. }
  146. }
  147. }
  148. if (!codecNotSupportedByRemote) {
  149. acc.push(localCodec);
  150. }
  151. return acc;
  152. }, []);
  153. if (!selectedCodecOrder.length) {
  154. logger.warn('Invalid codec list generated because of a user joining/leaving the call');
  155. return;
  156. }
  157. // Reconfigure the codecs on the media session.
  158. if (!selectedCodecOrder.every((val, index) => val === currentCodecOrder[index])) {
  159. session.setVideoCodecs(selectedCodecOrder);
  160. }
  161. }
  162. /**
  163. * Returns the current codec preference order for the given connection type.
  164. *
  165. * @param {String} connectionType The media connection type, 'p2p' or 'jvb'.
  166. * @returns {Array<string>}
  167. */
  168. getCodecPreferenceList(connectionType) {
  169. return this.codecPreferenceOrder[connectionType];
  170. }
  171. }