v22 beta updates

Current progress updates on version 22 BETA.  This is not tested well enough yet to be considered production ready.

Please see v21-stable for a stable tested version.
This commit is contained in:
Steve Seguin 2022-07-02 04:47:50 -04:00 committed by GitHub
parent e7084f288a
commit 2fde8ebe27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 2534 additions and 1363 deletions

View File

@ -104,10 +104,10 @@
</div>
</div>
<div class="card">
<h2>MKV to MP4 (no transcoding)</h2>
<h2>MKV (or FLV) to MP4 (no transcoding)</h2>
<div>
<small>The same as: fmpeg -i INPUTFILE -vcodec copy -acodec copy output.mp4</small>
<input type="file" id="uploader2" accept=".mkv" title="Convert MKV to MP4">
<input type="file" id="uploader2" accept=".mkv, .flv, .webm" title="Convert MKV (or FLV) to MP4">
</div>
</div>
<div id="processing">

View File

@ -278,9 +278,8 @@
</style>
</head>
<body >
<div id="header" style="-webkit-app-region: drag;color:#6f6f6f;font-size:40px; line-height: 40px; padding: 5px 10px; letter-spacing: 3; font-weight: bold;">Electron Capture</div>
<body>
<div id="header" style="-webkit-app-region: drag; color:#6f6f6f;font-size:20px; line-height: 20px; padding: 5px 10px; letter-spacing: 3; font-weight: bold;">Electron Capture</div>
<div class="container" >
<div id='warning4mac' style="display:none;"> ✨ Great News! OBS v26.1.2 <a href="https://github.com/obsproject/obs-browser/issues/209#issuecomment-748683083">now supports</a> VDO.Ninja without needing the Electron Capture app! 🥳</div>

View File

