mirror of
https://github.com/eliasstepanik/vdo.ninja.git
synced 2026-01-11 21:58:35 +00:00
1613 lines
61 KiB
HTML
1613 lines
61 KiB
HTML
<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="font-awesome.min.css">
|
|
<script type="text/javascript" src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
|
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
|
|
|
|
<script type="text/javascript" src="qrcode.min.js"></script>
|
|
<style type="text/css">
|
|
#mynetwork {
|
|
width: 600px;
|
|
height: 400px;
|
|
border: 1px solid lightgray;
|
|
}
|
|
</style>
|
|
<style>
|
|
* {
|
|
padding: 0;
|
|
margin: 0;
|
|
box-sizing: border-box;
|
|
|
|
}
|
|
.email { unicode-bidi: bidi-override; direction: rtl; user-select: none; }
|
|
.credits {
|
|
color:black;
|
|
position:absolute;
|
|
bottom:0;
|
|
right:0;
|
|
z-index:-1;
|
|
}
|
|
.credits >a {
|
|
color:black;
|
|
}
|
|
.credits >a:visited{
|
|
color:black;
|
|
}
|
|
.gowebcam {
|
|
font-size:110%;
|
|
}
|
|
|
|
.pressed {
|
|
background: #e3e3e3;
|
|
-webkit-box-shadow: inset 0px 0px 5px #a1a1a1;
|
|
-moz-box-shadow: inset 0px 0px 5px #a1a1a1;
|
|
box-shadow: inset 0px 0px 5px #a1a1a1;
|
|
outline: none;
|
|
}
|
|
.row {
|
|
align-content:center;
|
|
text-align: center;
|
|
margin-top:10px;
|
|
|
|
}
|
|
|
|
#videosource {
|
|
max-width:100%;
|
|
max-height:100%;
|
|
}
|
|
/* Clear floats after the columns */
|
|
.row:after {
|
|
content: "";
|
|
display: table;
|
|
clear: both;
|
|
}
|
|
|
|
.vidcon {
|
|
max-width:100%;
|
|
max-height:100%
|
|
}
|
|
.vidcon:nth-of-type(3n) { grid-column: 2; }
|
|
.vidcon:nth-of-type(3n) { grid-row: span ; }
|
|
|
|
.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;
|
|
grid-auto-columns:minmax(50%, auto);
|
|
grid-auto-rows: minmax(50%, auto);
|
|
}
|
|
|
|
.directorsgrid {
|
|
justify-items: normal;
|
|
grid-auto-columns: minmax(100px,500px);
|
|
grid-auto-rows: minmax(100px, 300px);
|
|
display:block ! important;
|
|
|
|
}
|
|
.directorsgrid video {
|
|
max-width: 300px;
|
|
max-height: 300px;
|
|
padding:10px 10px 0px 10px !important;
|
|
}
|
|
.directorsgrid .vidcon {
|
|
display: inline-block !important;
|
|
max-width: 300px !important;
|
|
max-height: 500px !important;
|
|
background: #E3E4EF;
|
|
}
|
|
.directorsgrid .tile {
|
|
width: auto;
|
|
height: auto;
|
|
}
|
|
|
|
html {
|
|
}
|
|
|
|
|
|
body {
|
|
padding: 0 3px;
|
|
height: 100%;
|
|
width: 100%;
|
|
background: #141926;
|
|
font-family: Helvetica, Arial, sans-serif;
|
|
display: flex;
|
|
flex-flow: column;
|
|
}
|
|
|
|
.gowebcam {
|
|
padding:20px;
|
|
background-color:white;
|
|
}
|
|
.infoblob {
|
|
color:white;
|
|
width:100%;
|
|
padding:20px;
|
|
max-width:1280px;
|
|
|
|
}
|
|
|
|
.outer {
|
|
position: relative;
|
|
margin: auto;
|
|
width: 70px;
|
|
margin-top: 0px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
|
|
.close {
|
|
position: absolute;
|
|
right: 20px;
|
|
top: 20px;
|
|
cursor: pointer;
|
|
display: none;
|
|
}
|
|
|
|
@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;
|
|
}
|
|
.outer {
|
|
width:50px;
|
|
}
|
|
.close {
|
|
top:0px;
|
|
right:0px;
|
|
}
|
|
}
|
|
|
|
|
|
@media only screen and (max-width: 480px) {
|
|
.outer {
|
|
width:50px;
|
|
}
|
|
.close{
|
|
top:0;
|
|
right:0;
|
|
}
|
|
select {
|
|
height:30px;
|
|
font-size:120%;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
h2 {
|
|
color: white;
|
|
}
|
|
|
|
|
|
.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;
|
|
}
|
|
|
|
.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:after {
|
|
bottom: 50%;
|
|
transform: rotate(-45deg);
|
|
}
|
|
|
|
.outer:hover label {
|
|
opacity: 1;
|
|
}
|
|
|
|
.outer:hover .inner:before,
|
|
.outer:hover .inner:after {
|
|
transform: rotate(0);
|
|
}
|
|
|
|
.outer:hover .inner:before {
|
|
top: 0;
|
|
}
|
|
|
|
.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);
|
|
}
|
|
|
|
/* 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) {
|
|
.column {
|
|
min-width:170px;
|
|
height: 180px;
|
|
}
|
|
}
|
|
|
|
.columnfade {
|
|
animation:fading 0.2s}@keyframes fading{0%{opacity:0}100%{opacity:1}}
|
|
}
|
|
|
|
img {
|
|
border-radius: 5px 5px 0 0;
|
|
margin:5px;
|
|
}
|
|
|
|
button {
|
|
padding:5px 10px 3px 10px;
|
|
margin:10px 0px;
|
|
}
|
|
/* Empty container that will replace the original container */
|
|
#empty-container {
|
|
display: inline-block;
|
|
float:left;
|
|
width: 20%;
|
|
min-width: 300px;
|
|
padding: 28px;
|
|
height: 220px; /* Should be removed. Only for demonstration */
|
|
margin: 1.8%;
|
|
text-align: center;
|
|
}
|
|
|
|
|
|
#container-1 {
|
|
background-repeat: no-repeat;
|
|
background-size: 80px;
|
|
background-position: 50% 65%;
|
|
background-image: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAxMjkgMTI5IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAxMjkgMTI5IiB3aWR0aD0iNTEycHgiIGhlaWdodD0iNTEycHgiPgogIDxnPgogICAgPGc+CiAgICAgIDxwYXRoIGQ9Im0xMC41LDU4LjloNDQuM2MyLjMsMCA0LjEtMS44IDQuMS00LjF2LTQ0LjNjMC0yLjMtMS44LTQuMS00LjEtNC4xaC00NC4zYy0yLjMsMC00LjEsMS44LTQuMSw0LjF2NDQuM2MwLDIuMiAxLjksNC4xIDQuMSw0LjF6bTQuMS00NC4zaDM2LjF2MzYuMWgtMzYuMXYtMzYuMXoiIGZpbGw9IiMwMDAwMDAiLz4KICAgICAgPHBhdGggZD0ibTEyMi42LDEwLjVjMC0yLjMtMS44LTQuMS00LjEtNC4xaC00NC4zYy0yLjMsMC00LjEsMS44LTQuMSw0LjF2NDQuM2MwLDIuMyAxLjgsNC4xIDQuMSw0LjFoNDQuM2MyLjMsMCA0LjEtMS44IDQuMS00LjF2LTQ0LjN6bS04LjIsNDAuMmgtMzYuMXYtMzYuMWgzNi4xdjM2LjF6IiBmaWxsPSIjMDAwMDAwIi8+CiAgICAgIDxwYXRoIGQ9Im0xMC41LDEyMi42aDQ0LjNjMi4zLDAgNC4xLTEuOCA0LjEtNC4xdi00NC4zYzAtMi4zLTEuOC00LjEtNC4xLTQuMWgtNDQuM2MtMi4zLDAtNC4xLDEuOC00LjEsNC4xdjQ0LjNjMCwyLjIgMS45LDQuMSA0LjEsNC4xem00LjEtNDQuM2gzNi4xdjM2LjFoLTM2LjF2LTM2LjF6IiBmaWxsPSIjMDAwMDAwIi8+CiAgICAgIDxwYXRoIGQ9Im0xMTguNSw3MC4xaC00NC4zYy0yLjMsMC00LjEsMS44LTQuMSw0LjF2NDQuM2MwLDIuMyAxLjgsNC4xIDQuMSw0LjFoNDQuM2MyLjMsMCA0LjEtMS44IDQuMS00LjF2LTQ0LjNjMC0yLjItMS45LTQuMS00LjEtNC4xem0tNC4xLDQ0LjNoLTM2LjF2LTM2LjFoMzYuMXYzNi4xeiIgZmlsbD0iIzAwMDAwMCIvPgogICAgPC9nPgogIDwvZz4KPC9zdmc+Cg==)
|
|
}
|
|
|
|
#container-2 {
|
|
background-repeat: no-repeat;
|
|
background-size: 80px;
|
|
background-position: 50% 65%;
|
|
background-image: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAxMjkgMTI5IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAxMjkgMTI5IiB3aWR0aD0iNTEycHgiIGhlaWdodD0iNTEycHgiPgogIDxnPgogICAgPHBhdGggZD0ibTExOC41LDEwLjVoLTEwOGMtMi4zLDAtNC4xLDEuOC00LjEsNC4xdjUxLjcgMjEuMWMwLDIuMyAxLjgsNC4xIDQuMSw0LjFoNDkuOXYxOC44aC0yMi45Yy0yLjMsMC00LjEsMS44LTQuMSw0LjFzMS44LDQuMSA0LjEsNC4xaDU0YzIuMywwIDQuMS0xLjggNC4xLTQuMXMtMS44LTQuMS00LjEtNC4xaC0yMi45di0xOC44aDQ5LjljMi4zLDAgNC4xLTEuOCA0LjEtNC4xdi0yMS4xLTUxLjdjMC0yLjMtMS44LTQuMS00LjEtNC4xem0tNC4xLDcyLjhoLTk5Ljh2LTEzaDk5Ljh2MTN6bTAtMjEuMWgtOTkuOHYtNDMuNWg5OS44djQzLjV6IiBmaWxsPSIjMDAwMDAwIi8+CiAgPC9nPgo8L3N2Zz4K)
|
|
}
|
|
#container-3 {
|
|
background-repeat: no-repeat;
|
|
background-size: 80px;
|
|
background-position: 50% 65%;
|
|
background-image: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAxMjkgMTI5IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAxMjkgMTI5IiB3aWR0aD0iNTEycHgiIGhlaWdodD0iNTEycHgiPgogIDxnPgogICAgPHBhdGggZD0ibTk2LjYsMjYuOGgtODYuMWMtMi4yLDAtNC4xLDEuOC00LjEsNC4xdjY3LjJjMCwyLjIgMS44LDQuMSA0LjEsNC4xaDg2LjFjMi4yLDAgNC4xLTEuOCA0LjEtNC4xdi0xOS40bDE0LjksMTQuOWMwLjgsMC44IDEuOCwxLjIgMi45LDEuMiAwLjUsMCAxLjEtMC4xIDEuNi0wLjMgMS41LTAuNiAyLjUtMi4xIDIuNS0zLjh2LTUyLjVjMC0xLjYtMS0zLjEtMi41LTMuOC0xLjUtMC42LTMuMy0wLjMtNC40LDAuOWwtMTQuOSwxNC45di0xOS4zYy0wLjEtMi4zLTEuOS00LjEtNC4yLTQuMXptLTQuMSwzMy4zdjguOCAyNS4yaC03OHYtNTkuMmg3OHYyNS4yem0yMS45LTEydjMyLjlsLTEzLjctMTMuN3YtNS40bDEzLjctMTMuOHoiIGZpbGw9IiMwMDAwMDAiLz4KICA8L2c+Cjwvc3ZnPgo=)
|
|
}
|
|
#container-4 {
|
|
background-repeat: no-repeat;
|
|
background-size: 80px;
|
|
background-position: 50% 65%;
|
|
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgNDkwLjI4MiA0OTAuMjgyIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA0OTAuMjgyIDQ5MC4yODI7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnPg0KCTxwYXRoIGQ9Ik0wLjA0MywyNDUuMTk3YzAuNiwxMC4xLDcuMywxOC42LDE3LDIxLjVsMTc5LjYsNTQuM2w2LjYsMTIzLjhjMC4zLDQuOSwzLjYsOS4yLDguMywxMC44YzEuMywwLjUsMi43LDAuNyw0LDAuNw0KCQljMy41LDAsNi44LTEuNCw5LjItNC4xbDYzLjUtNzAuM2w5MCw2Mi4zYzQsMi44LDguNyw0LjMsMTMuNiw0LjNjMTEuMywwLDIxLjEtOCwyMy41LTE5LjJsNzQuNy0zODAuN2MwLjktNC40LTAuOC05LTQuMi0xMS44DQoJCWMtMy41LTIuOS04LjItMy42LTEyLjQtMS45bC00NTksMTg2LjhDNS4xNDMsMjI1Ljg5Ny0wLjU1NywyMzUuMDk3LDAuMDQzLDI0NS4xOTd6IE0yMjYuMDQzLDQxNC4wOTdsLTQuMS03OC4xbDQ2LDMxLjgNCgkJTDIyNi4wNDMsNDE0LjA5N3ogTTM5MS40NDMsNDIzLjU5N2wtMTYzLjgtMTEzLjRsMjI5LjctMjIyLjJMMzkxLjQ0Myw0MjMuNTk3eiBNNDMyLjE0Myw3OC4xOTdsLTIyNy4xLDIxOS43bC0xNzkuNC01NC4yDQoJCUw0MzIuMTQzLDc4LjE5N3oiLz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjwvc3ZnPg0K)
|
|
|
|
}
|
|
.float{
|
|
position:fixed;
|
|
width:60px;
|
|
height:60px;
|
|
bottom:80px;
|
|
right:50px;
|
|
background-color:#C23;
|
|
color:#FFF;
|
|
border-radius:50px;
|
|
text-align:center;
|
|
box-shadow: 2px 2px 3px #999;
|
|
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;
|
|
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;
|
|
z-index:10;
|
|
}
|
|
|
|
|
|
.my-float{
|
|
margin-top:7px;
|
|
}
|
|
|
|
#container-6 {
|
|
background-repeat: no-repeat;
|
|
background-size: 80px;
|
|
background-position: 50% 65%;
|
|
background-image: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAxMjkgMTI5IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAxMjkgMTI5IiB3aWR0aD0iNTEycHgiIGhlaWdodD0iNTEycHgiPgogIDxnPgogICAgPHBhdGggZD0ibTExOC4yLDMzLjVjLTAuMiwwLTI1LjItMC42LTUwLjctMjUuOS0wLjgtMC44LTEuOC0xLjItMi45LTEuMmgtMC40Yy0xLjEsMC0yLjEsMC40LTIuOSwxLjItMjUuMywyNS4zLTUwLjMsMjUuOS01MC41LDI1LjktMi4yLDAtNCwxLjgtNCw0LjF2MjYuNGMwLDAuNSAwLjEsMSAwLjMsMS41IDAuNywxLjggMTgsNDQuNSA1Niw1Ni40IDAuNCwwLjEgMC44LDAuMiAxLjIsMC4yIDAuMSwwIDAuMywwIDAuNCwwIDAuNCwwIDAuOC0wLjEgMS4yLTAuMiAzOC0xMS45IDU1LjMtNTQuNiA1Ni01Ni40IDAuMi0wLjUgMC4zLTEgMC4zLTEuNXYtMjYuNGMwLTIuMi0xLjgtNC00LTQuMXptLTQuMSwyOS43Yy0yLjMsNS4zLTE4LjQsNDAuMi00OS42LDUwLjYtMzEuMi0xMC40LTQ3LjMtNDUuMy00OS42LTUwLjd2LTIxLjhjOC40LTEuMSAyOC41LTUuNyA0OS42LTI1LjQgMjEuMSwxOS43IDQxLjIsMjQuMyA0OS42LDI1LjR2MjEuOXoiIGZpbGw9IiMwMDAwMDAiLz4KICA8L2c+Cjwvc3ZnPgo=)
|
|
}
|
|
.container-inner {
|
|
display: none;
|
|
}
|
|
|
|
img {
|
|
max-width: 100%;
|
|
}
|
|
|
|
video {
|
|
flex: 1 1 auto;
|
|
background-color: transparent !important;
|
|
}
|
|
|
|
|
|
.in-animation {
|
|
animation: inlightbox 0.8s forwards;
|
|
position: fixed !important;
|
|
margin: 0 !important;
|
|
}
|
|
|
|
.out-animation {
|
|
animation: outlightbox 0.8s forwards;
|
|
}
|
|
|
|
@keyframes inlightbox
|
|
{
|
|
50% {
|
|
width: 100%;
|
|
left: 0;
|
|
height: 220px;
|
|
}
|
|
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>
|
|
</head>
|
|
<body id="main">
|
|
<script language="javascript" type="text/javascript" src="./CodecsHandler.js"></script>
|
|
<script language="javascript" type="text/javascript" src="./webrtc.js?v=8"></script>
|
|
|
|
<div id="header">
|
|
<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="joinroomID" name="joinroomID" size=26 placeholder="Join by Room Name here"></input>
|
|
<button style="padding:1px;" onclick="jumptoroom();">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="head4" style="display:inline-block" class='advanced'>
|
|
<font style="font-size:60%">   You are in the director's view for room:  <div id="dirroomid" style="font-size:140%;color:#99C;display:inline-block"></div></font>
|
|
</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>
|
|
|
|
</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" 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" style="align:center;">
|
|
<div id="container-1" class="column columnfade" style="background-color:#ddd;">
|
|
<h2>Add Group Video Chat to OBS</h2>
|
|
<div class="container-inner">
|
|
<br /><h2><font style="color:#D22">The Group Chat is EXPERIMENTAL and likely unstable.<br />Please report issues to steve@seguin.email</font></h2><br />
|
|
<p><b>Room Name:</b><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>
|
|
<li>There are numerous known issues. Please report feedback.</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 as Director</button><br />
|
|
</div>
|
|
<div class="outer close">
|
|
<div class="inner">
|
|
<label>Back</label>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div id="container-3" class="column columnfade" onclick="previewWebcam()" style="background-color:#ddd;">
|
|
<h2 id="add_camera">Add your Camera to OBS</h2>
|
|
<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;" /></p><br />
|
|
<p><video id="previewWebcam" oncanplay="log('resolution found');log(document.getElementById('previewWebcam').videoWidth|0);log(document.getElementById('previewWebcam').videoHeight|0);this.srcObject.getVideoTracks().forEach(function(track) {log(track.getSettings().frameRate);});" 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/>
|
|
<p>Audio source: <select id="audioSource"></select></p>
|
|
</div>
|
|
<div class="outer close">
|
|
<div class="inner">
|
|
<label>Back</label>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div id="container-2" class="column columnfade" style="background-color:#ddd;">
|
|
<h2 id="add_screen">Remote Screenshare into OBS</h2>
|
|
<div class="container-inner">
|
|
<p><b>note</b>: Do not forget to click "Share audio" in Chrome.<br />(Firefox does not support audio sharing.)</p>
|
|
<p><img src="share.jpg" style="max-height:55vh"/></p>
|
|
<button onclick="publishScreen()" >SELECT SCREEN TO SHARE</button>
|
|
<p><input id="videoname2" placeholder="Give this video source a name (optional)" size=35 maxlength=70 style="padding:5px;" /></br ><br /></p>
|
|
</div>
|
|
<div class="outer close">
|
|
<div class="inner">
|
|
<label>Back</label>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div id="container-4" class="column columnfade" style="background-color:#ddd;">
|
|
<h2>Generate Invite Link</h2>
|
|
<div class="container-inner">
|
|
<br /><br />
|
|
<p><input id="videoname4" placeholder="Give this video source a name (optional)" size=35 maxlength=70 style="padding:5px;" /></br ><br /></p>
|
|
<button onclick="generateQRPage(this)" >GENERATE THE INVITE LINK</button>
|
|
</div>
|
|
<div class="outer close">
|
|
<div class="inner">
|
|
<label>Back</label>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<p><div id="info" class="fullcolumn columnfade">
|
|
<center>
|
|
<div class="infoblob" align="left">
|
|
|
|
<h2>What is OBS.Ninja</h2><br />
|
|
<li>100% <b>free</b>; no downloads; no personal data collection; no sign-in</li>
|
|
<li>Bring video from your smartphone, laptop, computer, or from your friends directly into your OBS video stream</li>
|
|
<li>We use cutting edge Peer-to-Peer forwarding technology that offers privacy and ultra-low latency</li>
|
|
<br />
|
|
<li>Youtube video <a href="https://www.youtube.com/watch?v=6R_sQKxFAhg">Demoing it here</a></li>
|
|
<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><font style="color:red">Known issues:</font></i><br />
|
|
|
|
<li>** MacOS users need to use OBS v23, along with either a local microphone or virtual audio cable.</li>
|
|
<li>** The rear camera on some smartphones have issues. Please report these issues, including your phone's model.</li>
|
|
<li>** For some users the video fails to load into OBS; this is often caused by a network firewall.</li>
|
|
<br />
|
|
<li><b>April 7th, 2020</b>: Site updated. The previous version can be found at https://obs.ninja/old/</li><br />
|
|
<br /><br />
|
|
<i><h3>Check out the <a href="https://www.reddit.com/r/OBSNinja/">sub-reddit</a> for help and advanced info. Or email me steve@seguin.email</i></h3>
|
|
</div>
|
|
</center>
|
|
</p></div>
|
|
<form method="post" onsubmit="setFormSubmitting()" style="display:none;">
|
|
<input type="submit" />
|
|
</form>
|
|
<script>
|
|
/////////////
|
|
|
|
// Some browsers partially implement mediaDevices. We can't just assign an object
|
|
// with getUserMedia as it would overwrite existing properties.
|
|
// Here, we will just add the getUserMedia property if it's missing.
|
|
|
|
var VIS = vis;
|
|
var formSubmitting = true;
|
|
var setFormSubmitting = function() { formSubmitting = true; };
|
|
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;
|
|
}
|
|
|
|
var confirmationMessage = 'Leaving the page now will terminate your stream ';
|
|
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
|
|
return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
|
|
});
|
|
};
|
|
|
|
var lastTouchEnd = 0;
|
|
document.addEventListener('touchend', function (event) {
|
|
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();
|
|
(function (w) {
|
|
|
|
w.URLSearchParams = w.URLSearchParams || function (searchString) {
|
|
var self = this;
|
|
self.searchString = searchString;
|
|
self.get = function (name) {
|
|
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
|
|
if (results == null) {
|
|
return null;
|
|
}
|
|
else {
|
|
return decodeURI(results[1]) || 0;
|
|
}
|
|
};
|
|
}
|
|
|
|
})(window)
|
|
|
|
var urlParams = new URLSearchParams(window.location.search);
|
|
if (urlParams.has('permaid')){
|
|
var permaid = urlParams.get('permaid');
|
|
session.changeStreamID(permaid);
|
|
}
|
|
if (urlParams.has('stereo')){
|
|
log("STEREO ENABLED");
|
|
session.stereo = true;
|
|
}
|
|
|
|
if (urlParams.has('bitrate')){
|
|
session.bitrate = parseInt(urlParams.get('bitrate'));
|
|
log("BITRATE ENABLED");
|
|
log(session.bitrate);
|
|
}
|
|
if (urlParams.has('turn')){
|
|
try {
|
|
var turnstring = urlParams.get('turn').split(";");
|
|
var turn = {};
|
|
turn.username = turnstring[0]; // myusername
|
|
turn.credential = turnstring[1]; //mypassword
|
|
turn.urls = [turnstring[2]]; // ["turn:turn.obs.ninja:443"];
|
|
session.configuration.iceServers.push(turn);
|
|
} catch (e){
|
|
alert("TURN server parameters were wrong.");
|
|
errorlog(e);
|
|
}
|
|
} else { // THIS IS ME being Very Generous. For a little while.
|
|
var turn = {};
|
|
turn.username = "steve";
|
|
turn.credential = "justtesting";
|
|
turn.urls = ["turn:turn.obs.ninja:443"];
|
|
session.configuration.iceServers.push(turn);
|
|
}
|
|
|
|
|
|
function jumptoroom(){
|
|
document.getElementById("joinroomID").value;
|
|
|
|
var arr = window.location.href.split('?');
|
|
if (arr.length > 1 && arr[1] !== '') {
|
|
window.location+="&roomid="+document.getElementById("joinroomID").value;
|
|
} else {
|
|
window.location+="?roomid="+document.getElementById("joinroomID").value;
|
|
}
|
|
}
|
|
|
|
var micvolume = 100;
|
|
session.connect();
|
|
session.volume = micvolume;
|
|
if (urlParams.has('roomid')){
|
|
var roomid = urlParams.get('roomid');
|
|
session.roomid = roomid;
|
|
document.getElementById("videoname1").value = roomid;
|
|
document.getElementById("dirroomid").innerHTML = roomid;
|
|
document.getElementById("roomid").innerHTML = roomid;
|
|
document.getElementById("container-1").className = 'column columnfade advanced';
|
|
document.getElementById("container-4").className = 'column columnfade advanced';
|
|
document.getElementById("head1").innerHTML = '- Welcome. Please select an option to join the chat room';
|
|
document.getElementById("add_camera").innerHTML = "Join Room with Camera";
|
|
document.getElementById("add_screen").innerHTML = "Screenshare with Room";
|
|
if (urlParams.has('scene')){
|
|
session.scene = urlParams.get('scene');
|
|
document.getElementById("container-4").className = 'column columnfade';
|
|
document.getElementById("container-3").className = 'column columnfade';
|
|
document.getElementById("container-2").className = 'column columnfade';
|
|
document.getElementById("container-1").className = 'column columnfade';
|
|
document.getElementById("header").className = 'advanced';
|
|
document.getElementById("info").className = 'advanced';
|
|
document.getElementById("header").className = 'advanced';
|
|
document.getElementById("head1").className = 'advanced';
|
|
document.getElementById("head2").className = 'advanced';
|
|
document.getElementById("head3").className = 'advanced';
|
|
document.getElementById("mainmenu").style.display = "none";
|
|
joinRoom(roomid); // this is a scene, so we want high resolutions
|
|
}
|
|
}
|
|
|
|
|
|
|
|
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(){ // TODO: I need to have this be MUTE, toggle, with volume not touched.
|
|
var msg = {};
|
|
if (micvolume==0){
|
|
micvolume = 100;
|
|
document.getElementById("mutetoggle").className="fa fa-microphone my-float";
|
|
document.getElementById("mutebutton").className="float3";
|
|
} else{
|
|
micvolume=0;
|
|
document.getElementById("mutetoggle").className="fa fa-microphone-slash my-float";
|
|
document.getElementById("mutebutton").className="float";
|
|
}
|
|
msg.volume = micvolume;
|
|
session.volume = micvolume;
|
|
session.sendMessage(msg);
|
|
}
|
|
////////////////////////////
|
|
|
|
function directEnable(ele){ // A directing room only is controlled by the Director, with the exception of MUTE.
|
|
log("enable");
|
|
if (ele.parentNode.parentNode.dataset.enable==1){
|
|
ele.parentNode.parentNode.dataset.enable = 0;
|
|
ele.className = "";
|
|
ele.innerHTML = "Add to Scene 1";
|
|
ele.parentNode.parentNode.style.backgroundColor = "#E3E4EF";
|
|
} else {
|
|
ele.parentNode.parentNode.style.backgroundColor = "#AFA";
|
|
ele.parentNode.parentNode.dataset.enable = 1;
|
|
ele.className = "pressed";
|
|
ele.innerHTML = "Remove from Scene 1";
|
|
}
|
|
var msg = {};
|
|
msg.request = "sendroom";
|
|
msg.roomid = session.roomid;
|
|
msg.director = "1" // scene
|
|
msg.action = "display";
|
|
msg.value = ele.parentNode.parentNode.dataset.enable;
|
|
msg.target = ele.parentNode.parentNode.dataset.UUID;
|
|
session.sendMsg(msg); // send to everyone in the room, so they know if they are on air or not.
|
|
}
|
|
|
|
|
|
function directMute(ele){ // A directing room only is controlled by the Director, with the exception of MUTE.
|
|
log("mute");
|
|
if (ele.parentNode.parentNode.dataset.mute==0){
|
|
ele.parentNode.parentNode.dataset.mute = 1;
|
|
ele.className = "";
|
|
ele.innerHTML = "Mute";
|
|
} else {
|
|
ele.parentNode.parentNode.dataset.mute = 0;
|
|
ele.className = "pressed";
|
|
ele.innerHTML = "Unmute";
|
|
}
|
|
var msg = {};
|
|
msg.request = "sendroom";
|
|
msg.roomid = session.roomid;
|
|
msg.director = "1";
|
|
msg.action = "mute";
|
|
msg.value = ele.parentNode.parentNode.dataset.mute;
|
|
msg.target = ele.parentNode.parentNode.dataset.UUID;
|
|
session.sendMsg(msg); // send to everyone in the room, so they know if they are on air or not.
|
|
}
|
|
|
|
|
|
function directVolume(ele){ // A directing room only is controlled by the Director, with the exception of MUTE.
|
|
log("volume");
|
|
var msg = {};
|
|
msg.request = "sendroom";
|
|
msg.roomid = session.roomid;
|
|
msg.director = "1";
|
|
msg.action = "volume";
|
|
msg.target = ele.parentNode.parentNode.dataset.UUID; // i want to focus on the STREAM ID, not the UUID...
|
|
msg.value = ele.value;
|
|
|
|
session.sendMsg(msg); // send to everyone in the room, so they know if they are on air or not.
|
|
}
|
|
|
|
|
|
function chatRoom(chatmessage="hi"){ // A directing room only is controlled by the Director, with the exception of MUTE.
|
|
log("Chat message");
|
|
var msg = {};
|
|
msg.request = "sendroom";
|
|
msg.roomid = session.roomid;
|
|
msg.action = "chat";
|
|
msg.value = chatmessage;
|
|
session.sendMsg(msg); // send to everyone in the room, so they know if they are on air or not.
|
|
}
|
|
|
|
|
|
function changeTitle(aTitle="Untitled"){
|
|
log("changing title; if connected at least");
|
|
session.changeTitle(aTitle);
|
|
}
|
|
|
|
var activatedStream = false;
|
|
function publishScreen(){
|
|
if( activatedStream == true){return;}
|
|
activatedStream = true;
|
|
|
|
var title = document.getElementById("videoname2").value;
|
|
|
|
formSubmitting = false;
|
|
|
|
var width = {ideal: 1280};
|
|
var height = {ideal: 720};
|
|
|
|
if (urlParams.has('width')){
|
|
width = urlParams.get('width');
|
|
width = {ideal: width};
|
|
}
|
|
if (urlParams.has('height')){
|
|
height = urlParams.get('height');
|
|
height = {ideal: 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"}
|
|
};
|
|
|
|
if (session.roomid){
|
|
joinRoom(session.roomid,300);
|
|
document.getElementById("head3").className = 'advanced';
|
|
} else {
|
|
document.getElementById("head3").className = '';
|
|
}
|
|
|
|
session.publishScreen(constraints, title);
|
|
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';
|
|
|
|
}
|
|
function publishWebcam(){
|
|
if( activatedStream == true){return;}
|
|
activatedStream = true;
|
|
|
|
var title = document.getElementById("videoname3").value;
|
|
var ele = document.getElementById("previewWebcam");
|
|
|
|
var stream = ele.srcObject;
|
|
|
|
ele.parentNode.removeChild(ele);
|
|
|
|
formSubmitting = false;
|
|
window.scrollTo(0, 0); // iOS has a nasty habit of overriding the CSS when changing camaera selections, so this addresses that.
|
|
|
|
if (session.roomid){
|
|
joinRoom(session.roomid,300);
|
|
}
|
|
|
|
session.publishStream(stream, title);
|
|
log("streamID is: "+session.streamID);
|
|
document.getElementById("head1").className = 'advanced';
|
|
document.getElementById("head2").className = 'advanced';
|
|
document.getElementById("head3").className = '';
|
|
|
|
document.getElementById("mutebutton").className="float3";
|
|
document.getElementById("helpbutton").className="float2";
|
|
|
|
|
|
}
|
|
|
|
function joinRoom(roomname, maxbitrate=false){
|
|
roomname = roomname.replace(/[^0-9a-z]/gi, '');
|
|
if (roomname.length){
|
|
log("Join room",roomname);
|
|
log(roomname);
|
|
session.joinRoom(roomname,maxbitrate).then(function(response){
|
|
log("Members in Room");
|
|
log(response);
|
|
for (i in response){
|
|
if ("UUID" in response[i]){
|
|
if ("streamID" in response[i]){
|
|
if (response[i]['UUID'] in session.pcs){
|
|
console.log("RTC already connected"); /// lets just say instead of Stream, we have
|
|
} else {
|
|
//var title = "";
|
|
//if ("title" in response[i]){
|
|
// title = response[i]["title"];
|
|
//}
|
|
console.log("PLAYING VIDEO 729");
|
|
if (urlParams.has('streamid')){
|
|
session.single=true;
|
|
var streamlist = urlParams.get('streamid').split(",");
|
|
console.log(streamlist);
|
|
for (j in streamlist){
|
|
if (response[i]['streamID'] == streamlist[j]){
|
|
log("PLAYIGN!!!");
|
|
session.watchStream(response[i]['streamID'])
|
|
}
|
|
}
|
|
} else {
|
|
session.watchStream(response[i]['streamID']); // How do I make sure they aren't requesting the same movie twice as a race condition?
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
},function(error){return {}});
|
|
} else {
|
|
errorlog("Room name not long enough or contained all bad characaters");
|
|
}
|
|
}
|
|
|
|
|
|
function createRoom(){
|
|
|
|
var roomname = document.getElementById("videoname1").value;
|
|
log(roomname);
|
|
if (roomname.length==0){
|
|
alert("Please enter a room name before continuing");
|
|
return;
|
|
}
|
|
|
|
var gridlayout = document.getElementById("gridlayout");
|
|
gridlayout.classList.add("directorsgrid");
|
|
|
|
// var sheet = document.createElement('style');
|
|
// sheet.innerHTML = ".tile{object-fit:contain }";
|
|
// document.body.appendChild(sheet);
|
|
|
|
var roomname = document.getElementById("videoname1").value;
|
|
log(roomname);
|
|
session.roomid = 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("dirroomid").innerHTML = roomname;
|
|
document.getElementById("roomid").innerHTML = roomname;
|
|
|
|
|
|
//document.getElementById("mutebutton").className="float3";
|
|
//document.getElementById("helpbutton").className="float2";
|
|
session.director = true;
|
|
document.getElementById("reshare").parentNode.removeChild(document.getElementById("reshare"));
|
|
gridlayout.innerHTML = "<br /><font style='font-size:130%;color:white;'>Invite Link: Give this link to your guests --> <a data-share='' onclick='var range=document.createRange(); range.selectNodeContents(this); var selec = window.getSelection(); selec.removeAllRanges();selec.addRange(range);document.execCommand(\"copy\");' onmouseover='this.style.cursor=\"pointer\"'><font style='color:#54F'>https://"+location.hostname+location.pathname+"?roomid="+session.roomid+"</font></a><br /><br /></font><font style='color:white'><b>Scene 1</b> (auto-mix) for OBS: (experimental+optional) --> <a data-share='' onclick='var range=document.createRange(); range.selectNodeContents(this); var selec = window.getSelection(); selec.removeAllRanges();selec.addRange(range);document.execCommand(\"copy\");' onmouseover='this.style.cursor=\"pointer\"' id='reshare'><font style='color:#54F'><font style='color:#2F3'>https://"+location.hostname+location.pathname+"?scene=1&roomid="+session.roomid+"</font></a></font><br /><br />";
|
|
joinRoom(roomname,300);
|
|
|
|
}
|
|
|
|
function enumerateDevices() {
|
|
if (typeof navigator.enumerateDevices === "function") {
|
|
return navigator.enumerateDevices();
|
|
}
|
|
else if (typeof navigator.mediaDevices === "object" &&
|
|
typeof navigator.mediaDevices.enumerateDevices === "function") {
|
|
return navigator.mediaDevices.enumerateDevices();
|
|
}
|
|
else {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
if (window.MediaStreamTrack == null || window.MediaStreamTrack.getSources == null) {
|
|
throw new Error();
|
|
}
|
|
window.MediaStreamTrack.getSources((devices) => {
|
|
resolve(devices
|
|
.filter(device => {
|
|
return device.kind.toLowerCase() === "video" || device.kind.toLowerCase() === "videoinput";
|
|
})
|
|
.map(device => {
|
|
return {
|
|
deviceId: device.deviceId != null ? device.deviceId : "",
|
|
groupId: device.groupId,
|
|
kind: "videoinput",
|
|
label: device.label,
|
|
toJSON: /* istanbul ignore next */ function () {
|
|
return this;
|
|
}
|
|
};
|
|
}));
|
|
});
|
|
}
|
|
catch (e) {
|
|
errorlog(e);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
// 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);
|
|
}
|
|
});
|
|
log(deviceInfos);
|
|
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 {
|
|
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) {
|
|
errorlog(error);
|
|
}
|
|
|
|
function getUserMediaVideoParams(resolutionFallbackLevel, isSafariBrowser) {
|
|
switch (resolutionFallbackLevel) {
|
|
case 0:
|
|
if (isSafariBrowser) {
|
|
return {
|
|
width: { min: 360, ideal: 1280, max: 1920 },
|
|
height: { min: 360, ideal: 720, max: 1080 }
|
|
};
|
|
}
|
|
else {
|
|
return {
|
|
width: { min: 720, ideal: 1920, max: 1920 },
|
|
height: { min: 720, ideal: 1080, max: 1920 }
|
|
};
|
|
}
|
|
case 1:
|
|
if (isSafariBrowser) {
|
|
return {
|
|
width: { min: 640 },
|
|
height: { min: 360 }
|
|
};
|
|
}
|
|
else {
|
|
return {
|
|
width: { min: 720, ideal: 1280, max: 1280 },
|
|
height: { min: 720, ideal: 720, max: 1280 }
|
|
};
|
|
}
|
|
case 2:
|
|
if (isSafariBrowser) {
|
|
return {
|
|
width: { min: 640, ideal: 1200, max: 1280 },
|
|
height: { min: 640, ideal: 1200, max: 1280 }
|
|
};
|
|
}
|
|
else {
|
|
return {
|
|
width: { min: 640, ideal: 1280, max: 1920 },
|
|
height: { min: 640, ideal: 1280, max: 1080 }
|
|
};
|
|
}
|
|
case 3:
|
|
if (isSafariBrowser) {
|
|
return {
|
|
height: { min: 360, ideal: 720, max: 960 }
|
|
};
|
|
}
|
|
else {
|
|
return {
|
|
height: { min: 360, ideal: 960, max: 960 }
|
|
};
|
|
}
|
|
case 4:
|
|
if (isSafariBrowser) {
|
|
return {
|
|
width: { min: 360, ideal: 1280, max: 1440 },
|
|
};
|
|
}
|
|
else {
|
|
return {
|
|
width: { min: 360, ideal: 1280, max: 1440 },
|
|
};
|
|
}
|
|
case 5:
|
|
if (isSafariBrowser) {
|
|
return {
|
|
width: { min: 360, ideal: 640, max: 1440 },
|
|
height: { min: 360, ideal: 360, max: 720 }
|
|
};
|
|
}
|
|
else {
|
|
return {
|
|
width: { min: 360, ideal: 640, max: 3840 },
|
|
height: { min: 360, ideal: 360, max: 2160 }
|
|
};
|
|
}
|
|
case 6:
|
|
if (isSafariBrowser) {
|
|
return {}; // iphone users probably don't need to wait any longer, so let them just get to it
|
|
}
|
|
else {
|
|
return {width: {min:360,max:1920}, height: {min:360, max:1920}}; // same as default, but I didn't want to mess with framerates until I gave it all a try first
|
|
}
|
|
case 7:
|
|
return { // If the camera is recording in low-light, it may have a low framerate. It coudl also be recording at a very high resolution.
|
|
width: { min: 360, ideal: 640 },
|
|
height: { min: 360, ideal: 360 },
|
|
framerate: 10
|
|
};
|
|
|
|
case 8:
|
|
return {width: {min:360,max:1920}, height: {min:360, max:1920}}; // same as default, but I didn't want to mess with framerates until I gave it all a try first
|
|
case 9:
|
|
return {framerate: 0 }; // Some Samsung Devices report they can only support a framerate of 0.
|
|
default:
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function grabVideo(quality=0, audio=false){
|
|
if( activatedPreview == true){log("activeated preview return");return;}
|
|
activatedPreview = true;
|
|
log(quality);
|
|
log("trying with quality:");
|
|
|
|
var audioSelect = document.querySelector('select#audioSource');
|
|
var videoSelect = document.querySelector('select#videoSource');
|
|
var iOS = !!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform);
|
|
|
|
if (audio==true){
|
|
audio = {deviceId: {exact: audioSelect.value}};
|
|
if (urlParams.has('stereo')){
|
|
audio.echoCancellation = false;
|
|
audio.autoGainControl = false;
|
|
audio.noiseSuppression = false;
|
|
}
|
|
|
|
}
|
|
var constraints = {
|
|
audio: audio,
|
|
video: getUserMediaVideoParams(quality, iOS)
|
|
};
|
|
constraints.video.deviceId = { exact: videoSelect.value };
|
|
|
|
if (urlParams.has('width')){
|
|
var width = urlParams.get('width');
|
|
constraints.video.width = {exact: width};
|
|
}
|
|
if (urlParams.has('height')){
|
|
var height = urlParams.get('height');
|
|
constraints.video.height = {exact: height};
|
|
}
|
|
|
|
log(constraints);
|
|
|
|
setTimeout(()=>{
|
|
try {
|
|
|
|
log("Trying Constraints");
|
|
var oldstream= document.getElementById('previewWebcam').srcObject;
|
|
if (oldstream){
|
|
oldstream.getTracks().forEach(function(track) {
|
|
track.stop();
|
|
});
|
|
}
|
|
} catch(e){
|
|
errorlog(e);
|
|
}
|
|
navigator.mediaDevices.getUserMedia(constraints).then(function(stream){
|
|
if (audio ==false){
|
|
stream.getTracks().forEach(function(track) { // We don't want to keep it without audio; so we are going to try to add audio now.
|
|
track.stop();
|
|
});
|
|
log("GOT IT BUT WITH NO AUDIO");
|
|
activatedPreview = false;
|
|
grabVideo(quality,true);
|
|
}else {
|
|
document.getElementById('previewWebcam').srcObject = stream; // set the preview window and run with it
|
|
log("DONE - found stream");
|
|
}
|
|
}).catch(function(e){
|
|
activatedPreview = false;
|
|
errorlog(e);
|
|
if (e.name === "OverconstrainedError"){
|
|
errorlog(e.message);
|
|
log("Resolution didn't work");
|
|
} else if (e.name === "NotReadableError"){
|
|
if (iOS){
|
|
alert("An error occured. Upgrading to at least iOS 13.4 should fix this glitch from happening again");
|
|
} else {
|
|
alert("Error Listing Media Devices.\n\nThe default Camera may already be in use with another app. Typically webcams can only be accessed by one program at a time.\n\nThe selected device may also not be supported.");
|
|
}
|
|
activatedPreview=true;
|
|
return;
|
|
} else if (e.name === "NavigatorUserMediaError"){
|
|
alert("Unknown error: 'NavigatorUserMediaError'");
|
|
return;
|
|
} else {
|
|
errorlog("An unknown camera error occured");
|
|
}
|
|
if (quality<=9){
|
|
grabVideo(quality+1);
|
|
} else {
|
|
errorlog("********Camera failed to work");
|
|
activatedPreview=true;
|
|
alert("Camera failed to load. Please report which camera/device/browser you are using to steve@seguin.email");
|
|
}
|
|
});
|
|
},0);
|
|
}
|
|
|
|
var activatedPreview = false;
|
|
function previewWebcam(){
|
|
if( activatedPreview == true){log("activeated preview return");return;}
|
|
activatedPreview = true;
|
|
|
|
var audioSelect = document.querySelector('select#audioSource');
|
|
var videoSelect = document.querySelector('select#videoSource');
|
|
|
|
var constraints = {audio:true, video:true };
|
|
|
|
|
|
window.setTimeout(() => {
|
|
|
|
var oldstream= document.getElementById('previewWebcam').srcObject;
|
|
if (oldstream){
|
|
oldstream.getTracks().forEach(function(track) {
|
|
track.stop();
|
|
});
|
|
}
|
|
|
|
navigator.mediaDevices.getUserMedia(constraints).then(function(stream){ // Apple needs thi to happen before I can access EnumerateDevices.
|
|
//document.getElementById('previewWebcam').srcObject=stream;
|
|
stream.getTracks().forEach(function(track) { // We don't want to keep it without audio; so we are going to try to add audio now.
|
|
track.stop();
|
|
});
|
|
enumerateDevices().then(gotDevices).then(function(){
|
|
|
|
var audioSelect = document.querySelector('select#audioSource');
|
|
var videoSelect = document.querySelector('select#videoSource');
|
|
|
|
audioSelect.onchange = function(){log("AUDIO source CHANGED");activatedPreview=false;grabVideo();};
|
|
videoSelect.onchange = function(){log("video source changed");activatedPreview=false;grabVideo();};
|
|
activatedPreview = false;
|
|
grabVideo();
|
|
|
|
}).catch(handleError);
|
|
}).catch(function(e){
|
|
if (e.name === "NotReadableError"){
|
|
window.setTimeout(() => {
|
|
enumerateDevices().then(gotDevices).then(function(){
|
|
|
|
var audioSelect = document.querySelector('select#audioSource');
|
|
var videoSelect = document.querySelector('select#videoSource');
|
|
|
|
audioSelect.onchange = function(){log("AUDIO source CHANGED");activatedPreview=false;grabVideo();};
|
|
videoSelect.onchange = function(){log("video source changed");activatedPreview=false;grabVideo();};
|
|
activatedPreview = false;
|
|
grabVideo();
|
|
|
|
}).catch(handleError);
|
|
},0);
|
|
} else if (e.name === "NotAllowedError"){
|
|
alert("Error: Cannot access media devices \n\nPlease ensure both CAMERA and MICROPHONE permissions are allowed for this website to continue");
|
|
} else {
|
|
errorlog(e);
|
|
}
|
|
});
|
|
},0);
|
|
}
|
|
|
|
|
|
function checkOBS(){
|
|
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
|
|
log("enumerateDevices() not supported.");
|
|
return;
|
|
}
|
|
|
|
navigator.mediaDevices.enumerateDevices().then(function(devices) {
|
|
var matchFound = false;
|
|
devices.forEach(function(device) {
|
|
if (device.label.startsWith("OBS-Camera")){
|
|
alert("An OBS Virtual Camera was detected; Success!");
|
|
log(device.kind + ": " + device.label +
|
|
" id = " + device.deviceId);
|
|
matchFound = true;
|
|
|
|
}
|
|
log(device.kind + ": " + device.label + " id = " + device.deviceId);
|
|
});
|
|
if (matchFound == false){
|
|
alert("No OBS Virtual Camera was found");
|
|
}
|
|
}).catch(function(err) {
|
|
log(err.name + ": " + err.message);
|
|
});
|
|
}
|
|
|
|
function play(streamName){
|
|
log("play stream");
|
|
session.watchStream(streamName);
|
|
}
|
|
var retry=null;
|
|
function browse(){
|
|
log("browse streams");
|
|
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 />";
|
|
});
|
|
},function(error){return {}});
|
|
}
|
|
|
|
function generateQRPage(ele){
|
|
try{
|
|
var sid = session.generateStreamID();
|
|
ele.parentNode.innerHTML = '<br /><div id="qrcode" style="background-color:white;display:inline-block;color:black;max-width:300px;padding:20px;"><h2 style="color:black">Invite Link:</h2><p><a href="https://' + location.hostname+ location.pathname + '?permaid=' + sid + '">https://' + location.hostname + location.pathname + '?permaid=' + sid + '</a></p><br /></div>\
|
|
<br /><br />and don\'t forget the<h2 style="color:black">OBS Link:</h2><p><a style="font-size:120%" href="https://' + location.hostname+ location.pathname + '?streamid=' + sid + '">https://' + location.hostname + location.pathname + '?streamid=' + sid + '</a></p><br /><i>In OBS v25 you can drag this link directly into OBS, or you can create a Browse element in OBS and insert it the URL source.</i> \
|
|
<br /><br />\
|
|
Please also note, the invite link and OBS ingestion link created is reusable, but only one person may use a specific invite at a time.';
|
|
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=' + sid);
|
|
|
|
} catch(e){
|
|
errorlog(e);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((urlParams.has('streamid')) && (session.roomid==false)){
|
|
document.getElementById("container-4").className = 'column columnfade';
|
|
document.getElementById("container-3").className = 'column columnfade';
|
|
document.getElementById("container-2").className = 'column columnfade';
|
|
document.getElementById("container-1").className = 'column columnfade';
|
|
//document.getElementById("header").className = 'advanced';
|
|
document.getElementById("info").className = 'advanced';
|
|
document.getElementById("header").className = 'advanced';
|
|
document.getElementById("head1").className = 'advanced';
|
|
document.getElementById("head2").className = 'advanced';
|
|
document.getElementById("head3").className = 'advanced';
|
|
|
|
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(){
|
|
try{
|
|
if (urlParams.get("streamid")){
|
|
if (document.getElementById("mainmenu")){
|
|
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/>';
|
|
|
|
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"));
|
|
retry = setInterval(function(){
|
|
if (document.getElementById("mainmenu")){
|
|
play(urlParams.get('streamid'));
|
|
} else {
|
|
clearInterval(retry);
|
|
}
|
|
},10000)
|
|
}}
|
|
} catch(e){
|
|
errorlog("Error handling QR Code failure");
|
|
}
|
|
},2000);
|
|
|
|
log("auto playing");
|
|
|
|
if (navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1){
|
|
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'));
|
|
}).catch(function(){
|
|
play(urlParams.get('streamid'));
|
|
});
|
|
} else {
|
|
play(urlParams.get('streamid'));
|
|
//document.getElementById("mainmenu").style.display="none";
|
|
}
|
|
}
|
|
|
|
document.addEventListener("dragstart", e => {
|
|
var url = e.target.href || e.target.data;
|
|
if (!url || !url.startsWith('http')) return;
|
|
var streamId = url.split('=')[1];
|
|
url += '&layer-name=OBS.Ninja';
|
|
if (streamId) url += ': ' + streamId;
|
|
var video = document.getElementById('videosource');
|
|
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));
|
|
});
|
|
|
|
var vis = (function(){
|
|
var stateKey, eventKey, keys = {
|
|
hidden: "visibilitychange",
|
|
webkitHidden: "webkitvisibilitychange",
|
|
mozHidden: "mozvisibilitychange",
|
|
msHidden: "msvisibilitychange"
|
|
};
|
|
for (stateKey in keys) {
|
|
if (stateKey in document) {
|
|
eventKey = keys[stateKey];
|
|
break;
|
|
}
|
|
}
|
|
return function(c) {
|
|
if (c) {
|
|
document.addEventListener(eventKey, c);
|
|
//document.addEventListener("blur", c);
|
|
//document.addEventListener("focus", c);
|
|
}
|
|
return !document[stateKey];
|
|
}
|
|
})();
|
|
|
|
function poker(){
|
|
try{
|
|
Notification.requestPermission().then(function(result) {
|
|
if (result === 'denied') {
|
|
log('Permission wasn\'t granted. Allow a retry.');
|
|
return;
|
|
}
|
|
if (result === 'default') {
|
|
log('The permission request was dismissed.');
|
|
return;
|
|
}
|
|
});
|
|
} catch (error) {
|
|
// Safari doesn't return a promise for requestPermissions and it
|
|
// throws a TypeError. It takes a callback as the first argument
|
|
// instead.
|
|
if (error instanceof TypeError) {
|
|
Notification.requestPermission(() => {
|
|
log("permissions was approved");
|
|
});
|
|
} else {
|
|
log("permission was denied");
|
|
throw error;
|
|
}
|
|
}
|
|
vis(function(){
|
|
if (!vis()){
|
|
alert("DONT MINIMIZE OR CHANGE TABS");
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
|
|
</script>
|
|
<script>
|
|
|
|
/* We need to create dynamic keyframes to show the animation from full-screen to normal. So we create a stylesheet in which we can insert CSS keyframe rules */
|
|
$("body").append('<style id="lightbox-animations" type="text/css"></style>');
|
|
|
|
/* Click on the container */
|
|
$(".column").on('click', function() {
|
|
/* The position of the container will be set to fixed, so set the top & left properties of the container */
|
|
|
|
var bounding_box = $(this).get(0).getBoundingClientRect();
|
|
$(this).css({ top: bounding_box.top + 'px', left: bounding_box.left -20+ 'px' });
|
|
|
|
/* Set container to fixed position. Add animation */
|
|
$(this).addClass('in-animation');
|
|
|
|
/* An empty container has to be added in place of the lightbox container so that the elements below don't come up
|
|
Dimensions of this empty container is the same as the original container */
|
|
$("#empty-container").remove();
|
|
$('<div id="empty-container" class="column"></div>').insertAfter(this);
|
|
|
|
/* To animate the container from full-screen to normal, we need dynamic keyframes */
|
|
var styles = '';
|
|
styles = '@keyframes outlightbox {';
|
|
styles += '0% {';
|
|
styles += 'height: 100%;';
|
|
styles += 'width: 100%;';
|
|
styles += 'top: 0px;';
|
|
styles += 'left: 0px;';
|
|
styles += '}';
|
|
styles += '50% {';
|
|
styles += 'height: 220px;';
|
|
styles += 'top: ' + bounding_box.y + 'px;';
|
|
styles += '}';
|
|
styles += '100% {';
|
|
styles += 'height: 220px;';
|
|
styles += 'width: '+bounding_box.width+'px;';
|
|
styles += 'top: ' + bounding_box.y + 'px;';
|
|
styles += 'left: ' + bounding_box.x + 'px;';
|
|
styles += '}';
|
|
styles += '}';
|
|
|
|
/* Add keyframe to CSS */
|
|
$("#lightbox-animations").get(0).sheet.insertRule(styles, 0);
|
|
|
|
/* Hide the window scrollbar */
|
|
$("body").css('overflow', 'hidden');
|
|
});
|
|
|
|
/* Click on close button when full-screen */
|
|
$(".close").on('click', function(e) {
|
|
$(this).hide();
|
|
$(".container-inner").hide();
|
|
/* Window scrollbar normal */
|
|
$("body").css('overflow', 'auto');
|
|
|
|
var bounding_box = $(this).parent().get(0).getBoundingClientRect();
|
|
$(this).parent().css({ top: bounding_box.top + 'px', left: bounding_box.left + 'px' });
|
|
|
|
/* Show animation */
|
|
$(this).parent().addClass('out-animation');
|
|
|
|
e.stopPropagation();
|
|
});
|
|
|
|
/* On animationend : from normal to full screen & full screen to normal */
|
|
$(".column").on('animationend', function(e) {
|
|
/* On animation end from normal to full-screen */
|
|
if(e.originalEvent.animationName == 'inlightbox') {
|
|
$(this).children(".close").show();
|
|
$(this).children(".container-inner").show();
|
|
}
|
|
/* On animation end from full-screen to normal */
|
|
else if(e.originalEvent.animationName == 'outlightbox') {
|
|
/* Remove fixed positioning, remove animation rules */
|
|
$(this).removeClass('in-animation').removeClass('out-animation').removeClass('columnfade');
|
|
|
|
/* Remove the empty container that was earlier added */
|
|
$("#empty-container").remove();
|
|
|
|
/* Delete the dynamic keyframe rule that was earlier created */
|
|
$("#lightbox-animations").get(0).sheet.deleteRule(0);
|
|
}
|
|
});
|
|
</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> and by <a href="https://www.flaticon.com/authors/gregor-cresnar" title="Gregor Cresnar">Gregor Cresnar</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a></div>
|
|
</div>
|
|
<div style="margin:0;border:0;padding:0;width:100%;height:100%;" id="gridlayout">
|
|
</div>
|
|
<div id="controls_blank" style="display:none"><center><br /><b>Remote Control for OBS</b><br />
|
|
<button data-value="0" onclick="directEnable(this);">Add to Scene 1</button>
|
|
<button onclick="directMute(this);">Mute</button>
|
|
<br />Volume:<input type="range" min="1" max="100" value="100" onclick="directVolume(this);"><br />
|
|
<br /><hr /></center></div>
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|