improved &chunked mode; other features

for experimentation
This commit is contained in:
Steve Seguin 2022-01-03 19:17:22 -05:00 committed by GitHub
parent 518ac2a4ad
commit c24dac0253
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 973 additions and 522 deletions

View File

@ -233,6 +233,11 @@
button.onclick = function(){iframe.contentWindow.postMessage({"getStreamIDs":true}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "get media device list";
button.onclick = function(){iframe.contentWindow.postMessage({"getDeviceList":true}, '*');};
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Start AutoMixer";
button.onclick = function(){iframe.contentWindow.postMessage({"automixer":true}, '*');};
@ -347,6 +352,17 @@
iframeContainer.appendChild(outputWindow);
}
if ("deviceList" in e.data){
var outputWindow = document.createElement("div");
outputWindow.innerHTML = "child-page-action: deviceList<br />";
for (var i = 0;i<e.data.deviceList.length;i++){
outputWindow.innerHTML += e.data.deviceList[i].label + "<br />";
}
outputWindow.style.border="1px dotted black";
iframeContainer.appendChild(outputWindow);
}
if ("loudness" in e.data){
console.log(e.data);
if (document.getElementById("loudness")){

View File

@ -115,7 +115,7 @@
<div id="controlButtons" >
<div id="obsState" class="advanced" >ACTIVE</div>
<div id="subControlButtons">
<div id="queuebutton" title="Load the next guest in queue" alt="Load the next guest in queue" onmousedown="event.preventDefault(); event.stopPropagation();" onclick="session.nextQueue()" tabindex="16" role="button" aria-pressed="false" onkeyup="enterPressedClick(event,this);" class="advanced float" style="cursor: pointer;" >
<div id="queuebutton" title="Load the next guest in queue" alt="Load the next guest in queue" onmousedown="event.preventDefault(); event.stopPropagation();" onclick="nextQueue()" tabindex="16" role="button" aria-pressed="false" onkeyup="enterPressedClick(event,this);" class="advanced float" style="cursor: pointer;" >
<i id="queuetoggle" class="toggleSize las la-stream my-float"></i>
<div id="queueNotification"></div>
</div>

872
lib.js
View File

@ -316,6 +316,156 @@ function submitDebugLog(msg){
}
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));
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(){
for (var i in session.rpcs){
try{
if (session.rpcs[i].videoElement){
if (session.rpcs[i].videoElement.paused){
session.rpcs[i].videoElement.play().then(_ => {
log("playing");
}).catch(warnlog);
}
}
}catch(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(){
warnlog("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;
}
function createVideoElement(){
try{
deleteOldMedia();
} catch(e){errorlog(e);}
var v = document.createElement("video");
videoElements.push(v);
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){
@ -1337,6 +1487,100 @@ function getStorage(cname) {
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);
if (session.view===""){
log("Setting view to null disables all playback");
} else if (session.view !== false){
var viewlist = session.view.split(",");
var played = false;
for (var j in viewlist){
if (viewlist[j]==""){
played=true;
// view={blank} implies do not play anything. Useful for setting a default bitrate I guess
} else if (streamid===null){ // play what is in the view list ; not a group room probably
session.watchStream(viewlist[j]);
played=true;
} else if (streamid === viewlist[j]){ // plays if the group room list matches the explicit list
session.watchStream(viewlist[j]);
played=true;
}
}
if (!played){
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 (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);
}
}
function nextQueue(){
if (!session.queue){return;}
if (!session.director){return;}
if (session.queueList.length==0){
getById("queuebutton").classList.add("float2");
getById("queuebutton").classList.add("red");
getById("queuebutton").classList.remove("float");
setTimeout(function(){
getById("queuebutton").classList.add("float");
getById("queuebutton").classList.remove("float2");
getById("queuebutton").classList.remove("red");
},50);
return;
}
var nextStream = session.queueList.shift();
getById("queuebutton").classList.add("float2");
getById("queuebutton").classList.remove("float");
setTimeout(function(){
getById("queuebutton").classList.add("float");
getById("queuebutton").classList.remove("float2");
},200);
updateQueue();
session.watchStream(nextStream);
log("next stream loading: "+nextStream);
}
function updateQueue(){
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");
}
}
function setupIncomingScreenTracking(v, UUID){ // SCREEN element.
if (session.directorList.indexOf(UUID)>=0){
@ -1635,7 +1879,7 @@ function setupIncomingScreenTracking(v, UUID){ // SCREEN element.
}
setTimeout(session.processStats, 1000, UUID);
setTimeout(processStats, 1000, UUID);
}
function setupIncomingVideoTracking(v, UUID){ // video element.
@ -1914,7 +2158,7 @@ function setupIncomingVideoTracking(v, UUID){ // video element.
}
});
setTimeout(session.processStats, 1000, UUID);
setTimeout(processStats, 1000, UUID);
}
function mediaSourceUpdated(UUID, streamID, videoTrack){
@ -1968,6 +2212,34 @@ function updateVolume(update=false){
}
}
var updateMixerTimer = null;
var updateMixerActive = false;
//var cleanupTimeout = null;
function updateMixer(e=false){
clearInterval(updateMixerTimer);
if (updateMixerActive){
if (session.mobile){
updateMixerTimer = setTimeout(function(){updateMixer();},200);
} else {
updateMixerTimer = setTimeout(function(){updateMixer();},50);
}
return;
}
updateMixerActive=true;
log("updating mixer");
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 {
@ -4990,6 +5262,16 @@ function drawFace() {
}
//////// 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 remoteStats(msg, UUID){
if (session.director){
var output = "";
@ -5034,6 +5316,338 @@ function remoteStats(msg, UUID){
}
}
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){
log("trying to play");
session.rpcs[UUID].videoElement.play().then(_ => {
log("playing");
session.firstPlayTriggered=true;
}).catch(warnlog);
}
} catch (e){};
if (session.rpcs[UUID].mc){
processMeshcastStats(UUID);
}
try {
session.rpcs[UUID].getStats().then(function(stats){
if (!(UUID in session.rpcs)){return;}
setTimeout(processStats, 3000, UUID);
if (!session.rpcs[UUID].stats['Peer-to-Peer Connection']){
session.rpcs[UUID].stats['Peer-to-Peer Connection'] = {};
}
stats.forEach(stat=>{
if ((stat.type == "candidate-pair") && (stat.nominated==true)){
session.rpcs[UUID].stats['Peer-to-Peer Connection']._local_ice_id = stat.localCandidateId;
session.rpcs[UUID].stats['Peer-to-Peer Connection']._remote_ice_id = stat.remoteCandidateId;
session.rpcs[UUID].stats['Peer-to-Peer Connection'].Round_Trip_Time_ms = stat.currentRoundTripTime*1000;
} else if ((stat.type=="track") && (stat.remoteSource==true)){
if (stat.id in session.rpcs[UUID].stats){
session.rpcs[UUID].stats[stat.id]._trackID = stat.trackIdentifier;
session.rpcs[UUID].stats[stat.id].Buffer_Delay_in_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.Buffer_Delay_in_ms = 0;
media._trackID = stat.trackIdentifier;
session.rpcs[UUID].stats[stat.id] = media;
}
} else if (stat.type=="remote-candidate"){
if (("_remote_ice_id" in session.rpcs[UUID].stats['Peer-to-Peer Connection']) && (session.rpcs[UUID].stats['Peer-to-Peer Connection']._remote_ice_id != stat.id )){return;} // not matched to nominated one
if ("candidateType" in stat){
session.rpcs[UUID].stats['Peer-to-Peer Connection'].remote_candidateType = stat.candidateType;
if (stat.candidateType === "relay"){
if ("relayProtocol" in stat){
session.rpcs[UUID].stats['Peer-to-Peer Connection'].remote_relayProtocol = stat.relayProtocol;
}
if ("ip" in stat){session.rpcs[UUID].stats['Peer-to-Peer Connection'].remote_relay_IP = stat.ip;}
} else {
try {
delete session.rpcs[UUID].stats['Peer-to-Peer Connection'].local_relayIP;
delete session.rpcs[UUID].stats['Peer-to-Peer Connection'].local_relayProtocol;
} catch(e){}
}
}
if ("networkType" in stat){
session.rpcs[UUID].stats['Peer-to-Peer Connection'].remote_networkType = stat.networkType;
}
} else if (stat.type=="local-candidate"){
if (("_local_ice_id" in session.rpcs[UUID].stats['Peer-to-Peer Connection']) && (session.rpcs[UUID].stats['Peer-to-Peer Connection']._local_ice_id != stat.id )){return;} // not matched to nominated one
if ("candidateType" in stat){
session.rpcs[UUID].stats['Peer-to-Peer Connection'].local_candidateType = stat.candidateType;
if (stat.candidateType === "relay"){
if ("relayProtocol" in stat){
session.rpcs[UUID].stats['Peer-to-Peer Connection'].local_relayProtocol = stat.relayProtocol;
}
if ("ip" in stat){session.rpcs[UUID].stats['Peer-to-Peer Connection'].local_relayIP = stat.ip;}
} else {
try {
delete session.rpcs[UUID].stats['Peer-to-Peer Connection'].local_relayIP;
delete session.rpcs[UUID].stats['Peer-to-Peer Connection'].local_relayProtocol;
} catch(e){}
}
}
if ("networkType" in stat){
session.rpcs[UUID].stats['Peer-to-Peer Connection'].remote_networkType = stat.networkType;
}
} 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));
}
}
}
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" in stat)){
session.rpcs[UUID].stats[stat.trackId] = session.rpcs[UUID].stats[stat.trackId] || {};
session.rpcs[UUID].stats[stat.trackId].Bitrate_in_kbps = parseInt(8*(stat.bytesReceived - session.rpcs[UUID].stats[stat.trackId]._last_bytes)/( stat.timestamp - session.rpcs[UUID].stats[stat.trackId]._last_time));
session.rpcs[UUID].stats[stat.trackId]._last_bytes = stat.bytesReceived || session.rpcs[UUID].stats[stat.trackId]._last_bytes;
session.rpcs[UUID].stats[stat.trackId]._last_time = stat.timestamp || session.rpcs[UUID].stats[stat.trackId]._last_time;
session.rpcs[UUID].stats._codecId = stat.codecId;
session.rpcs[UUID].stats._codecIdTrackId = stat.trackId;
if (stat.mediaType=="video"){
session.rpcs[UUID].stats[stat.trackId].type = "Video Track"
session.rpcs[UUID].stats[stat.trackId]._type = "video";
if ((session.obsfix) && ("codec" in session.rpcs[UUID].stats) && (session.rpcs[UUID].stats.codec=="video/VP8")){
session.rpcs[UUID].stats[stat.trackId].pliDelta = (stat.pliCount - session.rpcs[UUID].stats[stat.trackId].keyFramesRequested_pli) || 0;
session.rpcs[UUID].stats[stat.trackId].nackTrigger = (stat.nackCount - session.rpcs[UUID].stats[stat.trackId].streamErrors_nackCount + session.rpcs[UUID].stats[stat.trackId].nackTrigger) || 0;
log("OBS PLI FIX MODE ON");
if ((session.rpcs[UUID].stats[stat.trackId].pliDelta===0) && (session.rpcs[UUID].stats[stat.trackId].nackTrigger >= session.obsfix)){ // heavy packet loss with no pliCount?
session.requestKeyframe(UUID);
session.rpcs[UUID].stats[stat.trackId].nackTrigger = 0;
log("TRYING KEYFRAME");
} else if (session.rpcs[UUID].stats[stat.trackId].pliDelta>0){
session.rpcs[UUID].stats[stat.trackId].nackTrigger = 0;
}
} else if ((session.obsfix) && ("codec" in session.rpcs[UUID].stats) && (session.rpcs[UUID].stats.codec=="video/VP9")){
session.rpcs[UUID].stats[stat.trackId].pliDelta = (stat.pliCount - session.rpcs[UUID].stats[stat.trackId].keyFramesRequested_pli) || 0;
session.rpcs[UUID].stats[stat.trackId].nackTrigger = (stat.nackCount - session.rpcs[UUID].stats[stat.trackId].streamErrors_nackCount + session.rpcs[UUID].stats[stat.trackId].nackTrigger) || 0;
log("OBS PLI FIX MODE ON");
if ((session.rpcs[UUID].stats[stat.trackId].pliDelta===0) && (session.rpcs[UUID].stats[stat.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[stat.trackId].nackTrigger = 0;
log("TRYING KEYFRAME");
} else if (session.rpcs[UUID].stats[stat.trackId].pliDelta>0){
session.rpcs[UUID].stats[stat.trackId].nackTrigger = 0;
}
}
session.rpcs[UUID].stats[stat.trackId].keyFramesRequested_pli = stat.pliCount || 0;
session.rpcs[UUID].stats[stat.trackId].streamErrors_nackCount = stat.nackCount || 0;
//warnlog(stat);
if ("framesPerSecond" in stat){
session.rpcs[UUID].stats[stat.trackId].FPS = parseInt(stat.framesPerSecond);
} else if (("framesDecoded" in stat) && (stat.timestamp)){
var lastFramesDecoded = 0;
var lastTimestamp = 0;
try{
lastFramesDecoded = session.rpcs[UUID].stats[stat.trackId]._framesDecoded;
lastTimestamp = session.rpcs[UUID].stats[stat.trackId]._timestamp;
} catch(e){}
session.rpcs[UUID].stats[stat.trackId].FPS = parseInt(10*(stat.framesDecoded - lastFramesDecoded)/(stat.timestamp/1000 - lastTimestamp))/10;
//session.rpcs[UUID].stats[stat.trackId].FPS = parseInt((stat.framesDecoded - lastFramesDecoded)/(stat.timestamp/1000 - lastTimestamp));
session.rpcs[UUID].stats[stat.trackId]._framesDecoded = stat.framesDecoded;
session.rpcs[UUID].stats[stat.trackId]._timestamp = stat.timestamp/1000;
}
} else if (stat.mediaType=="audio"){
//log("AUDIO LEVEL: "+stat.audioLevel);
session.rpcs[UUID].stats[stat.trackId].type = "Audio Track";
session.rpcs[UUID].stats[stat.trackId]._type = "audio";
if ("audioLevel" in stat){
session.rpcs[UUID].stats[stat.trackId].audio_level = parseInt(parseFloat(stat.audioLevel)*10000)/10000.0;
}
}
if ("packetsLost" in stat && "packetsReceived" in stat){
if (!("_packetsLost" in session.rpcs[UUID].stats[stat.trackId])){
session.rpcs[UUID].stats[stat.trackId]._packetsLost = stat.packetsLost;
}
if (!("_packetsReceived" in session.rpcs[UUID].stats[stat.trackId])){
session.rpcs[UUID].stats[stat.trackId]._packetsReceived = stat.packetsReceived;
}
if (!("packetLoss_in_percentage" in session.rpcs[UUID].stats[stat.trackId])){
session.rpcs[UUID].stats[stat.trackId].packetLoss_in_percentage = 0;
}
session.rpcs[UUID].stats[stat.trackId].packetLoss_in_percentage = session.rpcs[UUID].stats[stat.trackId].packetLoss_in_percentage*0.35 + 0.65*((stat.packetsLost-session.rpcs[UUID].stats[stat.trackId]._packetsLost)*100.0)/((stat.packetsReceived-session.rpcs[UUID].stats[stat.trackId]._packetsReceived)+(stat.packetsLost-session.rpcs[UUID].stats[stat.trackId]._packetsLost)) || 0;
if (session.rpcs[UUID].signalMeter && (session.rpcs[UUID].stats[stat.trackId]._type==="video")){
if (session.rpcs[UUID].stats[stat.trackId].packetLoss_in_percentage<0.01){
if (session.rpcs[UUID].stats[stat.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[stat.trackId].packetLoss_in_percentage<0.3){
session.rpcs[UUID].signalMeter.dataset.level = 4;
} else if (session.rpcs[UUID].stats[stat.trackId].packetLoss_in_percentage<1.0){
session.rpcs[UUID].signalMeter.dataset.level = 3;
} else if (session.rpcs[UUID].stats[stat.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[stat.trackId]._packetsReceived = stat.packetsReceived;
session.rpcs[UUID].stats[stat.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);
}
}
}
});
if (session.buffer!==false){
playoutdelay(UUID);
}
setTimeout(function(){
session.directorSpeakerMute();
session.directorDisplayMute();
},0);
});
} catch (e){errorlog(e);}
};
function playoutdelay(UUID){ // applies a delay to all videos
try {
var target_buffer = session.buffer || 0;
target_buffer = parseFloat(target_buffer);
if (session.buffer!==false){
// if buffer is set, then session.sync will be set; at least to 0.
var receivers = getReceivers2(UUID).reverse(); //session.rpcs[UUID].getReceivers().reverse();
var video_delay = 0;
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) && ("Buffer_Delay_in_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 - session.rpcs[UUID].stats[tid].Buffer_Delay_in_ms; // target_Butt
if (sync_offset>target_buffer){
sync_offset=target_buffer;
}
if (session.rpcs[UUID].stats[tid]._type=="audio"){
if (receiver.track.id in session.rpcs[UUID].delayNode){
log("updating audio delay");
var audio_delay = video_delay - session.rpcs[UUID].stats[tid].Buffer_Delay_in_ms + session.sync; // video is typically showing greater delay than video
if (audio_delay<0){audio_delay=0;}
log("audio_delay : "+audio_delay);
log("audioCtx : "+ session.audioCtx.currentTime);
session.rpcs[UUID].delayNode[receiver.track.id].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"){
//log("THIS SHOULD BE BEFORE AUDIO - video track"+session.rpcs[UUID].stats[tid].type);
video_delay = session.rpcs[UUID].stats[tid].Buffer_Delay_in_ms;
if(sync_offset<0){sync_offset=0;}
session.rpcs[UUID].stats[tid]._sync_offset = sync_offset;
receiver.playoutDelayHint = parseFloat(sync_offset/1000); // only the video we are going to do the playout delay for; doesn't work well with audio.
}
}
}
} 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]){
@ -5189,6 +5803,248 @@ function printValues(obj) { // see: printViewStats
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'] = {};
}
stats.forEach(stat=>{
if ((stat.type == "candidate-pair") && (stat.nominated==true)){
session.rpcs[UUID].stats['Meshcast Connection']._local_ice_id = stat.localCandidateId;
session.rpcs[UUID].stats['Meshcast Connection']._remote_ice_id = stat.remoteCandidateId;
session.rpcs[UUID].stats['Meshcast Connection'].Round_Trip_Time_ms = stat.currentRoundTripTime*1000;
} else if ((stat.type=="track") && (stat.remoteSource==true)){
if (stat.id in session.rpcs[UUID].stats){
session.rpcs[UUID].stats[stat.id]._trackID = stat.trackIdentifier;
session.rpcs[UUID].stats[stat.id].Buffer_Delay_in_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.Buffer_Delay_in_ms = 0;
media._trackID = stat.trackIdentifier;
session.rpcs[UUID].stats[stat.id] = media;
}
} else if (stat.type=="remote-candidate"){
if (("_remote_ice_id" in session.rpcs[UUID].stats['Meshcast Connection']) && (session.rpcs[UUID].stats['Meshcast Connection']._remote_ice_id != stat.id )){return;} // not matched to nominated one
if ("candidateType" in stat){
session.rpcs[UUID].stats['Meshcast Connection'].remote_candidateType = stat.candidateType;
if (stat.candidateType === "relay"){
if ("relayProtocol" in stat){
session.rpcs[UUID].stats['Meshcast Connection'].remote_relayProtocol = stat.relayProtocol;
}
if ("ip" in stat){session.rpcs[UUID].stats['Meshcast Connection'].remote_relay_IP = stat.ip;}
} else {
try {
delete session.rpcs[UUID].stats['Meshcast Connection'].local_relayIP;
delete session.rpcs[UUID].stats['Meshcast Connection'].local_relayProtocol;
} catch(e){}
}
}
if ("networkType" in stat){
session.rpcs[UUID].stats['Meshcast Connection'].remote_networkType = stat.networkType;
}
} else if (stat.type=="local-candidate"){
if (("_local_ice_id" in session.rpcs[UUID].stats['Meshcast Connection']) && (session.rpcs[UUID].stats['Meshcast Connection']._local_ice_id != stat.id )){return;} // not matched to nominated one
if ("candidateType" in stat){
session.rpcs[UUID].stats['Meshcast Connection'].local_candidateType = stat.candidateType;
if (stat.candidateType === "relay"){
if ("relayProtocol" in stat){
session.rpcs[UUID].stats['Meshcast Connection'].local_relayProtocol = stat.relayProtocol;
}
if ("ip" in stat){session.rpcs[UUID].stats['Meshcast Connection'].local_relayIP = stat.ip;}
} else {
try {
delete session.rpcs[UUID].stats['Meshcast Connection'].local_relayIP;
delete session.rpcs[UUID].stats['Meshcast Connection'].local_relayProtocol;
} catch(e){}
}
}
if ("networkType" in stat){
session.rpcs[UUID].stats['Meshcast Connection'].remote_networkType = stat.networkType;
}
} 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" in stat)){
session.rpcs[UUID].stats[stat.trackId] = session.rpcs[UUID].stats[stat.trackId] || {};
session.rpcs[UUID].stats[stat.trackId].Bitrate_in_kbps = parseInt(8*(stat.bytesReceived - session.rpcs[UUID].stats[stat.trackId]._last_bytes)/( stat.timestamp - session.rpcs[UUID].stats[stat.trackId]._last_time));
session.rpcs[UUID].stats[stat.trackId]._last_bytes = stat.bytesReceived || session.rpcs[UUID].stats[stat.trackId]._last_bytes;
session.rpcs[UUID].stats[stat.trackId]._last_time = stat.timestamp || session.rpcs[UUID].stats[stat.trackId]._last_time;
session.rpcs[UUID].stats._codecId = stat.codecId;
session.rpcs[UUID].stats._codecIdTrackId = stat.trackId;
if (stat.mediaType=="video"){
session.rpcs[UUID].stats[stat.trackId].type = "Video Track"
session.rpcs[UUID].stats[stat.trackId]._type = "video";
if ((session.obsfix) && ("codec" in session.rpcs[UUID].stats) && (session.rpcs[UUID].stats.codec=="video/VP8")){
session.rpcs[UUID].stats[stat.trackId].pliDelta = (stat.pliCount - session.rpcs[UUID].stats[stat.trackId].keyFramesRequested_pli) || 0;
session.rpcs[UUID].stats[stat.trackId].nackTrigger = (stat.nackCount - session.rpcs[UUID].stats[stat.trackId].streamErrors_nackCount + session.rpcs[UUID].stats[stat.trackId].nackTrigger) || 0;
log("OBS PLI FIX MODE ON");
if ((session.rpcs[UUID].stats[stat.trackId].pliDelta===0) && (session.rpcs[UUID].stats[stat.trackId].nackTrigger >= session.obsfix)){ // heavy packet loss with no pliCount?
session.requestKeyframe(UUID);
session.rpcs[UUID].stats[stat.trackId].nackTrigger = 0;
log("TRYING KEYFRAME");
} else if (session.rpcs[UUID].stats[stat.trackId].pliDelta>0){
session.rpcs[UUID].stats[stat.trackId].nackTrigger = 0;
}
} else if ((session.obsfix) && ("codec" in session.rpcs[UUID].stats) && (session.rpcs[UUID].stats.codec=="video/VP9")){
session.rpcs[UUID].stats[stat.trackId].pliDelta = (stat.pliCount - session.rpcs[UUID].stats[stat.trackId].keyFramesRequested_pli) || 0;
session.rpcs[UUID].stats[stat.trackId].nackTrigger = (stat.nackCount - session.rpcs[UUID].stats[stat.trackId].streamErrors_nackCount + session.rpcs[UUID].stats[stat.trackId].nackTrigger) || 0;
log("OBS PLI FIX MODE ON");
if ((session.rpcs[UUID].stats[stat.trackId].pliDelta===0) && (session.rpcs[UUID].stats[stat.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[stat.trackId].nackTrigger = 0;
log("TRYING KEYFRAME");
} else if (session.rpcs[UUID].stats[stat.trackId].pliDelta>0){
session.rpcs[UUID].stats[stat.trackId].nackTrigger = 0;
}
}
session.rpcs[UUID].stats[stat.trackId].keyFramesRequested_pli = stat.pliCount || 0;
session.rpcs[UUID].stats[stat.trackId].streamErrors_nackCount = stat.nackCount || 0;
//warnlog(stat);
if ("framesPerSecond" in stat){
session.rpcs[UUID].stats[stat.trackId].FPS = parseInt(stat.framesPerSecond);
} else if (("framesDecoded" in stat) && (stat.timestamp)){
var lastFramesDecoded = 0;
var lastTimestamp = 0;
try{
lastFramesDecoded = session.rpcs[UUID].stats[stat.trackId]._framesDecoded;
lastTimestamp = session.rpcs[UUID].stats[stat.trackId]._timestamp;
} catch(e){}
session.rpcs[UUID].stats[stat.trackId].FPS = parseInt(10*(stat.framesDecoded - lastFramesDecoded)/(stat.timestamp/1000 - lastTimestamp))/10;
//session.rpcs[UUID].stats[stat.trackId].FPS = parseInt((stat.framesDecoded - lastFramesDecoded)/(stat.timestamp/1000 - lastTimestamp));
session.rpcs[UUID].stats[stat.trackId]._framesDecoded = stat.framesDecoded;
session.rpcs[UUID].stats[stat.trackId]._timestamp = stat.timestamp/1000;
}
} else if (stat.mediaType=="audio"){
//log("AUDIO LEVEL: "+stat.audioLevel);
session.rpcs[UUID].stats[stat.trackId].type = "Audio Track";
session.rpcs[UUID].stats[stat.trackId]._type = "audio";
if ("audioLevel" in stat){
session.rpcs[UUID].stats[stat.trackId].audio_level = parseInt(parseFloat(stat.audioLevel)*10000)/10000.0;
}
}
if ("packetsLost" in stat && "packetsReceived" in stat){
if (!("_packetsLost" in session.rpcs[UUID].stats[stat.trackId])){
session.rpcs[UUID].stats[stat.trackId]._packetsLost = stat.packetsLost;
}
if (!("_packetsReceived" in session.rpcs[UUID].stats[stat.trackId])){
session.rpcs[UUID].stats[stat.trackId]._packetsReceived = stat.packetsReceived;
}
if (!("packetLoss_in_percentage" in session.rpcs[UUID].stats[stat.trackId])){
session.rpcs[UUID].stats[stat.trackId].packetLoss_in_percentage = 0;
}
session.rpcs[UUID].stats[stat.trackId].packetLoss_in_percentage = session.rpcs[UUID].stats[stat.trackId].packetLoss_in_percentage*0.35 + 0.65*((stat.packetsLost-session.rpcs[UUID].stats[stat.trackId]._packetsLost)*100.0)/((stat.packetsReceived-session.rpcs[UUID].stats[stat.trackId]._packetsReceived)+(stat.packetsLost-session.rpcs[UUID].stats[stat.trackId]._packetsLost)) || 0;
if (session.rpcs[UUID].signalMeter && (session.rpcs[UUID].stats[stat.trackId]._type==="video")){
if (session.rpcs[UUID].stats[stat.trackId].packetLoss_in_percentage<0.01){
if (session.rpcs[UUID].stats[stat.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[stat.trackId].packetLoss_in_percentage<0.3){
session.rpcs[UUID].signalMeter.dataset.level = 4;
} else if (session.rpcs[UUID].stats[stat.trackId].packetLoss_in_percentage<1.0){
session.rpcs[UUID].signalMeter.dataset.level = 3;
} else if (session.rpcs[UUID].stats[stat.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[stat.trackId]._packetsReceived = stat.packetsReceived;
session.rpcs[UUID].stats[stat.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);
}
}
}
});
});
} catch (e){errorlog(e);}
}
function printMyStats(menu) { // see: setupStatsMenu
var scrollLeft = getById("menuStatsBox").scrollLeft;
@ -18932,6 +19788,18 @@ async function recordVideo(target, event, videoKbps = false) { // event.currentT
var UUID = target.dataset.UUID;
var video = session.rpcs[UUID].videoElement;
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) {

View File

@ -1034,7 +1034,27 @@ input[type='radio'] { cursor:pointer; }
margin: 44vh auto;
cursor: help;
}
#retryimage{
display: block;
margin: 25vh auto;
max-width: 50%;
max-height: 50%;
animation: fadeIn 2s;
}
#retrymessage{
display: block;
margin: 80vh auto;
animation: fadeIn 2s;
color: white;
position: absolute;
left: 0;
top: 0;
float: left;
width: 100%;
height: 100%;
text-align: center;
font-size: 2em;
}
@keyframes spin-animation {
0% {

85
main.js
View File

@ -717,6 +717,10 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if (urlParams.has('preloadbitrate')) {
session.preloadbitrate = parseInt(urlParams.get('preloadbitrate')) || 0; // 1000
}
if (urlParams.has('rampuptime')) {
session.rampUpTime = parseInt(urlParams.get('rampuptime')) || 10000;
}
if (urlParams.has('scenetype') || urlParams.has('type')) {
session.sceneType = parseInt(urlParams.get('scenetype')) || parseInt(urlParams.get('type')) || false;
@ -1368,7 +1372,26 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.disableWebAudio = true; // default true; might be useful to disable on slow or old computers?
session.audioMeterGuest = false;
session.audioEffects = false;
session.obsfix = 15; // can be manually set via URL. ; VP8=15, VP9=30. (previous was 20.)
if (window.obsstudio.pluginVersion){
if (navigator.userAgent.indexOf('Mac OS X') !== -1){ // if mac, no fix
//session.obsfix = false;
} else if (window.obsstudio.pluginVersion=="2.17.4"){ // if obs v27.2 beta, no fix
//session.obsfix = false;
} else {
var ver = window.obsstudio.pluginVersion.split(".");
if (ver.length >= 2){
if (parseInt(ver[0])<=2){
if (parseInt(ver[0])==2){
if (parseInt(ver[1])<=16){
session.obsfix = 15;
}
} else {
session.obsfix = 15;
}
}
}
}
}
try {
log("OBS VERSION:" + window.obsstudio.pluginVersion);
log("macOS: " + navigator.userAgent.indexOf('Mac OS X') != -1);
@ -1493,9 +1516,9 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
} catch(e){errorlog(e);}
if (urlParams.has("videodevice") || urlParams.has("vdevice") || urlParams.has('vd') || urlParams.has('device') || urlParams.has('d')) {
if (urlParams.has("videodevice") || urlParams.has("vdevice") || urlParams.has('vd') || urlParams.has('device') || urlParams.has('d') || urlParams.has('vdo')) {
session.videoDevice = urlParams.get("videodevice") || urlParams.get("vdevice") || urlParams.get("vd") || urlParams.get("device") || urlParams.get("d");
session.videoDevice = urlParams.get("videodevice") || urlParams.get("vdevice") || urlParams.get("vd") || urlParams.get("device") || urlParams.get("d") || urlParams.get("vdo");
if (session.videoDevice === null) {
session.videoDevice = "1";
@ -1528,7 +1551,10 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
// whatever the user entered I guess, santitized.
session.videoDevice = session.videoDevice.replace(/[\W]+/g, "_").toLowerCase();
}
getById("videoMenu").style.display = "none";
if (!urlParams.has('vdo')){
getById("videoMenu").style.display = "none";
}
log("session.videoDevice:" + session.videoDevice);
}
@ -1741,6 +1767,14 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.sensorData = parseInt(session.sensorData);
}
if (urlParams.has('ptime')) {
session.ptime = parseInt(urlParams.get('ptime')) || 20;
if (session.ptime<10){
session.ptime = 10;
}
}
if (urlParams.has('minptime')) {
session.minptime = parseInt(urlParams.get('minptime')) || 10;
if (session.minptime < 10) {
@ -1761,16 +1795,6 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
}
if (urlParams.has('ptime')) {
session.ptime = parseInt(urlParams.get('ptime')) || 20;
if (session.minptime < 10) {
session.ptime = 10;
}
if (session.minptime > 300) {
session.ptime = 300;
}
}
if (urlParams.has('codec')) {
log("CODEC CHANGED");
@ -2954,23 +2978,38 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
getById("mainmenu").innerHTML = '';
getById("mainmenu").classList.remove("row");
var timeout = 5000;
if (urlParams.has('waittimeout')){
timeout = parseInt(urlParams.get('waittimeout')) || 0;
}
setTimeout(function() {
try {
if ((session.view) && (!(session.cleanOutput))) {
if ((session.view)) {
if (document.getElementById("mainmenu")) {
getById("mainmenu").innerHTML += '<div class="retry-spinner" id="retrySpinner"></div>';
retrySpinner.title = miscTranslations["waiting-for-the-stream"]
retrySpinner.onclick = function(){
updateURL("cleanoutput");
location.reload();
if (urlParams.has('waitimage')){
getById("mainmenu").innerHTML += '<img id="retryimage"/>';
getById("retryimage").src = decodeURIComponent(urlParams.get('waitimage'));
getById("retryimage").onerror = function(){this.style.display='none';};
} else if (!(session.cleanOutput)){
getById("mainmenu").innerHTML += '<div class="retry-spinner" id="retrySpinner"></div>';
getById("retrySpinner").onclick = function(){
updateURL("cleanoutput");
location.reload();
}
getById("retrySpinner").title = miscTranslations["waiting-for-the-stream"]
}
if (urlParams.has('waitmessage')){
getById("mainmenu").innerHTML += '<div id="retrymessage"></div>';
getById("retrymessage").innerText = urlParams.get('waitmessage');
getById("retrySpinner").title = urlParams.get('waitmessage');
}
}
}
} catch (e) {
errorlog("Error handling QR Code failure");
errorlog(e);
}
}, 5000);
}, timeout);
log("auto playing");
var SafariVer = safariVersion();
@ -3267,7 +3306,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
warnlog(e.data.getDeviceList);
enumerateDevices().then(function(deviceInfos) {
parent.postMessage({
"deviceList": deviceInfos
"deviceList": JSON.parse(JSON.stringify(deviceInfos))
}, "*");
});
}

View File

@ -7,7 +7,7 @@
body{
padding:0;
margin:0;
background-color: #0000;
background-color: #c5c2c2;
}
iframe {
border:0;
@ -89,452 +89,6 @@
padding: 10px 10px 0 15px;
border: 1px solid black;
}
@keyframes move {
100% {
transform: translate3d(0, 0, 1px) rotate(360deg);
}
}
.background {
position: fixed;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
background: #bfbfbf;
overflow: hidden;
z-index:-1;
}
.background span {
width: 50vmin;
height: 50vmin;
border-radius: 50vmin;
backface-visibility: hidden;
position: absolute;
animation: move;
animation-duration: 7;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
.background span:nth-child(0) {
color: #a9a0bb;
top: 26%;
left: 54%;
animation-duration: 133s;
animation-delay: -143s;
transform-origin: 17vw 23vh;
box-shadow: 100vmin 0 13.032236536031524vmin currentColor;
}
.background span:nth-child(1) {
color: #888aa0;
top: 32%;
left: 60%;
animation-duration: 98s;
animation-delay: -19s;
transform-origin: -9vw -17vh;
box-shadow: -100vmin 0 13.361880379427102vmin currentColor;
}
.background span:nth-child(2) {
color: #a9a0bb;
top: 76%;
left: 30%;
animation-duration: 27s;
animation-delay: -382s;
transform-origin: -21vw -12vh;
box-shadow: -100vmin 0 13.29654068929897vmin currentColor;
}
.background span:nth-child(3) {
color: #a9a0bb;
top: 13%;
left: 3%;
animation-duration: 89s;
animation-delay: -71s;
transform-origin: -5vw -23vh;
box-shadow: -100vmin 0 13.269376230706396vmin currentColor;
}
.background span:nth-child(4) {
color: #888aa0;
top: 98%;
left: 83%;
animation-duration: 61s;
animation-delay: -57s;
transform-origin: 19vw -16vh;
box-shadow: 100vmin 0 13.465063933009704vmin currentColor;
}
.background span:nth-child(5) {
color: #4b6477;
top: 73%;
left: 100%;
animation-duration: 312s;
animation-delay: -390s;
transform-origin: 23vw -6vh;
box-shadow: -100vmin 0 12.719463296930117vmin currentColor;
}
.background span:nth-child(6) {
color: #888aa0;
top: 6%;
left: 84%;
animation-duration: 176s;
animation-delay: -255s;
transform-origin: 16vw 3vh;
box-shadow: -100vmin 0 13.358884039462355vmin currentColor;
}
.background span:nth-child(7) {
color: #888aa0;
top: 99%;
left: 9%;
animation-duration: 221s;
animation-delay: -385s;
transform-origin: -16vw -9vh;
box-shadow: 100vmin 0 13.435082385169103vmin currentColor;
}
.background span:nth-child(8) {
color: #a9a0bb;
top: 8%;
left: 92%;
animation-duration: 142s;
animation-delay: -99s;
transform-origin: -1vw -14vh;
box-shadow: -100vmin 0 13.456440775816723vmin currentColor;
}
.background span:nth-child(9) {
color: #a9a0bb;
top: 80%;
left: 70%;
animation-duration: 99s;
animation-delay: -124s;
transform-origin: 21vw -10vh;
box-shadow: 100vmin 0 13.007405893839119vmin currentColor;
}
.background span:nth-child(10) {
color: #888aa0;
top: 42%;
left: 2%;
animation-duration: 70s;
animation-delay: -341s;
transform-origin: -15vw -23vh;
box-shadow: 100vmin 0 13.005431373357645vmin currentColor;
}
.background span:nth-child(11) {
color: #a9a0bb;
top: 28%;
left: 2%;
animation-duration: 244s;
animation-delay: -369s;
transform-origin: -17vw -16vh;
box-shadow: 100vmin 0 13.351086475613746vmin currentColor;
}
.background span:nth-child(12) {
color: #4b6477;
top: 80%;
left: 50%;
animation-duration: 117s;
animation-delay: -399s;
transform-origin: 24vw 8vh;
box-shadow: 100vmin 0 12.979558964166843vmin currentColor;
}
.background span:nth-child(13) {
color: #888aa0;
top: 74%;
left: 65%;
animation-duration: 283s;
animation-delay: -50s;
transform-origin: 13vw 14vh;
box-shadow: 100vmin 0 13.282742227479792vmin currentColor;
}
.background span:nth-child(14) {
color: #888aa0;
top: 64%;
left: 61%;
animation-duration: 194s;
animation-delay: -278s;
transform-origin: -6vw 4vh;
box-shadow: -100vmin 0 13.333280580396286vmin currentColor;
}
.background span:nth-child(15) {
color: #a9a0bb;
top: 11%;
left: 86%;
animation-duration: 403s;
animation-delay: -329s;
transform-origin: 3vw 0vh;
box-shadow: -100vmin 0 13.1162690433691vmin currentColor;
}
.background span:nth-child(16) {
color: #4b6477;
top: 14%;
left: 59%;
animation-duration: 100s;
animation-delay: -15s;
transform-origin: -23vw 21vh;
box-shadow: -100vmin 0 13.30508866110655vmin currentColor;
}
.background span:nth-child(17) {
color: #888aa0;
top: 68%;
left: 45%;
animation-duration: 394s;
animation-delay: -137s;
transform-origin: 18vw -20vh;
box-shadow: -100vmin 0 12.809008306332636vmin currentColor;
}
.background span:nth-child(18) {
color: #a9a0bb;
top: 7%;
left: 33%;
animation-duration: 132s;
animation-delay: -346s;
transform-origin: -22vw -17vh;
box-shadow: -100vmin 0 12.940258664941906vmin currentColor;
}
.background span:nth-child(19) {
color: #a9a0bb;
top: 24%;
left: 65%;
animation-duration: 49s;
animation-delay: -44s;
transform-origin: 0vw -24vh;
box-shadow: -100vmin 0 12.733081490401828vmin currentColor;
}
.background span:nth-child(20) {
color: #888aa0;
top: 98%;
left: 49%;
animation-duration: 204s;
animation-delay: -160s;
transform-origin: -1vw 3vh;
box-shadow: -100vmin 0 13.05844646387434vmin currentColor;
}
.background span:nth-child(21) {
color: #a9a0bb;
top: 2%;
left: 29%;
animation-duration: 294s;
animation-delay: -272s;
transform-origin: 13vw -18vh;
box-shadow: -100vmin 0 12.626917095797205vmin currentColor;
}
.background span:nth-child(22) {
color: #a9a0bb;
top: 73%;
left: 32%;
animation-duration: 424s;
animation-delay: -59s;
transform-origin: 19vw 3vh;
box-shadow: 100vmin 0 13.092155905631065vmin currentColor;
}
.background span:nth-child(23) {
color: #888aa0;
top: 45%;
left: 93%;
animation-duration: 277s;
animation-delay: -215s;
transform-origin: 0vw -14vh;
box-shadow: 100vmin 0 12.768802405913084vmin currentColor;
}
.background span:nth-child(24) {
color: #4b6477;
top: 35%;
left: 40%;
animation-duration: 375s;
animation-delay: -205s;
transform-origin: -16vw -21vh;
box-shadow: -100vmin 0 12.845025825087218vmin currentColor;
}
.background span:nth-child(25) {
color: #a9a0bb;
top: 82%;
left: 77%;
animation-duration: 211s;
animation-delay: -150s;
transform-origin: 15vw -14vh;
box-shadow: 100vmin 0 12.825514275205975vmin currentColor;
}
.background span:nth-child(26) {
color: #4b6477;
top: 99%;
left: 95%;
animation-duration: 342s;
animation-delay: -118s;
transform-origin: -16vw -10vh;
box-shadow: 100vmin 0 13.09952938429165vmin currentColor;
}
.background span:nth-child(27) {
color: #a9a0bb;
top: 24%;
left: 43%;
animation-duration: 191s;
animation-delay: -249s;
transform-origin: 10vw 25vh;
box-shadow: -100vmin 0 12.948079018854608vmin currentColor;
}
.background span:nth-child(28) {
color: #888aa0;
top: 35%;
left: 83%;
animation-duration: 38s;
animation-delay: -5s;
transform-origin: -15vw 21vh;
box-shadow: -100vmin 0 12.884993041770123vmin currentColor;
}
.background span:nth-child(29) {
color: #4b6477;
top: 50%;
left: 19%;
animation-duration: 83s;
animation-delay: -367s;
transform-origin: -17vw 24vh;
box-shadow: 100vmin 0 12.943907699048086vmin currentColor;
}
.background span:nth-child(30) {
color: #4b6477;
top: 94%;
left: 43%;
animation-duration: 139s;
animation-delay: -151s;
transform-origin: 15vw 13vh;
box-shadow: -100vmin 0 13.188888046545854vmin currentColor;
}
.background span:nth-child(31) {
color: #4b6477;
top: 1%;
left: 30%;
animation-duration: 276s;
animation-delay: -118s;
transform-origin: 20vw -13vh;
box-shadow: 100vmin 0 12.67400347628477vmin currentColor;
}
.background span:nth-child(32) {
color: #a9a0bb;
top: 62%;
left: 91%;
animation-duration: 179s;
animation-delay: -95s;
transform-origin: 21vw 0vh;
box-shadow: 100vmin 0 13.078495151792405vmin currentColor;
}
.background span:nth-child(33) {
color: #a9a0bb;
top: 62%;
left: 24%;
animation-duration: 176s;
animation-delay: -417s;
transform-origin: 11vw 1vh;
box-shadow: -100vmin 0 13.180095388225237vmin currentColor;
}
.background span:nth-child(34) {
color: #4b6477;
top: 44%;
left: 7%;
animation-duration: 15s;
animation-delay: -23s;
transform-origin: -5vw 24vh;
box-shadow: -100vmin 0 12.99638150831451vmin currentColor;
}
.background span:nth-child(35) {
color: #888aa0;
top: 41%;
left: 18%;
animation-duration: 52s;
animation-delay: -66s;
transform-origin: 10vw 18vh;
box-shadow: 100vmin 0 12.881618623995024vmin currentColor;
}
.background span:nth-child(36) {
color: #888aa0;
top: 32%;
left: 75%;
animation-duration: 377s;
animation-delay: -252s;
transform-origin: 19vw -21vh;
box-shadow: -100vmin 0 13.235523992130878vmin currentColor;
}
.background span:nth-child(37) {
color: #4b6477;
top: 26%;
left: 96%;
animation-duration: 352s;
animation-delay: -359s;
transform-origin: -22vw -3vh;
box-shadow: 100vmin 0 13.203141789834469vmin currentColor;
}
.background span:nth-child(38) {
color: #4b6477;
top: 63%;
left: 64%;
animation-duration: 9s;
animation-delay: -70s;
transform-origin: 16vw 10vh;
box-shadow: 100vmin 0 12.63072769188053vmin currentColor;
}
.background span:nth-child(39) {
color: #a9a0bb;
top: 69%;
left: 81%;
animation-duration: 56s;
animation-delay: -361s;
transform-origin: -17vw 11vh;
box-shadow: 100vmin 0 13.123746520776026vmin currentColor;
}
.background span:nth-child(40) {
color: #888aa0;
top: 54%;
left: 33%;
animation-duration: 142s;
animation-delay: -2s;
transform-origin: 4vw 24vh;
box-shadow: 100vmin 0 13.216474278399133vmin currentColor;
}
.background span:nth-child(41) {
color: #4b6477;
top: 56%;
left: 10%;
animation-duration: 65s;
animation-delay: -105s;
transform-origin: 3vw -24vh;
box-shadow: 100vmin 0 13.322200697362886vmin currentColor;
}
.background span:nth-child(42) {
color: #4b6477;
top: 2%;
left: 39%;
animation-duration: 213s;
animation-delay: -62s;
transform-origin: 4vw 10vh;
box-shadow: -100vmin 0 12.580279613916804vmin currentColor;
}
.background span:nth-child(43) {
color: #888aa0;
top: 100%;
left: 49%;
animation-duration: 12s;
animation-delay: -71s;
transform-origin: 9vw -21vh;
box-shadow: 100vmin 0 13.356960223158397vmin currentColor;
}
.background span:nth-child(44) {
color: #a9a0bb;
top: 75%;
left: 59%;
animation-duration: 286s;
animation-delay: -81s;
transform-origin: -6vw 18vh;
box-shadow: 100vmin 0 12.718648874626872vmin currentColor;
}
.background span:nth-child(45) {
color: #888aa0;
top: 22%;
left: 12%;
animation-duration: 87s;
animation-delay: -298s;
transform-origin: -17vw 5vh;
box-shadow: 100vmin 0 13.066314361220197vmin currentColor;
}
</style>
<script>
@ -1073,53 +627,7 @@
</div>
<div id="container">
</div>
<div class="background">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</body>
</html>

File diff suppressed because one or more lines are too long