@ -57,7 +57,7 @@
<meta property="twitter:image" content="./media/vdoNinja_logo_full.png" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#ffffff" />
<link rel="stylesheet" href="./main.css?ver=159" />
<link rel="stylesheet" href="./main.css?ver=165" />
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/adapter.min.js"></script>
<style id="lightbox-animations" type="text/css"></style>
<!-- <link rel="manifest" href="manifest.json" /> -->
@ -82,8 +82,9 @@
<link itemprop="url" href="./media/vdoNinja_logo_full.png" />
</span>
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/CodecsHandler.js?ver=37"></script>
<script type="text/javascript" crossorigin="anonymous" src="./webrtc.js?ver=451"></script>
<script type="text/javascript" crossorigin="anonymous" src="./webrtc.js?ver=472"></script>
<input id="zoomSlider" type="range" style="display: none;" />
<span id="electronDragZone" style="pointer-events: none; z-index:-10; position:absolute;top:0;left:0;width:100%;height:2%;-webkit-app-region: drag;min-height:20px;"></span>
<div id="header">
<a id="logoname" href="./" style="text-decoration: none; color: white; margin: 0 2px 0px 8px;">
@ -91,7 +92,7 @@
<font id="qos">V</font>DO.Ninja
</span>
</a>
<div id="head1" style="display: inline-block; padding:1px; position: relative;">
<div id="head1">
<input type="text" autocorrect="off" autocapitalize="none" id="joinroomID" name="joinroomID" size="22" placeholder="Join by Room Name here" alt="Enter a room name to join" title="Enter a room name to quick join" onkeyup="jumptoroom(event)"/>
<button onclick="jumptoroom();" role="button" aria-pressed="false" alt="Join room" title="Join room" >GO</button>
</div>
@ -200,7 +201,7 @@
</div>
<span id="miniPerformer" style="pointer-events: auto;" class="hidden"></span>
<span id="rooms" class="hidden" style="padding-top:3px;padding-left:6px;pointer-events: auto;color:#fff;"></span>
<span id="groups" class="hidden" style="padding-top:3px;padding-left:6px;pointer-events: auto;color:#fff;text-align: center;"></span>
<div id="hangupbutton2" onmousedown="event.preventDefault(); event.stopPropagation();" title="Cancel the Director's Video/Audio" onclick="hangup2()" class="hidden float" tabindex="26" role="button" aria-pressed="false" onkeyup="enterPressedClick(event,this);" style="cursor: pointer;" alt="Disconnect Direcotor's cam">
<i class="toggleSize my-float las la-phone rotate225" aria-hidden="true"></i>
</div>
@ -258,6 +259,7 @@
</b>
</div>
<input type="text" autocorrect="off" autocapitalize="none" id="videoname1" placeholder="Enter a room name here" onkeydown="checkStrengthRoom(event, 'securityLevelRoom');" onchange="checkStrengthRoom(event, 'securityLevelRoom');" onkeyup="enterPressed(event, createRoom);" maxlength="30" style="max-width: 431px;width: 100%;font-size: 110%; padding: 5px;" />
<button onclick="getById('videoname1').value = session.generateRandomString();getById('securityLevelRoom').style.display='none';" title="Generate a random room name" class="randomRoomName"></button>
<div id="securityLevelRoom" style="display:none;margin-top:3px;position:relative;top:3px;font-size:0.8em;"></div>
</th>
@ -381,12 +383,13 @@
<span><i class="las la-headphones"></i><span data-translate="you-are-using-headphones-earphones">You are using headphones / earphones</span></span>
</span>
<span id="videoMenu" class="videoMenu">
<i class="las la-video"></i><span data-translate="video-source"> Video Source </span>
<span style="padding-right:2px;display:inline-block;position:relative;top:1px;"><i class="las la-video"></i><span data-translate="video-source"> Video Source </span></span>
<span style="display:inline-block;padding-top: 5px;">
<select id="videoSourceSelect" ></select>
<span id="gear_webcam" style="display: inline-block; cursor:pointer;" onclick="toggle(document.getElementById('videoSettings'));">
&nbsp;&nbsp;
<span id="gear_webcam" onclick="toggle(document.getElementById('videoSettings'));">
<i class="las la-cog" style="font-size: 140%; vertical-align: middle;" aria-hidden="true"></i>
</span>
</span>
<div id="cameraTip1" class="cameraTip hidden">
<i class="las la-info-circle"></i>
<p><span id="cameraTipContext1"></span></p>
@ -430,11 +433,15 @@
</label>
</li>
</ul>
<div id="audioTip1" class="cameraTip hidden">
<i class="las la-info-circle"></i>
<p><span id="audioTipContext1"></span></p>
</div>
</div>
<br />
<span id="headphonesDiv" class="audioMenu">
<div class="audioTitle2">
<i class="las la-headphones"></i><span data-translate="select-output-source"> Audio Output Destination:
<i class="las la-headphones"></i><span data-translate="select-output-source"> Audio Output Destination
</span><button onclick="playtone()" class="white" style="margin:0 0 0 15px;padding: 2px 10px 0px 10px;" type="button">Test</button></div>
<select id="outputSource" ></select>
<div id="headphoneTip1" class="cameraTip hidden">
@ -445,7 +452,7 @@
<div id="avatarDiv" class="hidden">
<div style="text-align: left;display: inline-block;">
<i class="las la-robot"></i><span data-translate="select-avatar-image"> Default Avatar / Placeholder Image: </span>
<i class="las la-robot"></i><span data-translate="select-avatar-image"> Default Avatar / Placeholder Image </span>
</div>
<div id="selectAvatarImage" style="margin-top:10px;">
<img src="./media/avatar.webp" loading="lazy" id="defaultAvatar1" style="max-width:130px;max-height:73.5px;display:inline-block;margin:10px;cursor:pointer;" onclick="changeAvatarImage(event, this);"/>
@ -462,7 +469,7 @@
<div id="effectsDiv">
<div style="text-align: left;display: inline-block;">
<i class="las la-robot"></i><span data-translate="select-digital-effect"> Digital Video Effects: </span>
<i class="las la-robot"></i><span data-translate="select-digital-effect"> Digital Video Effects </span>
</div>
<select id="effectSelector" onchange="effectsDynamicallyUpdate(event, this);">
<option value="0" data-translate="no-effects-applied">No effects applied</option>
@ -484,13 +491,13 @@
</div>
<div id="selectEffectAmount" style="display:none;margin-top:10px;">
<label for="selectEffectAmountInput" style="width: 113px;display: inline-block;">Effect amount:</label>
<label for="selectEffectAmountInput" style="width: 113px;display: inline-block;">Effect Amount</label>
<input id="selectEffectAmountInput" style="display: inline-block;width: 350px; max-width: 60%;margin:6px 0;" name="selectEffectAmountInput" title="Adjust the amount of effect applied" type="range" oninput="changeEffectAmount(event, this)" onchange="changeEffectAmount(event, this)" min="0" step="1" max="20">
</div>
</div>
<span id="addPasswordBasic" >
<i class="las la-key"></i><span data-translate="add-a-password"> Add a Password:</span>
<i class="las la-key"></i><span data-translate="add-a-password"> Add a Password</span>
<input type="text" id="passwordBasicInput" placeholder="optional" style="border: solid 1px #AAA;
padding: 4px 6px;
width: 200px;
@ -866,7 +873,6 @@
<a href='https://github.com/steveseguin/vdoninja'>VDO.Ninja, by Steve Seguin</a>
</div>
</div>
<span id="electronDragZone" style="pointer-events: none; z-index:-10; position:absolute;top:0;left:0;width:100%;height:5%;-webkit-app-region: drag;min-height:33px;"></span>
<div id="gridlayout" >
<div id="roomHeader" style="display:none">
<div class="hideLinksClass">
@ -1089,12 +1095,20 @@
<span data-translate="prefix-screenshare">Prefix screenshare IDs</span>
</div>
<div style="display:inline-block;margin-top: 12px; position: relative; ">
<label class="switch" title="This low-fi video codec uses very little CPU, even with dozens of active viewers.">
<input type="checkbox" data-param="&webp" onchange="updateLinkWebP(1,this);">
<label class="switch" title="This mode encodes the video and audio into chunks, which are shared with multiple viewers. Limited browser support. Can potentially reduce CPU and improve video quality, but will rely on a buffer.">
<input type="checkbox" data-param="&chunked=500" onchange="updateLink(1,this);">
<span class="slider"></span>
</label>
<font class="tooltip" style='cursor: help;position:relative;bottom:2px;font-family:"Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", Times, Symbola, Aegyptus, Code2000, Code2001, Code2002, Musica, serif, LastResort;'><span class="tooltiptext">Pretty experimental and has Lo-Fi quality, though relatively low CPU usage.</span></font>
<span data-translate="low-cpu=broadcast-codec">Low-CPU broadcast codec</span>
<font class="tooltip" style='cursor: help;position:relative;bottom:2px;font-family:"Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", Times, Symbola, Aegyptus, Code2000, Code2001, Code2002, Musica, serif, LastResort;'><span class="tooltiptext">Pretty experimental and limited browser support, though relatively low CPU usage.</span></font>
<span data-translate="chunked-mode">P2P Chunked-mode</span>
<Br />
<label class="switch" title="Use Meshcast servers to restream video data from this guest to its viewers, reducing the CPU and upload load in some cases. Will increase latency a bit.">
<input type="checkbox" data-param="&meshcast" onchange="updateLink(1,this);">
<span class="slider"></span>
</label>
<font class="tooltip" style='cursor: help;position:relative;bottom:2px;font-family:"Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", Times, Symbola, Aegyptus, Code2000, Code2001, Code2002, Musica, serif, LastResort;'><span class="tooltiptext">Uses a server to restream data, rather than p2p.</span></font>
<span data-translate="meshcast-mode">Stream via server</span>
<Br />
<label class="switch" title="The guest's self-video preview will appear tiny in the top right">
@ -1259,7 +1273,7 @@
<div id="controls_blank" style="display: none;">
<div class="controlsGrid">
<button data-action-type="forward" class="mainonly advanced" data-value="0" title="Move the user to another room, controlled by another director" onclick="directMigrate(this, event);">
<button data-action-type="forward" class="mainonly advanced" title="Move the user to another room, controlled by another director" onclick="directMigrate(this, event);">
<i class="las la-paper-plane"></i>
<span data-translate="forward-to-room">Transfer</span>
</button>
@ -1268,7 +1282,7 @@
<span data-translate="send-direct-chat"><i class="las la-envelope"></i> Message</span>
</button>
<button data-action-type="hangup" class="mainonly" data-value="0" title="Force the user to Disconnect. They can always reconnect." onclick="directHangup(this, event);">
<button data-action-type="hangup" class="mainonly" title="Force the user to Disconnect. They can always reconnect." onclick="directHangup(this, event);">
<i class="las la-sign-out-alt" style="color:#900"></i>
<span data-translate="disconnect-guest" >Hangup</span>
</button>
@ -1277,22 +1291,22 @@
<span data-translate="voice-chat"><i class="las la-microphone" style="color:#090"></i> Solo Talk</span>
</button>
<button data-action-type="addToScene" class="advanced" data-scene="1" title="Add this Video to any remote '&scene=1'" onclick="directEnable(this, event, 1);">
<button data-action-type="addToScene" class="advanced" data-scene="1" title="Add this Video to any remote '&scene=1'" onclick="directEnable(this, event);">
<i class="las la-plus-square" style="color:#060"></i>
<span data-translate="add-to-scene">add to scene 1</span>
</button>
<button data-action-type="solo-video" class="advanced" style="text-shadow: 0px 0px yellow;" data-value="0" title="Solo this video everywhere" onclick="requestInfocus(this);">
<button data-action-type="solo-video" class="advanced" style="text-shadow: 0px 0px yellow;" title="Solo this video everywhere" onclick="requestInfocus(this);">
<i class="las la-user"></i>
<span data-translate="solo-video">Highlight guest</span>
</button>
<font class="tooltip" style="height: 0; border: 0;">
<input data-action-type="volume" type="range" min="0" max="200" value="100" title="Remotely change the volume of this guest" oninput="remoteVolumeUI(this)" ondblclick="this.value=100;remoteVolume(this);remoteVolumeUI(this);" onchange="remoteVolume(this);" style="grid-column: 2; margin:5px; width: 93%; position: relative;top: 0.6em; background-color:#fff0;"/>
<input data-action-type="volume" type="range" min="0" max="200" value="100" title="Remotely change the volume of this guest; updates on release. Dbl-click to reset." oninput="remoteVolumeUI(this)" ondblclick="this.value=100;remoteVolume(this);remoteVolumeUI(this);" onchange="remoteVolume(this);" style="grid-column: 2; margin:5px; width: 93%; position: relative;top: 0.6em; background-color:#fff0;"/>
<span class="tooltiptext" style='float: right; overflow: auto; left: 40px; width: 3em; top: -13px; margin: 0; position:relative;font-family:"Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", Times, Symbola, Aegyptus,Code2000, Code2001, Code2002, Musica, serif, LastResort;' >100</span>
</font>
<button data-action-type="mute-guest" data-value="0" title="Mute this guest everywhere" onclick="remoteMute(this, event);">
<button data-action-type="mute-guest" title="Mute this guest everywhere" onclick="remoteMute(this, event);">
<i class="las la-microphone-slash" style="color:#900"></i>
<span data-translate="mute-guest" >mute guest</span>
</button>
@ -1304,7 +1318,7 @@
</span>
<span class="hideDropMenu" style="grid-column: 2;"></span>
<button data-action-type="addToScene" class="hidden advanced" data-cluster="1" data-scene="2" title="Add this Video to any remote '&scene=2'" onclick="directEnable(this, event, 2);">
<button data-action-type="addToScene" class="hidden advanced" data-cluster="1" data-scene="2" title="Add this Video to any remote '&scene=2'" onclick="directEnable(this, event);">
<i class="las la-plus-square" style="color:#060"></i>
<span data-translate="add-to-scene2">add to scene 2</span>
</button>
@ -1315,42 +1329,42 @@
</button>
<span class="hidden advanced" data-cluster="1" data-action-type="sceneCluster1">
<button style="width: 35.2px" data-action-type="addToScene" data-scene="3" title="Add to Scene 3" onclick="directEnable(this, event, 3);">
<button style="width: 35.2px" data-action-type="addToScene" data-scene="3" title="Add to Scene 3" onclick="directEnable(this, event);">
<span >S3</span>
</button>
<button style="width:35.2px;" data-action-type="addToScene" data-scene="4" title="Add to Scene 4" onclick="directEnable(this, event, 4);">
<button style="width:35.2px;" data-action-type="addToScene" data-scene="4" title="Add to Scene 4" onclick="directEnable(this, event);">
<span >S4</span>
</button>
<button style="width: 35.2px" data-action-type="addToScene" data-scene="5" title="Add to Scene 5" onclick="directEnable(this, event, 5);">
<button style="width: 35.2px" data-action-type="addToScene" data-scene="5" title="Add to Scene 5" onclick="directEnable(this, event);">
<span >S5</span>
</button>
</span>
<span class="hidden advanced" data-cluster="1" data-action-type="sceneCluster2">
<button style="width: 35.2px" data-action-type="addToScene" data-scene="6" title="Add to Scene 6" onclick="directEnable(this, event, 6);">
<button style="width: 35.2px" data-action-type="addToScene" data-scene="6" title="Add to Scene 6" onclick="directEnable(this, event);">
<span >S6</span>
</button>
<button style="width: 35.2px" data-action-type="addToScene" data-scene="7" title="Add to Scene 7" onclick="directEnable(this, event, 7);">
<button style="width: 35.2px" data-action-type="addToScene" data-scene="7" title="Add to Scene 7" onclick="directEnable(this, event);">
<span >S7</span>
</button>
<button style="width: 35.2px" data-action-type="addToScene" data-scene="8" title="Add to Scene 8" onclick="directEnable(this, event, 8);">
<button style="width: 35.2px" data-action-type="addToScene" data-scene="8" title="Add to Scene 8" onclick="directEnable(this, event);">
<span >S8</span>
</button>
</span>
<button class="hidden advanced" data-cluster="1" data-action-type="force-keyframe" style=" background-image: linear-gradient(90deg, #C9F0FF 0%, #FFDFB9 39%, #FFDFDF 70%, #D9FFEC 100%);" data-value="0" title="Force the remote sender to issue a keyframe to all scenes, fixing Pixel Smearing issues." onclick="requestKeyframeScene(this);">
<button class="hidden advanced" data-cluster="1" data-action-type="force-keyframe" style=" background-image: linear-gradient(90deg, #C9F0FF 0%, #FFDFB9 39%, #FFDFDF 70%, #D9FFEC 100%);" title="Force the remote sender to issue a keyframe to all scenes, fixing Pixel Smearing issues." onclick="requestKeyframeScene(this);">
<span data-translate="force-keyframe">Rainbow Puke Fix</span>
</button>
<button class="hidden" data-cluster="1" data-action-type="stats-remote" data-value="0" title="Request the statistics of this video in any active scene" onclick="toggleSceneStats(this);">
<button class="hidden" data-cluster="1" data-action-type="stats-remote" title="Request the statistics of this video in any active scene" onclick="toggleSceneStats(this);">
<i class="las la-info-circle"></i>
<span data-translate="stats-remote"> Scene Stats</span>
</button>
<span class="graphSection hidden" data-action-type="stats-graphs-bitrate" data-value="0">
<span class="graphSection hidden" data-action-type="stats-graphs-bitrate" >
<span class="hidden" data-message="true" data-no-scenes="true">No scenes active</span>
</span>
<span class="graphSection hidden" data-action-type="stats-graphs-details" data-value="0">
<span class="graphSection hidden" data-action-type="stats-graphs-details" >
<span class="hidden" data-no-scenes="true"></span>
<span data-action-type="stats-graphs-details-container" class="hidden">
<span class="hidden" data-scene-name="true">scene</span>
@ -1406,7 +1420,7 @@
</button>
</span>
<button class="hidden mainonly advanced" data-cluster="2" data-action-type="change-url" data-value="0" title="Remotely reload the guest's page with a new URL" onclick="directPageReload(this, event);">
<button class="hidden mainonly advanced" data-cluster="2" data-action-type="change-url" title="Remotely reload the guest's page with a new URL" onclick="directPageReload(this, event);">
<i class="las la-sync"></i>
<span data-translate="change-url">Change URL</span>
</button>
@ -1420,17 +1434,17 @@
<i class="las la-circle"></i>
<span data-translate="record-local"> Record Local</span>
</button>
<button class="hidden" data-cluster="2" data-action-type="recorder-remote" data-value="0" title="The Remote Guest will record their local stream to their local drive. *experimental*" onclick="requestVideoRecord(this)">
<button class="hidden" data-cluster="2" data-action-type="recorder-remote" title="The Remote Guest will record their local stream to their local drive. *experimental*" onclick="requestVideoRecord(this)">
<i class="las la-circle"></i>
<span data-translate="record-remote"> Record Remote</span>
</button>
<button class="hidden mainonly advanced" data-cluster="2" data-action-type="open-file-share" data-value="0" title="Allow the guest to select a file to upload to the director. Once shared, it will show in the chat as a download link." onclick="requestFileUpload(this)">
<button class="hidden mainonly advanced" data-cluster="2" data-action-type="open-file-share" title="Allow the guest to select a file to upload to the director. Once shared, it will show in the chat as a download link." onclick="requestFileUpload(this)">
<i class="las la-file-upload"></i>
<span data-translate="request-upload"> Request File</span>
</button>
<button class="hidden mainonly" data-cluster="2" data-action-type="create-timer" data-value="0" title="Set a countdown timer that this guest sees. CTRL (cmd) + click to pause." onclick="directTimer(this, event);">
<button class="hidden mainonly" data-cluster="2" data-action-type="create-timer" title="Set a countdown timer that this guest sees. CTRL (cmd) + click to pause." onclick="directTimer(this, event);">
<i class="las la-clock"></i>
<span data-translate="create-timer">Create Timer</span>
</button>
@ -1463,31 +1477,30 @@
</span>
<span class="hidden advanced" data-cluster="2">
<button style="width:35.2px;" data-action-type="toggle-group" data-value="1" title="Add/remove from group 1" onclick="changeGroup(this);">
<button style="width:35.2px;" data-action-type="toggle-group" data-group="1" title="Add/remove from group 1" onclick="changeGroup(this);">
<span >G1</span>
</button>
<button style="width: 35.2px" data-action-type="toggle-group" data-value="2" title="Add/remove from group 2" onclick="changeGroup(this);">
<button style="width: 35.2px" data-action-type="toggle-group" data-group="2" title="Add/remove from group 2" onclick="changeGroup(this);">
<span >G2</span>
</button>
<button style="width: 35.2px" data-action-type="toggle-group" data-value="3" title="Add/remove from group 3" onclick="changeGroup(this);">
<button style="width: 35.2px" data-action-type="toggle-group" data-group="3" title="Add/remove from group 3" onclick="changeGroup(this);">
<span >G3</span>
</button>
</span>
<span class="hidden advanced" data-cluster="2">
<button style="width:35.2px;" data-action-type="toggle-group" data-value="4" title="Add/remove from group 4" onclick="changeGroup(this);">
<button style="width:35.2px;" data-action-type="toggle-group" data-group="4" title="Add/remove from group 4" onclick="changeGroup(this);">
<span >G4</span>
</button>
<button style="width: 35.2px" data-action-type="toggle-group" data-value="5" title="Add/remove from group 5" onclick="changeGroup(this);">
<button style="width: 35.2px" data-action-type="toggle-group" data-group="5" title="Add/remove from group 5" onclick="changeGroup(this);">
<span >G5</span>
</button>
<button style="width: 35.2px" data-action-type="toggle-group" data-value="6" title="Add/remove from group 6" onclick="changeGroup(this);">
<button style="width: 35.2px" data-action-type="toggle-group" data-group="6" title="Add/remove from group 6" onclick="changeGroup(this);">
<span >G6</span>
</button>
</span>
<button class="hidden" data-cluster="2" data-action-type="advanced-audio-settings" data-active="false" title="Remote Audio Settings" onclick="requestAudioSettings(this);">
<span data-translate="advanced-audio-settings"><i class="las la-sliders-h"></i> Audio Settings</span>
</button>
@ -1501,7 +1514,7 @@
<div id="controls_directors_blank" style="display: none;">
<div class="controlsGrid">
<button data-action-type="addToScene" data-scene="1" title="Add this Video to any remote '&scene=1'" onclick="directEnable(this, event, 1, true);">
<button data-action-type="addToScene" data-scene="1" title="Add this Video to any remote '&scene=1'" onclick="directEnable(this, event, true);">
<i class="las la-plus-square"></i>
<span data-translate="add-to-scene">add to scene 1</span>
</button>
@ -1511,60 +1524,60 @@
</button>
<span>
<button style="width: 35.2px" data-scene="2" data-action-type="addToScene" title="Add to Scene 2" onclick="directEnable(this, event, 2, true);">
<button style="width: 35.2px" data-scene="2" data-action-type="addToScene" title="Add to Scene 2" onclick="directEnable(this, event, true);">
<span >S2</span>
</button>
<button style="width:35.2px;" data-scene="3" data-action-type="addToScene" title="Add to Scene 3" onclick="directEnable(this, event, 3, true);">
<button style="width:35.2px;" data-scene="3" data-action-type="addToScene" title="Add to Scene 3" onclick="directEnable(this, event, true);">
<span >S3</span>
</button>
<button style="width: 35.2px" data-scene="4" data-action-type="addToScene" title="Add to Scene 4" onclick="directEnable(this, event, 4, true);">
<button style="width: 35.2px" data-scene="4" data-action-type="addToScene" title="Add to Scene 4" onclick="directEnable(this, event, true);">
<span >S4</span>
</button>
</span>
<span >
<button style="width: 35.2px" data-scene="5" data-action-type="addToScene" title="Add to Scene 5" onclick="directEnable(this, event, 5, true);">
<button style="width: 35.2px" data-scene="5" data-action-type="addToScene" title="Add to Scene 5" onclick="directEnable(this, event, true);">
<span >S5</span>
</button>
<button style="width: 35.2px" data-scene="6" data-action-type="addToScene" title="Add to Scene 6" onclick="directEnable(this, event, 6, true);">
<button style="width: 35.2px" data-scene="6" data-action-type="addToScene" title="Add to Scene 6" onclick="directEnable(this, event, true);">
<span >S6</span>
</button>
<button style="width: 35.2px" data-scene="7" data-action-type="addToScene" title="Add to Scene 7" onclick="directEnable(this, event, 7, true);">
<button style="width: 35.2px" data-scene="7" data-action-type="addToScene" title="Add to Scene 7" onclick="directEnable(this, event, true);">
<span >S7</span>
</button>
</span>
<span>
<button style="width:35.2px;" data-action-type="toggle-group" data-value="1" title="Add/remove from group 1" onclick="changeGroupDirector(this);">
<button style="width:35.2px;" data-action-type="toggle-group" data-group="1" title="Add/remove from group 1" onclick="changeGroupDirector(this);">
<span >G1</span>
</button>
<button style="width: 35.2px" data-action-type="toggle-group" data-value="2" title="Add/remove from group 2" onclick="changeGroupDirector(this);">
<button style="width: 35.2px" data-action-type="toggle-group" data-group="2" title="Add/remove from group 2" onclick="changeGroupDirector(this);">
<span >G2</span>
</button>
<button style="width: 35.2px" data-action-type="toggle-group" data-value="3" title="Add/remove from group 3" onclick="changeGroupDirector(this);">
<button style="width: 35.2px" data-action-type="toggle-group" data-group="3" title="Add/remove from group 3" onclick="changeGroupDirector(this);">
<span >G3</span>
</button>
</span>
<span>
<button style="width:35.2px;" data-action-type="toggle-group" data-value="4" title="Add/remove from group 4" onclick="changeGroupDirector(this);">
<button style="width:35.2px;" data-action-type="toggle-group" data-group="4" title="Add/remove from group 4" onclick="changeGroupDirector(this);">
<span >G4</span>
</button>
<button style="width: 35.2px" data-action-type="toggle-group" data-value="5" title="Add/remove from group 5" onclick="changeGroupDirector(this);">
<button style="width: 35.2px" data-action-type="toggle-group" data-group="5" title="Add/remove from group 5" onclick="changeGroupDirector(this);">
<span >G5</span>
</button>
<button style="width: 35.2px" data-action-type="toggle-group" data-value="6" title="Add/remove from group 6" onclick="changeGroupDirector(this);">
<button style="width: 35.2px" data-action-type="toggle-group" data-group="6" title="Add/remove from group 6" onclick="changeGroupDirector(this);">
<span >G6</span>
</button>
</span>
<button data-action-type="force-keyframe" data-value="0" title="Force the remote sender to issue a keyframe to all scenes, fixing Pixel Smearing issues." onclick="session.sendKeyFrameScenes();">
<button data-action-type="force-keyframe" title="Force the remote sender to issue a keyframe to all scenes, fixing Pixel Smearing issues." onclick="session.sendKeyFrameScenes();">
<span data-translate="force-keyframe">Rainbow Puke Fix</span>
</button>
<button data-action-type="solo-video" data-value="0" title="Solo this video everywhere" onclick="requestInfocus(this);">
<button data-action-type="solo-video" title="Solo this video everywhere" onclick="requestInfocus(this);">
<i class="las la-user"></i>
<span data-translate="solo-video">Highlight</span>
</button>
@ -1592,9 +1605,11 @@
<div id="popupSelector" style="display:none;">
<span id="videoMenu3">
<i class="las la-video"></i><span data-translate="video-source"> Video Source </span>
<i class="las la-video"></i><span data-translate="video-source"> Video Source: </span>
<select id="videoSource3" ></select>
<span id="refreshVideoButton" title="Activate or Reload this video device."><i style="top: 3px; cursor: pointer; font-size: 120%; position: relative; left: 10px;" class="las la-sync-alt"></i></span>
<span id="refreshVideoButton" title="Activate or Reload this video device.">
<i style="top: 3px; cursor: pointer; font-size: 120%; position: relative; left: 10px;" class="las la-sync-alt"></i>
</span>
<span id="gear_webcam3" style="display: none; cursor:pointer;" onclick="toggleQualityGear3();">
&nbsp;&nbsp;
<i class="las la-cog" style="font-size: 135%; top:1px; vertical-align: middle;" aria-hidden="true"></i>
@ -1742,6 +1757,12 @@
<span data-translate="edit-url" >Edit URL manually</span>
</a>
</li>
<li class="context-menu__item">
<a href="#" class="context-menu__link" data-action="QRCode">
📷
<span data-translate="show-qr-code" >Show as QR Code</span>
</a>
</li>
</ul>
</nav>
<nav id="context-menu-video" class="context-menu">
@ -1812,6 +1833,12 @@
<span data-translate="show-video-stats">Show Stats</span>
</a>
</li>
<li class="context-menu__item">
<a href="#" class="context-menu__link" data-action="OutputAudio">
<i class="las la-external-link"></i>
<span data-translate="custom-audio-output">Audio Destination</span>
</a>
</li>
<li class="context-menu__item hidden" id="RemoteHangupContextOption">
<a href="#" class="context-menu__link" data-action="RemoteHangup">
<i class="las la-external-link"></i>
@ -1854,21 +1881,21 @@
<span style="margin-left: 6px;" id="trbSettingInputFeedback"></span>-kbps
<input id="trbSettingInput" type="range" min="0" max="4000" value="500" onchange="changeTRB(this);" oninput="getById('trbSettingInputFeedback').innerHTML = this.value;" style="width:100%;display:block;" />
<span style="margin: 20px 0 0 0;display:block" id='highlightDirectorSpan' title="Only the director's video will be visible to guests and within group scenes">
<input id="highlightDirector" style="width: 15px; height: 15px; margin:10px;" name="highlightDirector" data-value="0" data-action-type="solo-video" type="checkbox" onchange="requestInfocus(this);" />
<input id="highlightDirector" style="width: 15px; height: 15px; margin:10px;" name="highlightDirector" data-action-type="solo-video" type="checkbox" onchange="requestInfocus(this);" />
<label for="highlightDirector" data-translate="highlight-director-only-video-guests-will-see">Highlight Director (only video guests will see)</label>
</span>
<span style="margin: 5px 0 0 0;display:block" id='enableCodirector' title="Allow for remote co-directors">
<span id="coDirectorEnableSpan">
<input id="coDirectorEnable" style="width: 15px; height: 15px; margin:10px;" name="coDirectorEnable" data-value="0" data-action-type="codirector" type="checkbox" onchange="toggleCoDirector(this);" />
<input id="coDirectorEnable" style="width: 15px; height: 15px; margin:10px;" name="coDirectorEnable" data-action-type="codirector" type="checkbox" onchange="toggleCoDirector(this);" />
<label for="coDirectorEnable" data-translate="allow-for-remote-co-directors">Allow for remote co-directors</label>
</span>
<span style="margin:0;display:none;" id='codirectorSettings'>
<div>
<input id="codirectorSettings_transfer" style="width: 15px; height: 15px; margin:10px;" name="codirectorSettings_transfer" data-value="0" data-action-type="codirector_transfer" type="checkbox" onchange="toggleCoDirector_transfer(this);" />
<input id="codirectorSettings_transfer" style="width: 15px; height: 15px; margin:10px;" name="codirectorSettings_transfer" data-action-type="codirector_transfer" type="checkbox" onchange="toggleCoDirector_transfer(this);" />
<label for="codirectorSettings_transfer" data-translate="allow-co-directors-to-transfer-guests">Allow co-directors to transfer guests</label>
</div>
<div style="display:none;">
<input id="codirectorSettings_changeurl" style="width: 15px; height: 15px; margin:10px; " name="codirectorSettings_changeurl" data-value="0" data-action-type="codirector_changeurl" type="checkbox" onchange="toggleCoDirector_changeurl(this);" />
<input id="codirectorSettings_changeurl" style="width: 15px; height: 15px; margin:10px; " name="codirectorSettings_changeurl" data-action-type="codirector_changeurl" type="checkbox" onchange="toggleCoDirector_changeurl(this);" />
<label for="codirectorSettings_changeurl" data-translate="allow-co-directors-to-change-a-guests-url">Allow co-directors to change a guest's URL</label>
</div>
<div style="margin:8px;">
@ -1953,6 +1980,7 @@
<div class="battery-charging">+</div>
</div>
<div id="slotPicker" class="hidden">
<h3>Assign to slot:</h3><br />
<div data-slot="0">Unset</div>
<div data-slot="1">Slot 1</div>
<div data-slot="2">Slot 2</div>
@ -1964,7 +1992,6 @@
<div data-slot="8">Slot 8</div>
<div data-slot="9">Slot 9</div>
<div data-slot="10">Slot 10</div>
<div class="battery-charging">+</div>
</div>
<div id="voiceMeterTemplate" class="video-meter hidden">
</div>
@ -2044,7 +2071,6 @@
<div id="meshcastMenu" class="hidden">
Publishing Region: <select name="edgelist" id="edgelist" onchange="reloadEdge();" title="Select a location that is closest to both you and your audience."></select>
</div>
<script>
if (window.location.hostname.indexOf("www.obs.ninja") == 0) {
@ -2057,28 +2083,31 @@
var session = WebRTC.Media; // session is a required global variable if configuring manually. Run before loading main.js but after webrtc.js.
session.version = "22.0b";
session.version = "22.1b";
session.streamID = session.generateStreamID(); // randomly generates a streamID for this session. You can set your own programmatically if needed
session.defaultPassword = "someEncryptionKey123"; // Change this password if self-deploying for added security/privacy
session.salt = location.hostname; // used only if password is not == False. You can change to "session.salt = location.hostname+location.pathname;" for greater deployment isolation
// session.configuration = {
// iceServers: [
// { urls: ["stun:stun.l.google.com:19302", "stun:stun4.l.google.com:19302"] }, // more than 4 stun+turn servers may cause issues
// ],
session.stunServers = [{ urls: ["stun:stun.l.google.com:19302", "stun:stun4.l.google.com:19302"]}]; // google stun servers. default
/////////////// ------ Custom TURN SETUP SECTION STARTS Here --------
// session.configuration = { // uncomment to disable the default usage of the vdo.ninja turn servers.
// iceServers: session.stunServers,
// sdpSemantics: 'unified-plan'
// };
// var turn = {};
// var turn = {}; // Just an example entry for a basic TURN server
// turn.username = "steve";
// turn.credential = "justtesting";
// turn.urls = ["turn:turn.obs.ninja:443"]; // US CENTRAL
// turn.urls = ["turn:turn.obs.ninja:443"];
// session.configuration.iceServers.push(turn);
// turn = {};
//
// turn = {}; // second turn server example entry.
// turn.username = "steve";
// turn.credential = "justtesting";
// turn.urls = ["turn:turn2.obs.ninja:443"]; // US WEST
// turn.urls = ["turn:turn2.obs.ninja:443"];
// session.configuration.iceServers.push(turn);
/////////////// ------------ END OF TURN SETUP SECTION -------
// session.configuration.iceTransportPolicy = "relay"; // uncomment to enable "&privacy" and force the TURN server
@ -2126,11 +2155,11 @@
// session.defaultBackgroundImages = ["./media/bg_sample1.webp", "./media/bg_sample2.webp"]; // for &effects=5 (virtual backgrounds)
</script>
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/aes.js"></script>
<script type="text/javascript" crossorigin="anonymous" id="lib-js" src="./lib.js?ver=327"></script>
<script type="text/javascript" crossorigin="anonymous" id="lib-js" src="./lib.js?ver=361"></script>
<!--
// If you wish to change branding, blank offers a good clean start.
<script type="text/javascript" id="main-js" src="./main.js" data-translation="blank"></script>
-->
<script type="text/javascript" crossorigin="anonymous" id="main-js" src="./main.js?ver=353"></script>
<script type="text/javascript" crossorigin="anonymous" id="main-js" src="./main.js?ver=375"></script>
</body>
</html>

1984
lib.js

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,7 @@
--myvideo-background: #FFF1;
--video-background-image: url("data:image/svg+xml,%3Csvg viewBox='-42 0 512 512.002' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m210.351562 246.632812c33.882813 0 63.222657-12.152343 87.195313-36.128906 23.972656-23.972656 36.125-53.304687 36.125-87.191406 0-33.875-12.152344-63.210938-36.128906-87.191406-23.976563-23.96875-53.3125-36.121094-87.191407-36.121094-33.886718 0-63.21875 12.152344-87.191406 36.125s-36.128906 53.308594-36.128906 87.1875c0 33.886719 12.15625 63.222656 36.132812 87.195312 23.976563 23.96875 53.3125 36.125 87.1875 36.125zm0 0'/%3E%3Cpath d='m426.128906 393.703125c-.691406-9.976563-2.089844-20.859375-4.148437-32.351563-2.078125-11.578124-4.753907-22.523437-7.957031-32.527343-3.308594-10.339844-7.808594-20.550781-13.371094-30.335938-5.773438-10.15625-12.554688-19-20.164063-26.277343-7.957031-7.613282-17.699219-13.734376-28.964843-18.199219-11.226563-4.441407-23.667969-6.691407-36.976563-6.691407-5.226563 0-10.28125 2.144532-20.042969 8.5-6.007812 3.917969-13.035156 8.449219-20.878906 13.460938-6.707031 4.273438-15.792969 8.277344-27.015625 11.902344-10.949219 3.542968-22.066406 5.339844-33.039063 5.339844-10.972656 0-22.085937-1.796876-33.046874-5.339844-11.210938-3.621094-20.296876-7.625-26.996094-11.898438-7.769532-4.964844-14.800782-9.496094-20.898438-13.46875-9.75-6.355468-14.808594-8.5-20.035156-8.5-13.3125 0-25.75 2.253906-36.972656 6.699219-11.257813 4.457031-21.003906 10.578125-28.96875 18.199219-7.605469 7.28125-14.390625 16.121094-20.15625 26.273437-5.558594 9.785157-10.058594 19.992188-13.371094 30.339844-3.199219 10.003906-5.875 20.945313-7.953125 32.523437-2.058594 11.476563-3.457031 22.363282-4.148437 32.363282-.679688 9.796875-1.023438 19.964844-1.023438 30.234375 0 26.726562 8.496094 48.363281 25.25 64.320312 16.546875 15.746094 38.441406 23.734375 65.066406 23.734375h246.53125c26.625 0 48.511719-7.984375 65.0625-23.734375 16.757813-15.945312 25.253906-37.585937 25.253906-64.324219-.003906-10.316406-.351562-20.492187-1.035156-30.242187zm0 0'/%3E%3C/svg%3E");
--advanced-mode: inline-block;
--background-main-image: unset;
}
* {
@ -154,18 +155,17 @@ th {
direction: rtl;
user-select: none;
}
a {
-webkit-app-region: no-drag;
}
a:link {
text-decoration: none;
color: #B9DFF9;
}
a:visited {
text-decoration: none;
color: #99BFD9;
}
a:hover {
color: #048AE8;
}
@ -250,7 +250,6 @@ button {
button.white {
-webkit-app-region: no-drag;
padding: 6px 10px 4px 9px;
;
margin: 10px 0px;
@ -262,7 +261,6 @@ button.white {
}
button.white:active {
-webkit-app-region: no-drag;
padding: 6px 10px 4px 9px;
;
margin: 10px 0px;
@ -277,9 +275,34 @@ button.white:active {
padding: 1px;
background-color: #0F131D;
color: #FFF;
min-height:18.8px;
-webkit-app-region: drag;
}
#head1{
display: inline-block;
padding:1px;
position: relative;
-webkit-app-region: no-drag;
}
.randomRoomName{
width: 20px;
height: 20px;
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%);
position: absolute;
margin: 6px;
}
.randomRoomName:active{
animation: shake 0.2s;
animation-iteration-count: once;
}
.randomRoomName:hover{
-webkit-box-shadow: 0px 0px 4px #000;
}
#head5 {
-webkit-app-region: no-drag;
display: inline-block;
text-decoration: none;
color: white;
@ -822,6 +845,11 @@ button.glyphicon-button.active.focus {
outline: 0px !important;
height:100%;
animation: fadeIn 0.2s;
background-size: cover;
background-image: var(--background-main-image);
background-repeat: no-repeat;
background-attachment: fixed;
background-position: center;
}
#controlButtons {
@ -1299,6 +1327,13 @@ body {
-webkit-app-region: no-drag;
}
#popupSelector{
-webkit-app-region: no-drag;
}
select{
-webkit-app-region: no-drag;
}
.advancedToggle {
display:none;
background-color:#EFEFEF;
@ -2495,7 +2530,11 @@ button.toggleSettings{
font-size: 100%;
max-width: 260px;
}
#gear_webcam{
cursor: pointer;
display: inline-block;
padding: 0 0 0 3px;
}
.gone {
position:absolute;
top: -150px;
@ -2564,7 +2603,7 @@ button.toggleSettings{
vertical-align: middle;
padding: 3px;
font-size: 93%;
max-width: 240px;
max-width: 235px;
}
#outputSource {
display: inline-block;
@ -2600,7 +2639,7 @@ button.toggleSettings{
background-color: #f3f3f3;
width: 450px;
display: inline-block;
padding: 10px 10px;
padding: 5px 10px 10px 10px;
border: 1px solid #ccc;
vertical-align: middle;
text-align: left;
@ -2807,6 +2846,8 @@ input[type=checkbox] {
color: white;
padding: 20px;
border: 2px solid #1d1d1d;
padding-bottom: 100px!important;
margin-bottom: 100px!important;
}
.debugStats::-webkit-scrollbar {
width: 0.5em;
@ -3694,6 +3735,9 @@ input:checked + .slider:before {
opacity: 1.0;
}
.alertModalMessage>select{
font-size: 100%;
}
@media only screen and (max-width: 390px) {
.alertModal {
@ -3948,6 +3992,8 @@ input:checked + .slider:before {
content: "\f1de"; }
.la-compress-arrows-alt:before {
content: "\f78c"; }
.la-users:before {
content: "\f0c0"; }
.la-spinner:before {
content: "\f110"; }
.la.la-external-link:before {

336
main.js
View File

@ -26,16 +26,17 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
if (urlParams.has('ln')) {
ln_template = urlParams.get('ln');
ln_template = urlParams.get('ln') || null;
}
} catch (e) {
errorlog(e);
}
if (ln_template) { // checking if manual lanuage override enabled
if (ln_template===null) {
getById("mainmenu").style.opacity = 1;
} else if (ln_template!==false) { // checking if manual lanuage override enabled
try {
log("Lang Template: " + ln_template);
changeLg(ln_template);
await changeLg(ln_template);
//getById("mainmenu").style.opacity = 1;
} catch (error) {
errorlog(error);
@ -64,7 +65,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
} catch (e) {}
}
try {
if (!ln_template){
if (ln_template===false){
changeLg("blank");
}
//getById("mainmenu").style.opacity = 1;
@ -99,6 +100,11 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.cleanViewer = true;
}
if (urlParams.has('director') || urlParams.has('dir')) {
session.director = urlParams.get('director') || urlParams.get('dir') || true;
}
if (urlParams.has('controls') || urlParams.has('videocontrols')) {
session.showControls = true; // show the video control bar
@ -167,17 +173,28 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1) {
try {
getById("electronDragZone").style.cursor="grab";
//getById("electronDragZone").style.cursor="grab";
if (!ipcRenderer){
ipcRenderer = require('electron').ipcRenderer;
}
if (ipcRenderer){
window.prompt = function(title, val){
return ipcRenderer.sendSync('prompt', {title, val}); // call if needed in the future
};
}
//ipcRenderer.sendSync('prompt', {title, val}); // call now -- but why?
} catch(e){}
}
if (window.electronApi && window.electronApi.exposeDoSomethingInWebApp){
window.electronApi.exposeDoSomethingInWebApp(function (fauxEventData) {
//alert("WORKS");
//errorlog("WORKS!");
session.remoteInterfaceAPI(fauxEventData);
});
window.electronApi.updateVersion(session.version);
}
if (urlParams.has('retrytimeout')) {
session.retryTimeout = parseInt(urlParams.get('retrytimeout')) || 5000;
if (session.retryTimeout<5000){
@ -206,8 +223,8 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
document.documentElement.style.setProperty('--advanced-mode', "none"); // hide advanced items
}
if (urlParams.has('bgimage')) { // URL or data:base64 image. Becomes local to this viewer only. This is like &avatar, but slightly different. Just CSS in this case
var avatarImg = urlParams.get('bgimage') || false;
if (urlParams.has('avatarimg') || urlParams.has('bgimage') || urlParams.has('bgimg')) { // URL or data:base64 image. Becomes local to this viewer only. This is like &avatar, but slightly different. Just CSS in this case
var avatarImg = urlParams.get('avatarimg') || urlParams.get('bgimage') || urlParams.get('bgimg') || false;
if (avatarImg){
try {
avatarImg = decodeURIComponent(avatarImg);
@ -219,9 +236,26 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
}
if (urlParams.has('background') || urlParams.has('appbg')) { // URL or data:base64 image. Use &chroma if you want to use a color instead of image.
var background = urlParams.get('background') || urlParams.get('appbg') || false;
if (background){
try {
background = decodeURIComponent(background);
} catch(e){}
try {
background = 'url("'+background+'")';
document.documentElement.style.setProperty('--background-main-image', background);
} catch(e){}
}
}
if (urlParams.has('nomouseevents') || urlParams.has('nme')) {
session.disableMouseEvents = true;
}
if (urlParams.has('overlaycontrols')) {
session.overlayControls = true;
}
if (urlParams.has('novideobutton') || urlParams.has('nvb')) {
getById("mutevideobutton").style.setProperty("display", "none", "important");
@ -519,7 +553,13 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.midiDevice = parseInt(session.midiDevice);
}
if (urlParams.has('webcam') || urlParams.has('wc') || urlParams.has('miconly')) {
if (directorLanding) {
getById("container-1").classList.remove('hidden');
getById("container-1").classList.add("skip-animation");
getById("container-1").classList.remove('pointer');
} else if (session.director){
// if a director, webcam/screenshare/iframe auto-defaults shouldn't work
} else if (urlParams.has('webcam') || urlParams.has('wc') || urlParams.has('miconly')) {
session.webcamonly = true;
session.screensharebutton = false;
if (urlParams.has('miconly')){
@ -555,11 +595,6 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
} else if (!Firefox){
getById("chrome_warning_fileshare").classList.remove('hidden');
}
} else if (directorLanding) {
getById("container-1").classList.remove('hidden');
getById("container-1").classList.add("skip-animation");
getById("container-1").classList.remove('pointer');
} else if (urlParams.has('website') || urlParams.has('iframe')) {
getById("container-6").classList.remove('hidden');
getById("container-6").classList.add("skip-animation");
@ -712,18 +747,26 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
if (urlParams.has('portrait') || urlParams.has('916') || urlParams.has('vertical')) { // playback aspect ratio
session.aspectratio = 1; // 9:16 (default of 0 is 16:9)
session.aspectRatio = 1; // 9:16 (default of 0 is 16:9)
} else if (urlParams.has('square') || urlParams.has('11')) {
session.aspectratio = 2; //1:1 ?
session.aspectRatio = 2; //1:1 ?
} else if (urlParams.has('43')) {
session.aspectratio = 3; //1:1 ?
session.aspectRatio = 3; //1:1 ?
}
if (urlParams.has('aspectratio') || urlParams.has('ar')) { // capture aspect ratio
session.forceAspectRatio = urlParams.get('aspectratio') || urlParams.get('ar') || false;
if (session.forceAspectRatio){
session.forceAspectRatio=parseFloat(session.forceAspectRatio);
if ((session.forceAspectRatio == 'portrait') || (session.forceAspectRatio == 'vertical')){
session.forceAspectRatio = 9.0/16.0;
} else if (session.forceAspectRatio == 'landscape'){
session.forceAspectRatio = 16.0/9.0;
} else if (session.forceAspectRatio == 'square'){
session.forceAspectRatio = 1.0;
} else {
session.forceAspectRatio = parseFloat(session.forceAspectRatio) || false;
}
}
}
@ -737,6 +780,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.forceAspectRatio = 1.3333333333;
}
}
if (urlParams.has('cover')) {
session.cover = true;
document.documentElement.style.setProperty('--fit-style', 'cover');
@ -772,12 +816,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if (urlParams.has('autorecord')) {
session.autorecord=true;
if (session.recordLocal===false){
session.recordLocal = urlParams.get('record');
if (session.recordLocal != parseInt(session.recordLocal)) {
session.recordLocal = 6000;
} else {
session.recordLocal = parseInt(session.recordLocal);
}
}
}
if (urlParams.has('autorecordlocal')) {
@ -989,6 +1028,24 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
document.head.appendChild(externalJavaascript);
}
if (urlParams.has("base64js") || urlParams.has("b64js") || urlParams.has("jsbase64") || urlParams.has("jsb64")) {
try {
var base64js = urlParams.get("base64js") || urlParams.get("b64js") || urlParams.get("jsbase64") || urlParams.get("jsb64");
base64js = decodeURIComponent(atob(base64js)); // window.btoa(encodeURIComponent("alert('hi')")); // ?jsb64=YWxlcnQoJ2hpJyk7
var externalJavaascript = document.createElement('script');
externalJavaascript.type = 'text/javascript';
externalJavaascript.crossorigin = 'anonymous';
externalJavaascript.innerHTML = base64js;
externalJavaascript.onerror = function() {
errorlog("Third-party Javascript failed to load");
};
externalJavaascript.onload = function() {
log("Third-party Javascript loaded");
};
document.head.appendChild(externalJavaascript);
} catch(e){console.error(e);}
};
if (urlParams.has("base64css") || urlParams.has("b64css") || urlParams.has("cssbase64") || urlParams.has("cssb64")) {
try {
var base64Css = urlParams.get("base64css") || urlParams.get("b64css") || urlParams.get("cssbase64") || urlParams.get("cssb64");
@ -1161,7 +1218,6 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
} else if (session.stereo === "off") {
session.stereo = 0;
session.audioInputChannels = 1;
} else if (session.stereo === "1") {
session.stereo = 1;
} else if (session.stereo === "both") {
@ -1254,12 +1310,14 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
}
if (urlParams.has("channelcount") || urlParams.has("ac")) {
session.audioInputChannels = urlParams.get('channelcount') || urlParams.get('ac');
if (urlParams.has("channelcount") || urlParams.has("ac") || urlParams.has("inputchannels")) {
session.audioInputChannels = urlParams.get('channelcount') || urlParams.get('ac') || urlParams.get('inputchannels') || 0;
session.audioInputChannels = parseInt(session.audioInputChannels);
if (!session.audioInputChannels) {
session.audioInputChannels = false;
}
} else if (urlParams.has("monomic")){
session.audioInputChannels = 1;
}
@ -1428,6 +1486,8 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
} catch (e){errorlog(e);}
} */
if (urlParams.has('streamid') || urlParams.has('view') || urlParams.has('v') || urlParams.has('pull')) { // the streams we want to view; if set, but let blank, we will request no streams to watch.
session.view = urlParams.get('streamid') || urlParams.get('view') || urlParams.get('v') || urlParams.get('pull') || null; // this value can be comma seperated for multiple streams to pull
@ -1435,20 +1495,24 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
getById("headphonesDiv").style.display = "inline-block";
getById("addPasswordBasic").style.display = "none";
if (session.view == null) {
session.view = "";
}
if (session.view) {
if (session.view.split(",").length > 1) {
session.view_set = session.view.split(",");
}
}
/* if (session.view){
if (urlParams.has('include') && urlParams.get('include')){
session.view += ","+urlParams.get('include');
}
} */
if ((session.scene !== false) && (session.style === false) && window.obsstudio){
session.style = 1;
}
}
if (session.view!==false) {
session.view_set = session.view.split(",");
}
if (session.view_set){
session.allowScreen = [];
@ -1462,21 +1526,22 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if (!(split[0] in session.view_set)){
session.view_set.push(split[0]);
}
} else {
} else if (split[0]){
session.allowVideos.push(split[0]);
} else {
session.view_set.splice(i, 1);
}
}
}
} else if (session.view){
session.allowScreen = [];
session.allowVideos = [];
var split = session.view.split(":s");
if (split.length>1){
session.allowScreen.push(split[0]);
session.view = split[0];
} else {
session.allowVideos.push(split[0]);
if (urlParams.has('include') && urlParams.get('include')){
urlParams.get('include').split(",").forEach(sid =>{
var sidd = sid.split(":s")[0];
if (sidd && !session.include.includes(sidd)){
session.include.push(sidd);
}
});
}
if (urlParams.has('directorview') || urlParams.has('dv')){
@ -1702,6 +1767,11 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
// getById("main").style.backgroundColor = "rgba(0,0,0,0)";
//}
//if (urlParams.has('bgimg')){
//
// getById("main").style.background = "#" + (urlParams.get('chroma') || "0F0");
//}
if (urlParams.has('margin')) {
try {
session.videoMargin = urlParams.get('margin') || 10;
@ -1744,8 +1814,16 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if (urlParams.has('retry')) {
session.forceRetry = parseInt(urlParams.get('retry')) || 30;
}
if (session.forceRetry){
setTimeout(function(){session.retryWatchInterval();},30000);
setTimeout(function(){
try {
session.retryWatchInterval();
} catch(e){
warnlog(e);
clearTimeout(this);
}
},30000);
}
try {
@ -1771,7 +1849,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
document.querySelector(':root').style.setProperty('--background-color',"#02050c" );
} else {
document.body.classList.remove("darktheme");
document.querySelector(':root').style.setProperty('--background-color',"#141926" );
//document.querySelector(':root').style.setProperty('--background-color',"#141926" ); // already set as default.
}
} catch(e){errorlog(e);}
@ -1864,7 +1942,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
getById("previewWebcam").classList.add("miconly");
if (session.audioDevice === 0) {
miniTranslate(getById("add_camera"), "click-start-to-join", "Click Start to Join");
getById("container-2").className = 'column columnfade hidden'; // Hide screen share on mobile
getById("container-2").className = 'column columnfade hidden';
getById("container-3").classList.add("skip-animation");
getById("container-3").classList.remove('pointer');
delayedStartupFuncs.push([previewWebcam]);
@ -1879,7 +1957,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if (session.mobile){
getById("shareScreenGear").style.display = "none";
getById("dropButton").style.display = "none";
getById("container-2").className = 'column columnfade hidden'; // Hide screen share on mobile
//getById("container-2").className = 'column columnfade hidden'; // Hide screen share on mobile
session.screensharebutton = false;
screensharesupport = false;
@ -1888,6 +1966,10 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
}
if (urlParams.has('androidfix')){
session.AndroidFix = true;
}
if (urlParams.has('consent')){
session.consent = true;
getById("consentWarning").classList.remove("hidden");
@ -2017,8 +2099,8 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
debugStart();
}
if (urlParams.has('group')) {
session.group = urlParams.get('group') || "";
if (urlParams.has('group') || urlParams.has('groups')) {
session.group = urlParams.get('group') || urlParams.get('groups') || "";
session.group = session.group.split(",");
}
@ -2034,7 +2116,10 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.sensorData = urlParams.get('sensors') || urlParams.get('sensor') || urlParams.get('gyro') || urlParams.get('gyros') || urlParams.get('accelerometer') || 30;
session.sensorData = parseInt(session.sensorData);
}
if (urlParams.has('sensorfilter') || urlParams.has('sensorsfilter') || urlParams.has('filtersensor') || urlParams.has('filtersensors')) {
session.sensorDataFilter = urlParams.get('sensorfilter') || urlParams.get('sensorsfilter') || urlParams.get('filtersensor') || urlParams.get('filtersensors') || "";
session.sensorDataFilter = session.sensorDataFilter.split(","); // ["pos","lin","ori","mag","gyro","acc"];
}
if (urlParams.has('ptime')) {
session.ptime = parseInt(urlParams.get('ptime')) || 20;
@ -2136,6 +2221,10 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.dynamicScale = false; // default true
}
if (urlParams.has('mcscale') || urlParams.has('meshcastscale')) {
session.meshcastScale = parseFloat(urlParams.get('mcscale')) || parseFloat(urlParams.get('meshcastscale')) || 100;
}
if (isIFrame) {
getById("helpbutton").style.display = "none";
@ -2272,8 +2361,14 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if (urlParams.has('mccodec') || urlParams.has('meshcastcodec')){
session.meshcastCodec = urlParams.get('mccodec') || urlParams.get('meshcastcodec') || false;
}
if (session.meshcastCodec){
session.meshcastCodec = session.meshcastCodec.toLowerCase();
if (session.meshcastCodec == "h264"){
if (Firefox){
session.meshcastCodec = false;
}
}
}
if (urlParams.has('height') || urlParams.has('h')) {
@ -2401,7 +2496,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
if (urlParams.has('channels')) { // must be loaded before channelOffset
session.audioChannels = parseInt(urlParams.get('channels'));
session.audioChannels = parseInt(urlParams.get('channels')); // for audio output ; not input. see: &channelcount instead.
session.offsetChannel = 0;
log("max channels is 32; channels offset");
session.audioEffects = true;
@ -2669,6 +2764,8 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.directorChat = true;
}
if (urlParams.has('style') || urlParams.has('st')) {
session.style = urlParams.get('style') || urlParams.get('st');
if ((parseInt(session.style) === 0) || (session.style == "controls")) { // no audio only
@ -2698,7 +2795,6 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.showall = true;
}
if (urlParams.has('samplerate') || urlParams.has('sr')) {
session.sampleRate = parseInt(urlParams.get('samplerate')) || parseInt(urlParams.get('samplerate')) || 48000;
if (session.audioCtx) {
@ -2709,8 +2805,6 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
});
session.audioEffects = true;
}
if (urlParams.has('noaudioprocessing') || urlParams.has('noap')) {
session.disableWebAudio = true; // default true; might be useful to disable on slow or old computers?
session.audioEffects = false; // disable audio inbound effects also.
@ -2731,14 +2825,6 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if (urlParams.has('tcp')){ // forces the TURN servers to use TCP mode; still need to add &private to force TURN also tho
session.forceTcpMode = true;
}
if (urlParams.has('speedtest')){ // forces essentially UDP mode, unless TCP is specified, and some other stuff
session.speedtest = true;
if (urlParams.get('speedtest')){
session.speedtest = urlParams.get('speedtest').toLowerCase();
}
}
var iceServers = [{ urls: ["stun:stun.l.google.com:19302", "stun:stun4.l.google.com:19302"]}]; // google stun servers.
if (urlParams.has('stun')) {
var stunstring = urlParams.get('stun');
@ -2752,9 +2838,9 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
} else if (stunstring.length==1){
stun.urls = [stunstring[0]];
}
iceServers = [stun];
session.stunServers = [stun];
} else {
iceServers = [];
session.stunServers = [];
}
}
if (urlParams.has('addstun')) {
@ -2768,14 +2854,15 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
} else if (stunstring.length==1){
stun.urls = [stunstring[0]];
}
iceServers = iceServers.concat(stun);
session.stunServers = session.stunServers.concat(stun);
}
if (urlParams.has('turn')) {
var turnstring = urlParams.get('turn');
if (turnstring == "twilio") { // a sample function on loading remote credentials for TURN servers.
try {
session.ws = false; // prevents connection
var twillioRequest = new XMLHttpRequest();
twillioRequest.onreadystatechange = function() {
@ -2806,20 +2893,20 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
// system does not connect if twilio API does not respond.
};
twillioRequest.open('GET', 'https://api.obs.ninja:1443/twilio', true); // `false` makes the request synchronous
twillioRequest.open('GET', 'https://turn.example.com:443/twilio', true); // `false` makes the request synchronous
twillioRequest.send();
} catch (e) {
errorlog("Twilio Failed");
}
} else if (turnstring == "nostun") { // disable TURN servers
session.configuration = {
sdpSemantics: 'unified-plan' // future-proofing
};
} else if ((turnstring == "false") || (turnstring == "off") || (turnstring == "0")) { // disable TURN servers
session.configuration = {
iceServers: iceServers,
iceServers: session.stunServers,
sdpSemantics: 'unified-plan' // future-proofing
};
} else {
@ -2836,7 +2923,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
turn.urls = [turnstring[0]];
}
session.configuration = {
iceServers: iceServers,
iceServers: session.stunServers,
sdpSemantics: 'unified-plan' // future-proofing
};
@ -2849,16 +2936,23 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
errorlog(e);
}
}
} else {
chooseBestTURN(iceServers); // vdo.ninja turn servers, if needed.
}
if (urlParams.has('speedtest')){ // must set this after any custom TURN / STUN settings, else it might over-ride them.
session.speedtest = true;
if (urlParams.get('speedtest')){ // forces essentially UDP mode, unless TCP is specified, and some other stuff
session.speedtest = urlParams.get('speedtest').toLowerCase(); // also limits bitrate
}
setupSpeedtest();
}
if (urlParams.has('privacy') || urlParams.has('private') || urlParams.has('relay')) { // please only use if you are also using your own TURN service.
session.privacy = true;
try {
try { // I'll re-apply this in the setupSpeedtest() promise callback, just to be case.
if (session.configuration){ // this needs to set last, otherwise it might be overridden
session.configuration.iceTransportPolicy = "relay"; // https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidate/address
}
} catch (e) {
if (!(session.cleanOutput)) {
warnUser("Privacy mode failed to configure.");
@ -2867,12 +2961,13 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
if (session.speedtest){
warnlog("Bitrate being throttled to max of 6000 kbps");
if (session.maxvideobitrate !== false) {
if (session.maxvideobitrate > 6000) {
session.maxvideobitrate = 6000; // Please feel free to get rid of this if using your own TURN servers...
}
} else {
session.maxvideobitrate = 6000; // don't let people pull more than 2500 from you
session.maxvideobitrate = 6000; // don't let people pull more than 6000 from you
}
if (session.bitrate !== false) {
if (session.bitrate > 6000) {
@ -2880,21 +2975,24 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
}
} else {
warnlog("Bitrate being throttled to max of 3000 kbps");
if (session.maxvideobitrate !== false) {
if (session.maxvideobitrate > 2500) {
session.maxvideobitrate = 2500; // Please feel free to get rid of this if using your own TURN servers...
if (session.maxvideobitrate > 4000) {
session.maxvideobitrate = 4000; // Please feel free to get rid of this if using your own TURN servers...
}
} else {
session.maxvideobitrate = 2500; // don't let people pull more than 2500 from you
session.maxvideobitrate = 4000; // don't let people pull more than 3000 from you
}
if (session.bitrate !== false) {
if (session.bitrate > 2500) {
session.bitrate = 2500; // Please feel free to get rid of this if using your own TURN servers...
if (session.bitrate > 4000) {
session.bitrate = 4000; // Please feel free to get rid of this if using your own TURN servers...
}
}
}
}
if (urlParams.has('wss')) {
session.customWSS = true;
if (urlParams.get('wss')) {
@ -2937,7 +3035,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
updateURL("push="+session.streamID, true, false);
}
if (urlParams.has('director') || urlParams.has('dir')) { // if I do a short form of this, it will cause duplications in the code elsewhere.
if (session.director) { // if I do a short form of this, it will cause duplications in the code elsewhere.
//var director_room_input = urlParams.get('director');
//director_room_input = sanitizeRoomName(director_room_input);
//createRoom(director_room_input);
@ -2978,7 +3076,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
}
if ((session.roomid) || (urlParams.has('roomid')) || (urlParams.has('r')) || (urlParams.has('room')) || (filename) || (session.permaid !== false)) {
if (session.roomid || urlParams.has('roomid') || urlParams.has('r') || urlParams.has('room') || filename || (session.permaid !== false)) {
var roomid = "";
if (urlParams.has('room')) { // needs to be first; takes priority
@ -3297,7 +3395,9 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
log("Update Mixer Event on REsize SET");
getById("translateButton").style.display = "none";
window.onresize = updateMixer;
window.onorientationchange = function(){setTimeout(updateMixer, 200);};
window.onorientationchange = function(){setTimeout(function(){
updateMixer();
}, 200);};
getById("main").style.overflow = "hidden";
if (session.chatbutton === true) {
@ -3506,11 +3606,18 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
getById("main").onmouseover = session.showControls;
}
if (urlParams.has('flagship')) {
session.flagship = true;
}
if (!session.flagship && session.mobile && (session.limitTotalBitrate===false)){
session.limitTotalBitrate = session.totalRoomBitrate_default; // 500, with the max per guest stream out at maxMobileBitrate (350kbps) or 35-kbps if more than X in the room.
}
// Please contact steve on discord.vdo.ninja if you'd like this iFRAME tweaked, expanded, etc -- it's updated based on user request
if (isIFrame) { // reduce CPU load if not needed. //iframe API
window.onmessage = function(e) { // iFRAME support
//log(e);
session.remoteInterfaceAPI = function(e) { // iFRAME api support
warnlog(e);
try {
if ("function" in e.data) { // these are calling in-app functions, with perhaps a callback -- TODO: add callbacks
var ret = null;
@ -3552,7 +3659,20 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if ("PPT" in e.data){
log("PTT activated-webmain");
toggleMute(true);
if (e.data.PPT === true) { // unmute
session.muted = false; // set
getById("mutebutton").classList.add("PPTActive");
log(session.muted);
toggleMute(true); // apply
} else if (e.data.PPT === false) { // mute
session.muted = true; // set
getById("mutebutton").classList.remove("PPTActive");
log(session.muted);
toggleMute(true); // apply
} else if (e.data.PPT === "toggle") { // toggle
toggleMute();
}
return; // this is a high-load call, so lets skip the rest of the checks to save cpu.
}
@ -3597,6 +3717,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.sendKeyFrameScenes();
}
if ("mute" in e.data) {
if (e.data.mute === true) { // unmute
session.speakerMuted = true; // set
@ -3624,12 +3745,17 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if ("recording" in session.videoElement) {
recordLocalVideo("stop");
}
} else if (e.data.record == true){
} else if (e.data.record === true){
if ("recording" in session.videoElement) {
// already recording
} else {
recordLocalVideo("start");
}
} else if (e.data.record){
var video = document.getElementById(e.data.record);
if (video){
recordLocalVideo(null, 4000, video);
}
}
}
@ -3921,6 +4047,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
errorlog(e);
}
}
hangup();
}
if ("style" in e.data) { // insert a custom style sheet
@ -3997,6 +4124,37 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
////////////////
if ("scale" in e.data) {
if (e.data.scale === false){
session.dynamicScale = true; // disable manual scaling
updateMixer();
} else {
session.dynamicScale = false;
var scale = parseInt(e.data.scale) || 100;
if (e.data.UUID){
session.sendRequest({scale:scale}, UUID);
} else if (e.data.target){
for (var i in session.rpcs) {
try {
if ("streamID" in session.rpcs[i]) {
if ("target" in e.data) {
if ((session.rpcs[i].streamID == e.data.target) || (e.data.target == "*")) { // specify a stream ID or let it apply to all videos
session.sendRequest({scale:scale}, i);
}
} else {
session.sendRequest({scale:scale}, i);
}
}
} catch (e) {
errorlog(e);
}
}
} else {
session.sendRequest({scale:scale});
}
}
}
if (("action" in e.data) && (e.data.action!="null")) { /////////////// reuse the Companion API
var resp = processMessage(e.data); // reuse the companion API
@ -4048,9 +4206,13 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
}
};
if (isIFrame) { // reduce CPU load if not needed. //iframe API
window.onmessage = session.remoteInterfaceAPI;
}
if (session.midiHotkeys || session.midiOut!==false) {
var script = document.createElement('script');
@ -4570,7 +4732,7 @@ setTimeout(function(){ // lazy load
script.onload = function() {
var script = document.createElement('script');
document.head.appendChild(script);
script.src = "./thirdparty/StreamSaver.js?v=10"; // dynamically load this only if its needed. Keeps loading time down.
script.src = "./thirdparty/StreamSaver.js?v=13"; // dynamically load this only if its needed. Keeps loading time down.
};
script.src = "./thirdparty/polyfill.min.js"; // dynamically load this only if its needed. Keeps loading time down.
},0);

View File

@ -113,6 +113,10 @@
https://github.com/djipco/webmidi
<br /><br />
Below you can test the <a href="https://docs.vdo.ninja/general-settings/midi">MIDI hotkey commands</a> for VDO.Ninja below:<br />
<br /><br />
The idea of this app is to select a MIDI output loopback device. Pressing any of the below buttons will trigger the respective MIDI note/command, sending it to the output MIDI device.
Since the MIDI output device should be a loopback device, you can select the MIDI loopback device in VDO.Ninja as its MIDI input source.
<br /><br />Refer to the documentation for what a MIDI note/value does in VDO.Ninja, but it can be used to control the director's room, either locally or remotely.
</div>
</div>
<div class="card">

View File

@ -60,6 +60,9 @@
position:absolute;
top: -150px;
}
#modal{
overflow:auto;
}
.message{
background: #3e3e3e00;
color: #FCFCFC;
@ -772,7 +775,7 @@
content: "8";
color: #a4a4a4;
}
#containermenu>div:nth-child(9)>div::before {
#containermenu>div:nth-child(10)>div::before {
content: "9";
color: #a4a4a4;
}
@ -2865,7 +2868,11 @@
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;
}

View File

@ -1006,12 +1006,25 @@
var startTime = false;
/////////// This is a very simple sample, of sending Note C0 to Channel 1
//function sendC0(channel=1){ // channel 1
// var msg = {};
// msg.sendRawMIDI = {};
// // ** Midi C0 is MIDI value 12.
// msg.sendRawMIDI.data = [143+channel,12,64]; // [143+channel , note-value , velocity value]
// // msg.UUID = xxx; // lets you specify a specific peer connection
// // msg.streamID = yyy; // lets you specify a specific stream ID
// iframe.contentWindow.postMessage(msg, '*');
//}
// sendC0();
///////////////////////////
function sendMIDI(e){
if (!this.data){console.log("no data");console.log(this);return;} // needs data array
var msg = {};
msg.sendRawMIDI = {};
msg.sendRawMIDI.timestamp = Date.now() - startTime;
msg.sendRawMIDI.channel = this.channel || false;
msg.sendRawMIDI.timestamp = Date.now() - startTime; // optional
msg.sendRawMIDI.channel = this.channel || false; // this doesn't do much
msg.sendRawMIDI.data = this.data;
if (this.data[2].length){
msg.sendRawMIDI.data[2] = parseInt(this.value);

View File

@ -69,15 +69,17 @@
<option value="de2">Frankfurt, Germany</option>
<option value="fr1">Strasbourg, France</option>
<option value="bra1">São Paulo, Brazil</option>
<option value="pol1">Warsaw, Poland</option>
<option value="cae1">Montreal, Canada</option>
<option value="use1">Virgina, USA</option>
<option value="usc1">Chicago, USA</option>
<option value="usw1">Los Angeles, USA</option>
<option disabled value="usc1">Chicago, USA</option>
<option disabled value="usw1">Los Angeles, USA</option>
<option value="usw2">Oregon, USA</option>
<option value="aus1">Sydney, Australia</option>
<option value="jap1">Tokyo, Japan</option>
<option value="sing1">Singapore</option>
<option value="ind1">Mumbai, India</option>
<option value="pol1">Warsaw, Poland</option>
</select>
<br /><br /><br />
</div>
@ -166,6 +168,34 @@
loadIframe(document.getElementById("turnlist").value);
}
function updateTurnlist(value){
var select = document.getElementById("turnlist");
var selected = select.value;
select.innerHTML = "";
var opt = document.createElement("option");
opt.value = ""
opt.title = "Choose the closest location automatically";
opt.innerHTML = "Automatic";
select.appendChild(opt);
if (selected == ""){
opt.selected = true;
}
for (var i =0;i<value.length;i++){
var opt = document.createElement("option");
opt.value = value[i].locale;
opt.title = value[i].name;
opt.innerHTML = value[i].name;
select.appendChild(opt);
if (selected == opt.value){
opt.selected = true;
}
}
}
function loadIframe(zone="") {
// this is pretty important if you want to avoid camera permission popup problems. YOu need to load the iFRAME after you load the parent body. A quick solution is like: <body onload=>loadIframe();"> !!!
@ -224,16 +254,19 @@
document.getElementById("container").appendChild(feeds);
document.getElementById("feeds").appendChild(iframeContainer);
setInterval(function (iframe1) {
try {
iframe1.contentWindow.postMessage({ getStats: true }, "*");
} catch(e){
clearInterval(this);
}
}, 1000, iframe);
var iframe = document.createElement("iframe");
var iframeContainer = document.createElement("span");
iframe.allow = "autoplay";
var srcString = "./?view=" + streamID + "&cleanoutput&privacy&noaudio&scale=0&speedtest="+zone;
var srcString = "./?view=" + streamID + "&cleanoutput&privacy&noaudio&scale=0&speedtest="+zone; // No TURN servers set on the reciever. Don't want to query for TURN servers needlessly.
if (urlParams.has("turn")) {
srcString = srcString + "&turn=" + urlParams.get("turn");
@ -301,9 +334,13 @@
document.getElementById("container").appendChild(buttonContainer);
setInterval(function () {
iframe.contentWindow.postMessage({ getStats: true }, "*");
}, 1000);
setInterval(function (iframe1) {
try {
iframe1.contentWindow.postMessage({ getStats: true }, "*");
} catch(e){
clearInterval(this);
}
}, 1000, iframe);
var eventMethod = window.addEventListener
? "addEventListener"
@ -314,7 +351,13 @@
eventer(messageEvent, function (e) {
if ("action" in e.data) {
logData(e.data.action, e.data.value || "");
if (e.data.action == "available-speedtest-servers"){
console.warn("Speedtest server list loaded");
updateTurnlist(e.data.value);
} else {
logData(e.data.action, e.data.value);
}
if (e.data.action == "new-view-connection") {
buttonContainer.querySelectorAll(

File diff suppressed because one or more lines are too long