From c24dac02534c6a4f96915e01d4ebdede20f95cce Mon Sep 17 00:00:00 2001 From: Steve Seguin Date: Mon, 3 Jan 2022 19:17:22 -0500 Subject: [PATCH] improved &chunked mode; other features for experimentation --- iframe.html | 16 + index.html | 2 +- lib.js | 872 +++++++++++++++++++++++++++++++++++++++++++++++++++- main.css | 22 +- main.js | 85 +++-- mixer.html | 496 +----------------------------- webrtc.js | 2 +- 7 files changed, 973 insertions(+), 522 deletions(-) diff --git a/iframe.html b/iframe.html index e959f2a..ae254c1 100644 --- a/iframe.html +++ b/iframe.html @@ -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
"; + for (var i = 0;i"; + } + outputWindow.style.border="1px dotted black"; + iframeContainer.appendChild(outputWindow); + } + + if ("loudness" in e.data){ console.log(e.data); if (document.getElementById("loudness")){ diff --git a/index.html b/index.html index 2b2df33..fa23c82 100644 --- a/index.html +++ b/index.html @@ -115,7 +115,7 @@
ACTIVE
-
+
diff --git a/lib.js b/lib.js index 1851e19..4194fb0 100644 --- a/lib.js +++ b/lib.js @@ -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) { diff --git a/main.css b/main.css index bf74030..977640e 100644 --- a/main.css +++ b/main.css @@ -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% { diff --git a/main.js b/main.js index f4e2f2b..26394ce 100644 --- a/main.js +++ b/main.js @@ -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 += '
'; - retrySpinner.title = miscTranslations["waiting-for-the-stream"] - retrySpinner.onclick = function(){ - updateURL("cleanoutput"); - location.reload(); + if (urlParams.has('waitimage')){ + getById("mainmenu").innerHTML += ''; + getById("retryimage").src = decodeURIComponent(urlParams.get('waitimage')); + getById("retryimage").onerror = function(){this.style.display='none';}; + } else if (!(session.cleanOutput)){ + getById("mainmenu").innerHTML += '
'; + getById("retrySpinner").onclick = function(){ + updateURL("cleanoutput"); + location.reload(); + } + getById("retrySpinner").title = miscTranslations["waiting-for-the-stream"] + } + if (urlParams.has('waitmessage')){ + getById("mainmenu").innerHTML += '
'; + 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)) }, "*"); }); } diff --git a/mixer.html b/mixer.html index e86e51f..bd8647f 100644 --- a/mixer.html +++ b/mixer.html @@ -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; - } -