Add files via upload

This commit is contained in:
Steve Seguin 2022-07-26 08:09:51 -04:00 committed by GitHub
parent b6e1a4804c
commit 0081e0ec39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 2498 additions and 909 deletions

View File

@ -563,7 +563,7 @@ function modURL(){
} else if (url.startsWith("https://")){
// pass
} else if (url.startsWith("file:")){
// pass
alert("Warning:\n\nFor security purposes, local files need to be loaded via the command-line or via the right-click context menu -> Edit URL.\n\nThis is supported in Electron Capture 2.15.2 and newer.");
} else {
url = "https://"+url;
}

View File

@ -33,6 +33,10 @@
padding:5px;
margin:5px;
}
video{
max-width:300px;
max-height:100px;
}
</style>
<script>
@ -41,7 +45,7 @@
var iframe = document.createElement("iframe");
var iframeContainer = document.createElement("div");
var iframesrc = document.getElementById("viewlink").value;
iframe.allow = "midi;geolocation;autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
iframe.allow = "document-domain;encrypted-media;sync-xhr;usb;web-share;cross-origin-isolated;accelerometer;midi;geolocation;autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
if (iframesrc==""){
iframesrc="./";
@ -304,12 +308,53 @@
var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
var media = {};
media.tracks = {};
media.streams = {};
window.addEventListener('messageerror', e => {
console.error(e);
});
eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
if (e.data.frame){ // add `&sendframes` to the view link to trigger this event; it lets you capture video/audio from the parent window
if (!media.tracks[e.data.trackID]){
media.tracks[e.data.trackID] = {};
media.tracks[e.data.trackID].generator = new MediaStreamTrackGenerator({kind:e.data.kind});
media.tracks[e.data.trackID].stream = new MediaStream([media.tracks[e.data.trackID].generator]);
media.tracks[e.data.trackID].frameWriter = media.tracks[e.data.trackID].generator.writable.getWriter();
media.tracks[e.data.trackID].frameWriter.write(e.data.frame);
if (!media.streams[e.data.streamID]){
media.streams[e.data.streamID] = document.createElement("video");
media.streams[e.data.streamID].id = "video_"+e.data.streamID;
media.streams[e.data.streamID].autoplay = true;
// media.streams[e.data.streamID].controls = true;
media.streams[e.data.streamID].srcObject = media.tracks[e.data.trackID].stream;
iframeContainer.appendChild(media.streams[e.data.streamID]);
} else {
if (e.data.kind=="video"){
media.streams[e.data.streamID].srcObject.getVideoTracks().forEach(trk=>{
media.streams[e.data.streamID].srcObject.removeTrack(trk);
});
} else if (e.data.kind=="audio"){
media.streams[e.data.streamID].srcObject.getAudioTracks().forEach(trk=>{
media.streams[e.data.streamID].srcObject.removeTrack(trk);
});
}
media.tracks[e.data.trackID].stream.getTracks().forEach(trk=>{
media.streams[e.data.streamID].srcObject.addTrack(trk);
});
}
} else {
media.tracks[e.data.trackID].frameWriter.write(e.data.frame);
}
return;
} // end of video/audio capture
if ("stats" in e.data){
var outputWindow = document.createElement("div");
console.log(e.data.stats);

View File

@ -57,7 +57,7 @@
<meta property="twitter:image" content="./media/vdoNinja_logo_full.png" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#ffffff" />
<link rel="stylesheet" href="./main.css?ver=165" />
<link rel="stylesheet" href="./main.css?ver=169" />
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/adapter.min.js"></script>
<style id="lightbox-animations" type="text/css"></style>
<!-- <link rel="manifest" href="manifest.json" /> -->
@ -82,7 +82,7 @@
<link itemprop="url" href="./media/vdoNinja_logo_full.png" />
</span>
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/CodecsHandler.js?ver=37"></script>
<script type="text/javascript" crossorigin="anonymous" src="./webrtc.js?ver=473"></script>
<script type="text/javascript" crossorigin="anonymous" src="./webrtc.js?ver=486"></script>
<input id="zoomSlider" type="range" style="display: none;" />
<span id="electronDragZone" style="pointer-events: none; z-index:-10; position:absolute;top:0;left:0;width:100%;height:2%;-webkit-app-region: drag;min-height:20px;"></span>
<div id="header">
@ -182,6 +182,10 @@
<i id="settingstoggle" class="toggleSize las la-sync-alt my-float"></i>
</div>
<div id="obscontrolbutton" onmousedown="event.preventDefault(); event.stopPropagation();" title="OBS Remote Controller; start/stop and change scenes." onclick="toggleOBSControls();" class="hidden float" tabindex="22" role="button" aria-pressed="false" onkeyup="enterPressedClick(event,this);" style="cursor: pointer;" alt="Toggle the Remote OBS Controls Menu">
<i id="obscontroltoggle" class="toggleSize las la-gamepad my-float"></i>
</div>
<div id="roomsettingsbutton" onmousedown="event.preventDefault(); event.stopPropagation();" title="Room Settings" onclick="toggleRoomSettings();" class="hidden float" tabindex="22" role="button" aria-pressed="false" onkeyup="enterPressedClick(event,this);" style="cursor: pointer;" alt="Toggle the Room Settings Menu">
<i id="roomsettingstoggle" class="toggleSize las la-users-cog my-float"></i>
</div>
@ -1050,6 +1054,16 @@
</label>
<span data-translate="disable-animated-mixing">Disable animations</span>
<Br />
<label class="switch" title="This mode encodes the video and audio into chunks, which are shared with multiple viewers. Limited browser support. Can potentially reduce CPU and improve video quality, but will rely on a buffer.">
<input type="checkbox" data-param="&chunked=500" onchange="updateLink(1,this);">
<span class="slider"></span>
</label>
<font class="tooltip" style='cursor: help;position:relative;bottom:2px;font-family:"Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", Times, Symbola, Aegyptus, Code2000, Code2001, Code2002, Musica, serif, LastResort;'><span class="tooltiptext">Pretty experimental and limited browser support, though relatively low CPU usage.</span></font>
<span data-translate="chunked-mode">P2P Chunked-mode</span>
</div>
<div style="display:inline-block;margin-top: 12px; position: relative; margin-right:10px;">
<label class="switch" title="Increase video quality that guests in room see.">
@ -1093,16 +1107,15 @@
<span class="slider"></span>
</label>
<span data-translate="prefix-screenshare">Prefix screenshare IDs</span>
</div>
<div style="display:inline-block;margin-top: 12px; position: relative; ">
<label class="switch" title="This mode encodes the video and audio into chunks, which are shared with multiple viewers. Limited browser support. Can potentially reduce CPU and improve video quality, but will rely on a buffer.">
<input type="checkbox" data-param="&chunked=500" onchange="updateLink(1,this);">
<Br />
<label class="switch" title="Allow the guest to select an avatar image for when they hide their camera">
<input type="checkbox" data-param="&avatar" onchange="updateLink(1,this);">
<span class="slider"></span>
</label>
<font class="tooltip" style='cursor: help;position:relative;bottom:2px;font-family:"Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", Times, Symbola, Aegyptus, Code2000, Code2001, Code2002, Musica, serif, LastResort;'><span class="tooltiptext">Pretty experimental and limited browser support, though relatively low CPU usage.</span></font>
<span data-translate="chunked-mode">P2P Chunked-mode</span>
<span data-translate="avatar-selection">Can select an Avatar image</span>
</div>
<div style="display:inline-block;margin-top: 12px; position: relative; ">
<Br />
<label class="switch" title="Use Meshcast servers to restream video data from this guest to its viewers, reducing the CPU and upload load in some cases. Will increase latency a bit.">
<input type="checkbox" data-param="&meshcast" onchange="updateLink(1,this);">
<span class="slider"></span>
@ -1117,6 +1130,13 @@
</label>
<span data-translate="mini-self-preview">Mini self-preview</span>
<Br />
<label class="switch" title="Show an ovelaid grid on the guest's preview video to help with self-centering of the guest.">
<input type="checkbox" data-param="&grid" onchange="updateLink(1,this);">
<span class="slider"></span>
</label>
<span data-translate="rule-of-thirds">Show rule-of-thirds grid</span>
<Br />
<label class="switch" title="The guest can only see the Director's video, if provided">
<input type="checkbox" data-param="&broadcast" id="broadcastSlider" onchange="updateLink(1,this);">
@ -1208,7 +1228,6 @@
</label>
<span data-translate="animate-mixing">Animate mixing</span>
</div>
<div style="display:inline-block;margin-top: 12px; position: relative; margin-right:10px;">
@ -1230,6 +1249,14 @@
<span class="slider"></span>
</label>
<font class="tooltip" style='cursor: help;position:relative;bottom:2px;font-family:"Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", Times, Symbola, Aegyptus, Code2000, Code2001, Code2002, Musica, serif, LastResort;'><span class="tooltiptext">This can cause video playback to lag</span></font> Unlock Video Bitrate
<br />
<label class="switch" title="Disable fit-to-window optmized video scaling for added sharpness; increases CPU / Network load though.">
<input type="checkbox" data-param="&scale=100" onchange="updateLink(3,this);">
<span class="slider"></span>
</label>
<span data-translate="disable-downscaling">Viewer-side downscaling</span>
</div>
<div style="display:inline-block;margin-top: 12px; position: relative; ">
@ -1642,12 +1669,14 @@
<div id="audioTitle2" class="title">
<i class="las la-microphone-alt"></i><span data-translate="select-audio-source"> Audio Source(s) </span>
<i id="chevarrow2" class="chevron bottom" aria-hidden="true"></i>
<div class="meter" id="meter3"></div><div class="meter2" id="meter4"></div>
</div>
</a>
<ul id="audioSource3" class="multiselect-contents">
<li>
</li>
</ul>
</div>
<br />
<span id="headphonesDiv3" style="display: block;">
@ -1872,12 +1901,12 @@
</div>
<div id="roomSettings" style="display:none; user-select: none;">
<div id="roomSettings" class="customModelPopup" style="display:none; user-select: none;">
<div class="promptModalInner">
<span class='modalClose' onclick="toggleRoomSettings();">×</span>
<span></span>
<h3 data-translate="change-room-settings">Change room settings</h3><br />
<label title="Increase this at your peril. Changes the total inbound video bitrate per guest; mobile devices excluded. Webp-mode also excluded." for="trbSettingInput" data-translate="change-room-video-quality">Change room video quality:</label>
<label title="Increase this at your peril. Changes the total inbound video bitrate per guest; mobile devices excluded." for="trbSettingInput" data-translate="change-room-video-quality">Change room video quality:</label>
<span style="margin-left: 6px;" id="trbSettingInputFeedback"></span>-kbps
<input id="trbSettingInput" type="range" min="0" max="4000" value="500" onchange="changeTRB(this);" oninput="getById('trbSettingInputFeedback').innerHTML = this.value;" style="width:100%;display:block;" />
<span style="margin: 20px 0 0 0;display:block" id='highlightDirectorSpan' title="Only the director's video will be visible to guests and within group scenes">
@ -1907,6 +1936,30 @@
</div>
</div>
<div id="remoteOBSControl" class="customModelPopup" style="display:none; user-select: none;">
<div class="promptModalInner">
<span class='modalClose' onclick="toggleOBSControls();">×</span>
<span></span>
<h3 data-translate="remote-control-obs-menu">Remote Controller for OBS Studio</h3><br />
<div id="obsControlHelp" class="hidden" style="margin: 10px 0;display:block" >
No remote controllable instances of OBS Studio were found
</div>
<div id="obsControlButtons" style="margin: 10px 0;display:block" >
</div>
<div id="obsSceneNames" style="margin: 10px 0;display:block" >
</div>
<div id="obsRemotePassword" class="hidden" style="margin: 10px 0;display:block;" >
<font style="font-size:117%"><i class="las la-key" style="margin: 10px;"></i>Remote OBS passcode:</font>
<input style="margin:0 10px;display:inline-block;padding: 8px 10px 6px 10px;" placeholder="Enter the remote OBS password here" />
</div>
<small style="margin: 20px 0 0 0;display:block;" >
See the <a href="https://docs.vdo.ninja/advanced-settings/upcoming-parameters/and-obs" style="color:#314350; cursor:pointer;" target="_blank">documentation</a> for help on using the remote OBS controller
</small>
<div id="debugRemoteOBSControl" class="hidden">
</div>
</div>
</div>
<div id="transferSettingsTemplate" style="display:none">
<h3>Change guest settings</h3><br />
<label class="switch" title="Cannot see videos">
@ -2142,7 +2195,7 @@
// session.offsetChannel // int
// session.audioChannels // int
// session.security // true to disable the wss connection after the first peer connection is made
// session.framerate // int ; publishing frame rate. will fail if camera does not support it.
// session.frameRate // int ; publishing frame rate. will fail if camera does not support it.
// session.sync // see the docs
// session.buffer // int in milliseconds ; see the docs
// session.roomid // "yyyy" -- the room name to use. alphanumeric.
@ -2155,11 +2208,11 @@
// session.defaultBackgroundImages = ["./media/bg_sample1.webp", "./media/bg_sample2.webp"]; // for &effects=5 (virtual backgrounds)
</script>
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/aes.js"></script>
<script type="text/javascript" crossorigin="anonymous" id="lib-js" src="./lib.js?ver=362"></script>
<script type="text/javascript" crossorigin="anonymous" id="lib-js" src="./lib.js?ver=377"></script>
<!--
// If you wish to change branding, blank offers a good clean start.
<script type="text/javascript" id="main-js" src="./main.js" data-translation="blank"></script>
-->
<script type="text/javascript" crossorigin="anonymous" id="main-js" src="./main.js?ver=376"></script>
<script type="text/javascript" crossorigin="anonymous" id="main-js" src="./main.js?ver=389"></script>
</body>
</html>

2535
lib.js

File diff suppressed because it is too large Load Diff

View File

@ -1054,9 +1054,6 @@ button.btnArmTransferRoom.selected{
#logoname{
display:none;
}
#head1{
display:none;
}
#head4{
display:none;
}
@ -2524,6 +2521,12 @@ button.toggleSettings{
vertical-align: middle;
font-size: 100%;
}
#minipreview > #videosource {
height:auto!important;
width:auto!important;;
}
#videoSourceSelect {
display: inline-block;
vertical-align: middle;
@ -3529,8 +3532,11 @@ input:checked + .slider:before {
transform: translateX(16px);
}
#promptModal, #roomSettings, .promptModal {
#remoteOBSControl button {
margin:5px;
padding:10px;
}
#promptModal, .customModelPopup, .promptModal {
position: absolute;
background-color: rgb(221 221 221);
box-shadow: 0 0 30px 10px #0000005c;
@ -3875,7 +3881,9 @@ input:checked + .slider:before {
.la-cog:before {
content: "\f013"; }
.la-phone:before {
content: "\f095"; }
content: "\f095"; }
.la-gamepad:before {
content: "\f11b"; }
.la-skull-crossbones:before {
content: "\f714"; }
.la-hand-paper:before {

234
main.js
View File

@ -1454,10 +1454,9 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if (urlParams.has('outboundvideobitrate') || urlParams.has('ovb')) {
session.outboundVideoBitrate = parseInt(urlParams.get('outboundvideobitrate')) || parseInt(urlParams.get('ovb')) || false;
}
if (urlParams.has('webp')){
session.webp = true;
if (urlParams.has('webp') || urlParams.has('images')){ // deprecicating this. chunked mode will replace it.
session.webp = urlParams.get('webp') || urlParams.get('images') || "webp";
}
if (urlParams.has('webpquality') || urlParams.has('webpq') || urlParams.has('wq')){
@ -1553,6 +1552,14 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if (urlParams.has('ruler') || urlParams.has('grid') || urlParams.has('thirds')) {
session.ruleOfThirds=true;
session.fullscreen = true;
if (!session.manual){
session.manual = false;
}
}
if (urlParams.has('smallshare')){
session.notifyScreenShare = false;
}
if (urlParams.has('proxy')) { // routes the wss traffic via an alternative network path. Not
@ -1610,6 +1617,18 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.remote = urlParams.get('remote') || urlParams.get('rem') || true;
}
if (urlParams.has("slideshow")){ // stream labs mobile fix ?
var ssinterval = parseInt(urlParams.get("slideshow")) || 25;
ssinterval = 1000/ssinterval;
session.manual = true;
session.dynamicScale = false;
setInterval(function(){
try {
slideshowHack();
} catch(e){errorlog(e);}
},ssinterval);
}
if (urlParams.has('latency') || urlParams.has('al') || urlParams.has('audiolatency')) {
log("latency ENABLED");
session.audioLatency = urlParams.get('latency') || urlParams.get('al') || urlParams.get('audiolatency');
@ -1659,15 +1678,38 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
//innerHTML =
}
if (urlParams.has('keyframeinterval') || urlParams.has('keyframerate') || urlParams.has('keyframe') || urlParams.has('fki')) {
log("keyframerate ENABLED");
session.keyframerate = parseInt(urlParams.get('keyframeinterval') || urlParams.get('keyframerate') || urlParams.get('keyframe') || urlParams.get('fki')) || 0;
if (urlParams.has('keyframeinterval') || urlParams.has('keyframeRate') || urlParams.has('keyframe') || urlParams.has('fki')) {
log("keyframeRate ENABLED");
session.keyframeRate = parseInt(urlParams.get('keyframeinterval') || urlParams.get('keyframeRate') || urlParams.get('keyframe') || urlParams.get('fki')) || 0;
}
if (urlParams.has('obsoff') || urlParams.has('oo') || urlParams.has('disableobs')) {
log("OBS feedback disabled");
session.disableOBS = true;
getById("obsState").style.setProperty("display", "none", "important");
}
if (urlParams.has('obscontrols') || urlParams.has('remoteobs') || urlParams.has('obsremote') || urlParams.has('obs') || urlParams.has('controlobs')) {
session.obsControls = urlParams.get('obscontrols') || urlParams.get('remoteobs') || urlParams.get('obsremote') || urlParams.get('obs') || urlParams.get('controlobs');
if (session.obsControls) { // whether to show the button or not; that's it.
session.obsControls = session.obsControls.toLowerCase();
}
if (session.obsControls == "false") {
session.obsControls = false;
} else if (session.obsControls == "0") {
session.obsControls = false;
} else if (session.obsControls == "no") {
session.obsControls = false;
} else if (session.obsControls == "off") {
session.obsControls = false;
} else if (session.obsControls){
session.obsControls = session.obsControls.toLowerCase();
} else {
session.obsControls = true;
}
}
if (session.obsControls){
getById("obscontrolbutton").classList.remove("hidden");
}
if (urlParams.has('tallyoff') || urlParams.has('notally') || urlParams.has('disabletally') || urlParams.has('to')) {
@ -1704,24 +1746,6 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
log("macOS: " + macOS);
log(window.obsstudio);
if (typeof document.visibilityState !== "undefined"){
session.obsState.visibility = document.visibilityState==="visible";
//session.obsState.sourceActive = session.obsState.visibility;
}
window.obsstudio.getStatus(function(obsStatus) {
log("OBS STATUS:");
log(obsStatus);
// TODO: update state here
if ("recording" in obsStatus){
session.obsState.recording = obsStatus.recording;
}
if ("streaming" in obsStatus){
session.obsState.streaming = obsStatus.streaming;
}
});
if (!(urlParams.has('streamlabs'))) {
var ver1 = window.obsstudio.pluginVersion.split(".");
@ -1746,6 +1770,12 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
//}
if (session.disableOBS===false){
if (typeof document.visibilityState !== "undefined"){
session.obsState.visibility = document.visibilityState==="visible";
}
getOBSDetails();
window.addEventListener("obsSourceVisibleChanged", obsSourceVisibleChanged);
window.addEventListener("obsSourceActiveChanged", obsSourceActiveChanged);
window.addEventListener("obsSceneChanged", obsSceneChanged);
@ -1753,6 +1783,8 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
window.addEventListener("obsStreamingStopped", obsStreamingStopped);
window.addEventListener("obsRecordingStarted", obsRecordingStarted);
window.addEventListener("obsRecordingStopped", obsRecordingStopped);
window.addEventListener("obsVirtualcamStarted", obsVirtualcamStarted);
window.addEventListener("obsVirtualcamStopped", obsVirtualcamStopped);
}
} catch (e) {
@ -2096,6 +2128,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
if (urlParams.has('debug')){
session.debug=true;
debugStart();
}
@ -2154,10 +2187,6 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.codec = urlParams.get('codec') || false;
if (session.codec){
session.codec = session.codec.toLowerCase();
if (session.codec=="webp"){
session.webp = true;
session.codec = false;
}
}
} else if (OperaGx){
session.codec = "vp8";
@ -2213,12 +2242,15 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.scale = parseFloat(urlParams.get('scale')) || 100;
}
session.dynamicScale = false; // default true
} else if (urlParams.has('viewwidth') || urlParams.has('vw')) {
session.viewwidth = urlParams.get('viewwidth') || urlParams.get('vw') ||false;
session.dynamicScale = false; // default true
} else if (urlParams.has('viewheight') || urlParams.has('vh')) {
session.viewheight = urlParams.get('viewheight') || urlParams.get('vh') ||false;
session.dynamicScale = false; // default true
} else {
if (urlParams.has('viewwidth') || urlParams.has('vw')) {
session.viewwidth = urlParams.get('viewwidth') || urlParams.get('vw') ||false;
session.dynamicScale = false; // default true
}
if (urlParams.has('viewheight') || urlParams.has('vh')) {
session.viewheight = urlParams.get('viewheight') || urlParams.get('vh') ||false;
session.dynamicScale = false; // default true
}
}
if (urlParams.has('mcscale') || urlParams.has('meshcastscale')) {
@ -2315,14 +2347,18 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
log("totalRoomBitrate ENABLED");
log(session.totalRoomBitrate);
}
if (session.totalRoomBitrate===false){
session.totalRoomBitrate = session.totalRoomBitrate_default;
} else {
session.totalRoomBitrate_default = session.totalRoomBitrate; // trb_default doesn't change dynamically, but trb can (per director I guess)
}
if (session.totalRoomBitrate_default>4000){
getById("trbSettingInput").max = Math.ceil(session.totalRoomBitrate_default);
}
if (urlParams.has('maxtotalscenebitrate') || urlParams.has('totalscenebitrate') || urlParams.has('mtsb') || urlParams.has('tsb') || urlParams.has('totalbitrate') || urlParams.has('tb')) {
session.totalSceneBitrate = urlParams.get('maxtotalscenebitrate') || urlParams.get('totalscenebitrate') || urlParams.get('mtsb') || urlParams.get('tsb') || urlParams.get('totalbitrate') || urlParams.get('tb') || false;
if (session.totalSceneBitrate){
@ -2491,6 +2527,23 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
}
if (urlParams.has('postinterval')){ // interval to post snapimage images
session.postInterval = urlParams.get('postinterval') || session.postInterval;
session.postInterval = parseInt(session.postInterval) || 60;
if (session.postInterval<5){
session.postInterval = 5;
}
}
if (urlParams.has('postimage')){
var postURL = decodeURIComponent(urlParams.get('postimage')) || session.postURL; // default will post to https://temp.vdo.ninja/images/STREAMIDHERE.jpg
setInterval(function(postURL){
try {
uploadImageSnapshot(postURL);
} catch(e){}
}, session.postInterval*1000 , postURL);
}
if (urlParams.has('cleanish')) {
session.cleanish = true;
}
@ -2513,6 +2566,11 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
//}
}
if (urlParams.has('degrade')) {
session.degrade = urlParams.get('degrade') || true; // Firefox, and maybe Safari, supported I think.
// the possible values are maintain-framerate, maintain-resolution, or balanced. The default value is balanced
}
if (urlParams.has('maxviewers') || urlParams.has('mv')) {
session.maxviewers = urlParams.get('maxviewers') || urlParams.get('mv');
@ -2559,11 +2617,11 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.randomize = true;
}
if (urlParams.has('framerate') || urlParams.has('fr') || urlParams.has('fps')) {
session.framerate = urlParams.get('framerate') || urlParams.get('fr') || urlParams.get('fps');
session.framerate = parseInt(session.framerate);
log("framerate Changed");
log(session.framerate);
if (urlParams.has('frameRate') || urlParams.has('fr') || urlParams.has('fps')) {
session.frameRate = urlParams.get('frameRate') || urlParams.get('fr') || urlParams.get('fps');
session.frameRate = parseInt(session.frameRate);
log("frameRate Changed");
log(session.frameRate);
}
if (urlParams.has('tz')){
@ -2575,11 +2633,11 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
}
if (urlParams.has('maxframerate') || urlParams.has('mfr') || urlParams.has('mfps')) {
session.maxframerate = urlParams.get('maxframerate') || urlParams.get('mfr') || urlParams.get('mfps');
session.maxframerate = parseInt(session.maxframerate);
log("max framerate assigned");
log(session.maxframerate);
if (urlParams.has('maxframeRate') || urlParams.has('mfr') || urlParams.has('mfps')) {
session.maxframeRate = urlParams.get('maxframeRate') || urlParams.get('mfr') || urlParams.get('mfps');
session.maxframeRate = parseInt(session.maxframeRate);
log("max frameRate assigned");
log(session.maxframeRate);
}
if (urlParams.has('buffer')) { // needs to be before sync
@ -2708,12 +2766,29 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
document.querySelector(':root').style.setProperty('--fadein-speed', 0.5);
setInterval(function(){activeSpeaker(false);},100);
} else if (urlParams.has('noisegate')){
session.quietOthers = urlParams.get('noisegate') || 1;
} else if (urlParams.has('noisegate') || urlParams.has('gating') || urlParams.has('gate') ||urlParams.has('ng')){
session.quietOthers = urlParams.get('noisegate') || urlParams.get('gating') || urlParams.get('gate') || urlParams.get('ng') || 1;
session.quietOthers = parseInt(session.quietOthers);
session.audioEffects = true;
session.audioMeterGuest = true;
setInterval(function(){activeSpeaker(false);},100);
if (session.quietOthers == 1){
session.quietOthers = false;
session.noisegate = true;
session.audioEffects = true;
session.audioMeterGuest = true;
} else if (session.quietOthers == 4){
session.quietOthers = 1;
session.audioEffects = true;
session.audioMeterGuest = true;
setInterval(function(){activeSpeaker(false);},100);
} else if (!session.quietOthers){
session.noisegate = false;
session.quietOthers = false;
} else {
session.audioEffects = true;
session.audioMeterGuest = true;
setInterval(function(){activeSpeaker(false);},100);
}
}
if (urlParams.has('fadein')) {
@ -2809,6 +2884,9 @@ 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.audioEffects = false; // disable audio inbound effects also.
session.audioMeterGuest = false;
if (session.noisegate===null){
session.noisegate = false;
}
}
// For info, see this: https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidatePairStats/availableOutgoingBitrate
@ -2822,6 +2900,26 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
}
if (urlParams.has('iframetarget')) {
session.iframetarget = urlParams.get('iframetarget'); // speciifies the IFRAME Hostname target
if (session.iframetarget){
session.iframetarget = decodeURIComponent(session.iframetarget);
} else {
session.iframetarget = window.location.hostname;
}
}
if (urlParams.has('sendframes')) {
session.sendframes = urlParams.get('sendframes');
if(session.sendframes){
session.sendframes = decodeURIComponent(session.sendframes);
} else {
session.sendframes = session.iframetarget || "*";
}
}
if (urlParams.has('tcp')){ // forces the TURN servers to use TCP mode; still need to add &private to force TURN also tho
session.forceTcpMode = true;
}
@ -3856,7 +3954,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
enumerateDevices().then(function(deviceInfos) {
parent.postMessage({
"deviceList": JSON.parse(JSON.stringify(deviceInfos))
}, "*");
}, session.iframetarget);
});
}
@ -3972,7 +4070,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
parent.postMessage({
"stats": stats
}, "*");
}, session.iframetarget);
}, 1000);
}
@ -4004,7 +4102,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
parent.postMessage({
"loudness": loudness
}, "*");
}, session.iframetarget);
} else {
session.pushLoudness = false;
@ -4019,7 +4117,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
//parent.postMessage({
// "effectsData": effectsData,
// "effectsID": session.pushEffectsData
//}, "*");
//}, session.iframetarget);
} else {
session.pushEffectsData = false;
@ -4034,20 +4132,24 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
parent.postMessage({
"streamIDs": streamIDs
}, "*");
}, session.iframetarget);
}
}
if ("close" in e.data) { // disconnect and hangup all inbound streams.
for (var i in session.rpcs) {
try {
session.rpcs[i].close();
} catch (e) {
errorlog(e);
}
if (("close" in e.data) || ("hangup" in e.data)) { // disconnect and hangup all inbound streams.
var tmp = e.data.close || e.data.hangup;
if (tmp == "estop"){ // try to stop the video recording even if not complete; if you can't wait even ms before a reload/exit.
console.log("ESTOP");
session.hangup(false,true);
} else if (tmp == "reload"){ // stop and reload the page safely.
session.hangup(true);
} else { // just hangup, but can take up to 1-second to do so fully.
session.hangup();
}
hangup();
}
if ("hangup" in e.data) { // disconnect and hangup all inbound streams.
session.hangup();
}
if ("style" in e.data) { // insert a custom style sheet
@ -4065,7 +4167,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
var detailedState = getDetailedState();
parent.postMessage({
"detailedState": detailedState
}, "*");
}, session.iframetarget);
}
if ("automixer" in e.data) { // stop the auto mixer if you want to control the layout and bitrate yourself
@ -4163,7 +4265,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
var resp = processMessage(e.data); // reuse the companion API
if (resp!==null){
log(resp);
parent.postMessage(resp, "*");
parent.postMessage(resp, session.iframetarget);
}
} else if ("target" in e.data) {
log(e.data);
@ -4369,7 +4471,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
return;
}
warnlog("Connection type changed from " + session.stats.network_type + " to " + Connection.effectiveType);
log("Connection type changed from " + session.stats.network_type + " to " + Connection.effectiveType);
session.stats.network_type = Connection.effectiveType + " / " + Connection.type;
session.ping();

