Add files via upload

part 1
This commit is contained in:
Steve Seguin 2021-11-24 20:53:57 -05:00 committed by GitHub
parent 23ccdea1b4
commit 9ed7ab3c44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1206 additions and 171 deletions

55
codecs.html Normal file
View File

@ -0,0 +1,55 @@
<html>
<body>
<div id="output"></div>
<script>
function getSupportedMimeTypes(media, types, codecs) {
const isSupported = MediaRecorder.isTypeSupported;
const supported = [];
types.forEach((type) => {
const mimeType = `${media}/${type}`;
acodecs.forEach((codec2) => {
codecs.forEach((codec) => [
mimeType+';codecs="'+codec+', '+codec2+'"',
mimeType+';codecs:"'+codec+', '+codec2+'"',
mimeType+';codecs="'+codec.toUpperCase()+', '+codec2.toUpperCase()+'"',
mimeType+';codecs:"'+codec.toUpperCase()+', '+codec2.toUpperCase()+'"'
].forEach(variation => {
if (isSupported(variation)){
supported.push(variation);
}
}));
});
if (isSupported(mimeType))
supported.push(mimeType);
});
return supported;
};
// Usage ------------------
const videoTypes = ["webm", "ogg", "mp4", "x-matroska"];
const audioTypes = ["webm", "ogg", "mp3", "x-matroska"];
const codecs = ["vp9", "vp9.0", "vp8", "vp8.0", "avc1", "av1", "h265", "h.265", "h264", "h.264"]
const acodecs = ["opus"];
const supportedVideos = getSupportedMimeTypes("video", videoTypes, codecs);
const supportedAudios = getSupportedMimeTypes("audio", audioTypes, codecs);
console.log('-- Top supported Video : ', supportedVideos[0])
console.log('-- Top supported Audio : ', supportedAudios[0])
console.log('-- All supported Videos : ', supportedVideos)
console.log('-- All supported Audios : ', supportedAudios)
document.getElementById("output").innerHTML= "";
for (var i=0;i<supportedVideos.length;i++){
document.getElementById("output").innerHTML += supportedVideos[i]+"<br/>";
}
//for (var i=0;i<supportedAudios.length;i++){
//document.getElementById("output").innerHTML += supportedAudios[i]+"<br/>";
//}
</script>
</body>
</html>

View File

