/*
* Copyright (c) 2022 Steve Seguin. All Rights Reserved.
*
* Use of this source code is governed by the APGLv3 open-source license
* that can be found in the LICENSE file in the root of the source
* tree. Alternative licencing options can be made available on request.
*
*/
/*jshint esversion: 6 */
var formSubmitting = true;
var activatedPreview = false;
var screensharesupport = true;
var FirefoxEnumerated = false;
var Callbacks = [];
var CtrlPressed = false; // global
var AltPressed = false;
var KeyPressedTimeout = 0;
var PPTKeyPressed = false;
var translation = false;
var miscTranslations = {
"start" : "START",
"new-display-name":"Enter a new Display Name for this stream",
"submit-error-report": "Press OK to submit any error logs to VDO.Ninja. Error logs may contain private information.",
"director-redirect-1": "The director wishes to redirect you to the URL: ",
"director-redirect-2": "\n\nPress OK to be redirected.",
"add-a-label": "Add a label",
"audio-processing-disabled": "Audio processing is disabled with this guest. Can't mute or change volume",
"not-the-director": "You are not the director of this room. You will have limited to no control. See &codirector on how to become a co-director.",
"room-is-claimed": "The room is already claimed by someone else.\n\nOnly the first person to join a room is the assigned director.\n\nRefresh after the first director leaves to claim.",
"token-room-is-claimed": "The room is claimed by someone else.\n\nJoin as a guest or co-director instead.",
"room-is-claimed-codirector": "The room is already claimed by someone else.\n\nTrying to join as a co-director...",
"streamid-already-published": "The stream ID you are publishing to is already in use.\n\nPlease try with a different invite link or refresh to retry again.\n\nYou will now be disconnected.",
"director": "Director",
"unknown-user": "Unknown User",
"room-test-not-good": "The room name 'test' is very commonly used and may not be secure.\n\nAre you sure you wish to proceed?",
"load-previous-session":"Would you like to load your previous session's settings?",
"enter-password" : "Please enter the password below: \n\n(Note: Passwords are case-sensitive and you will not be alerted if it is incorrect.)",
"enter-password-2" : "Please enter the password below: \n\n(Note: Passwords are case-sensitive.)",
"enter-director-password": "Please enter the director's password:\n\n(Note: Passwords are case-sensitive and you will not be alerted if it is incorrect.)",
"password-incorrect" : "The password was incorrect.\n\nRefresh and try again.",
"enter-display-name" : "Please enter your display name:",
"enter-new-display-name" :"Enter a new Display Name for this stream",
"what-bitrate":"What bitrate would you like to record at? (kbps)\n(note: This feature is experimental, so have backup recordings going)",
"enter-website": "Enter a website URL to share",
"press-ok-to-record": "Press OK to start recording. Press again to stop and download.\n\nWarning: Keep this browser tab active to continue recording.\n\nYou can change the default video bitrate if desired below (kbps)",
"no-streamID-provided": "No streamID was provided; one will be generated randomily.\n\nStream ID: ",
"alphanumeric-only": "Info: Only AlphaNumeric characters should be used for the stream ID.\n\nThe offending characters have been replaced by an underscore",
"stream-id-too-long": "The Stream ID should be less than 45 alPhaNuMeric characters long.\n\nWe will trim it to length.",
"share-with-trusted":"Share only with those you trust",
"pass-recommended" : "A password is recommended",
"insecure-room-name" : "Insecure room name.",
"allowed-chars" : "Allowed chars",
"transfer" : "transfer",
"armed" : "armed",
"transfer-guest-to-room" : "Transfer guests to room:\n\n(Please note: rooms must share the same password)",
"transfer-guest-to-url" :"Transfer guests to new website URL.\n\nGuests will be prompted to accept unless they are using &consent",
"change-url" : "change URL",
"mute-in-scene" : "mute in scene",
"unmute-guest": "unmute",
"undeafen" : "undeafen",
"deafen" : "deafen",
"unblind" : "unblind",
"blind" : "blind",
"unmute" : "unmute",
"mute-guest" : "mute",
"unhide" : "Unhide",
"hide-guest": "Hide",
"confirm-disconnect-users": "Are you sure you wish to disconnect these users?",
"confirm-disconnect-user": "Are you sure you wish to disconnect this user?",
"enter-new-codirector-password": "Enter a co-director password to use",
"control-room-co-director": "Control Room: Co-Director",
"volume-control": "Volume control for local playback only",
"signal-meter": "Video packet loss indicator of video preview; green is good, red is bad. Flame implies CPU is overloaded. May not reflect the packet loss seen by scenes or other guests.",
"waiting-for-the-stream": "Waiting for the stream. Tip: Adding &cleanoutput to the URL will hide this spinner, or click to retry, which will also hide it.",
"main-director": "Main Director",
"share-a-screen": "Share a screen",
"stop-screen-sharing": "Stop screen sharing",
"you-have-been-transferred": "You've been transferred to a different room",
"you-have-been-activated": "The director has allowed you to see others in the room now",
"you-are-no-longer-a-co-director": "You are no longer a co-director as you were transferred.",
"transferred": "Transferred",
"room-changed": "Your room has changed",
"headphones-tip": "Tip: Use headphones to avoid audio echo issues.",
"camera-tip-c922": "Tip: To achieve 60-fps with a C922 webcam, low-light compensation needs to be turned off, exposure set to auto, and 720p used.",
"camera-tip-camlink": "Tip: A Cam Link may glitch green/purple if accessed elsewhere while already in use.",
"samsung-a-series": "Samsung A-series phones may have issues with Chrome; if so, try Firefox Mobile instead or switch video codecs.",
"screen-permissions-denied": "Permission to capture denied. Ensure your browser has screen record system permissions\n\n1.On your Mac, choose Apple menu > System Preferences, click Security & Privacy , then click Privacy.\n2.Select Screen Recording.\n3.Select the checkbox next to your browser to allow it to record your screen.",
"change-audio-output-device": "Audio could not be captured. Please make sure you have an audio output device available.\n\nSome gaming headsets (ie: Corsair) may need to be set to 2-channel output to work, as surround sound drivers may cause problems.",
"prompt-access-request": " is trying to view your stream. Allow them?",
"confirm-reload-user": "Are you sure you wish to reload this user's browser?",
"webrtc-is-blocked": "⚠ This browser has either blocked WebRTC or does not support it.\n\nThis site will not work without it.\n\nDisable any browser extensions or privacy settings that may be blocking WebRTC, or try a different browser.",
"not-clean-session": "Video effects or canvas rendering failed.\n\nCheck to ensure any remotely hosted images are cross-origin allowed.",
"ios-no-screen-share": "Sorry, but your iOS browser does not support screen-sharing.\n\nPlease see this guide for an alternative method to do so.",
"android-no-screen-share": "Sorry, your mobile browser does not support screen-sharing.\n\nThe Android native app does offer basic support for it though.",
"no-screen-share-supported": "Sorry, your browser does not support screen-sharing.\n\nPlease use the desktop versions of Firefox or Chrome instead.",
"speech-not-suppoted": "⚠ Speech Recognition is not supported by this browser",
"blue-yeti-tip": "Tip: Blue Yeti microphones may experience issues being overly loud. Please see here for a solution or disable auto-gain in VDO.Ninja.",
"site-not-responsive": "
Notice: The system cannot be accessed or is currently slow to respond.
\nIf a routing issue, try adding &proxy to the URL; you can also try https://proxy.vdo.ninja or a VPN if the service is blocked in your country.\n\nIf the main service is down, a backup version is also available here: https://backup.vdo.ninja\n\nContact steve@seguin.email for added help.\n\nThis service requires the use of Websockets over port 443.",
"no-audio-source-detected": "No Audio Source was detected.\n\nIf you were wanting to capture an Application's Audio, please see:\nhttps://docs.vdo.ninja/help/guides-and-how-tos#audio for some guides.",
"viewer-count": "Total outbound p2p connections of this remote stream",
"enter-url-for-widget": "Enter a URL for a page to embed as a sidebar",
"director-password" : "Enter the main director's password",
"vision-disabled": "The Director has disabled your vision temporarily
",
"invalid-remote-code": "Invalid remote control code.\n\nUse the field below to try again with a different passcode.",
"invalid-remote-code-obs": "Invalid remote control code.\n\nThe remote OBS system needs a matching passcode set using &remote.\n\nSee the documentation for help..",
"request-rejected-obs": "The request was rejected.\n\nThe remote OBS system needs a matching passcode set using &remote.\n\nSee the documentation for help.",
"remote-token-rejected": "The remote request failed; the &remote token did not match or the remote user does not allow remote control.",
"remote-control-failed": "The remote control request failed.",
"remote-peer-connected": "Remote peer connected to video stream.\n\nConnection to handshake server being killed on request. This increases security, but the peer will not be able to reconnect automatically on connection failure.\n\nPress OK to start the stream!",
"director-denied": "The main director denied you as a co-director",
"only-main-director": "Only the main director can transfer this guest",
"request-failed": "The request failed; you can't apply this action",
"tokens-did-not-match": "The remote request failed; the remote token did not match or the remote user does not allow remote control.",
"token-not-director": "The request failed; the remote user did not recognize you as the director",
"approved-as-director": "The director approved you as a co-director",
"you-are-a-codirector": "You are a co-director of this room; you have partial director control assigned to you.",
"this-is-you": "This is you, a co-director. You are also a performer.",
"preview-meshcast-disabled": "You can't adjust the preview bitrate for Meshcast-based streams"
};
// function log(msg){ // uncomment to enable logging.
// console.log(msg);
// }
// function warnlog(msg, url=false, lineNumber=false){
// onsole.warn(msg);
// if (lineNumber){
// console.warn(lineNumber);
// }
// }
// function errorlog(msg, url=false, lineNumber=false){
// console.error(msg);
// if (lineNumber){
// console.error(lineNumber);
// }
// }
if (typeof session === 'undefined') { // make sure to init the WebRTC if not exists.
var session = WebRTC.Media;
session.streamID = session.generateStreamID();
errorlog("Serious error: WebRTC session didn't load in time");
}
(function(w) {
w.URLSearchParams = w.URLSearchParams || function(searchString) {
var self = this;
searchString = searchString.replace("??", "?");
self.searchString = searchString;
self.get = function(name) {
var results = new RegExp('[\?&]' + name + '=([^]*)').exec(self.searchString);
if (results == null) {
return null;
} else {
return decodeURI(results[1]) || 0;
}
};
};
})(window);
var urlEdited = window.location.search.replace(/\?\?/g, "?");
urlEdited = urlEdited.replace(/\?/g, "&");
urlEdited = urlEdited.replace(/\&/, "?");
var urlParams = new URLSearchParams(urlEdited);
if (urlParams.has("invite") || urlParams.has("i") || urlParams.has("code")){
session.decodeInvite(urlParams.get("invite") || urlParams.get("i") || urlParams.get("code"));
}
if (session.decrypted){
session.decrypted = session.decrypted + urlEdited.replace("?","&");
session.decrypted = session.decrypted.replace(/\?/g, "&");
session.decrypted = session.decrypted.replace(/\&/, "?");
urlParams = new URLSearchParams(session.decrypted);
//session.decrypted = true;
} else {
if (urlEdited !== window.location.search){
warnlog(window.location.search + " changed to " + urlEdited);
window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString());
}
}
delete urlEdited;
var isIFrame = false;
if ( parent && (window.location !== window.parent.location )) {
isIFrame = true;
}
function mapToAll(targets, callback, parentElement = document) { // js helper
if (!targets) {
return;
}
if (!parentElement) {
return;
}
const target = parentElement.querySelectorAll(targets);
for (let i = 0; i < target.length; i++) {
callback(target[i]);
}
}
function changeParam(url, paramName, paramValue) {
paramName = paramName.replace("?", "");
var qind = url.indexOf('?');
url = url.replace("?", "&");
var params = url.substring(qind + 1).split('&');
var query = '';
var match = false;
for (var i = 0; i < params.length; i++) {
var tokens = params[i].split('=');
var name = tokens[0];
var value = "";
if (tokens.length > 1 && tokens[1] !== '') {
value = tokens[1];
}
if (name == paramName) {
if (match) {
continue;
} // already matched the first time.
match = true;
value = paramValue;
}
if (value !== "") {
value = '=' + value;
}
if (query == '') {
query = "?" + name + value;
} else {
query = query + '&' + name + value;
}
}
return url.substring(0, qind) + query;
}
function saveRoom(ele){
//this.title = "Quick load settings stored locally";
session.sticky = true;
ele.parentNode.removeChild(ele);
setStorage("permission", "yes");
setStorage("settings", encodeURI(window.location.href), 999);
}
function updateURL(param, force = false, cleanUrl = false) {
if (session.decrypted){return;}
param = param.replace("?", "");
var para = param.split('=');
if (cleanUrl) {
if (history.pushState) {
var href = new URL(cleanUrl);
if (para.length == 1) {
href = changeParam(cleanUrl, para[0], "");
} else {
href = changeParam(cleanUrl, para[0], para[1]);
}
log("--" + href.toString());
window.history.pushState({path: href.toString()}, '', href.toString());
}
} else if (!(urlParams.has(para[0]))) { // don't need to replace as it doesn't exist.
if (history.pushState) {
var href = window.location.href;
href = href.replace("??", "?");
var arr = href.split('?');
var newurl;
if (arr.length > 1 && arr[1] !== '') {
newurl = href + '&' + param;
} else {
newurl = href + '?' + param;
}
window.history.pushState({path: newurl.toString()}, '', newurl.toString());
}
} else if (force) {
if (history.pushState) {
var href = new URL(window.location.href);
if (para.length == 1) {
href = changeParam(window.location.href, para[0], "");
} else {
href = changeParam(window.location.href, para[0], para[1]);
}
log("---" + href.toString());
window.history.pushState({path: href.toString()}, '', href.toString());
}
}
if (session.sticky) {
setStorage("settings", encodeURI(window.location.href), 999);
}
urlParams = new URLSearchParams(window.location.search);
}
/* function changeGuestSettings(ele){
var eles = ele.querySelectorAll('[data-param]');
var UUID = ele.dataset.UUID;
var settings = {};
for (var i = 0;i< eles.length; i++){
if (eles[i].tagName.toLowerCase() == "input"){
if (eles[i].checked===true){
settings[eles[i].dataset.param] = true;
} else if (eles[i].checked===false){
settings[eles[i].dataset.param] = false;
} else {
settings[eles[i].dataset.param] = eles[i].value;
}
}
}
warnlog(settings);
if (!settings.changepassword){
delete settings.password;
}
delete settings.changepassword;
if (!settings.changeroom){
// send Migration message
delete settings.roomid;
}
delete settings.roomid;
delete settings.changeroom;
warnlog(UUID);
var msg = {};
msg.changeParams = settings;
session.sendRequest(msg, UUID);
closeModal();
} */
// proper room migration needs to happen; in sync.
// updateMixer after settings changed
// password needs to be special cased
// room shouldn't be sent
function applyNewParams(changeParams){
for (var key in changeParams){
session[key] = changeParams[key];
log(key);
}
log(changeParams);
updateMixer();
}
function submitDebugLog(msg=false){
try {
if (navigator.userAgent){
var _, userAgent = navigator.userAgent;
appendDebugLog({"userAgent": userAgent});
}
if (navigator.platform){
appendDebugLog({"userAgent": navigator.platform});
}
} catch(e){}
window.focus();
var res = confirm(miscTranslations["submit-error-report"]);
if (res){
var request = new XMLHttpRequest();
var recordResults = session.streamID + "_"+parseInt(Date.now());
request.open('POST', "https://reports.vdo.ninja/?name="+recordResults); // php, well, whatever.
if (!session.cleanOutput){
warnUser("Report any details of your bug report to steve@seguin.email, along with the following link: https://reports.vdo.ninja/?name="+recordResults+"", false, false);
}
console.log("Report any details of your bug report to steve@seguin.email, along with the following ID: "+recordResults);
request.send(JSON.stringify(errorReport));
errorReport = [];
if (document.getElementById("reportbutton")){
getById("reportbutton").classList.add("hidden");
}
}
}
function URLFromFiles(files) {
const promises = files.map((file) =>
fetch(file).then((response) => response.text())
);
return Promise.all(promises).then((texts) => {
const text = texts.join("");
const blob = new Blob([text], { type: "application/javascript" });
return URL.createObjectURL(blob);
});
}
function detectCPUSupport(){
let cpuThreads = navigator.hardwareConcurrency;
if (cpuThreads){
return cpuThreads+" threads";
}
return false;
}
function detectGPUSupport() {
try {
const gl = document.createElement('canvas').getContext('webgl');
if (!gl) {
return false;
}
if (!Firefox){
try {
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info'); // chrome
if (debugInfo){
return gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
}
} catch(e){}
}
try {
return gl.getParameter(gl.RENDERER) || false; // firefox
} catch(e){}
} catch(e){}
return false;
}
function isOperaGX(){
return (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/75') >= 0;
}
function isSamsungASeries(){
return navigator.userAgent.includes("; SM-A") || false;
}
function getChromeVersion() {
var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
return raw ? parseInt(raw[2], 10) : false;
}
function safariVersion() {
var ver = 0;
try {
ver = navigator.appVersion.split("Version/");
if (ver.length > 1) {
ver = ver[1].split(" Safari");
}
if (ver.length > 1) {
ver = ver[0].split(".");
}
if (ver.length > 1) {
ver = parseInt(ver[0]);
} else {
ver = 0;
}
} catch (e) {
return 0;
}
return ver;
}
try{
var iOS = !!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform); // used by main.js also
var iPad = (navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform));
var macOS = navigator.userAgent.indexOf('Mac OS X') != -1;
macOS = macOS && !(iOS || iPad);
var Firefox = navigator.userAgent.indexOf("Firefox")>=0;
if (Firefox){
Firefox = parseInt(navigator.userAgent.split("irefox/").pop()) || true;
}
var Android = navigator.userAgent.toLowerCase().indexOf("android") > -1; //&& ua.indexOf("mobile");
var ChromeVersion = getChromeVersion();
var OperaGx = isOperaGX();
var SafariVersion = safariVersion();
var SamsungASeries = isSamsungASeries();
var isVingester = navigator.userAgent.indexOf("Vingester")>=0;
var gpgpuSupport = detectGPUSupport();
log(gpgpuSupport);
var cpuSupport = detectCPUSupport();
log(cpuSupport);
var iPhone12Up = false;
if (iOS && !iPad){
if ((window.devicePixelRatio.toFixed(2)>=3) && (window.screen.height>800) && (window.screen.width!=414)){ // for reference, https://www.ios-resolution.com/
iPhone12Up = true; // iPhone SE is left out.
}
}
} catch(e){errorlog(e);}
if (isVingester){
console.warn("If Vingester isn't able to capture audio, get a fixed version of Vingester from here: https://github.com/steveseguin/vingester/releases/");
}
function isAlphaNumeric(str) {
var code, i, len;
for (i = 0, len = str.length; i < len; i++) {
code = str.charCodeAt(i);
if (!(code > 47 && code < 58) && // numeric (0-9)
!(code > 64 && code < 91) && // upper alpha (A-Z)
!(code > 96 && code < 123)) { // lower alpha (a-z)
return false;
}
}
return true;
}
function convertStringToArrayBufferView(str){
var bytes = new Uint8Array(str.length);
for (var iii = 0; iii < str.length; iii++){
bytes[iii] = str.charCodeAt(iii);
}
return bytes;
}
function toHexString(byteArray){
return Array.prototype.map.call(byteArray, function(byte){
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
}).join('');
}
function toByteArray(hexString){
var result = [];
for (var i = 0; i < hexString.length; i += 2){
result.push(parseInt(hexString.substr(i, 2), 16));
}
return new Uint8Array(result);
}
function playAllVideos(){
if (session.firstPlayTriggered && (session.audioCtx.state == "suspended")){ // added oct 9th 2022
try {
session.audioCtx.resume();
} catch(e){warnlog(e);}
}
for (var i in session.rpcs){
if (session.rpcs[i].whip){continue;}
try{
if (session.rpcs[i].videoElement){
log("I: "+i);
if (session.rpcs[i].videoElement.paused){
setTimeout(function(UUID){
session.rpcs[UUID].videoElement.play().then(_ => {
log("playing 3 ");
if ((session.audioEffects===true) || session.pushLoudness){
log("updateIncomingAudioElement('"+UUID+"')");
updateIncomingAudioElement(UUID);
}
}).catch(errorlog);
},0,i);
} else if ((session.audioEffects===true) || session.pushLoudness){
updateIncomingAudioElement(i);
log("updateIncomingAudioElement('"+i+"')");
}
}
} catch(e){
errorlog(e);
}
}
}
var videoElements = Array.from(document.querySelectorAll("video"));
var audioElements = Array.from(document.querySelectorAll("audio"));
var mediaStreamCounter = 0;
function createMediaStream(){
mediaStreamCounter+=1;
return new MediaStream();
}
function deleteOldMedia(){
log("CHECKING FOR OLD MEDIA");
var i = videoElements.length;
while (i--) {
//if ((videoElements[i].id == "videosource") || (videoElements[i].id == "previewWebcam")){continue;} // exclude this one, for safety reasons. (Also, iOS safari blanks the video if streams are detached and moved between video elements)
if (videoElements[i].isConnected === false){
if ((videoElements[i].srcObject==null) || (videoElements[i].srcObject && videoElements[i].srcObject.active === false)){
if (videoElements[i].dataset && videoElements[i].dataset.UUID){
if (videoElements[i].dataset.UUID in session.rpcs){continue;} // still active, so lets not delete it.
}
videoElements[i].pause();
videoElements[i].removeAttribute("id");
videoElements[i].removeAttribute('src'); // empty source
videoElements[i].load();
videoElements[i].remove();
videoElements[i] = null;
videoElements.splice(i, 1);
}
}
}
i = audioElements.length;
while (i--) {
if (audioElements[i].isConnected === false){
if ((audioElements[i].srcObject==null) || (audioElements[i].srcObject && audioElements[i].srcObject.active === false)){
if (audioElements[i].dataset && audioElements[i].dataset.UUID){
if (audioElements[i].dataset.UUID in session.rpcs){continue;} // still active, so lets not delete it.
}
audioElements[i].pause();
audioElements[i].id = null;
audioElements[i].removeAttribute('src'); // empty source
audioElements[i].load();
audioElements[i].remove();
audioElements[i] = null;
audioElements.splice(i, 1);
}
}
}
}
function createAudioElement(){
try{
deleteOldMedia();
} catch(e){errorlog(e);}
var a = document.createElement("audio");
audioElements.push(a);
return a;
}
function compare_deltas( a, b ) {
var aa = a.delta || 0;
var bb = b.delta || 0;
if ( aa > bb ){
return 1;
}
if ( aa < bb ){
return -1;
}
return 0;
}
async function fetchWithTimeout(URL, timeout=8000){ // ref: https://dmitripavlutin.com/timeout-fetch-request/
try {
const controller = new AbortController();
const timeout_id = setTimeout(() => controller.abort(), timeout);
const response = await fetch(URL, {...{timeout:timeout}, signal: controller.signal});
clearTimeout(timeout_id);
return response;
} catch(e){
errorlog(e);
return await fetch(URL); // iOS 11.x/12.0
}
}
function createVideoElement(){
try{
deleteOldMedia();
} catch(e){errorlog(e);}
var v = document.createElement("video");
videoElements.push(v);
if (typeof session.volume == "number"){
v.volume = session.volume; // setting default volume
log("setting volume to manual");
}
return v;
}
function getTimezone(){
if (session.tz!==false){
return session.tz;
}
const stdTimezoneOffset = () => {
var jan = new Date(0, 1);
var jul = new Date(6, 1);
return Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
}
var today = new Date();
const isDstObserved = (today) => {
return today.getTimezoneOffset() < stdTimezoneOffset();
}
if (isDstObserved(today)) {
return today.getTimezoneOffset()+60;
} else {
return today.getTimezoneOffset();
}
}
function promptUser(eleId, UUID=null){
if (session.beepToNotify){
playtone();
}
if (document.getElementById("modalBackdrop")){
getById("promptModal").innerHTML = ''; // Delete modal
getById("promptModal").remove();
getById("modalBackdrop").innerHTML = ''; // Delete modal
getById("modalBackdrop").remove();
}
zindex = 30 + document.querySelectorAll('#promptModal').length;
modalTemplate =
`
×
`;
document.body.insertAdjacentHTML("beforeend", modalTemplate); // Insert modal at body end
getById("promptModalMessage").innerHTML = getById(eleId).innerHTML;
if (UUID){
getById("promptModalMessage").dataset.UUID = UUID;
}
document.getElementById("modalBackdrop").addEventListener("click", closeModal);
getById("promptModal").addEventListener("click", function(e) {
e.stopPropagation();
return false;
});
}
async function delay(ms) {
return await new Promise((resolve, reject) => {
setTimeout(resolve, ms);
});
}
var Prompts = {};
async function promptAlt(inputText, block=false, asterix=false, value=false, time=false){
var result = null;
if (session.beepToNotify){
playtone();
}
await new Promise((resolve, reject) => {
var promptID = "pid_"+Math.random().toString(36).substr(2, 9);
Prompts[promptID] = {};
Prompts[promptID].resolve = resolve;
Prompts[promptID].reject = reject;
var zindex = 30 + document.querySelectorAll('.promptModal').length;
if (block){
var backdropClass = "opaqueBackdrop";
} else {
var backdropClass = "modalBackdrop";
}
inputText = ""+inputText.replace("\n"," ")+"";
inputText = inputText.replace(/\n/g," ");
var type = "text";
if (asterix){
type = "password";
}
if (time){
modalTemplate =
`
×${inputText}
minutes,
seconds
Count up from zero instead
`;
} else {
modalTemplate =
`
×${inputText}
`;
}
document.body.insertAdjacentHTML("beforeend", modalTemplate); // Insert modal at body end
document.getElementById("input_"+promptID).focus();
if (value!==false){
if (time){
document.getElementById("input_"+promptID).value = parseInt(value/60);
document.getElementById("input_"+promptID+"_sec").value = parseInt(value)%60;
} else {
document.getElementById("input_"+promptID).value = value;
}
}
if (!time){
document.getElementById("input_"+promptID).addEventListener("keyup", function(event) {
if (event.key === "Enter") {
var pid = event.target.dataset.pid;
result = document.getElementById("input_"+pid).value;
document.getElementById("modal_"+pid).remove();
document.getElementById("modalBackdrop_"+pid).remove();
Prompts[pid].resolve();
}
});
} else {
document.getElementById("input_"+promptID).addEventListener("keyup", function(event) {
if (event.key === "Enter") {
document.getElementById("input_"+promptID+"_sec").focus();
}
});
document.getElementById("input_"+promptID+"_sec").addEventListener("keyup", function(event) {
if (event.key === "Enter") {
document.getElementById("submit_"+promptID).focus();
}
});
document.getElementById("countup_"+promptID).addEventListener("click", function(event) {
if (document.getElementById("countup_"+promptID).checked){
document.getElementById("input_"+promptID).disabled = true
document.getElementById("input_"+promptID+"_sec").disabled = true
} else {
document.getElementById("input_"+promptID).disabled = false
document.getElementById("input_"+promptID+"_sec").disabled = false
delete document.getElementById("input_"+promptID).disabled;
delete document.getElementById("input_"+promptID+"_sec").disabled;
}
});
}
document.getElementById("submit_"+promptID).addEventListener("click", function(event){
var pid = event.target.dataset.pid;
if (time){
result = parseInt(document.getElementById("input_"+pid+"_sec").value) + parseInt(document.getElementById("input_"+pid).value)*60;
if (document.getElementById("countup_"+promptID).checked){
result = 0;
}
} else {
result = document.getElementById("input_"+pid).value;
}
document.getElementById("modal_"+pid).remove();
document.getElementById("modalBackdrop_"+pid).remove();
Prompts[pid].resolve();
});
document.getElementById("cancel_"+promptID).addEventListener("click", function(event){
var pid = event.target.dataset.pid;
document.getElementById("modal_"+pid).remove();
document.getElementById("modalBackdrop_"+pid).remove();
Prompts[pid].resolve();
});
document.getElementById("close_"+promptID).addEventListener("click", function(event){
var pid = event.target.dataset.pid;
document.getElementById("modal_"+pid).remove();
document.getElementById("modalBackdrop_"+pid).remove();
Prompts[pid].resolve();
});
getById("modal_"+promptID).addEventListener("click", function(e) {
e.stopPropagation();
return false;
});
return;
});
return result;
}
async function promptTransfer(value=null, bcmode = null, updateurl = null, queueMode = null){
var result = {roomid:null};
if (session.beepToNotify){
playtone();
}
await new Promise((resolve, reject) => {
var promptID = "pid_"+Math.random().toString(36).substr(2, 9);
Prompts[promptID] = {};
Prompts[promptID].resolve = resolve;
Prompts[promptID].reject = reject;
var zindex = 30 + document.querySelectorAll('.promptModal').length;
var backdropClass = "modalBackdrop";
var inputText = ""+(miscTranslations["transfer-guest-to-room"].replace("\n"," "))+"";
inputText = inputText.replace(/\n/g," ");
modalTemplate =
`
×${inputText} Allow the guest to rejoin the transfer room on their own Guest will arrive in the new room in broadcast mode Guest will arrive in the new room in queue mode
`;
document.body.insertAdjacentHTML("beforeend", modalTemplate); // Insert modal at body end
document.getElementById("input_"+promptID).focus();
if (value!==null){
document.getElementById("input_"+promptID).value = value;
}
if (bcmode!==null){
document.getElementById("broadcast_"+promptID).checked = bcmode;
}
if (queueMode!==null){
document.getElementById("queued_"+promptID).checked = queueMode;
}
if (updateurl!==null){
document.getElementById("private_"+promptID).checked = updateurl;
}
document.getElementById("input_"+promptID).addEventListener("keyup", function(event) {
if (event.key === "Enter") {
var pid = event.target.dataset.pid;
var room = document.getElementById("input_"+pid).value;
var updateurl = document.getElementById("private_"+pid).checked;
var broadcast = document.getElementById("broadcast_"+pid).checked;
var queue = document.getElementById("queued_"+pid).checked;
document.getElementById("modal_"+pid).remove();
document.getElementById("modalBackdrop_"+pid).remove();
Prompts[pid].resolve();
result = {roomid:room, updateurl:updateurl, broadcast:broadcast, queue:queue};
}
});
document.getElementById("submit_"+promptID).addEventListener("click", function(event){
var pid = event.target.dataset.pid;
var room = document.getElementById("input_"+pid).value;
var updateurl = document.getElementById("private_"+pid).checked;
var broadcast = document.getElementById("broadcast_"+pid).checked;
var queue = document.getElementById("queued_"+pid).checked;
document.getElementById("modal_"+pid).remove();
document.getElementById("modalBackdrop_"+pid).remove();
Prompts[pid].resolve();
result = {roomid:room, updateurl:updateurl, broadcast:broadcast, queue:queue};
});
document.getElementById("cancel_"+promptID).addEventListener("click", function(event){
var pid = event.target.dataset.pid;
document.getElementById("modal_"+pid).remove();
document.getElementById("modalBackdrop_"+pid).remove();
Prompts[pid].resolve();
});
document.getElementById("close_"+promptID).addEventListener("click", function(event){
var pid = event.target.dataset.pid;
document.getElementById("modal_"+pid).remove();
document.getElementById("modalBackdrop_"+pid).remove();
Prompts[pid].resolve();
});
getById("modal_"+promptID).addEventListener("click", function(e) {
e.stopPropagation();
return false;
});
return;
});
return result;
}
function youveBeenTransferred(){
getChatMessage( miscTranslations["you-have-been-transferred"], label = false, director = false, overlay = true); // "you-have-been-transferred"
getById("head2").innerHTML = ''+miscTranslations["room-changed"]+''; //
if (session.director){
getById("head4").innerHTML = miscTranslations["you-are-no-longer-a-co-director"]; //"You are no longer a co-director as you were transferred."; //
}
if (session.label){
document.title = session.label + " - " + miscTranslations["transferred"];
} else {
document.title = miscTranslations["transferred"];
}
hideHomeCheck();
}
function youveBeenActivated(){
getChatMessage( miscTranslations["you-have-been-activated"], label = false, director = false, overlay = true); // "you-have-been-transferred"
hideHomeCheck();
}
async function confirmAlt(inputText, block=false){
var result = null;
if (session.beepToNotify){
playtone();
}
await new Promise((resolve, reject) => {
var promptID = "pid_"+Math.random().toString(36).substr(2, 9);
Prompts[promptID] = {};
Prompts[promptID].resolve = resolve;
Prompts[promptID].reject = reject;
var zindex = 30 + document.querySelectorAll('.promptModal').length;
if (block){
var backdropClass = "opaqueBackdrop";
} else {
var backdropClass = "modalBackdrop";
}
inputText = ""+inputText.replace("\n"," ")+"";
inputText = inputText.replace(/\n/g," ");
modalTemplate =
`
×${inputText}
`;
document.body.insertAdjacentHTML("beforeend", modalTemplate); // Insert modal at body end
document.getElementById("submit_"+promptID).focus();
document.getElementById("submit_"+promptID).addEventListener("click", function(event){
var pid = event.target.dataset.pid;
result = true;
document.getElementById("modalBackdrop_"+pid).remove();
document.getElementById("modal_"+pid).remove();
Prompts[pid].resolve();
});
document.getElementById("cancel_"+promptID).addEventListener("click", function(event){
var pid = event.target.dataset.pid;
document.getElementById("modalBackdrop_"+pid).remove();
document.getElementById("modal_"+pid).remove();
Prompts[pid].resolve();
});
document.getElementById("close_"+promptID).addEventListener("click", function(event){
var pid = event.target.dataset.pid;
document.getElementById("modalBackdrop_"+pid).remove();
document.getElementById("modal_"+pid).remove();
Prompts[pid].resolve();
});
getById("modal_"+promptID).addEventListener("click", function(e) {
e.stopPropagation();
return false;
});
return;
});
return result;
}
var modalTimeout=null;
function warnUser(message, timeout=false, sanitize=true){
// Allows for multiple alerts to stack better.
// Every modal and backdrop has an increasing z-index
// to block the previous modal
if (!message){return;}
if (document.getElementById("modalBackdrop")){
getById("alertModal").innerHTML = ''; // Delete modal
getById("alertModal").remove();
getById("modalBackdrop").innerHTML = ''; // Delete modal
getById("modalBackdrop").remove();
}
zindex = 31 + document.querySelectorAll('.alertModal').length;
try{
if (sanitize){
message = sanitizeChat(message,2000);
}
message = message.replace(/\n/g," ");
} catch(e){
errorlog(message);
}
modalTemplate =
`
×${message}
`;
document.body.insertAdjacentHTML("beforeend", modalTemplate); // Insert modal at body end
document.getElementById("modalBackdrop").addEventListener("click", closeModal);
clearTimeout(modalTimeout);
if (timeout){
modalTimeout = setTimeout(closeModal, timeout);
}
getById("alertModal").addEventListener("click", function(e) {
e.stopPropagation();
return false;
});
}
function closeModal(){
clearTimeout(modalTimeout);
getById("modalBackdrop").innerHTML = ''; // Delete modal
getById("modalBackdrop").remove();
getById("alertModal").innerHTML = ''; // Delete modal
getById("alertModal").remove();
getById("promptModal").innerHTML = ''; // Delete modal
getById("promptModal").remove();
}
var sanitizeStreamID = function(streamID) {
streamID = streamID.trim();
if (streamID.length < 1) {
streamID = session.generateStreamID(8);
if (!(session.cleanOutput)) {
warnUser(miscTranslations["no-streamID-provided"] + streamID, false, false);
}
}
var streamID_sanitized = streamID.replace(/[\W]+/g, "_");
if (streamID !== streamID_sanitized) {
if (!(session.cleanOutput)) {
warnUser(miscTranslations["alphanumeric-only"], false, false);
}
}
if (streamID_sanitized.length > 44) {
streamID_sanitized = streamID_sanitized.substring(0, 50);
if (!(session.cleanOutput)) {
warnUser(miscTranslations["stream-id-too-long"], false, false);
}
}
return streamID_sanitized;
};
var checkStrength = function(string) {
var matcher = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{7,30}$/;
if (string.match(matcher)) {
return true;
} else if (string.length > 20) {
return true;
} else {
return false;
}
};
var checkStrengthRoom = function() {
var result1 = checkStrength(getById('videoname1').value);
var result2 = getById('passwordRoom').value.length;
var target = getById('securityLevelRoom');
target.style.display = "block";
if (result1) {
if (result2) {
target.innerHTML = ""+miscTranslations["share-with-trusted"]+"";
} else {
target.innerHTML = ""+miscTranslations["pass-recommended"]+"";
}
} else {
target.innerHTML = ""+miscTranslations["insecure-room-name"]+" "+miscTranslations["allowed-chars"]+": A-Z, a-z, 0-9, _";
}
};
var emojiShortCodes ={":joy:":"😂",":heart:":"❤️",":heart_eyes:":"😍",":sob:":"😭",":blush:":"😊",":unamused:":"😒",":two_hearts:":"💕",":weary:":"😩",":ok_hand:":"👌",":pensive:":"😔",":smirk:":"😏",":grin:":"😁",":wink:":"😉",":thumbsup:":"👍",":pray:":"🙏",":relieved:":"😌",":notes:":"🎶",":flushed:":"😳",":raised_hands:":"🙌",":see_no_evil:":"🙈",":cry:":"😢",":sunglasses:":"😎",":v:":"✌️",":eyes:":"👀",":sweat_smile:":"😅",":sparkles:":"✨",":sleeping:":"😴",":smile:":"😄",":purple_heart:":"💜",":broken_heart:":"💔",":blue_heart:":"💙",":confused:":"😕",":disappointed:":"😞",":yum:":"😋",":neutral_face:":"😐",":sleepy:":"😪",":clap:":"👏",":cupid:":"💘",":heartpulse:":"💗",":kiss:":"💋",":point_right:":"👉",":scream:":"😱",":fire:":"🔥",":rage:":"😡",":smiley:":"😃",":tada:":"🎉",":tired_face:":"😫",":camera:":"📷",":rose:":"🌹",":muscle:":"💪",":skull:":"💀",":sunny:":"☀️",":yellow_heart:":"💛",":triumph:":"😤",":laughing:":"😆",":sweat:":"😓",":point_left:":"👈",":grinning:":"😀",":mask:":"😷",":green_heart:":"💚",":wave:":"👋",":persevere:":"😣",":heartbeat:":"💓",":crown:":"👑",":innocent:":"😇",":headphones:":"🎧",":confounded:":"😖",":angry:":"😠",":grimacing:":"😬",":star2:":"🌟",":gun:":"🔫",":raising_hand:":"🙋",":thumbsdown:":"👎",":dancer:":"💃",":musical_note:":"🎵",":no_mouth:":"😶",":dizzy:":"💫",":fist:":"✊",":point_down:":"👇",":no_good:":"🙅",":boom:":"💥",":tongue:":"👅",":poop:":"💩",":cold_sweat:":"😰",":gem:":"💎",":ok_woman:":"🙆",":pizza:":"🍕",":joy_cat:":"😹",":leaves:":"🍃",":sweat_drops:":"💦",":penguin:":"🐧",":zzz:":"💤",":walking:":"🚶",":airplane:":"✈️",":balloon:":"🎈",":star:":"⭐",":ribbon:":"🎀",":worried:":"😟",":underage:":"🔞",":fearful:":"😨",":hibiscus:":"🌺",":microphone:":"🎤",":open_hands:":"👐",":ghost:":"👻",":palm_tree:":"🌴",":nail_care:":"💅",":alien:":"👽",":bow:":"🙇",":cloud:":"☁",":soccer:":"⚽",":angel:":"👼",":dancers:":"👯",":snowflake:":"❄️",":point_up:":"☝️",":rainbow:":"🌈",":gift_heart:":"💝",":gift:":"🎁",":beers:":"🍻",":anguished:":"😧",":earth_africa:":"🌍",":movie_camera:":"🎥",":anchor:":"⚓",":zap:":"⚡",":runner:":"🏃",":sunflower:":"🌻",":bouquet:":"💐",":dog:":"🐶",":moneybag:":"💰",":herb:":"🌿",":couple:":"👫",":fallen_leaf:":"🍂",":tulip:":"🌷",":birthday:":"🎂",":cat:":"🐱",":coffee:":"☕",":dizzy_face:":"😵",":point_up_2:":"👆",":open_mouth:":"😮",":hushed:":"😯",":basketball:":"🏀",":ring:":"💍",":astonished:":"😲",":hear_no_evil:":"🙉",":dash:":"💨",":cactus:":"🌵",":hotsprings:":"♨️",":telephone:":"☎️",":maple_leaf:":"🍁",":princess:":"👸",":massage:":"💆",":love_letter:":"💌",":trophy:":"🏆",":blossom:":"🌼",":lips:":"👄",":fries:":"🍟",":doughnut:":"🍩",":frowning:":"😦",":ocean:":"🌊",":bomb:":"💣",":cyclone:":"🌀",":rocket:":"🚀",":umbrella:":"☔",":couplekiss:":"💏",":lollipop:":"🍭",":clapper:":"🎬",":pig:":"🐷",":smiling_imp:":"😈",":imp:":"👿",":bee:":"🐝",":kissing_cat:":"😽",":anger:":"💢",":santa:":"🎅",":earth_asia:":"🌏",":football:":"🏈",":guitar:":"🎸",":panda_face:":"🐼",":strawberry:":"🍓",":smirk_cat:":"😼",":banana:":"🍌",":watermelon:":"🍉",":snowman:":"⛄",":smile_cat:":"😸",":eggplant:":"🍆",":crystal_ball:":"🔮",":calling:":"📲",":iphone:":"📱",":partly_sunny:":"⛅",":warning:":"⚠️",":scream_cat:":"🙀",":baby:":"👶",":feet:":"🐾",":footprints:":"👣",":beer:":"🍺",":wine_glass:":"🍷",":video_camera:":"📹",":rabbit:":"🐰",":smoking:":"🚬",":peach:":"🍑",":snake:":"🐍",":turtle:":"🐢",":cherries:":"🍒",":kissing:":"😗",":frog:":"🐸",":milky_way:":"🌌",":closed_book:":"📕",":candy:":"🍬",":hamburger:":"🍔",":bear:":"🐻",":tiger:":"🐯",":icecream:":"🍦",":pineapple:":"🍍",":ear_of_rice:":"🌾",":syringe:":"💉",":tv:":"📺",":pill:":"💊",":octopus:":"🐙",":grapes:":"🍇",":smiley_cat:":"😺",":cd:":"💿",":cocktail:":"🍸",":cake:":"🍰",":video_game:":"🎮",":lipstick:":"💄",":whale:":"🐳",":cookie:":"🍪",":dolphin:":"🐬",":loud_sound:":"🔊",":man:":"👨",":monkey:":"🐒",":books:":"📚",":guardsman:":"💂",":loudspeaker:":"📢",":scissors:":"✂️",":girl:":"👧",":mortar_board:":"🎓",":baseball:":"⚾️",":woman:":"👩",":fireworks:":"🎆",":stars:":"🌠",":mushroom:":"🍄",":pouting_cat:":"😾",":left_luggage:":"🛅",":high_heel:":"👠",":dart:":"🎯",":swimmer:":"🏊",":key:":"🔑",":bikini:":"👙",":family:":"👪",":pencil2:":"✏",":elephant:":"🐘",":droplet:":"💧",":seedling:":"🌱",":apple:":"🍎",":dollar:":"💵",":book:":"📖",":haircut:":"💇",":computer:":"💻",":bulb:":"💡",":boy:":"👦",":tangerine:":"🍊",":sunrise:":"🌅",":poultry_leg:":"🍗",":shaved_ice:":"🍧",":bird:":"🐦",":eyeglasses:":"👓",":goat:":"🐐",":older_woman:":"👵",":new_moon:":"🌑",":customs:":"🛃",":house:":"🏠",":full_moon:":"🌕",":lemon:":"🍋",":baby_bottle:":"🍼",":spaghetti:":"🍝",":wind_chime:":"🎐",":fish_cake:":"🍥",":nose:":"👃",":pig_nose:":"🐽",":fish:":"🐟",":koala:":"🐨",":ear:":"👂",":shower:":"🚿",":bug:":"🐛",":ramen:":"🍜",":tophat:":"🎩",":fuelpump:":"⛽",":horse:":"🐴",":watch:":"⌚",":monkey_face:":"🐵",":baby_symbol:":"🚼",":sparkler:":"🎇",":corn:":"🌽",":tennis:":"🎾",":battery:":"🔋",":wolf:":"🐺",":moyai:":"🗿",":cow:":"🐮",":mega:":"📣",":older_man:":"👴",":dress:":"👗",":link:":"🔗",":chicken:":"🐔",":whale2:":"🐋",":bento:":"🍱",":pushpin:":"📌",":dragon:":"🐉",":hamster:":"🐹",":golf:":"⛳",":surfer:":"🏄",":mouse:":"🐭",":blue_car:":"🚙",":bread:":"🍞",":cop:":"👮",":tea:":"🍵",":bike:":"🚲",":rice:":"🍚",":radio:":"📻",":baby_chick:":"🐤",":sheep:":"🐑",":lock:":"🔒",":green_apple:":"🍏",":racehorse:":"🐎",":fried_shrimp:":"🍤",":volcano:":"🌋",":rooster:":"🐓",":inbox_tray:":"📥",":wedding:":"💒",":sushi:":"🍣",":ice_cream:":"🍨",":tomato:":"🍅",":rabbit2:":"🐇",":beetle:":"🐞",":bath:":"🛀",":no_entry:":"⛔",":crocodile:":"🐊",":dog2:":"🐕",":cat2:":"🐈",":hammer:":"🔨",":meat_on_bone:":"🍖",":shell:":"🐚",":poodle:":"🐩",":stew:":"🍲",":jeans:":"👖",":honey_pot:":"🍯",":unlock:":"🔓",":black_nib:":"✒",":snowboarder:":"🏂",":white_flower:":"💮",":necktie:":"👔",":womens:":"🚺",":ant:":"🐜",":city_sunset:":"🌇",":dragon_face:":"🐲",":snail:":"🐌",":dvd:":"📀",":shirt:":"👕",":game_die:":"🎲",":dolls:":"🎎",":8ball:":"🎱",":bus:":"🚌",":custard:":"🍮",":camel:":"🐫",":curry:":"🍛",":hospital:":"🏥",":bell:":"🔔",":pear:":"🍐",":door:":"🚪",":saxophone:":"🎷",":church:":"⛪",":bicyclist:":"🚴",":dango:":"🍡",":office:":"🏢",":rowboat:":"🚣",":womans_hat:":"👒",":mans_shoe:":"👞",":love_hotel:":"🏩",":mount_fuji:":"🗻",":handbag:":"👜",":hourglass:":"⌛",":trumpet:":"🎺",":school:":"🏫",":cow2:":"🐄",":toilet:":"🚽",":pig2:":"🐖",":violin:":"🎻",":credit_card:":"💳",":ferris_wheel:":"🎡",":bowling:":"🎳",":barber:":"💈",":purse:":"👛",":rat:":"🐀",":date:":"📅",":ram:":"🐏",":tokyo_tower:":"🗼",":kimono:":"👘",":ship:":"🚢",":mag_right:":"🔎",":mag:":"🔍",":fire_engine:":"🚒",":police_car:":"🚓",":black_joker:":"🃏",":package:":"📦",":calendar:":"📆",":horse_racing:":"🏇",":tiger2:":"🐅",":boot:":"👢",":ambulance:":"🚑",":boar:":"🐗",":pound:":"💷",":ox:":"🐂",":rice_ball:":"🍙",":sandal:":"👡",":tent:":"⛺",":seat:":"💺",":taxi:":"🚕",":briefcase:":"💼",":newspaper:":"📰",":circus_tent:":"🎪",":mens:":"🚹",":flashlight:":"🔦",":foggy:":"🌁",":bamboo:":"🎍",":ticket:":"🎫",":helicopter:":"🚁",":minidisc:":"💽",":oncoming_bus:":"🚍",":melon:":"🍈",":notebook:":"📓",":no_bell:":"🔕",":oden:":"🍢",":flags:":"🎏",":blowfish:":"🐡",":sweet_potato:":"🍠",":ski:":"🎿",":construction:":"🚧",":satellite:":"📡",":euro:":"💶",":ledger:":"📒",":leopard:":"🐆",":truck:":"🚚",":sake:":"🍶",":railway_car:":"🚃",":speedboat:":"🚤",":vhs:":"📼",":yen:":"💴",":mute:":"🔇",":wheelchair:":"♿",":paperclip:":"📎",":atm:":"🏧",":telescope:":"🔭",":rice_scene:":"🎑",":blue_book:":"📘",":postbox:":"📮",":e-mail:":"📧",":mouse2:":"🐁",":nut_and_bolt:":"🔩",":hotel:":"🏨",":wc:":"🚾",":green_book:":"📗",":tractor:":"🚜",":fountain:":"⛲",":metro:":"🚇",":clipboard:":"📋",":no_smoking:":"🚭",":slot_machine:":"🎰",":bathtub:":"🛁",":scroll:":"📜",":station:":"🚉",":rice_cracker:":"🍘",":bank:":"🏦",":wrench:":"🔧",":bar_chart:":"📊",":minibus:":"🚐",":tram:":"🚊",":microscope:":"🔬",":bookmark:":"🔖",":pouch:":"👝",":fax:":"📠",":sound:":"🔉",":chart:":"💹",":floppy_disk:":"💾",":post_office:":"🏣",":speaker:":"🔈",":japan:":"🗾",":mahjong:":"🀄",":orange_book:":"📙",":restroom:":"🚻",":train:":"🚋",":trolleybus:":"🚎",":postal_horn:":"📯",":factory:":"🏭",":train2:":"🚆",":pager:":"📟",":outbox_tray:":"📤",":mailbox:":"📫",":light_rail:":"🚈",":busstop:":"🚏",":file_folder:":"📁",":card_index:":"📇",":monorail:":"🚝",":no_bicycles:":"🚳",":hugging:":"🤗",":thinking:":"🤔",":nerd:":"🤓",":zipper_mouth:":"🤐",":rolling_eyes:":"🙄",":upside_down:":"🙃",":slight_smile:":"🙂",":writing_hand:":"✍",":eye:":"👁",":man_in_suit:":"🕴",":golfer:":"🏌",":golfer_woman:":"🏌♀",":anger_right:":"🗯",":coffin:":"⚰",":gear:":"⚙",":alembic:":"⚗",":scales:":"⚖",":keyboard:":"⌨",":shield:":"🛡",":bed:":"🛏",":ballot_box:":"🗳",":compression:":"🗜",":wastebasket:":"🗑",":file_cabinet:":"🗄",":trackball:":"🖲",":printer:":"🖨",":joystick:":"🕹",":hole:":"🕳",":candle:":"🕯",":prayer_beads:":"📿",":amphora:":"🏺",":label:":"🏷",":film_frames:":"🎞",":level_slider:":"🎚",":thermometer:":"🌡",":motorway:":"🛣",":synagogue:":"🕍",":mosque:":"🕌",":kaaba:":"🕋",":stadium:":"🏟",":desert:":"🏜",":cityscape:":"🏙",":camping:":"🏕",":rosette:":"🏵",":volleyball:":"🏐",":medal:":"🏅",":popcorn:":"🍿",":champagne:":"🍾",":hot_pepper:":"🌶",":burrito:":"🌯",":taco:":"🌮",":hotdog:":"🌭",":shamrock:":"☘",":comet:":"☄",":turkey:":"🦃",":scorpion:":"🦂",":lion_face:":"🦁",":crab:":"🦀",":spider_web:":"🕸",":spider:":"🕷",":chipmunk:":"🐿",":fog:":"🌫",":chains:":"⛓",":pick:":"⛏",":stopwatch:":"⏱",":ferry:":"⛴",":mountain:":"⛰",":ice_skate:":"⛸",":skier:":"⛷",":sad:":"😥",":egg:":"🥚",":drum:":"🥁"};
function convertShortcodes(string){
if (string.split(":").length>2){
for (var i in emojiShortCodes) {
if (string.includes(i)) {
string = string.replaceAll(i, emojiShortCodes[i]);
}
}
}
return string;
}
var sanitizeChat = function(string, maxlength=500) {
var temp = document.createElement('div');
temp.innerText = string;
temp.innerText = temp.innerHTML;
temp = temp.textContent || temp.innerText || "";
temp = temp.substring(0, Math.min(temp.length, maxlength));
return temp.trim();
};
var sanitizeString = function(str) {
str = str.replace(/[^a-z0-9áéíóúñü \.,_-]/gim, "");
return str.trim();
};
var sanitizeLabel = function(string) {
let temp = document.createElement("div");
temp.innerText = string;
temp.innerText = temp.innerHTML;
temp = temp.textContent || temp.innerText || "";
temp = temp.substring(0, Math.min(temp.length, 100));
return temp.trim();
};
var sanitizeRoomName = function(roomid) {
roomid = roomid.trim();
if (roomid === "") {
return roomid;
} else if (roomid === false) {
return roomid;
}
var sanitized = roomid.replace(/[\W]+/g, "_");
if (roomid.replace(/ /g, "_") !== sanitized) {
if (!(session.cleanOutput)) {
warnUser("Info: Only AlphaNumeric characters should be used for the room name.\n\nThe offending characters have been replaced by an underscore");
}
}
if (sanitized.length > 30) {
sanitized = sanitized.substring(0, 30);
if (!(session.cleanOutput)) {
warnUser("The Room name should be less than 31 alPhaNuMeric characters long.\n\nWe will trim it to length.");
}
}
return sanitized;
};
var sanitizePassword = function(passwrd) {
if (passwrd === "") {
return passwrd;
} else if (passwrd === false) {
return passwrd;
} else if (passwrd === null) {
return passwrd;
}
passwrd = passwrd.trim();
if (passwrd.length < 1) {
if (!(session.cleanOutput)) {
warnUser("The password provided was blank.");
}
}
var sanitized = encodeURIComponent(passwrd);//.replace(/[\W]+/g, "_");
//if (sanitized !== passwrd) {
// if (!(session.cleanOutput)) {
// warnUser("Info: Only AlphaNumeric characters should be used in the password.\n\nThe offending characters have been replaced by an underscore");
// }
//}
return sanitized;
};
function checkConnection() {
if (session.ws === null) {
return;
}
if (!session.cleanOutput){
if (document.getElementById("qos")) { // true or false; null might cause problems?
getById("logoname").style.display = "unset";
if ((session.ws) && (session.ws.readyState === WebSocket.OPEN)) {
getById("qos").style.color = "white";
} else {
getById("qos").style.color = "red";
}
}
}
}
function combinedLayout(layout){
var combined = {};
for (var i=0;i{
if (!layout[i]){return;}
if (i===""){
layout[i].forEach(j=>{
if (!j){return;}
var streamID = null;
if ("slot" in j){
try {
streamID = session.currentSlots[parseInt( j.slot)+1]; // slot 1 is index of 0, but slot 0 is considered NULL; I need to stream line this a bit
} catch(e){
errorlog(e);
streamID = null;
}
}
if (!streamID){
if (combined[""]){
combined[""].push(j);
} else {
combined[""] = j;
}
} else {
combined[streamID] = j;
}
});
} else {
var streamID = null;
if ("slot" in layout[i]){
try {
streamID = session.currentSlots[parseInt(layout[i].slot)+1]; // slot 1 is index of 0, but slot 0 is considered NULL; I need to stream line this a bit
} catch(e){
errorlog(e);
streamID = null;
}
}
if (!streamID){
if (combined[""]){
combined[""].push(layout[i]);
} else {
combined[""] = [layout[i]];
}
} else {
combined[streamID] = layout[i];
}
}
});
return combined;
}
session.obsSceneSync = function(){
if (session.layouts && session.obsSceneTriggers && session.obsState && session.obsState.details && session.obsState.details.currentScene.name && session.obsSceneTriggers.includes(session.obsState.details.currentScene.name)){
var idx = session.obsSceneTriggers.indexOf(session.obsState.details.currentScene.name);
if (idx>=0){
if (session.layouts[idx]){
var layout = combinedLayout(session.layouts[idx]);
if (layout){
session.layout = layout;
updateMixer();
}
}
}
return true
}
return false;
}
session.sceneSync = function(UUID){
if (!session.rpcs[UUID]){return;}
else if (!session.rpcs[UUID].videoElement){return;} // i'll want to consider other things, such as canvas at some point.
var msg = {};
msg.sceneDisplay = (session.rpcs[UUID].videoElement.style.display!="none");
msg.sceneMute = session.rpcs[UUID].mutedState;
if (session.optimize!==false){ // if not visible in the scene anymore, lets lets optimize. This is outside the scope of OBS
var bandwidth = parseInt(session.rpcs[UUID].targetBandwidth); // wtf is goign on here?
if (msg.sceneDisplay===false){
if ((bandwidth > session.optimize) || (bandwidth<0)){ // limit to optimized bitrate
bandwidth = session.optimize;
}
}
if (session.rpcs[UUID].bandwidth !== bandwidth){ // bandwidth already set correctly. don't resend.
msg.bitrate = bandwidth;
if (session.sendRequest(msg, UUID)){
session.rpcs[UUID].bandwidth=bandwidth; // this is letting the system know what the actual bandwidth is, even if it isn't the real target.
} else {
errorlog("Unable to set update OBS Visibility");
}
} else {
session.sendRequest(msg, UUID);
}
} else {
session.sendRequest(msg, UUID);
}
}
session.obsStateSync = function(data2send=false, uid=false){
if (session.disableOBS){return;}
if (!window.obsstudio){return;} // this isn't OBS
// they can disable remote control via OBS brower source drop-down itself.
log(data2send);
var needOptimize = false;
if (session.obsState.visibility!==null){
if (session.obsState.visibility===false){ /////////////////// I need to change tis to .state or whatever, anc catch/handle these events to update the buttons in the pop up menu
needOptimize=true;
}
}
session.obsSceneSync();
for (var UUID in session.rpcs){
if (uid && (uid!==UUID)){continue;} // target just a single connection.
var msg = {};
if (!data2send){
msg.obsState = session.obsState;
if (session.rpcs[UUID].obsControl===false){
msg.obsState.details = null; // we don't want to send needless data
}
} else if (data2send in session.obsState){
if (data2send == "details"){
if (session.rpcs[UUID].obsControl===false){
continue; // we don't want to send needless data; this isn't a visibility update, so skip.
}
msg.obsState = {};
msg.obsState[data2send] = session.obsState[data2send];
} else {
msg.obsState = {};
msg.obsState[data2send] = session.obsState[data2send];
}
}
if (session.filterOBSscenes && msg.obsState && msg.obsState.details && msg.obsState.details.scenes && msg.obsState.details.scenes.length){
var scenes = [];
msg.obsState.details.scenes.forEach(scene=>{
if (session.filterOBSscenes && session.filterOBSscenes.length){
if (session.filterOBSscenes.includes(scene)){
scenes.push(scene);
}
}
});
msg.obsState.details.scenes = scenes;
}
if (session.optimize!==false){
var bandwidth = parseInt(session.rpcs[UUID].targetBandwidth);
if (needOptimize){
if ((bandwidth > session.optimize) || (bandwidth<0)){ // limit to optimized bitrate
bandwidth = session.optimize;
}
}
if (session.rpcs[UUID].bandwidth !== bandwidth){ // bandwidth already set correctly. don't resend.
msg.bitrate = bandwidth;
warnlog("Message to be sent: ");
warnlog(msg);
if (session.sendRequest(msg, UUID)){
session.rpcs[UUID].bandwidth=bandwidth; // this is letting the system know what the actual bandwidth is, even if it isn't the real target.
} else {
errorlog("Unable to set update OBS Visibility");
}
} else {
warnlog("Message to be sent: ");
warnlog(msg);
session.sendRequest(msg, UUID);
}
} else {
warnlog("Message to be sent: ");
warnlog(msg);
session.sendRequest(msg, UUID);
}
}
}
session.getOBSOptimization = function(msg, UUID){
if (session.obsState){
msg.obsState = {};
var needOptimize = false;
if (session.obsState.visibility!==null){
msg.obsState.visibility = session.obsState.visibility;
if (session.obsState.visibility===false){
needOptimize=true;
}
}
if (session.obsState.sourceActive!==null){
msg.obsState.sourceActive = session.obsState.sourceActive;
//if (session.obsState.sourceActive===false){
// needOptimize=true;
//}
}
if (session.obsState.recording!==null){
msg.obsState.recording = session.obsState.recording;
}
if (session.obsState.streaming!==null){
msg.obsState.streaming = session.obsState.streaming;
}
if (session.obsState.virtualcam!==null){
msg.obsState.virtualcam = session.obsState.virtualcam;
}
}
if (session.optimize!==false){
msg.optimizedBitrate = parseInt(session.optimize); // not setting a bitrate; just letting them know what the optimized bitrate is.
if (needOptimize){
session.rpcs[UUID].bandwidth = msg.optimizedBitrate;
}
}
return msg;
}
function getOBSDetails(){
if (session.disableOBS){return false;}
if (!window.obsstudio){return;}
if (!("details" in session.obsState)){
session.obsState.details = {};
}
var readOnlyFuncs = [
"getControlLevel",
//"getStatus",
"getCurrentScene",
"getScenes",
//"getTransitions",
//"getCurrentTransition",
//"pluginVersion"
];
var promises = {};
promises.main = true;
Object.keys(window.obsstudio).forEach(async (key)=>{
try {
if (typeof window.obsstudio[key] === 'function'){
if (readOnlyFuncs.includes(key)){
try {
promises[key] = true;
window.obsstudio[key](function(out){
var shortkey = key.replace("get","");
shortkey = shortkey[0].toLowerCase() + shortkey.slice(1);
session.obsState.details[shortkey] = out;
delete promises[key]
if (!Object.keys(promises).length){
session.obsStateSync("details");
}
});
} catch(e){
delete promises[key]
}
}
/* } else if (typeof window.obsstudio[key] === 'object'){ // none of these values I really need right now.
var shortkey = key.replace("get","");
shortkey = shortkey[0].toLowerCase() + shortkey.slice(1);
session.obsState.details[shortkey] = window.obsstudio[key];
} else {
var shortkey = key.replace("get","");
shortkey = shortkey[0].toLowerCase() + shortkey.slice(1);
session.obsState.details[shortkey] = window.obsstudio[key]; */
}
} catch(e){
errorlog(e);
}
});
delete promises.main;
if (!Object.keys(promises).length){
session.obsStateSync("details");
}
}
function toggleOBSControls(){
toggle(getById('remoteOBSControl'));
if (getById('remoteOBSControl').style.display=="none"){
getById("modalBackdrop").innerHTML = ''; // Delete modal
getById("modalBackdrop").remove();
} else {
getById("modalBackdrop").innerHTML = ''; // Delete modal
getById("modalBackdrop").remove();
var modalTemplate = ``;
document.body.insertAdjacentHTML("beforeend", modalTemplate); // Insert modal at body end
document.getElementById("modalBackdrop").addEventListener("click", toggleOBSControls);
}
}
function requestOBSAction(ele){
if (session.disableOBS){return false;}
}
function obsSceneChanged(event){
log(event.detail.name);
getOBSDetails(); // contains obsStateSync
}
function obsVirtualcamStarted(event){
session.obsState.virtualcam = true;
session.obsStateSync("virtualcam");
}
function obsVirtualcamStopped(event){
session.obsState.virtualcam = false;
session.obsStateSync("virtualcam");
}
function obsStreamingStarted(event){
session.obsState.streaming = true;
session.obsStateSync("streaming");
}
function obsStreamingStopped(event){
session.obsState.streaming = false;
session.obsStateSync("streaming");
}
function obsRecordingStarted(event){
session.obsState.recording = true;
session.obsStateSync("recording");
}
function obsRecordingStopped(event){
session.obsState.recording = false;
session.obsStateSync("recording");
}
function obsSourceActiveChanged(event){
warnlog("obsSourceActiveChanged");
warnlog( event.detail);
try {
if (typeof event==="boolean"){var sourceActive = event;}
else if (typeof event.detail === "boolean"){var sourceActive = event.detail;}
else if (typeof event.detail.active === "boolean"){var sourceActive = event.detail.active;}
else {var sourceActive = event.detail.active;}
if (typeof sourceActive === "undefined"){return;} // Just fail.
if (session.obsState.sourceActive!==sourceActive){ // only move forward if there is a change; the event likes to double fire you see.
session.obsState.sourceActive = sourceActive;
session.obsStateSync("sourceActive");
}
} catch (e){errorlog(e);}
}
function obsSourceVisibleChanged(event){ // accounts for visible in VDO.Ninja scene AND visible in OBS scene
warnlog("obsSourceVisibleChanged");
warnlog(event.detail);
try {
if (typeof event==="boolean"){var visibility = event;}
else if (typeof event.detail === "boolean"){var visibility = event.detail;}
else if (typeof event.detail.visible === "boolean"){var visibility = event.detail.visible;}
else {var visibility = event.detail.visible;}
if (typeof visibility === "undefined"){ // fall back
if (typeof document.visibilityState !== "undefined"){
visibility = document.visibilityState==="visible"; // modern
} else if (typeof document.hidden !== "undefined"){
visibility = !document.hidden; // legacy
} else {
return; // ... unknown input? fail.
}
}
if (session.obsState.visibility!==visibility){ // only move forward if there is a change; the event likes to double fire you see.
session.obsState.visibility = visibility;
session.obsStateSync("visibility");
}
} catch (e){errorlog(e);}
}
function manageSceneState(data, UUID){ // incoming obs details
if (session.disableOBS){return;}
var processNeeded = false
try{
if ("sceneDisplay" in data){
processNeeded=true;
session.pcs[UUID].sceneDisplay = data.sceneDisplay;
}
if ("sceneMute" in data){
processNeeded=true;
session.pcs[UUID].sceneMute = data.sceneMute;
}
if (data.obsState){
if ("sourceActive" in data.obsState){
processNeeded=true;
session.pcs[UUID].obsState.sourceActive = data.obsState.sourceActive;
}
if ("visibility" in data.obsState){
processNeeded=true;
session.pcs[UUID].obsState.visibility = data.obsState.visibility;
session.optimizeBitrate(UUID); // &optimize flag; sets video bitrate to target value if this flag == HIDDEN (if optimize=0, disables both audio and video)
}
if ("details" in data.obsState){
processNeeded=true;
session.pcs[UUID].obsState.details = data.obsState.details;
}
if ("streaming" in data.obsState){
processNeeded=true;
session.pcs[UUID].obsState.streaming = data.obsState.streaming;
}
if ("recording" in data.obsState){
processNeeded=true;
session.pcs[UUID].obsState.recording = data.obsState.recording;
}
if ("virtualcam" in data.obsState){
processNeeded=true;
session.pcs[UUID].obsState.virtualcam = data.obsState.virtualcam;
}
}
} catch(e){
errorlog(e);
}
if (processNeeded){
log(data);
applySceneState();
} else {
return;
}
if (isIFrame){
pokeIframeAPI("obs-state", data.obsState, UUID);
}
if (session.obsControls===false){
return;
}
try {
var control = 0;
if (session.pcs[UUID].obsState && session.pcs[UUID].obsState.details){
control = parseInt(session.pcs[UUID].obsState.details.controlLevel) || 0; //0 for NONE, 1 for READ_OBS (OBS data), 2 for READ_USER (User data), 3 for BASIC, 4 for ADVANCED and 5 for ALL
}
if (control >= 4){
if (session.director || !session.roomid){
if (session.obsControls!==false){
getById("obscontrolbutton").classList.remove("hidden"); // so they get a tip.
}
}
}
var multi = false;
getById("obsControlButtons").querySelectorAll("[data-system]").forEach(ele=>{
if (ele.dataset.system in session.pcs){
if (ele.dataset.system !==UUID){
multi = true;
}
} else { // delete, since no longer active.
ele.remove();
}
});
getById("obsSceneNames").querySelectorAll("[data-system]").forEach(ele=>{
if (ele.dataset.system in session.pcs){
if (ele.dataset.system !==UUID){
multi = true;
}
} else { // delete, since no longer active.
ele.remove();
}
});
if (control==0){
var obsControlButtonsBox = getById("obsControlButtons").querySelector("[data-system='"+UUID+"']");
if (obsControlButtonsBox){
obsControlButtonsBox.remove();
}
var obsSceneNamesBox = getById("obsSceneNames").querySelector("[data-system='"+UUID+"']"); // this hides if less than 2, so hide it now.
if (obsSceneNamesBox){
obsSceneNamesBox.remove();
}
if (!multi){
getById("obsControlHelp").classList.remove("hidden");
}
return;
}
getById("obsControlHelp").classList.add("hidden");
var obsControlButtonsBox = getById("obsControlButtons").querySelector("[data-system='"+UUID+"']");
if (!obsControlButtonsBox){
obsControlButtonsBox = document.createElement("div");
obsControlButtonsBox.dataset.system = UUID;
getById("obsControlButtons").appendChild(obsControlButtonsBox);
} else {
obsControlButtonsBox.innerHTML = "";
}
if (multi){
var h3 = document.createElement("h3");
h3.innerText = "OBS instance: " + (session.pcs[UUID].label || session.pcs[UUID].scene || UUID);
obsControlButtonsBox.appendChild(h3);
}
if (session.pcs[UUID].obsState && ("streaming" in session.pcs[UUID].obsState)){
var controlButton = document.createElement("button");
controlButton.dataset.UUID = UUID;
if (session.pcs[UUID].obsState.streaming){
controlButton.classList.add("pressed");
controlButton.ariaPressed = "true";
controlButton.dataset.obsAction = "stopStreaming";
controlButton.innerText = "📡 stop streaming";
} else {
controlButton.dataset.obsAction = "startStreaming";
controlButton.innerText = "📡 start streaming";
}
if (control<5){
controlButton.disabled = true;
controlButton.style.cursor = "not-allowed";
controlButton.title = "Source is lacking required permissions.";
} else {
controlButton.onclick = async function(){
var msg = {};
msg.obsCommand = {}
msg.obsCommand.action = this.dataset.obsAction;
msg.UUID = this.dataset.UUID;
if (document.querySelector("#obsRemotePassword>input") && document.querySelector("#obsRemotePassword>input").value){
msg.remote = document.querySelector("#obsRemotePassword>input").value;
} else {
msg.remote = session.remote;
}
msg = await session.encodeRemote(msg);
session.anysend(msg);
log("action request: "+this.dataset.obsAction);
}
}
obsControlButtonsBox.appendChild(controlButton);
}
if (session.pcs[UUID].obsState && ("recording" in session.pcs[UUID].obsState)){
var controlButton = document.createElement("button");
controlButton.dataset.UUID = UUID;
if (session.pcs[UUID].obsState.recording){
controlButton.classList.add("pressed");
controlButton.ariaPressed = "true";
controlButton.dataset.obsAction = "stopRecording";
controlButton.innerText = "📽 stop recording";
} else {
controlButton.dataset.obsAction = "startRecording";
controlButton.innerText = "📽 start recording";
}
if (control<5){
controlButton.disabled = true;
controlButton.style.cursor = "not-allowed";
controlButton.title = "Source is lacking required permissions.";
} else {
controlButton.onclick = async function(){
var msg = {};
msg.obsCommand = {};
msg.obsCommand.action = this.dataset.obsAction;
msg.UUID = this.dataset.UUID;
if (document.querySelector("#obsRemotePassword>input").value){
msg.remote = document.querySelector("#obsRemotePassword>input").value;
} else {
msg.remote = session.remote;
}
msg = await session.encodeRemote(msg);
session.anysend(msg);
log("action request: "+this.dataset.obsAction);
}
}
obsControlButtonsBox.appendChild(controlButton);
}
if (session.pcs[UUID].obsState && ("virtualcam" in session.pcs[UUID].obsState)){
var controlButton = document.createElement("button");
controlButton.dataset.UUID = UUID;
if (session.pcs[UUID].obsState.virtualcam){
controlButton.classList.add("pressed");
controlButton.ariaPressed = "true";
controlButton.dataset.obsAction = "stopVirtualcam";
controlButton.innerText = "💻 stop virtualcam";
} else {
controlButton.dataset.obsAction = "startVirtualcam";
controlButton.innerText = "💻 start virtualcam";
}
if (control<5){
controlButton.disabled = true;
controlButton.style.cursor = "not-allowed";
controlButton.title = "Source is lacking required permissions.";
} else {
controlButton.onclick = async function(){
var msg = {};
msg.obsCommand = {}
msg.obsCommand.action = this.dataset.obsAction;
msg.UUID = this.dataset.UUID;
if (document.querySelector("#obsRemotePassword>input").value){
msg.remote = document.querySelector("#obsRemotePassword>input").value;
} else {
msg.remote = session.remote;
}
msg = await session.encodeRemote(msg);
session.anysend(msg);
log("action request: "+this.dataset.obsAction);
}
}
obsControlButtonsBox.appendChild(controlButton);
}
} catch(e){errorlog(e);} // just in case the client has disconnected.
if (control<2){
var obsSceneNamesBox = getById("obsSceneNames").querySelector("[data-system='"+UUID+"']");
if (obsSceneNamesBox){
obsSceneNamesBox.remove();
}
return;
}
var obsSceneNamesBox = getById("obsSceneNames").querySelectorAll("div[data-system='"+UUID+"']");
if (!obsSceneNamesBox.length){
obsSceneNamesBox = document.createElement("div");
obsSceneNamesBox.dataset.system = UUID;
getById("obsSceneNames").appendChild(obsSceneNamesBox);
} else {
obsSceneNamesBox = obsSceneNamesBox[0];
obsSceneNamesBox.innerHTML = "";
}
if (multi){
var h3 = document.createElement("h3");
h3.innerText = "OBS instance: " + (session.pcs[UUID].label || session.pcs[UUID].scene || UUID);
obsSceneNamesBox.appendChild(h3);
}
if (session.pcs[UUID].obsState.details){
var details = session.pcs[UUID].obsState.details;
if (details.scenes){
details.scenes.forEach(scene=>{
var sceneButton = document.createElement("button");
sceneButton.dataset.obsScene = scene;
sceneButton.dataset.UUID = UUID;
sceneButton.innerText = scene;
if (details.currentScene && details.currentScene.name && (details.currentScene.name === scene)){
sceneButton.classList.add("pressed");
sceneButton.ariaPressed = "true";
}
obsSceneNamesBox.appendChild(sceneButton);
if (control<4){
sceneButton.disabled = true;
sceneButton.style.cursor = "not-allowed";
sceneButton.title = "Source is lacking required permissions.";
} else {
sceneButton.onclick = async function(){
var msg = {};
msg.obsCommand = {action: "setCurrentScene", value: this.dataset.obsScene};
msg.UUID = this.dataset.UUID;
if (document.querySelector("#obsRemotePassword>input").value){
msg.remote = document.querySelector("#obsRemotePassword>input").value;
} else {
msg.remote = session.remote;
}
msg = await session.encodeRemote(msg);
session.anysend(msg);
log("scene change request: "+this.dataset.obsScene);
};
}
});
}
}
getById("debugRemoteOBSControl").innerText = JSON.stringify(session.pcs[UUID].obsState);
}
function processOBSCommand(msg){
if (session.disableOBS){return false;}
else if (!window.obsstudio){return false;}
else if (typeof msg.obsCommand !== "object"){return false;}
else if ("remote" in msg){
if (((msg.remote === session.remote) && session.remote) || (session.remote===true)){
// approved
} else {
if (msg.UUID && msg.obsCommand.action){
var data = {}
data.rejected = "obsCommand";
//data.debug = msg.remote;
session.sendRequest(data, msg.UUID); // this skips the server
}
warnlog("Denied access; remote does not match");
return false;
}
} else {
if (msg.UUID && msg.obsCommand.action){
var data = {}
data.rejected = "obsCommand";
//data.debug = "no remote code provided";
session.sendRequest(data, msg.UUID); // this skips the server
}
return false;
}
try { // {changeScene: this.dataset.obsScene}
if (msg.obsCommand.action && (typeof msg.obsCommand.action=="string")){
if (msg.obsCommand.value && (typeof msg.obsCommand.value=="string")){
if ((msg.obsCommand.action == "setCurrentScene") && session.filterOBSscenes && session.filterOBSscenes.length){
try {
if (!session.filterOBSscenes.includes(msg.obsCommand.value)){
return false;
}
} catch(e){errorlog(e);return false;}
}
window.obsstudio[msg.obsCommand.action](msg.obsCommand.value);
} else {
window.obsstudio[msg.obsCommand.action]();
}
}
} catch(e){
errorlog(e);
return false;
}
return true;
}
function applySceneState(){ // guest side; tally light, etc.
if (session.disableOBS){return;} // the guest can decide to hide the tally light
if (document.getElementById("videosource")){
var visibility = false;
var ondeck = false;
var recording = false;
for (var uid in session.pcs){
if (session.pcs[uid].obsState.sourceActive!==false && session.pcs[uid].obsState.visibility && (session.pcs[uid].sceneDisplay!==false)){
visibility=true;
} else if (session.pcs[uid].obsState.visibility && (session.pcs[uid].sceneDisplay!==false)){
ondeck=true;
}
if ((session.pcs[uid].obsState.recording || session.pcs[uid].obsState.streaming) && (session.pcs[uid].obsState.sourceActive!==false && session.pcs[uid].obsState.visibility && (session.pcs[uid].sceneDisplay!==false))){ // the scene that is recording must be visible also.
recording=true;
}
}
if (!session.cleanOutput){
getById("obsState").classList.remove("hidden");
}
if (recording){
getById("obsState").classList.remove("ondeck");
getById("obsState").classList.add("recording"); // TODO: this needs to check all peers to make sure it's valid
getById("obsState").innerHTML = "ON AIR";
if (session.tallyStyle){
getById("main").classList.remove("ondeck");
getById("main").classList.add("recording");
}
} else if (ondeck && !visibility){
getById("obsState").classList.remove("recording");
getById("obsState").classList.add("ondeck"); // TODO: this needs to check all peers to make sure it's valid
getById("obsState").innerHTML = "STAND BY";
if (session.tallyStyle){
getById("main").classList.remove("recording");
getById("main").classList.add("ondeck");
}
} else if (visibility){
getById("obsState").classList.remove("recording");
getById("obsState").classList.remove("ondeck");
getById("obsState").innerHTML = "ACTIVE";
if (session.tallyStyle){
getById("main").classList.remove("recording");
getById("main").classList.remove("ondeck");
}
} else {
getById("obsState").classList.remove("recording");
getById("obsState").classList.remove("ondeck");
getById("obsState").innerHTML = "INACTIVE";
getById("obsState").classList.add("hidden"); // I don't think most people care to see inactive.
if (session.tallyStyle){
getById("main").classList.remove("recording");
getById("main").classList.remove("ondeck");
}
}
if (visibility){ // BASIC TALLY LIGHT (on deck disabled)
getById("obsState").classList.add("onair"); // LIVE
if (session.tallyStyle){
getById("main").classList.add("onair");
}
} else {
getById("obsState").classList.remove("onair");
if (session.tallyStyle){
getById("main").classList.remove("onair");
}
}
if (session.automute){
if (!visibility){
session.micIsolatedAutoMute = [];
if (session.automute !== "2"){
for (var uid in session.pcs){
if (session.directorList.indexOf(uid)>=0){ // allow validated directors to hear the guest
session.micIsolatedAutoMute.push(uid);
}
}
}
} else {
session.micIsolatedAutoMute = false;
}
session.applyIsolatedChat();
}
}
}
function compare_vids( a, b ) {
var aa = a.order || 0;
var bb = b.order || 0;
if ( aa < bb ){
return 1;
}
if ( aa > bb ){
return -1;
}
return 0;
}
function compare_vids_sid( a, b ) {
var aa = a.dataset.sid || 0;
var bb = b.dataset.sid || 0;
if ( aa > bb ){
return 1;
}
if ( aa < bb ){
return -1;
}
return 0;
}
function compare_vids_label( a, b ) {
if (a.dataset.UUID && session.rpcs[a.dataset.UUID] && session.rpcs[a.dataset.UUID].label){
var aa = session.rpcs[a.dataset.UUID].label.toLowerCase();
} else {
var aa = 0;
}
if (b.dataset.UUID && session.rpcs[b.dataset.UUID] && session.rpcs[b.dataset.UUID].label){
var bb = session.rpcs[b.dataset.UUID].label.toLowerCase();
} else {
var bb = 0;
}
if ( aa > bb ){
return 1;
}
if ( aa < bb ){
return -1;
}
return 0;
}
function sortByZ(mediaPool, layout) {
function sortABZ( a, b ) {
if (layout[a.dataset.sid]){
var aa = layout[a.dataset.sid].zIndex || layout[a.dataset.sid].z || 0;
} else {
var aa = 0;
}
if (layout[b.dataset.sid]){
var bb = layout[b.dataset.sid].zIndex || layout[b.dataset.sid].z || 0;
} else {
var bb = 0;
}
if ( aa < bb ){
return -1;
}
if ( aa > bb ){
return 1;
}
return 0;
}
mediaPool.sort(sortABZ);
return mediaPool;
}
window.onpopstate = function() {
if (session.firstPlayTriggered) {
window.location.reload(true); // deprecated, but it seems to work, so w/e
}
};
var miniPerformerX = null;
var miniPerformerY = null;
function makeMiniDraggableElement(elmnt) {
if (session.disableMouseEvents){return;}
try {
elmnt.dragElement = false;
// elmnt.style.bottom = "auto";
elmnt.style.cursor = "grab";
elmnt.stashonmouseup = null;
elmnt.stashonmousemove = null;
} catch (e) {
errorlog(e);
return;
}
var pos1 = 0;
var pos2 = 0;
var pos3 = 0;
var pos4 = 0;
var timestamp = false;
function elementDrag(e) { // ON DRAG
timestamp = false;
if (session.infocus){return;}
try {
e = e || window.event;
if (e.type !== "touchmove"){
if (("buttons" in e) && (e.buttons!==1)){
closeDragElement(e);
return;
}
e.preventDefault();
}
e.stopPropagation();
elmnt.dragElement = true;
if (e.type === "touchmove"){
pos1 = pos3 - e.touches[0].clientX;
pos2 = pos4 - e.touches[0].clientY;
pos3 = e.touches[0].clientX;
pos4 = e.touches[0].clientY;
} else {
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
}
var topDrag = (elmnt.offsetTop - pos2 );
if (topDrag > (-3 + (window.innerHeight - elmnt.clientHeight))){
topDrag = (-3 + (window.innerHeight - elmnt.clientHeight));
}
miniPerformerY = topDrag;
miniPerformerX = elmnt.offsetLeft - pos1;
if (miniPerformerY > window.innerHeight-elmnt.clientHeight){
miniPerformerY = window.innerHeight-elmnt.clientHeight;
}
if (miniPerformerX > window.innerWidth-elmnt.clientWidth){
miniPerformerX = window.innerWidth-elmnt.clientWidth;
}
miniPerformerX = 100 * miniPerformerX/window.innerWidth ;
miniPerformerY = 100 * miniPerformerY/window.innerHeight;
if (session.widget && !session.leftMiniPreview){
if (miniPerformerX>74){
miniPerformerX = 74;
}
}
if (miniPerformerY<0){
miniPerformerY=0;
} else if (miniPerformerY>100){
miniPerformerY=100;
}
if (miniPerformerX<0){
miniPerformerX=0;
} else if (miniPerformerX>100){
miniPerformerX=100;
}
elmnt.style.right = "unset";
elmnt.style.top = miniPerformerY + "%";
elmnt.style.left = miniPerformerX + "%";
} catch(e){errorlog(e);}
}
function closeDragElement(e) { // TOUCH END
e = e || window.event;
if (e.type !== "touchend"){
if (e.button !== 0){return;}
document.onmouseup = elmnt.stashonmouseup;
document.onmousemove = elmnt.stashonmousemove;
elmnt.onmouseleave=null;
}
if (session.infocus){return;}
e.preventDefault();
if (timestamp && (Date.now()- timestamp>500)){ // long hold, so this is a drag
e.stopPropagation();
if (e.type === "touchend"){
if (session.infocus === true){
session.infocus = false;
} else {
session.infocus = true;
log("session: myself");
}
setTimeout(()=>updateMixer(),10);
}
} else if (timestamp && (e.type !== "touchend")){
if (session.infocus === true){
session.infocus = false;
} else {
session.infocus = true;
log("session: myself");
}
setTimeout(()=>updateMixer(),10);
}
}
function dragMouseDown(e) { ////// TOUCH START
if (event.ctrlKey || event.metaKey) {return;}
timestamp = Date.now();
e = e || window.event;
if (session.infocus){return;}
e.preventDefault();
if (e.type === "touchstart"){
pos3 = e.touches[0].clientX;
pos4 = e.touches[0].clientY;
elmnt.ontouchend = closeDragElement;
elmnt.ontouchmove = elementDrag;
} else {
if (e.button !== 0){return;}
pos3 = e.clientX;
pos4 = e.clientY;
elmnt.stashonmouseup = document.onmouseup; // I don't want to interfere with other drag events.
elmnt.stashonmousemove = document.onmousemove;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
elmnt.onmouseleave = function(event){
closeDragElement(event);
};
}
}
elmnt.onmousedown = dragMouseDown;
elmnt.ontouchstart = dragMouseDown;
}
function makeDraggableElement(element) {
if (session.disableMouseEvents){return;} // this is here for a reason. :P
element.initialX;
element.initialY;
element.currentX;
element.xOffset = 0;
element.currentY;
element.yOffset = 0;
element.isDragging = false;
element.dragElement = true;
element.addEventListener('mousedown', dragStart);
function dragStart(e) {
element.initialX = e.clientX - element.xOffset;
element.initialY = e.clientY - element.yOffset;
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', dragEnd);
document.addEventListener('onmouseleave', dragEnd);
document.addEventListener('onmouseenter', dragEnd);
element.isDragging = true;
}
function dragEnd(e) {
element.initialX = element.currentX;
element.initialY = element.currentY;
document.removeEventListener('mousemove', drag);
document.removeEventListener('mouseup', dragEnd);
document.removeEventListener('onmouseleave', dragEnd);
document.removeEventListener('onmouseenter', dragEnd);
element.isDragging = false;
}
function drag(e) {
if (element.isDragging) {
element.currentX = (e.clientX - element.initialX);
element.currentY = (e.clientY - element.initialY);
// Get the dimensions of the viewport
let vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
let vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
// Get the dimensions of the object
let elementWidth = element.offsetWidth;
let elementHeight = element.offsetHeight;
// console.log('elementWidth:\n',elementWidth)
// console.log('elementHeight:\n',elementHeight)
// Calculate the boundaries
let maxX = vw - elementWidth;
let maxY = vh - elementHeight;
let minX = 0;
let minY = 0;
// Calculate real boundaries (parent position: fixed issues)
let topOffset = 0;
let leftOffset = 0;
let elementOffset = element;
while (elementOffset) {
topOffset += elementOffset.offsetTop;
leftOffset += elementOffset.offsetLeft;
elementOffset = elementOffset.offsetParent;
}
// Adjust the position if it's going beyond the boundaries
let realX = element.currentX + leftOffset;
let realY = element.currentY + topOffset;
if (realX > maxX) {
element.currentX = maxX - leftOffset;
} else if (realX < minX) {
element.currentX = minX - leftOffset;
}
if (realY > maxY) {
element.currentY = maxY - topOffset;
} else if (realY < minY) {
element.currentY = minY - topOffset;
}
// Update the position and offset
element.xOffset = element.currentX;
element.yOffset = element.currentY;
element.style.transform = `translate(${element.currentX}px, ${element.currentY}px)`;
}
}
// if (session.disableMouseEvents){return;}
// try {
// elmnt.dragElement = false;
// elmnt.style.bottom = "auto";
// elmnt.style.cursor = "grab";
// elmnt.stashonmouseup = null;
// elmnt.stashonmousemove = null;
// } catch (e) {
// errorlog(e);
// return;
// }
// var pos1 = 0;
// var pos2 = 0;
// var pos3 = 0;
// var pos4 = 0;
// var timestamp = false;
// var enterEventCount = 0;
// var leaveEventCount = 0;
// function dragMouseDown(e) {
// timestamp = Date.now();
// e = e || window.event;
// e.preventDefault();
// pos3 = e.clientX;
// pos4 = e.clientY;
// //elmnt.stashonmouseup = document.onmouseup; // I don't want to interfere with other drag events.
// //elmnt.stashonmousemove = document.onmousemove;
// //elmnt.stashonclick = document.onclick;
// document.onmouseup = function(event){closeDragElement(event, elmnt);};
// document.onmousemove = function(event){elementDrag(elmnt,event);};
// if ("stopDragTimeout" in elmnt){clearTimeout(elmnt.stopDragTimeout);}
// elmnt.onmouseleave = function(event){
// leaveEventCount+=1;
// elmnt.stopDragTimeout = setTimeout(function(ele,evt1){
// closeDragElement(evt1, ele);}
// ,100, elmnt, event);
// };
// elmnt.onmouseenter = function(event){
// enterEventCount+=1;
// if (enterEventCount>=leaveEventCount){
// if ("stopDragTimeout" in elmnt){clearTimeout(elmnt.stopDragTimeout);}
// } else {
// if (("stopDragTimeout" in elmnt) && (elmnt.stopDragTimeout)){
// clearTimeout(elmnt.stopDragTimeout);
// elmnt.stopDragTimeout = setTimeout(function(ele,evt1){
// closeDragElement(evt1, ele);}
// ,100, elmnt, event);
// }
// }
// };
// }
// function elementDrag(ele,e) {
// e = e || window.event;
// if (("buttons" in e) && (e.buttons!==1)){return;}
// e.preventDefault();
// ele.dragElement = true;
// pos1 = pos3 - e.clientX;
// pos2 = pos4 - e.clientY;
// pos3 = e.clientX;
// pos4 = e.clientY;
// var topDrag = (ele.offsetTop - pos2 );
// if (absolute){
// if (topDrag > (-3 + (window.innerHeight - ele.clientHeight))){
// topDrag = (-3 + (window.innerHeight - ele.clientHeight));
// }
// } else {
// if (topDrag > -3){
// topDrag = -3;
// }
// }
// ele.style.top = topDrag + "px";
// ele.style.left = (ele.offsetLeft - pos1) + "px";
// }
// function closeDragElement(event=false, ele=false) {
// document.onmouseup = null;
// document.onmousemove = null
// ele.onmouseleave = null;
// ele.onmouseenter = null;
// enterEventCount = 0;
// leaveEventCount = 0;
// updateMixer();
// //document.onclick = elmnt.stashonclick;
// }
// elmnt.onmousedown = dragMouseDown;
}
function removeStorage(cname){
localStorage.removeItem(cname);
}
function clearStorage(){
localStorage.clear();
if (!session.cleanOutput){
warnUser("The local storage and saved settings have been cleared", 1000);
}
}
function setStorage(cname, cvalue, hours=9999){ // not actually a cookie
var now = new Date();
var item = {
value: cvalue,
expiry: now.getTime() + (hours * 60 * 60 * 1000),
};
try{
localStorage.setItem(cname, JSON.stringify(item));
}catch(e){errorlog(e);}
}
function getStorage(cname) {
try {
var itemStr = localStorage.getItem(cname);
} catch(e){
errorlog(e);
return;
}
if (!itemStr) {
return "";
}
var item = JSON.parse(itemStr);
var now = new Date();
if (now.getTime() > item.expiry) {
localStorage.removeItem(cname);
return "";
}
return item.value;
}
function play(streamid=null, UUID=false){ // play whatever is in the URL params; or filter by a streamID option
log("play stream: "+session.view+ " " +streamid);
if (session.viewDirectorOnly){
if (!(UUID || streamid)){
warnlog("No UUID and StreamID");
return;
} else if (session.directorList.indexOf(UUID)==-1){
warnlog("Not a director");
return;
}
}
if (session.view_set){
var played = false;
for (var j in session.view_set){
if (streamid===null){ // play what is in the view list ; not a group room probably
session.watchStream(session.view_set[j]);
played=true;
} else if (streamid === session.view_set[j]){ // plays if the group room list matches the explicit list
session.watchStream(session.view_set[j]);
played=true;
}
}
if (session.include){
session.include.forEach(sid=>{
if (session.view_set.includes(sid)){
// already played
} else if (streamid===null){ // play what is in the view list ; not a group room probably
session.watchStream(sid);
} else if (streamid === sid){ // plays if the group room list matches the explicit list
session.watchStream(sid);
played=true;
}
});
}
if (!played && streamid){
if (session.scene!==false){
if (!session.permaid){
if (!session.queue){ // I don't want to deal with queues.
if (session.exclude===false || (!session.exclude.includes(streamid))){
if (UUID){
if (session.directorList.indexOf(UUID)>=0){
warnlog("stream ID added to badStreamList: "+streamid);
session.badStreamList.push(streamid);
session.watchStream(streamid);
}
}
}
}
}
}
}
} else if (streamid && (session.exclude !== false)){
if (session.exclude.includes(streamid)){
// we don't play it at all. (if explicity listed as VIDEO, then OKay.)
} else {
session.watchStream(streamid); // I suppose we do play it.
}
} else if (streamid){
session.watchStream(streamid);
} else if (session.include.length){
session.include.forEach(sid=>{
session.watchStream(sid);
});
}
}
function nextQueue(){
if (!session.queue){return;}
if (!session.director){return;}
if (session.queueList.length==0){
getById("queuebutton").classList.add("red");
setTimeout(function(){
getById("queuebutton").classList.remove("red");
},50);
return;
}
var nextStream = session.queueList.shift();
getById("queuebutton").classList.add("red");
setTimeout(function(){
getById("queuebutton").classList.remove("red");
},200);
updateQueue();
session.watchStream(nextStream);
log("next stream loading: "+nextStream);
}
function updateQueue(adding=false){
if (!session.queue){return;}
if (!session.director){return;}
if (session.queueList.length) {
if (session.queueList.length>10){
getById("queueNotification").innerHTML = "‼";
} else {
getById("queueNotification").innerHTML = session.queueList.length;
}
getById("queueNotification").classList.add("queueNotification");
} else {
getById("queueNotification").innerHTML = "";
getById("queueNotification").classList.remove("queueNotification");
}
if (adding){
if (session.beepToNotify){
playtone();
showNotification("someone joined the queue", "queue length: "+session.queueList.length);
}
getById("queuebutton").classList.remove("shake");
setTimeout(function(){getById("queuebutton").classList.add("shake");},10);
}
}
function hideStreamLowBandwidth(bandwidth, UUID){
if (!session.lowBitrateCutoff){return;}
if (bandwidth<=session.lowBitrateCutoff){// <= used so 0 can be used as a trigger
if (session.lowBitrateSceneChange){
changeSceneLowBandwidth(true);
} else if (!session.rpcs[UUID].bandwidthMuted){
session.rpcs[UUID].bandwidthMuted = true;
updateMixer();
}
} else if (session.lowBitrateSceneChange){
changeSceneLowBandwidth(false);
} else if (session.rpcs[UUID].bandwidthMuted){
session.rpcs[UUID].bandwidthMuted = false;
if (session.rpcs[UUID].videoElement){
session.rpcs[UUID].videoElement.muted = checkMuteState(UUID);
}
updateMixer();
}
}
var changeSceneEnabled = false;
var changeSceneLowBandwidthRevert = false;
function changeSceneLowBandwidth(state){
if (!session.lowBitrateSceneChange){return;}
if (!session.obsState){return;}
try {
if (session.obsState.visibility && session.obsState.details && session.obsState.details.currentScene){
changeSceneLowBandwidthRevert = session.obsState.details.currentScene.name || false;
} else if (("visibility" in session.obsState) && !session.obsState.visibilit && session.obsState.details && session.obsState.details.currentScene){
if (session.obsState.details.currentScene.name !== session.lowBitrateSceneChange){
return; // not the FML scene, nor are we visible, so we're not going to switch back. Assume the user has overtaken the setup.
}
}
if (!window.obsstudio || !window.obsstudio["setCurrentScene"]){return;}
if (state && changeSceneLowBandwidthRevert){
if (changeSceneEnabled){ // bitrate was higher , so we can now cut off.
window.obsstudio["setCurrentScene"](session.lowBitrateSceneChange);
}
} else if (changeSceneLowBandwidthRevert){
changeSceneEnabled = true;
window.obsstudio["setCurrentScene"](changeSceneLowBandwidthRevert);
}
} catch(e){
errorlog(e);
}
}
function setupIncomingScreenTracking(v, UUID){ // SCREEN element.
if (session.directorList.indexOf(UUID)>=0){
v.muted=false;
}
v.addEventListener("playing", (e)=>{
try {
var bigPlayButton = document.getElementById("bigPlayButton");
if (bigPlayButton){
bigPlayButton.parentNode.removeChild(bigPlayButton);
}
} catch(e){}
resetupAudioOut(e.target, true);
try {
if (session.pip){
if (v.readyState >= 3){
if (!(v.pip)){
v.pip=true;
toggleSystemPip(v, true);
}
}
}
} catch(e){}
}, { once: true });
v.onpause = (event) => { // prevent things from pausing; human or other
if (v.dataset.UUID && session.rpcs[v.dataset.UUID] && (session.rpcs[v.dataset.UUID].manualBandwidth === 0)){
return true;
}
if (!((event.ctrlKey) || (event.metaKey) )){
warnlog("Video paused; force it to play again");
//return;
//session.audioCtx.resume();
//log("ctx resume");
event.currentTarget.play().then(_ => {
log("playing 4");
}).catch(error => {
warnlog("didnt play 1");
});
if (Firefox){
unPauseVideo(v);
}
}
return true;
}
if (session.pip){
v.onloadedmetadata = function(){
if (!v.paused){
if (!(v.pip)){
v.pip=true;
toggleSystemPip(v, true);
}
}
}
}
v.addEventListener('resize', (e) => { // if the aspect ratio changes, then we might want to update the mixer. If audio only, then this doesn't matter.
var v = e.target;
var aspectRatio = parseFloat(v.videoWidth/v.videoHeight) || 0;
log("resize event: "+aspectRatio);
if (!aspectRatio){
v.resetAR = true;
return;
} // if Audio only, then we don't want to set or update any aspect ratio.
if (v.resetAR){
log("ASPECT RATIO UNMUTED");
delete(v.resetAR);
v.dataset.aspectRatio = aspectRatio;
pokeIframeAPI("aspect-ratio", v.dataset.aspectRatio, v.dataset.UUID, v.dataset.sid);
setTimeout(function(){updateMixer();},1);
} else if (v.dataset.aspectRatio){
if (aspectRatio != parseFloat(v.dataset.aspectRatio)){
log("ASPECT RATIO CHANGED");
v.dataset.aspectRatio = aspectRatio;
pokeIframeAPI("aspect-ratio", v.dataset.aspectRatio, v.dataset.UUID, v.dataset.sid);
setTimeout(function(){updateMixer();},1); // We don't want to run this on the first resize? just subsequent ones.
}
} else {
log("NEW VIDEO ? ASPECT RATIO new");
v.dataset.aspectRatio = aspectRatio;
pokeIframeAPI("aspect-ratio", v.dataset.aspectRatio, v.dataset.UUID, v.dataset.sid);
setTimeout(function(){updateMixer();},1);
}
});
if (typeof session.volume == "number"){
v.volume = session.volume;
} else {
v.volume = 1.0; // play audio automatically
}
v.autoplay = true;
v.controls = session.showControls || false;
v.classList.add("tile");
v.setAttribute("playsinline","");
v.controlTimer = null;
v.dataset.menu = "context-menu-video";
if (!session.cleanOutput){
v.classList.add("task"); // this adds the right-click menu
}
if (document.getElementById("mainmenu")){
var m = getById("mainmenu");
m.remove();
}
if (session.director){
if (session.showControls!==null){
v.controls = session.showControls
} else {
v.controls = true;
}
var container = getById("screenContainer_"+UUID);
v.container = container;
v.disablePictureInPicture = false
v.setAttribute("controls","controls")
container.appendChild(v);
pokeIframeAPI("control-box-video-updated", v.id, UUID);
session.requestRateLimit(session.directorViewBitrate,UUID); /// limit resolution for director
v.title = "Hold CTRL or CMD (⌘) while clicking the video to open detailed stats";
if (session.beepToNotify) {
playtone();
}
} else if (session.scene!==false){
v.controls = session.showControls || false;
if (session.view){ // specific video to be played
v.style.display="block";
} else if (session.scene==="0"){ // auto plays, right?
v.style.display="block";
} else { // group scene I guess; needs to be added manually
v.style.display="none";
v.mutedStateScene = true;
}
setTimeout(function(){updateMixer();},1);
} else if (session.roomid!==false){
if (session.cleanOutput){
v.controls = session.showControls || false;
} else if (session.studioSoftware) {
v.controls = session.showControls || false;
} else if (session.showControls!==null){
v.controls = session.showControls;
} else {
v.controls = true;
}
//if ((session.roomid==="") && (session.bitrate)){
// let's keep the default bitrates, since this isn't a real room and bitrates are specified.
//} //else if (session.novideo !== false){
// if (session.novideo.includes(session.rpcs[UUID].streamID)){ // no video will have muted the video already anyways.
// session.requestRateLimit(0,UUID, false);// optimizing audio here doesn't later get turned back on. let the automixer disable audio instead
// }
//} //else {
// session.requestRateLimit(0,UUID, false);//// optimizing audio here doesn't later get turned back on. let the automixer disable audio instead
//}
setTimeout(function(){updateMixer();},1);
} else {
v.style.display="block";
setTimeout(function(){updateMixer();},1);
}
v.addEventListener('click', function(e) { // show stats of video if double clicked
log("clicked");
try {
var uid = e.currentTarget.dataset.UUID;
if ((e.ctrlKey)||(e.metaKey)){
e.preventDefault();
if ("stats" in session.rpcs[uid]){
var [menu, innerMenu] = statsMenuCreator();
printViewStats(innerMenu, uid );
menu.interval = setInterval(printViewStats, session.statsInterval, innerMenu, uid);
}
e.stopPropagation();
return false;
} else if ("prePausedBandwidth" in session.rpcs[uid]){
unPauseVideo(e.currentTarget);
}
} catch(e){errorlog(e);}
});
if (session.statsMenu){
if ("stats" in session.rpcs[UUID]){
if (getById("menuStatsBox")){
clearInterval(getById("menuStatsBox").interval);
getById("menuStatsBox").remove();
}
var [menu, innerMenu] = statsMenuCreator();
printViewStats(innerMenu, UUID );
menu.interval = setInterval(printViewStats, session.statsInterval, innerMenu, UUID);
}
}
v.touchTimeOut = null;
v.touchLastTap = 0;
v.touchCount = 0;
v.addEventListener('touchend', function(event) {
if (session.disableMouseEvents){return;}
log("touched");
//document.ontouchup = null;
//document.onmouseup = null;
document.onmousemove = null;
document.ontouchmove = null;
var currentTime = new Date().getTime();
var tapLength = currentTime - v.touchLastTap;
clearTimeout(v.touchTimeOut);
if (tapLength < 500 && tapLength > 0) {
///
log("double touched");
v.touchCount+=1;
event.preventDefault();
if (v.touchCount<5){
v.touchLastTap = currentTime;
return false;
}
v.touchLastTap = 0;
v.touchCount=0;
log("double touched");
var uid = event.currentTarget.dataset.UUID;
if ("stats" in session.rpcs[uid]){
var [menu, innerMenu] = statsMenuCreator();
printViewStats(innerMenu, uid );
menu.interval = setInterval(printViewStats, session.statsInterval, innerMenu, uid);
}
event.stopPropagation();
return false;
//////
} else {
v.touchCount=1;
v.touchTimeOut = setTimeout(function(vv) {
clearTimeout(vv.touchTimeOut);
vv.touchLastTap = 0;
vv.touchCount=0;
}, 5000, v);
v.touchLastTap = currentTime;
}
});
if (v.controls == false){
v.addEventListener("click", function () {
if (v.paused){
log("PLAYING MANUALLY?");
v.play().then(_ => {
log("playing 5");
}).catch(warnlog);
}
});
if (session.nocursor==false){ // we do not want to show the controls. This is because MacOS + OBS does not work; so electron app needs this.
if (!(session.cleanOutput)){
if (session.studioSoftware) {
} else if (session.showControls === false) { // explicitly disabled; default null.
} else if (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1) {
} else {
if (v.controlTimer){
clearInterval(v.controlTimer);
}
v.controlTimer = setTimeout(showControlBar.bind(null,v),1000);
//v.controlTimer = setTimeout(function (){v.controls=true;},3000); // 3 seconds before I enable the controls automatically. This way it doesn't auto appear during loading. 3s enough, right?
}
}
}
}
//if (session.fadein){
v.addEventListener('animationend', function(e) {
v.classList.remove("fadein"); // allows the video to fade in.
if (v.holder){
v.holder.classList.remove("fadein");
}
});
// v.classList.add("fadein"); // allows the video to fade in.
// if (v.holder){
// v.holder.classList.add("fadein");
// }
//}
applyMuteState(UUID);; // TODO; needs to be specific to screen video
v.usermuted = false;
v.addEventListener('volumechange',function(e){
var muteState = checkMuteState(UUID);
if (this.muted && (this.muted !== muteState)){
this.usermuted = true;
} else if (!this.muted){
this.usermuted = false;
}
});
if (session.screenShareStartPaused){ // we know this is a screen share already
pauseVideo(v, false);
}
if (session.director){
/* var wss = "";
if (session.customWSS || session.wssSetViaUrl){
if (session.customWSS && (session.customWSS!==true)){
wss = "&pie="+session.customWSS;
} else {
wss = "&wss="+session.wss;
}
} */
/*
var codecGroupFlag="";
if (session.codecGroupFlag){
codecGroupFlag = session.codecGroupFlag;
} */
/* var passAdd2="";
if (session.password){
if (session.defaultPassword===false){
passAdd2="&password="+session.password;
}
} */
if (session.customWSS && ("isScene" in msg) && (msg.isScene!==false)){
// this is a scene, so lets not show it.
} else {
var soloLink = soloLinkGenerator(session.rpcs[UUID].streamID);
createControlBoxScreenshare(UUID, soloLink, session.rpcs[UUID].streamID);
}
}
if (session.autorecord || session.autorecordremote){
log("AUTO RECORD START");
setTimeout(function(UUID, v){
if (session.director){
recordVideo(document.querySelector("[data-action-type='recorder-local'][data--u-u-i-d='"+UUID+"']"), null, session.recordLocal)
} else if (v.stopWriter || v.recording){
} else if (v.startWriter){
v.startWriter();
} else {
recordLocalVideo(null, session.recordLocal, v)
}
},2000, UUID, v);
}
setTimeout(processStats, 100, UUID);
}
function setupIncomingVideoTracking(v, UUID){ // video element.
if (session.directorList.indexOf(UUID)>=0){
v.muted=false;
}
v.onpause = (event) => { // prevent things from pausing; human or other
if (v.dataset.UUID && session.rpcs[v.dataset.UUID] && (session.rpcs[v.dataset.UUID].manualBandwidth === 0)){
return true;
}
if (!CtrlPressed){
warnlog("Video paused; force it to play again");
//return;
//session.audioCtx.resume();
//log("ctx resume");
event.currentTarget.play().then(_ => {
log("playing 6");
}).catch(error => {
warnlog("didnt play 1");
});
unPauseVideo(v);
} else if (Firefox && CtrlPressed){
log("CLICK 351");
var uid = event.currentTarget.dataset.UUID;
event.preventDefault();
if ("stats" in session.rpcs[uid]){
var [menu, innerMenu] = statsMenuCreator();
printViewStats(innerMenu, uid );
menu.interval = setInterval(printViewStats, session.statsInterval, innerMenu, uid);
}
event.stopPropagation();
return false;
}
return true;
}
/* v.onerror = function(event){
errorlog(event);
try{
warnlog("Vidieo element threw an error; going to reconnect it");
session.rpcs[UUID].videoElement.stop();
session.rpcs[UUID].videoElement.srcObject = null;
session.rpcs[UUID].videoElement.srcObject = session.rpcs[UUID].streamSrc; // replaecd with updateIncomingVideoElement these days
session.rpcs[UUID].videoElement.play();
setTimeout(function(){updateMixer();},1);
} catch(e){errorlog(e);}
} */
if (session.pip){
v.onloadedmetadata = function(){
if (!v.paused){
if (!(v.pip)){
v.pip=true;
toggleSystemPip(v, true);
}
}
}
}
v.addEventListener('resize', (e) => {
var v = e.target;
var aspectRatio = parseFloat(v.videoWidth/v.videoHeight) || 0;
log("resize event: "+aspectRatio);
if (!aspectRatio){
v.resetAR = true;
return;
} // if Audio only, then we don't want to set or update any aspect ratio.
if (v.resetAR){
log("ASPECT RATIO UNMUTED");
delete(v.resetAR);
v.dataset.aspectRatio = aspectRatio;
pokeIframeAPI("aspect-ratio", v.dataset.aspectRatio, v.dataset.UUID, v.dataset.sid);
setTimeout(function(){updateMixer();},1);
} else if (v.dataset.aspectRatio){
if (aspectRatio != parseFloat(v.dataset.aspectRatio)){
log("ASPECT RATIO CHANGED");
v.dataset.aspectRatio = aspectRatio;
pokeIframeAPI("aspect-ratio", v.dataset.aspectRatio, v.dataset.UUID, v.dataset.sid);
setTimeout(function(){updateMixer();},1); // We don't want to run this on the first resize? just subsequent ones.
}
} else {
log("NEW VIDEO ? ASPECT RATIO new");
v.dataset.aspectRatio = aspectRatio;
pokeIframeAPI("aspect-ratio", v.dataset.aspectRatio, v.dataset.UUID, v.dataset.sid);
setTimeout(function(){updateMixer();},1);
}
});
if (typeof session.volume == "number"){
v.volume = session.volume;
} else {
v.volume = 1.0; // play audio automatically
}
v.autoplay = true;
v.controls = session.showControls || false;
v.classList.add("tile");
v.setAttribute("playsinline","");
v.controlTimer = null;
v.dataset.menu = "context-menu-video";
if (!session.cleanOutput){
v.classList.add("task"); // this adds the right-click menu
}
if (document.getElementById("mainmenu")){
var m = getById("mainmenu");
m.remove();
}
if (session.director){
if (session.showControls===false){
v.controls = false;
} else {
v.controls = true;
}
var container = getById("videoContainer_"+UUID);
v.container = container;
v.disablePictureInPicture = false
v.setAttribute("controls","controls")
container.appendChild(v);
pokeIframeAPI("control-box-video-updated", v.id, UUID);
container.classList.add("hasMedia");
session.requestRateLimit(session.directorViewBitrate,UUID); /// limit resolution for director
v.title = "Hold CTRL or CMD (⌘) while clicking the video to open detailed stats";
if (session.beepToNotify) {
playtone();
}
} else if (session.scene!==false){
v.controls = session.showControls || false;
if (session.view){ // specific video to be played
v.style.display="block";
} else if (session.scene==="0"){ // auto plays, right?
v.style.display="block";
} else if ((session.scene!==false) && session.autoadd && session.rpcs[UUID].streamID && session.autoadd.includes(session.rpcs[UUID].streamID)){ /// session.autoadd
v.style.display="block"; // auto added because manually added.
} else { // group scene I guess; needs to be added manually
v.style.display="none";
session.rpcs[UUID].mutedStateScene = true;
}
} else if (session.roomid!==false){
if (session.cleanOutput){
v.controls = session.showControls || false;
} else if (session.studioSoftware) {
v.controls = session.showControls || false;
} else if (session.showControls!==null){
v.controls = session.showControls;
} else {
v.controls = true;
}
} else {
v.style.display="block";
}
v.addEventListener('click', function(e) { // show stats of video if double clicked
log("clicked");
try {
var uid = e.currentTarget.dataset.UUID;
if ((e.ctrlKey)||(e.metaKey)){
e.preventDefault();
if ("stats" in session.rpcs[uid]){
var [menu, innerMenu] = statsMenuCreator();
printViewStats(innerMenu, uid );
menu.interval = setInterval(printViewStats, session.statsInterval, innerMenu, uid);
}
e.stopPropagation();
return false;
} else if ("prePausedBandwidth" in session.rpcs[uid]){
unPauseVideo(e.currentTarget);
}
} catch(e){errorlog(e);}
});
if (session.statsMenu){
if ("stats" in session.rpcs[UUID]){
if (getById("menuStatsBox")){
clearInterval(getById("menuStatsBox").interval);
getById("menuStatsBox").remove();
}
var [menu, innerMenu] = statsMenuCreator();
printViewStats(innerMenu, UUID );
menu.interval = setInterval(printViewStats, session.statsInterval, innerMenu, UUID);
}
}
v.touchTimeOut = null;
v.touchLastTap = 0;
v.touchCount = 0;
v.addEventListener('touchend', function(event) {
if (session.disableMouseEvents){return;}
log("touched");
//document.ontouchup = null;
//document.onmouseup = null;
document.onmousemove = null;
document.ontouchmove = null;
var currentTime = new Date().getTime();
var tapLength = currentTime - v.touchLastTap;
clearTimeout(v.touchTimeOut);
if (tapLength < 500 && tapLength > 0) {
///
log("double touched");
v.touchCount+=1;
event.preventDefault();
if (v.touchCount<5){
v.touchLastTap = currentTime;
return false;
}
v.touchLastTap = 0;
v.touchCount=0;
log("double touched");
var uid = event.currentTarget.dataset.UUID;
if ("stats" in session.rpcs[uid]){
var [menu, innerMenu] = statsMenuCreator();
printViewStats(innerMenu, uid );
menu.interval = setInterval(printViewStats, session.statsInterval, innerMenu, uid);
}
event.stopPropagation();
return false;
//////
} else {
v.touchCount=1;
v.touchTimeOut = setTimeout(function(vv) {
clearTimeout(vv.touchTimeOut);
vv.touchLastTap = 0;
vv.touchCount=0;
}, 5000, v);
v.touchLastTap = currentTime;
}
});
if (session.rpcs[UUID].stats.info && ("remote" in session.rpcs[UUID].stats.info) && session.rpcs[UUID].stats.info.remote){
v.addEventListener("wheel", remoteFocusZoomRequest); // just remote focus
}
if (v.controls == false){
v.addEventListener("click", function () {
log("click 33");
if (v.paused){
log("PLAYING MANUALLY?");
v.play().then(_ => {
log("playing 7");
}).catch(warnlog);
}
});
if (session.nocursor==false){ // we do not want to show the controls. This is because MacOS + OBS does not work; so electron app needs this.
if (!(session.cleanOutput)){
if (session.studioSoftware) {
} else if (session.showControls === false) { // explicitly disabled; default null.
} else if (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1) {
} else {
if (v.controlTimer){
clearInterval(v.controlTimer);
}
v.controlTimer = setTimeout(showControlBar.bind(null,v),1000);
//v.controlTimer = setTimeout(function (){v.controls=true;},3000); // 3 seconds before I enable the controls automatically. This way it doesn't auto appear during loading. 3s enough, right?
}
}
}
}
//if (session.fadein){
v.addEventListener('animationend', function(e) {
v.classList.remove("fadein"); // allows the video to fade in.
if (v.holder){
v.holder.classList.remove("fadein");
}
});
//v.classList.add("fadein"); // allows the video to fade in.
// if (v.holder){
//// v.holder.classList.add("fadein");
// }
//}
applyMuteState(UUID);;
v.usermuted = false;
if (session.screenShareStartPaused && session.rpcs[UUID].screenShareState){
pauseVideo(v, false);
}
v.addEventListener('volumechange',function(e){
var muteState = checkMuteState(UUID);
if (this.muted && (this.muted !== muteState)){
this.usermuted = true;
} else if (!this.muted){
this.usermuted = false;
}
});
if (session.autorecord || session.autorecordremote){
log("AUTO RECORD START");
setTimeout(function(UUID, v){
if (session.director){
recordVideo(document.querySelector("[data-action-type='recorder-local'][data--u-u-i-d='"+UUID+"']"), null, session.recordLocal)
} else if (v.stopWriter || v.recording){
} else if (v.startWriter){
v.startWriter();
} else {
recordLocalVideo(null, session.recordLocal, v)
}
},2000, UUID, v);
}
setTimeout(processStats, 100, UUID);
}
function remoteFocusZoomRequest(event){
event.preventDefault();
var scale = parseFloat(event.deltaY * -0.001);
log(event.currentTarget);
if ((event.ctrlKey)||(event.metaKey)){ // focus
session.requestFocusChange(scale, event.currentTarget.dataset.UUID);
} else { // zoom
session.requestZoomChange(scale, event.currentTarget.dataset.UUID);
}
};
function mediaAudioTrackUpdated(UUID, streamID){
pokeIframeAPI("new-audio-track-added", true, UUID, streamID); // videoTrack is whether video. audio will be false I guess.
}
function mediaVideoTrackUpdated(UUID, streamID){
pokeIframeAPI("new-video-track-added", true, UUID, streamID); // videoTrack is whether video. audio will be false I guess.
}
function mediaSourceUpdated(UUID, streamID){
pokeIframeAPI("new-stream-added", true, UUID, streamID); // videoTrack is whether video. audio will be false I guess.
pokeAPI("streamAdded", streamID);
}
function showControlBar(vel){
try {
vel.controls=true;
} catch(e){errorlog(e);}
}
function createRichVideoElement(UUID){ // this function is used to check and generate a rich video element if needed
if (!session.rpcs[UUID].videoElement){
log("video element is being created and any media tracks added");
session.rpcs[UUID].videoElement = createVideoElement();
session.rpcs[UUID].videoElement.dataset.UUID = UUID;
session.rpcs[UUID].videoElement.id = "videosource_"+UUID; // could be set to UUID in the future
if (session.rpcs[UUID].streamID){
session.rpcs[UUID].videoElement.dataset.sid = session.rpcs[UUID].streamID;
}
if (session.rpcs[UUID].rotate !== false){
session.rpcs[UUID].videoElement.rotated = session.rpcs[UUID].rotate;
}
session.rpcs[UUID].videoElement.addEventListener("playing", (e)=>{
try {
var bigPlayButton = document.getElementById("bigPlayButton");
if (bigPlayButton){
bigPlayButton.parentNode.removeChild(bigPlayButton);
}
} catch(e){}
resetupAudioOut(e.target, true);
try {
if (session.pip){
if (v.readyState >= 3){
if (!(v.pip)){
v.pip=true;
toggleSystemPip(v, true);
}
}
}
} catch(e){}
}, { once: true });
if (session.rpcs[UUID].mirrorState){
applyMirrorGuest(session.rpcs[UUID].mirrorState, session.rpcs[UUID].videoElement);
} else if (session.rpcs[UUID].mirrorState===false){
applyMirrorGuest(session.rpcs[UUID].mirrorState, session.rpcs[UUID].videoElement);
}
if (session.posterImage){
session.rpcs[UUID].videoElement.poster = session.posterImage;
}
setupIncomingVideoTracking(session.rpcs[UUID].videoElement, UUID);
pokeIframeAPI("video-element-created", "videosource_"+UUID, UUID);
}
return session.rpcs[UUID].videoElement;
}
function updateVolume(update=false){
if (session.audioGain!==false){
if (update){
if (session.roomid){
var pswd = session.password || "";
generateHash(session.streamID + session.roomid + pswd + session.salt, 6).then(function(hash) {
setStorage("micVolume_"+hash, session.audioGain, hours=6);
});
}
}
if (session.audioGain === 0){
getById("header").classList.add('orange');
getById("head7").classList.remove('hidden');
} else {
getById("header").classList.remove('orange');
getById("head7").classList.add('hidden');
}
} else {
var pswd = session.password || "";
generateHash(session.streamID + session.roomid + pswd + session.salt, 6).then(function(hash) {
var volume = getStorage("micVolume_"+hash);
if (volume !== ""){
if (parseInt(volume) === 0){
getById("header").classList.add('orange');
getById("head7").classList.remove('hidden');
} else if (parseInt(volume)){
getById("header").classList.remove('orange');
getById("head7").classList.add('hidden');
} else {
return;
}
session.audioGain = parseInt(volume);
var vol = parseFloat(session.audioGain/100) || 0;
for (var waid in session.webAudios){ // TODO: EXCLUDE CURRENT TRACK IF ALREADY EXISTS ... if (trackid === wa.id){..
log("Adjusting Gain; only track 0 in all likely hood, unless more than track 0 support is added.");
session.webAudios[waid].gainNode.gain.setValueAtTime(vol, session.webAudios[waid].audioContext.currentTime);
}
}
});
}
}
function hideHomeCheck(){
if (session.hidehome){
getById("logoname").classList.add("permahide");
getById("container-1").classList.add("permahide");
getById("container-4").classList.add("permahide");
getById("dropButton").classList.add("permahide");
getById("head1").classList.add("permahide");
if ((session.permaid === false) && (session.roomid == false)){
getById("mainmenu").classList.add("permahide");
} else {
getById("mainmenu").classList.remove("permahide");
}
getById("audioScreenCaptureDocs").classList.add("permahide");
getById("audioScreenCaptureDocs2").classList.add("permahide");
getById("translateButton").classList.add("permahide");
getById("calendarButton").classList.add("permahide");
getById("info").classList.add("permahide");
getById("helpbutton").classList.add("permahide");
}
if (urlParams.has('headertitle')){
let pageTitle = urlParams.get('headertitle') || "";
pageTitle = decodeURIComponent(pageTitle) || "";
document.title = pageTitle
getById("metaTitle").content = pageTitle;
}
if (urlParams.has('favicon')){
let favicon = "";
if (urlParams.get('favicon')){
favicon = decodeURIComponent(urlParams.get('favicon')) || "";
}
getById("favicon1").href = favicon;
getById("favicon2").href = favicon;
getById("favicon3").href = favicon;
}
}
// toggleQualityDirector(1200, this.dataset.UUID, this)
function switchModes(state=null){
if (state===null){
session.switchMode = !session.switchMode;
} else {
session.switchMode = state;
}
if (session.switchMode){
getById("directorlayout").classList.add("hidden");
getById("gridlayout").classList.remove("hidden");
updateMixer();
} else {
getById("directorlayout").classList.remove("hidden");
getById("gridlayout").classList.add("hidden");
for (var UUID in session.rpcs){
if (session.rpcs[UUID].videoElement){
session.rpcs[UUID].videoElement.style = "";
session.rpcs[UUID].videoElement.alreadyAdded = false;
var target = document.querySelector("#container_"+UUID+" .controlVideoBox");
if (target){
target.prepend(session.rpcs[UUID].videoElement);
if (session.signalMeter){
if (session.rpcs[UUID].signalMeter){
target.appendChild(session.rpcs[UUID].signalMeter);
}
}
if (session.batteryMeter){
if (session.rpcs[UUID].batteryMeter){
target.appendChild(session.rpcs[UUID].batteryMeter);
}
}
if (session.rpcs[UUID].voiceMeter){
target.appendChild(session.rpcs[UUID].voiceMeter);
}
if (session.rpcs[UUID].remoteMuteElement){
target.appendChild(session.rpcs[UUID].remoteMuteElement);
}
}
}
}
if (session.videoElement){
session.videoElement.style = "";
session.videoElement.alreadyAdded = false;
if (session.showDirector == true) {
var target = document.querySelector("#videoContainer_director");
if (target && session.videoElement){
target.prepend(session.videoElement);
}
} else if ((session.videoElement.srcObject && session.videoElement.srcObject.getTracks().length) || (getById("press2talk").dataset.enabled == true)){
getById("miniPerformer").prepend(session.videoElement);
}
}
if (session.screenShareElement){
session.screenShareElement.style = "";
session.screenShareElement.alreadyAdded = false;
if (session.showDirector == true) {
var target = document.querySelector("#videoScreenContainer_director");
if (target && session.screenShareElement && session.screenShareElement.srcObject && session.screenShareElement.srcObject.getTracks().length){
target.prepend(session.screenShareElement);
}
} else if ((session.screenShareElement.srcObject && session.screenShareElement.srcObject.getTracks().length) || (getById("press2talk").dataset.enabled == true)){
getById("miniPerformer").prepend(session.videoElement);
}
}
applyQualityDirector();
}
}
var updateMixerTimer = null;
var updateMixerActive = false;
//var cleanupTimeout = null;
function updateMixer(e=false){
var controlBar = document.getElementById("subControlButtons");
if (controlBar && controlBar.dragElement && !controlBar.isDragging){
let vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
let vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
// Calculate real boundaries (parent position: fixed issues)
let topOffset = 0;
let leftOffset = 0;
let elementOffset = controlBar;
while (elementOffset) {
topOffset += elementOffset.offsetTop;
leftOffset += elementOffset.offsetLeft;
elementOffset = elementOffset.offsetParent;
}
let realX = controlBar.xOffset + leftOffset;
let realY = controlBar.yOffset + topOffset;
let maxX = vw - controlBar.offsetWidth;
let maxY = vh - controlBar.offsetHeight;
if (realX > maxX) {
controlBar.xOffset = maxX - leftOffset;
} else if (realX < 0) {
controlBar.xOffset = 0 - leftOffset;
}
if (realY > maxY) {
controlBar.yOffset = maxY - topOffset;
} else if (realY < 0) {
controlBar.yOffset = 0 - topOffset;
}
controlBar.style.transform = `translate(${controlBar.xOffset}px, ${controlBar.yOffset}px)`;
}
if (session.manual === true){return;}
else if (!session.switchMode && session.director){return;}
else if (session.windowed){return;}
clearInterval(updateMixerTimer);
if (updateMixerActive){
if (session.mobile){
updateMixerTimer = setTimeout(function(){updateMixer();},200);
} else {
updateMixerTimer = setTimeout(function(){updateMixer();},50);
}
return;
}
updateMixerActive=true;
log("updating mixer");
//console.log((new Error()).stack); // useful for breakpoints; finding what called updateMixer.
try{
updateMixerRun(e);
// clearInterval(cleanupTimeout);
// cleanupTimeout = setTimeout(function(){deleteOldMedia();},60000);
} catch(e){}
if (session.mobile){
setTimeout(function(){updateMixerActive=false;},500);
} else {
setTimeout(function(){updateMixerActive=false;},100);
}
}
function updateMixerRun(e=false){ // this is the main auto-mixing code. It's a giant function that runs when there are changes to screensize, video track statuses, etc.
try {
if (session.switchMode){}
else if (session.director){return;}
else if (session.manual === true){return;}
var header = getById("header");
var playarea = getById("gridlayout");
var hi = header.offsetHeight;
var w = window.innerWidth;
if (session.widget){
w *= 0.75;
try {
let widget = document.getElementById("widget");
if (!widget){
widget = document.createElement("iframe");
widget.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;midi;";
widget.id = "widget";
widget.src = parseURL4Iframe(session.widget);
log(widget.src);
document.body.appendChild(widget);
playarea.style.left = "0";
playarea.style.width = "75%";
}
widget.style.height = "calc(100% - " +hi + "px)";
widget.style.top = hi;
} catch(e){
errorlog(e);
}
}
var h = window.innerHeight - hi;
if (session.dedicatedControlBarSpace || window.innerHeight<=700 ){ // # This needs to be reviewed.
if (session.dedicatedControlBarSpace!==false){
if (document.getElementById("subControlButtons") && !session.overlayControls){
if (!document.getElementById("subControlButtons").yOffset || (document.getElementById("subControlButtons").yOffset>-10)){
h = window.innerHeight - hi - document.getElementById("subControlButtons").offsetHeight;
}
}
}
}
var arW = 16.0;
var arH = 9.0;
if (session.aspectRatio){
if (session.aspectRatio==1){
arW = 9.0;
arH = 16.0;
} else if (session.aspectRatio==2){
arW = 12.0; // square root; cause why not.
arH = 12.0;
} else if (session.aspectRatio==3){
arW = 12.0; // square root; cause why not.
arH = 9.0;
}
}
var groups = [...session.group];
if (session.groupView.length){
groups.push(...session.groupView)
}
var sssid = false;
var soloVideo = false;
if (session.infocus===true){
soloVideo = true;
} else if (session.infocus && (session.infocus in session.rpcs)){ // if the infocus stream is connected
if (groups.length || session.allowNoGroup){
try {
if (groups.some(item => session.rpcs[session.infocus].group.includes(item))){
soloVideo = session.infocus;
}
} catch(e){errorlog(e);}
} else {
soloVideo = session.infocus;
}
} else if (session.infocus2===true){
sssid = session.streamID;
} else if (session.infocus2 && (session.infocus2 in session.rpcs)){ // if the infocus2 stream is connected
if (groups.length || session.allowNoGroup){
try {
if (groups.some(item => session.rpcs[session.infocus2].group.includes(item))){
sssid = session.rpcs[session.infocus2].streamID;
}
} catch(e){errorlog(e);}
} else {
sssid = session.rpcs[session.infocus2].streamID;
}
}
var ww = w/arW;
var hh = h/arH;
var mediaPool = [];
var mediaPool_invisible = [];
var miniPreview = session.minipreview;
if (miniPreview && session.layout && session.streamID && (session.streamID in session.layout)){
miniPreview = false;
if (session.videoElement.container && (session.videoElement.container.id == "minipreview")){
delete session.videoElement.container;
}
if (document.getElementById("minipreview")){
document.getElementById("minipreview").remove();
}
}
if (session.iframeEle && (session.iframeEle.style.display!=="none")){ // local feed
if (session.order!==false){
session.iframeEle.order=session.order;
} else {
session.iframeEle.order=0;
}
if (session.activeSpeaker && (!session.activelySpeaking)){
mediaPool_invisible.push(session.iframeEle);
} else {
mediaPool.push(session.iframeEle);
}
}
if (session.videoElement && (session.videoElement.src || session.videoElement.srcObject)){ // I, myself, exist
if (session.videoElement.style.display!=="none"){ // local feed
if (miniPreview && (soloVideo!==true)){
/* session.videoElement.onclick = function(){
if (soloVideo === true){
soloVideo = false;
} else {
soloVideo = true;
log("session: myself");
}
setTimeout(()=>updateMixer(),10);
}; */
} else {
if (session.order!==false){
session.videoElement.order=session.order;
} else {
session.videoElement.order=0;
}
if (session.activeSpeaker && (!session.activelySpeaking)){
//mediaPool_invisible.push(session.videoElement);
//} else if (session.videoElement && session.videoElement.srcObject && (session.videoElement.srcObject.getTracks().length === 0)){
// do not show a video element if its completely empty.
} else if (session.videoElement && session.videoElement.srcObject && (session.videoElement.srcObject.getVideoTracks().length === 0)){
// do not show a video element if its completely empty.
} else if (soloVideo && soloVideo!==true){
//
} else {
mediaPool.push(session.videoElement);
}
}
}
}
if (session.screenShareState && session.screenShareElement){ // I, myself, exist
if (!session.screenShareElementHidden){
if (session.order!==false){
session.screenShareElement.order=session.order;
} else {
session.screenShareElement.order=0;
}
if (soloVideo!==false){
session.screenShareElement.style.display="none";
} else if (session.activeSpeaker && (!session.activelySpeaking)){
session.screenShareElement.style.display="none";
} else {
mediaPool.push(session.screenShareElement);
}
}
}
if ((soloVideo) && (soloVideo in session.rpcs)){ // remote guest being full screened; infocus == UUID
mediaPool = []; // remove myself from fullscreen
if (iOS || iPad){
if (!miniPreview){
miniPreview = 1;
}
}
for (var j in session.rpcs){
if (groups.length || session.allowNoGroup){
try {
if (!(groups.some(item => session.rpcs[j].group.includes(item)))){
continue;
}
} catch(e){errorlog(e);}
}
if (j != soloVideo){ // this remote guest is NOT in focus
try {
if (session.rpcs[j].videoElement && session.rpcs[j].videoElement.style.display!=="none" ){ // Add it if not hidden
if (document.pictureInPictureElement && document.pictureInPictureElement.id && (document.pictureInPictureElement.id == session.rpcs[j].videoElement.id)){
var bitratePIP = parseInt(session.zoomedBitrate/4);
//warnUser("GOOD");
session.requestRateLimit(bitratePIP, j);
} else {
session.requestRateLimit(0, j); // disable the video of non-fullscreen videos
}
if (session.rpcs[j].videoElement.srcObject && session.rpcs[j].videoElement.srcObject.getAudioTracks().length){
// mediaPool_invisible.push(session.rpcs[j].videoElement);
}
} else if (session.rpcs[j].videoElement){
session.requestRateLimit(0, j, true); // disable the video of non-fullscreen videos
}
} catch(e){errorlog(e);}
} else { // remote guest is in-focus video
////////
try {
if (session.rpcs[j].iframeEle){
if (session.rpcs[j].videoElement && (session.rpcs[j].videoElement.srcObject.getAudioTracks().length)){
//mediaPool_invisible.push(session.rpcs[j].videoElement);
}
session.requestRateLimit(0, j);
mediaPool.push(session.rpcs[j].iframeEle);
continue;
} else if (session.rpcs[j].videoElement){
if (session.rpcs[j].order!==false){
session.rpcs[j].videoElement.order=session.rpcs[j].order;
} else {
session.rpcs[j].videoElement.order=0;
}
///////////
//if (session.activeSpeaker && (!session.rpcs[j].defaultSpeaker)){ // not the active speaker
//mediaPool_invisible.push(session.rpcs[j].videoElement);
// session.requestRateLimit(0, j); // keep audio good, but disable video
//} else {
var totalRoomBitrate = session.totalRoomBitrate;
if ((session.controlRoomBitrate!==false) && (session.controlRoomBitrate!==true)){
totalRoomBitrate = Math.min(session.controlRoomBitrate, totalRoomBitrate);
}
var targetBitrate = session.zoomedBitrate;
if (totalRoomBitrate>session.zoomedBitrate){
targetBitrate = totalRoomBitrate;
}
mediaPool.push(session.rpcs[j].videoElement); // active speaker
session.rpcs[j].videoElement.style.visibility = "visible";
//if ((session.rpcs[j].targetBandwidth!==-1) && (session.rpcs[j].targetBandwidth session.rpcs[j].group.includes(item)))){
continue;
}
} catch(e){errorlog(e);}
}
try {
if (session.rpcs[j].videoElement && (session.rpcs[j].videoElement.style.display!=="none")){ // Add it if not hidden
if (document.pictureInPictureElement && document.pictureInPictureElement.id && (document.pictureInPictureElement.id == session.rpcs[j].videoElement.id)){
var bitratePIP = parseInt(session.zoomedBitrate/4);
session.requestRateLimit(bitratePIP, j);
//warnUser("GOOD");
} else {
session.requestRateLimit(0, j); // disable the video of non-fullscreen videos
}
// mediaPool_invisible.push(session.rpcs[j].videoElement);
} else if (session.rpcs[j].videoElement){
session.requestRateLimit(0, j, true); // other videos are disabled when previewing yourself, but audio retained
}
} catch(e){errorlog(e);}
}
} else {
var roomQuality = 0;
var screenShareTotal = 0;
for (var i in session.rpcs){
if (session.rpcs[i]===null){continue;}
if (groups.length || session.allowNoGroup){
try {
if (!(groups.some(item => session.rpcs[i].group.includes(item)))){
continue;
}
} catch(e){errorlog(e);}
}
if (session.rpcs[i].videoElement){ // remote feeds
if (session.rpcs[i].videoElement.style.display!=="none"){
if (session.rpcs[i].videoElement.srcObject && session.rpcs[i].videoElement.srcObject.getVideoTracks().length){ // only count videos with actual video tracks; audio-only excluded
if (session.rpcs[i].videoMuted){
// it's video muted
// mediaPool_invisible.push(session.rpcs[i].videoElement); // skipped later on
} else if (session.rpcs[i].directorVideoMuted){
// it's muted by the director, so likely disabled.
// mediaPool_invisible.push(session.rpcs[i].videoElement); // skipped later on
} else if (session.rpcs[i].virtualHangup){
} else if (session.rpcs[i].bandwidthMuted){
} else if (session.rpcs[i].videoElement.style.opacity==="0"){
// mediaPool_invisible.push(session.rpcs[i].videoElement); // skipped later on
} else {
roomQuality+=1;
if (session.rpcs[i].screenShareState){
screenShareTotal+=1;
}
}
}
}
}
}
if (session.broadcast !==false){
if (session.layout && (session.streamID in session.layout)){
// skip
} else {
if (roomQuality>0){
if (session.nopreview!==false){
for (var i=0;i0){
roomBitrate = parseInt(totalRoomBitrate/(roomQuality-screenShareTotal));
if (session.totalSceneBitrate){
sceneBitrate = parseInt(session.totalSceneBitrate/(roomQuality-screenShareTotal));
if (session.bitrate!==false){
sceneBitrate = Math.min(session.bitrate, sceneBitrate);
}
}
}
} else if (screenShareTotal){
try {
if ((session.roomid!==false) && (session.scene===false)){
if ((roomQuality-screenShareTotal)<=0){
roomBitrate = totalRoomBitrate;
screenShareBitrate = totalRoomBitrate;
} else {
screenShareBitrate = totalRoomBitrate/(1.5*screenShareTotal);
roomBitrate = parseInt((totalRoomBitrate - screenShareBitrate) /(roomQuality-screenShareTotal));
}
} else if (session.totalSceneBitrate!==false){
if ((roomQuality-screenShareTotal)<=0){
sceneBitrate = session.totalSceneBitrate;
if (session.bitrate!==false){
sceneBitrate = Math.min(session.bitrate, sceneBitrate);
}
screenShareBitrate = sceneBitrate;
} else {
screenShareBitrate = parseInt(totalRoomBitrate/(1.5*screenShareTotal));
sceneBitrate = parseInt((totalRoomBitrate - screenShareBitrate)/(roomQuality-screenShareTotal));
if (session.bitrate!==false){
sceneBitrate = Math.min(session.bitrate, sceneBitrate);
screenShareBitrate = Math.min(session.bitrate, screenShareBitrate);
}
}
} else {
screenShareBitrate = false;
}
} catch(e){errorlog(e);}
} else {
roomBitrate = parseInt(totalRoomBitrate/roomQuality);
if (session.totalSceneBitrate){
sceneBitrate = parseInt(session.totalSceneBitrate/roomQuality);
if (session.bitrate!==false){
sceneBitrate = Math.min(session.bitrate, sceneBitrate);
}
}
}
if (session.minimumRoomBitrate){
if (session.totalRoomBitrate && (roomBitratesession.totalRoomBitrate){
roomBitrate = session.totalRoomBitrate;
}
}
if (session.totalSceneBitrate && (sceneBitratesession.totalSceneBitrate){
sceneBitrate = session.totalSceneBitrate;
}
}
}
var i = null;
var countOrder = 0;
try{
var RPCSkeys = Object.keys(session.rpcs); // default sorting type: time added; //RPCSkeys.sort();
} catch(e){return;}
for (var keyIndex = 0; keyIndex session.rpcs[i].group.includes(item)))){
if (session.scene!==false){
if (session.groupAudio){
session.requestRateLimit(session.hiddenSceneViewBitrate, i, false);
} else {
session.requestRateLimit(session.hiddenSceneViewBitrate, i, true); // hidden. I dont want it to be super low, for video quality reasons.
session.rpcs[i].mutedStateMixer = true;
}
if (!session.hiddenSceneViewBitrate){
session.rpcs[i].videoElement.nogb = 2;
}
} else {
if (session.groupAudio){
session.requestRateLimit(0, i, false);
} else {
session.requestRateLimit(0, i, true); // w/e This is not in OBS, so we just set it as low as possible. Shoudln't exist really unless loading?
session.rpcs[i].mutedStateMixer = true;
}
}
applyMuteState(i);
continue;
}
} catch(e){}
}
applyMuteState(i);
var doNotPush = false;
if (session.rpcs[i].iframeEle){
if (session.rpcs[i].iframeEle.style.display=="none"){
// pass
} else if (session.rpcs[i].iframeEle.style.opacity==="0"){
// pass
} else {
session.rpcs[i].iframeEle.style.visibility = "visible";
if (session.rpcs[i].order!==false){
session.rpcs[i].iframeEle.order=session.rpcs[i].order;
} else {
session.rpcs[i].iframeEle.order=0;
}
try{
if (session.activeSpeaker && (!session.rpcs[i].defaultSpeaker)){
mediaPool_invisible.push(session.rpcs[i].iframeEle); // TODO: this needs validation; will the iframe be maintained if activer speaker is going? do we even want this?
/* } else if (session.rpcs[i].iframeEle.dataset.meshcast){ //////// MESH CAST ONLY LOGIC
if (session.rpcs[i].iframeEle.contentDocument && session.rpcs[i].iframeEle.contentDocument.querySelectorAll("video").length){
if (session.rpcs[i].iframeVideo){
mediaPool.push(session.rpcs[i].iframeVideo);
} else if (session.rpcs[i].iframeEle.contentDocument.querySelectorAll("video").length){
session.rpcs[i].iframeVideo = session.rpcs[i].iframeEle.contentDocument.querySelectorAll("video")[0];
session.rpcs[i].iframeVideo.id="meshcast_"+i;
//errorlog("THIS IS GOOD");
mediaPool.push(session.rpcs[i].iframeVideo);
} else {
//errorlog("No video yet");
}
} else { // this is a problem is not on the same domain.
if (!document.getElementById("iframe_"+i)){
if (document.getElementById("hiddenElements")){
document.getElementById("hiddenElements").append(session.rpcs[i].iframeEle);
} else {
document.body.append(session.rpcs[i].iframeEle);
}
if (session.rpcs[i].iframeVideo){
mediaPool.push(session.rpcs[i].iframeVideo);
} else if (session.rpcs[i].iframeEle.contentDocument.querySelectorAll("video").length){
session.rpcs[i].iframeVideo = session.rpcs[i].iframeEle.contentDocument.querySelectorAll("video")[0];
session.rpcs[i].iframeVideo.id="meshcast_"+i;
mediaPool.push(session.rpcs[i].iframeVideo);
} else {
//errorlog("No video yet");
}
} else {
if (session.rpcs[i].iframeVideo){
mediaPool.push(session.rpcs[i].iframeVideo);
} else {
//errorlog("Does not support contentDocument or something");
}
}
} */
} else { ///////// MESH CAST LOGIC ENDS HERE
//errorlog("not meshcast");
mediaPool.push(session.rpcs[i].iframeEle);
}
} catch(e){errorlog(e);}
}
}
if (session.rpcs[i].imageElement){
if (session.rpcs[i].videoElement && (session.rpcs[i].videoElement.srcObject.getAudioTracks().length)){ // is there audio?
// mediaPool_invisible.push(session.rpcs[i].videoElement); // include audio as hidden track;
}
if (session.rpcs[i].videoMuted || session.rpcs[i].directorVideoMuted || session.rpcs[i].virtualHangup || session.rpcs[i].bandwidthMuted){
continue;
}
if (session.rpcs[i].videoElement.style.display=="none"){ // currently this is considered the state of scenes. pertty dumb on my part.
continue;
}
if (session.rpcs[i].order!==false){
session.rpcs[i].imageElement.order=session.rpcs[i].order;
} else {
session.rpcs[i].imageElement.order=0;
}
if (session.activeSpeaker && (!session.rpcs[i].defaultSpeaker)){
// mediaPool_invisible.push(session.rpcs[i].imageElement);
} else {
mediaPool.push(session.rpcs[i].imageElement);
}
doNotPush = true;
}
if (session.rpcs[i].videoElement){ // remote feeds
//session.rpcs[i].targetBandwidth = -1;
if (session.rpcs[i].videoElement.style.opacity==="0"){
continue;
}
try{
session.rpcs[i].videoElement.style.visibility = "visible";
} catch(e){errorlog(e);}
if (session.rpcs[i].virtualHangup || session.rpcs[i].bandwidthMuted || session.rpcs[i].directorVideoMuted){
continue;
}
if (session.style && (session.style >= 2)){
if (session.rpcs[i].videoElement.srcObject && ((session.rpcs[i].videoElement.srcObject.getVideoTracks().length==0) || (session.rpcs[i].videoMuted))){
if (session.rpcs[i].videoElement.style.display=="none"){ // currently this is considered the state of scenes. pertty dumb on my part.
continue;
}
if (createStyleCanvas(i)){
applyStyleEffect(i);
}
if (session.rpcs[i].order!==false){
session.rpcs[i].canvas.order=session.rpcs[i].order;
} else {
session.rpcs[i].canvas.order=0;
}
if (session.activeSpeaker && (!session.rpcs[i].defaultSpeaker)){
// mediaPool_invisible.push(session.rpcs[i].canvas);
} else {
mediaPool.push(session.rpcs[i].canvas);
}
doNotPush = true;
//continue;
}
} else if (session.style==1){
if (session.rpcs[i].videoElement.srcObject && ((session.rpcs[i].videoElement.srcObject.getVideoTracks().length==0) || session.rpcs[i].videoMuted)){
//if (session.style==1){ // avatars and waveforms might be better done elsewhere? as a canvas effect even?
doNotPush = true;
//}
}
} else if (session.rpcs[i].videoElement.srcObject && ((session.rpcs[i].videoElement.srcObject.getVideoTracks().length==0) || session.rpcs[i].videoMuted)){
if (session.rpcs[i].screenShareState){
doNotPush = true;
}
}
//} else if (!session.directorList.indexOf(i)>=0){ // director is never audio-only. Video if need, yes, but not visualized-audio.
// if (session.rpcs[i].videoElement.srcObject && ((session.rpcs[i].videoElement.srcObject.getVideoTracks().length==0) || (session.rpcs[i].videoMuted)) && !session.rpcs[i].directorVideoMuted){
// continue;
// }
//}
session.rpcs[i].opacityMuted = "1";
if (session.rpcs[i].opacityDisconnect=="1"){
if (session.rpcs[i].videoElement){
session.rpcs[i].videoElement.style.opacity = "1";
}
}
if (session.rpcs[i].videoMuted){
if (session.rpcs[i].videoElement.srcObject.getAudioTracks().length==0){ // if no audio track, no point in removing the video track, since it will just stall out then.
continue; // easiest is to just not show anything if no video and no audio track.
}
if (session.rpcs[i].videoElement.srcObject){
session.rpcs[i].videoElement.srcObject.getVideoTracks().forEach(track=>{
session.rpcs[i].videoElement.srcObject.removeTrack(track);
session.rpcs[i].videoElement.load();
});
}
//continue; // currently disabling this, since we want to show it.
} else if (session.rpcs[i].virtualHangup || session.rpcs[i].bandwidthMuted || session.rpcs[i].directorVideoMuted){
continue
}
if (session.scene!==false){
if (session.sceneType === 3){ // order
countOrder+=1;
if (session.order === false){
if (countOrder==1){
session.rpcs[i].videoElement.style.display="block";
} else {
session.rpcs[i].videoElement.style.display="none";
}
} else if (session.order === countOrder){
session.rpcs[i].videoElement.style.display="block";
} else {
session.rpcs[i].videoElement.style.display="none";
}
}
}
if (session.rpcs[i].videoElement.style.display=="none"){ // Video is disabled; run at lowest
if (session.scene!==false){
session.requestRateLimit(session.hiddenSceneViewBitrate, i, true); // hidden. I dont want it to be super low, for video quality reasons.
if (!session.hiddenSceneViewBitrate){
session.rpcs[i].videoElement.nogb = 2;
}
} else {
session.requestRateLimit(0, i, true); // w/e This is not in OBS, so we just set it as low as possible. Shoudln't exist really unless loading?
}
} else if (session.scene!==false){ // max
if (sceneBitrate!==false){
if ((screenShareBitrate!==false) && session.rpcs[i].screenShareState){
session.requestRateLimit(screenShareBitrate, i); // well, screw that. Setting it to room quality.
} else {
session.requestRateLimit(sceneBitrate, i); // well, screw that. Setting it to room quality.
}
} else {
session.requestRateLimit(-1, i); // unlock.
}
if (session.rpcs[i].order!==false){
session.rpcs[i].videoElement.order=session.rpcs[i].order;
} else {
session.rpcs[i].videoElement.order=0;
}
if (session.activeSpeaker && (!session.rpcs[i].defaultSpeaker)){
if (!(session.rpcs[i].videoElement in mediaPool_invisible)){
// mediaPool_invisible.push(session.rpcs[i].videoElement);
} else {
errorlog("THIS SHOULD NOT HAPPEN; 650");
}
} else if (!doNotPush){
mediaPool.push(session.rpcs[i].videoElement);
}
} else if (session.roomid!==false){ // guests should see video at low bitrate, ie: 100kbps (not 35kbps like if disabled)
if (session.rpcs[i].order!==false){
session.rpcs[i].videoElement.order=session.rpcs[i].order;
} else {
session.rpcs[i].videoElement.order=0;
}
if (session.activeSpeaker && (!session.rpcs[i].defaultSpeaker)){
if (!(session.rpcs[i].videoElement in mediaPool_invisible)){
// mediaPool_invisible.push(session.rpcs[i].videoElement);
} else {
errorlog("THIS SHOULD NOT HAPPEN; 665");
}
} else if (!doNotPush){
mediaPool.push(session.rpcs[i].videoElement);
}
if ((session.roomid==="") && (session.bitrate)){
// we will let the URL specified bitrate hold, since this isn't a real room.
session.requestRateLimit(-1, i);
} else {
if ((screenShareBitrate!==false) && session.rpcs[i].screenShareState){
session.requestRateLimit(screenShareBitrate, i); // well, screw that. Setting it to room quality.
} else {
session.requestRateLimit(roomBitrate, i); // well, screw that. Setting it to room quality.
}
}
} else { // view=xx,yy or whatever. This should be highest quality.
if (session.rpcs[i].order!==false){
session.rpcs[i].videoElement.order=session.rpcs[i].order;
} else {
session.rpcs[i].videoElement.order=0;
}
if (session.activeSpeaker && (!session.rpcs[i].defaultSpeaker)){
if (!(session.rpcs[i].videoElement in mediaPool_invisible)){
// mediaPool_invisible.push(session.rpcs[i].videoElement);
} else {
errorlog("THIS SHOULD NOT HAPPEN; 684");
}
} else if (!doNotPush){
mediaPool.push(session.rpcs[i].videoElement);
}
if (sceneBitrate){
session.requestRateLimit(sceneBitrate, i);
} else if ((session.screenShareBitrate!==false) && session.rpcs[i].screenShareState){ // session.screenShareBitrate is non-room
session.requestRateLimit(session.screenShareBitrate, i); // well, screw that. Setting it to room quality.
} else {
session.requestRateLimit(-1, i);
}
}
if (session.rpcs[i].videoElement.nogb==2){
session.rpcs[i].videoElement.nogb = 1;
session.rpcs[i].videoElement.classList.add("nogb");
} else if (session.rpcs[i].videoElement.nogb==1){
session.rpcs[i].videoElement.nogb = 0;
session.rpcs[i].videoElement.classList.remove("nogb");
}
}
}
}
if (session.broadcastIFrame && session.broadcastIFrame.src){ // keep alive iframes whennot visible. i think
if (!mediaPool.length){
mediaPool.push(session.broadcastIFrame);
}
}
if (document.fullscreenElement) {
log("FULL SCREEN: "+document.fullscreenElement.id);
for (var i=0;i1){
var BB = 0;
var rw = 1;
var rh = 1;
var NW;
var NH;
var current;
for (NW=1; NW <= mpl; NW++){
NH = Math.ceil(mpl/NW);
var www = ww/NW;
var hhh = hh/NH;
if (www>hhh){
current = hhh * hhh * (mpl/(NW*NH));
} else {
current = www * www * (mpl/(NW*NH));
}
if (current>=BB){
BB = current;
rw = NW;
rh = NH;
}
if (mediaPool[NW-1]){
//if (mediaPool[NW-1].tagName == "VIDEO"){
if (mediaPool[NW-1].dataset.UUID){
if (mediaPool[NW-1].dataset.UUID in session.rpcs){
if (session.rpcs[mediaPool[NW-1].dataset.UUID].screenShareState){
sscount+=1;
sssid = mediaPool[NW-1].dataset.sid;
}
}
} else if (("id" in mediaPool[NW-1]) && (mediaPool[NW-1].id == "screensharesource") && session.notifyScreenShare){
sscount+=1;
sssid = mediaPool[NW-1].dataset.sid;
}
}
}
} else { var rw=1; var rh=1;}
if (sscount>1){
sssid = false; // lets not maximize if more than one screen share.
}
}
} catch(e){
errorlog(e);
sssid = false
}
var customLayout=false;
if (!session.notifyScreenShare && (session.scene!==false)){
// this is a scene, so lets assume &smallshare will disable larger screen shares since there is no one to screen share.
} else if (sssid && !session.layout){
customLayout = {};
if (mediaPool.length>=12){
customLayout[sssid] = {"x":0,"y":10,"w":90,"h":90, "c": false};
} else if (mediaPool.length>=10){
customLayout[sssid] = {"x":0,"y":10,"w":100,"h":90, "c": false};
} else if (mediaPool.length>=8){
customLayout[sssid] = {"x":0,"y":20,"w":80,"h":80, "c": false};
} else if (mediaPool.length==7){
customLayout[sssid] = {"x":0,"y":0,"w":83.33333,"h":100, "c": false};
} else if (mediaPool.length==5){
customLayout[sssid] = {"x":0,"y":0,"w":80,"h":100, "c": false};
} else if (mediaPool.length==6){
customLayout[sssid] = {"x":0,"y":0,"w":80,"h":100, "c": false};
} else {
customLayout[sssid] = {"x":0,"y":0,"w":80,"h":100, "c": false};
}
var posCount = 0;
for (var i = 0; i=10){
if (posCount<10){
customLayout[mediaPool[i].dataset.sid] = {"x":10*posCount,"y":0,"w":10,"h":10, "c":true};
} else {
customLayout[mediaPool[i].dataset.sid] = {"x":90,"y":(posCount-9)*10,"w":10,"h":10, "c":true};
}
} else {
customLayout[mediaPool[i].dataset.sid] = {"x":80,"y":posCount*20,"w":20,"h":20, "c":true}; //
}
posCount+=1;
}
}
try {
if (!skip){
var childNodes = playarea.childNodes;
for (var n=0;n74){
miniPerformerX = 74;
}
}
container.style.left = miniPerformerX + "%";
} else if (session.leftMiniPreview!==false){
container.style.left = session.leftMiniPreview + "%";
togglePreview.style.left = session.leftMiniPreview + "%";
} else if (session.widget){
container.style.right = "25%";
togglePreview.style.right = "25%";
} else {
container.style.right = "2vw";
togglePreview.style.right = "2vw";
}
container.appendChild(session.videoElement);
session.videoElement.container = container;
playarea.appendChild(container);
togglePreview.innerHTML = '';
if (!session.previewToggleState){
container.classList.toggle("hidden");
togglePreview.classList.toggle("blinded");
}
if (!(iOS || iPad)){
playarea.appendChild(togglePreview);
}
togglePreview.onclick = function(event){
event.preventDefault();
event.stopPropagation();
getById("minipreview").classList.toggle("hidden");
this.classList.toggle("blinded");
session.previewToggleState = !session.previewToggleState;
return false;
};
makeMiniDraggableElement(container);
container.id = "minipreview";
}
container.style.width = "18%";
//container.style.display = "flex";
container.style.zIndex = "3";
container.style.margin = "0";
container.style.position ="absolute";
container.style.cursor = "pointer";
container.style.border = "2px #BBB solid";
container.style.height = "block";
applyMirror(session.mirrorExclude);
} else if (soloVideo===true){
if (document.getElementById("minipreview")){
container = document.getElementById("minipreview");
container.style.height = "100%";
//container.style.transform = "block";
//container.style.transformOrigin = "unset";
}
}
if (session.ruleOfThirds){
if (container && (container.id == "minipreview") && !container.svg){
var svg = document.createElement("img");
svg.src = session.ruleOfThirds;
svg.style.width = "100%";
svg.style.height = "100%";
svg.style.position= "absolute";
svg.style.left = "0";
svg.style.top = "0";
container.svg = svg;
container.appendChild(svg);
}
}
container = null; // clear reference
}
} else if (session.streamSrc && !session.videoElement.srcObject){
warnlog("THIS SHOULD NOT HAPPEN; 2067");
}
}
}
try{
if (session.slots){
var slotArray = [];
mediaPool.forEach(vid=>{
if (vid.slotBlank){
vid.slotBlank=false;
vid.slot=0;
}
if (("slot" in vid) && vid.slot){
if (!slotArray.includes(parseInt(vid.slot))){
slotArray.push(parseInt(vid.slot));
} else {
vid.slot=0;
//mediaPool_invisible.push(vid);
//var index = mediaPool.indexOf(vid);
//if (index > -1) {
// mediaPool.splice(index, 1);
//}
}
}
})
var slotCounter = 1;
mediaPool.reverse()
var j = mediaPool.length;
while (j--){
if (!("slot" in mediaPool[j]) || ( mediaPool[j].slot=="0") || !mediaPool[j].slot){
while (slotArray.includes(slotCounter)){
slotCounter+=1;
}
slotArray.push(slotCounter);
mediaPool[j].slot = slotCounter;
mediaPool[j].slotBlank = true;
}
if (!("slot" in mediaPool[j]) || !parseInt(mediaPool[j].slot) || (mediaPool[j].slot=="0") || !mediaPool[j].slot || (session.slots{
if (vid){
try {
vid.style.width = "0px";
vid.style.height = "0px";
vid.style.top = "0px";
vid.style.left = "0px";
vid.isInvisible = true;
if (vid.alreadyAdded && vid.alreadyAdded==true){
vid.alreadyAdded=false;
return;
} else if (vid.dataset.doNotMove){
return;
}
playarea.appendChild(vid);
} catch(e){errorlog(e);}
}
});
} catch(e){errorlog(e);}
var layout = false;
if (customLayout || session.layout){
layout = session.layout || customLayout;
layout = {...layout};
if (layout[""]){
for(var i=0;i';
getById("retryimage").src = decodeURIComponent(session.waitImage);
getById("retryimage").onerror = function(){this.style.display='none';};
if (session.cover) {
getById("retryimage").style.objectFit = "cover";
}
}
getById("retryimage").style.display = "block";
}, session.waitImageTimeout);
}
} else if (session.waitImage){
try {
clearTimeout(session.waitImageTimeoutObject);
session.waitImageTimeoutObject = false;
getById("retryimage").style.display = "none";
} catch(e){}
}
mediaPool.forEach(vid=>{
try {
if (!vid || !("id" in vid)){
errorlog(vid);
return;
}
if (session.slots){
if (("slot" in vid) && parseInt(vid.slot)){
i = parseInt(vid.slot) - 1;
if(i<0){return;}
} else {
return;
}
}
var offsetx=0;
if (i!==0){
if (Math.ceil((i+0.01)/rw)==rh){
if (mpl%rw){
offsetx = Math.max(((rw - mpl%rw)*(w/rw))/2,0);
}
}
}
var cover = session.cover
var borderOffset = session.border || 0;
var videoMargin = session.videoMargin || 0;
var borderRadius = session.borderRadius || 0;
var borderColor = session.borderColor || "#000";
var fadein = session.fadein || false;
var backgroundMedia = session.defaultMedia || false;
var animated = session.animatedMoves || 0;
if (!borderOffset){
borderColor = "#0000";
}
if (layout){
if (!(vid.dataset.sid && (vid.dataset.sid in layout))){
vid.isInvisible = true;
if (vid.container){
vid.container.style.display = "none";
}
if (vid.dataset.UUID){
session.requestRateLimit(session.hiddenSceneViewBitrate, vid.dataset.UUID, false); // it's added already, so we know it needs sound. But lets d
}
return;
}
if ("borderThickness" in layout[vid.dataset.sid]){
borderOffset = layout[vid.dataset.sid].borderThickness || 0;
}
if ("animated" in layout[vid.dataset.sid]){
animated = layout[vid.dataset.sid].animated || 0;
if (animated===true){
animated = session.animatedMoves || 50;
}
}
if ("margin" in layout[vid.dataset.sid]){
videoMargin = layout[vid.dataset.sid].margin || 0;
}
if ("rounded" in layout[vid.dataset.sid]){
borderRadius = layout[vid.dataset.sid].rounded || 0;
}
if (layout[vid.dataset.sid].borderColor){
borderColor = layout[vid.dataset.sid].borderColor;
}
if (layout[vid.dataset.sid].fadeIn){
fadein = layout[vid.dataset.sid].fadeIn;
}
if ("backgroundMedia" in layout[vid.dataset.sid]){
backgroundMedia = layout[vid.dataset.sid].backgroundMedia || false;
}
if (vid.container){
if (!((vid.nodeName == "IFRAME") && vid.isConnected)){ // moving an iframe will break it.
if (!vid.alreadyAdded || (vid.nodeName == "IFRAME")){
playarea.appendChild(vid.container);
}
}
}
}
var skipAnimation = false;
if (vid.isInvisible){
vid.isInvisible = false;
skipAnimation = true;
if (fadein){
vid.classList.add("fadein");
if (vid.holder){
vid.holder.classList.add("fadein");
}
}
}
offsety = Math.max((h - Math.ceil(mpl/rw)*Math.ceil(h/rh))/2,0);
if (vid.container){
var container = vid.container;
if (container.move){
clearInterval(container.move);
container.move = null;
}
} else {
var container = document.createElement("div");
vid.container = container;
}
container.style.position = "absolute";
container.style.display = "block";
container.classList.add("container_holder_video");
// ANIMATED - CONTAINER ; width/height/z-index/cover///////////////
if (layout){
var left = (w/100*layout[vid.dataset.sid].x) || layout[vid.dataset.sid].xp || 0;
var top = (h/100*layout[vid.dataset.sid].y) || layout[vid.dataset.sid].yp || 0;
top+=hi;
var width = (w/100*layout[vid.dataset.sid].w) || layout[vid.dataset.sid].wp || 0;
var height = (h/100*layout[vid.dataset.sid].h) || layout[vid.dataset.sid].hp || 0;
if (layout[vid.dataset.sid].cover || layout[vid.dataset.sid].c){ // this should be true/false
//vid.style.objectFit = "cover";
cover = true;
} else {
//vid.style.objectFit = "contain"; // this should fall back to sessio.cover if no layout supplied
cover = false;
}
//container.style.zindex = 0;
container.style.zIndex = layout[vid.dataset.sid].zIndex || layout[vid.dataset.sid].z || 0;
} else {
var left = Math.max(offsetx+Math.floor(((i%rw)+0)*w/rw),0);
var top = Math.max(offsety+Math.floor((Math.floor(i/rw)+0)*h/rh + hi),0);
var width = Math.ceil(w/rw);
var height = Math.ceil(h/rh);
//container.style.zIndex = 0;
}
var computed = getComputedStyle(vid);
if (animated && !skipAnimation){
container.style.transition = "width "+animated+"ms ease-in-out 0s, height "+animated+"ms ease-in-out 0s, background-color "+animated+"ms ease-in-out 0s, transform "+animated+"ms ease-in-out 0s, top "+animated+"ms ease-in-out 0s, left "+animated+"ms ease-in-out 0s";
} else {
container.style.transition = "";
}
if (layout){ ////////////////// NOT ANIMATED - CONTAINER ; width/height/z-index/cover///////////////
container.style.left = left+"px";
container.style.top = top+"px";
container.style.width = width+"px";
container.style.height = height+"px";
} else {
container.style.left = offsetx+Math.floor(((i%rw)+0)*w/rw)+"px";
container.style.top = offsety+Math.floor((Math.floor(i/rw)+0)*h/rh + hi)+"px";
container.style.width = Math.ceil(w/rw)+"px";
container.style.height = Math.ceil(h/rh)+"px";
}
var maxWidth = 0;
if (parseInt(computed.width) > parseInt(container.style.width)){
maxWidth = computed.width;
} else {
maxWidth = container.style.width;
}
var maxHeight = 0;
if (parseInt(computed.height) > parseInt(container.style.height)){
maxHeight = computed.height;
} else {
maxHeight = container.style.height;
}
if (cover){
vid.style.maxWidth = maxWidth;
vid.style.maxHeight = maxHeight;
vid.style.objectFit = "cover";
} else {
vid.style.objectFit = "contain";
vid.style.maxWidth = maxWidth;
vid.style.maxHeight = maxHeight;
}
//try {
if (vid.alreadyAdded && vid.alreadyAdded==true){
if (!container.holder){
var holder = document.createElement("div");
container.holder = holder;
holder.className = "holder";
holder.dataset.holder = true;
container.appendChild(holder);
holder.appendChild(vid);
} else {
var holder = container.holder;
}
} else if (vid.dataset.doNotMove){
vid.style.position = "absolute";
vid.style.left = left+"px";
vid.style.top = top+"px";
vid.style.width = width+"px";
vid.style.height = height+"px";
vid.style.display = "flex";
i+=1;
return;
} else {
if (!container.holder){
var holder = document.createElement("div");
container.holder = holder;
holder.className = "holder";
holder.dataset.holder = true;
holder.appendChild(vid);
container.appendChild(holder);
} else {
var holder = container.holder;
holder.prepend(vid);
}
playarea.appendChild(container);
vid.style.maxWidth = "100%";
vid.style.maxHeight = "100%";
}
if (layout){
var wrw = (w/100*layout[vid.dataset.sid].w) || 0;
var hrh = (h/100*layout[vid.dataset.sid].h) || 0;
} else {
var wrw = (w/rw);
var hrh = (h/rh);
}
if (backgroundMedia){
container.style.backgroundImage = "url("+backgroundMedia+")";
if (cover){
container.style.backgroundSize = "cover";
} else {
container.style.backgroundSize = "contain";
}
container.style.backgroundPosition = "center";
container.style.backgroundRepeat = "no-repeat";
} else if (container.style.backgroundImage){
container.style.backgroundImage = "unset";
}
if (("rotated" in vid) && (vid.rotated!==false)){
if (vid.rotated==90){
vid.style.transform = "rotate(90deg)";
} else if (vid.rotated==270){
vid.style.transform = "rotate(270deg)";
} else if (vid.rotated==180){
vid.style.transform = "rotate(180deg)";
} else if (!vid.rotated){
vid.style.transform = "rotate(0deg)";
}
}
vid.style.width = "100%";
vid.style.height = "100%";
holder.style.position = "absolute";
if (vid.classList.contains("paused")){
if (holder.paused){
holder.paused.className = "playButton";
} else {
var paused = document.createElement("span");
paused.id = "paused_"+vid.dataset.UUID;
paused.className = "playButton";
paused.dataset.UUID = vid.dataset.UUID;
paused.onclick = function(){unPauseVideo(vid);};
holder.paused = paused;
holder.appendChild(paused);
}
} else if (holder.paused){
holder.paused.className = "hidden";
}
var vw = vid.naturalWidth || vid.videoWidth; // naturalWidth is for images I guess
var vh = vid.naturalHeight || vid.videoHeight;
// log(vw + " : "+vh);
if (cover){
if ((("rotated" in vid) && ((vid.rotated==90) || (vid.rotated==270)))){
holder.style.left = borderOffset + "px";
holder.style.top = borderOffset + "px";
holder.style.height = "calc(100% - "+(videoMargin*2)+"px)";
holder.style.width = "calc(100% - "+(videoMargin*2)+"px)";
vid.style.width = (height - (borderOffset + videoMargin)*2) + "px";
vid.style.height = (width - (borderOffset + videoMargin)*2) + "px";
vid.style.left = 0;
vid.style.top = 0;
} else {
holder.style.left = videoMargin + "px";
holder.style.top = videoMargin +"px";
holder.style.height = "calc(100% - "+(videoMargin*2)+"px)";
holder.style.width = "calc(100% - "+(videoMargin*2)+"px)";
vid.style.width = "100%";
vid.style.height = "100%";
vid.style.left = 0;
vid.style.top = 0;
}
////////// COVER VERSION
if (session.sharperScreen && sssid && vid.dataset.sid && (vid.dataset.sid === sssid) ){
// do not dynamically scale the screen share feed.
} else if (session.dynamicScale){
if (vid.dataset.UUID){
let targetWidth = wrw;
let targetHeight = hrh;
targetWidth -= (borderOffset + videoMargin)*2;
targetHeight -= (borderOffset + videoMargin)*2
if (targetWidth<0){targetWidth=0;}
if (targetHeight<0){targetHeight=0;}
if (session.devicePixelRatio){
session.requestResolution(vid.dataset.UUID, targetWidth * session.devicePixelRatio, targetHeight * session.devicePixelRatio, true, false, cover); // snap=true; if resolution close to 100%, send 100%. screenshare only
} else if (window.devicePixelRatio && parseInt(window.devicePixelRatio) > 1 ){
session.requestResolution(vid.dataset.UUID, targetWidth*window.devicePixelRatio, targetHeight*window.devicePixelRatio, true, false, cover);
} else {
session.requestResolution(vid.dataset.UUID, targetWidth, targetHeight, true, false, cover);
}
}
}
vid.style.borderColor = borderColor;
vid.style.borderWidth = borderOffset+"px";
vid.style.borderRadius = borderRadius+"px";
holder.style.borderColor = borderColor;
holder.style.borderWidth = "0px";
holder.style.borderRadius = borderRadius+"px";
} else if ((vw && vh) || (vid.width && vid.height) || vid.dataset.aspectRatio){
if (("rotated" in vid) && ((vid.rotated==90) || (vid.rotated==270))){
if (vw && vh){
var vvw = parseInt(vh);
var vvh = parseInt(vw);
} else if (vid.width && vid.height){
var vvw = parseInt(vid.height);
var vvh = parseInt(vid.width);
} else { // video disabled; fall back to aspect Ratio
var vvw = 1;
var vvh = vid.dataset.aspectRatio;
}
vid.style.objectFit = "cover"; //contain;
vid.style.overflow = "unset"; //contain;
//vid.style.maxWidth = "unset";
//vid.style.maxHeight = "unset";
} else {
if (vw && vh){
var vvw = parseInt(vw);
var vvh = parseInt(vh);
} else if (vid.width && vid.height){
var vvw = parseInt(vid.width);
var vvh = parseInt(vid.height);
} else {
var vvw = vid.dataset.aspectRatio;
var vvh =1;
}
}
var asw = (wrw - videoMargin*2 - borderOffset*2)/vvw; // (window.innerWidth/ N) / vid.videoHeight;
var ash = (hrh - videoMargin*2 - borderOffset*2)/vvh;
if (session.structure){
// wrw x hrh
var arx = (wrw - videoMargin*2 - borderOffset*2)/(hrh - videoMargin*2 - borderOffset*2);
var tarx = arW/arH;
//var arW = 16.0;
//var arH = 9.0;
if (arx>tarx){ // width is too long
var hsw = hrh*tarx - videoMargin*2*tarx - borderOffset*2;
var hsl = (wrw - hsw) / 2;
var hst = videoMargin;
var hsh = (hrh - videoMargin*2 );
} else {
var hsh = (wrw - videoMargin*2 + borderOffset*2)/tarx;
var hst = (hrh - hsh) / 2;
var hsl = videoMargin;
var hsw = (wrw - videoMargin*2);
}
} else if (asw > ash){
var hsh = hrh - videoMargin*2;
var hst = videoMargin;
var hsw = (hsh - borderOffset)*(vvw/vvh);
var hsl = (wrw - hsw)/2;
} else {
var hsw = wrw - videoMargin*2;
var hsl = videoMargin;
var hsh = hsw/(vvw/vvh) + borderOffset;
var hst = (hrh - hsh)/2;
}
holder.style.left = Math.floor(hsl )+ "px"; // this needs to be replaced with padding. This means testing with rotation = 90
holder.style.top = Math.floor(hst)+ "px";
holder.style.width = Math.ceil(hsw) + 'px';
holder.style.height = Math.ceil(hsh) + 'px';
//holder.style.padding = videoMargin + "px";
holder.style.borderColor = borderColor;
holder.style.borderWidth = borderOffset+"px";
holder.style.borderRadius = borderRadius+"px";
vid.style.borderWidth = "0px";
if (("rotated" in vid) && ((vid.rotated==90) || (vid.rotated==270))){
vid.style.width = Math.ceil(wrw - borderOffset*2) + "px";
vid.style.height = Math.ceil(hsw - borderOffset*2) + "px";
vid.style.left = 0;
vid.style.maxWidth = "100vh";
vid.style.maxHeight = "100vw";
if (ChromeVersion && ChromeVersion<77){
if (!animated && (parseInt(container.style.width)>parseInt(holder.style.height))){
vid.style.position = "relative";
vid.style.objectFit = "contain"; //contain;
} else if (animated && (container.twidth && (parseInt(container.twidth)>parseInt(holder.style.height)))){
vid.style.position = "relative";
vid.style.objectFit = "contain"; //contain;
}
} else {
vid.style.position = "relative";
}
} else if ((session.blurBackground!==false) && (vid.nodeName == "VIDEO") && !container.blurred && vid.srcObject && (( asw>1 && ash>1) || asw>=1 || ash>=1)){
vid.srcObject.getVideoTracks().forEach(trk=>{
if (!container.blurred){
container.blurred = document.createElement("video");
container.blurred.controls = false;
container.blurred.style = "z-index:-10000;position:absolute;left:0;width:100%;height:100%;top:0;object-fit:fill;-webkit-filter: blur("+session.blurBackground+"px)";
container.blurred.srcObject = createMediaStream();
container.blurred.srcObject.addTrack(trk);
container.blurred.play();
holder.appendChild(container.blurred);
} else {
if (container.blurred.paused){
container.blurred.play();
}
}
});
} else if ((session.blurBackground!==false) && (vid.nodeName == "VIDEO") && vid.srcObject && (( asw>1 && ash>1) || asw>=1 || ash>=1)){
if (container.blurred.paused){
container.blurred.play();
}
//container.blurred.style = "z-index:-10000;position:absolute;left:0;width:100%;height:100%;top:0;object-fit:fill;-webkit-filter: blur("+session.blurBackground+"px)";
} else if (container.blurred){
try {
container.blurred.remove();
} catch(e){}
try{
delete container.blurred;
} catch(e){}
}
////////// NON-COVER VERSION (based on holder)
if (session.sharperScreen && sssid && vid.dataset.sid && (vid.dataset.sid === sssid) ){
// do not dynamically scale the screen share feed.
} else if (session.dynamicScale){
if (vid.dataset.UUID){
let targetWidth = wrw;
let targetHeight = hrh;
targetWidth -= (borderOffset + videoMargin)*2;
targetHeight -= (borderOffset + videoMargin)*2
if (targetWidth<0){targetWidth=0;}
if (targetHeight<0){targetHeight=0;}
if (session.devicePixelRatio){
session.requestResolution(vid.dataset.UUID, targetWidth * session.devicePixelRatio, targetHeight * session.devicePixelRatio, true, false, cover); // snap=true; if resolution close to 100%, send 100%. screenshare only
} else if (window.devicePixelRatio && parseInt(window.devicePixelRatio) > 1 ){
session.requestResolution(vid.dataset.UUID, targetWidth*window.devicePixelRatio, targetHeight*window.devicePixelRatio, true, false, cover);
} else {
session.requestResolution(vid.dataset.UUID, targetWidth, targetHeight, true, false, cover);
}
}
}
} else {
holder.style.left = (borderOffset + videoMargin) + "px";
holder.style.top = (borderOffset + videoMargin) + "px";
holder.style.height = "calc(100% - "+((borderOffset + videoMargin*2))+"px)";
holder.style.width = "calc(100% - "+((borderOffset + videoMargin*2))+"px)";
////////// UNKNOWN VERSION
if (session.sharperScreen && sssid && vid.dataset.sid && (vid.dataset.sid === sssid) ){
// do not dynamically scale the screen share feed.
} else if (session.dynamicScale){
if (vid.dataset.UUID){
let targetWidth = wrw;
let targetHeight = hrh;
targetWidth -= (borderOffset + videoMargin)*2;
targetHeight -= (borderOffset + videoMargin)*2
if (targetWidth<0){targetWidth=0;}
if (targetHeight<0){targetHeight=0;}
if (session.devicePixelRatio){
session.requestResolution(vid.dataset.UUID, targetWidth * session.devicePixelRatio, targetHeight * session.devicePixelRatio, true, false, cover); // snap=true; if resolution close to 100%, send 100%. screenshare only
} else if (window.devicePixelRatio && parseInt(window.devicePixelRatio) > 1 ){
session.requestResolution(vid.dataset.UUID, targetWidth*window.devicePixelRatio, targetHeight*window.devicePixelRatio, true, false, cover);
} else {
session.requestResolution(vid.dataset.UUID, targetWidth, targetHeight, true, false, cover);
}
}
}
///////////////
holder.style.borderColor = borderColor;
holder.style.borderWidth = borderOffset+"px";
holder.style.borderRadius = borderRadius+"px";
vid.style.borderWidth = "0px";
}
if (session.colorVideosBackground){
vid.style.backgroundColor = session.colorVideosBackground;
} else {
vid.style.backgroundColor = "unset";
}
if (vid.dataset.UUID && session.rpcs[vid.dataset.UUID] && ("label" in session.rpcs[vid.dataset.UUID]) && (session.rpcs[vid.dataset.UUID].label !== false) && (session.showlabels===true)){ // remote source
if (container && container.move && container.twidth && container.theight && animated){
var vidwidth = container.twidth;
var vidheight = container.theight ;
} else {
var vidwidth = vid.offsetWidth;
var vidheight = vid.offsetHeight;
}
var fontsize = (vidwidth + vidheight)*0.03;
if ((vidwidth/16)>=(vidheight/9)){
var voar = (vidwidth/16)/(vidheight/9);
} else {
var voar = (vidheight/9)/(vidwidth/16);
}
voar = Math.pow(voar,0.5);
fontsize = fontsize/voar;
// creates a video label holder inside the recently created label holder
if (holder.label){
var label = holder.label;
} else {
var label = document.createElement("span");
holder.label = label;
if (session.labelstyle){
label.className = 'video-label '+session.labelstyle;
} else {
label.className = 'video-label';
}
holder.appendChild(label);
}
if (fontsize){
if (session.labelsize){
fontsize = fontsize*session.labelsize/100;
}
label.style.fontSize = parseInt(fontsize)+"px";
}
label.innerText = session.rpcs[vid.dataset.UUID].label;
} else if ((session.showlabels===true) && (vid.id === "videosource") && (session.label)){ // local source
// creates a label holder that's the same size of the vid element.
if (container && container.move && container.twidth && container.theight && animated){
var vidwidth = container.twidth;
var vidheight = container.theight ;
} else {
var vidwidth = vid.offsetWidth;
var vidheight = vid.offsetHeight;
}
var fontsize = (vidwidth + vidheight)*0.03;
if ((vidwidth/16)>=(vidheight/9)){
var voar = (vidwidth/16)/(vidheight/9);
} else {
var voar = (vidheight/9)/(vidwidth/16);
}
voar = Math.pow(voar,0.5);
fontsize = fontsize/voar;
if (holder.label){
var label = holder.label;
} else {
var label = document.createElement("span");
holder.label = label;
if (session.labelstyle){
label.className = 'video-label '+session.labelstyle;
} else {
label.className = 'video-label';
}
holder.appendChild(label);
}
if (fontsize){
if (session.labelsize){
fontsize = fontsize*session.labelsize/100;
}
label.style.fontSize = parseInt(fontsize)+"px";
}
label.innerText = sanitizeLabel(session.label);//.replace(/[\W]+/g,"_").replace(/_+/g, ' ');
holder.appendChild(label);
}
if (vid.dataset.UUID && session.rpcs[vid.dataset.UUID]){
if (session.rpcs[vid.dataset.UUID].voiceMeter){
holder.appendChild(session.rpcs[vid.dataset.UUID].voiceMeter);
}
if (session.rpcs[vid.dataset.UUID].remoteMuteElement){
holder.appendChild(session.rpcs[vid.dataset.UUID].remoteMuteElement);
}
if (session.signalMeter){
if (vid.dataset.UUID && !session.rpcs[vid.dataset.UUID].signalMeter){
session.rpcs[vid.dataset.UUID].signalMeter = getById("signalMeterTemplate").cloneNode(true);
session.rpcs[vid.dataset.UUID].signalMeter.classList.remove("hidden");
session.rpcs[vid.dataset.UUID].signalMeter.id = "signalMeter_" + vid.dataset.UUID;
session.rpcs[vid.dataset.UUID].signalMeter.dataset.level = 0;
session.rpcs[vid.dataset.UUID].signalMeter.title = miscTranslations["signal-meter"];
holder.appendChild(session.rpcs[vid.dataset.UUID].signalMeter);
holder.signalMeter = session.rpcs[vid.dataset.UUID].signalMeter;
} else if (vid.dataset.UUID && session.rpcs[vid.dataset.UUID].signalMeter){
if (!holder.signalMeter){
holder.appendChild(session.rpcs[vid.dataset.UUID].signalMeter);
holder.signalMeter = session.rpcs[vid.dataset.UUID].signalMeter;
}
}
}
if (session.batteryMeter){
if (vid.dataset.UUID && !session.rpcs[vid.dataset.UUID].batteryMeter){
session.rpcs[vid.dataset.UUID].batteryMeter = getById("batteryMeterTemplate").cloneNode(true);
session.rpcs[vid.dataset.UUID].batteryMeter.classList.remove("hidden");
session.rpcs[vid.dataset.UUID].batteryMeter.id = "batteryMeter_" + vid.dataset.UUID;
session.rpcs[vid.dataset.UUID].batteryMeter.dataset.level = 0;
session.rpcs[vid.dataset.UUID].batteryMeter.title = miscTranslations["battery-meter"];
holder.appendChild(session.rpcs[vid.dataset.UUID].batteryMeter);
holder.batteryMeter = session.rpcs[vid.dataset.UUID].batteryMeter;
} else if (vid.dataset.UUID && session.rpcs[vid.dataset.UUID].batteryMeter){
if (!holder.batteryMeter){
holder.appendChild(session.rpcs[vid.dataset.UUID].batteryMeter);
holder.batteryMeter = session.rpcs[vid.dataset.UUID].batteryMeter;
}
}
}
if (session.volumeControl && session.rpcs[vid.dataset.UUID].videoElement && (vid.tagName != "VIDEO")){
if (vid.dataset.UUID && !session.rpcs[vid.dataset.UUID].volumeControl){
session.rpcs[vid.dataset.UUID].volumeControl = getById("volumeControlTemplate").cloneNode(true);
session.rpcs[vid.dataset.UUID].volumeControl.classList.remove("hidden");
session.rpcs[vid.dataset.UUID].volumeControl.id = "volumeControl_" + vid.dataset.UUID;
session.rpcs[vid.dataset.UUID].volumeControl.value = parseInt(session.rpcs[vid.dataset.UUID].videoElement.volume*100);
session.rpcs[vid.dataset.UUID].volumeControl.dataset.UUID = vid.dataset.UUID;
session.rpcs[vid.dataset.UUID].volumeControl.title = miscTranslations["volume-control"];
session.rpcs[vid.dataset.UUID].volumeControl.oninput = function(){
if (this.dataset.UUID && session.rpcs[this.dataset.UUID] && session.rpcs[this.dataset.UUID].videoElement){
session.rpcs[this.dataset.UUID].videoElement.volume = parseFloat(this.value/100);
}
}
holder.appendChild(session.rpcs[vid.dataset.UUID].volumeControl);
holder.volumeControl = session.rpcs[vid.dataset.UUID].volumeControl;
} else if (vid.dataset.UUID && session.rpcs[vid.dataset.UUID].volumeControl){
if (!holder.volumeControl && session.rpcs[vid.dataset.UUID].videoElement){
holder.appendChild(session.rpcs[vid.dataset.UUID].volumeControl);
holder.volumeControl = session.rpcs[vid.dataset.UUID].volumeControl;
session.rpcs[vid.dataset.UUID].volumeControl.value = parseInt(session.rpcs[vid.dataset.UUID].videoElement.volume*100);
}
}
}
if (session.showConnections){
if (!session.rpcs[vid.dataset.UUID].connectionDetails){
createConnectionDetailsEle(vid.dataset.UUID);
}
holder.appendChild(session.rpcs[vid.dataset.UUID].connectionDetails);
}
}
if (session.ruleOfThirds){
if (vid.id == "videosource"){
if (!holder.svg){
var svg = document.createElement("img");
svg.src = session.ruleOfThirds;
svg.style.width = "100%";
svg.style.height = "100%";
svg.style.position= "absolute";
svg.style.left = "0";
svg.style.top = "0";
svg.style.pointerEvents= "none";
holder.svg = svg;
holder.appendChild(svg);
}
}
}
try {
if (!(session.cleanOutput && session.cleanish==false)){
if (session.firstPlayTriggered===false){ // don't play unless needed; might cause clicking or who knows what else.
if (vid.tagName.toLowerCase()=="video"){ // we don't want to try playing an Iframe or Canvas.
if (vid.paused){
warnlog("VIDEO IS NOT PLAYING");
var playPromise = vid.play();
if (playPromise !== undefined){
playPromise.then(_ => {
// playing
//session.firstPlayTriggered=true; // global tracking. "user gesture obtained", so no longer needed if playing.
}).catch((err)=>{
var bigPlayButton = document.getElementById("bigPlayButton");
if (bigPlayButton){
bigPlayButton.innerHTML = '';
bigPlayButton.style.display="block";
}
});
} else {
session.firstPlayTriggered=true; // well, I don't know if it's playing, and so whatever. fail gracefully.
}
}
}
}
}
} catch(e) {
errorlog(e);
var bigPlayButton = document.getElementById("bigPlayButton");
if (bigPlayButton){
bigPlayButton.parentNode.removeChild(bigPlayButton);
}
}
if (vid.nodeName == "IFRAME"){ // I need to add this back in at some point.
i+=1;
return;
}
if (!session.cleanOutput && !session.nocursor){
if ((session.roomid!==false) && (session.scene===false)){
if (!((vid.id === "videosource") && miniPreview) && !session.infocusForceMode){
if (!holder.button){
var button = document.createElement("div");
holder.button = button;
holder.appendChild(button);
} else {
var button = holder.button;
}
button.id = "button_"+vid.id;
button.dataset.button = true;
if (soloVideo){
button.innerHTML = "";
button.title = "Show all active videos togethers";
button.style.visibility = "visible";
} else if ((mpl>1) || session.fullscreenButton){ // with session.fullscreenButton we hide the actuall full screen button, so this replaces it
button.innerHTML = "";
button.title = "Enlarge video and increase its clarity";
button.style.visibility = "visible";
} else {
button.style.visibility = "hidden";
}
button.classList.add("fullwindowButton");
if (vid.id == "videosource"){
button.onclick = function(event){
if (session.infocus === true){
session.infocus = false;
} else {
session.infocus = true;
}
setTimeout(()=>updateMixer(),10);
if (session.fullscreenButton){
if (session.infocus){
fullscreenPageToggle(true);
} else {
fullscreenPageToggle(false);
}
}
};
} else {
button.dataset.UUID = vid.dataset.UUID;
button.onclick = function(event){
var target = event.currentTarget;
if (session.infocus === target.dataset.UUID){
//target.childNodes[0].className = 'las la-arrows-alt';
session.infocus = false;
} else {
//target.childNodes[0].className = 'las la-compress';
session.infocus = target.dataset.UUID;
//log("session:"+target.dataset.UUID);
}
if (session.fullscreenButton){
if (session.infocus){
fullscreenPageToggle(true);
} else {
fullscreenPageToggle(false);
}
}
setTimeout(()=>updateMixer(),10);
};
}
vid.onclick = function(event){
if (session.disableMouseEvents){return;}
button.style.display="block";
container.style.backgroundColor= "#4444";
button.style.opacity="100%";
};
button.onmouseenter = function(event){
if (session.disableMouseEvents){return;}
button.style.display="block";
container.style.backgroundColor= "#4444";
setTimeout(function(button){button.style.opacity="100%";},1,button);
};
button.onmousemove = function(event){
if (session.disableMouseEvents){return;}
button.style.display="block";
container.style.backgroundColor= "#4444";
button.style.opacity="100%";
};
container.onmousemove = function(event){
if (session.disableMouseEvents){return;}
button.style.display="block";
container.style.backgroundColor= "#4444";
button.style.opacity="100%";
};
container.onmouseenter = function(event){
if (session.disableMouseEvents){return;}
button.style.display="block";
container.style.backgroundColor= "#4444";
setTimeout(function(button){button.style.opacity="100%";},1,button);
};
container.onmouseleave = function(event){
if (session.disableMouseEvents){return;}
button.style.display="none";
container.style.backgroundColor= null;
button.style.opacity="10%";
};
} else if ((vid.id === "videosource") && miniPreview && soloVideo==true && !session.infocusForceMode){
if (!holder.button){
var button = document.createElement("div");
holder.button = button;
holder.appendChild(button);
} else {
var button = holder.button;
}
button.id = "button_videosource";
button.dataset.button = true;
if (soloVideo){
button.innerHTML = "";
button.title = "Show all active videos togethers";
button.style.display="unset";
} else {
button.style.visibility = "hidden";
button.style.display="none";
}
button.classList.add("fullwindowButton");
button.onclick = function(event){
event.stopPropagation();
event.preventDefault();
if (session.infocus === true){
session.infocus = false;
setTimeout(()=>updateMixer(),10);
}
if (session.fullscreenButton){
if (session.infocus){
fullscreenPageToggle(true);
} else {
fullscreenPageToggle(false);
}
}
};
} else if (session.infocusForceMode && holder.button){
try{
holder.button.remove();
} catch(e){
errorlog(e);
}
}
}
}
i+=1;
} catch(err){errorlog(err);}
});
updateUserList()
}
var translationBacklog = [];
function miniTranslate(ele, ident = false, direct=false) {
if (!translation){
translationBacklog.push([ele,ident]);
log('Translation backlogged');
if (!direct || !ident){
return;
}
}
if (ident){
if (translation.innerHTML && (ident in translation.innerHTML)){
if (ele.querySelector('[data-translate]')){
ele.querySelector('[data-translate]').innerHTML = translation.innerHTML[ident];
ele.querySelector('[data-translate]').dataset.translate = ident;
} else {
ele.innerHTML = translation.innerHTML[ident];
}
return;
} else if (direct){
if (ele.querySelector('[data-translate]')){
ele.querySelector('[data-translate]').innerHTML = direct;
ele.querySelector('[data-translate]').dataset.translate = ident;
} else {
ele.innerHTML = direct;
}
return;
} else {
warnlog(ident + ": not found in translation file");
}
}
var allItems = ele.querySelectorAll('[data-translate]');
allItems.forEach(function(ele) {
if (ele.dataset.translate in translation.innerHTML) {
ele.innerHTML = translation.innerHTML[ele.dataset.translate];
} else if (ele.dataset.translate in translation.miscellaneous) {
ele.innerHTML = translation.miscellaneous[ele.dataset.translate];
}
});
var allTitles = ele.querySelectorAll('[title]');
allTitles.forEach(function(ele) {
var key = ele.title.replace(/[\W]+/g, "-").toLowerCase();
if (key in translation.titles) {
ele.title = translation.titles[key];
}
});
var allPlaceholders = ele.querySelectorAll('[placeholder]');
allPlaceholders.forEach(function(ele) {
var key = ele.placeholder.replace(/[\W]+/g, "-").toLowerCase();
if (key in translation.placeholders) {
ele.placeholder = translation.placeholders[key];
}
});
//Object.keys(miscTranslations).forEach(key => {
// if (key in translation.miscellaneous) {
// miscTranslations[key] = translation.miscellaneous[key];
// }
//});
///
}
var controlBarTimeout = false;
function showControl(e){
if (controlBarTimeout){
clearTimeout(controlBarTimeout);
}
if (session.mobile){
getById("controlButtons").classList.remove("partialFadeout");
} else {
getById("controlButtons").classList.remove("fadeout");
}
controlBarTimeout = setTimeout(function(){
if (session.mobile){
getById("controlButtons").classList.add("partialFadeout");
} else {
getById("controlButtons").classList.add("fadeout");
}
}, 5000);
}
async function changeLg(lang) {
log("changeLg: "+lang);
await fetchWithTimeout("./translations/" + lang + '.json',2000).then(async function(response) {
try{
if (response.status !== 200) {
logerror('Language translation file not found.' + response.status);
getById("mainmenu").style.opacity = 1;
return;
}
await response.json().then(async function(data) {
translation = data; // translation.innerHTML[ele.dataset.translate]
var trans = data.innerHTML;
var allItems = document.querySelectorAll('[data-translate]');
allItems.forEach(function(ele) {
if (ele.dataset.translate in trans) {
ele.innerHTML = trans[ele.dataset.translate];
}
});
trans = data.titles;
var allTitles = document.querySelectorAll('[title]');
allTitles.forEach(function(ele) {
if (ele.dataset.key){
if (ele.dataset.key in trans) {
ele.title = trans[ele.dataset.key];
}
} else {
var key = ele.title.replace(/[\W]+/g, "-").toLowerCase();
ele.dataset.key = key;
if (key in trans) {
ele.title = trans[key];
}
}
});
trans = data.placeholders;
var allPlaceholders = document.querySelectorAll('[placeholder]');
allPlaceholders.forEach(function(ele) {
var key = ele.placeholder.replace(/[\W]+/g, "-").toLowerCase();
if (key in trans) {
ele.placeholder = trans[key];
}
});
if ("miscellaneous" in data){
trans = data.miscellaneous;
Object.keys(miscTranslations).forEach(key => {
if (key in trans) {
miscTranslations[key] = trans[key];
}
});
}
if (translationBacklog.length){
for (var i=0;i 1 && arr[1] !== '') {
window.location += "&room=" + roomname + passStr + "&host";
} else {
window.location += "?room=" + roomname + passStr + "&host";
}
} else {
getById("videoname1").focus();
getById("videoname1").classList.remove("shake");
setTimeout(function(){getById("videoname1").classList.add("shake");},10);
}
}
async function jumptoroom(event = null) {
if (event) {
if (event.which !== 13) {
return;
}
}
var arr = window.location.href.split('?');
var roomname = getById("joinroomID").value;
roomname = sanitizeRoomName(roomname);
if (roomname.length) {
var passStr = "";
window.focus();
var pass = await promptAlt("Enter a password if provided, otherwise just click Cancel", false, true); //sanitizePassword(session.password);
if (pass && pass.length) {
session.password = sanitizePassword(pass);
passStr = "&password=" + session.password;
} else {
session.password = false;
}
if (arr.length > 1 && arr[1] !== '') {
window.location += "&room=" + roomname + passStr;
} else {
window.location += "?room=" + roomname + passStr;
}
} else {
getById("joinroomID").focus();
getById("joinroomID").classList.remove("shake");
setTimeout(function(){getById("joinroomID").classList.add("shake");},10);
}
}
function sleep(ms = 0) {
return new Promise(r => setTimeout(r, ms)); // LOLz!
}
async function changeAvatarImage(ev, ele, set=false){
log("changeAvatarImage() triggered");
if (session.avatar && session.avatar.timer){
clearInterval(session.avatar.timer);
session.avatar.timer=null;
}
if (!session.streamSrc){
checkBasicStreamsExist();
}
if (ele.files && ele.files.length) {
session.avatar = document.querySelector('img');
session.avatar.ready=false;
session.avatar.onload = () => {
URL.revokeObjectURL(session.avatar.src); // no longer needed, free memory
session.avatar.ready=true;
getById("noAvatarSelected3").classList.remove("selected");
getById("noAvatarSelected").classList.remove("selected");
getById("defaultAvatar1").classList.add("selected");
getById("defaultAvatar2").classList.add("selected");
var tracks = session.streamSrc.getVideoTracks();
if (!tracks.length || session.videoMuted){
updateRenderOutpipe();
}
}
session.avatar.src = URL.createObjectURL(ele.files[0]); // set src to blob url
return;
} else if (ele.tagName.toLowerCase() == "img"){
session.avatar = ele
session.avatar.ready=true;
getById("noAvatarSelected3").classList.remove("selected");
getById("noAvatarSelected").classList.remove("selected");
getById("defaultAvatar1").classList.add("selected");
getById("defaultAvatar2").classList.add("selected");
var tracks = session.streamSrc.getVideoTracks();
if (!tracks.length || session.videoMuted){
updateRenderOutpipe();
}
} else {
session.avatar = false;
var tracks = session.streamSrc.getVideoTracks();
if (!tracks.length || session.videoMuted){
var msg = {};
msg.videoMuted = true;
session.sendMessage(msg);
if (document.getElementById("videosource")){
document.getElementById("videosource").load();
} else if (document.getElementById('previewWebcam')) {
document.getElementById("previewWebcam").load();
}
updateRenderOutpipe();
}
getById("noAvatarSelected3").classList.add("selected");
getById("noAvatarSelected").classList.add("selected");
getById("defaultAvatar1").classList.remove("selected");
getById("defaultAvatar2").classList.remove("selected");
return;
}
}
session.autoSyncCallback = function(UUID=null){ // session.autoSyncObject has been updated. You can overwrite this with your own function
log(session.autoSyncObject);
pokeIframeAPI('auto-sync-updated', session.autoSyncObject, UUID);
}
session.autoSync = function(alternative=null){ // Update session.autoSyncObject and then run session.autoSync()
var msg = {};
if (alternative===null){
msg.autoSync = session.autoSyncObject;
} else {
session.autoSyncObject = alternative;
msg.autoSync = session.autoSyncObject;
}
session.sendPeers(msg);
}
function setupSharpnessTool(){
var promise;
const worker = new Worker('./thirdparty/focus_worker.js', { type: 'module' });
worker.onerror = (event) => {
errorlog(event);
promise.reject(event);
};
worker.onmessage = (messageEvent) => {
log("Sharpness score: "+messageEvent.data.score.avg_edge_width_perc);
promise.resolve(messageEvent.data.score.avg_edge_width_perc);
};
measureBlur = (imageData) => {
worker.postMessage({ imageData });
};
const canvas = document.createElement("canvas");
// document.getElementById("header").appendChild(canvas);
async function getSharpness(x=50,y=50){
if (session.videoElement){
log("XY");
log(x + " : " +y);
canvas.width = session.videoElement.videoWidth/5;
canvas.height = session.videoElement.videoHeight/5;
if (x<10){
x=10;
}
if (y<10){
y=10;
}
if (x>90){
x=90;
}
if (y>90){
y=90;
}
var sx = session.videoElement.videoWidth/100*(x-10);
var sy = session.videoElement.videoHeight/100*(y-10);
var sw = session.videoElement.videoWidth*0.20;
var sh = session.videoElement.videoHeight*0.20
canvas.getContext('2d').filter = 'blur(3px)'; // denoise
canvas.getContext('2d').drawImage(session.videoElement, sx, sy, sw, sh, 0, 0, canvas.width, canvas.height); // for drawing the video element on the canvas
const canvasData = canvas.getContext('2d').getImageData(
0,
0,
canvas.width,
canvas.height
);
var res, rej;
promise = new Promise((resolve, reject) => {
res = resolve;
rej = reject;
});
promise.resolve = res;
promise.reject = rej;
measureBlur(canvasData);
return promise;
}
return null;
}
return getSharpness;
}
var sharpnessToolActive = false;
var sharpnessTool = false;
async function tapToFocus(x,y, force=false){
if (isNaN(x) || isNaN(y)){return;}
if (sharpnessToolActive){return;}
if (!session.streamSrc){
checkBasicStreamsExist();
return;
}
//var bestFocus = -1;
var track0 = session.streamSrc.getVideoTracks();
if (!track0.length){
log("No video tracks");
return;
}
track0 = track0[0];
if (!track0.getCapabilities){
log("Track lacks advanced features. Safari?");
return;
}
var capabilities = track0.getCapabilities();
if (!("focusDistance" in capabilities)){
log("Track doesn't support focusing");
return;
}
var settings = track0.getSettings();
if ("focusMode" in settings){
if (!force && (settings.focusMode !== "manual")){
log("Need to be in manual focus mode");
return;
}
}
if (!sharpnessTool){
sharpnessTool = setupSharpnessTool();
}
var bestFocus = -1;
var bestSharpness = 999;
sharpnessToolActive = true;
try {
log("Current focus distance: "+capabilities.focusDistance);
await track0.applyConstraints({advanced: [ {focusMode: "manual", focusDistance: capabilities.focusDistance.min} ]});
await sleep(250);
var stepping = capabilities.focusDistance.step || 0.1;
if ((capabilities.focusDistance.max - capabilities.focusDistance.min)/stepping>100){
stepping = parseInt((capabilities.focusDistance.max - capabilities.focusDistance.min)/100);
}
if (!stepping){
stepping = 0.1;
}
for (var i = capabilities.focusDistance.min; i <= capabilities.focusDistance.max; i+= stepping){
await track0.applyConstraints({advanced: [ {focusMode: "manual", focusDistance: i} ]});
await sleep(120); // wait long enough for a new frame and focus to adjust.
log("focus: " + i + ", " + x +"x"+y);
var response = await sharpnessTool(x,y);
if (response && (responsecapabilities.zoom.max){
session.zoom = capabilities.zoom.max;
} else if (session.zoomcapabilities.focusDistance.max){
session.focusDistance = capabilities.focusDistance.max;
} else if (session.focusDistancemaxH){
width = parseInt(maxH/session.avatar.naturalHeight * session.avatar.naturalWidth);
height = maxH;
if (width>maxW){
width = maxW;
height = parseInt(maxW/width * height);
}
} else if (session.avatar.naturalWidth && session.avatar.naturalWidth>maxW){
width = maxW;
height = parseInt(maxW/session.avatar.naturalWidth*session.avatar.naturalHeight);
} else {
width = session.avatar.naturalWidth;
height = session.avatar.naturalHeight;
}
session.canvasSource.width = width;
session.canvasSource.height = height;
session.canvas.height = 2 * parseInt(height / 2);
session.canvas.width = 2 * parseInt(width / 2);
session.canvasCtx.drawImage(session.avatar, 0, 0, session.canvas.width, session.canvas.height);
session.avatar.timer = setInterval(function(){
log("drawing");
session.canvasCtx.drawImage(session.avatar, 0, 0, session.canvas.width, session.canvas.height);
},200); // too slow and it takes way too long for the video to udpate when a new guest joins
applyMirror(true);
session.avatar.tracks = session.canvas.captureStream().getVideoTracks();
return session.avatar.tracks;
}
applyMirror(session.mirrorExclude);
return tracks;
}
var drawOnScreenObject = null;
function drawOnScreen(){
var canvas = document.getElementById("drawOnSCreen");
if (!canvas){
canvas = document.createElement("canvas");
getById("gridlayout").appendChild(canvas);
getById("gridlayout").style.position = "relative";
} else {
return;
}
var ctx = canvas.getContext('2d');
canvas.width = parseInt(getById("gridlayout").clientWidth/2);
canvas.height = parseInt(getById("gridlayout").clientHeight/2);
canvas.style.width = "100%";
canvas.style.height = "100%";
canvas.style.display = "block";
canvas.style.position = "absolute";
canvas.style.bottom = "0";
canvas.style.left = "0";
var flag = false,
prevX = 0,
currX = 0,
prevY = 0,
currY = 0,
dot_flag = false;
var x = "black",
y = 2;
var object = {};
object.stop = function stop() {
canvas.removeEventListener("mousemove", function (e) {
findxy('move', e)
}, false);
canvas.removeEventListener("mousedown", function (e) {
findxy('down', e)
}, false);
canvas.removeEventListener("mouseup", function (e) {
findxy('up', e)
}, false);
canvas.removeEventListener("mouseout", function (e) {
findxy('out', e)
}, false);
canvas.remove();
getById("startDrawScreen").classList.remove("hidden");
document.querySelectorAll(".drawActive").forEach(ele=>{
ele.classList.add("hidden");
});
delete canvas;
drawOnScreenObject = null;
object = null;
}
object.init = function init() {
//console.log("init");
canvas.addEventListener("mousemove", function (e) {
findxy('move', e)
}, false);
canvas.addEventListener("mousedown", function (e) {
findxy('down', e)
}, false);
canvas.addEventListener("mouseup", function (e) {
findxy('up', e)
}, false);
canvas.addEventListener("mouseout", function (e) {
findxy('out', e)
}, false);
getById("startDrawScreen").classList.add("hidden");
document.querySelectorAll(".drawActive").forEach(ele=>{
ele.classList.remove("hidden");
});
}
object.color = function color(obj) {
switch (obj.dataset.color) {
case "green":
x = "green";
break;
case "blue":
x = "blue";
break;
case "red":
x = "red";
break;
case "yellow":
x = "yellow";
break;
case "orange":
x = "orange";
break;
case "black":
x = "black";
break;
case "white":
x = "white";
break;
}
if (x == "white") y = 14;
else y = 2;
}
function draw() {
//console.log('draw',prevX,currX);
ctx.beginPath();
var mx = canvas.width/ parseInt(getById("gridlayout").clientWidth);
var my = canvas.height/parseInt(getById("gridlayout").clientHeight);
var mo = parseInt(getById("header").clientHeight);
ctx.moveTo(prevX*mx, prevY*my - mo*my);
ctx.lineTo(currX*mx, currY*my - mo*my);
ctx.strokeStyle = x;
ctx.lineWidth = y;
ctx.stroke();
ctx.closePath();
}
object.erase = function erase() {
//var m = confirm("Want to clear");
// if (m) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
//document.getElementById("canvasimg").style.display = "none";
// }
}
object.save = function save() {
//document.getElementById("canvasimg").style.border = "2px solid";
var dataURL = canvas.toDataURL();
// document.getElementById("canvasimg").src = dataURL;
// document.getElementById("canvasimg").style.display = "inline";
}
function findxy(res, e) {
//console.log(res,e);
if (res == 'down') {
prevX = currX;
prevY = currY;
currX = e.clientX - canvas.offsetLeft;
currY = e.clientY - canvas.offsetTop;
flag = true;
dot_flag = true;
if (dot_flag) {
ctx.beginPath();
ctx.fillStyle = x;
ctx.fillRect(currX, currY, 2, 2);
ctx.closePath();
dot_flag = false;
}
}
if (res == 'up' || res == "out") {
flag = false;
}
if (res == 'move') {
if (flag) {
prevX = currX;
prevY = currY;
currX = e.clientX - canvas.offsetLeft;
currY = e.clientY - canvas.offsetTop;
draw();
}
}
}
object.init();
drawOnScreenObject = object;
return object;
}
////////// Canvas Effects ///////////////
function drawFrameMirrored(mirror=true, flip=false) {
session.canvasCtx.save();
if (flip){
if (mirror){
session.canvasCtx.scale(-1, -1);
session.canvasCtx.drawImage(session.canvasSource, 0, 0, session.canvas.width * -1, session.canvas.height* -1);
} else {
session.canvasCtx.scale(1, -1);
session.canvasCtx.drawImage(session.canvasSource, 0, 0, session.canvas.width, session.canvas.height* -1);
}
} else if (mirror){
session.canvasCtx.scale(-1, 1);
session.canvasCtx.drawImage(session.canvasSource, 0, 0, session.canvas.width * -1, session.canvas.height);
} else {
session.canvasCtx.drawImage(session.canvasSource, 0, 0, session.canvas.width, session.canvas.height);
}
session.canvasCtx.restore();
}
function setupCanvas() {
log("SETUP CANVAS");
if (session.canvas === null) {
session.canvas = document.createElement("canvas");
session.canvas.width = 512;
session.canvas.height = 288;
try {
session.canvasCtx = session.canvas.getContext('2d', {alpha: true, willReadFrequently: true});
} catch(e){
errorlog(e);
session.canvasCtx = session.canvas.getContext('2d');
}
session.canvasCtx.fillStyle = "blue";
session.canvasCtx.fillRect(0, 0, 512, 288);
session.canvasSource = createVideoElement();
session.canvasSource.autoplay = true;
session.canvasSource.srcObject = createMediaStream();
session.canvasSource.id = "effectsVideoSource";
if (session.canvasSource.srcObject.getVideoTracks().length){
session.canvasSource.width = session.canvasSource.srcObject.getVideoTracks()[0].getSettings().width || 1280;
session.canvasSource.height = session.canvasSource.srcObject.getVideoTracks()[0].getSettings().height || 720;
}
if (iOS || iPad){
session.canvasSource.style.position = "absolute";
session.canvasSource.style.left = "0";
session.canvasSource.style.top ="0";
session.canvasSource.controls = session.showControls || false;
session.canvasSource.style.maxWidth = "1px";
session.canvasSource.style.maxHeight = "1px";
session.canvasSource.setAttribute("playsinline","");
document.body.appendChild(session.canvasSource);
//session.canvasSource.play();
}
} else {
session.canvasSource.srcObject.getVideoTracks().forEach(function(trk) {
session.canvasSource.srcObject.removeTrack(trk);
});
}
}
function applyEffects(track) { // video only please. do not touch audio. Run update Render Outpipe () instead of this directly.
log("applyEffects()");
if (session.effect == "0" || !session.effect) { // auto align face
return track;
} else if (session.effect == "1") { // auto align face
setupCanvas();
session.canvasSource.srcObject.addTrack(track);
session.canvasSource.width = track.getSettings().width || 1280;
session.canvasSource.height = track.getSettings().height || 720;
session.canvas.height = 2 * parseInt(session.canvasSource.height / 2);
session.canvas.width = 2 * parseInt(session.canvasSource.width / 2);
drawFace();
} else if (session.effect == "7") { // manual zoom
setupCanvas();
session.canvasSource.srcObject.addTrack(track);
session.canvasSource.width = track.getSettings().width || 1280;
session.canvasSource.height = track.getSettings().height || 720;
session.canvas.height = 2 * parseInt(session.canvasSource.height / 2);
session.canvas.width = 2 * parseInt(session.canvasSource.width / 2);
digitalZoom();
} else if (session.effect == "8") { // manual zoom
setupCanvas();
session.canvasSource.srcObject.addTrack(track);
session.canvasSource.width = track.getSettings().width || 1280;
session.canvasSource.height = track.getSettings().height || 720;
session.canvas.height = 2 * parseInt(session.canvasSource.height / 2);
session.canvas.width = 2 * parseInt(session.canvasSource.width / 2);
simpleDraw();
} else if (session.effect == "2") { // mirror video at a canvas level
setupCanvas();
session.canvasSource.srcObject.addTrack(track);
session.canvasSource.width = track.getSettings().width || 1280;
session.canvasSource.height = track.getSettings().height || 720;
session.canvas.height = 2 * parseInt(session.canvasSource.height / 2);
session.canvas.width = 2 * parseInt(session.canvasSource.width / 2);
var drawRate = parseInt(1000 / track.getSettings().frameRate) + 1;
if (session.canvasInterval !== null) {
clearInterval(session.canvasInterval);
}
session.canvasInterval = setInterval(function() {
drawFrameMirrored(true, false);
}, drawRate);
} else if (session.effect == "-1") { // mirror and flip video at a canvas level
setupCanvas();
session.canvasSource.srcObject.addTrack(track);
session.canvasSource.width = track.getSettings().width || 1280;
session.canvasSource.height = track.getSettings().height || 720;
session.canvas.height = 2 * parseInt(session.canvasSource.height / 2);
session.canvas.width = 2 * parseInt(session.canvasSource.width / 2);
var drawRate = parseInt(1000 / track.getSettings().frameRate) + 1;
if (session.canvasInterval !== null) {
clearInterval(session.canvasInterval);
}
session.canvasInterval = setInterval(function() {
drawFrameMirrored(false, true);
}, drawRate);
} else if (session.effect == "-2") { // mirror and flip video at a canvas level
setupCanvas();
session.canvasSource.srcObject.addTrack(track);
session.canvasSource.width = track.getSettings().width || 1280;
session.canvasSource.height = track.getSettings().height || 720;
session.canvas.height = 2 * parseInt(session.canvasSource.height / 2);
session.canvas.width = 2 * parseInt(session.canvasSource.width / 2);
var drawRate = parseInt(1000 / track.getSettings().frameRate) + 1;
if (session.canvasInterval !== null) {
clearInterval(session.canvasInterval);
}
session.canvasInterval = setInterval(function() {
drawFrameMirrored(true, true);
}, drawRate);
} else if ((session.effect == "3") || (session.effect == "4") || (session.effect == "5")){ // blur & greenscreen (low and high)
setupCanvas();
session.canvasSource.srcObject.addTrack(track);
session.canvasSource.width = track.getSettings().width || 1280;
session.canvasSource.height = track.getSettings().height || 720;
session.canvas.height = 2 * parseInt(session.canvasSource.height / 2);
session.canvas.width = 2 * parseInt(session.canvasSource.width / 2);
TFLiteWorker();
} else if (session.effect == "6"){
setupCanvas();
session.canvasSource.srcObject.addTrack(track);
session.canvasSource.width = track.getSettings().width || 1280;
session.canvasSource.height = track.getSettings().height || 720;
session.canvas.height = 2 * parseInt(session.canvasSource.height / 2);
session.canvas.width = 2 * parseInt(session.canvasSource.width / 2);
if (session.canvasSource.readyState >= 3){
mainMeshMask();
} else {
session.canvasSource.onloadeddata = mainMeshMask;
}
} else {
if (session.canvasource){
session.canvasSource.srcObject.getVideoTracks().forEach(function(trk) {
session.canvasSource.srcObject.removeTrack(trk);
});
} else {
session.canvasSource = createVideoElement();
session.canvasSource.srcObject = createMediaStream();
}
session.canvasSource.autoplay = true;
session.canvasSource.id = "effectsVideoSource";
session.canvasSource.srcObject.addTrack(track);
session.canvasSource.width = track.getSettings().width || 1280;
session.canvasSource.height = track.getSettings().height || 720;
session.canvas.width = 512;
session.canvas.height = 288;
if (iOS || iPad){
session.canvasSource.style.position = "absolute";
session.canvasSource.style.left = "0";
session.canvasSource.style.top = "0";
session.canvasSource.style.maxWidth = "1px";
session.canvasSource.style.maxHeight = "1px";
session.canvasSource.controls = session.showControls || false;
session.canvasSource.setAttribute("playsinline","");
document.body.appendChild(session.canvasSource);
//session.canvasSource.play();
}
try {
JEELIZFACEFILTER.destroy();
} catch(e){}
if (session.canvasWebGL){
session.canvasWebGL.remove()
session.canvasWebGL=null;
}
session.canvasWebGL = document.createElement("canvas");
session.canvasWebGL.width = track.getSettings().width || 1280;
session.canvasWebGL.height = track.getSettings().height || 720;
session.canvasWebGL.id = "effectsCanvasTarget";
session.canvasWebGL.style.position="fixed";
session.canvasWebGL.style.top= "-9999px";
session.canvasWebGL.style.left= "-9999px";
document.body.appendChild(session.canvasWebGL);
loadEffect(session.effect);
return session.canvasWebGL.captureStream().getVideoTracks()[0];
}
try {
return session.canvas.captureStream().getVideoTracks()[0];
} catch(e){
if (!session.cleanOutput){
warnUser(miscTranslations["not-clean-session"], false, false);
}
return track;
}
}
function dataURItoArraybuffer(dataURI) {
var byteString = atob(dataURI.split(',')[1]);
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return ab;
}
var makeImagesActive = null;
async function makeImages(startup=false){
if (!session.webp){return;}
else if (makeImagesActive===true){return;}
else if (!session.videoElement){return;}
else if (session.videoMuted){return;}
else if (session.videoElement && session.videoElement.srcObject){
//
} else if (session.videoElement && session.videoElement.src){
//
} else {
errorlog("No video element; can't make images for webp mode");
return;
}
if (makeImagesActive===null){
makeImagesActive=true;
session.webPcanvas = document.createElement("canvas");
session.webPcanvas.makeImagesTimeout = null;
session.webPcanvas.nowTime = new Date().getTime();
var width = 480;
var height = 270;
var timeout = 100; // the answer to everything.
var quality = 0.66;
if (session.webPquality===0){
width = 1920;
height = 1080;
timeout = 33;
} else if (session.webPquality===1){
width = 1280;
height = 720;
timeout = 33;
} else if (session.webPquality===2){
width = 960;
height = 540;
timeout = 33;
} else if (session.webPquality===3){
width = 853;
height = 480;
timeout = 33;
} else if (session.webPquality===4){
width = 640;
height = 360;
timeout = 33;
} else if (session.webPquality===5){
width = 480;
height = 270;
timeout = 33;
} else if (session.webPquality===6){
width = 480;
height = 270;
timeout = 67;
} else if (session.webPquality===7){
width = 480;
height = 270;
timeout = 200;
} else if (session.webPquality===8){
width = 480;
height = 270;
timeout = 400;
} else if (session.webPquality===9){
width = 640;
height = 360;
timeout = 1000;
}
session.webPcanvas.timeout = timeout;
session.webPcanvas.quality = quality;
const video = session.videoElement;
if (video.videoWidth < width){
session.webPcanvas.width = video.videoWidth;
} else {
session.webPcanvas.width = width;
}
if (video.videoHeight < height){
session.webPcanvas.height = video.videoHeight;
} else {
session.webPcanvas.height = height;
}
var ar = session.webPcanvas.width/session.webPcanvas.height;
if (session.forceAspectRatio && session.forceAspectRatio=ar){
height = width*session.forceAspectRatio;
}
session.webPcanvasCtx = session.webPcanvas.getContext('2d', {alpha: false});
session.webPcanvasCtx.fillStyle = "black";
session.webPcanvasCtx.fillRect(0, 0, width, height);
} else {
clearTimeout(session.webPcanvas.makeImagesTimeout);
makeImagesActive=true;
}
if (session.videoElement && session.videoElement.srcObject.getVideoTracks().length===0){
makeImagesActive=false;
var exit = true;
for (var i in session.pcs){
if (session.pcs[i].allowWebp){ // just for safety, to avoid a race condition, double check that it's still not active.
exit = false;
}
}
if (exit){
makeImagesActive=false;
return;
}
session.webPcanvas.makeImagesTimeout = setTimeout(function(){makeImages();},timeout*3);
return;
}
if (startup){
var exit = true;
for (var i in session.pcs){
if (session.pcs[i].allowWebp){ // just for safety, to avoid a race condition, double check that it's still not active.
exit = false;
}
}
if (exit){
makeImagesActive=false;
return;
}
log("MAKE IMAGES STARTING?");
}
try{
var broadcasting = false;
var arrayBuffer = false;
var width = 480;
var height = 270;
if (session.webPquality===0){
width = 1920;
height = 1080;
timeout = 33;
} else if (session.webPquality===1){
width = 1280;
height = 720;
timeout = 33;
} else if (session.webPquality===2){
width = 960;
height = 540;
timeout = 33;
} else if (session.webPquality===3){
width = 853;
height = 480;
timeout = 33;
} else if (session.webPquality===4){
width = 640;
height = 360;
timeout = 33;
} else if (session.webPquality===5){
width = 480;
height = 270;
timeout = 33;
} else if (session.webPquality===6){
width = 480;
height = 270;
timeout = 67;
} else if (session.webPquality===7){
width = 480;
height = 270;
timeout = 200;
} else if (session.webPquality===8){
width = 480;
height = 270;
timeout = 400;
} else if (session.webPquality===9){
width = 640;
height = 360;
timeout = 1000;
}
const video = session.videoElement;
if (video.videoWidth < width){
session.webPcanvas.width = video.videoWidth;
session.webPcanvasCtx.width = video.videoWidth;
} else {
session.webPcanvas.width = width;
session.webPcanvasCtx.width = width;
}
if (video.videoHeight < height){
session.webPcanvas.height = video.videoHeight;
session.webPcanvasCtx.height = video.videoHeight;
} else {
session.webPcanvas.height = height;
session.webPcanvasCtx.height = height;
}
var ar = session.webPcanvas.width/session.webPcanvas.height;
if (session.forceAspectRatio && session.forceAspectRatio>ar){
session.webPcanvas.width = session.webPcanvas.height*session.forceAspectRatio;
} else if (session.forceAspectRatio && session.forceAspectRatio<=ar){
session.webPcanvas.height = session.webPcanvas.width*session.forceAspectRatio;
}
for (var i in session.pcs){
try{
if (session.pcs[i].allowWebp){ // only publish to those seeking this stream
broadcasting = true;
if (!session.pcs[i].sendChannel.bufferedAmount){
if (!arrayBuffer){
session.webPcanvasCtx.drawImage(session.videoElement, 0, 0, session.webPcanvas.width, session.webPcanvas.height);
arrayBuffer = dataURItoArraybuffer(session.webPcanvas.toDataURL("image/"+session.webp, session.webPcanvas.quality));
}
session.pcs[i].sendChannel.send(arrayBuffer);
}
}
} catch(e){}
}
} catch(e){
errorlog(e);
makeImagesActive=false;
return;
}
makeImagesActive=false;
if (broadcasting){ // wait a bit of time, now that we sent a frame out.
session.webPcanvas.lastTime = session.webPcanvas.nowTime;
session.webPcanvas.nowTime = new Date().getTime();
var time = session.webPcanvas.timeout - (session.webPcanvas.nowTime - session.webPcanvas.lastTime);
if (time <= 0 ){
session.webPcanvas.makeImagesTimeout = setTimeout(function(){makeImages();},0);
} else {
session.webPcanvas.makeImagesTimeout = setTimeout(function(){makeImages();},time);
}
} else { // just double check that we shoulnd't be broadcasting.
for (var i in session.pcs){
if (session.pcs[i].allowWebp){
session.webPcanvas.makeImagesTimeout = setTimeout(function(){makeImages();},0);
return;
}
}
log("Stopping webP broadcast.");
}
}
var updateUserListTimeout=null
var updateUserListActive = false;
function updateUserList(){
if (session.showList===true){
// continue
}
else if ((session.showList!==true) && (session.cleanOutput || (session.scene!==false) || !session.roomid || session.director || (session.showList===false))){return;}
clearInterval(updateUserListTimeout);
updateUserListTimeout = setTimeout(function(){
if (updateUserListActive){return;}
updateUserListActive=true;
try {
var added = false;
getById("userList").innerHTML = "";
for (var UUID in session.rpcs){
if ((session.rpcs[UUID].videoElement && session.rpcs[UUID].streamSrc && session.rpcs[UUID].streamSrc.getTracks().length) || (session.rpcs[UUID].canvas) || (session.rpcs[UUID].imageElement)){
if (session.rpcs[UUID].videoElement && document.body.contains(session.rpcs[UUID].videoElement)){
continue;
} else if (session.rpcs[UUID].canvas && document.body.contains(session.rpcs[UUID].canvas)){
continue;
} else if (session.rpcs[UUID].imageElement && document.body.contains(session.rpcs[UUID].imageElement)){
continue;
}
}
if (session.rpcs[UUID].virtualHangup){ // end of screen share / director ?
continue;
}
if ((session.rpcs[UUID].videoMuted || (!session.rpcs[UUID].imageElement && !session.rpcs[UUID].canvas)) || ( session.infocus && session.infocus!==UUID ) || (!session.rpcs[UUID].defaultSpeaker && session.activeSpeaker)){
if (session.directorList.indexOf(UUID)>=0){
if (!session.rpcs[UUID].streamSrc){ // director not active yet, so we won't bother showing it.
continue;
}
}
var insert = document.createElement("div");
if (session.rpcs[UUID].label){
insert.innerText = session.rpcs[UUID].label + "";
} else if (session.directorList.indexOf(UUID)>=0){
insert.innerText = miscTranslations["director"];
} else {
insert.innerText = miscTranslations["unknown-user"];
}
try {
insert.dataset.UUID = UUID;
insert.dataset.sid = session.rpcs[UUID].streamID;
insert.title = "Stream ID: "+session.rpcs[UUID].streamID;
insert.addEventListener('click', function(e) { // show stats of video if double clicked
log("clicked");
try {
var uid = e.currentTarget.dataset.UUID;
if (e.ctrlKey||e.metaKey){
e.preventDefault();
if ("stats" in session.rpcs[uid]){
var [menu, innerMenu] = statsMenuCreator();
printViewStats(innerMenu, uid );
menu.interval = setInterval(printViewStats, session.statsInterval, innerMenu, uid);
}
e.stopPropagation();
return false;
}
} catch(e){errorlog(e);}
});
if (session.statsMenu){
if ("stats" in session.rpcs[UUID]){
if (getById("menuStatsBox")){
clearInterval(getById("menuStatsBox").interval);
getById("menuStatsBox").remove();
}
var [menu, innerMenu] = statsMenuCreator();
printViewStats(innerMenu, UUID );
menu.interval = setInterval(printViewStats, session.statsInterval, innerMenu, UUID);
}
}
} catch(e){}
getById("userList").appendChild(insert);
if (session.rpcs[UUID].videoElement){
var volumeBarInsert = document.createElement("input");
volumeBarInsert.type = "range";
volumeBarInsert.className = "hidden";
volumeBarInsert.setAttribute("orient","vertical");
volumeBarInsert.max = 100;
volumeBarInsert.min = 0;
volumeBarInsert.step = 1;
volumeBarInsert.dataset.UUID = UUID;
volumeBarInsert.setAttribute("value",parseFloat(session.rpcs[UUID].videoElement.volume)*100);
volumeBarInsert.title = volumeBarInsert.value+"%";
volumeBarInsert.oninput = function(e){
session.rpcs[this.dataset.UUID].videoElement.volume = parseInt(this.value)/100 || 0;
if (!session.rpcs[this.dataset.UUID].videoElement.volume){
this.parentNode.classList.add("red");
} else {
this.parentNode.classList.remove("red");
}
};
volumeBarInsert.onchange = function(e){
this.parentNode.querySelector("input").classList.toggle("hidden");
};
var volumeButton = document.createElement("i");
volumeButton.className = "las la-volume-up";
volumeButton.onclick = function(){
this.parentNode.querySelector("input").classList.toggle("hidden");
this.parentNode.volumeBarInsert.focus();
}
var volumeInsert = document.createElement("div");
volumeInsert.className = "volume-control-userlist";
volumeInsert.volumeBarInsert = volumeBarInsert;
insert.appendChild(volumeInsert);
volumeInsert.appendChild(volumeBarInsert);
volumeInsert.appendChild(volumeButton);
}
if (session.rpcs[UUID].remoteMuteState || !(session.rpcs[UUID].streamSrc)){
var muteInsert = document.createElement("div");
muteInsert.className = "video-mute-state-userlist";
muteInsert.innerHTML = '';
insert.appendChild(muteInsert);
} else if (session.rpcs[UUID].voiceMeter){
insert.appendChild(session.rpcs[UUID].voiceMeter);
}
//getById("userList").innerHTML += " ";
added=true;
}
}
if (!added){
getById("connectUsers").style.display = "none";
} else {
getById("connectUsers").style.display = "block";
}
} catch(e){}
updateUserListActive=false;
},200);
}
function resetCanvas(){
log("resetCanvas();");
if (!session.streamSrc){
checkBasicStreamsExist();
return;
}
session.streamSrc.getVideoTracks().forEach((track) => {
session.canvasSource.width = track.getSettings().width || 1280;
session.canvasSource.height = track.getSettings().height || 720;
});
}
var LaunchTFWorkerCallback = false;
function TFLiteWorker(){
if (session.tfliteModule==false){
LaunchTFWorkerCallback=true
return;
}
if (TFLITELOADING){LaunchTFWorkerCallback=true;return;}
LaunchTFWorkerCallback=false;
log("TFLiteWorker() called");
if (!session.tfliteModule.img){
if (!session.selectImageTFLITE_contents){
session.selectImageTFLITE_contents = getById("selectImageTFLITE_contents");
}
if (session.selectImageTFLITE_contents.querySelector("img")){
session.tfliteModule.img = session.selectImageTFLITE_contents.querySelector("img");
session.tfliteModule.img.classList.add("selectedTFImage");
} else if (session.defaultBackgroundImages && session.defaultBackgroundImages.length){
session.tfliteModule.img = document.createElement("img");
session.tfliteModule.img.onload = function(){
URL.revokeObjectURL(session.tfliteModule.img.src); // no longer needed, free memory
}
session.tfliteModule.img.src = session.defaultBackgroundImages[0];
session.tfliteModule.img.classList.add("selectedTFImage");
} else {
session.tfliteModule.img = document.createElement("img");
session.tfliteModule.img.onload = function(){
URL.revokeObjectURL(session.tfliteModule.img.src); // no longer needed, free memory
}
session.tfliteModule.img.src = "./media/bg_sample.webp";
}
}
if (session.tfliteModule.looping){return;}
const segmentationWidth = 256;
const segmentationHeight = 144;
const segmentationPixelCount = segmentationWidth * segmentationHeight;
const inputMemoryOffset = session.tfliteModule._getInputMemoryOffset() / 4;
const outputMemoryOffset = session.tfliteModule._getOutputMemoryOffset() / 4;
const segmentationMask = new ImageData(segmentationWidth, segmentationHeight);
const segmentationMaskCanvas = document.createElement('canvas');
segmentationMaskCanvas.width = segmentationWidth;
segmentationMaskCanvas.height = segmentationHeight;
const segmentationMaskCtx = segmentationMaskCanvas.getContext('2d', {alpha:true, willReadFrequently: true});
session.tfliteModule.nowTime = new Date().getTime();
session.tfliteModule.offsetTime = 0;
function process(){
clearTimeout(session.tfliteModule.timeout);
if (!(session.effect=="3" || session.effect=="4" || session.effect=="5")){
session.tfliteModule.looping=false;
return;
}
if (session.tfliteModule.activelyProcessing){return;}
session.tfliteModule.activelyProcessing=true;
if (session.mobile){
if (screenWidth !== window.innerWidth){
screenWidth = window.innerWidth;
setTimeout(function(){
updateRenderOutpipe();
},200);
session.tfliteModule.looping=false;
session.tfliteModule.activelyProcessing=false;
return;
}
}
try {
segmentationMaskCtx.filter = 'none';
segmentationMaskCtx.drawImage(
session.canvasSource,
0,
0,
session.canvasSource.width,
session.canvasSource.height,
0,
0,
segmentationWidth,
segmentationHeight
)
const imageData = segmentationMaskCtx.getImageData(
0,
0,
segmentationWidth,
segmentationHeight
);
for (let i = 0; i < segmentationPixelCount; i++) {
session.tfliteModule.HEAPF32[inputMemoryOffset + i * 3] = imageData.data[i * 4] / 255;
session.tfliteModule.HEAPF32[inputMemoryOffset + i * 3 + 1] = imageData.data[i * 4 + 1] / 255;
session.tfliteModule.HEAPF32[inputMemoryOffset + i * 3 + 2] = imageData.data[i * 4 + 2] / 255;
}
session.tfliteModule._runInference();
if (!session.experimental){ // standard mode
for (let i = 0; i < segmentationPixelCount; i++) {
const background = session.tfliteModule.HEAPF32[outputMemoryOffset + i * 2];
const person = session.tfliteModule.HEAPF32[outputMemoryOffset + i * 2 + 1];
const shift = Math.max(background, person);
const backgroundExp = Math.exp(background - shift);
const personExp = Math.exp(person - shift);
segmentationMask.data[i * 4 + 3] = Math.min(Math.pow((255 * personExp) / (backgroundExp + personExp),1.5) - 10,255); // softmax
}
segmentationMaskCtx.putImageData(segmentationMask, 0, 0);
session.canvasCtx.globalCompositeOperation = 'copy';
session.canvasCtx.filter = 'blur(8px)';
session.canvasCtx.drawImage(
segmentationMaskCanvas,
0,
0,
segmentationWidth,
segmentationHeight,
0,
0,
session.canvasSource.width,
session.canvasSource.height
)
session.canvasCtx.globalCompositeOperation = 'source-in';
session.canvasCtx.filter = 'none';
session.canvasCtx.drawImage(session.canvasSource, 0, 0);
} else { // experimental mode, so contouring
/* session.canvasCtx.clearRect(0, 0, session.canvasSource.width, session.canvasSource.height);
for (let i = 0; i < segmentationPixelCount; i++) {
const background = session.tfliteModule.HEAPF32[outputMemoryOffset + i * 2];
const person = session.tfliteModule.HEAPF32[outputMemoryOffset + i * 2 + 1];
const shift = Math.max(background, person);
const backgroundExp = Math.exp(background - shift);
const personExp = Math.exp(person - shift);
const value = (255 * personExp) / (backgroundExp + personExp); // softmax
if (value>70){
maskData[i] = 1;
} else if (value>20 && maskData[i]===1){ // was positive, lets keep it positive
maskData[i] = 1;
} else {
maskData[i] = 0;
}
}
try {
contours = FindContours(maskData, segmentationWidth, segmentationHeight);
} catch(e){
contours = [];
}
var awidth = session.canvasSource.width/segmentationWidth;
var aheight = session.canvasSource.height/segmentationHeight;
var biggestContour = [];
for (let contour of contours){
if (!contour.isHole && (contour.points.length > biggestContour.length)){
biggestContour = contour.points;
}
}
if (biggestContour.length) { // don't show small things
session.canvasCtx.filter = "blur(4px)";
session.canvasCtx.beginPath();
biggestContour = approxPolyDP(biggestContour);
let pt = biggestContour.pop();
let lastx= Math.round(pt[0]*awidth);
let lasty= Math.round(pt[1]*aheight);
session.canvasCtx.moveTo(lastx, lasty);
while (biggestContour.length){
pt = biggestContour.pop();
if (pt[0]<=1){pt[0]=-100;}
else if (pt[0]>=segmentationWidth-2){pt[0]=segmentationWidth+100;}
if (pt[1]<=1){pt[1]=-100;}
else if (pt[1]>=segmentationHeight-2){pt[1]=segmentationHeight+100;}
let nox= Math.round(pt[0]*awidth);
let noy= Math.round(pt[1]*aheight);
session.canvasCtx.quadraticCurveTo(lastx, lasty,nox,noy);
lastx = nox;
lasty = noy;
}
session.canvasCtx.closePath();
session.canvasCtx.fill();
}
session.canvasCtx.globalCompositeOperation = 'source-in';
session.canvasCtx.filter = 'none';
session.canvasCtx.drawImage(session.canvasSource, 0, 0); */
}
session.canvasCtx.globalCompositeOperation = 'destination-over';
if (session.effect=="4"){ // greenscreen
session.canvasCtx.filter = 'none';
session.canvasCtx.fillStyle = "#0F0";
session.canvasCtx.fillRect(0, 0, session.canvas.width, session.canvas.height);
} else if (session.effect=="5"){
session.canvasCtx.filter = 'none';
if (session.tfliteModule.img.complete){
try {
session.canvasCtx.drawImage(session.tfliteModule.img, 0, 0, session.canvas.width, session.canvas.height);
} catch(e){}
}
} else if (session.effect=="3"){ // BLUR
if (session.effectValue){
session.canvasCtx.filter = 'blur('+(parseInt(session.effectValue)*2)+'px)';
} else {
session.canvasCtx.filter = 'blur(4px)'; // Does not work on Safari
}
session.canvasCtx.drawImage(session.canvasSource, 0, 0);
session.canvasCtx.filter = 'none';
} else {
session.tfliteModule.activelyProcessing=false;
session.tfliteModule.looping=false;
return;
}
//////
} catch (e){
errorlog(e);
session.tfliteModule.activelyProcessing=false;
session.tfliteModule.looping=false;
return;
}
session.tfliteModule.lastTime = session.tfliteModule.nowTime;
session.tfliteModule.nowTime = new Date().getTime();
var time = 33 - (session.tfliteModule.nowTime - session.tfliteModule.lastTime);
time = time + session.tfliteModule.offsetTime;
session.tfliteModule.activelyProcessing=false;
if (time <= 0 ){
session.tfliteModule.timeout = setTimeout(function(){process();},0);
session.tfliteModule.offsetTime = 0;
} else {
session.tfliteModule.timeout = setTimeout(function(){process();},time);
session.tfliteModule.offsetTime = time;
}
}
function processiOS(){
clearTimeout(session.tfliteModule.timeout);
if (!(session.effect=="3" || session.effect=="4" || session.effect=="5")){
session.tfliteModule.looping=false;
return;
}
if (session.tfliteModule.activelyProcessing){return;}
session.tfliteModule.activelyProcessing=true;
if (screenWidth !== window.innerWidth){
screenWidth = window.innerWidth;
setTimeout(function(){
updateRenderOutpipe();
},200);
session.tfliteModule.looping=false;
session.tfliteModule.activelyProcessing=false;
return;
}
try{
segmentationMaskCtx.drawImage(
session.canvasSource,
0,
0,
session.canvasSource.width,
session.canvasSource.height,
0,
0,
segmentationWidth,
segmentationHeight
)
var imageData = segmentationMaskCtx.getImageData(
0,
0,
segmentationWidth,
segmentationHeight
);
for (let i = 0; i < segmentationPixelCount; i++) {
session.tfliteModule.HEAPF32[inputMemoryOffset + i * 3] = imageData.data[i * 4] / 255;
session.tfliteModule.HEAPF32[inputMemoryOffset + i * 3 + 1] = imageData.data[i * 4 + 1] / 255;
session.tfliteModule.HEAPF32[inputMemoryOffset + i * 3 + 2] = imageData.data[i * 4 + 2] / 255;
}
session.tfliteModule._runInference();
for (let i = 0; i < segmentationPixelCount; i++) {
const background = session.tfliteModule.HEAPF32[outputMemoryOffset + i * 2];
const person = session.tfliteModule.HEAPF32[outputMemoryOffset + i * 2 + 1];
const shift = Math.max(background, person);
const backgroundExp = Math.exp(background - shift);
const personExp = Math.exp(person - shift);
segmentationMask.data[i * 4 + 3] = 255 - (255 * personExp) / (backgroundExp + personExp); // softmax
}
segmentationMaskCtx.putImageData(segmentationMask, 0, 0);
session.canvasCtx.globalCompositeOperation = 'copy';
session.canvasCtx.drawImage(session.canvasSource, 0, 0);
session.canvasCtx.globalCompositeOperation = 'destination-out';
session.canvasCtx.drawImage(
segmentationMaskCanvas,
0,
0,
segmentationWidth,
segmentationHeight,
0,
0,
session.canvasSource.width,
session.canvasSource.height
);
session.canvasCtx.globalCompositeOperation = 'destination-over';
if (session.effect=="4"){ // greenscreen
session.canvasCtx.fillStyle = "#0F0";
session.canvasCtx.fillRect(0, 0, session.canvas.width, session.canvas.height);
} else if (session.effect=="5"){
if (session.tfliteModule.img.complete){
try {
session.canvasCtx.drawImage(session.tfliteModule.img, 0, 0, session.canvas.width, session.canvas.height);
} catch(e){}
}
} else if (session.effect=="3"){ // BLUR
const width = canvasBG.width;
const height = canvasBG.height;
ctxBG.drawImage(session.canvasSource, 0, 0, width, height);
imageData = ctxBG.getImageData(0, 0, width, height);
const { data } = imageData;
// THE BELOW BLUR CODE polyfil is by David Enke
// MIT License: Copyright (c) 2019
// https://github.com/steveseguin/context-filter-polyfill/blob/master/src/filters/blur.filter.ts
const wm = width - 1;
const hm = height - 1;
const rad1 = amount + 1;
const r = [];
const g = [];
const b = [];
//const a = [];
const vmin = [];
const vmax = [];
let iterations = 3; // 1 - 3
let p, p1, p2;
while (iterations-- > 0) {
let yw = 0;
let yi = 0;
for (let y = 0; y < height; y++) {
let rsum = data[yw] * rad1;
let gsum = data[yw + 1] * rad1;
let bsum = data[yw + 2] * rad1;
for (let i = 1; i <= amount; i++) {
p = yw + (((i > wm ? wm : i)) << 2);
rsum += data[p++];
gsum += data[p++];
bsum += data[p++];
}
for (let x = 0; x < width; x++) {
r[yi] = rsum;
g[yi] = gsum;
b[yi] = bsum;
if (y === 0) {
vmin[x] = ((p = x + rad1) < wm ? p : wm) << 2;
vmax[x] = ((p = x - amount) > 0 ? p << 2 : 0);
}
p1 = yw + vmin[x];
p2 = yw + vmax[x];
rsum += data[p1++] - data[p2++];
gsum += data[p1++] - data[p2++];
bsum += data[p1++] - data[p2++];
yi++;
}
yw += (width << 2);
}
for (let x = 0; x < width; x++) {
let yp = x;
let rsum = r[yp] * rad1;
let gsum = g[yp] * rad1;
let bsum = b[yp] * rad1;
for (let i = 1; i <= amount; i++) {
yp += (i > hm ? 0 : width);
rsum += r[yp];
gsum += g[yp];
bsum += b[yp];
}
yi = x << 2;
for (let y = 0; y < height; y++) {
data[yi] = ((rsum * mulSum) >>> shgSum);
data[yi + 1] = ((gsum * mulSum) >>> shgSum);
data[yi + 2] = ((bsum * mulSum) >>> shgSum);
if (x === 0) {
vmin[y] = ((p = y + rad1) < hm ? p : hm) * width;
vmax[y] = ((p = y - amount) > 0 ? p * width : 0);
}
p1 = x + vmin[y];
p2 = x + vmax[y];
rsum += r[p1] - r[p2];
gsum += g[p1] - g[p2];
bsum += b[p1] - b[p2];
yi += width << 2;
}
}
}
////////////// END OF BLUR CODE - MIT LICENCED.
ctxBG.putImageData(imageData, 0, 0);
session.canvasCtx.drawImage(canvasBG, 0, 0, width, height, 0, 0, session.canvas.width, session.canvas.height);
} else {
session.tfliteModule.activelyProcessing=false;
session.tfliteModule.looping=false;
return;
}
} catch (e){
session.tfliteModule.activelyProcessing=false;
session.tfliteModule.looping=false;
errorlog(e);
return;
}
session.tfliteModule.lastTime = session.tfliteModule.nowTime;
session.tfliteModule.nowTime = new Date().getTime();
var time = 33 - (session.tfliteModule.nowTime - session.tfliteModule.lastTime);
time = time + session.tfliteModule.offsetTime;
session.tfliteModule.activelyProcessing=false;
if (time <= 0 ){
session.tfliteModule.timeout = setTimeout(function(){processiOS();},0);
session.tfliteModule.offsetTime = 0;
} else {
session.tfliteModule.timeout = setTimeout(function(){processiOS();},time);
session.tfliteModule.offsetTime = time;
}
}
session.tfliteModule.looping=true;
var screenWidth = window.innerWidth;
if (iOS || iPad || SafariVersion){
var canvasBG = document.createElement("canvas");
var ctxBG = canvasBG.getContext("2d", {alpha: false});
var amount = 1.0;
var mulTable = [1, 57, 41, 21, 203, 34, 97, 73, 227, 91, 149, 62, 105, 45, 39, 137, 241, 107, 3, 173, 39, 71, 65, 238, 219, 101, 187, 87, 81, 151, 141, 133, 249, 117, 221, 209, 197, 187, 177, 169, 5, 153, 73, 139, 133, 127, 243, 233, 223, 107, 103, 99, 191, 23, 177, 171, 165, 159, 77, 149, 9, 139, 135, 131, 253, 245, 119, 231, 224, 109, 211, 103, 25, 195, 189, 23, 45, 175, 171, 83, 81, 79, 155, 151, 147, 9, 141, 137, 67, 131, 129, 251, 123, 30, 235, 115, 113, 221, 217, 53, 13, 51, 50, 49, 193, 189, 185, 91, 179, 175, 43, 169, 83, 163, 5, 79, 155, 19, 75, 147, 145, 143, 35, 69, 17, 67, 33, 65, 255, 251, 247, 243, 239, 59, 29, 229, 113, 111, 219, 27, 213, 105, 207, 51, 201, 199, 49, 193, 191, 47, 93, 183, 181, 179, 11, 87, 43, 85, 167, 165, 163, 161, 159, 157, 155, 77, 19, 75, 37, 73, 145, 143, 141, 35, 138, 137, 135, 67, 33, 131, 129, 255, 63, 250, 247, 61, 121, 239, 237, 117, 29, 229, 227, 225, 111, 55, 109, 216, 213, 211, 209, 207, 205, 203, 201, 199, 197, 195, 193, 48, 190, 47, 93, 185, 183, 181, 179, 178, 176, 175, 173, 171, 85, 21, 167, 165, 41, 163, 161, 5, 79, 157, 78, 154, 153, 19, 75, 149, 74, 147, 73, 144, 143, 71, 141, 140, 139, 137, 17, 135, 134, 133, 66, 131, 65, 129, 1];
var mulSum = mulTable[amount];
var shgTable = [0, 9, 10, 10, 14, 12, 14, 14, 16, 15, 16, 15, 16, 15, 15, 17, 18, 17, 12, 18, 16, 17, 17, 19, 19, 18, 19, 18, 18, 19, 19, 19, 20, 19, 20, 20, 20, 20, 20, 20, 15, 20, 19, 20, 20, 20, 21, 21, 21, 20, 20, 20, 21, 18, 21, 21, 21, 21, 20, 21, 17, 21, 21, 21, 22, 22, 21, 22, 22, 21, 22, 21, 19, 22, 22, 19, 20, 22, 22, 21, 21, 21, 22, 22, 22, 18, 22, 22, 21, 22, 22, 23, 22, 20, 23, 22, 22, 23, 23, 21, 19, 21, 21, 21, 23, 23, 23, 22, 23, 23, 21, 23, 22, 23, 18, 22, 23, 20, 22, 23, 23, 23, 21, 22, 20, 22, 21, 22, 24, 24, 24, 24, 24, 22, 21, 24, 23, 23, 24, 21, 24, 23, 24, 22, 24, 24, 22, 24, 24, 22, 23, 24, 24, 24, 20, 23, 22, 23, 24, 24, 24, 24, 24, 24, 24, 23, 21, 23, 22, 23, 24, 24, 24, 22, 24, 24, 24, 23, 22, 24, 24, 25, 23, 25, 25, 23, 24, 25, 25, 24, 22, 25, 25, 25, 24, 23, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 23, 25, 23, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 24, 22, 25, 25, 23, 25, 25, 20, 24, 25, 24, 25, 25, 22, 24, 25, 24, 25, 24, 25, 25, 24, 25, 25, 25, 25, 22, 25, 25, 25, 24, 25, 24, 25, 18];
var shgSum = shgTable[amount];
log("session.canvas: "+session.canvas.width+"x"+session.canvas.height);
canvasBG.width = parseInt(session.canvas.width/12);;
canvasBG.height = parseInt(session.canvas.height/12);;
ctxBG.width = canvasBG.width;
ctxBG.height = canvasBG.height;
processiOS();
} else {
process();
}
}
function mainMeshMask() {
if ((session.TFJSModel === null) || (session.TFJSModel === true)){
setTimeout(function(){mainMeshMask();},1000);
return;
}
function heatMapColorforValue(value){
var h = parseInt((1.0 - value) * 240);
if (h<0){h=0;}
if (h>240){h=240;}
return "hsl(" + h + ", 100%, 50%)";
}
async function process(){
if (session.TFJSModel.activelyProcessing){return;}
session.TFJSModel.activelyProcessing = true;
clearTimeout(session.TFJSModel.timeout);
if (session.effect!="6"){
//session.TFJSModel.looping=false;
session.TFJSModel.activelyProcessing = false;
return;
}
const predictions = await session.TFJSModel.estimateFaces({
input: session.canvasSource
});
var output = [];
if (predictions.length > 0) {
for (let j = 0; j < predictions.length; j++) {
const fp = predictions[j].annotations;
session.canvasCtx.fillStyle = "#000000";
session.canvasCtx.fillRect(0, 0, session.canvas.width, session.canvas.height);
const keypoints = predictions[j].scaledMesh
for (let i = 0; i < keypoints.length; i++) {
var [x,y,z] = keypoints[i];
x=parseInt(x);
y=parseInt(y);
z=parseInt(z);
if (session.pushEffectsData){
output.push(x);
output.push(y);
}
session.canvasCtx.fillStyle = heatMapColorforValue((z+40)/60);
session.canvasCtx.fillRect(x, y, 5, 5);
}
}
}
if (session.pushEffectsData){
//output = FastIntegerCompression.compress(output);
//log(output);
if (isIFrame){
parent.postMessage({
"effectsData": output,
"eID": session.pushEffectsData
}, session.iframetarget);
} else {
for (var i in session.pcs){
if (!session.pcs[i].sendChannel.bufferedAmount){ // don't overload things.
session.sendMessage({"effectsData": output, "eID":session.effect},i);
}
}
}
}
if (document.hidden) {
session.TFJSModel.lastTime = session.TFJSModel.nowTime || new Date().getTime();
session.TFJSModel.nowTime = new Date().getTime();
var time = 33 - (session.TFJSModel.nowTime - session.TFJSModel.lastTime);
if (time <= 0 ){
session.TFJSModel.timeout = setTimeout(function(){process();},0);
} else {
session.TFJSModel.timeout = setTimeout(function(){process();},time);
}
session.TFJSModel.activelyProcessing = false;
} else {
session.TFJSModel.timeout = setTimeout(function(){process();},33);
session.TFJSModel.activelyProcessing = false;
window.requestAnimationFrame(process);
}
}
process();
}
var faceDetector = false;
var faceAlignment=false;
var activeDetection = false;
function drawFace() {
if (session.effect !== "1"){return;}
if (faceAlignment){
faceAlignment();
return;
} else if (faceAlignment===null){
return;
}
faceAlignment = null;
var timers = {};
timers.activelyProcessing=false;
timers.activelyProcessingDraw = false;
var ctx = session.canvasCtx;
function fde1(){
warnlog("LOADED drawFace()");
var lastFace = {};
//session.canvasSource.width = session.canvasSource.srcObject.getVideoTracks()[0].getSettings().width || 1280;
//session.canvasSource.height = session.canvasSource.srcObject.getVideoTracks()[0].getSettings().height || 720;
lastFace.x = session.canvasSource.width / 2;
lastFace.y = session.canvasSource.height / 2;
lastFace.w = session.canvasSource.width;
lastFace.h = session.canvasSource.height;
session.canvas.height = 2 * parseInt(session.canvasSource.height / 2);
session.canvas.width = 2 * parseInt(session.canvasSource.width / 2);
function detectFace(){
if (activeDetection){return;}
activeDetection=true;
if (session.effect !== "1"){return;}
try {
faceDetector.detect(session.canvasSource).then(faces => {
if (faces.length){
for (let face of faces) {
lastFace.x = face.boundingBox.x;
lastFace.y = face.boundingBox.y;
lastFace.w = face.boundingBox.width;
lastFace.h = face.boundingBox.height;
break;
}
}
//setTimeout(function(){draw();},0);
}).catch((e) => {
//errorlog("Boo, Face Detection failed: " + e);
});
} catch(e){}
setTimeout(function(){detectFace();},200);
activeDetection = false;
}
var wh = null;
var xa = null;
var ya = null;
function draw() {
if (timers.activelyProcessingDraw){return;}
timers.activelyProcessingDraw = true;
clearTimeout(timers.timeoutDraw);
if (session.effect !== "1"){
timers.activelyProcessingDraw = false;
return;
}
try {
if (!session.canvasSource.width){
timers.timeoutDraw = setTimeout(function(){draw();},1000);
timers.activelyProcessingDraw = false;
return
}
if (wh === null && session.canvasSource.width){
wh = Math.pow(session.canvasSource.width * session.canvasSource.width/36,0.5);
xa = 0
ya = 0;
}
session.canvas.height = 2 * parseInt(session.canvasSource.height / 2);
session.canvas.width = 2 * parseInt(session.canvasSource.width / 2);
if (lastFace.w){
wh = wh * 0.999 + Math.pow(lastFace.w*lastFace.h,0.5)*0.001;
var w = wh*6;
if (w>session.canvasSource.width){
w = session.canvasSource.width;
}
if (session.canvasSource.height>session.canvasSource.width){
if (w>session.canvasSource.height && session.canvasSource.height>session.canvasSource.width){
w = session.canvasSource.height;
}
} else if (w>session.canvasSource.width){
w = session.canvasSource.width;
}
var h = (w/session.canvasSource.width) * session.canvasSource.height;
xa = xa*0.998 + 0.002*(lastFace.x + lastFace.w/2);
ya = ya*0.998 + 0.002*(lastFace.y + lastFace.h/2);
var x = xa - w/2;
var y = ya - h/2;
if (x<0){x=0;}
if (y<0){y=0;}
if (x>session.canvasSource.width-w){x=session.canvasSource.width-w;}
if (y>session.canvasSource.height-h){y=session.canvasSource.height-h;}
if (x<0){x=0;}
if (y<0){y=0;}
}
//console.log(x, y, w, h, session.canvasSource.width, session.canvasSource.height);
ctx.drawImage(session.canvasSource, x, y, w, h, 0, 0, session.canvasSource.width, session.canvasSource.height);
//ctx.beginPath();
//ctx.rect(lastFace.x, lastFace.y, lastFace.w, lastFace.h);
// ctx.stroke();
} catch(e){}
if (document.hidden){
timers.lastTimeDraw = timers.nowTimeDraw || new Date().getTime();
timers.nowTimeDraw = new Date().getTime();
var time = 33 - (timers.nowTimeDraw - timers.lastTimeDraw);
if (time <= 0 ){
timers.timeoutDraw = setTimeout(function(){draw();},0);
} else {
timers.timeoutDraw = setTimeout(function(){draw();},time);
}
timers.activelyProcessingDraw = false;
} else {
timers.timeoutDraw = setTimeout(function(){draw();},33);
timers.activelyProcessingDraw = false;
window.requestAnimationFrame(draw);
}
}
if (window.FaceDetector == undefined) {
if (!session.cleanOutput){
warnUser('Face Detection API not detected.\n\nYou may be able to enable it here: chrome://flags/#enable-experimental-web-platform-features');
}
faceDetector = false;
} else {
faceDetector = new FaceDetector();
}
function fde2(){
if (!timers.activelyProcessingDraw){
draw();
}
if (!activeDetection){
detectFace();
}
};
fde2();
return fde2;
};
faceAlignment = fde1();
}
//////// END CANVAS EFFECTS ///////////////////
var getFacesActive = false;
async function getFaces(){
if (getFacesActive){return;}
getFacesActive = true;
if (session.grabFaceData){
if (!faceDetector){
if (window.FaceDetector == undefined) {
if (!session.cleanOutput){
warnUser('Face Detection API not detected.\n\nYou may be able to enable it here: chrome://flags/#enable-experimental-web-platform-features');
}
session.grabFaceData = false;
faceDetector = false;
getFacesActive = false;
return;
} else {
session.grabFaceData = 1;
faceDetector = new FaceDetector();
}
}
try {
var videos = {};
for (var UUID in session.rpcs){
if (session.rpcs[UUID].videoElement){
await faceDetector.detect(session.rpcs[UUID].videoElement).then(faces => {
videos[session.rpcs[UUID].streamID] = {};
videos[session.rpcs[UUID].streamID].videoWidth = session.rpcs[UUID].videoElement.videoWidth
videos[session.rpcs[UUID].streamID].videoHeight = session.rpcs[UUID].videoElement.videoHeight
videos[session.rpcs[UUID].streamID].faces = faces;
}).catch((e) => {
//errorlog("Boo, Face Detection failed: " + e);
});
}
}
log(videos);
} catch(e){}
pokeIframeAPI('face-tracking-data',videos);
setTimeout(function(){getFaces();},200);
}
getFacesActive=false;
}
//////
var simpleDrawMain=false;
function simpleDraw(reinit=false) {
if (session.effect !== "8"){return;}
if (simpleDrawMain){
simpleDrawMain(reinit);
return;
} else if (simpleDrawMain===null){
return;
}
simpleDrawMain = null;
var timers = {};
timers.activelyProcessing=false;
timers.activelyProcessingDraw = false;
var ctx = session.canvasCtx;
function fde1(){
try{
warnlog("LOADED simpleDraw()");
session.canvas.height = 2 * parseInt(session.canvasSource.height / 2);
session.canvas.width = 2 * parseInt(session.canvasSource.width / 2);
function draw() {
if (timers.activelyProcessingDraw){return;}
timers.activelyProcessingDraw = true;
clearTimeout(timers.timeoutDraw);
if (session.effect !== "8"){
timers.activelyProcessingDraw = false;
return;
}
try {
if (!session.canvasSource.width){
timers.timeoutDraw = setTimeout(function(){draw();},1000);
timers.activelyProcessingDraw = false;
return
}
session.canvas.height = 2 * parseInt(session.canvasSource.height / 2);
session.canvas.width = 2 * parseInt(session.canvasSource.width / 2);
ctx.drawImage(session.canvasSource, 0, 0, session.canvasSource.width, session.canvasSource.height, 0,0,session.canvasSource.width, session.canvasSource.height);
} catch(e){errorlog(e);}
if (document.hidden){
timers.lastTimeDraw = timers.nowTimeDraw || new Date().getTime();
timers.nowTimeDraw = new Date().getTime();
var time = 33 - (timers.nowTimeDraw - timers.lastTimeDraw);
if (time <= 0 ){
timers.timeoutDraw = setTimeout(function(){draw();},0);
} else {
timers.timeoutDraw = setTimeout(function(){draw();},time);
}
timers.activelyProcessingDraw = false;
} else {
timers.timeoutDraw = setTimeout(function(){draw();},33);
timers.activelyProcessingDraw = false;
window.requestAnimationFrame(draw);
}
}
} catch(e){
errorlog(e);
timers.activelyProcessingDraw = false;
}
function fde2(reinit=false){
if (reinit){
if (session.canvasSource && session.canvasSource.srcObject && session.canvasSource.srcObject.getVideoTracks().length){
session.canvasSource.width = session.canvasSource.srcObject.getVideoTracks()[0].getSettings().width || 1280;
session.canvasSource.height = session.canvasSource.srcObject.getVideoTracks()[0].getSettings().height || 720;
}
}
if (!timers.activelyProcessingDraw){
draw();
}
};
fde2();
return fde2;
};
simpleDrawMain = fde1();
}
//////// END CANVAS EFFECTS ///////////////////
//////
var digitalZoomMain=false;
function digitalZoom(reinit=false) {
if (session.effect !== "7"){return;}
if (digitalZoomMain){
digitalZoomMain(reinit);
return;
} else if (digitalZoomMain===null){
return;
}
digitalZoomMain = null;
var timers = {};
timers.activelyProcessing=false;
timers.activelyProcessingDraw = false;
var ctx = session.canvasCtx;
function fde1(){
try{
warnlog("LOADED digitalZoom()");
session.canvas.height = 2 * parseInt(session.canvasSource.height / 2);
session.canvas.width = 2 * parseInt(session.canvasSource.width / 2);
var xa = null;
var ya = null;
var zz = 1;
function draw() {
if (timers.activelyProcessingDraw){return;}
timers.activelyProcessingDraw = true;
clearTimeout(timers.timeoutDraw);
if (session.effect !== "7"){
zz = 1.0;
xa = 0;
ya = 0;
timers.activelyProcessingDraw = false;
return;
}
try {
if (!session.canvasSource.width){
timers.timeoutDraw = setTimeout(function(){draw();},1000);
timers.activelyProcessingDraw = false;
return
}
if (xa === null && session.canvasSource.width){
xa = 0;
ya = 0;
}
session.canvas.height = 2 * parseInt(session.canvasSource.height / 2);
session.canvas.width = 2 * parseInt(session.canvasSource.width / 2);
if (session.effectValue){
zz = 0.9*zz + session.effectValue *0.1;
xa = xa*0.9+ 0.1*(session.canvasSource.width - session.canvasSource.width/zz);
ya = ya*0.9 + 0.1*(session.canvasSource.height - session.canvasSource.height/zz);
}
//console.log(parseInt(zz), parseInt(xa), parseInt(ya), parseInt(session.canvasSource.width-xa*2), parseInt(session.canvasSource.height-ya*2));
ctx.drawImage(session.canvasSource, xa, ya, session.canvasSource.width-xa*2, session.canvasSource.height-ya*2, 0,0,session.canvasSource.width, session.canvasSource.height);
//ctx.beginPath();
//ctx.rect(lastFace.x, lastFace.y, lastFace.w, lastFace.h);
// ctx.stroke();
} catch(e){errorlog(e);}
if (document.hidden){
timers.lastTimeDraw = timers.nowTimeDraw || new Date().getTime();
timers.nowTimeDraw = new Date().getTime();
var time = 33 - (timers.nowTimeDraw - timers.lastTimeDraw);
if (time <= 0 ){
timers.timeoutDraw = setTimeout(function(){draw();},0);
} else {
timers.timeoutDraw = setTimeout(function(){draw();},time);
}
timers.activelyProcessingDraw = false;
} else {
timers.timeoutDraw = setTimeout(function(){draw();},33);
timers.activelyProcessingDraw = false;
window.requestAnimationFrame(draw);
}
}
} catch(e){
errorlog(e);
timers.activelyProcessingDraw = false;
}
function fde2(reinit=false){
if (reinit){
if (session.canvasSource && session.canvasSource.srcObject && session.canvasSource.srcObject.getVideoTracks().length){
session.canvasSource.width = session.canvasSource.srcObject.getVideoTracks()[0].getSettings().width || 1280;
session.canvasSource.height = session.canvasSource.srcObject.getVideoTracks()[0].getSettings().height || 720;
}
xa = null;
}
if (!timers.activelyProcessingDraw){
draw();
}
};
fde2();
return fde2;
};
digitalZoomMain = fde1();
}
//////// END CANVAS EFFECTS ///////////////////
function getNativeOutputResolution(){
var tracks = session.videoElement.srcObject.getVideoTracks();
if (tracks.length && tracks[0].getSettings){
return tracks[0].getSettings();
} else {
return false;
}
}
function toggleSceneStats(button){
var UUID = button.dataset.UUID;
if (button.value==1){
button.value = 0;
button.classList.remove("pressed");
button.ariaPressed = "false";
session.rpcs[UUID].allowGraphs = false;
} else {
button.value = 1;
button.classList.add("pressed");
button.ariaPressed = "true";
session.rpcs[UUID].allowGraphs = true;
}
if (button.value==1){
getById("container_" + UUID).querySelectorAll('[data-no-scenes]').forEach(ele=>{
ele.classList.remove("hidden");
if (ele.dataset.message){
ele.innerHTML = "Requesting data ..";
}
});
if (getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-bitrate"]')){
getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-bitrate"]').classList.remove("hidden");
}
if (getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-details"]')){
getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-details"]').classList.remove("hidden");
}
session.sendRequest({'requestStatsContinuous':true, }, UUID);
} else {
session.sendRequest({'requestStatsContinuous':false, }, UUID);
if (getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-bitrate"]')){
getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-bitrate"]').classList.add("hidden");
}
if (getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-details"]')){
getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-details"]').classList.add("hidden");
}
}
}
function getColor(value) {
var hue = ((value) * 120).toString(10);
return ["hsl(", hue, ",100%,50%)"].join("");
}
function plotData(info, UUID, uuid) { // type = "bitrate" or "nacks"
log("plot data");
var container = getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-bitrate"]');
if (!container){
log("container not found");
return;
}
var canvas = getById("container_" + UUID).querySelector('canvas[data-uid="'+uuid+'"]');
var canvasNew = false
if (!canvas){
canvasNew = true;
canvas = document.createElement("canvas");
canvas.height = 50;
canvas.width = 124;
canvas.className = "canvasStats";
canvas.history_nacks = [];
canvas.history_bitrate = [];
canvas.target = 4000;
if (info.scene){
canvas.title = "Scene: "+info.scene+". Red/orange implies packet loss. Y-axis is marked with 2500-kbps increments.";
} else if (info.label){
canvas.title = "Label: "+info.label+". Red/orange implies packet loss. Y-axis is marked with 2500-kbps increments";
} else {
canvas.title = "Red/orange implies packet loss. Y-axis is marked with 2500-kbps increments";
}
canvas.dataset.uid = uuid;
container.appendChild(canvas);
}
selfDestructElement(UUID, uuid);
var context = canvas.getContext("2d");
var bitrate = 0;
if ("video_bitrate_kbps" in info){
bitrate = info.video_bitrate_kbps;
}
if (isNaN(bitrate)) {
bitrate = 0;
}
if (bitrate<0){bitrate = 0;}
var nacks = 0;
if ("nacks_per_second" in info){
nacks = info.nacks_per_second;
}
if (isNaN(nacks)) {
nacks = 0;
}
if (nacks<0){nacks = 0;}
var height = context.canvas.height;
var width = context.canvas.width;
canvas.history_nacks.push(nacks);
canvas.history_bitrate.push(bitrate);
canvas.history_nacks = canvas.history_nacks.slice(-125);
canvas.history_bitrate = canvas.history_bitrate.slice(-125);
var maxBitrate = Math.max(...canvas.history_bitrate);
var target = canvas.target || 4000;
if (target && (maxBitrate > target)){
canvas.target = maxBitrate*1.5; // set it higher than it needs to be, so it doens't jump around a lot
var yScale = height / canvas.target;
context.clearRect(0, 0, width, height);
var x = width - 1;
var w = 1;
for (var i = 0; i1){val=1;}
else if (val<0){val=0;}
var color = getColor(val);
var y = height - bitrate * yScale;
context.fillStyle = color;
context.fillRect(x, y, w, height);
context.fillStyle = "#DDD5";
context.fillRect(x, y-2, w, 4);
if (y-5>0){
context.fillStyle = "#FFF3";
context.fillRect(x, y+2, w, 1);
}
var imageData = context.getImageData(1, 0, width - 1, height);
context.putImageData(imageData, 0, 0);
context.clearRect(width - 1, 0, 1, height);
}
for (var tt = 2500; tt1){val=1;}
else if (val<0){val=0;}
var color = getColor(val);
var yScale = height / target;
var x = width - 1;
var y = height - bitrate * yScale;
var w = 1;
context.fillStyle = color;
context.fillRect(x, y, w, height);
context.fillStyle = "#DDD5";
context.fillRect(x, y-2, w, 4);
if (y-5>0){
context.fillStyle = "#FFF3";
context.fillRect(x, y+2, w, 1);
}
context.fillStyle = "#0555";
if (canvasNew){
for (var tt = 2500; tt{
ele.classList.remove("greyout");
clearTimeout(ele.selfFadeout);
ele.selfFadeout = setTimeout(function(ele){
ele.classList.add("greyout");
}, 4000, ele);
clearTimeout(ele.selfDestruct);
ele.selfDestruct = setTimeout(function(ele){
ele.remove();
}, 10000, ele);
});
}
function remoteStats(msg, UUID){
if (isIFrame){
parent.postMessage({"remoteStats": msg.remoteStats , "streamID": session.rpcs[UUID].streamID, "UUID": UUID}, session.iframetarget);
}
if (!(session.rpcs[UUID].allowGraphs || session.allowGraphs)){return;}
if (session.director){
//var output = "";
var size = 0;
for (var key in msg.remoteStats) {
if (msg.remoteStats.hasOwnProperty(key)){
size++;
}
}
if (!size){
getById("container_" + UUID).querySelectorAll('[data-no-scenes]').forEach(ele=>{
ele.classList.remove("hidden");
if (ele.dataset.message){
ele.innerHTML = "No scenes active";
}
});
log("zero size");
return;
}
getById("container_" + UUID).querySelectorAll('[data-no-scenes]').forEach(ele=>{
ele.classList.add("hidden");
});
for (var uuid in msg.remoteStats){
var container = getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-details-container"][data-uid="'+uuid+'"]');
if (!container){
container = getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-details-container"]').cloneNode(true);
container.dataset.uid = uuid;
container.classList.remove("hidden");
getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-details"]').appendChild(container);
}
plotData(msg.remoteStats[uuid], UUID, uuid);
if (("video_bitrate_kbps" in msg.remoteStats[uuid]) && (msg.remoteStats[uuid].video_bitrate_kbps!=="video_bitrate_kbps")){
var span = container.querySelector('[data-bitrate]');
if (span){
span.classList.remove("hidden");
span.innerHTML = "video bitrate: "+parseInt(msg.remoteStats[uuid].video_bitrate_kbps) + " (kbps)";
}
}
var span = container.querySelector('[data-scene-name]');
if (span && ("label" in msg.remoteStats[uuid]) && msg.remoteStats[uuid].label){
span.classList.remove("hidden");
span.innerHTML = "stats for viewer: " + msg.remoteStats[uuid].label;
} else if (span && ("scene" in msg.remoteStats[uuid]) && (msg.remoteStats[uuid].scene !==false)){
span.classList.remove("hidden");
span.innerHTML = "stats for scene: " + msg.remoteStats[uuid].scene;
} else if (uuid==="meshcast"){
span.classList.remove("hidden");
span.innerHTML = "stats for meshcast ingest";
span.title = "You can use &label=xxxx to give your view links a unique label";
} else {
span.classList.remove("hidden");
span.innerHTML = "stats for some viewer";
span.title = "You can use &label=xxxx to give your view links a unique label";
}
if ("resolution" in msg.remoteStats[uuid]){
var span = container.querySelector('[data-resolution]');
if (span){
span.classList.remove("hidden");
span.innerHTML = msg.remoteStats[uuid].resolution;
}
}
if ("video_encoder" in msg.remoteStats[uuid]){
var span = container.querySelector('[data-video-codec]');
if (span){
span.classList.remove("hidden");
span.innerHTML = "video codec: "+msg.remoteStats[uuid].video_encoder;
}
}
}
};
}
function processStats(UUID){
// for (pc in session.pcs){session.pcs[pc].getStats().then(function(stats) {stats.forEach(stat=>{if (stat.id.includes("RTCIce")){console.log(stat)}})})};
if (!session.rpcs || !(UUID in session.rpcs)){
return;
}
try {
if (session.rpcs[UUID].videoElement.paused){
if (session.firstPlayTriggered){
if (session.audioCtx.state == "suspended"){ // added oct 9th 2022
try {
session.audioCtx.resume();
} catch(e){warnlog(e);}
}
if (session.audioCtx.state == "running"){ // NOTE: I Don't know why this was
log("trying to play");
session.rpcs[UUID].videoElement.play().then(_ => {
log("playing 8");
//if ((session.audioEffects===true) || session.pushLoudness){
// updateIncomingAudioElement(UUID);
//}
}).catch(warnlog);
}
}
}
} catch (e){};
if (session.rpcs[UUID].mc){
processMeshcastStats(UUID);
}
try {
if (session.rpcs[UUID].realUUID && session.rpcs[session.rpcs[UUID].realUUID]){
var node = session.rpcs[session.rpcs[UUID].realUUID];
} else {
var node = session.rpcs[UUID];
}
var validTrackIds = [];
if (session.rpcs[UUID].videoElement){
session.rpcs[UUID].videoElement.srcObject.getTracks().forEach(trk=>{
validTrackIds.push(trk.id);
});
}
if (node.getStats){
node.getStats().then(function(stats){
if (!(UUID in session.rpcs)){return;}
setTimeout(processStats, session.statsInterval, UUID);
if (!session.rpcs[UUID].stats['Peer-to-Peer_Connection']){
session.rpcs[UUID].stats['Peer-to-Peer_Connection'] = {};
}
var nominatedCandidate = false;
var candidates = {};
stats.forEach(stat=>{
try {
if (stat.id && stat.id.startsWith("DEPRECATED_")){return;}
var trackID = stat.trackIdentifier || stat.id || false;
if ((stat.type=="track") && stat.remoteSource){
if (stat.trackIdentifier && !validTrackIds.includes(stat.trackIdentifier)){
return;
}
if (stat.id in session.rpcs[UUID].stats){
session.rpcs[UUID].stats[stat.id]._trackID = stat.trackIdentifier;
session.rpcs[UUID].stats[stat.id].Jitter_Buffer_ms = parseInt(1000*(parseFloat(stat.jitterBufferDelay) - session.rpcs[UUID].stats[stat.id]._jitter_delay)/(parseInt(stat.jitterBufferEmittedCount) - session.rpcs[UUID].stats[stat.id]._jitter_count)) || 0;
session.rpcs[UUID].stats[stat.id]._jitter_delay = parseFloat(stat.jitterBufferDelay) || 0;
session.rpcs[UUID].stats[stat.id]._jitter_count = parseInt(stat.jitterBufferEmittedCount) || 0;
if ("frameWidth" in stat){
if ("frameHeight" in stat){
session.rpcs[UUID].stats[stat.id].Resolution = stat.frameWidth+" x "+stat.frameHeight;
session.rpcs[UUID].stats[stat.id]._frameWidth = stat.frameWidth;
session.rpcs[UUID].stats[stat.id]._frameHeight = stat.frameHeight;
}
}
} else {
var media = {};
media._jitter_delay = parseFloat(stat.jitterBufferDelay) || 0;
media._jitter_count = parseInt(stat.jitterBufferEmittedCount) || 0;
media.Jitter_Buffer_ms = 0;
media._trackID = stat.trackIdentifier;
session.rpcs[UUID].stats[stat.id] = media;
if (stat.kind && stat.kind=="audio"){
session.rpcs[UUID].stats[stat.id].type = "Audio Track";
session.rpcs[UUID].stats[stat.id]._type = "audio";
} else if (stat.kind && stat.kind=="video"){
session.rpcs[UUID].stats[stat.id].type = "Video Track";
session.rpcs[UUID].stats[stat.id]._type = "video";
}
}
} else if (stat.type == "remote-candidate") {
candidates[stat.id] = stat;
} else if (stat.type == "local-candidate") {
candidates[stat.id] = stat;
} else if ((stat.type == "candidate-pair" ) && (stat.nominated)) {
if (!nominatedCandidate){
nominatedCandidate = stat;
} else if (nominatedCandidate.priority < stat.priority){
nominatedCandidate = stat;
}
} else if (stat.type == "transport"){
if ("bytesReceived" in stat) {
if ("_bytesReceived" in session.rpcs[UUID].stats['Peer-to-Peer_Connection']){
if (session.rpcs[UUID].stats['Peer-to-Peer_Connection']._timestamp){
if (stat.timestamp){
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].total_recv_bitrate_kbps = parseInt(8*(stat.bytesReceived - session.rpcs[UUID].stats['Peer-to-Peer_Connection']._bytesReceived)/(stat.timestamp - session.rpcs[UUID].stats['Peer-to-Peer_Connection']._timestamp));
hideStreamLowBandwidth(session.rpcs[UUID].stats['Peer-to-Peer_Connection'].total_recv_bitrate_kbps, UUID);
//changeSceneLowBandwidth(session.rpcs[UUID].stats['Peer-to-Peer_Connection'].total_recv_bitrate_kbps, UUID);
}
}
}
session.rpcs[UUID].stats['Peer-to-Peer_Connection']._bytesReceived = stat.bytesReceived;
}
if ("timestamp" in stat) {
session.rpcs[UUID].stats['Peer-to-Peer_Connection']._timestamp = stat.timestamp;
if (!session.rpcs[UUID].stats['Peer-to-Peer_Connection']._timestampStart){
session.rpcs[UUID].stats['Peer-to-Peer_Connection']._timestampStart = stat.timestamp;
} else {
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].time_active_minutes = parseInt((stat.timestamp - session.rpcs[UUID].stats['Peer-to-Peer_Connection']._timestampStart)/600)/100;
}
}
} else if ((stat.type=="inbound-rtp") && trackID){
if (stat.trackIdentifier && !validTrackIds.includes(stat.trackIdentifier)){
return;
}
session.rpcs[UUID].stats[trackID] = session.rpcs[UUID].stats[trackID] || {};
if (stat.trackIdentifier){
session.rpcs[UUID].stats[trackID]._trackID = stat.trackIdentifier;
}
if ("jitterBufferDelay" in stat){
session.rpcs[UUID].stats[trackID].Jitter_Buffer_ms = parseInt(1000*(parseFloat(stat.jitterBufferDelay) - session.rpcs[UUID].stats[trackID]._jitter_delay_2)/(parseInt(stat.jitterBufferEmittedCount) - session.rpcs[UUID].stats[trackID]._jitter_count_2)) || 0;
session.rpcs[UUID].stats[trackID]._jitter_delay_2 = parseFloat(stat.jitterBufferDelay) || 0;
session.rpcs[UUID].stats[trackID]._jitter_count_2 = parseInt(stat.jitterBufferEmittedCount) || 0;
}
if ("frameWidth" in stat){
if ("frameHeight" in stat){
session.rpcs[UUID].stats[trackID].Resolution = stat.frameWidth+" x "+stat.frameHeight;
session.rpcs[UUID].stats[trackID]._frameWidth = stat.frameWidth;
session.rpcs[UUID].stats[trackID]._frameHeight = stat.frameHeight;
}
}
session.rpcs[UUID].stats[trackID].Bitrate_in_kbps = parseInt(8*(stat.bytesReceived - (session.rpcs[UUID].stats[trackID]._last_bytes || 0))/( stat.timestamp - session.rpcs[UUID].stats[trackID]._last_time));
session.rpcs[UUID].stats[trackID]._last_bytes = stat.bytesReceived || session.rpcs[UUID].stats[trackID]._last_bytes;
session.rpcs[UUID].stats[trackID]._last_time = stat.timestamp || session.rpcs[UUID].stats[trackID]._last_time;
if (stat.mediaType=="video"){
session.rpcs[UUID].stats._codecId = stat.codecId;
session.rpcs[UUID].stats._codecIdTrackId = trackID;
session.rpcs[UUID].stats[trackID].type = "Video Stream"
session.rpcs[UUID].stats[trackID]._type = "video";
if ((session.obsfix) && ("codec" in session.rpcs[UUID].stats) && (session.rpcs[UUID].stats.codec=="video/VP8")){
session.rpcs[UUID].stats[trackID].pliDelta = (stat.pliCount - session.rpcs[UUID].stats[trackID].keyFramesRequested_pli) || 0;
session.rpcs[UUID].stats[trackID].nackTrigger = (stat.nackCount - session.rpcs[UUID].stats[trackID].streamErrors_nackCount + session.rpcs[UUID].stats[trackID].nackTrigger) || 0;
log("OBS PLI FIX MODE ON");
if ((session.rpcs[UUID].stats[trackID].pliDelta===0) && (session.rpcs[UUID].stats[trackID].nackTrigger >= session.obsfix)){ // heavy packet loss with no pliCount?
session.requestKeyframe(UUID);
session.rpcs[UUID].stats[trackID].nackTrigger = 0;
log("TRYING KEYFRAME");
} else if (session.rpcs[UUID].stats[trackID].pliDelta>0){
session.rpcs[UUID].stats[trackID].nackTrigger = 0;
}
} else if ((session.obsfix) && ("codec" in session.rpcs[UUID].stats) && (session.rpcs[UUID].stats.codec=="video/VP9")){
session.rpcs[UUID].stats[trackID].pliDelta = (stat.pliCount - session.rpcs[UUID].stats[trackID].keyFramesRequested_pli) || 0;
session.rpcs[UUID].stats[trackID].nackTrigger = (stat.nackCount - session.rpcs[UUID].stats[trackID].streamErrors_nackCount + session.rpcs[UUID].stats[trackID].nackTrigger) || 0;
log("OBS PLI FIX MODE ON");
if ((session.rpcs[UUID].stats[trackID].pliDelta===0) && (session.rpcs[UUID].stats[trackID].nackTrigger >= (session.obsfix*4) )){ // heavy packet loss with no pliCount? well, VP9 will trigger hopefully not as often.
session.requestKeyframe(UUID);
session.rpcs[UUID].stats[trackID].nackTrigger = 0;
log("TRYING KEYFRAME");
} else if (session.rpcs[UUID].stats[trackID].pliDelta>0){
session.rpcs[UUID].stats[trackID].nackTrigger = 0;
}
}
session.rpcs[UUID].stats[trackID].keyFramesRequested_pli = stat.pliCount || 0;
session.rpcs[UUID].stats[trackID].streamErrors_nackCount = stat.nackCount || 0;
//warnlog(stat);
if ("framesPerSecond" in stat){
session.rpcs[UUID].stats[trackID].FPS = parseInt(stat.framesPerSecond);
} else if (("framesDecoded" in stat) && (stat.timestamp)){
var lastFramesDecoded = 0;
var lastTimestamp = 0;
try{
lastFramesDecoded = session.rpcs[UUID].stats[trackID]._framesDecoded;
lastTimestamp = session.rpcs[UUID].stats[trackID]._timestamp;
} catch(e){}
session.rpcs[UUID].stats[trackID].FPS = parseInt(10*(stat.framesDecoded - lastFramesDecoded)/(stat.timestamp/1000 - lastTimestamp))/10;
//session.rpcs[UUID].stats[trackID].FPS = parseInt((stat.framesDecoded - lastFramesDecoded)/(stat.timestamp/1000 - lastTimestamp));
session.rpcs[UUID].stats[trackID]._framesDecoded = stat.framesDecoded;
session.rpcs[UUID].stats[trackID]._timestamp = stat.timestamp/1000;
}
} else if (stat.mediaType=="audio"){
//log("AUDIO LEVEL: "+stat.audioLevel);
session.rpcs[UUID].stats._audioCodecId = stat.codecId;
session.rpcs[UUID].stats._audioCodecIdTrackId = trackID;
session.rpcs[UUID].stats[trackID].type = "Audio Stream";
session.rpcs[UUID].stats[trackID]._type = "audio";
if ("audioLevel" in stat){
session.rpcs[UUID].stats[trackID].audio_level = parseInt(parseFloat(stat.audioLevel)*10000)/10000.0;
}
}
if ("packetsLost" in stat && "packetsReceived" in stat){
if (!("_packetsLost" in session.rpcs[UUID].stats[trackID])){
session.rpcs[UUID].stats[trackID]._packetsLost = stat.packetsLost;
}
if (!("_packetsReceived" in session.rpcs[UUID].stats[trackID])){
session.rpcs[UUID].stats[trackID]._packetsReceived = stat.packetsReceived;
}
if (!("packetLoss_in_percentage" in session.rpcs[UUID].stats[trackID])){
session.rpcs[UUID].stats[trackID].packetLoss_in_percentage = 0;
}
let packetLoss = ((stat.packetsLost-session.rpcs[UUID].stats[trackID]._packetsLost)*100.0)/((stat.packetsReceived-session.rpcs[UUID].stats[trackID]._packetsReceived)+(stat.packetsLost-session.rpcs[UUID].stats[trackID]._packetsLost)) || 0;
/* if (session.rpcs[UUID].stats[trackID]._type && (session.rpcs[UUID].stats[trackID]._type =="video")){
if (packetLoss>1){
var data = {};
data.bitrate = parseInt(session.rpcs[UUID].stats[trackID].Bitrate_in_kbps*0.8);
session.sendRequest(data,UUID);
} else {
var data = {};
data.bitrate = parseInt(session.rpcs[UUID].stats[trackID].Bitrate_in_kbps*1.1);
session.sendRequest(data,UUID);
}
} */
session.rpcs[UUID].stats[trackID].packetLoss_in_percentage = session.rpcs[UUID].stats[trackID].packetLoss_in_percentage*0.35 + 0.65*packetLoss;
if (session.rpcs[UUID].signalMeter && (session.rpcs[UUID].stats[trackID]._type==="video")){
if (session.rpcs[UUID].stats[trackID].packetLoss_in_percentage<0.01){
if (session.rpcs[UUID].stats[trackID].Bitrate_in_kbps==0){
session.rpcs[UUID].signalMeter.dataset.level = 0;
} else {
session.rpcs[UUID].signalMeter.dataset.level = 5;
}
} else if (session.rpcs[UUID].stats[trackID].packetLoss_in_percentage<0.3){
session.rpcs[UUID].signalMeter.dataset.level = 4;
} else if (session.rpcs[UUID].stats[trackID].packetLoss_in_percentage<1.0){
session.rpcs[UUID].signalMeter.dataset.level = 3;
} else if (session.rpcs[UUID].stats[trackID].packetLoss_in_percentage<3.5){
session.rpcs[UUID].signalMeter.dataset.level = 2;
} else {
session.rpcs[UUID].signalMeter.dataset.level = 1;
}
}
session.rpcs[UUID].stats[trackID]._packetsReceived = stat.packetsReceived;
session.rpcs[UUID].stats[trackID]._packetsLost = stat.packetsLost;
}
} else if (("_codecId" in session.rpcs[UUID].stats) && (stat.id == session.rpcs[UUID].stats._codecId)){
if ("mimeType" in stat){
if (session.rpcs[UUID].stats[session.rpcs[UUID].stats._codecIdTrackId]){
session.rpcs[UUID].stats[session.rpcs[UUID].stats._codecIdTrackId].codec = stat.mimeType;
} else {
session.rpcs[UUID].stats[session.rpcs[UUID].stats._codecIdTrackId] = {};
session.rpcs[UUID].stats[session.rpcs[UUID].stats._codecIdTrackId].codec = stat.mimeType;
}
}
if ("frameHeight" in stat){
if ("frameWidth" in stat){
session.rpcs[UUID].stats.Resolution = parseInt(stat.frameWidth)+" x "+parseInt(stat.frameHeight);
}
}
} else if (("_audioCodecId" in session.rpcs[UUID].stats) && (stat.id == session.rpcs[UUID].stats._audioCodecId)){
if ("mimeType" in stat){
var addOnDescription = stat.mimeType;
addOnDescription = addOnDescription.replace("audio/", "");
if ("sdpFmtpLine" in stat){
if (stat.sdpFmtpLine.includes("useinbandfec=1")){
addOnDescription += ", /w fec";
}
}
if (session.rpcs[UUID].stats[session.rpcs[UUID].stats._audioCodecIdTrackId]){
} else {
session.rpcs[UUID].stats[session.rpcs[UUID].stats._audioCodecIdTrackId] = {};
}
session.rpcs[UUID].stats[session.rpcs[UUID].stats._audioCodecIdTrackId].codec = addOnDescription;
session.rpcs[UUID].stats[session.rpcs[UUID].stats._audioCodecIdTrackId]
}
} else if (Firefox){
if ("frameWidth" in stat){
session.rpcs[UUID].stats.resolution = stat.frameWidth +" x " + stat.frameHeight;
if ("framesPerSecond" in stat){
session.rpcs[UUID].stats.resolution += " @ "+stat.framesPerSecond;
}
}
if (("mimeType" in stat) && ("type" in stat) && ("id" in stat) && (stat.type=="codec")) {
if (stat.mimeType.includes("video")){
session.rpcs[UUID].stats.video_codec = stat.mimeType.split("video/")[1];
} else if (stat.mimeType.includes("audio")){
session.rpcs[UUID].stats.audio_codec = stat.mimeType.split("audio/")[1];
}
}
/* if ("jitter" in stat){
if (("kind" in stat) && (stat.kind=="video")){
session.rpcs[UUID].stats.video_jitter_ms = parseInt(stat.jitter*1000);
} else if (("kind" in stat) && (stat.kind=="audio")){
session.rpcs[UUID].stats.audio_jitter_ms = parseInt(stat.jitter*1000);
}
} */
if ("bytesReceived" in stat){
if (("kind" in stat) && (stat.kind=="video")){
if ("_bytesReceived_video" in session.rpcs[UUID].stats){
session.rpcs[UUID].stats.videoBitrate_kbps = parseInt((stat.bytesReceived - session.rpcs[UUID].stats._bytesReceived_video)/(1024*session.statsInterval/8000));
}
session.rpcs[UUID].stats._bytesReceived_video = stat.bytesReceived
} else if (("kind" in stat) && (stat.kind=="audio")){
if ("_bytesReceived_audio" in session.rpcs[UUID].stats){
session.rpcs[UUID].stats.audioBitrate_kbps = parseInt((stat.bytesReceived - session.rpcs[UUID].stats._bytesReceived_audio)/(1024*session.statsInterval/8000));
}
session.rpcs[UUID].stats._bytesReceived_audio = stat.bytesReceived
}
}
}
} catch(e){
errorlog(e);
}
});
//////////
if (nominatedCandidate){
if (nominatedCandidate.localCandidateId && session.rpcs[UUID].stats['Peer-to-Peer_Connection']._local_ice_id && (session.rpcs[UUID].stats['Peer-to-Peer_Connection']._local_ice_id !== nominatedCandidate.localCandidateId)){
if ("candidateType" in nominatedCandidate){
try {
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].local_candidateType = null;
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].local_relay_IP = null;
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].local_relay_protocol = null;
} catch(e){}
}
}
if (nominatedCandidate.remoteCandidateId && session.rpcs[UUID].stats['Peer-to-Peer_Connection']._remote_ice_id && (session.rpcs[UUID].stats['Peer-to-Peer_Connection']._remote_ice_id !== nominatedCandidate.remoteCandidateId)){
if ("candidateType" in nominatedCandidate){
try {
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].remote_candidateType = null;
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].remote_relay_IP = null;
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].remote_relay_protocol = null;
} catch(e){}
}
}
if (nominatedCandidate.localCandidateId){
session.rpcs[UUID].stats['Peer-to-Peer_Connection']._local_ice_id = nominatedCandidate.localCandidateId;
}
if (nominatedCandidate.remoteCandidateId){
session.rpcs[UUID].stats['Peer-to-Peer_Connection']._remote_ice_id = nominatedCandidate.remoteCandidateId;
}
if ("currentRoundTripTime" in nominatedCandidate){
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].Round_Trip_Time_ms = nominatedCandidate.currentRoundTripTime*1000;
}
}
if (nominatedCandidate && nominatedCandidate.remoteCandidateId){
if (candidates[nominatedCandidate.remoteCandidateId]){
var candidate = candidates[nominatedCandidate.remoteCandidateId];
if ("candidateType" in candidate) {
session.rpcs[UUID].stats.remote_candidateType = candidate.candidateType;
if (candidate.candidateType === "relay"){
if ("ip" in candidate) {
session.rpcs[UUID].stats.remote_relay_IP = candidate.ip;
}
if ("relayProtocol" in candidate) {
session.rpcs[UUID].stats.remote_relay_protocol = candidate.relayProtocol;
}
} else {
try {
delete session.rpcs[UUID].stats.remote_relay_IP;
delete session.rpcs[UUID].stats.remote_relay_protocol;
} catch(e){}
}
}
}
}
if (nominatedCandidate && nominatedCandidate.localCandidateId){
if (candidates[nominatedCandidate.localCandidateId]){
var candidate = candidates[nominatedCandidate.localCandidateId];
if ("candidateType" in candidate) {
session.rpcs[UUID].stats.local_candidateType = candidate.candidateType;
if (candidate.candidateType === "relay"){
if ("ip" in candidate) {
session.rpcs[UUID].stats.local_relay_IP = candidate.ip;
}
if ("relayProtocol" in candidate) {
session.rpcs[UUID].stats.local_relay_protocol = candidate.relayProtocol;
}
} else {
try {
delete session.rpcs[UUID].stats.local_relay_IP;
delete session.rpcs[UUID].stats.local_relay_protocol;
} catch(e){}
}
}
}
}
///////////
if (nominatedCandidate && nominatedCandidate.remoteCandidateId){
if (candidates[nominatedCandidate.remoteCandidateId]){
var candidate = candidates[nominatedCandidate.remoteCandidateId];
if ("candidateType" in candidate){
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].remote_candidateType = candidate.candidateType;
if (candidate.candidateType === "relay"){
if ("relayProtocol" in candidate){
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].remote_relay_protocol = candidate.relayProtocol;
} else {
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].remote_relay_protocol = null;
}
if ("ip" in candidate){session.rpcs[UUID].stats['Peer-to-Peer_Connection'].remote_relay_IP = candidate.ip;}
else {session.rpcs[UUID].stats['Peer-to-Peer_Connection'].remote_relay_IP = null;}
} else {
try {
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].local_relay_IP = null;
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].local_relay_protocol = null;
} catch(e){}
}
}
if ("networkType" in candidate){
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].remote_networkType = candidate.networkType;
}
}
}
if (nominatedCandidate && nominatedCandidate.localCandidateId){
if (candidates[nominatedCandidate.localCandidateId]){
var candidate = candidates[nominatedCandidate.localCandidateId];
if ("candidateType" in candidate){
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].local_candidateType = candidate.candidateType;
if (candidate.candidateType === "relay"){
if ("relayProtocol" in candidate){
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].local_relay_protocol = candidate.relayProtocol;
} else {
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].local_relay_protocol = null;
}
if ("ip" in candidate){session.rpcs[UUID].stats['Peer-to-Peer_Connection'].local_relay_IP = candidate.ip;}
else {session.rpcs[UUID].stats['Peer-to-Peer_Connection'].local_relay_IP = null;}
} else {
try {
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].local_relay_IP = null;
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].local_relay_protocol = null;
} catch(e){}
}
}
if ("networkType" in candidate){
session.rpcs[UUID].stats['Peer-to-Peer_Connection'].local_networkType = candidate.networkType;
}
}
}
//////////////
//if (session.buffer!==false){
playoutdelay(UUID);
//}
setTimeout(function(){
session.directorSpeakerMute();
session.directorDisplayMute();
},0);
});
}
} catch (e){errorlog(e);}
pokeIframeAPI('view-stats-updated', true, UUID);
};
function createConnectionDetailsEle(UUID){
if (!session.rpcs[UUID]){return false;}
session.rpcs[UUID].connectionDetails = document.createElement("div");
session.rpcs[UUID].connectionDetails.id = "remoteConnections_" + UUID;
if (session.rpcs[UUID].stats.info && ("total_outbound_p2p_connections" in session.rpcs[UUID].stats.info)){
session.rpcs[UUID].connectionDetails.innerText = "🔗"+session.rpcs[UUID].stats.info.total_outbound_p2p_connections;
session.rpcs[UUID].connectionDetails.dataset.value = session.rpcs[UUID].stats.info.total_outbound_p2p_connections;
}
session.rpcs[UUID].connectionDetails.dataset.UUID = UUID;
session.rpcs[UUID].connectionDetails.title = miscTranslations["viewer-count"];
session.rpcs[UUID].connectionDetails.className = "rem-con-count";
session.rpcs[UUID].connectionDetails.addEventListener('click', function(e) { // show stats of video if double clicked
log("clicked connectionDetails icon ");
try {
e.preventDefault();
var uid = e.currentTarget.dataset.UUID;
if ("stats" in session.rpcs[uid]){
var [menu, innerMenu] = statsMenuCreator();
printViewStats(innerMenu, uid );
menu.interval = setInterval(printViewStats, session.statsInterval, innerMenu, uid);
}
e.stopPropagation();
return false;
} catch(e){errorlog(e);}
});
return true
}
function playoutdelay(UUID){ // applies a delay to all videos
try {
var target_buffer = session.buffer;
try{
if (session.rpcs[UUID].buffer!==false){
target_buffer = session.rpcs[UUID].buffer;
}
} catch(e){warnlog(e);}
if (target_buffer!==false){
target_buffer = parseFloat(target_buffer);
// if buffer is set, then session.sync will be set; at least to 0.
var receivers = getReceivers2(UUID).reverse() || []; //session.rpcs[UUID].getReceivers().reverse();
if (session.rpcs[UUID].mc){
receivers = receivers.concat(getReceiversMC(UUID).reverse()); // if I try to reuse getReceivers2, I get some confused stats (not able to tell tracks apart) TODO: see if this issue is a problem else where, esp with screen shares. sstype==3
}
receivers.forEach(function(receiver){
try {
for (var tid in session.rpcs[UUID].stats){
if ((typeof( session.rpcs[UUID].stats[tid])=="object") && ("_trackID" in session.rpcs[UUID].stats[tid]) && (session.rpcs[UUID].stats[tid]._trackID===receiver.track.id) && (session.rpcs[UUID].stats[tid]._type == receiver.track.kind) && ("Jitter_Buffer_ms" in session.rpcs[UUID].stats[tid])){
var sync_offset = 0.0;
if (session.rpcs[UUID].stats[tid]._sync_offset){
sync_offset = session.rpcs[UUID].stats[tid]._sync_offset;
} else {
session.rpcs[UUID].stats[tid]._sync_offset = 0;
}
sync_offset += target_buffer;
sync_offset -= session.rpcs[UUID].stats[tid].Jitter_Buffer_ms;
if (session.includeRTT){
sync_offset -= parseInt(session.rpcs[UUID].stats['Peer-to-Peer_Connection'].Round_Trip_Time_ms/2); // I can't be sure what the actual one-way delay is
}
if (sync_offset>target_buffer){
sync_offset=target_buffer;
}
if (sync_offset<0){sync_offset=0;}
session.rpcs[UUID].stats[tid].Added_Buffer_Delay_ms = sync_offset;
session.rpcs[UUID].stats[tid].Total_Playout_Delay_ms = sync_offset + parseInt(session.rpcs[UUID].stats['Peer-to-Peer_Connection'].Round_Trip_Time_ms/2) + session.rpcs[UUID].stats[tid].Jitter_Buffer_ms;
if (session.rpcs[UUID].stats[tid]._type=="audio"){
session.rpcs[UUID].stats[tid]._sync_offset = sync_offset;
receiver.playoutDelayHint = parseFloat(sync_offset/1000);
// receiver.jitterBufferDelayhint = parseFloat(sync_offset/1000); // This is deprecated I believe
if (session.sync!==false){
var audio_delay = session.sync || 0; // video is typically showing greater delay than audio.
audio_delay += target_buffer - session.rpcs[UUID].stats[tid].Jitter_Buffer_ms
if ((receiver.track.kind=="audio") && (receiver.track.id in session.rpcs[UUID].inboundAudioPipeline)){
if (session.rpcs[UUID].inboundAudioPipeline[receiver.track.id] && session.rpcs[UUID].inboundAudioPipeline[receiver.track.id].delayNode){
if (audio_delay<0){audio_delay=0;}
try {
session.rpcs[UUID].inboundAudioPipeline[receiver.track.id].delayNode.delayTime.linearRampToValueAtTime(parseFloat(audio_delay/1000.0), session.audioCtx.currentTime + parseFloat(session.statsInterval/3000));
} catch(e){
session.rpcs[UUID].inboundAudioPipeline[receiver.track.id].delayNode.delayTime.setValueAtTime(parseFloat(audio_delay/1000.0), session.audioCtx.currentTime+1);
}
session.rpcs[UUID].stats[tid].Audio_Sync_Delay_ms = audio_delay;
}
}
}
} else if (session.rpcs[UUID].stats[tid]._type=="video"){
session.rpcs[UUID].stats[tid]._sync_offset = sync_offset;
receiver.playoutDelayHint = parseFloat(sync_offset/1000); // Chrome seems to somewhat sync audio and video when using the delay
// receiver.jitterBufferDelayhint = parseFloat(sync_offset/1000); // This is deprecated I believe
}
}
}
} catch (e){errorlog(e);}
});
}
} catch (e){
errorlog("device does not support playout delay");
}
};
function printViewStats(menu, UUID) { // Stats for viewing a remote video
if (!session.rpcs[UUID]){
menu.innerHTML = "
Remote Publisher Disconnected";
return;
}
var statsObj = session.rpcs[UUID].stats;
var streamID = session.rpcs[UUID].streamID;
var scrollLeft = menu.scrollLeft;
var scrollTop = menu.scrollTop;
menu.innerHTML = "StreamID: " + streamID + " ";
//// doesn't work on viewer side.
//if (session.rpcs && session.rpcs[UUID] && session.rpcs[UUID] && session.rpcs[UUID].restartIce){ // only show if available
// menu.innerHTML += "";
//}
menu.innerHTML += printValues(statsObj);
menu.scrollTop = scrollTop;
menu.scrollLeft = scrollLeft;
}
function plotDataSimple(canvas, bitrate, nacks=0) {
canvas.height = 50;
canvas.width = 124;
canvas.className = "canvasStats";
var context = canvas.getContext("2d");
if (isNaN(bitrate)) {
bitrate = 0;
}
if (isNaN(nacks)) {
nacks = 0;
}
var height = context.canvas.height;
var width = context.canvas.width;
var val = (10-nacks)/10;
if (val>1){val=1;}
else if (val<0){val=0;}
var yScale = height / 4000;
var x = width - 1;
var y = height - bitrate * yScale;
var w = 1;
context.fillStyle = getColor(val);;
context.fillRect(x, y, w, height);
context.fillStyle = "#FFFFFF55";
context.fillRect(x, y-2, w, 4);
if (y-5>0){
context.fillStyle = "#FFFFFF44";
context.fillRect(x, y+2, w, 1);
}
context.putImageData(context.getImageData(1, 0, width - 1, height), 0, 0);
context.clearRect(width - 1, 0, 1, height);
}
function printValues(obj) { // see: printViewStats
var out = "";
for (var key in obj) {
if (typeof obj[key] === "object") {
if (obj[key] != null) {
var tmp = key;
tmp = sanitizeChat((tmp));
out += "
" + tmp + "
"
out += printValues(obj[key]);
}
} else {
if (key.startsWith("_")) {
// if it starts with _, we don't want to show it.
} else {
try {
var unit = '';
var value = obj[key];
var stat = sanitizeChat(key);
var hint = "";
if (typeof obj[key] == "string") {
value = sanitizeChat((value));
}
if (key == 'useragent') {
value = ""+value+""
}
if (key == 'Bitrate_in_kbps') {
var unit = " kbps";
stat = "Bitrate";
hint = "You can refer to the documentation for ways to increase the target bitrate";
}
else if (key == 'type') {
var unit = "";
stat = 'Type';
if (value == "Audio Track") {
value = "🔊 " + value;
//out += "";
}
if (value == "Video Track") {
value = "📺 " + value;
}
}
else if (key == 'packetLoss_in_percentage') {
var unit = " %";
stat = 'Packet Loss 📶';
value = parseInt(parseFloat(value) * 10000) / 10000.0;
hint = "A high packet loss will lower quality of the media";
}
else if (key == 'local_relay_IP') {
value = "" + value + "";
}
else if (key == 'remote_relay_IP') {
value = "" + value + "";
}
else if ((key == 'local_candidateType') && (value == "relay")){
value = "💸 relay server";
hint = 'no direct p2p connection made; using the TURN relay servers.';
}
else if ((key == 'remote_candidateType') && (value == "relay")) {
value = "💸 relay server"
hint = 'no direct p2p connection made; using the TURN relay servers.';
}
else if ((key == 'local_candidateType') && (value == "host")){
hint = 'No NAT firewall, typical of LAN to LAN';
}
else if ((key == 'remote_candidateType') && (value == "host")) {
hint = 'No NAT firewall, typical of LAN to LAN';
}
else if ((key == 'local_candidateType') && (value == "srflx")){
hint = 'direct p2p, but NAT firewall likely';
}
else if ((key == 'remote_candidateType') && (value == "srflx")) {
hint = 'direct p2p, but NAT firewall likely';
}
else if (key == 'height_url') {
if (value == false) {
continue;
}
}
else if (key == 'width_url') {
if (value == false) {
continue;
}
}
else if (key == 'height_url') {
if (value == false) {
continue;
}
}
else if (key == 'version') {
stat = "VDO.Ninja Version";
}
else if (key == 'platform') {
stat = "Platform (OS)";
}
else if (key == 'iPhone12Up') {
stat = "iPhone 12 and up";
}
else if (key == 'aec_url') {
stat = "Echo-Cancellation";
}
else if (key == 'agc_url') {
stat = "Auto-Gain (agc)";
}
else if (key == 'denoise_url') {
stat = "De-noising ";
}
else if (key == 'audio_level') {
stat = "Audio Level";
}
else if (key == 'Jitter_Buffer_ms') {
var unit = " ms";
stat = 'Jitter Buffer Delay';
}
else if (key == 'Added_Buffer_Delay_ms') {
var unit = " ms";
stat = 'Added Buffer Delay';
hint = "Value of playout buffer delay added if using &buffer";
}
else if (key == 'Total_Playout_Delay_ms') { // doesn't include bluetooth / monitor / capture delay, etc.
var unit = " ms";
stat = 'Total Playout Delay';
hint = "Network latency + Jitter buffer + any manually added playout delay"
}
else if (value === null) {
value = "null";
}
else if (key == "stereo_url") {
stat = "Pro-Audio (Stereo-mode)";
if (value == 3) {
value = "3 (outbound hi-fi) Use Headphones";
} else if (value == 1) {
value = "1 (in & out hi-fi) Use Headphones";
} else if (value == 2) {
value = "3 (inbound hi-fi)";
} else if (value == 4) {
value = "3 (multichannel) Use Headphones";
} else if (value == 5) {
value = "5 (auto-mode) Use Headphones";
}
}
else if (value === false) {
continue
}
else if (value === "false") {
continue
}
stat = stat.replaceAll("_", " ");
stat = stat.trim();
if (hint){
out += "
" + stat + "" + value + unit + "
";
} else {
out += "
" + stat + "" + value + unit + "
";
}
} catch (e) {
warnlog(e);
}
}
}
}
return out;
}
function processMeshcastStats(UUID){
try {
session.rpcs[UUID].mc.getStats().then(function(stats){
if (!(UUID in session.rpcs)){return;}
if (!session.rpcs[UUID].stats['Meshcast_Connection']){
session.rpcs[UUID].stats['Meshcast_Connection'] = {};
}
// var qos = false;]
var nominatedCandidate = false;
var candidates = {};
stats.forEach(stat=>{
if (stat.id && stat.id.startsWith("DEPRECATED_")){return;}
var trackID = stat.trackIdentifier || stat.id || false;
if (stat.type == "remote-candidate") {
candidates[stat.id] = stat;
} else if (stat.type == "local-candidate") {
candidates[stat.id] = stat;
} else if ((stat.type == "candidate-pair" ) && (stat.nominated)) {
if (!nominatedCandidate){
nominatedCandidate = stat;
} else if (nominatedCandidate.priority < stat.priority){
nominatedCandidate = stat;
}
} else if ((stat.type=="track") && stat.remoteSource){
if (stat.id in session.rpcs[UUID].stats){
session.rpcs[UUID].stats[stat.id]._trackID = stat.trackIdentifier;
session.rpcs[UUID].stats[stat.id].Jitter_Buffer_ms = parseInt(1000*(parseFloat(stat.jitterBufferDelay) - session.rpcs[UUID].stats[stat.id]._jitter_delay)/(parseInt(stat.jitterBufferEmittedCount) - session.rpcs[UUID].stats[stat.id]._jitter_count)) || 0;
session.rpcs[UUID].stats[stat.id]._jitter_delay = parseFloat(stat.jitterBufferDelay) || 0;
session.rpcs[UUID].stats[stat.id]._jitter_count = parseInt(stat.jitterBufferEmittedCount) || 0;
if ("frameWidth" in stat){
if ("frameHeight" in stat){
session.rpcs[UUID].stats[stat.id].Resolution = stat.frameWidth+" x "+stat.frameHeight;
session.rpcs[UUID].stats[stat.id]._frameWidth = stat.frameWidth;
session.rpcs[UUID].stats[stat.id]._frameHeight = stat.frameHeight;
}
}
} else {
session.rpcs[UUID].stats[stat.id] = {};
session.rpcs[UUID].stats[stat.id]._jitter_delay = parseFloat(stat.jitterBufferDelay) || 0;
session.rpcs[UUID].stats[stat.id]._jitter_count = parseInt(stat.jitterBufferEmittedCount) || 0;
session.rpcs[UUID].stats[stat.id].Jitter_Buffer_ms = 0;
session.rpcs[UUID].stats[stat.id]._trackID = stat.trackIdentifier;
if (stat.kind && stat.kind=="audio"){
session.rpcs[UUID].stats[stat.id].type = "Audio Track";
session.rpcs[UUID].stats[stat.id]._type = "audio";
} else if (stat.kind && (stat.kind=="video")){
session.rpcs[UUID].stats[stat.id].type = "Video Track";
session.rpcs[UUID].stats[stat.id]._type = "video";
}
}
} else if (stat.type == "transport"){
if ("bytesReceived" in stat) {
if ("_bytesReceived" in session.rpcs[UUID].stats['Meshcast_Connection']){
if (session.rpcs[UUID].stats['Meshcast_Connection']._timestamp){
if (stat.timestamp){
session.rpcs[UUID].stats['Meshcast_Connection'].total_recv_bitrate_kbps = parseInt(8*(stat.bytesReceived - session.rpcs[UUID].stats['Meshcast_Connection']._bytesReceived)/(stat.timestamp - session.rpcs[UUID].stats['Meshcast_Connection']._timestamp));
}
}
}
session.rpcs[UUID].stats['Meshcast_Connection']._bytesReceived = stat.bytesReceived;
}
if ("timestamp" in stat) {
session.rpcs[UUID].stats['Meshcast_Connection']._timestamp = stat.timestamp;
if (!session.rpcs[UUID].stats['Meshcast_Connection']._timestampStart){
session.rpcs[UUID].stats['Meshcast_Connection']._timestampStart = stat.timestamp;
} else {
session.rpcs[UUID].stats['Meshcast_Connection'].time_active_minutes = parseInt((stat.timestamp - session.rpcs[UUID].stats['Meshcast_Connection']._timestampStart)/600)/100;
}
}
} else if ((stat.type=="inbound-rtp") && trackID){
session.rpcs[UUID].stats[trackID] = session.rpcs[UUID].stats[trackID] || {};
if (stat.trackIdentifier){
session.rpcs[UUID].stats[trackID]._trackID = stat.trackIdentifier;
}
if ("jitterBufferDelay" in stat){
session.rpcs[UUID].stats[trackID].Jitter_Buffer_ms = parseInt(1000*(parseFloat(stat.jitterBufferDelay) - session.rpcs[UUID].stats[trackID]._jitter_delay_2)/(parseInt(stat.jitterBufferEmittedCount) - session.rpcs[UUID].stats[trackID]._jitter_count_2)) || 0;
session.rpcs[UUID].stats[trackID]._jitter_delay_2 = parseFloat(stat.jitterBufferDelay) || 0;
session.rpcs[UUID].stats[trackID]._jitter_count_2 = parseInt(stat.jitterBufferEmittedCount) || 0;
}
if ("frameWidth" in stat){
if ("frameHeight" in stat){
session.rpcs[UUID].stats[trackID].Resolution = stat.frameWidth+" x "+stat.frameHeight;
session.rpcs[UUID].stats[trackID]._frameWidth = stat.frameWidth;
session.rpcs[UUID].stats[trackID]._frameHeight = stat.frameHeight;
}
}
session.rpcs[UUID].stats[trackID].Bitrate_in_kbps = parseInt(8*(stat.bytesReceived - (session.rpcs[UUID].stats[trackID]._last_bytes || 0))/( stat.timestamp - session.rpcs[UUID].stats[trackID]._last_time));
session.rpcs[UUID].stats[trackID]._last_bytes = stat.bytesReceived || session.rpcs[UUID].stats[trackID]._last_bytes;
session.rpcs[UUID].stats[trackID]._last_time = stat.timestamp || session.rpcs[UUID].stats[trackID]._last_time;
session.rpcs[UUID].stats._codecId = stat.codecId;
session.rpcs[UUID].stats._codecIdTrackId = trackID;
if (stat.mediaType=="video"){
session.rpcs[UUID].stats[trackID].type = "Video Stream"
session.rpcs[UUID].stats[trackID]._type = "video";
if ((session.obsfix) && ("codec" in session.rpcs[UUID].stats) && (session.rpcs[UUID].stats.codec=="video/VP8")){
session.rpcs[UUID].stats[trackID].pliDelta = (stat.pliCount - session.rpcs[UUID].stats[trackID].keyFramesRequested_pli) || 0;
session.rpcs[UUID].stats[trackID].nackTrigger = (stat.nackCount - session.rpcs[UUID].stats[trackID].streamErrors_nackCount + session.rpcs[UUID].stats[trackID].nackTrigger) || 0;
log("OBS PLI FIX MODE ON");
if ((session.rpcs[UUID].stats[trackID].pliDelta===0) && (session.rpcs[UUID].stats[trackID].nackTrigger >= session.obsfix)){ // heavy packet loss with no pliCount?
session.requestKeyframe(UUID);
session.rpcs[UUID].stats[trackID].nackTrigger = 0;
log("TRYING KEYFRAME");
} else if (session.rpcs[UUID].stats[trackID].pliDelta>0){
session.rpcs[UUID].stats[trackID].nackTrigger = 0;
}
} else if ((session.obsfix) && ("codec" in session.rpcs[UUID].stats) && (session.rpcs[UUID].stats.codec=="video/VP9")){
session.rpcs[UUID].stats[trackID].pliDelta = (stat.pliCount - session.rpcs[UUID].stats[trackID].keyFramesRequested_pli) || 0;
session.rpcs[UUID].stats[trackID].nackTrigger = (stat.nackCount - session.rpcs[UUID].stats[trackID].streamErrors_nackCount + session.rpcs[UUID].stats[trackID].nackTrigger) || 0;
log("OBS PLI FIX MODE ON");
if ((session.rpcs[UUID].stats[trackID].pliDelta===0) && (session.rpcs[UUID].stats[trackID].nackTrigger >= (session.obsfix*4) )){ // heavy packet loss with no pliCount? well, VP9 will trigger hopefully not as often.
session.requestKeyframe(UUID);
session.rpcs[UUID].stats[trackID].nackTrigger = 0;
log("TRYING KEYFRAME");
} else if (session.rpcs[UUID].stats[trackID].pliDelta>0){
session.rpcs[UUID].stats[trackID].nackTrigger = 0;
}
}
session.rpcs[UUID].stats[trackID].keyFramesRequested_pli = stat.pliCount || 0;
session.rpcs[UUID].stats[trackID].streamErrors_nackCount = stat.nackCount || 0;
//warnlog(stat);
if ("framesPerSecond" in stat){
session.rpcs[UUID].stats[trackID].FPS = parseInt(stat.framesPerSecond);
} else if (("framesDecoded" in stat) && (stat.timestamp)){
var lastFramesDecoded = 0;
var lastTimestamp = 0;
try{
lastFramesDecoded = session.rpcs[UUID].stats[trackID]._framesDecoded;
lastTimestamp = session.rpcs[UUID].stats[trackID]._timestamp;
} catch(e){}
session.rpcs[UUID].stats[trackID].FPS = parseInt(10*(stat.framesDecoded - lastFramesDecoded)/(stat.timestamp/1000 - lastTimestamp))/10;
//session.rpcs[UUID].stats[trackID].FPS = parseInt((stat.framesDecoded - lastFramesDecoded)/(stat.timestamp/1000 - lastTimestamp));
session.rpcs[UUID].stats[trackID]._framesDecoded = stat.framesDecoded;
session.rpcs[UUID].stats[trackID]._timestamp = stat.timestamp/1000;
}
} else if (stat.mediaType=="audio"){
//log("AUDIO LEVEL: "+stat.audioLevel);
session.rpcs[UUID].stats[trackID].type = "Audio Stream";
session.rpcs[UUID].stats[trackID]._type = "audio";
if ("audioLevel" in stat){
session.rpcs[UUID].stats[trackID].audio_level = parseInt(parseFloat(stat.audioLevel)*10000)/10000.0;
}
}
if ("packetsLost" in stat && "packetsReceived" in stat){
if (!("_packetsLost" in session.rpcs[UUID].stats[trackID])){
session.rpcs[UUID].stats[trackID]._packetsLost = stat.packetsLost;
}
if (!("_packetsReceived" in session.rpcs[UUID].stats[trackID])){
session.rpcs[UUID].stats[trackID]._packetsReceived = stat.packetsReceived;
}
if (!("packetLoss_in_percentage" in session.rpcs[UUID].stats[trackID])){
session.rpcs[UUID].stats[trackID].packetLoss_in_percentage = 0;
}
session.rpcs[UUID].stats[trackID].packetLoss_in_percentage = session.rpcs[UUID].stats[trackID].packetLoss_in_percentage*0.35 + 0.65*((stat.packetsLost-session.rpcs[UUID].stats[trackID]._packetsLost)*100.0)/((stat.packetsReceived-session.rpcs[UUID].stats[trackID]._packetsReceived)+(stat.packetsLost-session.rpcs[UUID].stats[trackID]._packetsLost)) || 0;
if (session.rpcs[UUID].stats[trackID]._type==="video"){
qos = session.rpcs[UUID].stats[trackID].packetLoss_in_percentage; // packet loss of video track
}
if (session.rpcs[UUID].signalMeter && (session.rpcs[UUID].stats[trackID]._type==="video")){
if (session.rpcs[UUID].stats[trackID].packetLoss_in_percentage<0.01){
if (session.rpcs[UUID].stats[trackID].Bitrate_in_kbps==0){
session.rpcs[UUID].signalMeter.dataset.level = 0;
} else {
session.rpcs[UUID].signalMeter.dataset.level = 5;
}
} else if (session.rpcs[UUID].stats[trackID].packetLoss_in_percentage<0.3){
session.rpcs[UUID].signalMeter.dataset.level = 4;
} else if (session.rpcs[UUID].stats[trackID].packetLoss_in_percentage<1.0){
session.rpcs[UUID].signalMeter.dataset.level = 3;
} else if (session.rpcs[UUID].stats[trackID].packetLoss_in_percentage<3.5){
session.rpcs[UUID].signalMeter.dataset.level = 2;
} else {
session.rpcs[UUID].signalMeter.dataset.level = 1;
}
}
session.rpcs[UUID].stats[trackID]._packetsReceived = stat.packetsReceived;
session.rpcs[UUID].stats[trackID]._packetsLost = stat.packetsLost;
}
} else if (("_codecId" in session.rpcs[UUID].stats) && (stat.id == session.rpcs[UUID].stats._codecId)){
if ("mimeType" in stat){
if (session.rpcs[UUID].stats[session.rpcs[UUID].stats._codecIdTrackId]){
session.rpcs[UUID].stats[session.rpcs[UUID].stats._codecIdTrackId].codec = stat.mimeType;
} else {
session.rpcs[UUID].stats[session.rpcs[UUID].stats._codecIdTrackId] = {};
session.rpcs[UUID].stats[session.rpcs[UUID].stats._codecIdTrackId].codec = stat.mimeType;
}
}
if ("frameHeight" in stat){
if ("frameWidth" in stat){
session.rpcs[UUID].stats.Resolution = parseInt(stat.frameWidth)+" x "+parseInt(stat.frameHeight);
}
}
} else if (Firefox){
if (("mimeType" in stat) && ("type" in stat) && ("id" in stat) && (stat.type=="codec")) {
if (stat.mimeType.includes("video")){
session.rpcs[UUID].stats.video_codec = stat.mimeType.split("video/")[1];
} else if (stat.mimeType.includes("audio")){
session.rpcs[UUID].stats.audio_codec = stat.mimeType.split("audio/")[1];
}
}
if ("frameWidth" in stat){
session.rpcs[UUID].stats.resolution = stat.frameWidth +" x " + stat.frameHeight;
if ("framesPerSecond" in stat){
session.rpcs[UUID].stats.resolution += " @ "+stat.framesPerSecond;
}
}
if ("bytesReceived" in stat){
if (("kind" in stat) && (stat.kind=="video")){
if ("_bytesReceived_video" in session.rpcs[UUID].stats){
session.rpcs[UUID].stats.videoBitrate_kbps = parseInt((stat.bytesReceived - session.rpcs[UUID].stats._bytesReceived_video)/(1024*session.statsInterval/8000));
}
session.rpcs[UUID].stats._bytesReceived_video = stat.bytesReceived
} else if (("kind" in stat) && (stat.kind=="audio")){
if ("_bytesReceived_audio" in session.rpcs[UUID].stats){
session.rpcs[UUID].stats.audioBitrate_kbps = parseInt((stat.bytesReceived - session.rpcs[UUID].stats._bytesReceived_audio)/(1024*session.statsInterval/8000));
}
session.rpcs[UUID].stats._bytesReceived_audio = stat.bytesReceived
}
}
}
});
////////////
if (nominatedCandidate){
if ("currentRoundTripTime" in nominatedCandidate){
session.rpcs[UUID].stats['Meshcast_Connection'].Round_Trip_Time_ms = nominatedCandidate.currentRoundTripTime*1000;
}
}
if (nominatedCandidate && nominatedCandidate.remoteCandidateId){
if (candidates[nominatedCandidate.remoteCandidateId]){
var candidate = candidates[nominatedCandidate.remoteCandidateId];
if ("candidateType" in candidate){
session.rpcs[UUID].stats['Meshcast_Connection'].remote_candidateType = candidate.candidateType;
if (candidate.candidateType === "relay"){
if ("relayProtocol" in candidate){
session.rpcs[UUID].stats['Meshcast_Connection'].remote_relay_protocol = candidate.relayProtocol;
}
if ("ip" in candidate){session.rpcs[UUID].stats['Meshcast_Connection'].remote_relay_IP = candidate.ip;}
} else {
try {
delete session.rpcs[UUID].stats['Meshcast_Connection'].local_relay_IP;
delete session.rpcs[UUID].stats['Meshcast_Connection'].local_relay_protocol;
} catch(e){}
}
if ("networkType" in candidate){
session.rpcs[UUID].stats['Meshcast_Connection'].remote_networkType = candidate.networkType;
}
}
}
}
if (nominatedCandidate && nominatedCandidate.localCandidateId){
if (candidates[nominatedCandidate.localCandidateId]){
var candidate = candidates[nominatedCandidate.localCandidateId];
if ("candidateType" in candidate){
session.rpcs[UUID].stats['Meshcast_Connection'].local_candidateType = candidate.candidateType;
if (candidate.candidateType === "relay"){
if ("relayProtocol" in candidate){
session.rpcs[UUID].stats['Meshcast_Connection'].local_relay_protocol = candidate.relayProtocol;
}
if ("ip" in candidate){session.rpcs[UUID].stats['Meshcast_Connection'].local_relay_IP = candidate.ip;}
} else {
try {
delete session.rpcs[UUID].stats['Meshcast_Connection'].local_relay_IP;
delete session.rpcs[UUID].stats['Meshcast_Connection'].local_relay_protocol;
} catch(e){}
}
}
if ("networkType" in candidate){
session.rpcs[UUID].stats['Meshcast_Connection'].local_networkType = candidate.networkType;
}
}
}
/* try{ // we want to let meshcast know if our node is getting overloaded, to avoid making it worse
if ((qos!==false) && session.rpcs[UUID].settings && session.rpcs[UUID].settings.url){
var request = new XMLHttpRequest();
var node = session.rpcs[UUID].settings.url.split("https://")[1].split(".meshcast.io")[0];
if (node){
request.open('POST', " https://qos.meshcast.io/?name="+node);
request.send(qos);
}
}
} catch(e){
errorlog(e);
} */
//if (session.buffer!==false){
playoutdelay(UUID); // it will handle itself for now on I guess
//}
});
} catch (e){errorlog(e);}
}
function printMyStats(menu, screenshare=false) { // see: setupStatsMenu
if (!session){return;}
var scrollLeft = getById("menuStatsBox").scrollLeft;
var scrollTop = getById("menuStatsBox").scrollTop;
menu.innerHTML = "";
try {
session.stats.outbound_connections = Object.keys(session.pcs).length;
session.stats.inbound_connections = Object.keys(session.rpcs).length;
} catch(e){}
try {
var obscam = false;
if (document.querySelector("select#videoSource3")){
var videoSelect = document.querySelector("select#videoSource3").options;
if (videoSelect.length){
if (videoSelect[videoSelect.selectedIndex].text.startsWith("OBS-Camera")) { // OBS Virtualcam
obscam = true;
} else if (videoSelect[videoSelect.selectedIndex].text.startsWith("OBS Virtual Camera")) { // OBS Virtualcam
obscam = true;
}
}
}
if (session.streamSrc){
session.streamSrc.getVideoTracks().forEach(function(track) {
session.currentCameraConstraints = track.getSettings();
if (!window.matchMedia("(orientation: portrait)").matches){
if (session.currentCameraConstraints && session.currentCameraConstraints.aspectRatio){
session.currentCameraConstraints.aspectRatio = 1/session.currentCameraConstraints.aspectRatio;
}
}
if (obscam && (parseInt(session.currentCameraConstraints.frameRate) == 30)) {
session.stats.video_settings =(session.currentCameraConstraints.width || 0) + "x" + (session.currentCameraConstraints.height || 0);
} else {
var frameRateFPS = session.currentCameraConstraints.frameRate;
if (frameRateFPS){
session.stats.video_settings = (session.currentCameraConstraints.width || 0) + "x" + (session.currentCameraConstraints.height || 0) + " @ " + (parseInt(frameRateFPS * 100) / 100.0) + "fps";
} else {
session.stats.video_settings = (session.currentCameraConstraints.width || 0) + "x" + (session.currentCameraConstraints.height || 0);
}
}
});
}
} catch(e){errorlog(e);}
function printViewValues(obj, UUID=false) {
if (!(document.getElementById("menuStatsBox"))){
return;
}
var keys = Object.keys(obj);
keys.forEach(key=>{
if (typeof obj[key] === "object") {
try{
var tmp = key;
tmp = sanitizeChat((tmp));
if (tmp === "info"){
tmp = "Remote Peer Info";
}
menu.innerHTML += "
" + tmp + "
"
} catch(e){}
printViewValues(obj[key]);
menu.innerHTML += "";
}
});
if (UUID && session.pcs[UUID] && session.pcs[UUID].restartIce){ // only show if available
menu.innerHTML += "";
}
keys.forEach(key=>{
if (typeof obj[key] !== "object") {
if (key.startsWith("_")){return;}
var stat = sanitizeChat(key);
var value = obj[key];
if (typeof value == "string") {
value = sanitizeChat((value));
}
if (value === false){return;}
if (key == 'useragent') {
value = ""+value+""
}
if (key == 'local_relay_IP') {
value = "" + value + "";
}
if (key == 'remote_relay_IP') {
value = "" + value + "";
}
if (key == 'watch_URL') {
value = "" + value + "";
}
if ((key == 'local_candidateType') && (value == "relay")){
value = "💸
relay server
";
}
else if ((key == 'remote_candidateType') && (value == "relay")) {
value = "💸
relay server
";
}
else if ((key == 'local_candidateType') && (value == "host")){
value = "
host
";
}
else if ((key == 'remote_candidateType') && (value == "host")) {
value = "
host
";
}
else if ((key == 'local_candidateType') && (value == "srflx")){
value = "
srflx
";
}
else if ((key == 'remote_candidateType') && (value == "srflx")) {
value = "
srflx
";
}
menu.innerHTML += "
" + stat + "" + value + "
";
}
});
if (UUID && session.pcs[UUID]){
if (session.pcs[UUID].maxBandwidth){
menu.innerHTML += "
max bandwidth target" + session.pcs[UUID].maxBandwidth + "
";
}
if (session.pcs[UUID].setBitrate){
menu.innerHTML += "
";
}
}
}
printViewValues(session.stats);
menu.innerHTML += "";
if (!screenshare && session.mc && session.mc.stats){
printViewValues({"Meshcast_connection":session.mc.stats});
menu.innerHTML += "";
}
if (!screenshare && session.whepIn && session.whepIn.stats){
printViewValues({"Whep_In_connection":session.whepIn.stats});
menu.innerHTML += "";
}
if (!screenshare && session.whipOut && session.whipOut.stats){
printViewValues({"Whip_Out_connection":session.whipOut.stats});
menu.innerHTML += "";
}
for (var uuid in session.pcs) {
if (screenshare){
if (session.pcs[uuid].realUUID){
printViewValues(session.pcs[uuid].stats, uuid);
menu.innerHTML += "";
}
} else if (!session.pcs[uuid].realUUID){
printViewValues(session.pcs[uuid].stats, uuid);
menu.innerHTML += "";
}
}
if ((iOS) || (iPad)){
menu.innerHTML += " ";
}
try {
getById("menuStatsBox").scrollLeft = scrollLeft;
getById("menuStatsBox").scrollTop = scrollTop;
} catch (e) {}
}
function updateLocalStats(){
if (!session){return;}
var totalBitrate = 0;
var totalBitrate2 = 0;
var cpuLimited = false;
var relayUsed = false;
var totalVideo = 0;
var totalAudio = 0;
var totalScenes = 0;
var meshcastActive = false;
var nackRate = 0;
var totalStreams = 0;
var miscSenders = [];
if (session.mc && session.mc.getSenders && session.mc.stats){
miscSenders.push(session.mc);
}
if (session.whipOut && session.whipOut.getSenders && session.whipOut.stats){
miscSenders.push(session.whipOut);
}
miscSenders.forEach(data=>{
try {
var atot = 0;
var senders = data.getSenders(); // for any connected peer, update the video they have if connected with a video already.
senders.forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams?
if (sender.track && sender.track.kind == "video" && sender.track.enabled) {
meshcastActive = true;
} else if (sender.track && sender.track.kind == "audio" && sender.track.enabled && !session.muted) {
meshcastActive = true;
}
});
//totalAudio += atot;
if ("video_bitrate_kbps" in data.stats){
totalBitrate+=data.stats.video_bitrate_kbps || 0;
}
if ("audio_bitrate_kbps" in data.stats){
totalBitrate+=data.stats.audio_bitrate_kbps || 0;
}
if ("total_sending_bitrate_kbps" in data.stats){
totalBitrate2+=data.stats.total_sending_bitrate_kbps || 0;
}
if ("quality_limitation_reason" in data.stats){
if (data.stats.quality_limitation_reason == "cpu"){
cpuLimited=true;
}
}
if ("nacks_per_second" in data.stats){
nackRate += data.stats.nacks_per_second;
totalStreams += 1;
}
//if ("local_candidateType" in session.pcs[uuid].stats){
// if (session.pcs[uuid].stats.local_candidateType == "relay"){
// if (session.pcs[uuid].startTime && (Date.now() - session.pcs[uuid].startTime > 30000)){
// relayUsed=true;
// }
// }
//}
setTimeout(function(data){
if (!data){return;}
data.getStats().then(function(stats) {
if ("audio_bitrate_kbps" in data.stats){
data.stats.audio_bitrate_kbps=0;
}
var nominatedCandidate = false;
var candidates = {};
stats.forEach(stat => {
if (stat.id && stat.id.startsWith("DEPRECATED_")){return;}
if (stat.type == "transport"){
if ("bytesSent" in stat) {
if ("_bytesSent" in data.stats){
if (data.stats._timestamp){
if (stat.timestamp){
data.stats.total_sending_bitrate_kbps = parseInt(8*(stat.bytesSent - data.stats._bytesSent)/(stat.timestamp - data.stats._timestamp));
}
}
}
data.stats._bytesSent = stat.bytesSent;
}
if ("timestamp" in stat) {
data.stats._timestamp = stat.timestamp;
}
} else if (stat.type == "outbound-rtp") {
if (stat.kind == "video") {
if ("framesPerSecond" in stat) {
data.stats.resolution = stat.frameWidth + " x " + stat.frameHeight + " @ " + stat.framesPerSecond;
} else if ("frameHeight" in stat){
if (("framesEncoded" in stat) && stat.timestamp){
var lastFramesEncoded = 0;
var lastTimestamp = 0;
try{
lastFramesEncoded = data.stats._framesEncoded;
lastTimestamp = data.stats._timestamp;
} catch(e){}
data.stats._FPS = parseInt(10*(stat.framesEncoded - lastFramesEncoded)/(stat.timestamp/1000 - lastTimestamp))/10 || "?";
data.stats._framesEncoded = stat.framesEncoded;
data.stats._timestamp = stat.timestamp/1000;
data.stats.resolution = stat.frameWidth + " x " + stat.frameHeight + " @ " + data.stats._FPS;
} else {
data.stats.resolution = stat.frameWidth + " x " + stat.frameHeight;
}
}
if ("encoderImplementation" in stat) {
data.stats.video_encoder = stat.encoderImplementation;
if (stat.encoderImplementation=="ExternalEncoder"){
data.stats._hardwareEncoder = true; // I won't set this to false again, just because once I know it has one, I just need to assume it could always be used unexpectednly
data.encoder = true;
} else if (stat.encoderImplementation=="MediaFoundationVideoEncodeAccelerator"){
data.stats._hardwareEncoder = true; // I won't set this to false again, just because once I know it has one, I just need to assume it could always be used unexpectednly
data.encoder = true;
} else {
data.encoder = false; // this may not be actually accurate, but lets assume so.
}
}
if ("qualityLimitationReason" in stat) {
if (data.stats.quality_limitation_reason){
if (data.stats.quality_limitation_reason !== stat.qualityLimitationReason){
try{
var miniInfo = {};
miniInfo.qlr = stat.qualityLimitationReason;
if ("_hardwareEncoder" in data.stats){
miniInfo.hw_enc = data.stats._hardwareEncoder;
} else {
miniInfo.hw_enc = null;
}
session.sendMessage({"miniInfo":miniInfo});
} catch(e){warnlog(e);}
}
}
data.stats.quality_limitation_reason = stat.qualityLimitationReason;
}
if ("bytesSent" in stat) {
if ("_bytesSentVideo" in data.stats){
if (data.stats._timestamp1){
data.stats.video_bitrate_kbps = parseInt(8*(stat.bytesSent - data.stats._bytesSentVideo)/(stat.timestamp - data.stats._timestamp1));
if (stat.timestamp){
}
}
}
data.stats._bytesSentVideo = stat.bytesSent;
}
if ("nackCount" in stat) {
if ("_nackCount" in data.stats){
if (data.stats._timestamp1){
if (stat.timestamp){
data.stats.nacks_per_second = parseInt(10000*(stat.nackCount - data.stats._nackCount)/(stat.timestamp - data.stats._timestamp1))/10;
}
}
}
}
if ("retransmittedBytesSent" in stat) {
if ("_retransmittedBytesSent" in data.stats){
if (data.stats._timestamp1){
if (stat.timestamp){
data.stats.retransmitted_kbps = parseInt(8*(stat.retransmittedBytesSent - data.stats._retransmittedBytesSent)/(stat.timestamp - data.stats._timestamp1));
}
}
}
}
if ("nackCount" in stat) {
data.stats._nackCount = stat.nackCount;
}
if ("retransmittedBytesSent" in stat) {
data.stats._retransmittedBytesSent = stat.retransmittedBytesSent;
}
if ("timestamp" in stat) {
data.stats._timestamp1 = stat.timestamp;
}
if ("pliCount" in stat) {
data.stats.total_pli_count = stat.pliCount;
}
if ("keyFramesEncoded" in stat) {
data.stats.total_key_frames_encoded = stat.keyFramesEncoded;
}
} else if (stat.kind == "audio") {
if ("bytesSent" in stat) {
if (data.stats._bytesSentAudio){
if (data.stats._timestamp2){
if (stat.timestamp){
if ("audio_bitrate_kbps" in data.stats){
data.stats.audio_bitrate_kbps += parseInt(8*(stat.bytesSent - data.stats._bytesSentAudio)/(stat.timestamp - data.stats._timestamp2));
} else {
data.stats.audio_bitrate_kbps=0;
}
}
}
}
}
if ("timestamp" in stat) {
data.stats._timestamp2 = stat.timestamp;
}
if ("bytesSent" in stat) {
data.stats._bytesSentAudio = stat.bytesSent;
}
}
} else if (stat.type == "remote-candidate") {
candidates[stat.id] = stat;
} else if (stat.type == "local-candidate") {
candidates[stat.id] = stat;
} else if ((stat.type == "candidate-pair" ) && (stat.nominated)) {
if (!nominatedCandidate){
nominatedCandidate = stat;
} else if (nominatedCandidate.priority < stat.priority){
nominatedCandidate = stat;
}
} else if (Firefox && ("mimeType" in stat) && ("type" in stat) && (stat.type=="codec")) {
if (stat.mimeType.includes("video")){
data.stats.video_codec = stat.mimeType.split("video/")[1];
} else if (stat.mimeType.includes("audio")){
data.stats.audio_codec = stat.mimeType.split("audio/")[1];
}
}
return;
});
if (nominatedCandidate){
if ("availableOutgoingBitrate" in nominatedCandidate){
data.stats.available_outgoing_bitrate_kbps = parseInt(nominatedCandidate.availableOutgoingBitrate/1024);
if (session.maxBandwidth!==false){
session.limitMaxBandwidth(data.stats.available_outgoing_bitrate_kbps, session.pcs[UUID], false);
}
}
if ("totalRoundTripTime" in nominatedCandidate){
if ("responsesReceived" in nominatedCandidate){
data.stats.average_roundTripTime_ms = parseInt((nominatedCandidate.totalRoundTripTime/nominatedCandidate.responsesReceived)*1000);
}
}
}
if (nominatedCandidate && nominatedCandidate.remoteCandidateId){
if (candidates[nominatedCandidate.remoteCandidateId]){
var candidate = candidates[nominatedCandidate.remoteCandidateId];
if ("candidateType" in candidate) {
data.stats.remote_candidateType = candidate.candidateType;
if (candidate.candidateType === "relay"){
if ("ip" in candidate) {
data.stats.remote_relay_IP = candidate.ip;
}
if ("relayProtocol" in candidate) {
data.stats.remote_relay_protocol = candidate.relayProtocol;
}
} else {
try {
delete data.stats.remote_relay_IP;
delete data.stats.remote_relay_protocol;
} catch(e){}
}
}
}
}
if (nominatedCandidate && nominatedCandidate.localCandidateId){
if (candidates[nominatedCandidate.localCandidateId]){
var candidate = candidates[nominatedCandidate.localCandidateId];
if ("candidateType" in candidate) {
data.stats.local_candidateType = candidate.candidateType;
if (candidate.candidateType === "relay"){
if ("ip" in candidate) {
data.stats.local_relay_IP = candidate.ip;
}
if ("relayProtocol" in candidate) {
data.stats.local_relay_protocol = candidate.relayProtocol;
}
} else {
try {
delete data.stats.local_relay_IP;
delete data.stats.local_relay_protocol;
} catch(e){}
}
}
}
}
return;
});
}, 0, data);
} catch(e){errorlog(e);}
});
for (var uuid in session.pcs) {
if (!session.pcs[uuid].stats){continue;}
var atot = 0;
var senders = getSenders2(uuid); // for any connected peer, update the video they have if connected with a video already.
senders.forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams?
if (sender.track && sender.track.kind == "video" && sender.track.enabled) {
totalVideo+=1
} else if (sender.track && sender.track.kind == "audio" && sender.track.enabled && !session.muted) {
atot=1;
}
});
totalAudio += atot;
if ("scene" in session.pcs[uuid]){
if (session.pcs[uuid].scene!==false){
totalScenes+=1;
}
}
if ("video_bitrate_kbps" in session.pcs[uuid].stats){
totalBitrate+=session.pcs[uuid].stats.video_bitrate_kbps || 0;
}
if ("audio_bitrate_kbps" in session.pcs[uuid].stats){
totalBitrate+=session.pcs[uuid].stats.audio_bitrate_kbps || 0;
}
if ("total_sending_bitrate_kbps" in session.pcs[uuid].stats){
totalBitrate2+=session.pcs[uuid].stats.total_sending_bitrate_kbps || 0;
}
if ("quality_limitation_reason" in session.pcs[uuid].stats){
if (session.pcs[uuid].stats.quality_limitation_reason == "cpu"){
cpuLimited=true;
}
}
if ("nacks_per_second" in session.pcs[uuid].stats){
nackRate += session.pcs[uuid].stats.nacks_per_second;
totalStreams += 1;
}
//if ("local_candidateType" in session.pcs[uuid].stats){
// if (session.pcs[uuid].stats.local_candidateType == "relay"){
// if (session.pcs[uuid].startTime && (Date.now() - session.pcs[uuid].startTime > 30000)){
// relayUsed=true;
// }
// }
//}
if (uuid in session.rpcs){
if (session.pcs[uuid].stats.label){
session.pcs[uuid].stats.label = session.rpcs[uuid].label;
}
if (session.pcs[uuid].stats.streamID){
session.pcs[uuid].stats.streamID = session.rpcs[uuid].streamID;
}
}
var screenTracksIds = [];
if (session.screenStream !== false){ // null if already used. false if never used.
session.screenStream.getTracks().forEach(trk=>{
screenTracksIds.push(trk.id);
});
}
setTimeout(function(UUID) {
if (!( session.pcs[UUID])){return;}
if (session.pcs[UUID].realUUID && session.pcs[session.pcs[UUID].realUUID]){
var thisIsAlt = true;;
var node = session.pcs[session.pcs[UUID].realUUID];
} else {
var thisIsAlt = false;
var node = session.pcs[UUID];
}
node.getStats().then(function(stats) {
if (!(UUID in session.pcs)){return;}
if ("audio_bitrate_kbps" in session.pcs[UUID].stats){
session.pcs[UUID].stats.audio_bitrate_kbps=0;
}
var nominatedCandidate = false;
var candidates = {};
var statObject = [];
var altStreamList = {};
stats.forEach(stat => {
statObject.push(stat);
if (screenTracksIds.includes(stat.trackIdentifier)){
altStreamList[stat.id] = stat.trackIdentifier;
}
})
statObject.forEach(stat => {
if (stat.id && stat.id.startsWith("DEPRECATED_")){return;}
if (stat.type == "transport"){
if ("bytesSent" in stat) {
if ("_bytesSent" in session.pcs[UUID].stats){
if (session.pcs[UUID].stats._timestamp3){
if (stat.timestamp){
session.pcs[UUID].stats.total_sending_bitrate_kbps = parseInt(8*(stat.bytesSent - session.pcs[UUID].stats._bytesSent)/(stat.timestamp - session.pcs[UUID].stats._timestamp3));
}
}
}
session.pcs[UUID].stats._bytesSent = stat.bytesSent;
}
if ("timestamp" in stat) {
session.pcs[UUID].stats._timestamp3 = stat.timestamp;
}
} else if (stat.type == "outbound-rtp") {
if (thisIsAlt && stat.mediaSourceId && !altStreamList[stat.mediaSourceId]){
// this isn't an alt stream, but we are in alt mode
return;
} else if (!thisIsAlt && stat.mediaSourceId && altStreamList[stat.mediaSourceId]){
// this is an alt stream, but we are not in alt mode
return;
}
if (stat.kind == "video") {
if ("framesPerSecond" in stat) {
session.pcs[UUID].stats.resolution = stat.frameWidth + " x " + stat.frameHeight + " @ " + stat.framesPerSecond;
} else if ("frameHeight" in stat){
if (("framesEncoded" in stat) && stat.timestamp){
var lastFramesEncoded = 0;
var lastTimestamp = 0;
try{
lastFramesEncoded = session.pcs[UUID].stats._framesEncoded;
lastTimestamp = session.pcs[UUID].stats._timestamp;
} catch(e){}
session.pcs[UUID].stats._FPS = parseInt(10*(stat.framesEncoded - lastFramesEncoded)/(stat.timestamp - lastTimestamp))/10;
session.pcs[UUID].stats._framesEncoded = stat.framesEncoded;
session.pcs[UUID].stats._timestamp = stat.timestamp;
session.pcs[UUID].stats.resolution = stat.frameWidth + " x " + stat.frameHeight + " @ " + session.pcs[UUID].stats._FPS;
} else {
session.pcs[UUID].stats.resolution = stat.frameWidth + " x " + stat.frameHeight;
}
}
var miniInfo = {};
var sendMini = false;
if ("encoderImplementation" in stat) {
session.pcs[UUID].stats.video_encoder = stat.encoderImplementation;
if (stat.encoderImplementation=="ExternalEncoder"){
session.pcs[UUID].stats._hardwareEncoder = true; // I won't set this to false again, just because once I know it has one, I just need to assume it could always be used unexpectednly
if (session.pcs[UUID].encoder !== true){
session.pcs[UUID].encoder = true;
miniInfo.hw_enc = true;
sendMini = true;
}
} else if (stat.encoderImplementation=="MediaFoundationVideoEncodeAccelerator"){
session.pcs[UUID].stats._hardwareEncoder = true; // I won't set this to false again, just because once I know it has one, I just need to assume it could always be used unexpectednly
if (session.pcs[UUID].encoder !== true){
session.pcs[UUID].encoder = true;
miniInfo.hw_enc = true;
sendMini = true;
}
} else {
if (session.pcs[UUID].encoder === true){
session.pcs[UUID].encoder = false;
miniInfo.hw_enc = false;
sendMini = true;
}
}
}
if ("qualityLimitationReason" in stat) {
if (session.pcs[UUID].stats.quality_limitation_reason){
if (session.pcs[UUID].stats.quality_limitation_reason !== stat.qualityLimitationReason){
try{
sendMini = true;
miniInfo.qlr = stat.qualityLimitationReason;
} catch(e){warnlog(e);}
}
}
session.pcs[UUID].stats.quality_limitation_reason = stat.qualityLimitationReason;
}
if (sendMini){
session.sendMessage({"miniInfo":miniInfo}, UUID);
}
if ("bytesSent" in stat) {
if ("_bytesSentVideo" in session.pcs[UUID].stats){
if (session.pcs[UUID].stats._timestamp1){
if (stat.timestamp){
session.pcs[UUID].stats.video_bitrate_kbps = parseInt(8*(stat.bytesSent - session.pcs[UUID].stats._bytesSentVideo)/(stat.timestamp - session.pcs[UUID].stats._timestamp1));
}
}
}
session.pcs[UUID].stats._bytesSentVideo = stat.bytesSent;
}
if ("nackCount" in stat) {
if ("_nackCount" in session.pcs[UUID].stats){
if (session.pcs[UUID].stats._timestamp1){
if (stat.timestamp){
session.pcs[UUID].stats.nacks_per_second = parseInt(10000*(stat.nackCount - session.pcs[UUID].stats._nackCount)/(stat.timestamp - session.pcs[UUID].stats._timestamp1))/10;
}
}
}
}
if ("retransmittedBytesSent" in stat) {
if ("_retransmittedBytesSent" in session.pcs[UUID].stats){
if (session.pcs[UUID].stats._timestamp1){
if (stat.timestamp){
session.pcs[UUID].stats.retransmitted_kbps = parseInt(8*(stat.retransmittedBytesSent - session.pcs[UUID].stats._retransmittedBytesSent)/(stat.timestamp - session.pcs[UUID].stats._timestamp1));
}
}
}
}
if ("nackCount" in stat) {
session.pcs[UUID].stats._nackCount = stat.nackCount;
}
if ("retransmittedBytesSent" in stat) {
session.pcs[UUID].stats._retransmittedBytesSent = stat.retransmittedBytesSent;
}
if ("timestamp" in stat) {
session.pcs[UUID].stats._timestamp1 = stat.timestamp;
}
if ("pliCount" in stat) {
session.pcs[UUID].stats.total_pli_count = stat.pliCount;
}
if ("keyFramesEncoded" in stat) {
session.pcs[UUID].stats.total_key_frames_encoded = stat.keyFramesEncoded;
}
} else if (stat.kind == "audio") {
if ("bytesSent" in stat) {
if (session.pcs[UUID].stats._bytesSentAudio){
if (session.pcs[UUID].stats._timestamp2){
if (stat.timestamp){
if ("audio_bitrate_kbps" in session.pcs[UUID].stats){
session.pcs[UUID].stats.audio_bitrate_kbps += parseInt(8*(stat.bytesSent - session.pcs[UUID].stats._bytesSentAudio)/(stat.timestamp - session.pcs[UUID].stats._timestamp2));
} else {
session.pcs[UUID].stats.audio_bitrate_kbps=0;
}
}
}
}
}
if ("timestamp" in stat) {
session.pcs[UUID].stats._timestamp2 = stat.timestamp;
}
if ("bytesSent" in stat) {
session.pcs[UUID].stats._bytesSentAudio = stat.bytesSent;
}
}
} else if (stat.type == "remote-candidate") {
candidates[stat.id] = stat;
} else if (stat.type == "local-candidate") {
candidates[stat.id] = stat;
} else if ((stat.type == "candidate-pair" ) && (stat.nominated)) {
if (!nominatedCandidate){
nominatedCandidate = stat;
} else if (nominatedCandidate.priority < stat.priority){
nominatedCandidate = stat;
}
} else if (Firefox && ("mimeType" in stat) && ("type" in stat) && (stat.type=="codec")) {
if (stat.mimeType.includes("video")){
session.pcs[UUID].stats.video_codec = stat.mimeType.split("video/")[1];
} else if (stat.mimeType.includes("audio")){
session.pcs[UUID].stats.audio_codec = stat.mimeType.split("audio/")[1];
}
}
return;
});
if (nominatedCandidate){
if ("availableOutgoingBitrate" in nominatedCandidate){
session.pcs[UUID].stats.available_outgoing_bitrate_kbps = parseInt(nominatedCandidate.availableOutgoingBitrate/1024);
if (session.maxBandwidth!==false){
session.limitMaxBandwidth(session.pcs[UUID].stats.available_outgoing_bitrate_kbps, session.pcs[UUID], false);
}
}
if ("totalRoundTripTime" in nominatedCandidate){
if ("responsesReceived" in nominatedCandidate){
session.pcs[UUID].stats.average_roundTripTime_ms = parseInt((nominatedCandidate.totalRoundTripTime/nominatedCandidate.responsesReceived)*1000);
}
}
}
if (nominatedCandidate && nominatedCandidate.remoteCandidateId){
if (candidates[nominatedCandidate.remoteCandidateId]){
var candidate = candidates[nominatedCandidate.remoteCandidateId];
if ("candidateType" in candidate) {
session.pcs[UUID].stats.remote_candidateType = candidate.candidateType;
if (candidate.candidateType === "relay"){
if ("ip" in candidate) {
session.pcs[UUID].stats.remote_relay_IP = candidate.ip;
}
if ("relayProtocol" in candidate) {
session.pcs[UUID].stats.remote_relay_protocol = candidate.relayProtocol;
}
} else {
try {
delete session.pcs[UUID].stats.remote_relay_IP;
delete session.pcs[UUID].stats.remote_relay_protocol;
} catch(e){}
}
}
}
}
if (nominatedCandidate && nominatedCandidate.localCandidateId){
if (candidates[nominatedCandidate.localCandidateId]){
var candidate = candidates[nominatedCandidate.localCandidateId];
if ("candidateType" in candidate) {
session.pcs[UUID].stats.local_candidateType = candidate.candidateType;
if (candidate.candidateType === "relay"){
if ("ip" in candidate) {
session.pcs[UUID].stats.local_relay_IP = candidate.ip;
}
if ("relayProtocol" in candidate) {
session.pcs[UUID].stats.local_relay_protocol = candidate.relayProtocol;
}
} else {
try {
delete session.pcs[UUID].stats.local_relay_IP;
delete session.pcs[UUID].stats.local_relay_protocol;
} catch(e){}
}
}
}
}
return;
});
}, 0, uuid);
}
try{
var totalCon = Object.keys(session.pcs).length || 0;
var headerStats = "🔗 ";
headerStats += totalCon
if (meshcastActive){
if (totalAudio){
headerStats += ", 👂 "+totalAudio;
}
if (totalVideo){
headerStats += ", 👀 "+totalVideo;
}
headerStats += ", 📡Broadcast";
} else {
headerStats += ", 👂 "+totalAudio;
headerStats += ", 👀 "+totalVideo;
}
if (session.roomid){
headerStats += ", 🎬 "+totalScenes+"";
}
var changed = false;
if (!session.info.out){
session.info.out = {};
session.info.out.v = totalVideo;
session.info.out.a = totalAudio;
session.info.out.c = totalCon;
session.info.out.s = totalScenes;
changed = true;
} else {
if (session.info.out.a !== totalAudio){
session.info.out.a = totalAudio;
// changed = true; // I'm not sending this data, so why bother
}
if (session.info.out.v !== totalVideo){
session.info.out.v = totalAudio;
//changed = true; // I'm not sending this data, so why bother
}
if (session.info.out.c !== totalCon){
if (session.info.out.c){
changed = true; // update if I'm not the first one
}
session.info.out.c = totalCon;
}
if (session.info.out.s !== totalScenes){
if (session.info.out.s){
changed = true; // update if I'm not the first one
}
session.info.out.s = totalScenes;
}
}
} catch(e){}
//session.info.out = {};
var uploadQuality = nackRate / totalStreams || 0;
if (totalStreams === 0){
uploadQuality = "title='Connection seems good' style='color: transparent; text-shadow: 0 0 0 #4d9bff;'";
} else if (uploadQuality === 0){
uploadQuality = "title='Connection seems good' style='color: transparent; text-shadow: 0 0 0 #0F0;'";
} else if (uploadQuality <= 1){
uploadQuality = "title='Mild connection issues' style='color: transparent; text-shadow: 0 0 0 yellow;'";
} else if (uploadQuality <= 5){
uploadQuality = "title='Moderate connection issues' style='color: transparent; text-shadow: 0 0 0 orange;'";
} else {
uploadQuality = "title='Severe connection issues' style='color: transparent; text-shadow: 0 0 0 #F00;'";
}
if (Firefox && (totalBitrate===0 && totalBitrate2===0)){
// does not support the current stats system
} else if (totalBitrate > totalBitrate2){
headerStats += ", 🔺 "+(Math.round(totalBitrate/10.24)/100) + "-mbps";
} else if (totalBitrate2>1000){
headerStats += ", 🔺 "+(Math.round(totalBitrate2/10.24)/100) + "-mbps";
} else{
headerStats += ", 🔺 "+totalBitrate2 + "-kbps";
}
if (session.director || !session.roomid){ // show stats if the director or if not in a group room
if (cpuLimited){
headerStats += ", 🔥 CPU Overloaded";
}
//if (relayUsed){
// headerStats += " 💸";
//}
}
var miniInfo = {}
if (changed){
miniInfo.out = {};
miniInfo.out.c = session.info.out.c
}
if (session.cpuLimited!==cpuLimited){
session.cpuLimited = cpuLimited;
miniInfo.cpu = cpuLimited;
changed = true;
}
if (changed){
for (var uuid in session.pcs) {
session.sendMessage({"miniInfo":miniInfo}, uuid); // lets send it to everyone.
}
}
try{
if (Object.keys(session.pcs).length){
getById("head5").classList.remove("hidden");
}
} catch(e){}
getById("head5").innerHTML = headerStats;
getById("head5").onclick = function(){
var [menu, innerMenu] = statsMenuCreator();
menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu);
printMyStats(innerMenu);
}
}
function updateStats(obsvc = false) {
if (document.getElementById('previewWebcam')) {
var ele = document.getElementById('previewWebcam');
var wcs = "webcamstats";
} else if (document.getElementById('videosource')) {
var ele = document.getElementById('videosource');
var wcs = "webcamstats3";
} else {
return;
}
try {
getById(wcs).innerHTML = "";
ele.srcObject.getVideoTracks().forEach(
function(track) {
if ((obsvc) && (parseInt(track.getSettings().frameRate) == 30)) {
getById(wcs).innerHTML = "Video Settings: " + (track.getSettings().width || 0) + "x" + (track.getSettings().height || 0) + " @ up to 60fps";
} else {
var frameRateFPS = track.getSettings().frameRate;
if (frameRateFPS){
getById(wcs).innerHTML = "Current Video Settings: " + (track.getSettings().width || 0) + "x" + (track.getSettings().height || 0) + "@" + (parseInt(frameRateFPS * 100) / 100.0) + "fps";
} else {
getById(wcs).innerHTML = "Current Video Settings: " + (track.getSettings().width || 0) + "x" + (track.getSettings().height || 0);
}
}
}
);
} catch (e) {
errorlog(e);
}
}
function toggleControlBar() {
if (!getById("controlButtons").classList.contains("hidden")) {
getById("controlButtons").dataset.enabled = true;
getById("controlButtons").classList.add("hidden");
} else if (getById("controlButtons").dataset.enabled){
getById("controlButtons").classList.remove("hidden");
delete getById("controlButtons").dataset.enabled;
}
}
function toggleMute(apply = false, event=false) { // TODO: I need to have this be MUTE, toggle, with volume not touched.
var mouseUp = null;
var touchEnd = null;
var timeStart = Date.now();
if (event){
mouseUp = document.onmouseup;
touchEnd = document.ontouchend;
document.onmouseup = function(){
document.onmouseup = mouseUp;
document.ontouchend = touchEnd;
if (Date.now() - timeStart < 500){
return;
} else {
toggleMute();
}
}
document.ontouchend = function(){
document.onmouseup = mouseUp;
document.ontouchend = touchEnd;
if (Date.now() - timeStart < 300){
return;
} else {
toggleMute();
}
}
}
if (session.director) {
if (!session.directorEnabledPPT) {
log("Director doesn't have PPT enabled yet");
// director has not enabled PTT yet.
return;
}
}
if (apply) {
session.muted = !session.muted; // we flip here as we are going to flip again in a second.
}
//try{var ptt = getById("press2talk");} catch(e){var ptt=false;}
if (session.muted == false) {
session.muted = true;
getById("mutetoggle").className = "las la-microphone-slash toggleSize";
if (!(session.cleanOutput)){
getById("mutebutton").classList.add("red", "pulsate");
getById("mutebutton").ariaPressed = "true";
getById("header").classList.add('red');
if (session.localMuteElement){
session.localMuteElement.style.display = "block";
}
}
if (session.streamSrc) {
session.streamSrc.getAudioTracks().forEach((track) => {
track.enabled = false;
});
}
if ((iOS || iPad) && session.videoElement && session.videoElement.srcObject) {
session.videoElement.srcObject.getAudioTracks().forEach((track) => {
track.enabled = false;
});
}
} else {
session.muted = false;
getById("mutetoggle").className = "las la-microphone toggleSize";
if (!(session.cleanOutput)){
getById("mutebutton").classList.remove("red", "pulsate");
getById("mutebutton").ariaPressed = "false";
getById("header").classList.remove('red');
if (session.localMuteElement){
session.localMuteElement.style.display = "none";
}
}
if (session.streamSrc) {
session.streamSrc.getAudioTracks().forEach((track) => {
track.enabled = true;
});
}
if ((iOS || iPad) && session.videoElement && session.videoElement.srcObject) {
session.videoElement.srcObject.getAudioTracks().forEach((track) => {
track.enabled = true;
});
}
//if (ptt){
// ptt.innerHTML = "🔴 Push to Mute";
//}
}
try {
postMessageIframe(document.getElementById("screensharesource"), {"mic":!session.muted});
} catch(e){}
if (!apply) { // only if they are changing states do we bother to spam.
data = {};
data.muteState = session.muted;
session.sendMessage(data);
log("SEND MUTE STATE TO PEERS");
pokeIframeAPI('mic-mute-state', session.muted);
pokeAPI("muted", session.muted);
}
}
function postMessageIframe(iFrameEle, message){ // iframes seem to only have the contentWindow work on the last placed iframe object, so this checks the dom first.
if (iFrameEle && (iFrameEle.nodeName == "IFRAME")){
try{
if (iFrameEle.id && document.getElementById(iFrameEle.id)){
document.getElementById(iFrameEle.id).contentWindow.postMessage(message, '*');
} else {
iFrameEle.contentWindow.postMessage(message, '*');
}
} catch(e){errorlog(e);}
}
}
function toggleSpeakerMute(apply = false) { // TODO: I need to have this be MUTE, toggle, with volume not touched.
if (CtrlPressed) {
resetupAudioOut();
}
if (apply) {
session.speakerMuted = !session.speakerMuted;
}
if (session.speakerMuted == false) { // mute output
session.speakerMuted = true;
getById("mutespeakertoggle").className = "las la-volume-mute toggleSize";
if (!(session.cleanOutput)){
getById("mutespeakerbutton").className = "float red";
}
var sounds = document.getElementsByTagName("video");
if (iOS || iPad){
for (var i = 0; i < sounds.length; ++i) {
sounds[i].muted = !sounds[i].muted;
sounds[i].muted = session.speakerMuted;
}
} else {
for (var i = 0; i < sounds.length; ++i) {
sounds[i].muted = session.speakerMuted;
}
}
} else {
session.speakerMuted = false; // unmute output
getById("mutespeakertoggle").className = "las la-volume-up toggleSize";
if (!(session.cleanOutput)){
getById("mutespeakerbutton").className = "float";
}
var sounds = document.getElementsByTagName("video");
if (iOS || iPad){ // attempting to fix an iOS bug
for (var i = 0; i < sounds.length; ++i) {
sounds[i].muted = !sounds[i].muted;
if (sounds[i].id === "videosource") { // don't unmute ourselves. feedback galore if so.
sounds[i].muted = true;
continue;
} else if (sounds[i].id === "previewWebcam") {
sounds[i].muted = true;
continue;
} else if (sounds[i].id === "screensharesource") {
sounds[i].muted = true;
continue;
} else if (sounds[i].id === "screenshare") { // this is a webcam
sounds[i].muted = true;
continue;
} else {
sounds[i].muted = session.speakerMuted;
}
}
} else {
for (var i = 0; i < sounds.length; ++i) {
if (sounds[i].id === "videosource") { // don't unmute ourselves. feedback galore if so.
continue;
} else if (sounds[i].id === "screensharesource") { // don't unmute ourselves. feedback galore if so.
continue;
} else if (sounds[i].id === "previewWebcam") {
continue;
} else if (sounds[i].id === "screenshare") { // this is a webm
continue;
} else {
sounds[i].muted = session.speakerMuted;
}
}
}
}
for (var UUID in session.rpcs) {
applyMuteState(UUID);
postMessageIframe(session.rpcs[UUID].iframeEle, {"mute":session.speakerMuted});
}
pokeIframeAPI("audio-mute-state", session.speakerMuted);
if (!apply) {
pokeAPI("speakerMuted", session.speakerMuted);
}
if ((iOS) || (iPad)) {
resetupAudioOut();
}
}
function toggleFileshare(UUID=false, event = null){
if (UUID===false){
var string = 'Share a file with the group ';
} else if (session.directorList.indexOf(UUID)>=0){
var string = 'The director requested you share a file with them. ';
} else {
var string = 'Someone has requested you share a file with them. ';
}
warnUser(string, false, false);
if (session.hostedFiles){
if (session.hostedFiles.length){
getById("activeShares").innerHTML += "
Files being shared:
";
}
for (var i=0;i"+session.hostedFiles[i].name + " (" + Math.ceil(session.hostedFiles[i].size/(1024*1024/10))/10 + "-MB)";
}
}
if (session.hostedTransfers){
getById("activeShares").innerHTML += "
"+session.hostedTransfers.length + " file transfers in progress.
";
}
}
function toggleChat(event = null) { // TODO: I need to have this be MUTE, toggle, with volume not touched.
if (session.chat == false) {
setTimeout(function() {
document.addEventListener("click", toggleChat);
}, 10);
getById("chatModule").addEventListener("click", function(e) {
e.stopPropagation();
return false;
});
session.chat = true;
getById("chattoggle").className = "las la-comment-dots toggleSize";
getById("chatModule").classList.remove("hidden");
getById("chatInput").focus(); // give it keyboard focus
} else {
session.chat = false;
getById("chattoggle").className = "las la-comment-alt toggleSize";
getById("chatModule").classList.add("hidden");
document.removeEventListener("click", toggleChat);
getById("chatModule").removeEventListener("click", function(e) {
e.stopPropagation();
return false;
});
}
if (getById("chatNotification").value) {
getById("chatNotification").value = 0;
}
getById("chatNotification").classList.remove("notification", "red");
}
function directorAdvanced(ele) {
var target = document.createElement("div");
target.style = "position:absolute;float:left;width:270px;height:222px;background-color:#7E7E7E;";
var closeButton = document.createElement("button");
closeButton.innerHTML = " close";
closeButton.style.left = "5px";
closeButton.style.position = "relative";
closeButton.onclick = function() {
target.parentNode.removeChild(target);
};
target.appendChild(closeButton);
var someButton = document.createElement("button");
someButton.innerHTML = " some action ";
someButton.style.left = "5px";
someButton.style.position = "relative";
someButton.onclick = function() {
var actionMsg = {};
session.sendRequest(actionMsg, ele.dataset.UUID);
};
target.appendChild(someButton);
ele.parentNode.appendChild(target);
}
function directorSendMessage(ele) {
var UUID = ele.dataset.UUID;
var target = document.querySelector("[data--u-u-i-d='"+UUID+"'][data-action-type='messaging-box']");
if (!target){return;}
if (target.classList.contains("hidden")){
target.classList.remove("hidden");
ele.classList.add("pressed"); ele.ariaPressed = "true";
} else {
target.classList.add("hidden");
ele.classList.remove("pressed"); ele.ariaPressed = "false";
return;
}
var inputField = target.querySelector("[data-action-type='messaging-box-text']");
if (inputField){
inputField.focus();
inputField.select();
}
if ("overlay" in target){
return;
}
target.overlay = true;
if (inputField){
inputField.addEventListener("keydown", function(e) {
if (e.keyCode == 13) {
e.preventDefault();
sendButton.click();
} else if (e.keyCode == 27) {
e.preventDefault();
inputField.value = "";
target.parentNode.removeChild(target);
}
});
}
var sendButton = target.querySelector("[data-action-type='messaging-box-send']");
if (sendButton){
sendButton.onclick = function() {
var chatMsg = {};
chatMsg.chat = inputField.value;
if (sendButton.parentNode.overlay) {
chatMsg.overlay = sendButton.parentNode.overlay;
}
session.sendRequest(chatMsg, ele.dataset.UUID);
inputField.value = "";
//target.parentNode.removeChild(target);
};
}
var closeButton = target.querySelector("[data-action-type='messaging-box-close']");
if (closeButton){
closeButton.onclick = function() {
inputField.value = "";
target.classList.add("hidden");
ele.classList.remove("pressed"); ele.ariaPressed = "false";
};
}
var overlayMsg = target.querySelector("[data-action-type='messaging-box-toggle']");
if (overlayMsg){
overlayMsg.onclick = function(e) {
log(e.target.parentNode.parentNode);
if (e.target.parentNode.parentNode.overlay === true) {
e.target.parentNode.parentNode.overlay = false;
e.target.parentNode.innerHTML = "";
} else {
e.target.parentNode.parentNode.overlay = true;
e.target.parentNode.innerHTML = "";
}
}
}
}
function toggleAutoVideoMute(){ // for iOS devices, that tab out.
// document.visibilityState
if (!session.videoMuted && (session.permaid!==false)){
var msg = {};
msg.videoMuted = (document.visibilityState === 'hidden') || false;
//try {
session.sendMessage(msg);
//} catch(e){errorlog(e);}
pokeIframeAPI('video-mute-state', document.visibilityState);
}
}
function toggleVideoMute(apply = false) { // TODO: I need to have this be MUTE, toggle, with volume not touched.
if (apply) {
session.videoMuted = !session.videoMuted;
}
if (!session.remoteVideoMuted){
getById("head8").classList.add("hidden");
}
if (session.videoMuted == false) {
session.videoMuted = true;
getById("mutevideotoggle").className = "las la-video-slash toggleSize";
if (!(session.cleanOutput)){
getById("mutevideobutton").classList.add("red");
getById("mutevideobutton").ariaPressed = "true";
getById("header").classList.add("red");
if (session.remoteVideoMuted){
getById("head8").classList.remove("hidden");
}
}
if (session.streamSrc) {
session.streamSrc.getVideoTracks().forEach((track) => {
track.enabled = false;
});
}
} else if (session.remoteVideoMuted){ // the director has muted this guest's video feed
session.videoMuted = false; // just setting it back to the pre-toggled state
getById("mutevideotoggle").className = "las la-video toggleSize";
if (!(session.cleanOutput)){
getById("head8").classList.remove("hidden");
getById("header").classList.add("red");
getById("mutevideobutton").classList.remove("red");
getById("mutevideobutton").ariaPressed = "false";
}
if (session.streamSrc) {
session.streamSrc.getVideoTracks().forEach((track) => {
track.enabled = false;
});
}
} else {
session.videoMuted = false;
getById("mutevideotoggle").className = "las la-video toggleSize";
if (!(session.cleanOutput)){
getById("mutevideobutton").classList.remove("red");
getById("mutevideobutton").ariaPressed = "false";
getById("header").classList.remove("red");
}
if (session.streamSrc) {
session.streamSrc.getVideoTracks().forEach((track) => {
track.enabled = true;
});
}
}
if (session.avatar && session.avatar.ready && !apply){
updateRenderOutpipe();
if (session.videoMuted){
var msg = {};
msg.videoMuted = false; // doesn't matter the actual mute state; this is the avatar
session.sendMessage(msg);
}
} else if (!apply) {
var msg = {};
msg.videoMuted = session.videoMuted;
session.sendMessage(msg);
}
pokeIframeAPI("video-mute-state", (session.videoMuted || session.remoteVideoMuted));
if (!apply){
pokeAPI("videoMuted", (session.videoMuted || session.remoteVideoMuted));
}
}
var toggleSettingsState = false;
async function toggleSettings(forceShow = false) { // TODO: I need to have this be MUTE, toggle, with volume not touched.
if (session.nosettings){return;}
getById("multiselect-trigger3").dataset.state = "0";
getById("multiselect-trigger3").classList.add('closed');
getById("multiselect-trigger3").classList.remove('open');
getById("chevarrow2").classList.add('bottom');
if (toggleSettingsState == true) {
if (forceShow == true) {
await enumerateDevices().then(gotDevices2);
return;
}
} // don't close if already open
if (getById("popupSelector").style.display == "none") {
updateConstraintSliders();
setTimeout(function() {
document.addEventListener("click", toggleSettings);
}, 10);
getById("popupSelector").addEventListener("click", function(e) {
e.stopPropagation();
return false;
});
if (navigator.userAgent.indexOf('Chrome') != -1) {
try {
await navigator.permissions.query({
name: "camera"
}).then(async function(promise) {
if (promise && promise.state) {
if (promise.state == "prompt") {
await navigator.mediaDevices.getUserMedia({
video: true
, audio: false
}).then(async function(stream) {
await enumerateDevices().then(gotDevices2).then(function() {
stream.getTracks().forEach(function(track) {
//stream.removeTrack(track);
track.stop(); // clean up?
});
});
}).catch(async function(err) {
await enumerateDevices().then(gotDevices2).then(function() {});
});
} else {
await enumerateDevices().then(gotDevices2).then(function() {});
}
} else {
await enumerateDevices().then(gotDevices2).then(function() {});
}
});
} catch (e) {
await enumerateDevices().then(gotDevices2).then(function() {});
}
} else {
await enumerateDevices().then(gotDevices2).then(function() {});
}
getById("popupSelector").style.display = "inline-block"
getById("settingsbutton").classList.add("brown");
getById("settingsbutton").ariaPressed = "true";
loadTFLITEImages() // only triggers if effects==5 is true
setTimeout(function() {
getById("popupSelector").style.right = "0px";
}, 1);
toggleSettingsState = true;
} else {
document.removeEventListener("click", toggleSettings);
getById("popupSelector").removeEventListener("click", function(e) {
e.stopPropagation();
return false;
});
getById("popupSelector").style.right = "-500px";
getById("settingsbutton").classList.remove("brown");
getById("settingsbutton").ariaPressed = "false";
setTimeout(function() {
getById("popupSelector").style.display = "none";
}, 200);
toggleSettingsState = false;
document.getElementById('videoSettings3').style.display = "none";
}
pokeIframeAPI("settings-menu-state",toggleSettingsState);
}
function hangup() { // TODO: I need to have this be MUTE, toggle, with volume not touched.
if (session.hostedTransfers.length){
confirmAlt("There are still file transfer in progress\nAre you sure you wish to exit?").then(res=>{
if (res){
try {
if (document.fullscreenElement && session.mobile){
getById("main").innerHTML = document.getElementById("hangupTemplateMobileFullscreen").innerHTML;
} else {
getById("main").innerHTML = document.getElementById("hangupTemplate").innerHTML;
}
} catch(e){}
setTimeout(function() {
session.hangup();
}, 0);
}
});
} else {
try {
if (document.fullscreenElement && session.mobile){
getById("main").innerHTML = document.getElementById("hangupTemplateMobileFullscreen").innerHTML;
} else {
getById("main").innerHTML = document.getElementById("hangupTemplate").innerHTML;
}
} catch(e){}
setTimeout(function() {
session.hangup();
}, 0);
}
}
function hangup2() {
session.hangupDirector();
getById("miniPerformer").innerHTML = "";
getById("press2talk").dataset.enabled = false;
getById("screensharebutton").classList.add("hidden");
getById("screenshare2button").classList.add("hidden");
getById("screenshare3button").classList.add("hidden");
getById("settingsbutton").classList.add("hidden");
getById("mutebutton").classList.add("hidden");
getById("hangupbutton2").classList.add("hidden");
//getById("chatbutton").classList.remove("hidden");
getById("controlButtons").classList.remove("hidden");
//getById("mutespeakerbutton").classList.add("hidden");
getById("mutevideobutton").classList.add("hidden");
getById("screensharebutton").classList.remove("green");
getById("screensharebutton").ariaPressed = "false";
if (session.showDirector == false) {
getById("miniPerformer").innerHTML = '';
miniTranslate(getById("miniPerformer"));
} else {
getById("miniPerformer").innerHTML = '';
}
getById("miniPerformer").className = "";
}
function hangupComplete() {
try {
if (document.fullscreenElement && session.mobile){
getById("main").innerHTML = document.getElementById("hangupTemplateMobileFullscreen").innerHTML;
} else {
getById("main").innerHTML = document.getElementById("hangupTemplate").innerHTML;
}
} catch(e){}
pokeIframeAPI("hungup",true); // don't use Hangup, as that's an action.
pokeAPI("hangup",true);
}
function reloadRequested() {
pokeIframeAPI("reloading",true);
window.removeEventListener("beforeunload", confirmUnload); // clear the confirm on reload
location.reload(); // the main reload function call
}
function confirmUnload(event){
if (!session.noExitPrompt && !session.cleanOutput && (session.scene === false) && (session.seeding || (session.roomid!==false) || (session.permaid!==false) || session.director)){
(event || window.event).returnValue = "Are you sure you want to exit?"; //Gecko + IE
return "Are you sure you want to exit?";
} else {
//setTimeout(function(){session.hangup();},0);
return undefined; // ADDED OCT 29th; get rid of popup. Just close the socket connection if the user is refreshing the page. It's one or the other.
}
}
function gobackSlide(){
var data = {};
data.data = [176, 110, 10];
sendRawMIDI(data);
try {
pokeIframeAPI("back-slide",true);
} catch(e){}
}
function nextSlide(){
var data = {};
data.data = [176, 110, 11];
sendRawMIDI(data);
try {
pokeIframeAPI("next-slide",true);
} catch(e){}
}
function raisehand() {
if (session.directorUUID == false) { // fine
log("no director in room yet");
return false;
}
var data = {};
var handstate = false;
log(data);
if (getById("raisehandbutton").dataset.raised == "0") {
getById("raisehandbutton").dataset.raised = "1";
getById("raisehandbutton").classList.add("raisedHand");
data.chat = "Raised hand";
handstate = true;
log("hand raised");
} else {
log("hand lowered");
getById("raisehandbutton").dataset.raised = "0";
getById("raisehandbutton").classList.remove("raisedHand");
data.chat = "Lowered hand";
handstate = false;
}
for (var i=0;i{
if (ge.value==1){
cc+=1;
}
});
if (!cc){
getById("container_director").style.backgroundColor = null;
getById("container_director").classList.remove("containerGreen");
}
} else {
var cc = 0;
getById("container_" + ele.dataset.UUID).querySelectorAll('[data-action-type="addToScene"]').forEach(ge=>{
if (ge.value==1){
cc+=1;
log("ge.value: '"+ge.value+"'");
} else {
log("ge.value:--'"+ge.value+"'");
}
});
log(cc + " " +"container_" + ele.dataset.UUID);
if (!cc){
getById("container_" + ele.dataset.UUID).style.backgroundColor = null;
getById("container_" + ele.dataset.UUID).classList.remove("containerGreen");
}
}
} else {
ele.value = 1;
ele.classList.add("pressed"); ele.ariaPressed = "true";
if (ele.children[1]){
ele.children[1].innerHTML = "Remove";
}
if (director){
getById("container_director").classList.add("containerGreen");
} else {
getById("container_" + ele.dataset.UUID).classList.add("containerGreen");
}
}
}
var msg = {};
scene = scene+"";
msg.scene = scene;
msg.action = "display";
msg.value = ele.value;
msg.target = ele.dataset.sid;
try {
if (msg.value==1){
pokeIframeAPI("add-to-scene", scene, ele.dataset.UUID);
} else {
pokeIframeAPI("remove-from-scene", scene, ele.dataset.UUID);
}
} catch(e){}
//for (var uuid in session.pcs){ // removing this since it's obsolete at this point.
// if (session.pcs[uuid].stats.info && ("version" in session.pcs[uuid].stats.info) && (session.pcs[uuid].stats.info.version < 17.2)){
//// msg.request = "sendroom";
// session.sendMsg(msg);
// return;
// }
//}
for (var uuid in session.pcs){
if (session.pcs[uuid].scene===scene){
session.sendMessage(msg, uuid);
}
}
syncDirectorState(ele);
if (msg.value){
return true;
} else {
return false;
}
}
function syncDirectorState(ele){
//if (session.director){ // assumed director, since this is a directEnable sub-function
var msg = {};
msg.directorState = getDetailedState(ele.dataset.sid);
for (var uuid in session.pcs){
if (session.pcs[uuid].coDirector){
session.sendMessage(msg, uuid);
}
}
for (var i in session.directorList){
var uuid = session.directorList[i];
if (session.rpcs[uuid]){
session.sendRequest(msg, uuid);
}
}
}
function getDetailedState(sid=false){
var streamList = {};
var guestFeeds = document.getElementById("guestFeeds");
for (var UUID in session.rpcs){
if (session.rpcs[UUID].streamID){
if (sid && (sid!==session.rpcs[UUID].streamID)){continue;}
let item = {};
item.streamID = session.rpcs[UUID].streamID;
item.label = session.rpcs[UUID].label;
item.group = session.rpcs[UUID].group;
try {
item.layout = session.rpcs[UUID].layout;
if (session.director && session.slotmode){
item.slot = query("[data--u-u-i-d='"+UUID+"'][data-slot]").dataset.slot || false;
} else if (session.currentSlots){
item.slot = Object.keys(session.currentSlots).find(key => session.currentSlots[key] === session.rpcs[UUID].streamID) || false;
}
if (item.slot){
item.slot = parseInt(item.slot);
}
if (session.director){
let featured = query("[data--u-u-i-d='"+UUID+"'][data-action-type='solo-video']");
if (featured && parseInt(featured.value)){
item.featured = true;
} else {
item.featured = false;
}
} else if (session.infocus && session.infocus===session.rpcs[UUID].streamID){
item.featured = true;
} else {
item.featured = false;
}
} catch(e){errorlog(e);}
item.iframeSrc = session.rpcs[UUID].iframeSrc;
item.localStream = false;
item.muted = session.rpcs[UUID].remoteMuteState;
item.videoMuted = session.rpcs[UUID].videoMuted;
try {
item.activeSpeaker = session.rpcs[UUID].activelySpeaking;
item.defaultSpeaker = session.rpcs[UUID].defaultSpeaker;
} catch(e){
errorlog(e);
}
item.videoVisible = session.rpcs[UUID].videoElement && session.rpcs[UUID].videoElement.checkVisibility();
if (session.rpcs[UUID].videoElement){
item.videoVolume = session.rpcs[UUID].videoElement.volume;
}
item.iframeVisible = session.rpcs[UUID].iframeVisible && session.rpcs[UUID].iframeVisible.checkVisibility();
if (session.directorList.indexOf(UUID)>=0){
item.director = true;
} else {
item.director = false;
}
try {
if (session.director){
if (guestFeeds){
var lock = parseInt(document.getElementById("position_"+UUID).dataset.locked);
if (lock){
item.position = lock; // probably should make a universal function to do this, for all lock requesting
} else {
var child = document.getElementById('container_'+UUID);
if (child){
var parent = child.parentNode;
if (parent.id == "guestFeeds"){
item.position = Array.prototype.indexOf.call(parent.children, child) + 1;
}
}
}
}
var scenes = getById("container_" + UUID).querySelectorAll('[data-action-type="addToScene"][data-scene][data--u-u-i-d="'+UUID+'"]');
var sceneState = {};
for (var i=0;i session.currentSlots[key] === session.streamID) || false;
}
} catch(e){errorlog(e);}
if (session.info && session.info.out){
streamList[session.streamID].outbound = session.info.out;
}
if (session.showDirector && session.director){
var child = document.getElementById('container_director');
if (child){
var parent = child.parentNode;
if (parent.id == "guestFeeds"){
streamList[session.streamID].position = Array.prototype.indexOf.call(parent.children, child) + 1;
}
}
}
if (session.notifyScreenShare){
streamList[session.streamID].screenSharing = session.screenShareState;
} else {
streamList[session.streamID].screenSharing = false;
}
if (session.streamSrc){
streamList[session.streamID].audioTrack = (session.streamSrc.getAudioTracks().length !== 0);
streamList[session.streamID].videoTrack = (session.streamSrc.getVideoTracks().length !== 0);
} else {
streamList[session.streamID].audioTrack = false;
streamList[session.streamID].videoTrack = false;
}
return streamList;
}
function syncOtherState(sid){
if (!session.syncState){return;}
if (!session.syncState[sid]){return;}
/* if (session.rpcs[ele.dataset.UUID].directorMutedState==1){
pokeIframeAPI("director-mute-state", true, ele.dataset.UUID);
pokeAPI("directorMuted", true, session.rpcs[ele.dataset.UUID].streamID);
} else {
pokeIframeAPI("director-mute-state", false, ele.dataset.UUID);
pokeAPI("directorMuted", false, session.rpcs[ele.dataset.UUID].streamID);
} */
var others = session.syncState[sid].others;
log(others);
for (var other in others){
if (other == "toggle-group"){continue;}
var ele = document.querySelector('[data-sid="'+sid+'"][data-action-type="'+other+'"]');
if (ele){
if (others[other]){
if (!("value" in ele)){
errorlog("NO DEFAULT VALUE IN SPECIFIED ELEMENT; guessing default: "+other);
ele.value = 0;
}
var changed = true;
if (ele.value==others[other]){
changed=false
}
if (other == "mute-guest"){
if (changed){
remoteMute(ele, false, true);
}
} else if (other == "hide-guest"){
if (changed){
remoteHideVideo(ele, true, true);
}
} else if (other == "mute-video-guest"){
if (changed){
remoteMuteVideo(ele, true, true);
}
} else {
ele.value = others[other];
if (ele.nodeName.toLowerCase() == "input"){
ele.value = parseInt(others[other]);
} else if (parseInt(others[other])){
ele.classList.add("pressed"); ele.ariaPressed = "true";
} else {
ele.classList.remove("pressed"); ele.ariaPressed = "false";
}
}
}
}
}
var UUID = document.querySelector('[data-sid="'+sid+'"][data--u-u-i-d');
if (UUID && UUID.dataset.UUID){
UUID = UUID.dataset.UUID;
if (session.syncState[sid].group && session.rpcs[UUID]){
session.rpcs[UUID].group = session.syncState[sid].group;
syncGroup(session.rpcs[UUID].group, UUID);
}
}
}
function htmlToElement(html) {
var template = document.createElement('template');
html = html.trim(); // Never return a text node of whitespace as the result
template.innerHTML = html;
return template.content.firstChild;
}
function syncGroup(groups, UUID){
if (!groups || (typeof groups !== 'object')){
errorlog("Group isn't an object");
return;
}
groups.forEach(group=>{
var ele = getById("container_" + UUID).querySelector('[data-action-type="toggle-group"][data--u-u-i-d="'+UUID+'"][data-group="'+group+'"]');
if (!ele){
var newGroup = htmlToElement('');
var added = false;
getById("container_" + UUID).querySelectorAll('.customGroup>[data-group]').forEach(ele=>{
log(ele);
if (!added && ele.dataset.group>group+""){
ele.parentNode.insertBefore(newGroup, ele);
added = true;
}
});
if (!added){
var newGroupCon = getById("container_" + UUID).querySelector(".customGroup");
if (!newGroupCon){
newGroupCon = document.createElement("div");
newGroupCon.classList.add("customGroup");
getById("container_" + UUID).appendChild(newGroupCon);
}
newGroupCon.appendChild(newGroup);
}
}
});
var elements = document.querySelectorAll('[data-action-type="toggle-group"][data--u-u-i-d="'+UUID+'"][data-group]');
if (elements.length){
for (var i=0;i0){
session.roomTimer = false;
} else {
session.roomTimer = Date.now()/1000 - session.roomTimer;
}
ele.classList.add("red");
} else {
ele.value = 2;
ele.classList.remove("pressed"); ele.ariaPressed = "false";
session.roomTimer = false;
msg.stopClock = true;
stopClock();
msg.hideClock = true;
hideClock();
ele.innerHTML = ' Create Timer';
}
//miniTranslate(ele);
} else if (event.ctrlKey || event.metaKey){
if (ele.value == 1) {
ele.value = 3;
msg.pauseClock = true;
pauseClock();
if (!session.roomTimer){
session.roomTimer = false;
} else if (session.roomTimer0){
session.roomTimer = false;
} else {
session.roomTimer = Date.now()/1000 - session.roomTimer;
}
ele.classList.add("red");
}
}
if (ele.dataset.UUID){
session.sendRequest(msg, ele.dataset.UUID);
} else {
session.sendRequest(msg);
}
}
function updateRemoteTimerButton(UUID, currentTime) {
var elements = document.querySelectorAll('[data-action-type="create-timer"][data--u-u-i-d="' + UUID + '"]');
if (elements[0]){
if (elements[0].value != 2) {
var time = parseInt(currentTime) || 0;
elements[0].classList.add("pressed"); elements[0].ariaPressed = "true";
elements[0].value = 1;
if (time<0) {
time = time * -1;
var minutes = Math.floor(time / 60);
var seconds = time - minutes * 60;
elements[0].classList.add("red");
elements[0].innerHTML = ' -' + (minutes) + "m : " + zpadTime(seconds) + "s";
} else {
var minutes = Math.floor(time / 60);
var seconds = time - minutes * 60;
elements[0].classList.remove("red");
elements[0].innerHTML = ' ' + (minutes) + "m : " + zpadTime(seconds) + "s";
}
} else {
elements[0].classList.remove("pressed"); elements[0].ariaPressed = "false";
elements[0].classList.remove("red");
elements[0].innerHTML = ' Create Timer';
}
}
}
function directMute(ele, event=false) { // A directing room only is controlled by the Director, with the exception of MUTE.
log("mute 2");
if (!event || (!((event.ctrlKey) || (event.metaKey)))) {
if (ele.value == 1) {
ele.value = 0;
ele.classList.remove("pressed"); ele.ariaPressed = "false";
ele.innerHTML = ' mute in scene';
} else {
ele.value = 1;
ele.classList.add("pressed"); ele.ariaPressed = "true";
ele.innerHTML = ' unmute';
}
miniTranslate(ele);
}
var msg = {};
msg.scene = true;
msg.action = "mute";
msg.value = ele.value;
msg.target = ele.dataset.sid;
log(msg);
log("ele:");
log(ele);
//for (var uuid in session.pcs){ // obsolete at this point; v22
// if (session.pcs[uuid].stats.info && ("version" in session.pcs[uuid].stats.info) && (session.pcs[uuid].stats.info.version < 17.2)){
// msg.request = "sendroom";
// session.sendMsg(msg);
// return;
// }
//}
for (var uuid in session.pcs){
if (session.pcs[uuid].scene!==false){ // send to all scenes (but scene = 0)
session.sendMessage(msg, uuid);
}
}
syncDirectorState(ele);
if (msg.value){
return true;
} else {
return false;
}
}
function requestFileUpload(ele){
ele.classList.add("pressed"); ele.ariaPressed = "true";
ele.disabled = true;
ele.innerHTML = ' Requesting..';
setTimeout(function(ele){
try{
ele.innerHTML = ' Request File';
ele.classList.remove("pressed"); ele.ariaPressed = "false";
ele.disabled = false
} catch(e){}
},15000, ele);
var msg = {};
msg.requestUpload = true; // toggleFileshare
msg.UUID = ele.dataset.UUID;
session.sendRequest(msg, ele.dataset.UUID);
}
function remoteSpeakerMute(ele, event=false){
log("speaker mute");
if (!event || (!((event.ctrlKey) || (event.metaKey)))) {
if (ele.value == 1) {
ele.value = 0;
ele.classList.remove("pressed"); ele.ariaPressed = "false";
ele.innerHTML = ' Deafen';
} else {
ele.value = 1;
ele.classList.add("pressed"); ele.ariaPressed = "true";
ele.innerHTML = ' Undeafen';
}
miniTranslate(ele);
}
var msg = {};
if (ele.value == 0) {
msg.speakerMute = false
} else {
msg.speakerMute = true;
}
msg.UUID = ele.dataset.UUID;
session.sendRequest(msg, ele.dataset.UUID);
syncDirectorState(ele);
errorlog(msg);
return msg.speakerMute;
}
function updateRemoteSpeakerMute(UUID) {
var ele = document.querySelectorAll('[data-action-type="toggle-remote-speaker"][data--u-u-i-d="' + UUID + '"]');
if (ele[0]) {
ele[0].classList.add("pressed"); ele[0].ariaPressed = "true";
ele[0].value = 1;
ele[0].innerHTML = ' undeafen';
miniTranslate(ele[0]);
}
return true;
}
function updateRemoteDisplayMute(UUID, blind=true) {
var ele = document.querySelectorAll('[data-action-type="toggle-remote-display"][data--u-u-i-d="' + UUID + '"]');
if (ele[0]) {
if (blind){
ele[0].classList.add("pressed"); ele[0].ariaPressed = "true";
ele[0].value = 1;
ele[0].innerHTML = ' unblind';
miniTranslate(ele[0]);
return true;
} else {
ele[0].classList.remove("pressed"); ele[0].ariaPressed = "false";
ele[0].value = 0;
ele[0].innerHTML = ' blind';
miniTranslate(ele[0]);
return false;
}
}
return false;
}
function blindAllGuests(ele, event=false){
if (!session.director){
if (!session.cleanOutput){warnUser("Only a director can mute other guests");}
return;
} // only a director can use this button.
log("blind all display mute");
if (!event || (!((event.ctrlKey) || (event.metaKey)))) {
if (ele.value == 1) {
ele.value = 0;
ele.classList.remove("pressed"); ele.ariaPressed = "false";
ele.classList.remove("red");
ele.innerHTML = '';
} else {
ele.value = 1;
ele.classList.add("pressed"); ele.ariaPressed = "true";
ele.classList.add("red");
ele.innerHTML = '';
}
}
var msg = {};
if (ele.value == 0) {
msg.displayMute = false;
session.directorBlindAllGuests = false;
} else {
msg.displayMute = true;
session.directorBlindAllGuests= true;
}
for (var UUID in session.rpcs){ // doesn't include scenes, as they don't publiish and this is rpcs
if (session.directorList.indexOf(UUID)>=0){continue;} // don't try to mute other directors
try {
session.sendRequest(msg, UUID);
updateRemoteDisplayMute(UUID, msg.displayMute);
} catch(e){errorlog(e);}
}
syncDirectorState(ele);
return msg.displayMute;
}
function remoteDisplayMute(ele, event=false) {
log("display mute");
if (!event || (!((event.ctrlKey) || (event.metaKey)))) {
if (ele.value == 1) {
ele.value = 0;
ele.classList.remove("pressed"); ele.ariaPressed = "false";
ele.innerHTML = ' Blind';
} else {
ele.value = 1;
ele.classList.add("pressed"); ele.ariaPressed = "true";
ele.innerHTML = ' Unblind';
}
miniTranslate(ele);
}
var msg = {};
if (ele.value == 0) {
msg.displayMute = false;
} else {
msg.displayMute = true;
}
msg.UUID = ele.dataset.UUID;
session.sendRequest(msg, ele.dataset.UUID);
syncDirectorState(ele);
return msg.displayMute;
}
function remoteLowerhands(UUID) {
var msg = {};
msg.lowerhand = true;
msg.UUID = UUID;
session.sendRequest(msg, UUID);
try{
getById("hands_"+UUID).classList.add("hidden");
session.rpcs[UUID].remoteRaisedHandElement.classList.add("hidden");
} catch(e){}
return true;
}
function remoteMute(ele, event=false, skipSend=false) {
log("mute");
var val = parseInt(ele.value) || 0;
if (!event || (!((event.ctrlKey) || (event.metaKey)))) {
if (val == 1){
ele.value = 0;
ele.classList.remove("pressed"); ele.ariaPressed = "false";
ele.innerHTML = '';
ele.innerHTML += miscTranslations["mute"] || "Mute";
} else {
ele.value = 1;
ele.classList.add("pressed"); ele.ariaPressed = "true";
ele.innerHTML = '';
ele.innerHTML += miscTranslations["unmute"] || "Unmute";
}
}
try {
session.rpcs[ele.dataset.UUID].directorMutedState = ele.value;
var volume = session.rpcs[ele.dataset.UUID].directorVolumeState;
} catch (e) {
errorlog(e);
var volume = 100;
}
if (!skipSend){
var msg = {};
if (val == 1) {
msg.volume = volume;
} else {
msg.volume = 0;
}
msg.UUID = ele.dataset.UUID;
session.sendRequest(msg, ele.dataset.UUID);
syncDirectorState(ele);
log(msg);
}
if (session.rpcs[ele.dataset.UUID].directorMutedState==1){
pokeIframeAPI("director-mute-state", true, ele.dataset.UUID);
pokeAPI("directorMuted", true, session.rpcs[ele.dataset.UUID].streamID);
} else {
pokeIframeAPI("director-mute-state", false, ele.dataset.UUID);
pokeAPI("directorMuted", false, session.rpcs[ele.dataset.UUID].streamID);
}
if (val){
return true;
} else {
return false;
}
}
function toggleQualityGear3(){
toggle(document.getElementById('videoSettings3'), inline=false);
if (getById("gear_webcam3").style.display === "inline-block") {
var videoSelect = document.querySelector("select#videoSource3").options;
var obscam = false;
log(videoSelect[videoSelect.selectedIndex].text);
if (videoSelect[videoSelect.selectedIndex].text.startsWith("OBS-Camera")) { // OBS Virtualcam
obscam = true;
} else if (videoSelect[videoSelect.selectedIndex].text.startsWith("OBS Virtual Camera")) { // OBS Virtualcam
obscam = true;
}
updateStats(obscam);
}
}
function remoteHideVideo(ele, event=false, skipSend=false) {
log("video hide");
if (!event || ((event.ctrlKey) || (event.metaKey))) {
ele.children[1].innerHTML = miscTranslations["armed"]
//ele.style.backgroundColor = "#BF3F3F";
ele.classList.add("armed");
Callbacks.push([remoteHideVideo, ele, false]);
log("video queued");
return;
} else {
if (ele.value == 1) {
ele.value = 0;
ele.classList.remove("pressed"); ele.ariaPressed = "false";
ele.innerHTML = ' Hide';
} else {
ele.value = 1;
ele.classList.add("pressed"); ele.ariaPressed = "true";
ele.innerHTML = ' Unhide';
}
miniTranslate(ele);
ele.classList.remove("armed");//ele.style.backgroundColor = null;
}
var msg = {};
if (ele.value == 0) {
msg.directVideoMuted = false;
} else {
msg.directVideoMuted = true;
}
if (!skipSend){
for (var i in session.pcs){
msg.target = ele.dataset.UUID;
if (i === msg.target){
msg.target = true;
}
try{
session.pcs[i].sendChannel.send(JSON.stringify(msg));
} catch(e){}
}
syncDirectorState(ele);
}
pokeIframeAPI("director-video-hide-state", msg.directVideoMuted, ele.dataset.UUID);
pokeAPI("directorVideoHide", msg.directVideoMuted, session.rpcs[ele.dataset.UUID].streamID);
return msg.directVideoMuted;
}
function remoteMuteVideo(ele, event=false, skipSend=false) {
log("video mute");
if (!event || ((event.ctrlKey) || (event.metaKey))) {
ele.children[1].innerHTML = miscTranslations["armed"]
ele.classList.add("armed");//ele.style.backgroundColor = "#BF3F3F";
Callbacks.push([remoteMuteVideo, ele, false]);
log("video queued");
return;
} else {
if (ele.value == 1) {
ele.value = 0;
ele.classList.remove("pressed"); ele.ariaPressed = "false";
ele.innerHTML = ' Video off';
} else {
ele.value = 1;
ele.classList.add("pressed"); ele.ariaPressed = "true";
ele.innerHTML = ' Video on';
}
miniTranslate(ele);
ele.classList.remove("armed");
}
var msg = {};
if (ele.value == 0) {
msg.remoteVideoMuted = false;
} else {
msg.remoteVideoMuted = true;
}
if (!skipSend){
session.sendRequest(msg, ele.dataset.UUID)
syncDirectorState(ele);
}
pokeIframeAPI("remote-video-mute-state", msg.remoteVideoMuted, ele.dataset.UUID);
pokeAPI("remoteVideoMuted", msg.remoteVideoMuted, session.rpcs[ele.dataset.UUID].streamID);
return msg.remoteVideoMuted;
}
function updateDirectorVideoHide(UUID) {
var ele = document.querySelectorAll('[data-action-type="hide-guest"][data--u-u-i-d="' + UUID + '"]');
if (ele[0]) {
ele[0].value = 1;
ele[0].classList.add("pressed"); ele[0].ariaPressed = "true";
ele[0].innerHTML = ' Unhide';
miniTranslate(ele[0]);
}
return true;
}
function updateDirectorVideoMute(UUID) {
var ele = document.querySelectorAll('[data-action-type="mute-video-guest"][data--u-u-i-d="' + UUID + '"]');
if (ele[0]) {
ele[0].value = 1;
ele[0].classList.add("pressed"); ele[0].ariaPressed = "true";
ele[0].innerHTML = ' Video on';
miniTranslate(ele[0]);
}
return true;
}
function directVolume(ele) { // NOT USED ANYMORE
log("volume");
var msg = {};
msg.scene = true;
msg.action = "volume";
msg.target = ele.dataset.sid; // i want to focus on the STREAM ID, not the UUID...
msg.value = ele.value;
//for (var uuid in session.pcs){
// if (session.pcs[uuid].stats.info && ("version" in session.pcs[uuid].stats.info) && (session.pcs[uuid].stats.info.version < 17.2)){
// msg.request = "sendroom";
// session.sendMsg(msg);
// return;
// }
//}
for (var uuid in session.pcs){
if (session.pcs[uuid].scene!==false){ // send to all scenes (but scene = 0)
session.sendMessage(msg, uuid);
}
}
syncDirectorState(ele);
return msg.value;
}
function applyMuteState(UUID){ // this is the mute state of PLAYBACK audio; not the microphone or outbound.
if (!(UUID in session.rpcs)){return "UUID not found";}
var muteOutcome = session.rpcs[UUID].mutedState || session.rpcs[UUID].mutedStateMixer || session.rpcs[UUID].mutedStateScene || session.speakerMuted || session.rpcs[UUID].bandwidthMuted;
if (!muteOutcome && (session.noaudio !== false)){
if (session.noaudio===true){
muteOutcome = true;
} else if (session.noaudio.length){
if (("streamID" in session.rpcs[UUID]) && session.rpcs[UUID].streamID && !session.noaudio.includes(session.rpcs[UUID].streamID)){
muteOutcome = true;
}
} else {
muteOutcome = true;
}
}
if (session.rpcs[UUID].videoElement){
if (session.rpcs[UUID].videoElement && session.rpcs[UUID].videoElement.usermuted===true){return "usermuted true";}
session.rpcs[UUID].videoElement.muted = muteOutcome;
}
// session.scene
return muteOutcome;
}
function checkMuteState(UUID){ // this is the mute state of PLAYBACK audio; not the microphone or outbound.
if (!(UUID in session.rpcs)){return false;}
var muteOutcome = session.rpcs[UUID].mutedState || session.rpcs[UUID].mutedStateMixer || session.rpcs[UUID].mutedStateScene || session.speakerMuted || session.rpcs[UUID].bandwidthMuted;
return muteOutcome;
}
function remoteVolumeUI(ele){
ele.nextElementSibling.innerHTML = ele.value + "%";
if (Date.now() - remoteSliderTimeout > 100){
remoteSliderTimeout = Date.now();
remoteVolume(ele);
}
//setVolumeColor(ele);
return ele.value;
}
/* function setVolumeColor(ele){
var vol1 = 200-parseInt(ele.value);
if (vol1<0){vol1=0};
ele.style.backgroundColor = "hsl("+vol1+", 100%, 50%)";
} */
function remoteVolume(ele) { // A directing room only is controlled by the Director, with the exception of MUTE.
log("volume: "+session.rpcs[ele.dataset.UUID].directorMutedState);
var msg = {};
var muted = session.rpcs[ele.dataset.UUID].directorMutedState;
//
//log(ele);
if (muted == true) { // 1 is a string, not an int, so == and not ===. this happens in a few places :/
session.rpcs[ele.dataset.UUID].directorVolumeState = ele.value;
} else {
session.rpcs[ele.dataset.UUID].directorVolumeState = ele.value;
msg.volume = ele.value;
msg.UUID = ele.dataset.UUID;
session.sendRequest(msg, ele.dataset.UUID);
}
pokeIframeAPI("director-volume-state", ele.value, ele.dataset.UUID);
syncDirectorState(ele);
return ele.value;
}
function clearDirectorSettings(){ // make sure to wipe the director's room settings if creating a new room.
removeStorage("directorCustomize");
removeStorage("directorWebsiteShare");
}
function saveDirectorSettings(){
var settings = {};
if (getById("customizeLinks").classList.contains("hidden")){
settings.customizeLinks = true;
}
var customizeLinks1 = getById("customizeLinks1").querySelectorAll("input");
settings.customizeLinks1 = {};
for (var i=0;i {
try {
if (customizeLinks1.querySelector('[data-param="'+key+'"]').checked != settings.customizeLinks1[key]){
customizeLinks1.querySelector('[data-param="'+key+'"]').checked = settings.customizeLinks1[key];
customizeLinks1.querySelector('[data-param="'+key+'"]').onchange();
}
} catch(e){errorlog(e);}
});
}
if (settings.customizeLinks3){
var customizeLinks3 = getById("customizeLinks3");
Object.keys(settings.customizeLinks3).forEach((key, index) => {
try {
if (customizeLinks3.querySelector('[data-param="'+key+'"]').checked == settings.customizeLinks3[key]){
customizeLinks3.querySelector('[data-param="'+key+'"]').checked = settings.customizeLinks3[key];
customizeLinks3.querySelector('[data-param="'+key+'"]').onchange();
}
} catch(e){errorlog(e);}
});
}
if (settings.directorLinks1){
var directorLinks1 = getById("directorLinks1");
Object.keys(settings.directorLinks1).forEach((key, index) => {
try {
if (directorLinks1.querySelector('[data-param="'+key+'"]').checked == settings.directorLinks1[key]){
directorLinks1.querySelector('[data-param="'+key+'"]').checked = settings.directorLinks1[key];
directorLinks1.querySelector('[data-param="'+key+'"]').onchange();
}
} catch(e){
errorlog("key :"+key);
errorlog(e);
}
});
}
if (settings.directorLinks2){
var directorLinks2 = getById("directorLinks2");
Object.keys(settings.directorLinks2).forEach((key, index) => {
try {
if (directorLinks2.querySelector('[data-param="'+key+'"]').checked == settings.directorLinks2[key]){
directorLinks2.querySelector('[data-param="'+key+'"]').checked = settings.directorLinks2[key];
directorLinks2.querySelector('[data-param="'+key+'"]').onchange();
}
} catch(e){
errorlog("key :"+key);
errorlog(e);
}
});
}
}
function sendChat(chatmessage = "hi", UUID=false, overlay=false) { // A directing room only is controlled by the Director, with the exception of MUTE.
log("Chat message");
var msg = {};
msg.chat = chatmessage;
msg.overlay = overlay;
session.sendPeers(msg, UUID);
return true;
}
var activatedStream = false;
async function publishScreen() {
if (activatedStream == true) {
return;
}
activatedStream = true;
setTimeout(function() {
activatedStream = false;
}, 1000);
formSubmitting = false;
var quality = 0;
if (document.getElementById("webcamquality2")){
quality = parseInt(document.getElementById("webcamquality2").elements.namedItem("resolution2").value) || 0;
}
session.quality_ss = quality;
if (session.quality !== false) {
quality = session.quality; // override the user's setting
}
if (session.screensharequality !== false){
quality = session.screensharequality;
}
var video = {}
if (quality == -1) {
// unlocked capture resolution
} else if (quality == 0) {
video.width = {
ideal: 1920
};
video.height = {
ideal: 1080
};
} else if (quality == 1) {
video.width = {
ideal: 1280
};
video.height = {
ideal: 720
};
} else if (quality == 2) {
video.width = {
ideal: 640
};
video.height = {
ideal: 360
};
} else if (quality >= 3) { // lowest
video.width = {
ideal: 320
};
video.height = {
ideal: 180
};
} else {
video.width = {
min: 640
};
video.height = {
min: 360
};
}
if (session.width) {
video.width = {
ideal: session.width
};
}
if (session.height) {
video.height = {
ideal: session.height
};
}
var constraints = {
audio: {
echoCancellation: false,
autoGainControl: false,
noiseSuppression: false
},
video: video
};
if (session.noiseSuppression === true) {
constraints.audio.noiseSuppression = true;; // the defaults for screen publishing should be off.
}
if (session.autoGainControl === true) {
constraints.audio.autoGainControl = true; // the defaults for screen publishing should be off.
}
if (session.echoCancellation === true) {
constraints.audio.echoCancellation = true; // the defaults for screen publishing should be off.
}
try {
let supportedConstraints = navigator.mediaDevices.getSupportedConstraints();
if (supportedConstraints.cursor) {
if (session.screensharecursor){
constraints.video.cursor = ["always", "motion"];
} else {
constraints.video.cursor = "never";
}
}
if (session.suppressLocalAudioPlayback && supportedConstraints.suppressLocalAudioPlayback){
constraints.audio.suppressLocalAudioPlayback = true;
}
//
if (session.preferCurrentTab){
constraints.preferCurrentTab = true;
}
if (session.selfBrowserSurface){
constraints.selfBrowserSurface = session.selfBrowserSurface; // exclude or include
}
if (session.surfaceSwitching){
constraints.surfaceSwitching = session.surfaceSwitching; // exclude or include
}
if (session.systemAudio){
constraints.systemAudio = session.systemAudio; // exclude or include
}
if (session.displaySurface && supportedConstraints.displaySurface){
constraints.video.displaySurface = session.displaySurface; // monitor, window, or browser
}
} catch(e){
warnlog("navigator.mediaDevices.getSupportedConstraints() not supported");
}
var overrideFramerate = false;
if ((session.frameRate !== false) && (session.maxframeRate != false)){
overrideFramerate = session.frameRate;
constraints.video.frameRate = {
ideal: session.maxframeRate,
max: session.maxframeRate
};
} else if (session.frameRate !== false) {
constraints.video.frameRate = session.frameRate;
} else if (session.maxframeRate != false){
constraints.video.frameRate = {
ideal: session.maxframeRate,
max: session.maxframeRate
};
} else {
constraints.video.frameRate = {
ideal: 60
};
}
var audioSelect = getById('audioSourceScreenshare');
var outputSelect = getById('outputSourceScreenshare');
try {
session.sink = outputSelect.options[outputSelect.selectedIndex].value; // will probably fail on Safari.
log("Session Sink: " + session.sink);
saveSettings();
} catch (e){warnlog(e);}
return await publishScreen2(constraints, audioSelect, true, overrideFramerate).then((res) => {
if (res == false) {
return;
} // no screen selected
log("streamID is: " + session.streamID);
if (session.transcript) {
setTimeout(function() {
setupClosedCaptions();
}, 1000);
}
if (!session.cleanOutput){
getById("mutebutton").classList.remove("hidden");
getById("mutespeakerbutton").classList.remove("hidden");
//getById("mutespeakerbutton").className="float";
getById("chatbutton").className = "float";
getById('sharefilebutton').classList.remove("hidden"); // we won't override "display:none", if set, though.
getById("mutevideobutton").className = "float";
getById("hangupbutton").className = "float";
if (session.showSettings) {
getById("settingsbutton").className = "float";
}
if (session.raisehands) {
getById("raisehandbutton").className = "float";
}
if (session.pptControls){
getById("pptbackbutton").classList.remove("hidden");
getById("pptnextbutton").classList.remove("hidden");
}
if (session.recordLocal !== false) {
getById("recordLocalbutton").className = "float";
}
if (session.screensharebutton) {
getById("screensharebutton").className = "float";
}
getById("controlButtons").classList.remove("hidden");
getById("helpbutton").style.display = "inherit";
getById("reportbutton").style.display = "";
} else if (session.cleanish && session.recordLocal!==false){
getById("recordLocalbutton").className = "float";
getById("mutebutton").classList.add("hidden");
getById("mutespeakerbutton").classList.add("hidden");
getById("chatbutton").classList.add("hidden");
getById("mutevideobutton").classList.add("hidden");
getById("hangupbutton").classList.add("hidden");
getById("hangupbutton2").classList.add("hidden");
getById("controlButtons").classList.remove("hidden");
getById("settingsbutton").classList.add("hidden");
getById("screenshare2button").classList.add("hidden");
getById("screensharebutton").classList.add("hidden");
getById("screenshare3button").classList.add("hidden");
getById("queuebutton").classList.add("hidden");
} else {
getById("controlButtons").classList.add("hidden");
}
if (session.chatbutton === true) {
getById("chatbutton").classList.remove("hidden");
getById("controlButtons").classList.remove("hidden");
} else if (session.chatbutton === false) {
getById("chatbutton").classList.add("hidden");
}
if (session.screensharebutton === true) {
getById("controlButtons").classList.remove("hidden");
getById("screensharebutton").className = "float";
}
if (session.hangupbutton === true){
getById("controlButtons").classList.remove("hidden");
getById("hangupbutton").className = "float";
}
getById("head1").className = 'hidden';
getById("head2").className = 'hidden';
return res;
}).catch(() => {});
}
function getWidth() {
return Math.max(
document.body.scrollWidth,
document.documentElement.scrollWidth,
document.body.offsetWidth,
document.documentElement.offsetWidth,
document.documentElement.clientWidth
);
}
function getHeight() {
return Math.max(
document.documentElement.clientHeight
);
}
function updateForceRotate(){
if (session.orientation){
try {
var track = false;
if (session.streamSrc){
var tracks = session.streamSrc.getVideoTracks();
if (tracks.length){
track = tracks[0];
}
}
if (!track){
return;
}
const capabilities = track.getCapabilities();
const settings = track.getSettings();
session.currentCameraConstraints = settings;
if (!window.matchMedia("(orientation: portrait)").matches){
if (session.currentCameraConstraints && session.currentCameraConstraints.aspectRatio){
session.currentCameraConstraints.aspectRatio = 1/session.currentCameraConstraints.aspectRatio;
}
}
if ("width" in settings){
if ("height" in settings){
if (settings.width < settings.height){
if (session.orientation=="landscape"){
if (capabilities.facingMode == "environment"){
session.forceRotate=270;
} else {
session.forceRotate=90;
}
} else {
//if (!session.forceRotate){return;}
session.forceRotate = 0;
}
} else if (settings.width > settings.height){
if (session.orientation=="portrait"){
if (capabilities.facingMode == "environment"){
session.forceRotate=90;
} else {
session.forceRotate=270;
}
} else {
//if (!session.forceRotate){return;}
session.forceRotate = 0;
}
} else {
// if (!session.forceRotate){return;}
session.forceRotate = 0;
}
} else {
return;
}
} else {
return;
}
var msg = {};
if (session.forceRotate!==false){
if (session.rotate){
msg.rotate_video = session.forceRotate + parseInt(session.rotate);
} else {
msg.rotate_video = session.forceRotate;
}
} else {
msg.rotate_video = session.rotate;
}
if (msg.rotate_video && (msg.rotate_video>=360)){
msg.rotate_video-=360;
}
session.sendMessage(msg);
} catch(e){errorlog(e);}
updateForceRotatedCSS()
applyMirror(session.mirrorExclude);
}
session.setResolution(); // probably only triggers with mobile devices?
}
function updateForceRotatedCSS(){
if (session.forceRotate==270){
document.body.setAttribute( "style", "transform: rotate(270deg);position: absolute;top: 100vh;left: 0;height: 100vw;width: 100vh;transform-origin: 0 0;");
} else if (session.forceRotate==90){
document.body.setAttribute( "style", "transform: rotate(90deg);position: absolute;top: 0;left: 100vw;height: 100vw;width: 100vh;transform-origin: 0 0;");
} else {
document.body.setAttribute( "style", "");
}
}
async function joinDataMode(){ // join the room, but without publishing anything.
await session.connect();
if (session.roomid){
getById("head3").classList.add('hidden');
getById("head3a").classList.add('hidden');
joinRoom(session.roomid);
} else if (session.view){
window.onresize = updateMixer;
play();
if (session.permaid!==false){
session.postPublish();
}
} else if (session.permaid!==false){
session.postPublish();
}
}
function publishWebcam(btn = false, miconly=false) {
if (btn) {
if (btn.dataset.ready == "false") {
warnlog("Clicked too quickly; button not enabled yet");
return;
}
if (getById("passwordBasicInput").value.length){
session.password = getById("passwordBasicInput").value;
session.password = sanitizePassword(session.password);
if (session.password.length==0){
session.password = false;
} else {
session.defaultPassword = false;
if (urlParams.has('pass')) {
updateURL("pass=" + session.password);
} else if (urlParams.has('pw')) {
updateURL("pw=" + session.password);
} else if (urlParams.has('p')) {
updateURL("p=" + session.password);
} else {
updateURL("password=" + session.password);
}
}
}
}
if (activatedStream == true) {
return;
}
activatedStream = true;
log("PRESSED PUBLISH WEBCAM!!");
formSubmitting = false;
window.scrollTo(0, 0); // iOS has a nasty habit of overriding the CSS when changing camaera selections, so this addresses that.
getById("head2").className = 'hidden';
if (session.roomid !== false) { // they are in a room or a faux room
window.onresize = updateMixer;
window.onorientationchange = function(){setTimeout(async function(){
if (session.forceAspectRatio){
await updateCameraConstraints("aspectRatio", session.forceAspectRatio);
}
if (session.effect && (session.effect === "7")){digitalZoom(true);}
updateForceRotate();
updateMixer();
}, 200);};
if ((session.roomid === "") && ((!(session.view)) || (session.view === ""))) {
// no room, no viewing, viewing disabled
if (session.manual===null){
session.manual = true;
}
if (!(session.cleanOutput)) {
var showReshare = getStorage("showReshare");
if (showReshare){
generateHash(session.streamID + session.salt + "bca321", 4).then(function(hash) { // million to one error.
if (showReshare === hash){
getById("head3").classList.remove('hidden');
getById("head3a").classList.remove('hidden');
}
}).catch(errorlog);
}
}
} else {
log("ROOM ID ENABLED");
log("Update Mixer Event on REsize SET");
getById("main").style.overflow = "hidden";
//session.cbr=0; // we're just going to override it
if (session.stereo == 5) {
if (session.roomid === "") {
session.stereo = 1;
} else {
session.stereo = 3;
}
}
joinRoom(session.roomid);
if (session.roomid !== "") {
if (!(session.cleanOutput)) {
getById("head2").className = '';
}
}
getById("head3").classList.add('hidden');
getById("head3a").classList.add('hidden');
}
} else { // they are not in a room or faux room
if (session.manual===null){
session.manual = true;
}
getById("head3").classList.remove('hidden');
getById("head3a").classList.remove('hidden');
getById("logoname").style.display = 'none';
generateHash(session.streamID + session.salt + "bca321", 4).then(function(hash) { // million to one error.
setStorage("showReshare", hash, 24*30)
}).catch(errorlog);
}
log("streamID is: " + session.streamID);
getById("head1").className = 'hidden';
if (!session.cleanOutput){
getById("mutebutton").classList.remove("hidden");
getById("mutespeakerbutton").classList.remove("hidden");
//getById("mutespeakerbutton").className="float";
getById("chatbutton").className = "float";
getById('sharefilebutton').classList.remove("hidden"); // we won't override "display:none", if set, though.
getById("mutevideobutton").className = "float";
getById("hangupbutton").className = "float";
if (session.showSettings) {
getById("settingsbutton").className = "float";
}
if (session.raisehands) {
getById("raisehandbutton").className = "float";
}
if (session.pptControls){
getById("pptbackbutton").classList.remove("hidden");
getById("pptnextbutton").classList.remove("hidden");
}
if (session.recordLocal !== false) {
getById("recordLocalbutton").className = "float";
}
if (session.screensharebutton) {
if (session.roomid) {
if (session.screenshareType===3){
getById("screenshare3button").className = "float";
getById("screensharebutton").className = "float hidden";
getById("screenshare2button").className = "float hidden";
} else if (session.screenshareType===1){
getById("screensharebutton").className = "float";
getById("screenshare3button").className = "float hidden";
getById("screenshare2button").className = "float hidden";
} else if (session.screenshareType===2){
getById("screenshare2button").className = "float";
getById("screensharebutton").className = "float hidden";
getById("screenshare3button").className = "float hidden";
} else {
getById("screenshare3button").className = "float";
getById("screensharebutton").className = "float hidden";
getById("screenshare2button").className = "float hidden";
}
} else {
getById("screensharebutton").className = "float";
getById("screenshare2button").className = "float hidden";
getById("screenshare3button").className = "float hidden";
}
}
getById("controlButtons").classList.remove("hidden");
getById("helpbutton").style.display = "inherit";
getById("reportbutton").style.display = "";
} else if (session.cleanish && session.recordLocal!==false){
getById("recordLocalbutton").className = "float";
getById("mutebutton").classList.add("hidden");
getById("mutespeakerbutton").classList.add("hidden");
getById("chatbutton").classList.add("hidden");
getById("mutevideobutton").classList.add("hidden");
getById("hangupbutton").classList.add("hidden");
getById("hangupbutton2").classList.add("hidden");
getById("controlButtons").classList.remove("hidden");
getById("settingsbutton").classList.add("hidden");
getById("screenshare2button").classList.add("hidden");
getById("screensharebutton").classList.add("hidden");
getById("queuebutton").classList.add("hidden");
} else {
getById("controlButtons").classList.add("hidden");
}
if (session.chatbutton === true) {
getById("chatbutton").classList.remove("hidden");
getById("controlButtons").classList.remove("hidden");
} else if (session.chatbutton === false) {
getById("chatbutton").classList.add("hidden");
}
updatePushId()
if (session.dataMode){ // skip the media stuff.
errorlog("this shoulnd't happen..");
session.postPublish();
return;
}
if (!session.streamSrc){
checkBasicStreamsExist(); // create srcObject + videoElement
}
if (!session.avatar && session.mobile && session.streamSrc && !session.streamSrc.getVideoTracks().length){ // this just keeps the phone active.
setInterval(function(){
if (document.getElementById("keepAlivePlayer") && session.streamSrc.getVideoTracks().length){
getById("keepAlivePlayer").remove();
} else if (!document.getElementById("keepAlivePlayer")){
let fakeElement = document.createElement("video");
fakeElement.autoplay = true;
fakeElement.loop = true;
fakeElement.muted = true;
fakeElement.src = "./media/micro.mp4";
fakeElement.style.width = "1px";
fakeElement.style.height ="1px";
fakeElement.controls = false;
fakeElement.id = "keepAlivePlayer";
getById("main").appendChild(fakeElement);
}
}, 4000);
}
session.publishStream(getById("previewWebcam")); // calls session.postPublish at the end.
}
function createYoutubeLink(vidid){
return "https://www.youtube.com/embed/"+vidid+"?modestbranding=1&playsinline=1&enablejsapi=1";
}
function parseURL4Iframe(iframeURL){
if (iframeURL==""){
iframeURL="./";
}
if (iframeURL === session.iframeSrc){return iframeURL;}
if (iframeURL.startsWith("http://")){
try {
iframeURL = "https://"+ iframeURL.split("http://")[1];
} catch(e){errorlog(e);}
}
if (iframeURL.startsWith("https://") || iframeURL.startsWith("http://")){
var domain = (new URL(iframeURL));
domain = domain.hostname;
if (domain == "youtu.be"){
iframeURL = iframeURL.replace("youtu.be/","youtube.com/watch?v=");
}
if ((domain == "youtu.be") || (domain=="www.youtube.com") || (domain=="youtube.com")){
var regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
var match = iframeURL.match(regExp);
var vidid = (match&&match[7].length==11)? match[7] : false;
// https://www.youtube.com/live_chat?v=&embed_domain=
if (iframeURL.includes("/live_chat")){
if (!iframeURL.includes("&embed_domain=")){
iframeURL += "&embed_domain="+location.hostname;
}
}
if (vidid){
//specialResult = {};
//specialResult.originalSrc = iframeURL;
//specialResult.parsedSrc = "https://www.youtube.com/embed/"+vidid+"?autoplay=1&modestbranding=1&playsinline=1&enablejsapi=1";
//specialResult.handler = "youtube";
//specialResult.vid = vidid;
//iframeURL = specialResult;
iframeURL = createYoutubeLink(vidid);
} else { // see if there is a playlist link here or not.
// https://youtube.com/playlist?list=PLWodc2tCfAH1l_LDvEyxEqFf42hOBKqQM
iframeURL = iframeURL.replace("playlist?list=","embed/videoseries?list=");
var regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(videoseries\?))\??list?=?([^#&?]*).*/;
var match = iframeURL.match(regExp);
var plid = (match&&match[7].length==34)? match[7] : false;
if (plid){
iframeURL = 'https://www.youtube.com/embed/videoseries?list='+plid+"&autoplay=1&modestbranding=1&playsinline=1&enablejsapi=1";
}
}
} else if ((domain=="twitch.tv") || (domain=="www.twitch.tv")){
if (iframeURL.includes("twitch.tv/popout/")){
// this is a twitch live chat window
iframeURL = iframeURL.replace("/popout/","/embed/");
iframeURL = iframeURL.replace("?popout=","?parent="+location.hostname);
iframeURL = iframeURL.replace("?popout","?parent="+location.hostname);
iframeURL = iframeURL.replace("&popout=","?parent="+location.hostname);
iframeURL = iframeURL.replace("&popout","?parent="+location.hostname);
if (iframeURL.includes("darkpopout=")){
iframeURL = iframeURL.replace("?darkpopout=","?darkpopout=&parent="+location.hostname);
} else {
iframeURL = iframeURL.replace("?darkpopout","?darkpopout&parent="+location.hostname);
}
} else {
var vidid = iframeURL.split('/').pop().split('#')[0].split('?')[0];
if (vidid){
iframeURL = "https://player.twitch.tv/?channel="+vidid+"&parent="+location.hostname;
}
}
} else if ((domain=="www.vimeo.com") || (domain=="vimeo.com")){
iframeURL = iframeURL.replace("//vimeo.com/","//player.vimeo.com/video/");
iframeURL = iframeURL.replace("//www.vimeo.com/","//player.vimeo.com/video/");
} else if (domain.includes("tiktok.com")){
var split = iframeURL.split("/video/");
if (split.length>1){
split = split[1].split("/")[0].split("?")[0].split("#")[0];
iframeURL = "https://www.tiktok.com/embed/v2/" + split;
}
}
}
return iframeURL;
}
function soloLinkGenerator(streamID, scene=true){
var codecGroupFlag="";
if (session.codecGroupFlag){
codecGroupFlag = session.codecGroupFlag;
}
if (session.bitrateGroupFlag){
codecGroupFlag += session.bitrateGroupFlag;
}
var wss = "";
if (session.wssSetViaUrl){
if (session.customWSS && (session.customWSS!==true)){
wss = "&pie="+session.customWSS;
} else {
wss = "&wss="+session.wss;
}
}
var passAdd2="";
if (session.password){
if (session.defaultPassword===false){
passAdd2="&password="+session.password;
}
}
if (session.token){
passAdd2+="&token="+session.token;
}
if (scene){
return "https://"+location.host+location.pathname+"?view="+streamID+"&solo"+codecGroupFlag+"&room="+session.roomid+passAdd2+wss+soloLinkAppended;
} else {
return "https://"+location.host+location.pathname+"?view="+streamID+codecGroupFlag+passAdd2+wss+soloLinkAppended;
}
}
function YoutubeAPI(iframe, func, args) { // playVideo, pauseVideo, stopVideo
if (!(iframe && iframe.contentWindow)){return;}
try {
iframe.contentWindow.postMessage(JSON.stringify({
"event": "command",
"func": func,
"args": args || [],
"id": iframe.id || "unknown"
}), "*");
} catch(e){}
}
function YoutubeListen(iframe_id){
var iframe = document.getElementById(iframe_id);
if (!iframe){return;}
if (iframe.loadedYoutubeListen){return;}
try {
iframe.contentWindow.postMessage(JSON.stringify({"event":"listening","id":iframe_id}),'*'); //
} catch(e){ }
setTimeout(function(iframe_id){YoutubeListen(iframe_id);},1000,iframe_id);
}
function processYoutubeEvent(e){
if (!(e.type && (e.type === "message"))){return;}
try {
var data = JSON.parse(e.data);
if ("id" in data){
var iframe = document.getElementById(data.id);
if (!iframe){return;}
if (!iframe.loadedYoutubeListen){
iframe.loadedYoutubeListen = true;
}
}
if (!("mediaReferenceTime" in data.info)){
return;
}
} catch(e){return;}
log(e);
if (iframe.id=="iframe_source"){
if (!session.iframeEle.sendOnNewConnect){
session.iframeEle.sendOnNewConnect = {};
session.iframeEle.sendOnNewConnect.ifs = {};
session.iframeEle.sendOnNewConnect.ifs.t = null;
session.iframeEle.sendOnNewConnect.ifs.v = null;
session.iframeEle.sendOnNewConnect.ifs.s = null;
session.iframeEle.sendOnNewConnect.ifs.r = null;
}
try {
var msg = {};
msg.ifs = {}
try{
msg.ifs.t = parseFloat(data.info.mediaReferenceTime+0.01) || 0;
session.iframeEle.sendOnNewConnect.ifs.t = msg.ifs.t;
} catch(e){return;}
if ("playerState" in data.info){
msg.ifs.s = parseInt(data.info.playerState);
if (msg.ifs.s == -1){
msg.ifs.s = 0;
}
if (msg.ifs.s == 2){
if (session.iframeEle.sendOnNewConnect.ifs.s==3){
delete(msg.ifs.s);
} else {
msg.ifs.s = 3;
}
}
if (msg.ifs.s && (session.iframeEle.sendOnNewConnect.ifs.s != msg.ifs.s)){
session.iframeEle.sendOnNewConnect.ifs.s = msg.ifs.s;
} else {
//delete(msg.ifs.s);
}
if ("videoData" in data.info){
if (session.iframeEle.sendOnNewConnect.ifs.v != data.info.videoData.video_id){
session.iframeEle.sendOnNewConnect.ifs.v = data.info.videoData.video_id;
msg.ifs.v = data.info.videoData.video_id;
var vidSrc = createYoutubeLink(msg.ifs.v);
if (vidSrc!==session.iframeSrc){
session.iframeSrc = vidSrc;
var data = {}
data.iframeSrc = session.iframeSrc;
if (parseInt(msg.ifs.t)>1){
data.iframeSrc += "&start="+parseInt(Math.ceil(msg.ifs.t))+"";
}
for (var UUID in session.pcs){
if (session.pcs[UUID].allowIframe===true){
session.sendMessage(data, UUID);
}
}
return;
}
}
}
// we will still be sending the msg data if available.
} else if ("videoData" in data.info){
if (session.iframeEle.sendOnNewConnect.ifs.v != data.info.videoData.video_id){
msg.ifs.v = data.info.videoData.video_id;
session.iframeEle.sendOnNewConnect.ifs.v = msg.ifs.v;
var vidSrc = createYoutubeLink(msg.ifs.v);
if (vidSrc!==session.iframeSrc){
session.iframeSrc = vidSrc;
var data = {}
data.iframeSrc = session.iframeSrc;
if (parseInt(msg.ifs.t)>1){
data.iframeSrc += "&start="+parseInt(Math.ceil(msg.ifs.t))+"";
}
if (session.iframeEle.sendOnNewConnect.ifs.s == "1"){
data.iframeSrc += "&autoplay=1";
} else {
data.iframeSrc += "&autoplay=0";
}
for (var UUID in session.pcs){
if (session.pcs[UUID].allowIframe===true){
session.sendMessage(data, UUID);
}
}
return;
}
}
} else {
if ("playbackRate" in data.info){
msg.ifs.r = parseFloat(data.info.playbackRate);
if (session.iframeEle.sendOnNewConnect.ifs.r != msg.ifs.r){
session.iframeEle.sendOnNewConnect.ifs.r = msg.ifs.r;
} else {
delete(msg.ifs.r);
}
}
if (session.iframeEle.sendOnNewConnect.ifs.s == 1){
if ("t" in msg.ifs){
delete(msg.ifs.t);
}
}
}
if (Object.keys(msg.ifs).length == 0){return;}
for (var UUID in session.pcs){
if (session.pcs[UUID].allowIframe){
session.sendMessage(msg);
}
}
} catch(e){return;}
} else {
try{
var UUID = iframe.dataset.UUID;
var msg = {};
msg.ifs = {}
if ("t" in msg.ifs){
msg.ifs.t = parseFloat(data.info.mediaReferenceTime+0.01) || 0;
/* if (!iframe.sendOnNewConnect){
iframe.sendOnNewConnect = msg;
} else {
iframe.sendOnNewConnect.ifs.t = msg.ifs.t;
} */
}
if ("playerState" in data.info){
msg.ifs.s = parseInt(data.info.playerState);
}
if ("videoData" in data.info){
msg.ifs.v = data.info.videoData.video_id;
}
if (("playbackRate" in data.info) && (data.info.playbackRate!==1)){
msg.ifs.r = parseFloat(data.info.playbackRate);
}
// TODO: the viewers don't have a way to tell the director if they reload what the time is at.
session.sendRequest(msg, UUID); // send to the iframe's owner only. let them be the controller for others.
} catch(e){return;}
}
}
function processIframeSyncFeedback(ifs, UUID){ // remote iframe feedback from the remote viewers
// YoutubeAPI("iframe_source", "seekTo", [700]);
// YoutubeAPI("iframe_source", "volume", [100]);
warnlog(ifs);
return;
if (!session.iframeEle.sendOnNewConnect){
session.iframeEle.sendOnNewConnect = {};
session.iframeEle.sendOnNewConnect.ifs = {};
session.iframeEle.sendOnNewConnect.ifs.t = null;
session.iframeEle.sendOnNewConnect.ifs.v = null;
session.iframeEle.sendOnNewConnect.ifs.s = null;
session.iframeEle.sendOnNewConnect.ifs.r = null;
}
if ("t" in ifs){
if (Math.abs(session.iframeEle.sendOnNewConnect.ifs.t-ifs.t)>=1){
//session.iframeEle.sendOnNewConnect.ifs.t = ifs.t;
} else {
delete(ifs.t);
}
}
if ("v" in ifs){
if (session.iframeEle.sendOnNewConnect.ifs.v != ifs.v){
//session.iframeEle.sendOnNewConnect.ifs.v = ifs.v;
} else {
delete(ifs.v);
}
}
if ("s" in ifs){
if (ifs.s == -1){
ifs.s = 0;
}
if (session.iframeEle.sendOnNewConnect.ifs.s == -1){
session.iframeEle.sendOnNewConnect.ifs.s = 0;
}
if (ifs.s == 2){
ifs.s = 3;
}
if (session.iframeEle.sendOnNewConnect.ifs.s == 2){
session.iframeEle.sendOnNewConnect.ifs.s = 3;
}
if (session.iframeEle.sendOnNewConnect.ifs.s != ifs.s){
//session.iframeEle.sendOnNewConnect.ifs.s = ifs.s;
} else {
delete(ifs.s);
}
}
if ("r" in ifs){
if (session.iframeEle.sendOnNewConnect.ifs.r != ifs.r){
//session.iframeEle.sendOnNewConnect.ifs.r = ifs.r;
} else {
delete(ifs.r);
}
}
if (session.iframeEle){
if (ifs.v){ // I need to have this change videos .
var vidSrc = createYoutubeLink(ifs.v);
if (vidSrc!==session.iframeSrc){
session.iframeSrc = vidSrc;
session.iframeEle.src = vidSrc;
}
} else if ("t" in ifs){
YoutubeAPI(session.iframeEle, "seekTo", [parseFloat(ifs.t)]);
} else if (ifs.r){ /// setPlaybackRate
YoutubeAPI(session.iframeEle, "setPlaybackRate", [parseFloat(ifs.r)]);
} else if ("s" in ifs){ /// setPlaybackState
if (ifs.s == -1) { YoutubeAPI(session.iframeEle, "stopVideo"); }
else if (ifs.s == 0) { YoutubeAPI(session.iframeEle, "stopVideo"); } // player stops.
else if (ifs.s == 1) { YoutubeAPI(session.iframeEle, "playVideo"); } //Video is playing
else if (ifs.s == 2) { YoutubeAPI(session.iframeEle, "pauseVideo"); } //Video is paused
else if (ifs.s == 3) { YoutubeAPI(session.iframeEle, "pauseVideo"); } //video is buffering
else if (ifs.s == 5) { } //Video is cued.
}
} else if (session.iframeSrc){
if (ifs.v){
var vidSrc = createYoutubeLink(ifs.v);
if (vidSrc!==session.iframeSrc){
session.iframeSrc = vidSrc;
var data = {}
data.iframeSrc = session.iframeSrc;
if (ifs.t && (parseInt(ifs.t)>1)){
data.iframeSrc += "&start="+parseInt(Math.ceil(ifs.t));
}
if (ifs.s == "1"){
data.iframeSrc += "&autoplay=1";
} else {
data.iframeSrc += "&autoplay=0";
}
for (var uuid in session.pcs){
if (uuid == UUID){continue;}
if (session.pcs[uuid].allowIframe===true){
session.sendMessage(data, uuid);
}
}
return;
}
}
// we're going to forward the message directly to the other viewers instead
if ("s" in ifs){ /// setPlaybackState
var msg = {};
msg.ifs = ifs;
for (var uuid in session.pcs){
if (uuid == UUID){continue;}
if (session.pcs[uuid].allowIframe){
session.sendMessage(msg, uuid);
}
}
}
}
}
function processIframeSyncUpdates(ifs, UUID){ // playback updates from remote guest.
// YoutubeAPI("iframe_source", "seekTo", [700]);
// YoutubeAPI("iframe_source", "volume", [100]);
if (ifs.v && ("s" in ifs)){
//
} else if ("s" in ifs){
if ("t" in ifs){
YoutubeAPI(session.rpcs[UUID].iframeEle, "seekTo", [parseFloat(ifs.t)]);
}
YoutubeAPI(session.rpcs[UUID].iframeEle, "playVideo");
} else if ("t" in ifs){
YoutubeAPI(session.rpcs[UUID].iframeEle, "seekTo", [parseFloat(ifs.t)]);
}
if (ifs.r){ /// setPlaybackRate
YoutubeAPI(session.rpcs[UUID].iframeEle, "setPlaybackRate", [parseFloat(ifs.r)]);
}
if ("s" in ifs){ /// setPlaybackState
if (ifs.s == -1) { YoutubeAPI(session.rpcs[UUID].iframeEle, "stopVideo"); }
else if (ifs.s == 0) { YoutubeAPI(session.rpcs[UUID].iframeEle, "stopVideo"); } // player stops.
else if (ifs.s == 1) { YoutubeAPI(session.rpcs[UUID].iframeEle, "playVideo"); } //Video is playing
else if (ifs.s == 2) { YoutubeAPI(session.rpcs[UUID].iframeEle, "pauseVideo"); } //Video is paused
else if (ifs.s == 3) { YoutubeAPI(session.rpcs[UUID].iframeEle, "pauseVideo"); } //video is buffering
else if (ifs.s == 5) { } //Video is cued.
}
}
function updatePushId(){
if (session.doNotSeed){return;}
if (urlParams.has('push')){
updateURL("push="+session.streamID);
} else if (urlParams.has('id')){
updateURL("id="+session.streamID);
} else if (urlParams.has('permaid')){
updateURL("permaid="+session.streamID);
} else {
updateURL("push="+session.streamID);
}
}
session.publishIFrame = function(iframeURL){
if (!session.cleanOutput){
getById("websitesharebutton2").classList.remove('hidden');
}
if (session.transcript){
setTimeout(function(){setupClosedCaptions();},1000);
}
session.iframeSrc = parseURL4Iframe(iframeURL);
var iframe = document.createElement("iframe");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;midi;";
iframe.src = session.iframeSrc;
iframe.id = "iframe_source";
iframe.loadedYoutubeListen = false;
session.iframeEle = iframe;
var container = document.createElement("div");
iframe.container = container;
container.id = "container_iframe";
container.appendChild(iframe);
getById("gridlayout").appendChild(container);
if (session.iframeSrc.startsWith("https://www.youtube.com/")){ // special handler.
setTimeout(function(iframe_id){YoutubeListen(iframe_id);}, 1000, iframe.id);
}
if (session.cover){
container.style.setProperty('height', '100%', 'important');
}
if (session.roomid!==false){
if ((session.roomid==="") && ((!(session.view)) || (session.view===""))){
} else {
log("ROOMID EANBLED");
getById("head3").classList.add('hidden');
getById("head3a").classList.add('hidden');
joinRoom(session.roomid);
}
} else {
getById("head3").classList.remove('hidden');
getById("head3a").classList.remove('hidden');
getById("logoname").style.display = 'none';
}
getById("head1").className = 'hidden';
updatePushId()
getById("head1").className = 'hidden';
getById("head2").className = 'hidden';
if (!(session.cleanOutput)){
getById("chatbutton").className="float";
getById("hangupbutton").className="float";
getById("controlButtons").classList.remove("hidden");
getById('sharefilebutton').classList.remove("hidden"); // we won't override "display:none", if set, though.
getById("helpbutton").style.display = "inherit";
getById("reportbutton").style.display = "";
} else {
getById("controlButtons").classList.add("hidden");
}
if (session.chatbutton === false) {
getById("chatbutton").classList.add("hidden");
}
if (session.director){
//
} else if (session.scene!==false){
updateMixer();
} else if (session.roomid!==false){
if (session.roomid===""){
if (!(session.view) || (session.view==="")){
session.windowed = true;
container.classList.add("vidcon");
getById("mutespeakerbutton").classList.add("hidden");
container.style.width="100%";
container.style.height="100%";
container.style.alignItems = "center";
container.style.maxWidth= "100%";
container.style.maxHeight= "100%";
container.style.verticalAlign= "middle";
container.style.margin= "auto";
container.style.backgroundColor = "#666";
container.style.border = "2px solid";
} else {
session.windowed = false;
window.onresize = updateMixer;
updateMixer();
}
} else {
window.onresize = updateMixer;
session.windowed = false;
updateMixer();
}
} else {
window.onresize = updateMixer;
container.style.maxHeight= "1280px";
container.style.maxWidth= "720px";
container.style.verticalAlign= "middle";
container.style.height="100%";
container.style.width= "100%";
container.style.margin= "auto";
container.style.alignItems = "center";
container.style.backgroundColor = "#666";
}
session.seeding=true;
updateReshareLink();
pokeIframeAPI('started-iframe-share');
session.seedStream();
return container;
} // publishIframe
session.publishWhepSrc = function(){
if (!session.whepSrc){errorlog("no WHEP Src");return;}
if (!session.cleanOutput){
getById("websitesharebutton2").classList.remove('hidden');
}
var UUID = whepIn(session.whepSrc);
var container = document.createElement("div");
iframe.container = container;
container.id = "container_iframe";
container.appendChild(iframe);
getById("gridlayout").appendChild(container);
if (session.iframeSrc.startsWith("https://www.youtube.com/")){ // special handler.
setTimeout(function(iframe_id){YoutubeListen(iframe_id);}, 1000, iframe.id);
}
if (session.cover){
container.style.setProperty('height', '100%', 'important');
}
if (session.roomid!==false){
if ((session.roomid==="") && ((!(session.view)) || (session.view===""))){
} else {
log("ROOMID EANBLED");
getById("head3").classList.add('hidden');
getById("head3a").classList.add('hidden');
joinRoom(session.roomid);
}
} else {
getById("head3").classList.remove('hidden');
getById("head3a").classList.remove('hidden');
getById("logoname").style.display = 'none';
}
getById("head1").className = 'hidden';
updatePushId()
getById("head1").className = 'hidden';
getById("head2").className = 'hidden';
if (!(session.cleanOutput)){
getById("chatbutton").className="float";
getById("hangupbutton").className="float";
getById("controlButtons").classList.remove("hidden");
getById('sharefilebutton').classList.remove("hidden"); // we won't override "display:none", if set, though.
getById("helpbutton").style.display = "inherit";
getById("reportbutton").style.display = "";
} else {
getById("controlButtons").classList.add("hidden");
}
if (session.chatbutton === false) {
getById("chatbutton").classList.add("hidden");
}
if (session.director){
//
} else if (session.scene!==false){
updateMixer();
} else if (session.roomid!==false){
if (session.roomid===""){
if (!(session.view) || (session.view==="")){
session.windowed = true;
container.classList.add("vidcon");
getById("mutespeakerbutton").classList.add("hidden");
container.style.width="100%";
container.style.height="100%";
container.style.alignItems = "center";
container.style.maxWidth= "100%";
container.style.maxHeight= "100%";
container.style.verticalAlign= "middle";
container.style.margin= "auto";
container.style.backgroundColor = "#666";
container.style.border = "2px solid";
} else {
session.windowed = false;
window.onresize = updateMixer;
updateMixer();
}
} else {
window.onresize = updateMixer;
session.windowed = false;
updateMixer();
}
} else {
window.onresize = updateMixer;
container.style.maxHeight= "1280px";
container.style.maxWidth= "720px";
container.style.verticalAlign= "middle";
container.style.height="100%";
container.style.width= "100%";
container.style.margin= "auto";
container.style.alignItems = "center";
container.style.backgroundColor = "#666";
}
session.seeding=true;
updateReshareLink();
pokeIframeAPI('started-iframe-share');
session.seedStream();
return container;
} // publishWhepSrc
function outboundAudioPipeline(){ // this function isn't letting me change the audio source
if (session.disableWebAudio) {
// if (iOS || iPad){return session.streamSrc;} // iOS devices can't remap video tracks, else KABOOM. Might as well do this for android also.
if (session.streamSrcClone){
session.streamSrcClone.getTracks().forEach(function(track) {
session.streamSrcClone.removeTrack(track);
});
}
if (session.streamSrc && session.streamSrc.clone){
var streamSrc = session.streamSrc.clone();
session.streamSrcClone = streamSrc;
return streamSrc;
} else {
var newStream = createMediaStream();
session.streamSrcClone = newStream;
if (session.streamSrc){
session.streamSrc.getAudioTracks().forEach(function(track) { // this seems to fix a bug with macbooks.
newStream.addTrack(track, session.streamSrc);
});
}
if (session.videoElement && session.videoElement.srcObject){
session.videoElement.srcObject.getVideoTracks().forEach(function(track) { // this seems to fix a bug with macbooks.
newStream.addTrack(track, session.videoElement.srcObject);
});
} else if (session.streamSrc){
session.streamSrc.getVideoTracks().forEach(function(track) { // this seems to fix a bug with macbooks.
newStream.addTrack(track, session.streamSrc);
});
}
}
return newStream;
}
if (!session.streamSrc){
errorlog("STREAM DOES NOT EXIST. This is a problem");
checkBasicStreamsExist();
return session.streamSrc;
}
var streamSrc = session.streamSrc;
if (iOS || iPad){
if (session.streamSrcClone){
var tracks = session.streamSrcClone.getAudioTracks();
if (tracks.length) {
for (var waid in session.webAudios) { // TODO: EXCLUDE CURRENT TRACK IF ALREADY EXISTS ... if (track.id === wa.id){..
session.webAudios[waid].stop();
delete session.webAudios[waid];
}
}
session.streamSrcClone.getTracks().forEach(function(track) {
session.streamSrcClone.removeTrack(track);
});
}
if (session.streamSrc && session.streamSrc.clone){ // modern
streamSrc = session.streamSrc.clone();
session.streamSrcClone = streamSrc;
} else { // backup.
streamSrc = createMediaStream();
if (session.streamSrc){
session.streamSrc.getAudioTracks().forEach(function(track) { // this seems to fix a bug with macbooks.
streamSrc.addTrack(track, session.streamSrc);
});
}
if (session.videoElement && session.videoElement.srcObject){
session.videoElement.srcObject.getVideoTracks().forEach(function(track) { // this seems to fix a bug with macbooks.
streamSrc.addTrack(track, session.videoElement.srcObject);
});
} else if (session.streamSrc){
session.streamSrc.getVideoTracks().forEach(function(track) { // this seems to fix a bug with macbooks.
streamSrc.addTrack(track, session.streamSrc);
});
}
session.streamSrcClone = streamSrc;
}
}
for (var waid in session.webAudios) { // TODO: EXCLUDE CURRENT TRACK IF ALREADY EXISTS ... if (track.id === wa.id){..
session.webAudios[waid].stop();
delete session.webAudios[waid];
}
try {
log("Web Audio");
var tracks = streamSrc.getAudioTracks();
if (tracks.length) {
var webAudio = {};
webAudio.micDelay = false;
webAudio.compressor = false;
webAudio.analyser = false;
webAudio.gainNode = false;
webAudio.splitter = false;
webAudio.subGainNodes = false;
webAudio.lowEQ = false;
webAudio.midEQ = false;
webAudio.highEQ = false;
webAudio.lowcut1 = false;
webAudio.lowcut2 = false;
webAudio.lowcut3 = false;
webAudio.id = tracks[0].id; // first track is used.
if (session.audioCtxOutbound){
// already Created
} else if (session.audioLatency !== false) { // session.audioLatency could be useful for fixing clicking issues?
session.audioCtxOutbound = new AudioContext({
latencyHint: session.audioLatency / 1000.0 //, // needs to be in seconds, but VDON user input is via milliseconds
// sampleRate: 48000 // not sure this is a great idea, but might as well add this here, versus later on since it is needed anyways.
});
} else {
session.audioCtxOutbound = new AudioContext();
}
var audioContext = session.audioCtxOutbound;
webAudio.audioContext = session.audioCtxOutbound;
webAudio.destination = audioContext.createMediaStreamDestination();
if (tracks.length>1){ // tries to
try {
webAudio.mediaStreamSource = createMediaStream();
var maxChannelCount = 2;
if (session.stereo===false){
maxChannelCount = 1;
}
webAudio.subGainNodes = {};//
var merger = audioContext.createChannelMerger(maxChannelCount);
for (var i=0;i1){
ng2 = parseInt(session.noisegateSettings[1]) || ng2; // not loud (threshold level)
}
if (session.noisegateSettings.length>2){
ng3 = parseInt(session.noisegateSettings[2]) || 0; // stickiness; time (ms)
ng3 = ng3 / 100.0; // convert to the actual units (100ms)
}
}
if (session.noisegate){
changeGatingGain(ng1,200);
}
function draw() {
try {
analyser.getByteFrequencyData(dataArray);
var total = 0;
for (var i = 0; i < dataArray.length; i++) {
total += dataArray[i];
}
total = total / 100;
if (session.quietOthers && (session.quietOthers==2)){
if (total>10){
if (session.muted_activeSpeaker==false){
session.muted_activeSpeaker=true;
session.speakerMuted=true;
clearTimeout(timer);
toggleSpeakerMute(true); // okay, sicne this is quietOthers
}
} else if (session.muted_activeSpeaker==true){
session.speakerMuted=false;
session.muted_activeSpeaker=false;
session.activelySpeaking=false;
clearTimeout(timer);
timer = setTimeout(function(){toggleSpeakerMute(true);},250); // okay, sicne this is quietOthers
}
}
if (session.pushLoudness==true){
var loudnessObj = {};
loudnessObj[session.streamID] = parseInt(total);
if (isIFrame){
parent.postMessage({"loudness": loudnessObj, "action":"loudness", "value":total}, session.iframetarget);
}
}
if (session.noisegate){
if (total<=ng2){
if (currentlyActive==ng3){
changeGatingGain(ng1,200); // set volume to 40% relative to what it is now.
log("GAIN LOWERED");
currentlyActive=(ng3+1);
} else if (currentlyActive 150) {
if (total > 200) {
total = 200;
}
meter1.style.width = "150px";
meter2.style.width = (total - 150) + "px";
}
return;
} else if (toggleSettingsState && document.getElementById("meter3")){
if (total == 0) {
meter3.style.width = "1px";
meter4.style.width = "0px";
} else if (total <= 1) {
meter3.style.width = "1px";
meter4.style.width = "0px";
} else if (total <= 150) {
meter3.style.width = total + "px";
meter4.style.width = "0px";
} else if (total > 150) {
if (total > 200) {
meter3.style.width = "150px";
meter4.style.width = "50px";
} else {
meter3.style.width = "150px";
meter4.style.width = (total - 150) + "px";
}
}
if (document.getElementById("mutetoggle")) {
total *= 3;
if (total > 255) {
total = 255;
}
total = parseInt(total);
document.getElementById("mutetoggle").style.color = "rgb(" + (255 - total) + ",255," + (255 - total) + ")";
}
meter1 = false;
return;
} else if (session.cleanOutput){
meter1 = false;
return;
} else if (document.getElementById("mutetoggle")) {
total *= 3;
if (total > 255) {
total = 255;
}
total = parseInt(total);
document.getElementById("mutetoggle").style.color = "rgb(" + (255 - total) + ",255," + (255 - total) + ")";
} else {
clearInterval(analyser.interval);
warnlog("METERS NOT FOUND");
}
meter1 = false;
} catch(e){
errorlog(e);
}
};
analyser.interval = setInterval(function() {
draw();
}, 100);
return analyser;
}
function audioCompressor(mediaStreamSource, audioContext) {
var compressor = audioContext.createDynamicsCompressor();
compressor.threshold.value = -40;
compressor.knee.value = 10;
compressor.ratio.value = 4; // 3
compressor.attack.value = 0.002; // 0.001
compressor.release.value = 0.1; // 0.06
mediaStreamSource.connect(compressor);
return compressor;
}
function audioLimiter(mediaStreamSource, audioContext) {
var compressor = audioContext.createDynamicsCompressor();
compressor.threshold.value = -5;
compressor.knee.value = 0;
compressor.ratio.value = 20.0; // 1 to 20
compressor.attack.value = 0.001;
compressor.release.value = 0.1;
mediaStreamSource.connect(compressor);
return compressor;
}
function activeSpeaker(border=false) {
var lastActiveSpeaker = null;
var someoneElseIfSpeaking = false;
var anyoneIsSpeaking = false;
var defaultSpeaker = false;
for (var UUID in session.rpcs) {
if ((session.activeSpeaker>2) && !(session.rpcs[UUID].videoElement && session.rpcs[UUID].videoElement.srcObject && session.rpcs[UUID].videoElement.srcObject.getVideoTracks().length && !session.rpcs[UUID].videoMuted)){
session.rpcs[UUID].activelySpeaking = false; // we're not showing audio-only sources in this mode.
session.rpcs[UUID].defaultSpeaker = false;
continue;
}
if (session.rpcs[UUID].stats._Audio_Loudness_average) {
if (session.rpcs[UUID].stats.Audio_Loudness && (session.rpcs[UUID].stats.Audio_Loudness>10)){
session.rpcs[UUID].stats._Audio_Loudness_average = parseFloat(session.rpcs[UUID].stats.Audio_Loudness*0.07 + session.rpcs[UUID].stats._Audio_Loudness_average*0.93);
} else {
session.rpcs[UUID].stats._Audio_Loudness_average = parseFloat(session.rpcs[UUID].stats._Audio_Loudness_average*0.975);
}
} else {
session.rpcs[UUID].stats._Audio_Loudness_average = 1;
}
if (session.rpcs[UUID].stats._Audio_Loudness_average > 13) {
if (border) {
if (session.rpcs[UUID].videoElement) {
session.rpcs[UUID].videoElement.style.border = "green solid 1px";
session.rpcs[UUID].videoElement.style.padding = "0";
}
} else if (!session.rpcs[UUID].activelySpeaking){
session.rpcs[UUID].activelySpeaking = true;
lastActiveSpeaker = UUID;
session.rpcs[UUID].stats._Audio_Loudness_average+=50;
}
} else if (session.rpcs[UUID].stats._Audio_Loudness_average > 6) {
//
} else {
if (border){
if (session.rpcs[UUID].videoElement) {
session.rpcs[UUID].videoElement.style.border = "";
session.rpcs[UUID].videoElement.style.padding = "1px";
}
} else if (session.rpcs[UUID].activelySpeaking) {
session.rpcs[UUID].activelySpeaking=false;
lastActiveSpeaker = UUID;
}
}
if ((session.rpcs[UUID].stats.Audio_Loudness > 13) || ((session.rpcs[UUID].stats.Audio_Loudness > 5) && (session.rpcs[UUID].stats._Audio_Loudness_average>3)) || (session.rpcs[UUID].stats._Audio_Loudness_average>6)){
someoneElseIfSpeaking = true;
}
if (session.rpcs[UUID].activelySpeaking){
anyoneIsSpeaking=true;
}
if (session.rpcs[UUID].defaultSpeaker){
defaultSpeaker=true;
}
}
var loudest=null;
var loudestActive=null;
var changed = false;
if ((session.activeSpeaker===1) || (session.activeSpeaker===3)){ // will only show one speaker at a time; the loudest or last-loud speaker
if (!anyoneIsSpeaking){
if (defaultSpeaker){
// already good to go.
} else if (lastActiveSpeaker){
session.rpcs[lastActiveSpeaker].defaultSpeaker=true;
changed=true;
} else if (session.scene===false || (session.nopreview===false && session.minipreview!==1)){
// we don't need to care.
} else {
for (var UUID in session.rpcs) {
if (session.rpcs[UUID].videoElement && session.rpcs[UUID].videoElement.srcObject && session.rpcs[UUID].videoElement.srcObject.getVideoTracks().length && !session.rpcs[UUID].videoMuted){
session.rpcs[UUID].defaultSpeaker=true;
changed=true;
break
}
}
if (!changed && (session.activeSpeaker<=2)){ // switch to streams that have no video track
for (var UUID in session.rpcs) {
if (session.rpcs[UUID].label){
session.rpcs[UUID].defaultSpeaker=true;
changed=true;
break
} else if (!changed){
session.rpcs[UUID].defaultSpeaker=true;
changed=true;
}
}
}
}
} else {
for (var UUID in session.rpcs) {
if (!("_Audio_Loudness_average" in session.rpcs[UUID].stats)){ // never could have been loudest, since no loudness value.
continue;
}
/* if (!loudest){
loudest = UUID;
} else if (session.rpcs[UUID].stats._Audio_Loudness_average > session.rpcs[loudest].stats._Audio_Loudness_average){
loudest = UUID;
} */
if (session.rpcs[UUID].activelySpeaking){
if (!loudestActive){
loudestActive = UUID;
} else if (session.rpcs[UUID].stats._Audio_Loudness_average > session.rpcs[loudestActive].stats._Audio_Loudness_average){
if (session.rpcs[loudestActive].defaultSpeaker){
session.rpcs[loudestActive].defaultSpeaker=false;
changed=true
}
loudestActive = UUID;
} else if (session.rpcs[UUID].defaultSpeaker){
session.rpcs[UUID].defaultSpeaker=false;
changed=true;
}
} else if (session.rpcs[UUID].defaultSpeaker){
session.rpcs[UUID].defaultSpeaker=false;
changed=true
}
}
if (loudestActive && !session.rpcs[loudestActive].defaultSpeaker){
session.rpcs[loudestActive].defaultSpeaker = true;
changed = true;
}
}
} else if ((session.activeSpeaker===2) || (session.activeSpeaker===4)){ // will show whoever is talking; mixed together; if no one is talking, just shows yourself
if (!anyoneIsSpeaking){
if (defaultSpeaker){
// already good to go.
} else if (lastActiveSpeaker){
session.rpcs[lastActiveSpeaker].defaultSpeaker=true;
changed=true;
} else if (session.scene===false || (session.nopreview===false && session.minipreview!==1)){
// we don't need to care.
} else {
for (var UUID in session.rpcs) {
if (session.rpcs[UUID].videoElement && session.rpcs[UUID].videoElement.srcObject && session.rpcs[UUID].videoElement.srcObject.getVideoTracks().length && !session.rpcs[UUID].videoMuted){
session.rpcs[UUID].defaultSpeaker=true;
changed=true;
break
}
}
if (!changed && (session.activeSpeaker<=2)){
for (var UUID in session.rpcs) {
if (session.rpcs[UUID].label){
session.rpcs[UUID].defaultSpeaker=true;
changed=true;
break
} else if (!changed){
session.rpcs[UUID].defaultSpeaker=true;
changed=true;
}
}
}
}
} else {
for (var UUID in session.rpcs) {
if (session.rpcs[UUID].activelySpeaking && !session.rpcs[UUID].defaultSpeaker){
session.rpcs[UUID].defaultSpeaker = true;
changed = true;
} else if (!session.rpcs[UUID].activelySpeaking && session.rpcs[UUID].defaultSpeaker){
session.rpcs[UUID].defaultSpeaker = false;
changed=true
}
}
}
}
if (session.quietOthers && (session.quietOthers===1)){
if (someoneElseIfSpeaking){
if (session.muted_activeSpeaker==false){
session.muted_activeSpeaker=true;
session.muted=true;
toggleMute(true);
}
} else if (session.muted_activeSpeaker==true){
session.muted=false;
session.muted_activeSpeaker=false;
toggleMute(true);
}
} else if (session.quietOthers && (session.quietOthers===3)){ // purely for fun. It's the opposite of a noise-gate I guess.
if (someoneElseIfSpeaking){
if (session.muted_activeSpeaker==false){
session.muted_activeSpeaker=true;
session.speakerMuted=true;
toggleSpeakerMute(true); // okay, sicne this is quietOthers
}
} else if (session.muted_activeSpeaker==true){
session.speakerMuted=false;
session.muted_activeSpeaker=false;
toggleSpeakerMute(true); // okay, sicne this is quietOthers
}
}
if (changed) {
setTimeout(function(){updateMixer();},0);
}
}
function randomizeArray(unshuffled) {
var arr = unshuffled.map((a) => ({
sort: Math.random(), value: a
})).sort((a, b) => a.sort - b.sort).map((a) => a.value); // shuffle once
for (var i = arr.length - 1; i > 0; i--) { // shuffle twice
var j = Math.floor(Math.random() * (i + 1));
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
return arr
}
function joinRoom(roomname) {
if (roomname.length) {
roomname = sanitizeRoomName(roomname);
log("Join room: " + roomname);
updateVolume(false); // chance of a race condition, but unlikely and not a big deal if so.
session.joinRoom(roomname).then(function(response) { // callback from server; we've joined the room. Just the listing is returned
if (session.joiningRoom === "seedPlz") { // allow us to seed, now that we have joined the room.
session.joiningRoom = false; // joined
session.seedStream();
} else {
session.joiningRoom = false; // no seeding callback
}
var token = "";
if (session.token){
token+="&token="+session.token;
}
if (!session.cleanOutput){
if (session.roomhost){
if (session.defaultPassword===false){
if (session.password === false){
var invite = "https://"+location.host+location.pathname+"?room="+session.roomid+"&password=false"+token;
warnUser("You can invite others with:\n\n"+invite+"", false, false);
} else {
generateHash(session.password + session.salt, 4).then(function(hash) {
var invite = "https://"+location.host+location.pathname+"?room="+session.roomid+"&hash="+hash+token;
warnUser("You can invite others with:\n\n"+invite+"", false, false);
});
}
} else {
var invite = "https://"+location.host+location.pathname+"?room="+session.roomid+token;
warnUser("You can invite others with:\n\n"+invite+"", false, false);
}
}
}
log("Members in Room");
log(response);
if (session.randomize === true) {
response = randomizeArray(response);
log("Randomized List of Viewers");
log(response);
for (var i in response) {
if ("UUID" in response[i]) {
if ("streamID" in response[i]) {
if (response[i].UUID in session.rpcs) {
log("RTC already connected"); /// lets just say instead of Stream, we have
} else {
log(response[i].streamID);
var streamID = session.desaltStreamID(response[i].streamID);
if (session.queue){
if (session.directorList.indexOf(response[i].UUID)>=0){
warnlog("PLAYING DIRECTOR");
play(streamID, response[i].UUID);
} else if (session.view_set && session.view_set.includes(streamID)){
play(streamID, response[i].UUID);
} else if (session.queueList.length<5000){
if ((!(streamID in session.watchTimeoutList)) && (!session.queueList.includes(streamID))){
session.queueList.push(streamID);
}
}
} else {
log("STREAM ID DESALTED 3: " + streamID);
setTimeout(function(sid) {
play(sid);
}, (Math.floor(Math.random() * 100)), streamID); // add some furtherchance with up to 100ms added latency
}
}
}
}
}
} else {
for (var i in response) {
if ("UUID" in response[i]) {
if ("streamID" in response[i]) {
if (response[i].UUID in session.rpcs) {
log("RTC already connected"); /// lets just say instead of Stream, we have
} else {
log(response[i].streamID);
var streamID = session.desaltStreamID(response[i].streamID);
if (session.queue){
if (session.directorList.indexOf(response[i].UUID)>=0){
play(streamID, response[i].UUID);
} else if (session.view_set && session.view_set.includes(streamID)){
play(streamID, response[i].UUID);
} else if (session.queueList.length<5000){
if ((!(streamID in session.watchTimeoutList)) && (!session.queueList.includes(streamID))){
session.queueList.push(streamID);
}
}
} else {
log("STREAM ID DESALTED 3: " + streamID);
play(streamID, response[i].UUID); // play handles the group room mechanics here
}
}
}
}
}
}
updateQueue();
pokeIframeAPI("joined-room-complete");
if (session.include.length){ // we want to request what hasn't been requested already, since we are joining a room.
session.include.forEach(sid =>{
if (sid in session.waitingWatchList){
return;
} else {
session.watchStream(sid);
}
});
}
}, function(error) {
return {};
});
} else {
log("Room name not long enough or contained all bad characaters");
}
}
async function createRoom(roomname = false) {
if (roomname == false) {
roomname = getById("videoname1").value;
roomname = sanitizeRoomName(roomname);
clearDirectorSettings();
if (roomname.length != 0) {
if (urlParams.has('dir')){
updateURL("dir=" + roomname, true, false); // make the link reloadable.
} else {
updateURL("director=" + roomname, true, false); // make the link reloadable.
}
}
}
if (roomname.length == 0) {
//if (!(session.cleanOutput)) {
// warnUser("Please enter a room name before continuing");
//}
getById("videoname1").focus();
getById("videoname1").classList.remove("shake");
setTimeout(function(){getById("videoname1").classList.add("shake");},10);
return;
}
log(roomname);
session.roomid = roomname;
getById("dirroomid").innerHTML = decodeURIComponent(session.roomid);
getById("roomid").innerHTML = session.roomid;
var passwordRoom = getById("passwordRoom").value;
passwordRoom = sanitizePassword(passwordRoom);
if (passwordRoom.length) {
session.password = passwordRoom;
session.defaultPassword = false;
if (urlParams.has('pass')) {
updateURL("pass=" + session.password);
} else if (urlParams.has('pw')) {
updateURL("pw=" + session.password);
} else if (urlParams.has('p')) {
updateURL("p=" + session.password);
} else {
updateURL("password=" + session.password);
}
}
await registerToken();
var passAdd = "";
var passAdd2 = "";
if ((session.defaultPassword === false) && (session.password)) {
passAdd2 = "&password=" + session.password;
return generateHash(session.password + session.salt, 4).then(async function(hash) {
passAdd = "&hash=" + hash;
await createRoomCallback(passAdd, passAdd2);
}).catch(errorlog);
} else {
await createRoomCallback(passAdd, passAdd2);
}
if (session.meshcast){
if (!session.cleanOutput && !session.cleanDirector){
document.getElementById("meshcastMenu").classList.remove("hidden");
}
}
pokeIframeAPI("create-room", roomname);
}
function copyVideoFrameToClipboard(videoElement, e=false) {
try{
var canvas = document.createElement("canvas");
canvas.width = videoElement.videoWidth;
canvas.height = videoElement.videoHeight;
var ctx = canvas.getContext("2d");
ctx.drawImage(videoElement, 0, 0);
var img = new Image();
img.src = canvas.toDataURL();
canvas.toBlob(function(blob) {
navigator.clipboard.write([new ClipboardItem({'image/png': blob})]);
}, 'image/png');
popupMessage(e, "Frame copied to clipboard as as PNG Image");
} catch(e){
errorlog(e);
}
}
function saveVideoFrameToClipboard(videoElement, e=false) {
try{
var canvas = document.createElement("canvas");
canvas.width = videoElement.videoWidth;
canvas.height = videoElement.videoHeight;
var ctx = canvas.getContext("2d");
ctx.drawImage(videoElement, 0, 0);
var img = new Image();
img.src = canvas.toDataURL();
canvas.toBlob(function(blob) {
var link = document.createElement("a");
link.download = (videoElement.id||"video")+"_"+parseInt(performance.now())+".png";
link.href = URL.createObjectURL(blob);
link.click();
URL.revokeObjectURL(link.href);
}, 'image/png');
popupMessage(e, "Saving current frame to disk");
} catch(e){
errorlog(e);
}
}
async function checkDirectorStreamID(){
if (session.directorStreamID){
for (var UUID in session.rpcs){
if (session.rpcs[UUID].streamID){
var hashedSID = await generateHash(session.rpcs[UUID].streamID);
if (hashedSID===session.directorStreamID){
session.directorUUID = UUID; // main director
session.directorList = [];
session.directorList.push(UUID); // approved co/directors
session.directorUUID = UUID;
session.newMainDirectorSetup();
return;
}
}
}
for (var UUID in session.pcs){
if (session.pcs[UUID].streamID){
var hashedSID = await generateHash(session.pcs[UUID].streamID);
if (hashedSID===session.directorStreamID){
session.directorList = [];
session.directorList.push(UUID);
session.directorUUID = UUID;
session.newMainDirectorSetup();
return;
}
}
}
if (session.streamID == session.directorStreamID){
session.directorState = true;
session.directorUUID = false;
pokeAPI("director", true);
pokeIframeAPI("director", true);
warnlog("You are joining with a token, but are the director?");
}
session.directorList = [];
}
}
async function checkToken(){ // this lets us use a server+password validation method for the director.
if (!session.token){return;}
if (!session.roomid){return;}
if (session.mainDirectorPassword){return;}
try {
var request = new XMLHttpRequest();
var hashedRoom = session.roomid;
if (session.password){
hashedRoom += session.password;
}
hashedRoom += "i^4&u#Fz5Eu#MsK^chF5*XAEYi1g";
hashedRoom = await generateHash(hashedRoom);
hashedRoom = hashedRoom.slice(0, 50);
request.open('GET', "https://tokens.vdo.ninja/?token="+session.token+"&room="+hashedRoom, false);
request.send(null);
if (request.status === 200) {
try {
var result = JSON.parse(request.responseText);
if ("UUID" in result){
session.directorUUID = result.UUID;
session.directorList = [];
session.directorList.push(session.directorUUID);
session.directorStreamID = false;
session.newMainDirectorSetup();
} else if ("streamID" in result){
session.directorStreamID = result.streamID;
checkDirectorStreamID();
}
} catch(e){
session.directorUUID = false;
session.directorStreamID = false;
session.directorList = [];
errorlog(e);
}
} else {
session.directorUUID = false;
session.directorStreamID = false;
session.directorList = [];
errorlog("Didn't get a token response");
}
} catch(e){
errorlog(e);
}
}
async function registerToken(){ // this lets us use a server+password validation method for the director.
if (!session.roomid){return;}
if (!session.streamID){return;}
if (!session.mainDirectorPassword){return;}
var longToken = session.mainDirectorPassword + "3wJVW^5qYU4DxGi6VhxN6RF04Q%$"; // this lets us use the same token across multiple rooms
var hashedToken = await generateHash(longToken); // keep it anonymous
hashedToken = hashedToken.slice(0, 50);
var hashedRoom = session.roomid;
if (session.password){
hashedRoom += session.password;
}
hashedRoom += "i^4&u#Fz5Eu#MsK^chF5*XAEYi1g";
hashedRoom = await generateHash(hashedRoom);
hashedRoom = hashedRoom.slice(0, 50);
var data2send = {};
var hashedSID = await generateHash(session.streamID);
data2send.streamID = hashedSID; // not sure if there's a way around this.
data2send = JSON.stringify(data2send);
var request = new XMLHttpRequest();
request.open('POST', "https://tokens.vdo.ninja/?token="+hashedToken+"&room="+hashedRoom, false);
console.log("https://tokens.vdo.ninja/?token="+hashedToken+"&room="+hashedRoom);
request.send(data2send);
if (request.status === 200) {
try {
if (request.responseText && (request.responseText.length===16)){
session.token = request.responseText;
console.log("share token: "+session.token);
session.directorState = true;
pokeAPI("director", true);
pokeIframeAPI("director", true);
}
} catch(e){
session.directorState = false;
pokeAPI("director", false);
pokeIframeAPI("director", false);
}
} else {
session.directorState = false;
pokeAPI("director", false);
pokeIframeAPI("director", false);
}
}
function hideDirectorinvites(ele, skip=true) {
if (getById("directorLinks2").style.display == "none") {
ele.innerHTML = ' LINKS (GUEST INVITES & SCENES)';
getById("directorLinks2").style.display = "inline-block";
getById("customizeLinks").classList.remove("hidden");
} else {
ele.innerHTML = ' LINKS (GUEST INVITES & SCENES)'
getById("directorLinks2").style.display = "none";
getById("help_directors_room").style.display = "none";
getById("roomnotes2").style.display = "none";
getById("customizeLinks").classList.add("hidden");
}
if (getById("directorLinks1").style.display == "none") {
getById("directorLinks1").style.display = "inline-block";
getById("customizeLinks").classList.remove("hidden");
} else {
getById("directorLinks1").style.display = "none";
getById("help_directors_room").style.display = "none";
getById("roomnotes2").style.display = "none";
getById("customizeLinks").classList.add("hidden");
}
if (skip){
saveDirectorSettings();
}
}
function toggleCoDirector_changeurl(ele){
session.codirector_changeURL = ele.checked; // doesn't do anything yet though.
}
function toggleCoDirector_transfer(ele){
session.codirector_transfer = ele.checked;
}
async function toggleCoDirector(ele){
//session.coDirectorAllowed = ele.checked;
if (!ele.checked){
getById("codirectorSettings").style.display = "none";
return;
}
if (!session.directorPassword){
session.directorPassword = await promptAlt(miscTranslations["enter-new-codirector-password"], false);
if (!session.directorPassword){
session.directorPassword=false;
ele.checked=false;
return;
}
session.directorPassword = sanitizePassword(session.directorPassword)
}
updateURL("codirector="+session.directorPassword, true, false);
getById("coDirectorEnableSpan").style.display = "none";
await generateHash(session.directorPassword + session.salt + "abc123", 12).then(function(hash) { // million to one error.
log("dir room hash is " + hash);
session.directorHash = hash;
return;
}).catch(errorlog);
if (session.codirector_transfer){
getById("codirectorSettings_transfer").checked = true;
} else {
getById("codirectorSettings_transfer").checked = false;
}
if (session.codirector_changeURL){
getById("codirectorSettings_changeurl").checked = true;
} else {
getById(codirectorSettings_changeurl).checked = false;
}
var token = "";
if (session.token){
token+="&token="+session.token;
}
getById("codirectorSettings_invite").value = "https://"+location.host+location.pathname+"?dir="+session.roomid+"&codirector="+session.directorPassword+token;
if (session.password!==session.sitePassword){
if (session.password===false){
getById("codirectorSettings_invite").value += "&password=false";
} else{
getById("codirectorSettings_invite").value += "&password";
}
}
getById("codirectorSettings").style.display = "block";
}
async function toggleWidgetURL(ele){
if (ele.id === "widgetURL"){
ele = getById("widgetURCheck");
} else if (!ele.checked){
getById("widgetURL").classList.add("hidden");
session.widget = false;
var data = {};
data.widgetSrc = false;
for (var UUID in session.pcs){
if (session.pcs[UUID].allowWidget===true){
session.sendMessage(data, UUID);
}
}
if (session.director){
let widget = document.getElementById("widget");
if (widget){
getById("widget").remove();
getById("guestFeeds").style.width = "100%";
}
}
pokeIframeAPI("widget-src", session.widget);
return;
}
var widget = await promptAlt(miscTranslations["enter-url-for-widget"], false, false, session.widget);
if (widget!==null){
session.widget = widget;
}
if (session.widget){
getById("widgetURL").value = session.widget;
getById("widgetURL").classList.remove("hidden");
updateMixer();
} else {
session.widget = false;
getById("widgetURL").classList.add("hidden");
ele.checked = false;
}
var data = {};
data.widgetSrc = session.widget;
for (var UUID in session.pcs){
if (session.pcs[UUID].allowWidget===true){
session.sendMessage(data, UUID);
}
}
if (session.director){
let widget = document.getElementById("widget");
if (!widget){
if (session.widget){
widget = document.createElement("iframe");
widget.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;midi;";
widget.id = "widget";
widget.src = parseURL4Iframe(session.widget);
log(widget.src);
document.body.appendChild(widget);
getById("guestFeeds").style.width = "75%";
}
} else if (session.widget){
if (session.widget){
widget.src = parseURL4Iframe(session.widget);
} else {
getById("widget").remove();
getById("guestFeeds").style.width = "100%";
}
}
}
pokeIframeAPI("widget-src", session.widget);
}
async function createRoomCallback(passAdd, passAdd2) {
if (!session.switchMode){
getById("directorlayout").classList.remove("hidden");
getById("gridlayout").classList.add("hidden");
}
var broadcastFlag = getById("broadcastFlag");
try {
if (broadcastFlag.checked) {
broadcastFlag = true;
} else {
broadcastFlag = false;
}
} catch (e) {
broadcastFlag = false;
}
var broadcastString = "";
if (broadcastFlag) {
broadcastString = "&broadcast";
getById("broadcastSlider").checked = true;
}
var wss = "";
if (session.wssSetViaUrl){
if (session.customWSS && (session.customWSS!==true)){
wss = "&pie="+session.customWSS;
} else {
wss = "&wss="+session.wss;
}
}
var queue = "";
if (session.queue){
queue = "&queue";
getById("directorLinks2").style.opacity = "0.2";
getById("directorLinks2").style.pointerEvents = "none";
getById("directorLinks2").style.cursor = "not-allowed";
}
var showdirectorFlag = getById("showdirectorFlag");
try {
if (showdirectorFlag.checked) {
showdirectorFlag = true;
} else {
showdirectorFlag = false;
}
} catch (e) {
showdirectorFlag = false;
}
if (showdirectorFlag) {
updateURL("showdirector", true, false);
session.showDirector = session.showDirector || true;
//getById("broadcastSlider").checked=true;
}
var codecGroupFlag = getById("codecGroupFlag");
if (codecGroupFlag.value) {
if (codecGroupFlag.value === "vp9") {
codecGroupFlag = "&codec=vp9";
getById("codech264toggle").disabled=true;
} else if (codecGroupFlag.value === "h264") {
codecGroupFlag = "&codec=h264";
getById("codech264toggle").checked=true;
} else if (codecGroupFlag.value === "vp8") {
codecGroupFlag = "&codec=vp8";
getById("codech264toggle").disabled=true;
} else if (codecGroupFlag.value === "av1") {
codecGroupFlag = "&codec=av1";
getById("codech264toggle").disabled=true;
} else {
codecGroupFlag = "";
}
} else {
codecGroupFlag = "";
}
if (codecGroupFlag) {
session.codecGroupFlag = codecGroupFlag;
}
if (session.bitrateGroupFlag){
codecGroupFlag += session.bitrateGroupFlag;
}
formSubmitting = false;
var m = getById("mainmenu");
m.remove();
getById("head1").className = 'hidden';
getById("head2").className = 'hidden';
getById("head4").className = '';
try {
if (session.label === false) {
document.title = "Control Room";
}
} catch (e) {
errorlog(e);
};
session.director = true;
screensharesupport = false;
if (session.meterStyle ===false){
session.meterStyle = 1; // director specific style
}
if (session.signalMeter===null){
session.signalMeter = true;
}
if (session.batteryMeter===null){
session.batteryMeter = true;
}
if (session.directorPassword){
getById("coDirectorEnable").checked = true;
getById("coDirectorEnableSpan").style.display = "none";
var token = "";
if (session.token){
token+="&token="+session.token;
}
getById("codirectorSettings_invite").value = "https://"+location.host+location.pathname+"?dir="+session.roomid+"&codirector="+session.directorPassword+token;
if (session.password!==session.sitePassword){
if (session.password==false){
getById("codirectorSettings_invite").value += "&password=false";
} else{
getById("codirectorSettings_invite").value += "&password";
}
}
if (session.codirector_transfer){
getById("codirectorSettings_transfer").checked = true;
} else {
getById("codirectorSettings_transfer").checked = false;
}
if (session.codirector_changeURL){
getById("codirectorSettings_changeurl").checked = true;
} else {
getById("codirectorSettings_changeurl").checked = false;
}
getById("codirectorSettings").style.display = "block";
}
window.onresize = updateMixer;
window.onorientationchange = function(){setTimeout(async function(){
if (session.forceAspectRatio){
await updateCameraConstraints("aspectRatio", session.forceAspectRatio);
}
if (session.effect && (session.effect === "7")){digitalZoom(true);}
updateForceRotate();
updateMixer();
}, 200);};
getById("reshare").parentNode.removeChild(getById("reshare"));
//getById("mutespeakerbutton").style.display = null;
if (session.speakerMuted_default===false){
//session.speakerMuted = false; // the director will start with audio playback muted.
toggleSpeakerMute(true); // let it be what it is.
} else {
session.speakerMuted = true; // the director will start with audio playback muted.
toggleSpeakerMute(true); // okay since only run on start
}
var token = "";
if (session.token){
token+="&token="+session.token;
}
if (session.cleanDirector == false && session.cleanOutput==false) {
getById("roomHeader").style.display = "";
//getById("directorLinks").style.display = "";
getById("directorLinks1").style.display = "inline-block";
getById("directorLinks2").style.display = "inline-block";
getById("director_block_1").dataset.raw = "https://" + location.host + location.pathname + "?room=" + session.roomid + broadcastString + passAdd + wss + queue + token;
getById("director_block_1").href = "https://" + location.host + location.pathname + "?room=" + session.roomid + broadcastString + passAdd + wss + queue + token;
getById("director_block_1").innerText = "https://" + location.host + location.pathname + "?room=" + session.roomid + broadcastString + passAdd + wss + queue + token;
getById("director_block_3").dataset.raw = "https://" + location.host + location.pathname + "?scene&room=" + session.roomid + codecGroupFlag + passAdd2 + wss + token;
getById("director_block_3").href = "https://" + location.host + location.pathname + "?scene&room=" + session.roomid + codecGroupFlag + passAdd2 + wss + token;
getById("director_block_3").innerText = "https://" + location.host + location.pathname + "?scene&room=" + session.roomid + codecGroupFlag + passAdd2 + wss + token;
getById("calendarButton").style.display = "inline-block";
} else {
getById("guestFeeds").innerHTML = '';
}
getById("guestFeeds").style.display = "";
if (!(session.cleanOutput)) {
if (session.queue){
getById("queuebutton").classList.remove("hidden");
}
getById("chatbutton").classList.remove("hidden");
getById('sharefilebutton').classList.remove("hidden"); // we won't override "display:none", if set, though.
getById("controlButtons").classList.remove("hidden");
getById("mutespeakerbutton").classList.remove("hidden");
getById("websitesharebutton").classList.remove("hidden");
//getById("screensharebutton").classList.remove("hidden");
if (session.totalRoomBitrate){
getById("roomsettingsbutton").classList.remove("hidden");
}
if (session.showDirector == false) {
getById("miniPerformer").innerHTML = '';
miniTranslate(getById("miniPerformer"));
getById("grabDirectorSoloLink").dataset.raw = "https://" + location.host + location.pathname + "?solo&sd&r=" + session.roomid + "&v="+session.streamID + passAdd2 + wss + token;
getById("grabDirectorSoloLink").href = "https://" + location.host + location.pathname + "?solo&sd&r=" + session.roomid + "&v="+session.streamID + passAdd2 + wss + token;
getById("grabDirectorSoloLink").innerText = "https://" + location.host + location.pathname + "?solo&sd&r=" + session.roomid + "&v="+session.streamID + passAdd2 + wss + token;
getById("grabDirectorSoloLinkParent").classList.remove("hidden");
} else {
getById("miniPerformer").innerHTML = '';
}
getById("miniPerformer").className = "";
var tabindex = 26;
if (session.rooms && session.rooms.length > 0){
var container = getById("rooms");
container.innerHTML += 'Arm Transfer: ';
session.rooms.forEach(function (r) {
// if(session.roomid == r) return; //don't include self
container.innerHTML += '';
tabindex++;
});
}
} else {
getById("miniPerformer").style.display = "none";
getById("controlButtons").classList.add("hidden");
}
if (session.chatbutton === true) {
getById("chatbutton").classList.remove("hidden");
getById("controlButtons").classList.remove("hidden");
} else if (session.chatbutton === false) {
getById("chatbutton").classList.add("hidden");
}
if (session.effect===false){
session.effect = null; // so the director can see the effects
}
getById("avatarDiv3").classList.remove("hidden"); // lets the director see the avatar option
clearInterval(session.updateLocalStatsInterval);
session.updateLocalStatsInterval = setInterval(function(){updateLocalStats();},session.statsInterval);
var directorWebsiteShare = getStorage("directorWebsiteShare"); // {"website":session.iframeSrc, "roomid":session.roomid}
if (typeof directorWebsiteShare === 'object' && directorWebsiteShare !== null && "website" in directorWebsiteShare){
if (directorWebsiteShare.website == false){
clearDirectorSettings();
} else if (directorWebsiteShare.roomid && (directorWebsiteShare.roomid==session.roomid)){
session.iframeSrc = directorWebsiteShare.website;
session.defaultIframeSrc = directorWebsiteShare.website;
getById("websitesharebutton").classList.add("hidden");
getById("websitesharebutton2").classList.remove("hidden");
}
}
session.group.forEach(group=>{
// changeGroupDirectorAPI(group, state=null, update=true)
changeGroupDirectorAPI(group, true, false); // update the UI only
});
session.groupView.forEach(group=>{
// changeGroupDirectorAPI(group, state=null, update=true)
changeGroupViewDirectorAPI(group, true); // update the UI only
});
if (session.showDirector){
getById("highlightDirectorSpan").style.display = "none";
getById("highlightDirectorSpan").remove();
} else {
getById("highlightDirector").dataset.sid = session.streamID;
}
setTimeout(function(){loadDirectorSettings();},100);
joinRoom(session.roomid);
try {
if (!gotDevices2AlreadyRan && (iOS || iPad)){
await enumerateDevices().then(gotDevices2); // this is needed for iOS; was previous set to timeout at 100ms, but would be useful everywhere I think. (Breaks director's auto start, so just iOS for now)
}
}catch(e){
errorlog(e);
}
if (session.autostart){
setTimeout(function(){press2talk(true);},400);
} else {
session.seeding=true;
session.seedStream();
}
} // createRoomCallback
function handleRoomSelect(room) {
var elems = document.querySelectorAll(".btnArmTransferRoom");
[].forEach.call(elems, function(el) {
el.classList.remove("selected");
});
if (previousRoom == room) {
previousRoom = "";
armedTransfer = false;
stillNeedRoom = true;
} else {
previousRoom = room;
stillNeedRoom = false;
armedTransfer = true;
getById("roomselect_" + room).classList.add('selected');
}
}
function getDirectorSettings(scene=false){
var settings = {};
var eles = document.querySelectorAll('[data-action-type="solo-video"]');
settings.soloVideo = false;
for (var i=0;ibiggestSlot){
biggestSlot = parseInt(slots[i].dataset.slot);
}
if (slotDefault===parseInt(slots[i].dataset.slot)){
slotDefault = null;
}
}
biggestSlot+=1;
}
if (slotDefault!==null){
biggestSlot = slotDefault;
}
var slotName = "slot: "+biggestSlot;
if (!biggestSlot){
slotName = "unset";
}
buttons += "
\
";
container.innerHTML = buttons;
var oldGroups = [];
document.querySelectorAll("#groups [data-action-type='toggle-group'][data-group]:not(.green)").forEach(ee=>{
oldGroups.push(ee.dataset.group);
});
getById("groups").remove();
if (session.hidesololinks==false){ // won't be updating the solo link to a view-only one ever, since director is always expected to be in a room
controls.innerHTML += "
\
" + sanitizeChat(soloLink) + "\
\
\
";
if (session.directorUUID){
controls.innerHTML += "
This is you, a co-director. You are also a performer.
";
} else {
controls.innerHTML += "
This is you, the director. You are also a performer.
";
}
}
controls.querySelectorAll('[data-action-type]').forEach((ele) => { // give action buttons some self-reference
ele.dataset.sid = session.streamID;
});
container.appendChild(controls);
getById("guestFeeds").appendChild(container);
Object.keys(session.sceneList).forEach((scene, index) => {
if (document.getElementById("container_director")){
if (!(getById("container_director").querySelectorAll('[data-scene="'+scene+'"]').length)){
var newScene = document.createElement("div");
newScene.innerHTML = '';
newScene.classList.add("customScene");
//getById("container_director").appendChild(newScene);
var added = false;
getById("container_director").querySelectorAll('.customScene>[data-scene]').forEach(ele=>{
if (!added && ele.dataset.scene>scene+""){
ele.parentNode.parentNode.insertBefore(newScene, ele.parentNode);
added = true;
}
});
if (!added){
getById("container_director").appendChild(newScene);
}
}
}
});
getById("groups").showDirector = true;
session.group.forEach(group=>{
// changeGroupDirectorAPI(group, state=null, update=true)
changeGroupDirectorAPI(group, true, false); // update the UI only /
});
oldGroups.forEach(group=>{
// changeGroupDirectorAPI(group, state=null, update=true)
changeGroupDirectorAPI(group, false, false); // update the UI only /
});
var labelID = document.getElementById("label_director");
labelID.onclick = async function(ee){
var oldlabel = ee.target.innerText;
if (session.label===false){
oldlabel = "";
}
window.focus();
var newlabel = await promptAlt(miscTranslations["enter-new-display-name"], false, false, oldlabel);
if (newlabel!==null){
if (newlabel == ""){
newlabel = false;
ee.target.innerText = miscTranslations["add-a-label"];
ee.target.classList.add("addALabel");
} else {
ee.target.innerText = newlabel;
ee.target.classList.remove("addALabel");
}
session.label = newlabel;
var data = {};
data.changeLabel = true;
data.value = session.label;
session.sendMessage(data);
}
}
labelID.style.float = "left";
labelID.style.top = "2px";
labelID.style.marginLeft = "5px";
labelID.style.position = "relative";
labelID.style.cursor="pointer";
if (session.label){
labelID.innerText = session.label;
}
pokeIframeAPI("control-box", true, true);
if (session.slotmode){
pokeIframeAPI("slot-updated", biggestSlot, null, session.streamID); // need to support self-director
session.pastSlots[session.streamID] = biggestSlot;
createSlotUpdate();
}
}
function createSlotUpdate(UUID=false){
try {
var newSlots = {};
document.querySelectorAll("[data--u-u-i-d][data-slot]").forEach(ele=>{
newSlots[ele.dataset.slot] = ele.dataset.sid;
});
if (!UUID){
for (var uid in session.pcs){
if (session.pcs[uid].layout){
session.sendMessage({slotsUpdate:newSlots}, uid);
}
}
} else {
session.sendMessage({slotsUpdate:newSlots}, UUID);
}
} catch(e){
errorlog(e);
}
}
async function createDirectorScreenshareOnlyBox() { // sstype=3
var soloLink = soloLinkGenerator(session.streamID+":s");
if (document.getElementById("deleteme")) {
getById("deleteme").parentNode.removeChild(getById("deleteme"));
}
var controls = getById("controls_directors_blank").cloneNode(true);
controls.classList.remove("hidden");
controls.id = "controls_screen_director";
var container = document.createElement("div");
container.className = "vidcon directorMargins";
container.id = "container_screen_director"; // needed to delete on user disconnect
var buttons = "";
if (session.slotmode){
var slots = document.querySelectorAll("div.slotsbar[data-slot]");
var biggestSlot=0;
var slotDefault = null;
if (session.streamID+":s" in session.pastSlots){
slotDefault = session.pastSlots[session.streamID+":s"];
}
if (session.slotmode==1){
for (var i=0;ibiggestSlot){
biggestSlot = parseInt(slots[i].dataset.slot);
}
if (slotDefault===parseInt(slots[i].dataset.slot)){
slotDefault = null;
}
}
biggestSlot+=1;
}
if (slotDefault!==null){
biggestSlot = slotDefault;
}
var slotName = "slot: "+biggestSlot;
if (!biggestSlot){
slotName = "unset";
}
buttons += "
\
";
container.innerHTML = buttons;
var oldGroups = [];
document.querySelectorAll("#groups [data-action-type='toggle-group'][data-group]:not(.green)").forEach(ee=>{
oldGroups.push(ee.dataset.group);
});
getById("groups").remove();
if (session.hidesololinks==false){ // won't be updating the solo link to a view-only one ever, since director is always expected to be in a room
controls.innerHTML += "
\
" + sanitizeChat(soloLink) + "\
\
\
";
if (session.directorUUID){
controls.innerHTML += "
This is you, a co-director. You are also a performer.
";
}
}
controls.querySelectorAll('[data-action-type]').forEach((ele) => { // give action buttons some self-reference
ele.dataset.sid = session.streamID+":s" ;
});
container.appendChild(controls);
getById("guestFeeds").appendChild(container);
Object.keys(session.sceneList).forEach((scene, index) => {
if (document.getElementById("container_screen_director")){
if (!(getById("container_screen_director").querySelectorAll('[data-scene="'+scene+'"]').length)){
var newScene = document.createElement("div");
newScene.innerHTML = '';
newScene.classList.add("customScene");
//getById("container_screen_director").appendChild(newScene);
var added = false;
getById("container_screen_director").querySelectorAll('.customScene>[data-scene]').forEach(ele=>{
if (!added && ele.dataset.scene>scene+""){
ele.parentNode.parentNode.insertBefore(newScene, ele.parentNode);
added = true;
}
});
if (!added){
getById("container_screen_director").appendChild(newScene);
}
}
}
});
getById("groups").showDirector = true;
session.group.forEach(group=>{
// changeGroupDirectorAPI(group, state=null, update=true)
changeGroupDirectorAPI(group, true, false); // update the UI only /
});
oldGroups.forEach(group=>{
// changeGroupDirectorAPI(group, state=null, update=true)
changeGroupDirectorAPI(group, false, false); // update the UI only /
});
var labelID = document.getElementById("label_director");
labelID.onclick = async function(ee){
var oldlabel = ee.target.innerText;
if (session.label===false){
oldlabel = "";
}
window.focus();
var newlabel = await promptAlt(miscTranslations["enter-new-display-name"], false, false, oldlabel);
if (newlabel!==null){
if (newlabel == ""){
newlabel = false;
ee.target.innerText = miscTranslations["add-a-label"];
ee.target.classList.add("addALabel");
} else {
ee.target.innerText = newlabel;
ee.target.classList.remove("addALabel");
}
session.label = newlabel;
var data = {};
data.changeLabel = true;
data.value = session.label;
session.sendMessage(data);
}
}
labelID.style.float = "left";
labelID.style.top = "2px";
labelID.style.marginLeft = "5px";
labelID.style.position = "relative";
labelID.style.cursor="pointer";
if (session.label){
labelID.innerText = session.label;
}
pokeIframeAPI("control-box", true, true);
if (session.slotmode){
pokeIframeAPI("slot-updated", biggestSlot, null, session.streamID+":s"); // need to support self-director
session.pastSlots[session.streamID+":s"] = biggestSlot;
createSlotUpdate();
}
}
function shiftPC(ele, shift, director=false){
if (director){
var target = document.getElementById("container_director");
} else {
var target = document.getElementById("container_"+ele.dataset.UUID);
}
if (!target){return;}
target.shifted = true;
var target2 = false;
if (shift==1){
if (target.nextSibling){
target2 = target.nextSibling;
target.parentNode.insertBefore(target.nextSibling, target);
}
} else {
if (target.previousSibling){
target2 = target.previousSibling;
target.parentNode.insertBefore(target, target.previousSibling);
}
}
updateLockedElements();
if (session.api){
var slots = {}
var elements = getById("guestFeeds").children;
for (var i = 0;i";
ele.parentNode.classList.add("locked");
while (currentPosition>parseInt(ele.dataset.locked)){
var node = document.getElementById("container_"+UUID);
parent = node.parentNode,
prev = node.previousSibling,
oldChild = parent.removeChild(node);
parent.insertBefore( oldChild, prev );
currentPosition = Array.prototype.indexOf.call(getById("guestFeeds").children, document.getElementById("container_"+UUID))+1;
}
while ((currentPositioncurrentPosition)){
var node = document.getElementById("container_"+UUID);
parent = node.parentNode,
next = node.nextSibling,
oldChild = parent.removeChild(node);
parent.insertBefore(node, next.nextSibling);
currentPosition = Array.prototype.indexOf.call(getById("guestFeeds").children, document.getElementById("container_"+UUID))+1;
}
}
} else {
ele.dataset.locked = 0;
ele.innerHTML = "";
ele.parentNode.classList.remove("locked");
}
} else {
if (ele.dataset.locked && parseInt(ele.dataset.locked)){
ele.dataset.locked = 0;
ele.innerHTML = "";
ele.parentNode.classList.remove("locked");
} else {
if (getById("guestFeeds")){
ele.dataset.locked = Array.prototype.indexOf.call(getById("guestFeeds").children, document.getElementById("container_"+UUID))+1;
ele.innerHTML = "#"+ele.dataset.locked+"";
ele.parentNode.classList.add("locked");
}
}
}
}
function allowDropSlot(event) {
event.preventDefault();
}
function dragSlot(event) {
log("drag");
var ele = event.target;
if (!ele.dataset.UUID && ele.parentNode.dataset.UUID){
ele = ele.parentNode;
}
event.dataTransfer.setDragImage( getById('dragImage'), 24, 24);
event.dataTransfer.setData("text", ele.dataset.UUID);
var eles = document.querySelectorAll(".slotsbar");
for (var i=0;i
×${message}
`;
document.body.insertAdjacentHTML("beforeend", modalTemplate); // Insert modal at body end
document.getElementById("modalBackdrop").addEventListener("click", closeModal);
document.getElementById("alertModalMessage").querySelectorAll("div[data-slot]").forEach(choice=>{
choice.onclick = function(){
setSlot(ele, parseInt(this.dataset.slot));
closeModal();
};
});
document.getElementById("alertModal").style.left = (event.pageX)+"px"; // 165px 356px
document.getElementById("alertModal").style.top = (event.pageY+180)+"px";
getById("alertModal").addEventListener("click", function(e) {
e.stopPropagation();
return false;
});
} else {
var slot = await promptAlt("Which slot to change to?");
setSlot(ele,slot);
}
}
function setSlot(ele,slot){
log("setSlot()");
getById("slotPicker").classList.add("hidden");
if (slot!==null){
slot = (parseInt(slot) || 0);
var slots = document.querySelectorAll("div.slotsbar[data-slot]");
for (var i=0;i streamID.toLowerCase()){
getById("guestFeeds").insertBefore(container, getById("guestFeeds").children[i]);
added = true;
break;
}
}
}
}
if (!added){
getById("guestFeeds").appendChild(container);
}
} catch(e){
getById("guestFeeds").appendChild(container);
}
} else {
getById("guestFeeds").appendChild(container);
}
if (!session.rpcs[UUID].voiceMeter) {
if (session.meterStyle==1){ // director specific style
session.rpcs[UUID].voiceMeter = getById("voiceMeterTemplate2").cloneNode(true);
} else {
session.rpcs[UUID].voiceMeter = getById("voiceMeterTemplate").cloneNode(true);
session.rpcs[UUID].voiceMeter.style.opacity = 0;
if (session.meterStyle==2){
session.rpcs[UUID].voiceMeter.classList.add("video-meter-2");
session.rpcs[UUID].voiceMeter.classList.remove("video-meter");
} else {
session.rpcs[UUID].voiceMeter.classList.add("video-meter-director");
}
}
session.rpcs[UUID].voiceMeter.id = "voiceMeter_" + UUID;
session.rpcs[UUID].voiceMeter.dataset.level = 0;
session.rpcs[UUID].voiceMeter.classList.remove("hidden");
}
session.rpcs[UUID].remoteMuteElement = getById("muteStateTemplate").cloneNode(true);
session.rpcs[UUID].remoteMuteElement.id = "";
session.rpcs[UUID].remoteMuteElement.style.top = "5px";
session.rpcs[UUID].remoteMuteElement.style.right = "7px";
session.rpcs[UUID].remoteVideoMuteElement = getById("videoMuteStateTemplate").cloneNode(true);
session.rpcs[UUID].remoteVideoMuteElement.id = "";
session.rpcs[UUID].remoteVideoMuteElement.style.top = "5px";
session.rpcs[UUID].remoteVideoMuteElement.style.right = "28px";
session.rpcs[UUID].remoteRaisedHandElement = getById("raisedHandTemplate").cloneNode(true);
session.rpcs[UUID].remoteRaisedHandElement.id = "";
session.rpcs[UUID].remoteRaisedHandElement.style.top = "5px";
session.rpcs[UUID].remoteRaisedHandElement.style.right = "49px";
var videoContainer = document.createElement("div");
videoContainer.id = "videoContainer_" + UUID; // needed to delete on user disconnect
videoContainer.style.margin = "0";
videoContainer.style.position = "relative";
videoContainer.style.minHeight = "30px";
var iframeDetails = document.createElement("div");
iframeDetails.id = "iframeDetails_" + UUID; // needed to delete on user disconnect
iframeDetails.className = "iframeDetails hidden";
controls.innerHTML += "";
controls.innerHTML += "";
var handsID = "hands_" + UUID;
// controls.innerHTML += "
Links
"; //Seems to create an empty div.
if (session.hidesololinks==false){
controls.innerHTML += "
\
" + sanitizeChat(soloLink) + "\
\
";
}
controls.innerHTML += "\
";
controls.innerHTML += "\
";
controls.querySelectorAll('[data-action-type]').forEach((ele) => { // give action buttons some self-reference
ele.dataset.UUID = UUID;
ele.dataset.sid = streamID;
});
var buttons = "";
if (session.slotmode){
var slots = document.querySelectorAll("div.slotsbar[data-slot]");
var biggestSlot=0;
var slotDefault = null;
if (slot_init && (session.slotmode==1)){
slotDefault = slot_init || null;
}
if (streamID in session.pastSlots){
slotDefault = session.pastSlots[streamID];
}
if (session.slotmode==1){
for (var i=0;ibiggestSlot){
biggestSlot = parseInt(slots[i].dataset.slot);
}
if (slotDefault===parseInt(slots[i].dataset.slot)){ // already taken
slotDefault = null;
}
}
biggestSlot+=1;
}
if (slotDefault!==null){ // the default slot is avialable
biggestSlot = slotDefault;
} else if (slot_init && (session.slotmode==1)){ // was manually set, so can't be something else but 0; 0 or false would still end up with 0
biggestSlot = 0;
} // else, if slotmode==1, we'll go for the biggest slot available
var slotName = "slot: "+biggestSlot;
if (!biggestSlot){
slotName = "unset";
}
buttons += "
\
";
}
buttons += "
ID: " + streamID + "\
\
\
\
";
container.innerHTML = buttons;
updateLockedElements();
var videoContainerControlBox = document.createElement("div");
videoContainerControlBox.className = "controlVideoBox";
container.containerControlBox = videoContainerControlBox
container.appendChild(videoContainerControlBox);
videoContainerControlBox.appendChild(videoContainer);
if (session.signalMeter){
if (!session.rpcs[UUID].signalMeter){
session.rpcs[UUID].signalMeter = getById("signalMeterTemplate").cloneNode(true);
session.rpcs[UUID].signalMeter.id = "signalMeter_" + UUID;
session.rpcs[UUID].signalMeter.dataset.level = 0;
session.rpcs[UUID].signalMeter.classList.remove("hidden");
session.rpcs[UUID].signalMeter.dataset.UUID = UUID;
session.rpcs[UUID].signalMeter.title = miscTranslations["signal-meter"];
if (session.rpcs[UUID].stats.info && session.rpcs[UUID].stats.info.cpuLimited){ // was quality_limitation_reason
session.rpcs[UUID].signalMeter.dataset.cpu = "1";
}
session.rpcs[UUID].signalMeter.addEventListener('click', function(e) { // show stats of video if double clicked
log("clicked signal meter");
try {
e.preventDefault();
var uid = e.currentTarget.dataset.UUID;
if ("stats" in session.rpcs[uid]){
var [menu, innerMenu] = statsMenuCreator();
printViewStats(innerMenu, uid );
menu.interval = setInterval(printViewStats, session.statsInterval, innerMenu, uid);
}
e.stopPropagation();
return false;
} catch(e){errorlog(e);}
});
}
videoContainer.appendChild(session.rpcs[UUID].signalMeter);
}
if (session.batteryMeter){
if (!session.rpcs[UUID].batteryMeter){
session.rpcs[UUID].batteryMeter = getById("batteryMeterTemplate").cloneNode(true);
session.rpcs[UUID].batteryMeter.id = "batteryMeter_" + UUID;
batteryMeterInfoUpdate(UUID);
}
videoContainer.appendChild(session.rpcs[UUID].batteryMeter);
}
if (session.showConnections){
if (!session.rpcs[UUID].connectionDetails){
createConnectionDetailsEle(UUID);
}
videoContainer.appendChild(session.rpcs[UUID].connectionDetails);
}
videoContainer.appendChild(session.rpcs[UUID].voiceMeter);
videoContainer.appendChild(session.rpcs[UUID].remoteMuteElement);
videoContainer.appendChild(session.rpcs[UUID].remoteVideoMuteElement);
videoContainer.appendChild(session.rpcs[UUID].remoteRaisedHandElement);
videoContainer.appendChild(iframeDetails);
container.appendChild(controls);
session.group.forEach(group=>{
var ele = controls.querySelector('[data-action-type="toggle-group"][data--u-u-i-d="'+UUID+'"][data-group="'+group+'"]');
if (!ele){
var newGroup = htmlToElement('');
var added = false;
container.querySelectorAll('.customGroup>[data-group]').forEach(ele=>{
log(ele);
if (!added && ele.dataset.group>group+""){
ele.parentNode.insertBefore(newGroup, ele);
added = true;
}
});
if (!added){
var newGroupCon = container.querySelector(".customGroup");
if (!newGroupCon){
newGroupCon = document.createElement("div");
newGroupCon.classList.add("customGroup");
container.appendChild(newGroupCon);
}
newGroupCon.appendChild(newGroup);
}
}
});
initSceneList(UUID);
syncSceneState(streamID);
syncOtherState(streamID);
pokeIframeAPI("control-box", true, UUID);
if (session.slotmode){
pokeIframeAPI("slot-updated", biggestSlot, UUID); // need to support self-director
session.pastSlots[streamID] = biggestSlot;
createSlotUpdate();
}
}
function minimizeMe(button, director=false){
if (!director){
getById("container_"+button.dataset.UUID).classList.toggle("minimized");
} else {
getById(director).classList.toggle("minimized");
}
}
function cycleCameras(){
if (session.screenShareState) {
warnUser("Stop the screen-share first.");
return;
}
var videoSelect = document.querySelector("select#videoSource3").options;
// don't show flip option if only one camera.
// don't show if not a mobile device
// don't show if AD=0
var matched = false;
var maxIndex = parseInt(getById("flipcamerabutton").dataset.maxIndex) || parseInt(videoSelect.length);
if (maxIndex > parseInt(videoSelect.length)){
maxIndex = parseInt(videoSelect.length);
}
for(var i = 0; i < maxIndex; i++){
var selOption = videoSelect[i];
if (selOption.selected) {
matched=true;
} else if (matched){
if (getById("flipcamerabutton").classList.contains("flip")){
getById("flipcamerabutton").classList.remove("flip");
getById("flipcamerabutton").classList.add("flip2");
} else {
getById("flipcamerabutton").classList.remove("flip2");
getById("flipcamerabutton").classList.add("flip");
}
document.querySelector("select#videoSource3").value = selOption.value;
activatedPreview = false;
grabVideo(session.quality, "videosource", "select#videoSource3");
return;
}
}
for(var i = 0; i < maxIndex; i++){
var selOption = videoSelect[i];
if (selOption.selected) {
return; // do nothing; the camera that is selected is the only camera available it seems.
} else {
if (getById("flipcamerabutton").classList.contains("flip")){
getById("flipcamerabutton").classList.remove("flip");
getById("flipcamerabutton").classList.add("flip2");
} else {
getById("flipcamerabutton").classList.remove("flip2");
getById("flipcamerabutton").classList.add("flip");
}
document.querySelector("select#videoSource3").value = selOption.value;
activatedPreview = false;
grabVideo(session.quality, "videosource", "select#videoSource3");
return;
}
}
}
function addToGoogleCalendar(){
var title = "Live Stream";
//var dates = "20180512T230000Z/20180513T030000Z";
var linkout = getById("director_block_1").innerText;
var details = "Join the live stream as a performer at the following link:
===> "+linkout+"
To test your connection and camera ahead of time, please visit https://vdo.ninja/speedtest
Do not share the details of this invite with others, unless explicitly told to.";
details = details.split(' ').join('+');
details = details.split('&').join('%26');
var linkToOpen = "https://calendar.google.com/calendar/r/eventedit?text="+title+"&details="+details;
//https://calendar.google.com/calendar/r/eventedit?text=My+Custom+Event&dates=20180512T230000Z/20180513T030000Z&details=For+details,+link+here:+https://example.com/tickets-43251101208&location=Garage+Boston+-+20+Linden+Street+-+Allston,+MA+02134
window.open(linkToOpen);
}
function addToOutlookCalendar(){
var title = "Live Stream";
var linkout = getById("director_block_1").innerText;
var details = "Join the live stream as a performer at the following link:
===> "+linkout+"
To test your connection and camera ahead of time, please visit https://vdo.ninja/speedtest
Do not share the details of this invite with others, unless explicitly told to.";
details = details.split(' ').join('%20');
details = details.split('&').join('%26');
var linkToOpen = "https://outlook.live.com/owa/?path=%2Fcalendar%2Faction%2Fcompose&rru=addevent&subject="+title+"&body="+details;
//https://calendar.google.com/calendar/r/eventedit?text=My+Custom+Event&dates=20180512T230000Z/20180513T030000Z&details=For+details,+link+here:+https://example.com/tickets-43251101208&location=Garage+Boston+-+20+Linden+Street+-+Allston,+MA+02134
window.open(linkToOpen);
}
function addToYahooCalendar(){
var title = "Live Stream";
var linkout = getById("director_block_1").innerText;
var details = "Join the live stream as a performer at the following link:
===> "+linkout+"
To test your connection and camera ahead of time, please visit https://vdo.ninja/speedtest
Do not share the details of this invite with others, unless explicitly told to.";
details = details.split(' ').join('%20');
details = details.split('&').join('%26');
var linkToOpen = "https://calendar.yahoo.com?v60&title="+title+"&desc="+details;
//https://calendar.google.com/calendar/r/eventedit?text=My+Custom+Event&dates=20180512T230000Z/20180513T030000Z&details=For+details,+link+here:+https://example.com/tickets-43251101208&location=Garage+Boston+-+20+Linden+Street+-+Allston,+MA+02134
window.open(linkToOpen);
}
function toggle(ele, tog = false, inline = true) {
var x = ele;
if (x.style.display === "none") {
if (inline) {
x.style.display = "inline-block";
} else {
x.style.display = "block";
}
} else {
x.style.display = "none";
}
if (tog) {
if (tog.dataset.saved) {
tog.innerHTML = tog.dataset.saved;
delete(tog.dataset.saved);
} else {
tog.dataset.saved = tog.innerHTML;
tog.innerHTML = "Hide This";
}
}
}
function toggleByDataset(filter) {
var elements = document.querySelectorAll('[data-cluster="'+filter+'"]'); // ie: .cluster1
for (var i = 0; i < elements.length; i++) {
elements[i].classList.toggle('hidden');
}
}
var SelectedAudioOutputDevices = false; // session.sink
var SelectedAudioInputDevices = []; // ..
var SelectedVideoInputDevices = []; // ..
async function enumerateDevices() {
log("enumerated start");
if (typeof navigator.enumerateDevices === "function") {
log("enumerated failed 1");
return await navigator.enumerateDevices();
} else if (typeof navigator.mediaDevices === "object" && typeof navigator.mediaDevices.enumerateDevices === "function") {
return await navigator.mediaDevices.enumerateDevices();
} else {
return await new Promise((resolve, reject) => {
try {
if (window.MediaStreamTrack == null || window.MediaStreamTrack.getSources == null) {
throw new Error();
}
window.MediaStreamTrack.getSources((devices) => {
resolve(devices
.filter(device => {
return device.kind.toLowerCase() === "video" || device.kind.toLowerCase() === "videoinput";
})
.map(device => {
return {
deviceId: device.deviceId != null ? device.deviceId : ""
, groupId: device.groupId
, kind: "videoinput"
, label: device.label
, toJSON: /* istanbul ignore next */ function() {
return this;
}
};
}));
});
} catch (e) {
errorlog(e);
}
});
}
}
function requestOutputAudioStream() {
try {
//warnlog("GET USER MEDIA");
return navigator.mediaDevices.getUserMedia({
audio: true
, video: false
}).then(function(stream1) { // Apple needs thi to happen before I can access EnumerateDevices.
log("get media sources; request audio stream");
return enumerateDevices().then(function(deviceInfos) {
stream1.getTracks().forEach(function(track) { // We don't want to keep it without audio; so we are going to try to add audio now.
track.stop(); // I need to do this after the enumeration step, else it breaks firefox's labels
});
const audioOutputSelect = getById('outputSourceScreenshare');
audioOutputSelect.remove(0);
audioOutputSelect.removeAttribute("onclick");
for (let i = 0; i !== deviceInfos.length; ++i) {
const deviceInfo = deviceInfos[i];
if (deviceInfo == null) {
continue;
}
const option = document.createElement('option');
option.value = deviceInfo.deviceId;
if (deviceInfo.kind === 'audiooutput') {
const option = document.createElement('option');
if (audioOutputSelect.length === 0) {
option.dataset.default = true;
} else {
option.dataset.default = false;
}
option.value = deviceInfo.deviceId || "default";
if (option.value == session.sink) {
option.selected = "true";
}
option.text = deviceInfo.label || `Speaker ${audioOutputSelect.length + 1}`;
audioOutputSelect.appendChild(option);
} else {
log('Some other kind of source/device: ', deviceInfo);
}
}
});
});
} catch (e) {
if (!(session.cleanOutput)) {
if (window.isSecureContext) {
warnUser("An error has occured when trying to access the default audio device. The reason is not known.");
} else if ((iOS) || (iPad)) {
warnUser("iOS version 13.4 and up is generally recommended; older than iOS 11 is not supported.");
} else {
warnUser("Error acessing the default audio device.\n\nThe website may be loaded in an insecure context.\n\nPlease see: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia");
}
}
}
}
async function requestAudioStream() {
try {
//warnlog("GET USER MEDIA");
return await navigator.mediaDevices.getUserMedia({
audio: true
, video: false
}).then(async function(stream1) { // Apple needs thi to happen before I can access EnumerateDevices.
log("get media sources; request audio stream");
return await enumerateDevices().then(function async(deviceInfos) {
stream1.getTracks().forEach(function(track) { // We don't want to keep it without audio; so we are going to try to add audio now.
track.stop(); // I need to do this after the enumeration step, else it breaks firefox's labels
});
log("updating audio");
const audioInputSelect = getById('audioSourceScreenshare');
audioInputSelect.remove(1);
audioInputSelect.removeAttribute("onchange");
for (let i = 0; i !== deviceInfos.length; ++i) {
const deviceInfo = deviceInfos[i];
if (deviceInfo == null) {
continue;
}
const option = document.createElement('option');
option.value = deviceInfo.deviceId;
if (deviceInfo.kind === 'audioinput') {
option.text = deviceInfo.label || `Microphone ${audioInputSelect.length + 1}`;
audioInputSelect.appendChild(option);
} else {
log('Some other kind of source/device: ', deviceInfo);
}
}
audioInputSelect.style.minHeight = ((audioInputSelect.childElementCount + 1) * 1.15 * 16) + 'px';
audioInputSelect.style.minWidth = "342px";
if (session.audioDevice && (typeof session.audioDevice === "object") && session.audioDevice.length){
for (let i = 0; i !== audioInputSelect.length; ++i) {
let deviceInfo = audioInputSelect[i];
if (session.audioDevice.includes(deviceInfo.value)){
deviceInfo.selected = true;
} else if ((deviceInfo.innerText.replace(/[\W]+/g, "_").toLowerCase().startsWith(session.audioDevice))) {
deviceInfo.selected = true;
} else if ((deviceInfo.innerText.replace(/[\W]+/g, "_").toLowerCase().includes(session.audioDevice))) {
deviceInfo.selected = true;
}
}
}
});
});
} catch (e) {
if (!(session.cleanOutput)) {
if (window.isSecureContext) {
warnUser("An error has occured when trying to access the default audio device. The reason is not known.");
} else if ((iOS) || (iPad)) {
warnUser("iOS version 13.4 and up is generally recommended; older than iOS 11 is not supported.");
} else {
warnUser("Error acessing the default audio device.\n\nThe website may be loaded in an insecure context.\n\nPlease see: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia");
}
}
}
}
function saveSettings(){
if (session.store){
try {
var tmp = {};
if (SelectedAudioInputDevices){
tmp.SelectedAudioInputDevices = SelectedAudioInputDevices.filter(n => n);
}
if (session.sink && (session.sink!="default")){
tmp.SelectedAudioOutputDevices = session.sink;
} else if (!session.sink && SelectedAudioOutputDevices && (SelectedAudioOutputDevices!="default")){
tmp.SelectedAudioOutputDevices = SelectedAudioOutputDevices;
}
tmp.SelectedVideoInputDevices = SelectedVideoInputDevices;
setStorage("session_store", JSON.stringify(tmp));
log("Saving settings");
} catch(e){errorlog(e);}
}
}
function loadSettings(){
if (session.store){
try {
session.store = getStorage("session_store");
if (session.store){
session.store = JSON.parse(session.store);
} else {
session.store = {};
}
if (session.store && session.store.SelectedAudioOutputDevices){
if (typeof session.store.SelectedAudioOutputDevices == "string"){
SelectedAudioOutputDevices = session.store.SelectedAudioOutputDevices;
} else if (typeof session.store.SelectedAudioOutputDevices == "object"){
if (session.store.SelectedAudioOutputDevices.length){
SelectedAudioOutputDevices = session.store.SelectedAudioOutputDevices[0];
}
}
}
if (session.store && session.store.SelectedAudioInputDevices){
session.store.SelectedAudioInputDevices = session.store.SelectedAudioInputDevices.filter(n => n);
SelectedAudioInputDevices = session.store.SelectedAudioInputDevices;
}
if (session.store && session.store.SelectedVideoInputDevices){
SelectedVideoInputDevices = session.store.SelectedVideoInputDevices;
}
} catch(e){}
}
}
function gotDevices(deviceInfos, miconly=false) {
log("got devices!1");
log(deviceInfos);
try {
if (Firefox && !FirefoxEnumerated){
if (session.streamSrc && session.streamSrc.getTracks().length){
FirefoxEnumerated=true;
}
}
var option = document.createElement('input');
option.type = "checkbox";
option.value = "ZZZ";
option.name = "multiselect1";
option.id = "multiselect1";
option.style.display = "none";
option.checked = true;
var label = document.createElement('label');
label.for = option.name;
label.innerHTML = ' No Audio';
var listele = document.createElement('li');
listele.appendChild(option);
listele.appendChild(label);
const audioInputSelect = document.getElementById('audioSource') || document.getElementById('audioSource3');
audioInputSelect.innerHTML = "";
audioInputSelect.appendChild(listele);
const audioOutputSelect = document.getElementById('outputSource') || document.getElementById('outputSource3');
audioOutputSelect.innerHTML = "";
option.onchange = function(event) { // make sure to clear 'no audio option' if anything else is selected
if (!(getById("multiselect1").checked)){
getById("multiselect1").checked = true;
} else {
var list = audioInputSelect.querySelectorAll("li>input");
for (var i = 0; i < list.length; i++) {
if (list[i].id !== "multiselect1") {
list[i].checked = false;
}
}
}
SelectedAudioInputDevices = [event.currentTarget.value];
saveSettings();
};
const multiselectTrigger = document.getElementById('multiselect-trigger') || document.getElementById('multiselect-trigger3');
multiselectTrigger.dataset.state = '0';
multiselectTrigger.classList.add('closed');
multiselectTrigger.classList.remove('open');
getById('chevarrow1').classList.add('bottom');
const videoSelect = document.getElementById('videoSourceSelect') || document.getElementById('videoSource3');
const selectors = [videoSelect];
const values = selectors.map(select => select.value);
selectors.forEach(select => {
while (select.firstChild) {
select.removeChild(select.firstChild);
}
});
function comp(a, b) {
if (a.kind === 'audioinput') {
return 0;
} else if (a.kind === 'audiooutput') {
return 0;
}
const labelA = a.label.toUpperCase();
const labelB = b.label.toUpperCase();
if (labelA > labelB) {
return 1;
} else if (labelA < labelB) {
return -1;
}
return 0;
}
//deviceInfos.sort(comp); // I like this idea, but it messes with the defaults. I just don't know what it will do.
var deviceInfo;
// This is to hide NDI from default device. NDI Tools fucks up.
var tmp = [];
for (let i = 0; i !== deviceInfos.length; ++i) {
deviceInfo = deviceInfos[i];
if (!((deviceInfo.kind === 'videoinput') && (deviceInfo.label.toLowerCase().startsWith("ndi") || deviceInfo.label.toLowerCase().startsWith("newtek")))) {
tmp.push(deviceInfo);
}
}
for (let i = 0; i !== deviceInfos.length; ++i) {
deviceInfo = deviceInfos[i];
if ((deviceInfo.kind === 'videoinput') && (deviceInfo.label.toLowerCase().startsWith("ndi") || deviceInfo.label.toLowerCase().startsWith("newtek"))) {
tmp.push(deviceInfo);
log("V DEVICE FOUND = " + deviceInfo.label.replace(/[\W]+/g, "_").toLowerCase());
}
}
deviceInfos = tmp;
if (typeof session.audioDevice == "object") { // this sorts according to users's manual selection
var matched = [];
var notmatched = [];
for (let i = 0; i !== deviceInfos.length; ++i) {
if (deviceInfos[i].kind === 'audioinput'){
if (session.audioDevice.includes(deviceInfos[i].deviceId)) {
matched.push(deviceInfos[i]);
} else {
for (var j=0;j {
if (Array.prototype.slice.call(select.childNodes).some(n => n.value === values[selectorIndex])) {
select.value = values[selectorIndex];
}
});
} catch (e) {
errorlog(e);
}
}
function getUserMediaVideoParams(resolutionFallbackLevel, isSafariBrowser) {
switch (resolutionFallbackLevel) {
case -1:
return {};
case 0:
if (isSafariBrowser) {
return {
width: {
min: 360
, ideal: 1920
, max: 1920
}
, height: {
min: 360
, ideal: 1080
, max: 1080
}
};
} else if (Firefox){
return {
width: {
ideal: 1920
}
, height: {
ideal: 1080
}
};
} else {
return {
width: {
min: 720
, ideal: 1920
, max: 1920
}
, height: {
min: 720
, ideal: 1080
, max: 1920
}
};
}
case 1:
if (isSafariBrowser) {
return {
width: {
min: 360
, ideal: 1280
, max: 1280
}
, height: {
min: 360
, ideal: 720
, max: 720
}
};
} else if (Firefox){
return {
width: {
ideal: 1280
}
, height: {
ideal: 720
}
};
} else {
return {
width: {
min: 720
, ideal: 1280
, max: 1280
}
, height: {
min: 720
, ideal: 720
, max: 1280
}
};
}
case 2:
if (isSafariBrowser) {
return {
width: {
min: 640
}
, height: {
min: 360
}
};
} else if (Firefox){
return {
width: {
ideal: 640
}
, height: {
ideal: 360
}
};
} else {
return {
width: {
min: 240
, ideal: 640
, max: 1280
}
, height: {
min: 240
, ideal: 360
, max: 1280
}
};
}
case 3:
if (isSafariBrowser) {
return {
width: {
min: 360
, ideal: 1280
, max: 1440
}
};
} else {
return {
width: {
min: 360
, ideal: 1280
, max: 1440
}
};
}
case 4:
if (isSafariBrowser) {
return {
height: {
min: 360
, ideal: 720
, max: 960
}
};
} else {
return {
height: {
ideal: 720
, max: 960
}
};
}
case 5:
if (isSafariBrowser) {
return {
width: {
min: 360
, ideal: 640
, max: 1440
}
, height: {
min: 360
, ideal: 360
, max: 720
}
};
} else {
return {
width: {
ideal: 640
, max: 1920
}
, height: {
ideal: 360
, max: 1920
}
}; // same as default, but I didn't want to mess with frameRates until I gave it all a try first
}
case 6:
if (isSafariBrowser) {
return {}; // iphone users probably don't need to wait any longer, so let them just get to it
} else {
return {
width: {
min: 360
, ideal: 640
, max: 3840
}
, height: {
min: 360
, ideal: 360
, max: 2160
}
};
}
case 7:
return { // If the camera is recording in low-light, it may have a low frameRate. It coudl also be recording at a very high resolution.
width: {
min: 360
, ideal: 640
}
, height: {
min: 360
, ideal: 360
}
, };
case 8:
return {
width: {
min: 360
}
, height: {
min: 360
}
, frameRate: 10
}; // same as default, but I didn't want to mess with frameRates until I gave it all a try first
case 9:
return {
frameRate: 0
}; // Some Samsung Devices report they can only support a frameRate of 0.
case 10:
return {}
default:
return {};
}
}
function addScreenDevices(device) {
if (device.kind == "audio") {
const audioInputSelect = getById('audioSource3');
const listele = document.createElement('li');
listele.style.display = "block";
const option = document.createElement('input');
option.type = "checkbox";
option.checked = true;
if (getById('multiselect-trigger3').dataset.state == 0) {
option.style.display = "none";
}
option.value = device.id;
option.name = device.label;
option.dataset.type = "screen";
option.label = device.label;
const label = document.createElement('label');
label.for = option.name;
label.innerHTML = " " + device.label;
listele.appendChild(option);
listele.appendChild(label);
option.onchange = function(event) { // make sure to clear 'no audio option' if anything else is selected
log("change 4644");
if (!CtrlPressed) {
document.querySelectorAll("#audioSource3 input[type='checkbox']").forEach(function(item) {
if (!item.value){return;}
if (event.currentTarget.value !== item.value) { // this shoulnd't happen, but if it does.
item.checked = false;
if (item.dataset.type == "screen") {
item.parentElement.parentElement.removeChild(item.parentElement);
}
while (SelectedAudioInputDevices.indexOf(item.value) > -1) {
SelectedAudioInputDevices.splice(SelectedAudioInputDevices.indexOf(item.value), 1);
}
activatedPreview = false;
grabAudio("#audioSource3"); // exclude item.id
} else {
if (SelectedAudioInputDevices.indexOf(item.value) == -1){
if (SelectedAudioInputDevices.length && SelectedAudioInputDevices.includes("ZZZ")){
SelectedAudioInputDevices = [];
}
SelectedAudioInputDevices.push(item.value);
}
item.checked = true;
activatedPreview = false;
grabAudio("#audioSource3", item.value); // exclude item.id. we will reconnect, even if already connected, as a way to 'reset' a device if it isn't working.
}
});
}
saveSettings();
event.stopPropagation();
return false;
};
audioInputSelect.appendChild(listele);
getById("audioSourceNoAudio2").checked = false;
} else if (device.kind == "video") {
const videoSelect = getById('videoSource3');
//const selectors = [ videoSelect];
//const values = selectors.map(select => select.value);
const option = document.createElement('option');
option.value = device.id;
option.text = device.label;
option.selected = "true";
option.label = device.label;
videoSelect.appendChild(option);
}
}
var gotDevices2AlreadyRan = false;
function gotDevices2(deviceInfos) {
gotDevices2AlreadyRan=true;
log("got devices!2");
log(deviceInfos);
getById("multiselect-trigger3").dataset.state = "0";
getById("multiselect-trigger3").classList.add('closed');
getById("multiselect-trigger3").classList.remove('open');
getById("chevarrow2").classList.add('bottom');
if (!session.streamSrc){
checkBasicStreamsExist();
}
var knownTrack = false;
try {
const audioInputSelect = getById('audioSource3');
const videoSelect = getById('videoSource3');
const audioOutputSelect = getById('outputSource3');
const selectors = [videoSelect];
[audioInputSelect].forEach(select => {
while (select.firstChild) {
select.removeChild(select.firstChild);
}
});
const values = selectors.map(select => select.value);
selectors.forEach(select => {
while (select.firstChild) {
select.removeChild(select.firstChild);
}
});
[audioOutputSelect].forEach(select => {
while (select.firstChild) {
select.removeChild(select.firstChild);
}
});
var counter = 0;
for (let i = 0; i !== deviceInfos.length; ++i) {
const deviceInfo = deviceInfos[i];
if (deviceInfo == null) {
continue;
}
if (deviceInfo.kind === 'audioinput') {
var option = document.createElement('input');
option.type = "checkbox";
counter++;
var listele = document.createElement('li');
listele.style.display = "none";
session.streamSrc.getAudioTracks().forEach(function(track) {
if (deviceInfo.label == track.label) {
option.checked = true;
listele.style.display = "inherit";
}
});
option.style.display = "none"
option.value = deviceInfo.deviceId || "default";
option.name = "multiselecta" + counter;
option.id = "multiselecta" + counter;
option.dataset.label = deviceInfo.label || ("microphone " + ((audioInputSelect.length || 0) + 1));
var label = document.createElement('label');
label.for = option.name;
label.innerHTML = " " + (deviceInfo.label || ("microphone " + ((audioInputSelect.length || 0) + 1)));
listele.appendChild(option);
listele.appendChild(label);
audioInputSelect.appendChild(listele);
option.onchange = function(event) { // make sure to clear 'no audio option' if anything else is selected
log("change 4768");
if (!(CtrlPressed)) {
document.querySelectorAll("#audioSource3 input[type='checkbox']").forEach(function(item) {
if (event.currentTarget.value !== item.value) {
item.checked = false;
if (item.dataset.type == "screen") {
item.parentElement.parentElement.removeChild(item.parentElement);
}
while (SelectedAudioInputDevices.indexOf(item.value) > -1) {
SelectedAudioInputDevices.splice(SelectedAudioInputDevices.indexOf(item.value), 1);
}
} else {
item.checked = true;
if (SelectedAudioInputDevices.indexOf(event.currentTarget.value) == -1){
if (SelectedAudioInputDevices.length && SelectedAudioInputDevices.includes("ZZZ")){
SelectedAudioInputDevices = [];
}
SelectedAudioInputDevices.push(event.currentTarget.value);
}
}
});
} else {
if (SelectedAudioInputDevices.indexOf(event.currentTarget.value) == -1){
if (SelectedAudioInputDevices.length && SelectedAudioInputDevices.includes("ZZZ")){
SelectedAudioInputDevices = [];
}
SelectedAudioInputDevices.push(event.currentTarget.value);
}
getById("audioSourceNoAudio2").checked = false;
}
saveSettings();
};
} else if (deviceInfo.kind === 'videoinput') {
var option = document.createElement('option');
option.value = deviceInfo.deviceId || "default";
option.text = deviceInfo.label || `camera ${videoSelect.length + 1}`;
try {
if (!knownTrack && session.canvasSource){
session.canvasSource.srcObject.getVideoTracks().forEach(function(track) {
if (option.text == track.label) {
option.selected = "true";
knownTrack = true;
}
});
}
if (!knownTrack && session.streamSrc){
session.streamSrc.getVideoTracks().forEach(function(track) {
if (option.text == track.label) {
option.selected = "true";
knownTrack = true;
}
});
}
} catch (e) {
errorlog(e);
}
videoSelect.appendChild(option);
} else if (deviceInfo.kind === 'audiooutput') {
var option = document.createElement('option');
if (audioOutputSelect.length === 0) {
option.dataset.default = true;
} else {
option.dataset.default = false;
}
option.value = deviceInfo.deviceId || "default";
if (option.value == session.sink) {
option.selected = "true";
} else if (!session.sink && SelectedAudioOutputDevices && (SelectedAudioOutputDevices == option.value)){
option.selected = "true";
session.sink = option.value; // added 8-dec-22, as the director's saved mic wasn't applying otherwise.
}
option.text = deviceInfo.label || `Speaker ${audioOutputSelect.length + 1}`;
audioOutputSelect.appendChild(option);
} else {
log('Some other kind of source/device: ', deviceInfo);
}
}
if (Firefox && !session.mobile){
var option = document.createElement('option');
option.value = "others";
option.text = "Show more options";
audioOutputSelect.appendChild(option);
}
if (audioOutputSelect.childNodes.length == 0) {
var option = document.createElement('option');
option.value = "default";
option.text = "System Default";
audioOutputSelect.appendChild(option);
}
if (videoSelect.childNodes.length <= 1) {
getById("flipcamerabutton").style.display = "none"; // don't show the camera cycle button
getById("flipcamerabutton").dataset.maxndex = videoSelect.childNodes.length;
} else {
getById("flipcamerabutton").style.display = "unset";
getById("flipcamerabutton").dataset.maxIndex = videoSelect.childNodes.length;
}
////////////
session.streamSrc.getAudioTracks().forEach(function(track) { // add active ScreenShare audio tracks to the list
log("Checking for screenshare audio");
var matched = false;
for (var i = 0; i !== deviceInfos.length; ++i) {
var deviceInfo = deviceInfos[i];
if (deviceInfo == null) {
continue;
}
log("---");
if (track.label == deviceInfo.label) {
matched = true;
continue;
}
}
if (matched == false) { // Not a gUM device
var listele = document.createElement('li');
listele.style.display = "block";
var option = document.createElement('input');
option.type = "checkbox";
option.value = track.id;
option.checked = true;
option.style.display = "none";
option.name = track.label;
option.label = track.label;
option.dataset.type = "screen";
var label = document.createElement('label');
label.for = option.name;
label.innerHTML = " " + track.label;
listele.appendChild(option);
listele.appendChild(label);
option.onchange = function(event) { // make sure to clear 'no audio option' if anything else is selected
log("change 4873");
var trackid = null;
if (!(CtrlPressed)) {
document.querySelectorAll("#audioSource3 input[type='checkbox']").forEach(function(item) {
if (event.currentTarget.value !== item.value) { // this shoulnd't happen, but if it does.
item.checked = false;
if (item.dataset.type == "screen") {
item.parentElement.parentElement.removeChild(item.parentElement);
}
} else {
event.currentTarget.checked = true;
trackid = item.value;
}
});
} else {
//getById("audioSourceNoAudio2").checked=false;
if (event.currentTarget.dataset.type == "screen") {
event.currentTarget.parentElement.parentElement.removeChild(event.currentTarget.parentElement);
}
}
activatedPreview = false;
grabAudio("#audioSource3", trackid); // exclude item.id.
event.stopPropagation();
return false;
};
audioInputSelect.appendChild(listele);
}
});
/////////// no video option
var optionss = false;
if (screensharesupport) {
optionss = document.createElement('option');
optionss.text = "Screen Share (replace camera)";
optionss.value = "XXX";
videoSelect.appendChild(optionss); // NO AUDIO OPTION
}
var option = document.createElement('option'); // no video
option.text = "Disable Video";
option.value = "ZZZ";
videoSelect.appendChild(option);
if (session.streamSrc.getVideoTracks().length == 0) {
option.selected = "true";
} else if (knownTrack == false) {
var option = document.createElement('option'); // no video
option.text = session.streamSrc.getVideoTracks()[0].label;
option.value = "YYY";
videoSelect.appendChild(option);
option.selected = "true";
}
if (optionss) {
optionss.lastSelected = videoSelect.selectedIndex;
}
videoSelect.onchange = function(event) {
try {
if (event.target.options[event.target.options.selectedIndex].value === "XXX") {
videoSelect.selectedIndex = event.target.options[event.target.options.selectedIndex].lastSelected;
if (session.screenShareState == false) {
toggleScreenShare();
} else {
toggleScreenShare(true);
}
return;
}
} catch (e) {}
activatedPreview = false;
grabVideo(session.quality, "videosource", "select#videoSource3");
if (!(getById('audioSource3').querySelectorAll("input[data-type='screen']").length)){
if (session.screenShareState){
session.screenShareState = false;
pokeIframeAPI('screen-share-state', session.screenShareState, null, session.streamID);
notifyOfScreenShare();
//session.refreshScale();
}
getById("screensharebutton").classList.remove("green");
getById("screensharebutton").ariaPressed = "false";
}
};
///////////// /// NO AUDIO appended option
var option = document.createElement('input');
option.type = "checkbox";
option.value = "ZZZ";
option.style.display = "none"
option.id = "audioSourceNoAudio2";
var label = document.createElement('label');
label.for = option.name;
label.innerHTML = " No Audio";
var listele = document.createElement('li');
if (session.streamSrc.getAudioTracks().length == 0) {
option.checked = true;
} else {
listele.style.display = "none";
option.checked = false;
}
option.onchange = function(event) { // make sure to clear 'no audio option' if anything else is selected
log("change 4938");
if (!(CtrlPressed)) {
document.querySelectorAll("#audioSource3 input[type='checkbox']").forEach(function(item) {
if (event.currentTarget.value !== item.value) {
item.checked = false;
if (item.dataset.type == "screen") {
item.parentElement.parentElement.removeChild(item.parentElement);
}
while (SelectedAudioInputDevices.indexOf(item.value) > -1) {
SelectedAudioInputDevices.splice(SelectedAudioInputDevices.indexOf(item.value), 1);
}
} else {
item.checked = true;
if (SelectedAudioInputDevices.indexOf(event.currentTarget.value) == -1){
if (SelectedAudioInputDevices.length && SelectedAudioInputDevices.includes("ZZZ")){
SelectedAudioInputDevices = [];
}
SelectedAudioInputDevices.push(event.currentTarget.value);
}
}
});
} else {
document.querySelectorAll("#audioSource3 input[type='checkbox']").forEach(function(item) {
if (event.currentTarget.value === item.value) {
event.currentTarget.checked = true;
if (SelectedAudioInputDevices.indexOf(event.currentTarget.value) == -1){
if (SelectedAudioInputDevices.length && SelectedAudioInputDevices.includes("ZZZ")){
SelectedAudioInputDevices = [];
}
SelectedAudioInputDevices.push(event.currentTarget.value);
}
} else {
item.checked = false;
if (item.dataset.type == "screen") {
item.parentElement.parentElement.removeChild(item.parentElement);
}
while (SelectedAudioInputDevices.indexOf(item.value) > -1) {
SelectedAudioInputDevices.splice(SelectedAudioInputDevices.indexOf(item.value), 1);
}
}
});
}
saveSettings();
};
listele.appendChild(option);
listele.appendChild(label);
audioInputSelect.appendChild(listele);
////////////
//selectors.forEach((select, selectorIndex) => {
// if (Array.prototype.slice.call(select.childNodes).some(n => n.value === values[selectorIndex])) {
// select.value = values[selectorIndex];
// }
//});
audioInputSelect.onchange = function() {
log("Audio OPTION HAS CHANGED? 2");
activatedPreview = false;
setTimeout(function(){
grabAudio("#audioSource3");
},10)
};
getById("refreshVideoButton").onclick = function() {
refreshVideoDevice();
};
if (Firefox && !session.mobile){
audioOutputSelect.onclick = function() {
log("audioOutputSelect.onclick = function() {");
if (audioOutputSelect.options[audioOutputSelect.selectedIndex].value === "others"){
log("Trying to increase the output device list");
navigator.mediaDevices.selectAudioOutput().then((device) => {
if (device.kind == "audiooutput"){
session.sink = device.deviceId;
try {
var matched = false;
audioOutputSelect.childNodes.forEach(ele =>{
if (ele.value === device.deviceId){
matched = true;
ele.selected = true;
}
})
if (!matched){
var option = document.createElement('option');
option.value = device.deviceId;
option.text = device.label;
audioOutputSelect.appendChild(option);
option.selected = true;
}
saveSettings(); // we're saving because there was an explicit action to change devices
} catch(e){errorlog(e);}
if (!session.sink){return;} // Not sure this would ever happen, but whatever.
resetupAudioOut(); // we'll probalby use session.sink, since outputSelect3 doesn't exist.
}
});
}
}
}
audioOutputSelect.onchange = function() {
log("audioOutputSelect.onchange = function() {");
if ((iOS) || (iPad)) {
return;
}
if (Firefox && !session.mobile){
if (audioOutputSelect.options[audioOutputSelect.selectedIndex].value === "others"){ // we handle this elsewhere
return;
}
}
try {
session.sink = audioOutputSelect.options[audioOutputSelect.selectedIndex].value;
saveSettings();
} catch (e) {
errorlog(e);
}
if (!session.sink){return;}
resetupAudioOut();
log("done audioOutputSelect.onchange = function() {");
}
} catch (e) {
errorlog(e);
}
}
function refreshVideoDevice(){
if (session.screenShareState) {
log("can't refresh a screenshare");
return;
}
log("video source changed");
activatedPreview = false;
grabVideo(session.quality, "videosource", "select#videoSource3");
}
function gotDevicesRemote(deviceInfos, UUID) {
try {
if (document.getElementById("remoteVideoSelect_"+UUID)){
var videoSelect = document.getElementById("remoteVideoSelect_"+UUID);
var length = videoSelect.options.length;
for (i = length-1; i >= 0; i--) {
videoSelect.options[i] = null;
}
} else {
var videoSelect = document.createElement("select");
videoSelect.id = "remoteVideoSelect_"+UUID;
videoSelect.onchange = function(){
if (session.rpcs[UUID].stats.info && session.rpcs[UUID].stats.info.consent){
getById("requestVideoDevice_"+UUID).innerHTML = ' apply';
getById("requestVideoDevice_"+UUID).title = "This will update the remote device to the selected one";
} else {
getById("requestVideoDevice_"+UUID).innerHTML = ' request';
getById("requestVideoDevice_"+UUID).title = "This will ask the remote guest for permission to change";
}
}
var buttonGO = document.createElement("button");
buttonGO.innerHTML = ' refresh';
buttonGO.title = "This will refresh the current device";
buttonGO.id = "requestVideoDevice_"+UUID;
buttonGO.onclick = function(){
var data = {}
data.changeCamera = videoSelect.value;
data.UUID = UUID;
session.sendRequest(data, UUID); // Viewer is requesting the PUBLISHER
};
var videoSelectDiv = document.createElement("div");
query("#container_"+UUID+" .advancedVideoSettings").appendChild(videoSelectDiv);
videoSelectDiv.appendChild(videoSelect);
videoSelectDiv.appendChild(buttonGO);
}
if (document.getElementById("remoteAudioSelect_"+UUID)){
log("remoteAudioSelect_ ");
var audioSelect = document.getElementById("remoteAudioSelect_"+UUID);
var length = audioSelect.options.length;
for (i = length-1; i >= 0; i--) {
audioSelect.options[i] = null;
}
} else {
var audioSelect = document.createElement("select");
audioSelect.id = "remoteAudioSelect_"+UUID;
audioSelect.onchange = function(){
log("ON CHANGE");
if (session.rpcs[UUID].stats.info && session.rpcs[UUID].stats.info.consent){
getById("requestAudioDevice_"+UUID).innerHTML = ' apply';
getById("requestAudioDevice_"+UUID).title = "This will update the remote device to the selected one";
} else {
getById("requestAudioDevice_"+UUID).innerHTML = ' request';
getById("requestAudioDevice_"+UUID).title = "This will ask the remote guest for permission to change";
}
}
var buttonGO = document.createElement("button");
buttonGO.innerHTML = ' refresh';
// buttonGO.style = "padding: 5px;";
buttonGO.title = "This will refresh the current device";
buttonGO.id = "requestAudioDevice_"+UUID;
buttonGO.onclick = function(){
var data = {}
data.changeMicrophone = audioSelect.value;
data.UUID = UUID;
session.sendRequest(data, UUID); // Viewer is requesting the PUBLISHER
}
var audioSelectDiv = document.createElement("div");
query("#container_"+UUID+" .advancedAudioSettings").appendChild(audioSelectDiv);
audioSelectDiv.appendChild(audioSelect);
audioSelectDiv.appendChild(buttonGO);
}
if (document.getElementById("remoteAudioOutputSelect_"+UUID)){
var audioOutputSelect = document.getElementById("remoteAudioOutputSelect_"+UUID);
var length = audioOutputSelect.options.length;
for (i = length-1; i >= 0; i--) {
audioOutputSelect.options[i] = null;
}
} else {
var audioOutputSelect = document.createElement("select");
audioOutputSelect.id = "remoteAudioOutputSelect_"+UUID;
audioOutputSelect.onchange = function(){
if (session.rpcs[UUID].stats.info && session.rpcs[UUID].stats.info.consent){
getById("requestAudioOutputDevice_"+UUID).innerHTML = ' apply';
getById("requestAudioOutputDevice_"+UUID).title = "This will update the remote device to the selected one";
} else {
getById("requestAudioOutputDevice_"+UUID).innerHTML = ' request';
getById("requestAudioOutputDevice_"+UUID).title = "This will ask the remote guest for permission to change";
}
}
var buttonGO = document.createElement("button");
buttonGO.innerHTML = ' refresh';
buttonGO.title = "This will refresh the current device";
buttonGO.id = "requestAudioOutputDevice_"+UUID;
buttonGO.onclick = function(){
var data = {}
data.changeSpeaker = audioOutputSelect.value;
data.UUID = UUID;
session.sendRequest(data, UUID); // Viewer is requesting the PUBLISHER
}
var audioOutputSelectContainer = document.createElement("div");
query("#container_"+UUID+" .advancedAudioSettings").appendChild(audioOutputSelectContainer);
audioOutputSelectContainer.appendChild(audioOutputSelect);
audioOutputSelectContainer.appendChild(buttonGO);
}
var matched = false;
for (let i = 0; i !== deviceInfos.length; ++i) {
const deviceInfo = deviceInfos[i];
if (deviceInfo == null) {
continue;
}
if (deviceInfo.kind === 'videoinput'){
const option = document.createElement('option');
option.value = deviceInfo.deviceId || "default";
option.text = deviceInfo.label || `camera ${videoSelect.length + 1}`;
if (getById("remoteVideoLabel_"+UUID).innerText == option.text){
option.selected = "true";
matched = true;
}
videoSelect.appendChild(option);
} else if (deviceInfo.kind === 'audioinput'){
const option = document.createElement('option');
option.value = deviceInfo.deviceId || "default";
option.text = deviceInfo.label || `microphone ${audioSelect.length + 1}`;
if (getById("remoteAudioLabel_"+UUID).innerText == option.text){
option.selected = "true";
}
audioSelect.appendChild(option);
} else if (deviceInfo.kind === 'audiooutput'){
const option = document.createElement('option');
option.value = deviceInfo.deviceId || "default";
option.text = deviceInfo.label || `microphone ${audioOutputSelect.length + 1}`;
if (getById("remoteAudioOutputSelect_"+UUID).innerText == option.text){
option.selected = "true";
}
audioOutputSelect.appendChild(option);
}
}
if (!matched){
getById("requestVideoDevice_"+UUID).innerHTML = ' request';
getById("requestVideoDevice_"+UUID).title = "This will ask the remote guest for permission to change";
} else {
getById("requestVideoDevice_"+UUID).innerHTML = ' refresh';
getById("requestVideoDevice_"+UUID).title = "This will reconnect the guest's active video source.";
}
} catch(e){errorlog(e);}
pokeIframeAPI("remote-devices-info", deviceInfos, UUID);
}
var timeoutTone = false;
function playtone(screen = false, tonename="testtone") {
if (timeoutTone){return;}
setTimeout(function(){
timeoutTone = false;
},500);
timeoutTone = true;
if ((iOS) || (iPad)) {
// try{
// session.audioContext.resume();
// } catch(e){errorlog(e);}
var toneEle = document.getElementById(tonename);
if (toneEle) {
toneEle.mute
toneEle.play();
}
return;
}
if (screen) {
try{
var outputSelect = getById('outputSourceScreenshare');
if (outputSelect){
session.sink = outputSelect.options[outputSelect.selectedIndex].value;
saveSettings();
}
} catch(e){errorlog(e);}
}
var toneEle = document.getElementById(tonename);
if (toneEle) {
if (session.sink) {
try {
toneEle.setSinkId(session.sink).then(() => { // TODO: iOS doens't support sink. Needs to bypass if IOS
log("changing audio sink:" + session.sink);
toneEle.play();
}).catch(error => {
errorlog(error);
});
} catch (e) {
warnlog(e); // firefox?
toneEle.play();
}
} else {
toneEle.play();
}
}
}
function showNotification(title, body="") {
if (!Notification){return;}
if (Notification.permission !== 'granted') {
Notification.requestPermission();
} else {
let icon = '/media/old_icon.png'; //this is a large image may take more time to show notifiction, replace with small size icon
let notification = new Notification(title, { body, icon });
notification.onclick = () => {
notification.close();
window.parent.focus();
}
}
}
async function getAudioOnly(selector, trackid = null, override = false) {
var audioSelect = document.querySelector(selector).querySelectorAll("input");
var audioList = [];
var streams = [];
log("getAudioOnly()");
for (var i = 0; i < audioSelect.length; i++) {
if (audioSelect[i].value == "ZZZ") {
continue;
} else if (trackid == audioSelect[i].value) { // skip already excluded
continue;
} else if ("screen" == audioSelect[i].dataset.type) { // skip already excluded ---------- !!!!!! DOES THIS MAKE SENSE? TODO: CHECK
continue;
} else if (audioSelect[i].checked) {
log(audioSelect[i]);
audioList.push(audioSelect[i]);
}
}
for (var i = 0; i < audioList.length; i++) {
if ((session.echoCancellation !== false) && (session.autoGainControl !== false) && (session.noiseSuppression !== false)) {
var constraint = {
audio: {
deviceId: {
exact: audioList[i].value
}
}
};
} else { // Just trying to avoid problems with some systems that don't support these features
var constraint = {
audio: {
deviceId: {
exact: audioList[i].value
}
}
};
if (session.echoCancellation === false) {
constraint.audio.echoCancellation = false;
} else {
constraint.audio.echoCancellation = true;
}
if (session.autoGainControl === false) {
constraint.audio.autoGainControl = false;
} else {
constraint.audio.autoGainControl = true;
}
if (session.noiseSuppression === false) {
constraint.audio.noiseSuppression = false;
} else {
constraint.audio.noiseSuppression = true;
}
}
constraint.video = false;
if (override !== false) {
if (override.audio && override.audio.deviceId){
if (audioList[i].value == override.audio.deviceId){
constraint = override;
} else {
// not the device we want to hack.
}
} else {
constraint = override;
}
}
if (audioList[i].value && SelectedAudioInputDevices){
if (SelectedAudioInputDevices.indexOf(audioList[i].value) === -1) {
if (SelectedAudioInputDevices.length && SelectedAudioInputDevices.includes("ZZZ")){
SelectedAudioInputDevices = [];
}
SelectedAudioInputDevices.push(audioList[i].value);
}
}
if (session.audioInputChannels) {
if (constraint.audio === true) {
constraint.audio = {};
constraint.audio.channelCount = session.audioInputChannels;
} else if (constraint.audio) {
constraint.audio.channelCount = session.audioInputChannels;
}
}
if (session.micSampleRate){
if (constraint.audio === true) {
constraint.audio = {};
constraint.audio.sampleRate = parseInt(session.micSampleRate);
} else if (constraint.audio) {
constraint.audio.sampleRate = parseInt(session.micSampleRate);
}
}
log("CONSTRAINT");
log(constraint);
var stream = await navigator.mediaDevices.getUserMedia(constraint).then(function(stream2) {
pokeIframeAPI("local-microphone-event");
return stream2;
}).catch(function(err) {
warnlog(err);
if (!(session.cleanOutput)) {
if (override !== false) {
if (err.name) {
if (err.constraint) {
warnUser(err['name'] + ": " + err['constraint']);
}
}
}
}
}); // More error reporting maybe?
if (stream) {
streams.push(stream);
}
}
return streams;
}
function applyMirror(mirror) { // true unmirrors as its already mirrored
if (!session.videoElement){return;}
try {
var transFlip = "";
var transNorm = "";
if (document.getElementById('videosource') && (session.windowed)) {
transFlip = " translate(0, 50%)";
transNorm = " translate(0, -50%)";
}
if (session.mirrored == 2) {
mirror = true;
} else if (session.mirrored === 0) {
mirror = true;
}
if (!session.videoElement.style){
session.videoElement.style = "";
}
if (session.permaMirrored){
mirror = !mirror;
}
if (mirror) {
if (session.mirrored && session.flipped) {
session.videoElement.style.transform = "scaleX(-1) scaleY(-1)" + transFlip;
session.videoElement.classList.add("mirrorControl");
} else if (session.mirrored) {
session.videoElement.style.transform = "scaleX(-1)" + transNorm;
session.videoElement.classList.add("mirrorControl");
} else if (session.flipped) {
session.videoElement.style.transform = "scaleY(-1) scaleX(1)" + transFlip;
session.videoElement.classList.remove("mirrorControl");
} else {
session.videoElement.style.transform = "scaleX(1)" + transNorm;
session.videoElement.classList.remove("mirrorControl");
}
} else {
if (session.mirrored && session.flipped) {
session.videoElement.style.transform = "scaleX(1) scaleY(-1)" + transFlip;
session.videoElement.classList.remove("mirrorControl");
} else if (session.mirrored) {
session.videoElement.style.transform = "scaleX(1)" + transNorm;
session.videoElement.classList.remove("mirrorControl");
} else if (session.flipped) {
session.videoElement.style.transform = "scaleY(-1) scaleX(-1)" + transFlip;
session.videoElement.classList.add("mirrorControl");
} else {
session.videoElement.style.transform = "scaleX(-1)" + transNorm;
session.videoElement.classList.add("mirrorControl");
}
}
var rotate = 0;
if (session.forceRotate!==false){
if (session.rotate){
rotate = (session.forceRotate * -1) + parseInt(session.rotate);
} else {
rotate = session.forceRotate * -1;
}
if (session.forceRotate){
rotate+=180;
}
} else {
rotate = session.rotate;
}
if (rotate && (rotate>=360)){
rotate-=360;
}
session.videoElement.rotated = rotate;
if (document.getElementById("previewWebcam") || document.getElementById('videosource')){
var eleName = document.getElementById("previewWebcam") || document.getElementById('videosource');
if (rotate){
if (eleName.style.transform){
eleName.style.transform += " rotate("+rotate+"deg)";
} else {
eleName.style.transform = "rotate("+rotate+"deg)";
}
eleName.classList.add("rotate");
} else {
eleName.classList.remove("rotate");
}
} else if (document.getElementById("container")){
if (rotate==0){
document.getElementById("container").classList.remove("rotate");
document.getElementById("container").style.transform = "unset";
document.getElementById("container").style.transformOrigin = "unset";
} else {
document.getElementById("container").style.transform = "rotate("+rotate+"deg)";
}
} else if (document.getElementById("minipreview")){
var eleName = document.getElementById("minipreview");
if (rotate==90 ){
eleName.style.transform = "rotate(90deg)";
eleName.style.transformOrigin = "50% 100%";
eleName.style.height = eleName.style.width;
eleName.style.width = "unset";
} else if (session.videoElement.rotated==270){
eleName.style.transform = "rotate(270deg)";
eleName.style.transformOrigin = "50% 100%";
eleName.style.width = "unset";
eleName.style.height = eleName.style.width;
} else if (session.videoElement.rotated==180){
eleName.style.transform = "rotate(180deg)";
eleName.style.transformOrigin = "unset";
} else {
eleName.classList.remove("rotate");
eleName.style.transform = "unset";
eleName.style.transformOrigin = "unset";
}
}
// if not one of these, then it's going to be handled by the automixer automatically for us.
} catch(e){errorlog(e);}
}
function applyMirrorGuest(mirror, videoElement) { // true unmirrors as its already mirrored
try {
if (mirror) {
videoElement.style.transform = "scaleX(-1)";
videoElement.classList.add("mirrorControl");
} else {
videoElement.style.transform = "scaleX(1)";
videoElement.classList.remove("mirrorControl");
}
} catch(e){errorlog(e);}
}
function cleanupMediaTracks() {
getUserMediaRequestID += 1;
try {
if (session.streamSrcClone){
session.streamSrcClone.getTracks().forEach(function(track) {
session.streamSrcClone.removeTrack(track);
track.stop();
log("stopping old track");
});
}
if (session.streamSrc) {
session.streamSrc.getTracks().forEach(function(track) {
session.streamSrc.removeTrack(track);
track.stop();
log("stopping old track");
});
} else {
checkBasicStreamsExist();
}
if (session.videoElement && session.videoElement.srcObject) {
session.videoElement.srcObject.getTracks().forEach(function(track) {
session.videoElement.srcObject.removeTrack(track);
track.stop();
log("stopping old track");
});
} else {
session.videoElement.srcObject = createMediaStream();
}
activatedPreview = false;
} catch (e) {
errorlog(e);
}
}
/// Detect system changes; handle change or use for debugging
var lastAudioDevice = null;
var lastVideoDevice = null;
var lastPlaybackDevice = null;
var audioReconnectTimeout = null;
var videoReconnectTimeout = null;
var grabDevicesTimeout = null;
var playbackReconnectTimeout = null;
function reconnectDevices(event) { /// TODO: Perhaps change this to only if there is a DISCONNECT; rather than ON NEW DEVICE?
try {
if (session.firstPlayTriggered && (session.audioCtx.state == "suspended")){
session.audioCtx.resume();
}
} catch(e){warnlog("session.audioCtx.resume(); failed");}
warnlog("A media device has changed");
if ((iOS) || (iPad)) { // consider adding this back, but if no problem, whatever.
return;
}
if (document.getElementById("previewWebcam")) { // rest of the code isn't setup to support pre-connection setup.
clearTimeout(playbackReconnectTimeout);
playbackReconnectTimeout = setTimeout(function(){
if (document.getElementById("previewWebcam")){
enumerateDevices().then(gotDevices).then(function(){
if (document.getElementById("previewWebcam")){
resetupAudioOut(document.getElementById("previewWebcam"));
}
});
}
}, 1000);
return;
}
try {
if (!session.streamSrc){
checkBasicStreamsExist();
} else {
session.streamSrc.getTracks().forEach(function(track) {
if (track.readyState == "ended") {
if (track.kind == "audio") {
lastAudioDevice = track.label;
} else if (track.kind == "video") {
lastVideoDevice = track.label;
}
session.streamSrc.removeTrack(track);
log("remove ended old track");
}
});
if (session.streamSrcClone){
session.streamSrcClone.getTracks().forEach(function(track) {
if (track.readyState == "ended") {
session.streamSrcClone.removeTrack(track);
}
});
}
if (session.videoElement && session.videoElement.srcObject){
session.videoElement.srcObject.getTracks().forEach(function(track) {
if (track.readyState == "ended") {
session.videoElement.srcObject.removeTrack(track);
log("remove ended old track");
}
});
}
}
/* } else {
clearTimeout(playbackReconnectTimeout);
playbackReconnectTimeout = setTimeout(function() {
enumerateDevices().then(gotDevices2).then(function() {
resetupAudioOut();
});
}, 1000);
return;
} */
} catch (e) {
errorlog(e);
}
clearTimeout(audioReconnectTimeout);
audioReconnectTimeout = null;
if (lastAudioDevice) {
audioReconnectTimeout = setTimeout(function() { // only reconnect same audio device. If reconnected, clear the disconnected flag.
enumerateDevices().then(gotDevices2).then(function() {
// TODO: check to see if any audio is connected?
var streamConnected = false;
var audioSelect = getById("audioSource3").querySelectorAll("input");
for (var i = 0; i < audioSelect.length; i++) {
if (audioSelect[i].value == "ZZZ") {
continue;
} else if (audioSelect[i].checked) {
log("checked");
streamConnected = true;
break;
}
}
if (!streamConnected) {
for (var i = 0; i < audioSelect.length; i++) {
if (audioSelect[i].value == "ZZZ") {
continue;
}
if (lastAudioDevice == audioSelect[i].dataset.label) { // if the last disconnected device matches.
audioSelect[i].checked = true;
streamConnected = true;
lastAudioDevice = null;
warnlog("DISCONNECTED AUDIO DEVICE RECONNECTED");
break;
}
}
}
activatedPreview = false;
grabAudio("#audioSource3");
setTimeout(function() {
enumerateDevices().then(gotDevices2).then(function() {
});
}, 1000);
});
}, 2000);
}
clearTimeout(videoReconnectTimeout); // only reconnect same video device.
videoReconnectTimeout = null;
if (lastVideoDevice) {
videoReconnectTimeout = setTimeout(function() {
enumerateDevices().then(gotDevices2).then(function() {
var streamConnected = false;
var videoSelect = getById("videoSource3");
errorlog(videoSelect.value);
if (videoSelect.value == "ZZZ") {
for (var i = 0; i < videoSelect.options.length; i++) {
try {
if (videoSelect.options[i].innerHTML == lastVideoDevice) {
videoSelect.options[i].selected = "true";
streamConnected = true;
lastVideoDevice = null;
break;
}
} catch (e) {
errorlog(e);
}
}
}
if (streamConnected) {
activatedPreview = false;
grabVideo(session.quality, "videosource", "select#videoSource3");
setTimeout(function() {
enumerateDevices().then(gotDevices2).then(function() {});
}, 1000);
}
});
}, 2000);
}
clearTimeout(playbackReconnectTimeout);
playbackReconnectTimeout = setTimeout(function() {
enumerateDevices().then(gotDevices2).then(function() {
resetupAudioOut();
});
}, 1000);
}
var vingesterFixed = false;
function resetupAudioOut(ele=false, forceReset=false) { // this re-sets ALL output devices / sources
log("resetupAudioOut");
if (iOS || iPad || SafariVersion || (ChromeVersion && session.mobile)) { // TODO : TEST TO SEE IF THIS WORKS WITH SAFARI? it might.
if (ele){return;}
for (var UUID in session.rpcs) {
if (session.rpcs[UUID].videoElement){
try{
session.rpcs[UUID].videoElement.pause().then(() => {
setTimeout(function(uuid) {
log("win");
try{
session.rpcs[uuid].videoElement.play().then(() => {
// updateIncomingAudioElement(uuid); // DO NOT DO THIS; it would cause a loop, as this updateIncomingAudioElement calls reseet
log("toggle pause/play");
});
} catch(e){errorlog(e);}
}, 0, UUID);
}).catch(errorlog);
} catch(e){warnlog(e);}
}
}
return;
}
//if (isVingester){return;}
var outputSelect = document.getElementById("outputSource") || document.getElementById("outputSource3") || false;
var sinkSet = false;
try { // function tries to get vingester working, by listening for its first tweak.
if (!session.sink && !vingesterFixed && document.getElementById("testtone") && document.getElementById("testtone").sinkId && isVingester){
vingesterFixed = true; // only set the session.sink one time, as vingester on should be needing to set it one time.
session.sink = document.getElementById("testtone").sinkId;
}
} catch(e){
errorlog(e);
}
if (outputSelect && outputSelect.options && outputSelect.options.length){
for (var i = 0; i < outputSelect.options.length; i++) {
if (outputSelect.options[i].value == session.sink) {
outputSelect.options[i].selected = "true";
sinkSet = true;
}
}
if (sinkSet == false) {
if (outputSelect.options[0]) {
outputSelect.options[0].selected = "true";
sinkSet = outputSelect.value;
}
} else {
sinkSet = session.sink;
}
} else {
sinkSet = session.sink;
}
if (ele){
try {
var eleSink = sinkSet;
if (ele.manualSink){
eleSink = ele.manualSink;
log("Manual Sink Identified");
}
if (eleSink){
if (forceReset){
ele.setSinkId("default").then(() => {
ele.setSinkId(eleSink).then(() => {
log("New Output Device");
}).catch(error => {
if (!Firefox){
errorlog(error);
}
// TODO: If error, then see if I need to add mic support, and grab it if needed.
});
}).catch(error => {
if (!Firefox){
errorlog(error);
}
ele.setSinkId(eleSink).then(() => {
log("New Output Device");
}).catch(error => {
if (!Firefox){
errorlog(error);
}
// TODO: If error, then see if I need to add mic support, and grab it if needed.
});
});
}
ele.setSinkId(eleSink).then(() => {
log("New Output Device for self-preview");
}).catch(error => {
if (!Firefox){
warnlog("Need to add mic support, and grab it if needed.");
warnlog(error);
}
// TODO: If error, then see if I need to add mic support, and grab it if needed.
});
}
} catch(e){warnlog("can't use setsink");}
log("audio sink: "+eleSink);
return;
}
if (session.videoElement){ // this would be a preview or videosource
try {
var eleSink = sinkSet;
if (session.videoElement.manualSink){
eleSink = session.videoElement.manualSink;
}
if (eleSink){
session.videoElement.setSinkId(eleSink).then(() => {
log("New Output Device for self-preview");
}).catch(error => {
if (!Firefox){
errorlog(error);
}
// TODO: If error, then see if I need to add mic support, and grab it if needed.
});
}
} catch(e){warnlog("can't use setsink");}
}
for (UUID in session.rpcs) {
try{
if (session.rpcs[UUID].videoElement){
var eleSink = sinkSet;
if (session.rpcs[UUID].videoElement.manualSink){
eleSink = session.rpcs[UUID].videoElement.manualSink;
}
if (eleSink){
session.rpcs[UUID].videoElement.setSinkId(eleSink).then(() => {
log("New Output Device for: " + UUID);
}).catch(error => {
if (!Firefox){
errorlog(error);
}
// TODO: If error, then see if I need to add mic support, and grab it if needed.
});
}
}
} catch(e){warnlog(e);}
}
log("audio sink 2: "+eleSink);
}
function lyraDecode(receiver){ // WIP. does not work
warnlog("lyraDecode");
const receiverStreams = receiver.createEncodedStreams();
var xx = receiverStreams.readable;
xx.pipeThrough(new TransformStream());
xx.pipeTo(receiverStreams.writable);
}
function lyraEncode(UUID){ // WIP. does not work
warnlog("lyraEncode");
var senders = getSenders2(UUID);
senders.forEach((sender) => {
if (!sender.track){return;}
if (sender.track.kind === "audio") {
try {
var senderStreams = sender.createEncodedStreams();
senderStreams.readable.pipeThrough(new TransformStream()).pipeTo(senderStreams.writable);
} catch (e){
errorlog(e);
}
}
});
}
///////// WIP. does not work ////////////
// function encodeFunctionLyra(encodedFrame, controller) { // Snippet is Apache 2.0 licenced. Source: https://github.com/Flash-Meeting/lyra-webrtc
// const inputDataArray = new Uint8Array(encodedFrame.data);
// const inputBufferPtr = session.lyraCodecModule._malloc(encodedFrame.data.byteLength);
// const encodedBufferPtr = session.lyraCodecModule._malloc(1024);
// session.lyraCodecModule.HEAPU8.set(inputDataArray, inputBufferPtr);
// const length = session.lyraCodecModule.encode(inputBufferPtr, inputDataArray.length, 16000, encodedBufferPtr);
// const newData = new ArrayBuffer(length);
// if (length > 0){
// const newDataArray = new Uint8Array(newData);
// newDataArray.set(session.lyraCodecModule.HEAPU8.subarray(encodedBufferPtr, encodedBufferPtr + length));
// }
// session.lyraCodecModule._free(inputBufferPtr);
// session.lyraCodecModule._free(encodedBufferPtr);
// encodedFrame.data = newData;
// controller.enqueue(encodedFrame);
// }
// function decodeFunctionLyra(encodedFrame, controller) { // Apache 2.0 licenced. Source: https://github.com/Flash-Meeting/lyra-webrtc
// const newData = new ArrayBuffer(16000 * 0.02 * 2);
// if (encodedFrame.data.byteLength > 0) {
// const inputDataArray = new Uint8Array(encodedFrame.data);
// const inputBufferPtr = session.lyraCodecModule._malloc(encodedFrame.data.byteLength);
// const outputBufferPtr = session.lyraCodecModule._malloc(2048);
// session.lyraCodecModule.HEAPU8.set(inputDataArray, inputBufferPtr);
// const length = session.lyraCodecModule.decode(inputBufferPtr,
// inputDataArray.length, 16000,
// outputBufferPtr);
// const newDataArray = new Uint8Array(newData);
// newDataArray.set(session.lyraCodecModule.HEAPU8.subarray(outputBufferPtr, outputBufferPtr + length));
// session.lyraCodecModule._free(inputBufferPtr);
// session.lyraCodecModule._free(outputBufferPtr);
// }
// encodedFrame.data = newData;
// controller.enqueue(encodedFrame);
// }
//////////
function obfuscateURL(input) {
if (input.startsWith("https://obs.ninja/")) {
input = input.replace('https://obs.ninja/', 'obs.ninja/');
} else if (input.startsWith("http://obs.ninja/")) {
input = input.replace('http://obs.ninja/', 'obs.ninja/');
} else if (input.startsWith("obs.ninja/")) {
input = input.replace('obs.ninja/', 'obs.ninja/');
} else if (input.startsWith("https://vdo.ninja/")) {
input = input.replace('https://vdo.ninja/', 'vdo.ninja/');
} else if (input.startsWith("http://vdo.ninja/")) {
input = input.replace('http://vdo.ninja/', 'vdo.ninja/');
} else if (input.startsWith("vdo.ninja/")) {
input = input.replace('vdo.ninja/', 'vdo.ninja/');
}
input = input.replace('&view=', '&v=');
input = input.replace('&view&', '&v&');
input = input.replace('?view&', '?v&');
input = input.replace('?view=', '?v=');
input = input.replace('&videobitrate=', '&vb=');
input = input.replace('?videobitrate=', '?vb=');
input = input.replace('&bitrate=', '&vb=');
input = input.replace('?bitrate=', '?vb=');
input = input.replace('?audiodevice=', '?ad=');
input = input.replace('&audiodevice=', '&ad=');
input = input.replace('?label=', '?l=');
input = input.replace('&label=', '&l=');
input = input.replace('?stereo=', '?s=');
input = input.replace('&stereo=', '&s=');
input = input.replace('&stereo&', '&s&');
input = input.replace('?stereo&', '?s&');
input = input.replace('?webcam&', '?wc&');
input = input.replace('&webcam&', '&wc&');
input = input.replace('?remote=', '?rm=');
input = input.replace('&remote=', '&rm=');
input = input.replace('?password=', '?p=');
input = input.replace('&password=', '&p=');
input = input.replace('&maxvideobitrate=', '&mvb=');
input = input.replace('?maxvideobitrate=', '?mvb=');
input = input.replace('&maxbitrate=', '&mvb=');
input = input.replace('?maxbitrate=', '?mvb=');
input = input.replace('&height=', '&h=');
input = input.replace('?height=', '?h=');
input = input.replace('&width=', '&w=');
input = input.replace('?width=', '?w=');
input = input.replace('&quality=', '&q=');
input = input.replace('?quality=', '?q=');
input = input.replace('&cleanoutput=', '&clean=');
input = input.replace('?cleanoutput=', '?clean=');
input = input.replace('&maxviewers=', '&clean=');
input = input.replace('?maxviewers=', '?clean=');
input = input.replace('&frameRate=', '&fr=');
input = input.replace('?frameRate=', '?fr=');
input = input.replace('&fps=', '&fr=');
input = input.replace('?fps=', '?fr=');
input = input.replace('&roomid=', '&r=');
input = input.replace('?roomid=', '?r=');
input = input.replace('&room=', '&r=');
input = input.replace('?room=', '?r=');
log(input);
var key = "OBSNINJAFORLIFE";
var encrypted = CryptoJS.AES.encrypt(input, key);
var output = "https://invite.cam/" + encrypted.toString();
return output;
}
function notifyOfScreenShare(){
if (session.notifyScreenShare){
var data = {};
data.screenShareState = session.screenShareState;
session.sendMessage(data);
}
}
var beforeScreenShare = null; // video
var screenShareAudioTrack = null;
async function toggleScreenShare(reload = false) { /// &sstype=1
var quality = session.quality_ss;
if (quality === false){
quality = session.quality_wb;
}
if (session.quality !== false){
quality = session.quality;
}
if (session.screensharequality!==false){
quality = session.screensharequality;
}
if (reload) { // quality = 0, audio = true, videoOnEnd = false) {
await grabScreen(quality, true, true).then(res => {
if (res != false) {
session.screenShareState = true;
pokeIframeAPI('screen-share-state', session.screenShareState, null, session.streamID);
notifyOfScreenShare();
getById("screensharebutton").classList.add("green");
getById("screensharebutton").ariaPressed = "true";
enumerateDevices().then(gotDevices2).then(function() {});
}
});
return;
}
if (session.screenShareState == false) { // adding a screen
await grabScreen(quality, true, true).then(res => {
if (res != false) {
session.screenShareState = true;
pokeIframeAPI('screen-share-state', session.screenShareState, null, session.streamID);
notifyOfScreenShare();
//session.refreshScale();
getById("screensharebutton").classList.add("green");
getById("screensharebutton").ariaPressed = "true";
enumerateDevices().then(gotDevices2).then(function() {});
//if (session.videoElement.readyState!==4){
session.videoElement.play().then(() => {
log("start play doublecheck");
});
//}
updateMixer();
pokeIframeAPI("screen-share-state", true);
}
});
} else { // removing a screen . session.screenShareState already true true /////////////////////////////////
session.screenShareState = false;
pokeIframeAPI('screen-share-state', session.screenShareState, null, session.streamID);
notifyOfScreenShare();
if (!session.streamSrc){
checkBasicStreamsExist();
}
if (screenShareAudioTrack){
if (session.videoElement && session.videoElement.srcObject){
session.videoElement.srcObject.getAudioTracks().forEach(function(track) { // previous video track; saving it. Must remove the track at some point.
if (screenShareAudioTrack.id == track.id) { // since there are more than one audio track, lets see if we can remove JUST the audio track for the screen share.
session.videoElement.srcObject.removeTrack(track);
track.stop();
}
});
}
if (session.streamSrcClone){ //
session.streamSrcClone.getAudioTracks().forEach(function(track) {
if (screenShareAudioTrack.id == track.id) { // since there are more than one audio track, lets see if we can remove JUST the audio track for the screen share.
session.streamSrcClone.removeTrack(track);
track.stop();
}
});
}
if (session.streamSrc){
session.streamSrc.getAudioTracks().forEach(function(track) { // previous video track; saving it. Must remove the track at some point.
if (screenShareAudioTrack.id == track.id) { // since there are more than one audio track, lets see if we can remove JUST the audio track for the screen share.
session.streamSrc.removeTrack(track);
track.stop();
}
});
}
session.videoElement.srcObject = outboundAudioPipeline(); // updateREnderOoutput is just for video if videoElement is already activated.
screenShareAudioTrack=null;
senderAudioUpdate();
}
var addedAlready = false;
if (session.streamSrc){
session.streamSrc.getVideoTracks().forEach(function(track) {
if (beforeScreenShare && (track.id == beforeScreenShare.id)){
addedAlready=true;
} else {
session.streamSrc.removeTrack(track);
track.stop();
}
});
}
if (session.streamSrcClone){
session.streamSrcClone.getVideoTracks().forEach(function(track) {
if (beforeScreenShare && (track.id == beforeScreenShare.id)){
//
} else {
session.streamSrcClone.removeTrack(track);
track.stop();
}
});
}
if (session.videoElement && session.videoElement.srcObject){
session.videoElement.srcObject.getVideoTracks().forEach(function(track) {
if (beforeScreenShare && (track.id == beforeScreenShare.id)){
addedAlready=true;
} else {
session.videoElement.srcObject.removeTrack(track);
track.stop();
}
});
}
getById("screensharebutton").classList.remove("green");
getById("screensharebutton").ariaPressed = "false";
if (beforeScreenShare){
if (addedAlready==false){
session.streamSrc.addTrack(beforeScreenShare); // add back in the video track we had before we started screen sharing. It should be NULL if we changed the video track else where (such as via the settings). #TODO:
}
}
beforeScreenShare = null;
updateRenderOutpipe(); // this syncs the video
toggleSettings(true); // forceShow
updateMixer();
}
}
var ipcRenderer = false;
var ElectronDesktopCapture = false;
if (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1) { // this enables Screen Capture in Electron
try {
if (!ipcRenderer){
ipcRenderer = require('electron').ipcRenderer;
}
window.navigator.mediaDevices.getDisplayMedia = (constraints=false) => {
return new Promise(async (resolve, reject) => {
try {
if (session.autostart){
session.autostart = false;
if (parseInt(session.screenshare)+"" === session.screenshare){
var sscid = parseInt(session.screenshare)-1;
if (sscid<0){sscid=0;}
//ipcRenderer.sendSync('prompt', {title, val});
const sources = await ipcRenderer.sendSync('getSources',{types: ['screen']});
var new_constraints = {
audio:{
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: sources[sscid].id
}
},
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: sources[sscid].id
}
}
};
if (session.audioDevice===0){
new_constraints.audio = false;
}
try {
if (constraints.video.width.ideal){
new_constraints.video.mandatory.maxWidth = constraints.video.width.ideal;
}
} catch(e){}
try {
if (constraints.video.height.ideal){
new_constraints.video.mandatory.maxHeight = constraints.video.height.ideal;
}
} catch(e){}
try {
if (constraints.video.frameRate.ideal){
new_constraints.video.mandatory.maxFrameRate = constraints.video.frameRate.ideal;
}
} catch(e){}
///
const stream = await window.navigator.mediaDevices.getUserMedia(new_constraints);
resolve(stream);
} else if (session.screenshare && (session.screenshare!==true)){
var sscid=null;
const sources = await ipcRenderer.sendSync('getSources',{types: ['window']});
for (var i=0; i
`;
}
document.body.appendChild(selectionElem);
if (macOS){
getById("captureDesktopAudio").style.display = "none";
getById("alsoCaptureAudio").checked = false;
getById("alsoCaptureAudioParent1").style.display = "none";
getById("alsoCaptureAudioParent2").style.display = "inline-block";
}
document.getElementById('cancelscreenshare').addEventListener('click', async () => {
selectionElem.remove();
reject(null);
});
document.querySelectorAll('.desktop-capturer-click').forEach(button => {
button.addEventListener('click', async () => {
try {
if (button.id == "captureDesktopAudio"){
var new_constraints = {
audio: {
mandatory: {
chromeMediaSource: 'desktop'
}
},
video: {
mandatory: {
chromeMediaSource: 'desktop',
}
}
}
new_constraints.video.mandatory.maxFrameRate = 1;
warnlog(new_constraints);
const stream = await window.navigator.mediaDevices.getUserMedia(new_constraints);
if (stream.getVideoTracks().length){
var track = stream.getVideoTracks()[0];
stream.removeTrack(stream.getVideoTracks()[0]);
track.stop();
}
resolve(stream);
selectionElem.remove();
} else {
var audioStream = false;
if (getById("alsoCaptureAudio").checked){
var new_constraints = {
audio: {
mandatory: {
chromeMediaSource: 'desktop'
}
},
video: {
mandatory: {
chromeMediaSource: 'desktop',
}
}
}
new_constraints.video.mandatory.maxFrameRate = 1;
warnlog(new_constraints);
audioStream = await window.navigator.mediaDevices.getUserMedia(new_constraints);
if (audioStream.getVideoTracks().length){
var track = audioStream.getVideoTracks()[0];
audioStream.removeTrack(audioStream.getVideoTracks()[0]);
track.stop();
}
}
const id = button.getAttribute('data-id');
const source = sources.find(source => source.id === id);
if (!source) {
throw new Error(`Source with id ${id} does not exist`);
}
var new_constraints = {
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: source.id
}
}
};
try {
if (constraints.video.width.ideal){
new_constraints.video.mandatory.maxWidth = constraints.video.width.ideal;
}
} catch(e){}
try {
if (constraints.video.height.ideal){
new_constraints.video.mandatory.maxHeight = constraints.video.height.ideal;
}
} catch(e){}
try {
if (constraints.video.frameRate.ideal){
new_constraints.video.mandatory.maxFrameRate = constraints.video.frameRate.ideal;
}
} catch(e){}
warnlog(new_constraints);
const stream = await window.navigator.mediaDevices.getUserMedia(new_constraints);
if (audioStream && audioStream.getAudioTracks().length){
stream.addTrack(audioStream.getAudioTracks()[0]);
}
resolve(stream);
selectionElem.remove();
}
} catch (err) {
errorlog('Error selecting desktop capture source:', err);
reject(err);
}
})
});
}
} catch (err) {
errorlog('Error displaying desktop capture sources:', err);
reject(err);
}
})
}
ElectronDesktopCapture = true;
} catch(e){
warnlog("Couldn't load electron's screen capture. Elevate the app's permission to allow it (right-click?)");
}
}
async function grabScreen(quality = 0, audio = true, videoOnEnd = false) {
if (!navigator.mediaDevices.getDisplayMedia) {
if (!(session.cleanOutput)) {
setTimeout(function() {
if (iOS || iPad){
warnUser(miscTranslations["ios-no-screen-share"], false, false);
} else if (session.mobile){
warnUser(miscTranslations["android-no-screen-share"], false, false);
} else {
warnUser(miscTranslations["no-screen-share-supported"], false, false);
}
}, 1);
}
return false;
}
if (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1) {
if (!ElectronDesktopCapture){
if (!(session.cleanOutput)) {
warnUser("Enable Elevated Privileges to allow screen-sharing. (right click this window to see that option)");
}
return false;
}
}
var video = {}
if (quality == -1) {
// unlocked capture resolution
} else if (quality == 0) {
video.width = {
ideal: 1920
};
video.height = {
ideal: 1080
};
} else if (quality == 1) {
video.width = {
ideal: 1280
};
video.height = {
ideal: 720
};
} else if (quality == 2) {
video.width = {
ideal: 640
};
video.height = {
ideal: 360
};
} else if (quality >= 3) { // lowest
video.width = {
ideal: 320
};
video.height = {
ideal: 180
};
}
if (session.width) {
video.width = {
ideal: session.width
};
}
if (session.height) {
video.height = {
ideal: session.height
};
}
var constraints = { // this part is a bit annoying. Do I use the same settings? I can add custom setting controls here later
audio: {
echoCancellation: false, // For screen sharing, we want it off by default.
autoGainControl: false,
noiseSuppression: false
},
video: video
//,cursor: {exact: "none"}
};
try {
let supportedConstraints = navigator.mediaDevices.getSupportedConstraints();
if (supportedConstraints.cursor) {
if (session.screensharecursor){
constraints.video.cursor = ["always", "motion"];
} else {
constraints.video.cursor = "never";
}
}
if (session.suppressLocalAudioPlayback && supportedConstraints.suppressLocalAudioPlayback){
constraints.audio.suppressLocalAudioPlayback = true;
}
if (session.preferCurrentTab){
constraints.preferCurrentTab = true;
}
if (session.selfBrowserSurface){
constraints.selfBrowserSurface = session.selfBrowserSurface; // exclude or include
}
if (session.surfaceSwitching){
constraints.surfaceSwitching = session.surfaceSwitching; // exclude or include
}
if (session.systemAudio){
constraints.systemAudio = session.systemAudio; // exclude or include
}
if (session.displaySurface && supportedConstraints.displaySurface){
constraints.video.displaySurface = session.displaySurface; // monitor, window, or browser
}
} catch(e){
warnlog("navigator.mediaDevices.getSupportedConstraints() not supported");
}
if (session.echoCancellation === true) {
constraints.audio.echoCancellation = true;
}
if (session.autoGainControl === true) {
constraints.audio.autoGainControl = true;
}
if (session.noiseSuppression === true) {
constraints.audio.noiseSuppression = true;
}
if (audio == false) {
constraints.audio = false;
}
var overrideFramerate = false;
if ((session.frameRate !== false) && (session.maxframeRate != false)){
overrideFramerate = session.frameRate;
constraints.video.frameRate = {
ideal: session.maxframeRate,
max: session.maxframeRate
};
} else if (session.frameRate !== false) {
constraints.video.frameRate = session.frameRate;
} else if (session.maxframeRate != false){
constraints.video.frameRate = {
ideal: session.maxframeRate,
max: session.maxframeRate
};
} else {
constraints.video.frameRate = {
ideal: 60
};
}
if (session.screenshareVideoOnly){
constraints.audio = false;
}
if (session.forceAspectRatio){ // await updateCameraConstraints("aspectRatio", session.forceAspectRatio);
if (constraints.video && constraints.video!==true){
// if (window.matchMedia("(orientation: portrait)").matches){
// constraints.video.aspectRatio = { ideal: 1.0/parseFloat(session.forceAspectRatio)};
// } else {
constraints.video.aspectRatio = { ideal: parseFloat(session.forceAspectRatio)};
// }
if (constraints.video.width && !session.width){
delete constraints.video.width;
} else if (constraints.video.height && !session.height){
delete constraints.video.height;
}
}
}
if ((constraints.video!==false) && (Object.keys(constraints.video).length==0)){
constraints.video = true;
}
var wasDisabled = true;
return navigator.mediaDevices.getDisplayMedia(constraints).then(async function(stream) {
log("adding video tracks 2245");
try {
var constraint = {};
if (session.forceAspectRatio && (session.forceScreenShareAspectRatio===null)){
constraint.aspectRatio = parseFloat(session.forceAspectRatio);
} else if (session.forceScreenShareAspectRatio){
constraint.aspectRatio = parseFloat(session.forceScreenShareAspectRatio);
}
if (overrideFramerate){
constraint.frameRate = overrideFramerate;
}
if (Object.keys(constraint).length){
await stream.getVideoTracks()[0].applyConstraints({
advanced: [constraint]
});
log({
advanced: [constraint]
});
}
} catch(e){errorlog(e);}
try {
if (session.streamSrc) {
session.streamSrc.getVideoTracks().forEach(function(track) {
//track.stop();
beforeScreenShare = track;
session.streamSrc.removeTrack(track);
wasDisabled = false; //
log("stopping video track");
});
if (session.streamSrcClone){
session.streamSrcClone.getVideoTracks().forEach(function(track) {
session.streamSrcClone.removeTrack(track);
});
}
if (session.videoElement && session.videoElement.srcObject){
session.videoElement.srcObject.getVideoTracks().forEach(function(track) {
//track.stop();
wasDisabled = false;
session.videoElement.srcObject.removeTrack(track);
log("stopping video track 2");
});
} else {
checkBasicStreamsExist();
}
} else {
checkBasicStreamsExist(); // create srcObject + videoElement
}
} catch (e) {
warnlog(e);
}
try {
stream.getVideoTracks()[0].onended = function(e) { // if screen share stops,
warnlog(e);
if (session.streamSrc){
session.streamSrc.getVideoTracks().forEach(function(track) {
session.streamSrc.removeTrack(track);
track.stop();
log("stopping video track 3");
if (beforeScreenShare && (beforeScreenShare.id == track.id)){
beforeScreenShare.stop();
beforeScreenShare=null;
}
});
}
if (session.streamSrcClone){
session.streamSrcClone.getVideoTracks().forEach(function(track) {
session.streamSrcClone.removeTrack(track);
track.stop();
});
}
if (session.videoElement && session.videoElement.srcObject){
session.videoElement.srcObject.getVideoTracks().forEach(function(track) {
session.videoElement.srcObject.removeTrack(track);
track.stop();
log("stopping video track 4");
});
} else {
//session.videoElement.srcObject = createMediaStream();
session.videoElement.srcObject = outboundAudioPipeline();
}
if (screenShareAudioTrack){
if (session.streamSrc){
session.streamSrc.getAudioTracks().forEach(function(track) { // previous video track; saving it. Must remove the track at some point.
if (screenShareAudioTrack.id == track.id) { // since there are more than one audio track, lets see if we can remove JUST the audio track for the screen share.
session.streamSrc.removeTrack(track);
track.stop();
}
});
}
if (session.streamSrcClone){
session.streamSrcClone.getAudioTracks().forEach(function(track) { // previous video track; saving it. Must remove the track at some point.
if (screenShareAudioTrack.id == track.id) { // since there are more than one audio track, lets see if we can remove JUST the audio track for the screen share.
session.streamSrcClone.removeTrack(track);
track.stop();
}
});
}
screenShareAudioTrack=null;
senderAudioUpdate();
}
session.screenShareState = false;
pokeIframeAPI('screen-share-state', session.screenShareState, null, session.streamID);
notifyOfScreenShare();
getById("screensharebutton").classList.remove("green");
getById("screensharebutton").ariaPressed = "false";
if (videoOnEnd == true) {
if (beforeScreenShare) {
session.streamSrc.addTrack(beforeScreenShare); // updateRenderOutpipe
beforeScreenShare = null;
}
updateRenderOutpipe();
toggleSettings(true); // forceshow
} else {
//session.refreshScale(); // since updateREnderOutput already has htis.
}
updateMixer();
};
} catch (e) {
log("No Video selected; screensharing?");
}
stream.getTracks().forEach(function(track) {
addScreenDevices(track);
session.streamSrc.addTrack(track, stream); // Lets not add the audio to this preview; echo can be annoying
});
updateRenderOutpipe();
if (wasDisabled && stream.getVideoTracks().length && !session.videoMuted){
var msg = {};
msg.videoMuted = session.videoMuted;
session.sendMessage(msg);
}
if (stream.getAudioTracks().length){
screenShareAudioTrack = stream.getAudioTracks()[0];
senderAudioUpdate();
}
session.applySoloChat(); // mute streams that should be muted if a director
session.applyIsolatedChat();
applyMirror(true);
return true;
}).catch(function(err) {
errorlog(err);
errorlog(err.name);
if ((err.name == "NotAllowedError") || (err.name == "PermissionDeniedError")) {
// User Stopped it.
if (macOS){
warnUser(miscTranslations["screen-permissions-denied"], false, false);
}
} else {
if (audio == true) {
if (err.name == "NotReadableError"){
if (!(session.cleanOutput)){
warnUser(miscTranslations["change-audio-output-device"], false, false);
}
return false;
} else {
setTimeout(function() {
grabScreen(quality, false);
}, 1);
}
}
if (!(session.cleanOutput)) {
setTimeout(function(e) {
errorlog(e);
}, 1, err); // TypeError: Failed to execute 'getDisplayMedia' on 'MediaDevices': Audio capture is not supported
}
}
return false;
});
}
function toggleBufferSettings(UUID){
//bufferSliderValue
/* try {
session.rpcs[taskItemInContext.dataset.UUID].buffer = parseInt(inputElement.value);
inputElement.title = session.rpcs[taskItemInContext.dataset.UUID].buffer + " ms";
getById("bufferSliderValue").innerText = session.rpcs[taskItemInContext.dataset.UUID].buffer + " ms";
playoutdelay(taskItemInContext.dataset.UUID); // trigger
} catch(e){
errorlog(e);
*/
toggle(getById('bufferSettings'));
if (getById('bufferSettings').style.display=="none"){
getById("modalBackdrop").innerHTML = ''; // Delete modal
getById("modalBackdrop").remove();
} else {
getById("modalBackdrop").innerHTML = ''; // Delete modal
getById("modalBackdrop").remove();
zindex = 25;
getById('bufferSettings').style.zIndex = 25;
var modalTemplate = ``;
document.body.insertAdjacentHTML("beforeend", modalTemplate); // Insert modal at body end
document.getElementById("modalBackdrop").addEventListener("click", toggleBufferSettings);
var buffer = session.rpcs[UUID].buffer;
if (buffer === false){
buffer = session.buffer || 0;
}
getById('bufferSettings').querySelectorAll("input").forEach(ele=>{
ele.value = parseInt(buffer);
ele.title = ele.value + " ms";
//getById("bufferSliderValue").innerText = ele.title
ele.onchange = function(e){
session.rpcs[UUID].buffer = parseInt(e.target.value);
//getById("bufferSliderValue").innerText = session.rpcs[UUID].buffer + " ms";
getById('bufferSettings').querySelectorAll("input").forEach(ele2=>{
if (ele2!==e.target){
ele2.value = parseInt(e.target.value);
}
ele2.title = parseInt(e.target.value) + " ms";
});
playoutdelay(UUID); // trigger
};
ele.oninput = function(e){
getById('bufferSettings').querySelectorAll("input").forEach(ele2=>{
if (ele2!==e.target){
ele2.value = parseInt(e.target.value);
}
ele2.title = parseInt(e.target.value) + " ms";
});
};
});
}
}
function toggleRoomSettings(){
toggle(getById('roomSettings'));
if (getById('roomSettings').style.display=="none"){
//getById("modalBackdrop").innerHTML = ''; // Delete modal
//getById("modalBackdrop").remove();
} else {
//getById("modalBackdrop").innerHTML = ''; // Delete modal
//getById("modalBackdrop").remove();
zindex = 25;
getById('roomSettings').style.zIndex = 25;
var modalTemplate = ``;
// document.body.insertAdjacentHTML("beforeend", modalTemplate); // Insert modal at body end
// document.getElementById("modalBackdrop").addEventListener("click", toggleRoomSettings);
document.getElementById('trbSettingInput').value = session.totalRoomBitrate;
document.getElementById('trbSettingInputManual').value = session.totalRoomBitrate;
document.getElementById('trbSettingInputFeedback').innerHTML = session.totalRoomBitrate;
}
}
function changeTRB(ele){
session.totalRoomBitrate = parseInt(ele.value);
var msg = {};
msg.directorSettings={};
msg.directorSettings.totalRoomBitrate=session.totalRoomBitrate;
session.sendMessage(msg);
pokeIframeAPI('total-room-bitrate', session.totalRoomBitrate);
}
function sendMediaDevices(UUID){
enumerateDevices().then(function(deviceInfos){
var data = {};
data.UUID = UUID;
data.mediaDevices = deviceInfos;
session.sendMessage(data, data.UUID);
});
}
function changeVideoDevice(index, quality=0){
enumerateDevices().then(gotDevices2).then(function() {
activatedPreview=false;
document.getElementById("videoSource3").selectedIndex = index+"";
grabVideo(quality, "videosource", "#videoSource3");
});
}
function changeAudioDevice(index){
enumerateDevices().then(gotDevices2).then(function() {
activatedPreview=false;
var audioSelect = document.getElementById("audioSource3").querySelectorAll("input");
for (var i = 0; i < audioSelect.length; i++) {
audioSelect[i].checked = false;
}
audioSelect[index-1].checked = true;
grabAudio("#audioSource3");
});
}
function changeVideoDeviceById(deviceId, UUID=false){
enumerateDevices().then(gotDevices2).then(function() {
var opts = document.getElementById("videoSource3").options;
var index = false
for (var opt, j = 0; opt = opts[j]; j++) {
if (opt.value == deviceId) {
index = j;
break;
}
}
if (index!==false){
if (document.getElementById("videoSource3").selectedIndex === index){ // this is just refreshing the device.
activatedPreview=false;
grabVideo(0, "videosource", "#videoSource3", UUID);
} else if (UUID && !session.consent){
window.focus();
confirmAlt("Allow the director to change your video device to:\n\n"+opts[index].text+" ?").then(res=>{
if (res){
document.getElementById("videoSource3").selectedIndex = index;
activatedPreview=false;
grabVideo(0, "videosource", "#videoSource3", UUID);
} else {
try {
var data = {};
data.UUID = UUID;
data.rejected = "changeCamera";
session.sendMessage(data, data.UUID);
} catch(e){}
}
});
} else {
document.getElementById("videoSource3").selectedIndex = index;
activatedPreview=false;
grabVideo(0, "videosource", "#videoSource3", UUID);
}
}
});
}
function changeAudioDeviceById(deviceId, UUID=false){
enumerateDevices().then(gotDevices2).then(function() {
var audioSelect = document.getElementById("audioSource3").querySelectorAll("input");
var matched = false;
var exists = false;
for (var i = 0; i < audioSelect.length; i++) {
if (audioSelect[i].value == deviceId){
exists = true;
if (audioSelect[i].checked){
matched = true;
}
}
}
if (exists){
if (matched){ // this is just refreshing the device.
activatedPreview=false;
grabAudio("#audioSource3", UUID);
} else if (UUID && !session.consent){
window.focus();
confirmAlt("Allow the director to change your audio mic source?").then(res=>{
if (res){
// enumerateDevices().then(gotDevices2).then(function() {
var audioSelect = document.getElementById("audioSource3").querySelectorAll("input");
for (var i = 0; i < audioSelect.length; i++) {
if (audioSelect[i].value == deviceId){
audioSelect[i].checked=true;
} else {
audioSelect[i].checked = false;
}
}
activatedPreview=false;
grabAudio("#audioSource3", UUID);
// });
} else {
try {
var data = {};
data.UUID = UUID;
data.rejected = "changeMicrophone";
session.sendMessage(data, data.UUID);
} catch(e){}
}
});
} else {
//enumerateDevices().then(gotDevices2).then(function() {
var audioSelect = document.getElementById("audioSource3").querySelectorAll("input");
for (var i = 0; i < audioSelect.length; i++) {
if (audioSelect[i].value == deviceId){
audioSelect[i].checked=true;
} else {
audioSelect[i].checked = false;
}
}
activatedPreview=false;
grabAudio("#audioSource3", UUID);
// });
}
}
});
}
function changeAudioOutputDeviceById(deviceId, UUID=false){ // remote control of the speaker output.
warnlog(deviceId);
if (document.getElementById("outputSource3")){
enumerateDevices().then(gotDevices2).then(function() {
var index = false
if (document.getElementById("outputSource3")){
var opts = document.getElementById("outputSource3").options;
for (var opt, j = 0; opt = opts[j]; j++) {
if (opt.value == deviceId) {
index = j;
break;
}
}
}
if (index!==false){
if (document.getElementById("outputSource3").selectedIndex === index){ // this is just refreshing the device.
session.sink = deviceId;
saveSettings();
resetupAudioOut();
} else if (UUID && !session.consent){ // UUID just lets us inform the requester
window.focus();
confirmAlt("Allow the director to change your audio's speaker to:\n\n"+opts[index].text+" ?").then(res=>{
if (res){
if (index!==false){
document.getElementById("outputSource3").selectedIndex = index;
}
session.sink = deviceId;
saveSettings();
resetupAudioOut();
var data = {};
data.UUID = UUID;
sendMediaDevices(data.UUID);
session.sendMessage(data, data.UUID);
} else {
try {
var data = {};
data.UUID = UUID;
data.rejected = "changeSpeaker";
session.sendMessage(data, data.UUID);
} catch(e){}
}
});
} else {
if (index!==false){
document.getElementById("outputSource3").selectedIndex = index;
}
session.sink = deviceId;
saveSettings();
resetupAudioOut();
}
}
});
} else {
session.sink = deviceId;
saveSettings();
resetupAudioOut();
}
}
function checkBasicStreamsExist(){
if (!session.streamSrc) {
session.streamSrc = createMediaStream();
}
if (!session.videoElement) {
if (document.getElementById("videosource")) {
session.videoElement = document.getElementById("videosource");
} else if (document.getElementById("previewWebcam")) {
session.videoElement = document.getElementById("previewWebcam");
} else {
session.videoElement = createVideoElement();
}
session.videoElement.addEventListener("playing", (e)=>{
resetupAudioOut(session.videoElement, true);
}, { once: true });
}
session.videoElement.srcObject = outboundAudioPipeline();
toggleMute(true);
return session.videoElement;
}
var getUserMediaRequestID = 0;
var grabVideoUserMediaTimeout = null;
var grabVideoTimer = null;
async function grabVideo(quality = 0, eleName = 'previewWebcam', selector = "select#videoSourceSelect", callback = false) {
if (activatedPreview == true) {
log("activated preview return 2");
return;
}
if (session.miconly){return;}
activatedPreview = true;
log("Grabbing video: " + quality);
if (grabVideoTimer) {
clearTimeout(grabVideoTimer);
}
log("element:" + eleName);
var wasDisabled = true;
try {
if (session.streamSrc) {
if (session.canvasWebGL){
session.canvasWebGL.remove()
session.canvasWebGL=null;
}
if (session.canvasSource){
session.canvasSource.srcObject.getTracks().forEach(function(trk) {
session.canvasSource.srcObject.removeTrack(trk);
trk.stop();
wasDisabled=false;
});
}
if (session.streamSrc){
session.streamSrc.getVideoTracks().forEach(function(track) {
session.streamSrc.removeTrack(track);
track.stop();
wasDisabled=false;
});
}
if (session.streamSrcClone){
session.streamSrcClone.getVideoTracks().forEach(function(track) {
session.streamSrcClone.removeTrack(track);
track.stop();
});
}
if (session.videoElement && session.videoElement.srcObject) {
session.videoElement.srcObject.getVideoTracks().forEach(function(track) {
session.videoElement.srcObject.removeTrack(track);
track.stop();
session.videoElement.load();
wasDisabled=false;
});
} else {
checkBasicStreamsExist();
}
} else {
checkBasicStreamsExist();
log("CREATE NEW STREAM");
}
} catch (e) {
errorlog(e);
}
session.videoElement.controls = session.showControls || false;
log("selector: " + selector);
var videoSelect = document.querySelector(selector); // document.querySelector("videoSource3").value == "ZZZ"
log(videoSelect);
var mirror = false;
getById("cameraTip1").classList.add("hidden");
if (!videoSelect || videoSelect.value == "ZZZ"){ // if there is no video, or if manually set to audio ready, then do this step.
clearTimeout(grabVideoUserMediaTimeout);
getUserMediaRequestID += 1;
warnlog("ZZZ SET - so no VIDEO");
SelectedVideoInputDevices = [];
saveSettings();
if (session.avatar && session.avatar.ready){
updateRenderOutpipe();
} else if (session.mobile && !document.getElementById("keepAlivePlayer")){ // keep alive player doens't exist
setInterval(function(){
if (document.getElementById("keepAlivePlayer") && session.streamSrc.getVideoTracks().length){
getById("keepAlivePlayer").remove();
} else if (!document.getElementById("keepAlivePlayer")){
let fakeElement = document.createElement("video");
fakeElement.autoplay = true;
fakeElement.loop = true;
fakeElement.muted = true;
fakeElement.src = "./media/micro.mp4";
fakeElement.style.width = "1px";
fakeElement.style.height ="1px";
fakeElement.controls = false;
fakeElement.id = "keepAlivePlayer";
getById("main").appendChild(fakeElement);
}
}, 4000);
}
if ((eleName == "previewWebcam") && document.getElementById("previewWebcam")){
if (session.autostart) {
publishWebcam(); // no need to mirror as there is no video...
return;
} else {
log("4462");
updateStats();
if (document.getElementById("gowebcam")) {
document.getElementById("gowebcam").dataset.ready = "true";
if (document.getElementById("gowebcam").dataset.audioready == "true"){
document.getElementById("gowebcam").disabled = false;
document.getElementById("gowebcam").innerHTML = miscTranslations["start"];
}
}
}
} else { // If they disabled the video but not in preview mode; but actualy live. We will want to remove the stream from the publishing
// we don't want to do this otherwise, as we are "replacing" the track in other cases.
// this does cause a problem, as previous bitrate settings & resolutions might not be applied if switched back.... must test
if (session.avatar && session.avatar.ready){
updateRenderOutpipe();
return;
}
if (session.chunked){
for (UUID in session.pcs) {
session.chunkedStream(UUID); // make sure we check that this connection allows video / audio
}
return;
}
if (session.mc && session.mc.getSenders){
miscSenders.push(session.mc);
}
if (session.whipOut && session.whipOut.getSenders){
miscSenders.push(session.whipOut);
}
miscSenders.forEach(dataRTC=>{
if (dataRTC && dataRTC.getSenders){
dataRTC.getSenders().forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams?
if (sender.track && sender.track.kind == "video") {
var trk = getMeshcastCanvasTrack(dataRTC);
if (session.screenShareState && session.screenshareContentHint && (trk.kind === "video")){
try {
trk.contentHint = session.screenshareContentHint;
} catch(e){
errorlog(e);
}
} else if (session.contentHint && (trk.kind === "video")){
try {
trk.contentHint = session.contentHint;
} catch(e){
errorlog(e);
}
}
sender.replaceTrack(trk); // replace may not be supported by all browsers. eek.
}
});
}
});
for (UUID in session.pcs) {
if ("realUUID" in session.pcs[UUID]){continue;} // do not apply to screen shares.
// for any connected peer, update the video they have if connected with a video already.
var senders = getSenders2(UUID);
senders.forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams?
if (sender.track && sender.track.kind == "video") {
sender.track.enabled = false; // I'm not entirely sure if I shoudl be doing this to a video stream... but I suppose new connections won't get a stream, and old connections will just replace it?
getById("mutevideobutton").classList.add("hidden"); // hide the mute button, so they can't unmute while no video.
//session.pcs[UUID].removeTrack(sender); // replace may not be supported by all browsers. eek.
//errorlog("DELETED SENDER");
}
});
}
var msg = {};
msg.videoMuted = true; // doesn;t matter if video is actually muted or not; no video is being sent
session.sendMessage(msg);
}
return;
} else {
if (videoSelect && videoSelect.value){
SelectedVideoInputDevices = [videoSelect.value];
saveSettings();
}
if (session.avatar && session.avatar.timer){
clearInterval(session.avatar.timer);
session.avatar.timer=null;
}
var sq = 0;
if (session.quality === false) {
sq = session.quality_wb;
} else if (session.quality > 2) { // 1080, 720, and 360p
sq = 2; // hacking my own code. TODO: ugly, so I need to revisit this.
} else {
sq = session.quality;
}
if (session.director && (quality !== false)){ // URL-based quality won't matter if DIRECTOR;
// quality = quality;
} else if ((quality === false) || (quality < sq)) {
quality = sq; // override the user's setting
}
if ((iOS || iPad) && SafariVersion<15) { // iOS will not work correctly at 1080p; likely a h264 codec issue.
if (quality == 0) {
quality = 1;
}
}
var constraints = {
audio: false,
video: getUserMediaVideoParams(quality, (iOS || iPad))
};
//if (Firefox){
// constraints.video.height = constraints.video.height.ideal;
// constraints.video.width = constraints.video.height.ideal;
//}
log("Quality selected:" + quality);
if (session.facingMode){
constraints.video.facingMode = { exact: session.facingMode }; // user or environment
} else if ((iOS) || (iPad)) {
constraints.video.deviceId = {
exact: videoSelect.value
}; // iPhone 6s compatible ? Needs to be exact for iPhone 6s
} else if (Firefox){ // is firefox.
constraints.video.deviceId = {
exact: videoSelect.value
}; // Firefox is a dick. Needs it to be exact.
} else if (videoSelect.options[videoSelect.selectedIndex].text.includes("NDI Video")) { // NDI does not like "EXACT"
constraints.video.deviceId = videoSelect.value; // NDI is fucked up
} else {
constraints.video.deviceId = {
exact: videoSelect.value
}; // Default. Should work for Logitech, etc.
}
if (session.width) {
constraints.video.width = {
exact: session.width
}; // manually specified - so must be exact
}
if (session.height) {
constraints.video.height = {
exact: session.height
};
}
if (session.frameRate) {
constraints.video.frameRate = {
exact: session.frameRate
};
} else if (session.maxframeRate != false){
constraints.video.frameRate = {
ideal: session.maxframeRate,
max: session.maxframeRate
};
} else if ((iOS || iPad) && (SafariVersion>15)) { // iOS supports 720p60, but just 1080p30 : iphone 11 on march 2023
if (quality === 1) { // iphone 11 and older
if (!constraints.video.frameRate){
constraints.video.frameRate = {
ideal: 60,
max: 60
};
}
} else if (iPhone12Up && (quality<1)){ // iphone 12 and up?
if (!constraints.video.frameRate){
try {
if (videoSelect.options[videoSelect.selectedIndex].innerText.startsWith("Back ")){ // front seems to be limited to 720p60 / 1080p30
constraints.video.frameRate = {
ideal: 60,
max: 60
};
}
} catch(e){
errorlog(e);
}
}
}
}
if (session.ptz){
if (constraints.video && constraints.video!==true){
if (ChromeVersion && ChromeVersion>80){
constraints.video.pan=true;
constraints.video.tilt=true;
constraints.video.zoom=true;
}
}
}
if (session.forceAspectRatio){ // await updateCameraConstraints("aspectRatio", session.forceAspectRatio);
if (constraints.video && constraints.video!==true){
//if (window.matchMedia("(orientation: portrait)").matches){
// constraints.video.aspectRatio = { ideal: 1.0/parseFloat(session.forceAspectRatio)};
//} else {
constraints.video.aspectRatio = { ideal: parseFloat(session.forceAspectRatio)};
//}
if (constraints.video.width && !session.width){
delete constraints.video.width;
} else if (constraints.video.height && !session.height){
delete constraints.video.height;
}
}
}
var obscam = false;
var mirrorcheck = false;
log(videoSelect.options[videoSelect.selectedIndex].text);
if (!videoSelect.options[videoSelect.selectedIndex]){
if (session.mobile){
mirrorcheck = true;
mirror = false;
} else {
mirror = false;
}
} else if (videoSelect.options[videoSelect.selectedIndex].text.startsWith("OBS-Camera")) { // OBS Virtualcam
mirror = true;
obscam = true;
} else if (videoSelect.options[videoSelect.selectedIndex].text.startsWith("OBS Virtual Camera")) { // OBS Virtualcam
mirror = true;
obscam = true;
} else if (videoSelect.options[videoSelect.selectedIndex].text.startsWith("Streamlabs ")) { // OBS Virtualcam
mirror = true;
obscam = true;
} else if (videoSelect.options[videoSelect.selectedIndex].text.startsWith("Dummy video device")) { // Linuxv
mirror = true;
} else if (videoSelect.options[videoSelect.selectedIndex].text.startsWith("vMix Video")) { // vMix
mirror = true;
} else if (videoSelect.options[videoSelect.selectedIndex].text.startsWith("Blackmagic")) { // Blackmagic devices
mirror = true;
} else if (videoSelect.options[videoSelect.selectedIndex].text.startsWith("screen-capture-recorder")) { // screen-capture-recorder
mirror = true;
} else if (videoSelect.options[videoSelect.selectedIndex].text.includes(" back")) { // Android
mirror = true;
} else if (videoSelect.options[videoSelect.selectedIndex].text.includes(" rear")) { // Android
mirror = true;
} else if (videoSelect.options[videoSelect.selectedIndex].text.includes("NDI Video")) { // NDI Virtualcam
mirror = true;
} else if (videoSelect.options[videoSelect.selectedIndex].text.startsWith("Back Camera")) { // iPhone and iOS
mirror = true;
} else if (videoSelect.options[videoSelect.selectedIndex].text.toLowerCase().includes("c922")) {
if ((session.quality!==2) && !session.cleanOutput){
getById("cameraTipContext1").innerHTML = miscTranslations["camera-tip-c922"];
getById("cameraTip1").classList.remove("hidden");
}
} else if (videoSelect.options[videoSelect.selectedIndex].text.toLowerCase().includes("cam link")) {
if (!session.cleanOutput){
getById("cameraTipContext1").innerHTML = miscTranslations["camera-tip-camlink"];
getById("cameraTip1").classList.remove("hidden");
}
} else if (session.mobile){
mirrorcheck = true;
mirror = false;
} else {
mirror = false;
}
if (SamsungASeries && ChromeVersion){
if (!session.cleanOutput){
getById("cameraTipContext1").innerHTML = miscTranslations["samsung-a-series"];
getById("cameraTip1").classList.remove("hidden");
}
}
session.mirrorExclude = mirror;
if (constraints.video && (constraints.video!==true) && (Object.keys(constraints.video).length==0)){
constraints.video = true;
} else if (constraints.video && (constraints.video!==true) && (Object.keys(constraints.video).length==1) && ("deviceId" in constraints.video) && ("exact" in constraints.video.deviceId) && (constraints.video.deviceId.exact==="default")){
constraints.video = true; // solves issues with IOS, where no permission yet given - can't request device ID it seems until permissions is given.
}
log(constraints);
clearTimeout(grabVideoUserMediaTimeout);
getUserMediaRequestID += 1;
var gumMediaID = getUserMediaRequestID;
grabVideoUserMediaTimeout = setTimeout(function(gumID, callback2) {
if (getUserMediaRequestID !== gumID) {return;} // cancel
navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
if (getUserMediaRequestID !== gumID) {
warnlog("GET USER MEDIA CALL HAS EXPIRED");
stream.getTracks().forEach(function(track) {
stream.removeTrack(track);
track.stop();
log("stopping old track");
});
return;
}
log("adding video tracks 2412");
stream.getVideoTracks().forEach(function(track) {
try{
if (mirrorcheck){
const capabilities = track.getCapabilities();
if ("facingMode" in capabilities){
if (capabilities.facingMode == "environment"){
session.mirrorExclude = true;
}
}
}
} catch(e){}
session.streamSrc.addTrack(track); // tracks previously removed.
try{
track.onended = function(e) { // hurrah!
warnlog(e);
refreshVideoDevice();
}
} catch(e){errorlog(e);}
if (session.mobile){
if (!(iPad || iOS || Firefox)){
try{
applySavedVideoSettings(track);
} catch(e){errorlog(e);}
}
}
});
if (Firefox && !FirefoxEnumerated){
if (session.streamSrc && session.streamSrc.getTracks().length){
FirefoxEnumerated=true;
enumerateDevices().then(gotDevices);
}
}
updateRenderOutpipe();
// senderAudioUpdate
if (wasDisabled && !session.videoMuted){
var msg = {};
msg.videoMuted = session.videoMuted;
session.sendMessage(msg);
}
applyMirror(session.mirrorExclude);
session.videoElement.play().then(() => {
log("start play doublecheck");
});
if ((eleName == "previewWebcam") && document.getElementById("previewWebcam")){
if (session.autostart) {
publishWebcam();
} else {
log("4620");
if (document.getElementById("gear_webcam")) {
updateStats(obscam);
}
if (document.getElementById("gowebcam")) {
document.getElementById("gowebcam").dataset.ready = "true";
if (document.getElementById("gowebcam").dataset.audioready == "true"){
document.getElementById("gowebcam").disabled = false;
document.getElementById("gowebcam").innerHTML = miscTranslations["start"];
}
}
}
} else if (getById("gear_webcam3").style.display === "inline-block") {
updateStats(obscam);
}
// Once crbug.com/711524 is fixed, we won't need to wait anymore. This is
// currently needed because capabilities can only be retrieved after the
// device starts streaming. This happens after and asynchronously w.r.t.
// getUserMedia() returns.
if (grabVideoTimer) {
clearTimeout(grabVideoTimer);
if ((eleName == "previewWebcam") && document.getElementById("previewWebcam")){
session.videoElement.controls = true;
}
}
if (getById("popupSelector_constraints_video")) {
getById("popupSelector_constraints_video").innerHTML = "";
}
if (getById("popupSelector_constraints_audio")) {
getById("popupSelector_constraints_audio").innerHTML = "";
}
if (getById("popupSelector_constraints_loading")) {
getById("popupSelector_constraints_loading").style.display = "";
}
if (iOS || iPad){ // TEMPORARY: iOS 15.3 beta fix
toggleSpeakerMute(true);
}
if (!((eleName == "previewWebcam") || document.getElementById("previewWebcam"))){
updateMixer(); // not with the preview, but after.
}
pokeIframeAPI('local-camera-event');
grabVideoTimer = setTimeout(async function(callback3, gumid) {
if (getUserMediaRequestID !== gumid) { // new camera selected in this time.
return;
}
makeImages(true);
if (getById("popupSelector_constraints_loading")) {
getById("popupSelector_constraints_loading").style.display = "none";
}
if ((eleName == "previewWebcam") && document.getElementById("previewWebcam")){
session.videoElement.controls = true;
try {
var track0 = session.streamSrc.getVideoTracks();
if (track0.length) {
track0 = track0[0];
if (track0.getCapabilities) {
session.cameraConstraints = track0.getCapabilities();
} else {
session.cameraConstraints = {};
}
log(session.cameraConstraints);
if (track0.getSettings) {
session.currentCameraConstraints = track0.getSettings();
if (window.matchMedia("(orientation: portrait)").matches){
if (session.currentCameraConstraints && session.currentCameraConstraints.aspectRatio){
session.currentCameraConstraints.aspectRatio = 1/session.currentCameraConstraints.aspectRatio;
}
}
} else {
session.currentCameraConstraints = {};
}
log(session.currentCameraConstraints);
}
} catch (e) {
errorlog(e);
}
} else if (toggleSettingsState) {
log("16047");
updateConstraintSliders();//listCameraSettings();
}
if (callback3){
try {
var data = {};
data.UUID = callback3;
data.videoOptions = listVideoSettingsPrep();
sendMediaDevices(data.UUID);
session.sendMessage(data, data.UUID);
} catch(e){}
}
if (iOS || iPad){ // TEMPORARY: iOS 15.3 beta fix
toggleSpeakerMute(true);
}
if (session.forceAspectRatio){
//if (window.matchMedia("(orientation: portrait)").matches){
// log("16065");
// await updateCameraConstraints("aspectRatio", 1.0/session.forceAspectRatio);
//} else {
// log("16068");
await updateCameraConstraints("aspectRatio", session.forceAspectRatio);
//}
}// else {
//log("16072");
// session.setResolution(); // this runs already when updateCameraConstraints succeeds
//}
//log("16075");
updateForceRotate(); // this contains session.setResolution();
if (iOS || iPad){
// if we don't do this, portrait videos may be detected as horizontal
if (!document.getElementById("previewWebcam")){
updateMixer(); // not with the preview, but after.
}
}
// this will reset scaling for all viewers of this stream. I also call it when aspect ratio, width, or height is changed via applyConstraints
dragElement(session.videoElement);
}, 1000, callback2, gumID); // focus
log("DONE - found stream");
}).catch(function(e) {
if (getUserMediaRequestID !== gumID) {
warnlog("the previously selected camera attempted failed, but not a big deal, since its now void");
return;
}
warnlog(e);
if (e.name === "OverconstrainedError") {
warnlog(e.message);
log("Resolution or frameRate didn't work");
} else if (e.name === "NotReadableError"){
if (quality <= 10) {
activatedPreview = false;
grabVideo(quality + 1, eleName, selector);
} else if (session.facingMode){
session.facingMode = false;
activatedPreview = false;
grabVideo(false, eleName, selector); // restart.
} else {
if (!(session.cleanOutput)) {
if (iOS) {
warnUser("An error occured. Closing existing tabs in Safari may solve this issue.");
} else {
warnUser("Error: Could not start video source.\n\nTypically this means the Camera is already be in use elsewhere. Most webcams can only be accessed by one program at a time.\n\nTry a different camera or perhaps try re-plugging in the device.");
}
}
activatedPreview = true;
if (getById('gowebcam')) {
getById('gowebcam').innerHTML = "Problem with Camera";
}
}
return;
} else if (e.name === "NavigatorUserMediaError") {
if (getById('gowebcam')) {
getById('gowebcam').innerHTML = "Problem with Camera";
}
if (!(session.cleanOutput)) {
warnUser("Unknown error: 'NavigatorUserMediaError'");
}
return;
} else if (e.name === "timedOut") {
activatedPreview = true;
if (getById('gowebcam')) {
getById('gowebcam').innerHTML = "Problem with Camera";
}
if (!(session.cleanOutput)) {
warnUser(e.message);
}
return;
} else {
errorlog("An unknown camera error occured");
}
if (quality <= 10) {
activatedPreview = false;
grabVideo(quality + 1, eleName, selector);
} else if (session.facingMode){
session.facingMode = false;
activatedPreview = false;
grabVideo(false, eleName, selector); // restart.
} else {
errorlog("********Camera failed to work");
activatedPreview = true;
if (getById('gowebcam')) {
getById('gowebcam').innerHTML = "Problem with Camera";
}
if (!(session.cleanOutput)) {
if (session.width || session.height || session.frameRate) {
warnUser(" Camera failed to load.\n\nPlease ensure your camera supports the resolution and frameRate that has been manually specified. Perhaps use &quality=0 instead.", false, false);
} else {
warnUser(" Camera failed to load.\n\nPlease make sure it is not already in use by another application.\n\nPlease make sure you have accepted the camera permissions.", false, false);
}
}
}
});
}, 100, gumMediaID, callback);
}
}
function updateRenderOutpipe(){ // video only.
log("updateRenderOutpipe()");
if (session.canvasWebGL){
session.canvasWebGL.remove()
session.canvasWebGL=null;
}
if (session.canvasSource){
session.canvasSource.srcObject.getTracks().forEach(function(trk) {
session.canvasSource.srcObject.removeTrack(trk);
//trk.stop();
});
}
if (session.videoElement && session.videoElement.srcObject) {
session.videoElement.srcObject.getVideoTracks().forEach(function(track) {
session.videoElement.srcObject.removeTrack(track);
//track.stop();
//session.videoElement.load();
});
} else {
checkBasicStreamsExist();
}
if (session.streamSrc){
var tracks = session.streamSrc.getVideoTracks();
if (!tracks.length || session.videoMuted){
tracks = setAvatarImage(tracks);
if (tracks.length){
if (tracks.length && !session.cleanOutput && !session.cleanish){
getById("mutevideobutton").classList.remove("hidden");
}
tracks.forEach(function(track) {
session.videoElement.srcObject.addTrack(track);
if (session.avatar && session.avatar.tracks){
var msg = {};
msg.videoMuted = false; // doesn't matter actual mute state, since its the avatar
session.sendMessage(msg);
} else {
toggleVideoMute(true);
}
pushOutVideoTrack(track); // video only
});
} else {
var msg = {};
msg.videoMuted = true;
session.sendMessage(msg);
session.videoElement.load();
getById("mutevideobutton").classList.add("hidden");
}
} else if (tracks.length){
applyMirror(session.mirrorExclude);
tracks.forEach(function(track) {
track = applyEffects(track); // updates with the correct track session.streamSrc
session.videoElement.srcObject.addTrack(track);
toggleVideoMute(true);
pushOutVideoTrack(track); // video only
});
if (tracks.length && !session.cleanOutput && !session.cleanish){
getById("mutevideobutton").classList.remove("hidden");
}
}
}
}
function pushOutVideoTrack(track){
log("pushOutVideoTrack");
pokeIframeAPI('push-video-track', track.id, false, session.streamID); // (action, value = null, UUID = null, SID=null)
if (session.chunked){
for (UUID in session.pcs) {
session.chunkedStream(UUID); // I need to update chunkedStream with the current track? If sstype=3, then skip this
}
return;
}
if (session.audioContentHint && (track.kind === "audio")){ // why am I pushing an audio track?
errorlog("this shouldn't occur, since only video tracks are expected");
try {
track.contentHint = session.audioContentHint;
} catch(e){
errorlog(e);
}
}
if (session.screenShareState && session.screenshareContentHint && (track.kind === "video")){ // I need to check if this is actually a screenshare before setting the hint (sstype=3)
try {
track.contentHint = session.screenshareContentHint;
} catch(e){
errorlog(e);
}
} else if (session.contentHint && (track.kind === "video")){
try {
track.contentHint = session.contentHint;
} catch(e){
errorlog(e);
}
}
if (session.mc && session.mc.getSenders){ // should only be 0 or 1 video sender, ever.
//var added = false;
session.mc.getSenders().forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams?
if (sender.track && sender.track.kind == "video") {
sender.replaceTrack(track); // replace may not be supported by all browsers. eek.
//sender.track.enabled = true;
//added = true;
}
});
}
if (session.whipOut && session.whipOut.getSenders){ // should only be 0 or 1 video sender, ever.
//var added = false;
session.whipOut.getSenders().forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams?
if (sender.track && sender.track.kind == "video") {
errorlog("Replacing track");
sender.replaceTrack(track); // replace may not be supported by all browsers. eek.
//sender.track.enabled = true;
//added = true;
}
});
}
for (UUID in session.pcs){
var videoAdded = false;
try {
if ("realUUID" in session.pcs[UUID]){continue;}
if ((session.pcs[UUID].guest == true) && (session.roombitrate === 0)) {
log("room rate restriction detected. No videos will be published to other guests");
} else if (session.pcs[UUID].allowVideo == true) { // allow
// for any connected peer, update the video they have if connected with a video already.
var added = false;
var senders = getSenders2(UUID);
senders.forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams?
if (added) {
return;
}
if (sender.track && sender.track.kind == "video") {
sender.replaceTrack(track); // replace may not be supported by all browsers. eek.
log("Track replaced");
log(track);
sender.track.enabled = true;
added = true;
}
});
if (added == false) {
videoAdded = true;
session.pcs[UUID].addTrack(track, session.videoElement.srcObject); // can't replace, so adding
setTimeout(function(uuid){session.optimizeBitrate(uuid);},session.rampUpTime, UUID); // 3 seconds lets us ramp up the quality a bit and figure out the total bandwidth quicker
}
}
} catch (e) {
errorlog(e);
}
if (iOS || iPad){ ///////// THIS IS A FIX FOR iOS 15.4. When a video is loaded (view/push), the bitrate from iOS devices is stuck low, and resolution needs toggle to fix.
// videoAdded value needs to be deleted from above also
if (SafariVersion && (SafariVersion<=13)){
//
} else if (videoAdded){
setTimeout(function(uuid){
session.setScale(uuid, null);
}, 2000, UUID);
setTimeout(function(uuid){
var scale = 100;session.setScale
if (session.pcs[uuid].scale){
scale = session.pcs[uuid].scale;
}
session.setScale(uuid, scale);
},5000, UUID);
}
}
}
if (track.kind === "audio"){
session.applyIsolatedChat();
}
session.refreshScale();
}
async function grabAudio(selector = "#audioSource", trackid = null, override = false, callbackUUID = false, callback = false) { // trackid is the excluded track , callback is UUID
if (activatedPreview == true) {
log("activated preview return 2");
return;
}
activatedPreview = true;
log("TRACK EXCLUDED:" + trackid);
try {
if (session.videoElement && session.videoElement.srcObject) {
var audioSelect = document.querySelector(selector).querySelectorAll("input");
var audioExcludeList = [];
for (var i = 0; i < audioSelect.length; i++) {
try {
if ("screen" == audioSelect[i].dataset.type) { // skip already excluded ---------- !!!!!! DOES THIS MAKE SENSE? TODO: CHECK
if (audioSelect[i].checked) {
audioExcludeList.push(audioSelect[i]);
}
}
} catch (e) {
errorlog(e);
}
}
session.videoElement.srcObject.getAudioTracks().forEach(function(track) { // TODO: Confirm that I even need this?
for (var i = 0; i < audioExcludeList.length; i++) {
try {
if (audioExcludeList[i].label == track.label) {
warnlog("DONE");
return;
}
} catch (e) {}
}
if (trackid && (track.id == trackid)) {
warnlog("SKIPPED EXCLUDED TRACK?");
return;
}
session.videoElement.srcObject.removeTrack(track);
track.stop();
});
session.streamSrc.getAudioTracks().forEach(function(track) {
for (var i = 0; i < audioExcludeList.length; i++) {
try {
if (audioExcludeList[i].label == track.label) {
warnlog("EXCLUDING TRACK; PROBABLY SCREEN SHARE");
return;
}
} catch (e) {}
}
if (trackid && (track.id == trackid)) {
warnlog("SKIPPED EXCLUDED TRACK?");
return;
}
session.streamSrc.removeTrack(track);
track.stop();
});
if (session.streamSrcClone){
session.streamSrcClone.getAudioTracks().forEach(function(track) {
for (var i = 0; i < audioExcludeList.length; i++) {
try {
if (audioExcludeList[i].label == track.label) {
warnlog("EXCLUDING TRACK; PROBABLY SCREEN SHARE");
return;
}
} catch (e) {}
}
if (trackid && (track.id == trackid)) {
warnlog("SKIPPED EXCLUDED TRACK?");
return;
}
session.streamSrcClone.removeTrack(track);
track.stop();
});
}
} else { // if no stream exists
checkBasicStreamsExist();
}
} catch (e) {
errorlog(e);
}
var streams = await getAudioOnly(selector, trackid, override); // Get audio streams
try {
for (var i = 0; i < streams.length; i++) {
streams[i].getAudioTracks().forEach(function(track) {
session.streamSrc.addTrack(track); // add video track to the preview video
try {
track.onended = function(){
errorlog("Track ended unexpectedly");
if (!session.cleanOutput){
toggleSettings(true); // forceshow
}
};
// applySavedAudioSettings(track); ## this doesn't work as echo-cancellation(+) needs to be applied via getuserMedia only.
} catch(e){
errorlog(e);
}
});
}
} catch(e){errorlog(e);}
if (Firefox && !FirefoxEnumerated){
if (session.streamSrc && session.streamSrc.getTracks().length){
FirefoxEnumerated=true;
enumerateDevices().then(gotDevices);
}
}
if (callback){
callback();
}
senderAudioUpdate(callbackUUID);
}
session.toggleSoloChat = function(UUID, event=false){ // ==> applyIsolatedChat -- this should be trigger by the director only I think
if (session.director){
if (!session.directorEnabledPPT){
warnUser("Enable the director's microphone first.",2000);
return false;
}
}
if (Firefox){
warnlog("Solo talk support for Firefox is currently experimental");
}
var msg = {};
msg.micIsolate = false;
if (session.soloChatUUID.includes(UUID)){ // already added, so lets toggle off
session.soloChatUUID.splice(session.soloChatUUID.indexOf(UUID), 1); // Toggles. Adds target to soloChatUUID list
msg.lowerVolume = false;
} else {
session.soloChatUUID.push(UUID); //not added, so lets toggle on
msg.lowerVolume = true;
}
if (event){
if ( event.ctrlKey || event.metaKey){
if (session.soloChatUUID.includes(UUID)){
msg.micIsolate = 1;
}
}
}
session.sendRequest(msg, UUID);
log(session.soloChatUUID);
var ele = document.querySelector('[data-action-type="solo-chat"][data--u-u-i-d="'+UUID+'"]'); // [data--u-u-i-d="'+UUID+'"] // this all just updates the buttons
log(ele);
if (session.soloChatUUID.includes(UUID)){
if (msg.micIsolate){
ele.classList.add("altpress"); // we will do this later.
}
} else {
ele.classList.remove("pressed"); ele.ariaPressed = "false";
ele.classList.remove("altpress");
}
session.applySoloChat(false);
return msg.micIsolate;
};
///////////////////////
session.togglePrivateChat = function(ele){
var msg = {};
warnlog(ele);
if (ele.value == 0) {
msg.micIsolate = true;
ele.value = 1;
ele.classList.add("pressed"); ele.ariaPressed = "true";
} else {
msg.micIsolate = false;
ele.value = 0;
ele.classList.remove("pressed"); ele.ariaPressed = "false";
}
session.sendRequest(msg, ele.dataset.UUID);
warnlog(msg);
};
// we call this via session.applyIsolatedChat, just in case
session.applyIsolatedVolume = function(){ // mutes outbound mic audio; for guests, and not the director
var i = session.lowerVolume.length;
while (i--){
if (!(session.lowerVolume[i] in session.rpcs)){ // clean up dead connections
session.lowerVolume.splice(i, 1);
}
}
var soloMode = false;
/* if (!(session.cleanOutput)){
if (session.lowerVolume.length){
getById("header").classList.add('orange');
getById("head6").classList.remove('hidden');
} else if (session.audioGain === 0){
// do nothing.
} else {
getById("header").classList.remove('orange');
getById("head6").classList.add('hidden');
}
} */
if (session.lowerVolume.length){
soloMode = true;
}
if (soloMode){
for (var UUID in session.rpcs){
if (session.lowerVolume.includes(UUID)){
if (session.rpcs[UUID].videoElement && (session.rpcs[UUID].savedVolume!==false)){ // isolated
session.rpcs[UUID].videoElement.volume = session.rpcs[UUID].savedVolume;
session.rpcs[UUID].savedVolume = false;
}
continue;
}
if (session.rpcs[UUID].videoElement && (session.rpcs[UUID].savedVolume==false)){ // not isolated
session.rpcs[UUID].savedVolume = session.rpcs[UUID].videoElement.volume;
session.rpcs[UUID].videoElement.volume = session.rpcs[UUID].savedVolume*0.25;
}
}
} else {
for (var UUID in session.rpcs){
if (session.rpcs[UUID].videoElement && (session.rpcs[UUID].savedVolume!==false)){ // isolated
session.rpcs[UUID].videoElement.volume = session.rpcs[UUID].savedVolume;
session.rpcs[UUID].savedVolume = false;
}
}
}
}
session.applyIsolatedChat = function(UUID=false){ // mutes outbound mic audio; for guests, and not the director
log("applyIsolatedChat");
session.applyIsolatedVolume(); // this toggle the speaker output
var i = session.micIsolated.length;
while (i--){
if (!(session.micIsolated[i] in session.pcs) && !(session.micIsolated[i] in session.rpcs)){
session.micIsolated.splice(i, 1);
}
}
var muteList = [...session.micIsolated]; // one thing I hate about Javascript. Doesn't actually copy arrays.
var soloMode = false;
if (session.micIsolatedAutoMute){ // session.micIsolatedAutoMute
soloMode = true;
session.micIsolatedAutoMute.forEach(uid =>{
if (!muteList.includes(uid) && (uid in session.rpcs || uid in session.pcs)){
muteList.push(uid);
}
});
}
if (muteList.length){
soloMode = true;
}
if (!(session.cleanOutput)){
if (soloMode){
getById("header").classList.add('orange');
getById("head6").classList.remove('hidden');
} else if (session.audioGain === 0){
// do nothing.
} else {
getById("header").classList.remove('orange');
getById("head6").classList.add('hidden');
}
}
/////
if (session.directorSpeakerMuted!==null){
for (var uuid in session.rpcs){
try{
var receivers = getReceivers2(uuid);//session.rpcs[uuid].getReceivers();
for (var i=0; i {
if (!sender.track){return;}
if (sender.track.kind !== "audio"){return;}
var settings = {};
if (!soloMode){
settings.active = true;
session.pcs[UUID].audioMutedOverride = false;
} else if (muteList.indexOf(UUID)>=0){
settings.active = true;
session.pcs[UUID].audioMutedOverride = false;
} else {
log("MUTING via session.applyIsolatedChat");
settings.active = false;
session.pcs[UUID].audioMutedOverride = true
}
setEncodings(sender, settings);
});
} catch(e){errorlog(e);}
} else {
for (var UUID in session.pcs){
try {
var senders = getSenders2(UUID);
senders.forEach((sender) => {
if (!sender.track){return;}
if (sender.track.kind !== "audio"){return;}
var settings = {};
if (!soloMode){
settings.active = true;
session.pcs[UUID].audioMutedOverride = false;
} else if (muteList.indexOf(UUID)>=0){
settings.active = true;
session.pcs[UUID].audioMutedOverride = false;
} else {
log("MUTING via session.applyIsolatedChat");
settings.active = false;
session.pcs[UUID].audioMutedOverride = true;
}
setEncodings(sender, settings);
});
} catch(e){errorlog(e);}
}
}
}
var FirefoxSenders = {};
function setEncodings(sender, settings=null, callback=null, cbarg=null){
if (!settings){
if (!(sender.encodingsQueue)){ // not set
return;
} else if (!sender.encodingsQueue.length){ // none left
return;
}
} else if (!("encodingsQueue" in sender)){
sender.encodingsQueue = [[settings, callback, cbarg]];
} else {
sender.encodingsQueue.push([settings, callback, cbarg]);
}
if (sender.encodingsQueueActive){return;}
try {
sender.encodingsQueueActive = true; // we're now busy.
var options = sender.encodingsQueue.shift();
settings = options[0];
callback = options[1];
cbarg = options[2];
const params = sender.getParameters();
if (!params.encodings || (params.encodings.length==0)){
params.encodings = [{}];
}
var changed = false;
for (var setting in settings){
if (settings[setting]===null){
if (setting in params.encodings[0]){
delete params.encodings[0][setting];
changed = true
}
} else {
if (setting in params.encodings[0]){
if (params.encodings[0][setting] !== settings[setting]){
changed = true
}
} else {
changed = true
}
params.encodings[0][setting] = settings[setting];
}
}
log(settings);
// if old Firefox, see if I can do something other than Active?
if (!changed && !Firefox && !SafariVersion){
log("SET ENCODINGS MATCH INPUT; skipping");
if (callback){
if (cbarg){
setTimeout(function(){callback(cbarg);},0);
} else {
setTimeout(function(){callback();},0);
}
}
sender.encodingsQueueActive = false;
setEncodings(sender);
return;
}
if (Firefox && !(Firefox >=110)){ // https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpEncodingParameters now supported in v110, but old versions will need this function still
if ("active" in settings){
warnlog("Firefox does not support track active state. We will use enable/disable for that instead.");
if (FirefoxSenders.sender){
if (FirefoxSenders.sender.lastState === false){
FirefoxSenders.sender.activeState = settings.active;
// already set to false, so should stay disabled
} else {
FirefoxSenders.sender.activeState = settings.active;
sender.track.enabled = settings.active; // either true or false
}
} else {
FirefoxSenders.sender = {lastState: sender.track.enabled, activeState: settings.active};
sender.track.enabled = settings.active;
}
delete settings.active;
if (!Object.keys(settings).length){
if (callback){
if (cbarg){
setTimeout(function(){callback(cbarg);},0);
} else {
setTimeout(function(){callback();},0);
}
}
log("COMPELTED FIREFOX SET ENCODINGS");
sender.encodingsQueueActive = false;
setEncodings(sender);
return;
}
}
} else if (Firefox){ // Firefox , all versions, don't support active state with audio yet.?? GAhhhhhhhh!
if (("track" in sender) && ("kind" in sender.track) && (sender.track.kind == "audio")){
if ("active" in settings){
warnlog("Firefox does not support track active state with AUDIO yet... We will use enable/disable for that instead.");
if (FirefoxSenders.sender){
if (FirefoxSenders.sender.lastState === false){
FirefoxSenders.sender.activeState = settings.active;
// already set to false, so should stay disabled
} else {
FirefoxSenders.sender.activeState = settings.active;
sender.track.enabled = settings.active; // either true or false
}
} else {
FirefoxSenders.sender = {lastState: sender.track.enabled, activeState: settings.active};
sender.track.enabled = settings.active;
}
delete settings.active;
if (!Object.keys(settings).length){
if (callback){
if (cbarg){
setTimeout(function(){callback(cbarg);},0);
} else {
setTimeout(function(){callback();},0);
}
}
log("COMPELTED FIREFOX SET ENCODINGS");
sender.encodingsQueueActive = false;
setEncodings(sender);
return;
}
}
}
}
sender.setParameters(params).then(() => {
if (callback){
if (cbarg){
setTimeout(function(){callback(cbarg);},0);
} else {
setTimeout(function(){callback();},0);
}
}
sender.encodingsQueueActive = false;
setEncodings(sender);
}).catch((e)=>{
errorlog(e);
sender.encodingsQueueActive = false;
setEncodings(sender);
});
} catch(e){
errorlog(e);
sender.encodingsQueueActive = false;
}
}
session.applySoloChat = function(apply=true){ // mutes outbound mic audio; ;; does the actual solo chat muting for the director
if (session.director===false){
session.applyIsolatedChat();
return;
} else if (!session.directorEnabledPPT){
return;
}
log("applySoloChat()");
var i = session.soloChatUUID.length;
while (i--){
if (!(session.soloChatUUID[i] in session.pcs)){
session.soloChatUUID.splice(i, 1);
log("splicing out: "+i);
}
}
for (var uuid in session.pcs){ // not sure what to do here wrt to screen tracks
try {
var senders = getSenders2(uuid);
senders.forEach((sender) => {
if (!sender.track){return;}
if (sender.track.kind !== "audio"){return;}
var settings = {};
if (session.soloChatUUID.length && (session.soloChatUUID.includes(uuid))){
settings.active = true;
setEncodings(sender, settings, function(uid){
log("2: "+uid);
try {
document.querySelectorAll('[data-action-type="solo-chat"][data--u-u-i-d="'+uid+'"]')[0].classList.add("pressed");
document.querySelectorAll('[data-action-type="solo-chat"][data--u-u-i-d="'+uid+'"]')[0].ariaPressed = "true";
document.querySelectorAll('[data-action-type="solo-chat"][data--u-u-i-d="'+uid+'"]')[0].classList.remove("hint");
} catch(e){
warnlog(e);
}
}, uuid);
} else if (session.soloChatUUID.length==0){
settings.active = true;
setEncodings(sender, settings, function(uid){
log(uid);
try {
document.querySelectorAll('[data-action-type="solo-chat"][data--u-u-i-d="'+uid+'"]')[0].classList.remove("pressed");
document.querySelectorAll('[data-action-type="solo-chat"][data--u-u-i-d="'+uid+'"]')[0].ariaPressed = "false";
document.querySelectorAll('[data-action-type="solo-chat"][data--u-u-i-d="'+uid+'"]')[0].classList.remove("hint");
} catch(e){
warnlog(e);
}
}, uuid);
} else {
settings.active = false;
setEncodings(sender, settings, function(uid){
warnlog("muted the output to:"+ uid);
try {
document.querySelectorAll('[data-action-type="solo-chat"][data--u-u-i-d="'+uid+'"]')[0].classList.remove("pressed");
document.querySelectorAll('[data-action-type="solo-chat"][data--u-u-i-d="'+uid+'"]')[0].ariaPressed = "false";
document.querySelectorAll('[data-action-type="solo-chat"][data--u-u-i-d="'+uid+'"]')[0].classList.add("hint");
} catch(e){
warnlog(e);
}
}, uuid);
}
});
} catch(e){errorlog(e);}
}
if (apply==false){
if (session.soloChatUUID.length){
session.muted_savedState=session.muted;
session.muted=false;
data = {};
data.muteState = session.muted;
for (var i=0;i{
try {
trk.contentHint = session.audioContentHint;
} catch(e){
errorlog(e);
}
});
}
if (session.mc && session.mc.getSenders && tracks.length){ // mixMinus won't work with meshcast, so don't bother.
session.mc.getSenders().forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams?
if (sender.track && sender.track.kind == "audio") {
tracks.forEach(trk=>{
sender.replaceTrack(trk);
})
}
});
}
if (session.whipOut && session.whipOut.getSenders && tracks.length){ // mixMinus won't work with meshcast, so don't bother.
session.whipOut.getSenders().forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams?
if (sender.track && sender.track.kind == "audio") {
tracks.forEach(trk=>{
sender.replaceTrack(trk);
})
}
});
}
for (UUID in session.pcs) {
if ("realUUID" in session.pcs[UUID]){continue;} // do not process the screen share audio
if (session.pcs[UUID].allowAudio == true) {
var senders = getSenders2(UUID);
if (session.mixMinus){
log("mixMinus START ..");
var STRM = mixMinusAudio(UUID);
if (!STRM){continue;}
STRM.getAudioTracks().forEach(trk=>{
if (session.audioContentHint){
trk.contentHint = session.audioContentHint;
}
var added = false;
senders.forEach((sender) => {
if (added) {
if (sender.track && (sender.track.kind == "audio")){
sender.track.enabled = false;
}
return;
}
if (sender.track && (sender.track.kind == "audio")) {
sender.replaceTrack(trk);
sender.track.enabled = true;
added = true;
warnlog("ADDED 5");
}
});
if (added) {
return;
}
session.pcs[UUID].addTrack(trk, STRM);
});
continue;
}
senders.forEach((sender) => {
var good = false;
if (sender.track && sender.track.id && (sender.track.kind == "audio")) {
tracks.forEach(function(track) { // audio also
if (track.id == sender.track.id) {
good = true;
}
});
} else { // video or something else; ignore it.
return;
}
if (good) {
return;
}
sender.track.enabled = false;
//session.pcs[UUID].removeTrack(sender); // Apparently removeTrack causes renogiation; also kills send/recv.
});
if (tracks.length) {
tracks.forEach(function(track) {
var matched = false;
var senders = getSenders2(UUID);
senders.forEach((sender) => {
if (sender.track && sender.track.id && (sender.track.kind == "audio")) {
warnlog(sender.track.id + " " + track.id);
if (sender.track.id == track.id) {
warnlog("MATCHED 1");
matched = true;
}
}
});
if (matched) {
return;
}
var added = false;
var senders = getSenders2(UUID);
senders.forEach((sender) => {
if (added) {
return;
}
if (sender.track && (sender.track.kind == "audio") && (sender.track.enabled == false)) {
sender.replaceTrack(track);
sender.track.enabled = true;
added = true;
warnlog("ADDED 2");
}
});
if (added) {
return;
}
var sender = session.pcs[UUID].addTrack(track, session.videoElement.srcObject);
});
} else {
var senders = getSenders2(UUID);
senders.forEach((sender) => {
if (sender.track && sender.track.kind == "audio") {
sender.track.enabled = false; // (trying this instead)
//session.pcs[UUID].removeTrack(sender); // Apparently removeTrack causes renogiation; also kills send/recv.
}
});
}
}
}
if (session.director!==false){
session.applySoloChat(); // mute streams that should be muted if a director
}
session.applyIsolatedChat();
try {
if (toggleSettingsState){
updateConstraintSliders();
}
} catch(e){}
if (callback){
try{
var data = {};
data.UUID = callback;
data.audioOptions = listAudioSettingsPrep();
sendMediaDevices(data.UUID);
session.sendMessage(data, data.UUID);
} catch(e){}
}
} catch (e) {
errorlog(e);
}
if (document.getElementById("gowebcam")) {
document.getElementById("gowebcam").dataset.audioready = true;
if (document.getElementById("gowebcam").dataset.ready && (document.getElementById("gowebcam").dataset.ready=="true")){
document.getElementById("gowebcam").disabled = false;
document.getElementById("gowebcam").innerHTML = miscTranslations["start"];
}
}
}
async function press2talk(clean = false) {
var ele = getById("press2talk");
ele.style.minWidth = "127px";
ele.style.padding = "7px";
getById("settingsbutton").classList.remove("hidden");
if (!document.getElementById("controls_director") && session.showDirector){
createDirectorOnlyBox();
}
if (session.taintedSession){
var msg = {};
msg.virtualHangup = false;
session.sendMessage(msg);
}
log("DIRECTOR STREAM SETUP");
if (getById("press2talk").dataset.enabled == true){log("already enabled");return;}
getById("press2talk").dataset.enabled = true;
getById("press2talk").outerHTML = "";
getById("mutebutton").classList.remove("hidden");
getById("hangupbutton2").classList.remove("hidden");
if (session.screenshareType===3){
getById("screenshare3button").className = "float";
getById("screensharebutton").className = "float hidden";
getById("screenshare2button").className = "float hidden";
} else if (session.screenshareType===1){
getById("screensharebutton").className = "float";
getById("screenshare3button").className = "float hidden";
getById("screenshare2button").className = "float hidden";
} else if (session.screenshareType===2){
getById("screenshare2button").className = "float";
getById("screensharebutton").className = "float hidden";
getById("screenshare3button").className = "float hidden";
} else if (session.broadcast===null){
// sstype=1, since in self-broadcast mode
getById("screensharebutton").className = "float";
getById("screenshare2button").className = "float hidden";
getById("screenshare3button").className = "float hidden";
} else {
// sstype=3, since not in broadcast mode
getById("screensharebutton").className = "float hidden";
getById("screenshare2button").className = "float hidden";
getById("screenshare3button").className = "float";
}
checkBasicStreamsExist();
session.videoElement.id = "videosource"; // could be set to UUID in the future
session.videoElement.dataset.menu = "context-menu-video";
if (session.streamID){
session.videoElement.dataset.sid = session.streamID;
}
// videosource
session.videoElement.muted = true;
session.videoElement.autoplay = true;
session.videoElement.controls = session.showControls || false;
session.videoElement.setAttribute("playsinline","");
if (document.getElementById("videoContainer_director")){
getById("videoContainer_director").appendChild(session.videoElement);
} else {
getById("miniPerformer").appendChild(session.videoElement);
}
if (session.screenShareElement && document.getElementById("videoScreenContainer_director")){
getById("videoScreenContainer_director").appendChild(session.screenShareElement);
} else if (session.screenShareElement){
getById("miniPerformer").appendChild(session.screenShareElement);
}
session.videoElement.title = "This is the preview of the Director's audio and video output.";
session.videoElement.onpause = (event) => { // prevent things from pausing; human or other
if (!((event.ctrlKey) || (event.metaKey) )){
log("Video paused; auto playing");
event.currentTarget.play().then(_ => {
log("playing 9");
}).catch(warnlog);
}
};
session.videoElement.addEventListener('click', function(e) { // show stats of video if double clicked
log("click");
try {
if ((e.ctrlKey)||(e.metaKey)){
e.preventDefault();
////////////////////////
var [menu, innerMenu] = statsMenuCreator();
//////////////////////////////////
menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu);
printMyStats(innerMenu);
e.stopPropagation();
return false;
}
} catch(e){errorlog(e);}
});
updatePushId();
/* if (session.directorEnabledPPT){
enumerateDevices().then(gotDevices).then(async function() {
console.log("done");
toggleSettings();
});
return;
} */
//await toggleSettings();
var constraint = {video: false, audio: true};
if (session.videoDevice){
constraint.video = true;
}
if (session.audioDevice===0){
constraint.audio = false;
}
requestBasicPermissions(constraint, function(){
log("requestBasicPermissions done");
enumerateDevices().then(gotDevices).then(async function() {
log("enumerateDevices+gotDevices complete");
pokeIframeAPI('director-share', true, false, session.streamID); // director has started publishing; even if no audio/video.
log("session.directorEnabledPPT: " +session.directorEnabledPPT);
if (session.directorEnabledPPT){
return;
}
if (session.audioDevice!==0){ // change from Auto to Selected Audio Device
log("SETTING AUDIO DEVICE!!");
activatedPreview = false;
await grabAudio("#audioSource3");
}
if (session.videoDevice !== 0) {
activatedPreview = false;
if (session.quality !== false) {
await grabVideo(session.quality, 'videosource', "#videoSource3");
} else {
//session.quality_wb = parseInt(getById("webcamquality").elements.namedItem("resolution").value);
await grabVideo(session.quality_wb || 0, 'videosource', "#videoSource3");
}
}
if (session.videoMutedFlag){
session.videoMuted = true;
toggleVideoMute(true);
}
session.directorEnabledPPT = true;
toggleMute(true);
//await toggleSettings();
log("session.seeding: " +session.seeding);
if (session.seeding){
setTimeout(function(){meshcast()},1000);
return;
}
if (session.autorecord || session.autorecordlocal){
log("AUTO RECORD START");
setTimeout(function(v){
if (session.director){
recordVideo(document.querySelector("[data-action-type='recorder-local'][data-sid='"+session.streamID+"']"), null, session.recordLocal)
} else if (v.stopWriter || v.recording){
} else if (v.startWriter){
v.startWriter();
} else {
recordLocalVideo(null, session.recordLocal, v)
}
},2000, session.videoElement);
}
session.seeding=true;
await session.seedStream();
//meshcast();
});
});
}; // publishdirector
function statsMenuCreator(){
if (getById("menuStatsBox")){
clearInterval(getById("menuStatsBox").interval);
getById("menuStatsBox").remove();
}
var menu = document.createElement("div");
menu.id = "menuStatsBox";
menu.className = "debugStats remotestats";
getById('main').appendChild(menu);
menu.style.left = parseInt(Math.random()*10)+15+"px"
menu.style.top = parseInt(Math.random()*10)+"px"
menu.innerHTML="
Statistics
";
var menuCloseBtn = document.createElement("button");
menuCloseBtn.className="close";
menuCloseBtn.innerHTML="×";
menu.appendChild(menuCloseBtn);
var innerMenu = document.createElement("div");
menu.appendChild(innerMenu);
menuCloseBtn.addEventListener('click', function(eve) {
clearInterval(menu.interval);
eve.currentTarget.parentNode.remove();
eve.preventDefault();
eve.stopPropagation();
});
return [menu, innerMenu];
}
// WEBCAM
session.publishStream = function(v){ // stream is used to generated an SDP
log("STREAM SETUP");
if (session.transcript){
setTimeout(function(){setupClosedCaptions();},1000);
}
if (!session.streamSrc){
checkBasicStreamsExist();
}
session.streamSrc.oninactive = function streamoninactive() {
warnlog('Stream inactive');
if (session.videoElement.recording){
session.videoElement.recorder.stop();
}
};
if (session.streamSrc.getVideoTracks().length==0){
warnlog("NO VIDEO TRACK INCLUDED");
}
if (session.streamSrc.getAudioTracks().length==0){
warnlog("NO AUDIO TRACK INCLUDED");
}
var container = document.createElement("div");
v.container = container;
container.id = "container";
if (session.cleanOutput){
container.style.height = "100%";
v.style.maxWidth = "100%";
v.style.boxShadow = "none";
}
if (session.cover){
container.style.setProperty('height', '100%', 'important');
}
//container.className = "vidcon";
getById("gridlayout").appendChild(container);
v.className = "tile"; //"tile task"; TODO: get working (will add task later on instead)
v.muted = true;
v.autoplay = true;
if (session.showControls!==null){
v.controls = session.showControls;
} else if (session.mobile){
v.controls = true;
} else {
v.controls = session.showControls || false;
}
v.setAttribute("playsinline","");
v.id = "videosource"; // could be set to UUID in the future
v.oncanplay = null;
session.videoElement = v;
container.appendChild(v);
toggleMute(true);
if (session.nopreview){
v.style.display="none";
container.style.display="none";
}
if (((session.roomid===false || session.roomid==="") && (session.quality===false)) || session.forceMediaSettings){
try {
if ((session.quality_wb!==false) && (session.quality===false)){
getById("webcamquality3").elements.namedItem("resolution").value = session.quality_wb;
} else if (session.quality!==false){
getById("webcamquality3").elements.namedItem("resolution").value = session.quality;
}
getById("gear_webcam3").style.display = "inline-block";
getById("webcamquality3").onchange = function(event) {
if (parseInt(getById("webcamquality3").elements.namedItem("resolution").value) == 2) {
if (session.maxframeRate===false){
session.maxframeRate = 30;
session.maxframeRate_q2 = true;
}
} else if (session.maxframeRate_q2){
session.maxframeRate = false;
session.maxframeRate_q2 = false;
}
activatedPreview = false;
session.quality_wb = parseInt(getById("webcamquality3").elements.namedItem("resolution").value);
grabVideo(session.quality_wb, "videosource", "select#videoSource3");
};
} catch (e) {errorlog(e);}
}
var bigPlayButton = document.getElementById("bigPlayButton");
if (bigPlayButton){
bigPlayButton.parentNode.removeChild(bigPlayButton);
}
if (session.streamID){
session.videoElement.dataset.sid = session.streamID;
}
if (session.statsMenu){
var [menu, innerMenu] = statsMenuCreator();
menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu);
printMyStats(innerMenu);
}
if (session.director){ // the director doesn't load a webcam by default anyways.
// audio is not mucked with
} else if (session.scene!==false){ // it's a scene, and there are no previews in a scene.
//setTimeout(function(){updateMixer();},10);
} else if (session.roomid!==false){
if (session.roomid===""){
if (!session.view || (session.view==="")){
if (session.fullscreen){
session.windowed = false;
} else {
v.className = "myVideo"; //"myVideo task"; TODO: get working
session.windowed = true;
container.classList.add("vidcon");
}
getById("mutespeakerbutton").classList.add("hidden");
applyMirror(session.mirrorExclude);
container.style.width="100%";
//container.style.height="100%";
container.style.alignItems = "center";
container.backgroundColor = "#666";
setTimeout(function (){dragElement(v);},1000);
play();
} else {
session.windowed = false;
applyMirror(session.mirrorExclude);
play();
//setTimeout(function(){updateMixer();},10);
}
} else {
//session.cbr=0; // we're just going to override it
if (session.stereo==5){ // not a scene or director, so we will assume its a guest. changing to stereo=3
session.stereo=3;
}
session.windowed = false;
applyMirror(session.mirrorExclude);
if (session.include.length){
play();
}
//setTimeout(function(){updateMixer();},10);
}
} else {
if (session.fullscreen){
session.windowed = false;
} else {
v.className = "myVideo"; //"myVideo task"; TODO: get working
container.classList.add("vidcon");
session.windowed = true;
}
getById("mutespeakerbutton").classList.add("hidden");
applyMirror(session.mirrorExclude);
container.style.width="100%";
//container.style.height="100%";
//container.style.display = "flex";
container.style.alignItems = "center";
container.backgroundColor = "#666";
setTimeout(function (){dragElement(v);},1000);
}
v.onpause = (event) => { // prevent things from pausing; human or other
if (!((event.ctrlKey) || (event.metaKey) )){
log("Video paused; auto playing");
event.currentTarget.play().then(_ => {
log("playing 10");
}).catch(warnlog);
}
};
v.addEventListener('click', function(e) {
log("click");
try {
if ((e.ctrlKey)||(e.metaKey)){
e.preventDefault();
var [menu, innerMenu] = statsMenuCreator();
menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu);
printMyStats(innerMenu);
e.stopPropagation();
return false;
}
} catch(e){errorlog(e);}
});
v.touchTimeOut = null;
v.touchLastTap = 0;
v.touchCount = 0;
v.addEventListener('touchend', function(event) {
if (session.disableMouseEvents){return;}
log("touched");
//document.ontouchup = null;
//document.onmouseup = null;
document.onmousemove = null;
document.ontouchmove = null;
var currentTime = new Date().getTime();
var tapLength = currentTime - v.touchLastTap;
clearTimeout(v.touchTimeOut);
if (tapLength < 500 && tapLength > 0) {
///
log("double touched");
v.touchCount+=1;
event.preventDefault();
if (v.touchCount<5){
v.touchLastTap = currentTime;
return false;
}
v.touchLastTap = 0;
v.touchCount=0;
var [menu, innerMenu] = statsMenuCreator();
menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu);
printMyStats(innerMenu);
event.stopPropagation();
return false;
//////
} else {
v.touchCount=1;
v.touchLastTap = currentTime;
v.touchTimeOut = setTimeout(function(vv) {
clearTimeout(vv.touchTimeOut);
vv.touchLastTap = 0;
vv.touchCount=0;
}, 5000, v);
}
});
updateReshareLink();
pokeIframeAPI('started-camera'); // depreciated
pokeIframeAPI('camera-share', true);
if (session.videoMutedFlag){
session.videoMuted = true;
toggleVideoMute(true);
}
if (!gotDevices2AlreadyRan){
enumerateDevices().then(gotDevices2); // this is needed for iOS; was previous set to timeout at 100ms, but would be useful everywhere I think
}
v.dataset.menu = "context-menu-video";
if (!session.cleanOutput){
v.classList.add("task"); // this adds the right-click menu
}
session.postPublish();
if (session.autorecord || session.autorecordlocal){
log("AUTO RECORD START");
setTimeout(function(v){
if (session.director){
recordVideo(document.querySelector("[data-action-type='recorder-local'][data-sid='"+session.streamID+"']"), null, session.recordLocal)
} else if (v.stopWriter || v.recording){
} else if (v.startWriter){
v.startWriter();
} else {
recordLocalVideo(null, session.recordLocal, v)
}
},2000, v);
}
setTimeout(function(){updateMixer();},10);
}; // publishStream
function stickyMessage(message){
var textOverlay = getById("stickyMsgs");
if (textOverlay) {
var spanOverlay = document.createElement("span");
spanOverlay.innerHTML = message;
var closeBtn = document.createElement("button");
closeBtn.className = "overlayCloseBtn";
closeBtn.innerText = "X";
closeBtn.onclick = function(){this.parentNode.remove();};
textOverlay.appendChild(spanOverlay);
spanOverlay.appendChild(closeBtn);
textOverlay.classList.remove("hidden");
}
}
session.postPublish = async function(){
log("Post publish");
if (session.welcomeMessage){
stickyMessage(session.welcomeMessage);
// getChatMessage(session.welcomeMessage, false, true, true);
}
if (session.welcomeImage){
var welcomeoverlay = document.createElement("img");
welcomeoverlay.src = session.welcomeImage;
welcomeoverlay.className = "fadein";
welcomeoverlay.id = "welcomeImage";
document.body.appendChild(welcomeoverlay);
await sleep(2000);
setTimeout(function(welcomeoverlay){
welcomeoverlay.style = "animation: fadeout 1s;"
setTimeout(function(welcomeoverlay){
welcomeoverlay.remove();
},990,welcomeoverlay);
}, 1000, welcomeoverlay);
}
clearInterval(session.updateLocalStatsInterval);
session.updateLocalStatsInterval = setInterval(function(){updateLocalStats();},session.statsInterval);
pokeIframeAPI("screen-share-state", false);
session.seeding=true;
session.seedStream();
if (session.whipOutput){
whipOut();
}
if (session.whepHost){
whepOut();
}
}
async function publishScreen2(constraints, audioList=[], audio=true, overrideFramerate=false){ // webcam stream is used to generated an SDP
log("SCREEN SHARE SETUP");
if (!navigator.mediaDevices.getDisplayMedia){
setTimeout(function(){
if (iOS || iPad){
warnUser("Sorry, but your iOS browser does not support screen-sharing.\n\nPlease see this guide for an alternative method to do so.", false, false);
} else if (session.mobile){
warnUser("Sorry, your browser does not support screen-sharing.\n\nThe Android native app should support it though.", false, false);
} else {
warnUser("Sorry, your browser does not support screen-sharing.\n\nPlease use the desktop versions of Firefox or Chrome instead.");
}
},1);
return false;
}
if (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1) {
if (!ElectronDesktopCapture){
if (!(session.cleanOutput && session.cleanish==false)){
warnUser("Enable Elevated Privileges to allow screen-sharing. (right click this window to see that option)");
}
return false;
}
}
var streams = [];
for (var i=1; i{
if (getUserMediaRequestID !== gumID) {
warnlog("GET USER MEDIA CALL HAS EXPIRED 3");
stream.getTracks().forEach(function(track) {
stream.removeTrack(track);
track.stop();
log("stopping old track");
});
return;
}
streams.push(stream);
}).catch(errorlog);
}
}
if (session.audioDevice === 0 ){
constraints.audio = false;
}
if (session.screenshareVideoOnly){
constraints.audio = false;
}
if ((constraints.video!==false) && (Object.keys(constraints.video).length==0)){
constraints.video = true;
}
log(constraints);
getUserMediaRequestID+=1;
var gumID = getUserMediaRequestID;
return navigator.mediaDevices.getDisplayMedia(constraints).then(async function (stream){
if (getUserMediaRequestID !== gumID) {
warnlog("GET USER MEDIA CALL HAS EXPIRED 3");
stream.getTracks().forEach(function(track) {
stream.removeTrack(track);
track.stop();
log("stopping old track");
});
return;
}
try {
var constraint = {};
if (session.forceAspectRatio && (session.forceScreenShareAspectRatio===null)){
constraint.aspectRatio = parseFloat(session.forceAspectRatio);
} else if (session.forceScreenShareAspectRatio){
constraint.aspectRatio = parseFloat(session.forceScreenShareAspectRatio);
}
if (overrideFramerate){
constraint.frameRate = overrideFramerate;
}
if (Object.keys(constraint).length){
await stream.getVideoTracks()[0].applyConstraints({
advanced: [constraint]
});
log({
advanced: [constraint]
});
}
} catch(e){errorlog(e);}
/// RETURN stream for preview? rather than jumping right in.
session.screenShareState=true;
pokeIframeAPI('screen-share-state', session.screenShareState, null, session.streamID);
notifyOfScreenShare();
try {
stream.getVideoTracks()[0].onended = function () {
toggleScreenShare();
};
} catch(e){log("No Video selected; screensharing?");}
// OR, jump right in, and let user change from there
if (session.roomid!==false){
if ((session.roomid==="") && ((!(session.view)) || (session.view===""))){
} else {
getById("head3").classList.add('hidden');
getById("head3a").classList.add('hidden');
log("ROOMID EANBLED");
log("Update Mixer Event on REsize SET");
window.onresize = updateMixer;
window.onorientationchange = function(){setTimeout(async function(){
if (session.forceAspectRatio){
await updateCameraConstraints("aspectRatio", session.forceAspectRatio);
}
if (session.effect && (session.effect === "7")){digitalZoom(true);}
updateForceRotate();
updateMixer();
}, 200);};
joinRoom(session.roomid);
}
} else {
getById("head3").classList.remove('hidden');
getById("head3a").classList.remove('hidden');
getById("logoname").style.display = 'none';
}
updatePushId();
if (stream.getAudioTracks().length){
screenShareAudioTrack = stream.getAudioTracks()[0];
}
log("adding tracks");
for (var i=0; i{
stream.addTrack(track);
});
}
streams = null;
if (!session.screenshareVideoOnly && session.audioDevice !== 0){
if (stream.getAudioTracks().length==0){
if (!(session.cleanOutput)){
if (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1){
// Electron has no audio.
} else {
setTimeout(function(){warnUser(miscTranslations["no-audio-source-detected"]);},300);
}
}
}
}
try {
session.streamSrc = stream;
} catch (e){errorlog(e);}
toggleMute(true);
var v = createVideoElement();
session.videoElement = v;
if (session.streamID){
session.videoElement.dataset.sid = session.streamID;
}
var container = document.createElement("div");
v.container = container;
container.id = "container_screen";
container.style.height = "100%";
if (session.cleanOutput){
v.style.maxWidth = "100%";
v.style.boxShadow = "none";
}
//container.className = "vidcon";
getById("gridlayout").appendChild(container);
if (session.nopreview){
v.style.display="none";
container.style.display="none";
}
//if (session.cover){
// container.style.setProperty('height', '100%', 'important');
//}
container.appendChild(v);
v.className = "tile";
if (session.director){
} else if (session.scene!==false){
setTimeout(function(){updateMixer();},1);
} else if (session.roomid!==false){
if (session.roomid===""){
if (!(session.view) || (session.view==="")){
getById("mutespeakerbutton").classList.add("hidden");
if (session.fullscreen){
session.windowed = false;
if (session.mirrored && session.flipped){
v.style.transform = " scaleX(-1) scaleY(-1)";
v.classList.add("mirrorControl");
} else if (session.mirrored){
v.style.transform = "scaleX(-1)";
v.classList.add("mirrorControl");
} else if (session.flipped){
v.style.transform = "scaleY(-1)";
v.classList.remove("mirrorControl");
} else {
v.style.transform = "";
v.classList.remove("mirrorControl");
}
} else {
v.className = "myVideo";
session.windowed = true;
if (session.mirrored && session.flipped){
v.style.transform = " scaleX(-1) scaleY(-1) translate(0, 50%)";
v.classList.add("mirrorControl");
} else if (session.mirrored){
v.style.transform = "scaleX(-1) translate(0, -50%)";
v.classList.add("mirrorControl");
} else if (session.flipped){
v.style.transform = "scaleY(-1) translate(0, 50%)";
v.classList.remove("mirrorControl");
} else {
v.style.transform = " translate(0, -50%)";
v.classList.remove("mirrorControl");
}
}
container.style.width="100%";
//container.style.height="100%";
container.style.alignItems = "center";
container.backgroundColor = "#666";
setTimeout(function (){dragElement(v);},1000);
play();
} else {
play();
setTimeout(function(){updateMixer();},1);
}
} else {
setTimeout(function(){updateMixer();},1);
}
} else {
getById("mutespeakerbutton").classList.add("hidden");
if (session.fullscreen){
session.windowed = false;
if (session.mirrored && session.flipped){
v.style.transform = " scaleX(-1) scaleY(-1)";
v.classList.add("mirrorControl");
} else if (session.mirrored){
v.style.transform = "scaleX(-1)";
v.classList.add("mirrorControl");
} else if (session.flipped){
v.style.transform = "scaleY(-1)";
v.classList.remove("mirrorControl");
} else {
v.style.transform = "";
v.classList.remove("mirrorControl");
}
} else {
v.className = "myVideo";
session.windowed = true;
container.classList.add("vidcon");
if (session.mirrored && session.flipped){
v.style.transform = " scaleX(-1) scaleY(-1) translate(0, 50%)";
v.classList.add("mirrorControl");
} else if (session.mirrored){
v.style.transform = "scaleX(-1) translate(0, -50%)";
v.classList.add("mirrorControl");
} else if (session.flipped){
v.style.transform = "scaleY(-1) translate(0, 50%)";
v.classList.remove("mirrorControl");
} else {
v.style.transform = " translate(0, -50%)";
v.classList.remove("mirrorControl");
}
}
container.style.width="100%";
//container.style.height="100%";
container.style.alignItems = "center";
container.backgroundColor = "#666";
}
if (!session.windowed){
window.onresize = updateMixer;
window.onorientationchange = function(){setTimeout(async function(){
if (session.forceAspectRatio){
await updateCameraConstraints("aspectRatio", session.forceAspectRatio);
}
if (session.effect && (session.effect === "7")){digitalZoom(true);}
updateForceRotate();
updateMixer();
}, 200);};
}
v.autoplay = true;
v.controls = session.showControls || false;
v.setAttribute("playsinline","");
v.muted = true;
v.id = "videosource";
v.dataset.menu = "context-menu-video";
if (!session.cleanOutput){
v.classList.add("task"); // this adds the right-click menu
}
//if (!v.srcObject || v.srcObject.id !== stream.id) {
// v.srcObject = stream;
v.srcObject = outboundAudioPipeline();
//}
v.onpause = (event) => { // prevent things from pausing; human or other
if (!((event.ctrlKey) || (event.metaKey) )){
log("Video paused; auto playing");
event.currentTarget.play().then(_ => {
log("playing 11");
}).catch(warnlog);
}
};
v.addEventListener('click', function(e) { // show stats of video if double clicked
log("click");
try {
if ((e.ctrlKey)||(e.metaKey)){
e.preventDefault();
var [menu, innerMenu] = statsMenuCreator();
menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu);
printMyStats(innerMenu);
e.stopPropagation();
return false;
}
} catch(e){errorlog(e);}
});
updateReshareLink();
if (session.videoMutedFlag){
session.videoMuted = true;
toggleVideoMute(true);
}
clearInterval(session.updateLocalStatsInterval);
session.updateLocalStatsInterval = setInterval(function(){updateLocalStats();},session.statsInterval);
session.seeding=true;
session.seedStream();
//pokeIframeAPI('started-screenshare'); // depreciated
pokeIframeAPI('screen-share-state', true, null, session.streamID); // (action, value = null, UUID = null, SID=null)
if (session.autorecord || session.autorecordlocal){
log("AUTO RECORD START");
setTimeout(function(v){
if (session.director){
recordVideo(document.querySelector("[data-action-type='recorder-local'][data-sid='"+session.streamID+"']"), null, session.recordLocal)
} else if (v.stopWriter || v.recording){
} else if (v.startWriter){
v.startWriter();
} else {
recordLocalVideo(null, session.recordLocal, v)
}
},2000, v);
}
return true;
}).catch(function(err){
errorlog(err);
errorlog(err.name);
if ((err.name == "NotAllowedError") || (err.name == "PermissionDeniedError")){
// User Stopped it. (is this next part needed??)
session.screenShareState=false;
pokeIframeAPI('screen-share-state', session.screenShareState, null, session.streamID);
notifyOfScreenShare();
if (macOS){
warnUser(miscTranslations["screen-permissions-denied"], false, false);
}
return false;
} else {
if (audio==true){
if (err.name == "NotReadableError"){
if (!(session.cleanOutput)){
warnUser(miscTranslations["change-audio-output-device"], false, false);
}
return false;
} else {
constraints.audio=false;
if (!(session.cleanOutput)){
setTimeout(function(){warnUser(err);},1); // TypeError: Failed to execute 'getDisplayMedia' on 'MediaDevices': Audio capture is not supported
}
return publishScreen2(constraints, audioList, false);
}
} else {
if (!(session.cleanOutput)){
setTimeout(function(){warnUser(err);},1); // TypeError: Failed to execute 'getDisplayMedia' on 'MediaDevices': Audio capture is not supported
}
return false;
}
}
});
}; // publishStream2
var transferList = [];
var msgTransferList = [];
function cancelFile(ele){
var idx = ele.dataset.tid;
try{
transferList[idx].dc.close();
} catch(e){}
transferList[idx].status = 5;
updateDownloadLink(idx);
}
function requestFile(ele){
var idx = ele.dataset.tid;
transferList[idx].status = 1;
var fid = ele.dataset.fid;
var UUID = ele.dataset.uuid;
var msg = {};
msg.requestFile = fid;
msg.UUID = UUID;
session.sendRequest(msg, msg.UUID);
updateDownloadLink(idx);
pokeIframeAPI('request-file', fid, UUID);
}
function clearDownloadFile(ele){
var idx = ele.dataset.tid;
transferList[idx].status = 6;
updateDownloadLink(idx);
}
function addDownloadLink(fileList, UUID, pc){
if (session.nodownloads){return;} // downloads are blocked
log(fileList);
if (!fileList || !fileList.length){return;}
for (var i = 0; i< fileList.length; i++){
fileList[i].UUID = UUID;
fileList[i].completed = 0;
fileList[i].status = 0;
fileList[i].time = Date.now();
fileList[i].pc = pc[UUID];
transferList.push(fileList[i]);
}
if (session.chatbutton===false){return;} // messages can still appear as overlays
updateMessages();
if (session.beepToNotify) {
playtone();
}
if (session.chat == false) {
getById("chattoggle").className = "las la-comments toggleSize pulsate";
getById("chatbutton").className = "float";
if (getById("chatNotification").value) {
getById("chatNotification").value = getById("chatNotification").value + 1;
} else {
getById("chatNotification").value = 1;
}
getById("chatNotification").classList.add("notification", "red");
}
//if (session.broadcastChannel !== false) {
// session.broadcastChannel.postMessage(data); /* send */
//}
}
function updateDownloadLink(idx){
idx = parseInt(idx);
var elements = document.querySelectorAll('[data-tid="'+idx+'"]');
if (elements[0]) {
if (transferList[idx].status === 0){
elements[0].innerHTML = "Download it here";
} else if (transferList[idx].status === 1){
elements[0].innerHTML = "Requested";
//elements[0].onclick='cancelFile(this);'
} else if (transferList[idx].status === 2){
elements[0].innerHTML = "Downloading: "+parseInt(transferList[idx].completed*100)+"%";
elements[0].onclick = function(){cancelFile(this);}
} else if (transferList[idx].status === 3){
elements[0].innerHTML = "Completed";
elements[0].onclick = null;
elements[0].disabled = true;
} else if (transferList[idx].status === 4){
elements[0].innerHTML = "No longer available";
elements[0].onclick = null;
elements[0].disabled = true;
} else if (transferList[idx].status === 5){
elements[0].innerHTML = "Cancelled";
elements[0].onclick = null;
elements[0].disabled = true;
} else if (transferList[idx].status === 6){
getById("transfer_"+idx).style.display = "none";
//delete(transferList[idx]);
}
}
}
function showDownloadLinks(){
if (session.nodownloads){return;} // downloads are blocked
msgTransferList=[];
if (!transferList || !transferList.length){return;}
for (var i = 0; i< transferList.length; i++){
fileShareMessage(transferList[i], i);
}
}
function fileShareMessage(fileinfo, idx){
fileinfo.name = sanitizeChat(fileinfo.name); // keep it clean.
var label = false;
if (fileinfo.pc){
if (fileinfo.pc.label) {
label = sanitizeLabel(fileinfo.pc.label);
}
}
var data = {};
data.idx = idx;
if (fileinfo.status === 0){
data.msg = " has a shared a file with you: "+fileinfo.name+" Do you trust them? ";
} else if (fileinfo.status === 1){
data.msg = " has a shared a file with you: "+fileinfo.name+" ";
} else if (fileinfo.status === 2){
data.msg = " has a shared a file with you: "+fileinfo.name+" ";
} else if (fileinfo.status === 3){
data.msg = " has a shared a file with you: "+fileinfo.name+" ";
transferList[idx].status = 6;
} else if (fileinfo.status === 4){
data.msg = " has a shared a file with you: "+fileinfo.name+" ";
} else if (fileinfo.status === 5){
data.msg = " has a shared a file with you: "+fileinfo.name+" ";
transferList[idx].status = 6;
} else if (fileinfo.status === 6){
return;
}
var director=false; // add back in later.
if (session.directorList.indexOf(fileinfo.UUID)>=0){
director=true;
}
if (label) {
data.label = label;
if (director) {
data.label = "" + data.label + "";
} else {
data.label = "" + data.label + "";
}
} else if (director) {
data.label = "Director";
} else {
data.label = "Someone";
}
data.type = "action";
msgTransferList.push(data);
}
session.shareFile = function(ele, UUID=false, event=false){ // webcam stream is used to generated an SDP
if (session.hostedFiles===false){return;} // disabled
for (var i = 0; i < ele.files.length; i++){ // changing from a FileList to an Array. Arrays are easier to modify later on
ele.files[i].id = session.generateStreamID(8); // can't be too short, else can be brute forced
ele.files[i].state = 1;
ele.files[i].restricted = UUID;
session.hostedFiles.push(ele.files[i]);
}
log(session.hostedFiles);
//for (var in rpcs and pcs .... goes here
if (UUID===false){
for (UUID in session.pcs){
session.provideFileList(UUID);
}
for (UUID in session.rpcs){
if (UUID in session.pcs){continue;}
session.provideFileList(UUID);
}
} else {
session.provideFileList(UUID);
}
pokeIframeAPI('file-share', true);
closeModal();
}
session.hostFile = function(ele, event){ // webcam stream is used to generated an SDP
log("FILE TRANSFER SETUP");
session.hostedFiles = [];
for (var i = 0; i < ele.files.length; i++){ // changing from a FileList to an Array. Arrays are easier to modify later on
ele.files[i].id = session.generateStreamID(8); // can't be too short, else can be brute forced
ele.files[i].state = 1;
session.hostedFiles.push(ele.files[i]);
}
log(session.hostedFiles);
var container = document.createElement("div");
container.id = "container_host";
getById("gridlayout").appendChild(container);
if (session.cover){
container.style.setProperty('height', '100%', 'important');
}
if (session.roomid!==false){
if ((session.roomid==="") && ((!(session.view)) || (session.view===""))){
} else {
log("ROOMID EANBLED");
//log("Update Mixer Event on REsize SET");
//window.addEventListener("resize", updateMixer);// TODO FIX
//window.addEventListener("orientationchange", updateMixer);// TODO FIX
getById("head3").classList.add('hidden');
getById("head3a").classList.add('hidden');
joinRoom(session.roomid);
}
} else {
getById("head3").classList.remove('hidden');
getById("head3a").classList.remove('hidden');
getById("logoname").style.display = 'none';
}
getById("head1").className = 'hidden';
updatePushId()
getById("head1").className = 'hidden';
getById("head2").className = 'hidden';
if (!(session.cleanOutput)){
getById("chatbutton").className="float";
getById('sharefilebutton').classList.remove("hidden"); // we won't override "display:none", if set, though.
getById("hangupbutton").className="float";
getById("controlButtons").classList.remove("hidden");
getById("helpbutton").style.display = "inherit";
getById("reportbutton").style.display = "";
} else {
getById("controlButtons").classList.add("hidden");
}
updateReshareLink();
pokeIframeAPI('file-share', true);
pokeIframeAPI('started-fileshare'); // deprecated
clearInterval(session.updateLocalStatsInterval);
session.updateLocalStatsInterval = setInterval(function(){updateLocalStats();},session.statsInterval);
session.seeding=true;
session.seedStream();
}
function updateReshareLink(){
try{
var m = getById("mainmenu");
m.remove();
} catch (e){}
var added = "";
if (session.defaultPassword===false){
if (session.password!==false){
added="&pw="+session.password;
} else {
added="&pw=false";
}
}
var wss = "";
if (session.wssSetViaUrl){
if (session.customWSS && (session.customWSS!==true)){
wss = "&pie="+session.customWSS;
} else {
wss = "&wss="+session.wss;
}
}
var shareLink = "https://"+location.host+location.pathname+"?view="+session.streamID+added+wss;
if (document.getElementById("reshare")){
document.getElementById("reshare").href = shareLink;
document.getElementById("reshare").text = shareLink;
document.getElementById("reshare").style.width = ((document.getElementById("reshare").text.length + 1)*1.15 * 8) + 'px';
}
pokeIframeAPI('share-link', shareLink);
}
session.publishFile = function(ele, event){ // webcam stream is used to generated an SDP
log("FILE STREAM SETUP");
if (session.transcript){
setTimeout(function(){setupClosedCaptions();},1000);
}
var files = [];
for (var i = 0; i < ele.files.length; i++){ // changing from a FileList to an Array. Arrays are easier to modify later on
files.push(ele.files[i]);
}
log(files);
//var type = file.type;
var fileURL = URL.createObjectURL(files[0]);
var container = document.createElement("div");
container.id = "container";
//container.className = "vidcon";
if (session.cover){
container.style.setProperty('height', '100%', 'important');
}
var v = createVideoElement();
v.container = container;
if (session.cleanOutput){
container.style.height = "100%";
v.style.maxWidth = "100%";
v.style.boxShadow = "none";
}
if (session.streamID){
v.dataset.sid = session.streamID;
}
getById("gridlayout").appendChild(container);
if (session.roomid!==false){
if ((session.roomid==="") && ((!(session.view)) || (session.view===""))){
} else {
log("ROOMID EANBLED");
log("Update Mixer Event on REsize SET");
//window.addEventListener("resize", updateMixer);// TODO FIX
//window.addEventListener("orientationchange", updateMixer);// TODO FIX
getById("head3").classList.add('hidden');
getById("head3a").classList.add('hidden');
joinRoom(session.roomid);
}
} else {
getById("head3").classList.remove('hidden');
getById("head3a").classList.remove('hidden');
getById("logoname").style.display = 'none';
}
getById("head1").className = 'hidden';
updatePushId()
getById("head1").className = 'hidden';
getById("head2").className = 'hidden';
if (!(session.cleanOutput)){
getById("chatbutton").className="float";
getById('sharefilebutton').classList.remove("hidden"); // we won't override "display:none", if set, though.
getById("hangupbutton").className="float";
getById("controlButtons").classList.remove("hidden");
getById("helpbutton").style.display = "inherit";
getById("reportbutton").style.display = "";
} else {
getById("controlButtons").classList.add("hidden");
}
var bigPlayButton = document.getElementById("bigPlayButton");
if (bigPlayButton){
bigPlayButton.parentNode.removeChild(bigPlayButton);
}
v.autoplay = false;
if (session.showControls!==null){
v.controls = session.showControls;
} else {
v.controls = true;
}
v.muted = false;
if (files.length ==1){ // we don't want to do the complex logic if there is just one video
v.loop = true;
} else {
v.loop = false; // triggers the complex track/rtc logic.
}
v.setAttribute("playsinline","");
v.src = fileURL;
try {
if (Firefox){
session.streamSrc = v.mozCaptureStream();
} else {
session.streamSrc = v.captureStream(); // gaaaaaaaaaaaahhhhhhhh!
}
toggleMute(true);
} catch (e){
errorlog(e);
return;
}
v.id = "videosource"; // could be set to UUID in the future
v.dataset.menu = "context-menu-video";
v.playlist = files;
v.addEventListener('ended',myHandler,false); // only fires if the video doesn't loop.
function myHandler(e) {
log("MY HANDLER TRIGGERED");
var vid = getById("videosource");
log(vid.playlist);
vid.playlist.unshift(vid.playlist.pop());
vid.src = URL.createObjectURL(vid.playlist[0]);
vid.onloadeddata = function(){
if (Firefox){
session.streamSrc = vid.mozCaptureStream();
} else {
session.streamSrc = vid.captureStream(); // gaaaaaaaaaaaahhhhhhhh!
}
var tracks = session.streamSrc.getVideoTracks();
if (tracks.length){
pushOutVideoTrack(tracks[0]); // video only
}
var tracks = session.streamSrc.getAudioTracks();
senderAudioUpdate();
}
session.applySoloChat(); // mute streams that should be muted if a director
session.applyIsolatedChat();
vid.load();
vid.play().then(_ => {
log("playing 2");
}).catch(warnlog);
}
// no preview doesn't work, so just stop it from doing its thing.
v.className = "tile clean fileshare";
session.videoElement = v;
container.appendChild(v);
session.mirrorExclude=true;
if (session.director){
} else if (session.scene!==false){
} else if (session.roomid!==false){
if (session.roomid===""){
if (!(session.view) || (session.view==="")){
if (session.fullscreen){
session.windowed = false;
} else {
v.className = "myVideo clean fileshare";
container.classList.add("vidcon");
session.windowed = true;
}
getById("mutespeakerbutton").classList.add("hidden");
container.style.width="100%";
container.style.alignItems = "center";
container.backgroundColor = "#666";
play();
} else {
session.windowed = false;
play();
}
} else {
//session.cbr=0; // we're just going to override it
if (session.stereo==5){
session.stereo=3;
}
session.windowed = false;
}
applyMirror(session.mirrorExclude);
} else {
if (session.fullscreen){
session.windowed = false;
} else {
v.className = "myVideo clean fileshare";
container.classList.add("vidcon");
session.windowed = true;
}
getById("mutespeakerbutton").classList.add("hidden");
container.style.width="100%";
container.style.alignItems = "center";
container.backgroundColor = "#666";
applyMirror(session.mirrorExclude);
}
v.addEventListener('click', function(e){
log("click");
try {
if ((e.ctrlKey)||(e.metaKey)){
e.preventDefault();
var [menu, innerMenu] = statsMenuCreator();
menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu);
printMyStats(innerMenu);
e.stopPropagation();
return false;
}
} catch(e){errorlog(e);}
});
v.touchTimeOut = null;
v.touchLastTap = 0;
v.touchCount = 0;
v.addEventListener('touchend', function(event) {
if (session.disableMouseEvents){return;}
log("touched");
//document.ontouchup = null;
//document.onmouseup = null;
document.onmousemove = null;
document.ontouchmove = null;
var currentTime = new Date().getTime();
var tapLength = currentTime - v.touchLastTap;
clearTimeout(v.touchTimeOut);
if (tapLength < 500 && tapLength > 0) {
///
log("double touched");
v.touchCount+=1;
event.preventDefault();
if (v.touchCount<5){
v.touchLastTap = currentTime;
return false;
}
v.touchLastTap = 0;
v.touchCount=0;
var [menu, innerMenu] = statsMenuCreator();
menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu);
printMyStats(innerMenu);
event.stopPropagation();
return false;
//////
} else {
v.touchCount=1;
v.touchTimeOut = setTimeout(function(vv) {
clearTimeout(vv.touchTimeOut);
vv.touchLastTap = 0;
vv.touchCount=0;
}, 5000, v);
v.touchLastTap = currentTime;
}
});
updateReshareLink();
pokeIframeAPI('started-fileshare'); // depreciated
pokeIframeAPI('file-share', true);
clearInterval(session.updateLocalStatsInterval);
session.updateLocalStatsInterval = setInterval(function(){updateLocalStats();},session.statsInterval);
session.seeding=true;
if (session.videoMutedFlag){
session.videoMuted = true;
toggleVideoMute(true);
}
session.seedStream();
}; // publishFile
function tryAgain(event) { // audio or video agnostic track reconnect ------------not actually in use,. maybe out of date
log("TRY AGAIN TRIGGERED");
warnlog(event);
}
function enterPressedClick(event, ele) {
if (event.keyCode === 13) {
event.preventDefault();
ele.click();
}
}
function enterPressed(event, callback) {
// Number 13 is the "Enter" key on the keyboard
if (event.keyCode === 13) {
event.preventDefault();
callback();
}
}
function dragElement(elmnt) {
if (session.disableMouseEvents){return;}
log("dragElement started");
function onvideoclick() {
log("onvideoclick");
log(pos3 + " " + pos4);
//log(pos3o + " " + pos4o);
tapToFocus(parseInt(pos3*100/elmnt.clientWidth), parseInt(pos4/elmnt.clientHeight*100));
return false;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
// calculate the new cursor position:
log("dragging");
log(e);
if (Date.now() - millis < 100) {
return;
}
dragged = true;
millis = Date.now();
if (e.type == 'touchstart' || e.type == 'touchmove' || e.type == 'touchend' || e.type == 'touchcancel') {
var touch = e.touches[0] || e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
pos1 = touch.clientX;
pos2 = touch.clientY;
} else if (e.type == 'mousedown' || e.type == 'mouseup' || e.type == 'mousemove' || e.type == 'mouseover' || e.type == 'mouseout' || e.type == 'mouseenter' || e.type == 'mouseleave') {
pos1 = e.clientX;
pos2 = e.clientY;
}
var zoom = parseFloat((pos4 - pos2) * 2 / elmnt.offsetHeight);
if (zoom > 1) {
zoom = 1.0;
} else if (zoom < -1) {
zoom = -1.0;
}
input.value = zoom * (input.max - input.min) + input.min;
//if (input.value != pos0) {
updateCameraConstraints("zoom", input.value, false, false);
//}
}
function closeDragElement(e) {
log("closeDragElement");
log(e);
// focusable
if (!dragged){
log("dragged: "+dragged);
onvideoclick();
}
dragged = false;
elmnt.removeEventListener('touchend', closeDragElement);
elmnt.removeEventListener('mouseup', closeDragElement);
/* stop moving when mouse button is released:*/
//document.ontouchend = null;
//document.onmouseup = null;
document.onmousemove = null;
document.ontouchmove = null;
}
function dragMouseDown(e) {
log("dragMouseDown");
log(e);
dragged = false;
millis = Date.now();
e = e || window.event;
e.preventDefault();
pos0 = input.value;
if (e.type == 'touchstart' || e.type == 'touchmove' || e.type == 'touchend' || e.type == 'touchcancel') {
var touch = e.touches[0] || e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
pos3 = touch.clientX;
pos4 = touch.clientY;
//pos3o = touch.offsetX;
//pos4o = touch.offsetX;
} else if (e.type == 'mousedown' || e.type == 'mouseup' || e.type == 'mousemove' || e.type == 'mouseover' || e.type == 'mouseout' || e.type == 'mouseenter' || e.type == 'mouseleave') {
pos3 = e.clientX;
pos4 = e.clientY;
//pos3o = e.offsetX;
//pos4o = e.offsetX;
}
elmnt.addEventListener('touchend', closeDragElement);
elmnt.addEventListener('mouseup', closeDragElement);
document.ontouchmove = elementDrag;
document.onmousemove = elementDrag;
}
try {
var stream = elmnt.srcObject;
try {
var track0 = stream.getVideoTracks();
} catch (e) {
return;
}
if (!(track0.length)) {
return;
}
var focusable = false;
var zoomable = false;
var dragged = false;
var input = getById("zoomSlider");
track0 = track0[0];
if (track0.getCapabilities) {
var capabilities = track0.getCapabilities();
var settings = track0.getSettings();
if ("focusDistance" in capabilities){
log("focusable");
focusable = true;
}
if ('zoom' in capabilities) {
log("zoomable;");
zoomable = true;
input.min = capabilities.zoom.min;
input.max = capabilities.zoom.max;
input.step = capabilities.zoom.step;
input.value = settings.zoom;
}
}
var millis = Date.now();
var pos0 = 1;
var pos3 = 0;
var pos4 = 0;
var pos1 = 0;
var pos2 = 0;
//var pos3o = 0;
//var pos4o = 0;
} catch (e) {
errorlog(e);
return;
}
if (!focusable && !zoomable){return;} // can't be zoomed or focused.
log("drag on");
elmnt.onmousedown = dragMouseDown;
elmnt.ontouchstart = dragMouseDown;
}
function previewIframe(iframeSrc) { // this is pretty important if you want to avoid camera permission popup problems. You can also call it automatically via: loadIframe();"> , but don't call it before the page loads.
var iframe = document.createElement("iframe");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;midi;";
iframe.style.width = "100%";
iframe.style.height = "100%";
iframe.style.border = "10px dashed rgb(64 65 62)";
iframeSrc = parseURL4Iframe(iframeSrc);
/* if (typeof iframeSrc == "object"){ // special handler.
iframeSrc = iframeSrc.parsedSrc;
} */
iframe.src = iframeSrc;
getById("previewIframe").innerHTML = "";
getById("previewIframe").style.width = "640px";
getById("previewIframe").style.height = "360px";
getById("previewIframe").style.margin = "auto";
getById("previewIframe").appendChild(iframe);
}
function loadIframe(iframesrc, UUID) { // this is pretty important if you want to avoid camera permission popup problems. You can also call it automatically via: loadIframe();"> , but don't call it before the page loads.
/* if (document.getElementById("mainmenu")) {
var m = getById("mainmenu");
m.remove();
} */
var iframeID = "iframe_"+UUID;
var iframe = document.createElement("iframe");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;midi;";
iframe.style.width = "100%";
iframe.style.height = "100%";
iframe.style.border = "10px dashed rgb(64 65 62)";
iframe.id = iframeID;
iframe.dataset.UUID = UUID;
iframe.loadedYoutubeListen = false;
if (session.director){
//
} else if (session.scene!==false){
if (session.view){ // specific video to be played
iframe.style.display="block";
} else if (session.scene==="0"){
iframe.style.display="block";
} else { // group scene I guess; needs to be added manually
iframe.style.display="none";
}
} else if (session.roomid!==false){
//
} else {
iframe.style.display="block";
}
if (iframesrc == "") {
iframesrc = "./";
iframe.style.border = "0";
}
// trusted domains
if (iframesrc.startsWith("https://vdo.ninja/")){
iframe.style.border = "0";
} else if (iframesrc.startsWith("https://obs.ninja/")){
iframe.style.border = "0";
} else if (iframesrc.startsWith("https://vmix.ninja/")){
iframe.style.border = "0";
} else if (iframesrc.startsWith("https://backup.vdo.ninja/")){
iframe.style.border = "0";
} else if (iframesrc.startsWith("https://backup.obs.ninja/")){
iframe.style.border = "0";
} else if (iframesrc.startsWith("https://www.youtube.com/")){
iframe.style.border = "0";
setTimeout(function(iframe_id){YoutubeListen(iframe_id);}, 1000, iframeID); // create stats feedback for the director; syncing.
} else if (iframesrc.startsWith("https://player.twitch.tv/")){
iframe.style.border = "0";
} else if (iframesrc.startsWith("https://twitch.tv/")){
iframe.style.border = "0";
} else if (iframesrc.startsWith("https://www.twitch.tv/")){
iframe.style.border = "0";
} else if (iframesrc.startsWith("https://vimeo.com/")){
iframe.style.border = "0";
} else if (iframesrc.startsWith("https://player.vimeo.com/")){
iframe.style.border = "0";
} else if (iframesrc.startsWith("https://meshcast.io/")){
//iframesrc = iframesrc.replace("//meshcast.io/", "//meshcast.vdo.ninja/");
iframe.style.border = "0";
// iframe.dataset.meshcast = true; // TODO: this was a bit of a fail
if (document.domain==="backup.vdo.ninja"){
document.domain = 'vdo.ninja';
} else if (document.domain==="isolated.vdo.ninja"){
document.domain = 'vdo.ninja';
}
} else if (iframesrc.startsWith("https://s10.fun/")){
iframe.style.border = "0";
} else if (iframesrc.startsWith("https://play.rozy.tv/")){
iframe.style.border = "0";
}
iframe.src = iframesrc;
pokeIframeAPI('iframe-loaded', iframesrc);
return iframe
}
function dropDownButtonAction(ele) {
var ele = getById("dropButton");
if (ele) {
ele.parentNode.removeChild(ele);
//getById('container-5').classList.remove('hidden');
//getById('container-8').classList.remove('hidden');
//getById('container-6').classList.remove('hidden');
document.querySelectorAll("div.column.card").forEach(child=>{
child.classList.remove('hidden');
});
}
}
function updateConstraintSliders() {
log("updateConstraintSliders");
if (session.roomid !== false && session.roomid !== "" && session.director !== true && session.forceMediaSettings == false) {
if (session.controlRoomBitrate !== false) {
listCameraSettings();
}
if (session.effect!==false){
//if ((iOS) || (iPad)){
//} else {
getById("effectsDiv3").style.display = "block";
getById("effectSelector3").value = session.effect || "0";
//}
}
} else {
listAudioSettings();
listCameraSettings();
//if ((iOS) || (iPad)){
// } else {
if (session.effect!==false){
getById("effectsDiv3").style.display = "block";
try{
getById("effectSelector3").value = session.effect || "0";
} catch(E){}
}
//}
}
//checkIfPIP(); // this doesn't actually work on iOS still, so whatever.
}
function checkIfPIP() {
try {
if (session.videoElement && ((session.videoElement.webkitSupportsPresentationMode && typeof session.videoElement.webkitSetPresentationMode === "function") || (document.pictureInPictureEnabled || !videoElement.disablePictureInPicture))) {
// Toggle PiP when the user clicks the button.
getById("pIpStartButton").addEventListener("click", function(event) {
// if ( (document.pictureInPictureEnabled || !videoElement.disablePictureInPicture)){
//session.videoElement.requestPictureInPicture();
// } else {
session.videoElement.webkitSetPresentationMode(session.videoElement.webkitPresentationMode === "picture-in-picture" ? "inline" : "picture-in-picture");
// }
});
getById("pIpStartButton").style.display = "inline-block";
}
} catch (e) {
errorlog(e);
}
}
function togglePictureInPicture(videoElement) {
if (document.pictureInPictureElement) {
if (document.pictureInPictureElement.id == videoElement.id){
document.exitPictureInPicture();
pokeIframeAPI('picture-in-picture', false);
return false;
} else {
document.exitPictureInPicture();
pokeIframeAPI('picture-in-picture', false);
videoElement.requestPictureInPicture();
pokeIframeAPI('picture-in-picture', true);
}
} else if (document.pictureInPictureEnabled) {
videoElement.requestPictureInPicture();
pokeIframeAPI('picture-in-picture', true);
}
return true;
}
function mixMinusAudio(uid=false){
var audioContext = new AudioContext();
if (session.stereo===false){
var merger = audioContext.createChannelMerger(1);
} else {
var merger = audioContext.createChannelMerger(2);
}
if (session.videoElement && session.videoElement.srcObject){
var tracks = session.videoElement.srcObject.getAudioTracks();
for (var i=0;i 0) {
log("FINAL:" + Final_transcript);
try {
var data = {};
data.isFinal = true;
data.transcript = Final_transcript;
data.counter = TranscriptionCounter;
session.sendMessage(data);
TranscriptionCounter += 1;
Final_transcript = "";
Interim_transcript = "";
pokeIframeAPI('transcription-text', Final_transcript);
} catch (e) {
errorlog(e);
}
} else {
try {
var data = {};
data.isFinal = false;
data.transcript = Interim_transcript;
data.counter = TranscriptionCounter;
session.sendMessage(data);
} catch (e) {
errorlog(e);
Interim_transcript = "";
}
}
};
Recognition.start();
} else if (!session.cleanOutput){
warnUser(miscTranslations["speech-not-suppoted"], false, false);
}
}
async function requestVideoRecord(ele, state=null, bitrate=null) {
var UUID = ele.dataset.UUID;
if (!state && ele.classList.contains("pressed")) {
var msg = {};
msg.requestVideoRecord = false;
msg.UUID = UUID;
session.sendRequest(msg, msg.UUID);
ele.classList.remove("pressed"); ele.ariaPressed = "false";
} else if (state==null || state){
var msg = {};
msg.requestVideoRecord = true;
msg.UUID = UUID;
if (bitrate===null){
window.focus();
bitrate = await promptAlt(miscTranslations["what-bitrate"], false, false, 6000);
}
if (bitrate) {
msg.value = bitrate;
session.sendRequest(msg, msg.UUID);
ele.classList.add("pressed"); ele.ariaPressed = "true";
}
}
pokeIframeAPI('request-video-record', msg.requestVideoRecord, UUID);
}
function changeOrderDirector(value) {
if (session.order==false){
session.order=0;
}
session.order += parseInt(value) || 0;
var elements = document.querySelectorAll('[data-action-type="order-value-director"]');
//log(elements);
if (elements[0]){
elements[0].innerText = parseInt(session.order) || 0;
}
var data = {};
data = {};
data.order = session.order;
session.sendPeers(data);
pokeIframeAPI('director-order', data.order);
}
function changeOrder(value, UUID) {
var msg = {};
msg.changeOrder = value;
msg.UUID = UUID;
session.sendRequest(msg, msg.UUID);
pokeIframeAPI('change-order', value, UUID);
}
function requestVideoHack(keyname, value, UUID, ctrl=false) {
var msg = {};
msg.requestVideoHack = true;
msg.keyname = keyname;
msg.value = value;
msg.UUID = UUID;
msg.ctrl = ctrl;
session.sendRequest(msg, msg.UUID);
pokeIframeAPI('request-video-setting', {value:value, keyname:keyname, ctrl:ctrl}, UUID);
}
function requestAudioHack(keyname, value, UUID, deviceId = "default") { // updateAudioConstraints
var msg = {};
msg.requestAudioHack = true;
msg.keyname = keyname;
msg.value = value;
msg.UUID = UUID;
msg.deviceId = deviceId;
session.sendRequest(msg, msg.UUID);
pokeIframeAPI('request-audio-setting', {value:value, keyname:keyname, deviceId:deviceId}, UUID);
}
function requestChangeEQ(keyname, value, UUID, track = 0) { // updateAudioConstraints
var msg = {};
msg.requestChangeEQ = true;
msg.keyname = keyname;
msg.value = value;
msg.UUID = UUID;
msg.track = track; // pointless atm
session.sendRequest(msg, msg.UUID);
pokeIframeAPI('request-change-eq', {value:value, keyname:keyname, track:track}, UUID);
}
function requestChangeGating(keyname, value, UUID, track = 0) { // updateAudioConstraints
var msg = {};
msg.requestChangeGating = true;
msg.keyname = keyname;
msg.value = value;
msg.UUID = UUID;
msg.track = track; // pointless atm
session.sendRequest(msg, msg.UUID);
pokeIframeAPI('request-change-gating', {value:value, keyname:keyname, track:track}, UUID);
}
function requestChangeCompressor(keyname, value, UUID, track = 0) { // updateAudioConstraints
var msg = {};
msg.requestChangeCompressor = true;
msg.keyname = keyname;
msg.value = value;
msg.UUID = UUID;
msg.track = track; // pointless atm
session.sendRequest(msg, msg.UUID);
pokeIframeAPI('request-change-compressor', {value:value, keyname:keyname, track:track}, UUID);
}
function requestChangeMicDelay(value, UUID, track = 0) { // updateAudioConstraints
var msg = {};
msg.requestChangeMicDelay = true;
msg.value = value;
msg.UUID = UUID;
msg.track = track; // pointless atm
session.sendRequest(msg, msg.UUID);
pokeIframeAPI('request-change-mic-delay', {value:value, track:track}, UUID);
}
function requestChangeSubGain(value, UUID, deviceId) { // updateAudioConstraints
var msg = {};
msg.requestChangeSubGain = true;
msg.value = value;
msg.UUID = UUID;
msg.deviceId = deviceId; // pointless atm
log(msg);
session.sendRequest(msg, msg.UUID);
pokeIframeAPI('request-sub-gain', {value:value, deviceId:deviceId}, UUID);
}
function requestChangeLowcut(value, UUID, track = 0) { // updateAudioConstraints
var msg = {};
msg.requestChangeLowcut = true;
msg.value = value;
msg.UUID = UUID;
msg.track = track; // pointless atm
session.sendRequest(msg, msg.UUID);
pokeIframeAPI('request-low-cut', value, UUID);
}
function toggleSystemPip(vid) {
if (vid.webkitSupportsPresentationMode && (typeof vid.webkitSetPresentationMode === "function")) {
vid.webkitSetPresentationMode(
vid.webkitPresentationMode === "picture-in-picture"
? "inline"
: "picture-in-picture"
);
} else {
if (document.pictureInPictureElemen) {
document.exitPictureInPicture();
vid.requestPictureInPicture();
} else {
vid.requestPictureInPicture();
}
}
}
function updateDirectorsAudio(dataN, UUID) {
var audioEle = document.createElement("div");
query("#container_"+UUID+" .advancedAudioSettings").innerHTML = "";
query("#container_"+UUID+" .advancedAudioSettings").classList.remove("hidden");
//log(dataN);
if (!dataN.length) {
return;
}
for (var n = 0; n < dataN.length; n += 1) {
var data = dataN[n];
if (dataN.length==1) {
if (data.trackLabel) {
var label = document.createElement("label");
label.innerText = data.trackLabel;
label.style.display = "block";
label.id = "remoteAudioLabel_"+UUID;
label.classList.add("settingsLabel");
label.dataset.UUID = UUID;
audioEle.appendChild(label);
}
}
//if (n !== 0) {
//var label = document.createElement("span");
//label.innerText = "Coming Soon";
//audioEle.appendChild(label);
// continue; // remove to more than one audio device (assuming other fixes are applied)
//}
if (("micDelay" in data) && n==0) {
var label = document.createElement("label");
var i = "micDelay";
var div = document.createElement("div");
label.id = "label_" + i + "_"+UUID;
label.htmlFor = "constraints_" + i + "_"+UUID;
var input = document.createElement("input");
input.min = 0;
input.max = 500;
input.value = data.micDelay || 0;
input.title = "Previously was: "+input.value;
input.type = "range";
input.dataset.keyname = i;
//input.dataset.labelname = "mic delay (ms):";
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + " (ms):";
var manualInput = document.createElement("input");
manualInput.type = "number";
manualInput.dataset.keyname = i;
manualInput.value = parseFloat(input.value);
manualInput.className = "manualInput";
manualInput.id = "constraints_manual_" + i + "_"+UUID;
manualInput.dataset.UUID = UUID;
manualInput.dataset.track = n;
input.dataset.track = n;
input.dataset.UUID = UUID;
input.id = "constraints_" + i + "_"+UUID;
input.style = "display:block; width:100%;";
input.name = "constraints_" + i;
input.style.margin = "2px 0px 5px";
manualInput.onchange = function(e) {
getById("constraints_" + e.target.dataset.keyname+"_"+e.target.dataset.UUID).value = parseFloat(e.target.value);
requestChangeMicDelay(parseInt(e.target.value), e.target.dataset.UUID, parseInt(e.target.dataset.track));
};
input.onchange = function(e) {
//e.target.title = e.target.value;
getById("constraints_manual_" + e.target.dataset.keyname+"_"+e.target.dataset.UUID).value = parseFloat(e.target.value);
requestChangeMicDelay(parseInt(e.target.value), e.target.dataset.UUID, parseInt(e.target.dataset.track));
};
input.oninput = function(e) {
getById("constraints_manual_" + e.target.dataset.keyname+"_"+e.target.dataset.UUID).value = parseFloat(e.target.value);
if (Date.now() - remoteSliderTimeout > 100){
remoteSliderTimeout = Date.now();
requestChangeMicDelay(parseInt(e.target.value), e.target.dataset.UUID, parseInt(e.target.dataset.track));
}
};
audioEle.appendChild(div)
div.appendChild(label);
div.appendChild(manualInput);
audioEle.appendChild(input);
}
if (data.lowcut!==false && n==0) {
var label = document.createElement("label");
var i = "lowCut";
label.id = "label_" + i + "_"+UUID;
label.htmlFor = "constraints_" + i + "_"+UUID;
var input = document.createElement("input");
input.min = 50;
input.max = 150;
input.value = data.lowcut;
input.title = "Previously was: "+input.value;
input.type = "range";
input.dataset.keyname = i;
//input.dataset.labelname = "low cut:";
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
var manualInput = document.createElement("input");
manualInput.type = "number";
manualInput.dataset.keyname = i;
manualInput.value = parseFloat(input.value);
manualInput.className = "manualInput";
manualInput.id = "constraints_manual_" + i + "_"+UUID;
manualInput.dataset.UUID = UUID;
manualInput.dataset.track = n;
input.dataset.track = n;
input.dataset.UUID = UUID;
input.id = "constraints_" + i + "_"+UUID;
input.style = "display:block; width:100%;";
input.name = "constraints_" + i;
input.style.margin = "2px 0px 5px";
manualInput.onchange = function(e) {
getById("constraints_" + e.target.dataset.keyname+"_"+e.target.dataset.UUID).value = parseFloat(e.target.value);
requestChangeLowcut(parseInt(e.target.value), e.target.dataset.UUID, parseInt(e.target.dataset.track));
};
input.onchange = function(e) {
//e.target.title = e.target.value;
getById("constraints_manual_" + e.target.dataset.keyname+"_"+e.target.dataset.UUID).value = parseFloat(e.target.value);
requestChangeLowcut(parseInt(e.target.value), e.target.dataset.UUID, parseInt(e.target.dataset.track));
};
input.oninput = function(e) {
getById("constraints_manual_" + e.target.dataset.keyname+"_"+e.target.dataset.UUID).value = parseFloat(e.target.value);
if (Date.now() - remoteSliderTimeout > 100){
remoteSliderTimeout = Date.now();
requestChangeLowcut(parseInt(e.target.value), e.target.dataset.UUID, parseInt(e.target.dataset.track));
}
};
audioEle.appendChild(label);
audioEle.appendChild(manualInput);
audioEle.appendChild(input);
}
if (data.equalizer && n==0) {
var label = document.createElement("label");
var i = "Low_EQ";
//label.id = "label_" +i + "_"+UUID;
label.htmlFor = "constraints_" +i + "_"+UUID;
var input = document.createElement("input");
input.min = -50;
input.max = 50;
input.value = data.lowEQ;
input.title = "Previously was: "+input.value;
input.type = "range";
input.dataset.keyname = i;
input.dataset.labelname = "low EQ:"
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
var manualInput = document.createElement("input");
manualInput.type = "number";
manualInput.dataset.keyname = i;
manualInput.value = parseFloat(input.value);
manualInput.className = "manualInput";
manualInput.id = "label_" + i + "_"+UUID;
manualInput.dataset.UUID = UUID;
manualInput.dataset.track = n;
input.dataset.track = n;
input.dataset.UUID = UUID;
input.id = "constraints_" + i + "_"+UUID;
input.style = "display:block; width:100%;";
input.name = "constraints_" + i;
input.style.margin = "2px 0px 5px";
manualInput.onchange = function(e) {
getById("constraints_" + e.target.dataset.keyname+"_"+e.target.dataset.UUID).value = parseFloat(e.target.value);
requestChangeEQ("low", parseInt(e.target.value), e.target.dataset.UUID, parseInt(e.target.dataset.track));
};
input.onchange = function(e) {
getById("label_" + e.target.dataset.keyname+"_"+e.target.dataset.UUID).value = parseFloat(e.target.value);
requestChangeEQ("low", parseInt(e.target.value), e.target.dataset.UUID, parseInt(e.target.dataset.track));
};
input.oninput = function(e) {
getById("label_"+ e.target.dataset.keyname+"_"+e.target.dataset.UUID).value = parseFloat(e.target.value);
if (Date.now() - remoteSliderTimeout > 100){
remoteSliderTimeout = Date.now();
requestChangeEQ("low", parseInt(e.target.value), e.target.dataset.UUID, parseInt(e.target.dataset.track));
}
};
audioEle.appendChild(label);
audioEle.appendChild(manualInput);
audioEle.appendChild(input);
var label = document.createElement("label");
var i = "midEQ";
//label.id = "label_" + i + "_"+UUID;
label.htmlFor = "constraints_" + i + "_"+UUID;
var input = document.createElement("input");
input.min = -50;
input.max = 50;
input.value = data.midEQ;
input.title = "Previously was: "+input.value;
input.type = "range";
input.dataset.keyname = i;
input.dataset.labelname = "mid EQ:";
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
var manualInput = document.createElement("input");
manualInput.type = "number";
manualInput.dataset.keyname = i;
manualInput.value = parseFloat(input.value);
manualInput.className = "manualInput";
manualInput.id = "label_" + i + "_"+UUID;
manualInput.dataset.UUID = UUID;
manualInput.dataset.track = n;
input.dataset.track = n;
input.dataset.UUID = UUID;
input.id = "constraints_" + i + "_"+UUID;
input.style = "display:block; width:100%;";
input.name = "constraints_" + i;
input.style.margin = "2px 0px 5px";
manualInput.onchange = function(e) {
getById("constraints_" + e.target.dataset.keyname+"_"+e.target.dataset.UUID).value = parseFloat(e.target.value);
requestChangeEQ("mid", parseInt(e.target.value), e.target.dataset.UUID, parseInt(e.target.dataset.track));
};
input.onchange = function(e) {
getById("label_" + e.target.dataset.keyname+"_"+e.target.dataset.UUID).value = parseFloat(e.target.value);
requestChangeEQ("mid", parseInt(e.target.value), e.target.dataset.UUID, parseInt(e.target.dataset.track));
};
input.oninput = function(e) {
getById("label_" + e.target.dataset.keyname+"_"+e.target.dataset.UUID).value = parseFloat(e.target.value);
if (Date.now() - remoteSliderTimeout > 100){
remoteSliderTimeout = Date.now();
requestChangeEQ("mid", parseInt(e.target.value), e.target.dataset.UUID, parseInt(e.target.dataset.track));
}
};
audioEle.appendChild(label);
audioEle.appendChild(manualInput);
audioEle.appendChild(input);
var label = document.createElement("label");
var i = "highEQ";
//label.id = "label_" + i + "_"+UUID;
label.htmlFor = "constraints_" + i + "_"+UUID;
var input = document.createElement("input");
input.min = -50;
input.max = 50;
input.value = data.highEQ;
input.title = "Previously was: "+input.value;
input.type = "range";
input.dataset.keyname = i;
input.dataset.labelname = "high EQ:";
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
var manualInput = document.createElement("input");
manualInput.type = "number";
manualInput.dataset.keyname = i;
manualInput.value = parseFloat(input.value);
manualInput.className = "manualInput";
manualInput.id = "label_" + i + "_"+UUID;
manualInput.dataset.UUID = UUID;
manualInput.dataset.track = n;
input.dataset.track = n;
input.dataset.UUID = UUID;
input.id = "constraints_" + i + "_"+UUID;
input.classList.add("inputConstraint");
input.name = "constraints_" + i;
manualInput.onchange = function(e) {
getById("constraints_" + e.target.dataset.keyname+"_"+e.target.dataset.UUID).value = parseFloat(e.target.value);
requestChangeEQ("high", parseInt(e.target.value), e.target.dataset.UUID, parseInt(e.target.dataset.track));
};
input.onchange = function(e) {
getById("label_" + e.target.dataset.keyname+"_"+e.target.dataset.UUID).value = parseFloat(e.target.value);
requestChangeEQ("high", parseInt(e.target.value), e.target.dataset.UUID, parseInt(e.target.dataset.track));
};
input.oninput = function(e) {
getById("label_" + e.target.dataset.keyname+"_"+e.target.dataset.UUID).value = parseFloat(e.target.value);
if (Date.now() - remoteSliderTimeout > 100){
remoteSliderTimeout = Date.now();
requestChangeEQ("high", parseInt(e.target.value), e.target.dataset.UUID, parseInt(e.target.dataset.track));
}
};
audioEle.appendChild(label);
audioEle.appendChild(manualInput);
audioEle.appendChild(input);
}
if (("gating" in data) && n==0) { // only show once.
var label = document.createElement("label");
var i = "noiseGate";
var div = document.createElement("div");
var label = document.createElement("label");
label.id = "label_" + i + "_"+n + "_"+UUID;
label.htmlFor = "constraints_" + i + "_"+n + "_"+UUID;
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
label.style = "display:inline-block; padding:0;";
label.dataset.keyname = i;
label.dataset.track = n;
var input = document.createElement("select");
var c = document.createElement("option");
var opt = new Option("Off", false);
input.options.add(opt);
opt = new Option("On", true);
input.options.add(opt);
if (data.gating){
opt.selected = "true";
}
input.dataset.deviceId = data.deviceId;
input.id = "constraints_" + i + "_"+n + "_"+UUID;
input.className = "constraintCameraInput";
input.name = "constraints_" + i + "_"+n;
input.style = "display:inline; padding:2px;";
input.dataset.keyname = i;
input.dataset.track = n;
input.dataset.UUID = UUID;
input.dataset.chosen = input.value;
input.onchange = function(e) {
this.dataset.chosen = this.value;
//getById("label_"+e.target.dataset.keyname).innerText =e.target.dataset.keyname+": "+e.target.value;
//updateAudioConstraints(e.target.dataset.keyname, e.target.value);
requestChangeGating("gating", e.target.value, e.target.dataset.UUID, parseInt(e.target.dataset.track));
log(e.target.dataset.keyname, e.target.value);
};
audioEle.appendChild(div);
div.appendChild(label);
div.appendChild(input);
}
if (("compressor" in data) && n==0) {
var label = document.createElement("label");
var i = "compressor";
var div = document.createElement("div");
var label = document.createElement("label");
label.id = "label_" + i + "_"+n+ "_"+UUID;
label.htmlFor = "constraints_" + i + "_"+n+ "_"+UUID;
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
label.style = "display:inline-block; padding:0;";
label.dataset.keyname = i;
label.dataset.track = n;
var input = document.createElement("select");
var c = document.createElement("option");
var opt = new Option("Off", false);
input.options.add(opt);
opt = new Option("On", 1);
input.options.add(opt);
if (data.compressor==1){
opt.selected = "true";
}
opt = new Option("Limiter", 2);
input.options.add(opt);
if (data.compressor==2){
opt.selected = "true";
}
input.dataset.deviceId = data.deviceId;
input.id = "constraints_" + i + "_"+n+ "_"+UUID;
input.className = "constraintCameraInput";
input.name = "constraints_" + i + "_"+n;
input.style = "display:inline; padding:2px;";
input.dataset.keyname = i;
input.dataset.track = n;
input.dataset.UUID = UUID;
input.dataset.chosen = input.value;
input.onchange = function(e) {
this.dataset.chosen = this.value;
//getById("label_"+e.target.dataset.keyname).innerText =e.target.dataset.keyname+": "+e.target.value;
//updateAudioConstraints(e.target.dataset.keyname, e.target.value);
requestChangeCompressor("compressor", e.target.value, e.target.dataset.UUID, parseInt(e.target.dataset.track));
log(e.target.dataset.keyname, e.target.value);
};
audioEle.appendChild(div);
div.appendChild(label);
div.appendChild(input);
}
if (dataN.length>1){
if (data.trackLabel) {
var label = document.createElement("label");
label.innerText = data.trackLabel;
label.style.display = "block";
label.id = "remoteAudioLabel_"+UUID+"_"+n+ "_"+UUID;
label.classList.add("settingsLabel");
audioEle.appendChild(label);
}
}
warnlog(data);
for (var i in data.audioConstraints) {
try {
log(i);
log(data.audioConstraints[i]);
if ((typeof data.audioConstraints[i] === 'object') && (data.audioConstraints[i] !== null) && ("max" in data.audioConstraints[i]) && ("min" in data.audioConstraints[i])) {
if (i === "aspectRatio") {
continue;
} else if (i === "width") {
continue;
} else if (i === "height") {
continue;
} else if (i === "frameRate") {
continue;
} else if (i === "latency") {
// continue;
} else if (i === "sampleRate") {
continue;
} else if (i === "channelCount") {
// continue;
} else if (i === "volume"){
continue;
}
if (!("deviceId" in data.audioConstraints)){continue;} // not going to support older versions.
var label = document.createElement("label");
//label.id = "label_" + i + "_"+n+ "_"+UUID;
label.htmlFor = "constraints_" + i + "_"+n+ "_"+UUID;
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
var input = document.createElement("input");
input.min = data.audioConstraints[i].min;
input.max = data.audioConstraints[i].max;
if (parseFloat(input.min) == parseFloat(input.max)) {
continue;
}
var manualInput = document.createElement("input");
manualInput.type = "number";
if ("step" in data.audioConstraints[i]) {
input.step = data.audioConstraints[i].step;
manualInput.step = data.audioConstraints[i].step;
} else if ("volume" == i) {
input.step = 0.01;
manualInput.step = 0.01;
}
manualInput.dataset.keyname = i;
manualInput.className = "manualInput";
manualInput.id = "label_" + i + "_"+n+ "_"+UUID;
manualInput.max = data.audioConstraints[i].max;
manualInput.min = data.audioConstraints[i].min;
manualInput.dataset.UUID = UUID;
manualInput.dataset.track = n;
manualInput.dataset.keyname = i;
if (i in data.currentAudioConstraints) {
input.value = data.currentAudioConstraints[i];
manualInput.value = parseFloat(input.value);
//label.innerText = i + ": " + data.currentAudioConstraints[i];
label.title = "Previously was: " + data.currentAudioConstraints[i];
input.title = "Previously was: " + data.currentAudioConstraints[i];
} else {
label.innerText = i;
}
if ((i === "height") || (i === "width")){
input.title = "Hold CTRL (or cmd) to lock width and height together when changing them";
input.min = 16;
}
input.type = "range";
input.dataset.keyname = i;
input.dataset.track = n;
input.dataset.deviceId = data.deviceId;
input.dataset.UUID = UUID;
input.id = "constraints_" + i + "_"+n+ "_"+UUID;
input.style = "display:block; width:100%;";
input.name = "constraints_" + i + "_"+n + "_"+ UUID;
if (i=="channelCount"){
input.style.display = "none";
manualInput.style.margin = "5px 0px 9px 10px";
}
manualInput.onchange = function(e) {
getById("constraints_" + e.target.dataset.keyname + "_"+e.target.dataset.track + "_"+ e.target.dataset.UUID).value = parseFloat(e.target.value);
requestAudioHack(e.target.dataset.keyname, e.target.value, e.target.dataset.UUID, e.target.dataset.deviceId);
};
input.onchange = function(e) {
//e.target.title = e.target.value;
getById("label_" + e.target.dataset.keyname + "_"+e.target.dataset.track + "_"+ e.target.dataset.UUID ).value = parseFloat(e.target.value);
//updateAudioConstraints(e.target.dataset.keyname, e.target.value);
requestAudioHack(e.target.dataset.keyname, e.target.value, e.target.dataset.UUID, e.target.dataset.deviceId);
};
audioEle.appendChild(label);
audioEle.appendChild(manualInput);
audioEle.appendChild(input);
} else if ((typeof data.audioConstraints[i] === 'object') && (data.audioConstraints[i] !== null)) {
if (i == "resizeMode") {
continue;
}
var div = document.createElement("div");
var label = document.createElement("label");
label.id = "label_" + i + "_"+n+ "_"+UUID;
label.htmlFor = "constraints_" + i + "_"+n+ "_"+UUID;
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
label.style = "display:inline-block; padding:0;";
var input = document.createElement("select");
var c = document.createElement("option");
if (data.audioConstraints[i].length > 1) {
for (var opts in data.audioConstraints[i]) {
log(opts);
if (data.audioConstraints[i][opts] === false){
var opt = new Option("Off", data.audioConstraints[i][opts]);
} else if (data.audioConstraints[i][opts] === true){
var opt = new Option("On", data.audioConstraints[i][opts]);
} else {
var opt = new Option(data.audioConstraints[i][opts], data.audioConstraints[i][opts]);
}
input.options.add(opt);
if (i in data.currentAudioConstraints) {
if (data.audioConstraints[i][opts] == data.currentAudioConstraints[i]) {
opt.selected = "true";
}
}
}
} else if (i.toLowerCase == "torch") {
var opt = new Option("Off", false);
input.options.add(opt);
opt = new Option("On", true);
input.options.add(opt);
try{
if (i in data.currentAudioConstraints) {
if (data.audioConstraints[i]['torch'] == true) {
opt.selected = "true";
}
}
} catch(e){}
} else {
continue;
}
input.id = "constraints_" + i + "_"+n+ "_"+UUID;
input.className = "constraintCameraInput";
input.name = input.id;
input.style = "display:inline; padding:2px;";
input.dataset.keyname = i;
input.dataset.track = n;
input.dataset.deviceId = data.deviceId;
input.dataset.UUID = UUID;
input.dataset.chosen = input.value;
input.onchange = function(e) {
this.dataset.chosen = this.value;
//getById("label_"+e.target.dataset.keyname).innerText =e.target.dataset.keyname+": "+e.target.value;
//updateAudioConstraints(e.target.dataset.keyname, e.target.value);
requestAudioHack(e.target.dataset.keyname, e.target.value, e.target.dataset.UUID, e.target.dataset.deviceId);
log(e.target.dataset.keyname, e.target.value);
};
audioEle.appendChild(div);
div.appendChild(label);
div.appendChild(input);
} else if (typeof data.audioConstraints[i] === 'boolean') {
var div = document.createElement("div");
var label = document.createElement("label");
label.id = "label_" + i + "_"+n+ "_"+UUID;
label.htmlFor = "constraints_" + i + "_"+n+ "_"+UUID;
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
label.style = "display:inline-block; padding:0;";
label.dataset.keyname = i ;
label.dataset.track = n;
var input = document.createElement("select");
var c = document.createElement("option");
var opt = new Option("Off", false);
input.options.add(opt);
opt = new Option("On", true);
input.options.add(opt);
try{
if (data.audioConstraints[i] === true){
opt.selected = "true";
}
} catch(e){}
input.dataset.deviceId = data.deviceId;
input.id = "constraints_" + i + "_"+n+ "_"+UUID;
input.className = "constraintCameraInput";
input.name = input.id;
input.style = "display:inline; padding:2px;";
input.dataset.keyname = i;
input.dataset.track = n;
input.dataset.UUID = UUID;
input.dataset.chosen = input.value;
input.onchange = function(e) {
this.dataset.chosen = this.value;
//getById("label_"+e.target.dataset.keyname).innerText =e.target.dataset.keyname+": "+e.target.value;
//updateAudioConstraints(e.target.dataset.keyname, e.target.value);
requestAudioHack(e.target.dataset.keyname, e.target.value, e.target.dataset.UUID, e.target.dataset.deviceId);
log(e.target.dataset.keyname, e.target.value);
};
audioEle.appendChild(div);
div.appendChild(label);
div.appendChild(input);
}
} catch (e) {
errorlog(e);
}
}
if (data.subGain!==false) {
var label = document.createElement("label");
var i = "Gain";
var div = document.createElement("div");
label.id = "label_" + i + "_"+n+ "_"+UUID;
label.htmlFor = "constraints_" + i + "_" + n+ "_"+UUID;
var input = document.createElement("input");
input.min = 0;
input.max = 200;
input.value = data.subGain*100;
input.title = "Previously was: "+parseInt(input.value);
input.type = "range";
input.dataset.keyname = i;
input.dataset.track = n;
input.dataset.labelname = "Gain:"
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
var manualInput = document.createElement("input");
manualInput.type = "number";
manualInput.dataset.keyname = i;
manualInput.value = parseFloat(input.value);
manualInput.className = "manualInput";
manualInput.id = "label_" + i + "_" + n+ "_"+UUID;
manualInput.dataset.UUID = UUID;
manualInput.dataset.track = n;
input.dataset.track = data.deviceId;
input.dataset.UUID = UUID;
input.id = "constraints_" + i + "_" + n+ "_"+UUID;
input.style = "display:block; width:100%;";
input.name = input.id;
input.style.margin = "2px 0px 5px";
manualInput.onchange = function(e) {
getById("constraints_" + e.target.dataset.keyname + "_"+e.target.dataset.track + "_"+ e.target.dataset.UUID).value = parseFloat(e.target.value);
requestChangeSubGain(parseInt(e.target.value), e.target.dataset.UUID, e.target.dataset.track);
};
input.onchange = function(e) {
getById("label_" + e.target.dataset.keyname + "_"+e.target.dataset.track + "_"+ e.target.dataset.UUID).value = parseFloat(e.target.value);
requestChangeSubGain(parseInt(e.target.value), e.target.dataset.UUID, e.target.dataset.track);
};
input.oninput = function(e) {
getById("label_" + e.target.dataset.keyname + "_"+e.target.dataset.track + "_"+ e.target.dataset.UUID).value = parseFloat(e.target.value);
if (Date.now() - remoteSliderTimeout > 100){
remoteSliderTimeout = Date.now();
requestChangeSubGain(parseInt(e.target.value), e.target.dataset.UUID, e.target.dataset.track);
}
};
audioEle.appendChild(div)
div.appendChild(label);
div.appendChild(manualInput);
audioEle.appendChild(input);
}
query("#container_"+UUID+" .advancedAudioSettings").appendChild(audioEle);
}
if (fixScrollReset){
clearTimeout(fixScrollReset);
fixScrollReset = null;
getById("directorlayout").scrollTop = fixScrollResetValue;
}
}
var remoteSliderTimeout = 0;
function updateDirectorsVideo(data, UUID) {
var videoEle = document.createElement("div");
if (data.trackLabel) {
var label = document.createElement("label");
label.innerText = data.trackLabel;
label.style.display = "block";
label.id = "remoteVideoLabel_"+UUID;
label.dataset.UUID = UUID;
label.classList.add("settingsLabel")
videoEle.appendChild(label);
}
for (var i in data.cameraConstraints) {
try {
log(i);
log(data.cameraConstraints[i]);
if (i === "focusMode") {
continue // I'll handle this with FocusDistance instead
} else if (i === "whiteBalanceMode") {
continue // I'll handle this elsewhere
} else if (i === "exposureMode") {
continue // I'll handle this elsewhere
}
if ((typeof data.cameraConstraints[i] === 'object') && (data.cameraConstraints[i] !== null) && ("max" in data.cameraConstraints[i]) && ("min" in data.cameraConstraints[i])){
if (i === "aspectRatio") {
// continue;
} else if (i === "width") {
// continue;
} else if (i === "height") {
// continue;
} else if (i === "frameRate") {
// continue;
} else if (i === "latency") {
// continue;
} else if (i === "sampleRate") {
continue;
} else if (i === "channelCount") {
// continue;
}
var manualMode = false;
var manualLabel = false;
if (i ==="exposureTime"){
if (data.currentCameraConstraints["exposureMode"]){
manualMode = document.createElement("input");
manualMode.type = "checkbox";
manualMode.id = "manual_"+i+"_"+UUID;
manualMode.dataset.UUID = UUID;
manualMode.dataset.keyname = "exposureMode";
manualMode.onchange = function(e) {
var value = "manual";
if (e.target.checked){
value = "continuous";
}
requestVideoHack(e.target.dataset.keyname, value, e.target.dataset.UUID, true);
//getById("constraints_" + e.target.dataset.keyname + "_" + e.target.dataset.UUID).value = parseFloat(e.target.value);
//getById("label_" + e.target.dataset.keyname+ "_" + e.target.dataset.UUID).value = parseFloat(e.target.value);
};
manualLabel = document.createElement("label");
manualLabel.htmlFor = manualMode.id;
manualLabel.innerHTML = "Auto: ";
manualLabel.style.marginLeft = "20px";
if (data.currentCameraConstraints["exposureMode"] == "continuous"){
manualMode.checked = true;
}
}
} else if (i ==="focusDistance"){
if (data.currentCameraConstraints["focusMode"]){
manualMode = document.createElement("input");
manualMode.type = "checkbox";
manualMode.id = "manual_"+i+"_"+UUID;
manualMode.dataset.UUID = UUID;
manualMode.dataset.keyname = "focusMode";
manualMode.onchange = function(e) {
var value = "manual";
if (e.target.checked){
value = "continuous";
}
requestVideoHack(e.target.dataset.keyname, value, e.target.dataset.UUID, true);
};
manualLabel = document.createElement("label");
manualLabel.htmlFor = manualMode.id;
manualLabel.innerHTML = "Auto: ";
manualLabel.style.marginLeft = "20px";
if (data.currentCameraConstraints["focusMode"] == "continuous"){
manualMode.checked = true;
}
}
} else if (i ==="colorTemperature"){
if (data.currentCameraConstraints["whiteBalanceMode"]){
manualMode = document.createElement("input");
manualMode.type = "checkbox";
manualMode.id = "manual_"+i+"_"+UUID;
manualMode.dataset.UUID = UUID;
manualMode.dataset.keyname = "whiteBalanceMode";
manualMode.onchange = function(e) {
var value = "manual";
if (e.target.checked){
value = "continuous";
}
requestVideoHack(e.target.dataset.keyname, value, e.target.dataset.UUID, true);
};
manualLabel = document.createElement("label");
manualLabel.htmlFor = manualMode.id;
manualLabel.innerHTML = "Auto: ";
manualLabel.style.marginLeft = "20px";
if (data.currentCameraConstraints["whiteBalanceMode"] == "continuous"){
manualMode.checked = true;
}
}
}
var label = document.createElement("label");
//label.id = "label_" + i;
label.htmlFor = "constraints_" + i + " _"+ UUID;
if (i === "colorTemperature"){
label.innerText = "Color Temp:";
} else if (i === "exposureCompensation"){
label.innerText = "Exposure Comp:";
} else if (i === "exposureTime"){
label.innerText = "Exposure:";
} else if (i === "focusDistance"){
label.innerText = "Focus:";
} else {
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
}
if (i === "zoom" || i === "pan" || i === "til"){
label.innerHTML = "⚠ "+label.innerText
}
var input = document.createElement("input");
if (i === "aspectRatio") {
input.max = 5;
input.min = 0.2;
input.step = 0.00001
} else {
input.min = data.cameraConstraints[i].min;
input.max = data.cameraConstraints[i].max;
}
if (parseFloat(input.min) == parseFloat(input.max)) {
continue;
}
if (i in data.currentCameraConstraints) {
input.value = data.currentCameraConstraints[i];
label.title = "Previously was: " + data.currentCameraConstraints[i];
input.title = "Previously was: " + data.currentCameraConstraints[i];
}
input.type = "range";
input.dataset.keyname = i;
input.dataset.UUID = UUID;
input.id = "constraints_" + i + "_" + UUID;
input.name = input.id;
input.classList.add("inputConstraint");
input.manualMode = manualMode;
if ((i === "height") || (i === "width")){
input.title = "Hold CTRL (or cmd) to lock width and height together when changing them";
input.min = 16;
}
var manualInput = document.createElement("input");
manualInput.type = "number";
manualInput.value = parseFloat(input.value);
manualInput.className = "manualInput";
manualInput.id = "label_" + i + "_" + UUID;
manualInput.name = manualInput.id;
manualInput.dataset.keyname = i;
manualInput.dataset.UUID = UUID;
manualInput.manualMode = manualMode;
if ("step" in data.cameraConstraints[i]) {
manualInput.step = data.cameraConstraints[i].step;
input.step = data.cameraConstraints[i].step;
} else if (i === "aspectRatio") {
input.step = 0.000001
manualInput.step = 0.005;
}
manualInput.onchange = function(e) {
getById("constraints_" + e.target.dataset.keyname + "_" + e.target.dataset.UUID).value = parseFloat(e.target.value);
if (e.target.manualMode){
requestVideoHack(e.target.dataset.keyname, e.target.value, e.target.dataset.UUID, true);
} else {
requestVideoHack(e.target.dataset.keyname, e.target.value, e.target.dataset.UUID, false);
}
};
input.onchange = function(e) {
getById("label_" + e.target.dataset.keyname+ "_" + e.target.dataset.UUID).value = parseFloat(e.target.value);
//updateVideoConstraints(e.target.dataset.keyname, e.target.value);
if (CtrlPressed || e.target.manualMode){
requestVideoHack(e.target.dataset.keyname, e.target.value, e.target.dataset.UUID, true);
} else {
requestVideoHack(e.target.dataset.keyname, e.target.value, e.target.dataset.UUID, false);
}
};
input.oninput = function(e) {
getById("label_" + e.target.dataset.keyname+ "_" + e.target.dataset.UUID).value = parseFloat(e.target.value);
if (Date.now() - remoteSliderTimeout > 100){
remoteSliderTimeout = Date.now();
if (CtrlPressed){
requestVideoHack(e.target.dataset.keyname, e.target.value, e.target.dataset.UUID, true);
} else {
requestVideoHack(e.target.dataset.keyname, e.target.value, e.target.dataset.UUID, false);
}
}
};
videoEle.appendChild(label);
videoEle.appendChild(manualInput);
if (manualMode && manualLabel){
videoEle.appendChild(manualLabel);
videoEle.appendChild(manualMode);
}
if (i === "aspectRatio") {
var preSelectButton = document.createElement("button");
preSelectButton.value = 16/9.0;
preSelectButton.innerText = "16:9";
preSelectButton.dataset.keyname = i;
preSelectButton.dataset.UUID = UUID;
preSelectButton.className = "preSelectButton";
preSelectButton.onclick = function(e) {
getById("constraints_" + e.target.dataset.keyname+ "_" + e.target.dataset.UUID).value = parseFloat(e.target.value);
getById("label_" + e.target.dataset.keyname+ "_" + e.target.dataset.UUID).value = parseFloat(e.target.value);
requestVideoHack(e.target.dataset.keyname, e.target.value, e.target.dataset.UUID, false);
};
videoEle.appendChild(preSelectButton);
var preSelectButton = document.createElement("button");
preSelectButton.value = 9/16.0;
preSelectButton.innerText = "9:16";
preSelectButton.dataset.UUID = UUID;
preSelectButton.className = "preSelectButton";
preSelectButton.dataset.keyname = i;
preSelectButton.onclick = function(e) {
getById("constraints_" + e.target.dataset.keyname+ "_" + e.target.dataset.UUID).value = parseFloat(e.target.value);
getById("label_" + e.target.dataset.keyname+ "_" + e.target.dataset.UUID).value = parseFloat(e.target.value);
requestVideoHack(e.target.dataset.keyname, e.target.value, e.target.dataset.UUID, false);
};
videoEle.appendChild(preSelectButton);
}
videoEle.appendChild(input);
} else if ((typeof data.cameraConstraints[i] === 'object') && (data.cameraConstraints[i] !== null)) {
if (i == "resizeMode") {
continue;
}
var div = document.createElement("div");
var label = document.createElement("label");
label.id = "label_" + i + "_"+UUID;
label.name = label.id;
label.htmlFor = "constraints_" + i + "_"+UUID;
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
label.style = "display:inline-block; padding:0;";
label.dataset.keyname = i;
label.dataset.UUID = UUID;
var input = document.createElement("select");
var c = document.createElement("option");
if (data.cameraConstraints[i].length > 1) {
for (var opts in data.cameraConstraints[i]) {
log(opts);
if (data.cameraConstraints[i][opts] === false){
var opt = new Option("Off", data.cameraConstraints[i][opts]);
} else if (data.cameraConstraints[i][opts] === true){
var opt = new Option("On", data.cameraConstraints[i][opts]);
} else {
var opt = new Option(data.cameraConstraints[i][opts], data.cameraConstraints[i][opts]);
}
input.options.add(opt);
if (i in data.currentCameraConstraints) {
if (data.cameraConstraints[i][opts] == data.currentCameraConstraints[i]) {
opt.selected = "true";
}
}
}
} else if (i.toLowerCase == "torch") {
var opt = new Option("Off", false);
input.options.add(opt);
opt = new Option("On", true);
input.options.add(opt);
try{
if (i in data.currentCameraConstraints) {
if (data.cameraConstraints[i]['torch'] == true) {
opt.selected = "true";
}
}
} catch(e){}
} else {
continue;
}
input.id = "constraints_" + i + "_"+UUID;
input.className = "constraintCameraInput";
input.name = input.id;
input.dataset.UUID = UUID;
input.style = "display:inline; padding:2px;";
input.dataset.keyname = i;
input.dataset.chosen = input.value;
input.onchange = function(e) {
this.dataset.chosen = this.value;
//getById("label_"+e.target.dataset.keyname+ "_" + e.target.dataset.UUID).innerText =e.target.dataset.keyname+": "+e.target.value;
//updateVideoConstraints(e.target.dataset.keyname, e.target.value);
if (CtrlPressed){
requestVideoHack(e.target.dataset.keyname, e.target.value, e.target.dataset.UUID, true);
} else {
requestVideoHack(e.target.dataset.keyname, e.target.value, e.target.dataset.UUID, false);
}
log(e.target.dataset.keyname, e.target.value);
};
videoEle.appendChild(div);
div.appendChild(label);
div.appendChild(input);
} else if (typeof data.cameraConstraints[i] === 'boolean') {
var div = document.createElement("div");
var label = document.createElement("label");
label.id = "label_" + + i + "_"+UUID;
label.name = label.id;
label.htmlFor = "constraints_" + + i + "_"+UUID;
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
label.style = "display:inline-block; padding:0;";
label.dataset.keyname = i;
label.dataset.UUID = UUID;
var input = document.createElement("select");
var c = document.createElement("option");
var opt = new Option("Off", false);
input.options.add(opt);
opt = new Option("On", true);
input.options.add(opt);
try{
if (data.audioConstraints[i] === true){
opt.selected = "true";
}
} catch(e){}
input.id = "constraints_" + + i + "_"+UUID;
input.className = "constraintCameraInput";
input.name = input.id;
input.style = "display:inline; padding:2px;";
input.dataset.UUID = UUID;
input.dataset.keyname = i;
input.dataset.chosen = input.value;
input.onchange = function(e) {
this.dataset.chosen = this.value;
//getById("label_"+e.target.dataset.keyname+ "_" + e.target.dataset.UUID).innerText =e.target.dataset.keyname+": "+e.target.value;
//updateVideoConstraints(e.target.dataset.keyname, e.target.value);
if (CtrlPressed){
requestVideoHack(e.target.dataset.keyname, e.target.value, e.target.dataset.UUID, true);
} else {
requestVideoHack(e.target.dataset.keyname, e.target.value, e.target.dataset.UUID, false);
}
log(e.target.dataset.keyname, e.target.value);
};
videoEle.appendChild(div);
div.appendChild(label);
div.appendChild(input);
}
} catch (e) {
errorlog(e);
}
}
query("#container_"+UUID+" .advancedVideoSettings").innerHTML = "";
query("#container_"+UUID+" .advancedVideoSettings").appendChild(videoEle);
query("#container_"+UUID+" .advancedVideoSettings").classList.remove("hidden");
if (fixScrollReset){
clearTimeout(fixScrollReset);
fixScrollReset = null;
getById("directorlayout").scrollTop = fixScrollResetValue;
}
}
///////
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function listAudioSettings() {
getById("popupSelector_constraints_audio").innerHTML = "";
var tracks = session.streamSrc.getAudioTracks();
if (!tracks.length){
warnlog("session.streamSrc contains no audio tracks");
return
}
for (var ii = 0; ii< tracks.length; ii++){
track0 = tracks[ii];
if (track0.getCapabilities) {
session.audioConstraints = track0.getCapabilities();
} else if (Firefox){ // let's pretend like Firefox doesn't actually suck
session.audioConstraints = {
"autoGainControl": [
true,
false
],
// "channelCount": {
// "max": 2,
// "min": 1
// },
// "deviceId": "default",
"echoCancellation": [
true,
false
],
// "groupId": "a3cbdec54a9b6ed473fd950415626f7e76f9d1b90f8c768faab572175a355a17",
// "latency": {
// "max": 0.01,
// "min": 0.01
// },
"noiseSuppression": [
true,
false
],
// "sampleRate": {
// "max": 48000,
// "min": 48000
// },
// "sampleSize": {
// "max": 16,
// "min": 16
/// }
};
}
try {
if (track0.getSettings) {
session.currentAudioConstraints = track0.getSettings();
if (!session.stereo){
try {
delete session.currentAudioConstraints.channelCount;
delete session.audioConstraints.channelCount;
} catch(e){};
} else if (session.audioInputChannels && (session.audioInputChannels==1)){ // this is pretty hacky, but it gets around not being able to actually set 1-channel. Not sure why.
session.currentAudioConstraints.channelCount = 1;
}
}
} catch (e) {
errorlog(e);
}
//////
if (ii==0){
for (var webAudio in session.webAudios) {
if (session.webAudios[webAudio].gainNode ) {
if (getById("popupSelector_constraints_audio").style.display == "none") {
getById("advancedOptionsAudio").style.display = "inline-flex";
}
var div = document.createElement("div");
var label = document.createElement("label");
var i = "masterGain";
//label.id = "label_" + i;
label.htmlFor = "constraints_" + i;
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
label.style = "display:inline-block;";
var input = document.createElement("input");
input.min = 0;
input.max = 200;
input.dataset.deviceid = track0.id; // pointless
input.type = "range";
input.dataset.keyname = i;
input.dataset.labelname = label.innerHTML;
input.id = "constraints_" + i;
input.style = "display:block; width:100%;";
input.name = "constraints_" + i;
input.value = session.webAudios[webAudio].gainNode.gain.value * 100;
//label.innerHTML += " " + parseInt(session.webAudios[webAudio].gainNode.gain.value * 100);
input.title = parseInt(input.value);
var manualInput = document.createElement("input");
manualInput.type = "number";
manualInput.dataset.keyname = i;
manualInput.dataset.deviceid = track0.id;
manualInput.dataset.labelname = label.innerHTML;
manualInput.value = parseFloat(input.value);
manualInput.className = "manualInput";
manualInput.id = "label_" + i;
manualInput.onchange = function(e) {
getById("constraints_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
changeMainGain(e.target.value);
e.target.title = e.target.value;
};
input.oninput = function(e) {
getById("label_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
changeMainGain(e.target.value);
e.target.title = e.target.value;
};
getById("popupSelector_constraints_audio").appendChild(div);
div.appendChild(label);
div.appendChild(manualInput);
div.appendChild(input);
break;
}
}
}
if (session.micDelay!==false && ii==0) { // ii==0 implies only track0 is supported by the web audio pipeline currently (or everything after the mixer node)
if (getById("popupSelector_constraints_audio").style.display == "none") {
getById("advancedOptionsAudio").style.display = "inline-flex";
}
var label = document.createElement("label");
var i = "micDelay";
label.htmlFor = "constraints_" + i;
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + " (ms):";
var input = document.createElement("input");
input.min = 0;
input.max = 500;
input.dataset.deviceid = track0.id; // pointless, for now
input.type = "range";
input.dataset.keyname = i;
input.dataset.labelname = label.innerHTML;
input.id = "constraints_" + i;
input.style = "display:block; width:100%;";
input.name = "constraints_" + i;
for (var webAudio in session.webAudios) {
if (session.webAudios[webAudio].micDelay) { // session.webAudios[waid].micDelay.delayTime.setValueAtTime
input.value = session.webAudios[webAudio].micDelay.delayTime.value*1000;
label.innerHTML += " " + parseInt(session.webAudios[webAudio].micDelay.delayTime.value*1000);
input.title = input.value;
break;
}
}
var manualInput = document.createElement("input");
manualInput.type = "number";
manualInput.dataset.keyname = i;
manualInput.dataset.labelname = label.innerHTML;
manualInput.value = parseFloat(input.value);
manualInput.className = "manualInput";
manualInput.id = "label_" + i;
manualInput.onchange = function(e) {
getById("constraints_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
changeMicDelay(e.target.value, e.target.dataset.deviceid);
e.target.title = e.target.value;
};
input.oninput = function(e) {
getById("label_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
changeMicDelay(e.target.value, e.target.dataset.deviceid);
e.target.title = e.target.value;
};
getById("popupSelector_constraints_audio").appendChild(label);
getById("popupSelector_constraints_audio").appendChild(manualInput);
getById("popupSelector_constraints_audio").appendChild(input);
}
if (session.lowcut && ii==0) { // ii==0 implies only track0 is supported by the web audio pipeline currently (or everything after the mixer node)
if (getById("popupSelector_constraints_audio").style.display == "none") {
getById("advancedOptionsAudio").style.display = "inline-flex";
}
var label = document.createElement("label");
var i = "Low_Cut";
label.htmlFor = "constraints_" + i;
label.innerText = "Low Cut:";
var input = document.createElement("input");
input.min = 50;
input.max = 400;
input.dataset.deviceid = track0.id; // pointless
input.type = "range";
input.dataset.keyname = i;
input.dataset.labelname = label.innerHTML;
input.id = "constraints_" + i;
input.style = "display:block; width:100%;";
input.name = "constraints_" + i;
for (var webAudio in session.webAudios) {
if (session.webAudios[webAudio].lowcut1) {
input.value = session.webAudios[webAudio].lowcut1.frequency.value;
label.innerHTML += " " + session.webAudios[webAudio].lowcut1.frequency.value;
input.title = input.value;
break;
}
}
var manualInput = document.createElement("input");
manualInput.type = "number";
manualInput.dataset.keyname = i;
manualInput.dataset.labelname = label.innerHTML;
manualInput.value = parseFloat(input.value);
manualInput.className = "manualInput";
manualInput.id = "label_" + i;
manualInput.onchange = function(e) {
getById("constraints_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
changeLowCut(e.target.value, e.target.dataset.deviceid);
e.target.title = e.target.value;
};
input.oninput = function(e) {
getById("label_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
changeLowCut(e.target.value, e.target.dataset.deviceid);
e.target.title = e.target.value;
};
getById("popupSelector_constraints_audio").appendChild(label);
getById("popupSelector_constraints_audio").appendChild(manualInput);
getById("popupSelector_constraints_audio").appendChild(input);
}
if (session.equalizer && ii==0) { // ii==0 implies only track0 is supported by the web audio pipeline currently (or everything after the mixer node)
if (getById("popupSelector_constraints_audio").style.display == "none") {
getById("advancedOptionsAudio").style.display = "inline-flex";
}
var label = document.createElement("label");
var i = "Low_EQ";
//label.id = "label_" + i;
label.htmlFor = "constraints_" + i;
label.innerHTML = "Low EQ:";
var input = document.createElement("input");
input.min = -50;
input.max = 50;
input.dataset.deviceid = track0.id; // pointless
input.type = "range";
input.dataset.keyname = i;
input.dataset.labelname = label.innerHTML;
input.id = "constraints_" + i;
input.style = "display:block; width:100%;";
input.name = "constraints_" + i;
for (var webAudio in session.webAudios) {
if (session.webAudios[webAudio].lowEQ) {
input.value = session.webAudios[webAudio].lowEQ.gain.value;
label.innerHTML += " " + session.webAudios[webAudio].lowEQ.gain.value;
input.title = input.value;
}
}
var manualInput = document.createElement("input");
manualInput.type = "number";
manualInput.dataset.keyname = i;
manualInput.dataset.labelname = label.innerHTML;
manualInput.value = parseFloat(input.value);
manualInput.className = "manualInput";
manualInput.id = "label_" + i;
manualInput.onchange = function(e) {
getById("constraints_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
changeLowEQ(e.target.value, e.target.dataset.deviceid);
e.target.title = e.target.value;
};
input.oninput = function(e) {
getById("label_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
changeLowEQ(e.target.value, e.target.dataset.deviceid);
e.target.title = e.target.value;
};
getById("popupSelector_constraints_audio").appendChild(label);
getById("popupSelector_constraints_audio").appendChild(manualInput);
getById("popupSelector_constraints_audio").appendChild(input);
//
if (getById("popupSelector_constraints_audio").style.display == "none") {
getById("advancedOptionsAudio").style.display = "inline-flex";
}
var label = document.createElement("label");
var i = "Mid_EQ";
//label.id = "label_" + i;
label.htmlFor = "constraints_" + i;
label.innerHTML = "Mid EQ:";
var input = document.createElement("input");
input.min = -50;
input.max = 50;
input.dataset.deviceid = track0.id; // pointless
input.type = "range";
input.dataset.keyname = i;
input.dataset.labelname = label.innerHTML;
input.id = "constraints_" + i;
input.style = "display:block; width:100%;";
input.name = "constraints_" + i;
for (var webAudio in session.webAudios) {
if (session.webAudios[webAudio].midEQ) {
input.value = session.webAudios[webAudio].midEQ.gain.value;
label.innerHTML += " " + session.webAudios[webAudio].midEQ.gain.value;
input.title = input.value;
}
}
var manualInput = document.createElement("input");
manualInput.type = "number";
manualInput.dataset.keyname = i;
manualInput.dataset.labelname = label.innerHTML;
manualInput.value = parseFloat(input.value);
manualInput.className = "manualInput";
manualInput.id = "label_" + i;
manualInput.onchange = function(e) {
getById("constraints_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
changeMidEQ(e.target.value, e.target.dataset.deviceid);
e.target.title = e.target.value;
};
input.oninput = function(e) {
getById("label_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
changeMidEQ(e.target.value, e.target.dataset.deviceid);
e.target.title = e.target.value;
};
getById("popupSelector_constraints_audio").appendChild(label);
getById("popupSelector_constraints_audio").appendChild(manualInput);
getById("popupSelector_constraints_audio").appendChild(input);
//
if (getById("popupSelector_constraints_audio").style.display == "none") {
getById("advancedOptionsAudio").style.display = "inline-flex";
}
var label = document.createElement("label");
var i = "High_EQ";
//label.id = "label_" + i;
label.htmlFor = "constraints_" + i;
label.innerHTML = "High EQ:";
var input = document.createElement("input");
input.min = -50;
input.max = 50;
input.dataset.deviceid = track0.id; // pointless
input.type = "range";
input.dataset.keyname = i;
input.dataset.labelname = label.innerHTML;
input.id = "constraints_" + i;
input.style = "display:block; width:100%;";
input.name = "constraints_" + i;
for (var webAudio in session.webAudios) {
if (session.webAudios[webAudio].highEQ) {
input.value = session.webAudios[webAudio].highEQ.gain.value;
label.innerHTML += " " + session.webAudios[webAudio].highEQ.gain.value;
input.title = input.value;
}
}
var manualInput = document.createElement("input");
manualInput.type = "number";
manualInput.dataset.keyname = i;
manualInput.dataset.labelname = label.innerHTML;
manualInput.value = parseFloat(input.value);
manualInput.className = "manualInput";
manualInput.id = "label_" + i;
manualInput.onchange = function(e) {
getById("constraints_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
changeHighEQ(e.target.value, e.target.dataset.deviceid);
e.target.title = e.target.value;
};
input.oninput = function(e) {
getById("label_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
changeHighEQ(e.target.value, e.target.dataset.deviceid);
e.target.title = e.target.value;
};
getById("popupSelector_constraints_audio").appendChild(label);
getById("popupSelector_constraints_audio").appendChild(manualInput);
getById("popupSelector_constraints_audio").appendChild(input);
}
if ((session.noisegate!==false) && (ii==0)) {
for (var webAudio in session.webAudios) {
if (session.webAudios[webAudio].gatingNode) {
var div = document.createElement("div");
var label = document.createElement("label");
var i = "noiseGating";
label.id = "label_" + i + "_"+ii;
label.htmlFor = "constraints_" + i + "_"+ii;
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
label.style = "display:inline-block;";
label.dataset.keyname = i;
label.title = "This will reduce the gain ~80% when there is no one talking loudly";
var input = document.createElement("select");
var c = document.createElement("option");
input.dataset.deviceid = track0.id;
var opt = new Option("Off", false);
input.options.add(opt);
opt = new Option("On", true);
if (session.noisegate){
opt.selected = "true";
}
input.options.add(opt);
if (getById("popupSelector_constraints_audio").style.display == "none") {
getById("advancedOptionsAudio").style.display = "inline-flex";
}
input.id = "constraints_" + i + "_"+ii;
input.className = "constraintCameraInput";
input.name = "constraints_" + i + "_"+ii;
input.style = "display:inline; padding:2px;";
input.dataset.keyname = i;
input.dataset.chosen = input.value;
input.onchange = function(e) {
this.dataset.chosen = this.value;
if (e.target.value == "false"){
session.noisegate = null;
} else if (e.target.value == "true"){
session.noisegate = true;
} else {
session.noisegate = e.target.value;
}
if (!session.noisegate){
changeGatingGain(100);
changeGatingGain(100,3100);
}
};
getById("popupSelector_constraints_audio").appendChild(div);
div.appendChild(label);
div.appendChild(input);
break;
}
}
}
////////
if (tracks.length>1){
var label = document.createElement("h4");
label.innerHTML = track0.label;
label.style = "text-shadow: 0 0 10px #fff3;margin:0px 0 10px 0"
if (ii>0){
label.style = "text-shadow: 0 0 10px #fff3;margin:40px 0 10px 0"
}
getById("popupSelector_constraints_audio").appendChild(label);
}
for (var i in session.audioConstraints) {
try {
log(i);
log(session.audioConstraints[i]);
if ((typeof session.audioConstraints[i] === 'object') && (session.audioConstraints[i] !== null) && ("max" in session.audioConstraints[i]) && ("min" in session.audioConstraints[i])) {
if (i === "aspectRatio") {
continue;
} else if (i === "width") {
continue;
} else if (i === "height") {
continue;
} else if (i === "frameRate") {
continue;
} else if (i === "latency") {
// continue;
} else if (i === "sampleRate") {
continue;
} else if (i === "channelCount") {
if (!session.stereo){
continue;
}
} else if (!session.disableWebAudio && (i === "volume")){
continue;
}
var label = document.createElement("label");
//label.id = "label_" + i + "_"+ii;
label.htmlFor = "constraints_" + i + "_"+ii;
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
var input = document.createElement("input");
input.min = session.audioConstraints[i].min;
input.max = session.audioConstraints[i].max;
input.dataset.deviceid = track0.id;
if (parseFloat(input.min) == parseFloat(input.max)) {
continue;
}
if (getById("popupSelector_constraints_audio").style.display == "none") {
getById("advancedOptionsAudio").style.display = "inline-flex";
}
var manualInput = document.createElement("input");
manualInput.type = "number";
if ("step" in session.audioConstraints[i]) {
input.step = session.audioConstraints[i].step;
manualInput.step = session.audioConstraints[i].step;
} else if ("volume" == i) {
input.step = 0.01;
manualInput.step = 0.01;
}
if (i in session.currentAudioConstraints) {
input.value = parseFloat(session.currentAudioConstraints[i]);
label.title = "Previously was: " + session.currentAudioConstraints[i];
input.title = "Previously was: " + session.currentAudioConstraints[i];
}
if ((i === "height") || (i === "width")){
input.title = "Hold CTRL (or cmd) to lock width and height together when changing them";
input.min = 16;
}
input.type = "range";
input.dataset.keyname = i;
input.dataset.track = ii;
input.id = "constraints_" + i + "_"+ii;
input.style = "display:block; width:100%;";
input.name = "constraints_" + i + "_"+ii;
manualInput.dataset.keyname = i;
manualInput.dataset.track = ii;
manualInput.dataset.deviceid = track0.id;
manualInput.className = "manualInput";
manualInput.id = "label_" + i + "_"+ii;
manualInput.max = session.audioConstraints[i].max;
manualInput.min = session.audioConstraints[i].min;
manualInput.value = parseFloat(session.currentAudioConstraints[i]);
if (i=="channelCount"){
input.style.display = "none";
}
manualInput.onchange = function(e) {
try {
getById("constraints_" + e.target.dataset.keyname+"_"+ e.target.dataset.track).value = parseFloat(e.target.value);
applyAudioHack(e.target.dataset.keyname, e.target.value, e.target.dataset.deviceid);
e.target.title = e.target.value;
}catch(e){errorlog(e);}
};
input.onchange = function(e) {
try {
getById("label_" + e.target.dataset.keyname+"_"+ e.target.dataset.track).value = parseFloat(e.target.value);
applyAudioHack(e.target.dataset.keyname, e.target.value, e.target.dataset.deviceid);
e.target.title = e.target.value;
}catch(e){errorlog(e);}
};
getById("popupSelector_constraints_audio").appendChild(label);
getById("popupSelector_constraints_audio").appendChild(manualInput);
getById("popupSelector_constraints_audio").appendChild(input);
} else if ((typeof session.audioConstraints[i] === 'object') && (session.audioConstraints[i] !== null)) {
if (i == "resizeMode") {
continue;
}
var div = document.createElement("div");
var label = document.createElement("label");
label.id = "label_" + i + "_"+ii;
label.htmlFor = "constraints_" + i + "_"+ii;
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
label.style = "display:inline-block;";
label.dataset.keyname = i;
var input = document.createElement("select");
var c = document.createElement("option");
if (session.audioConstraints[i].length == 2) {
for (var opts in session.audioConstraints[i]) {
log(opts);
if (session.audioConstraints[i][opts]===true){
var opt = new Option("On", session.audioConstraints[i][opts]);
} else if (session.audioConstraints[i][opts]===false){
var opt = new Option("Off", session.audioConstraints[i][opts]);
} else {
var opt = new Option(session.audioConstraints[i][opts], session.audioConstraints[i][opts]);
}
input.options.add(opt);
if (i in session.currentAudioConstraints) {
if (session.audioConstraints[i][opts] == session.currentAudioConstraints[i]) {
opt.selected = "true";
}
}
}
} else if (session.audioConstraints[i].length > 1) {
for (var opts in session.audioConstraints[i]) {
log(opts);
var opt = new Option(session.audioConstraints[i][opts], session.audioConstraints[i][opts]);
input.options.add(opt);
if (i in session.currentAudioConstraints) {
if (session.audioConstraints[i][opts] == session.currentAudioConstraints[i]) {
opt.selected = "true";
}
}
}
} else if (i.toLowerCase == "torch") {
var opt = new Option("Off", false);
input.options.add(opt);
opt = new Option("On", true);
input.options.add(opt);
} else {
continue;
}
if (getById("popupSelector_constraints_audio").style.display == "none") {
getById("advancedOptionsAudio").style.display = "inline-flex";
}
input.id = "constraints_" + i + "_"+ii;
input.className = "constraintCameraInput";
input.name = "constraints_" + i + "_"+ii;
input.dataset.deviceid = track0.id;
input.style = "display:inline; padding:2px;";
input.dataset.keyname = i;
input.dataset.chosen = input.value;
input.onchange = function(e) {
this.dataset.chosen = this.value;
applyAudioHack(e.target.dataset.keyname, e.target.value, e.target.dataset.deviceid);
log(e.target.dataset.keyname, e.target.value);
};
getById("popupSelector_constraints_audio").appendChild(div);
div.appendChild(label);
div.appendChild(input);
} else if (typeof session.audioConstraints[i] === 'boolean') {
var div = document.createElement("div");
var label = document.createElement("label");
label.id = "label_" + i + "_"+ii;
label.htmlFor = "constraints_" + i + "_"+ii;
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
label.style = "display:inline-block;";
label.dataset.keyname = i;
var input = document.createElement("select");
var c = document.createElement("option");
input.dataset.deviceid = track0.id;
var opt = new Option("Off", false);
input.options.add(opt);
opt = new Option("On", true);
input.options.add(opt);
if (getById("popupSelector_constraints_audio").style.display == "none") {
getById("advancedOptionsAudio").style.display = "inline-flex";
}
input.id = "constraints_" + i + "_"+ii;
input.className = "constraintCameraInput";
input.name = "constraints_" + i + "_"+ii;
input.style = "display:inline; padding:2px;";
input.dataset.keyname = i;
input.dataset.chosen = input.value;
input.onchange = function(e) {
this.dataset.chosen = this.value;
//getById("label_"+e.target.dataset.keyname).innerHTML =e.target.dataset.keyname+": "+e.target.value;
//updateAudioConstraints(e.target.dataset.keyname, e.target.value);
applyAudioHack(e.target.dataset.keyname, e.target.value, e.target.dataset.deviceid);
log(e.target.dataset.keyname, e.target.value);
};
getById("popupSelector_constraints_audio").appendChild(div);
div.appendChild(label);
div.appendChild(input);
}
} catch (e) {
errorlog(e);
}
}
if (tracks.length>1){
for (var webAudio in session.webAudios) {
if (session.webAudios[webAudio].subGainNodes && (track0.id in session.webAudios[webAudio].subGainNodes)) {
if (getById("popupSelector_constraints_audio").style.display == "none") {
getById("advancedOptionsAudio").style.display = "inline-flex";
}
var div = document.createElement("div");
var label = document.createElement("label");
var i = "Gain";
//label.id = "label_" + i + "_" + track0.id;
label.htmlFor = "constraints_" + i + "_" + track0.id;
label.innerText = "Gain:";
label.style = "display:inline-block; padding:0;margin-top: 15px";
var input = document.createElement("input");
input.min = 0;
input.max = 200;
input.dataset.deviceid = track0.id; // pointless
input.type = "range";
input.dataset.keyname = i;
input.dataset.labelname = label.innerHTML;
input.id = "constraints_" + i+ "_" + track0.id;
input.style = "display:block; width:100%;";
input.name = "constraints_" + i + "_" + track0.id;
input.value = session.webAudios[webAudio].subGainNodes[track0.id].gain.value * 100;
label.innerHTML += " " + parseInt(session.webAudios[webAudio].subGainNodes[track0.id].gain.value * 100);
input.title = parseInt(input.value);
var manualInput = document.createElement("input");
manualInput.type = "number";
manualInput.dataset.keyname = i;
manualInput.dataset.deviceid = track0.id;
manualInput.dataset.labelname = label.innerHTML;
manualInput.value = parseFloat(input.value);
manualInput.className = "manualInput";
manualInput.id = "label_" + i + "_" + track0.id;
manualInput.onchange = function(e) {
getById("constraints_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
changeSubGain(e.target.value, e.target.dataset.deviceid);
e.target.title = e.target.value;
};
input.input = function(e) {
getById("label_" + e.target.dataset.keyname).caluse = parseFloat(e.target.value);
changeSubGain(e.target.value, e.target.dataset.deviceid);
e.target.title = e.target.value;
};
getById("popupSelector_constraints_audio").appendChild(div);
div.appendChild(label);
div.appendChild(manualInput);
div.appendChild(input);
break;
}
}
}
}
}
function applyAudioHack(constraint, value = null, deviceid="default") {
if (value == parseFloat(value)) {
value = parseFloat(value);
if (constraint == "channelCount"){
session.audioInputChannels = value;
}
value = {
exact: value
};
} else if (value == "true") {
value = true;
} else if (value == "false") {
value = false;
}
////////////////
try {
var tracks = session.streamSrc.getAudioTracks();
if (tracks.length) {
var track0 = tracks[0];
for (var ii = 0;ii session.totalRoomBitrate) {
return;
} else {
session.controlRoomBitrate = parseInt(e.target.value);
}
updateMixer();
};
input.onchange = function(e) {
getById("label_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
if (e.target.value > session.totalRoomBitrate) {
return;
} else {
session.controlRoomBitrate = parseInt(e.target.value);
}
updateMixer();
};
getById("popupSelector_constraints_video").appendChild(label);
getById("popupSelector_constraints_video").appendChild(manualInput);
getById("popupSelector_constraints_video").appendChild(input);
}
try {
var track0 = session.streamSrc.getVideoTracks();
if (track0.length) {
track0 = track0[0];
if (track0.getCapabilities) {
session.cameraConstraints = track0.getCapabilities();
} else {
session.cameraConstraints = {};
}
log(session.cameraConstraints);
}
} catch (e) {
errorlog(e);
return;
}
try {
if (track0.getSettings) {
session.currentCameraConstraints = track0.getSettings();
if (!window.matchMedia("(orientation: portrait)").matches){
if (session.currentCameraConstraints && session.currentCameraConstraints.aspectRatio){
session.currentCameraConstraints.aspectRatio = 1/session.currentCameraConstraints.aspectRatio;
}
}
} else {
session.currentCameraConstraints = {};
}
} catch (e) {
errorlog(e);
}
for (var i in session.cameraConstraints) {
try {
log(i);
log(session.cameraConstraints[i]);
if (i === "focusMode") {
continue // I'll handle this with FocusDistance instead
} else if (i === "whiteBalanceMode") {
continue // I'll handle this elsewhere
} else if (i === "exposureMode") {
continue // I'll handle this elsewhere
}
if ((typeof session.cameraConstraints[i] === 'object') && (session.cameraConstraints[i] !== null) && ("max" in session.cameraConstraints[i]) && ("min" in session.cameraConstraints[i])) {
var manualMode = false;
var manualLabel = false;
if (i ==="exposureTime"){
if (session.currentCameraConstraints["exposureMode"]){
manualMode = document.createElement("input");
manualMode.type = "checkbox";
manualMode.id = "manual_"+i;
manualMode.dataset.keyname = "exposureMode";
manualMode.onchange = function(e) {
var value = "manual";
if (e.target.checked){
value = "continuous";
}
if (CtrlPressed){
updateCameraConstraints(e.target.dataset.keyname, value, true, false);
} else {
updateCameraConstraints(e.target.dataset.keyname, value, false, false);
}
};
manualLabel = document.createElement("label");
manualLabel.htmlFor = manualMode.id;
manualLabel.innerHTML = "Auto: ";
manualLabel.style.marginLeft = "20px";
if (session.currentCameraConstraints["exposureMode"] == "continuous"){
manualMode.checked = true;
}
}
} else if (i ==="focusDistance"){
if (session.currentCameraConstraints["focusMode"]){
manualMode = document.createElement("input");
manualMode.type = "checkbox";
manualMode.id = "manual_"+i;
manualMode.dataset.keyname = "focusMode";
manualMode.onchange = function(e) {
var value = "manual";
if (e.target.checked){
value = "continuous";
}
if (CtrlPressed){
updateCameraConstraints(e.target.dataset.keyname, value, true, false);
} else {
updateCameraConstraints(e.target.dataset.keyname, value, false, false);
}
};
manualLabel = document.createElement("label");
manualLabel.htmlFor = manualMode.id;
manualLabel.innerHTML = "Auto: ";
manualLabel.style.marginLeft = "20px";
if (session.currentCameraConstraints["focusMode"] == "continuous"){
manualMode.checked = true;
}
}
} else if (i ==="colorTemperature"){
if (session.currentCameraConstraints["whiteBalanceMode"]){
manualMode = document.createElement("input");
manualMode.type = "checkbox";
manualMode.id = "manual_"+i;
manualMode.dataset.keyname = "whiteBalanceMode";
manualMode.onchange = function(e) {
var value = "manual";
if (e.target.checked){
value = "continuous";
}
if (CtrlPressed){
updateCameraConstraints(e.target.dataset.keyname, value, true, false);
} else {
updateCameraConstraints(e.target.dataset.keyname, value, false, false);
}
};
manualLabel = document.createElement("label");
manualLabel.htmlFor = manualMode.id;
manualLabel.innerHTML = "Auto: ";
manualLabel.style.marginLeft = "20px";
if (session.currentCameraConstraints["whiteBalanceMode"] == "continuous"){
manualMode.checked = true;
}
}
}
var label = document.createElement("label");
label.htmlFor = "constraints_" + i;
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
var input = document.createElement("input");
input.min = parseFloat(session.cameraConstraints[i].min);
if (i === "aspectRatio") {
input.max = 5;
input.min = 0.2
} else {
input.min = parseFloat(session.cameraConstraints[i].min);
input.max = parseFloat(session.cameraConstraints[i].max);
}
if (parseFloat(input.min) == parseFloat(input.max)) {
continue;
}
if (getById("popupSelector_constraints_video").style.display == "none") {
getById("advancedOptionsCamera").style.display = "inline-flex";
}
var manualInput = document.createElement("input");
manualInput.type = "number";
manualInput.dataset.keyname = i;
manualInput.className = "manualInput";
manualInput.id = "label_" + i;
if ("step" in session.cameraConstraints[i]) {
input.step = session.cameraConstraints[i].step;
manualInput.step = session.cameraConstraints[i].step;
} else if (i === "aspectRatio") {
input.step = 0.000001
manualInput.step = 0.005;
}
if (i in session.currentCameraConstraints) {
input.value = parseFloat(session.currentCameraConstraints[i]);
//label.innerHTML = i + ": " + session.currentCameraConstraints[i];
manualInput.value = parseFloat(session.currentCameraConstraints[i]);
label.title = "Previously was: " + session.currentCameraConstraints[i];
input.title = "Previously was: " + session.currentCameraConstraints[i];
} else {
label.innerHTML = i;
}
if ((i === "height") || (i === "width")){
input.title = "Hold CTRL (or cmd) to lock width and height together when changing them";
input.min = 16;
}
input.type = "range";
input.dataset.keyname = i;
input.id = "constraints_" + i;
input.style = "display:block; width:100%;";
input.name = "constraints_" + i;
// on manualInput.change = .. update the input field! gotta riprocate
manualInput.onchange = function(e) {
getById("constraints_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
updateCameraConstraints(e.target.dataset.keyname, e.target.value, false, false);
};
input.oninput = function(e) {
getById("label_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
if (CtrlPressed){
updateCameraConstraints(e.target.dataset.keyname, e.target.value, true, false, false);
} else {
updateCameraConstraints(e.target.dataset.keyname, e.target.value, false, false, false);
}
};
input.onchange = function(e) {
getById("label_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
if (CtrlPressed){
updateCameraConstraints(e.target.dataset.keyname, e.target.value, true, false);
} else {
updateCameraConstraints(e.target.dataset.keyname, e.target.value, false, false);
}
};
getById("popupSelector_constraints_video").appendChild(label);
getById("popupSelector_constraints_video").appendChild(manualInput);
if (manualMode && manualLabel){
getById("popupSelector_constraints_video").appendChild(manualLabel);
getById("popupSelector_constraints_video").appendChild(manualMode);
}
if (i === "aspectRatio") {
var preSelectButton = document.createElement("button");
preSelectButton.value = 16/9.0;
preSelectButton.innerText = "16:9";
preSelectButton.dataset.keyname = i;
preSelectButton.className = "preSelectButton";
preSelectButton.onclick = function(e) {
getById("constraints_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
getById("label_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
updateCameraConstraints(e.target.dataset.keyname, e.target.value, false, false);
};
getById("popupSelector_constraints_video").appendChild(preSelectButton);
var preSelectButton = document.createElement("button");
preSelectButton.value = 9/16.0;
preSelectButton.innerText = "9:16";
preSelectButton.className = "preSelectButton";
preSelectButton.dataset.keyname = i;
preSelectButton.onclick = function(e) {
getById("constraints_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
getById("label_" + e.target.dataset.keyname).value = parseFloat(e.target.value);
updateCameraConstraints(e.target.dataset.keyname, e.target.value, false, false);
};
getById("popupSelector_constraints_video").appendChild(preSelectButton);
}
getById("popupSelector_constraints_video").appendChild(input);
} else if ((typeof session.cameraConstraints[i] === 'object') && (session.cameraConstraints[i] !== null)) {
if (i == "resizeMode") {
continue;
}
var div = document.createElement("div");
var label = document.createElement("label");
label.id = "label_" + i;
label.htmlFor = "constraints_" + i;
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
label.style = "display:inline-block;";
label.dataset.keyname = i;
var input = document.createElement("select");
if (session.cameraConstraints[i].length > 1) {
var included = false;
for (var opts in session.cameraConstraints[i]) {
log(opts);
var opt = new Option(session.cameraConstraints[i][opts], session.cameraConstraints[i][opts]);
input.options.add(opt);
if (i in session.currentCameraConstraints) {
if (session.cameraConstraints[i][opts] == session.currentCameraConstraints[i]) {
opt.selected = "true";
included = true;
}
}
}
if (!included){
if (i in session.currentCameraConstraints) {
var opt = new Option(session.currentCameraConstraints[i], session.currentCameraConstraints[i]);
input.options.add(opt);
opt.selected = "true";
}
}
} else if (i.toLowerCase == "torch") {
warnlog("TORCH");
var opt = new Option("Off", false);
input.options.add(opt);
opt = new Option("On", true);
input.options.add(opt);
try{
if (session.currentCameraConstraints[i]){
opt.selected = "selected";
}
} catch(e){}
} else if (session.cameraConstraints[i].length && ("continuous" == session.cameraConstraints[i][0])){
var opt = new Option("continuous", "continuous");
input.options.add(opt);
if (i in session.currentCameraConstraints) {
if ("continuous" == session.currentCameraConstraints[i]) {
opt.selected = "true";
var opt = new Option("manual", "manual");
input.options.add(opt);
var opt = new Option("none", "none");
input.options.add(opt);
} else {
var opt = new Option(session.currentCameraConstraints[i], session.currentCameraConstraints[i]);
input.options.add(opt);
opt.selected = "true";
if (session.currentCameraConstraints[i]=="none"){
var opt = new Option("manual", "manual");
input.options.add(opt);
} else {
var opt = new Option("none", "none");
input.options.add(opt);
}
}
} else {
opt.selected = "true";
var opt = new Option("manual", "manual");
input.options.add(opt);
var opt = new Option("none", "none");
input.options.add(opt);
}
} else if (session.cameraConstraints[i].length && ("manual" == session.cameraConstraints[i][0])){
var opt = new Option("manual", "manual");
input.options.add(opt);
if (i in session.currentCameraConstraints) {
if ("manual" == session.currentCameraConstraints[i]) {
opt.selected = "true";
var opt = new Option("continuous", "continuous");
input.options.add(opt);
var opt = new Option("none", "none");
input.options.add(opt);
} else {
var opt = new Option(session.currentCameraConstraints[i], session.currentCameraConstraints[i]);
input.options.add(opt);
opt.selected = "true";
if (session.currentCameraConstraints[i]=="none"){
var opt = new Option("continuous", "continuous");
input.options.add(opt);
} else {
var opt = new Option("none", "none");
input.options.add(opt);
}
}
} else {
opt.selected = "true";
var opt = new Option("continuous", "continuous");
input.options.add(opt);
var opt = new Option("none", "none");
input.options.add(opt);
}
} else {
continue;
}
if (getById("popupSelector_constraints_video").style.display == "none") {
getById("advancedOptionsCamera").style.display = "inline-flex";
}
input.id = "constraints_" + i;
input.className = "constraintCameraInput";
input.name = "constraints_" + i;
input.style = "display:inline; padding:2px;";
input.dataset.keyname = i;
input.dataset.chosen = input.value;
input.onchange = function(e) {
this.dataset.chosen = this.value;
//getById("label_"+e.target.dataset.keyname).innerHTML =e.target.dataset.keyname+": "+e.target.value;
if (CtrlPressed){
updateCameraConstraints(e.target.dataset.keyname, e.target.value, true, false, false);
} else {
updateCameraConstraints(e.target.dataset.keyname, e.target.value, false, false, false);
}
log(e.target.dataset.keyname + " " + e.target.value);
};
getById("popupSelector_constraints_video").appendChild(div);
div.appendChild(label);
div.appendChild(input);
} else if (typeof session.cameraConstraints[i] === 'boolean') {
var div = document.createElement("div");
var label = document.createElement("label");
label.id = "label_" + i;
label.htmlFor = "constraints_" + i;
label.innerText = capitalizeFirstLetter(i).replace(/([a-z])([A-Z])/g, '$1 $2') + ":";
label.style = "display:inline-block;";
label.dataset.keyname = i;
var input = document.createElement("select");
var opt = new Option("Off", "false");
input.options.add(opt);
opt = new Option("On", "true");
input.options.add(opt);
if (session.currentCameraConstraints[i]){
opt.selected = "true";
}
if (getById("popupSelector_constraints_video").style.display == "none") {
getById("advancedOptionsCamera").style.display = "inline-flex";
}
input.id = "constraints_" + i;
input.className = "constraintCameraInput";
input.name = "constraints_" + i;
input.style = "display:inline; padding:2px;";
input.dataset.keyname = i;
input.dataset.chosen = input.value;
input.onchange = function(e) {
this.dataset.chosen = this.value;
//getById("label_"+e.target.dataset.keyname).innerHTML =e.target.dataset.keyname+": "+e.target.value;
if (CtrlPressed){
updateCameraConstraints(e.target.dataset.keyname, e.target.value, true, false, false);
} else {
updateCameraConstraints(e.target.dataset.keyname, e.target.value, false, false, false);
}
log(e.target.dataset.keyname + " " + e.target.value);
};
getById("popupSelector_constraints_video").appendChild(div);
div.appendChild(label);
div.appendChild(input);
}
} catch (e) {
errorlog(e);
}
}
if (session.currentCameraConstraints.deviceId){
if (getStorage("camera_"+session.currentCameraConstraints.deviceId)){
var button = document.createElement("button");
button.innerHTML = "Reset video settings to default";
button.style.display = "block";
button.style.padding = "10px 5px";
button.dataset.deviceId = session.currentCameraConstraints.deviceId;
button.onclick = function(){
var deviceId = this.dataset.deviceId;
var cameraSettings = getStorage("camera_"+deviceId);
var constraints = {};
var resetResolution = false;
var failed = false;
if (cameraSettings['default']){
if (cameraSettings['current']){
for (var i in cameraSettings['default']){
if (i == "groupId"){
continue;
} else if (i === "aspectRatio") { // do not load from storage; causes issues
continue;
} else if (i === "width") {
// continue;
} else if (i === "height") {
// continue;
} else { // if I include any of these, it will complain about mixing types and fail
if (i in cameraSettings['current']){
if (cameraSettings['current'][i] != cameraSettings['default'][i]){
track0.applyConstraints({
advanced: [{[i]:cameraSettings['default'][i]}]
}).then(() => {}).catch(e => {
errorlog("Failed to reset to defaults");
failed = true;
});
}
}
continue;
}
if (i in cameraSettings['current']){
if (cameraSettings['current'][i] != cameraSettings['default'][i]){
if (i in session.cameraConstraints){
if ("min" in session.cameraConstraints[i]){
if (session.cameraConstraints[i].min>cameraSettings['default'][i]){
continue;
}
}
if ("max" in session.cameraConstraints[i]){
if (session.cameraConstraints[i].max {
if (!failed){
removeStorage("camera_"+deviceId);
}
listCameraSettings();
if (resetResolution){
session.setResolution(); // this will reset scaling for all viewers of this stream
}
}).catch(e => {
errorlog("Failed to reset to defaults");
errorlog(e);
});
} else if (!failed){
removeStorage("camera_"+deviceId);
listCameraSettings();
}
};
getById("popupSelector_constraints_video").appendChild(button);
}
}
}
// applySavedAudioSettings is currently disabled since aec/denoise/etc do not work except with constraints.
function applySavedAudioSettings(track0){ // just applies any saved settings. This then assumes there are already default settings saved, as saved won't be there without the default also.
if (track0.getSettings) {
log("applySavedAudioSettings");
session.currentAudioConstraints = track0.getSettings();
if ("deviceId" in session.currentAudioConstraints){
var deviceId = session.currentAudioConstraints.deviceId;
if (getStorage("audio_"+deviceId)){
var audioSettings = getStorage("audio_"+deviceId);
var constraints = {};
if (audioSettings['deviceId']){
for (var i in session.currentAudioConstraints){
if (i in audioSettings){
if (audioSettings[i] != session.currentAudioConstraints[i]){
if (i == "autoGainControl"){
} else if (i == "echoCancellation"){
} else if (i == "noiseSuppression"){
} else {
continue;
}
constraints[i]=audioSettings[i];
warnlog("DIFF: "+i);
}
}
}
}
warnlog(constraints);
if (Object.keys(constraints).length){
track0.applyConstraints({
advanced: [constraints] // ignore
}).then(() => {
warnlog("audio settings updated for deviceId:"+deviceId);
//removeStorage("audio_"+deviceId);
//listCameraSettings();
}).catch(e => {
errorlog("Failed to reset to audio defaults");
});
}
}
}
}
}
function applySavedVideoSettings(track0){ // just applies any saved settings. This then assumes there are already default settings saved, as saved won't be there without the default also.
if (track0.getSettings) {
session.currentCameraConstraints = track0.getSettings();
if (!window.matchMedia("(orientation: portrait)").matches){
if (session.currentCameraConstraints && session.currentCameraConstraints.aspectRatio){
session.currentCameraConstraints.aspectRatio = 1/session.currentCameraConstraints.aspectRatio;
}
}
if ("deviceId" in session.currentCameraConstraints){
var deviceId = session.currentCameraConstraints.deviceId;
if (getStorage("camera_"+deviceId)){
var cameraSettings = getStorage("camera_"+deviceId);
var constraints = {};
if (cameraSettings['current']){
for (var i in session.currentCameraConstraints){
if (i in cameraSettings['current']){
if (cameraSettings['current'][i] != session.currentCameraConstraints[i]){
if (i == "groupId"){
continue;
} else if (session.forceAspectRatio && (i === "aspectRatio")){
log("Skipping saved AspectRatio setting");
continue;
}
constraints[i]=cameraSettings['current'][i];
warnlog("DIFF: "+i);
}
}
}
}
warnlog(constraints);
if (Object.keys(constraints).length){
track0.applyConstraints({
advanced: [constraints] // ignore
}).then(() => {
warnlog("video settings updated for deviceId:"+deviceId);
//removeStorage("camera_"+deviceId);
//listCameraSettings();
}).catch(e => {
errorlog("Failed to reset to defaults");
});
}
}
}
}
}
var updateCameraConstraintsBusy = false;
var updateCameraConstraintsNext = false;
async function updateCameraConstraints(constraint, value = null, ctrl=false, UUID=false, save=true) {
log("updateCameraConstraintsBusy..?");
if (updateCameraConstraintsBusy){
updateCameraConstraintsNext = [constraint, value, ctrl, UUID, save];
return;
} else {
updateCameraConstraintsBusy = true;
updateCameraConstraintsNext = false;
}
try {
var track0 = session.streamSrc.getVideoTracks();
track0 = track0[0]; // shoud only be one video track anyways.
if (!track0 || (track0.readyState && track0.readyState != 'live') || (!track0.enabled)) {
if (!save){
errorlog("TRACK IS NOT ENABLED");
updateCameraConstraintsBusy = false;
updateCameraConstraintsNext = false;
}
}
if (value == parseFloat(value)) {
value = parseFloat(value);
} else if (value == "true") {
value = true;
} else if (value == "false") {
value = false;
}
log({
advanced: [{
[constraint]: value
}]
});
} catch(e){
errorlog(e);
updateCameraConstraintsBusy = false;
updateCameraConstraintsNext = false;
return e;
}
log("updateCameraConstraintsNext:");
log(updateCameraConstraintsNext);
try {
if (track0.getSettings){
var cameraSettings = {};
session.currentCameraConstraints = track0.getSettings();
if (!window.matchMedia("(orientation: portrait)").matches){
if (session.currentCameraConstraints && session.currentCameraConstraints.aspectRatio){
session.currentCameraConstraints.aspectRatio = 1/session.currentCameraConstraints.aspectRatio;
}
}
if (session.currentCameraConstraints.deviceId){
if (!getStorage("camera_"+session.currentCameraConstraints.deviceId)){
cameraSettings['default'] = JSON.parse(JSON.stringify(session.currentCameraConstraints));
log(cameraSettings['default']);
} else {
cameraSettings = getStorage("camera_"+session.currentCameraConstraints.deviceId);
}
}
}
} catch(e){errorlog(e);}
if (constraint=="width"){
var constraits = {"width": value};
if (session.currentCameraConstraints && session.currentCameraConstraints.frameRate){
constraits.frameRate = session.currentCameraConstraints.frameRate;
}
if (!window.matchMedia("(orientation: portrait)").matches && session.currentCameraConstraints){
if (!ctrl && session.currentCameraConstraints.height){
constraits.height = session.currentCameraConstraints.height;
}
}
} else if (constraint=="height"){
var constraits = {"height": value};
if (session.currentCameraConstraints && session.currentCameraConstraints.frameRate){
constraits.frameRate = session.currentCameraConstraints.frameRate;
}
if (!window.matchMedia("(orientation: portrait)").matches && session.currentCameraConstraints){
if (!ctrl && session.currentCameraConstraints.width){
constraits.width = session.currentCameraConstraints.width;
}
}
} else if (!ctrl && (constraint=="frameRate")){
var constraits = {"frameRate": value};
if (!window.matchMedia("(orientation: portrait)").matches && session.currentCameraConstraints){
if (session.currentCameraConstraints.height && session.currentCameraConstraints.width){
constraits.height = session.currentCameraConstraints.height;
constraits.width = session.currentCameraConstraints.width;
}
}
} else if ((constraint=="exposureMode") && (value=="manual")){
var constraits = {}; // try to force the current focus, to get the actual current value.
if (session.currentCameraConstraints && session.currentCameraConstraints.exposureTime){ // just requested a second a go
constraits.exposureTime = session.currentCameraConstraints.exposureTime; // needs the focus set for the manual to activate.
}
await track0.applyConstraints({ // apply what we have on record, to try to force it.
advanced: [constraits]
})
session.currentCameraConstraints = track0.getSettings(); // now get the actual focus distance; solves a bug
if (!window.matchMedia("(orientation: portrait)").matches){
if (session.currentCameraConstraints && session.currentCameraConstraints.aspectRatio){
session.currentCameraConstraints.aspectRatio = 1/session.currentCameraConstraints.aspectRatio;
}
}
var constraits = {[constraint]: value}; // now we can set things to manual; if we don't set the focusDistance, it won't work otherwise.
if (session.currentCameraConstraints && session.currentCameraConstraints.exposureTime){
constraits.exposureTime = session.currentCameraConstraints.exposureTime; // needs the focus set for the manual to activate.
}
} else if (constraint=="exposureTime"){
var constraits = {[constraint]: value};
constraits.exposureMode = "manual";
} else if ((constraint=="focusMode") && (value=="manual")){
var constraits = {}; // try to force the current focus, to get the actual current value.
if (session.currentCameraConstraints && session.currentCameraConstraints.focusDistance){ // just requested a second a go
constraits.focusDistance = session.currentCameraConstraints.focusDistance; // needs the focus set for the manual to activate.
}
await track0.applyConstraints({ // apply what we have on record, to try to force it.
advanced: [constraits]
})
session.currentCameraConstraints = track0.getSettings(); // now get the actual focus distance; solves a bug
if (!window.matchMedia("(orientation: portrait)").matches){
if (session.currentCameraConstraints && session.currentCameraConstraints.aspectRatio){
session.currentCameraConstraints.aspectRatio = 1/session.currentCameraConstraints.aspectRatio;
}
}
var constraits = {[constraint]: value}; // now we can set things to manual; if we don't set the focusDistance, it won't work otherwise.
if (session.currentCameraConstraints && session.currentCameraConstraints.focusDistance){
constraits.focusDistance = session.currentCameraConstraints.focusDistance; // needs the focus set for the manual to activate.
}
} else if (constraint=="focusDistance"){
var constraits = {[constraint]: value};
constraits.focusMode = "manual";
} else if ((constraint=="whiteBalanceMode") && (value=="manual")){
var constraits = {}; // try to force the current colorTemperature, to get the actual current value.
if (session.currentCameraConstraints && session.currentCameraConstraints.colorTemperature){ // just requested a second a go
constraits.colorTemperature = session.currentCameraConstraints.colorTemperature; // needs the colorTemperature set for the manual to activate.
}
await track0.applyConstraints({ // apply what we have on record, to try to force it.
advanced: [constraits]
})
session.currentCameraConstraints = track0.getSettings(); // now get the actual colorTemperature; solves a bug
if (!window.matchMedia("(orientation: portrait)").matches){
if (session.currentCameraConstraints && session.currentCameraConstraints.aspectRatio){
session.currentCameraConstraints.aspectRatio = 1/session.currentCameraConstraints.aspectRatio;
}
}
var constraits = {[constraint]: value};
if (session.cameraConstraints.colorTemperature && ("max" in session.cameraConstraints.colorTemperature) && ("min" in session.cameraConstraints.colorTemperature)){
if (session.currentCameraConstraints && session.currentCameraConstraints.colorTemperature){
constraits.colorTemperature = session.currentCameraConstraints.colorTemperature;
} else if ((5000>=session.cameraConstraints.colorTemperature.min) && (5000<=session.cameraConstraints.colorTemperature.max)){
constraits.colorTemperature = 5000; // whiteBalanceMode won't work unless a colorTemperature is set. 5000 is a good default.
} else {
constraits.colorTemperature = session.cameraConstraints.colorTemperature.max;
}
}
} else if ((constraint=="whiteBalanceMode") && (value=="continuous")){
var constraits = {[constraint]: value};
if (session.mobile && ChromeVersion){ // trying to fix the issue that chrome mobile has.
constraits.colorTemperature = 5000;
}
} else if (constraint=="colorTemperature"){
var constraits = {[constraint]: value};
constraits.whiteBalanceMode = "manual";
} else if (constraint=="aspectRatio"){
var constraits = {[constraint]: value};
if (session.currentCameraConstraints && session.currentCameraConstraints.frameRate){
constraits.frameRate = session.currentCameraConstraints.frameRate;
}
} else {
var constraits = {[constraint]: value};
}
if (window.matchMedia("(orientation: portrait)").matches){
if (constraits.aspectRatio){
constraits.aspectRatio = 1/constraits.aspectRatio;
}
}
log("20788");
log(constraits);
await track0.applyConstraints({
advanced: [constraits]
}).then(() => {
log("applied constraint");
if (save){
if (track0.getSettings){ // -- updateCameraConstraints
if (session.currentCameraConstraints.deviceId){
session.currentCameraConstraints = track0.getSettings();
if (!window.matchMedia("(orientation: portrait)").matches){
if (session.currentCameraConstraints && session.currentCameraConstraints.aspectRatio){
session.currentCameraConstraints.aspectRatio = 1/session.currentCameraConstraints.aspectRatio;
}
}
cameraSettings['current'] = session.currentCameraConstraints; // this won't let failed settings be stored.
//cameraSettings['current'][constraint] = value; // setting value is a problem, as it will allow for failed settings to be stored.
setStorage("camera_"+session.currentCameraConstraints.deviceId, cameraSettings);
if (toggleSettingsState == true) {
listCameraSettings();
}
}
}
if (UUID){
var data = {};
data.UUID = UUID;
data.videoOptions = listVideoSettingsPrep();
sendMediaDevices(data.UUID);
session.sendMessage(data, data.UUID);
}
if ((constraint == "width") || (constraint == "height") || (constraint == "aspectRatio")){
session.setResolution(); // this will reset scaling for all viewers of this stream
}
}
if (updateCameraConstraintsNext){
setTimeout(function(){
updateCameraConstraintsBusy = false;
updateCameraConstraints(updateCameraConstraintsNext[0], updateCameraConstraintsNext[1], updateCameraConstraintsNext[2], updateCameraConstraintsNext[3], updateCameraConstraintsNext[4]);
},30)
} else {
updateCameraConstraintsBusy = false;
}
}).catch(e => {
errorlog(e.message);
errorlog("coulnd't save defaults"); // this doesn't get triggered when a setting fails for some reason.
window.focus();
updateCameraConstraintsBusy = false;
updateCameraConstraintsNext = false;
return;
});
return;
}
function toggleAudioUser(ele){
ele.classList.toggle('highlight');
toggle(getById('popupSelector_constraints_audio'),false,false);
getById('popupSelector_constraints_loading').style.visibility='visible';
getById('popupSelector_constraints_video').style.display = "none";
getById('popupSelector_user_settings').style.display = "none";
}
function toggleVideoUser(ele){
ele.classList.toggle('highlight');
toggle(getById('popupSelector_constraints_video'),false,false);
getById('popupSelector_constraints_loading').style.visibility='visible';
getById('popupSelector_user_settings').style.display = "none";
getById('popupSelector_constraints_audio').style.display = "none";
}
function toggleUserUser(ele){
ele.classList.toggle('highlight');
toggle(getById('popupSelector_user_settings'),false,false);
getById('popupSelector_user_settings').style.visibility='visible';
getById('popupSelector_constraints_video').style.display = "none";
getById('popupSelector_constraints_audio').style.display = "none";
}
function setupWebcamSelection(miconly=false) {
log("setupWebcamSelection();");
checkBasicStreamsExist();
try {
return enumerateDevices().then(function(dInfo){return gotDevices(dInfo, miconly)}).then(function() {
if (getById("webcamquality").elements && parseInt(getById("webcamquality").elements.namedItem("resolution").value) == 3) { // this is junk??
if (session.maxframeRate===false){
session.maxframeRate = 30;
session.maxframeRate_q2 = true;
}
} else if (session.maxframeRate_q2){
session.maxframeRate = false;
session.maxframeRate_q2 = false;
}
var audioSelect = getById('audioSource');
var videoSelect = getById('videoSourceSelect');
var outputSelect = getById('outputSource');
audioSelect.onchange = function() {
if (document.getElementById("gowebcam")) {
document.getElementById("gowebcam").disabled = true;
document.getElementById("gowebcam").dataset.audioready = "false";
document.getElementById("gowebcam").style.backgroundColor = "#DDDDDD";
document.getElementById("gowebcam").style.fontWeight = "normal";
document.getElementById("gowebcam").innerHTML = "Waiting for mic to load";
miniTranslate(document.getElementById("gowebcam"), "waiting-for-mic-to-load");
}
activatedPreview = false;
grabAudio();
};
videoSelect.onchange = function() {
if (document.getElementById("gowebcam")) {
document.getElementById("gowebcam").disabled = true;
document.getElementById("gowebcam").dataset.ready = "false";
document.getElementById("gowebcam").style.backgroundColor = "#DDDDDD";
document.getElementById("gowebcam").style.fontWeight = "normal";
document.getElementById("gowebcam").innerHTML = "Waiting for Camera to load";
miniTranslate(document.getElementById("gowebcam"), "waiting-for-camera-to-load");
}
warnlog("video source changed");
activatedPreview = false;
if (session.quality !== false) {
grabVideo(session.quality);
} else {
session.quality_wb = parseInt(getById("webcamquality").elements.namedItem("resolution").value);
grabVideo(session.quality_wb);
}
};
if (Firefox && !session.mobile){
outputSelect.onclick = function() {
log("on click");
if (outputSelect.options[outputSelect.selectedIndex].value === "others"){
navigator.mediaDevices.selectAudioOutput().then((device) => {
if (device.kind == "audiooutput"){
session.sink = device.deviceId;
try {
var matched = false;
outputSelect.childNodes.forEach(ele =>{
if (ele.value === device.deviceId){
matched = true;
ele.selected = true;
}
})
if (!matched){
var option = document.createElement('option');
option.value = device.deviceId;
option.text = device.label;
outputSelect.appendChild(option);
option.selected = true;
}
saveSettings(); // we're saving because there was an explicit action to change devices
} catch(e){errorlog(e);}
if (!session.sink){return;} // Not sure this would ever happen, but whatever.
resetupAudioOut(); // we'll probalby use session.sink, since outputSelect3 doesn't exist.
}
});
}
}
}
outputSelect.onchange = function() {
if ((iOS) || (iPad)) {
return;
}
if (Firefox && !session.mobile){
if (outputSelect.options[outputSelect.selectedIndex].value === "others"){
return;
}
}
try{
session.sink = outputSelect.options[outputSelect.selectedIndex].value;
saveSettings(); // we're saving because there was an explicit action to change devices
} catch(e){errorlog(e);}
if (!session.sink){return;} // Not sure this would ever happen, but whatever.
resetupAudioOut(); // we'll probalby use session.sink, since outputSelect3 doesn't exist.
}
getById("webcamquality").onchange = function() {
if (document.getElementById("gowebcam")) {
document.getElementById("gowebcam").disabled = true;
document.getElementById("gowebcam").dataset.ready = "false";
document.getElementById("gowebcam").style.backgroundColor = "#DDDDDD";
document.getElementById("gowebcam").style.fontWeight = "normal";
document.getElementById("gowebcam").innerHTML = "Waiting for Camera to load";
miniTranslate(document.getElementById("gowebcam"), "waiting-for-camera-to-load");
}
if (parseInt(getById("webcamquality").elements.namedItem("resolution").value) == 2) {
if (session.maxframeRate===false){
session.maxframeRate = 30;
session.maxframeRate_q2 = true;
}
} else if (session.maxframeRate_q2){
session.maxframeRate = false;
session.maxframeRate_q2 = false;
}
activatedPreview = false;
session.quality_wb = parseInt(getById("webcamquality").elements.namedItem("resolution").value);
grabVideo(session.quality_wb);
};
if (session.safemode){
if (document.getElementById("gowebcam")){
document.getElementById("gowebcam").disabled = false;
document.getElementById("gowebcam").innerHTML = miscTranslations["start"];
document.getElementById("gowebcam").dataset.audioready = "true";
document.getElementById("gowebcam").dataset.ready = "true";
setTimeout(function(){updateForceRotate();},1000);
if (session.autostart) {
publishWebcam(); // no need to mirror as there is no video...
}
return;
}
}
if (session.audioDevice!==0){ // change from Auto to Selected Audio Device
log("SETTING AUDIO DEVICE!!");
activatedPreview = false;
grabAudio();
} else if (document.getElementById("gowebcam")){
document.getElementById("gowebcam").dataset.audioready = "true";
}
if ((session.videoDevice === 0) || miconly) {
if (session.autostart) {
publishWebcam(); // no need to mirror as there is no video...
return;
} else {
if (document.getElementById("gowebcam")) {
document.getElementById("gowebcam").dataset.ready = "true";
if (document.getElementById("gowebcam").dataset.audioready == "true"){
document.getElementById("gowebcam").disabled = false;
document.getElementById("gowebcam").innerHTML = miscTranslations["start"];
}
}
}
} else {
log("GRabbing video: " + session.quality);
activatedPreview = false;
if (session.quality !== false) {
grabVideo(session.quality);
} else {
session.quality_wb = parseInt(getById("webcamquality").elements.namedItem("resolution").value);
grabVideo(session.quality_wb);
}
}
if (!(iOS || iPad || session.mobile)) {
try {
if (outputSelect.selectedIndex >= 0) {
session.sink = outputSelect.options[outputSelect.selectedIndex].value;
saveSettings();
if (session.videoElement && !session.videoElement.paused){
resetupAudioOut();
}
}
} catch(e){errorlog(e);}
}
}).catch(e => {
errorlog(e);
});
} catch (e) {
errorlog(e);
}
}
Promise.wait = function(ms) {
return new Promise(function(resolve) {
setTimeout(resolve, ms);
});
};
Promise.prototype.timeout = function(ms) {
return Promise.race([
this, Promise.wait(ms).then(function() {
if (iOS || iPad){
var errormsg = new Error("Time Out\nDid you accept camera permissions in time? Please do so first.\n\nIf using an iPhone or iPad, try fully closing your browser and open it again; Safari sometimes jams up the camera.");
errormsg.name = "timedOut";
errormsg.message = "Time Out\nDid you accept camera permissions in time? Please do so first.\n\nIf using an iPhone or iPad, try fully closing your browser and open it again; Safari sometimes jams up the camera."
throw errormsg;
} else if (session.mobile){
var errormsg = new Error("Time Out\nDid you accept camera permissions in time? Please do so first.\n\nMake sure no other application is using the camera already and that you are using a compatible browser. If issues persist, maybe try the official native mobile app.");
errormsg.name = "timedOut";
errormsg.message = "Time Out\nDid you accept camera permissions in time? Please do so first.\n\nMake sure no other application is using the camera already and that you are using a compatible browser. If issues persist, maybe try the official native mobile app."
throw errormsg;
} else {
var errormsg = new Error("Time Out\nDid you accept camera permissions in time? Please do so first.\n\nOtherwise, do you have NDI Tools installed? Maybe try uninstalling it.\n\nPlease also ensure your camera and audio device are correctly connected and not already in use. You may also need to refresh the page.");
errormsg.name = "timedOut";
errormsg.message = "Time Out\nDid you accept camera permissions in time? Please do so first.\n\nOtherwise, do you have NDI Tools installed? Maybe try uninstalling it.\n\nPlease also ensure your camera and audio device are correctly connected and not already in use. You may also need to refresh the page."
throw errormsg;
}
})
])
};
async function shareWebsite(autostart=false, evt=false){
if (session.iframeSrc){
if (!session.cleanOutput){
getById("websitesharebutton2").classList.remove('hidden');
}
if (evt && (evt.ctrlKey || evt.metaKey)){
if (getById("websitesharebutton2").classList.contains("green")){
var actionMsg = {};
actionMsg.infocus = false;
for (var UUID in session.pcs){
if (session.pcs[UUID].allowIframe===true){
session.sendMessage(actionMsg, UUID);
}
}
getById("websitesharebutton2").classList.remove("green");
getById("websitesharebutton2").ariaPressed = "false";
getById("websitesharebutton2").title = "Hold CTRL (or CMD) and click to spotlight this shared website"
} else {
if (session.streamID){
var actionMsg = {};
actionMsg.infocus = session.streamID;
for (var UUID in session.pcs){
if (session.pcs[UUID].allowIframe===true){
session.sendMessage(actionMsg, UUID);
}
}
getById("websitesharebutton2").classList.add("green");
getById("websitesharebutton2").ariaPressed = "true";
getById("websitesharebutton2").title = "Video is currently spotlighted";
}
}
return;
}
getById("websitesharebutton2").classList.remove("green");
getById("websitesharebutton2").ariaPressed = "false";
session.iframeSrc = false;
if (session.director){
clearDirectorSettings();
//setStorage("directorWebsiteShare", {"website":session.iframeSrc, "roomid":session.roomid});
} else if (document.getElementById("container_iframe") || session.iframeEle){
if (session.iframeEle){
session.iframeEle.remove();
session.iframeEle = false;
}
if (document.getElementById("container_iframe")){
document.getElementById("container_iframe").remove();
}
updateMixer();
}
getById("websitesharebutton2").classList.add("hidden");
getById("websitesharebutton").classList.remove("hidden");
var data = {};
data.iframeSrc = false;
for (var UUID in session.pcs){
if (session.pcs[UUID].allowIframe===true){
session.sendMessage(data, UUID);
}
}
getById("websitesharebutton2").title = "Hold CTRL (or CMD) and click to spotlight this shared website"
return
}
getById("websitesharebutton2").classList.remove("green");
getById("websitesharebutton2").ariaPressed = "false";
if (autostart===false){
window.focus();
var iframeURL = await promptAlt(miscTranslations["enter-website"], false, false, session.defaultIframeSrc);
} else {
var iframeURL = autostart;
}
if (!iframeURL){
return;
}
if (iframeURL == session.iframeSrc){return;}
session.defaultIframeSrc = iframeURL;
warnlog(iframeURL);
session.iframeSrc = parseURL4Iframe(iframeURL);
if (session.director && !autostart){
setStorage("directorWebsiteShare", {"website":session.iframeSrc, "roomid":session.roomid});
} else if (session.iframeEle){
session.iframeEle.src = session.iframeSrc;
if (session.iframeSrc.startsWith("https://www.youtube.com/")){ // special handler.
setTimeout(function(iframe_id){ YoutubeListen(iframe_id);}, 1000, iframe.id);
}
} else {
var iframe = document.createElement("iframe");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;midi;";
iframe.src = session.iframeSrc;
iframe.id = "iframe_source";
iframe.loadedYoutubeListen = false;
session.iframeEle = iframe;
var container = document.createElement("div");
iframe.container = container;
container.id = "container_iframe";
if (session.iframeSrc.startsWith("https://www.youtube.com/")){ // special handler.
setTimeout(function(iframe_id){ YoutubeListen(iframe_id);}, 1000, iframe.id);
}
updateMixer();
}
getById("websitesharebutton2").classList.remove("hidden");
getById("websitesharebutton").classList.add("hidden");
var data = {};
data.iframeSrc = session.iframeSrc;
for (var UUID in session.pcs){
if (session.pcs[UUID].allowIframe===true){
session.sendMessage(data, UUID);
}
}
}
function screenshareTypeDecider(sstype=1){
if (session.screenshareType){
sstype = session.screenshareType;
}
if (sstype==1){
toggleScreenShare();
} else if (sstype==2){
createIframePopup();
} else if (sstype==3){
createSecondStream();
}
}
function createScreenShareURL(transparent=true){ // iframe.src =
if (session.screenShareElement) {
var iFrameID = session.streamID.substring(0, 12) + "_" + session.generateStreamID(5);
} else if (session.screenshareid) {
var iFrameID = session.screenshareid;
} else {
var iFrameID = session.streamID.substring(0, 12) + "_" + session.generateStreamID(5);
}
if (session.exclude) {
session.exclude.push(iFrameID);
} else {
session.exclude = [];
session.exclude.push(iFrameID);
}
var extras = "";
if (session.password){
extras += "&password=" + session.password; // encodeURIComponent(
}
if (session.privacy){
extras += "&privacy";
}
if (session.meshcast){
extras += "&meshcast";
}
if (session.token){
extras+="&token="+session.token;
}
if (session.remote){
if (session.remote===true){
extras += "&remote";
} else {
extras += "&remote="+session.remote;
}
}
if (session.salt !== location.hostname){ // this is default.
extras += "&salt="+session.salt;
}
if (session.meshcastBitrate){
extras += "&mcb="+session.meshcastBitrate;
}
if (session.meshcastScreenShareBitrate){
extras += "&mcssbitrate="+session.meshcastScreenShareBitrate;
}
if (!session.notifyScreenShare){
extras += "&smallshare";
}
if (session.screenshareContentHint){
extras += "&sshint="+session.screenshareContentHint;
} else if (session.contentHint){
extras += "&sshint="+session.contentHint;
}
if (session.audioContentHint){
extras += "&audiohint="+session.audioContentHint;
}
if (session.meshcastScreenShareCodec){
extras += "&mccodec="+session.meshcastScreenShareCodec;
} else if (session.meshcastCodec){
extras += "&mccodec="+session.meshcastCodec;
}
if (session.screensharequality!==false){
extras += "&q="+session.screensharequality; // &quality works here, since only thing we are doing
} else if (session.quality!==false){
extras += "&q="+session.quality;
} else if (session.quality_ss!==false){
extras += "&q="+session.quality_ss;
} else {
extras += "&q=0";
}
if (session.screenShareLabel!==false){
if (session.screenShareLabel){
extras += "&label="+encodeURIComponent(session.screenShareLabel);
} else if (session.label){
extras += "&label="+encodeURIComponent(session.label);
}
}
if (session.screensharefps!==false){
extras += "&maxframeRate="+parseInt(session.screensharefps*100)/100.0;
}
if (session.screenshareAEC!==false){
extras += "&aec=1";
}
if (session.screenshareDenoise!==false){
extras += "&denoise=1";
}
if (session.screenshareAutogain!==false){
extras += "&autogain=1";
}
if (session.screenshareStereo!==false){
extras += "&stereo="+session.screenshareStereo;
}
if (session.forceAspectRatio && (session.forceScreenShareAspectRatio===null)){
extras += "&ar="+session.forceAspectRatio;
} else if (session.forceScreenShareAspectRatio){
extras += "&ar="+session.forceScreenShareAspectRatio;
}
if (session.muted){
extras += "&muted";
}
if (session.recordLocal){
extras += "&record="+session.recordLocal;
}
if (session.autorecordlocal){
extras += "&autorecordlocal";
}
if (transparent){
extras += "&transparent&cleanish";
}
// manual, since I don't want to use the auto-mixer.
return "?manual&audiodevice=1&screenshare&cb=0&nvb&nsb&autostart&view&room=" + session.roomid + "&push=" + iFrameID + extras;
}
function createIframePopup() {
if (session.screenShareElement) {
postMessageIframe(session.screenShareElement, {"close": true});
session.screenShareElement.parentNode.removeChild(session.screenShareElement);
session.screenShareElement = false;
updateMixer();
getById("screenshare2button").classList.remove("green");
getById("screenshare2button").ariaPressed = "false";
return;
}
if ((session.queue && !session.transferred) || (session.screenShareState && !session.queue && session.transferred)){ // if (session.queue || session.transferred){
//getById("screenshare2button").classList.add("hidden");
//getById("screensharebutton").classList.remove("hidden");
toggleScreenShare();
return;
} // can't secondary-screen share if in a queue.
var iframe = document.createElement("iframe");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;midi;";
iframe.src = "./"+createScreenShareURL();
iframe.style.width = "100%";
iframe.style.height = "100%";
iframe.style.overflow = "hidden";
iframe.id = "screensharesource";
iframe.dataset.sid = "#screensharesource";
iframe.style.zIndex = "0";
session.screenShareElement = iframe;
session.screenShareElement.dataset.doNotMove = true;
document.getElementById("main").appendChild(iframe);
if (session.screenShareElementHidden){
session.screenShareElement.style.display = "none";
}
updateMixer();
getById("screenshare2button").classList.add("green");
getById("screenshare2button").ariaPressed = "true";
return; // ignore the rest.
}
function previewWebcam(miconly=false) {
if (session.taintedSession === null) {
log("STILL WAITING ON HASH TO VALIDATE");
setTimeout(function(miconly) {
previewWebcam(miconly);
}, 1000, miconly);
return;
} else if (session.taintedSession === true) {
warnlog("HASH FAILED; PASSWORD NOT VALID");
return;
} else {
log("NOT TAINTED");
}
if (activatedPreview == true) {
log("activeated preview return 1");
return;
}
activatedPreview = true;
if (miconly){ // this just shares the preview section with the mic-only and video+mic modes
if (!getById("add_camera_inner").cloned){
getById("add_camera_inner").cloned = true;
insertAfter(getById("add_camera_inner"),getById("add_microphone"));
document.getElementById('videoSourceSelect').innerHTML = "";
}
} else if (getById("add_camera_inner").cloned){
getById("add_camera_inner").cloned = false;
insertAfter(getById("add_camera_inner"),getById("add_camera"));
document.getElementById('videoSourceSelect').innerHTML = "";
}
if (session.audioDevice === 0) { // OFF
var constraint = {
audio: false
};
} else if ((session.echoCancellation !== false) && (session.autoGainControl !== false) && (session.noiseSuppression !== false)) { // AUTO
var constraint = {
audio: true
};
} else { // Disable Echo Cancellation and stuff for the PREVIEW (DEFAULT CAM/MIC)
var constraint = {
audio: {}
};
if (session.echoCancellation !== false) { // if not disabled, we assume it's on
constraint.audio.echoCancellation = true;
} else {
constraint.audio.echoCancellation = false;
if (!session.cleanoutput){
getById("headphoneTip1").classList.remove("hidden");
getById("headphoneTipContext1").innerHTML = miscTranslations["headphones-tip"];
}
}
if (session.autoGainControl !== false) {
constraint.audio.autoGainControl = true;
} else {
constraint.audio.autoGainControl = false;
}
if (session.noiseSuppression !== false) {
constraint.audio.noiseSuppression = true;
} else {
constraint.audio.noiseSuppression = false;
}
}
if ((session.videoDevice === 0) || miconly) {
constraint.video = false;
} else {
constraint.video = true;
}
window.onorientationchange = function(){setTimeout(async function(){
if (session.forceAspectRatio){
await updateCameraConstraints("aspectRatio", session.forceAspectRatio);
}
if (session.effect && (session.effect === "7")){digitalZoom(true);}
updateForceRotate();
}, 200);};
if ((constraint.video === false) && (constraint.audio === false)){
if (session.autostart) {
publishWebcam(false, miconly); // no need to mirror as there is no video...
return;
} else {
getById("getPermissions").style.display = "none";
if (document.getElementById("gowebcam")) {
document.getElementById("gowebcam").dataset.ready = "true";
document.getElementById("gowebcam").dataset.audioready = "true";
document.getElementById("gowebcam").disabled = false;
document.getElementById("gowebcam").innerHTML = miscTranslations["start"];
}
}
return;
}
enumerateDevices().then(function(devices) {
log("enumeratated");
log(devices);
var vtrue = false;
var atrue = false;
devices.forEach(function(device) {
if (device.kind === 'audioinput') {
atrue = true;
} else if (device.kind === 'videoinput') {
vtrue = true;
}
});
if (atrue === false) {
constraint.audio = false;
}
if (vtrue === false) {
constraint.video = false;
}
setTimeout(function(constraint, miconly) {
requestBasicPermissions(constraint, setupWebcamSelection, miconly);
}, 0, constraint, miconly);
}).catch((error) => {
log("enumeratated failed. Seeking permissions.");
setTimeout(function(constraint, miconly) {
requestBasicPermissions(constraint, setupWebcamSelection, miconly);
}, 0, constraint, miconly);
});
}
async function requestBasicPermissions(constraint = {video: true, audio: true}, callback=setupWebcamSelection, miconly=false) {
if (session.taintedSession === null) {
log("STILL WAITING ON HASH TO VALIDATE");
setTimeout(function(constraint) {
requestBasicPermissions(constraint, callback, miconly);
}, 1000, constraint);
return null;
} else if (session.taintedSession === true) {
warnlog("HASH FAILED; PASSWORD NOT VALID");
return false;
} else {
log("NOT TAINTED 1");
}
setTimeout(function() {
getById("getPermissions").style.display = "none";
getById("gowebcam").style.display = "";
}, 0);
log("REQUESTING BASIC PERMISSIONS");
try {
var timerBasicCheck = null;
if (!(session.cleanOutput)) {
log("Setting Timer for getUserMedia");
timerBasicCheck = setTimeout(function() {
if (!(session.cleanOutput)) {
if (session.mobile){
warnUser("Notice: Camera timed out\n\nDid you accept the camera permissions?\n\nThis error may also appear if you are in a phone call or another app is already using the camera or microphone.");
} else {
warnUser("Camera Access Request Timed Out\nDid you accept camera permissions? Please do so first.\n\nOtherwise, do you have NDI Tools installed? Maybe try uninstalling NDI tools.\n\nPlease also ensure that your camera and audio devices are correctly connected and not already in use. You may also need to refresh the page.");
}
}
}, 10000);
}
if (session.audioInputChannels) {
if (constraint.audio === true) {
constraint.audio = {};
constraint.audio.channelCount = session.audioInputChannels;
} else if (constraint.audio) {
constraint.audio.channelCount = session.audioInputChannels;
}
}
if (session.micSampleRate){
if (constraint.audio === true) {
constraint.audio = {};
constraint.audio.sampleRate = parseInt(session.micSampleRate);
} else if (constraint.audio) {
constraint.audio.sampleRate = parseInt(session.micSampleRate);
}
}
if (session.safemode){
if (constraint.video){
constraint.video = true;
}
if (constraint.audio){
constraint.audio = true;
}
}
getUserMediaRequestID +=1 ;
var gumID = getUserMediaRequestID;
log("CONSTRAINT");
log(constraint);
navigator.mediaDevices.getUserMedia(constraint).then(function(stream) { // Apple needs thi to happen before I can access EnumerateDevices.
log("got first stream");
clearTimeout(timerBasicCheck);
if (getUserMediaRequestID !== gumID) {
warnlog("GET USER MEDIA CALL HAS EXPIRED 3");
stream.getTracks().forEach(function(track) {
stream.removeTrack(track);
track.stop();
log("stopping old track");
});
return;
}
closeModal();
log(stream.getTracks());
session.streamSrc=stream;
checkBasicStreamsExist();
updateRenderOutpipe();
if (callback){
callback(miconly);
}
}).catch(function(err) {
clearTimeout(timerBasicCheck);
warnlog("some error with GetUSERMEDIA");
console.warn(err); /* handle the error */
if (err.name == "NotFoundError" || err.name == "DevicesNotFoundError") {
//required track is missing
} else if (err.name == "NotReadableError" || err.name == "TrackStartError") {
//webcam or mic are already in use
} else if (err.name == "OverconstrainedError" || err.name == "ConstraintNotSatisfiedError") {
//constraints can not be satisfied by avb. devices
} else if (err.name == "NotAllowedError" || err.name == "PermissionDeniedError") {
//permission denied in browser
if (!(session.cleanOutput)) {
setTimeout(function() {
if (window.obsstudio){
warnUser("Permissions denied.\n\nTo access the camera or microphone from within OBS, please refer to:\ndocs.vdo.ninja/guides/share-webcam-from-inside-obs.", false, false);
} else if (ChromeVersion && !session.mobile){
warnUser("
Camera/mic permissions denied
\nPlease ensure you have allowed the mic/camera permissions in your browser, such as like:\n\n\n\nFor further help on how to resolve this issue, please refer to:\n\nhttps://docs.vdo.ninja/common-errors-and-known-issues/enable-camera-microphone-permissions.", false, false);
} else {
warnUser("Permission access to the camera or microphone was denied.\n\nPlease ensure you have allowed the mic/camera permissions in your browser.\n\nFor guides on how to resolve this issue, please refer to:\n\nhttps://docs.vdo.ninja/common-errors-and-known-issues/enable-camera-microphone-permissions.", false, false);
}
}, 1);
}
return;
} else if (err.name == "TypeError" || err.name == "TypeError") {
//empty constraints object
} else {
//permission denied in browser
if (!(session.cleanOutput)) {
setTimeout(function() {
warnUser(err);
}, 1);
}
}
warnlog("trying to list webcam again");
if (callback){
callback(miconly);
}
});
} catch (e) {
console.warn(e);
if (!(session.cleanOutput)) {
if (window.isSecureContext) {
warnUser("An error has occured when trying to access the webcam or microphone. The reason is not known.");
} else if ((iOS) || (iPad)) {
warnUser("iOS version 13.4 and up is generally recommended; older than iOS 11 is not supported.");
} else {
warnUser("Error acessing camera or microphone.\n\nThe website may be loaded in an insecure context.\n\nPlease see: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia");
}
}
}
return null
}
function copyFunction(copyText, evt = false) {
if (evt){
if ("buttons" in evt) {
if (evt.buttons !== 0){return;}
} else if ("which" in evt){
if (evt.which !== 0){return;}
}
popupMessage(evt);
evt.preventDefault();
evt.stopPropagation();
}
try {
copyText.select();
copyText.setSelectionRange(0, 99999);
document.execCommand("copy");
} catch (e) {
var dummy = document.createElement('input');
document.body.appendChild(dummy);
dummy.value = copyText;
dummy.select();
document.execCommand('copy');
document.body.removeChild(dummy);
}
return false;
}
function generateQRPage() {
var pass = sanitizePassword(getById("invite_password").value);
if (pass.length) {
return generateHash(pass + session.salt, 4).then(function(hash) {
generateQRPageCallback(hash);
}).catch(errorlog);
} else {
generateQRPageCallback("");
}
}
async function updateLinkWelcome(arg, input) {
if (input.checked){
var response = await promptAlt("Enter the message you'd like the guests to see");
response = encodeURIComponent(response);
var param = input.dataset.param.split("=")[0];
input.dataset.param = param + "=" + response;
}
updateLink(arg, input);
}
function updateLinkWebP(arg, input) {
if (input.checked){
if (!((getById("director_block_" + arg).dataset.raw.includes("&broadcast")) || (getById("director_block_" + arg).dataset.raw.includes("?broadcast")))){
getById("broadcastSlider").checked=true;
updateLink(arg, getById("broadcastSlider"));
}
}
updateLink(arg, input);
}
var soloLinkAppended = "";
function updateLink(arg, input, solo=false) {
log("updateLink");
log(input.dataset.param);
if (input.checked) {
getById("director_block_" + arg).dataset.raw += input.dataset.param;
if (solo){
soloLinkAppended += input.dataset.param;
}
var string = getById("director_block_" + arg).dataset.raw;
if ((arg==1) && (getById("obfuscate_director_" + arg).checked)) {
string = obfuscateURL(string);
}
getById("director_block_" + arg).href = string;
getById("director_block_" + arg).innerText = string;
} else {
var string = getById("director_block_" + arg).dataset.raw + "&";
string = string.replace(input.dataset.param + "&", "&");
string = string.substring(0, string.length - 1);
getById("director_block_" + arg).dataset.raw = string;
if (solo){
soloLinkAppended += "&";
soloLinkAppended = soloLinkAppended.replace(input.dataset.param + "&", "&");
soloLinkAppended = soloLinkAppended.substring(0, soloLinkAppended.length - 1);
}
if ((arg==1) && (getById("obfuscate_director_" + arg).checked)) {
string = obfuscateURL(string);
}
// document.querySelector("soloLink")
// soloLink
getById("director_block_" + arg).href = string;
getById("director_block_" + arg).innerText = string;
}
if (solo){
document.querySelectorAll("a.soloLink").forEach(ele=>{
try {
var href = ele.getAttribute("value") + soloLinkAppended;
ele.href = href;
ele.innerHTML = href;
} catch(e){
errorlog(e);
}
});
}
saveDirectorSettings();
}
function changeURL(changeURL){
window.focus();
if (session.consent){
hangup();
window.location.href = changeURL;
} else {
confirmAlt(miscTranslations["director-redirect-1"]+changeURL+miscTranslations["director-redirect-2"]).then(res=>{
if (res){
hangup();
window.location.href = changeURL;
};
});
}
}
function updateLinkInverse(arg, input) {
log("updateLinkInverse");
log(input.dataset.param);
if (!(input.checked)) {
getById("director_block_" + arg).dataset.raw += input.dataset.param;
var string = getById("director_block_" + arg).dataset.raw;
if ((arg==1) && (getById("obfuscate_director_" + arg).checked)) {
string = obfuscateURL(string);
}
getById("director_block_" + arg).href = string;
getById("director_block_" + arg).innerText = string;
} else {
var string = getById("director_block_" + arg).dataset.raw + "&";
string = string.replace(input.dataset.param + "&", "&");
string = string.substring(0, string.length - 1);
getById("director_block_" + arg).dataset.raw = string;
if ((arg==1) && (getById("obfuscate_director_" + arg).checked)) {
string = obfuscateURL(string);
}
getById("director_block_" + arg).href = string;
getById("director_block_" + arg).innerText = string;
}
}
function updateLinkScene(arg, input) {
log("updateLinkScene");
var string = getById("director_block_" + arg).dataset.raw;
if (input.checked) {
string = changeParam(string, "scene", "0");
} else {
string = changeParam(string, "scene", "1");
}
getById("director_block_" + arg).dataset.raw = string;
if ((arg==1) && (getById("obfuscate_director_" + arg).checked)) {
string = obfuscateURL(string);
}
getById("director_block_" + arg).href = string;
getById("director_block_" + arg).innerText = string;
}
function fullscreenPageToggle(state=null){
try {
if (!document.fullscreenElement) { // not currently full screen
if (state!==false){ // if state is false, we are already not full screen
if (document.documentElement.requestFullscreen){
document.documentElement.requestFullscreen();
} else if (document.documentElement.webkitRequestFullscreen){
document.documentElement.webkitRequestFullscreen();
}
}
} else if (document.exitFullscreen) {
if (!state){ // if toggle mode or state=false
document.exitFullscreen();
}
} else if (document.webkitExitFullscreen) {
if (!state){ // if toggle mode or state=false
document.webkitExitFullscreen();
}
}
//updateMixer(); // we will do this on the event for this instead
} catch(e){errorlog(e);}
}
function resetGen() {
getById("gencontent").style.display = "block";
getById("gencontent2").style.display = "none";
getById("gencontent2").className = ""; //container-inner
getById("gencontent").className = "container-inner"; //
getById("gencontent2").innerHTML = "";
getById("videoname4").focus();
}
function generateQRPageCallback(hash) {
try {
var title = getById("videoname4").value;
if (title.length) {
title = title.replace(/[\W]+/g, "_").replace(/_+/g, '_'); // but not what others might get. TODO: allow for non-alphanumeric characters; santitize, then URL encode instead,
title = "&label=" + title;
}
var sid = session.generateStreamID();
var viewstr = "";
var sendstr = "";
if (getById("invite_bitrate").checked) {
viewstr += "&bitrate=20000";
}
if (getById("invite_vp9").checked) {
viewstr += "&codec=vp9";
}
if (getById("invite_stereo").checked) {
viewstr += "&stereo";
sendstr += "&stereo";
}
if (getById("invite_automic").checked) {
sendstr += "&audiodevice=1";
}
if (getById("invite_automic").checked) {
sendstr += "&audiodevice=1";
}
if (getById("invite_effects").checked) {
sendstr += "&effects";
}
if (getById("invite_remotecontrol").checked) { //
var remote_gen_id = session.generateStreamID();
sendstr += "&remote=" + remote_gen_id; // security
viewstr += "&remote=" + remote_gen_id;
}
if (getById("invite_joinroom").value.trim().length) {
sendstr += "&ssid&room=" + getById("invite_joinroom").value.trim();
viewstr += "&solo&room=" + getById("invite_joinroom").value.trim();
}
if (getById("invite_password").value.trim().length) {
sendstr += "&hash=" + hash;
viewstr += "&password=" + sanitizePassword(getById("invite_password").value.trim());
}
if (session.token){
sendstr += "&token=" + session.token;
viewstr += "&token=" + session.token;
}
if (getById("invite_group_chat_type").value) { // 0 is default
if (getById("invite_group_chat_type").value == 1) { // no video
sendstr += "&novideo";
} else if (getById("invite_group_chat_type").value == 2) { // no view or audio
sendstr += "&view";
}
}
if (getById("invite_quality").value) {
if (getById("invite_quality").value == 0) {
sendstr += "&quality=0";
} else if (getById("invite_quality").value == 1) {
sendstr += "&quality=1";
} else if (getById("invite_quality").value == 2) {
sendstr += "&quality=2";
}
}
var wss = "";
if (session.wssSetViaUrl){
if (session.customWSS && (session.customWSS!==true)){
wss = "&pie="+session.customWSS;
} else {
wss = "&wss="+session.wss;
}
}
var hoststr = "";
if (getById("invite_hostlink").checked) {
hoststr = 'https://' + location.host + location.pathname + '?push=' + sid + "_hostlink" + "&view="+ sid + sendstr + "&bitrate=500" + title + wss;
sendstr = 'https://' + location.host + location.pathname + '?push=' + sid + "&view="+sid + "_hostlink" + sendstr + "&bitrate=1200" + title + wss;
} else {
sendstr = 'https://' + location.host + location.pathname + '?push=' + sid + sendstr + title + wss;
}
if (getById("invite_obfuscate").checked) {
sendstr = obfuscateURL(sendstr);
}
viewstr = 'https://' + location.host + location.pathname + '?view=' + sid + viewstr + title + wss;
getById("gencontent").style.display = "none";
getById("gencontent").className = ""; //
getById("gencontent2").style.display = "block";
getById("gencontent2").className = "container-inner";
getById("gencontent2").innerHTML = '
This invite link and OBS ingestion link are reusable.
\
Only one person may use a specific invite at a time.
\
The stream ID can be changed manually to something else; keep it unique and alphanumeric.
\
Nothing is stored server-side; links do not expire, nor is there anything to delete.
\
\
';
var qrcode = new QRCode(getById("qrcode"), {
width: 300
, height: 300
, colorDark: "#000000"
, colorLight: "#FFFFFF"
, useSVG: false
});
qrcode.makeCode(sendstr);
getById("qrcode").title = "";
setTimeout(function() {
getById("qrcode").title = "";
if (getById("qrcode").getElementsByTagName('img').length) {
getById("qrcode").getElementsByTagName('img')[0].style.cursor = "none";
getById("qrcode").getElementsByTagName('img')[0].style.margin = "0 auto";
}
}, 100); // i really hate the title overlay that the qrcode function makes
} catch (e) {
errorlog(e);
}
}
function initSceneList(UUID){
Object.keys(session.sceneList).forEach((scene, index) => {
if (getById("container_" + UUID).querySelectorAll('[data-scene="'+scene+'"]').length){return;} // already exists.
var newScene = document.createElement("div");
newScene.innerHTML = '';
newScene.classList.add("customScene");
var added = false;
getById("container_" + UUID).querySelectorAll('.customScene>[data-scene]').forEach(ele=>{
log(ele);
if (!added && ele.dataset.scene>scene+""){
ele.parentNode.parentNode.insertBefore(newScene, ele.parentNode);
added = true;
}
});
if (!added){
getById("container_" + UUID).appendChild(newScene);
}
});
}
function updateSceneList(scene){ // custom scenes only.
if (!session.director){return;}
if (scene in session.sceneList){return;}
if ((parseInt(scene)+"")===scene){
if ((parseInt(scene)>=0) && (parseInt(scene)<=8)){
return;
}
}
session.sceneList[scene] = true;
for (var UUID in session.rpcs){
var newScene = document.createElement("div");
newScene.innerHTML = '';
newScene.classList.add("customScene");
var added = false;
getById("container_" + UUID).querySelectorAll('.customScene>[data-scene]').forEach(ele=>{
log(ele);
if (!added && ele.dataset.scene>scene+""){
ele.parentNode.parentNode.insertBefore(newScene, ele.parentNode);
added = true;
}
});
if (!added){
getById("container_" + UUID).appendChild(newScene);
}
}
if (session.showDirector){
if (document.getElementById("container_director")){
var newScene = document.createElement("div");
newScene.innerHTML = '';
newScene.classList.add("customScene");
//getById("container_director").appendChild(newScene);
var added = false;
getById("container_director").querySelectorAll('.customScene>[data-scene]').forEach(ele=>{
if (!added && ele.dataset.scene>scene+""){
ele.parentNode.parentNode.insertBefore(newScene, ele.parentNode);
added = true;
}
});
if (!added){
getById("container_director").appendChild(newScene);
}
}
}
}
var vis = (function() {
var stateKey, eventKey, keys = {
hidden: "visibilitychange"
, webkitHidden: "webkitvisibilitychange"
, mozHidden: "mozvisibilitychange"
, msHidden: "msvisibilitychange"
};
for (stateKey in keys) {
if (stateKey in document) {
eventKey = keys[stateKey];
break;
}
}
return function(c) {
if (c) {
document.addEventListener(eventKey, c);
//document.addEventListener("blur", c);
//document.addEventListener("focus", c);
}
return !document[stateKey];
};
})();
function unPauseVideo(videoEle, update=true){
try {
if (!videoEle){return;}
else if (!(videoEle.dataset.UUID in session.rpcs)){return;}
else if (!("prePausedBandwidth" in session.rpcs[videoEle.dataset.UUID])){return;} // not paused; useless to have, but might as well
session.rpcs[videoEle.dataset.UUID].manualBandwidth = false;
//session.rpcs[videoEle.dataset.UUID].manualAudioBandwidth = false;
if (session.rpcs[videoEle.dataset.UUID].videoElement){
session.rpcs[videoEle.dataset.UUID].videoElement.play();
}
delete(session.rpcs[videoEle.dataset.UUID].prePausedBandwidth);
session.requestRateLimit(false, videoEle.dataset.UUID, false); // passing a bitrate of false forces the saved existing bitrate to be requested.
videoEle.classList.remove("paused");
videoEle.classList.remove("partialFadeout");
if (update){
updateMixer();
}
}catch(e){errorlog(e);}
}
function pauseVideo(videoEle, update=true){
if (!videoEle){return;}
else if (!(videoEle.dataset.UUID in session.rpcs)){return;}
session.rpcs[videoEle.dataset.UUID].prePausedBandwidth = session.rpcs[videoEle.dataset.UUID].manualBandwidth; // useless, but whatever
session.rpcs[videoEle.dataset.UUID].manualBandwidth = 0;
if (session.rpcs[videoEle.dataset.UUID].videoElement){
session.rpcs[videoEle.dataset.UUID].videoElement.pause();
}
//session.rpcs[videoEle.dataset.UUID].manualAudioBandwidth = 0;
session.requestRateLimit(false, videoEle.dataset.UUID, true); // passing a bitrate of false forces the saved existing bitrate to be requested.
videoEle.classList.add("paused");
videoEle.classList.add("partialFadeout");
if (update){
updateMixer();
}
}
(function rightclickmenuthing() { // right click menu
"use strict";
function clickInsideElement(e, value="menu") {
var el = e.srcElement || e.target;
if (el.dataset && (value in el.dataset)) {
return el;
} else {
while (el = el.parentNode) {
if (el.dataset && (value in el.dataset)) {
return el;
}
}
}
return false;
}
function getPosition(event2) {
var posx = 0;
var posy = 0;
if (!event2){var event = window.event;}
if (event2.pageX || event2.pageY){
posx = event2.pageX;
posy = event2.pageY;
} else if (event2.clientX || event2.clientY) {
posx = event2.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
posy = event2.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
return {x: posx, y: posy};
}
var taskItemInContext;
var clickCoordsX;
var clickCoordsY;
var menu;
var menuState = 0;
var lastMenu= false;
var menuWidth;
var menuHeight;
var windowWidth;
var windowHeight;
function contextListener() {
document.addEventListener("contextmenu", function(e) {
if (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1){
if (e && (!e.ctrlKey && !e.metaKey)){return;}
} else if (e && (e.ctrlKey || e.metaKey)){return;} // allow for development ease
taskItemInContext = clickInsideElement(e, "menu");
if (taskItemInContext) {
e.preventDefault();
e.stopPropagation();
if (taskItemInContext.dataset && taskItemInContext.dataset.menu){
toggleMenuOn(taskItemInContext.dataset.menu, taskItemInContext);
} else {
toggleMenuOn();
}
positionMenu(e);
return false;
} else {
taskItemInContext = null;
toggleMenuOff();
}
});
}
function menuClickListener(e) {
var clickeElIsLink = clickInsideElement(e, "action");
if (clickeElIsLink) {
e.preventDefault();
e.stopPropagation();
menuItemListener(clickeElIsLink, false, e);
return false;
} else {
var button = e.which || e.button;
if (button === 1) {
toggleMenuOff();
}
}
}
function handleInputElement(e){ // for the input range slider version
var clickeElIsLink = clickInsideElement(e, "action");
if (clickeElIsLink) {
e.preventDefault();
e.stopPropagation();
menuItemListener(clickeElIsLink, e.srcElement, e);
return false;
} else {
var button = e.which || e.button;
if (button === 1) {
toggleMenuOff();
}
}
}
function toggleMenuOn(menutype=false, ele=false) {
if (lastMenu && (lastMenu !== menutype)){
try {
menuState = 0;
getById(lastMenu).classList.remove("context-menu--active");
document.removeEventListener("click", menuClickListener);
menu.querySelectorAll("input").forEach(ele=>{
ele.removeEventListener("input", handleInputElement);
});
} catch(e){}
}
menu = getById(menutype || "context-menu");
menuItemSyncState(menu);
if (menuState !== 1) {
menuState = 1;
menu.classList.add("context-menu--active");
document.addEventListener("click", menuClickListener);
menu.querySelectorAll("input").forEach(ele=>{
ele.addEventListener("input", handleInputElement);
});
}
if (ele && ele.classOptions){
menu.classList.add(ele.classOptions);
}
lastMenu = menutype || "context-menu";
}
function toggleMenuOff() {
if (menuState !== 0) {
menuState = 0;
menu.classList.remove("context-menu--active");
document.removeEventListener("click", menuClickListener);
menu.querySelectorAll("input").forEach(ele=>{
ele.removeEventListener("input", handleInputElement);
});
}
lastMenu = false;
}
function positionMenu(e) {
var clickCoords = getPosition(e);
clickCoordsX = clickCoords.x;
clickCoordsY = clickCoords.y;
menuWidth = menu.offsetWidth + 4;
menuHeight = menu.offsetHeight + 4;
windowWidth = window.innerWidth;
windowHeight = window.innerHeight;
if ((windowWidth - clickCoordsX) < menuWidth) {
menu.style.left = windowWidth - menuWidth + "px";
} else {
menu.style.left = clickCoordsX + "px";
}
if ((windowHeight - clickCoordsY) < menuHeight) {
menu.style.top = windowHeight - menuHeight + "px";
} else {
menu.style.top = clickCoordsY + "px";
}
}
async function menuItemListener(link, inputElement=false, e=false) {
if (link.getAttribute("data-action") === "Open") {
window.open(taskItemInContext.href);
} else if (link.getAttribute("data-action") === "Copy") {
copyFunction(taskItemInContext.href);
} else if (link.getAttribute("data-action") === "Mirror") {
if ((taskItemInContext.id == "videosource") || (taskItemInContext.id == "previewWebcam")){
session.mirrored = !session.mirrored;
applyMirror(false);
log("session.mirrored");
} else {
if ("mirror" in taskItemInContext){
taskItemInContext.mirror = !taskItemInContext.mirror;
applyMirrorGuest(taskItemInContext.mirror, taskItemInContext);
} else {
taskItemInContext.mirror = true;
applyMirrorGuest(taskItemInContext.mirror, taskItemInContext);
}
}
} else if (link.getAttribute("data-action") === "FullWindow") {
if ((taskItemInContext.id == "videosource") || (taskItemInContext.id == "previewWebcam")){
session.infocus=true;
} else {
session.infocus = taskItemInContext.dataset.UUID;
}
updateMixer();
} else if (link.getAttribute("data-action") === "ShrinkWindow") {
session.infocus=false;
updateMixer();
} else if (link.getAttribute("data-action") === "Pause") {
pauseVideo(taskItemInContext);
} else if (link.getAttribute("data-action") === "UnPause") {
unPauseVideo(taskItemInContext);
} else if (link.getAttribute("data-action") === "PiP") {
togglePictureInPicture(taskItemInContext);
} else if (link.getAttribute("data-action") === "Record") {
if (taskItemInContext.stopWriter || taskItemInContext.recording){
} else if (taskItemInContext.startWriter){
taskItemInContext.startWriter();
} else {
recordLocalVideo(null, 4000, taskItemInContext)
}
} else if (link.getAttribute("data-action") === "StopRecording") {
if (taskItemInContext.stopWriter){
taskItemInContext.stopWriter();
} else if (taskItemInContext.recording){
recordLocalVideo("stop", null, taskItemInContext);
}
} else if (link.getAttribute("data-action") === "CopyFrameAsImage") {
copyVideoFrameToClipboard(taskItemInContext, e);
} else if (link.getAttribute("data-action") === "SaveFrameToDisk") {
saveVideoFrameToClipboard(taskItemInContext, e);
} else if (link.getAttribute("data-action") === "ChangeBuffer") {
toggleBufferSettings(taskItemInContext.dataset.UUID);
} else if (link.getAttribute("data-action") === "Cast") {
//copyFunction(taskItemInContext.href);
} else if (link.getAttribute("data-action") === "Controls") {
taskItemInContext.controls = true;
} else if (link.getAttribute("data-action") === "HideControls") {
taskItemInContext.controls = false;
} else if (link.getAttribute("data-action") === "Edit") {
//copyFunction(taskItemInContext.href);
var response = await promptAlt("Please note, manual edits to the URL may conflict with the toggles", false, false, taskItemInContext.href);
if (response){
taskItemInContext.href = response;
taskItemInContext.dataset.raw = response;
taskItemInContext.innerHTML = response;
}
} else if (link.getAttribute("data-action") === "QRCode") {
warnUser("Loading QR Code");
loadQR(function tt(url){
getById("alertModalMessage").innerHTML = "";
var qrcode = new QRCode(getById("alertModalMessage"), {
width: 300
, height: 300
, colorDark: "#000000"
, colorLight: "#FFFFFF"
, useSVG: false
});
qrcode.makeCode(url);
getById("alertModalMessage").title = "";
setTimeout(function() {
getById("alertModalMessage").title = "";
if (getById("alertModalMessage").getElementsByTagName('img').length) {
getById("alertModalMessage").getElementsByTagName('img')[0].style.cursor = "none";
}
}, 100);
}, taskItemInContext.href);
} else if (link.getAttribute("data-action") === "ShowStats"){
if ((taskItemInContext.id == "videosource") || (taskItemInContext.id == "previewWebcam")){
var [menu, innerMenu] = statsMenuCreator();
menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu);
printMyStats(innerMenu);
} else if (taskItemInContext.dataset.UUID && (taskItemInContext.dataset.UUID in session.rpcs)){
var [menu, innerMenu] = statsMenuCreator();
printViewStats(innerMenu, taskItemInContext.dataset.UUID );
menu.interval = setInterval(printViewStats, session.statsInterval, innerMenu, taskItemInContext.dataset.UUID);
}
} else if (link.getAttribute("data-action") === "OutputAudio"){
enumerateDevices().then(function(deviceInfo){
var ele = getById(taskItemInContext.id);
var deviceListElement = gotDevices3(deviceInfo, ele);
if (deviceListElement){
warnUser("Select the audio playback destination for this media:\n\n");
getById("alertModalMessage").appendChild(deviceListElement);
} else {
warnUser("No output devices available");
}
});
//
} else if (link.getAttribute("data-action") === "RemoteHangup") {
if (session.rpcs[taskItemInContext.dataset.UUID] && session.rpcs[taskItemInContext.dataset.UUID].stats.info && ("remote" in session.rpcs[taskItemInContext.dataset.UUID].stats.info) && session.rpcs[taskItemInContext.dataset.UUID].stats.info.remote){
var confirmHangup = confirm(miscTranslations["confirm-disconnect-user"]);
if (confirmHangup) {
var msg = {};
msg.hangup = true;
msg.remote = session.remote;
msg = await session.encodeRemote(msg);
session.sendRequest(msg, taskItemInContext.dataset.UUID);
pokeIframeAPI("hungup", "remote", taskItemInContext.dataset.UUID);
}
}
} else if (link.getAttribute("data-action") === "RemoteReload") {
if (session.rpcs[taskItemInContext.dataset.UUID] && session.rpcs[taskItemInContext.dataset.UUID].stats.info && ("remote" in session.rpcs[taskItemInContext.dataset.UUID].stats.info) && session.rpcs[taskItemInContext.dataset.UUID].stats.info.remote){
var confirmReload = confirm(miscTranslations["confirm-reload-user"]);
if (confirmReload) {
var msg = {};
msg.reload = true;
msg.remote = session.remote;
msg = await session.encodeRemote(msg);
session.sendRequest(msg, taskItemInContext.dataset.UUID);
pokeIframeAPI("reload", "remote", taskItemInContext.dataset.UUID);
}
}
} else if (link.getAttribute("data-action") === "SSNewTab") {
var URL = "https://"+window.location.hostname+location.pathname+createScreenShareURL(false);
log(URL);
window.open(URL, '_blank').focus();
} else if (link.getAttribute("data-action") === "pip-clock") {
popOutClock(taskItemInContext.children[0]);
} else if (link.getAttribute("data-action") === "Publish") {
var URL = taskItemInContext.href;
URL+="&clean&chroma=000&ssar=landscape&nosettings&prefercurrenttab&selfbrowsersurface=include&displaysurface=browser&np&nopush&publish&whippush&whippushtoken";
var win = window.open( URL ,'targetWindow', 'toolbar=no,location=no,status=no,scaling=no,menubar=no,scrollbars=no,resizable=no,width=1280,height=720');
win.focus();
win.resizeTo(1280,720);
}
if (inputElement===false){
log("Task ID - " + taskItemInContext + ", Task action - " + link.getAttribute("data-action"));
toggleMenuOff();
}
}
function menuItemSyncState(menu) {
var items = menu.querySelectorAll("[data-action]");
for (var i=0;i -1){
items[i].parentNode.classList.add("hidden");
} else {
items[i].parentNode.classList.remove("hidden");
}
} else if (items[i].getAttribute("data-action") === "Publish") {
if (taskItemInContext.classList.contains("publish")){
items[i].parentNode.classList.remove("hidden");
} else {
items[i].parentNode.classList.add("hidden");
}
}
}
}
contextListener();
})();
function gotDevices3(deviceInfos, vid){
var audioEle = document.createElement("select");
log(deviceInfos);
if (!deviceInfos.length) {
return false;
}
for (let i = 0; i !== deviceInfos.length; ++i) {
if (deviceInfos[i].kind === 'audiooutput') {
var opt = document.createElement("option");
opt.innerText = deviceInfos[i].label;
opt.value = deviceInfos[i].deviceId;
audioEle.appendChild(opt);
audioEle.videoTarget = vid;
if (vid.sinkId){
if (vid.sinkId == deviceInfos[i].deviceId){
opt.selected = true;
}
} else if (vid.manualSink){
if (vid.manualSink == deviceInfos[i].deviceId){
opt.selected = true;
}
} else if (session.sink){
if (session.sink == deviceInfos[i].deviceId){
opt.selected = true;
}
}
}
}
audioEle.onchange = function(){
vid.manualSink = this.options[this.selectedIndex].value;
if (this.videoTarget && this.videoTarget.dataset.UUID){
session.audioEffects = true;
updateIncomingAudioElement(this.videoTarget.dataset.UUID);
}
resetupAudioOut(this.videoTarget);
}
return audioEle;
}
function popupMessage(e, message = "Copied to Clipboard") { // right click menu
var posx = 0;
var posy = 0;
if (!e) var e = window.event;
if (e.pageX || e.pageY) {
posx = e.pageX;
posy = e.pageY;
} else if (e.clientX || e.clientY) {
posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
posx += 10;
var menu = getById("messagePopup");
menu.innerHTML = "
" + message + "
";
var menuState = 0;
var menuWidth;
var menuHeight;
var menuPosition;
var menuPositionX;
var menuPositionY;
var windowWidth;
var windowHeight;
if (menuState !== 1) {
menuState = 1;
menu.classList.add("context-menu--active");
}
menuWidth = menu.offsetWidth + 4;
menuHeight = menu.offsetHeight + 4;
windowWidth = window.innerWidth;
windowHeight = window.innerHeight;
if ((windowWidth - posx) < menuWidth) {
menu.style.left = windowWidth - menuWidth + "px";
} else {
menu.style.left = posx + "px";
}
if ((windowHeight - posy) < menuHeight) {
menu.style.top = windowHeight - menuHeight + "px";
} else {
menu.style.top = posy + "px";
}
function toggleMenuOff() {
if (menuState !== 0) {
menuState = 0;
menu.classList.remove("context-menu--active");
}
}
menu.classList.remove("fadeout");
var showlength = message.length*50 || 500;
setTimeout(function() {
menu.classList.add("fadeout");
}, showlength);
setTimeout(function() {
toggleMenuOff();
}, showlength+1000);
}
function timeSince(date) {
var seconds = Math.floor((new Date() - date) / 1000);
var interval = seconds / 31536000;
if (interval > 1) {
return Math.floor(interval) + " years";
}
interval = seconds / 2592000;
if (interval > 1) {
return Math.floor(interval) + " months";
}
interval = seconds / 86400;
if (interval > 1) {
return Math.floor(interval) + " days";
}
interval = seconds / 3600;
if (interval > 1) {
return Math.floor(interval) + " hours";
}
interval = seconds / 60;
if (interval > 1) {
return Math.floor(interval) + " minutes";
}
return "Seconds ago";
}
var messageList = []
function sendChatMessage(chatMsg = false, bc = false) { // filtered + visual
var data = {};
if (chatMsg === false) {
var msg = document.getElementById('chatInput').value;
} else {
var msg = chatMsg;
}
//msg = sanitizeChat(msg);
if (msg == "") {
return false;
}
msg = convertShortcodes(msg);
var label = "";
if (session.label){
if (session.director){
label = "" + session.label + ": ";
} else {
label = "" + session.label + ": ";
}
} else if (session.director){
label = "Director: ";
}
if (msg.trim()==="/list"){
var listMsg = null;
for (var UUID in session.rpcs){
if (session.rpcs[UUID].label){
listMsg = UUID+": "+session.rpcs[UUID].label
} else if (session.directorList.indexOf(UUID)>=0){
listMsg = UUID+": Director";
} else {
listMsg = UUID+": Unknown User";
}
var data = {};
data.msg = listMsg;
data.label = false;
data.type = "alert";
data.time = Date.now();
messageList.push(data);
}
for (var UUID in session.pcs){
if (UUID in session.rpcs){continue;}
if (session.pcs[UUID].label){
listMsg = UUID+"; "+session.pcs[UUID].label
} else if (session.directorList.indexOf(UUID)>=0){
listMsg = UUID+"; Director";
} else {
listMsg = UUID+"; Unknown User";
}
var data = {};
data.msg = listMsg;
data.label = false;
data.type = "alert";
data.time = Date.now();
messageList.push(data);
}
if (listMsg===null){
data.msg = "No other users are connected to you";
data.label = false;
data.type = "alert";
data.time = Date.now();
messageList.push(data);
}
} else if (msg.startsWith("\/msg ")){
var msg = msg.split("\/msg ")[1];
msg = msg.split(" ");
uid = msg.shift().toLowerCase();
msg = msg.join(" ");
if (msg == ""){return false;}
var sent = false;
for (var UUID in session.rpcs){
if (UUID.startsWith(uid)){
sendChat(msg, UUID); // send message to peers
var data = {};
data.time = Date.now();
data.msg = sanitizeChat(msg); // this is what the other person should see
data.label = label;
data.type = "sent";
messageList.push(data);
sent=true;
} else if (session.rpcs[UUID].label && session.rpcs[UUID].label.toLowerCase().startsWith(uid)){
sendChat(msg, UUID); // send message to peers
var data = {};
data.time = Date.now();
data.msg = sanitizeChat(msg); // this is what the other person should see
data.label = label;
data.type = "sent";
messageList.push(data);
sent=true;
} else if ((session.directorList.indexOf(UUID)>=0) && "director".startsWith(uid)){
sendChat(msg, UUID); // send message to peers
var data = {};
data.time = Date.now();
data.msg = sanitizeChat(msg); // this is what the other person should see
data.label = label;
data.type = "sent";
messageList.push(data);
sent=true;
}
}
for (var UUID in session.pcs){
if (UUID in session.rpcs){continue;}
if (UUID.startsWith(uid)){
sendChat(msg, UUID); // send message to peers
var data = {};
data.time = Date.now();
data.msg = sanitizeChat(msg); // this is what the other person should see
data.label = label;
data.type = "sent";
messageList.push(data);
sent=true;
} else if (session.pcs[UUID].label && session.pcs[UUID].label.toLowerCase().startsWith(uid)){
sendChat(msg, UUID); // send message to peers
var data = {};
data.time = Date.now();
data.msg = sanitizeChat(msg); // this is what the other person should see
data.label = label;
data.type = "sent";
messageList.push(data);
sent=true;
} else if ((session.directorList.indexOf(UUID)>=0) && "director".startsWith(uid)){
sendChat(msg, UUID); // send message to peers
var data = {};
data.time = Date.now();
data.msg = sanitizeChat(msg); // this is what the other person should see
data.label = label;
data.type = "sent";
messageList.push(data);
sent=true;
}
}
if (sent == false){
var data = {};
data.msg = "No user found. Message not sent.";
data.label = false;
data.type = "alert";
data.time = Date.now();
messageList.push(data);
updateMessages();
return false;
}
} else if (msg.startsWith("\/")){
data.msg = "Unknown command. Try '/list' or '/msg username message'.";
data.label = false;
data.type = "alert";
data.time = Date.now();
messageList.push(data);
updateMessages();
return false;
} else if (session.directorChat===true){
if (session.directorList.length){
for (var i = 0;i{
ele.click();
});
}
function toggleQualityDirector(bitrate, UUID, ele) { // ele is specific to the button in the director's room
var eles = ele.parentNode.childNodes;
for (var i=0;i/g, ">").replace(/["']/g, ""); // try to sanitize things, just in case.
var punc = "";
while (url[url.length-1] === "."){
url = url.slice(0,-1);
punc += ".";
}
while (url[url.length-1] === ";"){
url = url.slice(0,-1);
punc += ";";
}
while (url[url.length-1] === ","){
url = url.slice(0,-1);
punc += ",";
}
while (url[url.length-1] === "!"){
url = url.slice(0,-1);
punc += "!";
}
while (url[url.length-1] === ":"){
url = url.slice(0,-1);
punc += ":";
}
while (url[url.length-1] === "*"){
url = url.slice(0,-1);
punc += "*";
}
while (url[url.length-1] === ")"){
url = url.slice(0,-1);
punc += ")";
}
while (url[url.length-1] === "?"){
url = url.slice(0,-1);
punc += "?";
}
var hyperlink = url;
if (!hyperlink.match('^https?:\/\/')) {
hyperlink = 'http://' + hyperlink;
}
if (url.length>35){
url = url.substring(0, 35)+"...";
}
return '' + url + ''+punc;
});
}
function getChatMessage(msg, label = false, director = false, overlay = false) {
msg = sanitizeChat(msg); // keep it clean.
if (msg == "") {
return;
}
if (label) {
label = sanitizeLabel(label);
}
data = {};
data.time = Date.now();
data.msg = msg;
if (label) {
data.label = label;
if (director) {
data.label = "" + data.label + ": ";
} else {
data.label = "" + data.label + ": ";
}
label = ""+label+":"; // label+":";
} else if (director) {
data.label = "Director: ";
label = "Director:";
} else {
if (session.director){
data.label = "Someone: ";
} else {
data.label = "";
}
label = "";
}
data.type = "recv";
if (overlay) {
if (!(session.cleanOutput && session.cleanish==false)){
var textOverlay = getById("overlayMsgs");
if (textOverlay) {
var spanOverlay = document.createElement("span");
spanOverlay.innerHTML = "" + label + " " + msg + " ";
textOverlay.appendChild(spanOverlay);
textOverlay.style.display = "block";
var showtime = msg.length * 200 + 3000;
if (showtime > 8000) {
showtime = 8000;
}
setTimeout(function(ele) {
ele.parentNode.removeChild(ele);
}, showtime, spanOverlay);
}
}
}
if (isIFrame) {
parent.postMessage({
"gotChat": data
}, session.iframetarget);
}
if (session.chatbutton===false){return;} // messages can still appear as overlays ^
messageList.push(data);
messageList = messageList.slice(-100);
if (session.beepToNotify) {
playtone();
showNotification("new message", msg);
}
updateMessages();
if (session.chat == false) {
getById("chattoggle").className = "las la-comments toggleSize pulsate";
getById("chatbutton").className = "float";
if (getById("chatNotification").value) {
getById("chatNotification").value = getById("chatNotification").value + 1;
} else {
getById("chatNotification").value = 1;
}
getById("chatNotification").classList.add("notification", "red");
}
if (session.broadcastChannel !== false) {
session.broadcastChannel.postMessage(data); /* send */
}
}
function updateClosedCaptions(msg, label, UUID) {
msg.counter = parseInt(msg.counter);
var temp = document.createElement('div');
temp.innerText = msg.transcript;
temp.innerText = temp.innerHTML;
var transcript = temp.textContent || temp.innerText || "";
if (transcript == "") {
return;
}
transcript = transcript.charAt(0).toUpperCase() + transcript.slice(1);
//transcript = transcript.substr(-1, 5000); // keep it from being too long
if (label && (!(session.view && !session.view_set))) {
label = sanitizeLabel(label);
label = "" + label + ": ";
} else {
label = "";
}
var textOverlay = getById("overlayMsgs");
if (textOverlay) {
if (document.getElementById(UUID + "_" + msg.counter)) {
var spanOverlay = document.getElementById(UUID + "_" + msg.counter);
} else {
var spanOverlay = document.createElement("span");
spanOverlay.id = UUID + "_" + msg.counter;
textOverlay.appendChild(spanOverlay);
textOverlay.style.height = "unset";
textOverlay.style.textAlign = "left";
textOverlay.style.display = "block";
textOverlay.style.position = "fixed";
textOverlay.style.bottom = "0";
}
spanOverlay.innerHTML = label + transcript + " ";
spanOverlay.style.fontSize = (parseInt(session.labelsize || 100) / 100.0 * 4.5) + "vh";
spanOverlay.style.lineHeight = (parseInt(session.labelsize || 100) / 100 * 6) + "vh";
spanOverlay.style.margin = (parseInt(session.labelsize || 100) / 100.0 * 0.75) + "vh";
if (msg.isFinal) {
var showtime = 3000;
clearTimeout(spanOverlay.timeout);
spanOverlay.timeout = setTimeout(function(ele) {
ele.parentNode.removeChild(ele);
}, showtime, spanOverlay);
} else {
clearTimeout(spanOverlay.timeout);
spanOverlay.timeout = setTimeout(function(ele) {
ele.parentNode.removeChild(ele);
}, 30000, spanOverlay);
}
}
}
var chatUpdateTimeout = null;
function updateMessages(){
if (session.chatbutton===false){return;}
document.getElementById("chatBody").innerHTML = "";
for (var i in messageList) {
var time = timeSince(messageList[i].time) || "";
time = " - "+time+"";
var msg = document.createElement("div");
var message = replaceURLs(messageList[i].msg);
if (messageList[i].type == "sent") {
msg.innerHTML = message + "" + time + "";
msg.classList.add("outMessage");
} else if ((messageList[i].type == "recv") || (messageList[i].type == "action")) {
var label = "";
if (messageList[i].label) {
label = messageList[i].label;
}
msg.innerHTML = label + message + "" + time + "";
msg.classList.add("inMessage");
} else if (messageList[i].type == "alert") {
msg.innerHTML = message + "" + time + "";
msg.classList.add("inMessage");
} else {
msg.innerHTML = message;
msg.classList.add("outMessage");
}
document.getElementById("chatBody").appendChild(msg);
}
showDownloadLinks();
for (var i in msgTransferList) {
var time = timeSince(msgTransferList[i].time) || "";
time = " - "+time+"";
var msg = document.createElement("div");
if ("idx" in msgTransferList[i]){
msg.id = "transfer_"+msgTransferList[i].idx;
msg.classList.add("transfer");
}
if (msgTransferList[i].type == "sent") {
msg.innerHTML = msgTransferList[i].msg + "" + time + "";
msg.classList.add("outMessage");
} else if ((msgTransferList[i].type == "recv") || (msgTransferList[i].type == "action")) {
var label = "";
if (msgTransferList[i].label) {
label = msgTransferList[i].label;
}
msg.innerHTML = label + msgTransferList[i].msg + "" + time + "";
msg.classList.add("inMessage");
} else if (msgTransferList[i].type == "alert") {
msg.innerHTML = msgTransferList[i].msg + "" + time + "";
msg.classList.add("inMessage");
} else {
msg.innerHTML = msgTransferList[i].msg;
msg.classList.add("outMessage");
}
if (msg.id && document.getElementById(msg.id)){
document.getElementById(msg.id).innerHTML = msg.innerHTML;
} else {
getById("chatBody").appendChild(msg);
}
}
if (chatUpdateTimeout) {
clearInterval(chatUpdateTimeout);
}
document.getElementById("chatBody").scrollTop = document.getElementById("chatBody").scrollHeight;
chatUpdateTimeout = setTimeout(function() {
updateMessages();
}, 60000);
}
function EnterButtonChat(event) {
// Number 13 is the "Enter" key on the keyboard
var key = event.which || event.keyCode;
if (key === 13) {
// Cancel the default action, if needed
event.preventDefault();
// Trigger the button element with a click
sendChatMessage();
}
}
function showCustomizer(arg, ele) {
//getById("directorLinksButton").innerHTML=' LINKS (GUEST INVITES & SCENES)'
getById("showCustomizerButton1").style.backgroundColor = "";
getById("showCustomizerButton2").style.backgroundColor = "";
getById("showCustomizerButton3").style.backgroundColor = "";
getById("showCustomizerButton4").style.backgroundColor = "";
getById("showCustomizerButton1").style.boxShadow = "";
getById("showCustomizerButton2").style.boxShadow = "";
getById("showCustomizerButton3").style.boxShadow = "";
getById("showCustomizerButton4").style.boxShadow = "";
if (getById("customizeLinks" + arg).style.display != "none") {
getById("customizeLinks").style.display = "none";
getById("customizeLinks" + arg).style.display = "none";
} else {
//directorLinks").style.display="none";
getById("showCustomizerButton" + arg).style.backgroundColor = "#1e0000";
getById("showCustomizerButton" + arg).style.boxShadow = "inset 0px 0px 1px #b90000";
getById("customizeLinks1").style.display = "none";
getById("customizeLinks3").style.display = "none";
getById("customizeLinks").style.display = "block";
getById("customizeLinks" + arg).style.display = "block";
}
}
var PPTHotkey = getStorage("PPTHotkey") || false;
if (PPTHotkey){
var key = "";
if (PPTHotkey.ctrl){
key += "Control";
}
if (PPTHotkey.meta){
if (key){
key += " + ";
}
key += "Meta";
}
if (PPTHotkey.alt){
if (key){
key += " + ";
}
key += "Alt";
}
if (PPTHotkey.key=="Control"){
//
} else if (PPTHotkey.key=="Alt"){
//
} else if (PPTHotkey.key=="Meta"){
//
} else if (PPTHotkey.key !== false){
if (key){
key += " + ";
}
if (PPTHotkey.key === " "){
key += "Space"
} else {
key += PPTHotkey.key;
}
} else if (key && (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1)){
getById("pptHotKey").title = "Note: Global hot-keys can't simply be Control, Alt, or Meta keys.";
}
getById("pptHotKey").value = key;
try {
if (window.electronApi && window.electronApi.updatePPT){
window.electronApi.updatePPT(PPTHotkey);
} else if (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1) {
if (!ipcRenderer){
ipcRenderer = require('electron').ipcRenderer;
}
if (ipcRenderer){
ipcRenderer.send('PPTHotkey', PPTHotkey);
}
}
} catch(e){errorlog(e);}
}
function setHotKey(keyinput=true){
if (!keyinput){ // clears if false
getById("pptHotKey").value = "";
PPTHotkey = false;
removeStorage("PPTHotkey");
try {
if (window.electronApi && window.electronApi.updatePPT){
window.electronApi.updatePPT(PPTHotkey);
} else if (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1) {
if (!ipcRenderer){
ipcRenderer = require('electron').ipcRenderer;
}
if (ipcRenderer){
ipcRenderer.send('PPTHotkey', PPTHotkey);
}
}
} catch(e){errorlog(e);}
return;
}
PPTHotkey = {
ctrl:false,
alt: false,
meta: false,
key: false
};
log(event);
var key = "";
if (event.ctrlKey){
key += "Control";
PPTHotkey.ctrl = true;
}
if (event.metaKey){
if (key){
key += " + ";
}
key += "Meta";
PPTHotkey.meta = true;
}
if (event.altKey){
if (key){
key += " + ";
}
key += "Alt";
PPTHotkey.alt = true;
}
if (event.key=="Control"){
//
} else if (event.key=="Alt"){
//
} else if (event.key=="Meta"){
//
} else if (event.key || (event.key === " " || (event.key===0))){
if (key){
key += " + ";
}
if (event.key === " "){
key += "Space"
} else {
key += event.key;
}
PPTHotkey.key = event.key;
}
setStorage("PPTHotkey", PPTHotkey, 99999);
event.target.value = key;
try {
if (window.electronApi && window.electronApi.updatePPT){
window.electronApi.updatePPT(PPTHotkey);
} else if (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1) {
if (!ipcRenderer){
ipcRenderer = require('electron').ipcRenderer;
}
if (ipcRenderer){
ipcRenderer.send('PPTHotkey', PPTHotkey);
}
}
} catch(e){errorlog(e);}
event.preventDefault();
event.stopPropagation();
return false;
}
async function streamVideoToDropbox(filename) {
if (!session.dbx){return;}
return await session.dbx.filesUploadSessionStart({ close: false }).then(function (response) {
var sessionId = response.result.session_id;
var offset = 0;
var queue = [];
console.log(response);
function uploadChunk(chunk, oldCursor = false) {
if (queue.length){ // still uploading
queue.push(chunk);
return;
}
queue.push(chunk);
if (chunk===false){
console.log("DONE UPLOADING.. closing dropbox file: "+offset);
console.log(oldCursor);
session.dbx.filesUploadSessionFinish({ cursor: { session_id: sessionId, offset: offset }, commit: { path: '/' + filename } }).then(function (response) {
console.log(response);
console.log('File uploaded to Dropbox:', response.path_display);
})
.catch(function (error) {
console.error('Error uploading file:', error);
});
} else {
var currentOffset = offset;
offset += chunk.size;
var cursor = { session_id: sessionId, offset: currentOffset };
console.log(cursor);
session.dbx.filesUploadSessionAppendV2({ cursor: cursor, close: false, contents: chunk }).then(function () {
console.log("uploaded");
console.log(queue);
var x = queue.shift();
if (queue.length){
uploadChunk(queue.shift(), cursor)
}
}).catch(function (error) {
console.error('Error appending chunk:', error);
});
}
}
return uploadChunk;
}).catch(function (error) {
console.error('Error starting upload session:', error);
});
}
var recordingBitratePromise = false;
var defaultRecordingBitrate = false;
async function recordVideo(target, event = null, videoKbps = false) { // event.currentTarget,this.parentNode.parentNode.dataset.UUID
if (session.record === false){warnlog("recordings are disabled by decree of thy host magistrate");}
var UUID = target.dataset.UUID;
if (!UUID){return;}
var video = session.rpcs[UUID].videoElement;
if (!video){return;}
if (video.stopWriter){
video.stopWriter();
updateLocalRecordButton(UUID, -1);
return;
} else if (video.startWriter){
await video.startWriter();
updateLocalRecordButton(UUID, 0);
return;
}
var audioKbps = false;
if (event === null) {
if (defaultRecordingBitrate === null) {
updateLocalRecordButton(UUID, -1);
return;
}
} else if ((event.ctrlKey) || (event.metaKey)) {
updateLocalRecordButton(UUID, -3);
Callbacks.push([recordVideo, target, null, false]);
log("Record Video queued");
defaultRecordingBitrate = false;
recordingBitratePromise = false;
return;
} else {
defaultRecordingBitrate = false;
recordingBitratePromise = false;
}
log("Record Video Clicked");
if ("recording" in video) {
log("ALREADY RECORDING!");
updateLocalRecordButton(UUID, -2);
video.recorder.stop();
session.requestRateLimit(35, UUID); // 100kbps
if (session.audiobitrate===false){
session.requestAudioRateLimit(-1,UUID);
}
var elements = document.querySelectorAll('[data-action-type="change-quality2"][data--u-u-i-d="' + UUID + '"]');
if (elements[0]) {
elements[0].classList.add("pressed"); elements[0].ariaPressed = "true";
}
var elements = document.querySelectorAll('[data-action-type="change-quality1"][data--u-u-i-d="' + UUID + '"]');
if (elements[0]) {
elements[0].classList.remove("pressed"); elements[0].ariaPressed = "false";
}
var elements = document.querySelectorAll('[data-action-type="change-quality3"][data--u-u-i-d="' + UUID + '"]');
if (elements[0]) {
elements[0].classList.remove("pressed"); elements[0].ariaPressed = "false";
}
return;
} else {
updateLocalRecordButton(UUID, 0);
//target.style.backgroundColor = "#FCC";
//target.innerHTML = " Download";
video.recording = true;
}
video.recorder = {};
if (videoKbps == false) {
if (defaultRecordingBitrate == false) {
videoKbps = 4000; // 4mbps recording bitrate
if (!recordingBitratePromise){
window.focus();
recordingBitratePromise = promptAlt(miscTranslations["press-ok-to-record"], false, false, videoKbps);
}
videoKbps = await recordingBitratePromise;
log("videoKbps: "+videoKbps+", UUID:"+UUID);
if (videoKbps === null) {
//target.style.backgroundColor = null;
//target.innerHTML = ' record local';
updateLocalRecordButton(UUID, -1);
target.style.backgroundColor = "";
delete(video.recorder);
delete(video.recording);
defaultRecordingBitrate = null;
return;
}
videoKbps = parseInt(videoKbps);
defaultRecordingBitrate = videoKbps;
} else {
videoKbps = defaultRecordingBitrate;
}
}
if (videoKbps <= 0) {
audioKbps = videoKbps * (-1);
videoKbps = false;
if (session.audiobitrate===false){
if ((audioKbps>0) && (audioKbps>=128)){
session.requestAudioRateLimit(128,UUID); // no point going higher
} else if (audioKbps==0){
session.requestAudioRateLimit(256,UUID); // PCM
} else {
session.requestAudioRateLimit(parseInt(audioKbps),UUID); // exact? sure. why not.
}
}
} else if (videoKbps < 50) { // this just makes sure you can't set 0 on the record bitrate.
videoKbps = 50;
session.requestRateLimit(parseInt(videoKbps * 0.8), UUID); // 3200kbps transfer bitrate. Less than the recording bitrate, to avoid waste.
} else {
session.requestRateLimit(parseInt(videoKbps * 0.8), UUID); // 3200kbps transfer bitrate. Less than the recording bitrate, to avoid waste.
if (videoKbps>4000){
if (session.audiobitrate===false){
if (session.pcm){
session.requestAudioRateLimit(256,UUID);
} else {
session.requestAudioRateLimit(128,UUID);
}
}
} else if (videoKbps>2500){
if (session.audiobitrate===false){
if (session.pcm){
session.requestAudioRateLimit(256,UUID);
} else {
session.requestAudioRateLimit(80,UUID);
}
}
}
}
var timestamp = Date.now();
var filename = "";
if (session.rpcs[UUID].label || session.rpcs[UUID].streamID) {
filename = session.rpcs[UUID].label || session.rpcs[UUID].streamID;
filename = filename.replace(/[\W]+/g, "_");
filename = filename.substring(0, 200);
}
filename += "_" + timestamp.toString();
var cancell = false;
if (typeof video.srcObject === "undefined" || !video.srcObject) {
return;
}
video.recorder.stop = function(restart = false, notify = false) {
if (session.dbx && video.recorder && video.recorder.dropbox){
video.recorder.dropbox(false);
}
if (!video.recording) {
errorlog("ALREADY STOPPED");
updateLocalRecordButton(UUID, -1);
return;
}
if (notify){
if (!session.cleanOutput){
warnUser("A local recording has stopped unexpectedly.");
}
if (session.beepToNotify){
playtone();
}
target.classList.remove("shake");
setTimeout(function(target){target.classList.add("shake");},10, target);
}
video.recording = false;
updateLocalRecordButton(UUID, -2);
try {
if (video.recorder.mediaRecorder.state !== "inactive") {
video.recorder.mediaRecorder.stop();
}
} catch (e) {
errorlog(e);
}
session.requestRateLimit(35, UUID); // 100kbps
if (session.audiobitrate===false){
session.requestAudioRateLimit(-1,UUID);
}
var elements = document.querySelectorAll('[data-action-type="change-quality2"][data--u-u-i-d="' + UUID + '"]');
if (elements[0]) {
elements[0].classList.add("pressed"); elements[0].ariaPressed = "true";
}
var elements = document.querySelectorAll('[data-action-type="change-quality1"][data--u-u-i-d="' + UUID + '"]');
if (elements[0]) {
elements[0].classList.remove("pressed"); elements[0].ariaPressed = "false";
}
var elements = document.querySelectorAll('[data-action-type="change-quality3"][data--u-u-i-d="' + UUID + '"]');
if (elements[0]) {
elements[0].classList.remove("pressed"); elements[0].ariaPressed = "false";
}
cancell = true;
// log('Recorded Blobs: ', recordedBlobs);
// download();
setTimeout((writer1,UUID1,video1) => {
try{
writer1.close();
} catch(e){}
updateLocalRecordButton(UUID1, -1);
delete(video1.recorder);
delete(video1.recording);
}, 1200, writer, UUID, video);
};
const {readable, writable} = new TransformStream({
transform: (chunk, ctrl) => chunk.arrayBuffer().then(b => ctrl.enqueue(new Uint8Array(b)))
});
var writer = writable.getWriter();
readable.pipeTo(streamSaver.createWriteStream(filename.toString() + '.webm', video.recorder.stop));
video.recorder.writer = writer;
pokeIframeAPI("recording-started");
let options = {};
if (videoKbps) {
var tryCodec = false;
if (session.recordingVideoCodec){
tryCodec = session.recordingVideoCodec;
}
if (tryCodec && MediaRecorder.isTypeSupported('video/webm;codecs='+tryCodec)) {
if (!session.cleanOutput){
warnUser("The browser 'says' it supports "+tryCodec);
}
options.mimeType = 'video/webm;codecs='+tryCodec;
if (session.pcm){
if (MediaRecorder.isTypeSupported('video/webm;codecs="'+tryCodec+', pcm"')){
options.mimeType = 'video/webm;codecs="'+tryCodec+', pcm"';
} else {
options.mimeType = "video/webm;codecs=pcm";
}
}
} else {
if (session.pcm){
if (MediaRecorder.isTypeSupported("video/webm;codecs=pcm")) {
options.mimeType = "video/webm;codecs=pcm";
} else {
options.mimeType = "video/webm";
}
} else {
options.mimeType = "video/webm";
}
}
if (videoKbps < 1000) {
options.videoBitsPerSecond = parseInt(videoKbps * 1024); // 100 kbps audio
} else {
options.bitsPerSecond = parseInt(videoKbps * 1024); // 100 to 132 kbps audio
}
video.recorder.mediaRecorder = new MediaRecorder(video.srcObject, options);
//if (session.dbx){
// video.recorder.dropbox = await streamVideoToDropbox(); // i don't want to upload to dropbox remote streams; just local
//}
} else {
options.mimeType = 'audio/webm';
if (audioKbps == 0) {
if (MediaRecorder.isTypeSupported("audio/webm;codecs=pcm")) {
options.mimeType = "audio/webm;codecs=pcm";
}
} else {
options.bitsPerSecond = parseInt(audioKbps * 1024);
}
var stream = createMediaStream();
video.srcObject.getAudioTracks().forEach((track) => {
stream.addTrack(track, video.srcObject);
});
video.recorder.mediaRecorder = new MediaRecorder(stream, options);
//if (session.dbx){
// video.recorder.dropbox = await streamVideoToDropbox();
//}
}
log(options);
function download() {
const blob = new Blob(recordedBlobs, {
type: "video/webm"
});
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = filename + ".webm";
document.body.appendChild(a);
a.click();
setTimeout(function(uu,aa){
document.body.removeChild(aa);
window.URL.revokeObjectURL(uu);
}, 100, url,a);
}
function handleDataAvailable(event) {
if (event.data && event.data.size > 0) {
//recordedBlobs.push(event.data);
try{
writer.write(event.data); ////////////
if (video.recording) {
updateLocalRecordButton(UUID, (parseInt((Date.now() - timestamp) / 1000) || 0));
}
} catch(e){warnlog("Stream recording error or ended");}
try {
if (session.dbx && video.recorder && video.recorder.dropbox){
video.recorder.dropbox(event.data);
}
} catch(e){
errorlog(e);
}
}
}
video.recorder.mediaRecorder.ondataavailable = handleDataAvailable;
video.recorder.mediaRecorder.onerror = function(event) {
errorlog(event);
video.recorder.stop();
session.requestRateLimit(35, UUID);
if (!(session.cleanOutput)) {
setTimeout(function() {
warnUser("an error occured with the media recorder; stopping recording");
}, 1);
}
};
video.srcObject.ended = function(event) {
video.recorder.stop();
session.requestRateLimit(35, UUID);
if (!(session.cleanOutput)) {
setTimeout(function() {
warnUser("stream ended! stopping recording");
}, 1);
}
};
setTimeout(function(v) {
v.recorder.mediaRecorder.start(1000);
}, 500, video); // 100ms chunks
return;
}
function updateRemoteRecordButton(UUID, recorder) {
var elements = document.querySelectorAll('[data-action-type="recorder-remote"][data--u-u-i-d="' + UUID + '"]');
if (elements[0]) {
var time = parseInt(recorder) || 0;
if (time == -4) {
if (!session.cleanOutput){
warnUser("A remote recording has stopped unexpectedly.\n\nDid a user cancel the file downlaod?");
}
if (session.beepToNotify){
playtone();
}
elements[0].classList.add("pressed"); elements[0].ariaPressed = "true";
elements[0].classList.remove("shake");
elements[0].innerHTML = ' stopping...';
setTimeout(function(ele){ele.classList.add("shake");},10,elements[0]);
} else if (time == -3) {
elements[0].classList.remove("pressed"); elements[0].ariaPressed = "false";
elements[0].disabled = true;
elements[0].innerHTML = ' Not Supported';
if (!(session.cleanOutput)) {
setTimeout(function() {
warnUser('The remote browser does not support recording.\n\nPerhaps try local recording instead.');
}, 0);
}
} else if (time == -5) {
if (!(session.cleanOutput)) {
setTimeout(function() {
warnUser('The remote browser has only experimental support for media recording.\n\nAlso, when this download stops, the remote user may be asked to download the file for it to save.');
}, 0);
}
} else if (time == -2) {
elements[0].classList.add("pressed"); elements[0].ariaPressed = "true";
elements[0].innerHTML = ' stopping...';
} else if (time == -1) {
elements[0].classList.remove("pressed"); elements[0].ariaPressed = "false";
elements[0].innerHTML = ' Record Remote';
} else {
var minutes = Math.floor(time / 60);
var seconds = time - minutes * 60;
elements[0].classList.add("pressed"); elements[0].ariaPressed = "true";
elements[0].innerHTML = ' ' + (minutes) + "m : " + zpadTime(seconds) + "s";
}
}
}
function updateLocalRecordButton(UUID, recorder) {
var elements = document.querySelectorAll('[data-action-type="recorder-local"][data--u-u-i-d="' + UUID + '"]');
if (elements[0]) {
var time = parseInt(recorder) || 0;
//target.innerHTML = ' ARMED';
//
if (time == -3) {
elements[0].classList.add("pressed"); elements[0].ariaPressed = "true";
elements[0].innerHTML = ' ARMED';
elements[0].style.backgroundColor = "#BF3F3F";
} else if (time == -2) {
elements[0].classList.add("pressed"); elements[0].ariaPressed = "true";
elements[0].innerHTML = ' stopping...';
elements[0].style.backgroundColor = "";
} else if (time == -1) {
elements[0].classList.remove("pressed"); elements[0].ariaPressed = "false";
elements[0].innerHTML = ' Record Local';
elements[0].style.backgroundColor = "";
} else {
var minutes = Math.floor(time / 60);
var seconds = time - minutes * 60;
elements[0].classList.add("pressed"); elements[0].ariaPressed = "true";
elements[0].innerHTML = ' ' + (minutes) + "m : " + zpadTime(seconds) + "s";
elements[0].style.backgroundColor = "";
}
}
}
function recordLocalVideoToggle() {
if (!session.videoElement){return;}
log("recordLocalVideoToggle()");
var ele = getById("recordLocalbutton");
if (ele.dataset.state == "0") {
ele.dataset.state = "1";
ele.style.backgroundColor = "red";
ele.innerHTML = '';
if ("recording" in session.videoElement) {
} else {
recordLocalVideo("start");
}
if (session.director){
var elements = document.querySelectorAll('[data-action-type="recorder-local"][data-sid="' + session.streamID + '"]');
if (elements[0]) {
elements[0].classList.add("pressed"); elements[0].ariaPressed = "true";
elements[0].innerHTML = ' Record';
}
}
return true;
} else {
if ("recording" in session.videoElement) {
recordLocalVideo("stop");
}
ele.dataset.state = "0";
ele.style.backgroundColor = "";
ele.innerHTML = '';
if (session.director){
var elements = document.querySelectorAll('[data-action-type="recorder-local"][data-sid="' + session.streamID + '"]');
if (elements[0]) {
elements[0].classList.remove("pressed"); elements[0].ariaPressed = "false";
elements[0].innerHTML = ' Record';
}
}
return false;
}
}
function setupSensorData(pollrate = 30) {
session.sensors = {};
session.sensors.data = {};
if (window.Accelerometer && session.sensorDataFilter.includes("acc")) {
session.sensors.data.acc = {};
session.sensors.Accelerometer = new Accelerometer({
frequency: pollrate
});
session.sensors.Accelerometer.addEventListener('reading', e => {
session.sensors.data.acc.x = session.sensors.Accelerometer.x.toFixed(5);
session.sensors.data.acc.y = session.sensors.Accelerometer.y.toFixed(5);
session.sensors.data.acc.z = session.sensors.Accelerometer.z.toFixed(5);
session.sensors.data.acc.t = parseInt(Math.round(session.sensors.Accelerometer.timestamp));
});
session.sensors.Accelerometer.start();
}
if (window.Gyroscope && session.sensorDataFilter.includes("gyro")) {
session.sensors.data.gyro = {};
session.sensors.Gyroscope = new Gyroscope({
frequency: pollrate
});
session.sensors.Gyroscope.addEventListener('reading', e => {
session.sensors.data.gyro.x = session.sensors.Gyroscope.x.toFixed(5);
session.sensors.data.gyro.y = session.sensors.Gyroscope.y.toFixed(5);
session.sensors.data.gyro.z = session.sensors.Gyroscope.z.toFixed(5);
session.sensors.data.gyro.t = parseInt(Math.round(session.sensors.Gyroscope.timestamp));
});
session.sensors.Gyroscope.start();
}
if (window.Magnetometer && session.sensorDataFilter.includes("mag")) {
session.sensors.data.mag = {};
session.sensors.Magnetometer = new Magnetometer({
frequency: pollrate
});
session.sensors.Magnetometer.addEventListener('reading', e => {
session.sensors.data.mag.x = session.sensors.Magnetometer.x.toFixed(5);
session.sensors.data.mag.y = session.sensors.Magnetometer.y.toFixed(5);
session.sensors.data.mag.z = session.sensors.Magnetometer.z.toFixed(5);
session.sensors.data.mag.t = parseInt(Math.round(session.sensors.Magnetometer.timestamp));
});
session.sensors.Magnetometer.start();
session.sensors.deviceorientation = false;
} else if (session.sensorDataFilter.includes("ori")){
try{
window.addEventListener('deviceorientation', e => {
session.sensors.data.ori = {};
try{
session.sensors.data.ori.d = e.absolute;
} catch(event){}
session.sensors.data.ori.a = e.alpha.toFixed(5);
session.sensors.data.ori.b = e.beta.toFixed(5);
session.sensors.data.ori.g = e.gamma.toFixed(5);
session.sensors.data.ori.t = parseInt(Math.round(e.timestamp)) || Date.now();
});
session.sensors.deviceorientation = true;
} catch(e){
session.sensors.deviceorientation = false;
}
}
if (window.LinearAccelerationSensor && session.sensorDataFilter.includes("lin")) {
session.sensors.data.lin = {};
session.sensors.LinearAccelerationSensor = new LinearAccelerationSensor({
frequency: pollrate
});
session.sensors.LinearAccelerationSensor.addEventListener('reading', e => {
session.sensors.data.lin.x = session.sensors.LinearAccelerationSensor.x.toFixed(5);
session.sensors.data.lin.y = session.sensors.LinearAccelerationSensor.y.toFixed(5);
session.sensors.data.lin.z = session.sensors.LinearAccelerationSensor.z.toFixed(5);
session.sensors.data.lin.t = parseInt(Math.round(session.sensors.LinearAccelerationSensor.timestamp));
});
session.sensors.LinearAccelerationSensor.start();
}
if (navigator.geolocation && session.sensorDataFilter.includes("pos")){
navigator.geolocation.watchPosition(function(pos){
try {
session.sensors.data.pos = {};
session.sensors.data.pos.speed = pos.coords.speed.toFixed(3);
session.sensors.data.pos.alt = pos.coords.altitude.toFixed(3);
session.sensors.data.pos.t = pos.timestamp;
}catch(e){}
}, errorlog, {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
});
}
setInterval(function() {
session.sendMessage({sensors: session.sensors.data});
}, parseInt(1000 / pollrate));
}
async function recordLocalVideo(action = null, videoKbps = 6000, remote=false) { // event.currentTarget,this.parentNode.parentNode.dataset.UUID
if (session.record === false){warnlog("recordings are disabled by decree of thy host magistrate");}
var audioKbps = false;
if (remote){
var video = remote;
if (remote.id === "videosource"){
remote = false;
}
} else {
var video = session.videoElement;
}
log(video.id);
if (!video){return;}
if ("recording" in video) {
if (action == "estop") {
video.recorder.eStop();
log("EMERGENCY Stopping RECORDING!");
video.recorder.stop();
delete(video.recorder);
delete(video.recording);
return;
} else if (action == "stop") {
log("Stopping RECORDING!");
video.recorder.stop();
delete(video.recorder);
delete(video.recording);
return;
} else if (action == "start") {
log("ALREADY RECORDING!");
if (remote){
getById("recordLocalbutton").dataset.state = "1";
getById("recordLocalbutton").style.backgroundColor = "red";
getById("recordLocalbutton").innerHTML = '';
}
return;
} else {
log("STOPPING RECORDING by default toggle!");
video.recorder.stop();
return;
}
return;
} else if (action == "start") {
if (!MediaRecorder) {
var msg = {};
msg.recorder = -3;
for (var i = 0;i';
}
} else if (action == "stop") {
return;
} else {
if (!remote){
getById("recordLocalbutton").dataset.state = "1";
getById("recordLocalbutton").style.backgroundColor = "red";
getById("recordLocalbutton").innerHTML = '';
}
video.recording = true;
}
video.recorder = {};
if (session.recordLocal !== false) {
videoKbps = session.recordLocal;
}
if (videoKbps <= 0) {
audioKbps = videoKbps * (-1);
videoKbps = false;
} else if (videoKbps < 50) { // this just makes sure you can't set 0 on the record bitrate.
videoKbps = 50;
}
if (typeof video.srcObject === "undefined" || !video.srcObject) {
return;
}
var timestamp = Date.now();
var filename = "";
if (session.label || session.streamID) {
filename = session.label || session.streamID;
filename = filename.replace(/[\W]+/g, "_");
filename = filename.substring(0, 200);
}
filename += "_" + timestamp.toString();
video.recorder.eStop = function() {
writer.close();
}
video.recorder.stop = function(restart = false, notify=false) {
if (session.dbx && video.recorder && video.recorder.dropbox){
video.recorder.dropbox(false);
}
if (!remote){
if (restart){
if (getById("recordLocalbutton").dataset.state == 2) {
getById("recordLocalbutton").dataset.state = "0";
getById("recordLocalbutton").style.backgroundColor = "";
getById("recordLocalbutton").innerHTML = '';
restart = false;
warnUser("Media Recording Stopped due to an error.");
} else {
getById("recordLocalbutton").innerHTML = '';
getById("recordLocalbutton").dataset.state = "2";
}
} else {
getById("recordLocalbutton").dataset.state = "0";
getById("recordLocalbutton").style.backgroundColor = "";
getById("recordLocalbutton").innerHTML = '';
if (notify){
if (!session.cleanOutput){
warnUser("A recording has stopped unexpectedly.");
}
if (session.beepToNotify){
playtone();
}
getById("recordLocalbutton").classList.remove("shake");
setTimeout(function(){getById("recordLocalbutton").classList.add("shake");},10);
}
}
}
if (!video.recording) {
errorlog("ALREADY STOPPED");
return;
}
video.recording = false;
try {
if (video.recorder.mediaRecorder.state !== "inactive") {
video.recorder.mediaRecorder.stop();
}
} catch (e) {
errorlog(e);
}
setTimeout(() => {
try {
writer.close();
} catch(e){}
pokeIframeAPI("recording-stopped");
if (!remote){
try {
if (session.directorUUID) {
var msg = {};
msg.recorder = -1;
for (var i = 0;i chunk.arrayBuffer().then(b => ctrl.enqueue(new Uint8Array(b)))
});
var writer = writable.getWriter();
readable.pipeTo(streamSaver.createWriteStream(filename.toString() + '.webm', video.recorder.stop));
video.recorder.writer = writer;
pokeIframeAPI("recording-started");
let options = {};
if (videoKbps) {
var tryCodec = false;
if (session.recordingVideoCodec){
tryCodec = session.recordingVideoCodec;
}
if (tryCodec && MediaRecorder.isTypeSupported('video/webm;codecs='+tryCodec)) {
if (!session.cleanOutput){
warnUser("The browser 'says' it supports "+tryCodec);
}
options.mimeType = 'video/webm;codecs='+tryCodec;
if (session.pcm){
if (MediaRecorder.isTypeSupported('video/webm;codecs="'+tryCodec+', pcm"')){
options.mimeType = 'video/webm;codecs="'+tryCodec+', pcm"';
} else {
options.mimeType = "video/webm;codecs=pcm";
}
}
} else {
if (session.pcm){
if (MediaRecorder.isTypeSupported("video/webm;codecs=pcm")) {
options.mimeType = "video/webm;codecs=pcm";
} else {
options.mimeType = "video/webm";
}
} else {
options.mimeType = "video/webm";
}
}
if (videoKbps < 1000) {
options.videoBitsPerSecond = parseInt(videoKbps * 1024); // 100 kbps audio
} else {
options.bitsPerSecond = parseInt(videoKbps * 1024); // 100 to 132 kbps audio
}
try {
video.recorder.mediaRecorder = new MediaRecorder(video.srcObject, options);
} catch(e){
warnlog(e);
try {
video.recorder.mediaRecorder = new MediaRecorder(video.srcObject);
} catch(e){
errorlog(e);
errorlog("Failing the recording");
var msg = {};
msg.recorder = -3;
for (var i = 0;i {
stream.addTrack(track, video.srcObject);
});
video.recorder.mediaRecorder = new MediaRecorder(stream, options);
if (session.dbx){
video.recorder.dropbox = await streamVideoToDropbox(filename.toString() + '.webm');
}
}
log(options);
function handleDataAvailable(event) {
if (event.data && event.data.size > 0) {
writer.write(event.data);
if (session.directorList.length) {
if (video.recording) {
var msg = {};
msg.recorder = parseInt((Date.now() - timestamp) / 1000) || 0;
for (var i =0;i -1) {
newTracks.splice(index, 1);
}
}
});
});
}
var screenshare = false;
if (session.rpcs[UUID].screenIndexes && session.rpcs[UUID].screenIndexes.length){
log("session.rpcs[UUID].screenIndexes: " + session.rpcs[UUID].screenIndexes);
var receievers = session.rpcs[UUID].getReceivers(); // excluded
for (var i=0;i{
if ((trk.id == e1.track.id) && (trk.kind == e1.track.kind)){
session.rpcs[UUID].streamSrc.removeTrack(trk);
}
});
if ( e1.track.kind=="video"){
updateIncomingVideoElement(UUID, true, false);
} else {
updateIncomingVideoElement(UUID, false, true);
}
updateIncomingVideoElement(UUID); // session.rpcs[UUID].videoElement.srcObject = session.rpcs[UUID].streamSrc;
setTimeout(function(){updateMixer();},1);
} catch(e){}
};
newStream.onerror = function(e1){
errorlog(e1);
try{
warnlog("Track threw an error; going to reconnect it");
session.rpcs[UUID].streamSrc.getTracks().forEach((trk)=>{
try{
if ((trk.id == e1.track.id) && (trk.kind == e1.track.kind)){
session.rpcs[UUID].streamSrc.removeTrack(trk);
}
} catch(e){}
});
if ( e1.track.kind=="video"){
updateIncomingVideoElement(UUID, true, false);
} else {
updateIncomingVideoElement(UUID, false, true);
}
setTimeout(function(){updateMixer();},1);
} catch(e){errorlog(e);}
};
}
createRichVideoElement(UUID);
if (!session.rpcs[UUID].streamSrc) {
session.rpcs[UUID].streamSrc = createMediaStream();
mediaSourceUpdated(UUID, session.rpcs[UUID].streamID);
}
var videoAdded=false;
var audioAdded=false;
newTracks.forEach((trk)=>{
if (trk.kind=="video"){
videoAdded=true;
} else if (trk.kind=="audio"){
audioAdded=true;
}
log("adding track");
session.rpcs[UUID].streamSrc.addTrack(trk);
});
if (newTracks.length > session.rpcs[UUID].streamSrc.getTracks().length){
errorlog("Not all the tracks were added to the local stream; are the tracks' IDs not unique?");
console.log("streamSrc total tracks: "+session.rpcs[UUID].streamSrc.getTracks().length);
}
if (isIFrame && session.sendframes){
newTracks.forEach((trk)=>{
if (trk.kind==="video"){
log("STARTING NEW VIDEO TRACK");
trk.frameReader = new MediaStreamTrackProcessor(trk).readable.getReader();
trk.frameReader.read().then(function processFrame2({done, value}) {
if (done) {
if (value){
value.close();
}
return;
}
try {
parent.postMessage({"frame":value, UUID:UUID, streamID:session.rpcs[UUID].streamID, trackID: trk.id, kind: "video"}, session.sendframes, [value]);
} catch(e){
value.close();
return;
}
value.close();
trk.frameReader.read().then(processFrame2);
});
} else if (trk.kind==="audio"){
log("STARTING NEW AUDIO TRACK");
trk.frameReader = new MediaStreamTrackProcessor(trk).readable.getReader();
trk.frameReader.read().then(function processFrameAudio2({done, value}) {
if (done) {
if (value){
value.close();
}
return;
}
try {
parent.postMessage({"frame":value, UUID:UUID, streamID:session.rpcs[UUID].streamID, trackID: trk.id, kind: "audio"}, session.sendframes, [new ArrayBuffer(value)]);
} catch(e){
value.close();
return;
}
value.close();
trk.frameReader.read().then(processFrameAudio2);
});
}
});
}
if (audioAdded && videoAdded){
updateIncomingVideoElement(UUID);
} else if (videoAdded){
updateIncomingVideoElement(UUID, true, false);
} else if (audioAdded){
try {
if (session.audioCodec == "lyra"){ // not supported currently
lyraDecode(event.receiver);
}
} catch(e){errorlog(e);}
updateIncomingVideoElement(UUID, false, true);
if (!session.roomid && session.view && !session.permaid){
setTimeout(function(){updateMixer();},10); // video already has an auto-start, with aspect ratio size change. audio doesn't.
}
}
return session;
};
function updateIncomingVideoElement(UUID, video=true, audio=true){
if (!session.rpcs[UUID].videoElement){
return;}
if (!session.rpcs[UUID].streamSrc){
return;}
if (!session.rpcs[UUID].videoElement.srcObject) {
session.rpcs[UUID].videoElement.srcObject = createMediaStream();
}
if (video){
var tracks = session.rpcs[UUID].videoElement.srcObject.getVideoTracks(); // add video track
session.rpcs[UUID].streamSrc.getVideoTracks().forEach((trk)=>{
var added = false;
tracks.forEach(trk2 =>{
if ((trk.id == trk2.id) && (trk.kind == trk2.kind)){
added=true;
}
});
if (!added){
session.rpcs[UUID].videoElement.srcObject.getVideoTracks().forEach((trk2)=>{ // make sure only one video track is added at a time.
session.rpcs[UUID].videoElement.srcObject.removeTrack(trk2);
});
if (trk.muted && (trk.kind=="video") && session.director){
trk.onunmute = function(e){
if (!session.rpcs[UUID]){return;}
this.onunmute = null;
warnlog("ON UN-MUTE");
updateIncomingVideoElement(UUID, true, false);
};
} else {
if (session.rpcs[UUID].videoElement.controls){
session.rpcs[UUID].videoElement.controls = session.showControls || false;
if (session.showControls===null){
setTimeout(function(ele){
if (ele){
ele.controls = true;
}
},500, session.rpcs[UUID].videoElement);
}
}
session.rpcs[UUID].videoElement.srcObject.addTrack(trk);
mediaVideoTrackUpdated(UUID, session.rpcs[UUID].streamID);
}
}
});
}
if (audio){
updateIncomingAudioElement(UUID) // do the same for audio now.
}
}
function updateIncomingAudioElement(UUID){ // this can be called when turning on/off inbound audio processing.
if (!session.rpcs[UUID] || !session.rpcs[UUID].videoElement || !session.rpcs[UUID].streamSrc){return;}
if (!session.rpcs[UUID].videoElement.srcObject) {
session.rpcs[UUID].videoElement.srcObject = createMediaStream();
}
log("updateIncomingAudioElement: "+UUID);
if ((session.audioEffects===true) || session.pushLoudness){
var tracks = session.rpcs[UUID].streamSrc.getAudioTracks();
if (tracks.length){
var track = tracks[0];
track = addAudioPipeline(UUID, track);
log(track);
var added = false;
var tracks2 = session.rpcs[UUID].videoElement.srcObject.getAudioTracks();
log(tracks2);
tracks2.forEach(trk2 =>{
if (trk2.label && (trk2.label == "MediaStreamAudioDestinationNode")){ // an old morphed node; delete it.
session.rpcs[UUID].videoElement.srcObject.removeTrack(trk2);
} else if ((track.id == trk2.id) && (track.kind == trk2.kind)){ // maybe it didn't morph; already added either way
added = true;
} else if ((tracks[0].id == trk2.id) && (tracks[0].kind == trk2.kind) && (track.id != tracks[0].id)){ // remove original audio track that is now morphed
session.rpcs[UUID].videoElement.srcObject.removeTrack(trk2);
}
});
if (!added){
session.rpcs[UUID].videoElement.srcObject.addTrack(track);
mediaAudioTrackUpdated(UUID, session.rpcs[UUID].streamID);
}
} else {
session.rpcs[UUID].videoElement.srcObject.getAudioTracks().forEach(trk=>{ // make sure to remove all tracks.
session.rpcs[UUID].videoElement.srcObject.remove(trk);
});
}
} else {
var expected = [];
tracks = session.rpcs[UUID].videoElement.srcObject.getAudioTracks(); // add audio tracks
session.rpcs[UUID].streamSrc.getAudioTracks().forEach((trk)=>{
var added = false;
tracks.forEach(trk2 =>{
if ((trk.id == trk2.id) && (trk.kind == trk2.kind)){
added=true;
expected.push(trk2); //
}
});
if (!added){
session.rpcs[UUID].videoElement.srcObject.addTrack(trk);
mediaAudioTrackUpdated(UUID, session.rpcs[UUID].streamID);
}
});
tracks.forEach((trk)=>{
var added = false;
expected.forEach((trk2)=>{
if ((trk.id == trk2.id) && (trk.kind == trk2.kind)){
added=true;
}
});
if (!added){ // not expected. so lets delete.
warnlog("this shouldn't happen that often, audio track orphaned. removing it");
session.rpcs[UUID].videoElement.srcObject.removeTrack(trk);
}
});
}
if (session.mixMinus){
stream = mixMinusAudio(UUID); // only works with p2p; no chunked mode.
}
}
function cycleStyleOptions(){
session.style +=1;
if (session.style >6 ){
session.style = 1;
} else if (session.style == 4 ){
session.style = 5;
}
for (var UUID in session.rpcs){
if (session.rpcs[UUID].canvas){
try{
if (session.rpcs[UUID].canvas){
session.rpcs[UUID].canvas.remove();
}
} catch(e){}
session.rpcs[UUID].canvas = null;
}
updateIncomingAudioElement(UUID);
}
updateMixer();
}
function addAudioPipeline(UUID, track){ // INBOUND AUDIO EFFECTS ; audio tracks only
try{
if (session.disableViewerWebAudioPipeline){
log("ignoring addAudioPipeline - disableViewerWebAudioPipeline is enabled (noap)");
return track;
}
log("Triggered webaudio effects path");
for (var tid in session.rpcs[UUID].inboundAudioPipeline){
delete session.rpcs[UUID].inboundAudioPipeline[tid]; // get rid of old nodes.
}
var trackid = track.id; // this is an audio track, or should be.
session.rpcs[UUID].inboundAudioPipeline[trackid] = {};
session.rpcs[UUID].inboundAudioPipeline[trackid].mediaStream = createMediaStream();
session.rpcs[UUID].inboundAudioPipeline[trackid].mediaStream.addTrack(track);
if (ChromeVersion && session.audioEffects){ // I'm going to deprecate this.
session.rpcs[UUID].inboundAudioPipeline[trackid].mutedAudio = createAudioElement(); // TODO: I don't know if this mutedAudio thing matters any more, in recent versions of Chrome, since it won't play even if muted.
session.rpcs[UUID].inboundAudioPipeline[trackid].mutedAudio.muted = true;
session.rpcs[UUID].inboundAudioPipeline[trackid].mutedAudio.playsinline = true; // ## Added Oct 9th 2022. Not sure it's does anything, but might help with iPhones?
session.rpcs[UUID].inboundAudioPipeline[trackid].mutedAudio.srcObject = session.rpcs[UUID].inboundAudioPipeline[trackid].mediaStream; // needs to be added as an streamed element to be usable, even if its hidden
session.rpcs[UUID].inboundAudioPipeline[trackid].mutedAudio.muted = true;
//session.rpcs[UUID].inboundAudioPipeline[trackid].mutedAudio.volume = 0.01;
session.rpcs[UUID].inboundAudioPipeline[trackid].mutedAudio.play().then(_ => {
//session.rpcs[UUID].inboundAudioPipeline[trackid].mutedAudio.muted = false;
log("playing 1");
}).catch(warnlog);
}
// https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/createMediaStreamTrackSource
var source = session.audioCtx.createMediaStreamSource(session.rpcs[UUID].inboundAudioPipeline[trackid].mediaStream);
//////////////////
var screwedUp = false;
session.rpcs[UUID].inboundAudioPipeline[trackid].destination = false;
if (session.sync!==false){
log("adding a delay node to audio");
source = addDelayNode( source, UUID, trackid);
screwedUp = true;
}
if (session.style===2){
log("adding a fftwave node to audio");
try {
if (session.rpcs[UUID].inboundAudioPipeline[trackid]){ // clear audioMeterGuest, if active.
clearTimeout(session.rpcs[UUID].inboundAudioPipeline[trackid].analyser.interval);
}
} catch(e){ }
source = fftWaveform( source, UUID, trackid);
} else if (session.style===3 || session.meterStyle){
log("adding a loudness meter node to audio");
source = audioMeterGuest(source, UUID, trackid);
} else if (session.audioMeterGuest){
log("adding a loudness meter node to audio");
source = audioMeterGuest(source, UUID, trackid);
} else if (session.activeSpeaker){
log("adding a loudness meter node to audio");
source = audioMeterGuest(source, UUID, trackid);
} else if (session.quietOthers){
log("adding a loudness meter node to audio");
source = audioMeterGuest(source, UUID, trackid);
} else if (session.pushLoudness){
source = audioMeterGuest(source, UUID, trackid);
} else {
try {
if (session.rpcs[UUID].inboundAudioPipeline[trackid]){ // nothign active, so clear
clearTimeout(session.rpcs[UUID].inboundAudioPipeline[trackid].analyser.interval);
}
} catch(e){ }
}
if (session.playChannel){
session.rpcs[UUID].inboundAudioPipeline[trackid].destination = session.audioCtx.createMediaStreamDestination();
source = selectChannel( session.rpcs[UUID].inboundAudioPipeline[trackid].destination, source, session.playChannel);
screwedUp = true;
} else if (session.rpcs[UUID].channelOffset !== false){
log("custom offset set");
session.rpcs[UUID].inboundAudioPipeline[trackid].destination = session.audioCtx.createMediaStreamDestination();
source = offsetChannel( session.rpcs[UUID].inboundAudioPipeline[trackid].destination, source, session.rpcs[UUID].channelOffset, session.rpcs[UUID].channelWidth);
screwedUp = true;
} else if (session.offsetChannel !== false){ // proably better to do this last.
log("adding offset channels");
session.rpcs[UUID].inboundAudioPipeline[trackid].destination = session.audioCtx.createMediaStreamDestination();
source = offsetChannel( session.rpcs[UUID].inboundAudioPipeline[trackid].destination, source, session.offsetChannel, session.channelWidth);
screwedUp = true;
} else if (session.panning !== false){ // proably better to do this last.
log("adding offset channels");
session.rpcs[UUID].inboundAudioPipeline[trackid].destination = session.audioCtx.createMediaStreamDestination();
source = stereoPanning( source, UUID, trackid, session.panning);
screwedUp = true;
} else if (session.rpcs[UUID].videoElement && session.rpcs[UUID].videoElement.manualSink){
screwedUp = true; // added June-3-22 to allow for custom outputs to different audio output destinations.
}
if (screwedUp){
warnlog("screwedUp mode activated. dun dun");
if (session.rpcs[UUID].inboundAudioPipeline[trackid].destination===false){
session.rpcs[UUID].inboundAudioPipeline[trackid].destination = session.audioCtx.createMediaStreamDestination();
}
source.connect(session.rpcs[UUID].inboundAudioPipeline[trackid].destination);
try {
if (session.firstPlayTriggered && (session.audioCtx.state == "suspended")){
log("trying to resume..");
session.audioCtx.resume();
}
} catch(e){warnlog("session.audioCtx.resume(); failed");}
return session.rpcs[UUID].inboundAudioPipeline[trackid].destination.stream.getAudioTracks()[0];
}
try {
if (session.firstPlayTriggered && (session.audioCtx.state == "suspended")){
session.audioCtx.resume();
}
} catch(e){warnlog("session.audioCtx.resume(); failed 2");}
return track;
} catch(e) {errorlog(e);}
return track;
}
function processMiniInfoUpdate(miniInfo, UUID){
if ("qlr" in miniInfo){
session.rpcs[UUID].stats.info.quality_limitation_reason = miniInfo.qlr;
}
if ("con" in miniInfo){
session.rpcs[UUID].stats.info.conn_type = miniInfo.con;
}
if ("cpu" in miniInfo){
session.rpcs[UUID].stats.info.cpuLimited = miniInfo.cpu;
if (session.rpcs[UUID].signalMeter){
if (miniInfo.cpu){
session.rpcs[UUID].signalMeter.dataset.cpu = "1";
} else if ("cpu" in miniInfo){
session.rpcs[UUID].signalMeter.dataset.cpu = "0";
}
}
}
if ("hw_enc" in miniInfo){
session.rpcs[UUID].stats.info.hardware_video_encoder = miniInfo.hw_enc;
}
if ("bat" in miniInfo){
if (typeof miniInfo.bat == "number"){
session.rpcs[UUID].stats.info.power_level = miniInfo.bat*100;
} else {
session.rpcs[UUID].stats.info.power_level = null;
}
}
if ("chrg" in miniInfo){
session.rpcs[UUID].stats.info.plugged_in = miniInfo.chrg;
}
if (("out" in miniInfo) && ("c" in miniInfo.out)){
session.rpcs[UUID].stats.info.total_outbound_p2p_connections = miniInfo.out.c;
if (session.showConnections && session.rpcs[UUID].connectionDetails){
session.rpcs[UUID].connectionDetails.innerText = "🔗"+session.rpcs[UUID].stats.info.total_outbound_p2p_connections;
session.rpcs[UUID].connectionDetails.dataset.value = session.rpcs[UUID].stats.info.total_outbound_p2p_connections;
}
}
if (session.rpcs[UUID].batteryMeter){
batteryMeterInfoUpdate(UUID);
}
}
function batteryMeterInfoUpdate(UUID){
if (session.rpcs[UUID].stats.info && (session.rpcs[UUID].stats.info.power_level!==null)){
var level = session.rpcs[UUID].batteryMeter.querySelector(".battery-level");
if (level){
var value = session.rpcs[UUID].stats.info.power_level;
if (value > 100){value = 100;}
if (value < 0){ value = 0;}
level.style.height = parseInt(value)+"%";
if (value<15){
session.rpcs[UUID].batteryMeter.classList.remove("warn");
session.rpcs[UUID].batteryMeter.classList.add("alert");
} else if (value<25){
session.rpcs[UUID].batteryMeter.classList.remove("alert");
session.rpcs[UUID].batteryMeter.classList.add("warn");
} else {
session.rpcs[UUID].batteryMeter.classList.remove("alert");
session.rpcs[UUID].batteryMeter.classList.remove("warn");
}
if (value<100){
session.rpcs[UUID].batteryMeter.classList.remove("hidden");
}
//session.rpcs[UUID].batteryMeter.title = value+"% battery remaining";
session.rpcs[UUID].batteryMeter.title = parseInt(value)+"% battery remaining";
}
}
if (session.rpcs[UUID].stats.info && ("plugged_in" in session.rpcs[UUID].stats.info) && (session.rpcs[UUID].stats.info.plugged_in===false)){
session.rpcs[UUID].batteryMeter.dataset.plugged = "0";
session.rpcs[UUID].batteryMeter.classList.remove("hidden");
} else {
session.rpcs[UUID].batteryMeter.dataset.plugged = "1";
// add on
session.rpcs[UUID].batteryMeter.title = parseInt(value)+"% charging";
session.rpcs[UUID].batteryMeter.classList.add("hidden");
}
}
function updateLabelDirectors(UUID){
var elements = getById("label_"+UUID);
if (session.rpcs[UUID].label){
elements.innerText = session.rpcs[UUID].label;
elements.classList.remove("addALabel");
} else if (session.directorUUID === UUID){
elements.innerText = miscTranslations["main-director"];
elements.classList.remove("addALabel");
} else {
elements.innerText = miscTranslations["add-a-label"];
elements.classList.add("addALabel");
}
}
function updateLabelDirectors2(UUID){
var elements = getById("label_"+UUID);
if (session.directorUUID === UUID){
elements.innerText = miscTranslations["main-director"];
elements.classList.remove("addALabel");
} else {
elements.innerText = miscTranslations["add-a-label"];
elements.classList.add("addALabel");
}
}
function directorCoDirectorColoring(UUID){
if (UUID === session.directorUUID){
try{
session.rpcs[UUID].stats.info.director = true;
getById("container_"+UUID).classList.add("directorBox");
} catch(e){}
} else if (session.directorList.indexOf(UUID)>=0){
try{
session.rpcs[UUID].stats.info.coDirector = true;
addDirectorBlue(UUID);
} catch(e){}
}
}
function addDirectorBlue(UUID){
getById("container_"+UUID).classList.add("directorBlue");
}
function soloLinkGeneratorInit(UUID){
document.querySelectorAll("container_"+UUID).forEach(ele=>{
ele.querySelectorAll("[data-sololink]").forEach(ele2=>{ // value='" + soloLink + "' href='" + soloLink + "'/>" + soloLink + "
var soloLink = soloLinkGenerator(session.rpcs[UUID].streamID, false);
ele2.value = soloLink;
ele2.href = soloLink;
ele2.innerText = soloLink;
});
});
}
function initRecordingImpossible(UUID){
var ele = document.querySelectorAll('[data-action-type="mute-guest"][data--u-u-i-d="'+UUID+'"]');
if (ele){
ele.disabled = true;
ele.title = miscTranslations["Audio processing is disabled with this guest. Can't mute or change volume"];
}
var ele = document.querySelectorAll('[data-action-type="volume"][data--u-u-i-d="'+UUID+'"]');
if (ele){
ele.disabled = true;
ele.title = title = miscTranslations["Audio processing is disabled with this guest. Can't mute or change volume"];
ele.style.opacity = 0.2;
}
}
function initAudioButtons(audioGain, UUID){
if (audioGain===0){
var ele = document.querySelector('[data-action-type="mute-guest"][data--u-u-i-d="'+UUID+'"]');
if (ele){
ele.value = 1;
ele.classList.add("pressed"); ele.ariaPressed = "true";
ele.children[1].innerHTML = miscTranslations["unmute"] || "Unmute";
//ele.title = miscTranslations["unmute-guest"];
session.rpcs[UUID].directorMutedState = 1;
}
pokeIframeAPI("director-mute-state", true, UUID);
} else {
var ele = document.querySelector('[data-action-type="volume"][data--u-u-i-d="'+UUID+'"]');
if (ele){
ele.value = audioGain;
session.rpcs[UUID].directorVolumeState = audioGain;
remoteVolumeUI(ele);
}
}
}
function initGroupButtons(UUID){
var elements = document.querySelectorAll('[data-action-type="toggle-group"][data--u-u-i-d="'+UUID+'"]');
for (var i=0;i -1){
session.group.splice(index, 1);
change=true;
}
} else if (ele.classList.contains("pressed")){
ele.classList.remove("pressed"); ele.ariaPressed = "false";
if (index > -1){
session.group.splice(index, 1);
change=true;
}
} else {
ele.classList.add("pressed"); ele.ariaPressed = "true";
if (index === -1){
session.group.push(group);
change=true;
}
}
if (session.group.length || session.allowNoGroup){
session.sendMessage({"group":session.group.join(",")});
} else {
session.sendMessage({"group":false});
}
if (change){
pokeIframeAPI("group-set-updated", session.group);
}
if (session.group.indexOf(group)===-1){
return false;
} else {
return true;
}
}
function changeGroupDirectorAPI(group, state=null, update=true){
log("changeGroupDirectorAPI()");
group = sanitizeLabel(group);
if (document.getElementById("container_director")){
var ele = getById("container_director").querySelector('[data-action-type="toggle-group"][data-group="'+group+'"]');
if (ele){
if (update){
ele.click();
} else if (state===true){
ele.classList.add("pressed"); ele.ariaPressed = "true";
}
if (session.group.indexOf(group)===-1){
return false;
} else {
return true;
}
}
}
var index = session.group.indexOf(group);
var eleGroup = getById("groups");
eleGroup.classList.remove("hidden");
var ele = eleGroup.querySelector('[data-action-type="toggle-group"][data-group="'+group+'"');
if (eleGroup.showDirector){
if (!ele){
ele = htmlToElement('');
var added = false;
eleGroup.querySelectorAll('[data-group]').forEach(ele2=>{
log(ele2);
if (!added && ele2.dataset.group>group+""){
ele2.parentNode.insertBefore(ele, ele2);
added = true;
}
});
if (!added){
eleGroup.appendChild(ele);
}
}
} else if (!ele){
ele = document.createElement("div");
ele.dataset.actionType = "toggle-group";
ele.dataset.group = group;
ele.classList.add('float');
ele.style.display = "inline-block";
ele.role = "button";
ele.innerHTML = ' '+group;
eleGroup.appendChild(ele);
ele.onclick = function(){
changeGroupDirectorAPI(this.dataset.group);
}
}
var changed = false;
if (state===true){
if (eleGroup.showDirector){
ele.classList.add("pressed"); ele.ariaPressed = "true";
} else {
ele.classList.add("green"); ele.ariaPressed = "true";
}
if (index === -1){
session.group.push(group);
changed=true;
}
} else if (state === false){
if (eleGroup.showDirector){
ele.classList.remove("pressed"); ele.ariaPressed = "false";
} else {
ele.classList.remove("green"); ele.ariaPressed = "false";
}
if (index > -1){
session.group.splice(index, 1);
changed=true;
}
} else if (ele.classList.contains("green")){
if (eleGroup.showDirector){
ele.classList.remove("pressed"); ele.ariaPressed = "false";
} else {
ele.classList.remove("green"); ele.ariaPressed = "false";
}
if (index > -1){
session.group.splice(index, 1);
changed=true;
}
} else {
if (eleGroup.showDirector){
ele.classList.add("pressed"); ele.ariaPressed = "true";
} else {
ele.classList.add("green"); ele.ariaPressed = "true";
}
if (index === -1){
session.group.push(group);
changed=true;
}
}
if (update){
if (session.group.length || session.allowNoGroup){
session.sendMessage({"group":session.group.join(",")});
} else {
session.sendMessage({"group":false});
}
}
if (changed){
pokeIframeAPI("group-set-updated", session.group);
}
if (state!==null){
return true;
} else if (session.group.indexOf(group)===-1){
return false;
} else {
return true;
}
}
function changeGroupViewDirectorAPI(group, state=null){
log("changeGroupViewDirectorAPI()");
group = sanitizeLabel(group);
var index = session.groupView.indexOf(group);
var changed = false;
if (state===true){
if (index === -1){
session.groupView.push(group);
changed=true;
}
} else if (state === false){
if (index > -1){
session.groupView.splice(index, 1);
changed=true;
}
} else {
if (index > -1){
session.groupView.splice(index, 1);
} else {
session.groupView.push(group);
}
changed=true;
}
if (changed){
pokeIframeAPI("group-view-set-updated", session.groupView);
}
if (state!==null){
return true;
} else if (session.groupView.indexOf(group)===-1){
return false;
} else {
return true;
}
}
function changeGroup(ele, state=null){
var group = ele.dataset.group;
var index = session.rpcs[ele.dataset.UUID].group.indexOf(group);
if (state===true){
ele.classList.add("pressed"); ele.ariaPressed = "true";
if (index === -1){
session.rpcs[ele.dataset.UUID].group.push(group);
}
} else if (state === false){
ele.classList.remove("pressed"); ele.ariaPressed = "false";
if (index > -1){
session.rpcs[ele.dataset.UUID].group.splice(index, 1);
}
} else if (ele.classList.contains("pressed")){
ele.classList.remove("pressed"); ele.ariaPressed = "false";
if (index > -1){
session.rpcs[ele.dataset.UUID].group.splice(index, 1);
}
} else {
ele.classList.add("pressed"); ele.ariaPressed = "true";
if (index === -1){
session.rpcs[ele.dataset.UUID].group.push(group);
}
}
if (session.rpcs[ele.dataset.UUID].group.length){
session.sendRequest({"group":session.rpcs[ele.dataset.UUID].group.join(",")}, ele.dataset.UUID);
} else {
session.sendRequest({"group":false}, ele.dataset.UUID);
}
syncDirectorState(ele);
if (session.rpcs[ele.dataset.UUID].group.indexOf(group)===-1){
return false;
} else {
return true;
}
}
function changeChannelOffset(UUID, channel){
var ele = document.querySelectorAll('[data-action-type="add-channel"][data--u-u-i-d="' + UUID + '"]');
for (var i=0;i1){value=1;}
}
//// some reverb logic goes here...
///var reverbNode = session.audioCtx.createStereoPanner();
///session.rpcs[UUID].inboundAudioPipeline[trackid].reverbNode = reverbNode;
////
source.connect(reverbNode);
return reverbNode;
}
function stereoPanning(source, UUID, trackid, value){
if (parseInt(value) === -1){
value = Math.random() * (Math.random()*2-1);
warnlog(value);
} else if (value === false){
return source;
} else if (value === true){
value = 90;
} else {
value = parseFloat(value/90) -1 || 0;
if (value<-1){value=-1;}
if (value>1){value=1;}
}
var gainNode = session.audioCtx.createGain();
session.rpcs[UUID].inboundAudioPipeline[trackid].gainPanNode = gainNode;
gainNode.value = (1-Math.abs(value)/2); // the stereo panner seems to make things extra loud, so they clip. REDUCE IT.
source.connect(gainNode);
var panNode = session.audioCtx.createStereoPanner();
session.rpcs[UUID].inboundAudioPipeline[trackid].panNode = panNode;
panNode.pan.value = value;
gainNode.connect(panNode);
return panNode;
}
function adjustPan(UUID, value){
if (value === true){
value = Math.random() * (Math.random()*2-1);
} else if (value === false){
value=0;
} else {
value = parseFloat(value/90) -1 || 0;
if (value<-1){value=-1;}
if (value>1){value=1;}
}
for (var trackid in session.rpcs[UUID].inboundAudioPipeline){
if ("panNode" in session.rpcs[UUID].inboundAudioPipeline[trackid]){
session.rpcs[UUID].inboundAudioPipeline[trackid].panNode.pan.setValueAtTime(value, session.audioCtx.currentTime);
}
if ("gainPanNode" in session.rpcs[UUID].inboundAudioPipeline[trackid]){
session.rpcs[UUID].inboundAudioPipeline[trackid].gainPanNode.setValueAtTime((1-Math.abs(value)/2), session.audioCtx.currentTime);
}
}
}
function addDelayNode(source, UUID, trackid){ // append the delay Node to the track??? WOULD THIS WORK?
var delay = parseFloat(session.sync) || 0;
if (delay<0){delay=0;}
if (session.buffer && (session.buffer>0)){
delay += parseFloat(session.buffer);
}
delay = delay/1000;
session.rpcs[UUID].inboundAudioPipeline[trackid].delayNode = session.audioCtx.createDelay(delay+5);// 5 seconds additionally added for the purpose of flexibility
session.rpcs[UUID].inboundAudioPipeline[trackid].delayNode.delayTime.value = delay; // delayTime takes it in seconds.
source.connect(session.rpcs[UUID].inboundAudioPipeline[trackid].delayNode);
log("added new delay node");
return session.rpcs[UUID].inboundAudioPipeline[trackid].delayNode;
}
function createStyleCanvas(UUID){ // append the delay Node to the track??? WOULD THIS WORK?
if (!session.rpcs[UUID].canvas){ // just make sure that if using &effects or something, to null the canvas after use, else this won't trigger.
session.rpcs[UUID].canvas = document.createElement("canvas");
session.rpcs[UUID].canvas.dataset.UUID = UUID
if (session.rpcs[UUID].streamID){
session.rpcs[UUID].canvas.dataset.sid = session.rpcs[UUID].streamID;
}
session.rpcs[UUID].canvas.style.pointerEvents = "auto";
session.rpcs[UUID].canvasCtx = session.rpcs[UUID].canvas.getContext('2d', { alpha: session.alpha });
//
session.rpcs[UUID].canvas.addEventListener('click', function(e) { // show stats of video if double clicked
log("clicked");
try {
var uid = e.currentTarget.dataset.UUID;
if ((e.ctrlKey)||(e.metaKey)){
e.preventDefault();
if ("stats" in session.rpcs[uid]){
var [menu, innerMenu] = statsMenuCreator();
printViewStats(innerMenu, uid );
menu.interval = setInterval(printViewStats, session.statsInterval, innerMenu, uid);
}
e.stopPropagation();
return false;
} else if ("prePausedBandwidth" in session.rpcs[uid]){
unPauseVideo(e.currentTarget);
}
} catch(e){errorlog(e);}
});
if (session.statsMenu){
if ("stats" in session.rpcs[UUID]){
var [menu, innerMenu] = statsMenuCreator();
printViewStats(innerMenu, UUID );
menu.interval = setInterval(printViewStats, session.statsInterval, innerMenu, UUID);
}
}
if (session.aspectRatio){
if (session.aspectRatio==1){
session.rpcs[UUID].canvas.width="720";
session.rpcs[UUID].canvas.height="1280";
} else if (session.aspectRatio==2){
session.rpcs[UUID].canvas.width="960";
session.rpcs[UUID].canvas.height="960";
} else if (session.aspectRatio==3){
session.rpcs[UUID].canvas.width="1280";
session.rpcs[UUID].canvas.height="960";
}
} else {
session.rpcs[UUID].canvas.width="1280";
session.rpcs[UUID].canvas.height="720";
}
updateMixer();
return true;
} else {
return false;
}
}
function applyStyleEffect(UUID){
if (!session.rpcs[UUID].canvas || !session.rpcs[UUID].canvasCtx){return;}
/* session.rpcs[UUID].canvasContainer = document.createElement("div");
session.rpcs[UUID].canvasContainer.appendChild(session.rpcs[UUID].canvas);
session.rpcs[UUID].canvas.style = "width:100%;height:100%;display:block;";
session.rpcs[UUID].canvasContainer.appendChild(session.rpcs[UUID].videoElement); */
if (session.style==3){ // black
session.rpcs[UUID].canvasCtx.fillStyle = "rgb(0, 0, 0)";
session.rpcs[UUID].canvasCtx.fillRect(0, 0, session.rpcs[UUID].canvas.width, session.rpcs[UUID].canvas.height);
} else if (session.style==4){
session.rpcs[UUID].canvasCtx.fillStyle = "rgb(0, 0, 0)";
session.rpcs[UUID].canvasCtx.fillRect(0, 0, session.rpcs[UUID].canvas.width, session.rpcs[UUID].canvas.height);
} else if (session.style==5){
var r = Math.random()*255;
var g = Math.random()*255;
var b = Math.random()*255;
session.rpcs[UUID].canvasCtx.fillStyle = "rgb("+r+", "+g+", "+b+")";
session.rpcs[UUID].canvasCtx.fillRect(0, 0, session.rpcs[UUID].canvas.width, session.rpcs[UUID].canvas.height);
} else if (session.style==6){
session.rpcs[UUID].canvasCtx.fillStyle = "rgb(0,0,0)";
session.rpcs[UUID].canvasCtx.fillRect(0, 0, session.rpcs[UUID].canvas.width, session.rpcs[UUID].canvas.height);
var r = Math.random()*150+50;
var g = Math.random()*150+50;
var b = Math.random()*150+50;
session.rpcs[UUID].canvasCtx.fillStyle = "rgb("+r+", "+g+", "+b+")";
session.rpcs[UUID].canvasCtx.beginPath();
session.rpcs[UUID].canvasCtx.arc(parseInt(session.rpcs[UUID].canvas.width/2), parseInt(session.rpcs[UUID].canvas.height/2), parseInt(session.rpcs[UUID].canvas.height/4), 0, 2 * Math.PI, false);
session.rpcs[UUID].canvasCtx.fill();
if (session.rpcs[UUID].label){
session.rpcs[UUID].canvasCtx.fillStyle = "rgb(0,0,0)";
session.rpcs[UUID].canvasCtx.textAlign = "center";
session.rpcs[UUID].canvasCtx.font = parseInt(session.rpcs[UUID].canvas.height/2.11)+"px Arial";
session.rpcs[UUID].canvasCtx.fillText(session.rpcs[UUID].label[0].toUpperCase(), parseInt(session.rpcs[UUID].canvas.width/2), parseInt(session.rpcs[UUID].canvas.height*2/3));
} else {
var tmp = getComputedStyle(document.querySelector(':root')).getPropertyValue('--video-background-image').split('"');
if (tmp.length===3){
var img = new Image();
img.onload = function() {
session.rpcs[UUID].canvasCtx.fillStyle = "rgb(25,0,0)";
session.rpcs[UUID].canvasCtx.drawImage(img, parseInt(session.rpcs[UUID].canvas.width/2-110), parseInt(session.rpcs[UUID].canvas.height/2-110),220,220);
}
img.src = tmp[1];
}
}
}
}
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function fftWaveform( source, UUID, trackid){ // append the delay Node to the track??? WOULD THIS WORK?
// https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode
session.rpcs[UUID].inboundAudioPipeline[trackid].analyser = session.audioCtx.createAnalyser();
session.rpcs[UUID].inboundAudioPipeline[trackid].analyser.fftSize = 512;
var bufferLength = session.rpcs[UUID].inboundAudioPipeline[trackid].analyser.frequencyBinCount;
var dataArray = new Uint8Array(bufferLength);
session.rpcs[UUID].inboundAudioPipeline[trackid].analyser.getByteTimeDomainData(dataArray);
// analyser.getByteTimeDomainData(dataArray);
source.connect(session.rpcs[UUID].inboundAudioPipeline[trackid].analyser);
createStyleCanvas(UUID);
clearInterval(session.rpcs[UUID].canvasIntervalAction);
var canvasIntervalAction = setInterval(function(uuid){
if (session.style!==2){
clearInterval(canvasIntervalAction); // this is FFT only, so okay to kill.
return;
}
try{
session.rpcs[uuid].inboundAudioPipeline[trackid].analyser.getByteTimeDomainData(dataArray);
session.rpcs[uuid].canvasCtx.fillStyle = "rgba(0, 0, 0, 0.1)";
session.rpcs[uuid].canvasCtx.fillRect(0, 0, session.rpcs[uuid].canvas.width, session.rpcs[uuid].canvas.height);
session.rpcs[uuid].canvasCtx.lineWidth = 10;
session.rpcs[uuid].canvasCtx.strokeStyle = "rgb(111, 255, 111)";
var sliceWidth = session.rpcs[uuid].canvas.width * 1.0 / bufferLength;
var loudness = dataArray;
var Squares = loudness.map((val) => ((val-128.0)*(val-128.0)));
var Sum = Squares.reduce((acum, val) => (acum + val));
var Mean = Sum/loudness.length;
loudness = Math.sqrt(Mean)*10;
session.rpcs[uuid].stats.Audio_Loudness = parseInt(loudness);
if (session.pushLoudness==true){
var loudnessObj = {};
loudnessObj[session.rpcs[uuid].streamID] = session.rpcs[uuid].stats.Audio_Loudness;
if (isIFrame){
parent.postMessage({"loudness": loudnessObj, "action":"loudness", "value":loudness, "UUID":uuid}, session.iframetarget);
}
}
if (loudness<2){return;}
//log(bufferLength);
session.rpcs[uuid].canvasCtx.beginPath();
var m = session.rpcs[uuid].canvas.height / 256.0;
session.rpcs[uuid].canvasCtx.moveTo(0, dataArray[0]*m);
var x = 0;
for (var i = 1; i < bufferLength; i++){
var y = dataArray[i] * m;
session.rpcs[uuid].canvasCtx.lineTo(x, y);
x += sliceWidth;
}
session.rpcs[uuid].canvasCtx.lineTo(session.rpcs[uuid].canvas.width, session.rpcs[uuid].canvas.height / 2);
session.rpcs[uuid].canvasCtx.stroke();
} catch(e){
warnlog(e);
warnlog("Did the remote source disconnect?");
clearInterval(canvasIntervalAction);
warnlog(session.rpcs[uuid]);
}
},50, UUID);
session.rpcs[UUID].canvasIntervalAction = canvasIntervalAction;
return session.rpcs[UUID].inboundAudioPipeline[trackid].analyser;
}
function audioMeterGuest(mediaStreamSource, UUID, trackid){
log("audioMeterGuest started");
session.rpcs[UUID].inboundAudioPipeline[trackid].analyser = session.audioCtx.createAnalyser();
mediaStreamSource.connect(session.rpcs[UUID].inboundAudioPipeline[trackid].analyser);
session.rpcs[UUID].inboundAudioPipeline[trackid].analyser.fftSize = 256;
session.rpcs[UUID].inboundAudioPipeline[trackid].analyser.smoothingTimeConstant = 0.05;
var bufferLength = session.rpcs[UUID].inboundAudioPipeline[trackid].analyser.frequencyBinCount;
var dataArray = new Uint8Array(bufferLength);
function updateLevels() {
try {
session.rpcs[UUID].inboundAudioPipeline[trackid].analyser.getByteFrequencyData(dataArray);
var total = 0;
for (var i = 0; i < dataArray.length; i++){
total += dataArray[i];
}
total = parseInt(total/150);
session.rpcs[UUID].stats.Audio_Loudness = total;
if (session.pushLoudness==true){
var loudnessObj = {};
loudnessObj[session.rpcs[UUID].streamID] = session.rpcs[UUID].stats.Audio_Loudness;
if (isIFrame){
parent.postMessage({"loudness": loudnessObj, "action":"loudness", "value":session.rpcs[UUID].stats.Audio_Loudness, "UUID":UUID}, session.iframetarget);
}
}
try{
clearTimeout(session.rpcs[UUID].inboundAudioPipeline[trackid].analyser.interval);
session.rpcs[UUID].inboundAudioPipeline[trackid].analyser.interval = setTimeout(function(){updateLevels();},100);
} catch(e){
log("closing old inaudio pipeline");
}
if (session.style==3 || session.meterStyle){ // overrides style
if (session.rpcs[UUID].videoElement){
if (total>40){
session.rpcs[UUID].videoElement.dataset.speaking = "2";
} else if (total>10){
session.rpcs[UUID].videoElement.dataset.speaking = "1";
} else {
session.rpcs[UUID].videoElement.dataset.speaking = "0";
}
if (session.meterStyle==4){
session.rpcs[UUID].videoElement.dataset.loudness = total;
return; // this is cause we are using the data-loudness
}
} else if (session.meterStyle==4){
return;
}
} else if (session.scene!==false){ // if a scene, cancel
return;
} else if (session.audioMeterGuest===false){ // don't show if we just want the volume levels
return;
}
if (session.rpcs[UUID].voiceMeter){
session.rpcs[UUID].voiceMeter.dataset.level = total;
if (session.meterStyle==1){
var perct = Math.min(total,100);
session.rpcs[UUID].voiceMeter.style.height = perct + "%";
if (total>80){
var R = parseInt(255 * perct/100).toString(16).padStart(2, '0');
var G = parseInt(255 - 255 * perct /100).toString(16).padStart(2, '0');
session.rpcs[UUID].voiceMeter.style.backgroundColor = "#" + R + G + "00";
} else {
session.rpcs[UUID].voiceMeter.style.backgroundColor = "#00FF00";
}
} else {
if (total>15){
session.rpcs[UUID].voiceMeter.style.opacity = 100; // temporary
} else {
session.rpcs[UUID].voiceMeter.style.opacity = 0; // temporary
}
}
} else {
session.rpcs[UUID].voiceMeter = document.createElement("div");
session.rpcs[UUID].voiceMeter.id = "voiceMeter_"+UUID;
session.rpcs[UUID].voiceMeter.dataset.level = total;
if (session.meterStyle==1){
session.rpcs[UUID].voiceMeter.classList.add("video-meter2");
} else {
if (total>15){
session.rpcs[UUID].voiceMeter.style.opacity = 100; // temporary
} else {
session.rpcs[UUID].voiceMeter.style.opacity = 0; // temporary
}
if (session.meterStyle==2){
session.rpcs[UUID].voiceMeter.classList.add("video-meter-2");
} else {
session.rpcs[UUID].voiceMeter.classList.add("video-meter");
}
}
updateMixer();
}
} catch(e){
warnlog(e);
// fail as an exception; this is a control close.
return;
}
};
clearTimeout(session.rpcs[UUID].inboundAudioPipeline[trackid].analyser.interval);
session.rpcs[UUID].inboundAudioPipeline[trackid].analyser.interval = setTimeout(function(){updateLevels();},100);
return session.rpcs[UUID].inboundAudioPipeline[trackid].analyser;
}
function effectsDynamicallyUpdate(event, ele){
log("effectsDynamicallyUpdate");
session.effect = ele.options[ele.selectedIndex].value;
getById("selectImageTFLITE").style.display = "none";
getById("selectImageTFLITE3").style.display = "none";
getById("selectEffectAmount").style.display = "none";
getById("selectEffectAmount3").style.display = "none";
if (session.effect === "1"){
updateRenderOutpipe();
return;
}
if (session.effect === "7"){ // digitalZoom
getById("selectEffectAmount").style.display = "block";
getById("selectEffectAmount3").style.display = "block";
session.effectValue = 1.0;
getById("selectEffectAmountInput").min = 1;
getById("selectEffectAmountInput").max = 1.99;
getById("selectEffectAmountInput").step = 0.01
getById("selectEffectAmountInput3").min = 1;
getById("selectEffectAmountInput3").max = 1.99;
getById("selectEffectAmountInput3").step = 0.01
getById("selectEffectAmountInput").value = session.effectValue;
getById("selectEffectAmountInput3").value = session.effectValue;
updateRenderOutpipe();
return;
}
if (session.effect === "8"){ // like zoom but none
updateRenderOutpipe();
return;
}
if (session.effect == "3a"){
session.effect = "3";
session.effectValue = 5;
}
if ((session.effectValue_default===false) && (session.effect=="3")){
session.effectValue = 2;
} else {
session.effectValue = session.effectValue_default;
}
if (session.effect == "0" || !session.effect){
updateRenderOutpipe();
return;
} else if (session.effect === "3" || session.effect === "4"){
attemptTFLiteJsFileLoad();
if (!session.tfliteModule.looping){
updateRenderOutpipe();
}
if ((session.effect === "3") && (session.effectValue_default==false)){
getById("selectEffectAmount").style.display = "block";
getById("selectEffectAmount3").style.display = "block";
getById("selectEffectAmountInput").min = 0;
getById("selectEffectAmountInput").max = 20;
getById("selectEffectAmountInput").step = 1;
getById("selectEffectAmountInput3").min = 0;
getById("selectEffectAmountInput3").max = 20;
getById("selectEffectAmountInput3").step = 1;
getById("selectEffectAmountInput").value = session.effectValue;
getById("selectEffectAmountInput3").value = session.effectValue;
}
} else if (session.effect === "5"){
attemptTFLiteJsFileLoad();
if (!session.tfliteModule.looping){
updateRenderOutpipe();
}
loadTFLITEImages();
} else if (session.effect === "6"){
if (!gpgpuSupport){
if (!session.cleanOutput){
warnUser("Hardware acceleration isn't detected.
This effect will not work",4000,false);
return;
}
} else if (gpgpuSupport == "Google SwiftShader"){
if (!session.cleanOutput){
warnUser("Hardware acceleration isn't detected.
Please enable it for this effect to work correctly.
Settings -> Advanced -> System -> Use hardware-accleration", false, false);
}
return;
}
loadTensorflowJS();
updateRenderOutpipe();
//mainMeshMask();
} else {
//loadEffect(session.effect);
updateRenderOutpipe();
}
if ((session.permaid===false) && (session.roomid===false) && (session.view===false) && (session.director===false)){
updateURL("effects");
}
}
function loadTFLITEImages(){
if (session.effect!=="5"){return;} // only load if effects 5 is set.
if (session.defaultBackgroundImages){
try {
session.defaultBackgroundImages.reverse();
}catch(e){
errorlog("Could not process image list");
session.defaultBackgroundImages = false;
session.selectImageTFLITE_contents = getById("selectImageTFLITE_contents");
return;
}
session.defaultBackgroundImages.forEach(imgSrc=>{
try {
var img = document.createElement("img");
img.onerror = function(){this.style.display="none";}; // hide images that fail to load
img.crossOrigin = "Anonymous";
img.src = imgSrc;
img.style="max-width:130px;max-height:73.5px;display:inline-block;margin:10px;cursor:pointer;";
img.onclick=function(event){changeTFLiteImage(event, this);};
getById("selectImageTFLITE_contents").prepend(img);
} catch(e){};
});
session.defaultBackgroundImages = false;
session.selectImageTFLITE_contents = getById("selectImageTFLITE_contents");
} else if (!session.selectImageTFLITE_contents){
session.selectImageTFLITE_contents = getById("selectImageTFLITE_contents");
}
if (document.getElementById("selectImageTFLITE")){
document.getElementById("selectImageTFLITE").style.display = "block";
document.getElementById("selectImageTFLITE").appendChild(session.selectImageTFLITE_contents);
session.selectImageTFLITE_contents.classList.remove("hidden");
} else if (document.getElementById("selectImageTFLITE3")){
document.getElementById("selectImageTFLITE3").style.display = "block";
document.getElementById("selectImageTFLITE3").appendChild(session.selectImageTFLITE_contents);
session.selectImageTFLITE_contents.classList.remove("hidden");
}
}
var effectsLoaded = {};
var JEELIZFACEFILTER = null;
async function loadEffect(effect){
warnlog("effect:"+effect);
var filename = effect.replace(/\W/g, '');
if (effectsLoaded[filename]){
effectsLoaded[filename]();
return;
} else {
effectsLoaded[filename] = function(){};
}
warnlog("Loading Effect: "+effect);
var script = document.createElement('script');
script.onload = async function() {
log("LOADED EFFECT");
effectsLoaded[filename] = await effectsEngine(filename);
log("effectsEngine();");
if (gpgpuSupport == "Google SwiftShader"){
if (!session.cleanOutput){
warnUser("Hardware acceleration isn't detected.
Please enable it for better performance.
Settings -> Advanced -> System -> Use hardware-accleration", false, false);
}
}
effectsLoaded[filename]();
}
script.src = "./filters/"+filename+".js?"+parseInt(1000*Math.random());
document.head.appendChild(script);
warnUser("Loading custom effects model...",1000);
}
async function loadScript(url, callback=false){
var res = null;
var rej = null;
var promise = new Promise((resolve, reject) => {
res = resolve;
rej = reject;
});
var check = document.querySelector("script[src='"+url+"']");
if (check){
if(callback){callback();}
} else {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
script.onload = function(){
res();
if(callback){
callback();
}
};
document.head.appendChild(script);
}
return await promise;
}
var tokenClient=false;
function YoutubeChatInterface(remote=false){ // this lets us query Youtube for chat messages, but its quota limited :(
if (!tokenClient){
tokenClient=true;
} else {
return;
}
var gisInited = false;
var gapiInited = false;
var busy = 0;
function handleAuthClick() {
tokenClient.callback = async (resp) => {
if (resp.error){
errorlog(resp.error);
}
closeModal();
var auths = gapi.client.getToken();
if (auths){
setStorage("YoutubeAuth", JSON.stringify(auths), auths.expires_in || 3600);
}
listBroadcasts();
};
var saved = getStorage("YoutubeAuth");
if (saved){
gapi.client.setToken(JSON.parse(saved));
listBroadcasts();
} else if (gapi.client.getToken() === null) {
if (remote){
tokenClient.requestAccessToken({prompt:"consent"});
} else {
warnUser("", false, false);
}
} else {
if (remote){
tokenClient.requestAccessToken({prompt: ""});
} else {
warnUser("", false, false);
}
}
}
function maybeEnableButtons() {
if (gapiInited && gisInited){
handleAuthClick();
}
}
async function initializeGapiClient() {
await gapi.client.init({
apiKey: session.youtubeKey.split(",")[1],
discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/youtube/v3/rest'],
});
gapiInited = true;
maybeEnableButtons();
}
function handleSignoutClick() {
let token = gapi.client.getToken();
if (token !== null) {
google.accounts.oauth2.revoke(token.access_token);
gapi.client.setToken('');
}
}
async function listBroadcasts() {
try {
var response = await gapi.client.youtube.liveBroadcasts.list({
"broadcastStatus": "active"
});
} catch (err) {
errorlog(err);
return;
}
let broadcasts = response.result.items;
if (!broadcasts || broadcasts.length == 0) {
return;
}
broadcasts.forEach(broadcast=>{
setTimeout(function(liveChatId){
listMessages(liveChatId);
busy+=1;
},1000, broadcast.snippet.liveChatId);
});
}
async function listMessages(liveChatId, pageToken = false) {
try {
if (pageToken){
var response = await gapi.client.youtube.liveChatMessages.list({
"liveChatId": liveChatId,
"part": ["id", "snippet", "authorDetails"],
"pageToken": pageToken
});
} else {
var response = await gapi.client.youtube.liveChatMessages.list({
"liveChatId": liveChatId,
"part": ["id", "snippet", "authorDetails"]
});
}
var messages = response.result.items;
messages.forEach(msg =>{
pokeIframeAPI("YoutubeChat",msg);
});
var polling = response.result.pollingIntervalMillis;
var pageToken = response.result.nextPageToken;
if (busy>1){
// popular eh? Lets quickly check for more.
} else if (busy>0){ // a message ! hurrah
if (polling<2000){polling=2000;} // Was it just luck?
} else if (polling<5000){
polling=5000; // let's not spam the api, cause we know there isn't anything waiting..
}
busy=0; // reset
setTimeout(function(liveChatId,pageToken){
listMessages(liveChatId, pageToken);
}, polling, liveChatId, pageToken)
} catch (err) {
return;
}
}
function gisLoaded() {
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: session.youtubeKey.split(",")[0],
scope: 'https://www.googleapis.com/auth/youtube',
callback: '',
});
gisInited = true;
maybeEnableButtons();
}
function gapiLoaded() {
gapi.load('client', initializeGapiClient);
}
loadScript("https://apis.google.com/js/api.js",gapiLoaded);
loadScript("https://accounts.google.com/gsi/client",gisLoaded);
}
function loadTensorflowJS(){
if (session.TFJSModel!=null){
return;
}
log("loadTensorflowJS()");
session.TFJSModel=true;
var script = document.createElement('script');
var script2 = document.createElement('script');
var script3 = document.createElement('script');
var script4 = document.createElement('script');
script.onload = function() {
document.head.appendChild(script2);
}
script2.onload = function() {
document.head.appendChild(script3);
}
script3.onload = function() {
document.head.appendChild(script4);
}
script4.onload = function() {
async function loadModel(){
session.TFJSModel = await faceLandmarksDetection.load(faceLandmarksDetection.SupportedPackages.mediapipeFacemesh);
closeModal();
warnUser("Almost done loading model...",3000);
}
loadModel();
}
script.src = "./thirdparty/tfjs/tf-core.js";
script2.src = "./thirdparty/tfjs/tf-converter.js";
script3.src = "./thirdparty/tfjs/tf-backend-webgl.js";
script4.src = "./thirdparty/tfjs/face-landmarks-detection.js";
warnUser("Downloading a big effects model... may take a minute",15000);
script.type = 'text/javascript';script2.type = 'text/javascript';script3.type = 'text/javascript';script4.type = 'text/javascript';
document.head.appendChild(script);
}
var TFLITELOADING = false;
function attemptTFLiteJsFileLoad(){
if (session.tfliteModule!==false){
return true;
}
warnUser("Loading effects model...");
TFLITELOADING=true;
session.tfliteModule={};
if (!document.getElementById("tflitesimdjs")){
var tmpScript = document.createElement('script');
tmpScript.onload = loadTFLiteModel;
tmpScript.type = 'text/javascript';
tmpScript.src = "./thirdparty/tflite/tflite-simd.js?ver=2";
tmpScript.id = "tflitesimdjs";
document.head.appendChild(tmpScript);
}
return false;
}
async function changeTFLiteImage(ev, ele){
if (ele.files && ele.files[0]) {
if (session.tfliteModule.img){
session.tfliteModule.img.classList.remove("selectedTFImage");
}
session.tfliteModule.img = document.createElement("img");
session.tfliteModule.img.style="max-width:130px;max-height:73.5px;display:inline-block;margin:10px;cursor:pointer;";
session.tfliteModule.img.onclick=function(event){changeTFLiteImage(event, this);};
ele.parentNode.parentNode.insertBefore(session.tfliteModule.img, ele.parentNode);
session.tfliteModule.img.onload = () => {
URL.revokeObjectURL(session.tfliteModule.img.src); // no longer needed, free memory
}
session.tfliteModule.img.src = URL.createObjectURL(ele.files[0]); // set src to blob url
session.tfliteModule.img.classList.add("selectedTFImage");
} else if (ele.tagName.toLowerCase() == "img"){
session.tfliteModule.img.classList.remove("selectedTFImage");
session.tfliteModule.img = ele
session.tfliteModule.img.classList.add("selectedTFImage");
}
}
async function changeEffectAmount(ev, ele){
session.effectValue = ele.value;
if (ele.id === "selectEffectAmountInput"){
getById("selectEffectAmountInput3").value = ele.value
}
log("session.effectValue: "+session.effectValue);
}
async function loadTFLiteModel(){
try {
if (session.tfliteModule && (session.tfliteModule.img)){
var img = session.tfliteModule.img;
session.tfliteModule = await createTFLiteSIMDModule();
session.tfliteModule.img = img;
} else {
session.tfliteModule = {};
session.tfliteModule = await createTFLiteSIMDModule();
}
if (!session.tfliteModule.simd){
var elements = document.querySelectorAll('[data-warnSimdNotice]')
for (let i = 0; i < elements.length; i++) {
elements[i].style.display = "inline-block";
}
}
} catch(e){
warnlog("TF-LITE FAILED TO LOAD");
closeModal();
return;
}
const modelResponse = await fetch("./thirdparty/tflite/segm_full_v679.tflite");
session.tfliteModule.model = await modelResponse.arrayBuffer();
session.tfliteModule.HEAPU8.set(new Uint8Array(session.tfliteModule.model), session.tfliteModule._getModelBufferMemoryOffset());
session.tfliteModule._loadModel(session.tfliteModule.model.byteLength);
session.tfliteModule.activelyProcessing = false;
TFLITELOADING = false;
closeModal();
if (LaunchTFWorkerCallback){TFLiteWorker();}
}
function smdInfo(){
warnUser("For improved performance, use Chrome v87 or newer with SIMD support enabled. Enable SIMD here: chrome://flags/#enable-webassembly-simd", false, false);
}
function getGuestTarget(type, id){
var element = document.querySelectorAll('[data-action-type="'+type+'"][data-sid="'+id+'"]'); // data-sid="P5MQpia"
if (!element.length){
return element = getRightOrderedElement('[data-action-type="'+type+'"][data--u-u-i-d]', id);
} else {
element = element[0];
}
return element;
}
function getGuestTargetScene(scene, id){
var element = document.querySelectorAll('[data-action-type="addToScene"][data-scene="'+scene+'"][data-sid="'+id+'"]'); // data-sid="P5MQpia"
if (!element.length){
return element = getRightOrderedElement('[data-action-type="addToScene"][data-scene="'+scene+'"][data--u-u-i-d]', id);
} else {
element = element[0];
}
return element;
}
function getGuestTargetGroup(group, id){
var element = document.querySelectorAll('[data-action-type="toggle-group"][data-group="'+group+'"][data-sid="'+id+'"]'); // data-sid="P5MQpia"
if (!element.length){
return getRightOrderedElement('[data-action-type="toggle-group"][data-group="'+group+'"][data--u-u-i-d]', id);
} else {
element = element[0];
}
return element;
}
function targetGuest(target, action, value=null){
if (target){
if ((target == (parseInt(target)+"")) && target<100){
target -=1;
}
} else {
target=1;
}
warnlog("target "+target);
warnlog("action "+action);
warnlog("value "+value);
if ((action == 0) || (action == "forward")) {
var element = getGuestTarget("forward", target);
if (element) {
directMigrate(element, true, value); // if value is set, it will auto transfer the guest to that room.
}
} else if ((action == 1) || (action == "addScene")) {
var scene = 1;
if (value == "null" || value == null || value == "toggle"){
scene = 1;
} else if ((value !== true) && (value !== false)){
scene = value;
}
var element = getGuestTargetScene(scene, target); // oscid/action/target/value 1/1/scene
if (element) {
if (value===true){
element.value = 1;
} else if (value===false){
element.value = 0;
}
return directEnable(element, true); // false or true return
}
} else if ((action == 2) || (action == "muteScene")) {
var element = getGuestTarget("mute-scene", target);
if (element) {
if (value===true){
element.value = 1;
} else if (value===false){
element.value = 0;
}
return directMute(element, true); // false/true
}
} else if ((action == 3) || (action == "mic")) {
var element = getGuestTarget("mute-guest", target);
if (element) {
if (value===true){
element.value = 1;
} else if (value===false){
element.value = 0;
}
return remoteMute(element, true); // false/true
}
} else if ((action == 4) || (action == "hangup")) {
var element = getGuestTarget("hangup", target);
if (element) {
return directHangup(element, true); // false or true; false if confirmed no
}
} else if ((action == 5) || (action == "soloChat")) { // see soloChatBidirectional action=9 for two-way
var element = getGuestTarget("solo-chat", target);
if (element) {
if (value===true){
element.value = 1;
} else if (value===false){
element.value = 0;
}
return session.toggleSoloChat(element.dataset.UUID);
}
} else if ((action == 6) || (action == "speaker")) {
var element = getGuestTarget("toggle-remote-speaker", target);
if (element) {
if (value===true){
element.value = 1;
} else if (value===false){
element.value = 0;
}
return remoteSpeakerMute(element);
}
} else if ((action == 7) || (action == "display")) {
var element = getGuestTarget("toggle-remote-display", target);
if (element) {
if (value===true){
element.value = 1;
} else if (value===false){
element.value = 0;
}
return remoteDisplayMute(element);
}
} else if ((action == 8) || (action == "group")) {
if (value == "null" || value == null){
value = 1;
}
var element = getGuestTargetGroup(value, target);
if (element) {
return changeGroup(element, null, value);
}
} else if ((action == 9) || (action == "soloChatBidirectional")) {
var element = getGuestTarget("solo-chat", target);
if (element) {
var ctrl = {};
ctrl.ctrlKey = true;
if (value===true){
element.value = 1;
} else if (value===false){
element.value = 0;
}
return session.toggleSoloChat(element.dataset.UUID, ctrl);
}
} else if ((action == 12) || (action == "addScene2")) {
var element = getGuestTargetScene(2, target);
if (element) {
if (value===true){
element.value = 1;
} else if (value===false){
element.value = 0;
}
return directEnable(element, true)
}
} else if ((action == 13) || (action == "addScene3")) {
var element = getGuestTargetScene(3, target);
if (element) {
if (value===true){
element.value = 1;
} else if (value===false){
element.value = 0;
}
return directEnable(element, true)
}
} else if ((action == 14) || (action == "addScene4")) {
var element = getGuestTargetScene(4, target);
if (element) {
if (value===true){
element.value = 1;
} else if (value===false){
element.value = 0;
}
return directEnable(element, true)
}
} else if ((action == 15) || (action == "addScene5")) {
var element = getGuestTargetScene(5, target);
if (element) {
if (value===true){
element.value = 1;
} else if (value===false){
element.value = 0;
}
return directEnable(element, true)
}
} else if ((action == 16) || (action == "addScene6")) {
var element = getGuestTargetScene(6, target);
if (element) {
if (value===true){
element.value = 1;
} else if (value===false){
element.value = 0;
}
return directEnable(element, true)
}
} else if ((action == 17) || (action == "addScene7")) {
var element = getGuestTargetScene(7, target);
if (element) {
if (value===true){
element.value = 1;
} else if (value===false){
element.value = 0;
}
return directEnable(element, true)
}
} else if ((action == 18) || (action == "addScene8")) {
var element = getGuestTargetScene(8, target);
if (element) {
if (value===true){
element.value = 1;
} else if (value===false){
element.value = 0;
}
return directEnable(element, true)
}
} else if ((action == 19) || (action == "forceKeyframe")) {
var element = getGuestTarget("force-keyframe", target);
if (element) {
return requestKeyframeScene(element);
}
} else if ((action == 20) || (action == "soloVideo")) {
var element = getGuestTarget("solo-video", target);
if (element) {
if (value===true){
element.value = 1;
} else if (value===false){
element.value = 0;
}
return requestInfocus(element);
}
} else if ((action == 21) || (action == "sendChat")) {
var element = getGuestTarget("solo-video", target); // just something that probably exists.
if (element) {
return sendChat(value, element.dataset.UUID);
}
} else if ((action == 22) || (action == "sendDirectorChat")) {
var element = getGuestTarget("solo-video", target); // just something that probably exists.
if (element) {
return sendChat(value, element.dataset.UUID, true);
}
} else if ((action == 27) || (action == "volume")){
var element = getGuestTarget("volume", target);
if (element) {
element.value = parseInt(value) || 100;
return remoteVolume(element);
}
}
return false;
}
async function startPublishing(){
if (query("#publishOutURL input[type='text']").dataset.twitch=="true"){
session.whipOutput = "https://g.webrtc.live-video.net:4443/v2/offer";
} else {
session.whipOutput = query("#publishOutURL input[type='text']").value || session.whipOutput || null;
}
if (!session.whipOutput){
warnUser("Please first provided an output destination",2500);
return;
}
if (!session.whipOutputToken){
session.whipOutputToken = query("#publishOutToken input[type='password']").value || false;
}
if (!session.whipOutputToken && query("#publishOutURL input[type='text']").dataset.twitch=="true"){
warnUser("Please enter a Twitch stream token first",2000);
return;
}
getById("publishSettings").classList.add("hidden");
var ret = await publishScreen();
if (ret){
getById("publishSettings").classList.add("hidden");
resizeWindow(1280,720);
document.title="PUBLISHING🔴"+document.title;
} else {
getById("publishSettings").classList.remove("hidden");
}
}
function twitchSelect(ele){
if (ele.checked){
//query("#publishOutURL input[type='text']").value =
query("#publishOutURL input[type='text']").disabled = true;
query("#publishOutURL input[type='text']").classList.add("disable");
query("#publishOutURL input[type='text']").dataset.twitch = "true";
query("#publishOutToken input[type='password']").placeholder = "Twitch stream token here";
} else {
query("#publishOutURL input[type='text']").disabled = null;
query("#publishOutURL input[type='text']").classList.remove("disable");
delete getById("publishOutURL").disabled;
query("#publishOutURL input[type='text']").dataset.twitch = "false";
query("#publishOutToken input[type='password']").placeholder = "WHIP auth token here";
}
}
function resizeWindow(width, height){
if (window.outerWidth) {
window.resizeTo(
width + (window.outerWidth - window.innerWidth),
height + (window.outerHeight - window.innerHeight)
);
} else {
window.resizeTo(500, 500);
window.resizeTo(
width + (500 - document.body.offsetWidth),
height + (500 - document.body.offsetHeight)
);
}
setInterval(function(){
if ((window.innerWidth/window.innerHeight > 17/9) && (window.innerWidth/window.innerHeight < 15/9)){
return;
}
if (window.outerWidth) {
window.resizeTo(
width + (window.outerWidth - window.innerWidth),
height + (window.outerHeight - window.innerHeight)
);
} else {
window.resizeTo(500, 500);
window.resizeTo(
width + (500 - document.body.offsetWidth),
height + (500 - document.body.offsetHeight)
);
}
},5000);
}
function configureWhipOutSDP(description){ // THIS IS FOR WHIP-OUTPUT; it has
var configs = false;
if (SafariVersion && (SafariVersion<=13) && (iOS || iPad)){
// skip. Not going to try to tinker with older iOS SDPs
} else if ((session.stereo==3) || (session.stereo==5) || (session.stereo==6) || (session.stereo==1)){ // stereo out
configs = {
'stereo': 1,
'useinbandfec': session.noFEC ? 0 : 1,
'maxptime': session.maxptime,
'minptime': session.minptime,
'ptime': session.ptime,
'dtx': session.dtx // "usedtx", if no loud audio, stops sending audio for 400ms. default.
};
log("stereo enabled");
} else if (iOS || iPad){ // iOS doesn't have multichannel, so why even bother
configs = {
'useinbandfec': session.noFEC ? 0 : 1,
'maxptime': session.maxptime,
'minptime': session.minptime,
'ptime': session.ptime,
'dtx': session.dtx // "usedtx", if no loud audio, stops sending audio for 400ms. default.
};
} else if (session.stereo==4){
configs = {
'stereo': 2,
'useinbandfec': session.noFEC ? 0 : 1,
'maxptime': session.maxptime,
'minptime': session.minptime,
'ptime': session.ptime,
'dtx': session.dtx // "usedtx", if no loud audio, stops sending audio for 400ms. default.
};
log("stereo enabled");
} else {
configs = {
'stereo': 0,
'useinbandfec': session.noFEC ? 0 : 1,
'maxptime': session.maxptime,
'minptime': session.minptime,
'ptime': session.ptime,
'dtx': session.dtx // "usedtx", if no loud audio, stops sending audio for 400ms. default.
};
}
if (session.whipOutAudioBitrate){
if (!configs){
configs = {
'maxaveragebitrate': session.whipOutAudioBitrate * 1024,
'cbr': session.cbr
};
} else{
configs.maxaveragebitrate = session.whipOutAudioBitrate * 1024;
configs.cbr = session.cbr;
}
}
if (configs){
console.log("Processing sdp of type: "+description.type+ " ...");
description.sdp = CodecsHandler.setOpusAttributes(description.sdp, configs, true);
}
if (iOS || iPad){ // solves issues with iOS rotation not being correct
if (session.removeOrientationFlag && description.sdp.includes("a=extmap:3 urn:3gpp:video-orientation\r\n")){
description.sdp = description.sdp.replace('a=extmap:3 urn:3gpp:video-orientation\r\n', '');
}
}
if (typeof session.whipOutCodec === "object"){
session.whipOutCodec.reverse().forEach(codec=>{
description.sdp = CodecsHandler.preferCodec(description.sdp, codec);
if (session.whipOutVideoBitrate){
description.sdp = CodecsHandler.setVideoBitrates(description.sdp , {
min: parseInt(session.whipOutVideoBitrate/10) || 1,
max: session.whipOutVideoBitrate || 1
}, codec);
}
});
} else if (session.whipOutCodec){
description.sdp = CodecsHandler.preferCodec(description.sdp, session.whipOutCodec);
if (session.whipOutVideoBitrate){
description.sdp = CodecsHandler.setVideoBitrates(description.sdp , {
min: parseInt(session.whipOutVideoBitrate/10) || 1,
max: session.whipOutVideoBitrate || 1
}, session.whipOutCodec);
}
} else {
description.sdp = CodecsHandler.preferCodec(description.sdp,"h264"); // default
description.sdp = description.sdp.replace(/42001f/gi,"42e01f"); // openh264 set as default.
description.sdp = description.sdp.replace(/420029/gi,"42e01f");
if (session.whipOutVideoBitrate){
description.sdp = CodecsHandler.setVideoBitrates(description.sdp , {
min: parseInt(session.whipOutVideoBitrate/10) || 1,
max: session.whipOutVideoBitrate || 1
}, "h264");
}
}
return description;
}
function whipOut(){
log("whipOut");
var candidates = [];
var codec = false;
var keyframe = false;
async function whipConnect(){
try {
if (!session.configuration){
await chooseBestTURN();
}
session.whipOut = new RTCPeerConnection(session.configuration);
session.whipOut.stats = {};
session.whipOut.maxBandwidth = null; // based on max available bitrate
session.whipOut.scale = false;
session.whipOut.offerToReceiveAudio = false;
session.whipOut.offerToReceiveVideo = false;
} catch(err){
errorlog(err);
if (!session.cleanOutput){
warnUser("An RTC error occured");
}
}
try {
//if (session.meshcast!=="video"){
var tracks = false;
if (session.videoElement && session.videoElement.srcObject){
tracks = session.videoElement.srcObject.getAudioTracks();
}
var streamsource = false;
if (!tracks || !tracks.length){
var audioCtx = new AudioContext();
warnlog("No audio track; using a webaudio node instead");
var destination = audioCtx.createMediaStreamDestination();
streamsource = destination.stream;
destination.stream.getAudioTracks().forEach(trk=>{
tracks = trk;
});
} else {
tracks = tracks[0];
streamsource = session.videoElement.srcObject;
}
if (session.audioContentHint && (tracks.kind === "audio")){
try {
tracks.contentHint = session.audioContentHint;
} catch(e){
errorlog(e);
}
}
if (tracks){
try {
session.whipOut.addTransceiver(tracks, {
streams: [ streamsource ],
direction: 'sendonly'
});
} catch(e){
errorlog(e);
session.whipOut.addTrack(tracks);
}
}
//}
///////
//// video tracks
//if (session.meshcast!=="audio"){
var tracks = false;
if (session.videoElement && session.videoElement.srcObject){
tracks = session.videoElement.srcObject.getVideoTracks();
}
////
//if (!tracks || !tracks.length){
// tracks = getMeshcastCanvasTrack();
//} else {
tracks = tracks[0];
//}
if (session.screenShareState && session.screenshareContentHint && (tracks.kind === "video")){
try {
tracks.contentHint = session.screenshareContentHint;
} catch(e){
errorlog(e);
}
} else if (session.contentHint && (tracks.kind === "video")){
try {
tracks.contentHint = session.contentHint;
} catch(e){
errorlog(e);
}
}
if (tracks){
try {
session.whipOut.addTransceiver(tracks, {
streams: [ session.videoElement.srcObject ],
direction: 'sendonly'
});
} catch(e){
errorlog(e);
session.whipOut.addTrack(tracks);
}
}
//}
session.whipOut.onnegotiationneeded = publish; // bug: https://groups.google.com/forum/#!topic/discuss-webrtc/3-TmyjQ2SeE
session.whipOut.onicecandidate = function(event){ //event
if (event.candidate==null){
log("END OF ICE CANDIDATES");
return;
}
//log(event.candidate);
candidates.push(event.candidate);
};
} catch(e){errorlog(e);}
}
var publishing = false;
function publish(event){
if (publishing){
log(event);
errorlog("onnegotiationneeded again?");
return;
}
publishing = true;
warnlog("ON NEGO NEEDED");
warnlog(event);
try {
session.whipOut.createOffer().then(function(description){
try {
description = configureWhipOutSDP(description)
} catch(e){
errorlog(e);
}
return session.whipOut.setLocalDescription(description);
}).then(function() {
warnlog(session.whipOut.localDescription.sdp);
var sdp = session.whipOut.localDescription.sdp;
if (sdp.includes("sendrecv")){
errorlog("Should not include sendrecv");
sdp = sdp.replace("a=sendrecv","a=sendonly");
sdp = sdp.replace("v=sendrecv","v=sendonly");
}
ajax(sdp, "sdp");
}).catch(function(err){});
} catch(e){errorlog(e);}
}
function ajax(data, type, callback=false){
log("AJAX: "+type);
//log(data);
try {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && (this.status == 200 || this.status == 201)) {
var contentType = this.getResponseHeader('content-type');
if (contentType.startsWith("text/plain")){
console.warn("The WHIP output destination responded with an incorrect content type; will attempt to continue still.");
}
if (contentType.startsWith("application/sdp") || contentType.startsWith("text/plain")){
var jsep = {};
jsep.sdp = this.responseText;
jsep.type = "answer";
try {
jsep = configureWhipOutSDP(jsep);
} catch(e){
errorlog(e);
}
warnlog("Processing answer:");
warnlog(jsep);
session.whipOut.setRemoteDescription(jsep).then(function(){
warnlog("SHOULD BE CONNECTED?");
var content = "";
while (candidates.length){
var candidate = candidates.pop();
content += candidate.candidate;
}
if (content){
warnlog("SENDING TRICKLE");
if (!keyframe){
keyframe = setInterval(function(){GOP();},6000); // ensure GOP no longer than 6s
}
//ajax(content, "trickle-ice-sdpfrag", function(){
//});
}
}).catch(function(e){log(e);});
} else if (contentType == "application/error"){
if (this.responseText==432){
warnUser("Whip out error: 432");
} else {
warnUser("Unknown Whipe Out error");
}
} else if (callback){
callback();
}
}
};
if (type==="trickle-ice-sdpfrag"){
xhttp.open("PATCH", session.whipOutput, true); // Not supported by most sites yet
} else {
xhttp.open("POST", session.whipOutput, true);
}
if (session.whipOutputToken){
xhttp.setRequestHeader('Authorization', 'Bearer ' + session.whipOutputToken);
}
xhttp.setRequestHeader('Content-Type', 'application/'+type);
xhttp.onerror = function(e) {
errorlog(e);
warnUser("Whip out failed.");
};
xhttp.send(data);
} catch(e){errorlog(e);}
}
function GOP(){
log("Sending keyframe");
try {
if (!session.whipOut){return;}
var senders = session.whipOut.getSenders();
var sender = false;
senders.forEach((senderVideo)=>{
if (senderVideo.track && senderVideo.track.id && (senderVideo.track.kind == "video")){
sender = senderVideo;
}
});
if (!sender){
warnlog("can't change bitrate; no video sender found");
return false;
}
var settings = {};
settings.scaleResolutionDownBy = 10; // 50% of default max
setEncodings(sender, settings, function(sendr){
var settings = {};
var chromeVersion = getChromeVersion();
if (chromeVersion>80){ // just because
settings.scaleResolutionDownBy = null;
} else {
settings.scaleResolutionDownBy = 1.0;
}
setEncodings(sendr, settings, function(){
//log("scaleResolutionDownBy set 3b!");
});
}, sender);
return true;
} catch(e){
errorlog(e);
}
}
whipConnect();
}
function whipClient(){ // publish to whip.vdo.ninja with obs, to use. experimental
if (!session.whipView){return;}
warnlog("WHIP Client started");
var socket = null;
var connecting = false;
var failedCount = 0;
function connect(){
clearTimeout(connecting);
if (socket){
if (socket.readyState === socket.OPEN){return;}
try{
socket.close();
} catch(e){}
}
log("Trying to load whip websocket...");
socket = new WebSocket("wss://whip.vdo.ninja");
socket.onclose = function (){
failedCount+=1;
clearTimeout(connecting);
connecting = setTimeout(function(){connect();},100*(failedCount-1));
};
socket.onerror = function (e){
console.error(e);
failedCount+=1;
clearTimeout(connecting);
connecting = setTimeout(function(){connect();},100*failedCount);
};
socket.onopen = function (){
failedCount = 0;
try{
var settings = {};
socket.send(JSON.stringify({"join":session.whipView}));
} catch(e){
connecting = setTimeout(function(){connect();},1);
}
};
socket.addEventListener('message', async function (event) {
if (event.data){
var data = JSON.parse(event.data);
if ("sdp" in data){
var resp = await processWHIP(data);
if (resp){
var ret = {};
var get = data.get;
data = {};
if (get){
data.get = get;
data.result = resp;
ret.callback = data;
log(ret);
socket.send(JSON.stringify(ret));
}
}
} else if ("delete" in data){
warnlog("WHIP Client is actively disconnecting");
// session.closeRPC(i, true);
}
}
});
}
connect();
}
async function processWHIP(data){ // LISTEN FOR REMOTE WHIP
var msg = {};
msg.description = {};
msg.description.type = "offer";
msg.description.sdp = data.sdp;
// msg.session = session.generateRandomString(5);
msg.UUID = session.generateRandomString(25); // fake
if (data.streamID){
msg.streamID = data.streamID;
} else {
msg.streamID = session.generateRandomString(15); // fake
}
log("setupIncoming");
await session.setupIncoming(msg); // could end up setting up the peer the wrong way.
try {
// session.rpcs[msg.UUID].addTransceiver('video', {direction: 'recvonly'});
// session.rpcs[msg.UUID].addTransceiver('audio', {direction: 'recvonly'});
} catch(e){errorlog(e);}
session.rpcs[msg.UUID].whip = true;
var callback = null;
var promise = new Promise((resolve, reject) => {
callback = resolve;
});
session.rpcs[msg.UUID].whipCallback = callback;
var callback2 = null;
var promise2 = new Promise((resolve, reject) => {
callback2 = resolve;
});
session.rpcs[msg.UUID].whipCallback2 = callback2;
log("CONNECT PEEER");
session.connectPeer(msg);
log("CONNECT PEEER DONE");
if (!session.manual || !session.director){
window.onresize = updateMixer;
window.onorientationchange = function(){
setTimeout(updateMixer, 200);
};
}
if ((session.roomid === false) && !session.permaid){
getById("header").classList.add("hidden");
}
log("ICE BUNDLE PROMISE");
setTimeout(function(UUID){
log("ICE BUNDLE PROMISE TIMEOUT");
if (session.rpcs[UUID].whipCallback2){
session.rpcs[UUID].whipCallback2([...session.rpcs[UUID].iceBundle]);
clearTimeout(session.rpcs[UUID].iceTimer);
session.rpcs[UUID].iceTimer = null;
session.rpcs[UUID].iceBundle = []
session.rpcs[UUID].whipCallback2 = null;
}
},3000,msg.UUID);
var iceBundle = await promise2; // waiting for ICE GATHER COMPLETE
session.rpcs[msg.UUID].whipCallback2 = null;
log("ICE BUNDLE DONE");
log(iceBundle);
await promise;
session.rpcs[msg.UUID].whipCallback = null;
sdpAnswer = session.rpcs[msg.UUID].localDescription.sdp;
var insertIce = "";
iceBundle.forEach(ice=>{
if (ice.candidate){
insertIce += "a="+ice.candidate+"\r\n";
}
});
sdpAnswer = sdpAnswer.replace("a=ice-ufrag", insertIce+"a=ice-ufrag");
//if (session.stereo){
// sdpAnswer = CodecsHandler.setOpusAttributes(sdpAnswer, {stereo:1}, true);
//}
if (sdpAnswer.includes("sendrecv")){
errorlog("Should not include sendrecv");
sdpAnswer = sdpAnswer.replace("a=sendrecv","a=recvonly");
sdpAnswer = sdpAnswer.replace("v=sendrecv","v=recvonly");
}
log("completed");
warnlog(sdpAnswer);
return sdpAnswer; // return SDP answer for the remote WHIP request
}
async function whepIn(whepInput=false,whepInputToken, UUID=false){ // PLAY WHEP
var candidates = [];
var responseLocation = false;
UUID = UUID+"_whep" || session.generateRandomString(25); // fake
whepInput = whepInput || session.whepInput;
whepInputToken = whepInputToken || session.whepInputToken;
async function whepConnect(){
try {
if (!session.configuration){
await chooseBestTURN();
}
if (!(UUID in session.rpcs)){
session.rpcs[UUID] = {};
session.rpcs[UUID].stats = {};
session.rpcs[UUID].allowGraphs = false;
session.rpcs[UUID].inboundAudioPipeline = {};
session.rpcs[UUID].channelOffset = false;
session.rpcs[UUID].channelWidth = false;
session.rpcs[UUID].settings = false;
session.rpcs[UUID].lockedVideoBitrate = false; // doesn't do anything
session.rpcs[UUID].lockedAudioBitrate = false;
session.rpcs[UUID].manualBandwidth = false; // doesn't do anything, except maybe help keep track of pause/play states
}
var config = {...session.configuration};
if (whepInput.includes("cloudflare")){
config.iceTransportPolicy = "relay"; // oof. Doesn't work with Cloudflare without this?
}
try {
session.rpcs[UUID].mc = new RTCPeerConnection(config);
} catch(err){
errorlog(err);
if (!session.cleanOutput){
warnUser("An RTC error occured");
}
}
var video = true;
var audio = true;
if ((session.novideo !== false) && (!session.novideo.includes(session.rpcs[UUID].streamID))){
video = false;
} else if (session.rpcs[UUID].settings && !session.rpcs[UUID].settings.video){
video = false;
}
if ((session.noaudio !== false) && (!session.noaudio.includes(session.rpcs[UUID].streamID))){
audio = false;
} else if (session.rpcs[UUID].settings && !session.rpcs[UUID].settings.audio){
audio = false;
}
if (!audio && !video){
errorlog("We will not request the meshcast as no audio or video is requested");
return;
}
if (!session.manual || !session.director){
window.onresize = updateMixer;
window.onorientationchange = function(){
setTimeout(updateMixer, 200);
};
}
try {
if (video){
session.rpcs[UUID].mc.addTransceiver('video', {direction: 'recvonly'});
}
if (audio){
session.rpcs[UUID].mc.addTransceiver('audio', {direction: 'recvonly'});
}
} catch(e){errorlog(e);}
session.rpcs[UUID].mc.ontrack = function(event) {
warnlog("TRACK INBOUND!");
warnlog(event);
session.onTrack(event, UUID);
};
} catch(err){
errorlog(err);
if (!session.cleanOutput){
warnUser("An RTC error occured");
}
}
session.rpcs[UUID].mc.onnegotiationneeded = requestStream; // bug: https://groups.google.com/forum/#!topic/discuss-webrtc/3-TmyjQ2SeE
session.rpcs[UUID].mc.onicecandidate = function(event){ //event
if (event.candidate==null){
log("END OF ICE CANDIDATES");
return;
}
//log(event.candidate);
candidates.push(event.candidate);
};
log("onnegotiationneeded event setup");
}
var requestingStream = false;
function requestStream(event){
if (requestingStream){
log(event);
errorlog("onnegotiationneeded again?");
return;
}
requestingStream = true;
warnlog("ON NEGO NEEDED");
warnlog(event);
try {
session.rpcs[UUID].mc.createOffer().then(async function(offer){
return session.rpcs[UUID].mc.setLocalDescription(offer);
}).then(async function() {
//log(session.rpcs[UUID].mc.localDescription);
await sleep(6000);
var sdp = session.rpcs[UUID].mc.localDescription.sdp;
if (sdp.includes("sendrecv")){
errorlog("Should not include sendrecv");
sdp = sdp.replace("a=sendrecv","a=recvonly");
sdp = sdp.replace("v=sendrecv","v=recvonly");
}
ajax(sdp, "sdp");
}).catch(function(err){});
} catch(e){errorlog(e);}
}
function ajax(data, type, callback=false){
log("AJAX: "+type);
//log(data);
try {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && (this.status == 200 || this.status == 201)) {
var contentType = this.getResponseHeader('content-type');
responseLocation = this.getResponseHeader('location');
if (contentType.startsWith("application/sdp")){
var jsep = {};
jsep.sdp = this.responseText;
jsep.type = "answer";
warnlog("Processing answer:");
session.rpcs[UUID].mc.setRemoteDescription(jsep).then(function(){
warnlog("SHOULD BE CONNECTED?");
/* var content = "";
while (candidates.length){
var candidate = candidates.pop();
content += candidate.candidate;
}
if (content){
ajax(content, "trickle-ice-sdpfrag", function(){
});
} */
}).catch(function(e){log(e);});
} else if (contentType == "application/error"){
if (this.responseText==432){
warnUser("Whep in error: 432");
} else {
warnUser("Unknown Whep In error");
}
} else if (callback){
callback();
}
}
};
if (type==="trickle-ice-sdpfrag"){
if (responseLocation){
xhttp.open("PATCH", whepInput, true);
} else {
xhttp.open("PATCH", whepInput, true);
}
} else {
xhttp.open("POST", whepInput, true);
}
xhttp.setRequestHeader('Content-Type', 'application/'+type);
if (whepInputToken){
xhttp.setRequestHeader('Authorization', 'Bearer ' + whepInputToken);
}
xhttp.onerror = function(e) {
errorlog(e);
warnUser("Whep in failed.");
};
xhttp.send(data);
} catch(e){errorlog(e);}
}
whepConnect();
return UUID;
}
////////
function whepOut(){ // publish to whep.vdo.ninja with obs, to use. experimental
if (!session.whepHost){return;}
warnlog("WHEP Client started");
var socket = null;
var connecting = false;
var failedCount = 0;
function connect(){
clearTimeout(connecting);
if (socket){
if (socket.readyState === socket.OPEN){return;}
try{
socket.close();
} catch(e){}
}
log("Trying to load whep websocket...");
socket = new WebSocket("wss://whep.vdo.ninja:81");
socket.onclose = function (){
failedCount+=1;
clearTimeout(connecting);
connecting = setTimeout(function(){connect();},100*(failedCount-1));
};
socket.onerror = function (e){
console.error(e);
failedCount+=1;
clearTimeout(connecting);
connecting = setTimeout(function(){connect();},100*failedCount);
};
socket.onopen = function (){
failedCount = 0;
try{
var settings = {};
socket.send(JSON.stringify({"join":session.whepHost}));
} catch(e){
connecting = setTimeout(function(){connect();},1);
}
};
socket.addEventListener('message', async function (event) {
if (event.data){
var data = JSON.parse(event.data);
if ("sdp" in data){
var resp = await processWHEPout(data);
if (resp){
var ret = {};
var get = data.get;
data = {};
if (get){
data.get = get;
data.result = resp;
ret.callback = data;
log(ret);
socket.send(JSON.stringify(ret));
}
}
} else if ("delete" in data){
warnlog("WHIP Client is actively disconnecting");
// session.closeRPC(i, true);
}
}
});
}
connect();
}
async function processWHEPout(data){ // LISTEN FOR REMOTE WHIP
var msg = {};
msg.description = {};
msg.description.type = "offer";
msg.description.sdp = data.sdp;
// msg.session = session.generateRandomString(5);
msg.UUID = session.generateRandomString(25); // fake
log("setupoutgoing");
try {
//if (session.meshcast!=="video"){
var tracks = false;
if (session.videoElement && session.videoElement.srcObject){
tracks = session.videoElement.srcObject.getAudioTracks();
}
var streamsource = false;
if (!tracks || !tracks.length){
var audioCtx = new AudioContext();
streamsource = destination.stream;
var destination = audioCtx.createMediaStreamDestination();
destination.stream.getAudioTracks().forEach(trk=>{
tracks = trk;
});
} else {
tracks = tracks[0];
streamsource = session.videoElement.srcObject;
}
if (session.audioContentHint && (tracks.kind === "audio")){
try {
tracks.contentHint = session.audioContentHint;
} catch(e){
errorlog(e);
}
}
if (tracks){
try {
session.whipOut.addTransceiver(tracks, {
streams: [ streamsource ],
direction: 'sendonly'
});
} catch(e){
errorlog(e);
session.whipOut.addTrack(tracks);
}
}
//}
///////
//// video tracks
//if (session.meshcast!=="audio"){
var tracks = false;
if (session.videoElement && session.videoElement.srcObject){
tracks = session.videoElement.srcObject.getVideoTracks();
}
////
//if (!tracks || !tracks.length){
// tracks = getMeshcastCanvasTrack();
//} else {
tracks = tracks[0];
//}
if (session.screenShareState && session.screenshareContentHint && (tracks.kind === "video")){
try {
tracks.contentHint = session.screenshareContentHint;
} catch(e){
errorlog(e);
}
} else if (session.contentHint && (tracks.kind === "video")){
try {
tracks.contentHint = session.contentHint;
} catch(e){
errorlog(e);
}
}
if (tracks){
try {
session.whipOut.addTransceiver(tracks, {
streams: [ session.videoElement.srcObject ],
direction: 'sendonly'
});
} catch(e){
errorlog(e);
session.whipOut.addTrack(tracks);
}
}
//}
session.whipOut.onnegotiationneeded = publish; // bug: https://groups.google.com/forum/#!topic/discuss-webrtc/3-TmyjQ2SeE
session.whipOut.onicecandidate = function(event){ //event
if (event.candidate==null){
log("END OF ICE CANDIDATES");
return;
}
//log(event.candidate);
candidates.push(event.candidate);
};
} catch(e){errorlog(e);}
session.rpcs[msg.UUID].whip = true;
var callback = null;
var promise = new Promise((resolve, reject) => {
callback = resolve;
});
session.rpcs[msg.UUID].whipCallback = callback;
var callback2 = null;
var promise2 = new Promise((resolve, reject) => {
callback2 = resolve;
});
session.rpcs[msg.UUID].whipCallback2 = callback2;
log("CONNECT PEEER");
session.connectPeer(msg);
log("CONNECT PEEER DONE");
if (!session.manual || !session.director){
window.onresize = updateMixer;
window.onorientationchange = function(){
setTimeout(updateMixer, 200);
};
}
log("ICE BUNDLE PROMISE");
setTimeout(function(UUID){
log("ICE BUNDLE PROMISE TIMEOUT");
if (session.rpcs[UUID].whipCallback2){
session.rpcs[UUID].whipCallback2([...session.rpcs[UUID].iceBundle]);
clearTimeout(session.rpcs[UUID].iceTimer);
session.rpcs[UUID].iceTimer = null;
session.rpcs[UUID].iceBundle = []
session.rpcs[UUID].whipCallback2 = null;
}
},3000,msg.UUID);
var iceBundle = await promise2; // waiting for ICE GATHER COMPLETE
session.rpcs[msg.UUID].whipCallback2 = null;
log("ICE BUNDLE DONE");
log(iceBundle);
await promise;
session.rpcs[msg.UUID].whipCallback = null;
sdpAnswer = session.rpcs[msg.UUID].localDescription.sdp;
var insertIce = "";
iceBundle.forEach(ice=>{
if (ice.candidate){
insertIce += "a="+ice.candidate+"\r\n";
}
});
sdpAnswer = sdpAnswer.replace("a=ice-ufrag", insertIce+"a=ice-ufrag");
if (sdpAnswer.includes("sendrecv")){
errorlog("Should not include sendrecv");
sdpAnswer = sdpAnswer.replace("a=sendrecv","a=recvonly");
sdpAnswer = sdpAnswer.replace("v=sendrecv","v=recvonly");
}
log("completed");
warnlog(sdpAnswer);
return sdpAnswer; // return SDP answer for the remote WHIP request
}
/////
function pokePostAPI(action, data, streamID){
var msg = {};
msg.update = {};
msg.update.streamID = streamID || session.streamID || null;
msg.update.action = action;
msg.update.value = data;
try {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && (this.status == 200 || this.status == 201)) {
log("good");
} else {
warnlog("post api didn't work?");
}
};
xhttp.open("POST", session.postApi, true);
xhttp.setRequestHeader('Content-type', 'application/json');
xhttp.onerror = function(e) {
errorlog(e);
};
xhttp.send(JSON.stringify(msg));
} catch(e){errorlog(e);}
}
var queuedSendingAPIMsgs = [];
function pokeAPI(action, data, streamID = null){
if (session.postApi){
pokePostAPI(action, data, streamID);
}
if (!session.api){return;}
if (session.apiSocket){
try {
var msg = {};
msg.update = {};
msg.update.streamID = streamID || session.streamID || null;
msg.update.action = action;
msg.update.value = data;
session.apiSocket.send(JSON.stringify(msg));
} catch(e){
errorlog(e);
}
} else if (session.apiSocket!==null){
queuedSendingAPIMsgs.push([action, data, streamID]);
if (queuedSendingAPIMsgs.length>20){
queuedSendingAPIMsgs.shift();
}
}
}
function oscClient(){ // api.vdo.ninja api OSC (websocket / https API hotkey support). The iFrame API method provides greater customization.
if (!session.api){return;}
warnlog("oscClient started");
var socket = null;
var connecting = false;
var failedCount = 0;
function connect(){
clearTimeout(connecting);
if (socket){
if (socket.readyState === socket.OPEN){return;}
try{
socket.close();
} catch(e){}
}
socket = new WebSocket(session.apiserver);
socket.onclose = function (){
session.apiSocket = false;
failedCount+=1;
clearTimeout(connecting);
connecting = setTimeout(function(){connect();},100*(failedCount-1));
};
socket.onerror = function (){
failedCount+=1;
clearTimeout(connecting);
connecting = setTimeout(function(){connect();},100*failedCount);
};
socket.onopen = function (){
failedCount = 0;
try{
socket.send(JSON.stringify({"join":session.api}));
session.apiSocket = socket;
if (queuedSendingAPIMsgs.length){
queuedSendingAPIMsgs.forEach(msg=>{
pokeAPI(msg[0],msg[1], msg[2]);
});
queuedSendingAPIMsgs = [];
}
pokeAPI("details", getDetailedState(session.streamID));
} catch(e){
connecting = setTimeout(function(){connect();},1);
}
};
socket.addEventListener('message', async function (event) {
if (event.data){
var data = JSON.parse(event.data);
if ("msg" in data){
data = data.msg
}
if ("value" in data){
if (("action" in data) && (data.action == "layout")){
try {
data.value = JSON.parse(data.value) || data.value;
} catch(e){}
}
}
var resp = processMessage(data);
if (resp!==null){
var ret = {};
data.result = resp;
ret.callback = data;
log(ret);
socket.send(JSON.stringify(ret));
}
}
});
}
connect();
}
function setupCommands(){
var commands = {}
commands.raisehand = function(value=null,value2=null){
return raisehand();
};
commands.togglehand = function(value=null,value2=null){
return raisehand();
};
commands.togglescreenshare = function(value=null,value2=null){
toggleScreenShare();
return session.screenShareState;
};
commands.chat = function(value=null,value2=null){
toggleChat(value);
return session.chat;
};
commands.speaker = function(value=null,value2=null){
if (value === true) { // unmute
session.speakerMuted = false; // set
toggleSpeakerMute(true); // apply
} else if (value === false) { // mute
session.speakerMuted = true; // set
toggleSpeakerMute(true); // apply
} else if (value === "toggle") { // toggle
toggleSpeakerMute();
}
return session.speakerMuted;
}; // mute speaker
commands.mic = function(value=null,value2=null){
if (value === true) { // unmute
session.muted = false; // set
log(session.muted);
toggleMute(true); // apply
} else if (value === false) { // mute
session.muted = true; // set
log(session.muted);
toggleMute(true); // apply
} else if (value === "toggle") { // toggle
toggleMute();
}
return session.muted;
};
commands.camera = function(value=null,value2=null){
if (value === true) { // unmute
session.videoMuted = false; // set
log(session.videoMuted);
toggleVideoMute(true); // apply
} else if (value === false) { // mute
session.videoMuted = true; // set
log(session.videoMuted);
toggleVideoMute(true); // apply
} else if (value === "toggle") { // toggle
toggleVideoMute();
}
return session.videoMuted;
}
commands.hangup = function(value=null,value2=null){
hangup();
return true;
};
commands.bitrate = function(value=null,value2=null){
if (value===false){
value = 0;
} else if (value===true){
value = -1;
} else {
value = parseInt(value) || 0;
}
for (var i in session.rpcs) {
try {
session.requestRateLimit(value, i);
} catch (e) {
errorlog(e);
}
}
return value;
};
commands.getDetails = function(value=null,value2=null){
return getDetailedState();
}
commands.reload = function(value=null,value2=null){
reloadRequested();
return true;
};
commands.volume = function(value=null,value2=null){
if (value===false){
value = 0;
} else if (value===true){
value = 100
} else {
value = parseInt(value) || 0;
}
value = parseFloat(value/100);
for (var i in session.rpcs) {
try {
session.rpcs[i].videoElement.volume = parseFloat(value);
} catch (e) {
errorlog(e);
}
}
return value;
};
commands.forceKeyframe = function(value=null,value2=null){
return session.forcePLI();
};
commands.panning = function(value=null,value2=null){
if (value===false){
value = 90;
} else if (value===true){
value = 90
} else {
value = parseInt(value);
}
for (var uuid in session.rpcs) {
try {
adjustPan(uuid, value); // &panning needs to be added to enable. playback only; not mic out.
} catch (e) {
errorlog(e);
}
}
return value;
};
commands.record = function(value=null,value2=null){
if (!session.videoElement){return;}
if (value === false) { // mute
if ("recording" in session.videoElement) {
recordLocalVideo("stop");
}
} else if (value === true){
if ("recording" in session.videoElement) {
// already recording
} else {
recordLocalVideo("start");
}
}
return value;
};
commands.group = function(value=null,value2=null){
if (value && (value !== "null")){
return changeGroupDirectorAPI(value);
}
return false;
};
commands.joinGroup = function(value=null,value2=null){
if (value && (value !== "null")){
return changeGroupDirectorAPI(value, true);
}
return false;
};
commands.leaveGroup = function(value=null,value2=null){
if (value && (value !== "null")){
return changeGroupDirectorAPI(value, false);
}
return false;
};
//
commands.viewGroup = function(value=null,value2=null){
if (value && (value !== "null")){
return changeGroupViewDirectorAPI(value);
}
return false;
};
commands.joinViewGroup = function(value=null,value2=null){
if (value && (value !== "null")){
return changeGroupViewDirectorAPI(value, true);
}
return false;
};
commands.leaveViewGroup = function(value=null,value2=null){
if (value && (value !== "null")){
return changeGroupViewDirectorAPI(value, false);
}
return false;
};
commands.sendChat = function(value=null,value2=null){
sendChat(value);
// sendChatMessage // this would add it to the chat message
return true;
};
commands.prevSlide = function(value=null,value2=null){
var data = {};
data.d = [176, 110, 10];
playbackMIDI(data);
return true;
};
commands.nextSlide = function(value=null,value2=null){
var data = {};
data.d = [176, 110, 11];
playbackMIDI(data);
return true;
};
commands.nextSlide = function(value=null,value2=null){
var data = {};
data.d = [176, 110, 11];
playbackMIDI(data);
return true;
};
commands.soloVideo = function(value=null,value2=null){
var element = getById("highlightDirector");
if (value && (value == "toggle")){
return requestInfocus(element);
} else if (value && (value !== "null")){
return requestInfocus(element, null, true);
} else if (value && (value === "null")){
return requestInfocus(element);
} else {
return requestInfocus(element, null, false);
}
return false;
};
commands.highlight = function(value=null,value2=null){
return commands.soloVideo(value, value2);
};
commands.layout = function(value=null,value2=null){
try {
if (parseInt(value)==value){
value = parseInt(value);
if (value ==0){
value = false;
} else {
value -= 1;
}
} else if (typeof value === "object"){
session.layout = value;
pokeIframeAPI("layout-updated", session.layout);
if (session.director){
var combined = {};
for (var i=0;i 110){
var guestslot = command-111;
if (value == 0) {
var ele = getRightOrderedElement('[data-action-type="forward"][data--u-u-i-d]', guestslot);
if (ele) {
directMigrate(ele, true);
}
} else if (value == 1) {
var ele = getRightOrderedElement('[data-action-type="addToScene"][data-scene="1"][data--u-u-i-d]', guestslot);
if (ele) {
directEnable(ele, true);
}
} else if (value == 2) {
var ele = getRightOrderedElement('[data-action-type="mute-scene"][data--u-u-i-d]', guestslot);
if (ele) {
directMute(ele, true);
}
} else if (value == 3) {
var ele = getRightOrderedElement('[data-action-type="mute-guest"][data--u-u-i-d]', guestslot);
if (ele) {
remoteMute(ele, true);
}
} else if (value == 4) {
var ele = getRightOrderedElement('[data-action-type="hangup"][data--u-u-i-d]', guestslot);
if (ele) {
directHangup(ele, true);
}
} else if (value == 5) {
var ele = getRightOrderedElement('[data-action-type="solo-chat"][data--u-u-i-d]', guestslot);
if (ele) {
session.toggleSoloChat(ele.dataset.UUID);
}
} else if (value == 6) {
var ele = getRightOrderedElement('[data-action-type="toggle-remote-speaker"][data--u-u-i-d]', guestslot);
if (ele) {
remoteSpeakerMute(ele);
}
} else if (value == 7) {
var ele = getRightOrderedElement('[data-action-type="toggle-remote-display"][data--u-u-i-d]', guestslot);
if (ele) {
remoteDisplayMute(ele);
}
} else if (value == 8) {
var ele = getRightOrderedElement('[data-action-type="force-keyframe"][data--u-u-i-d]', guestslot);
if (ele) {
requestKeyframeScene(ele);
}
} else if (value == 12) {
var ele = getRightOrderedElement('[data-action-type="addToScene"][data-scene="2"][data--u-u-i-d]', guestslot);
if (ele) {
directEnable(ele, true);
}
} else if (value == 13) {
var ele = getRightOrderedElement('[data-action-type="addToScene"][data-scene="3"][data--u-u-i-d]', guestslot);
if (ele) {
directEnable(ele, true);
}
} else if (value == 14) {
var ele = getRightOrderedElement('[data-action-type="addToScene"][data-scene="4"][data--u-u-i-d]', guestslot);
if (ele) {
directEnable(ele, true);
}
} else if (value == 15) {
var ele = getRightOrderedElement('[data-action-type="addToScene"][data-scene="5"][data--u-u-i-d]', guestslot);
if (ele) {
directEnable(ele, true);
}
} else if (value == 16) {
var ele = getRightOrderedElement('[data-action-type="addToScene"][data-scene="6"][data--u-u-i-d]', guestslot);
if (ele) {
directEnable(ele, true);
}
} else if (value == 17) {
var ele = getRightOrderedElement('[data-action-type="addToScene"][data-scene="7"][data--u-u-i-d]', guestslot);
if (ele) {
directEnable(ele, true);
}
} else if (value == 18) {
var ele = getRightOrderedElement('[data-action-type="addToScene"][data-scene="8"][data--u-u-i-d]', guestslot);
if (ele) {
directEnable(ele, true);
}
} else if ((value => 27)) {
var ele = getRightOrderedElement('[data-action-type="volume"][data--u-u-i-d]', guestslot);
if (ele) {
ele.value = parseInt(value-27);
remoteVolume(ele);
}
}
}
}
function sendRawMIDI(input, UUID=false, streamID=false){
// session.sendRawMIDI(e.data.sendRawMIDI);
var msg = {};
msg.midi = {};
msg.midi.d = input.data;
if ("timestamp" in input){
msg.midi.s = input.timestamp;
} else {
msg.midi.s = Date.now(); // unix timestamp
}
if (input.message && input.message.channel){
msg.midi.c = input.message.channel;
} else if (input && input.channel){
msg.midi.c = input.channel;
}
if (UUID && session.pcs[UUID] && session.pcs[UUID].allowMIDI){
session.sendMessage(msg, UUID);
} else if (UUID && session.rpcs[UUID] && session.rpcs[UUID].allowMIDI){
session.sendRequest(msg, UUID);
} else if (streamID){
for (var UID in session.rpcs){
if (session.rpcs[UID].allowMIDI && (session.rpcs[UID].streamID === streamID)){ // specific to gstreamer code aplication
session.sendRequest(msg, UID)
return; // only one stream ID should match
}
}
} else {
var list = [];
for (var UID in session.pcs){
if (session.pcs[UID].allowMIDI){
if (session.sendMessage(msg, UID)){
list.push(UID);
}
}
}
for (var UID in session.rpcs){
if (session.rpcs[UID].allowMIDI){ // specific to gstreamer code aplication
if (!list.includes(UID)){
session.sendRequest(msg, UID)
}
}
}
}
}
function playOutMidi(msg){
console.log("Playing out remotely sourced MIDI");
if (session.midiIn===true){
if ("d" in msg){
for (var i in WebMidi.outputs){
try {
if ("c" in msg){
WebMidi.outputs[i].channels[msg.c].send(msg.d);
} else {
WebMidi.outputs[i].send(msg.d);
}
} catch(e){errorlog(e);}
}
}
} else if (session.midiIn==parseInt(session.midiIn)){
try {
var i = parseInt(session.midiIn)-1;
if ("d" in msg){
if ("c" in msg){
WebMidi.outputs[i].channels[msg.c].send(msg.d);
} else {
WebMidi.outputs[i].send(msg.d);
}
}
} catch(e){errorlog(e);};
}
}
function playbackMIDI(msg, unsafe=false){
if (session.midiIn===false && session.midiRemote===false){return;} // just in case; security
else if ((session.midiOut===session.midiIn) && (session.midiRemote===false)){return;} // avoid feedback loops
//msg.midi.d = e.data;
//msg.midi.s = e.timestamp;
//msg.midi.t = e.type;
if (session.midiDelay && ("t" in msg)){
var timeDelay = session.midiDelay - (Date.now() - msg.t);
if (timeDelay<=0){
playOutMidi(msg);
} else {
setTimeout(function(msg){playOutMidi(msg)},timeDelay,msg);
}
} else {
playOutMidi(msg);
}
if (unsafe){return;} // I don't know how midi remote works in reverse, so lets ignore it
if (session.midiRemote==4){
if (msg.d[0] == 176){
midiHotkeysCommand(msg.d[1], msg.d[2]);
}
} else if (session.midiRemote==1 || session.midiRemote==2 || session.midiRemote==3){
if (msg.d[0] == 156){
if (msg.d[1] == 33){
midiHotkeysNote("A1", msg.d[2]);
} else if (msg.d[1] == 55){
midiHotkeysNote("G3", msg.d[2]);
} else if (msg.d[1] == 57){
midiHotkeysNote("A3", msg.d[2]);
} else if (msg.d[1] == 59){
midiHotkeysNote("B3", msg.d[2]);
} else if (msg.d[1] == 60){
midiHotkeysNote("C4", msg.d[2]);
} else if (msg.d[1] == 62){
midiHotkeysNote("D4", msg.d[2]);
} else if (msg.d[1] == 64){
midiHotkeysNote("E4", msg.d[2]);
} else if (msg.d[1] == 65){
midiHotkeysNote("F4", msg.d[2]);
} else if (msg.d[1] == 67){
midiHotkeysNote("G4", msg.d[2]);
} else if (msg.d[1] == 69){
midiHotkeysNote("A4", msg.d[2]);
} else if (msg.d[1] == 43){
midiHotkeysNote("G2", msg.d[2]);
} else if (msg.d[1] == 35){
midiHotkeysNote("B1", msg.d[2]);
} else if (msg.d[1] == 36){
midiHotkeysNote("C2", msg.d[2]);
} else if (msg.d[1] == 38){
midiHotkeysNote("D2", msg.d[2]);
} else if (msg.d[1] == 40){
midiHotkeysNote("E2", msg.d[2]);
} else if (msg.d[1] == 41){
midiHotkeysNote("F2", msg.d[2]);
} else if (msg.d[1] == 24){
midiHotkeysNote("C1", msg.d[2]);
}
}
}
//var output = WebMidi.getOutputById("123456789");
//output = WebMidi.getOutputByName("Axiom Pro 25 Ext Out");
//output = WebMidi.outputs[0];
}
function addEventToAll(targets, trigger, callback) { // js helper
const target = document.querySelectorAll(targets);
var triggers = trigger.split(" ");
for (let i = 0; i < target.length; i++) {
for (let j = 0; j < triggers.length; j++) {
setTimeout(function(t1,t2){
t1.addEventListener(t2, function(e) {
callback(e, t1);
});
},0,target[i],triggers[j]);
}
}
}
function insertAfter(newNode, existingNode) {
existingNode.parentNode.insertBefore(newNode, existingNode.nextSibling);
}
addEventToAll(".column", 'click', function(e, ele) {
if (ele.classList.contains("skip-animation")) {
return;
}
try {
var bounding_box = ele.getBoundingClientRect();
} catch(e){
return;
}
ele.style.top = bounding_box.top + "px";
ele.style.left = (bounding_box.left - 20) + "px";
ele.classList.add('in-animation');
ele.classList.remove('pointer');
ele.classList.remove('rounded');
if (document.getElementById("empty-container")) {
getById("empty-container").parentNode.removeChild(getById("empty-container"));
}
var empty = document.createElement("DIV");
empty.id = "empty-container";
empty.className = "column";
ele.parentNode.insertBefore(empty, ele.nextSibling);
const styles = "\
@keyframes outlightbox {\
0% {\
height: 100%;\
width: 100%;\
top: 0px;\
left: 0px;\
}\
50% {\
height: 200px;\
top: " + bounding_box.y + "px;\
}\
100% {\
height: 200px;\
width: " + bounding_box.width + "px;\
top: " + bounding_box.y + "px;\
left: " + bounding_box.x + "px;\
}\
}\
";
if (document.getElementById('lightbox-animations')) {
getById("lightbox-animations").innerHTML = styles;
}
document.body.style.overflow = "hidden";
});
addEventToAll(".close", 'click', function(e, ele) {
cleanupMediaTracks();
ele.style.display = "none";
mapToAll(".container-inner", function(target) {
target.style.display = "none";
});
document.body.style.overflow = "auto";
var bounding_box = getById("empty-container").parentNode.getBoundingClientRect();
setTimeout(function() { // just smoothes things out; breathing room to clean up things first.
ele.parentNode.classList.add('out-animation');
}, 1);
ele.parentNode.style.top = bounding_box.top + 'px';
ele.parentNode.style.left = bounding_box.left + 'px';
e.stopPropagation();
});
addEventToAll(".column", 'animationend', function(e, ele) {
if (e.animationName == 'inlightbox') {
ele.classList.add("skip-animation");
mapToAll(".close", function(target) {
target.style.display = "block";
}, ele);
mapToAll(".container-inner", function(target) {
target.style.display = "block";
}, ele);
} else if (e.animationName == 'outlightbox') {
ele.classList.remove('in-animation');
ele.classList.remove('out-animation');
ele.classList.remove("skip-animation");
ele.classList.remove('columnfade');
ele.classList.add('pointer');
ele.classList.add('rounded');
getById("empty-container").parentNode.removeChild(getById("empty-container"));
getById("lightbox-animations").sheet.deleteRule(0);
}
});
addEventToAll("#audioSource", 'mousedown touchend focusin focusout', function(e, ele) {
var state = getById('multiselect-trigger').dataset.state || 0; // Does this return TRU instead??. GAH. #TODO:
if (state == 0) {
getById('multiselect-trigger').dataset.state = 1;
getById('multiselect-trigger').classList.add('open');
getById('multiselect-trigger').classList.remove('closed');
mapToAll('.chevron', function(ele) {
ele.classList.remove('bottom');
}, parentElement = getById('multiselect-trigger'));
mapToAll('.multiselect-contents', function(ele) {
ele.style.display = "block";
mapToAll('input[type="checkbox"]', function(ele2) {
ele2.parentNode.style.display = "block";
ele2.style.display = "inline-block";
}, ele);
}, parentElement = getById('multiselect-trigger').parentNode);
}
e.stopPropagation();
//e.preventDefault();
});
addEventToAll("#audioSource3", 'mousedown touchend focusin focusout', function(e, ele) {
var state = getById('multiselect-trigger3').dataset.state || 0; // Does this return TRU instead??. GAH. #TODO:
if (state == 0) {
getById('multiselect-trigger3').dataset.state = 1;
getById('multiselect-trigger3').classList.add('open');
getById('multiselect-trigger3').classList.remove('closed');
mapToAll(".chevron", function(target) {
target.classList.remove('bottom');
}, getById('multiselect-trigger3'));
mapToAll(".multiselect-contents", function(target) {
target.style.display = "block";
}, getById('multiselect-trigger3').parentNode);
mapToAll(".multiselect-contents", function(target) {
mapToAll('input[type="checkbox"]', function(target2) {
target2.style.display = "inline-block";
target2.parentNode.style.display = "block";
}, target);
}, getById('multiselect-trigger3').parentNode);
}
e.stopPropagation();
//e.preventDefault();
});
addEventToAll("#multiselect-trigger", 'mousedown touchend focusin focusout', function(e, ele) {
var state = ele.dataset.state || 0; // Does this return TRU instead??. GAH. #TODO:
if (state == 0) { // open the dropdown
ele.dataset.state = 1;
ele.classList.add('open');
ele.classList.remove('closed');
mapToAll(".chevron", function(target) {
target.classList.remove('bottom');
}, getById('multiselect-trigger'));
mapToAll(".multiselect-contents", function(target) {
target.style.display = "block";
}, ele.parentNode);
mapToAll(".multiselect-contents", function(target) {
mapToAll('input[type="checkbox"]', function(target2) {
target2.style.display = "inline-block";
target2.parentNode.style.display = "block";
}, target);
}, ele.parentNode);
} else { // close the dropdown
ele.dataset.state = 0;
ele.classList.add('closed');
ele.classList.remove('open');
mapToAll(".chevron", function(target) {
target.classList.add('bottom');
}, ele);
mapToAll(".multiselect-contents", function(target) {
mapToAll('input[type="checkbox"]', function(target2) {
target2.style.display = "none";
if (!target2.checked) {
target2.parentNode.style.display = "none";
}
}, target);
}, ele.parentNode);
}
e.preventDefault();
e.stopPropagation();
});
addEventToAll("#multiselect-trigger3", 'mousedown touchend focusin focusout', function(e, ele) {
var state = ele.dataset.state || 0; // Does this return TRU instead??. GAH. #TODO:
if (state == 0) { // open the dropdown
ele.dataset.state = 1;
ele.classList.add('open');
ele.classList.remove('closed');
mapToAll(".chevron", function(target) {
target.classList.remove('bottom');
}, ele);
mapToAll(".multiselect-contents", function(target) {
target.style.display = "block";
}, ele.parentNode);
mapToAll(".multiselect-contents", function(target) {
mapToAll('input[type="checkbox"]', function(target2) {
target2.style.display = "inline-block";
target2.parentNode.style.display = "block";
}, target);
}, ele.parentNode);
} else { // close the dropdown
ele.dataset.state = 0;
ele.classList.add('closed');
ele.classList.remove('open');
mapToAll(".chevron", function(target) {
target.classList.add('bottom');
}, ele);
mapToAll(".multiselect-contents", function(target) {
mapToAll('input[type="checkbox"]', function(target2) {
target2.style.display = "none";
if (!target2.checked) {
target2.parentNode.style.display = "none";
}
}, target);
}, ele.parentNode);
}
e.preventDefault();
e.stopPropagation();
});
function getSenders2(UUID){
var fixedSenders = [];
var isAlt = false;
if (!(UUID in session.pcs)){return fixedSenders;}
if ("realUUID" in session.pcs[UUID]){
isAlt=true;
UUID = session.pcs[UUID].realUUID;
if (!(UUID in session.pcs)){return fixedSenders;}
}
var senders = session.pcs[UUID].getSenders();
if (isAlt){
senders.forEach((sender)=>{
if (sender.track && sender.track.id){
if (sender.track.id in screenshareTracks) { // I'm not going to change track.kind, since OBS isn't part of this list
fixedSenders.push(sender);
}
}
});
} else {
senders.forEach((sender)=>{
if (sender.track && sender.track.id){
if (!(sender.track.id in screenshareTracks)){
fixedSenders.push(sender);
}
}
});
}
return fixedSenders;
}
function getReceivers2(UUID){
var fixedReceivers = [];
var isAlt = false;
var ssTracks = [];
if ("realUUID" in session.rpcs[UUID]){
isAlt=true;
UUID = session.rpcs[UUID].realUUID;
if (!("screenIndexes" in session.rpcs[UUID])){
errorlog("this is supposed to be a screen share, but no screen share index was found");
return;
}
ssTracks = session.rpcs[UUID].screenIndexes;
} else if (("screenIndexes" in session.rpcs[UUID]) && session.rpcs[UUID].screenIndexes){
ssTracks = session.rpcs[UUID].screenIndexes;
}
var receivers = session.rpcs[UUID].getReceivers();
if (isAlt){
for (var i=0;i {
resolve([]);
});
}
}
/* if (session.audioContentHint && tracks.length){
tracks.forEach(trk=>{
try {
trk.contentHint = session.audioContentHint;
} catch(e){
errorlog(e);
}
});
} */
var senders = getSenders2(UUID+"_screen");
var tracks = session.screenStream.getTracks();
for (var i=0;i= 3) { // lowest
video.width = {
ideal: 320
};
video.height = {
ideal: 180
};
}
if (session.width) {
video.width = {
ideal: session.width
};
}
if (session.height) {
video.height = {
ideal: session.height
};
}
var constraints = { // this part is a bit annoying. Do I use the same settings? I can add custom setting controls here later
audio: {
echoCancellation: true, // we want to cancel echo, since this is a secondary stream
autoGainControl: false,
noiseSuppression: false
},
video: video
//,cursor: {exact: "none"}
};
try {
let supportedConstraints = navigator.mediaDevices.getSupportedConstraints();
if (supportedConstraints.cursor) {
if (session.screensharecursor){
constraints.video.cursor = ["always", "motion"];
} else {
constraints.video.cursor = "never";
}
}
if (session.suppressLocalAudioPlayback && supportedConstraints.suppressLocalAudioPlayback){
constraints.audio.suppressLocalAudioPlayback = true;
}
//
if (session.preferCurrentTab){
constraints.preferCurrentTab = true;
}
if (session.selfBrowserSurface){
constraints.selfBrowserSurface = session.selfBrowserSurface; // exclude or include
}
if (session.surfaceSwitching){
constraints.surfaceSwitching = session.surfaceSwitching; // exclude or include
}
if (session.systemAudio){
constraints.systemAudio = session.systemAudio; // exclude or include
}
if (session.displaySurface && supportedConstraints.displaySurface){
constraints.video.displaySurface = session.displaySurface; // monitor, window, or browser
}
} catch(e){
warnlog("navigator.mediaDevices.getSupportedConstraints() not supported");
}
if (session.echoCancellation === false) {
constraints.audio.echoCancellation = false;
}
if (session.autoGainControl === true) {
constraints.audio.autoGainControl = true;
}
if (session.noiseSuppression === true) {
constraints.audio.noiseSuppression = true;
}
//if (audio == false) {
// constraints.audio = false;
//}
var overrideFramerate = false;
if ((session.frameRate !== false) && (session.maxframeRate != false)){
overrideFramerate = session.frameRate;
constraints.video.frameRate = {
ideal: session.maxframeRate,
max: session.maxframeRate
};
} else if (session.frameRate !== false) {
constraints.video.frameRate = session.frameRate;
} else if (session.maxframeRate != false){
constraints.video.frameRate = {
ideal: session.maxframeRate,
max: session.maxframeRate
};
} else {
constraints.video.frameRate = {
ideal: 60
};
}
if (session.screenshareVideoOnly){
constraints.audio = false;
}
if (session.forceAspectRatio){ // await updateCameraConstraints("aspectRatio", session.forceAspectRatio);
if (constraints.video && constraints.video!==true){
// if (window.matchMedia("(orientation: portrait)").matches){
// constraints.video.aspectRatio = { ideal: 1.0/parseFloat(session.forceAspectRatio)};
// } else {
constraints.video.aspectRatio = { ideal: parseFloat(session.forceAspectRatio)};
// }
if (constraints.video.width && !session.width){
delete constraints.video.width;
} else if (constraints.video.height && !session.height){
delete constraints.video.height;
}
}
}
if ((constraints.video!==false) && (Object.keys(constraints.video).length==0)){
constraints.video = true;
}
if (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1) {
if (!ElectronDesktopCapture){
if (!(session.cleanOutput)) {
warnUser("Enable Elevated Privileges to allow screen-sharing. (right click this window to see that option)");
}
return false;
}
}
log("sstype3 screen share");
log(constraints);
navigator.mediaDevices.getDisplayMedia(constraints).then(async function(stream) {
try {
var constraint = {};
if (session.forceAspectRatio && (session.forceScreenShareAspectRatio===null)){
constraint.aspectRatio = parseFloat(session.forceAspectRatio);
} else if (session.forceScreenShareAspectRatio){
constraint.aspectRatio = parseFloat(session.forceScreenShareAspectRatio);
}
if (overrideFramerate){
constraint.frameRate = overrideFramerate;
}
if (Object.keys(constraint).length){
await stream.getVideoTracks()[0].applyConstraints({
advanced: [constraint]
});
log({
advanced: [constraint]
});
}
} catch(e){errorlog(e);}
session.screenShareState = true;
session.screenStream = stream;
pokeIframeAPI('screen-share-state', session.screenShareState, null, session.streamID);
//if (!session.screenVideoElement){
// session.screenVideoElement = createVideoElement()
//}
try {
stream.getVideoTracks()[0].onended = function () {
stopSecondScreenshare();
};
} catch(e){log("No Video selected; screensharing?");}
session.screenStream.getTracks().forEach(function(track){
screenshareTracks[track.id] = true; // obs isn't included, so no point to check track.kind
});
for (UUID in session.pcs){
createSecondStream2(UUID);
}
if (!firsttime){
var msg = {};
msg.screenStopped = false;
session.sendMessage(msg);
} else if (!session.screenShareElement){
session.screenShareElement = createVideoElement();
session.screenShareElement.muted = true;
session.screenShareElement.autoplay = true;
session.screenShareElement.controls = session.showControls || false;
session.screenShareElement.id = "screensharesource";
session.screenShareElement.dataset.sid = session.streamID + ":s";
if (typeof session.volume == "number"){
session.screenShareElement.volume = session.volume;
} else {
session.screenShareElement.volume = 1.0; // play audio automatically
}
session.screenShareElement.classList.add("tile");
session.screenShareElement.setAttribute("playsinline","");
session.screenShareElement.controlTimer = null;
session.screenShareElement.dataset.menu = "context-menu-video";
if (!session.cleanOutput){
session.screenShareElement.classList.add("task"); // this adds the right-click menu
}
createDirectorScreenshareOnlyBox();
if (document.getElementById("videoScreenContainer_director")){
getById("videoScreenContainer_director").appendChild(session.screenShareElement);
}
session.screenShareElement.onpause = (event) => { // prevent things from pausing; human or other
if (!((event.ctrlKey) || (event.metaKey) )){
log("Video paused; auto playing");
event.currentTarget.play().then(_ => {
log("playing 10");
}).catch(warnlog);
}
}
session.screenShareElement.addEventListener('click', function(e) {
log("click");
try {
if ((e.ctrlKey)||(e.metaKey)){
e.preventDefault();
var [menu, innerMenu] = statsMenuCreator();
menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu, true);
printMyStats(innerMenu, true);
e.stopPropagation();
return false;
}
} catch(e){errorlog(e);}
});
session.screenShareElement.touchTimeOut = null;
session.screenShareElement.touchLastTap = 0;
session.screenShareElement.touchCount = 0;
session.screenShareElement.addEventListener('touchend', function(event) {
if (session.disableMouseEvents){return;}
log("touched");
//document.ontouchup = null;
//document.onmouseup = null;
document.onmousemove = null;
document.ontouchmove = null;
var currentTime = new Date().getTime();
var tapLength = currentTime - session.screenShareElement.touchLastTap;
clearTimeout(session.screenShareElement.touchTimeOut);
if (tapLength < 500 && tapLength > 0) {
///
log("double touched");
session.screenShareElement.touchCount+=1;
event.preventDefault();
if (session.screenShareElement.touchCount<5){
session.screenShareElement.touchLastTap = currentTime;
return false;
}
session.screenShareElement.touchLastTap = 0;
session.screenShareElement.touchCount=0;
var [menu, innerMenu] = statsMenuCreator();
menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu, true);
printMyStats(innerMenu, true);
event.stopPropagation();
return false;
//////
} else {
session.screenShareElement.touchCount=1;
session.screenShareElement.touchLastTap = currentTime;
session.screenShareElement.touchTimeOut = setTimeout(function(vv) {
clearTimeout(vv.touchTimeOut);
vv.touchLastTap = 0;
vv.touchCount=0;
}, 5000, session.screenShareElement);
}
});
}
firsttime=false
session.screenShareElement.srcObject = session.screenStream;
getById("screensharebutton").classList.add("green");
getById("screensharebutton").ariaPressed = "true";
getById("screensharebutton").title = miscTranslations["stop-screen-sharing"];
getById("screenshare2button").classList.add("green");
getById("screenshare2button").ariaPressed = "true";
getById("screenshare2button").title = miscTranslations["stop-screen-sharing"];
getById("screenshare3button").classList.add("green");
getById("screenshare3button").ariaPressed = "true";
getById("screenshare3button").title = miscTranslations["stop-screen-sharing"];
if (session.autorecord || session.autorecordlocal){
log("AUTO RECORD START SSTYPE3");
setTimeout(function(s){
if (!session.screenStream){return;}
try {
var ele = document.getElementById("recordLocalScreenbutton");
if (ele){
ele.classList.add("red");
ele.classList.remove("hidden");
if (!ele.vid){
var v = createVideoElement();
v.muted = true;
v.srcObject = s;
ele.vid = v;
}
if (ele.vid.recorder || ele.vid.recording){
ele.vid.recorder.stop();
ele.classList.remove("red");
ele.classList.add("hidden");
ele.vid = null;
} else {
recordLocalVideo(null, session.recordLocal, ele.vid)
}
}
} catch(e){errorlog(e);}
},2000, session.screenStream);
}
setTimeout(function(){
updateMixer();
},100);
setTimeout(function(){
updateMixer();
},1000);
}).catch(function(err) {
errorlog(err);
});
} else { // removing a screen
stopSecondScreenshare();
}
}
function recordLocalScreenStopRecord(){
var ele = document.getElementById("recordLocalScreenbutton");
if (ele){
try {
ele.classList.remove("red");
ele.classList.add("hidden");
if (ele.vid){
if (ele.vid.recorder || ele.vid.recording){
ele.vid.recorder.stop();
}
ele.vid = null;
}
}catch(e){errorlog(e);}
}
}
function stopSecondScreenshare(){
var msg = {};
msg.screenStopped = true;
session.sendMessage(msg);
var ele = document.getElementById("recordLocalScreenbutton");
if (ele){
try {
ele.classList.remove("red");
ele.classList.add("hidden");
if (ele.vid){
if (ele.vid.recorder || ele.vid.recording){
ele.vid.recorder.stop();
}
ele.vid = null;
}
}catch(e){errorlog(e);}
}
session.screenStream.getTracks().forEach(function(track) { // previous video track; saving it. Must remove the track at some point.
for (UUID in session.pcs){
if (!("realUUID" in session.pcs[UUID])){continue;} // not a screen share, so skip
var senders = getSenders2(UUID);
senders.forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams?
if (sender.track && sender.track.kind == "video") {
sender.track.enabled = false;
}
});
}
if (track.id in screenshareTracks) { // obs isn't included, so no point to check track.kind
session.screenStream.removeTrack(track);
track.stop();
screenshareTracks[track.id] = false;
}
});
session.screenStream = false;
session.screenShareState = false;
pokeIframeAPI('screen-share-state', session.screenShareState, null, session.streamID);
getById("screensharebutton").classList.remove("green");
getById("screensharebutton").ariaPressed = "false";
getById("screensharebutton").title = miscTranslations["share-a-screen"];
getById("screenshare2button").classList.remove("green");
getById("screenshare2button").ariaPressed = "false";
getById("screenshare2button").title = miscTranslations["share-a-screen"];
getById("screenshare3button").classList.remove("green");
getById("screenshare3button").ariaPressed = "false";
getById("screenshare3button").title = miscTranslations["share-a-screen"];
setTimeout(function(){
updateMixer();
},100);
setTimeout(function(){
updateMixer();
},1000);
}
function createControlBoxScreenshare(UUID, soloLink, streamID) {
if (document.getElementById("deleteme")) {
getById("deleteme").parentNode.removeChild(getById("deleteme"));
}
var controls = getById("controls_blank").cloneNode(true);
controls.classList.remove("hidden");
controls.id = "controls_" + UUID;
var container = document.createElement("div");
container.className = "vidcon directorMargins";
container.id = "container_" + UUID; // needed to delete on user disconnect
container.UUID = UUID;
container.dataset.UUID = UUID;
if (session.orderby){
try {
var added = false;
for (var i=0;i streamID.toLowerCase()){
getById("guestFeeds").insertBefore(container, getById("guestFeeds").children[i]);
added = true;
break;
}
}
}
}
if (!added){
getById("guestFeeds").appendChild(container);
}
} catch(e){
getById("guestFeeds").appendChild(container);
}
} else {
getById("guestFeeds").appendChild(container);
}
controls.querySelector(".controlsGrid").classList.add("notmain");
if (!session.rpcs[UUID].voiceMeter) {
if (session.meterStyle==1){
session.rpcs[UUID].voiceMeter = getById("voiceMeterTemplate2").cloneNode(true);
} else {
session.rpcs[UUID].voiceMeter = getById("voiceMeterTemplate").cloneNode(true);
session.rpcs[UUID].voiceMeter.style.opacity = 0;
if (session.meterStyle==2){
session.rpcs[UUID].voiceMeter.classList.add("video-meter-2");
session.rpcs[UUID].voiceMeter.classList.remove("video-meter");
} else {
session.rpcs[UUID].voiceMeter.classList.add("video-meter-director");
}
}
session.rpcs[UUID].voiceMeter.id = "voiceMeter_" + UUID;
session.rpcs[UUID].voiceMeter.dataset.level = 0;
session.rpcs[UUID].voiceMeter.classList.remove("hidden");
}
session.rpcs[UUID].remoteMuteElement = getById("muteStateTemplate").cloneNode(true);
session.rpcs[UUID].remoteMuteElement.id = "";
session.rpcs[UUID].remoteMuteElement.style.top = "5px";
session.rpcs[UUID].remoteMuteElement.style.right = "7px";
session.rpcs[UUID].remoteVideoMuteElement = getById("videoMuteStateTemplate").cloneNode(true);
session.rpcs[UUID].remoteVideoMuteElement.id = "";
session.rpcs[UUID].remoteVideoMuteElement.style.top = "5px";
session.rpcs[UUID].remoteVideoMuteElement.style.right = "28px";
session.rpcs[UUID].remoteRaisedHandElement = getById("raisedHandTemplate").cloneNode(true);
session.rpcs[UUID].remoteRaisedHandElement.id = "";
session.rpcs[UUID].remoteRaisedHandElement.style.top = "5px";
session.rpcs[UUID].remoteRaisedHandElement.style.right = "49px";
var videoContainer = document.createElement("div");
videoContainer.id = "videoContainer_" + UUID; // needed to delete on user disconnect
videoContainer.style.margin = "0";
videoContainer.style.position = "relative";
videoContainer.style.minHeight = "30px";
var iframeDetails = document.createElement("div");
iframeDetails.id = "iframeDetails_" + UUID; // needed to delete on user disconnect
iframeDetails.className = "iframeDetails hidden";
//controls.innerHTML += "";
//controls.innerHTML += "";
var handsID = "hands_" + UUID;
controls.innerHTML += "
";
if (session.hidesololinks==false){
controls.innerHTML += "
\
" + sanitizeChat(soloLink) + "\
\
";
}
controls.innerHTML += "\
";
controls.querySelectorAll('[data-action-type]').forEach((ele) => { // give action buttons some self-reference
ele.dataset.UUID = UUID;
ele.dataset.sid = streamID;
});
var buttons = "";
if (session.slotmode){
var slots = document.querySelectorAll("div.slotsbar[data-slot]");
var biggestSlot=0;
var slotDefault = null;
if (streamID in session.pastSlots){
slotDefault = session.pastSlots[streamID];
}
if (session.slotmode==1){
for (var i=0;ibiggestSlot){
biggestSlot = parseInt(slots[i].dataset.slot);
}
if (slotDefault===parseInt(slots[i].dataset.slot)){
slotDefault = null;
}
}
biggestSlot+=1;
}
if (slotDefault!==null){
biggestSlot = slotDefault;
}
var slotName = "slot: "+biggestSlot;
if (!biggestSlot){
slotName = "unset";
}
session.pastSlots[streamID] = biggestSlot;
buttons += "
\
";
}
buttons += "
ID: " + streamID + "\
\
\
\
";
container.innerHTML = buttons;
updateLockedElements();
var videoContainerControlBox = document.createElement("div");
videoContainerControlBox.className = "controlVideoBox";
container.containerControlBox = videoContainerControlBox
container.appendChild(videoContainerControlBox);
videoContainerControlBox.appendChild(videoContainer);
if (session.signalMeter){
if (!session.rpcs[UUID].signalMeter){
session.rpcs[UUID].signalMeter = getById("signalMeterTemplate").cloneNode(true);
session.rpcs[UUID].signalMeter.id = "signalMeter_" + UUID;
session.rpcs[UUID].signalMeter.dataset.level = 0;
session.rpcs[UUID].signalMeter.classList.remove("hidden");
session.rpcs[UUID].signalMeter.dataset.UUID = UUID;
session.rpcs[UUID].signalMeter.title = miscTranslations["signal-meter"];
//if (session.rpcs[UUID].stats.info && session.rpcs[UUID].stats.info.cpu_maxed){
// session.rpcs[UUID].signalMeter.dataset.cpu = "1";
//}
session.rpcs[UUID].signalMeter.addEventListener('click', function(e) { // show stats of video if double clicked
log("clicked signal meter");
try {
e.preventDefault();
var uid = e.currentTarget.dataset.UUID;
if ("stats" in session.rpcs[uid]){
var [menu, innerMenu] = statsMenuCreator();
printViewStats(innerMenu, uid );
menu.interval = setInterval(printViewStats, session.statsInterval, innerMenu, uid);
}
e.stopPropagation();
return false;
} catch(e){errorlog(e);}
});
}
videoContainer.appendChild(session.rpcs[UUID].signalMeter);
}
if (session.batteryMeter){
if (!session.rpcs[UUID].batteryMeter){
session.rpcs[UUID].batteryMeter = getById("batteryMeterTemplate").cloneNode(true);
session.rpcs[UUID].batteryMeter.id = "batteryMeter_" + UUID;
batteryMeterInfoUpdate(UUID);
}
videoContainer.appendChild(session.rpcs[UUID].batteryMeter);
}
if (session.showConnections){
if (!session.rpcs[UUID].connectionDetails){
createConnectionDetailsEle(UUID);
}
videoContainer.appendChild(session.rpcs[UUID].connectionDetails);
}
videoContainer.appendChild(session.rpcs[UUID].voiceMeter);
videoContainer.appendChild(session.rpcs[UUID].remoteMuteElement);
videoContainer.appendChild(session.rpcs[UUID].remoteVideoMuteElement);
videoContainer.appendChild(session.rpcs[UUID].remoteRaisedHandElement);
videoContainer.appendChild(iframeDetails);
videoContainer.appendChild(session.rpcs[UUID].videoElement);
container.appendChild(controls);
session.group.forEach(group=>{
var ele = controls.querySelector('[data-action-type="toggle-group"][data--u-u-i-d="'+UUID+'"][data-group="'+group+'"]');
if (!ele){
var newGroup = htmlToElement('');
var added = false;
container.querySelectorAll('.customGroup>[data-group]').forEach(ele=>{
log(ele);
if (!added && ele.dataset.group>group+""){
ele.parentNode.insertBefore(newGroup, ele);
added = true;
}
});
if (!added){
var newGroupCon = container.querySelector(".customGroup");
if (!newGroupCon){
newGroupCon = document.createElement("div");
newGroupCon.classList.add("customGroup");
container.appendChild(newGroupCon);
}
newGroupCon.appendChild(newGroup);
}
}
});
initSceneList(UUID);
pokeIframeAPI("control-box", true, UUID);
}