Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

ReceiveVideoController.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. import { getLogger } from '@jitsi/logger';
  2. import isEqual from 'lodash.isequal';
  3. import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
  4. const logger = getLogger(__filename);
  5. const MAX_HEIGHT_ONSTAGE = 2160;
  6. const MAX_HEIGHT_THUMBNAIL = 180;
  7. const LASTN_UNLIMITED = -1;
  8. /**
  9. * This class translates the legacy signaling format between the client and the bridge (that affects bandwidth
  10. * allocation) to the new format described here https://github.com/jitsi/jitsi-videobridge/blob/master/doc/allocation.md
  11. */
  12. export class ReceiverVideoConstraints {
  13. /**
  14. * Creates a new instance.
  15. */
  16. constructor() {
  17. // Default constraints used for endpoints that are not explicitly included in constraints.
  18. // These constraints are used for endpoints that are thumbnails in the stage view.
  19. this._defaultConstraints = { 'maxHeight': MAX_HEIGHT_THUMBNAIL };
  20. // The number of videos requested from the bridge.
  21. this._lastN = LASTN_UNLIMITED;
  22. // The number representing the maximum video height the local client should receive from the bridge.
  23. this._maxFrameHeight = MAX_HEIGHT_ONSTAGE;
  24. // The endpoint IDs of the participants that are currently selected.
  25. this._selectedEndpoints = [];
  26. this._receiverVideoConstraints = {
  27. constraints: {},
  28. defaultConstraints: this.defaultConstraints,
  29. lastN: this._lastN,
  30. onStageEndpoints: [],
  31. selectedEndpoints: this._selectedEndpoints
  32. };
  33. }
  34. /**
  35. * Returns the receiver video constraints that need to be sent on the bridge channel.
  36. */
  37. get constraints() {
  38. this._receiverVideoConstraints.lastN = this._lastN;
  39. if (!this._selectedEndpoints.length) {
  40. return this._receiverVideoConstraints;
  41. }
  42. // The client is assumed to be in TileView if it has selected more than one endpoint, otherwise it is
  43. // assumed to be in StageView.
  44. this._receiverVideoConstraints.constraints = {};
  45. if (this._selectedEndpoints.length > 1) {
  46. /**
  47. * Tile view.
  48. * Only the default constraints are specified here along with lastN (if it is set).
  49. * {
  50. * 'colibriClass': 'ReceiverVideoConstraints',
  51. * 'defaultConstraints': { 'maxHeight': 360 }
  52. * }
  53. */
  54. this._receiverVideoConstraints.defaultConstraints = { 'maxHeight': this._maxFrameHeight };
  55. this._receiverVideoConstraints.onStageEndpoints = [];
  56. this._receiverVideoConstraints.selectedEndpoints = [];
  57. } else {
  58. /**
  59. * Stage view.
  60. * The participant on stage is specified in onStageEndpoints and a higher maxHeight is specified
  61. * for that endpoint while a default maxHeight of 180 is applied to all the other endpoints.
  62. * {
  63. * 'colibriClass': 'ReceiverVideoConstraints',
  64. * 'onStageEndpoints': ['A'],
  65. * 'defaultConstraints': { 'maxHeight': 180 },
  66. * 'constraints': {
  67. * 'A': { 'maxHeight': 720 }
  68. * }
  69. * }
  70. */
  71. this._receiverVideoConstraints.constraints[this._selectedEndpoints[0]] = {
  72. 'maxHeight': this._maxFrameHeight
  73. };
  74. this._receiverVideoConstraints.defaultConstraints = this._defaultConstraints;
  75. this._receiverVideoConstraints.onStageEndpoints = this._selectedEndpoints;
  76. this._receiverVideoConstraints.selectedEndpoints = [];
  77. }
  78. return this._receiverVideoConstraints;
  79. }
  80. /**
  81. * Updates the lastN field of the ReceiverVideoConstraints sent to the bridge.
  82. *
  83. * @param {number} value
  84. * @returns {boolean} Returns true if the the value has been updated, false otherwise.
  85. */
  86. updateLastN(value) {
  87. const changed = this._lastN !== value;
  88. if (changed) {
  89. this._lastN = value;
  90. logger.debug(`Updating ReceiverVideoConstraints lastN(${value})`);
  91. }
  92. return changed;
  93. }
  94. /**
  95. * Updates the resolution (height requested) in the contraints field of the ReceiverVideoConstraints
  96. * sent to the bridge.
  97. *
  98. * @param {number} maxFrameHeight
  99. * @requires {boolean} Returns true if the the value has been updated, false otherwise.
  100. */
  101. updateReceiveResolution(maxFrameHeight) {
  102. const changed = this._maxFrameHeight !== maxFrameHeight;
  103. if (changed) {
  104. this._maxFrameHeight = maxFrameHeight;
  105. logger.debug(`Updating receive maxFrameHeight: ${maxFrameHeight}`);
  106. }
  107. return changed;
  108. }
  109. /**
  110. * Updates the receiver constraints sent to the bridge.
  111. *
  112. * @param {Object} videoConstraints
  113. * @returns {boolean} Returns true if the the value has been updated, false otherwise.
  114. */
  115. updateReceiverVideoConstraints(videoConstraints) {
  116. const changed = !isEqual(this._receiverVideoConstraints, videoConstraints);
  117. if (changed) {
  118. this._receiverVideoConstraints = videoConstraints;
  119. logger.debug(`Updating ReceiverVideoConstraints ${JSON.stringify(videoConstraints)}`);
  120. }
  121. return changed;
  122. }
  123. /**
  124. * Updates the list of selected endpoints.
  125. *
  126. * @param {Array<string>} ids
  127. * @returns {void}
  128. */
  129. updateSelectedEndpoints(ids) {
  130. logger.debug(`Updating selected endpoints: ${JSON.stringify(ids)}`);
  131. this._selectedEndpoints = ids;
  132. }
  133. }
  134. /**
  135. * This class manages the receive video contraints for a given {@link JitsiConference}. These constraints are
  136. * determined by the application based on how the remote video streams need to be displayed. This class is responsible
  137. * for communicating these constraints to the bridge over the bridge channel.
  138. */
  139. export class ReceiveVideoController {
  140. /**
  141. * Creates a new instance for a given conference.
  142. *
  143. * @param {JitsiConference} conference the conference instance for which the new instance will be managing
  144. * the receive video quality constraints.
  145. * @param {RTC} rtc the rtc instance which is responsible for initializing the bridge channel.
  146. */
  147. constructor(conference, rtc) {
  148. this._conference = conference;
  149. this._rtc = rtc;
  150. const { config } = conference.options;
  151. // The number of videos requested from the bridge, -1 represents unlimited or all available videos.
  152. this._lastN = config?.startLastN ?? (config?.channelLastN || LASTN_UNLIMITED);
  153. // The number representing the maximum video height the local client should receive from the bridge.
  154. this._maxFrameHeight = MAX_HEIGHT_ONSTAGE;
  155. // Enable new receiver constraints by default unless it is explicitly disabled through config.js.
  156. const useNewReceiverConstraints = config?.useNewBandwidthAllocationStrategy ?? true;
  157. if (useNewReceiverConstraints) {
  158. this._receiverVideoConstraints = new ReceiverVideoConstraints();
  159. const lastNUpdated = this._receiverVideoConstraints.updateLastN(this._lastN);
  160. lastNUpdated && this._rtc.setNewReceiverVideoConstraints(this._receiverVideoConstraints.constraints);
  161. } else {
  162. this._rtc.setLastN(this._lastN);
  163. }
  164. // The endpoint IDs of the participants that are currently selected.
  165. this._selectedEndpoints = [];
  166. this._conference.on(
  167. JitsiConferenceEvents._MEDIA_SESSION_STARTED,
  168. session => this._onMediaSessionStarted(session));
  169. }
  170. /**
  171. * Handles the {@link JitsiConferenceEvents.MEDIA_SESSION_STARTED}, that is when the conference creates new media
  172. * session. The preferred receive frameHeight is applied on the media session.
  173. *
  174. * @param {JingleSessionPC} mediaSession - the started media session.
  175. * @returns {void}
  176. * @private
  177. */
  178. _onMediaSessionStarted(mediaSession) {
  179. if (mediaSession.isP2P || !this._receiverVideoConstraints) {
  180. mediaSession.setReceiverVideoConstraint(this._maxFrameHeight);
  181. } else {
  182. this._receiverVideoConstraints.updateReceiveResolution(this._maxFrameHeight);
  183. this._rtc.setNewReceiverVideoConstraints(this._receiverVideoConstraints.constraints);
  184. }
  185. }
  186. /**
  187. * Returns the lastN value for the conference.
  188. *
  189. * @returns {number}
  190. */
  191. getLastN() {
  192. return this._lastN;
  193. }
  194. /**
  195. * Elects the participants with the given ids to be the selected participants in order to always receive video
  196. * for this participant (even when last n is enabled).
  197. *
  198. * @param {Array<string>} ids - The user ids.
  199. * @returns {void}
  200. */
  201. selectEndpoints(ids) {
  202. this._selectedEndpoints = ids;
  203. if (this._receiverVideoConstraints) {
  204. // Filter out the local endpointId from the list of selected endpoints.
  205. const remoteEndpointIds = ids.filter(id => id !== this._conference.myUserId());
  206. const oldConstraints = JSON.parse(JSON.stringify(this._receiverVideoConstraints.constraints));
  207. remoteEndpointIds.length && this._receiverVideoConstraints.updateSelectedEndpoints(remoteEndpointIds);
  208. const newConstraints = this._receiverVideoConstraints.constraints;
  209. // Send bridge message only when the constraints change.
  210. if (!isEqual(newConstraints, oldConstraints)) {
  211. this._rtc.setNewReceiverVideoConstraints(newConstraints);
  212. }
  213. return;
  214. }
  215. this._rtc.selectEndpoints(ids);
  216. }
  217. /**
  218. * Selects a new value for "lastN". The requested amount of videos are going to be delivered after the value is
  219. * in effect. Set to -1 for unlimited or all available videos.
  220. *
  221. * @param {number} value the new value for lastN.
  222. * @returns {void}
  223. */
  224. setLastN(value) {
  225. if (this._lastN !== value) {
  226. this._lastN = value;
  227. if (this._receiverVideoConstraints) {
  228. const lastNUpdated = this._receiverVideoConstraints.updateLastN(value);
  229. // Send out the message on the bridge channel if lastN was updated.
  230. lastNUpdated && this._rtc.setNewReceiverVideoConstraints(this._receiverVideoConstraints.constraints);
  231. return;
  232. }
  233. this._rtc.setLastN(value);
  234. }
  235. }
  236. /**
  237. * Sets the maximum video resolution the local participant should receive from remote participants.
  238. *
  239. * @param {number|undefined} maxFrameHeight - the new value.
  240. * @returns {void}
  241. */
  242. setPreferredReceiveMaxFrameHeight(maxFrameHeight) {
  243. this._maxFrameHeight = maxFrameHeight;
  244. for (const session of this._conference._getMediaSessions()) {
  245. if (session.isP2P || !this._receiverVideoConstraints) {
  246. maxFrameHeight && session.setReceiverVideoConstraint(maxFrameHeight);
  247. } else {
  248. const resolutionUpdated = this._receiverVideoConstraints.updateReceiveResolution(maxFrameHeight);
  249. resolutionUpdated
  250. && this._rtc.setNewReceiverVideoConstraints(this._receiverVideoConstraints.constraints);
  251. }
  252. }
  253. }
  254. /**
  255. * Sets the receiver constraints for the conference.
  256. *
  257. * @param {Object} constraints The video constraints.
  258. */
  259. setReceiverConstraints(constraints) {
  260. if (!this._receiverVideoConstraints) {
  261. this._receiverVideoConstraints = new ReceiverVideoConstraints();
  262. }
  263. const constraintsChanged = this._receiverVideoConstraints.updateReceiverVideoConstraints(constraints);
  264. if (constraintsChanged) {
  265. this._lastN = constraints.lastN ?? this._lastN;
  266. this._selectedEndpoints = constraints.selectedEndpoints ?? this._selectedEndpoints;
  267. this._rtc.setNewReceiverVideoConstraints(constraints);
  268. const p2pSession = this._conference._getMediaSessions().find(session => session.isP2P);
  269. if (p2pSession) {
  270. let maxFrameHeight = Object.values(constraints.constraints)[0]?.maxHeight;
  271. if (!maxFrameHeight) {
  272. maxFrameHeight = constraints.defaultConstraints?.maxHeight;
  273. }
  274. maxFrameHeight && p2pSession.setReceiverVideoConstraint(maxFrameHeight);
  275. }
  276. }
  277. }
  278. }