+
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;
- }
-