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:
Steve Seguin 2020-03-28 11:33:02 -04:00 committed by GitHub
parent db1a01cf15
commit 52b0128db7
2 changed files with 415 additions and 276 deletions

View File

@ -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> &nbsp
<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%"> &nbsp Copy this URL into an OBS "Browser Source" => &nbsp </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> &nbsp
<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%"> &nbsp Copy this URL into an OBS "Browser Source" => &nbsp </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%"> &nbsp You are in a director's view &nbsp </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

File diff suppressed because one or more lines are too long