diff --git a/index.html b/index.html index 2524c3c..abfd6d4 100644 --- a/index.html +++ b/index.html @@ -5,454 +5,17 @@ - - + + - - + + - +
-   Copy this URL into an OBS "Browser Source" =>   +   Copy this URL into an OBS "Browser Source" =>     
@@ -501,10 +64,9 @@ video {
- +
-
@@ -513,17 +75,28 @@ video {

Select the audio/video source below and when you're ready just click START SHARING WEBCAM




-

-
+

+

Video source:


+ +
+ + | + + | + + +
+

+

Audio source:

+
- +
-
@@ -536,10 +109,9 @@ video {
- +
-
@@ -547,11 +119,22 @@ video {




+
+
+ + + | + + | + + +
+
- +
@@ -576,7 +159,8 @@ video {
  • ** The rear camera on some smartphones have issues. Please report these issues, including your phone's model.
  • ** For some users the video fails to load into OBS; this is often caused by a network firewall.

  • -
  • April 7th, 2020: Site updated. The previous version can be found at https://obs.ninja/old/

  • +
  • April 7th, 2020: Site updated. The previous version can be found at https://obs.ninja/old/
  • +

    Check out the sub-reddit for help and advanced info. Or email me steve@seguin.email

    @@ -585,1027 +169,21 @@ video {
    - - + + + +
    Icons made by Lucy G from www.flaticon.com is licensed by CC 3.0 BY and by Gregor Cresnar from www.flaticon.com
    -
    +
    + - - 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 = '
    \ +

    and don\'t forget the

    OBS Link:

    https://' + location.hostname + location.pathname + '?streamid=' + sid + '


    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.

    '; + + document.getElementById("mainmenu").innerHTML += ''; + 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"; +} +} + +function updateMixer(){ + //log("update mixer"); + var playarea = document.getElementById("gridlayout"); + + //log(session.mediaPool); + + var header = document.getElementById("header"); + + var hi = header.offsetHeight ; + //log("Header:"+hi); + var w = window.innerWidth; + var h = window.innerHeight - hi; + var ww = w/16; + var hh = h/9; + //log("window.innerWidth"+window.innerWidth+" "+"window.innerHeight"+window.innerHeight); + + var mediaPool = []; + + if (session.videoElement){ + if (session.videoElement.style.display!="none"){ + mediaPool.push(session.videoElement); + } + } + + for (i in session.rpcs){ + if (session.rpcs[i].videoElement){ + if (session.rpcs[i].videoElement.style.display!="none"){ + mediaPool.push(session.rpcs[i].videoElement); + } + } + }; + + + if (mediaPool.length>1){ + var mod = Math.pow((ww*hh)/(mediaPool.length),0.5) // 80 + var rw = Math.ceil(ww/mod); // 80/80 + var rh = Math.ceil(hh/mod); + } else { rw=1; rh=1;} + + //log(mod+","+rw+","+rh); //80,1,1 + playarea.innerHTML = ""; + var i=0; + var offset = 0; + + //var xhh = h/rh*9/16; + + mediaPool.forEach(vid=>{ + + vid.style.position = "absolute"; + vid.display = "block"; + + offsetx=0; + if (Math.ceil((i+0.1)/rw)==rh){ + offsetx = (window.innerWidth- (rw - mediaPool.length%rh)*Math.ceil(window.innerWidth/rw))/2; + }// else { + //console.error((i+0.1)/rw,rh); + //} + + + + + offsety = (h- Math.ceil(mediaPool.length/rw)*Math.ceil(h/rh))/2; + + + vid.style.left = offsetx+Math.floor(((i%rw)+0)*w/rw)+"px"; + //vid.style.left = Math.floor(((i%rw)+0)*w/rw)+"px"; + + + vid.style.top = offsety+Math.floor((Math.floor(i/rw)+0)*h/rh + hi)+"px"; + + vid.style.width = Math.ceil(w/rw)+"px"; + vid.style.height = Math.ceil(h/rh)+"px"; + playarea.appendChild(vid); + i+=1; + }); +} + +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; + try{ + 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; + } catch(e){ + url += '&layer-width=1280'; // this isn't always 100% correct, as the resolution can fluxuate, but it is probably good enough + url += '&layer-height=720'; + } + 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]; + } +})(); diff --git a/template.js b/template.js new file mode 100644 index 0000000..9b0e421 --- /dev/null +++ b/template.js @@ -0,0 +1,82 @@ + +/* 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(''); + +/* 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(); +$('
    ').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); +} +}); \ No newline at end of file