You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

load-test-participant.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. /* global $, config, JitsiMeetJS */
  2. import 'jquery';
  3. import { setConfigFromURLParams } from '../../react/features/base/config/functions';
  4. import { parseURLParams } from '../../react/features/base/util/parseURLParams';
  5. import { parseURIString } from '../../react/features/base/util/uri';
  6. import { validateLastNLimits, limitLastN } from '../../react/features/base/lastn/functions';
  7. setConfigFromURLParams(config, {}, {}, window.location);
  8. const params = parseURLParams(window.location, false, 'hash');
  9. const { isHuman = false } = params;
  10. const {
  11. localVideo = config.startWithVideoMuted !== true,
  12. remoteVideo = isHuman,
  13. remoteAudio = isHuman,
  14. autoPlayVideo = config.testing.noAutoPlayVideo !== true,
  15. stageView = config.disableTileView
  16. } = params;
  17. let {
  18. localAudio = config.startWithAudioMuted !== true,
  19. } = params;
  20. const { room: roomName } = parseURIString(window.location.toString());
  21. let connection = null;
  22. let connected = false;
  23. let room = null;
  24. let numParticipants = 1;
  25. let localTracks = [];
  26. const remoteTracks = {};
  27. let maxFrameHeight = 0;
  28. let selectedParticipant = null;
  29. window.APP = {
  30. conference: {
  31. getStats() {
  32. return room.connectionQuality.getStats();
  33. },
  34. getConnectionState() {
  35. return room && room.getConnectionState();
  36. },
  37. muteAudio(mute) {
  38. localAudio = mute;
  39. for (let i = 0; i < localTracks.length; i++) {
  40. if (localTracks[i].getType() === 'audio') {
  41. if (mute) {
  42. localTracks[i].mute();
  43. }
  44. else {
  45. localTracks[i].unmute();
  46. // if track was not added we need to add it to the peerconnection
  47. if (!room.getLocalAudioTrack()) {
  48. room.replaceTrack(null, localTracks[i]);
  49. }
  50. }
  51. }
  52. }
  53. }
  54. },
  55. get room() {
  56. return room;
  57. },
  58. get connection() {
  59. return connection;
  60. },
  61. get numParticipants() {
  62. return numParticipants;
  63. },
  64. get localTracks() {
  65. return localTracks;
  66. },
  67. get remoteTracks() {
  68. return remoteTracks;
  69. },
  70. get params() {
  71. return {
  72. roomName,
  73. localAudio,
  74. localVideo,
  75. remoteVideo,
  76. remoteAudio,
  77. autoPlayVideo,
  78. stageView
  79. };
  80. }
  81. };
  82. /**
  83. * Simple emulation of jitsi-meet's screen layout behavior
  84. */
  85. function updateMaxFrameHeight() {
  86. if (!connected) {
  87. return;
  88. }
  89. let newMaxFrameHeight;
  90. if (stageView) {
  91. newMaxFrameHeight = 2160;
  92. }
  93. else {
  94. if (numParticipants <= 2) {
  95. newMaxFrameHeight = 720;
  96. } else if (numParticipants <= 4) {
  97. newMaxFrameHeight = 360;
  98. } else {
  99. newMaxFrameHeight = 180;
  100. }
  101. }
  102. if (room && maxFrameHeight !== newMaxFrameHeight) {
  103. maxFrameHeight = newMaxFrameHeight;
  104. room.setReceiverVideoConstraint(maxFrameHeight);
  105. }
  106. }
  107. /**
  108. * Simple emulation of jitsi-meet's lastN behavior
  109. */
  110. function updateLastN() {
  111. if (!connected) {
  112. return;
  113. }
  114. let lastN = typeof config.channelLastN === 'undefined' ? -1 : config.channelLastN;
  115. const limitedLastN = limitLastN(numParticipants, validateLastNLimits(config.lastNLimits));
  116. if (limitedLastN !== undefined) {
  117. lastN = lastN === -1 ? limitedLastN : Math.min(limitedLastN, lastN);
  118. }
  119. if (lastN === room.getLastN()) {
  120. return;
  121. }
  122. room.setLastN(lastN);
  123. }
  124. /**
  125. * Helper function to query whether a participant ID is a valid ID
  126. * for stage view.
  127. */
  128. function isValidStageViewParticipant(id) {
  129. return (id !== room.myUserId() && room.getParticipantById(id));
  130. }
  131. /**
  132. * Simple emulation of jitsi-meet's stage view participant selection behavior.
  133. * Doesn't take into account pinning or screen sharing, and the initial behavior
  134. * is slightly different.
  135. * @returns Whether the selected participant changed.
  136. */
  137. function selectStageViewParticipant(selected, previous) {
  138. let newSelectedParticipant;
  139. if (isValidStageViewParticipant(selected)) {
  140. newSelectedParticipant = selected;
  141. }
  142. else {
  143. newSelectedParticipant = previous.find(isValidStageViewParticipant);
  144. }
  145. if (newSelectedParticipant && newSelectedParticipant !== selectedParticipant) {
  146. selectedParticipant = newSelectedParticipant;
  147. return true;
  148. }
  149. return false;
  150. }
  151. /**
  152. * Simple emulation of jitsi-meet's selectParticipants behavior
  153. */
  154. function selectParticipants() {
  155. if (!connected) {
  156. return;
  157. }
  158. if (stageView) {
  159. if (selectedParticipant) {
  160. room.selectParticipants([selectedParticipant]);
  161. }
  162. }
  163. else {
  164. /* jitsi-meet's current Tile View behavior. */
  165. const ids = room.getParticipants().map(participant => participant.getId());
  166. room.selectParticipants(ids);
  167. }
  168. }
  169. /**
  170. * Called when number of participants changes.
  171. */
  172. function setNumberOfParticipants() {
  173. $('#participants').text(numParticipants);
  174. if (!stageView) {
  175. selectParticipants();
  176. updateMaxFrameHeight();
  177. }
  178. updateLastN();
  179. }
  180. /**
  181. * Called when ICE connects
  182. */
  183. function onConnectionEstablished() {
  184. connected = true;
  185. selectParticipants();
  186. updateMaxFrameHeight();
  187. updateLastN();
  188. }
  189. /**
  190. * Handles dominant speaker changed.
  191. * @param id
  192. */
  193. function onDominantSpeakerChanged(selected, previous) {
  194. if (selectStageViewParticipant(selected, previous)) {
  195. selectParticipants();
  196. }
  197. updateMaxFrameHeight();
  198. }
  199. /**
  200. * Handles local tracks.
  201. * @param tracks Array with JitsiTrack objects
  202. */
  203. function onLocalTracks(tracks = []) {
  204. localTracks = tracks;
  205. for (let i = 0; i < localTracks.length; i++) {
  206. if (localTracks[i].getType() === 'video') {
  207. $('body').append(`<video ${autoPlayVideo ? 'autoplay="1" ' : ''}id='localVideo${i}' />`);
  208. localTracks[i].attach($(`#localVideo${i}`)[0]);
  209. room.addTrack(localTracks[i]);
  210. } else {
  211. if (localAudio) {
  212. room.addTrack(localTracks[i]);
  213. } else {
  214. localTracks[i].mute();
  215. }
  216. $('body').append(
  217. `<audio autoplay='1' muted='true' id='localAudio${i}' />`);
  218. localTracks[i].attach($(`#localAudio${i}`)[0]);
  219. }
  220. }
  221. }
  222. /**
  223. * Handles remote tracks
  224. * @param track JitsiTrack object
  225. */
  226. function onRemoteTrack(track) {
  227. if (track.isLocal()
  228. || (track.getType() === 'video' && !remoteVideo) || (track.getType() === 'audio' && !remoteAudio)) {
  229. return;
  230. }
  231. const participant = track.getParticipantId();
  232. if (!remoteTracks[participant]) {
  233. remoteTracks[participant] = [];
  234. }
  235. const idx = remoteTracks[participant].push(track);
  236. const id = participant + track.getType() + idx;
  237. if (track.getType() === 'video') {
  238. $('body').append(`<video autoplay='1' id='${id}' />`);
  239. } else {
  240. $('body').append(`<audio autoplay='1' id='${id}' />`);
  241. }
  242. track.attach($(`#${id}`)[0]);
  243. }
  244. /**
  245. * That function is executed when the conference is joined
  246. */
  247. function onConferenceJoined() {
  248. console.log('Conference joined');
  249. }
  250. /**
  251. * Handles start muted events, when audio and/or video are muted due to
  252. * startAudioMuted or startVideoMuted policy.
  253. */
  254. function onStartMuted() {
  255. // Give it some time, as it may be currently in the process of muting
  256. setTimeout(() => {
  257. const localAudioTrack = room.getLocalAudioTrack();
  258. if (localAudio && localAudioTrack && localAudioTrack.isMuted()) {
  259. localAudioTrack.unmute();
  260. }
  261. const localVideoTrack = room.getLocalVideoTrack();
  262. if (localVideo && localVideoTrack && localVideoTrack.isMuted()) {
  263. localVideoTrack.unmute();
  264. }
  265. }, 2000);
  266. }
  267. /**
  268. *
  269. * @param id
  270. */
  271. function onUserJoined(id) {
  272. numParticipants++;
  273. setNumberOfParticipants();
  274. remoteTracks[id] = [];
  275. }
  276. /**
  277. *
  278. * @param id
  279. */
  280. function onUserLeft(id) {
  281. numParticipants--;
  282. setNumberOfParticipants();
  283. if (!remoteTracks[id]) {
  284. return;
  285. }
  286. const tracks = remoteTracks[id];
  287. for (let i = 0; i < tracks.length; i++) {
  288. const container = $(`#${id}${tracks[i].getType()}${i + 1}`)[0];
  289. if (container) {
  290. tracks[i].detach(container);
  291. container.parentElement.removeChild(container);
  292. }
  293. }
  294. }
  295. /**
  296. * That function is called when connection is established successfully
  297. */
  298. function onConnectionSuccess() {
  299. room = connection.initJitsiConference(roomName.toLowerCase(), config);
  300. room.on(JitsiMeetJS.events.conference.STARTED_MUTED, onStartMuted);
  301. room.on(JitsiMeetJS.events.conference.TRACK_ADDED, onRemoteTrack);
  302. room.on(JitsiMeetJS.events.conference.CONFERENCE_JOINED, onConferenceJoined);
  303. room.on(JitsiMeetJS.events.conference.CONNECTION_ESTABLISHED, onConnectionEstablished);
  304. room.on(JitsiMeetJS.events.conference.USER_JOINED, onUserJoined);
  305. room.on(JitsiMeetJS.events.conference.USER_LEFT, onUserLeft);
  306. if (stageView) {
  307. room.on(JitsiMeetJS.events.conference.DOMINANT_SPEAKER_CHANGED, onDominantSpeakerChanged);
  308. }
  309. const devices = [];
  310. if (localVideo) {
  311. devices.push('video');
  312. }
  313. // we always create audio local tracks
  314. devices.push('audio');
  315. if (devices.length > 0) {
  316. JitsiMeetJS.createLocalTracks({ devices })
  317. .then(onLocalTracks)
  318. .then(() => {
  319. room.join();
  320. })
  321. .catch(error => {
  322. throw error;
  323. });
  324. } else {
  325. room.join();
  326. }
  327. updateMaxFrameHeight();
  328. }
  329. /**
  330. * This function is called when the connection fail.
  331. */
  332. function onConnectionFailed() {
  333. console.error('Connection Failed!');
  334. }
  335. /**
  336. * This function is called when we disconnect.
  337. */
  338. function disconnect() {
  339. console.log('disconnect!');
  340. connection.removeEventListener(
  341. JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED,
  342. onConnectionSuccess);
  343. connection.removeEventListener(
  344. JitsiMeetJS.events.connection.CONNECTION_FAILED,
  345. onConnectionFailed);
  346. connection.removeEventListener(
  347. JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED,
  348. disconnect);
  349. }
  350. /**
  351. *
  352. */
  353. function unload() {
  354. for (let i = 0; i < localTracks.length; i++) {
  355. localTracks[i].dispose();
  356. }
  357. room.leave();
  358. connection.disconnect();
  359. }
  360. $(window).bind('beforeunload', unload);
  361. $(window).bind('unload', unload);
  362. JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR);
  363. JitsiMeetJS.init(config);
  364. config.serviceUrl = config.bosh = `${config.websocket || config.bosh}?room=${roomName.toLowerCase()}`;
  365. if (config.websocketKeepAliveUrl) {
  366. config.websocketKeepAliveUrl += `?room=${roomName.toLowerCase()}`;
  367. }
  368. connection = new JitsiMeetJS.JitsiConnection(null, null, config);
  369. connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED, onConnectionSuccess);
  370. connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED, onConnectionFailed);
  371. connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, disconnect);
  372. connection.connect();