mirror of
https://github.com/eliasstepanik/vdo.ninja.git
synced 2026-01-12 06:08:33 +00:00
Features updates from last few days
Group Room *PARTIALLY* complete on the front-end side Server-side Code re-written, but still needing heavy testing -- added support for group video and smarter routing If OBS video fails, the default screen has better information O in OBS turns red if server is down after loading obs.ninja QR Code link works, including the option for use of a usable PERMA LINK , which can be used to invite someone RETRY button if stream does not connect; useful if sending out a guest link More error handling
This commit is contained in:
parent
db1a01cf15
commit
52b0128db7
690
index.html
690
index.html
@ -1,10 +1,12 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
|
||||
<meta content="utf-8" http-equiv="encoding">
|
||||
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
|
||||
|
||||
<script src="//console.re/connector.js" data-channel="obsninja" id="consolerescript"></script>
|
||||
|
||||
<script src="//console.re/connector.js" data-channel="obsninjaalpha" id="consolerescript"></script>
|
||||
<script type="text/javascript" src="qrcode.min.js"></script>
|
||||
<style type="text/css">
|
||||
#mynetwork {
|
||||
width: 600px;
|
||||
@ -41,6 +43,7 @@
|
||||
|
||||
}
|
||||
|
||||
|
||||
#videosource {
|
||||
max-width:100%;
|
||||
max-height:100%;
|
||||
@ -52,6 +55,33 @@
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.vidcon {
|
||||
max-width:100%;
|
||||
max-height:100%
|
||||
}
|
||||
.vidcon:nth-of-type(3n) { grid-column: span 2; }
|
||||
.vidcon:nth-of-type(5n) { grid-row: span 2; }
|
||||
|
||||
.tile {
|
||||
object-fit: contain;
|
||||
background-color:black;
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:0;
|
||||
padding:0;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
.gridlayout {
|
||||
display: grid;
|
||||
width:100%;
|
||||
height:100%;
|
||||
grid-gap: 0;
|
||||
overflow: hidden;
|
||||
justify-items: stretch;
|
||||
grid-auto-flow: dense;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
@ -68,142 +98,147 @@ body {
|
||||
}
|
||||
|
||||
.gowebcam {
|
||||
padding:20px;
|
||||
}
|
||||
.infoblob {
|
||||
color:white;
|
||||
width:100%;
|
||||
padding:20px;
|
||||
max-width:1280px;
|
||||
|
||||
}
|
||||
|
||||
@media only screen and (max-height: 480px) {
|
||||
body {
|
||||
font-size: 0.5em;
|
||||
}
|
||||
.gowebcam {
|
||||
padding:5px;
|
||||
}
|
||||
|
||||
background-color:white;
|
||||
}
|
||||
.infoblob {
|
||||
color:white;
|
||||
width:100%;
|
||||
padding:80px;
|
||||
max-width:1280px;
|
||||
color:white;
|
||||
width:100%;
|
||||
padding:20px;
|
||||
max-width:1280px;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@media only screen and (max-height: 480px) {
|
||||
body {
|
||||
font-size: 0.5em;
|
||||
}
|
||||
.gowebcam {
|
||||
padding:5px;
|
||||
}
|
||||
|
||||
.infoblob {
|
||||
color:white;
|
||||
width:100%;
|
||||
padding:80px;
|
||||
max-width:1280px;
|
||||
|
||||
}
|
||||
|
||||
#qrcode img {
|
||||
max-height:150px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
h2 {
|
||||
color: white;
|
||||
}
|
||||
h2 {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.outer {
|
||||
position: relative;
|
||||
margin: auto;
|
||||
width: 70px;
|
||||
margin-top: 0px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.outer {
|
||||
position: relative;
|
||||
margin: auto;
|
||||
width: 70px;
|
||||
margin-top: 0px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.inner {
|
||||
width: inherit;
|
||||
text-align: center;
|
||||
}
|
||||
.inner {
|
||||
width: inherit;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 1.1em;
|
||||
line-height: 4em;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
color: #000;
|
||||
transition: all .3s ease-in;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
label {
|
||||
font-size: 1.1em;
|
||||
line-height: 4em;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
color: #000;
|
||||
transition: all .3s ease-in;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.inner:before, .inner:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 7px;
|
||||
width: inherit;
|
||||
background: #000;
|
||||
left: 0;
|
||||
transition: all .3s ease-in;
|
||||
}
|
||||
.inner:before, .inner:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 7px;
|
||||
width: inherit;
|
||||
background: #000;
|
||||
left: 0;
|
||||
transition: all .3s ease-in;
|
||||
}
|
||||
|
||||
.inner:before {
|
||||
top: 50%;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.inner:before {
|
||||
top: 50%;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.inner:after {
|
||||
bottom: 50%;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
.inner:after {
|
||||
bottom: 50%;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.outer:hover label {
|
||||
opacity: 1;
|
||||
}
|
||||
.outer:hover label {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.outer:hover .inner:before,
|
||||
.outer:hover .inner:after {
|
||||
transform: rotate(0);
|
||||
}
|
||||
.outer:hover .inner:before,
|
||||
.outer:hover .inner:after {
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
.outer:hover .inner:before {
|
||||
top: 0;
|
||||
}
|
||||
.outer:hover .inner:before {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.outer:hover .inner:after {
|
||||
bottom: 0;
|
||||
}
|
||||
.advanced { display: none !important}
|
||||
.outer:hover .inner:after {
|
||||
bottom: 0;
|
||||
}
|
||||
.advanced { display: none !important}
|
||||
|
||||
.fullcolumn {
|
||||
float:left;
|
||||
display: inline-block;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
/* Add shadows to create the "card" effect */
|
||||
box-shadow: 0 4px 8px 0 rgba(0,0,0,.1);
|
||||
}
|
||||
.fullcolumn {
|
||||
float:left;
|
||||
display: inline-block;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
/* Add shadows to create the "card" effect */
|
||||
box-shadow: 0 4px 8px 0 rgba(0,0,0,.1);
|
||||
}
|
||||
|
||||
/* Create four equal columns that floats next to each other */
|
||||
.column {
|
||||
float:left;
|
||||
display: inline-block;
|
||||
margin: 1.8%;
|
||||
min-width: 300px;
|
||||
width: 20%;
|
||||
padding: 28px;
|
||||
height: 220px; /* Should be removed. Only for demonstration */
|
||||
|
||||
text-align: center;
|
||||
|
||||
/* Add shadows to create the "card" effect */
|
||||
box-shadow: 0 4px 8px 0 rgba(0,0,0,.1);
|
||||
}
|
||||
/* On mouse-over, add a deeper shadow */
|
||||
.column:hover {
|
||||
box-shadow: 0 8px 16px 0 rgba(0,0,0,.3);
|
||||
}
|
||||
.column > h2 {color:black;}
|
||||
|
||||
|
||||
@media only screen and (max-height: 480px) {
|
||||
/* Create four equal columns that floats next to each other */
|
||||
.column {
|
||||
min-width:170px;
|
||||
height: 180px;
|
||||
}
|
||||
}
|
||||
float:left;
|
||||
display: inline-block;
|
||||
margin: 1.8%;
|
||||
min-width: 300px;
|
||||
width: 20%;
|
||||
padding: 28px;
|
||||
height: 220px; /* Should be removed. Only for demonstration */
|
||||
|
||||
.columnfade {
|
||||
animation:fading 0.2s}@keyframes fading{0%{opacity:0}100%{opacity:1}}
|
||||
text-align: center;
|
||||
|
||||
/* Add shadows to create the "card" effect */
|
||||
box-shadow: 0 4px 8px 0 rgba(0,0,0,.1);
|
||||
}
|
||||
/* On mouse-over, add a deeper shadow */
|
||||
.column:hover {
|
||||
box-shadow: 0 8px 16px 0 rgba(0,0,0,.3);
|
||||
}
|
||||
.column > h2 {color:black;}
|
||||
|
||||
|
||||
@media only screen and (max-height: 480px) {
|
||||
.column {
|
||||
min-width:170px;
|
||||
height: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
.columnfade {
|
||||
animation:fading 0.2s}@keyframes fading{0%{opacity:0}100%{opacity:1}}
|
||||
}
|
||||
|
||||
img {
|
||||
@ -277,29 +312,29 @@ button {
|
||||
z-index:10;
|
||||
}
|
||||
.float2{
|
||||
position:fixed;
|
||||
width:60px;
|
||||
height:60px;
|
||||
bottom:80px;
|
||||
right:132px;
|
||||
background-color:#15B;
|
||||
color:#FFF;
|
||||
border-radius:50px;
|
||||
text-align:center;
|
||||
box-shadow: 2px 2px 3px #999;
|
||||
position:fixed;
|
||||
width:60px;
|
||||
height:60px;
|
||||
bottom:80px;
|
||||
right:132px;
|
||||
background-color:#15B;
|
||||
color:#FFF;
|
||||
border-radius:50px;
|
||||
text-align:center;
|
||||
box-shadow: 2px 2px 3px #999;
|
||||
z-index:10;
|
||||
}
|
||||
.float3{
|
||||
position:fixed;
|
||||
width:60px;
|
||||
height:60px;
|
||||
bottom:80px;
|
||||
right:52px;
|
||||
background-color:#0C2;
|
||||
color:#FFF;
|
||||
border-radius:50px;
|
||||
text-align:center;
|
||||
box-shadow: 2px 2px 3px #999;
|
||||
position:fixed;
|
||||
width:60px;
|
||||
height:60px;
|
||||
bottom:80px;
|
||||
right:52px;
|
||||
background-color:#0C2;
|
||||
color:#FFF;
|
||||
border-radius:50px;
|
||||
text-align:center;
|
||||
box-shadow: 2px 2px 3px #999;
|
||||
z-index:10;
|
||||
}
|
||||
|
||||
@ -345,20 +380,20 @@ video {
|
||||
animation: outlightbox 0.8s forwards;
|
||||
}
|
||||
|
||||
@keyframes inlightbox
|
||||
{
|
||||
50% {
|
||||
width: 100%;
|
||||
left: 0;
|
||||
height: 220px;
|
||||
@keyframes inlightbox
|
||||
{
|
||||
50% {
|
||||
width: 100%;
|
||||
left: 0;
|
||||
height: 220px;
|
||||
}
|
||||
100% {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
100% {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
|
||||
@ -367,36 +402,48 @@ video {
|
||||
<script language="javascript" type="text/javascript" src="./webrtc.js"></script>
|
||||
|
||||
<div id="header">
|
||||
<h2>
|
||||
<a href="./" style="text-decoration:none;color:white;;">OBS.Ninja</a>  
|
||||
<div id="head1" style="display:inline-block">
|
||||
<input id="StreamID" name="StreamID" size=30 placeholder="Join by Room Name here"></input>
|
||||
<button style="padding:1px;" onclick="play(document.getElementById('StreamID').value)">GO</button>
|
||||
</div>
|
||||
<div id="head3" style="display:inline-block" class='advanced'>
|
||||
<font style="font-size:60%">   Copy this URL into an OBS "Browser Source" =>   </font><a id="reshare" data-share="" onclick="var range=document.createRange(); range.selectNodeContents(document.getElementById('reshare')); var selec = window.getSelection(); selec.removeAllRanges();selec.addRange(range);document.execCommand('copy');" onmouseover="this.style.cursor='pointer'"></a>
|
||||
</div>
|
||||
<h2>
|
||||
<a href="./" style="text-decoration:none;color:white;"><font id="qos">O</font>BS.Ninja</a>  
|
||||
<div id="head1" style="display:inline-block;;">
|
||||
<input id="StreamID" name="StreamID" size=26 placeholder="Join by Room Name here"></input>
|
||||
<button style="padding:1px;" onclick="play(document.getElementById('StreamID').value)">GO</button>
|
||||
</div>
|
||||
<div id="head3" style="display:inline-block" class='advanced'>
|
||||
<font style="font-size:60%">   Copy this URL into an OBS "Browser Source" =>   </font><a id="reshare" data-share="" onclick="var range=document.createRange(); range.selectNodeContents(document.getElementById('reshare')); var selec = window.getSelection(); selec.removeAllRanges();selec.addRange(range);document.execCommand('copy');" onmouseover="this.style.cursor='pointer'"></a>
|
||||
</div>
|
||||
|
||||
<div id="head2" class="advanced" style="display:inline-block;text-decoration:none;font-size:60%;color:white;">
|
||||
You are joining room: <div id="roomid" style="display:inline-block"></div>
|
||||
</div>
|
||||
<div id="head4" style="display:inline-block" class='advanced'>
|
||||
<font style="font-size:60%">   You are in a director's view   </font>
|
||||
</div>
|
||||
|
||||
</h2>
|
||||
<hr />
|
||||
|
||||
<div id="head2" class="advanced" style="display:inline-block;text-decoration:none;font-size:60%;color:white;">
|
||||
You are joining room: <div id="roomid" style="display:inline-block"></div>
|
||||
</div>
|
||||
|
||||
</h2>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div id="mutebutton" onclick="toggleMute()" class='advanced float3' style="cursor:pointer" alt="Toggle the mic">
|
||||
<i style="font-size:48px;color:white" id="mutetoggle" class="fa fa-microphone my-float"></i>
|
||||
</div>
|
||||
<div id="helpbutton" class='advanced float2' style="cursor:pointer" alt="How to Use This with OBS">
|
||||
<div id="helpbutton" onclick="alert('Email steve@seguin.email if the system breaks or check https://reddit.com/r/obsninja for support.\n\nThere are some advanced options hidden away, such as persistent streamIDs and custom resolutions.\n\nMacOS users should be using OBS v23 due to a bug in v24 and v25')" class='advanced float2' style="cursor:pointer" alt="How to Use This with OBS">
|
||||
<i style="font-size:48px;color:white;" class="fa fa-question-circle my-float"></i>
|
||||
</div>
|
||||
|
||||
<div id="mainmenu" class="row">
|
||||
<div id="container-1" class="column columnfade" onclick="alert('coming soon');" style="background-color:#ddd;">
|
||||
<div id="mainmenu" class="row" style="align:center;">
|
||||
<div id="container-1" class="column columnfade" style="background-color:#ddd;">
|
||||
<h2>Add Group Video Chat to OBS</h2>
|
||||
(Under Re-Development: coming soon)
|
||||
<div class="container-inner">
|
||||
<br /><br />
|
||||
<p><input id="videoname1" placeholder="Enter a ROOM NAME here" size=35 maxlength=50 style="padding:5px;" /></br ><br /></p>
|
||||
<li>Anyone can enter a room if they know the name, so keep it unique</li>
|
||||
<li>Having more than four (4) people in a room is not advisable due to performance reasons</li>
|
||||
<br />
|
||||
With a room name entered, enter the room as a director. Links to invite guests will be provided.
|
||||
<br />
|
||||
<button onclick="createRoom()" class="gowebcam">Enter Room</button><br />
|
||||
</div>
|
||||
<div class="outer close">
|
||||
<div class="inner">
|
||||
@ -411,7 +458,7 @@ video {
|
||||
<div class="container-inner"><br />
|
||||
<p>Select the audio/video source below and when you're ready just click START SHARING WEBCAM</p><br />
|
||||
<button onclick="publishWebcam()" class="gowebcam">CLICK HERE WHEN READY</button><br />
|
||||
<p><input id="videoname3" placeholder="Give this video source a name (optional)" size=35 maxlength=50 style="padding:5px;" /></br ><br /></p>
|
||||
<p><input id="videoname3" placeholder="Give this video source a name (optional)" size=35 maxlength=50 style="padding:5px;" /></p><br />
|
||||
<p><video id="previewWebcam" muted controls autoplay playsinline style="max-width:640px; max-width:83vw; max-height:35vh"></video></p>
|
||||
<br />
|
||||
<p>Video source: <select id="videoSource"></select></p><br/>
|
||||
@ -455,9 +502,9 @@ video {
|
||||
<li>Code is open-sourced: <a href="https://github.com/steveseguin/obsninja">https://github.com/steveseguin/obsninja</a></li>
|
||||
<li>You can also check out <a href="https://stageten.tv">StageTEN.tv</a> for a more feature-rich paid-solution</li>
|
||||
<br />
|
||||
<i>Known issues:</i><br />
|
||||
<i>Known issues:</i><br />
|
||||
|
||||
<li>** MacOS users need to use OBS v23. v24/v25 have a bug in it</li>
|
||||
<li>** MacOS users need to use OBS v23. v24/v25 have a bug in it</li>
|
||||
|
||||
<br /><br />
|
||||
<i><h3>Send feature requests and support to steve@seguin.email, or check out the <a href="https://www.reddit.com/r/OBSNinja/">sub-reddit</a></i></h3>
|
||||
@ -468,14 +515,13 @@ video {
|
||||
<form method="post" onsubmit="setFormSubmitting()" style="display:none;">
|
||||
<input type="submit" />
|
||||
</form>
|
||||
|
||||
<script>
|
||||
|
||||
/////////////
|
||||
var VIS = vis;
|
||||
var formSubmitting = true;
|
||||
var setFormSubmitting = function() { formSubmitting = true; };
|
||||
window.onload = function() {
|
||||
window.onload = function() { // This just keeps people from killing the live stream accidentally. Also give me a headsup that the stream is ending
|
||||
window.addEventListener("beforeunload", function (e) {
|
||||
if (formSubmitting) {
|
||||
return undefined;
|
||||
@ -489,19 +535,41 @@ window.onload = function() {
|
||||
|
||||
var lastTouchEnd = 0;
|
||||
document.addEventListener('touchend', function (event) {
|
||||
var now = (new Date()).getTime();
|
||||
if (now - lastTouchEnd <= 300) {
|
||||
event.preventDefault();
|
||||
}
|
||||
lastTouchEnd = now;
|
||||
var now = (new Date()).getTime();
|
||||
if (now - lastTouchEnd <= 300) {
|
||||
event.preventDefault();
|
||||
}
|
||||
lastTouchEnd = now;
|
||||
}, false);
|
||||
|
||||
/////////////
|
||||
function updateURL(param) {
|
||||
if (history.pushState) {
|
||||
var newurl = window.location.protocol + "//" + window.location.host + window.location.pathname + '?' +param;
|
||||
window.history.pushState({path:newurl},'',newurl);
|
||||
}
|
||||
}
|
||||
var session = Ooblex.Media;
|
||||
session.streamID = session.generateStreamID();
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.has('permaid')){
|
||||
var permaid = urlParams.get('permaid');
|
||||
session.changeStreamID(permaid);
|
||||
}
|
||||
var micvolume = 100;
|
||||
session.connect();
|
||||
|
||||
session.volume = micvolume;
|
||||
|
||||
function checkConnection(){
|
||||
if (session.ws.readyState === WebSocket.OPEN) {
|
||||
document.getElementById("qos").style.color = "white";
|
||||
} else {
|
||||
document.getElementById("qos").style.color = "red";
|
||||
}
|
||||
}
|
||||
setInterval(function(){checkConnection();},5000);
|
||||
|
||||
function toggleMute(){
|
||||
var msg = {};
|
||||
if (micvolume==0){
|
||||
@ -531,15 +599,33 @@ function publishScreen(){
|
||||
var title = document.getElementById("videoname2").value;
|
||||
|
||||
formSubmitting = false;
|
||||
session.publishScreen(title);
|
||||
|
||||
var width = {ideal: 1280};
|
||||
var height = {ideal: 720};
|
||||
|
||||
if (urlParams.has('width')){
|
||||
width = urlParams.get('width');
|
||||
width = {exact: width};
|
||||
}
|
||||
if (urlParams.has('height')){
|
||||
height = urlParams.get('height');
|
||||
height = {exact: height};
|
||||
}
|
||||
|
||||
var constraints = window.constraints = {
|
||||
audio: {echoCancellation: false, autoGainControl: false, noiseSuppression:false }, // I hope this doesn't break things..
|
||||
video: {width: width, height: height, cursor: "never", mediaSource: "browser"}
|
||||
};
|
||||
|
||||
session.publishScreen(constraints, title);
|
||||
console.log("streamID is: "+session.streamID);
|
||||
|
||||
|
||||
document.getElementById("mutebutton").className="float3";
|
||||
document.getElementById("helpbutton").className="float2";
|
||||
|
||||
document.getElementById("head1").className = 'advanced';
|
||||
document.getElementById("head2").className = 'advanced';
|
||||
document.getElementById("head3").className = '';
|
||||
document.getElementById("head2").className = 'advanced';
|
||||
document.getElementById("head3").className = '';
|
||||
|
||||
}
|
||||
function publishWebcam(){
|
||||
@ -555,35 +641,45 @@ function publishWebcam(){
|
||||
|
||||
var iOS = !!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform);
|
||||
|
||||
//try{
|
||||
// updateURL("permaid="+session.streamID); // OKAY , I disabled this, as i just found it way too annoying and confusing for the user.
|
||||
//} catch (e){
|
||||
// console.error("perma id url update failed");
|
||||
//}
|
||||
|
||||
if (iOS){
|
||||
var width1 = 640;
|
||||
var height1 = 360;
|
||||
var width = {min: 640};
|
||||
var height = {min: 360};
|
||||
|
||||
if (urlParams.has('width')){
|
||||
var width1 = urlParams.get('width');
|
||||
width = urlParams.get('width');
|
||||
width = {exact: width};
|
||||
}
|
||||
if (urlParams.has('height')){
|
||||
var height1 = urlParams.get('height');
|
||||
height = urlParams.get('height');
|
||||
height = {exact: height};
|
||||
}
|
||||
var constraints = {
|
||||
audio: {
|
||||
deviceId: {exact: audioSelect.value}
|
||||
},
|
||||
video: {
|
||||
width: {min: width1},
|
||||
height: {min: height1},
|
||||
height: height,
|
||||
width: width,
|
||||
deviceId: {exact: videoSelect.value}
|
||||
}
|
||||
};
|
||||
|
||||
} else {
|
||||
var width1 = 360;
|
||||
var height1 = 360;
|
||||
var width = {min: 360, max: 1920};
|
||||
var height = {min: 360, max: 1920};
|
||||
|
||||
if (urlParams.has('width')){
|
||||
var width1 = urlParams.get('width');
|
||||
width = urlParams.get('width');
|
||||
width = {exact: width};
|
||||
}
|
||||
if (urlParams.has('height')){
|
||||
var height1 = urlParams.get('height');
|
||||
height = urlParams.get('height');
|
||||
height = {exact: height};
|
||||
}
|
||||
|
||||
var constraints = {
|
||||
@ -591,8 +687,8 @@ function publishWebcam(){
|
||||
deviceId: {exact: audioSelect.value}
|
||||
},
|
||||
video: {
|
||||
height: {min: height1, max:2160},
|
||||
width: {min: width1, max:3840},
|
||||
height: height,
|
||||
width: width,
|
||||
deviceId: {exact: videoSelect.value}
|
||||
}
|
||||
};
|
||||
@ -610,39 +706,85 @@ function publishWebcam(){
|
||||
|
||||
}
|
||||
|
||||
function joinRoom(roomname){
|
||||
console.log("Join room",roomname);
|
||||
session.joinRoom(roomname).then(function(response){
|
||||
console.log("Members in Room",response);
|
||||
},function(error){return {}});
|
||||
}
|
||||
|
||||
|
||||
function createRoom(){
|
||||
|
||||
var roomname = document.getElementById("videoname1").value;
|
||||
console.log(roomname);
|
||||
if (roomname.length==0){
|
||||
alert("Please enter a room name before continuing");
|
||||
return;
|
||||
}
|
||||
|
||||
var gridlayout = document.getElementById("gridlayout");
|
||||
gridlayout.className = "gridlayout";
|
||||
|
||||
// var sheet = document.createElement('style');
|
||||
// sheet.innerHTML = ".tile{object-fit:contain }";
|
||||
// document.body.appendChild(sheet);
|
||||
|
||||
var roomname = document.getElementById("videoname1").value;
|
||||
console.log(roomname);
|
||||
formSubmitting = false;
|
||||
|
||||
var m = document.getElementById("mainmenu");
|
||||
m.remove();
|
||||
|
||||
document.getElementById("head1").className = 'advanced';
|
||||
document.getElementById("head2").className = 'advanced';
|
||||
document.getElementById("head3").className = 'advanced';
|
||||
document.getElementById("head4").className = '';
|
||||
|
||||
//document.getElementById("reshare").innerHTML = "https://obs.ninja/?room="+roomname;
|
||||
//document.getElementById("reshare").setAttribute("data-share","?room="+roomname);
|
||||
|
||||
//document.getElementById("mutebutton").className="float3";
|
||||
//document.getElementById("helpbutton").className="float2";
|
||||
joinRoom(roomname);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function gotDevices(deviceInfos) { // https://github.com/webrtc/samples/blob/gh-pages/src/content/devices/input-output/js/main.js#L19
|
||||
const audioInputSelect = document.querySelector('select#audioSource');
|
||||
const videoSelect = document.querySelector('select#videoSource');
|
||||
const selectors = [audioInputSelect, videoSelect];
|
||||
// TODO: Add in the option to select the OUTPUT and Disable Mic/Cam
|
||||
const audioInputSelect = document.querySelector('select#audioSource');
|
||||
const videoSelect = document.querySelector('select#videoSource');
|
||||
const selectors = [audioInputSelect, videoSelect];
|
||||
// TODO: Add in the option to select the OUTPUT and Disable Mic/Cam
|
||||
|
||||
// Handles being called several times to update labels. Preserve values.
|
||||
const values = selectors.map(select => select.value);
|
||||
selectors.forEach(select => {
|
||||
while (select.firstChild) {
|
||||
select.removeChild(select.firstChild);
|
||||
}
|
||||
});
|
||||
for (let i = 0; i !== deviceInfos.length; ++i) {
|
||||
const deviceInfo = deviceInfos[i];
|
||||
const option = document.createElement('option');
|
||||
option.value = deviceInfo.deviceId;
|
||||
if (deviceInfo.kind === 'audioinput') {
|
||||
option.text = deviceInfo.label || `microphone ${audioInputSelect.length + 1}`;
|
||||
audioInputSelect.appendChild(option);
|
||||
} else if (deviceInfo.kind === 'videoinput') {
|
||||
option.text = deviceInfo.label || `camera ${videoSelect.length + 1}`;
|
||||
videoSelect.appendChild(option);
|
||||
} else {
|
||||
console.log('Some other kind of source/device: ', deviceInfo);
|
||||
}
|
||||
}
|
||||
selectors.forEach((select, selectorIndex) => {
|
||||
if (Array.prototype.slice.call(select.childNodes).some(n => n.value === values[selectorIndex])) {
|
||||
select.value = values[selectorIndex];
|
||||
}
|
||||
});
|
||||
// Handles being called several times to update labels. Preserve values.
|
||||
const values = selectors.map(select => select.value);
|
||||
selectors.forEach(select => {
|
||||
while (select.firstChild) {
|
||||
select.removeChild(select.firstChild);
|
||||
}
|
||||
});
|
||||
for (let i = 0; i !== deviceInfos.length; ++i) {
|
||||
const deviceInfo = deviceInfos[i];
|
||||
const option = document.createElement('option');
|
||||
option.value = deviceInfo.deviceId;
|
||||
if (deviceInfo.kind === 'audioinput') {
|
||||
option.text = deviceInfo.label || `microphone ${audioInputSelect.length + 1}`;
|
||||
audioInputSelect.appendChild(option);
|
||||
} else if (deviceInfo.kind === 'videoinput') {
|
||||
option.text = deviceInfo.label || `camera ${videoSelect.length + 1}`;
|
||||
videoSelect.appendChild(option);
|
||||
} else {
|
||||
console.log('Some other kind of source/device: ', deviceInfo);
|
||||
}
|
||||
}
|
||||
selectors.forEach((select, selectorIndex) => {
|
||||
if (Array.prototype.slice.call(select.childNodes).some(n => n.value === values[selectorIndex])) {
|
||||
select.value = values[selectorIndex];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleError(error) {
|
||||
@ -671,34 +813,40 @@ function previewWebcam(){
|
||||
|
||||
|
||||
if (iOS){
|
||||
var width = {min: 640};
|
||||
var height = {min: 360};
|
||||
|
||||
var width = 640;
|
||||
var height = 360;
|
||||
if (urlParams.has('width')){
|
||||
var width = urlParams.get('width');
|
||||
width = urlParams.get('width');
|
||||
width = {exact: width};
|
||||
}
|
||||
if (urlParams.has('height')){
|
||||
var height = urlParams.get('height');
|
||||
height = urlParams.get('height');
|
||||
height = {exact: height};
|
||||
}
|
||||
|
||||
var constraints = {
|
||||
audio: {
|
||||
deviceId: {exact: audioSelect.value}
|
||||
},
|
||||
video: {
|
||||
width: {ideal: width},
|
||||
height: {ideal: height},
|
||||
height: height,
|
||||
width: width,
|
||||
deviceId: {exact: videoSelect.value}
|
||||
}
|
||||
};
|
||||
|
||||
} else {
|
||||
var width1 = 360;
|
||||
var height1 = 360;
|
||||
var width = {min: 360, max: 1920};
|
||||
var height = {min: 360, max: 1920};
|
||||
|
||||
if (urlParams.has('width')){
|
||||
var width1 = urlParams.get('width');
|
||||
width = urlParams.get('width');
|
||||
width = {exact: width};
|
||||
}
|
||||
if (urlParams.has('height')){
|
||||
var height1 = urlParams.get('height');
|
||||
height = urlParams.get('height');
|
||||
height = {exact: height};
|
||||
}
|
||||
|
||||
var constraints = {
|
||||
@ -706,11 +854,12 @@ function previewWebcam(){
|
||||
deviceId: {exact: audioSelect.value}
|
||||
},
|
||||
video: {
|
||||
height: {min: height1, max:2160},
|
||||
width: {min: width1, max:3840},
|
||||
height: height,
|
||||
width: width,
|
||||
deviceId: {exact: videoSelect.value}
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
navigator.mediaDevices.getUserMedia(constraints).then(function(stream){
|
||||
@ -760,45 +909,11 @@ function browse(){
|
||||
session.listStreams().then(function(response){
|
||||
document.getElementById("browserlist").innerHTML='No Active Broadcasts';
|
||||
response.forEach(streamID => {
|
||||
document.getElementById("browserlist").innerHTML="<a href='../?streamid="+streamID[1]+"'>"+streamID[2]+"</a> - "+streamID[0]+" seeders<br />";
|
||||
document.getElementById("browserlist").innerHTML="<a href='./?streamid="+streamID[1]+"'>"+streamID[2]+"</a> - "+streamID[0]+" seeders<br />";
|
||||
});
|
||||
},function(error){return {}});
|
||||
}
|
||||
|
||||
function browsegraph(){
|
||||
console.log("graph streams");
|
||||
session.graphStreams().then(function(response){
|
||||
// create an array with nodes
|
||||
var nodes = [];
|
||||
var edges = [];
|
||||
for (var key in response){
|
||||
if (response[key]==null){
|
||||
edges.push({from:key, to:response[key]});
|
||||
nodes.push({id:key,label:"PUBLISHER"});
|
||||
continue;
|
||||
} else if (key.length<=16){
|
||||
edges.push({from:key, to:response[key]});
|
||||
nodes.push({id:key,label:"SUPER SEEDER"});
|
||||
continue;
|
||||
}
|
||||
nodes.push({id:key,label:"VIEWER"});
|
||||
edges.push({from:key, to:response[key]});
|
||||
};
|
||||
// create a network
|
||||
var container = document.getElementById('browsergraph');
|
||||
|
||||
// provide the data in the vis format
|
||||
var data = {
|
||||
nodes: nodes,
|
||||
edges: edges
|
||||
};
|
||||
var options = {};
|
||||
|
||||
// initialize your network!
|
||||
var network = new VIS.Network(container, data, options);
|
||||
|
||||
},function(error){return {}});
|
||||
}
|
||||
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.has('streamid')){
|
||||
@ -813,8 +928,30 @@ if (urlParams.has('streamid')){
|
||||
document.getElementById("head2").className = 'advanced';
|
||||
document.getElementById("head3").className = 'advanced';
|
||||
document.getElementById("roomid").innerHTML = urlParams.get('streamid');
|
||||
document.getElementById("mainmenu").style.backgroundImage = "url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgo8c3ZnIHdpZHRoPSI0MHB4IiBoZWlnaHQ9IjQwcHgiIHZpZXdCb3g9IjAgMCA0MCA0MCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWw6c3BhY2U9InByZXNlcnZlIiBzdHlsZT0iZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjE7IiB4PSIwcHgiIHk9IjBweCI+CiAgICA8ZGVmcz4KICAgICAgICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPjwhW0NEQVRBWwogICAgICAgICAgICBALXdlYmtpdC1rZXlmcmFtZXMgc3BpbiB7CiAgICAgICAgICAgICAgZnJvbSB7CiAgICAgICAgICAgICAgICAtd2Via2l0LXRyYW5zZm9ybTogcm90YXRlKDBkZWcpCiAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIHRvIHsKICAgICAgICAgICAgICAgIC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoLTM1OWRlZykKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgICAgQGtleWZyYW1lcyBzcGluIHsKICAgICAgICAgICAgICBmcm9tIHsKICAgICAgICAgICAgICAgIHRyYW5zZm9ybTogcm90YXRlKDBkZWcpCiAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIHRvIHsKICAgICAgICAgICAgICAgIHRyYW5zZm9ybTogcm90YXRlKC0zNTlkZWcpCiAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICAgIHN2ZyB7CiAgICAgICAgICAgICAgICAtd2Via2l0LXRyYW5zZm9ybS1vcmlnaW46IDUwJSA1MCU7CiAgICAgICAgICAgICAgICAtd2Via2l0LWFuaW1hdGlvbjogc3BpbiAxLjVzIGxpbmVhciBpbmZpbml0ZTsKICAgICAgICAgICAgICAgIC13ZWJraXQtYmFja2ZhY2UtdmlzaWJpbGl0eTogaGlkZGVuOwogICAgICAgICAgICAgICAgYW5pbWF0aW9uOiBzcGluIDEuNXMgbGluZWFyIGluZmluaXRlOwogICAgICAgICAgICB9CiAgICAgICAgXV0+PC9zdHlsZT4KICAgIDwvZGVmcz4KICAgIDxnIGlkPSJvdXRlciI+CiAgICAgICAgPGc+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0yMCwwQzIyLjIwNTgsMCAyMy45OTM5LDEuNzg4MTMgMjMuOTkzOSwzLjk5MzlDMjMuOTkzOSw2LjE5OTY4IDIyLjIwNTgsNy45ODc4MSAyMCw3Ljk4NzgxQzE3Ljc5NDIsNy45ODc4MSAxNi4wMDYxLDYuMTk5NjggMTYuMDA2MSwzLjk5MzlDMTYuMDA2MSwxLjc4ODEzIDE3Ljc5NDIsMCAyMCwwWiIgc3R5bGU9ImZpbGw6YmxhY2s7Ii8+CiAgICAgICAgPC9nPgogICAgICAgIDxnPgogICAgICAgICAgICA8cGF0aCBkPSJNNS44NTc4Niw1Ljg1Nzg2QzcuNDE3NTgsNC4yOTgxNSA5Ljk0NjM4LDQuMjk4MTUgMTEuNTA2MSw1Ljg1Nzg2QzEzLjA2NTgsNy40MTc1OCAxMy4wNjU4LDkuOTQ2MzggMTEuNTA2MSwxMS41MDYxQzkuOTQ2MzgsMTMuMDY1OCA3LjQxNzU4LDEzLjA2NTggNS44NTc4NiwxMS41MDYxQzQuMjk4MTUsOS45NDYzOCA0LjI5ODE1LDcuNDE3NTggNS44NTc4Niw1Ljg1Nzg2WiIgc3R5bGU9ImZpbGw6cmdiKDIxMCwyMTAsMjEwKTsiLz4KICAgICAgICA8L2c+CiAgICAgICAgPGc+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0yMCwzMi4wMTIyQzIyLjIwNTgsMzIuMDEyMiAyMy45OTM5LDMzLjgwMDMgMjMuOTkzOSwzNi4wMDYxQzIzLjk5MzksMzguMjExOSAyMi4yMDU4LDQwIDIwLDQwQzE3Ljc5NDIsNDAgMTYuMDA2MSwzOC4yMTE5IDE2LjAwNjEsMzYuMDA2MUMxNi4wMDYxLDMzLjgwMDMgMTcuNzk0MiwzMi4wMTIyIDIwLDMyLjAxMjJaIiBzdHlsZT0iZmlsbDpyZ2IoMTMwLDEzMCwxMzApOyIvPgogICAgICAgIDwvZz4KICAgICAgICA8Zz4KICAgICAgICAgICAgPHBhdGggZD0iTTI4LjQ5MzksMjguNDkzOUMzMC4wNTM2LDI2LjkzNDIgMzIuNTgyNCwyNi45MzQyIDM0LjE0MjEsMjguNDkzOUMzNS43MDE5LDMwLjA1MzYgMzUuNzAxOSwzMi41ODI0IDM0LjE0MjEsMzQuMTQyMUMzMi41ODI0LDM1LjcwMTkgMzAuMDUzNiwzNS43MDE5IDI4LjQ5MzksMzQuMTQyMUMyNi45MzQyLDMyLjU4MjQgMjYuOTM0MiwzMC4wNTM2IDI4LjQ5MzksMjguNDkzOVoiIHN0eWxlPSJmaWxsOnJnYigxMDEsMTAxLDEwMSk7Ii8+CiAgICAgICAgPC9nPgogICAgICAgIDxnPgogICAgICAgICAgICA8cGF0aCBkPSJNMy45OTM5LDE2LjAwNjFDNi4xOTk2OCwxNi4wMDYxIDcuOTg3ODEsMTcuNzk0MiA3Ljk4NzgxLDIwQzcuOTg3ODEsMjIuMjA1OCA2LjE5OTY4LDIzLjk5MzkgMy45OTM5LDIzLjk5MzlDMS43ODgxMywyMy45OTM5IDAsMjIuMjA1OCAwLDIwQzAsMTcuNzk0MiAxLjc4ODEzLDE2LjAwNjEgMy45OTM5LDE2LjAwNjFaIiBzdHlsZT0iZmlsbDpyZ2IoMTg3LDE4NywxODcpOyIvPgogICAgICAgIDwvZz4KICAgICAgICA8Zz4KICAgICAgICAgICAgPHBhdGggZD0iTTUuODU3ODYsMjguNDkzOUM3LjQxNzU4LDI2LjkzNDIgOS45NDYzOCwyNi45MzQyIDExLjUwNjEsMjguNDkzOUMxMy4wNjU4LDMwLjA1MzYgMTMuMDY1OCwzMi41ODI0IDExLjUwNjEsMzQuMTQyMUM5Ljk0NjM4LDM1LjcwMTkgNy40MTc1OCwzNS43MDE5IDUuODU3ODYsMzQuMTQyMUM0LjI5ODE1LDMyLjU4MjQgNC4yOTgxNSwzMC4wNTM2IDUuODU3ODYsMjguNDkzOVoiIHN0eWxlPSJmaWxsOnJnYigxNjQsMTY0LDE2NCk7Ii8+CiAgICAgICAgPC9nPgogICAgICAgIDxnPgogICAgICAgICAgICA8cGF0aCBkPSJNMzYuMDA2MSwxNi4wMDYxQzM4LjIxMTksMTYuMDA2MSA0MCwxNy43OTQyIDQwLDIwQzQwLDIyLjIwNTggMzguMjExOSwyMy45OTM5IDM2LjAwNjEsMjMuOTkzOUMzMy44MDAzLDIzLjk5MzkgMzIuMDEyMiwyMi4yMDU4IDMyLjAxMjIsMjBDMzIuMDEyMiwxNy43OTQyIDMzLjgwMDMsMTYuMDA2MSAzNi4wMDYxLDE2LjAwNjFaIiBzdHlsZT0iZmlsbDpyZ2IoNzQsNzQsNzQpOyIvPgogICAgICAgIDwvZz4KICAgICAgICA8Zz4KICAgICAgICAgICAgPHBhdGggZD0iTTI4LjQ5MzksNS44NTc4NkMzMC4wNTM2LDQuMjk4MTUgMzIuNTgyNCw0LjI5ODE1IDM0LjE0MjEsNS44NTc4NkMzNS43MDE5LDcuNDE3NTggMzUuNzAxOSw5Ljk0NjM4IDM0LjE0MjEsMTEuNTA2MUMzMi41ODI0LDEzLjA2NTggMzAuMDUzNiwxMy4wNjU4IDI4LjQ5MzksMTEuNTA2MUMyNi45MzQyLDkuOTQ2MzggMjYuOTM0Miw3LjQxNzU4IDI4LjQ5MzksNS44NTc4NloiIHN0eWxlPSJmaWxsOnJnYig1MCw1MCw1MCk7Ii8+CiAgICAgICAgPC9nPgogICAgPC9nPgo8L3N2Zz4K')";
|
||||
document.getElementById("mainmenu").style.backgroundRepeat = "no-repeat";
|
||||
document.getElementById("mainmenu").style.backgroundPosition = "bottom center";
|
||||
document.getElementById("mainmenu").style.minHeight = "300px";
|
||||
document.getElementById("mainmenu").style.backgroundSize = "100px 100px";
|
||||
document.getElementById("mainmenu").innerHTML = '<font style="color:#666"><h1>Attempting to load video stream.</h1></font>';
|
||||
|
||||
setTimeout(function(){
|
||||
|
||||
document.getElementById("mainmenu").innerHTML += '<font style="color:#EEE">If the stream does not load within a few seconds, the stream may not be available or some other error has occured. If the issue persists, please check out the <a href="https://reddit.com/r/obsninja">https://reddit.com/r/obsninja</a> for possible solutions or contact <a href="mailto:steve@seguin.email" target="_top">steve@seguin.email</a>.</font><br/><button onclick="location.reload();">Retry Connecting</button><br/>';
|
||||
|
||||
if (urlParams.get("streamid")){
|
||||
document.getElementById("mainmenu").innerHTML += '<div id="qrcode" style="background-color:white;display:inline-block;color:black;max-width:300px;padding:20px;"><h2 style="color:black">Stream Invite URL:</h2><p><a href="https://' + location.hostname+ location.pathname + '?permaid=' + session.streamID + '">https://' + location.hostname + location.pathname + '?permaid=' + urlParams.get("streamid") + '</a></p><br /></div>';
|
||||
var qrcode = new QRCode(document.getElementById("qrcode"), {
|
||||
width : 300,
|
||||
height : 300,
|
||||
colorDark : "#000000",
|
||||
colorLight : "#FFFFFF",
|
||||
useSVG: false
|
||||
});
|
||||
qrcode.makeCode('https://' + location.hostname + location.pathname + '?permaid=' + urlParams.get("streamid"));
|
||||
|
||||
}
|
||||
},2000);
|
||||
|
||||
console.log("auto playing");
|
||||
|
||||
@ -822,10 +959,8 @@ if (urlParams.has('streamid')){
|
||||
alert("Safari requires us to ask for an audio permission to use peer-to-peer technology. You will need to accept it in a moment if asked to view this live video");
|
||||
navigator.mediaDevices.getUserMedia({audio: true}).then(function(){
|
||||
play(urlParams.get('streamid'));
|
||||
document.getElementById("mainmenu").style.display="none";
|
||||
}).catch(function(){
|
||||
play(urlParams.get('streamid'));
|
||||
document.getElementById("mainmenu").style.display="none";
|
||||
});
|
||||
} else {
|
||||
play(urlParams.get('streamid'));
|
||||
@ -840,7 +975,7 @@ document.addEventListener("dragstart", e => {
|
||||
url += '&layer-name=OBS.Ninja';
|
||||
if (streamId) url += ': ' + streamId;
|
||||
var video = document.getElementById('videosource');
|
||||
url += '&layer-width=' + video.videoWidth;
|
||||
url += '&layer-width=' + video.videoWidth; // this isn't always 100% correct, as the resolution can fluxuate, but it is probably good enough
|
||||
url += '&layer-height=' + video.videoHeight;
|
||||
e.dataTransfer.setData("text/uri-list", encodeURI(url));
|
||||
});
|
||||
@ -988,6 +1123,9 @@ function poker(){
|
||||
});
|
||||
</script>
|
||||
<div class='credits'>Icons made by <a href="https://www.flaticon.com/authors/lucy-g" title="Lucy G">Lucy G</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a> is licensed by <a href="https://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div>
|
||||
</div>
|
||||
<div style="margin:0;border:0;padding:0;width:100%;height:100%;" id="gridlayout"></div>
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
|
||||
1
qrcode.min.js
vendored
Normal file
1
qrcode.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user