fixes, features, and darkmode=true fix

This commit is contained in:
steveseguin 2023-06-29 02:20:57 -04:00
parent 70262002db
commit 7ee9653dfd
6 changed files with 467 additions and 89 deletions

View File

@ -56,7 +56,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=335" />
<link rel="stylesheet" href="./main.css?ver=342" />
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/adapter.js"></script>
<style id="lightbox-animations" type="text/css"></style>
<!-- <link rel="manifest" href="manifest.json" /> -->
@ -83,7 +83,7 @@
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/CodecsHandler.js?ver=47"></script>
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/aes.js"></script>
<script type="text/javascript" crossorigin="anonymous" src="./webrtc.js?ver=645"></script>
<script type="text/javascript" crossorigin="anonymous" src="./webrtc.js?ver=662"></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">
@ -144,6 +144,7 @@
</div>
<input type="text" id="chatInput" placeholder="Enter chat message to send here" onkeypress="EnterButtonChat(event)" />
<button class="chatBarInputButton" onclick="sendChatMessage()" data-translate='send-chat'>Send</button>
<button onclick="toggleFileshare()" data-translate='upload-chat'><i class="las la-file-upload"></i> Upload File</button>
</div>
<div id="subControlButtons">
@ -186,7 +187,7 @@
</div>
<div id="websitesharebutton2" onmousedown="event.preventDefault(); event.stopPropagation();" title="Hold CTRL (or CMD) and click to spotlight this video" alt="Share a website as an embedded iFRAME" aria-label="Share a website" onclick="shareWebsite(false, event)" tabindex="21" role="button" aria-pressed="false" onkeyup="enterPressedClick(event,this);" class="float2 orange shake hidden" style="cursor: pointer;max-width: 200px;margin: auto;padding: 0 10px;">
<i onmousedown="event.preventDefault(); event.stopPropagation();" class="toggleSize las la-window-close" style="display: inline-block;"></i>
<div style="display: inline-block;width: 85px;line-height: 1; font-size: 0.9em;">
<div style="display: inline-block;width: 85px;line-height: 1; font-size: 0.9em; background-color: unset;box-shadow: unset;">
Stop Sharing Website
</div>
</div>
@ -356,7 +357,7 @@
</table>
<span style="margin: 0 auto; width: 470px; max-width:100%;display: block;">
<button onclick="createRoom()" class="gobutton" style="width:100%;" alt="Enter the room as the group's director" title="You'll enter as the room's director">
<button onclick="createRoom()" class="gobutton gowebcam" style="width:100%;" alt="Enter the room as the group's director" title="You'll enter as the room's director">
<span data-translate="enter-the-rooms-control">Enter the room's Control Center in the director's role</span>
</button>
<br />
@ -379,7 +380,7 @@
<i data-translate="looking-to-just-chat-and-not-direct">Looking to just chat and not direct?</i>
<br />
<br />
<button onclick="jumptoroom2()" class="gobutton" style="width:100%;" alt="Enter the room as the group's director" title="You'll enter as the room's director">
<button onclick="jumptoroom2()" class="gobutton gowebcam" style="width:100%;" alt="Enter the room as the group's director" title="You'll enter as the room's director">
<span data-translate="join-the-room-basic">Join the room as a Participant, rather than a director</span>
</button>
</span>
@ -555,6 +556,11 @@
Safari is more prone to having audio issues</span></p>
</div>
<div id="oldiOSWarning" class="startupWarning hidden">
<i class="las la-exclamation-circle"></i>
<p><span data-translate="update-your-device">We've detected that you are using an old version of Apple iOS, which is known to have many issues.<br /><br />Please consider updating.</span></p>
</div>
</div>
<div class="outer close">
<div class="inner">
@ -894,6 +900,36 @@
<i style="margin-top:30px;font-size:600%;overflow:hidden;" class="largeDarkIcon las la-heartbeat"></i>
</div>
<div id="container-16" class="column columnfade pointer rounded card hidden" style=" overflow-y: auto;">
<h2><span data-translate="share-whepsrc">Share WHEP Media Source</span></h2>
<i style="margin-top:30px;font-size:560%;overflow:hidden;" class="largeDarkIcon las la-broadcast-tower"></i>
<div class="container-inner">
<br />
<div id="previewWhepSource"></div>
<br />
<span data-translate="enter-the-whep-URL-you-wish-to-share">Enter the WHEP URL you wish to share.</span><br /><br />
<input type="text" autocorrect="off" id="whepURL" autocapitalize="none" style="margin:10px; border:2px solid; padding:10px; width:400px;" title="Enter an HTTPS URL" multiple/><br />
<button onclick="previewIframe(getById('whepURL').value);" >Preview WHEP Stream</button>
<button onclick="this.innerHTML = 'Update'; session.publishIFrame(getById('iframeURL').value);" >Start Sharing</button><br />
<div class="message-card info">
<h1>Usage information</h1>
<p></p>
<ul style="text-align: left;">
<li>WHEP sources are expected to support multiple viewers; simulcasting will be used if possible.</li>
<li>Remote URLs must allows cross-origin requests (CORS), along with having SSL (https).</li>
</ul>
</div>
</div>
<div class="outer close">
<div class="inner">
<label class="labelclass">
<span data-translate="back">Back</span>
</label>
</div>
</div>
</div>
<p></p>
<div id="info" class="fullcolumn columnfade">
<center>
@ -1007,7 +1043,7 @@
<div class='directorBlock' style="background-color: var(--green-accent);" >
<h2 title="Use this link in the OBS Browser Source to capture the video or audio" style="margin-left: 1px;margin-top: 5px;"><i class="las la-th-large director-link-icons" style="margin-right: 6px;" ></i> <span data-translate="capture-a-group-scene">CAPTURE A GROUP SCENE</span></h2>
<span style="margin:5px; line-height: 1.6;" data-translate='this-is-obs-browser-source-link'>Use in OBS or other studio software to capture the group video mix</span>
<a onclick='copyFunction(this,event)' data-drag="1" draggable="true" id="director_block_3" data-menu="context-menu" class='task grabLinks' style='cursor:grab;background-color: #0003;'></a>
<a onclick='copyFunction(this,event)' data-drag="1" draggable="true" id="director_block_3" data-menu="context-menu" class='task grabLinks publish' style='cursor:grab;background-color: #0003;'></a>
<span style="display:block;">
<span style="bottom: 0; margin: 0 0 0 10px; top: 22px; position: relative; display:inline-block; max-width: 45%;">
<label class="switch" title="If disabled, you must manually add a video to a scene for it to appear.">
@ -1562,6 +1598,11 @@
<i class="las la-file-upload"></i>
<span data-translate="mirror-guest"> Mirror Video</span>
</button>
<button class="mainonly advanced" 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>
</div>
<!-- Row of Channels -->
@ -1815,7 +1856,7 @@
</div>
<div id="selectEffectAmount3" style="display:none;margin-top:10px;">
<label for="selectEffectAmountInput" style="width: 113px;display: inline-block;">Effect amount:</label>
<label for="selectEffectAmountInput3" style="width: 113px;display: inline-block;">Effect amount:</label>
<input id="selectEffectAmountInput3" style="display: inline-block;width: 350px; max-width: 60%;margin:6px 0;" name="selectEffectAmountInput3" title="Adjust the amount of effect applied" type="range" oninput="changeEffectAmount(event, this)" onchange="changeEffectAmount(event, this)" min="0" step="1" max="20">
</div>
@ -1897,6 +1938,12 @@
<span data-translate="edit-url" >Edit URL manually</span>
</a>
</li>
<li class="context-menu__item hidden">
<a href="#" class="context-menu__link" data-action="Publish">
<i class="las la-tv"></i>
<span data-translate="publish-url" >Publish via WHIP</span>
</a>
</li>
<li class="context-menu__item">
<a href="#" class="context-menu__link" data-action="QRCode">
📷
@ -2017,7 +2064,6 @@
<span data-translate="save-current-frame">Save frame to disk</span>
</a>
</li>
<li class="context-menu__item">
<a href="#" class="context-menu__link" data-action="ShowStats">
<i class="las la-external-link"></i>
@ -2171,6 +2217,32 @@
</div>
</div>
<div id="publishSettings" style="display:none; user-select: none;">
<div class="promptModalInner">
<h3 data-translate="publish-settings">Publish setup</h3>
<br />
<small>To publish this browser window, click the start publishing button below and then select the current browser window, with audio-selected if desired. The stream will go live afterwards, automatically.</small>
<div id="publishOutURL" class="hidden">
<br />
I'm publishing to Twitch: <input type="checkbox" onchange="twitchSelect(this)"; />
<br/> or
<br />
Custom WHIP URL: <input type="text" size="30" placeholder="WHIP URL to publish to goes here">
</div>
<div id="publishOutToken" class="hidden">
<br />
Stream token: <input type="password" placeholder="Stream or auth token here">
</div>
<br />
<br />
<button onclick='startPublishing();'>🎦 Select window and start publishing</button>
<br /><br />
<i>note: To stop the stream, simply close this browser window.</i>
</div>
</div>
<div id="remoteOBSControl" class="customModelPopup" style="display:none;">
<div class="promptModalInner">
<span class='modalClose' onclick="toggleOBSControls();">×</span>
@ -2376,7 +2448,7 @@
var session = WebRTC.Media; // session is a required global variable if configuring manually. Run before loading main.js but after webrtc.js.
session.version = "23.4";
session.version = "23.7";
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
@ -2489,11 +2561,11 @@
// session.hidehome = true; // If used, 'hide home' will make the landing page inaccessible, along with hiding a few go-home elements.
// session.record = false; // uncomment to block users from being able to record via vdo.ninja's built in recording function
</script>
<script type="text/javascript" crossorigin="anonymous" id="lib-js" src="./lib.js?ver=821"></script>
<script type="text/javascript" crossorigin="anonymous" id="lib-js" src="./lib.js?ver=833"></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=639"></script>
<script type="text/javascript" crossorigin="anonymous" id="main-js" src="./main.js?ver=650"></script>
</body>
</html>

