Files
movim-rs/frontend/widgets/Visio/visio_utils.js
T
2026-10-24 14:03:52 +07:00

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');
});
}
}