@ -548,6 +548,7 @@ function addUrlToHistory(url){
function modURL(){
var url = document.getElementById('changeText').value;
url = url.trim();
if (url.startsWith("obs.ninja")){
url = "https://"+url;
} else if (url.startsWith("youtube.com")){

View File

@ -66,8 +66,8 @@
<span itemprop="thumbnail" itemscope itemtype="http://schema.org/ImageObject">
<link itemprop="url" href="./media/vdoNinja_logo_full.png" />
</span>
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/CodecsHandler.js?ver=34"></script>
<script type="text/javascript" crossorigin="anonymous" src="./webrtc.js?ver=334"></script>
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/CodecsHandler.js?ver=35"></script>
<script type="text/javascript" crossorigin="anonymous" src="./webrtc.js?ver=336"></script>
<input id="zoomSlider" type="range" style="display: none;" />
<div id="header">
@ -745,7 +745,9 @@
<li>
Chrome v95 with an AMD GPUs have an issue hardware encoding H264 video; this will be fixed in Chrome v96. <a href='https://bugs.chromium.org/p/chromium/issues/detail?id=1252710' target="_blank">Details here</a>.
</li>
<li>
Samsung smartphones (A-series) may fail to publish video with some mobile browsers; try using Firefox or the native <a href='https://docs.vdo.ninja/getting-started/native-mobile-app-versions#android-download-link'>VDO.Ninja Android app</a> in these cases.
</li>
<br />
<h4>
<font style="color:#daad09;">Welcome to VDO Ninja! We've rebranded! Nothing else is changing and we're staying 100% free.</font>
@ -1804,7 +1806,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 = "19.4";
session.version = "20.0-beta";
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
@ -1875,11 +1877,11 @@
// session.introOnClean = true; // this will load the page with the webcam selection screen if &push or &room is in the URL; no need to use &webcam.
</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=241"></script>
<script type="text/javascript" crossorigin="anonymous" id="lib-js" src="./lib.js?ver=242"></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=283"></script>
<script type="text/javascript" crossorigin="anonymous" id="main-js" src="./main.js?ver=284"></script>
</body>
</html>

314
lib.js
View File

@ -74,7 +74,8 @@ var miscTranslations = {
"enter-new-codirector-password": "Enter a co-director password to use",
"control-room-co-director": "Control Room: Co-Director",
"signal-meter": "Video packet loss indicator of video preview; green is good, red is bad. Flame implies CPU is overloaded. May not reflect the packet loss seen by scenes or other guests.",
"waiting-for-the-stream": "Waiting for the stream. Tip: Adding &cleanoutput to the URL will hide this spinner, or click to retry, which will also hide it."
"waiting-for-the-stream": "Waiting for the stream. Tip: Adding &cleanoutput to the URL will hide this spinner, or click to retry, which will also hide it.",
"main-director": "Main Director"
};
// function log(msg){ // uncomment to enable logging.
@ -1592,7 +1593,6 @@ function setupIncomingVideoTracking(v, UUID){ // video element.
v.usermuted = false;
v.addEventListener('volumechange',function(e){
console.warn("volume changed");
var muteState = checkMuteState(UUID);
if (this.muted && (this.muted !== muteState)){
this.usermuted = true;
@ -1696,6 +1696,9 @@ function updateMixerRun(e=false){ // this is the main auto-mixing code. It's a
} else if (session.aspectratio==2){
arW = 12.0; // square root; cause why not.
arH = 12.0;
} else if (session.aspectratio==3){
arW = 12.0; // square root; cause why not.
arH = 9.0;
}
}
@ -3051,7 +3054,7 @@ function updateMixerRun(e=false){ // this is the main auto-mixing code. It's a
if (session.signalMeter){
if (vid.dataset.UUID && !session.rpcs[vid.dataset.UUID].signalMeter){
session.rpcs[vid.dataset.UUID].signalMeter = getById("signalMeterTemplate").cloneNode(true);
session.rpcs[vid.dataset.UUID].signalMeter.style.display = "block";
//session.rpcs[vid.dataset.UUID].signalMeter.style.display = "block";
session.rpcs[vid.dataset.UUID].signalMeter.id = "signalMeter_" + vid.dataset.UUID;
session.rpcs[vid.dataset.UUID].signalMeter.dataset.level = 0;
session.rpcs[vid.dataset.UUID].signalMeter.title = miscTranslations["signal-meter"];
@ -4734,6 +4737,11 @@ function printMyStats(menu) { // see: setupStatsMenu
}
printViewValues(session.stats);
menu.innerHTML += "<button onclick='session.forcePLI(null,event);' data-translate='send-keyframe-to-viewer'>Send Keyframe to Viewers</button>";
if (session.mc){
printViewValues(session.mc.stats);
menu.innerHTML += "<hr>";
}
for (var uuid in session.pcs) {
printViewValues(session.pcs[uuid].stats);
menu.innerHTML += "<hr>";
@ -4748,6 +4756,10 @@ function printMyStats(menu) { // see: setupStatsMenu
}
function publisherMeshcastStats(){
}
function updateLocalStats(){
var totalBitrate = 0;
@ -4757,6 +4769,223 @@ function updateLocalStats(){
var totalVideo = 0;
var totalAudio = 0;
var totalScenes = 0;
if (session.mc){
try {
var atot = 0;
var senders = session.mc.getSenders(); // for any connected peer, update the video they have if connected with a video already.
senders.forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams?
if (sender.track && sender.track.kind == "video" && sender.track.enabled) {
totalVideo+=1
} else if (sender.track && sender.track.kind == "audio" && sender.track.enabled && !session.muted) {
atot=1;
}
});
totalAudio += atot;
if ("video_bitrate_kbps" in session.mc.stats){
totalBitrate+=session.mc.stats.video_bitrate_kbps || 0;
}
if ("audio_bitrate_kbps" in session.mc.stats){
totalBitrate+=session.mc.stats.audio_bitrate_kbps || 0;
}
if ("total_sending_bitrate_kbps" in session.mc.stats){
totalBitrate2+=session.mc.stats.total_sending_bitrate_kbps || 0;
}
if ("quality_limitation_reason" in session.mc.stats){
if (session.mc.stats.quality_limitation_reason == "cpu"){
cpuLimited=true;
}
}
setTimeout(function(){
if (!session.mc){return;}
session.mc.getStats().then(function(stats) {
if ("audio_bitrate_kbps" in session.mc.stats){
session.mc.stats.audio_bitrate_kbps=0;
}
stats.forEach(stat => {
if (stat.type == "transport"){
if ("bytesSent" in stat) {
if ("_bytesSent" in session.mc.stats){
if (session.mc.stats._timestamp){
if (stat.timestamp){
session.mc.stats.total_sending_bitrate_kbps = parseInt(8*(stat.bytesSent - session.mc.stats._bytesSent)/(stat.timestamp - session.mc.stats._timestamp));
}
}
}
session.mc.stats._bytesSent = stat.bytesSent;
}
if ("timestamp" in stat) {
session.mc.stats._timestamp = stat.timestamp;
}
} else if (stat.type == "outbound-rtp") {
if (stat.kind == "video") {
if ("framesPerSecond" in stat) {
session.mc.stats.resolution = stat.frameWidth + " x " + stat.frameHeight + " @ " + stat.framesPerSecond;
}
if ("encoderImplementation" in stat) {
session.mc.stats.video_encoder = stat.encoderImplementation;
if (stat.encoderImplementation=="ExternalEncoder"){
session.mc.stats._hardwareEncoder = true; // I won't set this to false again, just because once I know it has one, I just need to assume it could always be used unexpectednly
session.mc.encoder = true;
} else {
session.mc.encoder = false; // this may not be actually accurate, but lets assume so.
}
}
if ("qualityLimitationReason" in stat) {
if (session.mc.stats.quality_limitation_reason){
if (session.mc.stats.quality_limitation_reason !== stat.qualityLimitationReason){
try{
var miniInfo = {};
miniInfo.qlr = stat.qualityLimitationReason;
if ("_hardwareEncoder" in session.mc.stats){
miniInfo.hw_enc = session.mc.stats._hardwareEncoder;
} else {
miniInfo.hw_enc = null;
}
session.sendMessage({"miniInfo":miniInfo});
} catch(e){warnlog(e);}
}
}
session.mc.stats.quality_limitation_reason = stat.qualityLimitationReason;
}
if ("bytesSent" in stat) {
if ("_bytesSentVideo" in session.mc.stats){
if (session.mc.stats._timestamp1){
session.mc.stats.video_bitrate_kbps = parseInt(8*(stat.bytesSent - session.mc.stats._bytesSentVideo)/(stat.timestamp - session.mc.stats._timestamp1));
if (stat.timestamp){
}
}
}
session.mc.stats._bytesSentVideo = stat.bytesSent;
}
if ("nackCount" in stat) {
if ("_nackCount" in session.mc.stats){
if (session.mc.stats._timestamp1){
if (stat.timestamp){
session.mc.stats.nacks_per_second = parseInt(10000*(stat.nackCount - session.mc.stats._nackCount)/(stat.timestamp - session.mc.stats._timestamp1))/10;
}
}
}
}
if ("retransmittedBytesSent" in stat) {
if ("_retransmittedBytesSent" in session.mc.stats){
if (session.mc.stats._timestamp1){
if (stat.timestamp){
session.mc.stats.retransmitted_kbps = parseInt(8*(stat.retransmittedBytesSent - session.mc.stats._retransmittedBytesSent)/(stat.timestamp - session.mc.stats._timestamp1));
}
}
}
}
if ("nackCount" in stat) {
session.mc.stats._nackCount = stat.nackCount;
}
if ("retransmittedBytesSent" in stat) {
session.mc.stats._retransmittedBytesSent = stat.retransmittedBytesSent;
}
if ("timestamp" in stat) {
session.mc.stats._timestamp1 = stat.timestamp;
}
if ("pliCount" in stat) {
session.mc.stats.total_pli_count = stat.pliCount;
}
if ("keyFramesEncoded" in stat) {
session.mc.stats.total_key_frames_encoded = stat.keyFramesEncoded;
}
} else if (stat.kind == "audio") {
if ("bytesSent" in stat) {
if (session.mc.stats._bytesSentAudio){
if (session.mc.stats._timestamp2){
if (stat.timestamp){
if ("audio_bitrate_kbps" in session.mc.stats){
session.mc.stats.audio_bitrate_kbps += parseInt(8*(stat.bytesSent - session.mc.stats._bytesSentAudio)/(stat.timestamp - session.mc.stats._timestamp2));
} else {
session.mc.stats.audio_bitrate_kbps=0;
}
}
}
}
}
if ("timestamp" in stat) {
session.mc.stats._timestamp2 = stat.timestamp;
}
if ("bytesSent" in stat) {
session.mc.stats._bytesSentAudio = stat.bytesSent;
}
}
} else if (stat.type == "remote-candidate") {
if ("candidateType" in stat) {
session.mc.stats.remote_candidateType = stat.candidateType;
if (stat.candidateType === "relay"){
if ("ip" in stat) {
session.mc.stats.remote_relay_IP = stat.ip;
}
if ("relayProtocol" in stat) {
session.mc.stats.remote_relayProtocol = stat.relayProtocol;
}
} else {
try {
delete session.mc.stats.remote_relay_IP;
delete session.mc.stats.remote_relayProtocol;
} catch(e){}
}
}
} else if (stat.type == "local-candidate") {
if ("candidateType" in stat) {
session.mc.stats.local_candidateType = stat.candidateType;
if (stat.candidateType === "relay"){
if ("ip" in stat) {
session.mc.stats.local_relayIP = stat.ip;
}
if ("relayProtocol" in stat) {
session.mc.stats.local_relayProtocol = stat.relayProtocol;
}
} else {
try {
delete session.mc.stats.local_relayIP;
delete session.mc.stats.local_relayProtocol;
} catch(e){}
}
}
} else if ((stat.type == "candidate-pair" ) && (stat.nominated)) {
if ("availableOutgoingBitrate" in stat){
session.mc.stats.available_outgoing_bitrate_kbps = parseInt(stat.availableOutgoingBitrate/1024);
}
if ("totalRoundTripTime" in stat){
if ("responsesReceived" in stat){
session.mc.stats.average_roundTripTime_ms = parseInt((stat.totalRoundTripTime/stat.responsesReceived)*1000);
}
}
}
return;
});
return;
});
}, 0);
} catch(e){errorlog(e);}
}
for (var uuid in session.pcs) {
var atot = 0;
var senders = session.pcs[uuid].getSenders(); // for any connected peer, update the video they have if connected with a video already.
@ -4933,23 +5162,42 @@ function updateLocalStats(){
}
} else if (stat.type == "remote-candidate") {
if ("relayProtocol" in stat) {
if ("ip" in stat) {
session.pcs[UUID].stats.remote_relay_IP = stat.ip;
}
session.pcs[UUID].stats.remote_relayProtocol = stat.relayProtocol;
}
if ("candidateType" in stat) {
session.pcs[UUID].stats.remote_candidateType = stat.candidateType;
if (stat.candidateType === "relay"){
if ("ip" in stat) {
session.pcs[UUID].stats.remote_relay_IP = stat.ip;
}
if ("relayProtocol" in stat) {
session.pcs[UUID].stats.remote_relayProtocol = stat.relayProtocol;
}
} else {
try {
delete session.pcs[UUID].stats.remote_relay_IP;
delete session.pcs[UUID].stats.remote_relayProtocol;
} catch(e){}
}
}
} else if (stat.type == "local-candidate") {
if ("relayProtocol" in stat) {
if ("candidateType" in stat) {
session.pcs[UUID].stats.local_candidateType = stat.candidateType;
if (stat.candidateType === "relay"){
if ("ip" in stat) {
session.pcs[UUID].stats.local_relayIP = stat.ip;
}
if ("relayProtocol" in stat) {
session.pcs[UUID].stats.local_relayProtocol = stat.relayProtocol;
}
if ("candidateType" in stat) {
session.pcs[UUID].stats.local_candidateType = stat.candidateType;
} else {
try {
delete session.pcs[UUID].stats.local_relayIP;
delete session.pcs[UUID].stats.local_relayProtocol;
} catch(e){}
}
}
} else if ((stat.type == "candidate-pair" ) && (stat.nominated)) {
@ -5018,7 +5266,7 @@ function updateStats(obsvc = false) {
} else {
var framerateFPS = track.getSettings().frameRate;
if (framerateFPS){
getById(wcs).innerHTML = "Current Video Settings: " + (track.getSettings().width || 0) + "x" + (track.getSettings().height || 0) + "@" + (framerateFPS * 10 / 10) + "fps";
getById(wcs).innerHTML = "Current Video Settings: " + (track.getSettings().width || 0) + "x" + (track.getSettings().height || 0) + "@" + (parseInt(framerateFPS * 100) / 100.0) + "fps";
} else {
getById(wcs).innerHTML = "Current Video Settings: " + (track.getSettings().width || 0) + "x" + (track.getSettings().height || 0);
}
@ -8326,7 +8574,7 @@ function createControlBox(UUID, soloLink, streamID) {
session.rpcs[UUID].signalMeter = getById("signalMeterTemplate").cloneNode(true);
session.rpcs[UUID].signalMeter.id = "signalMeter_" + UUID;
session.rpcs[UUID].signalMeter.dataset.level = 0;
session.rpcs[UUID].signalMeter.style.display = "block";
//session.rpcs[UUID].signalMeter.style.display = "block";
session.rpcs[UUID].signalMeter.dataset.UUID = UUID;
session.rpcs[UUID].signalMeter.title = miscTranslations["signal-meter"];
session.rpcs[UUID].signalMeter.addEventListener('click', function(e) { // show stats of video if double clicked
@ -11330,7 +11578,7 @@ async function grabVideo(quality = 0, eleName = 'previewWebcam', selector = "sel
}
if ((iOS) || (iPad)) { // iOS will not work correctly at 1080p; likely a h264 codec issue.
if ((iOS || iPad) && safariVersion()<15) { // iOS will not work correctly at 1080p; likely a h264 codec issue.
if (quality == 0) {
quality = 1;
}
@ -11338,7 +11586,7 @@ async function grabVideo(quality = 0, eleName = 'previewWebcam', selector = "sel
var constraints = {
audio: false,
video: getUserMediaVideoParams(quality, iOS)
video: getUserMediaVideoParams(quality, (iOS || iPad))
};
log("Quality selected:" + quality);
@ -11635,8 +11883,8 @@ function updateRenderOutpipe(){ // video only.
toggleVideoMute(true);
}
if (session.meshcast){
if (session.mc.getSenders){ // should only be 0 or 1 video sender, ever.
if (session.mc && session.mc.getSenders){ // should only be 0 or 1 video sender, ever.
var added = false;
session.mc.getSenders().forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams?
if (sender.track && sender.track.kind == "video") {
@ -11649,7 +11897,7 @@ function updateRenderOutpipe(){ // video only.
session.mc.addTrack(track, session.videoElement.srcObject); // can't replace, so adding
}
}
}
for (UUID in session.pcs) {
try {
@ -11809,8 +12057,7 @@ function senderAudioUpdate(callback=false){
if (session.videoElement.srcObject.getAudioTracks()) {
var tracks = session.videoElement.srcObject.getAudioTracks();
if (session.meshcast){
if (session.mc.getSenders){
if (session.mc && session.mc.getSenders){
session.mc.getSenders().forEach((sender) => { // disable senders that aren't part of the active tracks
var good = false;
if (sender.track && sender.track.id && (sender.track.kind == "audio")) {
@ -11872,7 +12119,7 @@ function senderAudioUpdate(callback=false){
}
}
}
}
for (UUID in session.pcs) {
if (session.pcs[UUID].allowAudio == true) {
session.pcs[UUID].getSenders().forEach((sender) => {
@ -16250,7 +16497,19 @@ function createIframePopup() {
}
if (session.screensharefps!==false){
extras += "&maxframerate="+session.screensharefps;
extras += "&maxframerate="+parseInt(session.screensharefps*100)/100.0;
}
if (session.screenshareAEC!==false){
extras += "&aec=1";
}
if (session.screenshareDenoise!==false){
extras += "&denoise=1";
}
if (session.screenshareAutogain!==false){
extras += "&autogain=1";
}
if (session.screenshareStereo!==false){
extras += "&stereo="+session.screenshareStereo;
}
if (session.muted){
@ -19436,6 +19695,11 @@ function midiHotkeysCommand(command, value){
if (elements[guestslot]) {
remoteDisplayMute(elements[guestslot]);
}
} else if (value == 8) {
var elements = document.querySelectorAll('[data-action-type="force-keyframe"][data--u-u-i-d]');
if (elements[guestslot]) {
requestKeyframeScene(elements[guestslot]);
}
} else if (value == 12) {
var elements = document.querySelectorAll('[data-action-type="addToScene"][data-scene="2"][data--u-u-i-d]');
if (elements[guestslot]) {
@ -19492,7 +19756,11 @@ function playbackMIDI(msg){
if ("d" in msg){
for (var i in WebMidi.outputs){
try {
if ("c" in msg){
WebMidi.outputs[i].channels[msg.c].send(msg.d[0], [msg.d[1], msg.d[2]]);
} else {
WebMidi.outputs[i].send(msg.d[0], [msg.d[1], msg.d[2]]);
}
} catch(e){errorlog(e);}
}
}
@ -19500,8 +19768,12 @@ function playbackMIDI(msg){
try {
var i = parseInt(session.midiIn)-1;
if ("d" in msg){
if ("c" in msg){
WebMidi.outputs[i].channels[msg.c].send(msg.d[0], [msg.d[1], msg.d[2]]);
} else {
WebMidi.outputs[i].send(msg.d[0], [msg.d[1], msg.d[2]]);
}
}
} catch(e){errorlog(e);};
}
if (session.midiRemote==4){

View File

@ -343,6 +343,19 @@ button.white:active {
color: #101020;
}
body.darktheme .credits {
color: #707a93;
}
body.darktheme .credits>a {
color: #707a93;
}
body.darktheme .credits>a:visited {
color: #707a93;
}
.chevron {
padding: 0px 6px;
}
@ -538,7 +551,7 @@ hr {
top: 1px;
background-color: #FFF2;
font-size: 1.5em;
display:none;
display:block;
z-index: 2;
cursor: help;
}
@ -551,11 +564,15 @@ hr {
.signal-meter[data-cpu="1"]>.la-signal {
display:none;
}
.signal-meter[data-cpu="1"]>.la-fire {
.signal-meter[data-cpu="1"]>.la-fire-alt {
display:block;
}
.signal-meter[data-cpu="1"] {
display:block!important;
}
.signal-meter[data-level="0"] {
color:#000F;
display:none;
}
.signal-meter[data-level="1"] {
color:#FF1B01;

209
main.js
View File

@ -357,7 +357,6 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if (urlParams.has('rotate') ) {
session.rotate = urlParams.get('rotate') || 90;
session.rotate = parseInt(session.rotate);
}
if (urlParams.has('midi') || urlParams.has('hotkeys')) {
@ -367,18 +366,33 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if (urlParams.has('midiremote') || urlParams.has('remotemidi')){
if (session.director!==false){
session.midiRemote = urlParams.get('midiremote') || urlParams.get ('remotemidi') || 4;
session.midiRemote = parseInt(urlParams.get('midiremote')) || parseInt(urlParams.get ('remotemidi')) || 4;
} else {
session.midiRemote = urlParams.get('midiremote') || urlParams.get ('remotemidi') || 1;
session.midiRemote = parseInt(urlParams.get('midiremote')) || parseInt(urlParams.get ('remotemidi')) || 1;
}
}
if (urlParams.has('midipush') || urlParams.has('midiout') || urlParams.has('mo')){
session.midiOut = urlParams.get('midipush') || urlParams.get('midiout') || urlParams.get('mo') || true;
session.midiOut = parseInt(urlParams.get('midipush')) || parseInt(urlParams.get('midiout')) || parseInt(urlParams.get('mo')) || true;
}
if (urlParams.has('midipull') || urlParams.has('midiin') || urlParams.has('mi')){
session.midiIn = urlParams.get('midipull') || urlParams.get('midiin') || urlParams.get('mi') || true;
session.midiIn = parseInt(urlParams.get('midipull')) || parseInt(urlParams.get('midiin')) || parseInt(urlParams.get('mi')) || true;
}
if (urlParams.has('midichannel')){
session.midiChannel = parseInt(urlParams.get('midichannel')) || false;
}
if (session.midiChannel){
session.midiChannel = parseInt(session.midiChannel);
if (session.midiChannel>16){session.midiChannel=false;}
if (session.midiChannel<1){session.midiChannel=false;}
}
if (urlParams.has('mididevice')){
session.midiDevice = parseInt(urlParams.get('mididevice')) || false;
}
if (session.midiDevice){
session.midiDevice = parseInt(session.midiDevice);
}
@ -536,6 +550,8 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.aspectratio = 1; // 9:16 (default of 0 is 16:9)
} else if (urlParams.has('square') || urlParams.has('11')) {
session.aspectratio = 2; //1:1 ?
} else if (urlParams.has('43')) {
session.aspectratio = 3; //1:1 ?
}
if (urlParams.has('forceaspectratio') || urlParams.has('far')) {
@ -827,6 +843,46 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
}
if (urlParams.has('screensharestereo') || urlParams.has('sss') || urlParams.has('ssproaudio')) { // both peers need this enabled for HD stereo to be on. If just pub, you get no echo/noise cancellation. if just viewer, you get high bitrate mono
log("screenshare stereo ENABLED");
session.screenshareStereo = urlParams.get('screensharestereo') || urlParams.get('sss') || urlParams.get('ssproaudio');
if (session.screenshareStereo) {
session.screenshareStereo = session.screenshareStereo.toLowerCase();
}
if (session.screenshareStereo === "false") {
session.screenshareStereo = 0;
} else if (session.screenshareStereo === "0") {
session.screenshareStereo = 0;
} else if (session.screenshareStereo === "no") {
session.screenshareStereo = 0;
} else if (session.screenshareStereo === "off") {
session.screenshareStereo = 0;
} else if (session.screenshareStereo === "1") {
session.screenshareStereo = 1;
} else if (session.screenshareStereo === "both") {
session.screenshareStereo = 1;
} else if (session.screenshareStereo === "3") {
session.screenshareStereo = 3;
} else if (session.screenshareStereo === "out") {
session.screenshareStereo = 3;
} else if (session.screenshareStereo === "mono") {
session.screenshareStereo = 3;
} else if (session.screenshareStereo === "4") {
session.screenshareStereo = 4;
} else if (session.screenshareStereo === "multi") {
session.screenshareStereo = 4;
} else if (session.screenshareStereo === "2") {
session.screenshareStereo = 2;
} else if (session.screenshareStereo === "in") {
session.screenshareStereo = 2;
} else {
session.screenshareStereo = 5; // guests; no stereo in, no high bitrate in, but otherwise like stereo=1
}
}
if (urlParams.has('pie')){
session.customWSS = urlParams.get('pie') || false; // If session.customWSS == true, then there is no need to set parameters via URL
@ -924,6 +980,63 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
}
if (urlParams.has("screenshareaec") || urlParams.has("ssec") || urlParams.has("ssaec")) {
session.screenshareAEC = urlParams.get('screenshareaec') || urlParams.get('ssec') || urlParams.get("ssaec");
if (session.screenshareAEC) {
session.screenshareAEC = session.screenshareAEC.toLowerCase();
}
if (session.screenshareAEC == "false") {
session.screenshareAEC = false;
} else if (session.screenshareAEC == "0") {
session.screenshareAEC = false;
} else if (session.screenshareAEC == "no") {
session.screenshareAEC = false;
} else if (session.screenshareAEC == "off") {
session.screenshareAEC = false;
} else {
session.screenshareAEC = true;
}
}
if (urlParams.has("screenshareautogain") || urlParams.has("ssag") || urlParams.has("ssagc")) {
session.screenshareAutogain = urlParams.get('screenshareautogain') || urlParams.get('ssag') || urlParams.get('ssagc');
if (session.screenshareAutogain) {
session.screenshareAutogain = session.screenshareAutogain.toLowerCase();
}
if (session.screenshareAutogain == "false") {
session.screenshareAutogain = false;
} else if (session.screenshareAutogain == "0") {
session.screenshareAutogain = false;
} else if (session.screenshareAutogain == "no") {
session.screenshareAutogain = false;
} else if (session.screenshareAutogain == "off") {
session.screenshareAutogain = false;
} else {
session.screenshareAutogain = true;
}
}
if (urlParams.has("screensharedenoise") || urlParams.has("ssdn")) {
session.screenshareDenoise = urlParams.get('screensharedenoise') || urlParams.get('ssdn');
if (session.screenshareDenoise) {
session.screenshareDenoise = session.screenshareDenoise.toLowerCase();
}
if (session.screenshareDenoise == "false") {
session.screenshareDenoise = false;
} else if (session.screenshareDenoise == "0") {
session.screenshareDenoise = false;
} else if (session.screenshareDenoise == "no") {
session.screenshareDenoise = false;
} else if (session.screenshareDenoise == "off") {
session.screenshareDenoise = false;
} else {
session.screenshareDenoise = true;
}
}
if (urlParams.has('roombitrate') || urlParams.has('roomvideobitrate') || urlParams.has('rbr')) {
log("Room BITRATE SET");
@ -1626,6 +1739,18 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.limitTotalBitrate = parseInt(session.limitTotalBitrate);
}
if (urlParams.has('mcvb') || urlParams.has('meshcastbitrate')){
session.meshcastBitrate = urlParams.get('mcvb') || urlParams.get('meshcastbitrate') || 2500;
session.meshcastBitrate = parseInt(session.meshcastBitrate);
}
if (urlParams.has('mccodec') || urlParams.has('meshcastcodec')){
session.meshcastCodec = urlParams.get('mccodec') || urlParams.get('meshcastcodec') || false;
}
if (session.meshcastCodec){
session.meshcastCodec = session.meshcastCodec.toLowerCase();
}
if (urlParams.has('height') || urlParams.has('h')) {
session.height = urlParams.get('height') || urlParams.get('h');
session.height = parseInt(session.height);
@ -3176,11 +3301,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
var script = document.createElement('script');
script.onload = function() {
WebMidi.enable(function(err) { // hotkeys
if (err) {
errorlog(err);
}
WebMidi.enable().then(() =>{
WebMidi.addListener("connected", function(e) {
log(e);
@ -3194,16 +3315,18 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if (session.midiOut===true){
for (var i = 0; i < WebMidi.inputs.length; i++) {
var input = WebMidi.inputs[i];
input.addListener("midimessage", "all", function(e) {
input.addListener("midimessage", function(e) {
log(e);
var msg = {};
msg.midi = {};
msg.midi.d = e.data;
msg.midi.s = e.timestamp;
msg.midi.t = e.type;
if (e.message && e.message.channel){
msg.midi.c = e.message.channel;
}
for (var UUID in session.pcs){
if (session.pcs[UUID].allowMIDI){
session.sendMessage(msg, UUID);
@ -3214,14 +3337,15 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
} else if (session.midiOut==parseInt(session.midiOut)){
try{
var input = WebMidi.inputs[parseInt(session.midiOut)-1];
input.addListener("midimessage", "all", function(e) {
log(e);
input.addListener("midimessage", function(e) {
console.log(e);
var msg = {};
msg.midi = {};
msg.midi.d = e.data;
msg.midi.s = e.timestamp;
msg.midi.t = e.type;
msg.midi.s = parseInt(10000*e.timestamp)/10000.0;
if (e.message && e.message.channel){
msg.midi.c = e.message.channel;
}
for (var UUID in session.pcs){
if (session.pcs[UUID].allowMIDI){
session.sendMessage(msg, UUID);
@ -3232,51 +3356,36 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
for (var i = 0; i < WebMidi.inputs.length; i++) {
if (session.midiDevice && (session.midiDevice!==(i+1))){continue;}
var input = WebMidi.inputs[i];
input.addListener('noteon', "all", function(e) {
if (session.midiChannel){
input = input.channels[session.midiChannel];
}
if (session.midiHotkeys==4){
input.addListener('controlchange', function(e) {
log(e);
midiHotkeysCommand(e.controller.number, e.rawValue);
});
} else {
input.addListener('noteon', function(e) {
log(e);
var note = e.note.name + e.note.octave;
var velocity = e.velocity || false;
midiHotkeysNote(note,velocity);
});
input.addListener('controlchange', "all", function(e) {
if (session.midiHotkeys==4){
/* channel: 1
controller: {number: 110, name: undefined}
data: Uint8Array(3) [176, 110, 3]
target: Input {_userHandlers: {}, _midiInput: MIDIInput, }
timestamp: 98235.34000001382
type: "controlchange"
value: 3 */
log(e);
if (e.channel!==1){
errorlog("VDO.Ninja is currently configured for use on channel 1 for MIDI hotkeys");
return;
} // channel 1?
var command = e.controller.number;
var value = e.value;
midiHotkeysCommand(command, value);
}
});
}
});
}).catch(errorlog);
};
script.src = "./thirdparty/webmidi.js"; // dynamically load this only if its needed. Keeps loading time down.
script.src = "./thirdparty/webmidi3.js"; // dynamically load this only if its needed. Keeps loading time down.
document.head.appendChild(script);
} else if (session.midiIn){
var script = document.createElement('script');
script.src = "./thirdparty/webmidi.js"; // dynamically load this only if its needed. Keeps loading time down.
script.src = "./thirdparty/webmidi3.js"; // dynamically load this only if its needed. Keeps loading time down.
script.onload = function() {
WebMidi.enable(function(err) { // hotkeys
if (err) {
errorlog(err);
}
console.log(WebMidi.outputs);
});
WebMidi.enable().then(() => console.log(WebMidi.outputs)).catch(errorlog);
}
document.head.appendChild(script);
}

571
midi.html Normal file
View File

@ -0,0 +1,571 @@
<html>
<head>
<script src="https://vdo.ninja/thirdparty/webmidi.js"></script>
<link rel="stylesheet" href="https://vdo.ninja/main.css" />
<style>
.container {
max-width: 80%;
width: fit-content;
margin: 0 auto;
}
h1 {
margin-top: 1em;
margin-bottom: 1em;
padding: 10px;
}
.card {
margin: 10px;
box-shadow: 0 4px 8px 0 rgb(0 0 0 / 10%);
background-color: #ddd;
color: black;
margin-bottom: 1.5em;
}
.card>div {
padding: 10px;
}
.card h2 {
font-size: 1.5em;
padding: 10px;
background-color: #457b9d;
color: white;
border-bottom: 2px solid #3b6a87;
}
small {
font-style: italic;
display: block;
margin-left: 1em;
}
span.warning {
color: rgb(212, 191, 0);
}
input {
padding: 10px;
}
video {
max-width: 640px;
max-height: 360px;
padding: 20px;
}
audio {
max-width: 640px;
max-height: 360px;
padding: 20px;
}
div#processing {
display: none;
justify-content: center;
place-items: center;
position: absolute;
inset: 0;
font-size: 1.5em;
font-weight: bold;
background: #141926;
flex-direction: column;
}
button {
margin:5px;
border:solid black 2px;
}
body {
color:white;
}
a {
color: #225273!important;
}
</style>
<title>VDO.Ninja MIDI Controller</title>
</head>
<body>
<div id="header">
<a id="logoname" href="./" style="text-decoration: none; color: white; margin: 2px">
<span data-translate="logo-header">
<font id="qos">V</font>DO.Ninja
</span>
</a>
</div>
<div class="container">
<div id="info">
<h1>VDO.Ninja MIDI test app</h1>
<div class="card">
<h2>About</h2>
<div>
You can check the console debug logs for added of input/output events and device details.
<br /><br />You can download a virtual MIDI I/O controller for windwos here:<br />
http://www.tobias-erichsen.de/software/loopmidi.html
<br /><br />This code uses the WebMIDI.js library, referenced here:<br />
https://github.com/djipco/webmidi
<br /><br />
Below you can test the <a href="https://docs.vdo.ninja/general-settings/midi">MIDI hotkey commands</a> for VDO.Ninja below:<br />
</div>
</div>
<div class="card">
<h2>Select the MIDI Output device:</h2>
<div>
<label for="outputdevice">MIDI Output device:</label>
<select name="outputdevice" id="outputdevice">
</select>
</div>
</div>
<div class="card">
<h2>&midi=1</h2>
<div id="container1">
</div>
</div>
<div class="card">
<h2>&midi=3</h2>
<div id="container2">
</div>
</div>
<div class="card">
<h2>&midi=4 ; director</h2>
<div id="container3">
</div>
</div>
<div class="card">
<h2>&midi=4 ; guest 1</h2>
<div id="container4">
</div>
</div>
<div class="card">
<h2>&midi=4 ; guest 2</h2>
<div id="container5">
</div>
</div>
<div class="card">
<h2>Sample Remote Director Control links</h2>
<div id="container6">
</div>
</div>
</div>
</div>
<div id='commands'>
</div>
<script>
// Enable WebMidi.js
WebMidi.enable(function (err) {
if (err) {
console.log("WebMidi could not be enabled.", err);
}
// Viewing available inputs and outputs
console.log(WebMidi.inputs);
console.log(WebMidi.outputs);
var output = WebMidi.outputs[0];
var midiout = 0;
var outputdevice = document.getElementById("outputdevice");
for (var i=0;i<WebMidi.outputs.length;i++){
var opt = document.createElement('option');
opt.value = WebMidi.outputs[i].id;
opt.innerHTML = WebMidi.outputs[i].name + " (id:"+(1+i)+")";
if (i==0){
midiout = opt.value;
opt.selected = true;
}
outputdevice.appendChild(opt);
}
var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
outputdevice.onchange = function(e){
midiout = outputdevice.value;
output = WebMidi.getOutputById(midiout);
//input = WebMidi.getInputById(midiout);
console.log("MIDI DEVICE CHANGED: "+midiout);
var container = document.getElementById("container6");
container.innerHTML = "<br />https://"+path+"/?midiremote=4&director=ROOMNAMEHERE";
container.innerHTML += "<br /><br />";
container.innerHTML += "https://"+path+"/?room=ROOMNAMEHERE&midiout="+(outputdevice.selectedIndex+1)+"&vd=0&ad=0&push&autostart&label=MIDI_CONTROLLER";
}
var container = document.getElementById("container6");
container.innerHTML = "<br />https://"+path+"/?midiremote=4&director=ROOMNAMEHERE";
container.innerHTML += "<br /><br />";
container.innerHTML += "https://"+path+"/?room=ROOMNAMEHERE&midiout="+(outputdevice.selectedIndex+1)+"&vd=0&ad=0&push&autostart&label=MIDI_CONTROLLER";
// Reacting when a new device becomes available
WebMidi.addListener("connected", function(e) {
console.log(e);
});
// Reacting when a device becomes unavailable
WebMidi.addListener("disconnected", function(e) {
console.log(e);
});
// Display the current time
console.log(WebMidi.time);
// Retrieve an input by name, id or index
// var input = WebMidi.getInputByName("StreamDeck2Daw");
// input = WebMidi.getInputById("1809568182");
try{
for (var i =0;i<WebMidi.inputs.length;i++){
var input = WebMidi.inputs[i];
// Listen for a 'note on' message on all channels
input.addListener('noteon', "all",
function (e) {
console.log("Received 'noteon' message (" + e.note.name + e.note.octave + ").");
console.log(e);
}
);
// Listen to pitch bend message on channel 3
input.addListener('pitchbend', 3,
function (e) {
console.log("Received 'pitchbend' message.", e);
}
);
// Listen to control change message on all channels
input.addListener('controlchange', "all",
function (e) {
console.log("Received 'controlchange' message.", e);
}
);
// Listen to NRPN message on all channels
input.addListener('nrpn', "all",
function (e) {
if(e.controller.type === 'entry') {
console.log("Received 'nrpn' 'entry' message.", e);
}
if(e.controller.type === 'decrement') {
console.log("Received 'nrpn' 'decrement' message.", e);
}
if(e.controller.type === 'increment') {
console.log("Received 'nrpn' 'increment' message.", e);
}
console.log("message value: " + e.controller.value + ".", e);
}
);
}
} catch(e){errorlog("no input midi found");}
var container = document.getElementById("container1");
///
var button = document.createElement("button");
button.innerHTML = "Note G3; Chat";
button.onclick = function(){output.playNote("G3");}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Note A3; Mute";
button.onclick = function(){output.playNote("A3");}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Note B3; Mute Video";
button.onclick = function(){output.playNote("B3");}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Note C4; ScreenShare";
button.onclick = function(){output.playNote("C4");}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Note D4; Hangup";
button.onclick = function(){output.playNote("D4");}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Note E4; Hands";
button.onclick = function(){output.playNote("E4");}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Note F4; Record";
button.onclick = function(){output.playNote("F4");}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Note G4; Turn on Dir's Audio";
button.onclick = function(){output.playNote("G4");}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Note A4; Stop Dir's Audio";
button.onclick = function(){output.playNote("A4");}; // "speaker" also works in the same way.
container.appendChild(button);
///
var container = document.getElementById("container2");
///
var button = document.createElement("button");
button.innerHTML = "Note C1; velocity 0";
button.onclick = function(){output.playNote("C1", 1, {velocity: 0});}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Note C1; velocity 1";
button.onclick = function(){output.playNote("C1", 1, {velocity: 1});}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Note C1; velocity 2";
button.onclick = function(){output.playNote("C1", 1, {velocity: 2});}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Note C1; velocity 3";
button.onclick = function(){output.playNote("C1", 1, {velocity: 3});}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Note C1; velocity 4";
button.onclick = function(){output.playNote("C1", 1, {velocity: 4});}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Note C1; velocity 5";
button.onclick = function(){output.playNote("C1", 1, {velocity: 5});}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Note C1; velocity 6";
button.onclick = function(){output.playNote("C1", 1, {velocity: 6});}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Note C1; velocity 7";
button.onclick = function(){output.playNote("C1", 1, {velocity: 7});}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Note C1; velocity 8";
button.onclick = function(){output.playNote("C1", 1, {velocity: 8});}; // "speaker" also works in the same way.
container.appendChild(button);
///
var container = document.getElementById("container3");
///
var button = document.createElement("button");
button.innerHTML = "channel 110; value 0";
button.onclick = function(){output.sendControlChange(110, 0, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "channel 110; value 1";
button.onclick = function(){output.sendControlChange(110, 1, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "channel 110; value 2";
button.onclick = function(){output.sendControlChange(110, 2, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "channel 110; value 3";
button.onclick = function(){output.sendControlChange(110, 3, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "channel 110; value 4";
button.onclick = function(){output.sendControlChange(110, 4, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "channel 110; value 5";
button.onclick = function(){output.sendControlChange(110, 5, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "channel 110; value 6";
button.onclick = function(){output.sendControlChange(110, 6, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "channel 110; value 7";
button.onclick = function(){output.sendControlChange(110, 7, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "channel 110; value 8";
button.onclick = function(){output.sendControlChange(110, 8, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
///
var container = document.getElementById("container4");
///
var button = document.createElement("button");
button.innerHTML = "channel 111; value 0";
button.onclick = function(){output.sendControlChange(111, 0, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "channel 111; value 1";
button.onclick = function(){output.sendControlChange(111, 1, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "channel 111; value 2";
button.onclick = function(){output.sendControlChange(111, 2, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "channel 111; value 3";
button.onclick = function(){output.sendControlChange(111, 3, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "channel 111; value 4";
button.onclick = function(){output.sendControlChange(111, 4, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "channel 111; value 5";
button.onclick = function(){output.sendControlChange(111, 5, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Rainbow Puke Fix; value 8";
button.onclick = function(){output.sendControlChange(111, 8, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
///
var container = document.getElementById("container5");
///
var button = document.createElement("button");
button.innerHTML = "channel 112; transfer popup";
button.onclick = function(){output.sendControlChange(112, 0, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "channel 112; scene 1";
button.onclick = function(){output.sendControlChange(112, 1, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "channel 112; mute in scene";
button.onclick = function(){output.sendControlChange(112, 2, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "channel 112; mute everywhere";
button.onclick = function(){output.sendControlChange(112, 3, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "channel 112; hang up";
button.onclick = function(){output.sendControlChange(112, 4, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "channel 112; solo chat";
button.onclick = function(){output.sendControlChange(112, 5, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "remote speaker";
button.onclick = function(){output.sendControlChange(112, 6, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "remote display";
button.onclick = function(){output.sendControlChange(112, 7, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Rainbow Puke Fix";
button.onclick = function(){output.sendControlChange(112, 8, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "scene 2";
button.onclick = function(){output.sendControlChange(112, 12, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "scene 3";
button.onclick = function(){output.sendControlChange(112, 13, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "scene 4";
button.onclick = function(){output.sendControlChange(112, 14, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "scene 5";
button.onclick = function(){output.sendControlChange(112, 15, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "scene 6";
button.onclick = function(){output.sendControlChange(112, 16, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = " scene 7";
button.onclick = function(){output.sendControlChange(112, 17, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "scene 8";
button.onclick = function(){output.sendControlChange(112, 18, 1);}; // "speaker" also works in the same way.
container.appendChild(button);
});
</script>
</body>
</html>

View File

@ -2,7 +2,7 @@
<head>
<meta charset="utf8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>OBSN Monitoring</title>
<title>VDO.Ninja Monitoring</title>
<style>
body {
@ -99,7 +99,9 @@
canvas {
background-color: black;
margin: 20px;
margin: 1vw;
width: 22vw;
height: 160px;
}
#log {
@ -430,14 +432,12 @@
<body onload="loadIframe();">
<div id="container">
<h1 >
<span id="streamID_header">OBS.Ninja Remote Monitor</span>
<span id="streamID_header">VDO.Ninja Remote Monitor</span>
</h1>
</div>
<div id="graphs" style="display:none">
<div class="graph">
<h2>Bitrate (kbps)</h2>
<span>0</span>

View File

@ -116,6 +116,7 @@
var quality_reason = "";
var encoder = "";
var Round_Trip_Time_ms = "";
var recordResults = false;
function copyFunction(copyText) {
alert("Log copied to the clipboard.");
@ -241,6 +242,10 @@
srcString = srcString + "&buffer=" + urlParams.get("buffer");
}
if (urlParams.has("record")) {
recordResults = true;
}
iframe.src = srcString;
iframeContainer.appendChild(iframe);
@ -363,14 +368,12 @@
if (out.split("Bitrate_in_kbps").length > 1) {
for (var key in e.data.stats.inbound_stats[streamID]) {
if (key.startsWith("RTCMediaStreamTrack_receiver")) {
var bitrate =
e.data.stats.inbound_stats[streamID][key][
var bitrate = e.data.stats.inbound_stats[streamID][key][
"Bitrate_in_kbps"
];
updateData("bitrate", bitrate);
var buffer =
e.data.stats.inbound_stats[streamID][key][
var buffer = e.data.stats.inbound_stats[streamID][key][
"Buffer_Delay_in_ms"
];
updateData("buffer", buffer);
@ -381,8 +384,7 @@
updateData("packetloss", packetloss);
}
var resolution =
e.data.stats.inbound_stats[streamID][key]["Resolution"];
var resolution = e.data.stats.inbound_stats[streamID][key]["Resolution"];
if (previousResolution != resolution) {
previousResolution = resolution;
@ -392,6 +394,12 @@
}
}
if (recordResults){
var request = new XMLHttpRequest();
request.open('POST', "https://reports.vdo.ninja/record");
request.send(JSON.stringify(e.data.stats));
}
}
});
}

File diff suppressed because one or more lines are too long