Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

ReceiveVideoController.ts 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. import { getLogger } from '@jitsi/logger';
  2. import { isEqual } from 'lodash-es';
  3. import JitsiConference from '../../JitsiConference';
  4. import { MediaType } from '../../service/RTC/MediaType';
  5. import { ASSUMED_BANDWIDTH_BPS, LAST_N_UNLIMITED } from '../../service/RTC/StandardVideoQualitySettings';
  6. import RTC from '../RTC/RTC';
  7. import JingleSessionPC from '../xmpp/JingleSessionPC';
  8. const logger = getLogger('modules/qualitycontrol/ReceiveVideoController');
  9. const MAX_HEIGHT = 2160;
  10. export interface IReceiverVideoConstraint {
  11. maxHeight: number;
  12. }
  13. export interface IReceiverVideoConstraints {
  14. assumedBandwidthBps?: number;
  15. constraints?: { [sourceName: string]: IReceiverVideoConstraint; };
  16. defaultConstraints?: IReceiverVideoConstraint;
  17. lastN?: number;
  18. }
  19. /**
  20. * This class manages the receive video contraints for a given {@link JitsiConference}. These constraints are
  21. * determined by the application based on how the remote video streams need to be displayed. This class is responsible
  22. * for communicating these constraints to the bridge over the bridge channel.
  23. */
  24. export default class ReceiveVideoController {
  25. private _conference: JitsiConference;
  26. private _rtc: RTC;
  27. private _lastN: number;
  28. private _maxFrameHeight: number;
  29. /**
  30. * The map that holds the max frame height requested per remote source for p2p connection.
  31. *
  32. * @type Map<string, number>
  33. */
  34. private _sourceReceiverConstraints: Map<string, number>;
  35. /**
  36. * The number of bps requested from the bridge.
  37. */
  38. private _assumedBandwidthBps: number;
  39. private _lastNLimitedByCpu: boolean;
  40. private _receiveResolutionLimitedByCpu: boolean;
  41. private _receiverVideoConstraints: IReceiverVideoConstraints;
  42. /**
  43. * Creates a new instance for a given conference.
  44. *
  45. * @param {JitsiConference} conference the conference instance for which the new instance will be managing
  46. * the receive video quality constraints.
  47. */
  48. constructor(conference: JitsiConference) {
  49. this._conference = conference;
  50. this._rtc = conference.rtc;
  51. const { config } = conference.options;
  52. // The number of videos requested from the bridge, -1 represents unlimited or all available videos.
  53. this._lastN = config?.startLastN ?? config?.channelLastN ?? LAST_N_UNLIMITED;
  54. // The number representing the maximum video height the local client should receive from the bridge.
  55. this._maxFrameHeight = MAX_HEIGHT;
  56. /**
  57. * The map that holds the max frame height requested per remote source for p2p connection.
  58. *
  59. * @type Map<string, number>
  60. */
  61. this._sourceReceiverConstraints = new Map();
  62. /**
  63. * The number of bps requested from the bridge.
  64. */
  65. this._assumedBandwidthBps = ASSUMED_BANDWIDTH_BPS;
  66. this._lastNLimitedByCpu = false;
  67. this._receiveResolutionLimitedByCpu = false;
  68. // The default receiver video constraints.
  69. this._receiverVideoConstraints = {
  70. assumedBandwidthBps: this._assumedBandwidthBps,
  71. lastN: this._lastN
  72. };
  73. }
  74. /**
  75. * Returns a map of all the remote source names and the corresponding max frame heights.
  76. *
  77. * @param {JingleSessionPC} mediaSession - the media session.
  78. * @param {number} maxFrameHeight - the height to be requested for remote sources.
  79. * @returns
  80. */
  81. _getDefaultSourceReceiverConstraints(
  82. mediaSession: JingleSessionPC,
  83. maxFrameHeight?: number
  84. ): Map<string, number> {
  85. const height = maxFrameHeight ?? MAX_HEIGHT;
  86. const remoteVideoTracks = mediaSession.peerconnection?.getRemoteTracks(null, MediaType.VIDEO) || [];
  87. const receiverConstraints = new Map<string, number>();
  88. for (const track of remoteVideoTracks) {
  89. receiverConstraints.set(track.getSourceName(), height);
  90. }
  91. return receiverConstraints;
  92. }
  93. /**
  94. * Updates the source based constraints based on the maxHeight set.
  95. *
  96. * @param {number} maxFrameHeight - the height to be requested for remote sources.
  97. * @returns {void}
  98. */
  99. _updateIndividualConstraints(maxFrameHeight?: number): void {
  100. const individualConstraints = this._receiverVideoConstraints.constraints;
  101. if (individualConstraints && Object.keys(individualConstraints).length) {
  102. for (const value of Object.values(individualConstraints)) {
  103. value.maxHeight = maxFrameHeight ?? Math.min(value.maxHeight, this._maxFrameHeight);
  104. }
  105. } else {
  106. this._receiverVideoConstraints.defaultConstraints = { 'maxHeight': this._maxFrameHeight };
  107. }
  108. }
  109. /**
  110. * Returns the last set of receiver constraints that were set on the bridge channel.
  111. *
  112. * @returns {IReceiverVideoConstraints}
  113. */
  114. getCurrentReceiverConstraints(): IReceiverVideoConstraints {
  115. return this._receiverVideoConstraints;
  116. }
  117. /**
  118. * Returns the lastN value for the conference.
  119. *
  120. * @returns {number}
  121. */
  122. getLastN(): number {
  123. return this._lastN;
  124. }
  125. /**
  126. * Checks whether last-n was lowered because of a cpu limitation.
  127. *
  128. * @returns {boolean}
  129. */
  130. isLastNLimitedByCpu(): boolean {
  131. return this._lastNLimitedByCpu;
  132. }
  133. /**
  134. * Handles the {@link JitsiConferenceEvents.MEDIA_SESSION_STARTED}, that is when the conference creates new media
  135. * session. The preferred receive frameHeight is applied on the media session.
  136. *
  137. * @param {JingleSessionPC} mediaSession - the started media session.
  138. * @returns {void}
  139. */
  140. onMediaSessionStarted(mediaSession: JingleSessionPC): void {
  141. if (mediaSession.isP2P) {
  142. mediaSession.setReceiverVideoConstraint(this._getDefaultSourceReceiverConstraints(mediaSession));
  143. } else {
  144. this._rtc.setReceiverVideoConstraints(this._receiverVideoConstraints);
  145. }
  146. }
  147. /**
  148. * Sets the assumed bandwidth bps the local participant should receive from remote participants.
  149. *
  150. * @param {number|undefined} assumedBandwidthBps - the new value.
  151. * @returns {void}
  152. */
  153. setAssumedBandwidthBps(assumedBandwidthBps?: number): void {
  154. if (this._receiverVideoConstraints.assumedBandwidthBps !== assumedBandwidthBps) {
  155. this._receiverVideoConstraints.assumedBandwidthBps = assumedBandwidthBps;
  156. this._rtc.setReceiverVideoConstraints(this._receiverVideoConstraints);
  157. }
  158. }
  159. /**
  160. * Selects a new value for "lastN". The requested amount of videos are going to be delivered after the value is
  161. * in effect. Set to -1 for unlimited or all available videos.
  162. *
  163. * @param {number} value the new value for lastN.
  164. * @returns {void}
  165. */
  166. setLastN(value: number): void {
  167. if (this._lastN !== value) {
  168. this._lastN = value;
  169. this._receiverVideoConstraints.lastN = value;
  170. this._rtc.setReceiverVideoConstraints(this._receiverVideoConstraints);
  171. }
  172. }
  173. /**
  174. * Updates the lastNLimitedByCpu field.
  175. *
  176. * @param {boolean} enabled
  177. * @returns {void}
  178. */
  179. setLastNLimitedByCpu(enabled: boolean): void {
  180. if (this._lastNLimitedByCpu !== enabled) {
  181. this._lastNLimitedByCpu = enabled;
  182. logger.info(`ReceiveVideoController - Setting the lastNLimitedByCpu flag to ${enabled}`);
  183. }
  184. }
  185. /**
  186. * Sets the maximum video resolution the local participant should receive from remote participants.
  187. *
  188. * @param {number|undefined} maxFrameHeight - the new value.
  189. * @returns {void}
  190. */
  191. setPreferredReceiveMaxFrameHeight(maxFrameHeight?: number): void {
  192. this._maxFrameHeight = maxFrameHeight;
  193. for (const session of this._conference.getMediaSessions()) {
  194. if (session.isP2P) {
  195. session.setReceiverVideoConstraint(this._getDefaultSourceReceiverConstraints(session, maxFrameHeight));
  196. } else {
  197. this._updateIndividualConstraints(maxFrameHeight);
  198. this._rtc.setReceiverVideoConstraints(this._receiverVideoConstraints);
  199. }
  200. }
  201. }
  202. /**
  203. * Sets the receiver constraints for the conference.
  204. *
  205. * @param {IReceiverVideoConstraints} constraints The video constraints.
  206. */
  207. setReceiverConstraints(constraints: IReceiverVideoConstraints): void {
  208. if (!constraints) {
  209. return;
  210. }
  211. const constraintsChanged = !isEqual(this._receiverVideoConstraints, constraints);
  212. if (constraintsChanged || this._lastNLimitedByCpu || this._receiveResolutionLimitedByCpu) {
  213. this._receiverVideoConstraints = constraints;
  214. this._assumedBandwidthBps = constraints.assumedBandwidthBps ?? this._assumedBandwidthBps;
  215. this._lastN = typeof constraints.lastN !== 'undefined' && !this._lastNLimitedByCpu
  216. ? constraints.lastN : this._lastN;
  217. this._receiverVideoConstraints.lastN = this._lastN;
  218. this._receiveResolutionLimitedByCpu && this._updateIndividualConstraints();
  219. // Send the constraints on the bridge channel.
  220. this._rtc.setReceiverVideoConstraints(this._receiverVideoConstraints);
  221. const p2pSession = this._conference.getMediaSessions().find(session => session.isP2P);
  222. if (!p2pSession || !this._receiverVideoConstraints.constraints) {
  223. return;
  224. }
  225. const mappedConstraints: [string, number][] = Array.from(Object.entries(this._receiverVideoConstraints.constraints))
  226. .map(([ key, value ]) => [ key, value.maxHeight ]);
  227. this._sourceReceiverConstraints = new Map(mappedConstraints);
  228. // Send the receiver constraints to the peer through a "content-modify" message.
  229. p2pSession.setReceiverVideoConstraint(this._sourceReceiverConstraints);
  230. }
  231. }
  232. /**
  233. * Updates the receivedResolutioLimitedByCpu field.
  234. *
  235. * @param {boolean} enabled
  236. * @return {void}
  237. */
  238. setReceiveResolutionLimitedByCpu(enabled: boolean): void {
  239. if (this._receiveResolutionLimitedByCpu !== enabled) {
  240. this._receiveResolutionLimitedByCpu = enabled;
  241. logger.info(`ReceiveVideoController - Setting the receiveResolutionLimitedByCpu flag to ${enabled}`);
  242. }
  243. }
  244. }