314
lib.js
View File

@ -81,6 +81,7 @@ var miscTranslations = {
"share-a-screen": "Share a screen",
"stop-screen-sharing": "Stop screen sharing",
"you-have-been-transferred": "You've been transferred to a different room",
"you-have-been-activated": "The director has allowed you to see others in the room now",
"you-are-no-longer-a-co-director": "You are no longer a co-director as you were transferred.",
"transferred": "Transferred",
"room-changed": "Your room has changed",
@ -873,8 +874,8 @@ async function promptAlt(inputText, block=false, asterix=false, value=false, tim
return result;
}
async function promptTransfer(value=null, bcmode = null, updateurl = null){
var result = {room:null};
async function promptTransfer(value=null, bcmode = null, updateurl = null, queueMode = null){
var result = {roomid:null};
if (session.beepToNotify){
playtone();
}
@ -898,6 +899,7 @@ async function promptTransfer(value=null, bcmode = null, updateurl = null){
<input id="input_${promptID}" data-pid="${promptID}" type="text" autocorrect="off" autocapitalize="none" class="largeTextEntry" />
<span class='promptModalLabel'><input id="private_${promptID}" data-pid="${promptID}" type="checkbox" title="Note: this won't work fully if using obfuscated links" /> Allow the guest to rejoin the transfer room on their own</span>
<span class='promptModalLabel'><input id="broadcast_${promptID}" data-pid="${promptID}" type="checkbox" /> Guest will arrive in the new room in <i>broadcast</i> mode</span>
<span class='promptModalLabel'><input id="queued_${promptID}" data-pid="${promptID}" type="checkbox" /> Guest will arrive in the new room in <i>queue</i> mode</span>
<button id="submit_${promptID}" data-pid="${promptID}" style="width:120px; background-color: #fff; position: relative;border: 1px solid #999; margin: 0 0 0 55px;" data-translate='ok'> OK</button>
<button id="cancel_${promptID}" data-pid="${promptID}" style="width:120px; background-color: #fff; position: relative;border: 1px solid #999; margin: 0;" data-translate='cancel'> Cancel</button>
</div>
@ -916,6 +918,9 @@ async function promptTransfer(value=null, bcmode = null, updateurl = null){
if (bcmode!==null){
document.getElementById("broadcast_"+promptID).checked = bcmode;
}
if (queueMode!==null){
document.getElementById("queued_"+promptID).checked = queueMode;
}
if (updateurl!==null){
document.getElementById("private_"+promptID).checked = updateurl;
@ -927,10 +932,11 @@ async function promptTransfer(value=null, bcmode = null, updateurl = null){
var room = document.getElementById("input_"+pid).value;
var updateurl = document.getElementById("private_"+pid).checked;
var broadcast = document.getElementById("broadcast_"+pid).checked;
var queue = document.getElementById("queued_"+pid).checked;
document.getElementById("modal_"+pid).remove();
document.getElementById("modalBackdrop_"+pid).remove();
Prompts[pid].resolve();
result = {roomid:room, updateurl:updateurl, broadcast:broadcast};
result = {roomid:room, updateurl:updateurl, broadcast:broadcast, queue:queue};
}
});
@ -939,11 +945,11 @@ async function promptTransfer(value=null, bcmode = null, updateurl = null){
var room = document.getElementById("input_"+pid).value;
var updateurl = document.getElementById("private_"+pid).checked;
var broadcast = document.getElementById("broadcast_"+pid).checked;
var queue = document.getElementById("queued_"+pid).checked;
document.getElementById("modal_"+pid).remove();
document.getElementById("modalBackdrop_"+pid).remove();
Prompts[pid].resolve();
result = {roomid:room, updateurl:updateurl, broadcast:broadcast};
result = {roomid:room, updateurl:updateurl, broadcast:broadcast, queue:queue};
});
document.getElementById("cancel_"+promptID).addEventListener("click", function(event){
@ -985,6 +991,11 @@ function youveBeenTransferred(){
hideHomeCheck();
}
function youveBeenActivated(){
getChatMessage( miscTranslations["you-have-been-activated"], label = false, director = false, overlay = true); // "you-have-been-transferred"
hideHomeCheck();
}
async function confirmAlt(inputText, block=false){
var result = null;
if (session.beepToNotify){
@ -2116,6 +2127,40 @@ function compare_vids( a, b ) {
return 0;
}
function compare_vids_sid( a, b ) {
var aa = a.dataset.sid || 0;
var bb = b.dataset.sid || 0;
if ( aa > bb ){
return 1;
}
if ( aa < bb ){
return -1;
}
return 0;
}
function compare_vids_label( a, b ) {
if (a.dataset.UUID && session.rpcs[a.dataset.UUID] && session.rpcs[a.dataset.UUID].label){
var aa = session.rpcs[a.dataset.UUID].label.toLowerCase();
} else {
var aa = 0;
}
if (b.dataset.UUID && session.rpcs[b.dataset.UUID] && session.rpcs[b.dataset.UUID].label){
var bb = session.rpcs[b.dataset.UUID].label.toLowerCase();
} else {
var bb = 0;
}
if ( aa > bb ){
return 1;
}
if ( aa < bb ){
return -1;
}
return 0;
}
function sortByZ(mediaPool, layout) {
function sortABZ( a, b ) {
if (layout[a.dataset.sid]){
@ -2216,9 +2261,15 @@ function makeMiniDraggableElement(elmnt) {
miniPerformerX = window.innerWidth-elmnt.clientWidth;
}
miniPerformerX = 100 * miniPerformerX/window.innerWidth;
miniPerformerX = 100 * miniPerformerX/window.innerWidth ;
miniPerformerY = 100 * miniPerformerY/window.innerHeight;
if (session.widget && !session.leftMiniPreview){
if (miniPerformerX>74){
miniPerformerX = 74;
}
}
if (miniPerformerY<0){
miniPerformerY=0;
} else if (miniPerformerY>100){
@ -3503,6 +3554,11 @@ function createRichVideoElement(UUID){ // this function is used to check and gen
applyMirrorGuest(session.rpcs[UUID].mirrorState, session.rpcs[UUID].videoElement);
}
if (session.posterImage){
session.rpcs[UUID].videoElement.poster = session.posterImage;
}
setupIncomingVideoTracking(session.rpcs[UUID].videoElement, UUID);
pokeIframeAPI("video-element-created", "videosource_"+UUID, UUID);
}
@ -4503,6 +4559,15 @@ function updateMixerRun(e=false){ // this is the main auto-mixing code. It's a
}
if (!session.layout){
if (session.orderby){
if (session.orderby=="id"){
mediaPool.sort(compare_vids_sid);
} else if (session.orderby=="label"){
mediaPool.sort(compare_vids_label);
} else {
mediaPool.sort(compare_vids_sid);
}
}
mediaPool.sort(compare_vids);
}
@ -4785,10 +4850,18 @@ function updateMixerRun(e=false){ // this is the main auto-mixing code. It's a
container.style.top = miniPerformerY + "%";
}
if (miniPerformerX !== null){
if (session.widget && !session.leftMiniPreview){
if (miniPerformerX>74){
miniPerformerX = 74;
}
}
container.style.left = miniPerformerX + "%";
} else if (session.leftMiniPreview!==false){
container.style.left = session.leftMiniPreview + "%";
togglePreview.style.left = session.leftMiniPreview + "%";
} else if (session.widget){
container.style.right = "25%";
togglePreview.style.right = "25%";
} else {
container.style.right = "2vw";
togglePreview.style.right = "2vw";
@ -5732,7 +5805,7 @@ function updateMixerRun(e=false){ // this is the main auto-mixing code. It's a
if (!session.cleanOutput && !session.nocursor){
if ((session.roomid!==false) && (session.scene===false)){
if (!((vid.id === "videosource") && (miniPreview))){
if (!((vid.id === "videosource") && miniPreview) && !session.infocusForceMode){
if (!holder.button){
var button = document.createElement("div");
@ -5835,7 +5908,7 @@ function updateMixerRun(e=false){ // this is the main auto-mixing code. It's a
container.style.backgroundColor= null;
button.style.opacity="10%";
};
} else if ((vid.id === "videosource") && miniPreview && soloVideo==true){
} else if ((vid.id === "videosource") && miniPreview && soloVideo==true && !session.infocusForceMode){
if (!holder.button){
var button = document.createElement("div");
@ -5875,6 +5948,12 @@ function updateMixerRun(e=false){ // this is the main auto-mixing code. It's a
}
};
} else if (session.infocusForceMode && holder.button){
try{
holder.button.remove();
} catch(e){
errorlog(e);
}
}
}
}
@ -8601,11 +8680,11 @@ function plotData(info, UUID, uuid) { // type = "bitrate" or "nacks"
canvas.history_bitrate = [];
canvas.target = 4000;
if (info.scene){
canvas.title = "Scene: "+info.scene+". Red/orange implies packet loss. Y-axis is 0 to 4000-kbps.";
canvas.title = "Scene: "+info.scene+". Red/orange implies packet loss. Y-axis is marked with 2500-kbps increments.";
} else if (info.label){
canvas.title = "Label: "+info.label+". Red/orange implies packet loss. Y-axis is 0 to 4000-kbps.";
canvas.title = "Label: "+info.label+". Red/orange implies packet loss. Y-axis is marked with 2500-kbps increments";
} else {
canvas.title = "Red/orange implies packet loss. Y-axis is 0 to 4000-kbps.";
canvas.title = "Red/orange implies packet loss. Y-axis is marked with 2500-kbps increments";
}
canvas.dataset.uid = uuid;
@ -8872,9 +8951,11 @@ function processStats(UUID){
}
var validTrackIds = [];
session.rpcs[UUID].videoElement.srcObject.getTracks().forEach(trk=>{
validTrackIds.push(trk.id);
});
if (session.rpcs[UUID].videoElement){
session.rpcs[UUID].videoElement.srcObject.getTracks().forEach(trk=>{
validTrackIds.push(trk.id);
});
}
if (node.getStats){
node.getStats().then(function(stats){
@ -11778,10 +11859,17 @@ async function directMigrate(ele, event, room=false) { // everyone in the room w
broadcastMode = transferSettings.broadcast;
} else if (session.rpcs[ele.dataset.UUID] && session.rpcs[ele.dataset.UUID].stats.info && ("broadcast_mode" in session.rpcs[ele.dataset.UUID].stats.info)){
broadcastMode = session.rpcs[ele.dataset.UUID].stats.info.broadcast_mode;
} else if (session.broadcastTransfer){
} else if (session.broadcastTransfer!==null){
broadcastMode = session.broadcastTransfer;
}
var queuedMode = null;
if ("queue" in transferSettings){
queuedMode = transferSettings.queue;
} else if (session.queueTransfer){
queuedMode = session.queueTransfer;
}
var updateurl = null;
if ("updateurl" in transferSettings){
updateurl = transferSettings.updateurl;
@ -11789,7 +11877,7 @@ async function directMigrate(ele, event, room=false) { // everyone in the room w
window.focus();
var response = await promptTransfer(previousRoom, broadcastMode, updateurl);
var response = await promptTransfer(previousRoom, broadcastMode, updateurl, queuedMode);
var migrateRoom = response.roomid;
if (migrateRoom !== null){
transferSettings = response;
@ -14509,6 +14597,121 @@ session.publishIFrame = function(iframeURL){
return container;
} // publishIframe
session.publishWhepSrc = function(){
if (!session.whepSrc){errorlog("no WHEP Src");return;}
if (!session.cleanOutput){
getById("websitesharebutton2").classList.remove('hidden');
}
var UUID = whepIn(session.whepSrc);
var container = document.createElement("div");
iframe.container = container;
container.id = "container_iframe";
container.appendChild(iframe);
getById("gridlayout").appendChild(container);
if (session.iframeSrc.startsWith("https://www.youtube.com/")){ // special handler.
setTimeout(function(iframe_id){YoutubeListen(iframe_id);}, 1000, iframe.id);
}
if (session.cover){
container.style.setProperty('height', '100%', 'important');
}
if (session.roomid!==false){
if ((session.roomid==="") && ((!(session.view)) || (session.view===""))){
} else {
log("ROOMID EANBLED");
getById("head3").classList.add('hidden');
getById("head3a").classList.add('hidden');
joinRoom(session.roomid);
}
} else {
getById("head3").classList.remove('hidden');
getById("head3a").classList.remove('hidden');
getById("logoname").style.display = 'none';
}
getById("head1").className = 'hidden';
updatePushId()
getById("head1").className = 'hidden';
getById("head2").className = 'hidden';
if (!(session.cleanOutput)){
getById("chatbutton").className="float";
getById("hangupbutton").className="float";
getById("controlButtons").classList.remove("hidden");
getById('sharefilebutton').classList.remove("hidden"); // we won't override "display:none", if set, though.
getById("helpbutton").style.display = "inherit";
getById("reportbutton").style.display = "";
} else {
getById("controlButtons").classList.add("hidden");
}
if (session.chatbutton === false) {
getById("chatbutton").classList.add("hidden");
}
if (session.director){
//
} else if (session.scene!==false){
updateMixer();
} else if (session.roomid!==false){
if (session.roomid===""){
if (!(session.view) || (session.view==="")){
session.windowed = true;
container.classList.add("vidcon");
getById("mutespeakerbutton").classList.add("hidden");
container.style.width="100%";
container.style.height="100%";
container.style.alignItems = "center";
container.style.maxWidth= "100%";
container.style.maxHeight= "100%";
container.style.verticalAlign= "middle";
container.style.margin= "auto";
container.style.backgroundColor = "#666";
container.style.border = "2px solid";
} else {
session.windowed = false;
window.onresize = updateMixer;
updateMixer();
}
} else {
window.onresize = updateMixer;
session.windowed = false;
updateMixer();
}
} else {
window.onresize = updateMixer;
container.style.maxHeight= "1280px";
container.style.maxWidth= "720px";
container.style.verticalAlign= "middle";
container.style.height="100%";
container.style.width= "100%";
container.style.margin= "auto";
container.style.alignItems = "center";
container.style.backgroundColor = "#666";
}
session.seeding=true;
updateReshareLink();
pokeIframeAPI('started-iframe-share');
session.seedStream();
return container;
} // publishWhepSrc
function outboundAudioPipeline(){ // this function isn't letting me change the audio source
if (session.disableWebAudio) {
@ -15527,7 +15730,7 @@ function joinRoom(roomname) {
}
if (!session.cleanOutput){
if (session.roomhost){
if (session.roomhost){
if (session.defaultPassword===false){
if (session.password === false){
var invite = "https://"+location.host+location.pathname+"?room="+session.roomid+"&password=false"+token;
@ -16285,7 +16488,7 @@ async function createRoomCallback(passAdd, passAdd2) {
var container = getById("rooms");
container.innerHTML += '<span style="display:inline-block">Arm Transfer: </span>';
session.rooms.forEach(function (r) {
if(session.roomid == r) return; //don't include self
// if(session.roomid == r) return; //don't include self
container.innerHTML += '<button id="roomselect_' + r + '" onmousedown="event.preventDefault(); event.stopPropagation();" style="display:inline-block" class="float btnArmTransferRoom" onclick="handleRoomSelect(\'' + r + '\');" title="Arm/disarm transfer to this room" tabindex="' + tabindex + '"><i class="las la-paper-plane"></i>' + r + '</button>';
tabindex++;
});
@ -17250,7 +17453,15 @@ function swapNodes(n1, n2) {
p2.insertBefore(n1, p2.children[i2]);
}
function createControlBox(UUID, soloLink, streamID) {
function remoteRemoveQueue(ele){
let ts = {...transferSettings};
ts.justResetting = true;
session.directMigrateIssue(session.roomid, ts, ele.dataset.UUID);
ele.classList.add("hidden");
}
function createControlBox(UUID, soloLink, streamID, slot_init=false) {
if (document.getElementById("deleteme")) {
getById("deleteme").parentNode.removeChild(getById("deleteme"));
}
@ -17336,7 +17547,7 @@ function createControlBox(UUID, soloLink, streamID) {
controls.innerHTML += "<div id='advanced_video_director_" + UUID + "' class='hidden advancedVideoSettings'></div>";
var handsID = "hands_" + UUID;
// controls.innerHTML += "<div class='flexBreak'><span data-translate='links'>Links</span></div>"; //Seems to create an empty div.
if (session.hidesololinks==false){
@ -17347,11 +17558,18 @@ function createControlBox(UUID, soloLink, streamID) {
</div>";
}
controls.innerHTML += "<button data-action-type=\"hand-raised\" id='" + handsID + "' class='hidden lowerRaisedHand' title=\"This guest raised their hand. Click this to clear notification.\" onclick=\"remoteLowerhands('" + UUID + "');\">\
controls.innerHTML += "<button data-action-type=\"hand-raised\" data--u-u-i-d='"+UUID+"' id='" + handsID + "' class='hidden lowerRaisedHand' title=\"This guest raised their hand. Click this to clear notification.\" onclick=\"remoteLowerhands('" + UUID + "');\">\
<i class=\"las la-hand-paper\"></i>\
<span data-translate=\"user-raised-hand\">Lower Raised Hand</span>\
</button>\
</div>";
controls.innerHTML += "<button data-action-type=\"remove-queue\" data--u-u-i-d='"+UUID+"' class='hidden removeFromQueue' title=\"Takes the guest out of queue mode; they will then join as a normal guest.\" onclick=\"remoteRemoveQueue(this);\">\
<i class=\"las la-plus\"></i>\
<span data-translate=\"remove-from-queue\">Activate Guest</span>\
</button>\
</div>";
controls.querySelectorAll('[data-action-type]').forEach((ele) => { // give action buttons some self-reference
ele.dataset.UUID = UUID;
@ -17363,7 +17581,13 @@ function createControlBox(UUID, soloLink, streamID) {
var slots = document.querySelectorAll("div.slotsbar[data-slot]");
var biggestSlot=0;
var slotDefault = null;
if (slot_init && (session.slotmode==1)){
slotDefault = slot_init || null;
}
if (streamID in session.pastSlots){
slotDefault = session.pastSlots[streamID];
}
@ -17373,15 +17597,18 @@ function createControlBox(UUID, soloLink, streamID) {
if (parseInt(slots[i].dataset.slot)>biggestSlot){
biggestSlot = parseInt(slots[i].dataset.slot);
}
if (slotDefault===parseInt(slots[i].dataset.slot)){
if (slotDefault===parseInt(slots[i].dataset.slot)){ // already taken
slotDefault = null;
}
}
biggestSlot+=1;
}
if (slotDefault!==null){
if (slotDefault!==null){ // the default slot is avialable
biggestSlot = slotDefault;
}
} else if (slot_init && (session.slotmode==1)){ // was manually set, so can't be something else but 0; 0 or false would still end up with 0
biggestSlot = 0;
} // else, if slotmode==1, we'll go for the biggest slot available
var slotName = "slot: "+biggestSlot;
if (!biggestSlot){
slotName = "unset";
@ -23369,6 +23596,10 @@ session.postPublish = async function(){
if (session.whipOutput){
whipOut();
}
if (session.whepHost){
whepOut();
}
}
@ -28648,6 +28879,7 @@ async function shareWebsite(autostart=false, evt=false){
}
}
function screenshareTypeDecider(sstype=1){
if (session.screenshareType){
sstype = session.screenshareType;
@ -33325,7 +33557,6 @@ function audioMeterGuest(mediaStreamSource, UUID, trackid){
function updateLevels() {
if (!session.rpcs || !(UUID in session.rpcs)){return;}
try {
session.rpcs[UUID].inboundAudioPipeline[trackid].analyser.getByteFrequencyData(dataArray);
var total = 0;
@ -33409,6 +33640,7 @@ function audioMeterGuest(mediaStreamSource, UUID, trackid){
} catch(e){
warnlog(e);
// fail as an exception; this is a control close.
return;
}
};
@ -34641,11 +34873,13 @@ async function processWHIP(data){ // LISTEN FOR REMOTE WHIP
return sdpAnswer; // return SDP answer for the remote WHIP request
}
async function whepIn(){ // PLAY WHEP
async function whepIn(whepInput=false,whepInputToken, UUID=false){ // PLAY WHEP
var candidates = [];
var responseLocation = false;
var UUID = session.generateRandomString(25); // fake
UUID = UUID+"_whep" || session.generateRandomString(25); // fake
whepInput = whepInput || session.whepInput;
whepInputToken = whepInputToken || session.whepInputToken;
async function whepConnect(){
try {
@ -34667,7 +34901,7 @@ async function whepIn(){ // PLAY WHEP
}
var config = {...session.configuration};
if (session.whepInput.includes("cloudflare")){
if (whepInput.includes("cloudflare")){
config.iceTransportPolicy = "relay"; // oof. Doesn't work with Cloudflare without this?
}
@ -34814,17 +35048,17 @@ async function whepIn(){ // PLAY WHEP
};
if (type==="trickle-ice-sdpfrag"){
if (responseLocation){
xhttp.open("PATCH", session.whepInput, true);
xhttp.open("PATCH", whepInput, true);
} else {
xhttp.open("PATCH", session.whepInput, true);
xhttp.open("PATCH", whepInput, true);
}
} else {
xhttp.open("POST", session.whepInput, true);
xhttp.open("POST", whepInput, true);
}
xhttp.setRequestHeader('Content-Type', 'application/'+type);
if (session.whepInputToken){
xhttp.setRequestHeader('Authorization', 'Bearer ' + session.whepInputToken);
if (whepInputToken){
xhttp.setRequestHeader('Authorization', 'Bearer ' + whepInputToken);
}
xhttp.onerror = function(e) {
errorlog(e);
@ -34836,11 +35070,12 @@ async function whepIn(){ // PLAY WHEP
}
whepConnect();
return UUID;
}
////////
function whepOut(){ // publish to whip.vdo.ninja with obs, to use. experimental
if (!session.whipView){return;}
warnlog("WHIP Client started");
function whepOut(){ // publish to whep.vdo.ninja with obs, to use. experimental
if (!session.whepHost){return;}
warnlog("WHEP Client started");
var socket = null;
var connecting = false;
@ -34876,7 +35111,7 @@ function whepOut(){ // publish to whip.vdo.ninja with obs, to use. experimental
failedCount = 0;
try{
var settings = {};
socket.send(JSON.stringify({"join":session.whipView}));
socket.send(JSON.stringify({"join":session.whepHost}));
} catch(e){
connecting = setTimeout(function(){connect();},1);
}
@ -34919,11 +35154,6 @@ async function processWHEPout(data){ // LISTEN FOR REMOTE WHIP
// msg.session = session.generateRandomString(5);
msg.UUID = session.generateRandomString(25); // fake
if (data.streamID){
msg.streamID = data.streamID;
} else {
msg.streamID = session.generateRandomString(15); // fake
}
log("setupoutgoing");
try {

View File

@ -2464,11 +2464,23 @@ span[data-action-type="stats-graphs-details-container"]>span{
.lowerRaisedHand{
margin: auto;
margin-top: 5px;
margin-left: 5px;
margin-bottom: 10px;
background-color: yellow;
width: calc(100% - 10px);
margin-bottom: 5px;
background-color: yellow!important;
width: 100%;
color:black!important;
}
.removeFromQueue{
margin: auto;
margin-top: 5px;
margin-left: 5px;
margin-bottom: 5px;
background-color: #ff00b8!important;
width: 100%
}
.float {
opacity: 0.8;
min-width: 45px;
@ -3415,9 +3427,7 @@ div#roomnotes2 {
margin: 0 var(--regular-margin) 10px var(--regular-margin);
width: 100%;
}
.directorsgrid button {
text-transform: capitalize;
}
#yourDirectorStatus {
color: var(--discord-text);
@ -3431,6 +3441,7 @@ div#roomnotes2 {
background-color: #606383 !important;
display: var(--show-codirectors) !important;
}
/* ---- DIRECTORS PAGE - Guest Controls Box ---- */
.controlsGrid {
display: flex;

107
main.js
View File

@ -141,7 +141,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.showControls = false; // show the video control bar
}
if (!isIFrame){
if (!isIFrame && !window.obsstudio){
if (ChromeVersion===65){
// pass, since probably manycam and that's bugged
} else if (getStorage("redirect") == "yes") {
@ -285,6 +285,24 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
}
if (urlParams.has('poster')) { // URL or data:base64 image. Becomes local to this viewer only. This is like &avatar, but slightly different. Just CSS in this case
var posterImage = urlParams.get('poster') || "./media/avatar.webp";
if (posterImage){
try {
posterImage = decodeURIComponent(posterImage);
session.posterImage = posterImage;
} catch(e){}
}
}
if (urlParams.has('hideplaybutton') || urlParams.has('hpb')) { // URL or data:base64 image. Becomes local to this viewer only. This is like &avatar, but slightly different. Just CSS in this case
try {
document.getElementById("bigPlayButton").classList.add("hidden");
} catch(e){
}
}
if (urlParams.has('whip') || urlParams.has('whipview')) {
session.whipView = urlParams.get('whip') || urlParams.get('whipview') || false;
if (session.whipView){
@ -339,15 +357,8 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
}
if (urlParams.has('whepplaytoken')) { // URL or data:base64 image. Becomes local to this viewer only. This is like &avatar, but slightly different. Just CSS in this case
if (urlParams.get('whepplaytoken')){
try {
session.whepInputToken = urlParams.get('whepplaytoken')
} catch(e){
errorlog(e);
}
}
if (urlParams.has("hostwhep")){
session.whepHost = urlParams.get("hostwhep") || session.streamID || false;
}
if (urlParams.has('nomouseevents') || urlParams.has('nme')) {
@ -391,6 +402,9 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.audioEffects = false; // disable audio inbound effects also.
session.audioMeterGuest = false;
} else if (iOS || iPad) {
if (SafariVersion<16){
getById("oldiOSWarning").classList.remove('hidden');
}
session.mobile = true;
session.audioEffects = false; // disable audio inbound effects also.
session.audioMeterGuest = false;
@ -456,6 +470,25 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
}
if (urlParams.has('queuetransfer') || urlParams.has('qt')) {
log("Broadcast transfer flag set");
session.queueTransfer = urlParams.get('queuetransfer') || urlParams.get('qt') || null;
if (session.queueTransfer === "false") {
session.queueTransfer = false;
} else if (session.queueTransfer=== "0") {
session.queueTransfer = false;
} else if (session.queueTransfer === "no") {
session.queueTransfer = false;
} else if (session.queueTransfer === "off") {
session.queueTransfer = false;
} else {
session.queueTransfer = true;
}
if (transferSettings){
transferSettings.queue = session.queueTransfer;
}
}
if (urlParams.has('broadcast') || urlParams.has('bc')) {
log("Broadcast flag set");
session.broadcast = urlParams.get('broadcast') || urlParams.get('bc') || null;
@ -2129,8 +2162,9 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
getById("obsState").style.setProperty("display", "none", "important");
}
if (urlParams.has('hidecodirectors')){
if (urlParams.has('hidecodirectors') || urlParams.has('hidecodirector') || urlParams.has('hidedirector') || urlParams.has('hidedirectors') || urlParams.has('hd')){
document.querySelector(':root').style.setProperty("--show-codirectors", "none", "important");
session.hideDirector = true;
}
if (urlParams.has('pptcontrols') || urlParams.has('slides') || urlParams.has('ppt') || urlParams.has('powerpoint')){
@ -2345,7 +2379,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.darkmode = false;
} else if (window.obsstudio){
session.darkmode = false; // prevent OBS from defaulting to dark mode, avoiding possible overlooked bugs.
} else {
} else if (session.darkmode===null){
session.darkmode = getComputedStyle(document.querySelector(':root')).getPropertyValue('--color-mode').trim();
if (session.darkmode == "dark"){
session.darkmode = true;
@ -2609,7 +2643,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
if (urlParams.has('orderby')) {
session.orderby = urlParams.get('orderby') || "id";
session.orderby = urlParams.get('orderby') || "id"; // "label" also an option; the default is stream ID tho.
}
if (urlParams.has('slot')) {
@ -3171,6 +3205,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.cleanish = true;
}
if (session.cleanish || !session.cleanOutput){
if (session.obsControls){
getById("obscontrolbutton").classList.remove("hidden");
@ -3266,8 +3301,8 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
}
if (urlParams.has('maxframeRate') || urlParams.has('mfr') || urlParams.has('mfps')) {
session.maxframeRate = urlParams.get('maxframeRate') || urlParams.get('mfr') || urlParams.get('mfps');
if (urlParams.has('maxframerate') || urlParams.has('mfr') || urlParams.has('mfps')) {
session.maxframeRate = urlParams.get('maxframerate') || urlParams.get('mfr') || urlParams.get('mfps');
session.maxframeRate = parseInt(session.maxframeRate);
log("max frameRate assigned");
log(session.maxframeRate);
@ -3826,6 +3861,13 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if (urlParams.has('queue')) {
session.queue = true;
if (urlParams.get('queue') === "false"){
session.queue = false;
} else if (urlParams.get('queue') === "0"){
session.queue = false;
} else if (urlParams.get('queue') === "off"){
session.queue = false;
}
}
@ -4017,6 +4059,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
// session.studioSoftware = true; // vmix
if (window.obsstudio){
session.studioSoftware = true;
getById("saveRoom").style.display = "none"; // don't let the user save the room if in OBS
}
if (session.cleanViewer){
if (session.view && !session.director && session.permaid===false){
@ -4155,22 +4198,30 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.screenShareLabel = session.screenShareLabel.replace(/_/g, " ")
}
// this is not the same as creating a whep source
if (urlParams.has('whepshare') || urlParams.has('whepsrc')) { // URL or data:base64 image. Becomes local to this viewer only. This is like &avatar, but slightly different. Just CSS in this case
try {
session.whepSrc = urlParams.get('whepshare') || urlParams.get('whepsrc') || false;
console.log(session.whepSrc);
if (!session.whepSrc){
session.whepSrc = await promptAlt("Enter the WHEP source as a URL");
} else {
session.whepSrc = decodeURIComponent(session.whepSrc, true);
}
getById("container-6").classList.remove('hidden');
getById("container-6").classList.add("skip-animation");
getById("container-6").classList.remove('pointer');
session.whepSrc = urlParams.get('whepshare') || urlParams.get('whepsrc') || null;
log("WHEP SRC: "+session.whepSrc);
if (session.whepSrc){
delayedStartupFuncs.push([shareWebsite, session.whepSrc]);
try {
session.whepSrc = decodeURIComponent(session.whepSrc);
} catch(e){
session.whepSrc=null;
}
}
if (!session.whepSrc && session.autostart){
session.whepSrc = await promptAlt("Enter the WHEP source as a URL");
}
if (session.whepSrc){
getById("whepURL").value = session.whepSrc;
}
getById("container-16").classList.remove('hidden');
getById("container-16").classList.add("skip-animation");
getById("container-16").classList.remove('pointer');
if (session.autostart && session.whepSrc){
delayedStartupFuncs.push([session.publishWhepSrc, session.whepSrc]);
}
} catch(e){
errorlog(e);

File diff suppressed because one or more lines are too long

View File

@ -185,6 +185,17 @@
div.urlInput {
padding: 0 0 4vh 0;
}
@media only screen and (max-height: 839px) {
body{
zoom: 0.74;
-moz-transform: scale(0.74);
-moz-transform-origin: 0 0;
}
}
@media only screen and (max-width: 940px) {
body{
zoom: 0.74;
@ -353,7 +364,9 @@
</div>
</div>
<div id="urlInput4" style="display:none;" class="urlInput" title="Put the link you want to load here">
<div id="urlInput4" class="urlInput" title="Put the link you want to load here">
<h3>Host a stream as a WHEP sourceP</h3>
<div class="inputCombo" id="inputCombo4">
<label for="changeText">
@ -365,6 +378,7 @@
</div>
<div id="history" title="History of past links used. You can clear this history using the button to the left">
<h3 style='cursor:pointer;' onclick="resetHistory()">Clear History</h3>
<label for="lastUrls" onclick="resetHistory()">
<i class="las la-history"></i>
</label>