474 lines
15 KiB
JavaScript
474 lines
15 KiB
JavaScript
var MovimVisio = {
|
|
id: null,
|
|
|
|
calling: false,
|
|
|
|
pc: null,
|
|
|
|
screenshareSid: null,
|
|
screenshareSFUPc: null,
|
|
|
|
states: null,
|
|
services: [],
|
|
|
|
localStream: null,
|
|
localVideo: null,
|
|
localAudio: null,
|
|
screenSharing: null,
|
|
|
|
observer: null,
|
|
|
|
activeSpeakerIntervalId: null,
|
|
|
|
bundleRegex: 'a=group:(\\S+) (.+)',
|
|
midRegex: 'a=mid:(.+)',
|
|
|
|
mouseMovement: null,
|
|
mouseMovementTimeout: 5000,
|
|
|
|
startedAtTimer: null,
|
|
|
|
sfu: null,
|
|
|
|
load: function () {
|
|
MovimVisio.localVideo = document.getElementById('local_video');
|
|
|
|
// Disable the video toggle by default
|
|
MovimVisio.localVideo.classList.add('video_off');
|
|
|
|
MovimVisio.localVideo.addEventListener('loadeddata', () => {
|
|
MovimVisio.localVideo.play()
|
|
});
|
|
|
|
MovimVisio.screenSharing = document.getElementById('screen_sharing_video');
|
|
MovimVisio.localAudio = document.getElementById('local_audio');
|
|
},
|
|
|
|
init: function (fullJid, jid, id, withVideo, isMuji, sfuConference, contactName, contactAvatarUrl) {
|
|
MovimVisio.id = id;
|
|
|
|
// Set a lock for the current browser (in case there's several others opened)
|
|
localStorage.setItem('callId', id);
|
|
localStorage.setItem('callJid', jid);
|
|
|
|
let visio = document.querySelector('#visio');
|
|
delete visio.dataset.type;
|
|
visio.dataset.jid = jid;
|
|
visio.dataset.muji = isMuji ? 'true' : 'false';
|
|
|
|
if (typeof sfuConference == 'string') {
|
|
visio.dataset.muji = 'true'; // To use the Muji layout... without using Muji
|
|
visio.dataset.sfu = 'true';
|
|
visio.dataset.jid = sfuConference;
|
|
|
|
Visio_ajaxHttpPrepareInfo(sfuConference, true);
|
|
MovimVisio.activeSpeakerIntervalId = setInterval(MovimJingles.checkActiveSpeaker, 1000);
|
|
} else {
|
|
Visio_ajaxHttpPrepareInfo(jid, isMuji);
|
|
}
|
|
|
|
if (isMuji == true) {
|
|
MovimVisio.mujiPublish(true);
|
|
MovimVisio.activeSpeakerIntervalId = setInterval(MovimJingles.checkActiveSpeaker, 1000);
|
|
} else {
|
|
MovimJingles.initSession(jid, fullJid, id, contactName, contactAvatarUrl);
|
|
|
|
if (MovimVisio.id) {
|
|
// Called
|
|
Visio_ajaxProceed(fullJid, MovimVisio.id);
|
|
} else {
|
|
// Calling
|
|
MovimVisio.id = crypto.randomUUID();
|
|
localStorage.setItem('callId', MovimVisio.id);
|
|
Visio_ajaxPropose(fullJid, MovimVisio.id, withVideo, (typeof sfuConference == 'string'));
|
|
}
|
|
}
|
|
|
|
if (typeof sfuConference == 'string') {
|
|
MovimVisio.sfu = jid;
|
|
|
|
let sfuParticipant = visio.querySelector('div.participant[data-jid="' + jid + '"]');
|
|
if (sfuParticipant) {
|
|
// Ugly but works for now
|
|
sfuParticipant.remove();
|
|
}
|
|
|
|
Visio_ajaxUpgradeCurrentCallToSFU(jid, sfuConference);
|
|
}
|
|
|
|
visio.classList.add('movements');
|
|
|
|
Notif.setCallStatus(MovimVisio.states.in_call);
|
|
|
|
visio.addEventListener('mousemove', e => {
|
|
clearTimeout(MovimVisio.mouseMovement);
|
|
visio.classList.add('movements');
|
|
|
|
MovimVisio.mouseMovement = setTimeout(() => {
|
|
visio.classList.remove('movements');
|
|
}, MovimVisio.mouseMovementTimeout);
|
|
})
|
|
|
|
if (typeof navigator.mediaDevices.getDisplayMedia == 'undefined') {
|
|
document.querySelector('#screen_sharing').classList.add('hide');
|
|
}
|
|
|
|
if (MovimVisio.startedAtTimer) {
|
|
clearInterval(MovimVisio.startedAtTimer);
|
|
}
|
|
|
|
setInterval(VisioUtils.updateStartedAtCounters, 1000);
|
|
|
|
Dialog_ajaxClear();
|
|
Notif.snackbarClear();
|
|
},
|
|
|
|
startScreenshareSFU: function () {
|
|
if (!MovimVisio.screenSharing.srcObject) return;
|
|
|
|
const pc = new RTCPeerConnection({
|
|
iceServers: MovimVisio.services,
|
|
bundlePolicy: 'max-bundle',
|
|
iceCandidatePoolSize: 10
|
|
});
|
|
|
|
MovimVisio.screenshareSid = crypto.randomUUID();
|
|
MovimVisio.screenshareSFUPc = pc;
|
|
|
|
MovimVisio.screenSharing.srcObject.getTracks().forEach(track => {
|
|
pc.addTrack(track, MovimVisio.screenSharing.srcObject);
|
|
});
|
|
|
|
pc.onicecandidate = event => {
|
|
if (event.candidate && event.candidate.candidate) {
|
|
Visio_ajaxCandidate(MovimVisio.sfu, MovimVisio.screenshareSid, event.candidate);
|
|
}
|
|
};
|
|
|
|
pc.createOffer()
|
|
.then(offer => pc.setLocalDescription(offer))
|
|
.then(() => Visio_ajaxSessionInitiate(MovimVisio.sfu, pc.localDescription, MovimVisio.screenshareSid, null))
|
|
.catch(err => MovimUtils.logError(err));
|
|
},
|
|
|
|
stopScreenshareSFU: function () {
|
|
if (!MovimVisio.screenshareSFUPc) return;
|
|
|
|
Visio_ajaxTerminate(MovimVisio.sfu, MovimVisio.screenshareSid, 'success', false);
|
|
|
|
MovimVisio.screenshareSFUPc.close();
|
|
MovimVisio.screenshareSFUPc = null;
|
|
MovimVisio.screenshareSid = null;
|
|
},
|
|
|
|
onScreenshareAcceptSDP: function (sdp) {
|
|
if (!MovimVisio.screenshareSFUPc) return;
|
|
|
|
MovimVisio.screenshareSFUPc
|
|
.setRemoteDescription({ 'sdp': sdp + "\n", 'type': 'answer' })
|
|
.catch(err => MovimUtils.logError(err));
|
|
},
|
|
|
|
onScreenshareCandidate: function (candidate, mid, mlineindex) {
|
|
if (!MovimVisio.screenshareSFUPc) return;
|
|
|
|
MovimVisio.screenshareSFUPc.addIceCandidate(new RTCIceCandidate({
|
|
'candidate': candidate.split(/\n/).filter(line => line.startsWith('a=candidate')).join('').substring(2),
|
|
'sdpMid': mid,
|
|
'sdpMLineIndex': mlineindex
|
|
})).catch(error => MovimUtils.logError(error));
|
|
},
|
|
|
|
mujiPublish: function (init) {
|
|
let pc = new RTCPeerConnection({ 'iceServers': MovimVisio.services });
|
|
|
|
/**
|
|
* Ugly heuristic because it is not possible to match the SDP mids with the tranceiver sender track ids...
|
|
*/
|
|
let id = 0;
|
|
|
|
MovimVisio.localStream.getTracks().forEach(track => {
|
|
pc.addTrack(track, MovimVisio.localStream);
|
|
id++;
|
|
});
|
|
|
|
let screenIds = [];
|
|
|
|
if (MovimVisio.screenSharing.srcObject) {
|
|
MovimVisio.screenSharing.srcObject.getTracks().forEach(track => {
|
|
pc.addTrack(track);
|
|
screenIds.push(id);
|
|
id++;
|
|
});
|
|
}
|
|
|
|
pc.createOffer().then(function (offer) {
|
|
if (MovimVisio.screenSharing.srcObject) {
|
|
// XEP-0507: Jingle Content Category
|
|
newMedias = offer.sdp.split('m=').map(media => {
|
|
let mid = media.match(MovimVisio.midRegex);
|
|
if (mid != null) {
|
|
if (screenIds.includes(parseInt(mid[1]))) {
|
|
return media + 'a=content:slides' + "\n";
|
|
}
|
|
}
|
|
|
|
return media;
|
|
});
|
|
|
|
localDescription = newMedias.join('m=');
|
|
offer.sdp = localDescription;
|
|
}
|
|
|
|
Visio_ajaxMujiPublish(MovimVisio.id, offer, init);
|
|
pc.close();
|
|
});
|
|
},
|
|
|
|
setStates: function (states) {
|
|
MovimVisio.states = states;
|
|
},
|
|
|
|
setServices: function (services) {
|
|
MovimVisio.services = services;
|
|
},
|
|
|
|
getUserMedia: function (withVideo) {
|
|
MovimVisio.load();
|
|
|
|
let lobby = document.querySelector('#visio_lobby');
|
|
|
|
if (lobby) {
|
|
VisioUtils.disableLobbyCallButton();
|
|
}
|
|
|
|
MovimTpl.loadingPage();
|
|
|
|
return navigator.mediaDevices.getUserMedia(VisioUtils.getConstraints(withVideo)).then(stream => {
|
|
MovimTpl.finishedPage();
|
|
|
|
MovimVisio.localStream = stream;
|
|
|
|
if (lobby) {
|
|
lobby.classList.add('configure');
|
|
} else if (MovimVisio.localAudio.srcObject == null) {
|
|
Visio_ajaxClear();
|
|
return;
|
|
}
|
|
|
|
stream.getTracks().forEach(track => {
|
|
if (lobby) {
|
|
VisioUtils.enableLobbyCallButton();
|
|
}
|
|
|
|
if (track.kind == 'audio' && MovimVisio.localAudio && MovimVisio.localAudio.srcObject == null /* In case we're just adding the video */) {
|
|
MovimVisio.localAudio.srcObject = stream;
|
|
} else if (withVideo && track.kind === 'video') {
|
|
MovimVisio.localVideo.srcObject = stream;
|
|
|
|
// Toggle the video on
|
|
MovimVisio.localVideo.classList.remove('video_off');
|
|
document.querySelector('#toggle_video i').innerText = 'videocam';
|
|
|
|
if (lobby) {
|
|
let cameraPreview = lobby.querySelector('video#camera_preview');
|
|
cameraPreview.addEventListener('loadeddata', () => cameraPreview.play());
|
|
cameraPreview.srcObject = stream;
|
|
cameraPreview.disablePictureInPicture = true;
|
|
}
|
|
|
|
}
|
|
});
|
|
|
|
VisioUtils.handleAudio();
|
|
|
|
if (lobby) {
|
|
navigator.mediaDevices.enumerateDevices().then(devices => MovimVisio.gotDevices(withVideo, devices));
|
|
}
|
|
}, (e) => {
|
|
MovimTpl.finishedPage();
|
|
});
|
|
},
|
|
|
|
gotDevices: function (withVideo, devicesInfo) {
|
|
microphoneFound = false;
|
|
cameraFound = false;
|
|
|
|
let microphoneSelect = document.querySelector('select[name=default_microphone]');
|
|
microphoneSelect.onchange = (e) => {
|
|
localStorage.defaultMicrophone = e.target.value;
|
|
MovimVisio.getUserMedia(withVideo);
|
|
};
|
|
microphoneSelect.innerText = '';
|
|
|
|
VisioUtils.handleAudio();
|
|
|
|
let cameraSelect = document.querySelector('select[name=default_camera]');
|
|
|
|
if (cameraSelect) {
|
|
cameraSelect.addEventListener('change', e => {
|
|
localStorage.defaultCamera = e.target.value;
|
|
|
|
let cameraPreview = document.querySelector('video#camera_preview');
|
|
|
|
if (cameraPreview.srcObject) {
|
|
cameraPreview.srcObject.getTracks().forEach(track => track.stop());
|
|
}
|
|
|
|
cameraPreview.srcObject = null;
|
|
|
|
MovimVisio.getUserMedia(withVideo);
|
|
});
|
|
cameraSelect.innerText = '';
|
|
}
|
|
|
|
for (const deviceInfo of devicesInfo) {
|
|
if (deviceInfo.kind === 'audioinput') {
|
|
const option = document.createElement('option');
|
|
option.value = deviceInfo.deviceId;
|
|
option.text = deviceInfo.label || `Microphone ${microphoneSelect.length + 1}`;
|
|
|
|
if (deviceInfo.deviceId == localStorage.defaultMicrophone) {
|
|
option.selected = true;
|
|
microphoneFound = true;
|
|
}
|
|
|
|
microphoneSelect.appendChild(option);
|
|
}
|
|
|
|
if (withVideo && deviceInfo.kind === 'videoinput') {
|
|
const option = document.createElement('option');
|
|
option.value = deviceInfo.deviceId;
|
|
option.text = deviceInfo.label || `Camera ${microphoneSelect.length + 1}`;
|
|
|
|
if (deviceInfo.deviceId == localStorage.defaultCamera) {
|
|
option.selected = true;
|
|
cameraFound = true;
|
|
}
|
|
|
|
// Sometimes we can have two devices with the same id
|
|
if (!cameraSelect.querySelector('option[value="' + deviceInfo.deviceId + '"]')) {
|
|
cameraSelect.appendChild(option);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (microphoneFound == false) {
|
|
localStorage.defaultMicrophone = microphoneSelect.value;
|
|
}
|
|
|
|
if (withVideo && cameraFound == false) {
|
|
localStorage.defaultCamera = cameraSelect.value;
|
|
}
|
|
},
|
|
|
|
clear: function () {
|
|
MovimTpl.finishedPage();
|
|
|
|
MovimVisio.id = null;
|
|
MovimVisio.sfu = null;
|
|
|
|
Notif.setCallStatus(null);
|
|
|
|
clearInterval(MovimVisio.activeSpeakerIntervalId);
|
|
|
|
let visio = document.querySelector('#visio');
|
|
//delete visio.dataset.type;
|
|
delete visio.dataset.jid;
|
|
delete visio.dataset.muji;
|
|
delete visio.dataset.sfu;
|
|
|
|
if (document.fullscreenElement) {
|
|
document.exitFullscreen();
|
|
}
|
|
|
|
VisioUtils.stopAudio();
|
|
|
|
if (MovimVisio.localAudio) {
|
|
let stream = MovimVisio.localAudio.srcObject;
|
|
|
|
if (stream) {
|
|
stream.getTracks().forEach(track => track.stop());
|
|
stream = null;
|
|
}
|
|
|
|
MovimVisio.localAudio.srcObject = null;
|
|
}
|
|
|
|
if (MovimVisio.localVideo) {
|
|
let stream = MovimVisio.localVideo.srcObject;
|
|
|
|
if (stream) {
|
|
stream.getTracks().forEach(track => track.stop());
|
|
stream = null;
|
|
}
|
|
|
|
MovimVisio.localVideo.srcObject = null;
|
|
}
|
|
|
|
if (MovimVisio.localStream) {
|
|
MovimVisio.localStream.getTracks().forEach(function (track) {
|
|
track.stop();
|
|
});
|
|
MovimVisio.localStream = null;
|
|
}
|
|
|
|
VisioUtils.disableScreenSharing();
|
|
|
|
MovimVisio.localAudio = null;
|
|
MovimVisio.localVideo = null;
|
|
MovimVisio.screenSharing = null;
|
|
},
|
|
|
|
moveToChat: function (jid) {
|
|
if (MovimVisio.observer != null) {
|
|
MovimVisio.observer.disconnect();
|
|
}
|
|
|
|
// SFU case
|
|
if (MovimVisio.sfu != null) {
|
|
let visio = document.querySelector('#visio');
|
|
jid = visio.dataset.jid;
|
|
}
|
|
|
|
var parts = MovimUtils.urlParts();
|
|
|
|
if ((parts.page == 'chat' && parts.params[0] == jid)
|
|
|| (parts.page == 'space' && parts.params[2] == jid)) {
|
|
const visio = document.getElementById('visio');
|
|
const body = document.body;
|
|
|
|
document.querySelector('#chat_widget header').after(visio);
|
|
Chat.scrollRestore();
|
|
|
|
const callback = (mutationList, observer) => {
|
|
if (!document.getElementById('visio')) {
|
|
document.getElementById('endcommon').before(visio);
|
|
}
|
|
};
|
|
|
|
MovimVisio.observer = new MutationObserver(callback);
|
|
MovimVisio.observer.observe(body, { childList: true, subtree: true });
|
|
}
|
|
|
|
},
|
|
|
|
callStop: function (id, jid) {
|
|
localStorage.removeItem('callId');
|
|
localStorage.removeItem('callJid');
|
|
}
|
|
}
|
|
|
|
if (typeof Visio_ajaxHttpGetStates == 'function') {
|
|
Visio_ajaxHttpGetStates();
|
|
}
|
|
|
|
if (typeof MovimWebsocket != 'undefined') {
|
|
MovimWebsocket.attach(() => {
|
|
if (MovimVisio.services.length == 0 && typeof Visio_ajaxResolveServices == 'function') {
|
|
Visio_ajaxResolveServices();
|
|
Visio_ajaxCheckStatus(localStorage.getItem('callId'), localStorage.getItem('callJid'));
|
|
}
|
|
});
|
|
}
|