feat(load-test) make it possible to start multiple load-test clients from the same tab
* Refactor load-test into an object. * Convert to class syntax. * Bind member function callbacks. * More binding and thisage. * More thisage. * More tweaks * Rename numParticipants as remoteParticipants. * Change back. * Fix userLeft. * Add members for event listeners, to be able to remove them. * Add numClients parameter that allows multiple clients to be started. * Clear clients array on unload. * Add latency between starting clients.
This commit is contained in:
parent
3ab47ff96c
commit
5b86182f94
|
@ -14,7 +14,9 @@ const {
|
||||||
remoteVideo = isHuman,
|
remoteVideo = isHuman,
|
||||||
remoteAudio = isHuman,
|
remoteAudio = isHuman,
|
||||||
autoPlayVideo = config.testing.noAutoPlayVideo !== true,
|
autoPlayVideo = config.testing.noAutoPlayVideo !== true,
|
||||||
stageView = config.disableTileView
|
stageView = config.disableTileView,
|
||||||
|
numClients = 1,
|
||||||
|
clientInterval = 100 // ms
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
@ -23,42 +25,430 @@ let {
|
||||||
|
|
||||||
const { room: roomName } = parseURIString(window.location.toString());
|
const { room: roomName } = parseURIString(window.location.toString());
|
||||||
|
|
||||||
let connection = null;
|
class LoadTestClient {
|
||||||
|
constructor(id) {
|
||||||
|
this.id = id;
|
||||||
|
this.connection = null;
|
||||||
|
this.connected = false;
|
||||||
|
this.room = null;
|
||||||
|
this.numParticipants = 1;
|
||||||
|
this.localTracks = [];
|
||||||
|
this.remoteTracks = {};
|
||||||
|
this.maxFrameHeight = 0;
|
||||||
|
this.selectedParticipant = null;
|
||||||
|
}
|
||||||
|
|
||||||
let connected = false;
|
/**
|
||||||
|
* Simple emulation of jitsi-meet's screen layout behavior
|
||||||
|
*/
|
||||||
|
updateMaxFrameHeight() {
|
||||||
|
if (!this.connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let room = null;
|
let newMaxFrameHeight;
|
||||||
|
|
||||||
let numParticipants = 1;
|
if (stageView) {
|
||||||
|
newMaxFrameHeight = 2160;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (this.numParticipants <= 2) {
|
||||||
|
newMaxFrameHeight = 720;
|
||||||
|
} else if (this.numParticipants <= 4) {
|
||||||
|
newMaxFrameHeight = 360;
|
||||||
|
} else {
|
||||||
|
this.newMaxFrameHeight = 180;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let localTracks = [];
|
if (this.room && this.maxFrameHeight !== newMaxFrameHeight) {
|
||||||
const remoteTracks = {};
|
this.maxFrameHeight = newMaxFrameHeight;
|
||||||
|
this.room.setReceiverVideoConstraint(this.maxFrameHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let maxFrameHeight = 0;
|
/**
|
||||||
|
* Simple emulation of jitsi-meet's lastN behavior
|
||||||
|
*/
|
||||||
|
updateLastN() {
|
||||||
|
if (!this.connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let selectedParticipant = null;
|
let lastN = typeof config.channelLastN === 'undefined' ? -1 : config.channelLastN;
|
||||||
|
|
||||||
|
const limitedLastN = limitLastN(this.numParticipants, validateLastNLimits(config.lastNLimits));
|
||||||
|
|
||||||
|
if (limitedLastN !== undefined) {
|
||||||
|
lastN = lastN === -1 ? limitedLastN : Math.min(limitedLastN, lastN);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastN === this.room.getLastN()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.room.setLastN(lastN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to query whether a participant ID is a valid ID
|
||||||
|
* for stage view.
|
||||||
|
*/
|
||||||
|
isValidStageViewParticipant(id) {
|
||||||
|
return (id !== room.myUserId() && room.getParticipantById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple emulation of jitsi-meet's stage view participant selection behavior.
|
||||||
|
* Doesn't take into account pinning or screen sharing, and the initial behavior
|
||||||
|
* is slightly different.
|
||||||
|
* @returns Whether the selected participant changed.
|
||||||
|
*/
|
||||||
|
selectStageViewParticipant(selected, previous) {
|
||||||
|
let newSelectedParticipant;
|
||||||
|
|
||||||
|
if (this.isValidStageViewParticipant(selected)) {
|
||||||
|
newSelectedParticipant = selected;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newSelectedParticipant = previous.find(isValidStageViewParticipant);
|
||||||
|
}
|
||||||
|
if (newSelectedParticipant && newSelectedParticipant !== this.selectedParticipant) {
|
||||||
|
this.selectedParticipant = newSelectedParticipant;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple emulation of jitsi-meet's selectParticipants behavior
|
||||||
|
*/
|
||||||
|
selectParticipants() {
|
||||||
|
if (!this.connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stageView) {
|
||||||
|
if (this.selectedParticipant) {
|
||||||
|
this.room.selectParticipants([this.selectedParticipant]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* jitsi-meet's current Tile View behavior. */
|
||||||
|
const ids = this.room.getParticipants().map(participant => participant.getId());
|
||||||
|
this.room.selectParticipants(ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when number of participants changes.
|
||||||
|
*/
|
||||||
|
setNumberOfParticipants() {
|
||||||
|
if (this.id === 0) {
|
||||||
|
$('#participants').text(this.numParticipants);
|
||||||
|
}
|
||||||
|
if (!stageView) {
|
||||||
|
this.selectParticipants();
|
||||||
|
this.updateMaxFrameHeight();
|
||||||
|
}
|
||||||
|
this.updateLastN();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when ICE connects
|
||||||
|
*/
|
||||||
|
onConnectionEstablished() {
|
||||||
|
this.connected = true;
|
||||||
|
|
||||||
|
this.selectParticipants();
|
||||||
|
this.updateMaxFrameHeight();
|
||||||
|
this.updateLastN();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles dominant speaker changed.
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
onDominantSpeakerChanged(selected, previous) {
|
||||||
|
if (this.selectStageViewParticipant(selected, previous)) {
|
||||||
|
this.selectParticipants();
|
||||||
|
}
|
||||||
|
this.updateMaxFrameHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles local tracks.
|
||||||
|
* @param tracks Array with JitsiTrack objects
|
||||||
|
*/
|
||||||
|
onLocalTracks(tracks = []) {
|
||||||
|
this.localTracks = tracks;
|
||||||
|
for (let i = 0; i < this.localTracks.length; i++) {
|
||||||
|
if (this.localTracks[i].getType() === 'video') {
|
||||||
|
if (this.id === 0) {
|
||||||
|
$('body').append(`<video ${autoPlayVideo ? 'autoplay="1" ' : ''}id='localVideo${i}' />`);
|
||||||
|
this.localTracks[i].attach($(`#localVideo${i}`)[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.room.addTrack(this.localTracks[i]);
|
||||||
|
} else {
|
||||||
|
if (localAudio) {
|
||||||
|
this.room.addTrack(this.localTracks[i]);
|
||||||
|
} else {
|
||||||
|
this.localTracks[i].mute();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.id === 0) {
|
||||||
|
$('body').append(
|
||||||
|
`<audio autoplay='1' muted='true' id='localAudio${i}' />`);
|
||||||
|
this.localTracks[i].attach($(`#localAudio${i}`)[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles remote tracks
|
||||||
|
* @param track JitsiTrack object
|
||||||
|
*/
|
||||||
|
onRemoteTrack(track) {
|
||||||
|
if (track.isLocal()
|
||||||
|
|| (track.getType() === 'video' && !remoteVideo) || (track.getType() === 'audio' && !remoteAudio)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const participant = track.getParticipantId();
|
||||||
|
|
||||||
|
if (!this.remoteTracks[participant]) {
|
||||||
|
this.remoteTracks[participant] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.id !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const idx = this.remoteTracks[participant].push(track);
|
||||||
|
const id = participant + track.getType() + idx;
|
||||||
|
|
||||||
|
if (track.getType() === 'video') {
|
||||||
|
$('body').append(`<video autoplay='1' id='${id}' />`);
|
||||||
|
} else {
|
||||||
|
$('body').append(`<audio autoplay='1' id='${id}' />`);
|
||||||
|
}
|
||||||
|
track.attach($(`#${id}`)[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* That function is executed when the conference is joined
|
||||||
|
*/
|
||||||
|
onConferenceJoined() {
|
||||||
|
console.log(`Participant ${this.id} Conference joined`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles start muted events, when audio and/or video are muted due to
|
||||||
|
* startAudioMuted or startVideoMuted policy.
|
||||||
|
*/
|
||||||
|
onStartMuted() {
|
||||||
|
// Give it some time, as it may be currently in the process of muting
|
||||||
|
setTimeout(() => {
|
||||||
|
const localAudioTrack = this.room.getLocalAudioTrack();
|
||||||
|
|
||||||
|
if (localAudio && localAudioTrack && localAudioTrack.isMuted()) {
|
||||||
|
localAudioTrack.unmute();
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVideoTrack = this.room.getLocalVideoTrack();
|
||||||
|
|
||||||
|
if (localVideo && localVideoTrack && localVideoTrack.isMuted()) {
|
||||||
|
localVideoTrack.unmute();
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
onUserJoined(id) {
|
||||||
|
this.numParticipants++;
|
||||||
|
this.setNumberOfParticipants();
|
||||||
|
this.remoteTracks[id] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
onUserLeft(id) {
|
||||||
|
this.numParticipants--;
|
||||||
|
this.setNumberOfParticipants();
|
||||||
|
if (!this.remoteTracks[id]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.id !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tracks = this.remoteTracks[id];
|
||||||
|
|
||||||
|
for (let i = 0; i < tracks.length; i++) {
|
||||||
|
const container = $(`#${id}${tracks[i].getType()}${i + 1}`)[0];
|
||||||
|
|
||||||
|
if (container) {
|
||||||
|
tracks[i].detach(container);
|
||||||
|
container.parentElement.removeChild(container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles private messages.
|
||||||
|
*
|
||||||
|
* @param {string} id - The sender ID.
|
||||||
|
* @param {string} text - The message.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
onPrivateMessage(id, text) {
|
||||||
|
switch (text) {
|
||||||
|
case 'video on':
|
||||||
|
this.onVideoOnMessage();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles 'video on' private messages.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
onVideoOnMessage() {
|
||||||
|
console.debug(`Participant ${this.id}: Turning my video on!`);
|
||||||
|
|
||||||
|
const localVideoTrack = this.room.getLocalVideoTrack();
|
||||||
|
|
||||||
|
if (localVideoTrack && localVideoTrack.isMuted()) {
|
||||||
|
console.debug(`Participant ${this.id}: Unmuting existing video track.`);
|
||||||
|
localVideoTrack.unmute();
|
||||||
|
} else if (!localVideoTrack) {
|
||||||
|
JitsiMeetJS.createLocalTracks({ devices: ['video'] })
|
||||||
|
.then(([videoTrack]) => videoTrack)
|
||||||
|
.catch(console.error)
|
||||||
|
.then(videoTrack => {
|
||||||
|
return this.room.replaceTrack(null, videoTrack);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.debug(`Participant ${this.id}: Successfully added a new video track for unmute.`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log(`Participant ${this.id}: No-op! We are already video unmuted!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called to connect.
|
||||||
|
*/
|
||||||
|
connect() {
|
||||||
|
this._onConnectionSuccess = this.onConnectionSuccess.bind(this)
|
||||||
|
this._onConnectionFailed = this.onConnectionFailed.bind(this)
|
||||||
|
this._disconnect = this.disconnect.bind(this)
|
||||||
|
|
||||||
|
this.connection = new JitsiMeetJS.JitsiConnection(null, null, config);
|
||||||
|
this.connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED, this._onConnectionSuccess);
|
||||||
|
this.connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED, this._onConnectionFailed);
|
||||||
|
this.connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, this._disconnect);
|
||||||
|
this.connection.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* That function is called when connection is established successfully
|
||||||
|
*/
|
||||||
|
onConnectionSuccess() {
|
||||||
|
this.room = this.connection.initJitsiConference(roomName.toLowerCase(), config);
|
||||||
|
this.room.on(JitsiMeetJS.events.conference.STARTED_MUTED, this.onStartMuted.bind(this));
|
||||||
|
this.room.on(JitsiMeetJS.events.conference.TRACK_ADDED, this.onRemoteTrack.bind(this));
|
||||||
|
this.room.on(JitsiMeetJS.events.conference.CONFERENCE_JOINED, this.onConferenceJoined.bind(this));
|
||||||
|
this.room.on(JitsiMeetJS.events.conference.CONNECTION_ESTABLISHED, this.onConnectionEstablished.bind(this));
|
||||||
|
this.room.on(JitsiMeetJS.events.conference.USER_JOINED, this.onUserJoined.bind(this));
|
||||||
|
this.room.on(JitsiMeetJS.events.conference.USER_LEFT, this.onUserLeft.bind(this));
|
||||||
|
this.room.on(JitsiMeetJS.events.conference.PRIVATE_MESSAGE_RECEIVED, this.onPrivateMessage.bind(this));
|
||||||
|
if (stageView) {
|
||||||
|
this.room.on(JitsiMeetJS.events.conference.DOMINANT_SPEAKER_CHANGED, this.onDominantSpeakerChanged.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
const devices = [];
|
||||||
|
|
||||||
|
if (localVideo) {
|
||||||
|
devices.push('video');
|
||||||
|
}
|
||||||
|
|
||||||
|
// we always create audio local tracks
|
||||||
|
devices.push('audio');
|
||||||
|
|
||||||
|
if (devices.length > 0) {
|
||||||
|
JitsiMeetJS.createLocalTracks({ devices })
|
||||||
|
.then(this.onLocalTracks.bind(this))
|
||||||
|
.then(() => {
|
||||||
|
this.room.join();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.room.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateMaxFrameHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called when the connection fail.
|
||||||
|
*/
|
||||||
|
onConnectionFailed() {
|
||||||
|
console.error(`Participant ${this.id}: Connection Failed!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called when we disconnect.
|
||||||
|
*/
|
||||||
|
disconnect() {
|
||||||
|
console.log('disconnect!');
|
||||||
|
this.connection.removeEventListener(
|
||||||
|
JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED,
|
||||||
|
this._onConnectionSuccess);
|
||||||
|
this.connection.removeEventListener(
|
||||||
|
JitsiMeetJS.events.connection.CONNECTION_FAILED,
|
||||||
|
this._onConnectionFailed);
|
||||||
|
this.connection.removeEventListener(
|
||||||
|
JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED,
|
||||||
|
this._disconnect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let clients = [];
|
||||||
|
|
||||||
window.APP = {
|
window.APP = {
|
||||||
conference: {
|
conference: {
|
||||||
getStats() {
|
getStats() {
|
||||||
return room.connectionQuality.getStats();
|
return clients[0]?.room?.connectionQuality.getStats();
|
||||||
},
|
},
|
||||||
getConnectionState() {
|
getConnectionState() {
|
||||||
return room && room.getConnectionState();
|
return clients[0] && clients[0].room && room.getConnectionState();
|
||||||
},
|
},
|
||||||
muteAudio(mute) {
|
muteAudio(mute) {
|
||||||
localAudio = mute;
|
localAudio = mute;
|
||||||
for (let i = 0; i < localTracks.length; i++) {
|
for (let j = 0; j < clients.length; j++) {
|
||||||
if (localTracks[i].getType() === 'audio') {
|
for (let i = 0; i < clients[j].localTracks.length; i++) {
|
||||||
if (mute) {
|
if (clients[j].localTracks[i].getType() === 'audio') {
|
||||||
localTracks[i].mute();
|
if (mute) {
|
||||||
}
|
clients[j].localTracks[i].mute();
|
||||||
else {
|
}
|
||||||
localTracks[i].unmute();
|
else {
|
||||||
|
clients[j].localTracks[i].unmute();
|
||||||
// if track was not added we need to add it to the peerconnection
|
|
||||||
if (!room.getLocalAudioTrack()) {
|
// if track was not added we need to add it to the peerconnection
|
||||||
room.replaceTrack(null, localTracks[i]);
|
if (!clients[j].room.getLocalAudioTrack()) {
|
||||||
|
clients[j].room.replaceTrack(null, clients[j].localTracks[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,19 +457,19 @@ window.APP = {
|
||||||
},
|
},
|
||||||
|
|
||||||
get room() {
|
get room() {
|
||||||
return room;
|
return clients[0]?.room;
|
||||||
},
|
},
|
||||||
get connection() {
|
get connection() {
|
||||||
return connection;
|
return clients[0]?.connection;
|
||||||
},
|
},
|
||||||
get numParticipants() {
|
get numParticipants() {
|
||||||
return numParticipants;
|
return clients[0]?.remoteParticipants;
|
||||||
},
|
},
|
||||||
get localTracks() {
|
get localTracks() {
|
||||||
return localTracks;
|
return clients[0]?.localTracks;
|
||||||
},
|
},
|
||||||
get remoteTracks() {
|
get remoteTracks() {
|
||||||
return remoteTracks;
|
return clients[0]?.remoteTracks;
|
||||||
},
|
},
|
||||||
get params() {
|
get params() {
|
||||||
return {
|
return {
|
||||||
|
@ -94,368 +484,18 @@ window.APP = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple emulation of jitsi-meet's screen layout behavior
|
|
||||||
*/
|
|
||||||
function updateMaxFrameHeight() {
|
|
||||||
if (!connected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let newMaxFrameHeight;
|
|
||||||
|
|
||||||
if (stageView) {
|
|
||||||
newMaxFrameHeight = 2160;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (numParticipants <= 2) {
|
|
||||||
newMaxFrameHeight = 720;
|
|
||||||
} else if (numParticipants <= 4) {
|
|
||||||
newMaxFrameHeight = 360;
|
|
||||||
} else {
|
|
||||||
newMaxFrameHeight = 180;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (room && maxFrameHeight !== newMaxFrameHeight) {
|
|
||||||
maxFrameHeight = newMaxFrameHeight;
|
|
||||||
room.setReceiverVideoConstraint(maxFrameHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple emulation of jitsi-meet's lastN behavior
|
|
||||||
*/
|
|
||||||
function updateLastN() {
|
|
||||||
if (!connected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let lastN = typeof config.channelLastN === 'undefined' ? -1 : config.channelLastN;
|
|
||||||
|
|
||||||
const limitedLastN = limitLastN(numParticipants, validateLastNLimits(config.lastNLimits));
|
|
||||||
|
|
||||||
if (limitedLastN !== undefined) {
|
|
||||||
lastN = lastN === -1 ? limitedLastN : Math.min(limitedLastN, lastN);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastN === room.getLastN()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
room.setLastN(lastN);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to query whether a participant ID is a valid ID
|
|
||||||
* for stage view.
|
|
||||||
*/
|
|
||||||
function isValidStageViewParticipant(id) {
|
|
||||||
return (id !== room.myUserId() && room.getParticipantById(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple emulation of jitsi-meet's stage view participant selection behavior.
|
|
||||||
* Doesn't take into account pinning or screen sharing, and the initial behavior
|
|
||||||
* is slightly different.
|
|
||||||
* @returns Whether the selected participant changed.
|
|
||||||
*/
|
|
||||||
function selectStageViewParticipant(selected, previous) {
|
|
||||||
let newSelectedParticipant;
|
|
||||||
|
|
||||||
if (isValidStageViewParticipant(selected)) {
|
|
||||||
newSelectedParticipant = selected;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
newSelectedParticipant = previous.find(isValidStageViewParticipant);
|
|
||||||
}
|
|
||||||
if (newSelectedParticipant && newSelectedParticipant !== selectedParticipant) {
|
|
||||||
selectedParticipant = newSelectedParticipant;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple emulation of jitsi-meet's selectParticipants behavior
|
|
||||||
*/
|
|
||||||
function selectParticipants() {
|
|
||||||
if (!connected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (stageView) {
|
|
||||||
if (selectedParticipant) {
|
|
||||||
room.selectParticipants([selectedParticipant]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
/* jitsi-meet's current Tile View behavior. */
|
|
||||||
const ids = room.getParticipants().map(participant => participant.getId());
|
|
||||||
room.selectParticipants(ids);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when number of participants changes.
|
|
||||||
*/
|
|
||||||
function setNumberOfParticipants() {
|
|
||||||
$('#participants').text(numParticipants);
|
|
||||||
if (!stageView) {
|
|
||||||
selectParticipants();
|
|
||||||
updateMaxFrameHeight();
|
|
||||||
}
|
|
||||||
updateLastN();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when ICE connects
|
|
||||||
*/
|
|
||||||
function onConnectionEstablished() {
|
|
||||||
connected = true;
|
|
||||||
|
|
||||||
selectParticipants();
|
|
||||||
updateMaxFrameHeight();
|
|
||||||
updateLastN();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles dominant speaker changed.
|
|
||||||
* @param id
|
|
||||||
*/
|
|
||||||
function onDominantSpeakerChanged(selected, previous) {
|
|
||||||
if (selectStageViewParticipant(selected, previous)) {
|
|
||||||
selectParticipants();
|
|
||||||
}
|
|
||||||
updateMaxFrameHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles local tracks.
|
|
||||||
* @param tracks Array with JitsiTrack objects
|
|
||||||
*/
|
|
||||||
function onLocalTracks(tracks = []) {
|
|
||||||
localTracks = tracks;
|
|
||||||
for (let i = 0; i < localTracks.length; i++) {
|
|
||||||
if (localTracks[i].getType() === 'video') {
|
|
||||||
$('body').append(`<video ${autoPlayVideo ? 'autoplay="1" ' : ''}id='localVideo${i}' />`);
|
|
||||||
localTracks[i].attach($(`#localVideo${i}`)[0]);
|
|
||||||
|
|
||||||
room.addTrack(localTracks[i]);
|
|
||||||
} else {
|
|
||||||
if (localAudio) {
|
|
||||||
room.addTrack(localTracks[i]);
|
|
||||||
} else {
|
|
||||||
localTracks[i].mute();
|
|
||||||
}
|
|
||||||
|
|
||||||
$('body').append(
|
|
||||||
`<audio autoplay='1' muted='true' id='localAudio${i}' />`);
|
|
||||||
localTracks[i].attach($(`#localAudio${i}`)[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles remote tracks
|
|
||||||
* @param track JitsiTrack object
|
|
||||||
*/
|
|
||||||
function onRemoteTrack(track) {
|
|
||||||
if (track.isLocal()
|
|
||||||
|| (track.getType() === 'video' && !remoteVideo) || (track.getType() === 'audio' && !remoteAudio)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const participant = track.getParticipantId();
|
|
||||||
|
|
||||||
if (!remoteTracks[participant]) {
|
|
||||||
remoteTracks[participant] = [];
|
|
||||||
}
|
|
||||||
const idx = remoteTracks[participant].push(track);
|
|
||||||
const id = participant + track.getType() + idx;
|
|
||||||
|
|
||||||
if (track.getType() === 'video') {
|
|
||||||
$('body').append(`<video autoplay='1' id='${id}' />`);
|
|
||||||
} else {
|
|
||||||
$('body').append(`<audio autoplay='1' id='${id}' />`);
|
|
||||||
}
|
|
||||||
track.attach($(`#${id}`)[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* That function is executed when the conference is joined
|
|
||||||
*/
|
|
||||||
function onConferenceJoined() {
|
|
||||||
console.log('Conference joined');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles start muted events, when audio and/or video are muted due to
|
|
||||||
* startAudioMuted or startVideoMuted policy.
|
|
||||||
*/
|
|
||||||
function onStartMuted() {
|
|
||||||
// Give it some time, as it may be currently in the process of muting
|
|
||||||
setTimeout(() => {
|
|
||||||
const localAudioTrack = room.getLocalAudioTrack();
|
|
||||||
|
|
||||||
if (localAudio && localAudioTrack && localAudioTrack.isMuted()) {
|
|
||||||
localAudioTrack.unmute();
|
|
||||||
}
|
|
||||||
|
|
||||||
const localVideoTrack = room.getLocalVideoTrack();
|
|
||||||
|
|
||||||
if (localVideo && localVideoTrack && localVideoTrack.isMuted()) {
|
|
||||||
localVideoTrack.unmute();
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
*/
|
|
||||||
function onUserJoined(id) {
|
|
||||||
numParticipants++;
|
|
||||||
setNumberOfParticipants();
|
|
||||||
remoteTracks[id] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
*/
|
|
||||||
function onUserLeft(id) {
|
|
||||||
numParticipants--;
|
|
||||||
setNumberOfParticipants();
|
|
||||||
if (!remoteTracks[id]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tracks = remoteTracks[id];
|
|
||||||
|
|
||||||
for (let i = 0; i < tracks.length; i++) {
|
|
||||||
const container = $(`#${id}${tracks[i].getType()}${i + 1}`)[0];
|
|
||||||
|
|
||||||
if (container) {
|
|
||||||
tracks[i].detach(container);
|
|
||||||
container.parentElement.removeChild(container);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles private messages.
|
|
||||||
*
|
|
||||||
* @param {string} id - The sender ID.
|
|
||||||
* @param {string} text - The message.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
function onPrivateMessage(id, text) {
|
|
||||||
switch(text) {
|
|
||||||
case 'video on':
|
|
||||||
onVideoOnMessage();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles 'video on' private messages.
|
|
||||||
*
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
function onVideoOnMessage() {
|
|
||||||
console.debug('Turning my video on!');
|
|
||||||
|
|
||||||
const localVideoTrack = room.getLocalVideoTrack();
|
|
||||||
|
|
||||||
if (localVideoTrack && localVideoTrack.isMuted()) {
|
|
||||||
console.debug('Unmuting existing video track.');
|
|
||||||
localVideoTrack.unmute();
|
|
||||||
} else if (!localVideoTrack) {
|
|
||||||
JitsiMeetJS.createLocalTracks({ devices: [ 'video' ] })
|
|
||||||
.then(([ videoTrack ]) => videoTrack)
|
|
||||||
.catch(console.error)
|
|
||||||
.then(videoTrack => {
|
|
||||||
return room.replaceTrack(null, videoTrack);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
console.debug('Successfully added a new video track for unmute.');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log('No-op! We are already video unmuted!');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* That function is called when connection is established successfully
|
|
||||||
*/
|
|
||||||
function onConnectionSuccess() {
|
|
||||||
room = connection.initJitsiConference(roomName.toLowerCase(), config);
|
|
||||||
room.on(JitsiMeetJS.events.conference.STARTED_MUTED, onStartMuted);
|
|
||||||
room.on(JitsiMeetJS.events.conference.TRACK_ADDED, onRemoteTrack);
|
|
||||||
room.on(JitsiMeetJS.events.conference.CONFERENCE_JOINED, onConferenceJoined);
|
|
||||||
room.on(JitsiMeetJS.events.conference.CONNECTION_ESTABLISHED, onConnectionEstablished);
|
|
||||||
room.on(JitsiMeetJS.events.conference.USER_JOINED, onUserJoined);
|
|
||||||
room.on(JitsiMeetJS.events.conference.USER_LEFT, onUserLeft);
|
|
||||||
room.on(JitsiMeetJS.events.conference.PRIVATE_MESSAGE_RECEIVED, onPrivateMessage);
|
|
||||||
if (stageView) {
|
|
||||||
room.on(JitsiMeetJS.events.conference.DOMINANT_SPEAKER_CHANGED, onDominantSpeakerChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
const devices = [];
|
|
||||||
|
|
||||||
if (localVideo) {
|
|
||||||
devices.push('video');
|
|
||||||
}
|
|
||||||
|
|
||||||
// we always create audio local tracks
|
|
||||||
devices.push('audio');
|
|
||||||
|
|
||||||
if (devices.length > 0) {
|
|
||||||
JitsiMeetJS.createLocalTracks({ devices })
|
|
||||||
.then(onLocalTracks)
|
|
||||||
.then(() => {
|
|
||||||
room.join();
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
room.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMaxFrameHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is called when the connection fail.
|
|
||||||
*/
|
|
||||||
function onConnectionFailed() {
|
|
||||||
console.error('Connection Failed!');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is called when we disconnect.
|
|
||||||
*/
|
|
||||||
function disconnect() {
|
|
||||||
console.log('disconnect!');
|
|
||||||
connection.removeEventListener(
|
|
||||||
JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED,
|
|
||||||
onConnectionSuccess);
|
|
||||||
connection.removeEventListener(
|
|
||||||
JitsiMeetJS.events.connection.CONNECTION_FAILED,
|
|
||||||
onConnectionFailed);
|
|
||||||
connection.removeEventListener(
|
|
||||||
JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED,
|
|
||||||
disconnect);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function unload() {
|
function unload() {
|
||||||
for (let i = 0; i < localTracks.length; i++) {
|
for (let j = 0; j < clients.length; j++) {
|
||||||
localTracks[i].dispose();
|
for (let i = 0; i < clients[j].localTracks.length; i++) {
|
||||||
|
clients[j].localTracks[i].dispose();
|
||||||
|
}
|
||||||
|
clients[j].room.leave();
|
||||||
|
clients[j].connection.disconnect();
|
||||||
}
|
}
|
||||||
room.leave();
|
clients = [];
|
||||||
connection.disconnect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$(window).bind('beforeunload', unload);
|
$(window).bind('beforeunload', unload);
|
||||||
|
@ -470,8 +510,14 @@ if (config.websocketKeepAliveUrl) {
|
||||||
config.websocketKeepAliveUrl += `?room=${roomName.toLowerCase()}`;
|
config.websocketKeepAliveUrl += `?room=${roomName.toLowerCase()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
connection = new JitsiMeetJS.JitsiConnection(null, null, config);
|
function startClient(i) {
|
||||||
connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED, onConnectionSuccess);
|
clients[i] = new LoadTestClient(i);
|
||||||
connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED, onConnectionFailed);
|
clients[i].connect();
|
||||||
connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, disconnect);
|
if (i + 1 < numClients) {
|
||||||
connection.connect();
|
setTimeout(() => { startClient(i+1) }, clientInterval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numClients > 0) {
|
||||||
|
startClient(0)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue