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

SendVideoController.js 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import { getLogger } from '@jitsi/logger';
  2. import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
  3. import RTCEvents from '../../service/RTC/RTCEvents';
  4. import FeatureFlags from '../flags/FeatureFlags';
  5. import MediaSessionEvents from '../xmpp/MediaSessionEvents';
  6. const logger = getLogger(__filename);
  7. const MAX_LOCAL_RESOLUTION = 2160;
  8. /**
  9. * The class manages send video constraints across media sessions({@link JingleSessionPC}) which belong to
  10. * {@link JitsiConference}. It finds the lowest common value, between the local user's send preference and
  11. * the remote party's receive preference. Also this module will consider only the active session's receive value,
  12. * because local tracks are shared and while JVB may have no preference, the remote p2p may have and they may be totally
  13. * different.
  14. */
  15. export default class SendVideoController {
  16. /**
  17. * Creates new instance for a given conference.
  18. *
  19. * @param {JitsiConference} conference - the conference instance for which the new instance will be managing
  20. * the send video quality constraints.
  21. * @param {RTC} rtc - the rtc instance that is responsible for sending the messages on the bridge channel.
  22. */
  23. constructor(conference, rtc) {
  24. this._conference = conference;
  25. this._preferredSendMaxFrameHeight = MAX_LOCAL_RESOLUTION;
  26. this._rtc = rtc;
  27. /**
  28. * Source name based sender constraints.
  29. * @type {Map<string, number>};
  30. */
  31. this._sourceSenderConstraints = new Map();
  32. this._conference.on(
  33. JitsiConferenceEvents._MEDIA_SESSION_STARTED,
  34. session => this._onMediaSessionStarted(session));
  35. this._conference.on(
  36. JitsiConferenceEvents._MEDIA_SESSION_ACTIVE_CHANGED,
  37. () => this._configureConstraintsForLocalSources());
  38. this._rtc.on(
  39. RTCEvents.SENDER_VIDEO_CONSTRAINTS_CHANGED,
  40. videoConstraints => this._onSenderConstraintsReceived(videoConstraints));
  41. }
  42. /**
  43. * Configures the video encodings on the local sources when a media connection is established or becomes active.
  44. *
  45. * @returns {Promise<void[]>}
  46. * @private
  47. */
  48. _configureConstraintsForLocalSources() {
  49. if (FeatureFlags.isSourceNameSignalingEnabled()) {
  50. for (const track of this._rtc.getLocalVideoTracks()) {
  51. const sourceName = track.getSourceName();
  52. sourceName && this._propagateSendMaxFrameHeight(sourceName);
  53. }
  54. } else {
  55. this._propagateSendMaxFrameHeight();
  56. }
  57. }
  58. /**
  59. * Handles the {@link JitsiConferenceEvents.MEDIA_SESSION_STARTED}, that is when the conference creates new media
  60. * session. It doesn't mean it's already active though. For example the JVB connection may be created after
  61. * the conference has entered the p2p mode already.
  62. *
  63. * @param {JingleSessionPC} mediaSession - the started media session.
  64. * @private
  65. */
  66. _onMediaSessionStarted(mediaSession) {
  67. if (FeatureFlags.isSourceNameSignalingEnabled()) {
  68. mediaSession.addListener(
  69. MediaSessionEvents.REMOTE_SOURCE_CONSTRAINTS_CHANGED,
  70. (session, sourceConstraints) => {
  71. session === this._conference.getActiveMediaSession()
  72. && sourceConstraints.forEach(constraint => this._onSenderConstraintsReceived(constraint));
  73. });
  74. } else {
  75. mediaSession.addListener(
  76. MediaSessionEvents.REMOTE_VIDEO_CONSTRAINTS_CHANGED,
  77. session => session === this._conference.getActiveMediaSession()
  78. && this._configureConstraintsForLocalSources());
  79. }
  80. }
  81. /**
  82. * Propagates the video constraints if they have changed.
  83. *
  84. * @param {Object} videoConstraints - The sender video constraints received from the bridge.
  85. * @returns {Promise<void[]>}
  86. * @private
  87. */
  88. _onSenderConstraintsReceived(videoConstraints) {
  89. if (FeatureFlags.isSourceNameSignalingEnabled()) {
  90. const { maxHeight, sourceName } = videoConstraints;
  91. const localVideoTracks = this._conference.getLocalVideoTracks() ?? [];
  92. for (const track of localVideoTracks) {
  93. // Propagate the sender constraint only if it has changed.
  94. if (track.getSourceName() === sourceName
  95. && (!this._sourceSenderConstraints.has(sourceName)
  96. || this._sourceSenderConstraints.get(sourceName) !== maxHeight)) {
  97. this._sourceSenderConstraints.set(sourceName, maxHeight);
  98. logger.debug(`Sender constraints for source:${sourceName} changed to maxHeight:${maxHeight}`);
  99. this._propagateSendMaxFrameHeight(sourceName);
  100. }
  101. }
  102. } else if (this._senderVideoConstraints?.idealHeight !== videoConstraints.idealHeight) {
  103. this._senderVideoConstraints = videoConstraints;
  104. this._propagateSendMaxFrameHeight();
  105. }
  106. }
  107. /**
  108. * Figures out the send video constraint as specified by {@link _selectSendMaxFrameHeight} and sets it on all media
  109. * sessions for the reasons mentioned in this class description.
  110. *
  111. * @param {string} sourceName - The source for which sender constraints have changed.
  112. * @returns {Promise<void[]>}
  113. * @private
  114. */
  115. _propagateSendMaxFrameHeight(sourceName = null) {
  116. if (FeatureFlags.isSourceNameSignalingEnabled() && !sourceName) {
  117. throw new Error('sourceName missing for calculating the sendMaxHeight for video tracks');
  118. }
  119. const sendMaxFrameHeight = this._selectSendMaxFrameHeight(sourceName);
  120. const promises = [];
  121. if (sendMaxFrameHeight >= 0) {
  122. for (const session of this._conference.getMediaSessions()) {
  123. promises.push(session.setSenderVideoConstraint(sendMaxFrameHeight, sourceName));
  124. }
  125. }
  126. return Promise.all(promises);
  127. }
  128. /**
  129. * Selects the lowest common value for the local video send constraint by looking at local user's preference and
  130. * the active media session's receive preference set by the remote party.
  131. *
  132. * @param {string} sourceName - The source for which sender constraints have changed.
  133. * @returns {number|undefined}
  134. * @private
  135. */
  136. _selectSendMaxFrameHeight(sourceName = null) {
  137. if (FeatureFlags.isSourceNameSignalingEnabled() && !sourceName) {
  138. throw new Error('sourceName missing for calculating the sendMaxHeight for video tracks');
  139. }
  140. const activeMediaSession = this._conference.getActiveMediaSession();
  141. const remoteRecvMaxFrameHeight = activeMediaSession
  142. ? activeMediaSession.isP2P
  143. ? sourceName
  144. ? this._sourceSenderConstraints.get(sourceName)
  145. : activeMediaSession.getRemoteRecvMaxFrameHeight()
  146. : sourceName ? this._sourceSenderConstraints.get(sourceName) : this._senderVideoConstraints?.idealHeight
  147. : undefined;
  148. if (this._preferredSendMaxFrameHeight >= 0 && remoteRecvMaxFrameHeight >= 0) {
  149. return Math.min(this._preferredSendMaxFrameHeight, remoteRecvMaxFrameHeight);
  150. } else if (remoteRecvMaxFrameHeight >= 0) {
  151. return remoteRecvMaxFrameHeight;
  152. }
  153. return this._preferredSendMaxFrameHeight;
  154. }
  155. /**
  156. * Sets local preference for max send video frame height.
  157. *
  158. * @param {number} maxFrameHeight - the new value to set.
  159. * @returns {Promise<void[]>} - resolved when the operation is complete.
  160. */
  161. setPreferredSendMaxFrameHeight(maxFrameHeight) {
  162. this._preferredSendMaxFrameHeight = maxFrameHeight;
  163. if (FeatureFlags.isSourceNameSignalingEnabled()) {
  164. const promises = [];
  165. for (const sourceName of this._sourceSenderConstraints.keys()) {
  166. promises.push(this._propagateSendMaxFrameHeight(sourceName));
  167. }
  168. return Promise.allSettled(promises);
  169. }
  170. return this._propagateSendMaxFrameHeight();
  171. }
  172. }