diff --git a/convert.html b/convert.html index d1097ce..23aedb5 100644 --- a/convert.html +++ b/convert.html @@ -92,14 +92,7 @@
-

MKV to MP4 (no transcoding)

-
- The same as: fmpeg -i INPUTFILE -vcodec copy -acodec copy output.mp4 - -
-
-
-

WebM MP4 files (no transcoding, attempts to force high resolutions)

+

WebM to MP4 files (no transcoding, attempts to force high resolutions)

@@ -110,6 +103,13 @@
+
+

MKV to MP4 (no transcoding)

+
+ The same as: fmpeg -i INPUTFILE -vcodec copy -acodec copy output.mp4 + +
+
diff --git a/electron.html b/electron.html index cbaf502..e251005 100644 --- a/electron.html +++ b/electron.html @@ -277,7 +277,7 @@ - +
@@ -400,15 +400,16 @@ if ((location.hostname.toLowerCase() == "vdo.ninja") || (location.hostname.toLow var checkVersion = setTimeout(function(){ // pre 1.5.2 compareVersions("0.0.0"); },500); - - const ipcRenderer = require('electron').ipcRenderer; - console.log("ELECTRON DETECTED"); - ipcRenderer.on('appVersion', function(event, version) { - clearTimeout(checkVersion); - console.log("version: "+version); - compareVersions(version); - }) - ipcRenderer.send('getAppVersion'); + try{ + const ipcRenderer = require('electron').ipcRenderer; + console.log("ELECTRON DETECTED"); + ipcRenderer.on('appVersion', function(event, version) { + clearTimeout(checkVersion); + console.log("version: "+version); + compareVersions(version); + }) + ipcRenderer.send('getAppVersion'); + } catch(e){} } } } catch(e){console.error(e);} @@ -549,6 +550,20 @@ function modURL(){ var url = document.getElementById('changeText').value; if (url.startsWith("obs.ninja")){ url = "https://"+url; + } else if (url.startsWith("youtube.com")){ + url = "https://"+url; + } else if (url.startsWith("twitch.tv")){ + url = "https://"+url; + } else if (url.startsWith("vdo.ninja")){ + url = "https://"+url; + } else if (url.startsWith("http://")){ + // pass + } else if (url.startsWith("https://")){ + // pass + } else if (url.startsWith("file:")){ + // pass + } else { + url = "https://"+url; } console.log(url); return url; @@ -558,7 +573,7 @@ function gohere(){ localStorage.setItem('lastUrls', JSON.stringify(lastUrls)); var url = modURL(); if ((document.getElementById('changeText').value.includes("obs.ninja")) && (document.getElementById('changeText').value.includes("http")) && (document.getElementById('changeText').value.includes("&sink"))){ - alert("Notice: OBS.Ninja has been replaced by VDO.Ninja.\n\nPlease update your links accordingly for audio output to work correctly."); + alert("Notice: OBS.Ninja has been renamed to VDO.Ninja.\n\nPlease update your links accordingly for audio output to work correctly."); } else if (!(document.getElementById('changeText').value.includes(window.location.hostname)) && (document.getElementById('changeText').value.includes("http")) && (document.getElementById('changeText').value.includes("&sink"))){ alert("Notice: The &sink command is domain specific.\nVisit https://YOURDOMAIN.com/electron instead."); } diff --git a/examples/chat.html b/examples/chat.html new file mode 100644 index 0000000..3a5080b --- /dev/null +++ b/examples/chat.html @@ -0,0 +1,159 @@ + + + + + OBSN Chat Overlay + + + + + + diff --git a/examples/draggable.html b/examples/draggable.html new file mode 100644 index 0000000..d968048 --- /dev/null +++ b/examples/draggable.html @@ -0,0 +1,331 @@ + +Dual Input + + + + + + +You can drag and resize the generated windows; multiple can be created. + +
+ + + + \ No newline at end of file diff --git a/examples/dual.html b/examples/dual.html index d968048..8873eee 100644 --- a/examples/dual.html +++ b/examples/dual.html @@ -1,328 +1,73 @@ Dual Input + + - -You can drag and resize the generated windows; multiple can be created. - -
+ + + + + + VDO.Ninja MIDI Controller + + + + +
+
+

VDO.Ninja MIDI test app

+ +
+

About

+
+ You can check the console debug logs for added details. +

You can download a virtual MIDI I/O controller for windwos here:
+ http://www.tobias-erichsen.de/software/loopmidi.html +

This code uses the WebMIDI.js library, referenced here:
+ https://github.com/djipco/webmidi +

+ Below you can test the MIDI hotkey commands for VDO.Ninja below:
+
+
+
+

Select the MIDI Output device:

+
+ + +
+
+
+

&midi=1

+
+ +
+
+
+

&midi=3

+
+ +
+
+
+

&midi=4 ; director

+
+ +
+
+
+

&midi=4 ; guest 1

+
+ +
+
+
+

&midi=4 ; guest 2

+
+ +
+
+
+

Sample Remote Director Control links

+
+ +
+
+
+
+ + + +
+ +
+ + + diff --git a/examples/minidirector.css b/examples/minidirector.css new file mode 100644 index 0000000..5821180 --- /dev/null +++ b/examples/minidirector.css @@ -0,0 +1,97 @@ +body{ + zoom: 75%; +} +.hidden{ + display:unset!important; + visibility: visible; + width:unset; + height:unset; + opacity: 1; +} +button[data-action-type='solo-chat'] { + display:none! important; +} + +button[data-action-type='recorder-local'] { + display:none! important; +} +span[data-action-type='ordering'] { + display:none! important; +} +button[data-action-type='open-file-share'] { + display:none! important; +} + +button[data-action-type='add-channel']{ + display:none! important; +} +button[data-action-type='toggle-remote-speaker']{ + display:none! important; +} +button[data-action-type='toggle-remote-display']{ + display:none! important; +} +button[data-action-type='hide-guest']{ + display:none! important; +} +button[data-action-type='create-timer']{ + display:none! important; +} +button[data-action-type='change-url']{ + display:none! important; +} +button[data-action-type='change-params']{ + display:none! important; +} +span[data-action-type='change-quality']{ + display:none! important; +} + +span[data-action-type='sceneCluster2']{ + display:none! important; +} +span[data-action-type='sceneCluster1']{ + display:none! important; +} + +.orderspan{ + display:none! important; +} +#roomHeader{ + display:none! important; +} +.directorContainer { + display:none!important; +} + +.hideDropMenu{ + display:none!important; +} + +#header{ + display:none!important; +} +body { + background-color: #000; +} + +button[class="pull-right"]{ + display:none! important; +} + +div#guestFeeds { + padding: 1px!important; + margin: 1px!important; +} +div[class="vidcon directorMargins"] { + padding: 1px!important; + margin: 1px!important; + width: 260px; +} +button[data-action-type]{ + margin: 1px!important; +} + + + + diff --git a/examples/obs_client.html b/examples/obs_client.html new file mode 100644 index 0000000..b6d0e75 --- /dev/null +++ b/examples/obs_client.html @@ -0,0 +1,486 @@ + + + + + + + + OBS Controller Demo using VDO.NInja + + +
+

OBS remote (client)

+
+
+

Scenes

+
+
+
+ +
+

Output

+
+ +
+
+
+

Active Sources

+
+
+
+
+

All Sources

+
+
+
+ + +
+

Custom Commands

+ +
+ A list of possible commands available here:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/examples/obs_ws_dock.html b/examples/obs_ws_dock.html new file mode 100644 index 0000000..172700c --- /dev/null +++ b/examples/obs_ws_dock.html @@ -0,0 +1,388 @@ + + + + + OBS Controller Demo using VDO.Ninja + + + +
+

OBS remote (server)

+ + + + + +
+ + + + +
+ + +
+ +
+
+ + + \ No newline at end of file diff --git a/examples/p2p.html b/examples/p2p.html new file mode 100644 index 0000000..36b6989 --- /dev/null +++ b/examples/p2p.html @@ -0,0 +1,69 @@ + + +
+ starting... +
+ + + \ No newline at end of file diff --git a/examples/sensors.html b/examples/sensors.html new file mode 100644 index 0000000..9265939 --- /dev/null +++ b/examples/sensors.html @@ -0,0 +1,280 @@ + +Sensor and video stream access example + + + + + + + + + + + + +

+Add &sensor to the push link to send data; see: https://docs.vdo.ninja/source-settings/sensor +
+ +
+ + + + \ No newline at end of file diff --git a/examples/twitch.html b/examples/twitch.html new file mode 100644 index 0000000..17ea7fe --- /dev/null +++ b/examples/twitch.html @@ -0,0 +1,111 @@ + +Twitch + Video + + + + + + + + +
+
+
+ + + +
+ + + \ No newline at end of file diff --git a/iframe.html b/iframe.html index 0881ae3..28df0bd 100644 --- a/iframe.html +++ b/iframe.html @@ -38,9 +38,7 @@ function loadIframe(){ // this is pretty important if you want to avoid camera var iframe = document.createElement("iframe"); var iframeContainer = document.createElement("div"); var iframesrc = document.getElementById("viewlink").value; - iframe.allow="autoplay;camera;microphone"; - iframe.allowtransparency="true"; - iframe.allowfullscreen ="true"; + iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;"; if (iframesrc==""){ iframesrc="./"; @@ -163,8 +161,21 @@ function loadIframe(){ // this is pretty important if you want to avoid camera iframeContainer.appendChild(button); var button = document.createElement("button"); - button.innerHTML = "Request Stats"; - button.onclick = function(){iframe.contentWindow.postMessage({"getStats":true}, '*');}; + button.innerHTML = "Pan Left"; + button.title = "Requires &panning to be added to the view link"; + button.onclick = function(){iframe.contentWindow.postMessage({"panning":0}, '*');}; + iframeContainer.appendChild(button); + + var button = document.createElement("button"); + button.innerHTML = "Pan right"; + button.title = "Requires &panning to be added to the view link"; + button.onclick = function(){iframe.contentWindow.postMessage({"panning":180}, '*');}; + iframeContainer.appendChild(button); + + var button = document.createElement("button"); + button.innerHTML = "Pan Center"; + button.title = "Requires &panning to be added to the view link"; + button.onclick = function(){iframe.contentWindow.postMessage({"panning":90}, '*');}; iframeContainer.appendChild(button); var button = document.createElement("button"); @@ -394,7 +405,7 @@ function printValues( obj) { - + Clean Output Transparent diff --git a/index.html b/index.html index f173550..0d4b4a4 100644 --- a/index.html +++ b/index.html @@ -55,7 +55,7 @@ } - + @@ -67,8 +67,8 @@ - - + +
@@ -143,7 +142,7 @@
-
+
@@ -317,10 +316,15 @@ - -
+ + + +
+ +

Privacy warning: The director will be able to remotely change your camera and microphone.

+
+
@@ -1178,9 +1191,10 @@ - More scene options + More scene options + - @@ -1190,7 +1204,7 @@ mute in scene -
-
@@ -1697,7 +1743,7 @@ var session = WebRTC.Media; // session is a required global variable if configuring manually. Run before loading main.js but after webrtc.js. - session.version = "18.4b"; + session.version = "19.0"; session.streamID = session.generateStreamID(); // randomly generates a streamID for this session. You can set your own programmatically if needed session.defaultPassword = "someEncryptionKey123"; // Change this password if self-deploying for added security/privacy @@ -1774,11 +1820,11 @@ // session.title // "zzzz" - + - + diff --git a/lib.js b/lib.js index 5ebca53..3554a44 100644 --- a/lib.js +++ b/lib.js @@ -16,6 +16,7 @@ var screensharebutton = true; var screensharesupport = true; var model = null; + var Callbacks = []; var CtrlPressed = false; // global var AltPressed = false; @@ -23,6 +24,7 @@ var AltPressed = false; var translation = false; var miscTranslations = { + "start" : "START", "new-display-name":"Enter a new Display Name for this stream", "submit-error-report": "Press OK to submit any error logs to VDO.Ninja. Error logs may contain private information.", "director-redirect-1": "The director wishes to redirect you to the URL: ", @@ -881,6 +883,9 @@ window.onpopstate = function() { var miniPerformerX = null; var miniPerformerY = null; function makeMiniDraggableElement(elmnt) { + + if (session.disableMouseEvents){return;} + try { elmnt.dragElement = false; elmnt.style.bottom = "auto"; @@ -908,10 +913,12 @@ function makeMiniDraggableElement(elmnt) { e = e || window.event; if (e.type !== "touchmove"){ - if (e.button !== 0){return;} - + if (("buttons" in e) && (e.buttons!==1)){ + closeDragElement(e); + return; + } e.preventDefault(); - } + } e.stopPropagation(); elmnt.dragElement = true; @@ -973,6 +980,8 @@ function makeMiniDraggableElement(elmnt) { if (e.button !== 0){return;} document.onmouseup = elmnt.stashonmouseup; document.onmousemove = elmnt.stashonmousemove; + elmnt.onmouseleave=null; + } @@ -1026,6 +1035,9 @@ function makeMiniDraggableElement(elmnt) { document.onmouseup = closeDragElement; document.onmousemove = elementDrag; + elmnt.onmouseleave = function(event){ + closeDragElement(event); + }; } } @@ -1035,25 +1047,30 @@ function makeMiniDraggableElement(elmnt) { } function makeDraggableElement(elmnt, absolute=false) { + + if (session.disableMouseEvents){return;} + try { elmnt.dragElement = false; elmnt.style.bottom = "auto"; elmnt.style.cursor = "grab"; elmnt.stashonmouseup = null; elmnt.stashonmousemove = null; - } catch (e) { errorlog(e); return; } - var pos1 = 0; var pos2 = 0; var pos3 = 0; var pos4 = 0; - var timestamp = false; + + var enterEventCount = 0; + var leaveEventCount = 0; + + function dragMouseDown(e) { timestamp = Date.now(); @@ -1062,43 +1079,83 @@ function makeDraggableElement(elmnt, absolute=false) { pos3 = e.clientX; pos4 = e.clientY; - elmnt.stashonmouseup = document.onmouseup; // I don't want to interfere with other drag events. - elmnt.stashonmousemove = document.onmousemove; + //elmnt.stashonmouseup = document.onmouseup; // I don't want to interfere with other drag events. + //elmnt.stashonmousemove = document.onmousemove; + //elmnt.stashonclick = document.onclick; - document.onmouseup = closeDragElement; - document.onmousemove = elementDrag; + document.onmouseup = function(event){closeDragElement(event, elmnt);}; + + document.onmousemove = function(event){elementDrag(elmnt,event);}; + + if ("stopDragTimeout" in elmnt){clearTimeout(elmnt.stopDragTimeout);} + + elmnt.onmouseleave = function(event){ + leaveEventCount+=1; + console.log("LEFT MOUSE"); + console.log(event); + elmnt.stopDragTimeout = setTimeout(function(ele,evt1){ + console.log("CLOSING AFTER TIMER"); + closeDragElement(evt1, ele);} + ,100, elmnt, event); + }; + elmnt.onmouseenter = function(event){ + enterEventCount+=1; + console.log("ENTER MOUSE"); + console.log(event); + if (enterEventCount>=leaveEventCount){ + if ("stopDragTimeout" in elmnt){clearTimeout(elmnt.stopDragTimeout);} + } else { + if (("stopDragTimeout" in elmnt) && (elmnt.stopDragTimeout)){ + clearTimeout(elmnt.stopDragTimeout); + elmnt.stopDragTimeout = setTimeout(function(ele,evt1){ + console.log("CLOSING AFTER TIMER"); + closeDragElement(evt1, ele);} + ,100, elmnt, event); + } + } + }; + + //document.onclick = function(event){closeDragElement(event);}; } - - function elementDrag(e) { + function elementDrag(ele,e) { + e = e || window.event; + if (("buttons" in e) && (e.buttons!==1)){return;} + e.preventDefault(); - elmnt.dragElement = true; + ele.dragElement = true; pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; - var topDrag = (elmnt.offsetTop - pos2 ); + var topDrag = (ele.offsetTop - pos2 ); if (absolute){ - if (topDrag > (-3 + (window.innerHeight - elmnt.clientHeight))){ - topDrag = (-3 + (window.innerHeight - elmnt.clientHeight)); + if (topDrag > (-3 + (window.innerHeight - ele.clientHeight))){ + topDrag = (-3 + (window.innerHeight - ele.clientHeight)); } } else { if (topDrag > -3){ topDrag = -3; } } - elmnt.style.top = topDrag + "px"; - elmnt.style.left = (elmnt.offsetLeft - pos1) + "px"; + ele.style.top = topDrag + "px"; + ele.style.left = (ele.offsetLeft - pos1) + "px"; } - - elmnt.onmousedown = dragMouseDown; - function closeDragElement(e) { - document.onmouseup = elmnt.stashonmouseup; - document.onmousemove = elmnt.stashonmousemove; + function closeDragElement(event=false, ele=false) { + document.onmouseup = null; + document.onmousemove = null + ele.onmouseleave = null; + ele.onmouseenter = null; + enterEventCount = 0; + leaveEventCount = 0; + updateMixer(); + //document.onclick = elmnt.stashonclick; } + + elmnt.onmousedown = dragMouseDown; } function setStorage(cname, cvalue, exdays=999){ // not actually a cookie @@ -1107,11 +1164,18 @@ function setStorage(cname, cvalue, exdays=999){ // not actually a cookie value: cvalue, expiry: now.getTime() + (exdays * 24 * 60 * 60 * 1000), }; - localStorage.setItem(cname, JSON.stringify(item)); + try{ + localStorage.setItem(cname, JSON.stringify(item)); + }catch(e){errorlog(e);} } function getStorage(cname) { - var itemStr = localStorage.getItem(cname); + try { + var itemStr = localStorage.getItem(cname); + } catch(e){ + errorlog(e); + return; + } if (!itemStr) { return ""; } @@ -1255,10 +1319,10 @@ function setupIncomingVideoTracking(v, UUID){ // video element. // let's keep the default bitrates, since this isn't a real room and bitrates are specified. } else if (session.novideo !== false){ if (session.novideo.includes(session.rpcs[UUID].streamID)){ - session.requestRateLimit(0,UUID);// limit resolution for guests see ln: 1804 in main.js also + session.requestRateLimit(0,UUID, true);// limit resolution for guests see ln: 1804 in main.js also } } else { - session.requestRateLimit(0,UUID);// limit resolution for guests see ln: 1804 in main.js also + session.requestRateLimit(0,UUID, true);// limit resolution for guests see ln: 1804 in main.js also } setTimeout(function(){updateMixer();},1); } else { @@ -1311,6 +1375,9 @@ function setupIncomingVideoTracking(v, UUID){ // video element. v.touchLastTap = 0; v.touchCount = 0; v.addEventListener('touchend', function(event) { + + if (session.disableMouseEvents){return;} + log("touched"); document.ontouchup = null; @@ -1404,428 +1471,522 @@ function showControlBar(vel){ } catch(e){errorlog(e);} } - -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. - if (getById("subControlButtons").dragElement){ - if (parseInt(getById("subControlButtons").style.top) > 0){ - getById("subControlButtons").style.top = "0px"; - } else if (parseInt(getById("subControlButtons").style.top) < parseInt(50 - window.innerHeight) ){ - getById("subControlButtons").style.top = parseInt( 50 - window.innerHeight)+"px"; - } - if (parseInt(getById("subControlButtons").style.left) < 0){ - getById("subControlButtons").style.left = "0px"; - } else if (parseInt(getById("subControlButtons").style.left) > parseInt( window.innerWidth - getById("subControlButtons").offsetWidth) ){ - getById("subControlButtons").style.left = parseInt( window.innerWidth -getById("subControlButtons").offsetWidth )+"px"; - } - } - if (session.director){return;} - if (session.manual === true){return;} - - var header = getById("header"); - - var hi = header.offsetHeight ; - var w = window.innerWidth; - var h = window.innerHeight - hi; - - if ( window.innerHeight<=700 ){ - if (document.getElementById("controlButtons")){ - var h = window.innerHeight - hi - document.getElementById("controlButtons").offsetHeight; - } else { - var h = window.innerHeight - hi; - } - } - - var arW = 16.0; - var arH = 9.0; - - if (session.aspectratio){ - if (session.aspectratio==1){ - arW = 9.0; - arH = 16.0; - } else if (session.aspectratio==2){ - arW = 12.0; // square root; cause why not. - arH = 12.0; - } - } - - var ww = w/arW; - var hh = h/arH; - - var mediaPool = []; - var mediaPool_invisible = []; - - if (session.videoElement){ // I, myself, exist - if (session.videoElement.style.display!="none"){ // local feed - if (session.minipreview && (session.infocus!==true)){ - - /* session.videoElement.onclick = function(){ - if (session.infocus === true){ - session.infocus = false; - } else { - session.infocus = true; - log("session: myself"); - } - setTimeout(()=>updateMixer(),10); - }; */ - +function updateVolume(update=false){ + if (session.audioGain!==false){ + if (update){ + if (session.roomid){ + var pswd = session.password || ""; + generateHash(session.streamID + session.roomid + pswd + session.salt, 6).then(function(hash) { + setStorage("micVolume_"+hash, session.audioGain, exdays=999); + }); + } + if (session.audioGain == 0){ + getById("header").classList.add('orange'); + getById("head7").classList.remove('advanced'); } else { - if (session.order!==false){ - session.videoElement.order=session.order; - } else { - session.videoElement.order=0; - } - if (session.activeSpeaker && (!session.activelySpeaking)){ - mediaPool_invisible.push(session.videoElement); - } else { - mediaPool.push(session.videoElement); - } + getById("header").classList.remove('orange'); + getById("head7").classList.add('advanced'); } } - } - - if (session.screenShareElement){ // I, myself, exist - if (session.screenShareElement.style.display!="none"){ // local feed - if (session.order!==false){ - session.screenShareElement.order=session.order; - } else { - session.screenShareElement.order=0; - } - - if (session.infocus!==false){ - mediaPool_invisible.push(session.screenShareElement); - } else if (session.activeSpeaker && (!session.activelySpeaking)){ - mediaPool_invisible.push(session.screenShareElement); - } else { - mediaPool.push(session.screenShareElement); - } - } - } - - if (session.iframeEle){ // I, myself, exist - if (session.iframeEle.style.display!="none"){ // local feed - if (session.order!==false){ - session.iframeEle.order=session.order; - } else { - session.iframeEle.order=0; - } - if (session.activeSpeaker && (!session.activelySpeaking)){ - mediaPool_invisible.push(session.iframeEle); - } else { - mediaPool.push(session.iframeEle); - } - } - } - - - if ((session.infocus) && (session.infocus in session.rpcs)){ // remote guest being full screened; infocus == UUID - mediaPool = []; // remove myself from fullscreen - for (var j in session.rpcs){ - if (j != session.infocus){ - session.requestRateLimit(0, j); // disable the video of non-fullscreen videos - try { - if (session.rpcs[j].videoElement.style.display!="none"){ // Add it if not hidden - mediaPool_invisible.push(session.rpcs[j].videoElement); - } - } catch(e){} - } else { // in focus video - //////// - try { - if (session.rpcs[j].order!==false){ - session.rpcs[j].videoElement.order=session.rpcs[j].order; - } else { - session.rpcs[j].videoElement.order=0; - } - /////////// - if (session.activeSpeaker && (!session.rpcs[j].activelySpeaking)){ - mediaPool_invisible.push(session.rpcs[j].videoElement); - } else { - mediaPool.push(session.rpcs[j].videoElement); - } - - session.rpcs[j].videoElement.style.visibility = "visible"; - if ((session.rpcs[j].targetBandwidth!==-1) && (session.rpcs[j].targetBandwidth