vdo.ninja/mixer.html

4192 lines
137 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<html>
<head>
<title>Mixer app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<script src="./thirdparty/aes.js"></script>
<script src="./thirdparty/jquery/jquery-3.6.0.js?asdf"></script>
<script src="./thirdparty/jquery/jquery-ui.js"></script>
<link rel="stylesheet" href="./thirdparty/jquery/jquery-ui.css">
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" />
<link rel="icon" type="image/png" sizes="32x32" href="./media/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="./media/favicon-16x16.png" />
<link rel="icon" href="./media/favicon.ico" />
<link itemprop="thumbnailUrl" href="./media/vdoNinja_logo_full.png" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Sora:wght@200;400;700&display=swap" rel="stylesheet">
<style>
:root{
--aspect-ratio: 1.7777777777;
--chat-width: 450px;
--iframe-width: 1280px;
--iframe-height: 720px;
}
body {
padding:0;
margin:0;
background-color: #c9c9c9;
font-family: 'Sora', sans-serif;
overflow: auto;
position: absolute;
border-radius: 50px;
background: #2e445c;
box-shadow: 20px 20px 60px #273a4e, -20px -20px 60px #354e6a;
scrollbar-color:#666 #201c29;
}
iframe {
border: 0;
padding: 0;
display: block;
height: 100%;
width: var(--iframe-width);
max-height: calc(100vh - 92px);
background-color: #0002;
border-radius: 3px;
position: absolute;
left: 0;
top: 0;
transition: background-color 0.05s ease-out;
}
iframe.aspectRatio{
max-height: min(calc(100vh - 92px), calc(100vw - 160px - var(--chat-width)) / var(--aspect-ratio-widget)) !important;
max-width: min(calc((100vh - 92px) * var(--aspect-ratio-widget)), calc(100vw - 160px - var(--chat-width))) !important;
height: var(--iframe-height) !important;
width: var(--iframe-width) !important;
}
.gone {
position:absolute;
top: -150px;
}
.modal{
overflow:auto;
}
.message{
background: #3e3e3e00;
color: #FCFCFC;
vertical-align: top;
border: 1px solid #2e445c;
border-radius: 10px;
background: #2e445c;
box-shadow: 5px 5px 10px #121620, -5px -5px 10px #162a36;
}
.inMessage {
color: #000;
margin: 3px;
border-radius: 5px;
background: #FFF;
padding: 5px;
text-align: left;
}
.actionMessage {
color: #000;
margin: 3px;
border-radius: 5px;
background: #FFF;
padding: 5px;
text-align: left;
}
.outMessage {
color: #000;
margin: 3px;
border-radius: 5px;
background: #BCF;
padding: 5px;
text-align: right;
}
#chatBody {
background-color: #0004;
margin: 0 10px 10px 0;
border-radius: 5px;
overflow-y:scroll;
overflow-wrap: anywhere;
bottom: 45px;
position: absolute;
}
.ui-widget-content {
border-left:0;
border-top:0;
}
#chatBody::-webkit-scrollbar {
width: 0px;
background: transparent; /* make scrollbar transparent */
}
.xbutton{
float: right;
cursor: pointer;
user-select: none;
color: white;
font-size: 150%;
font-family: Verdana;
line-height: 14px;
}
#sceneSettings{
font-size: 80%;
}
#chatModule {
bottom: 0;
position: fixed;
margin: 10px;
align-self: center;
width: 400px;
max-width: 100%;
z-index:3;
height: calc(100vh - 40px);
overflow: hidden;
right:0;
background:#0001;
border: solid 2px #0005;
border-radius: 10px;
padding: 10px;
transition: all .05s ease-in-out;
}
#chatInput {
display: inline-block;
color: #000;
background-color: #FFFE;
width: 324px;
font-size: 105%;
margin-left: 7px;
}
.part0{
display:inline-block;
cursor:default;
}
.part{
display:inline-block;
cursor:pointer;
}
.part:hover{
text-shadow: 0 0 black;
}
.dimensions{
cursor:default;
background-color: #FFF9;
z-index:1;
}
#chatSendBar{
display: inline-block;
bottom: 0px;
position:absolute;
}
#savedroompassword{
width:50px;
}
button[data-state='true']{
background-color:#CEF !important;
}
input[type="checkbox"] {
width: 20px;
height: 20px;
text-align: center;
vertical-align: middle;
transition: all .2s ease-in-out;
}
input[type="checkbox"]:checked {
transform: scale(1.1);
}
label {
margin: 0 0px 6px 0;
display: inline-block;
font-weight: 600;
}
.ui-widget-header{
background: rgb(225,225,225); /* Old browsers */
background: -moz-linear-gradient(-45deg, rgba(255,255,255,1) 0%, rgba(241,241,241,1) 50%, rgba(225,225,225,1) 51%, rgba(246,246,246,1) 100%); /* FF3.6-15 */
background: -webkit-linear-gradient(-45deg, rgba(255,255,255,1) 0%,rgba(241,241,241,1) 50%,rgba(225,225,225,1) 51%,rgba(246,246,246,1) 100%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(135deg, rgba(255,255,255,1) 0%,rgba(241,241,241,1) 50%,rgba(225,225,225,1) 51%,rgba(246,246,246,1) 100%);
}
#containerMenu{
overflow-x: auto;
overflow-y: hidden;
position: absolute;
left: 160px;
width: calc(100vw - var(--chat-width) - 160px);
display: flex;
top: 0;
height: 92px;
}
#iframeContainer{
position: absolute;
left: 160px;
box-shadow: 1px 1px 3px #1b1b1b, -1px -1px 3px #1d1d1d;
}
#vdoninja {
max-width: calc(100vw - 160px - var(--chat-width));
width: 100vw;
height: calc(100vh - 90px);
}
#viewlink {
width:400px;
}
#container {
display:block;
padding:0px;
background-color: #e5e5e5;
}
button[data-state='true']{
background-color:#CEF !important;
}
.disconnected{
border: 4px dotted red!important;
padding: 6px 0.25em 0.25em 0.25em!important;
outline: dashed 2px black!important;
}
.thing:hover{
animation: horizontalShake 2s;
animation-iteration-count: 1;
}
#col1>.thing:hover{
animation: enlargeAnimation 2s;
animation-iteration-count: 1;
}
#col1>.thing:active{
transform: scale(1.05);
animation:none;
}
.thing:active{
animation:none;
transform: translate(7px, 0px) rotate(0deg);
}
#delete:hover{
animation: none!important;
}
#delete:active{
transform: none!important;
}
@keyframes enlargeAnimation {
0% { transform: scale(1.01); }
20% { transform: scale(1.03); }
80% { transform: scale(1.05); }
100% { transform: scale(1.06); }
}
@keyframes horizontalShake {
0% { transform: translate(3px, 0px) rotate(0deg); }
20% { transform: translate(7px, 0px) rotate(0deg); }
80% { transform: translate(8px, 0px) rotate(0deg); }
100% { transform: translate(-1px, 0px) rotate(0deg); }
}
.shake {
animation: shake 0.5s;
animation-iteration-count: 1;
}
@keyframes shake {
0% { transform: translate(1px, 1px) rotate(0deg); }
10% { transform: translate(-1px, -2px) rotate(-1deg); }
20% { transform: translate(-3px, 0px) rotate(1deg); }
30% { transform: translate(3px, 2px) rotate(0deg); }
40% { transform: translate(1px, -1px) rotate(1deg); }
50% { transform: translate(-1px, 2px) rotate(-1deg); }
60% { transform: translate(-3px, 1px) rotate(0deg); }
70% { transform: translate(3px, 1px) rotate(-1deg); }
80% { transform: translate(-1px, -1px) rotate(1deg); }
90% { transform: translate(1px, 2px) rotate(0deg); }
100% { transform: translate(1px, -2px) rotate(-1deg); }
}
input{
padding:5px;
margin:5px;
border-radius: 3px;
}
.menuButton{
width: 92%;
}
button{
user-select: none;
padding:5px;
margin:5px;
border-radius: 3px;
cursor:pointer;
background: linear-gradient(135deg, rgba(238,238,238,1) 60%,rgb(225, 225, 225, 1) 80%,rgb(210, 209, 209, 1) 100%);
}
canvas{
padding:10px;
cursor:pointer;
border-radius: 10px;
box-shadow: 2px 2px 6px #273a4e, -2px -2px 6px #354e6a;
}
h2 {
text-shadow: 0 0 2px #a7a7a7;
color: #000b3a;
user-select: none;
}
.inline{
display:inline-block;
}
table {
font-size: 1em;
}
::-webkit-scrollbar {
width: 15px;
height: 15px;
}
::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 13px rgb(0 0 0 / 90%);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
border-radius: 4px;
-webkit-box-shadow: inset 0 0 16px rgb(150 150 150 / 100%);
border: solid 3px transparent;
}
.ui-draggable, .ui-droppable {
background-position: top;
}
#containerMenu2 {
width: calc(100vw - 300px);
position: absolute;
left: 155px;
margin: 20px 0;
top: 60px;
}
.tFadeOut{
animation-duration: 1s;
animation-name: tFadeOut;
transition: top 1s;
top: 92px;
}
.tFadeIn{
animation-duration: 1s;
animation-name: tFadeIn;
transition: top 1s;
top: 122px;
}
.tFadeOut{
animation-duration: 1s;
animation-name: tFadeOut;
transition: top 1s;
top: 92px;
}
.tFadeStart{
top: 92px;
}
@keyframes tFadeOut {
from {
top: 122px;
}
to {
top: 92px;
}
}
@keyframes tFadeIn {
from {
top: 92px;
}
to {
top: 122px;
}
}
.hFadeOut{
overflow:hidden;
animation-duration: 1s;
animation-name: hFadeOut;
transition: height 1s;
height:0;
}
@keyframes hFadeOut {
from {
height: 43px;
}
to {
height: 0;
}
}
.hFadeIn{
overflow:hidden;
animation-duration: 1s;
animation-name: hFadeIn;
transition: height 1s;
height:43px;
}
@keyframes hFadeIn {
from {
height: 0;
}
to {
height: 43px;
}
}
.draggable { width: 150px; height: 150px; padding: 0; margin:0; border:0; }
.resizable { width: 150px; height: 150px; padding: 0; margin:0; border:0; }
.resizable h3 { text-align: center; margin: 0; cursor: grab; border: 1px solid black;}
.ui-menu { width: 150px; }
.ui-state-selected {
background-color: #64b1ff;
}
.ui-state-disabled {
background-color: grey;
}
.widget {
background-color: #DDD;
position: absolute;
width:100%;
height:100%;
}
#canvas{
background-color: #000;
width: var(--iframe-width);
height: var(--iframe-height);
margin:0;
padding:0;
border:0;
display: inline-block;
}
h3 {
margin-bottom: 6px;
}
.settings {
display: block;
background: #c0e3ff;
position: absolute;
top: 100px;
left: 100px;
z-Index:20;
}
.hidden {
display:none!important;
}
.fadeout {
visibility: hidden;
opacity: 0;
transition: visibility 0s 0.5s, opacity 0.5s linear;
}
.fade2black{
background-color: 000F;
transition: background-color 0.5s linear;
}
.hidden2{
display:none!important;
}
.hidden3{
display:none!important;
}
.thing {
width: 100px;
padding: 10px 0.5em 0.5em 0.5em;
margin: 6px;
border: #565656 solid 1px;
background: rgba(0,0,0,0.8);
color: white;
font-family: sans-serif;
cursor: grab;
text-align: center;
border-radius: 6px;
word-wrap: break-word;
box-shadow: inset 6px 6px 6px #dadada1c, inset -6px -6px 6px #27272724;
}
.empty {
width: 100px;
padding: 10px 0.5em 0 0.5em;
margin: 0.5em 0.4em;
background: rgba(0,0,0,0.8);
color: white;
font-family: sans-serif;
user-select: none;
text-align: center;
border-radius: 6px;
height: 1.7em;
cursor: crosshair;
border: 1px solid black;
}
.col {
width: 130px;
height: calc(100vh - 20px);
padding: 5px;
border: 1px solid;
border-radius: 5px;
position: relative;
float: left;
user-select: none;
}
.pressed>canvas{
box-shadow: inset 2px 2px 10px #0007, inset -2px -2px 10px #0007;
background-color: #FFFA;
}
.pressed>.group{
box-shadow: inset 2px 2px 10px #0007, inset -2px -2px 10px #0007;
background-color: #276022aa;
}
button.pressed {
background-color: #CEF;
}
.editButton{
display:none;
position: absolute;
z-index: 2;
padding: 6px 0;
width: 28px;
margin: 2px;
height: 28px;
line-height: 0px;
border-radius: 14px;
}
.editButton[data-state="active"]{
display:block!important;
background: #defffd;
border: #166b49 solid 2px;
}
.setButton{
display:none;
position:absolute;
margin: 20px 57px;
z-index:2;
}
.canvasContainer{
display:inline-block;
}
.canvasContainer>canvas{
transform: scale(calc( var(--aspect-ratio) / (16 / 9)), 1);
}
.canvasContainer:hover>canvas{
box-shadow: 0 0 6px #273a4e, 0 0px 6px #fffC;
}
.canvasContainer:hover>button{
display:inline-block;
padding: 3.2px;
opacity: 80%;
}
.canvasContainer>button:hover{
display:inline-block;
padding: 3.2px;
opacity: 100!important;
box-shadow: 2px 2px 2px #918c8c7c, -2px -2px 2px #27272774
}
b {
text-shadow: 0 0 1px #f4f4f4;
}
a {
display: inline-block;
margin: 5px;
background-color: #c9c9c9;
border: 2px solid black;
padding: 4px;
border-radius: 6px;
cursor:pointer;
}
[title]{
cursor: help;
}
#delete {
background-color: rgb(191 191 191);
text-align: center;
border: 1px solid black;
color: black;
cursor: crosshair;
margin: 5.5px;
border-radius: 0px;
display:none;
}
.modal {
position: fixed;
padding-top: 50px;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgb(0, 0, 0);
background-color: rgba(0, 0, 0, 0.5);
z-Index: 20;
}
.modal-content {
position: relative;
padding: 20px;
margin: auto;
margin-bottom: 100px;
width: 75%;
-webkit-animation-name: animatetop;
-webkit-animation-duration: 0.4s;
animation-name: animatetop;
animation-duration: 0.4s;
border-radius: 4px;
background-color: #e2e2e2;
background-image: url("data:image/svg+xml,%3Csvg width='6' height='6' viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%239C92AC' fill-opacity='0.1' fill-rule='evenodd'%3E%3Cpath d='M5 0h1L0 6V5zM6 5v1H5z'/%3E%3C/g%3E%3C/svg%3E");
}
.close-btn {
color: #333;
font-size: 42px;
font-weight: bold;
user-select: none;
}
.close-btn:hover {
color: black;
cursor:pointer;
}
span.close-btn {
float: right;
}
@-webkit-keyframes animatetop {
from {top:-300px; opacity:0}
to {top:0; opacity:1}
}
@keyframes animatetop {
from {top:-300px; opacity:0}
to {top:0; opacity:1}
}
#welcomeWindow{
display:none;
position:absolute;
top:0;
left:0;
width:100vw;
height:100vh;
z-index:5;
background: #2775dc;
box-shadow: 20px 20px 60px #51729d,
-20px -20px 60px #6d9ad5;
background: -moz-linear-gradient(-45deg, rgba(59,103,158,1) 2%, rgba(43,136,217,1) 50%, rgba(32,124,202,1) 79%, rgba(89, 165, 224,1) 100%); /* FF3.6-15 */
background: -webkit-linear-gradient(-45deg, rgba(59,103,158,1) 2%,rgba(56, 134, 202,1) 50%,rgba(32,124,202,1) 79%,rgba(89, 165, 224,1) 100%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(135deg, rgba(59,103,158,1) 2%,rgba(56, 134, 202,1) 50%,rgba(32,124,202,1) 79%,rgba(89, 165, 224,1) 100%);
background: linear-gradient(135deg, rgba(59,103,158,1) 2%,rgba(56, 134, 202,1) 50%,rgba(32,124,202,0.8) 79%,rgba(89, 165, 224,1) 100%), url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='500' height='500'%3E%3Cfilter id='noise' x='0' y='0'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3CfeBlend mode='screen'/%3E%3C/filter%3E%3Crect width='500' height='500' filter='url(%23noise)' opacity='0.5'/%3E%3C/svg%3E");
}
.center-content{
align-content: center;
margin: 20px auto;
display: block;
width: 500px;
max-width: 100%;
}
.footer {
bottom: 0;
display: inline-block;
vertical-align: middle;
margin: 5px 20px;
height: 28px;
display: flex;
align-items: center;
position: fixed;
right: 0;
}
.footer>div{
align-items: center;
}
.popup-message {
display: none;
align-text: center;
position: absolute;
z-index: 35 !important;
padding: 5px !important;
border-radius: 3px;
min-width: 180px !important;
background-color: #fff !important;
border: solid 1px #dfdfdf !important;
box-shadow: 1px 1px 2px #cfcfcf !important;
}
.context-menu--active {
display: block !important;
}
.context-menu__items {
list-style: none !important;
margin: 0;
padding: 0;
}
.context-menu__item {
display: block;
margin-bottom: 4px !important;
}
.context-menu__item:last-child {
margin-bottom: 0 !important;
}
.context-menu__link {
display: block;
padding: 4px 12px;
color: #0066aa !important;
text-decoration: none;
}
button.menuButtons{
background-color: #b4c5ca !important;
background: linear-gradient(135deg, #c2d2d7 60%,#c7d3d7 80%, #a3b5ba 100%)
}
.context-menu__link:hover {
color: #fff !important;
background-color: #0066aa !important;
}
.discord{
background-image: url("data:image/svg+xml,%3Csvg width='71' height='55' viewBox='0 0 71 55' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0)'%3E%3Cpath d='M60.1045 4.8978C55.5792 2.8214 50.7265 1.2916 45.6527 0.41542C45.5603 0.39851 45.468 0.440769 45.4204 0.525289C44.7963 1.6353 44.105 3.0834 43.6209 4.2216C38.1637 3.4046 32.7345 3.4046 27.3892 4.2216C26.905 3.0581 26.1886 1.6353 25.5617 0.525289C25.5141 0.443589 25.4218 0.40133 25.3294 0.41542C20.2584 1.2888 15.4057 2.8186 10.8776 4.8978C10.8384 4.9147 10.8048 4.9429 10.7825 4.9795C1.57795 18.7309 -0.943561 32.1443 0.293408 45.3914C0.299005 45.4562 0.335386 45.5182 0.385761 45.5576C6.45866 50.0174 12.3413 52.7249 18.1147 54.5195C18.2071 54.5477 18.305 54.5139 18.3638 54.4378C19.7295 52.5728 20.9469 50.6063 21.9907 48.5383C22.0523 48.4172 21.9935 48.2735 21.8676 48.2256C19.9366 47.4931 18.0979 46.6 16.3292 45.5858C16.1893 45.5041 16.1781 45.304 16.3068 45.2082C16.679 44.9293 17.0513 44.6391 17.4067 44.3461C17.471 44.2926 17.5606 44.2813 17.6362 44.3151C29.2558 49.6202 41.8354 49.6202 53.3179 44.3151C53.3935 44.2785 53.4831 44.2898 53.5502 44.3433C53.9057 44.6363 54.2779 44.9293 54.6529 45.2082C54.7816 45.304 54.7732 45.5041 54.6333 45.5858C52.8646 46.6197 51.0259 47.4931 49.0921 48.2228C48.9662 48.2707 48.9102 48.4172 48.9718 48.5383C50.038 50.6034 51.2554 52.5699 52.5959 54.435C52.6519 54.5139 52.7526 54.5477 52.845 54.5195C58.6464 52.7249 64.529 50.0174 70.6019 45.5576C70.6551 45.5182 70.6887 45.459 70.6943 45.3942C72.1747 30.0791 68.2147 16.7757 60.1968 4.9823C60.1772 4.9429 60.1437 4.9147 60.1045 4.8978ZM23.7259 37.3253C20.2276 37.3253 17.3451 34.1136 17.3451 30.1693C17.3451 26.225 20.1717 23.0133 23.7259 23.0133C27.308 23.0133 30.1626 26.2532 30.1066 30.1693C30.1066 34.1136 27.28 37.3253 23.7259 37.3253ZM47.3178 37.3253C43.8196 37.3253 40.9371 34.1136 40.9371 30.1693C40.9371 26.225 43.7636 23.0133 47.3178 23.0133C50.9 23.0133 53.7545 26.2532 53.6986 30.1693C53.6986 34.1136 50.9 37.3253 47.3178 37.3253Z' fill='%23ffffff'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0'%3E%3Crect width='71' height='55' fill='white'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E");
background-size: contain;
background-repeat: no-repeat;
width:14px;
height:10px;
border:0;
background-color:#0000;
}
.github {
background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z'/%3E%3C/svg%3E");
background-size: contain;
width:10px;
height:10px;
border:0;
background-color:#0000;
}
#containermenu>div:nth-child(1)>div::before {
content: "0";
color: #a4a4a4;
}
#containermenu>div:nth-child(2)>div::before {
content: "1";
color: #a4a4a4;
}
#containermenu>div:nth-child(3)>div::before {
content: "2";
color: #a4a4a4;
}
#containermenu>div:nth-child(4)>div::before {
content: "3";
color: #a4a4a4;
}
#containermenu>div:nth-child(5)>div::before {
content: "4";
color: #a4a4a4;
}
#containermenu>div:nth-child(6)>div::before {
content: "5";
color: #a4a4a4;
}
#containermenu>div:nth-child(7)>div::before {
content: "6";
color: #a4a4a4;
}
#containermenu>div:nth-child(8)>div::before {
content: "7";
color: #a4a4a4;
}
#containermenu>div:nth-child(9)>div::before {
content: "8";
color: #a4a4a4;
}
#containermenu>div:nth-child(10)>div::before {
content: "9";
color: #a4a4a4;
}
#containermenu>div>div {
width: 15px;
height: 15px;
border-radius: 10px;
margin: 0 auto;
color: #a4a4a4;
position: relative;
left: 4px;
font-size: 70%;
cursor:help;
}
.tooltip {
z-index: 100;
}
.tooltip .tooltiptext {
visibility: hidden;
background-color: #333;
color: #fff;
text-align: center;
border-radius: 3px;
padding: 3px;
overflow: auto;
margin: 0; position:relative;
font-family: "Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", Times, Symbola, Aegyptus,Code2000, Code2001, Code2002, Musica, serif, LastResort;
}
.tooltip:hover .tooltiptext {
visibility: visible;
}
.randomRoomName{
width: 24px;
height: 24px;
background-size: contain;
background-repeat: no-repeat;
border-radius: 5px;
background: rgb(238,238,238);
background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 29 29'%3E%3Cpath d='M18 9v-3c-1 0-3.308-.188-4.506 2.216l-4.218 8.461c-1.015 2.036-3.094 3.323-5.37 3.323h-3.906v-2h3.906c1.517 0 2.903-.858 3.58-2.216l4.218-8.461c1.356-2.721 3.674-3.323 6.296-3.323v-3l6 4-6 4zm-9.463 1.324l1.117-2.242c-1.235-2.479-2.899-4.082-5.748-4.082h-3.906v2h3.906c2.872 0 3.644 2.343 4.631 4.324zm15.463 8.676l-6-4v3c-3.78 0-4.019-1.238-5.556-4.322l-1.118 2.241c1.021 2.049 2.1 4.081 6.674 4.081v3l6-4z'/%3E%3C/svg%3E"), linear-gradient(135deg, rgba(238,238,238,1) 0%,rgba(204,204,204,1) 100%);
}
#demoDrop{
background-color: #30892c;
cursor: help;
display:none;
}
.demoThing{
width: 100px;
padding: 10px 0.5em 0.5em 0.5em;
margin: 6px;
border: #565656 solid 1px;
background: rgba(0,0,0,0.8);
color: white;
font-family: sans-serif;
cursor: grab;
text-align: center;
border-radius: 6px;
word-wrap: break-word;
box-shadow: inset 6px 6px 6px #dadada1c, inset -6px -6px 6px #27272724;
display:none;
}
#sendChat{
bottom: 1px;
position: relative;
}
</style>
</head>
<body>
<div class="col" id="sources">
<div id="col2" ondrop="dropRemove(event)" ondragover="allowDrop(event)" ondragenter="dragenter(event)" ondragleave="dragleave(event)">
<div class="demoThing" draggable="false" id="demoDrop" title="Drag stream IDs as guest join to a corresponding SLOT, then active the desired layout.">Streams IDs appearing above are draggable</div>
<div class="thing" draggable="false" id="delete" title="Dragging stream IDs here will either remove them from a SLOT or delete them all together.">Remove</div>
</div>
</div>
<div class="col hidden" id="col1" ondrop="drop(event)" ondragover="allowDrop(event)" ondragenter="dragenter(event)" title="Drag a stream ID here, to the desired slot, then activate the desired layout" ondragleave="dragleave(event)">
<div class="empty" data-slot="1" >SLOT 1</div>
<div class="empty" data-slot="2">SLOT 2</div>
<div class="empty" data-slot="3">SLOT 3</div>
<div class="empty" data-slot="4">SLOT 4</div>
</div>
<div id="container">
<div id="containermenu"></div>
<div id="containermenu2" class="hidden">
<button onclick="addElement();"> Add Element to Scene</button>
<button onclick="saveScene(false, event);">💾 Save Scene</button>
<button onclick="saveScene(true, event);">💾<span style="position:relative;right:12px;top:2px;width:12px;display: inline-block;">💾</span>Duplicate Scene</button>
<button onclick="setobsSceneName();" title="When this layout is triggered, OBS will change to the specified scene as well">Link OBS Scene</button>
<button onclick="copyJSON();" title="This can be used with &format directly in VDO.Ninja without the mixer app. Requires a guest being active.">Show as JSON</button>
<button onclick="removeScene();">🗑️ Remove Scene</button>
<button id="saveAndClose" onclick="saveScene(false, event);closeScene();">💾❌ Save and Close</button>
<button onclick="closeScene();">❌ Close Scene Maker</button>
</div>
</div>
<div id="chatModuleButton" class="hidden" onclick="toggleChat();" style="user-select: none;position: fixed;top: 10px;right: 10px;cursor: pointer;" title="Show the chat window">💬</div>
<div id="chatModule" class="hidden">
<div class="xbutton" onclick="toggleChat();" title="Hide the chat window">x</div>
<div id="chatBody" class="message">
<div class="inMessage" data-translate='welcome-to-vdo-ninja-chat'>
Welcome to VDO.Ninja! You can send text messages directly to connected peers from here.
</div>
</div>
<div id="chatSendBar">
<input id="chatInput" placeholder="Enter chat message to send here" onkeypress="EnterButtonChat(event)" />
<button onclick="sendChatMessage()" id='sendChat'>Send</button>
</div>
</div>
<div id="welcomeWindow">
<div class="center-content">
<div class="title"><h1 class="main-heading">Mixer App</h1><h2>Video chat with custom <b>layouts</b></h2></div>
<label for="roomname"><input name="roomname" autocorrect="off" autocapitalize="off" id="roomname" type="text" placeholder="Room Name" /></label>
<font class="tooltip">
<button onclick="randomRoomName();" class="randomRoomName"></button><span class="tooltiptext">Generate a random room name</span>
</font>
<br />
<label for="roompassword"><input name="roompassword" autocorrect="off" autocapitalize="off" id="roompassword" type="text" placeholder="Room Password (optional)"/></label><br />
<button onclick="startRoom();">Get started!</button><br /><br />
<span id="lastSavedRoom" class="hidden">
<br /><br />
<label for="savedroomname">
<input name="savedroomname" autocorrect="off" autocapitalize="off" id="savedroomname" type="text" disabled placeholder="Room Name" /></label>
<label for="savedroompassword" id="savedpasswordlabel"><br />
<input name="savedroompassword" autocorrect="off" autocapitalize="off" id="savedroompassword" disabled type="password" placeholder="Room Password"/></label>
<button onclick="startLastRoom();">Restore last room</button>
</span>
<br /><br />
<div class="footer">
<div>
<a href="https://discord.vdo.ninja" class="discord" target="_blank"></a>
</div>
<div>
<a class="github" href="https://github.com/steveseguin/vdoninja" rel="noopener" target="_blank" title="Star steveseguin/vdoninja on GitHub"></a>
</div>
<div style="cursor:pointer;" onclick="window.open('https://vdo.ninja');">Powered by VDO.Ninja (v23)</div>
</div>
</div>
</div>
<div id="iframeContainer" class="tFadeStart">
<div id='canvas' class="hidden">
</div>
</div>
<!--
<div class="modal" >
<span class="close-btn">&times;</span>
<div id="modal-content">
<p>this is the text inside the modal</p>
</div>
</div>
</div>
-->
<div id='sceneSettings' class="hidden modal">
<div class="modal-content">
<span class="close-btn">&times;</span>
<h2>General Mixer Settings</h2>
<h3>Aspect Ratio</h3>
<input type="checkbox" checked class="aspectbutton" data-value="169" onchange="changeAspectRatio(16/9.0, this);">16:9
<input type="checkbox" class="aspectbutton" data-value="0.5625" onchange="changeAspectRatio(9.0/16, this);">9:16
<input type="checkbox" class="aspectbutton" data-value="1" onchange="changeAspectRatio(1.0, this);">1:1
<br /><small><i>This just impacts the aspect ratio of the local preview.</i></small><br />
<h3 title="Does not impact the output; just increases pixel accuracy">Canvas Size</h3>
<input type="checkbox" checked class="pixeldensity" data-value="720" onchange="changePixelDensity(720, this);">720p
<input type="checkbox" class="pixeldensity" data-value="1080" onchange="changePixelDensity(1080, this);">1080p
<h3>Unit type for saved scene edits 📏</h3>
<input type="checkbox" checked class="absolutePosition" data-value="false" onchange="changeAbsolutePosition(false, this);">Relative (Percentage)
<input type="checkbox" class="absolutePosition" data-value="true" onchange="changeAbsolutePosition(true, this);">Absolute (Pixels)
<h3>Layout switching</h3>
<input type="checkbox" title="When a user is assigned a slot or switches slots, the last active layout is re-applied automatically" id="updateOnSlotChange" checked onchange="submitChange(this)";>Update layout on a slot change
<h3>Trigger OBS scenes change on layout change</h3>
<input type="checkbox" title="" id="syncOBS" onchange="submitChange3(this)";>Activate linked OBS scene on layout change
<h3>Switch layouts to match selected OBS scene</h3>
<input type="checkbox" title="" id="remoteSyncOBS" onchange="submitChange5(this)";>Activate the linked layout on remote OBS scene change
<h3>Slot assignment</h3>
<input type="checkbox" title="A guest is assigned a slot when they join, automatically. If disabled, they must be assigned a slot manually." id="assignSlotToGuest" checked onchange="submitChange2(this)";>Assign a slot to new guests automatically
<h3>Show advanced controls</h3>
<input type="checkbox" title="Shows more director control options" id="advancedMode" onchange="toggleAdvanced(this)";>Show the advanced director control options
<h3>Show director</h3>
<input type="checkbox" checked title="If disabled, the director will not be visible or audible in scene links" id="showDirector" onchange="submitChange4(this)";>The director can be visable and audible in scenes
<h3>🗑 Remove all Layouts</h3>
<button onclick="wipeLayouts();">This will remove all the scene layouts from the current session.</button><br />
<h3>🚿 Load Default Layouts</h3>
<button onclick="resetLayouts();">This will replace all current scene layouts with the initial defaults.</button><br />
<h3>📤 Export settings </h3>
<button onclick="exportSession();">Export all scenes and settings to disk</button><br />
<h3>📥 Import settings </h3>
Import scenes and settings from local file:<br /><input type="file" accept=".json" onchange="importSession(event);"/><br /><br />
<button class='close-btn'>Close Settings</button>
</div>
</div>
<div id='roomSettings' class="hidden modal">
<div class="modal-content">
<span class="close-btn">&times;</span>
<h2>Room Setup</h2>
<br /><br />
<button onclick="showSettings();">Close</button>
</div>
</div>
<div id='inviteOptions' class="hidden modal">
<div class="modal-content">
<span class="close-btn">&times;</span>
<h2>Invite options</h2>
<h4>Your room name is: <b class="roomname">ROOMNAME</b></h4>
<h3>Copy invite to clipboard</h3>
<a class="inviteLink" target="_blank">Invite Link</a><i class="inviteLink"></i>
<br /><br />
<h3>Customize invite link</h3>
<span >
<input type="checkbox" id="toggleLabel" onclick="updateInviteLinks(event)" /><label>💬Prompt user for a display name</label><br />
<input type="checkbox" id="toggleBroadcast" onclick="updateInviteLinks(event)"/><label>🪟Guest can see other guests and the active layout <small>(higher CPU for guest)</small></label><br />
<input type="checkbox" id="obfuscateInvites" onclick="updateInviteLinks(event)"/><label>*⃣Obfuscate the invite links so they cannot be easily modified by guests</label><br />
<br />
<input type="checkbox" id="echoInvite" onclick="updateInviteLinks(event)"/><label>🎙Guest's echo cancellation will be turned off (may improve audio quality)</label><br />
<input type="checkbox" id="denoiseInvite" onclick="updateInviteLinks(event)"/><label>🎙Guest's noise reduction featured will be turned off</label><br />
<input type="checkbox" id="autogainInvite" onclick="updateInviteLinks(event)"/><label>🎙Guest's automatic mic-gain control will be turned off</label><br />
</span>
<label>Append additional URL params: </label><input size='50' oninput="updateInviteLinks(event)" style='max-width:50%' type="text" id="additionalParams" placeholder='optional URL params here. eg: &showlabels&ruler' /><br />
<i>You can manually customize the invite link further; see the documentation at <a href="https://docs.vdo.ninja" target="_blank">docs.vdo.ninja</a></i>
<br /><br />
<button class='close-btn'>Close</button>
</div>
</div>
<div class="gone" >
<!-- This image is used when dragging elements -->
<img src="./media/favicon-32x32.png" style="pointer-events: none;" id="dragImage" loading="lazy" />
</div>
<div id="messagePopup" class="popup-message"></div>
<script>
function allowDrop(ev) {
ev.preventDefault();
}
(function(w) {
w.URLSearchParams = w.URLSearchParams || function(searchString) {
var self = this;
searchString = searchString.replace("??", "?");
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);
function errorlog(e,a=null,b=null){
console.error(e);
}
function warnlog(msg){
console.warn(msg);
}
function log(msg){
console.log(msg);
}
function getById(id){
var ele = document.getElementById(id);
if (!ele){
warnlog(id+" not found.");
return document.createElement("span");
} else {
return ele;
}
}
function toggleChat(){
document.getElementById("chatModule").classList.toggle("fadeout");
document.getElementById("chatModuleButton").classList.toggle("hidden");
if (document.getElementById("chatModule").classList.contains("fadeout")){
document.documentElement.style.setProperty('--chat-width', "0px");
} else {
document.documentElement.style.setProperty('--chat-width', "450px");
}
}
function sanitizeRoomName(roomid) {
if (!roomid){
return false;
}
roomid = roomid.trim();
if (roomid === "") {
return false;
} else if (!roomid) {
return false;
} else if (roomid=="test") {
return false;
}
var roomid = roomid.replace(/[\W]+/g, "_");
if (roomid.length > 50) {
roomid = roomid.substring(0, 50);
}
return roomid;
}
var CtrlPressed = false;
document.addEventListener("keydown", event => {
if ((event.ctrlKey) || (event.metaKey)) { // detect if CTRL is pressed
if (!CtrlPressed){
CtrlPressed = true;
$(function(){
// $(".draggable").unbind();
$(".draggable").draggable({ snap: false , grid: [ 1,1 ] });
});
$(function(){
//$(".resizable").unbind();
$(".resizable").resizable({ snap: false , grid: [ 1,1 ] });
});
//errorlog("CtrlPressed :"+CtrlPressed);
}
}
});
document.addEventListener("keyup", event => {
if ((event.ctrlKey) || (event.metaKey)) { // detect if CTRL is pressed
//
} else if (CtrlPressed){
CtrlPressed = false;
$(function(){
// $(".draggable").unbind();
$(".draggable").draggable({ snap: true , grid: [ 10,10 ] });
});
$(function(){
//$(".resizable").unbind();
$(".resizable").resizable({ snap: true , grid: [ 10, 10] });
});
//errorlog("CtrlPressed :"+CtrlPressed);
}
});
var urlEdited = window.location.search.replace(/\?\?/g, "?");
urlEdited = urlEdited.replace(/\?/g, "&");
urlEdited = urlEdited.replace(/\&/, "?");
if (urlEdited !== window.location.search){
warnlog(window.location.search + " changed to " + urlEdited);
window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString());
}
var urlParams = new URLSearchParams(urlEdited);
var api = false;
if (urlParams.has('osc') || urlParams.has('api')) {
if (urlParams.get('osc') || urlParams.get('api')) {
api = urlParams.get('osc') || urlParams.get('api');
}
}
var streamIDs = [];
var slotsNeeded = 1;
var lastLayout = {"scene":"0", "layout":false};
var lastLayoutRaw = false;
var updateOnSlotChange = true;
var assignSlotToGuest = true;
var toggleLabel = false;
var toggleBroadcast = false;
var toggleEchoInvite = false;
var toggleDenoiseInvite = false;
var toggleAutogainInvite = false;
var obfuscateInvites = false;
var additionalParams = "";
var messageList = [];
var password = false;
var syncOBS = false;
var remoteSyncOBS = false;
var showDirector = true;
var currentOBSState = false;
var aspectRatio = 16/9.0;
var pixelDensity = 720;
document.documentElement.style.setProperty('--aspect-ratio', aspectRatio);
document.documentElement.style.setProperty('--aspect-ratio-widget', aspectRatio);
var absolutePixel = false;
var advancedMode = false;
var hh = pixelDensity;
var ww = parseInt((pixelDensity*16/9) * (aspectRatio/(16/9)));
document.documentElement.style.setProperty('--iframe-width', ww);
document.documentElement.style.setProperty('--iframe-height',hh);
var roomname = false;
if (urlParams.has("room") || urlParams.has("r") ||urlParams.has("dir") || urlParams.has("director")){
roomname = urlParams.get("room") || urlParams.get("r") ||urlParams.get("dir") || urlParams.get("director");
roomname = sanitizeRoomName(roomname);
}
var savedLastRoom = getStorage("savedRoom");
if (savedLastRoom){
if ("roomname" in savedLastRoom && savedLastRoom.roomname!==false){
document.getElementById("savedroomname").value = savedLastRoom.roomname;
document.getElementById("lastSavedRoom").classList.remove("hidden");
if ("password" in savedLastRoom && savedLastRoom.password!==false){
document.getElementById("savedpasswordlabel").classList.remove("hidden");
document.getElementById("savedroompassword").value = savedLastRoom.password;
} else {
document.getElementById("savedpasswordlabel").classList.add("hidden");
}
}
}
if (urlParams.has('password') || urlParams.has('pass') || urlParams.has('pw') || urlParams.has('p')) {
password = urlParams.get('password') || urlParams.get('pass') || urlParams.get('pw') || urlParams.get('p') || "";
password = password.trim();
document.getElementById("savedroompassword").classList.add("hidden");
document.getElementById("roompassword").classList.add("hidden");
}
function randomRoomName(){
document.getElementById("roomname").value = generateString(8);
}
function startLastRoom(){
document.getElementById("roomname").value = document.getElementById("savedroomname").value;
document.getElementById("roompassword").type = "password";
document.getElementById("roompassword").value = document.getElementById("savedroompassword").value;
startRoom();
}
function startRoom(){
if (password===false){
var pid = document.getElementById("roompassword").value.trim();
if (pid){
password = pid;
}
}
var rid = document.getElementById("roomname").value.trim();
if (rid == "test"){
alert("Please enter a unique room name");
}
rid = sanitizeRoomName(rid);
if (rid){
if (this){
this.disabled = true;
this.onclick = null;
}
roomname = rid;
loadIframe();
document.getElementById("welcomeWindow").classList.add("fadeout");
setTimeout(function(){
document.getElementById("welcomeWindow").classList.add("hidden");
},500);
} else {
document.getElementById("roomname").classList.remove("shake");
setTimeout(function(){document.getElementById("roomname").classList.add("shake");},10);
}
}
function sanitize(string) {
var temp = document.createElement('div');
temp.textContent = string;
return temp.innerHTML;
}
function EnterButtonChat(event){
// Number 13 is the "Enter" key on the keyboard
var key = event.which || event.keyCode;
if (key === 13) {
// Cancel the default action, if needed
event.preventDefault();
// Trigger the button element with a click
sendChatMessage();
}
}
function generateHash(str, length=false){
var buffer = new TextEncoder("utf-8").encode(str);
return crypto.subtle.digest("SHA-256", buffer).then(
function (hash) {
hash = new Uint8Array(hash);
if (length){
hash = hash.slice(0, parseInt(parseInt(length)/2));
}
hash = toHexString(hash);
return hash;
}
);
};
function toHexString(byteArray){
return Array.prototype.map.call(byteArray, function(byte){
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
}).join('');
}
function sendChatMessage(){ // filtered + visual
var msg = document.getElementById('chatInput').value;
msg = sanitize(msg);
if (msg==""){return;}
iframe.contentWindow.postMessage({ sendChat: msg }, "*");
document.getElementById('chatInput').value = "";
var message = {};
message.label = "You:";
message.type = "sent";
message.msg = msg;
updateMessages(message);
}
function timeSince(date) {
var seconds = Math.floor((new Date() - date) / 1000);
var interval = seconds / 31536000;
if (interval > 1) {
return Math.floor(interval) + " years";
}
interval = seconds / 2592000;
if (interval > 1) {
return Math.floor(interval) + " months";
}
interval = seconds / 86400;
if (interval > 1) {
return Math.floor(interval) + " days";
}
interval = seconds / 3600;
if (interval > 1) {
return Math.floor(interval) + " hours";
}
interval = seconds / 60;
if (interval > 1) {
return Math.floor(interval) + " minutes";
}
return "Seconds ago";
}
function updateMessages(message = false){
if (message){
var time = timeSince(message.time);
var msg = document.createElement("div");
////// KEEP THIS IN /////////
log(message.msg); // Display Recieved messages for View-Only clients.
/////////////////////////////
var label = "";
if (message.label){
label = message.label;
}
if (message.type == "sent"){
msg.innerHTML = "<span class='chat_message chat_sent'>"+message.msg + " </span><i><small> <small>- "+time+"</small></small></i><span style='display:none'>"+label+"</span>";
msg.classList.add("outMessage");
} else if (message.type == "recv"){
msg.innerHTML = label+"<span class='chat_message chat_recv'>"+message.msg + " </span><i><small> <small>- "+time+"</small></small></i>";
msg.classList.add("inMessage");
} else if (message.type == "action"){
msg.innerHTML = label+"<span class='chat_message chat_action'>"+message.msg + " </span><i><small> <small>- "+time+"</small></small></i>";
msg.classList.add("actionMessage");
} else if (message.type == "alert"){
msg.innerHTML = "<span class='chat_message chat_alert'>"+message.msg + " </span><i><small> <small>- "+time+"</small></small></i>";
msg.classList.add("inMessage");
} else {
msg.innerHTML = "<span class='chat_message chat_other'>"+message.msg + " </span><i><small> <small>- "+time+"</small></small></i>";
msg.classList.add("inMessage");
}
document.getElementById("chatBody").appendChild(msg);
} else {
document.getElementById("chatBody").innerHTML = "";
for (i in messageList){
var time = timeSince(messageList[i].time);
var msg = document.createElement("div");
////// KEEP THIS IN /////////
log(messageList[i].msg); // Display Recieved messages for View-Only clients.
/////////////////////////////
var label = "";
if (messageList[i].label){
label = messageList[i].label;
}
if (messageList[i].type == "sent"){
msg.innerHTML = "<span class='chat_message chat_sent'>"+messageList[i].msg + " </span><i><small> <small>- "+time+"</small></small></i><span style='display:none'>"+label+"</span>";
msg.classList.add("outMessage");
} else if (messageList[i].type == "recv"){
msg.innerHTML = label+"<span class='chat_message chat_recv'>"+messageList[i].msg + " </span><i><small> <small>- "+time+"</small></small></i>";
msg.classList.add("inMessage");
} else if (messageList[i].type == "action"){
msg.innerHTML = label+"<span class='chat_message chat_action'>"+messageList[i].msg + " </span><i><small> <small>- "+time+"</small></small></i>";
msg.classList.add("actionMessage");
} else if (messageList[i].type == "alert"){
msg.innerHTML = "<span class='chat_message chat_alert'>"+messageList[i].msg + " </span><i><small> <small>- "+time+"</small></small></i>";
msg.classList.add("inMessage");
} else {
msg.innerHTML = "<span class='chat_message chat_other'>"+messageList[i].msg + " </span><i><small> <small>- "+time+"</small></small></i>";
msg.classList.add("inMessage");
}
document.getElementById("chatBody").appendChild(msg);
}
}
//if (chatUpdateTimeout){
// clearInterval(chatUpdateTimeout);
//}
document.getElementById("chatBody").scrollTop = document.getElementById("chatBody").scrollHeight;
//chatUpdateTimeout = setTimeout(function(){updateMessages()},60000);
}
var currentLayout = {};
$(function(){
$( "#containermenu" ).sortable({
stop: function( event, ui ) {saveSession();},
distance: 20
});
});
function swapNodes(n1, n2) {
var p1 = n1.parentNode;
var p2 = n2.parentNode;
var i1, i2;
if ( !p1 || !p2 || p1.isEqualNode(n2) || p2.isEqualNode(n1) ) return;
for (var i = 0; i < p1.children.length; i++) {
if (p1.children[i].isEqualNode(n1)) {
i1 = i;
}
}
for (var i = 0; i < p2.children.length; i++) {
if (p2.children[i].isEqualNode(n2)) {
i2 = i;
}
}
if ( p1.isEqualNode(p2) && i1 < i2 ) {
i2++;
}
p1.insertBefore(n2, p1.children[i1]);
p2.insertBefore(n1, p2.children[i2]);
}
function drag(ev) {
var data = ev.dataTransfer.getData("text");
var origThing = document.getElementById(data);
ev.dataTransfer.setData("text", ev.target.id);
var eles = document.querySelectorAll(".thing");
for (var i=0;i<eles.length;i++){
if (eles[i].id == ev.target.id){continue;}
if (!ev.target.dataset.slot){
if (eles[i].id == "delete"){
if (!ev.target.classList.contains("disconnected")){
continue;
} else {
eles[i].style.boxShadow = "0px 0px 8px 2px #F00";
continue;
}
}
}
eles[i].style.boxShadow = "0px 0px 8px 2px #Fff";
}
var eles = document.querySelectorAll(".empty");
for (var i=0;i<eles.length;i++){
eles[i].style.boxShadow = "0px 0px 8px 2px #Fff";
}
}
function drop(ev) {
ev.preventDefault();
var data = ev.dataTransfer.getData("text");
var origThing = document.getElementById(data);
ev.target.style.border = "";
origThing.style.border = "";
if (ev.target.classList.contains("thing")){
swapNodes( ev.target, origThing);
var slot = parseInt(origThing.dataset.slot);
origThing.dataset.slot = parseInt(ev.target.dataset.slot);
ev.target.dataset.slot = slot;
var oldColor = origThing.style.backgroundColor;
origThing.style.backgroundColor = ev.target.style.backgroundColor;
ev.target.style.backgroundColor = oldColor;
//ev.target.classList.add("empty");
} else if (ev.target.classList.contains("empty")){
if (!ev.target.dataset.slot){return;}
if (ev.target.dataset.slot=="undefined"){return;}
if (ev.target.dataset.slot=="undefined"){return;}
ev.target.parentNode.insertBefore(origThing, ev.target.nextSibling);
if (origThing.dataset.slot && (origThing.dataset.slot!=="undefined")){
document.querySelectorAll("[data-slot='"+origThing.dataset.slot+"']").forEach(ele=>{ele.style.display = "block";})
document.querySelectorAll("[data-slot='"+origThing.dataset.slot+"']").forEach(ele=>{ele.classList.remove("hidden");})
}
origThing.dataset.slot = ev.target.dataset.slot;
ev.target.style.display = "none";
ev.target.classList.add("hidden");
//ev.target.classList.remove("empty");
origThing.style.backgroundColor = ev.target.style.backgroundColor;
}
var eles = document.querySelectorAll(".thing");
for (var i=0;i<eles.length;i++){
eles[i].style.boxShadow = "unset";
}
var eles = document.querySelectorAll(".empty");
for (var i=0;i<eles.length;i++){
eles[i].style.boxShadow = "unset";
}
}
function dragenter(event) {
event.preventDefault();
if ( event.target.classList.contains("thing") ) {
event.target.style.border = "3px dotted black";
} else if (event.target.classList.contains("empty")){
event.target.style.border = "3px dotted black";
}
}
function dragleave(event) {
event.preventDefault();
if (event.target.classList.contains("thing")){
event.target.style.border = "";
} else if (event.target.classList.contains("empty")){
event.target.style.border = "";
}
}
function dropRemove(ev) {
ev.preventDefault();
var data = ev.dataTransfer.getData("text");
var origThing = document.getElementById(data);
ev.target.style.border = "";
origThing.style.border = "";
if (origThing.dataset.slot){
document.querySelector("[data-slot='"+origThing.dataset.slot+"']").style.display = "block";
document.querySelector("[data-slot='"+origThing.dataset.slot+"']").classList.remove("hidden");
document.querySelector("[data-slot='"+origThing.dataset.slot+"']").classList.add("empty");
delete origThing.dataset.slot;
}
origThing.style.backgroundColor = "#000";
if (ev.target.classList.contains("thing")){
ev.target.parentNode.insertBefore(origThing, ev.target.nextSibling);
} else {
ev.target.appendChild(origThing);
}
document.getElementById("col2").appendChild(document.getElementById("demoDrop"));
document.getElementById("col2").appendChild(document.getElementById("delete"));
var eles = document.querySelectorAll(".thing");
for (var i=0;i<eles.length;i++){
eles[i].style.boxShadow = "unset";
}
var eles = document.querySelectorAll(".empty");
for (var i=0;i<eles.length;i++){
eles[i].style.boxShadow = "unset";
}
if (ev.target.id == "delete"){
if (origThing.classList.contains("disconnected")){
if (!origThing.dataset.slot){
origThing.remove();
return;
}
}
}
}
function updateList(autoadd=false){
//<div id="col2" ondrop="dropRemove(event)" ondragover="allowDrop(event)">
// <div class="thing" draggable="true" ondragstart="drag(event)" id="thing4">THING 4</div>
// <div class="thing" draggable="true" ondragstart="drag(event)" id="thing1">THING 1</div>
//</div>
for (var i=0;i<streamIDs.length;i++){
if (!document.getElementById("sid_"+streamIDs[i])){
var thing = document.createElement("div");
thing.draggable = true;
thing.classList.add("thing");
thing.ondblclick = function(ev){
var origThing = ev.target;
if (origThing.parentNode.id == "col1"){return;}
ev.preventDefault();
var target = document.querySelector("[data-slot][class='empty']:not([class='hidden'])");
origThing.style.border = "";
var eles = document.querySelectorAll(".thing");
for (var i=0;i<eles.length;i++){
eles[i].style.boxShadow = "unset";
}
var eles = document.querySelectorAll(".empty");
for (var i=0;i<eles.length;i++){
eles[i].style.boxShadow = "unset";
}
if (target.classList.contains("thing")){
swapNodes( target, origThing);
var slot = origThing.dataset.slot;
origThing.dataset.slot = target.dataset.slot;
target.dataset.slot = slot;
var oldColor = origThing.style.backgroundColor;
origThing.style.backgroundColor = target.style.backgroundColor;
target.style.backgroundColor = oldColor;
//target.classList.add("empty");
//target.classList.add("empty");
} else if (target.classList.contains("empty")){
if (!target.dataset.slot){return;}
if (target.dataset.slot=="undefined"){return;}
if (target.dataset.slot=="undefined"){return;}
target.parentNode.insertBefore(origThing, target.nextSibling);
if (origThing.dataset.slot && (origThing.dataset.slot!=="undefined")){
document.querySelector("[data-slot='"+origThing.dataset.slot+"']").style.display = "block";
document.querySelector("[data-slot='"+origThing.dataset.slot+"']").classList.remove("hidden");
}
origThing.dataset.slot = target.dataset.slot;
target.style.display = "none";
target.classList.add("hidden");
//target.classList.remove("empty");
origThing.style.backgroundColor = target.style.backgroundColor;
}
};
thing.addEventListener("dragstart", drag);
thing.dataset.sid = streamIDs[i];
thing.id = "sid_"+streamIDs[i];
thing.innerText = streamIDs[i];
thing.title = "This object represents a media stream. Drag it to a slot to include it in a layout. It will not activate until the layout is re-set or triggered."
document.getElementById("col2").appendChild(thing);
thing.classList.add("shake");
setTimeout(function(ele){ele.classList.remove("shake");},500,thing);
} else{
document.getElementById("sid_"+streamIDs[i]).classList.remove("disconnected");
document.getElementById("sid_"+streamIDs[i]).title = "Connected and ready to be placed";
}
}
try {
if (streamIDs.length>2){
document.getElementById("demoDrop").style.display = "none"
} else if (streamIDs.length==0){
document.getElementById("demoDrop").style.display = "unset"
document.getElementById("col2").appendChild(document.getElementById("demoDrop"));
} else {
document.getElementById("col2").appendChild(document.getElementById("demoDrop"));
}
}catch(e){}
document.getElementById("col2").appendChild(document.getElementById("delete"));
if (autoadd){
var event = new MouseEvent('dblclick', {
'view': window,
'bubbles': true,
'cancelable': true
});
document.getElementById("sid_"+autoadd).dispatchEvent(event);
}
}
var colors = [
"#00AAAA",
"#FF0000",
"#0000FF",
"#AA00AA",
"#00FF00",
"#AAAA00",
"#AACC44",
"#CCAA44",
"#CC44AA",
"#44AACC"
];
var initialLayouts = {};
initialLayouts.layouts = [];
var data = [
{x:0, y:0, w:100, h:100}
];
initialLayouts.layouts.push(data);
var data = [
null,
{x:0, y:0, w:100, h:100}
];
initialLayouts.layouts.push(data);
var data = [
null,
null,
{x:0, y:0, w:100, h:100}
];
initialLayouts.layouts.push(data);
var data = [
null,
null,
null,
{x:0, y:0, w:100, h:100}
];
initialLayouts.layouts.push(data);
var data = [
{x:0, y:0, w:50, h:100, c:false},
{x:50, y:0, w:50, h:100, c:false}
];
initialLayouts.layouts.push(data);
var data = [
{x:70, y:70, w:30, h:30, z:1, c:true},
{x:0, y:0, w:100, h:100, z:0, c:false}
];
initialLayouts.layouts.push(data);
var data = [
{x:0, y:0, w:50, h:50, c:true},
{x:50, y:0, w:50, h:50, c:true},
{x:0, y:50, w:50, h:50, c:true},
{x:50, y:50, w:50, h:50, c:true}
];
initialLayouts.layouts.push(data);
var data = [
{x:0, y:16.667, w:66.667, h:66.667, c:true},
{x:66.667, y:0, w:33.333, h:33.333, c:true},
{x:66.667, y:33.333, w:33.333, h:33.333, c:true},
{x:66.667, y:66.667, w:33.333, h:33.333, c:true}
];
initialLayouts.layouts.push(data);
function applySettings(){
if (savedSession.settings && ("updateOnSlotChange" in savedSession.settings)){
updateOnSlotChange = savedSession.settings.updateOnSlotChange;
if (!updateOnSlotChange){
getById("updateOnSlotChange").value = "off";
getById("updateOnSlotChange").checked = false;
getById("updateOnSlotChange").removeAttribute('checked');
} else {
getById("updateOnSlotChange").value = "on";
getById("updateOnSlotChange").checked = true;
}
}
if (savedSession.settings && ("assignSlotToGuest" in savedSession.settings)){
assignSlotToGuest = savedSession.settings.assignSlotToGuest;
if (!assignSlotToGuest){
getById("assignSlotToGuest").value = "off";
getById("assignSlotToGuest").checked = false;
getById("assignSlotToGuest").removeAttribute('checked');
} else {
getById("assignSlotToGuest").value = "on";
getById("assignSlotToGuest").checked = true;
}
}
if (savedSession.settings && ("toggleLabel" in savedSession.settings)){
toggleLabel = savedSession.settings.toggleLabel;
if (!toggleLabel){
getById("toggleLabel").value = "off";
getById("toggleLabel").checked = false;
getById("toggleLabel").removeAttribute('checked');
} else {
getById("toggleLabel").value = "on";
getById("toggleLabel").checked = true;
}
}
if (savedSession.settings && ("toggleBroadcast" in savedSession.settings)){
toggleBroadcast = savedSession.settings.toggleBroadcast;
if (!toggleBroadcast){
getById("toggleBroadcast").value = "off";
getById("toggleBroadcast").checked = false;
getById("toggleBroadcast").removeAttribute('checked');
} else {
getById("toggleBroadcast").value = "on";
getById("toggleBroadcast").checked = true;
}
}
if (savedSession.settings && ("toggleEchoInvite" in savedSession.settings)){
toggleEchoInvite = savedSession.settings.toggleEchoInvite;
if (!toggleEchoInvite){
getById("echoInvite").value = "off";
getById("echoInvite").checked = false;
getById("echoInvite").removeAttribute('checked');
} else {
getById("echoInvite").value = "on";
getById("echoInvite").checked = true;
}
}
if (savedSession.settings && ("toggleDenoiseInvite" in savedSession.settings)){
toggleDenoiseInvite = savedSession.settings.toggleDenoiseInvite;
if (!toggleDenoiseInvite){
getById("denoiseInvite").value = "off";
getById("denoiseInvite").checked = false;
getById("denoiseInvite").removeAttribute('checked');
} else {
getById("denoiseInvite").value = "on";
getById("denoiseInvite").checked = true;
}
}
if (savedSession.settings && ("toggleAutogainInvite" in savedSession.settings)){
toggleAutogainInvite = savedSession.settings.toggleAutogainInvite;
if (!toggleAutogainInvite){
getById("autogainInvite").value = "off";
getById("autogainInvite").checked = false;
getById("autogainInvite").removeAttribute('checked');
} else {
getById("autogainInvite").value = "on";
getById("autogainInvite").checked = true;
}
}
if (savedSession.settings && ("obfuscateInvites" in savedSession.settings)){
obfuscateInvites = savedSession.settings.obfuscateInvites;
if (!obfuscateInvites){
getById("obfuscateInvites").value = "off";
getById("obfuscateInvites").checked = false;
getById("obfuscateInvites").removeAttribute('checked');
} else {
getById("obfuscateInvites").value = "on";
getById("obfuscateInvites").checked = true;
}
}
//
if (savedSession.settings && ("additionalParams" in savedSession.settings)){
additionalParams = savedSession.settings.additionalParams;
if (!additionalParams){
getById("additionalParams").value = "";
} else {
getById("additionalParams").value = additionalParams;
}
}
if (savedSession.settings && ("syncOBS" in savedSession.settings)){
syncOBS = savedSession.settings.syncOBS;
if (!syncOBS){
getById("syncOBS").value = "off";
getById("syncOBS").checked = false;
getById("syncOBS").removeAttribute('checked');
} else {
getById("syncOBS").value = "on";
getById("syncOBS").checked = true;
}
}
if (savedSession.settings && ("remoteSyncOBS" in savedSession.settings)){
remoteSyncOBS = savedSession.settings.remoteSyncOBS;
if (!remoteSyncOBS){
getById("remoteSyncOBS").value = "off";
getById("remoteSyncOBS").checked = false;
getById("remoteSyncOBS").removeAttribute('checked');
} else {
getById("remoteSyncOBS").value = "on";
getById("remoteSyncOBS").checked = true;
}
}
if (savedSession.settings && ("showDirector" in savedSession.settings)){
showDirector = savedSession.settings.showDirector;
if (!showDirector){
getById("showDirector").value = "off";
getById("showDirector").checked = false;
getById("showDirector").removeAttribute('checked');
} else {
getById("showDirector").value = "on";
getById("showDirector").checked = true;
}
}
if (savedSession.settings && ("aspectRatio" in savedSession.settings)){
aspectRatio = savedSession.settings.aspectRatio;
changeAspectRatio(aspectRatio,false);
document.querySelectorAll(".aspectbutton").forEach(ele=>{
if ((ele.dataset.value == "169") && (aspectRatio == 16/9.0)){
ele.checked = true;
ele.value = true;
} else if (ele.dataset.value == aspectRatio){
ele.checked = true;
ele.value = true;
} else {
ele.checked = false;
ele.value = false;
}
});
}
if (savedSession.settings && ("advancedMode" in savedSession.settings)){
advancedMode = savedSession.settings.advancedMode;
if (!advancedMode){
getById("advancedMode").value = "off";
getById("advancedMode").checked = false;
getById("advancedMode").removeAttribute('checked');
} else {
getById("advancedMode").value = "on";
getById("advancedMode").checked = true;
}
if (iframe && iframe.contentWindow){
iframe.contentWindow.postMessage({"advancedMode":advancedMode}, '*');
}
}
if (savedSession.settings && ("absolutePixel" in savedSession.settings)){
absolutePixel = savedSession.settings.absolutePixel;
document.querySelectorAll(".absolutePosition").forEach(ele=>{
if (ele.dataset.value == "true"){
if (true == absolutePixel){
ele.checked = true;
ele.value = true;
} else {
ele.checked = false;
ele.value = false;
}
} else {
if (false == absolutePixel){
ele.checked = true;
ele.value = true;
} else {
ele.checked = false;
ele.value = false;
}
}
});
}
if (savedSession.settings && ("pixelDensity" in savedSession.settings)){
pixelDensity = savedSession.settings.pixelDensity;
if (pixelDensity==1080){
document.querySelectorAll(".pixeldensity").forEach(ele=>{
if (ele.dataset.value == pixelDensity){
ele.checked = true;
ele.value = true;
} else {
ele.checked = false;
ele.value = false;
}
});
var hh = pixelDensity;
var ww = parseInt((pixelDensity*16/9) * (aspectRatio/(16/9)));
changeAspectRatio(aspectRatio,false);
document.documentElement.style.setProperty('--iframe-width', ww+"px");
document.documentElement.style.setProperty('--iframe-height', hh+"px");
// getById("syncOBS").value = "off";
// getById("syncOBS").checked = false;
// getById("syncOBS").removeAttribute('checked');
}
}
}
var savedSession = getStorage("savedSession");
if (savedSession){
savedSession = JSON.parse(savedSession);
applySettings();
} else {
savedSession = initialLayouts;
}
if (urlParams.has("hidedirector")){
showDirector = false;
if (savedSession.settings){
savedSession.settings.showDirector = false;
}
}
if (iframe){
if (remoteSyncOBS){
iframe.contentWindow.postMessage({ layouts: savedSession.layouts , obsSceneTriggers: savedSession.obsScenes}, "*");
} else {
iframe.contentWindow.postMessage({ layouts: savedSession.layouts }, "*");
}
}
var guestPositions = {};
function submitChange(element){
if (element.checked){
updateOnSlotChange=true;
} else {
element.removeAttribute('checked');
updateOnSlotChange=false;
}
saveSession();
}
function submitChange2(element){
if (element.checked){
assignSlotToGuest=true;
iframe.contentWindow.postMessage({"slotmode":1}, '*');
} else { // do not assign guests to slots automatically
element.removeAttribute('checked');
assignSlotToGuest=false
iframe.contentWindow.postMessage({"slotmode":2}, '*');;
}
saveSession();
}
function submitChange3(element){
if (element.checked){
syncOBS=true;
} else { // do not assign guests to slots automatically
element.removeAttribute('checked');
syncOBS=false
}
saveSession();
}
function submitChange5(element){
if (element.checked){
remoteSyncOBS=true;
if (iframe){
iframe.contentWindow.postMessage({ layouts: savedSession.layouts , obsSceneTriggers: savedSession.obsScenes}, "*");
}
} else { // do not assign guests to slots automatically
if (iframe){
iframe.contentWindow.postMessage({ layouts: savedSession.layouts , obsSceneTriggers: false}, "*");
}
element.removeAttribute('checked');
remoteSyncOBS=false
}
saveSession();
}
function submitChange4(element){
if (element.checked){
showDirector=true;
} else { // do not assign guests to slots automatically
element.removeAttribute('checked');
showDirector=false
}
saveSession();
if (document.getElementById("vdoninja")){
document.getElementById("vdoninja").src = createIframeURL();
}
}
function toggleAdvanced(element){
if (element.checked){
iframe.contentWindow.postMessage({"advancedMode":true}, '*');
advancedMode = true;
} else {
element.removeAttribute('checked');
iframe.contentWindow.postMessage({"advancedMode":false}, '*');
advancedMode = false;
}
saveSession();
}
function exportSession() {
var content = JSON.stringify(savedSession,null,2);
var fileName = roomname + ".json";
var a = document.createElement("a");
var file = new Blob([content], {type: 'text/plain'});
a.href = URL.createObjectURL(file);
a.download = fileName;
a.click();
}
function importSession(event){
var reader = new FileReader();
reader.onload = function(event){
log(event.target.result);
try {
var obj = JSON.parse(event.target.result);
} catch(e){
alert("File is not a valid JSON file");
return;
}
if ("layouts" in obj){
var yes = confirm("Are you sure? This will clear the current session");
if (!yes){return;}
savedSession = obj;
document.getElementById("containermenu").innerHTML = "";
drawAddNewLayout2();
drawAddNewLayout3();
for (var i in savedSession.layouts){
if (savedSession.obsScenes && savedSession.obsScenes[i]){
drawLayout(savedSession.layouts[i], false, savedSession.obsScenes[i]);
} else {
drawLayout(savedSession.layouts[i]);
}
}
alert("The saved session has been imported and loaded.");
} else {
alert("File contains no valid session information");
}
};
reader.readAsText(event.target.files[0]);
}
function resetLayouts(){
var yes = confirm("Are you sure? This will reset all the layouts.");
if (!yes){return;}
document.getElementById("containermenu").innerHTML = "";
drawAddNewLayout2();
drawAddNewLayout3();
savedSession.obsScenes = [];
savedSession.layouts = initialLayouts.layouts;
for (var i in savedSession.layouts){
drawLayout(savedSession.layouts[i]);
}
saveSession();
}
function updateLayouts(){
document.getElementById("containermenu").innerHTML = "";
var slots = document.getElementById("col1").children;
for (var i=0;i<slots.length;i++){
slots[i].style.backgroundColor = colors[i];
slots[i].style.opacity = "0.9";
}
drawAddNewLayout2();
drawAddNewLayout3();
for (var i in savedSession.layouts){
if (savedSession.obsScenes && savedSession.obsScenes[i]){
drawLayout(savedSession.layouts[i], false, savedSession.obsScenes[i]);
} else {
drawLayout(savedSession.layouts[i]);
}
}
}
function wipeLayouts(){
var yes = confirm("Are you sure? This will delete all the layouts.");
if (!yes){return;}
document.getElementById("containermenu").innerHTML = "";
drawAddNewLayout2();
drawAddNewLayout3();
savedSession.obsScenes = [];
savedSession.layouts = [];
for (var i in savedSession.layouts){
drawLayout(savedSession.layouts[i]);
}
saveSession();
}
var injectCSS = `
.directorsgrid .vidcon {
display: inline-block !important;
width: 293.7px !important;
background: #3e3e3e00;
color: #FCFCFC;
vertical-align: top;
border: 1px solid #2e445c;
padding: 10px;
border-radius: 10px;
background: #2e445c;
box-shadow: 20px 20px 60px #182430, -20px -20px 60px #283c52;
}
`;
injectCSS = encodeURIComponent(btoa(injectCSS));
function copyFunction(copyText, evt = false) {
if (evt){
if ("buttons" in evt) {
if (evt.buttons !== 0){return;}
} else if ("which" in evt){
if (evt.which !== 0){return;}
}
popupMessage(evt);
evt.preventDefault();
evt.stopPropagation();
}
try {
copyText.select();
copyText.setSelectionRange(0, 99999);
document.execCommand("copy");
} catch (e) {
var dummy = document.createElement('input');
document.body.appendChild(dummy);
dummy.value = copyText;
dummy.select();
document.execCommand('copy');
document.body.removeChild(dummy);
}
return false;
}
function popupMessage(e, message = "Copied to Clipboard") { // right click menu
var posx = 0;
var posy = 0;
if (!e) var e = window.event;
if (e.pageX || e.pageY) {
posx = e.pageX;
posy = e.pageY;
} else if (e.clientX || e.clientY) {
posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
posx += 10;
var menu = getById("messagePopup");
menu.innerHTML = "<center>" + message + "</center>";
var menuState = 0;
var menuWidth;
var menuHeight;
var menuPosition;
var menuPositionX;
var menuPositionY;
var windowWidth;
var windowHeight;
if (menuState !== 1) {
menuState = 1;
menu.classList.add("context-menu--active");
}
menuWidth = menu.offsetWidth + 4;
menuHeight = menu.offsetHeight + 4;
windowWidth = window.innerWidth;
windowHeight = window.innerHeight;
if ((windowWidth - posx) < menuWidth) {
menu.style.left = windowWidth - menuWidth + "px";
} else {
menu.style.left = posx + "px";
}
if ((windowHeight - posy) < menuHeight) {
menu.style.top = windowHeight - menuHeight + "px";
} else {
menu.style.top = posy + "px";
}
function toggleMenuOff() {
if (menuState !== 0) {
menuState = 0;
menu.classList.remove("context-menu--active");
}
}
menu.classList.remove("fadeout");
setTimeout(function() {
menu.classList.add("fadeout");
}, 500);
setTimeout(function() {
toggleMenuOff();
}, 1500);
}
function generateString(LLL = 7){
var text = "";
var words = ["the","of","to","and","a","in","is","it","you","that","he","was","for","on","are","with","as","I","his","they","be","at","one","have","this","from","or","had","by","word","but","what","some","we","can","out","other","were","all","there","when","up","use","your","how","said","an","each","she","which","do","their","time","if","will","way","about","many","then","them","write","would","like","so","these","her","long","make","thing","see","him","two","has","look","more","day","could","go","come","did","number","sound","no","most","people","my","over","know","water","than","call","first","who","may","down","side","been","now","find","any","new","work","part","take","get","place","made","live","where","after","back","little","only","round","man","year","came","show","every","good","me","give","our","under","name","very","through","just","form","sentence","great","think","say","help","low","line","differ","turn","cause","much","mean","before","move","right","boy","old","too","same","tell","does","set","three","want","air","well","also","play","small","end","put","home","read","hand","port","large","spell","add","even","land","here","must","big","high","such","follow","act","why","ask","men","change","went","light","kind","off","need","house","picture","try","us","again","animal","point","mother","world","near","build","self","earth","father","head","stand","own","page","should","country","found","answer","school","grow","study","still","learn","plant","cover","food","sun","four","between","state","keep","eye","never","last","let","thought","city","tree","cross","farm","hard","start","might","story","saw","far","sea","draw","left","late","run","don't","while","press","close","night","real","life","few","north","open","seem","together","next","white","children","begin","got","walk","example","ease","paper","group","always","music","those","both","mark","often","letter","until","mile","river","car","feet","care","second","book","carry","took","science","eat","room","friend","began","idea","fish","mountain","stop","once","base","hear","horse","cut","sure","watch","color","face","wood","main","enough","plain","girl","usual","young","ready","above","ever","red","list","though","feel","talk","bird","soon","body","dog","family","direct","pose","leave","song","measure","door","product","black","short","numeral","class","wind","question","happen","complete","ship","area","half","rock","order","fire","south","problem","piece","told","knew","pass","since","top","whole","king","space","heard","best","hour","better","true .","during","hundred","five","remember","step","early","hold","west","ground","interest","reach","fast","verb","sing","listen","six","table","travel","less","morning","ten","simple","several","vowel","toward","war","lay","against","pattern","slow","center","love","person","money","serve","appear","road","map","rain","rule","govern","pull","cold","notice","voice","unit","power","town","fine","certain","fly","fall","lead","cry","dark","machine","note","wait","plan","figure","star","box","noun","field","rest","correct","able","pound","done","beauty","drive","stood","contain","front","teach","week","final","gave","green","oh","quick","develop","ocean","warm","free","minute","strong","special","mind","behind","clear","tail","produce","fact","street","inch","multiply","nothing","course","stay","wheel","full","force","blue","object","decide","surface","deep","moon","island","foot","system","busy","test","record","boat","common","gold","possible","plane","stead","dry","wonder","laugh","thousand","ago","ran","check","game","shape","equate","hot","miss","brought","heat","snow","tire","bring","yes","distant","fill","east","paint","language","among","grand","ball","yet","wave","drop","heart","am","present","heavy","dance","engine","position","arm","wide","sail","material","size","vary","settle","speak","weight","general","ice","matter","circle","pair","include","divide","syllable","felt","perhaps","pick","sudden","count","square","reason","length","represent","art","subject","region","energy","hunt","probable","bed","brother","egg","ride","cell","believe","fraction","forest","sit","race","window","store","summer","train","sleep","prove","lone","leg","exercise","wall","catch","mount","wish","sky","board","joy","winter","sat","written","wild","instrument","kept","glass","grass","cow","job","edge","sign","visit","past","soft","fun","bright","gas","weather","month","million","bear","finish","happy","hope","flower","clothe","strange","gone","jump","baby","eight","village","meet","root","buy","raise","solve","metal","whether","push","seven","paragraph","third","shall","held","hair","describe","cook","floor","either","result","burn","hill","safe","cat","century","consider","type","law","bit","coast","copy","phrase","silent","tall","sand","soil","roll","temperature","finger","industry","value","fight","lie","beat","excite","natural","view","sense","ear","else","quite","broke","case","middle","kill","son","lake","moment","scale","loud","spring","observe","child","straight","consonant","nation","dictionary","milk","speed","method","organ","pay","age","section","dress","cloud","surprise","quiet","stone","tiny","climb","cool","design","poor","lot","experiment","bottom","key","iron","single","stick","flat","twenty","skin","smile","crease","hole","trade","melody","trip","office","receive","row","mouth","exact","symbol","die","least","trouble","shout","except","wrote","seed","tone","join","suggest","clean","break","lady","yard","rise","bad","blow","oil","blood","touch","grew","cent","mix","team","wire","cost","lost","brown","wear","garden","equal","sent","choose","fell","fit","flow","fair","bank","collect","save","control","decimal","gentle","woman","captain","practice","separate","difficult","doctor","please","protect","noon","whose","locate","ring","character","insect","caught","period","indicate","radio","spoke","atom","human","history","effect","electric","expect","crop","modern","element","hit","student","corner","party","supply","bone","rail","imagine","provide","agree","thus","capital","won't","chair","danger","fruit","rich","thick","soldier","process","operate","guess","necessary","sharp","wing","create","neighbor","wash","bat","rather","crowd","corn","compare","poem","string","bell","depend","meat","rub","tube","famous","dollar","stream","fear","sight","thin","triangle","planet","hurry","chief","colony","clock","mine","tie","enter","major","fresh","search","send","yellow","gun","allow","print","dead","spot","desert","suit","current","lift","rose","continue","block","chart","hat","sell","success","company","subtract","event","particular","deal","swim","term","opposite","wife","shoe","shoulder","spread","arrange","camp","invent","cotton","born","determine","quart","nine","truck","noise","level","chance","gather","shop","stretch","throw","shine","property","column","molecule","select","wrong","gray","repeat","require","broad","prepare","salt","nose","plural","anger","claim","continent","oxygen","sugar","death","pretty","skill","women","season","solution","magnet","silver","thank","branch","match","suffix","especially","fig","afraid","huge","sister","steel","discuss","forward","similar","guide","experience","score","apple","bought","led","pitch","coat","mass","card","band","rope","slip","win","dream","evening","condition","feed","tool","total","basic","smell","valley","nor","double","seat","arrive","master","track","parent","shore","division","sheet","substance","favor","connect","post","spend","chord","fat","glad","original","share","station","dad","bread","charge","proper","bar","offer","segment","slave","duck","instant","market","degree","populate","chick","dear","enemy","reply","drink","occur","support","speech","nature","range","steam","motion","path","liquid","log","meant","quotient","teeth","shell","neck"];
for (var i=0;i<2;i++){
try{
var rndint = parseInt(Math.random()*1000);
text += words[rndint];
} catch(e){}
}
var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
text += possible.charAt(Math.floor(Math.random() * possible.length));
while (text.length<LLL){
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
try{
text = text.replaceAll('AD', 'vDAv'); // avoiding adblockers
text = text.replaceAll('Ad', 'vdAv');
text = text.replaceAll('ad', 'vdav');
text = text.replaceAll('aD', 'vDav');
} catch(e){errorlog(e);}
log(text);
return text;
};
function hotkeyCheck(event){
if (event.target && (event.target.tagName == "INPUT")){warnlog("input in focus; return");return;}
var value = parseInt(event.key);
if (value == event.key){
log(value);
try {
if (document.querySelector("#containermenu").children[value]){
document.querySelector("#containermenu").children[value].querySelector("canvas").click();
document.querySelector("#containermenu").children[value].classList.add("shake");
setTimeout(function(ele){ele.classList.remove("shake");},500,document.querySelector("#containermenu").children[value]);
}
} catch(e){}
}
}
var iframe = null;
function createIframeURL(){
var additional = ""; // guest/scene links also
if (password){
additional = "&password="+password;
}
var additional2 = ""; // this iframe only
if (api){
additional2 += "&api="+api;
}
if (assignSlotToGuest){
additional2+="&slotmode";
} else {
additional2+="&slotmode=2";
}
if (!advancedMode){
additional2+="&novice";
}
if (showDirector){
additional2+="&showdirector";
}
var iframesrc = "./index.html?ltb=350&transparent&hideheader&hidetranslate&cleandirector&chatbutton=0&director="+roomname+additional+additional2+"&b64css="+injectCSS;
var params = window.location.search || "";
if (params.startsWith("?")){
params = params.slice(1);
iframesrc = iframesrc + "&" + params
} else {
iframesrc = iframesrc + params
}
return iframesrc
}
function loadIframe(){
if (iframe){return;}
iframe = document.createElement("iframe");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
iframe.id = "vdoninja";
roomname = sanitizeRoomName(roomname);
if (!roomname){
roomname = generateString(10);
}
if (roomname!==false){
setStorage("savedRoom", {roomname:roomname,password:password}, 9999);
}
var iframesrc = createIframeURL();
var additional = ""; // guest/scene links also
if (password){
additional = "&password="+password;
}
document.title = "Mixer: "+roomname;
<!-- var button = document.createElement("button"); -->
<!-- button.innerHTML = "Refresh list &#x21bb;"; -->
<!-- button.onclick = function(){iframe.contentWindow.postMessage({"getStreamIDs":true}, '*');}; -->
<!-- button.style.display = "block"; -->
<!-- button.classList.add("menuButton"); -->
<!-- document.getElementById("sources").appendChild(button); -->
var button = document.createElement("button");
button.innerHTML = "Switch Modes &#x21bb;";
button.title = "Toggle between director view and scene preview modes";
button.classList.add("menuButton");
button.state = false;
button.dataset.state = button.state;
button.onclick = function(){
this.state = !this.state;
this.dataset.state = this.state;
iframe.contentWindow.postMessage({"previewMode":this.state, "layout":currentLayout, "target": "*"}, '*');
if (this.state){
this.innerHTML = "Director View &#x21bb;";
iframe.classList.add("aspectRatio");
iframe.classList.add("fade2black");
this.title = "Switch to the director room mode";
} else {
this.innerHTML = "Scene Preview &#x21bb;";
this.title = "Switch to scene preview mode";
iframe.classList.remove("aspectRatio");
iframe.classList.remove("fade2black");
}
};
document.getElementById("sources").appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Mixer Settings ⚙︎";
button.onclick = showSettings;
button.id = "showSettings";
button.classList.add("menuButton");
document.getElementById("sources").appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Add Stream ID";
button.title = "Create a placeholder for a stream ID; the guest doesn't need to be connected yet.";
button.classList.add("menuButton");
//button.state = false;
button.dataset.state = button.state;
button.onclick = function(){
//this.state = !this.state;
//this.dataset.state = this.state;
var sid = prompt("What is the stream ID for this invite?");
if (sid){
if (sid in streamIDs){
if (document.getElementById("sid_"+sid)){
document.getElementById("sid_"+sid).classList.remove("shake");
setTimeout(function(){document.getElementById("sid_"+sid).classList.add("shake");},10);
} else {
updateList();
}
} else {
streamIDs.push(sid);
updateList();
if (document.getElementById("sid_"+sid)){
document.getElementById("sid_"+sid).classList.add("disconnected");
document.getElementById("sid_"+sid).title = "Not currently connected, but can still be placed";
}
}
}
};
button.style.display = "none";
document.getElementById("sources").appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Invite Settings";
button.classList.add("menuButton");
button.state = false;
button.dataset.state = button.state;
button.id = "inviteGuestButton";
button.onclick = showInviteOptions;
document.getElementById("sources").appendChild(button);
var a = document.createElement("a");
a.innerHTML = "Invite Guest Link 📎";
//a.href = "./?room="+roomname+"&broadcast"+additional;
a.target = "_blank";
a.id = "mainInviteLink";
a.onclick = function(evt){copyFunction(this, evt);};
document.getElementById("sources").appendChild(a);
updateInviteLinks();
var a = document.createElement("a");
a.innerHTML = "Scene View Link 📎";
a.href = "./?scene=0&layout&remote&room="+roomname+additional;
a.target = "_blank";
a.onclick = function(evt){copyFunction(this, evt);};
document.getElementById("sources").appendChild(a);
var button = document.createElement("button");
button.innerHTML = "Publish to Twitch";
button.id = "publishTwitch";
button.onclick = function(){
var URL = window.location.href.split("/");
URL.pop();
URL = URL.join("/");
URL+="/?scene=0&layout&remote&room="+roomname+additional;
URL+="&clean&chroma=000&ssar=landscape&nosettings&prefercurrenttab&selfbrowsersurface=include&displaysurface=browser&np&nopush&publish&whippush=twitch&whippushtoken&screenshareaspectratio="+aspectRatio+"&locked="+aspectRatio;
var win = window.open( URL ,'targetWindow', 'toolbar=no,location=no,status=no,scaling=no,menubar=no,scrollbars=no,resizable=no,width=1280,height=720');
win.focus();
win.resizeTo(1280,720);
};
document.getElementById("sources").appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Publish via WHIP";
button.id = "publishOther";
button.onclick = function(){
var URL = window.location.href.split("/");
URL.pop();
URL = URL.join("/");
URL+="/?scene=0&layout&remote&room="+roomname+additional;
URL+="&clean&chroma=000&ssar=landscape&nosettings&prefercurrenttab&selfbrowsersurface=include&displaysurface=browser&np&nopush&publish&whippush&screenshareaspectratio="+aspectRatio+"&locked="+aspectRatio;
var win = window.open( URL ,'targetWindow', 'toolbar=no,location=no,status=no,scaling=no,menubar=no,scrollbars=no,resizable=no,width=1280,height=720');
win.focus();
win.resizeTo(1280,720);
};
document.getElementById("sources").appendChild(button);
var slots = document.getElementById("col1").children;
for (var i=0;i<slots.length;i++){
slots[i].style.backgroundColor = colors[i];
slots[i].style.opacity = "0.9";
}
drawAddNewLayout2();
drawAddNewLayout3();
for (var i in savedSession.layouts){
if (savedSession.obsScenes && savedSession.obsScenes[i]){
drawLayout(savedSession.layouts[i], false, savedSession.obsScenes[i]);
} else {
drawLayout(savedSession.layouts[i]);
}
}
document.getElementById("chatModule").classList.remove("hidden");
iframe.onload = function(){
if (remoteSyncOBS){
iframe.contentWindow.postMessage({ layouts: savedSession.layouts , obsSceneTriggers: savedSession.obsScenes}, "*");
} else {
iframe.contentWindow.postMessage({ layouts: savedSession.layouts }, "*");
}
}
iframe.src = iframesrc;
var iframeContainer = document.getElementById("iframeContainer");
iframeContainer.appendChild(iframe);
document.getElementById("container").appendChild(iframeContainer);
iframe.contentWindow.addEventListener("keydown", hotkeyCheck);
document.addEventListener("keydown", hotkeyCheck);
//////////// LISTEN FOR EVENTS
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
if ("gotChat" in e.data){
messageList.push(e.data.gotChat);
messageList = messageList.slice(-100);
updateMessages(e.data.gotChat);
} else if ("messageList" in e.data){
messageList = e.data.messageList;
updateMessages();
}
if ("action" in e.data){
if (e.data.action === "widget-src"){
if (e.data.value){
widgetSrc = true;
} else {
widgetSrc = false;
}
changeAspectRatio(aspectRatio,false);
}
if (e.data.action === "slot-updated"){
for (var i in guestPositions){
if (guestPositions[i] === e.data.streamID){
delete guestPositions[i];
}
}
guestPositions[e.data.value] = e.data.streamID; // value is slot ID
if (updateOnSlotChange){
remoteActivate(false, lastLayoutRaw);
}
}
if (e.data.action === "layout-updated"){
console.log(e.data);
}
if (e.data.action === "layout-index"){
if ("value" in e.data){
console.log(e.data);
var idx = parseInt(e.data.value) || 0;
if (!idx){
var ele = document.getElementById("automix");
} else {
var ele = document.querySelectorAll("#containermenu .canvasContainer")[idx-1];
}
var layoutButtons = document.querySelectorAll(".pressed");
for (var i = 0;i<layoutButtons.length;i++){
layoutButtons[i].classList.remove("pressed");
}
if (ele){
ele.classList.add("pressed");
ele.classList.add("shake");
setTimeout(function(ele){ele.classList.remove("shake");},500,ele);
}
}
}
if (e.data.action && (e.data.action == "scene-connected")){
if (lastLayout && lastLayout.scene == e.data.value){
var layoutIssue = {};
layoutIssue.layout = lastLayout.layout;
if (e.data.UUID){
layoutIssue.UUID = e.data.UUID;
}
layoutIssue.scene = lastLayout.scene
iframe.contentWindow.postMessage(lastLayout, '*');
}
}
if (e.data.action && (e.data.action == "guest-connected")){
if (lastLayout){
var layoutIssue = {};
layoutIssue.layout = lastLayout.layout;
if (e.data.UUID){
layoutIssue.UUID = e.data.UUID;
}
iframe.contentWindow.postMessage(lastLayout, '*');
}
}
if (e.data.action && (e.data.action == "view-connection")){
if (!e.data.value && e.data.streamID){
for (var i in guestPositions){
if (guestPositions[i] === e.data.streamID){
delete guestPositions[i];
}
}
if (updateOnSlotChange){
remoteActivate(false, lastLayoutRaw);
}
}
}
if (e.data.action && (e.data.action == "director-share")){
if (!e.data.value && e.data.streamID){
for (var i in guestPositions){
if (guestPositions[i] === e.data.streamID){
delete guestPositions[i];
}
}
if (updateOnSlotChange){
remoteActivate(false, lastLayoutRaw);
}
}
}
if (e.data.action == "obs-state"){
if (e.data.value && e.data.value.details){
currentOBSState = e.data.value.details;
if (currentOBSState.currentScene && currentOBSState.currentScene.name){
console.log("Current OBS scene: " + currentOBSState.currentScene.name);
if (remoteSyncOBS){
var layouts = document.querySelectorAll(".canvasContainer>canvas");
for (var i=0;i<layouts.length;i++){
if (!layouts[i].layout){
console.log("no layout");
continue;
}
try {
var obs = layouts[i].obsSceneName || false;
console.log("obs scene names:"+obs+ " vs "+currentOBSState.currentScene.name);
if (obs && obs.toLowerCase().trim() == currentOBSState.currentScene.name.toLowerCase().trim()){
if (layouts[i].parentNode && layouts[i].parentNode.classList.contains("pressed")){
console.log("Matched scene already active");
} else {
console.log("Syncing with OBS; matched");
layouts[i].click(); // triggers for everyone else.
}
break;
}
} catch(e){
errorlog(e);
}
}
}
} else {
console.log("No current scene in OBS's output");
}
}
}
}
<!-- if ("streamIDs" in e.data){ -->
<!-- streamIDs = []; -->
<!-- for (var key in e.data.streamIDs){ -->
<!-- streamIDs.push(key); -->
<!-- updateList(key); -->
<!-- } -->
<!-- console.log(streamIDs); -->
<!-- } -->
});
}
function removeStorage(cname){
localStorage.removeItem(cname);
}
function setStorage(cname, cvalue, hours=9999){ // not actually a cookie
var now = new Date();
var item = {
value: cvalue,
expiry: now.getTime() + (hours * 60 * 60 * 1000),
};
try{
localStorage.setItem(cname, JSON.stringify(item));
}catch(e){errorlog(e);}
}
function getStorage(cname) {
try {
var itemStr = localStorage.getItem(cname);
} catch(e){
errorlog(e);
return;
}
if (!itemStr) {
return "";
}
var item = JSON.parse(itemStr);
var now = new Date();
if (now.getTime() > item.expiry) {
localStorage.removeItem(cname);
return "";
}
return item.value;
}
function showSettings(){
applySettings();
document.getElementById("sceneSettings").classList.remove("hidden");
}
function showInviteOptions(event=false){
document.getElementById("inviteOptions").classList.remove("hidden");
updateInviteLinks();
}
async function updateInviteLinks(event=false){
var additional = "";
if (password){
var hash = password.trim();
hash = encodeURIComponent(hash);
hash = await generateHash(hash + location.hostname, 4);
additional += "&hash="+hash;
}
if (document.getElementById("toggleLabel").checked){
additional += "&label";
toggleLabel = true;
} else {
toggleLabel= false;
}
if (document.getElementById("toggleBroadcast").checked){
additional += "&layout"; // do not use &broadcast with &layout, else you will get broken results.
toggleBroadcast = true;
} else {
additional += "&broadcast";
toggleBroadcast = false;
}
if (document.getElementById("echoInvite").checked){
additional += "&ec=0"; // do not use &broadcast with &layout, else you will get broken results.
toggleEchoInvite = true;
}
if (document.getElementById("denoiseInvite").checked){
additional += "&dn=0"; // do not use &broadcast with &layout, else you will get broken results.
toggleDenoiseInvite = true;
}
if (document.getElementById("autogainInvite").checked){
additional += "&ag=0"; // do not use &broadcast with &layout, else you will get broken results.
toggleAutogainInvite = true;
}
document.querySelectorAll(".roomname").forEach(ele=>{
ele.innerText = roomname;
});
var URL = window.location.href.split("/");
URL.pop();
URL = URL.join("/");
var inviteURL = URL+"/?room="+roomname+additional;
if (document.getElementById("additionalParams").value.trim().length){
additionalParams = document.getElementById("additionalParams").value.trim();
if (!additionalParams.startsWith("&")){
additionalParams = "&"+additionalParams;
}
inviteURL += additionalParams;
} else {
additionalParams = "";
}
if (document.getElementById("obfuscateInvites").checked){
obfuscateInvites = true;
inviteURL = processInvite(inviteURL);
} else {
obfuscateInvites = false;
}
document.getElementById("mainInviteLink").href = inviteURL
document.querySelectorAll(".inviteLink").forEach(ele=>{
if (ele.tagName == "A"){
ele.href = inviteURL
} else if (document.getElementById("obfuscateInvites").checked){
ele.innerHTML = inviteURL;
} else if (ele.tagName == "I"){
ele.innerHTML = "URL + ?room="+roomname+additional+additionalParams;
}
ele.onclick = function(evt){copyFunction(this, evt);};
});
if (event){
document.querySelectorAll(".inviteLink").forEach(ele=>{
ele.classList.remove("shake");
ele.classList.add("shake");
setTimeout(function(ele){ele.classList.remove("shake");},500,ele);
});
saveSession();
}
}
function addLayout(){
var layout = prompt("Enter your new layout as a JSON string", '[{"x":0,"y":0,"w":100,"h":100}]');
layout = JSON.parse(layout);
log(layout);
drawLayout(layout);
}
function addLayout2(item=false){
closeScene();
//document.getElementById("containermenu").classList.add("hidden");
iframe.classList.add("hidden");
document.getElementById("containermenu2").classList.add("hFadeIn");
document.getElementById("containermenu2").classList.remove("hFadeOut");
document.getElementById("containermenu2").classList.remove("hidden");
//document.getElementById("canvas").classList.remove("shake");
document.getElementById("iframeContainer").classList.remove("tFadeStart");
document.getElementById("iframeContainer").classList.add("tFadeIn");
document.getElementById("iframeContainer").classList.remove("tFadeout");
document.getElementById("canvas").classList.remove("hidden");
//if (item.target){
// item = item.target;
//}
try {
if (!item.target){
var obsSceneName = item.parentNode.querySelector("canvas").obsSceneName;
document.getElementById("canvas").obsSceneName = obsSceneName;
} else {
document.getElementById("canvas").obsSceneName = parseInt(Math.random()*1000000000);
document.getElementById("canvas").sceneName = parseInt(Math.random()*1000000000);
}
} catch(e){
document.getElementById("canvas").obsSceneName = "";
errorlog(e);
}
try {
var tar = item.parentNode.querySelector("canvas").sceneName;
document.getElementById("canvas").sceneName = tar;
} catch(e){
document.querySelectorAll(".editButton").forEach(ele=>{
ele.dataset.state = "inactive";
});
addOldElement({});
addElement();
}
}
function closeScene(){
//document.getElementById("containermenu").classList.remove("hidden");
iframe.classList.remove("hidden");
document.getElementById("containermenu2").classList.remove("hFadeIn");
document.getElementById("containermenu2").classList.add("hFadeOut");
//document.getElementById("containermenu2").classList.add("hidden");
document.getElementById("canvas").classList.add("hidden");
document.getElementById("canvas").innerHTML = "";
document.getElementById("iframeContainer").classList.remove("tFadeIn");
document.getElementById("iframeContainer").classList.add("tFadeout");
document.querySelectorAll(".editButton").forEach(ele=>{
ele.dataset.state = "inactive";
});
}
function compareZ( a, b ) { // sorts layout based on z-index.
var aa = a.z || a.zIndex || 0;
var bb = b.z || b.zIndex || 0;
if ( aa > bb ){
return 1;
}
if ( aa < bb ){
return -1;
}
return 0;
}
function processInvite(input){
if (input.startsWith("https://obs.ninja/")){
input = input.replace('https://vdo.ninja/', '');
} else if (input.startsWith("http://obs.ninja/")){
input = input.replace('http://vdo.ninja/', '');
} else if (input.startsWith("obs.ninja/")){
input = input.replace('vdo.ninja/', '');
} else if (input.startsWith("https://vdo.ninja/")){
input = input.replace('https://vdo.ninja/', 'vdo.ninja/');
} else if (input.startsWith("http://vdo.ninja/")){
input = input.replace('http://vdo.ninja/', 'vdo.ninja/');
}
input = input.replace('&view=', '&v=');
input = input.replace('&view&', '&v&');
input = input.replace('?view&', '?v&');
input = input.replace('?view=', '?v=');
input = input.replace('&videobitrate=', '&vb=');
input = input.replace('?videobitrate=', '?vb=');
input = input.replace('&bitrate=', '&vb=');
input = input.replace('?bitrate=', '?vb=');
input = input.replace('?audiodevice=', '?ad=');
input = input.replace('&audiodevice=', '&ad=');
input = input.replace('?label=', '?l=');
input = input.replace('&label=', '&l=');
input = input.replace('?stereo=', '?s=');
input = input.replace('&stereo=', '&s=');
input = input.replace('&stereo&', '&s&');
input = input.replace('?stereo&', '?s&');
input = input.replace('?webcam&', '?wc&');
input = input.replace('&webcam&', '&wc&');
input = input.replace('?remote=', '?rm=');
input = input.replace('&remote=', '&rm=');
input = input.replace('?password=', '?p=');
input = input.replace('&password=', '&p=');
input = input.replace('&maxvideobitrate=', '&mvb=');
input = input.replace('?maxvideobitrate=', '?mvb=');
input = input.replace('&maxbitrate=', '&mvb=');
input = input.replace('?maxbitrate=', '?mvb=');
input = input.replace('&height=', '&h=');
input = input.replace('?height=', '?h=');
input = input.replace('&width=', '&w=');
input = input.replace('?width=', '?w=');
input = input.replace('&quality=', '&q=');
input = input.replace('?quality=', '?q=');
input = input.replace('&cleanoutput=', '&clean=');
input = input.replace('?cleanoutput=', '?clean=');
input = input.replace('&maxviewers=', '&clean=');
input = input.replace('?maxviewers=', '?clean=');
input = input.replace('&framerate=', '&fr=');
input = input.replace('?framerate=', '?fr=');
input = input.replace('&fps=', '&fr=');
input = input.replace('?fps=', '?fr=');
input = input.replace('&permaid=', '&push=');
input = input.replace('?permaid=', '?push=');
input = input.replace('&roomid=', '&r=');
input = input.replace('?roomid=', '?r=');
input = input.replace('&room=', '&r=');
input = input.replace('?room=', '?r=');
return "https://invite.cam/"+CryptoJS.AES.encrypt(input, atob('T0JTTklOSkFGT1JMSUZF')).toString();
}
function drawLayout(layoutOriginal, sceneName=false, obsSceneName = ""){
var layout = [];
for (var i=0;i<layoutOriginal.length;i++){
if (!layoutOriginal[i]){
continue;
}
if (!("slot" in layoutOriginal[i])){
layoutOriginal[i].slot = i;
}
layout.push(layoutOriginal[i]);
}
layout.sort(compareZ);
var canvas = document.createElement('canvas');
canvas.width="80";
canvas.height="45";
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.rect(0, 0, 80, 45);
ctx.fillStyle = "#000";
ctx.fill();
for (var i=0;i<layout.length;i++){
ctx.fillStyle = colors[parseInt(layout[i].slot)];
ctx.lineWidth = 3;
var x = layout[i].x*0.8 || parseFloat(100*layout[i].xp/(pixelDensity*16/9)*0.8) || 0;
var y = layout[i].y*0.45 || parseFloat(100*layout[i].yp/(pixelDensity*16/9)*0.8) || 0;
var w = layout[i].w*0.8 || parseFloat(100*layout[i].wp/(pixelDensity*16/9)*0.8) || 0;
var h = layout[i].h*0.45 || parseFloat(100*layout[i].hp/(pixelDensity*16/9)*0.8) || 0;
ctx.beginPath();
ctx.rect(x, y, w, h);
ctx.fill();
}
canvas.layout = JSON.stringify(layout);
canvas.onclick = remoteActivate;
if (slotsNeeded<layout.length){
slotsNeeded = layout.length;
for (var i=0;i<slotsNeeded;i++){
if (!document.querySelector("div[data-slot='"+(i+1)+"']")){
var emptySlot = document.createElement("div");
emptySlot.innerHTML = "SLOT "+(i+1);
emptySlot.classList.add("empty");
emptySlot.dataset.slot = (i+1)+"";
emptySlot.style.backgroundColor = colors[i];
document.getElementById("col1").appendChild(emptySlot);
}
}
}
var editButton = document.createElement("button");
editButton.innerHTML = "🔧";
editButton.classList.add("editButton");
editButton.title = "Edit this layout";
editButton.dataset.state = "inactive";
editButton.onclick = function(){
getById("saveAndClose").classList.remove("shake");
if (this.dataset.state == "active"){
setTimeout(function(){getById("saveAndClose").classList.add("shake");},10);
}
document.querySelectorAll(".editButton").forEach(ele=>{
ele.dataset.state = "inactive";
});
this.dataset.state = "active";
addLayout2(this);
addOldElement(JSON.parse(this.parentNode.querySelector("canvas").layout));
}
var setButton = document.createElement("button");
setButton.innerHTML = "Set";
setButton.classList.add("setButton");
setButton.onclick = function(e){
remoteActivate(e, setButton.parentNode.querySelector("canvas").layout);
}
var hotkey = document.createElement("div");
hotkey.title = "Keyboard hotkey value. Drag elements around to change order.";
if (!sceneName){
sceneName = parseInt(Math.random()*1000000);
}
canvas.sceneName = sceneName;
canvas.obsSceneName = obsSceneName;
canvas.title = "Activate this layout; the view scene link will be updated";
var canvasContainer = document.createElement("div");
canvasContainer.appendChild(editButton);
//canvasContainer.appendChild(setButton);
canvasContainer.appendChild(canvas);
canvasContainer.appendChild(hotkey);
canvasContainer.classList.add("canvasContainer");
var eles = document.getElementById("containermenu").children;
for (var i =0;i<eles.length;i++){ // replace if existing
var t = eles[i].querySelector("canvas");
if (t && t.sceneName && (t.sceneName ==sceneName)){
t.parentNode.parentNode.insertBefore(canvasContainer, t.parentNode);
t.parentNode.remove();
return;
}
}
//if (customlayout){
// document.getElementById("addlayout").parentNode.insertBefore(canvasContainer, document.getElementById("addlayout"));
//} else {
document.getElementById("containermenu").appendChild(canvasContainer);
//}
return canvasContainer;
}
function drawAutoLayout(){
var canvas = document.createElement('canvas');
canvas.width="80";
canvas.height="45";
var ctx = canvas.getContext('2d');
var container = document.createElement('div');
container.classList.add("inline");
container.appendChild(canvas);
document.getElementById("containermenu").appendChild(container);
ctx.beginPath();
ctx.rect(0, 0, 80, 45);
ctx.fillStyle = "#000";
ctx.fill();
ctx.fillStyle = "#FFF";
ctx.font = "15px Arial";
ctx.fillText(" Clear ", 16, 20);
ctx.fillText(" Layout ", 12, 37);
canvas.layout = false;
canvas.onclick = remoteActivate;
}
function drawAddNewLayout(){
var canvas = document.createElement('canvas');
canvas.width="80";
canvas.height="45";
var ctx = canvas.getContext('2d');
var container = document.createElement('div');
container.classList.add("inline");
container.appendChild(canvas);
document.getElementById("containermenu").appendChild(container);
ctx.beginPath();
ctx.rect(0, 0, 80, 45);
ctx.fillStyle = "#000";
ctx.fill();
ctx.fillStyle = "#FFF";
ctx.font = "15px Arial";
ctx.fillText(" Add ", 20, 20);
ctx.fillText(" Layout ", 12, 37);
//canvas.id = "addlayout";
canvas.layout = false;
canvas.onclick = addLayout;
var hotkey = document.createElement('div');
hotkey.title = "Keyboard hotkey value. Drag elements around to change order.";
container.appendChild(hotkey);
}
function drawAddNewLayout2(){
var canvas = document.createElement('canvas');
canvas.width="80";
canvas.height="45";
var ctx = canvas.getContext('2d');
var container = document.createElement('div');
container.classList.add("inline");
//container.classList.add("canvasContainer");
container.appendChild(canvas);
document.getElementById("containermenu").appendChild(container);
ctx.beginPath();
ctx.rect(0, 0, 80, 45);
ctx.fillStyle = "#000";
ctx.fill();
ctx.fillStyle = "#FFF";
ctx.font = "15px Arial";
ctx.fillText(" Create ", 12, 20);
ctx.fillText(" Layout ", 12, 37);
//canvas.id = "addlayout";
canvas.layout = {};
canvas.onclick = addLayout2;
canvas.title = "Create a new custom layout, with the visual scene editor.";
var hotkey = document.createElement('div');
hotkey.title = "Keyboard hotkey value. Drag elements around to change order.";
container.appendChild(hotkey);
}
function drawAddNewLayout3(){
var canvas = document.createElement('canvas');
canvas.width="80";
canvas.height="45";
var ctx = canvas.getContext('2d');
var container = document.createElement('div');
container.classList.add("inline");
//container.classList.add("canvasContainer");
container.appendChild(canvas);
container.id = "automix"
document.getElementById("containermenu").appendChild(container);
ctx.beginPath();
ctx.rect(0, 0, 80, 45);
ctx.fillStyle = "#000";
ctx.fill();
ctx.fillStyle = "#FFF";
ctx.font = "15px Arial";
ctx.fillText(" Auto ", 18, 20);
ctx.fillText(" Mix All ", 13, 37);
//canvas.id = "addlayout";
canvas.layout = false;
canvas.onclick = remoteActivate;
canvas.title = "This layout will auto-mix all available videos into a single dynamic layout.";
var hotkey = document.createElement('div');
hotkey.title = "Keyboard hotkey value. Drag elements around to change order.";
container.appendChild(hotkey);
}
function pullUp(event){
this.parentNode.zIndex = parseInt(this.parentNode.zIndex || 0) + 1;
this.parentNode.style.opacity = "0.9";
this.parentNode.parent.style.zIndex=this.parentNode.zIndex;
this.parentNode.dimensions = updateSize(this.parentNode);
}
function pushDown(event){
this.parentNode.zIndex = parseInt(this.parentNode.zIndex || 0) - 1;
if (this.parentNode.zIndex<0){
this.parentNode.zIndex=0;
this.parentNode.style.opacity = "1.0";
} else {
this.parentNode.style.opacity = "0.9";
}
this.parentNode.parent.style.zIndex=this.parentNode.zIndex;
this.parentNode.dimensions = updateSize(this.parentNode);
}
function updateSize(supercontainer){
var container = supercontainer.container || supercontainer;
supercontainer = container.parent;
if (container.dimensions){
var dimensions = container.dimensions;
} else {
var dimensions = document.createElement("div");
dimensions.className = "dimensions";
container.dimensions = dimensions;
}
dimensions.innerHTML = "";
var part = document.createElement("div");
dimensions.appendChild(part);
part.className = "part";
part.innerHTML = parseInt(supercontainer.style.width);
part.onclick = function(){
var value = prompt("Change the width",this.innerHTML);
if (value!==null){
value=parseInt(value);
}
if (value>=0){
supercontainer.style.width = value+"px";
updateSize(supercontainer);
}
};
var part = document.createElement("div");
dimensions.appendChild(part);
part.className = "part0";
part.innerHTML = "x";
var part = document.createElement("div");
dimensions.appendChild(part);
part.className = "part";
part.innerHTML = parseInt(supercontainer.style.height);
part.onclick = function(){
var value = prompt("Change the height",this.innerHTML);
if (value!==null){
value=parseInt(value);
}
if (value>=0){
supercontainer.style.height = value+"px";
updateSize(supercontainer);
}
};
var part = document.createElement("div");
dimensions.appendChild(part);
part.className = "part0";
part.innerText = ":";
part.style.margin = "0px 5px";
var part = document.createElement("div");
dimensions.appendChild(part);
part.className = "part";
part.innerHTML += parseInt(supercontainer.style.left);
part.onclick = function(){
var value = prompt("Left offset",this.innerHTML);
if (value!==null){
value=parseInt(value);
}
if (value>=0){
supercontainer.style.left = value+"px";
updateSize(supercontainer);
}
};
var part = document.createElement("div");
dimensions.appendChild(part);
part.className = "part0";
part.innerHTML = "x";
var part = document.createElement("div");
dimensions.appendChild(part);
part.className = "part";
part.innerHTML += parseInt(supercontainer.style.top);
part.onclick = function(){
var value = prompt("Top offset",this.innerHTML);
if (value!==null){
value=parseInt(value);
}
if (value>=0){
supercontainer.style.top = value+"px";
updateSize(supercontainer);
}
};
var part = document.createElement("div");
dimensions.appendChild(part);
part.className = "part0";
part.innerHTML += " , layer: "+parseInt(container.zIndex);
dimensions.style = "position:absolute;left:10px;bottom:0;max-width:250px;height:20px;";
return dimensions;
}
function addOldElement(object){
document.getElementById("canvas").innerHTML = "";
var hh = pixelDensity;
var ww = parseInt((pixelDensity*16/9) * (aspectRatio/(16/9)));
document.documentElement.style.setProperty('--iframe-width', ww+"px");
document.documentElement.style.setProperty('--iframe-height',hh+"px");
for (var i=0;i<object.length;i++){
if (object[i] == null){continue;}
var slot = parseInt(object[i].slot) || 0;
var color = colors[slot];
var containerSuper = document.createElement("div");
document.getElementById("canvas").appendChild(containerSuper);
var container = document.createElement("div");
containerSuper.appendChild(container);
containerSuper.container = container;
container.parent = containerSuper;
container.className = "widget ui-widget-content";
containerSuper.className = "draggable resizable";
container.slot = slot;
if ("cover" in object[i]){
container.cover = object[i].cover || false;
} else if ("c" in object[i]){
container.cover = object[i].c || false;
} else {
container.cover = true;
}
container.zIndex = parseInt(object[i].zIndex) || parseInt(object[i].z) || 0;
//container.backgroundColor = object[i].backgroundColor || "#0000";
container.borderThickness = object[i].borderThickness || 0;
container.animated = parseInt(object[i].animated) || 0;
container.borderColor = object[i].borderColor || "#0000";
container.backgroundMedia = object[i].backgroundMedia || "";
container.iframeSrc = object[i].iframeSrc || "";
container.defaultStreamID = object[i].defaultStreamID || "";
container.margin = object[i].margin || 0;
container.muted = object[i].muted || false;
container.rounded = object[i].rounded || 0;
var hh = pixelDensity;
var ww = (pixelDensity*16/9) * (aspectRatio/(16/9));
var yoffset = object[i].y*hh/100 || object[i].yp || 0;
var xoffset = object[i].x*ww/100 || object[i].xp || 0;
var w = object[i].w*ww/100 || object[i].wp || 0;
var h = object[i].h*hh/100 || object[i].hp || 0;
containerSuper.style = "z-index:"+container.zIndex+";position: absolute;left:"+xoffset+"px;top:"+yoffset+"px;width:"+w+"px;height:"+h+"px;";
container.style = "background-color:"+color+";";
var h3 = document.createElement("h3");
h3.className = "ui-widget-header";
h3.innerHTML = "drag/resize me";
h3.title = "Drag with your mouse to move. Tip: Hold CTRL (cmd) to disable grid snapping";
container.appendChild(h3);
var button = document.createElement("button");
//button.className = "ui-widget-header";
button.innerHTML = "Settings";
button.onclick = settings;
container.appendChild(button);
var button = document.createElement("button");
//button.className = "ui-widget-header";
button.innerHTML = "Delete";
button.onclick = deleteElement;
container.appendChild(button);
var button = document.createElement("button");
//button.className = "ui-widget-header";
button.innerHTML = "Pull Front";
button.onclick = pullUp;
container.appendChild(button);
var button = document.createElement("button");
//button.className = "ui-widget-header";
button.innerHTML = "Push Back";
button.onclick = pushDown;
container.appendChild(button);
//part.onclick = function(){console.log(this.innerHTML);};
var dimensions = updateSize(container);
container.appendChild(dimensions);
container.dimensions = dimensions;
containerSuper.ondrag = function(e){
updateSize(this);
}
containerSuper.onresize = function(e){
updateSize(this);
}
$(function(){
$(".draggable").draggable({ snap: true , grid: [ 10,10 ] });
});
$(function(){
$(".resizable").resizable({ snap: true , grid: [ 10, 10] });
});
}
}
function addElement(x=false,y=false){
var eles = document.querySelectorAll(".widget");
var indexs = [];
for (var i=0;i<eles.length;i++){
indexs.push(parseInt(eles[i].slot));
}
indexs.sort();
var slot = 0;
for (var i=0;i<indexs.length;i++){
if (slot!=indexs[i]){
break;
} else {
slot+=1;
}
}
var color = colors[slot];
var containerSuper = document.createElement("div");
document.getElementById("canvas").appendChild(containerSuper);
var container = document.createElement("div");
containerSuper.appendChild(container);
containerSuper.container = container;
container.parent = containerSuper;
container.className = "widget ui-widget-content";
containerSuper.className = "draggable resizable";
container.slot = slot;
container.zIndex = 10;
var yoffset = 20*(slot+1);
var xoffset = 20*(slot+1);
var w = 640;
var h = 360;
containerSuper.style = "z-index:"+container.zIndex+";position: absolute;left:"+xoffset+"px;top:"+yoffset+"px;width:"+w+"px;height:"+h+"px;";
container.style = "background-color:"+color+";";
var h3 = document.createElement("h3");
h3.className = "ui-widget-header";
h3.innerHTML = "drag/resize me";
h3.title = "Drag with your mouse to move. Tip: Hold CTRL (cmd) to disable grid snapping";
container.appendChild(h3);
var button = document.createElement("button");
//button.className = "ui-widget-header";
button.innerHTML = "Settings";
button.onclick = settings;
container.appendChild(button);
var button = document.createElement("button");
//button.className = "ui-widget-header";
button.innerHTML = "Delete";
button.onclick = deleteElement;
container.appendChild(button);
var button = document.createElement("button");
//button.className = "ui-widget-header";
button.innerHTML = "Pull Front";
button.onclick = pullUp;
container.appendChild(button);
var button = document.createElement("button");
//button.className = "ui-widget-header";
button.innerHTML = "Push Back";
button.onclick = pushDown;
container.appendChild(button);
var dimensions = updateSize(container);
container.appendChild(dimensions);
container.dimensions = dimensions;
containerSuper.ondrag = function(e){
updateSize(this);
}
containerSuper.onresize = function(e){
updateSize(this);
}
$(function(){
$(".draggable").draggable({ snap: true , grid: [ 10,10 ] });
});
$(function(){
$(".resizable").resizable({ snap: true , grid: [ 10, 10] });
});
}
function combinedLayout(layout){
var combined = {};
for (var i=0;i<layout.length;i++){
if (!layout[i]){continue;}
var stream = null;
if ("slot" in layout[i]){
try {
stream = guestPositions[parseInt(layout[i].slot)+1]; // slot 1 is index of 0, but slot 0 is considered NULL; I need to stream line this a bit
} catch(e){
errorlog(e);
stream = null;
}
}
if (!stream){
//if (layout[i].defaultStreamID){
// combined[layout[i].defaultStreamID] = layout[i];
//} else
if (combined[""]){
combined[""].push(layout[i]);
} else {
combined[""] = [layout[i]];
}
} else {
combined[stream] = layout[i];
}
}
return combined;
}
function remoteActivate(event=null, layout=null){
if (event.target && event.target.layout && layout===null){
layout = event.target.layout;
var layoutButtons = document.querySelectorAll(".pressed");
for (var i = 0;i<layoutButtons.length;i++){
layoutButtons[i].classList.remove("pressed");
}
event.target.parentNode.classList.add("pressed");
}
lastLayoutRaw = layout;
var combined = false;
if (layout){
layout = JSON.parse(layout);
combined = combinedLayout(layout);
}
currentLayout = combined; // global current state
lastLayout = {"scene":"0", "layout":combined};
if (event.target && event.target.obsSceneName && syncOBS){
var obsCommand = {"action": "setCurrentScene", "value": event.target.obsSceneName};
log({"scene":"0", "layout":combined, "obsCommand": obsCommand});
iframe.contentWindow.postMessage({"scene":"0", "layout":combined, "obsCommand": obsCommand}, '*');
} else {
log({"scene":"0", "layout":combined});
iframe.contentWindow.postMessage({"scene":"0", "layout":combined}, '*');
}
}
function deleteElement(event){
this.parentNode.remove();
}
function settings(){
var parent = this.parentNode;
if (parent.setting){
parent.setting.classList.toggle("hidden");
return;
}
var setEle = document.createElement("div");
setEle.parent = parent;
parent.setting = setEle;
parent.parent.appendChild(setEle);
setEle.className = "settings";
setEle.innerHTML = "";
setEle.style.left = 0;
setEle.style.top = "63px";
setEle.style.margin = "5px";
setEle.style.padding = "10px";
var br = document.createElement("br");
setEle.appendChild(br);
var label = document.createElement("label");
label.innerHTML = "IFrame (URL)";
label.title = "If no video stream is present, load the specified website here as an IFRAME instead";
setEle.appendChild(label);
var input = document.createElement("input");
input.style.width = "200px";
setEle.appendChild(input);
input.value = parent.iframeSrc || "";
input.onchange = function(){
parent.iframeSrc = this.value;
}
var br = document.createElement("br");
setEle.appendChild(br);
var label = document.createElement("label");
label.innerHTML = "Background Image (URL)";
label.title = "Set a background image for this space";
setEle.appendChild(label);
var input = document.createElement("input");
input.style.width = "200px";
setEle.appendChild(input);
input.value = parent.backgroundMedia || "";
input.onchange = function(){
parent.backgroundMedia = this.value;
}
var br = document.createElement("br");
setEle.appendChild(br);
var label = document.createElement("label");
label.innerHTML = "Default Stream ID";
label.title = "If the specified stream is connected, and both it and this element are unassigned, load that stream here.";
setEle.appendChild(label);
var input = document.createElement("input");
input.style.width = "200px";
setEle.appendChild(input);
input.value = parent.defaultStreamID || "";
input.onchange = function(){
parent.defaultStreamID = this.value;
}
<!-- var br = document.createElement("br"); -->
<!-- setEle.appendChild(br); -->
<!-- var label = document.createElement("label"); -->
<!-- label.innerHTML = "Hex Color"; -->
<!-- setEle.appendChild(label); -->
<!-- var input = document.createElement("input"); -->
<!-- input.style.width = "80px"; -->
<!-- setEle.appendChild(input); -->
<!-- input.value = parent.backgroundColor || "#000"; -->
<!-- input.onchange = function(){ -->
<!-- parent.backgroundColor = this.value; -->
<!-- } -->
var br = document.createElement("br");
setEle.appendChild(br);
var label = document.createElement("label");
label.innerHTML = "Border Thickness (px)";
setEle.appendChild(label);
var input = document.createElement("input");
input.type = "number";
input.style.width = "80px";
setEle.appendChild(input);
input.value = parent.borderThickness || 0;
input.onchange = function(){
parent.borderThickness = this.value;
}
var br = document.createElement("br");
setEle.appendChild(br);
var label = document.createElement("label");
label.innerHTML = "Margin (px)";
setEle.appendChild(label);
var input = document.createElement("input");
input.type = "number";
input.style.width = "80px";
setEle.appendChild(input);
input.value = parent.margin || "0";
input.onchange = function(){
parent.margin = this.value;
}
var br = document.createElement("br");
setEle.appendChild(br);
var label = document.createElement("label");
label.innerHTML = "Border/Background Color";
setEle.appendChild(label);
var input = document.createElement("input");
input.type = "text";
input.style.width = "80px";
setEle.appendChild(input);
input.value = parent.borderColor || "#0000";
input.onchange = function(){
parent.borderColor = this.value;
}
var br = document.createElement("br");
setEle.appendChild(br);
var label = document.createElement("label");
label.innerHTML = "Rounded Edges (px)";
setEle.appendChild(label);
var input = document.createElement("input");
input.type = "number";
input.style.width = "80px";
setEle.appendChild(input);
input.value = parent.rounded || 0;
input.onchange = function(){
parent.rounded = this.value;
}
var br = document.createElement("br");
setEle.appendChild(br);
var br = document.createElement("br");
setEle.appendChild(br);
var label = document.createElement("label");
label.innerHTML = "Media fully cover area?" ;
label.title = "This applies to the background image and video element.";
setEle.appendChild(label);
var checkbox = document.createElement("input");
checkbox.type = "checkbox";
setEle.appendChild(checkbox);
if ("cover" in parent){
checkbox.checked = parent.cover || false;
} else {
checkbox.checked = true;
}
checkbox.onchange = function(){
parent.cover = this.checked;
}
var br = document.createElement("br");
setEle.appendChild(br);
var br = document.createElement("br");
setEle.appendChild(br);
var label = document.createElement("label");
label.innerHTML = "Animated transitions";
setEle.appendChild(label);
var input = document.createElement("input");
var checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.linked = input;
input.linked = checkbox;
setEle.appendChild(checkbox);
if (parent.animated===true){
parent.animated = 300;
}
if (parent.animated){
checkbox.checked = true;
} else {
input.disabled = true;
}
checkbox.onchange = function(){
if (this.checked){
this.linked.disabled = null;
delete this.linked.disabled;
parent.animated = this.linked.value || 300;
} else {
this.linked.disabled = true;
parent.animated = 0;
}
}
var label = document.createElement("label");
label.innerHTML = "Speed (ms)";
label.style.marginLeft = "10px";
setEle.appendChild(label);
input.type = "number";
input.style.width = "70px";
setEle.appendChild(input);
input.value = parent.animated || 0;
input.onchange = function(){
parent.animated = this.value;
}
var br = document.createElement("br");
setEle.appendChild(br);
var br = document.createElement("br");
setEle.appendChild(br);
var button = document.createElement("button");
button.innerHTML = "OK";
setEle.appendChild(button);
button.onclick = function(){
setEle.classList.add("hidden");
}
setEle.classList.remove("hidden");
}
function copyJSON(){
var layout = [];
var compute = window.getComputedStyle(document.getElementById("canvas"));
var hh = parseInt(compute.height);
var ww = parseInt(compute.width);
var eles = document.querySelectorAll(".widget");
for (var i=0;i<eles.length;i++){
compute = window.getComputedStyle(eles[i].parent);
var ele = {};
if (absolutePixel){
ele.wp = parseFloat(compute.width);
ele.hp = parseFloat(compute.height);
ele.xp = parseFloat(compute.left);
ele.yp = parseFloat(compute.top);
} else {
ele.w = parseFloat(compute.width)/ww*100;
ele.h = parseFloat(compute.height)/hh*100;
ele.x = parseFloat(compute.left)/ww*100;
ele.y = parseFloat(compute.top)/hh*100;
}
try {
ele.z = parseInt(eles[i].zIndex) || 0;
ele.slot = parseInt(eles[i].slot) || i;
ele.cover = eles[i].cover || false;
if ("cover" in eles[i]){
ele.cover = eles[i].cover || false;
} else {
ele.cover = true;
}
//ele.backgroundColor = eles[i].backgroundColor || "#000";
ele.borderThickness = parseInt(eles[i].borderThickness) || 0;
ele.animated = eles[i].animated || 0;
ele.borderColor = eles[i].borderColor || "#0000";
ele.backgroundMedia = eles[i].backgroundMedia || "";
ele.iframeSrc = eles[i].iframeSrc || "";
ele.defaultStreamID = eles[i].defaultStreamID || "";
ele.margin = parseInt(eles[i].margin) || 0;
ele.rounded = parseInt(eles[i].rounded) || 0;
ele.muted = eles[i].muted || false;
} catch(e){errorlog(e);}
layout.push(ele);
}
log(layout);
var combined = combinedLayout(layout);
prompt("Layout as URL-encoded JSON. StreamIDs are based on current and default values.", encodeURIComponent(JSON.stringify(combined)));
}
function setobsSceneName(){
var obsSceneName = document.getElementById("canvas").obsSceneName || "";
var input = prompt("Enter the OBS scene you want to link this scene to.\n\nWhen changing layouts it will auto-select the linked OBS scene, and vice versas.\n\nTo activate the syncing you also need to enable this feature in the mixer settings and enable permissions in the OBS browser source.", obsSceneName);
if (input !==null){
document.getElementById("canvas").obsSceneName = input;
}
}
function saveScene(makenew=false, event=null){
var scene = [];
var compute = window.getComputedStyle(document.getElementById("canvas"));
var hh = parseInt(compute.height);
var ww = parseInt(compute.width);
var eles = document.querySelectorAll(".widget");
for (var i=0;i<eles.length;i++){
var ele = {};
if (absolutePixel){
ele.wp = parseFloat(eles[i].parent.style.width) || 0;
ele.hp = parseFloat(eles[i].parent.style.height) || 0;
ele.xp = parseFloat(eles[i].parent.style.left) || 0;
ele.yp = parseFloat(eles[i].parent.style.top) || 0;
} else {
ele.w = parseFloat(eles[i].parent.style.width)/ww*100 || 0;
ele.h = parseFloat(eles[i].parent.style.height)/hh*100 || 0;
ele.x = parseFloat(eles[i].parent.style.left)/ww*100 || 0;
ele.y = parseFloat(eles[i].parent.style.top)/hh*100 || 0;
}
//ele.w = parseFloat(eles[i].style.width)/ww*100;
//ele.h = parseFloat(eles[i].style.height)/hh*100;
//ele.x = parseFloat(eles[i].style.left)/ww*100;
//ele.y = parseFloat(eles[i].style.top)/hh*100;
try {
if ("slot" in eles[i]){
ele.slot = parseInt(eles[i].slot);
}
if ("cover" in eles[i]){
ele.cover = eles[i].cover || false;
} else {
ele.cover = true;
}
ele.zIndex = parseInt(eles[i].zIndex) || 0;
//ele.backgroundColor = eles[i].backgroundColor || "#000";
ele.borderThickness = parseInt(eles[i].borderThickness) || 0;
ele.animated = eles[i].animated || 0;
ele.borderColor = eles[i].borderColor || "#0000";
ele.backgroundMedia = eles[i].backgroundMedia || "";
ele.iframeSrc = eles[i].iframeSrc || "";
ele.defaultStreamID = eles[i].defaultStreamID || "";
ele.margin = parseInt(eles[i].margin) || 0;
ele.rounded = parseInt(eles[i].rounded) || 0;
ele.muted = eles[i].muted || false;
} catch(e){errorlog(e);}
scene.push(ele);
//scene[sid] = ele;
}
log(scene);
if (makenew){
var canvasContainer = drawLayout(scene);
popupMessage(event, "Saved as a new scene");
document.querySelectorAll(".editButton").forEach(ele=>{
ele.dataset.state = "inactive";
});
try {
canvasContainer.focus();
canvasContainer.querySelector(".editButton").dataset.state = "active";
canvasContainer.classList.remove("shake");
setTimeout(function(canvasContainer){canvasContainer.classList.add("shake");},10,canvasContainer);
} catch(e){}
} else {
var sceneName = document.getElementById("canvas").sceneName || parseInt(Math.random()*1000000000);
var obsSceneName = document.getElementById("canvas").obsSceneName || "";
log("'sceneName: "+sceneName);
drawLayout(scene, sceneName, obsSceneName);
popupMessage(event, "Scene saved");
}
saveSession();
}
function removeScene(){
var sceneName = document.getElementById("canvas").sceneName || false;
if (sceneName){
var eles = document.getElementById("containermenu").children;
for (var i =0;i<eles.length;i++){ // replace if existing
var t = eles[i].querySelector("canvas");
if (t && t.sceneName && (t.sceneName ==sceneName)){
t.parentNode.remove();
break
}
}
}
popupMessage(event, "Scene removed");
closeScene();
saveSession();
}
function saveSession(){
var layouts = document.querySelectorAll(".canvasContainer>canvas");
savedSession.layouts = [];
savedSession.settings = {};
savedSession.settings.updateOnSlotChange = updateOnSlotChange;
savedSession.settings.assignSlotToGuest = assignSlotToGuest;
savedSession.settings.toggleLabel = toggleLabel;
savedSession.settings.toggleBroadcast = toggleBroadcast;
savedSession.settings.obfuscateInvites = obfuscateInvites;
savedSession.settings.additionalParams = additionalParams;
savedSession.settings.syncOBS = syncOBS;
savedSession.settings.remoteSyncOBS = remoteSyncOBS;
savedSession.settings.aspectRatio = aspectRatio;
savedSession.settings.pixelDensity = pixelDensity;
savedSession.settings.absolutePixel = absolutePixel;
savedSession.settings.showDirector = showDirector;
savedSession.settings.toggleEchoInvite = toggleEchoInvite;
savedSession.settings.toggleDenoiseInvite = toggleDenoiseInvite;
savedSession.settings.toggleAutogainInvite = toggleAutogainInvite;
savedSession.settings.advancedMode = advancedMode;
savedSession.version = 1;
savedSession.obsScenes = [];
for (var i=0;i<layouts.length;i++){
if (!layouts[i].layout){continue;}
try {
savedSession.layouts.push(JSON.parse(layouts[i].layout));
var obs = layouts[i].obsSceneName || false;
savedSession.obsScenes.push(obs);
} catch(e){
errorlog(e);
}
}
setStorage("savedSession", JSON.stringify(savedSession));
if (iframe){
if (remoteSyncOBS){
iframe.contentWindow.postMessage({ layouts: savedSession.layouts , obsSceneTriggers: savedSession.obsScenes}, "*");
} else {
iframe.contentWindow.postMessage({ layouts: savedSession.layouts }, "*");
}
}
log(getStorage("savedSession"));
}
var widgetSrc = false;
function changeAspectRatio(ar, button=false){
if (button){
document.querySelectorAll(".aspectbutton").forEach(ele=>{
if (ele == button){return;}
ele.checked = false;
ele.value = false;
});
aspectRatio = ar;
saveSession();
}
document.documentElement.style.setProperty('--aspect-ratio', ar);
if (widgetSrc){
document.documentElement.style.setProperty('--aspect-ratio-widget', ar/0.75);
} else {
document.documentElement.style.setProperty('--aspect-ratio-widget', ar);
}
}
function changeAbsolutePosition(value, button){
document.querySelectorAll(".absolutePosition").forEach(ele=>{
if (ele == button){return;}
ele.checked = false;
ele.value = false;
});
absolutePixel = value;
saveSession();
updateLayouts();
}
function changePixelDensity(pd, button){
document.querySelectorAll(".pixeldensity").forEach(ele=>{
if (ele == button){return;}
ele.checked = false;
ele.value = false;
});
pixelDensity = pd;
saveSession();
var hh = pixelDensity;
var ww = parseInt((pixelDensity*16/9) * (aspectRatio/(16/9)));
changeAspectRatio(aspectRatio,false);
document.documentElement.style.setProperty('--iframe-width', ww+"px");
document.documentElement.style.setProperty('--iframe-height', hh+"px");
updateLayouts();
//document.getElementById('canvas').style.width = ww+"px";
//document.getElementById('canvas').style.height = hh+"px";
}
//let modal = document.querySelector("#modal");
document.querySelectorAll(".close-btn").forEach(ele2=>{
ele2.onclick = function(){
document.querySelectorAll(".modal").forEach(ele=>{
ele.classList.add("hidden");
});
}
});
document.querySelectorAll(".modal").forEach(ele=>{
ele.onclick = function(e){
if (e.target.classList.contains("modal")){
e.target.classList.add("hidden");
}
}
});
if (roomname){
loadIframe();
} else {
document.getElementById("welcomeWindow").style.display = "block";
}
</script>
</body>
</html>