935 lines
32 KiB
JavaScript
935 lines
32 KiB
JavaScript
var MovimJingleSession = function (jid, fullJid, id, name, avatarUrl) {
|
|
this.id = id ?? crypto.randomUUID();
|
|
this.jid = jid;
|
|
this.fullJid = fullJid;
|
|
this.tracksTypes = {};
|
|
this.tracksScreen = {};
|
|
this.name = name;
|
|
this.avatarUrl = avatarUrl;
|
|
this.audioLevel = 0;
|
|
this.lastPostMessage = 0;
|
|
|
|
this.audioSharingSender = null;
|
|
this.screenSharingSender = null;
|
|
|
|
this.pc = new RTCPeerConnection({
|
|
iceServers: MovimVisio.services,
|
|
bundlePolicy: 'max-bundle',
|
|
iceCandidatePoolSize: 10
|
|
});
|
|
|
|
this.screenSharingPc = null; // RTCPc specific for the SFU
|
|
|
|
this.adaptToNetworkCondition = setInterval(() => {
|
|
VisioUtils.adaptToNetworkCondition(this.pc);
|
|
}, 5000);
|
|
|
|
this.participant = document.createElement('div');
|
|
this.participant.dataset.jid = this.jid;
|
|
this.participant.classList.add('participant');
|
|
|
|
document.querySelector('#participants').appendChild(this.participant);
|
|
|
|
this.participant.classList.add('audio_off');
|
|
this.participant.classList.add('screen_off');
|
|
this.participant.classList.add('video_off');
|
|
|
|
this.remoteScreenVideo = document.createElement('video');
|
|
this.remoteScreenVideo.classList.add('screen');
|
|
this.remoteScreenVideo.autoplay = true;
|
|
this.remoteScreenVideo.disablePictureInPicture = true;
|
|
this.remoteScreenVideo.poster = BASE_URI + 'theme/img/empty.png';
|
|
this.participant.appendChild(this.remoteScreenVideo);
|
|
|
|
this.remoteVideo = document.createElement('video');
|
|
this.remoteVideo.autoplay = true;
|
|
this.remoteVideo.disablePictureInPicture = true;
|
|
this.remoteVideo.poster = BASE_URI + 'theme/img/empty.png';
|
|
this.remoteVideo.muted = true;
|
|
this.participant.appendChild(this.remoteVideo);
|
|
|
|
this.remoteAudio = document.createElement('audio');
|
|
this.remoteAudio.autoplay = true;
|
|
this.participant.appendChild(this.remoteAudio);
|
|
|
|
this.remoteScreenAudio = document.createElement('audio');
|
|
this.remoteScreenAudio.classList.add('screen');
|
|
this.remoteScreenAudio.autoplay = true;
|
|
this.participant.appendChild(this.remoteScreenAudio);
|
|
|
|
this.callStatus = document.createElement('div')
|
|
this.callStatus.classList.add('status');
|
|
this.participant.appendChild(this.callStatus);
|
|
|
|
this.audioOffStatus = document.createElement('span');
|
|
this.audioOffStatus.classList.add('audio_off');
|
|
this.callStatus.appendChild(this.audioOffStatus);
|
|
|
|
this.videoOffStatus = document.createElement('span');
|
|
this.videoOffStatus.classList.add('video_off');
|
|
this.callStatus.appendChild(this.videoOffStatus);
|
|
|
|
this.screenStatus = document.createElement('span');
|
|
this.screenStatus.classList.add('screen');
|
|
this.callStatus.appendChild(this.screenStatus);
|
|
|
|
// Pin button
|
|
this.pin = document.createElement('span');
|
|
this.pin.classList.add('pin');
|
|
this.participant.appendChild(this.pin);
|
|
|
|
this.pin.addEventListener('click', e => {
|
|
VisioUtils.toggleMode(true);
|
|
|
|
if (MovimJingles.pinned == this.jid) {
|
|
MovimJingles.pinned = null;
|
|
Visio_ajaxHttpUnpin();
|
|
} else {
|
|
MovimJingles.pinned = this.jid;
|
|
|
|
if (this.name) {
|
|
Visio_ajaxHttpPin(this.name);
|
|
}
|
|
}
|
|
|
|
MovimJingles.checkActiveSpeaker();
|
|
})
|
|
|
|
this.screenQualityInterval = setInterval(() => {
|
|
this.callStatus.classList.remove('screen_hd', 'screen_fhd');
|
|
if (!this.participant.classList.contains('screen_off')) {
|
|
if (this.remoteScreenVideo.videoWidth >= 1920) {
|
|
this.callStatus.classList.add('screen_fhd');
|
|
} else if (this.remoteScreenVideo.videoWidth >= 1280) {
|
|
this.callStatus.classList.add('screen_hd');
|
|
}
|
|
}
|
|
}, 3000);
|
|
|
|
if (this.name) {
|
|
this.callStatus.dataset.name = this.name;
|
|
}
|
|
|
|
if (this.avatarUrl) {
|
|
let background = document.createElement('img');
|
|
background.classList.add('avatar');
|
|
background.src = this.avatarUrl;
|
|
this.participant.prepend(background);
|
|
}
|
|
|
|
this.pc.ontrack = event => {
|
|
var srcObject = null;
|
|
|
|
if (event.streams && event.streams[0]) {
|
|
srcObject = event.streams[0];
|
|
} else {
|
|
srcObject = new MediaStream();
|
|
srcObject.addTrack(event.track);
|
|
}
|
|
|
|
if (event.track.kind == 'audio') {
|
|
if (this.tracksScreen[event.transceiver.mid]) {
|
|
this.remoteScreenAudio.srcObject = srcObject;
|
|
this.tracksTypes['mid' + event.transceiver.mid] = 'audio_screen';
|
|
} else {
|
|
this.remoteAudio.srcObject = srcObject;
|
|
this.participant.classList.remove('audio_off');
|
|
this.handleRemoteAudio();
|
|
this.tracksTypes['mid' + event.transceiver.mid] = 'audio';
|
|
}
|
|
} else if (event.track.kind === 'video') {
|
|
if (this.tracksScreen[event.transceiver.mid]) {
|
|
this.remoteScreenVideo.srcObject = srcObject;
|
|
this.tracksTypes['mid' + event.transceiver.mid] = 'video_screen';
|
|
this.remoteScreenVideo.oncanplay = event => {
|
|
this.participant.classList.remove('screen_off');
|
|
}
|
|
} else {
|
|
this.remoteVideo.srcObject = srcObject;
|
|
this.tracksTypes['mid' + event.transceiver.mid] = 'video';
|
|
this.participant.classList.remove('video_off');
|
|
}
|
|
}
|
|
}
|
|
|
|
this.pc.onnegotiationneeded = event => {
|
|
if (this.pc.localDescription) {
|
|
this.oldLocalDescription = this.pc.localDescription.sdp;
|
|
|
|
this.pc.getTransceivers().forEach(transceiver => {
|
|
if (transceiver.sender.track && transceiver.sender.track.kind === 'video') {
|
|
VisioUtils.setVideoCodecPreferences(transceiver);
|
|
}
|
|
});
|
|
|
|
this.pc.createOffer()
|
|
.then(offer => {
|
|
offer.sdp = this.maybeInjectScreenCategory(offer.sdp);
|
|
return this.pc.setLocalDescription(offer)
|
|
}).then(() => this.updateContent())
|
|
.catch(err => MovimUtils.logError(err));
|
|
}
|
|
};
|
|
|
|
this.pc.onicecandidate = event => {
|
|
let candidate = event.candidate;
|
|
if (candidate && candidate.candidate && candidate.candidate.length > 0) {
|
|
Visio_ajaxCandidate(this.fullJid, this.id, event.candidate);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Add all the existing tracks
|
|
*/
|
|
|
|
if (MovimVisio.sfu == null) {
|
|
MovimVisio.localStream.getTracks().forEach(track => {
|
|
this.pc.addTrack(track, MovimVisio.localStream);
|
|
});
|
|
|
|
if (MovimVisio.screenSharing.srcObject) {
|
|
this.enableScreenSharing();
|
|
}
|
|
}
|
|
|
|
Notif.userJoinedCall();
|
|
MovimJingles.checkActiveSpeaker();
|
|
}
|
|
|
|
MovimJingleSession.prototype.handleRemoteAudio = async function () {
|
|
this.remoteAudioContext = new AudioContext();
|
|
|
|
try {
|
|
var remoteMicrophone = this.remoteAudioContext.createMediaStreamSource(
|
|
this.remoteAudio.srcObject
|
|
);
|
|
|
|
await this.remoteAudioContext.audioWorklet.addModule(BASE_URI + 'scripts/movim_jingle_session_audio_worklet.js');
|
|
const audioWorkletNode = new AudioWorkletNode(this.remoteAudioContext, 'jinglesession-audioworklet');
|
|
remoteMicrophone.connect(audioWorkletNode);
|
|
|
|
audioWorkletNode.port.onmessage = (e) => {
|
|
this.processRemoteAudioMessage(e.data);
|
|
};
|
|
|
|
} catch (error) {
|
|
MovimUtils.logError(error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* In some clients, such as Dino, the buffer is stopped when the microphone is muted
|
|
* we detect that there and send a clear message properly
|
|
*/
|
|
MovimJingleSession.prototype.clearRemoteAudioMessage = function () {
|
|
const second = 1000;
|
|
const secondAgo = Date.now() - second;
|
|
|
|
if (this.lastPostMessage < secondAgo) {
|
|
this.processRemoteAudioMessage({
|
|
"isMuteStep": 5,
|
|
"level": 0,
|
|
"published": Date.now()
|
|
});
|
|
}
|
|
}
|
|
|
|
MovimJingleSession.prototype.processRemoteAudioMessage = function (message) {
|
|
if (message.isMuteStep >= 5) {
|
|
this.participant.classList.add('audio_off');
|
|
} else {
|
|
this.participant.classList.remove('audio_off');
|
|
}
|
|
|
|
this.lastPostMessage = message.published;
|
|
this.audioLevel = message.level;
|
|
this.participant.style.setProperty('--level', message.level);
|
|
|
|
if (message.level > 0) {
|
|
clearTimeout(this.clearRemoteAudioMessageId);
|
|
this.clearRemoteAudioMessageId = setTimeout(this.clearRemoteAudioMessage.bind(this), 2000);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* movim-rs: the signaling messages can arrive faster than the asynchronous
|
|
* WebRTC calls complete (a content-add right after a session-accept, candidates
|
|
* before the remote description), run them one after the other.
|
|
*/
|
|
MovimJingleSession.prototype.enqueue = function (task) {
|
|
this.queue = (this.queue || Promise.resolve())
|
|
.then(() => this.pc ? task() : null)
|
|
.catch(error => MovimUtils.logError(error));
|
|
}
|
|
|
|
MovimJingleSession.prototype.onCandidate = function (candidate, mid, mlineindex) {
|
|
this.enqueue(() => this._onCandidate(candidate, mid, mlineindex));
|
|
}
|
|
|
|
MovimJingleSession.prototype.onContentAdd = function (sdp, mids) {
|
|
this.enqueue(() => this._onContentAdd(sdp, mids));
|
|
}
|
|
|
|
MovimJingleSession.prototype.onContentModify = function (sdp, mid) {
|
|
this.enqueue(() => this._onContentModify(sdp, mid));
|
|
}
|
|
|
|
MovimJingleSession.prototype.onContentRemove = function (sdp, mids) {
|
|
this.enqueue(() => this._onContentRemove(sdp, mids));
|
|
}
|
|
|
|
MovimJingleSession.prototype.onAcceptSDP = function (sdp) {
|
|
this.enqueue(() => this._onAcceptSDP(sdp));
|
|
}
|
|
|
|
MovimJingleSession.prototype.onInitiateSDP = function (sdp) {
|
|
this.enqueue(() => this._onInitiateSDP(sdp));
|
|
}
|
|
|
|
MovimJingleSession.prototype.terminate = function (reason, muji) {
|
|
if (MovimVisio.sfu == null) {
|
|
Visio_ajaxTerminate(this.fullJid, this.id, reason, muji);
|
|
}
|
|
this.close();
|
|
}
|
|
|
|
MovimJingleSession.prototype.close = function () {
|
|
clearInterval(this.adaptToNetworkCondition);
|
|
clearInterval(this.screenQualityInterval);
|
|
|
|
Notif.userLeftCall();
|
|
|
|
if (this.pc) {
|
|
this.pc.close();
|
|
this.pc = null;
|
|
}
|
|
|
|
if (this.remoteAudioContext) {
|
|
this.remoteAudioContext.close();
|
|
this.remoteAudioContext = null;
|
|
}
|
|
|
|
let participant = document.querySelector('.participant[data-jid="' + this.jid + '"]');
|
|
if (participant) participant.remove();
|
|
|
|
let remoteVideoStream = this.remoteVideo.srcObject;
|
|
if (remoteVideoStream) {
|
|
remoteVideoStream.getTracks().forEach(track => track.stop());
|
|
remoteVideoStream = null;
|
|
}
|
|
|
|
let remoteAudioStream = this.remoteAudio.srcObject;
|
|
if (remoteAudioStream) {
|
|
remoteAudioStream.getTracks().forEach(track => track.stop());
|
|
remoteAudioStream = null;
|
|
}
|
|
}
|
|
|
|
MovimJingleSession.prototype._onCandidate = function (candidate, mid, mlineindex) {
|
|
const iceCandidate = new RTCIceCandidate({
|
|
// filter the a=candidate lines
|
|
'candidate': candidate.split(/\n/).filter(line => {
|
|
return line.startsWith('a=candidate');
|
|
}).join('').substring(2),
|
|
'sdpMid': mid,
|
|
'sdpMLineIndex': mlineindex
|
|
});
|
|
|
|
// When we share our own screen have some candidates from the SFU for our PC
|
|
if (this.jid == MovimVisio.sfu && MovimVisio.screenshareSFUPc) {
|
|
MovimVisio.screenshareSFUPc.addIceCandidate(iceCandidate).catch(error => MovimUtils.logError(error));
|
|
}
|
|
|
|
if (this.screenSharingPc) {
|
|
// In this case we send the candidate to both pcs, and let it sort it out ¯\_(ツ)_/¯
|
|
this.screenSharingPc.addIceCandidate(iceCandidate).catch(error => MovimUtils.logError(error));
|
|
} else if (this.pc && this.pc.remoteDescription) {
|
|
return this.pc.addIceCandidate(iceCandidate).catch(error => MovimUtils.logError(error));
|
|
}
|
|
}
|
|
|
|
MovimJingleSession.prototype._onContentAdd = function (sdp, mids) {
|
|
remoteDescription = this.pc.remoteDescription.sdp;
|
|
remoteDescription += sdp.match('m=[^]*');
|
|
remoteDescription = remoteDescription.replace(/^a=group.*/m, sdp.match(/a=group.*/)[0]);
|
|
|
|
this.maybeDetectTracksScreen(sdp);
|
|
|
|
return this.pc.setRemoteDescription({ 'sdp': remoteDescription + "\n", 'type': 'offer' })
|
|
.then(() => {
|
|
return this.pc.createAnswer()
|
|
.then(answer => this.pc.setLocalDescription(answer))
|
|
.then(() => Visio_ajaxContentAccept(this.fullJid, this.id, this.pc.localDescription.sdp))
|
|
.catch(MovimUtils.logError);
|
|
})
|
|
.catch(error => MovimUtils.logError(error));
|
|
}
|
|
|
|
MovimJingleSession.prototype._onContentModify = function (sdp, mid) {
|
|
// TODO, implement mid
|
|
this.maybeDetectTracksScreen(sdp);
|
|
|
|
return this.pc.setRemoteDescription({ 'sdp': sdp + "\n", 'type': 'offer' })
|
|
.then(() => {
|
|
return this.pc.createAnswer()
|
|
.then(answer => this.pc.setLocalDescription(answer))
|
|
.then(() => Visio_ajaxContentAccept(this.fullJid, this.id, this.pc.localDescription.sdp))
|
|
.catch(MovimUtils.logError);
|
|
})
|
|
.catch(error => MovimUtils.logError(error));
|
|
}
|
|
|
|
MovimJingleSession.prototype._onContentRemove = function (sdp, mids) {
|
|
remoteDescription = this.pc.remoteDescription.sdp;
|
|
mids.forEach(mid => delete this.tracksScreen[mid]);
|
|
|
|
let parts = remoteDescription.split('m=');
|
|
let filtered = [];
|
|
|
|
parts.forEach(part => {
|
|
let mid = part.match(/a=mid:(.*)/);
|
|
if (!mid || !mids.includes(mid[1])) {
|
|
filtered.push(part)
|
|
}
|
|
});
|
|
|
|
remoteDescription = filtered.join('m=');
|
|
return this.pc.setRemoteDescription({ 'sdp': sdp + "\n", 'type': 'offer' })
|
|
.then(() => {
|
|
return this.pc.createAnswer()
|
|
.then(answer => this.pc.setLocalDescription(answer))
|
|
.then(() => Visio_ajaxContentAccept(this.fullJid, this.id, this.pc.localDescription.sdp))
|
|
.catch(MovimUtils.logError);
|
|
})
|
|
.catch(error => MovimUtils.logError(error));
|
|
}
|
|
|
|
MovimJingleSession.prototype.sessionInitiate = function (fullJid, id, mujiRoom) {
|
|
this.id = id;
|
|
this.fullJid = fullJid;
|
|
|
|
this.pc.getTransceivers().forEach(transceiver => {
|
|
if (transceiver.sender.track.kind === 'video') {
|
|
VisioUtils.setVideoCodecPreferences(transceiver)
|
|
}
|
|
});
|
|
|
|
this.pc.createOffer()
|
|
.then(offer => this.pc.setLocalDescription(offer))
|
|
.then(() => Visio_ajaxSessionInitiate(this.fullJid, this.pc.localDescription, id, mujiRoom));
|
|
}
|
|
|
|
MovimJingleSession.prototype.updateContent = function () {
|
|
let oldMedias = this.oldLocalDescription.match(MovimVisio.bundleRegex)[2].split(' ');
|
|
let newMedias = this.pc.localDescription.sdp.match(MovimVisio.bundleRegex)[2].split(' ');
|
|
|
|
let createdMedias = newMedias.filter((e) => !oldMedias.includes(e));
|
|
let destroyedMedias = oldMedias.filter((e) => !newMedias.includes(e));
|
|
|
|
let changed = false;
|
|
let localDescription = this.maybeInjectScreenCategory(this.pc.localDescription.sdp);
|
|
|
|
if (createdMedias.length > 0) {
|
|
changed = true;
|
|
console.log('CONTENT ADD ?' + this.fullJid + ' ' + this.jid);
|
|
Visio_ajaxContentAdd(this.fullJid, localDescription, this.id, createdMedias);
|
|
}
|
|
|
|
if (destroyedMedias.length > 0) {
|
|
changed = true;
|
|
Visio_ajaxContentRemove(this.fullJid, this.oldLocalDescription, this.id, destroyedMedias);
|
|
}
|
|
|
|
// Nothing added, nothing removed, lets update everything...
|
|
if (changed == false) {
|
|
Visio_ajaxContentModify(this.fullJid, localDescription, this.id);
|
|
}
|
|
}
|
|
|
|
MovimJingleSession.prototype._onAcceptSDP = function (sdp) {
|
|
this.maybeDetectTracksScreen(sdp);
|
|
|
|
return this.pc.setRemoteDescription({ 'sdp': sdp + "\n", 'type': 'answer' })
|
|
.catch(error => {
|
|
let visio = document.querySelector('#visio');
|
|
Visio_ajaxGoodbye(visio.dataset.jid, this.id, 'incompatible-parameters');
|
|
|
|
MovimUtils.logError(error);
|
|
});
|
|
}
|
|
|
|
MovimJingleSession.prototype._onInitiateSDP = function (sdp) {
|
|
this.maybeDetectTracksScreen(sdp);
|
|
|
|
return this.pc.setRemoteDescription({ 'sdp': sdp + "\n", 'type': 'offer' })
|
|
.then(() => {
|
|
return this.pc.createAnswer()
|
|
.then(answer => this.pc.setLocalDescription(answer))
|
|
.then(() => {
|
|
if (MovimVisio.sfu != null) {
|
|
Visio_ajaxContentAccept(
|
|
this.fullJid,
|
|
this.id,
|
|
this.maybeInjectScreenCategory(this.pc.localDescription.sdp),
|
|
this.jid
|
|
);
|
|
} else {
|
|
Visio_ajaxSessionAccept(
|
|
this.fullJid,
|
|
this.id,
|
|
this.maybeInjectScreenCategory(this.pc.localDescription.sdp)
|
|
);
|
|
}
|
|
})
|
|
.catch(MovimUtils.logError);
|
|
}).catch(error => MovimUtils.logError(error));
|
|
}
|
|
|
|
/**
|
|
* With the SFU we can't have the screen as a new content, so we have to build a completely
|
|
* new RTPPc
|
|
*/
|
|
MovimJingleSession.prototype.onSFUScreenSharingPropose = function (sdp) {
|
|
this.maybeDetectTracksScreen(sdp);
|
|
|
|
this.screenSharingPc = new RTCPeerConnection({
|
|
iceServers: MovimVisio.services,
|
|
bundlePolicy: 'max-bundle',
|
|
iceCandidatePoolSize: 10
|
|
});
|
|
|
|
this.screenSharingPc.ontrack = event => {
|
|
var srcObject = null;
|
|
|
|
if (event.streams && event.streams[0]) {
|
|
srcObject = event.streams[0];
|
|
} else {
|
|
srcObject = new MediaStream();
|
|
srcObject.addTrack(event.track);
|
|
}
|
|
|
|
if (event.track.kind == 'audio') {
|
|
if (this.tracksScreen[event.transceiver.mid]) {
|
|
this.remoteScreenAudio.srcObject = srcObject;
|
|
this.tracksTypes['sfu_screen_mid' + event.transceiver.mid] = 'audio_screen';
|
|
}
|
|
} else if (event.track.kind === 'video') {
|
|
if (this.tracksScreen[event.transceiver.mid]) {
|
|
this.remoteScreenVideo.srcObject = srcObject;
|
|
this.tracksTypes['sfu_screen_mid' + event.transceiver.mid] = 'video_screen';
|
|
this.remoteScreenVideo.oncanplay = event => {
|
|
this.participant.classList.remove('screen_off');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.screenSharingPc.onicecandidate = event => {
|
|
let candidate = event.candidate;
|
|
if (candidate && candidate.candidate && candidate.candidate.length > 0) {
|
|
Visio_ajaxCandidate(this.fullJid, this.id, event.candidate);
|
|
}
|
|
};
|
|
|
|
this.screenSharingPc.setRemoteDescription({ 'sdp': sdp + "\n", 'type': 'offer' })
|
|
.then(this.screenSharingPc.createAnswer()
|
|
.then(answer => this.screenSharingPc.setLocalDescription(answer))
|
|
.then(() => {
|
|
Visio_ajaxContentAccept(
|
|
this.fullJid,
|
|
this.id,
|
|
this.maybeInjectScreenCategory(this.screenSharingPc.localDescription.sdp),
|
|
this.jid
|
|
)
|
|
})
|
|
.catch(MovimUtils.logError));
|
|
}
|
|
|
|
MovimJingleSession.prototype.onSFUScreenSharingTerminate = function () {
|
|
if (this.screenSharingPc) {
|
|
this.screenSharingPc.close();
|
|
this.screenSharingPc = null;
|
|
}
|
|
|
|
this.participant.classList.add('screen_off');
|
|
|
|
Object.keys(MovimJingles.sessions[this.jid].tracksTypes).forEach(
|
|
key => {
|
|
if (key.startsWith('sfu_screen_mid')) {
|
|
delete MovimJingles.sessions[this.jid].tracksTypes[key]
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
MovimJingleSession.prototype.enableScreenSharing = function () {
|
|
MovimVisio.screenSharing.srcObject.getTracks().forEach(track => {
|
|
if (track.kind == 'video') {
|
|
if (this.screenSharingSender) {
|
|
this.screenSharingSender.replaceTrack(track).then(() => {
|
|
this.unmute(track);
|
|
});
|
|
} else {
|
|
this.screenSharingSender = this.pc.addTrack(track);
|
|
}
|
|
} else {
|
|
if (this.audioSharingSender) {
|
|
this.audioSharingSender.replaceTrack(track).then(() => {
|
|
this.unmute(track);
|
|
});
|
|
} else {
|
|
this.audioSharingSender = this.pc.addTrack(track);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
MovimJingleSession.prototype.disableScreenSharing = function () {
|
|
if (this.pc && MovimVisio.screenSharing.srcObject) {
|
|
MovimVisio.screenSharing.srcObject.getTracks().forEach(track => this.mute(track));
|
|
}
|
|
}
|
|
|
|
MovimJingleSession.prototype.resolveTransceiver = function (track) {
|
|
const transceiver = this.pc.getTransceivers().find(transceiver => transceiver.sender.track && transceiver.sender.track.id == track.id);
|
|
return transceiver ?? null;
|
|
}
|
|
|
|
MovimJingleSession.prototype.mute = function (track) {
|
|
const transceiver = this.resolveTransceiver(track);
|
|
if (transceiver) {
|
|
Visio_ajaxMute(this.fullJid, this.id, 'mid' + transceiver.mid);
|
|
}
|
|
}
|
|
|
|
MovimJingleSession.prototype.unmute = function (track) {
|
|
const transceiver = this.resolveTransceiver(track);
|
|
if (transceiver) {
|
|
Visio_ajaxUnmute(this.fullJid, this.id, 'mid' + transceiver.mid);
|
|
} else if (track.kind == 'video') {
|
|
this.pc.addTrack(track);
|
|
}
|
|
}
|
|
|
|
MovimJingleSession.prototype.onMute = function (name) {
|
|
if (this.tracksTypes[name]) {
|
|
if (this.tracksTypes[name] == 'audio') {
|
|
this.participant.classList.add('audio_off');
|
|
}
|
|
|
|
if (this.tracksTypes[name] == 'video') {
|
|
this.participant.classList.add('video_off');
|
|
}
|
|
|
|
if (this.tracksTypes[name] == 'video_screen') {
|
|
this.participant.classList.add('screen_off');
|
|
}
|
|
}
|
|
}
|
|
|
|
MovimJingleSession.prototype.onUnmute = function (name) {
|
|
if (this.tracksTypes[name]) {
|
|
if (this.tracksTypes[name] == 'audio') {
|
|
this.participant.classList.remove('audio_off');
|
|
}
|
|
|
|
if (this.tracksTypes[name] == 'video') {
|
|
this.participant.classList.remove('video_off');
|
|
}
|
|
|
|
if (this.tracksTypes[name] == 'video_screen') {
|
|
this.participant.classList.remove('screen_off');
|
|
}
|
|
}
|
|
}
|
|
|
|
MovimJingleSession.prototype.insertDtmf = function (s) {
|
|
var rtc = this.pc.getSenders().find(rtc => rtc.track && rtc.track.kind == 'audio');
|
|
|
|
if (!rtc || !rtc.dtmf.canInsertDTMF) return;
|
|
|
|
rtc.dtmf.insertDTMF(s);
|
|
}
|
|
|
|
/**
|
|
* Detect RFC4796 content attribute
|
|
*/
|
|
MovimJingleSession.prototype.maybeDetectTracksScreen = function (sdp) {
|
|
sdp.split('m=').forEach(media => {
|
|
let content = media.match(/a=content:(.*)/);
|
|
if (content && content.includes('slides')) {
|
|
let mid = media.match(/a=mid:(.*)/);
|
|
this.tracksScreen[mid[1]] = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Inject RFC4796 content attribute to respect XEP-0507: Jingle Content Category
|
|
*/
|
|
MovimJingleSession.prototype.maybeInjectScreenCategory = function (sdp) {
|
|
if (this.audioSharingSender?.track || this.screenSharingSender?.track) {
|
|
let ids = [];
|
|
if (this.audioSharingSender) ids.push(this.audioSharingSender.track.id);
|
|
if (this.screenSharingSender) ids.push(this.screenSharingSender.track.id);
|
|
|
|
sdp = sdp.split('m=').map(media => {
|
|
let mid = media.match(MovimVisio.midRegex);
|
|
if (mid != null) {
|
|
const transceiver = this.pc.getTransceivers().find(transceiver => transceiver.mid == mid[1]);
|
|
if (transceiver) {
|
|
if (ids.includes(transceiver.sender.track.id)) {
|
|
return media + 'a=content:slides' + "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
return media;
|
|
}).join('m=');
|
|
}
|
|
|
|
return sdp;
|
|
}
|
|
|
|
var MovimJingles = {
|
|
sessions: {},
|
|
pinned: null,
|
|
|
|
startCalls: function (mujiRoom) {
|
|
for (const jid of Object.keys(MovimJingles.sessions)) {
|
|
MovimJingles.onProceed(jid, MovimJingles.sessions[jid].fullJid, MovimJingles.sessions[jid].id, mujiRoom);
|
|
}
|
|
},
|
|
|
|
initSession: function (jid, fullJid, id, name, avatarUrl) {
|
|
if (Object.keys(MovimJingles.sessions).includes(jid)) {
|
|
return;
|
|
}
|
|
|
|
if (!MovimVisio.localStream) {
|
|
throw Error('localStream is not ready');
|
|
};
|
|
|
|
MovimJingles.sessions[jid] = new MovimJingleSession(jid, fullJid, id, name, avatarUrl);
|
|
return MovimJingles.sessions[jid].id;
|
|
},
|
|
|
|
checkActiveSpeaker: function () {
|
|
let maxLevel = 0;
|
|
let maxJid = null;
|
|
|
|
for (const jid of Object.keys(MovimJingles.sessions)) {
|
|
if (maxLevel < MovimJingles.sessions[jid].audioLevel) {
|
|
maxLevel = MovimJingles.sessions[jid].audioLevel;
|
|
maxJid = jid;
|
|
}
|
|
|
|
MovimJingles.sessions[jid].participant.classList.remove('active', 'pinned');
|
|
}
|
|
|
|
// Pinned has priority
|
|
if (MovimJingles.pinned && MovimJingles.sessions[MovimJingles.pinned]) {
|
|
MovimJingles.sessions[MovimJingles.pinned].participant.classList.add('active', 'pinned');
|
|
return;
|
|
}
|
|
|
|
if (maxJid != null) {
|
|
MovimJingles.sessions[maxJid].participant.classList.add('active');
|
|
} else if (Object.values(MovimJingles.sessions).length > 0) {
|
|
Object.values(MovimJingles.sessions)[MovimVisio.sfu != null ? 1 : 0]?.participant.classList.add('active');
|
|
}
|
|
},
|
|
|
|
onManageTrack: function (stream, enable) {
|
|
for (const jid of Object.keys(MovimJingles.sessions)) {
|
|
MovimJingles.sessions[jid].onManageTrack(stream, enable);
|
|
}
|
|
},
|
|
|
|
enableScreenSharing: function () {
|
|
for (const jid of Object.keys(MovimJingles.sessions)) {
|
|
MovimJingles.sessions[jid].enableScreenSharing();
|
|
}
|
|
},
|
|
|
|
disableScreenSharing: function () {
|
|
for (const jid of Object.keys(MovimJingles.sessions)) {
|
|
MovimJingles.sessions[jid].disableScreenSharing();
|
|
}
|
|
},
|
|
|
|
enableAudio: function (enable = true) {
|
|
MovimVisio.localStream.getTracks().filter(track => track.kind == 'audio').forEach(track => {
|
|
track.enabled = enable;
|
|
|
|
for (const jid of Object.keys(MovimJingles.sessions)) {
|
|
if (enable) {
|
|
MovimJingles.sessions[jid].unmute(track);
|
|
} else {
|
|
MovimJingles.sessions[jid].mute(track);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
enableVideo: function (enable = true) {
|
|
MovimVisio.mujiPublish();
|
|
|
|
MovimVisio.localStream.getTracks().filter(track => track.kind == 'video').forEach(track => {
|
|
track.enabled = enable;
|
|
|
|
for (const jid of Object.keys(MovimJingles.sessions)) {
|
|
if (enable) {
|
|
MovimJingles.sessions[jid].unmute(track);
|
|
} else {
|
|
MovimJingles.sessions[jid].mute(track);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
insertDtmf: function (s) {
|
|
for (const jid of Object.keys(MovimJingles.sessions)) {
|
|
MovimJingles.sessions[jid].insertDtmf(s);
|
|
}
|
|
},
|
|
|
|
onCandidate: function (jid, candidate, mid, mlineindex, sid) {
|
|
if (MovimJingles.sessions[jid] == undefined) {
|
|
console.log('Candidate from a non initiated session ' + jid); return;
|
|
}
|
|
|
|
if (sid && sid === MovimVisio.screenshareSid) {
|
|
MovimVisio.onScreenshareCandidate(candidate, mid, mlineindex);
|
|
return;
|
|
}
|
|
|
|
MovimJingles.sessions[jid].onCandidate(candidate, mid, mlineindex);
|
|
},
|
|
|
|
onInitiateSDP: function (jid, sdp, sid) {
|
|
if (MovimJingles.sessions[jid] == undefined) {
|
|
console.log('Initiate SDP from a non initiated session ' + jid); return;
|
|
}
|
|
|
|
// Put the real sid
|
|
MovimJingles.sessions[jid].id = sid;
|
|
MovimJingles.sessions[jid].onInitiateSDP(sdp);
|
|
},
|
|
|
|
onAcceptSDP: function (jid, sdp, sid) {
|
|
if (MovimJingles.sessions[jid] == undefined) {
|
|
console.log('Accept SDP from a non initiated session ' + jid); return;
|
|
}
|
|
|
|
if (sid && sid === MovimVisio.screenshareSid) {
|
|
MovimVisio.onScreenshareAcceptSDP(sdp);
|
|
return;
|
|
}
|
|
|
|
MovimJingles.sessions[jid].onAcceptSDP(sdp);
|
|
},
|
|
|
|
onProceed: function (jid, fullJid, id, mujiRoom) {
|
|
if (MovimJingles.sessions[jid] == undefined) {
|
|
console.log('Proceed from a non initiated session ' + jid); return;
|
|
}
|
|
|
|
MovimJingles.sessions[jid].sessionInitiate(fullJid, id, mujiRoom);
|
|
},
|
|
|
|
onMute: function (jid, name) {
|
|
if (MovimJingles.sessions[jid] == undefined) {
|
|
console.log('Mute from a non initiated session ' + jid); return;
|
|
}
|
|
|
|
MovimJingles.sessions[jid].onMute(name);
|
|
},
|
|
|
|
onUnmute: function (jid, name) {
|
|
if (MovimJingles.sessions[jid] == undefined) {
|
|
console.log('Unmute from a non initiated session ' + jid); return;
|
|
}
|
|
|
|
MovimJingles.sessions[jid].onUnmute(name);
|
|
},
|
|
|
|
onContentAdd: function (jid, sdp, mid) {
|
|
if (MovimJingles.sessions[jid] == undefined) {
|
|
console.log('Content add from a non initiated session ' + jid); return;
|
|
}
|
|
|
|
MovimJingles.sessions[jid].onContentAdd(sdp, mid);
|
|
},
|
|
|
|
onContentModify: function (jid, sdp, mid) {
|
|
if (MovimJingles.sessions[jid] == undefined) {
|
|
console.log('Content modify from a non initiated session ' + jid); return;
|
|
}
|
|
|
|
MovimJingles.sessions[jid].onContentModify(sdp, mid);
|
|
},
|
|
|
|
onContentRemove: function (jid, sdp, mid) {
|
|
if (MovimJingles.sessions[jid] == undefined) {
|
|
console.log('Content remove from a non initiated session ' + jid); return;
|
|
}
|
|
|
|
MovimJingles.sessions[jid].onContentRemove(sdp, mid);
|
|
},
|
|
|
|
onSFUScreenSharingPropose: function (jid, sdp) {
|
|
if (MovimJingles.sessions[jid] == undefined) {
|
|
console.log('SFU screen sharing propose from a non initiated session ' + jid); return;
|
|
}
|
|
|
|
MovimJingles.sessions[jid].onSFUScreenSharingPropose(sdp);
|
|
},
|
|
|
|
onSFUScreenSharingTerminate: function (jid) {
|
|
if (MovimJingles.sessions[jid] == undefined) {
|
|
console.log('SFU screen sharing terminate from a non initiated session ' + jid); return;
|
|
}
|
|
|
|
MovimJingles.sessions[jid].onSFUScreenSharingTerminate();
|
|
},
|
|
|
|
terminate: function (jid, reason) {
|
|
if (MovimJingles.sessions[jid] == undefined) return;
|
|
|
|
let visio = document.querySelector('#visio');
|
|
MovimJingles.sessions[jid].terminate(reason, (visio.dataset.muji == 'true'));
|
|
delete MovimJingles.sessions[jid];
|
|
},
|
|
|
|
onTerminate: function (jid) {
|
|
if (MovimJingles.sessions[jid] == undefined) return;
|
|
|
|
MovimJingles.sessions[jid].close();
|
|
delete MovimJingles.sessions[jid];
|
|
|
|
let visio = document.querySelector('#visio');
|
|
|
|
// No sessions left
|
|
if (Object.keys(MovimJingles.sessions).length == 0) {
|
|
if (visio.dataset.muji == 'false') {
|
|
Visio_ajaxClear();
|
|
} else {
|
|
let state = document.querySelector('p.state');
|
|
if (state) {
|
|
state.innerText = MovimVisio.states.no_participants_left;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
terminateAll: function (reason) {
|
|
for (const jid of Object.keys(MovimJingles.sessions)) {
|
|
MovimJingles.terminate(jid, reason);
|
|
}
|
|
|
|
MovimJingles.pinned = null;
|
|
Visio_ajaxClear();
|
|
}
|
|
} |