-
diff --git a/main.css b/main.css
new file mode 100644
index 0000000..b698482
--- /dev/null
+++ b/main.css
@@ -0,0 +1,452 @@
+* {
+ padding: 0;
+ margin: 0;
+ box-sizing: border-box;
+
+}
+#mynetwork {
+ width: 600px;
+ height: 400px;
+ border: 1px solid lightgray;
+ }
+.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: 650px) {
+ 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: 650px) {
+ .outer {
+ width:50px;
+ }
+ .close{
+ top:0;
+ right:0;
+ }
+ select {
+ height:30px;
+ font-size:120%;
+
+ }
+
+ }
+
+
+ h2 {
+ color: white;
+ }
+
+
+ .inner {
+ width: inherit;
+ text-align: center;
+ }
+
+ .labelclass {
+ opacity: 0;
+ font-size: 1.1em;
+ line-height: 4em;
+ text-transform: uppercase;
+
+ transition: all .3s ease-in;
+ cursor: pointer;
+ }
+
+ label {
+ color: #000;
+ }
+
+
+ .inner:before, .inner:after {
+ position: absolute;
+ content: '';
+ height: 7px;
+ width: inherit;
+ background: #000;
+ left: 0;
+ font-weight: bold;
+ transition: all .3s ease-in;
+ }
+
+ .inner:before {
+ top: 50%;
+ transform: rotate(45deg);
+ }
+
+ .inner:after {
+ bottom: 50%;
+ transform: rotate(-45deg);
+ }
+
+ .outer:hover .labelclass {
+ 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: 650px) {
+ .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)
+
+}
+
+#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;
+ background-color: rgb(221, 221, 221);
+}
+
+.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;
+}
+
+
+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;
+ }
+}
+
+#gridlayout {
+ margin:0;
+ border:0;
+ padding:0;
+ width:100%;
+ height:100%;
+}
+
diff --git a/main.js b/main.js
new file mode 100644
index 0000000..b51eb45
--- /dev/null
+++ b/main.js
@@ -0,0 +1,1046 @@
+/////////////
+
+// 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);
+
+/////////////
+
+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);
+ document.getElementById("container-1").className = 'column columnfade advanced';
+ document.getElementById("container-4").className = 'column columnfade advanced';
+}
+if (urlParams.has('stereo')){
+ log("STEREO ENABLED");
+ session.stereo = true;
+}
+
+if (urlParams.has('codec')){
+ log("CODEC CHANGED");
+ session.codec = urlParams.get('codec');
+}
+
+if (urlParams.has('bitrate')){
+ session.bitrate = parseInt(urlParams.get('bitrate'));
+ log("BITRATE ENABLED");
+ log(session.bitrate);
+}
+
+if (urlParams.has('height')){
+ session.height = parseInt(urlParams.get('height'));
+}
+
+if (urlParams.has('width')){
+ session.width = parseInt(urlParams.get('width'));
+}
+
+if (urlParams.has('framerate')){
+ session.framerate = parseInt(urlParams.get('framerate'));
+ log("framerate Changed");
+ log(session.framerate);
+}
+
+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 updateURL(param) {
+ var para = param.split('=')[0];
+ if (!(urlParams.has(para))){
+
+ if (history.pushState){
+
+ var arr = window.location.href.split('?');
+ if (arr.length > 1 && arr[1] !== '') {
+ var newurl = window.location.href + '&' +param;
+ } else {
+ var newurl = window.location.href + '?' +param;
+ }
+
+
+ window.history.pushState({path:newurl},'',newurl);
+ }
+ }
+}
+
+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";
+ document.getElementById("head3").className = 'advanced';
+ 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";
+ window.addEventListener("resize", updateMixer);
+ 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 updateStats(){
+ log('resolution found');
+ //log(document.getElementById('previewWebcam').videoWidth|0);
+ //log(document.getElementById('previewWebcam').videoHeight|0);
+ document.getElementById('previewWebcam').srcObject.getVideoTracks().forEach(
+ function(track) {
+ log(track.getSettings());
+ log(track.getSettings().frameRate);
+ //log(track.getSettings().frameRate);
+ document.getElementById("webcamstats").innerHTML = "Current Video Settings: "+(track.getSettings().width|0) +"x"+(track.getSettings().height|0)+"@"+(parseInt(track.getSettings().frameRate*10)/10)+"fps";
+ }
+ );
+}
+
+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 (session.width){
+ width = {ideal: session.width};
+ }
+ if (session.height){
+ height = {ideal: session.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.framerate){
+ constraints.video.frameRate = {exact: session.framerate};
+ }
+
+ if (session.roomid){
+ window.addEventListener("resize", updateMixer);
+ joinRoom(session.roomid,100);
+ document.getElementById("head3").className = 'advanced';
+ //updateURL("permaid="+session.streamID);
+ } else {
+ document.getElementById("head3").className = '';
+ }
+ updateURL("permaid="+session.streamID);
+ 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){
+ window.addEventListener("resize", updateMixer);
+ joinRoom(session.roomid,100);
+ document.getElementById("head3").className = 'advanced';
+ //updateURL("permaid="+session.streamID);
+ } else {
+ document.getElementById("head3").className = '';
+ }
+ updateURL("permaid="+session.streamID);
+ session.publishStream(stream, title);
+ log("streamID is: "+session.streamID);
+ document.getElementById("head1").className = 'advanced';
+ document.getElementById("head2").className = 'advanced';
+
+ 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){ // callback from server; we've joined the room
+ 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 = ""; // TODO: Assign labels
+ //if ("title" in response[i]){
+ // title = response[i]["title"];
+ //}
+ 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 = " Group Chat Invite Link: Give this link to your guests --> https://"+location.hostname+location.pathname+"?roomid="+session.roomid+"\
+ ";
+
+ gridlayout.innerHTML += " Add OBS VirtualCam Output Link--> https://"+location.hostname+location.pathname+"?roomid="+session.roomid+"&streamid\
+ ";
+
+ gridlayout.innerHTML += "
\
+ From here, using the OBS PULL links provided below each video that appears, you can create individual video elements within OBS. \
+ You can also use the following Scene 1 link to crete a single element in OBS that comprises all the video elements and automixes them for you\
+
";
+
+ gridlayout.innerHTML += "Optional Scene 1 link for OBS. This will auto-mix for you, if you prefer that instead.https://"+location.hostname+location.pathname+"?scene=1&roomid="+session.roomid+" ";
+
+ joinRoom(roomname,100);
+
+}
+
+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: 1920, max: 1920 },
+ height: { min: 360, ideal: 1080, 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: 360, ideal: 1280, max: 1280 },
+ height: { min: 360, ideal: 720, max: 720 }
+ };
+ } else {
+ return {
+ width: { min: 720, ideal: 1280, max: 1280 },
+ height: { min: 720, ideal: 720, max: 1280 }
+ };
+ }
+ case 2:
+ if (isSafariBrowser) {
+ return {
+ width: { min: 360, ideal: 1280, max: 1440 },
+ };
+ }
+ else {
+ return {
+ width: { min: 360, ideal: 1280, max: 1440 },
+ };
+ }
+ case 3:
+ if (isSafariBrowser) {
+ return {
+ width: { min: 640 },
+ height: { min: 360 }
+ };
+ } else {
+ return {
+ width: { min: 240, ideal: 640, max: 1280 },
+ height: { min: 240, ideal: 360, max: 1280 }
+ };
+ }
+
+ case 4:
+ if (isSafariBrowser) {
+ return {
+ height: { min: 360, ideal: 720, max: 960 }
+ };
+ }
+ else {
+ return {
+ height: { min: 360, ideal: 960, max: 960 }
+ };
+ }
+ 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 (session.width){
+ constraints.video.width = {exact: session.width};
+ }
+ if (session.height){
+ constraints.video.height = {exact: session.height};
+ }
+ if (session.framerate){
+ constraints.video.frameRate = {exact: session.framerate};
+ } else if (session.maxframerate){
+ constraints.video.frameRate = {max: session.maxframerate};
+ }
+
+ 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 make sure it is not already in use by another application.");
+ }
+ });
+ },0);
+ }
+
+ var activatedPreview = false;
+ function previewWebcam(){
+ if( activatedPreview == true){log("activeated preview return");return;}
+ activatedPreview = true;
+
+ window.setTimeout(() => {
+
+ var oldstream= document.getElementById('previewWebcam').srcObject;
+ if (oldstream){
+ oldstream.getTracks().forEach(function(track) {
+ track.stop();
+ });
+ }
+
+ navigator.mediaDevices.getUserMedia({audio:true, video:true }).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(){
+
+ if (parseInt(document.getElementById("webcamquality").elements.namedItem("resolution").value)==3){
+ session.maxframerate = 30;
+ } else {
+ session.maxframerate = false;
+ }
+
+ var audioSelect = document.querySelector('select#audioSource');
+ var videoSelect = document.querySelector('select#videoSource');
+
+ audioSelect.onchange = function(){
+ log("AUDIO source CHANGED");
+ activatedPreview=false;
+ grabVideo(parseInt(document.getElementById("webcamquality").elements.namedItem("resolution").value));
+ };
+ videoSelect.onchange = function(){
+ log("video source changed");
+ activatedPreview=false;
+ grabVideo(parseInt(document.getElementById("webcamquality").elements.namedItem("resolution").value));
+ };
+ document.getElementById("webcamquality").onchange = function(){
+ log("AUDIO source CHANGED");
+ activatedPreview=false;
+ if (parseInt(document.getElementById("webcamquality").elements.namedItem("resolution").value)==3){
+ session.maxframerate = 30;
+ } else {
+ session.maxframerate = false;
+ }
+ grabVideo(parseInt(document.getElementById("webcamquality").elements.namedItem("resolution").value));
+ };
+
+
+ activatedPreview = false;
+ grabVideo(parseInt(document.getElementById("webcamquality").elements.namedItem("resolution").value));
+
+ }).catch(handleError);
+ }).catch(handleError);
+
+ },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=""+streamID[2]+" - "+streamID[0]+" seeders ";
+ });
+ },function(error){return {}});
+}
+
+function generateQRPage(ele){
+ try{
+ var sid = session.generateStreamID();
+ ele.parentNode.innerHTML = '
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. \
+
\
+ 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 = '
Attempting to load video stream.
';
+
+ setTimeout(function(){
+ try{
+ if (urlParams.get("streamid")){
+ if (document.getElementById("mainmenu")){
+ document.getElementById("mainmenu").innerHTML += '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 https://reddit.com/r/obsninja for possible solutions or contact steve@seguin.email.