420 lines
14 KiB
JavaScript
420 lines
14 KiB
JavaScript
var VisioUtils = {
|
|
microphone: null,
|
|
maxLevel: 0,
|
|
remoteMaxLevel: 0,
|
|
audioContext: null,
|
|
meter: null,
|
|
rafId: null,
|
|
|
|
handleAudio: function () {
|
|
if (VisioUtils.audioContext) {
|
|
VisioUtils.audioContext.close();
|
|
VisioUtils.audioContext = null;
|
|
}
|
|
|
|
VisioUtils.audioContext = new AudioContext();
|
|
|
|
try {
|
|
VisioUtils.microphone = VisioUtils.audioContext.createMediaStreamSource(
|
|
MovimVisio.localAudio.srcObject
|
|
);
|
|
} catch (error) {
|
|
MovimUtils.logError(error);
|
|
return;
|
|
}
|
|
|
|
let javascriptNode = VisioUtils.audioContext.createScriptProcessor(128 * 64, 1, 1);
|
|
let icon = document.querySelector('#toggle_audio i');
|
|
let toggleAudio = document.getElementById('toggle_audio');
|
|
icon.innerText = 'mic';
|
|
let isMuteStep = 251;
|
|
let noMicSound = document.querySelector('#no_mic_sound');
|
|
let defaultMicrophone = document.querySelector('#default_microphone');
|
|
|
|
if (defaultMicrophone) {
|
|
defaultMicrophone.classList.add('muted');
|
|
}
|
|
|
|
VisioUtils.meter = VisioUtils.audioContext.createAnalyser();
|
|
VisioUtils.meter.fftSize = 256;
|
|
VisioUtils.microphone.connect(VisioUtils.meter);
|
|
|
|
function startAnalyzer() {
|
|
if (!VisioUtils.meter) return;
|
|
|
|
const dataArray = new Uint8Array(VisioUtils.meter.frequencyBinCount);
|
|
|
|
function update() {
|
|
if (!VisioUtils.meter) return;
|
|
|
|
VisioUtils.meter.getByteFrequencyData(dataArray);
|
|
|
|
let sum = 0;
|
|
for (let i = 0; i < dataArray.length; i++) {
|
|
sum += dataArray[i];
|
|
}
|
|
|
|
const avg = sum / dataArray.length;
|
|
const level = Math.min(1, (avg / 255) ** .3);
|
|
VisioUtils.rafId = requestAnimationFrame(update);
|
|
|
|
let step = 0;
|
|
|
|
if (level == 0) {
|
|
isMuteStep++;
|
|
} else {
|
|
isMuteStep = 0;
|
|
}
|
|
|
|
if (isMuteStep > 32) {
|
|
if (noMicSound) {
|
|
noMicSound.classList.remove('disabled');
|
|
}
|
|
|
|
if (defaultMicrophone) {
|
|
defaultMicrophone.classList.add('muted');
|
|
}
|
|
} else {
|
|
if (noMicSound) {
|
|
noMicSound.classList.add('disabled');
|
|
}
|
|
|
|
if (defaultMicrophone) {
|
|
defaultMicrophone.classList.remove('muted');
|
|
}
|
|
|
|
// Lobby level
|
|
if (isMuteStep <= 5) {
|
|
document.querySelectorAll('.level span').forEach(span => {
|
|
if (step < Math.floor(level * 10)) {
|
|
span.classList.remove('disabled');
|
|
} else {
|
|
span.classList.add('disabled');
|
|
}
|
|
|
|
step++;
|
|
});
|
|
|
|
toggleAudio.style.setProperty('--level', level.toFixed(2));
|
|
}
|
|
}
|
|
}
|
|
update();
|
|
}
|
|
|
|
startAnalyzer();
|
|
},
|
|
|
|
stopAudio: function () {
|
|
if (VisioUtils.rafId) {
|
|
cancelAnimationFrame(VisioUtils.rafId);
|
|
VisioUtils.rafId = null;
|
|
}
|
|
|
|
if (VisioUtils.microphone) {
|
|
VisioUtils.microphone.disconnect();
|
|
VisioUtils.microphone = null;
|
|
}
|
|
|
|
if (VisioUtils.audioContext) {
|
|
VisioUtils.audioContext.close();
|
|
VisioUtils.audioContext = null;
|
|
}
|
|
},
|
|
|
|
toggleFullScreen: function () {
|
|
let button = document.querySelector('#toggle_fullscreen i');
|
|
|
|
if (!document.fullscreenElement) {
|
|
if (document.querySelector('#visio').requestFullscreen) {
|
|
document.querySelector('#visio').requestFullscreen();
|
|
}
|
|
|
|
button.innerText = 'fullscreen_exit';
|
|
} else {
|
|
if (document.exitFullscreen) {
|
|
document.exitFullscreen();
|
|
}
|
|
|
|
button.innerText = 'fullscreen';
|
|
}
|
|
},
|
|
|
|
toggleMode: function (active) {
|
|
let button = document.querySelector('#toggle_mode i');
|
|
let participants = document.querySelector('#participants');
|
|
|
|
if (button.innerText == 'tile_small' || active) {
|
|
participants.classList.add('active');
|
|
button.innerHTML = 'tile_large';
|
|
} else {
|
|
participants.classList.remove('active');
|
|
button.innerHTML = 'tile_small';
|
|
}
|
|
},
|
|
|
|
toggleAudio: function () {
|
|
let button = document.querySelector('#toggle_audio i');
|
|
|
|
if (button.innerText == 'mic_off') {
|
|
MovimJingles.enableAudio(true);
|
|
button.innerText = 'mic';
|
|
} else {
|
|
MovimJingles.enableAudio(false);
|
|
button.innerText = 'mic_off';
|
|
}
|
|
},
|
|
|
|
toggleVideo: async function () {
|
|
let button = document.querySelector('#toggle_video i');
|
|
|
|
if (MovimVisio.localVideo.srcObject == null) {
|
|
MovimVisio.getUserMedia(true).then(e => {
|
|
MovimJingles.enableVideo(true);
|
|
MovimVisio.localVideo.classList.remove('video_off');
|
|
button.innerText = 'videocam';
|
|
});
|
|
} else {
|
|
if (button.innerText == 'videocam_off') {
|
|
MovimJingles.enableVideo(true);
|
|
MovimVisio.localVideo.classList.remove('video_off');
|
|
button.innerText = 'videocam';
|
|
} else {
|
|
MovimJingles.enableVideo(false);
|
|
MovimVisio.localVideo.classList.add('video_off');
|
|
button.innerText = 'videocam_off';
|
|
}
|
|
}
|
|
},
|
|
|
|
toggleScreenSharing: async function () {
|
|
MovimVisio.switchCamera = document.querySelector("#visio #switch_camera");
|
|
let button = document.querySelector('#screen_sharing i');
|
|
|
|
if (MovimVisio.screenSharing.srcObject == null) {
|
|
try {
|
|
MovimVisio.screenSharing.srcObject = await navigator.mediaDevices.getDisplayMedia({
|
|
video: { cursor: "always" },
|
|
audio: true
|
|
});
|
|
|
|
MovimVisio.screenSharing.classList.add('sharing');
|
|
VisioUtils.disableSwitchCameraButton();
|
|
button.innerText = 'stop_screen_share';
|
|
|
|
if (MovimVisio.sfu) {
|
|
MovimVisio.startScreenshareSFU();
|
|
} else {
|
|
MovimJingles.enableScreenSharing();
|
|
MovimVisio.mujiPublish();
|
|
}
|
|
} catch (err) {
|
|
console.error("Error: " + err);
|
|
}
|
|
return;
|
|
} else {
|
|
if (MovimVisio.sfu) {
|
|
MovimVisio.stopScreenshareSFU();
|
|
VisioUtils.disableScreenSharing();
|
|
} else {
|
|
VisioUtils.disableScreenSharing();
|
|
MovimVisio.mujiPublish();
|
|
}
|
|
}
|
|
},
|
|
|
|
disableScreenSharing: function () {
|
|
MovimJingles.disableScreenSharing();
|
|
|
|
if (MovimVisio.screenSharing && MovimVisio.screenSharing.srcObject) {
|
|
MovimVisio.screenSharing.srcObject.getTracks().forEach(track => track.stop());
|
|
MovimVisio.screenSharing.srcObject = null;
|
|
|
|
MovimVisio.screenSharing.classList.remove('sharing');
|
|
MovimVisio.switchCamera.classList.remove('disabled');
|
|
}
|
|
|
|
if (button = document.querySelector('#screen_sharing i')) {
|
|
button.innerText = 'screen_share';
|
|
}
|
|
},
|
|
|
|
switchChat: function () {
|
|
let visio = document.querySelector('#visio');
|
|
|
|
if (visio.dataset.jid) {
|
|
Search.chat(visio.dataset.jid, (visio.dataset.muji == 'true'));
|
|
}
|
|
},
|
|
|
|
toggleDtmf: function () {
|
|
document.querySelector('#visio #dtmf').classList.toggle('hide');
|
|
},
|
|
|
|
insertDtmf: function (s) {
|
|
VisioDTMF.pressButton(s);
|
|
setTimeout(() => VisioDTMF.stop(), 100);
|
|
|
|
let insert = (s == '*') ? '🞳' : s;
|
|
document.querySelector('#dtmf p.dtmf span').innerText += insert;
|
|
|
|
MovimJingles.insertDtmf(s);
|
|
},
|
|
|
|
clearDtMf: function () {
|
|
document.querySelector('#dtmf p.dtmf span').innerText = '';
|
|
},
|
|
|
|
disableSwitchCameraButton: function () {
|
|
MovimVisio.switchCamera.classList.add('disabled');
|
|
},
|
|
|
|
enableLobbyCallButton: function () {
|
|
if (document.querySelector('#lobby_start')) {
|
|
document.querySelector('#lobby_start').classList.remove('disabled');
|
|
}
|
|
},
|
|
|
|
disableLobbyCallButton: function () {
|
|
if (document.querySelector('#lobby_start')) {
|
|
document.querySelector('#lobby_start').classList.add('disabled');
|
|
}
|
|
},
|
|
|
|
getConstraints: function (withVideo) {
|
|
const cpuCores = navigator.hardwareConcurrency || 2;
|
|
let constraints = {
|
|
audio: true,
|
|
video: false,
|
|
};
|
|
|
|
if (withVideo) {
|
|
if (cpuCores <= 2) {
|
|
constraints.video = {
|
|
width: { ideal: 640 },
|
|
height: { ideal: 480 },
|
|
frameRate: { max: 15, ideal: 15 }
|
|
};
|
|
} else if (cpuCores <= 4) {
|
|
constraints.video = {
|
|
width: { ideal: 1280 },
|
|
height: { ideal: 720 },
|
|
frameRate: { max: 30, ideal: 24 }
|
|
};
|
|
} else {
|
|
constraints.video = {
|
|
width: { ideal: 1920 },
|
|
height: { ideal: 1080 },
|
|
frameRate: { max: 30, ideal: 30 }
|
|
};
|
|
}
|
|
|
|
constraints.video.facingMode = 'user';
|
|
|
|
if (localStorage.defaultCamera) {
|
|
constraints.video.deviceId = localStorage.defaultCamera
|
|
}
|
|
}
|
|
|
|
if (localStorage.defaultMicrophone) {
|
|
constraints.audio = {
|
|
deviceId: localStorage.defaultMicrophone
|
|
}
|
|
}
|
|
|
|
return constraints;
|
|
},
|
|
|
|
setVideoCodecPreferences: function (transceiver) {
|
|
let codecs = RTCRtpReceiver.getCapabilities('video').codecs;
|
|
|
|
if (transceiver.setCodecPreferences != undefined) {
|
|
let preferredOrder = [/*'video/AV1',*/ 'video/H264', 'video/VP8', 'video/VP9'];
|
|
codecs.sort((a, b) => {
|
|
const indexA = preferredOrder.indexOf(a.mimeType);
|
|
const indexB = preferredOrder.indexOf(b.mimeType);
|
|
const orderA = indexA >= 0 ? indexA : Number.MAX_VALUE;
|
|
const orderB = indexB >= 0 ? indexB : Number.MAX_VALUE;
|
|
return orderA - orderB;
|
|
});
|
|
transceiver.setCodecPreferences(codecs);
|
|
}
|
|
},
|
|
|
|
adaptToNetworkCondition: async function (peerConnection) {
|
|
const stats = await peerConnection.getStats();
|
|
let packetLossRate = 0;
|
|
let rtt = 0;
|
|
|
|
stats.forEach(stat => {
|
|
if (stat.type === 'outbound-rtp' && stat.kind === 'video') {
|
|
if (stat.packetsSent && stat.packetsLost) {
|
|
packetLossRate = stat.packetsLost / stat.packetsSent;
|
|
}
|
|
}
|
|
|
|
if (stat.type === 'remote-inbound-rtp') {
|
|
rtt = stat.roundTripTime;
|
|
}
|
|
});
|
|
|
|
let networkIcon = document.querySelector('#network_condition');
|
|
networkIcon.classList.remove('excellent', 'good', 'bad');
|
|
|
|
peerConnection.getSenders().filter(s =>
|
|
s.track && s.track.kind === 'video'
|
|
).forEach(sender => {
|
|
const parameters = sender.getParameters();
|
|
|
|
// Don't exceed original encoding
|
|
if (!parameters.encodings || parameters.encodings.length === 0) {
|
|
parameters.encodings = [{}];
|
|
}
|
|
|
|
if (packetLossRate > 0.1 || rtt > 0.6) {
|
|
networkIcon.classList.add('bad');
|
|
parameters.encodings[0].maxBitrate = 250000; // 250 kbps
|
|
parameters.encodings[0].scaleResolutionDownBy = 4; // 1/4 resolution
|
|
} else if (packetLossRate > 0.05 || rtt > 0.3) {
|
|
networkIcon.classList.add('good');
|
|
parameters.encodings[0].maxBitrate = 500000; // 500 kbps
|
|
parameters.encodings[0].scaleResolutionDownBy = 2; // 1/2 resolution
|
|
} else {
|
|
networkIcon.classList.add('excellent');
|
|
parameters.encodings[0].maxBitrate = 1500000; // 1.5 Mbps
|
|
parameters.encodings[0].scaleResolutionDownBy = 1; // Full resolution
|
|
}
|
|
|
|
sender.setParameters(parameters);
|
|
});
|
|
},
|
|
|
|
cancelLobby: function (fullJid, id) {
|
|
if (fullJid && id) {
|
|
Visio_ajaxReject(fullJid, id);
|
|
}
|
|
|
|
Visio_ajaxClear(); Dialog_ajaxClear(); Notif.snackbarClear();
|
|
},
|
|
|
|
updateStartedAtCounters: function (startedAt) {
|
|
document.querySelectorAll('span[data-started-at]').forEach(span => {
|
|
var dateStartedAt = Date.parse(span.dataset.startedAt);
|
|
var dateNow = new Date();
|
|
|
|
var seconds = Math.floor((dateNow - (dateStartedAt)) / 1000);
|
|
var minutes = Math.floor(seconds / 60);
|
|
var hours = Math.floor(minutes / 60);
|
|
var days = Math.floor(hours / 24);
|
|
|
|
hours = hours - (days * 24);
|
|
minutes = minutes - (days * 24 * 60) - (hours * 60);
|
|
seconds = seconds - (days * 24 * 60 * 60) - (hours * 60 * 60) - (minutes * 60);
|
|
|
|
span.innerHTML = hours > 0
|
|
? hours + ':' + String(minutes).padStart(2, '0') + ':' + String(seconds).padStart(2, '0')
|
|
: String(minutes).padStart(2, '0') + ':' + String(seconds).padStart(2, '0');
|
|
});
|
|
}
|
|
}
|