View File

@ -33,9 +33,12 @@ span{
iframe {
border: 0;
padding: 0;
display: block;
height: 100%;
width: 100%;
display: none;
height: 0%;
width: 0%;
position: absolute;
left: -100px;
top: -100px;
}
.popup-message {

View File

@ -1,12 +1,12 @@
<html>
<head>
<title>Mixer app</title>
<title>Versus.cam</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<script src="./thirdparty/jquery/jquery-3.6.0.js?asdf"></script>
<script src="./thirdparty/jquery/jquery-ui.js"></script>
<link rel="stylesheet" href="./thirdparty/jquery/jquery-ui.css">
<link rel="stylesheet" href="./stats.css" >
<link rel="stylesheet" href="./versus.css" >
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" />
<link rel="icon" type="image/png" sizes="32x32" href="./media/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="./media/favicon-16x16.png" />
@ -24,10 +24,12 @@
<div><div id="streamsConnected">0</div> connections</div>
</span>
<span>
<span><button class="menuButtons" onclick="copyFunction(this.value, event);" id="inviteLink" value="test">Copy Invite Link</button></span>
<span><button class="menuButtons" onclick="copyFunction(this.value, event);" id="inviteLink" value="test"><img src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpath d='M 15 3 C 13.742188 3 12.847656 3.890625 12.40625 5 L 5 5 L 5 28 L 13 28 L 13 30 L 27 30 L 27 14 L 25 14 L 25 5 L 17.59375 5 C 17.152344 3.890625 16.257813 3 15 3 Z M 15 5 C 15.554688 5 16 5.445313 16 6 L 16 7 L 19 7 L 19 9 L 11 9 L 11 7 L 14 7 L 14 6 C 14 5.445313 14.445313 5 15 5 Z M 7 7 L 9 7 L 9 11 L 21 11 L 21 7 L 23 7 L 23 14 L 13 14 L 13 26 L 7 26 Z M 15 16 L 25 16 L 25 28 L 15 28 Z'/%3E%3C/svg%3E" class="icon"> Copy Invite Link</button></span>
<span><button class="menuButtons" onclick="getStreamModal();">Add a Stream Manually</button></span>
</span>
</header>
<div id="mainContainer">
</div>
<div id="iframeContainer">
<div id='canvas' class="hidden">
</div>
@ -119,20 +121,6 @@
</div>
</div>
</div>
<div id="graphTemplate" display="none">
<div class="graph">
<div class='graphTitle'>Bitrate (kbps)</div>
<canvas data-bitrate-graph="true"></canvas>
</div>
<div class="graph">
<div class='graphTitle'>Reported lost packets (per second)</div>
<span>0</span>
<canvas data-nackrate-graph="true"></canvas>
</div>
<div data-log="true" onclick="copyFunction(this.innerText)" style="max-height:400px;display:inline-block;">
<ul></ul>
</div>
</div>
<div id="modal" class="modal">
<div class="modal-content">
<span class="close-btn">&times;</span>
@ -142,6 +130,28 @@
</div>
</div>
<div id="messagePopup" class="popup-message"></div>
<div id="graphTemplate" class="container hidden fadein">
<span class="video" data-action-type="video-container">
</span>
<div class='right-side'>
<span class="streamTitle">
<button data-sololink="false" title="Copy the view link for this video to your clipboard" onclick="copyFunction(this.dataset.sololink, event);"><img src="data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpath d='M 15 3 C 13.742188 3 12.847656 3.890625 12.40625 5 L 5 5 L 5 28 L 13 28 L 13 30 L 27 30 L 27 14 L 25 14 L 25 5 L 17.59375 5 C 17.152344 3.890625 16.257813 3 15 3 Z M 15 5 C 15.554688 5 16 5.445313 16 6 L 16 7 L 19 7 L 19 9 L 11 9 L 11 7 L 14 7 L 14 6 C 14 5.445313 14.445313 5 15 5 Z M 7 7 L 9 7 L 9 11 L 21 11 L 21 7 L 23 7 L 23 14 L 13 14 L 13 26 L 7 26 Z M 15 16 L 25 16 L 25 28 L 15 28 Z'/%3E%3C/svg%3E" class='icon'> View Link</button>
</span>
<span class="graphSection" data-action-type="stats-graphs-bitrate" data-value="0">
<span class="hidden" data-message="true" data-no-scenes="true"></span>
</span>
<span class="graphSection" data-action-type="stats-graphs-details" data-value="0">
<span class="hidden" data-no-scenes="true"></span>
<span class="stats-container" data-action-type="stats-graphs-details-container">
<span class="hidden stats-sub-container" data-scene-name="true">scene</span>
<span class="hidden stats-sub-container" data-bitrate="true">bitrate (kbps)</span>
<span class="hidden stats-sub-container" data-resolution="true">resolution</span>
<span class="hidden stats-sub-container" style="display:none; word-break: break-all;" data-video-codec="true">video codec</span>
</span>
</span>
</div>
</div>
<script>
function allowDrop(ev) {
@ -321,13 +331,7 @@
}
var urlParams = new URLSearchParams(urlEdited);
var password = false;
if (urlParams.has("password") || urlParams.has("pw") || urlParams.has("p")){
password = urlParams.get("password") || urlParams.get("pw") || urlParams.get("p") || false;
if (password===false){
password = prompt("Please enter a password") || false;
}
}
var viewList = false;
if (urlParams.has("view") || urlParams.has("v")){
@ -337,6 +341,21 @@
viewList = viewList.split(",");
}
var password = false;
if (urlParams.has("password") || urlParams.has("pw") || urlParams.has("p")){
password = urlParams.get("password") || urlParams.get("pw") || urlParams.get("p") || false;
if (password===false){
password = prompt("Please enter a password") || false;
}
}
var additional = "";
if (password){
additional = "&password="+password;
}
var iframe = null;
var aspectRatio = 16/9.0;
document.documentElement.style.setProperty('--aspect-ratio', aspectRatio);
@ -424,6 +443,7 @@
var streamIDs = [];
var slotsNeeded = 1;
var lastLayout = false;
var session = false;
var colors = [
"#00AAAA",
@ -445,6 +465,11 @@
savedSession = {};
}
var pathname = window.location.pathname.split('/');
pathname.pop();
pathname = pathname.join("/");
function exportSession() {
var content = JSON.stringify(savedSession);
var fileName = roomname + ".json";
@ -557,15 +582,8 @@
}
}
var iframe = null;
function loadIframe(){
var additional = "";
if (password){
additional = "&password="+password;
}
roomname = sanitizeRoomName(roomname);
iframe = document.createElement("iframe");
@ -575,11 +593,12 @@
if (!roomname){
roomname = generateString(10);
}
// &viewonly&showall&bitrate=35
var iframesrc = "./index.html?graphs&lightmode&bitrate=300&viewheight=180&viewwidth=320&transparent&cleanoutput&label=Stats_Monitor&scenelinkcodec=h264&manual&nopush&showall&scenelinkbitrate=12000&room="+roomname+additional+"&b64css="+injectCSS;
// note: it's possible to set the width/height dyanmically via the IFRAME API also now. just call on video track load.
var pathname = window.location.pathname.split('/');
pathname.pop();
pathname = pathname.join("/");
getById("inviteLink").value = "https://"+window.location.host+pathname+"/?room="+roomname+additional+"&label&quality&view";
getById("inviteLink").value = "https://"+window.location.host+pathname+"/?room="+roomname+additional+"&label&quality&maxbandwidth&view";
if (roomname!==false){
setStorage("savedRoom", {roomname:roomname,password:password,viewlist:viewList}, 9999);
@ -591,7 +610,13 @@
ele.innerHTML = roomname;
});
iframe.src = "./index.html?graphs&lightmode&ltb=350&transparent&cleanoutput&directorview&label=Stats_Monitor&scenelinkcodec=h264&scenelinkbitrate=12000&director="+roomname+additional+"&b64css="+injectCSS;
iframe.src = iframesrc;
iframe.onload = function(){
session = iframe.contentWindow['session'];
console.log(session);
};
iframeContainer.appendChild(iframe);
//document.getElementById("container").appendChild(iframeContainer);
@ -608,14 +633,24 @@
eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
console.log(e.data);
if (!session){
session = iframe.contentWindow['session'];
}
if ("action" in e.data){
if (e.data.action === "view-connection"){
//if (e.data.value){
// iframe.contentWindow.postMessage({"getStreamIDs":true}, '*');
if (e.data.streamID){
updateStreams();
if (streamIDs.includes(e.data.streamID)){return;}
streamIDs.push(e.data.streamID);
if (e.data.value){ // connected
if (e.data.streamID){
updateStreams();
if (streamIDs.includes(e.data.streamID)){return;}
streamIDs.push(e.data.streamID);
}
} else { // disconnected
try {
getById("container_"+e.data.UUID).remove();
} catch(e){console.error(e);}
}
} else if (e.data.action == "requested-stream"){
if (streamIDs.includes(e.data.value)){return;}
@ -629,6 +664,32 @@
}
}
},500);
} else if (e.data.action == "control-box-video-updated"){
var container = getContainer(e.data.UUID, e.data.streamID);
getById("container_"+e.data.UUID).classList.remove("greyout");
var video = container.querySelector("#videoContainer_"+e.data.UUID);
if (!video && iframe){
try {
video = iframe.contentDocument.body.querySelector("#videoContainer_"+e.data.UUID);
container.querySelector('[data-action-type="video-container"]').appendChild(video);
} catch(e){errorlog(e);}
}
} else if (e.data.action == "video-element-created"){
var container = getContainer(e.data.UUID, e.data.streamID);
getById("container_"+e.data.UUID).classList.remove("greyout");
var video = container.querySelector("#videoContainer_"+e.data.UUID);
if (!video && iframe){
try {
video = session.rpcs[e.data.UUID].videoElement;
if (video){
container.querySelector('[data-action-type="video-container"]').appendChild(video);
}
} catch(e){errorlog(e);}
}
}
}
@ -640,38 +701,28 @@
messageList = e.data.messageList;
updateMessages();
}
//
if ("remoteStats" in e.data) {
var UUID = e.data.UUID;
for (var uuid in e.data.remoteStats) {
if (e.data.remoteStats[uuid].video_bitrate_kbps){
var video_bitrate_kbps = e.data.remoteStats[uuid].video_bitrate_kbps;
updateData("bitrate", video_bitrate_kbps, UUID, uuid);
} else if (document.getElementById(uuid)){
updateData("bitrate", 0, UUID, uuid);
}
if (e.data.remoteStats[uuid].nacks_per_second){
var nacks_per_second = e.data.remoteStats[uuid].nacks_per_second;
updateData("nackrate", nacks_per_second, UUID, uuid);
} else if (document.getElementById(uuid)){
updateData("nackrate", 0, UUID, uuid);
}
}
remoteStats(e.data.remoteStats, e.data.UUID, e.data.streamID)
}
//if ("streamIDs" in e.data){
// streamIDs = [];
// for (var key in e.data.streamIDs){
// streamIDs.push(key);
// }
// updateStreams();
// console.log(streamIDs);
//}
});
}
function getContainer(UUID, streamID){
var container = document.getElementById("container_" + UUID);
if (!container){
container = document.getElementById('graphTemplate').cloneNode(true);
container.id = "container_"+UUID;
container.sid = streamID;
container.UUID = UUID;
container.classList.remove("hidden");
document.getElementById("mainContainer").append(container);
container.sololink = "https://"+window.location.host+pathname+"/?scene&room="+roomname+additional+"&bitrate=20000&codec=h264&view="+container.sid+"&label=solo_link"
container.querySelector("[data-sololink]").dataset.sololink = container.sololink;
}
return container;
}
function updateStreams(){
document.getElementById("streamsConnected").innerHTML = streamIDs.length;
if (iframe){
@ -679,39 +730,261 @@
}
}
var bitrate = {
element: "bitrate-graph",
data: 0,
max: 6000,
target: 3000,
};
var frames;
var nackrate = {
element: "nackrate-graph",
data: 0,
max: 15,
target: 15,
};
function updateData(type, data, UUID, uuid) {
if (type == "bitrate") {
bitrate.data = data;
plotData("bitrate", bitrate, UUID, uuid);
}
if (type == "nackrate") {
nackrate.data = data;
plotData("nackrate", nackrate, UUID, uuid);
}
function getColor(value) {
var hue = ((value) * 120).toString(10);
return ["hsl(", hue, ",100%,50%)"].join("");
}
function plotData(type, stat, UUID, uuid){
iframe.contentWindow.document.body.querySelector("#container_"+UUID).querySelectorAll(".graphSection>[data-uid='"+uuid+"']").forEach(ele=>{
iframe.contentWindow.document.body.querySelector("#container_"+UUID).appendChild(ele);
ele.classList.remove("hidden");
function plotData(info, UUID, uuid) { // type = "bitrate" or "nacks"
log("plot data");
var container = getById("container_" + UUID).querySelector('[data-action-type="stats-graphs-bitrate"]');
if (!container){
log("container not found");
return;
}
var canvas = getById("container_" + UUID).querySelector('canvas[data-uid="'+uuid+'"]');
var canvasNew = false
if (!canvas){
canvasNew = true;
canvas = document.createElement("canvas");
canvas.height = 100;
canvas.width = 200;
canvas.className = "canvasStats";
canvas.history_nacks = [];
canvas.history_bitrate = [];
canvas.target = 4000;
if (info.scene){
canvas.title = "Scene: "+info.scene+". Red/orange implies packet loss. Y-axis is 0 to 4000-kbps.";
} else if (info.label){
canvas.title = "Label: "+info.label+". Red/orange implies packet loss. Y-axis is 0 to 4000-kbps.";
} else {
canvas.title = "Red/orange implies packet loss. Y-axis is 0 to 4000-kbps.";
}
canvas.dataset.uid = uuid;
container.appendChild(canvas);
}
selfDestructElement(UUID, uuid);
var context = canvas.getContext("2d");
var bitrate = 0;
if ("video_bitrate_kbps" in info){
bitrate = info.video_bitrate_kbps;
}
if (isNaN(bitrate)) {
bitrate = 0;
}
if (bitrate<0){bitrate = 0;}
var nacks = 0;
if ("nacks_per_second" in info){
nacks = info.nacks_per_second;
}
if (isNaN(nacks)) {
nacks = 0;
}
if (nacks<0){nacks = 0;}
var height = context.canvas.height;
var width = context.canvas.width;
canvas.history_nacks.push(nacks);
canvas.history_bitrate.push(bitrate);
canvas.history_nacks = canvas.history_nacks.slice(-1 * canvas.width);
canvas.history_bitrate = canvas.history_bitrate.slice(-1 * canvas.width);
var maxBitrate = Math.max(...canvas.history_bitrate, (info.available_outgoing_bitrate_kbps || 0));
var target = canvas.target || 4000;
if (target && (maxBitrate > target)){
canvas.target = maxBitrate*1.5; // set it higher than it needs to be, so it doens't jump around a lot
var yScale = height / canvas.target;
context.clearRect(0, 0, width, height);
var w = 1;
var x = width - w;
for (var i = 0; i<canvas.history_bitrate.length;i++){
var nacks = canvas.history_nacks[i];
var bitrate = canvas.history_bitrate[i];
var val = (10-nacks)/10;
if (val>1){val=1;}
else if (val<0){val=0;}
var color = getColor(val);
var y = height - bitrate * yScale;
context.fillStyle = color;
context.fillRect(x, y, w, height);
context.fillStyle = "#DDD5";
context.fillRect(x, y-2, w, 4);
if (y-5>0){
context.fillStyle = "#FFF3";
context.fillRect(x, y+2, w, 1);
}
var imageData = context.getImageData(w, 0, x, height);
context.putImageData(imageData, 0, 0);
context.clearRect(x, 0, w, height);
}
for (var tt = 2500; tt<canvas.target;tt+=2500){
var y = parseInt(height - tt * yScale);
context.fillStyle = "#0555";
context.fillRect(0, y, width, 1);
}
log("finished plotting a new y-axis");
return;
}
//if (info.available_outgoing_bitrate_kbps){
// limit target, but requires a history
//}
var val = (10-nacks)/10;
if (val>1){val=1;}
else if (val<0){val=0;}
var color = getColor(val);
var yScale = height / target;
var w = 1;
var x = width - w;
var y = height - bitrate * yScale;
context.fillStyle = color;
context.fillRect(x, y, w, height);
context.fillStyle = "#DDD5";
context.fillRect(x, y-2, w, 4);
if (y-5>0){
context.fillStyle = "#FFF3";
context.fillRect(x, y+2, w, 1);
}
context.fillStyle = "#0555";
if (canvasNew){
for (var tt = 2500; tt<target;tt+=2500){
var y = parseInt(height - tt * yScale);
context.fillRect(0, y, width, 1);
}
} else {
for (var tt = 2500; tt<target;tt+=2500){
var y = parseInt(height - tt * yScale);
context.fillRect(x, y, w, 1);
}
}
var imageData = context.getImageData(w, 0, x, height);
context.putImageData(imageData, 0, 0);
context.clearRect(x, 0, w, height);
log("finished plotting");
}
function selfDestructElement(UUID, uid){
getById("container_" + UUID).querySelectorAll('[data-uid="'+uid+'"]').forEach(ele=>{
ele.classList.remove("greyout");
clearTimeout(ele.selfFadeout);
ele.selfFadeout = setTimeout(function(ele){
ele.classList.add("greyout");
}, 4000, ele);
clearTimeout(ele.selfDestruct);
ele.selfDestruct = setTimeout(function(ele){
try {
ele.remove();
} catch(e){console.error(e);}
}, 10000, ele);
});
return;
}
function remoteStats(data, UUID, streamID){
var container = getContainer(UUID, streamID);
var size = 0;
for (var key in data) {
if (data.hasOwnProperty(key)){
size++;
}
}
if (!size){
container.querySelectorAll('[data-no-scenes]').forEach(ele=>{
ele.classList.remove("hidden");
if (ele.dataset.message){
ele.innerHTML = "<h3>No viewers active yet</h3>Statistics for this stream will be available once its view or scene link becomes active";
}
});
log("zero size");
return;
}
container.querySelectorAll('[data-no-scenes]').forEach(ele=>{
ele.classList.add("hidden");
});
for (var uuid in data){
var container2 = container.querySelector('[data-action-type="stats-graphs-details-container"][data-uid="'+uuid+'"]');
if (!container2){
container2 = document.querySelector('[data-action-type="stats-graphs-details-container"]').cloneNode(true);
container2.dataset.uid = uuid;
container2.classList.remove("hidden");
}
plotData(data[uuid], UUID, uuid);
if (("video_bitrate_kbps" in data[uuid]) && (data[uuid].video_bitrate_kbps!=="video_bitrate_kbps")){
var span = container.querySelector('[data-bitrate]');
if (span){
span.classList.remove("hidden");
span.innerHTML = "video bitrate:<br />"+numberWithCommas(parseInt(data[uuid].video_bitrate_kbps)) + " (kbps)";
}
}
var span = container.querySelector('[data-scene-name]');
if (span && ("label" in data[uuid]) && data[uuid].label){
span.classList.remove("hidden");
span.innerHTML = "stats for:<br />" + data[uuid].label;
} else if (span && ("scene" in data[uuid]) && (data[uuid].scene !==false)){
span.classList.remove("hidden");
span.innerHTML = "stats for:<br />scene" + data[uuid].scene;
} else if (uuid==="meshcast"){
span.classList.remove("hidden");
span.innerHTML = "stats for:<br />meshcast";
span.title = "You can use &label=xxxx to give your view links a unique label";
} else {
span.classList.remove("hidden");
span.innerHTML = "stats for:<br />a viewer";
span.title = "You can use &label=xxxx to give your view links a unique label";
}
if ("resolution" in data[uuid]){
var span = container.querySelector('[data-resolution]');
if (span){
span.classList.remove("hidden");
span.innerHTML = "res: "+data[uuid].resolution.replace(" @ ","<br />fps: ");
}
}
if ("video_encoder" in data[uuid]){
var span = container.querySelector('[data-video-codec]');
if (span){
span.classList.remove("hidden");
span.innerHTML = "video codec:<br />"+data[uuid].video_encoder;
}
}
}
}
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function removeStorage(cname){

File diff suppressed because one or more lines are too long