whip page only partially done in this push

This commit is contained in:
steveseguin 2023-07-07 01:26:12 -04:00
parent 7ee9653dfd
commit 5a42948014
12 changed files with 845 additions and 110 deletions

View File

@ -1,14 +1,19 @@
<html>
<head><title>Twitch + Video</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7, maximum-scale=1.0, user-scalable=yes" />
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0,user-scalable=no" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<style>
body{
padding:0;
margin:0;
background-color:#003;
width:100%;
height:100%;
background-color:#000;
width:100vw;
height:100vh;
color:white;
overscroll-behavior: contain;
overflow: hidden;
display:block;
}
iframe {
@ -17,33 +22,92 @@ iframe {
border:0;
margin:0;
padding:0;
position:absolute;
display:block;
}
input{
padding:10px;
width:80%;
font-size:1.2em;
padding:10px 2px;
width:calc(100vw - 60px);
font-size: min(4vw, 20px);
margin:10px 0 30px 0;
z-index: 1000;
color:black;
display:block;
}
#clean{
max-width:100%;
width: 90vw;
max-width: 100%;
display: inline-block;
}
h1{
color: white;
font-family: verdana;
margin: 10px;
margin: 10px 3px 30px 3px;
color:white;
}
button {
font-size: min(10vw, 28px);
color:black;
display:inline-block;
}
#controlbar {
position: absolute;
top: 0;
left: 0;
background-color: #0000;
height: max(7vh, 50px);
width: 100%;
display: none;
justify-content: center;
}
#container2{
background-color:#000;
}
#container1{
transition: all ease 0.4ms;
}
#controlbar button{
vertical-align: middle;
text-align: center;
height: 100%;
background-color: black;
color: white;
box-shadow: inset 0 0 20px 7px #FFF7;
font-size: 1.0em;
border-radius: 19px;
max-width: 20%;
margin: 0;
padding: 0 2px;
}
.pressed {
background-color: #A00!important;
}
.loading {
width: 100%!important; height:100%!important; position: absolute; top: 0; right:unset;left:0;opacity:0%; animation: fadeIn 3s;
}
.fullwindow {
height: calc(100% - max(7vh, 50px)) !important; width:100%!important; position: absolute; top: max(7vh, 50px)!important; right:unset!important;left:0!important;
}
@keyframes fadeIn {
0% { opacity: 0; }
10% { opacity: 0; }
50% { opacity: 0.2; }
100% { opacity: 1; }
}
@media screen and (orientation:portrait) {
#container2{
width:100%;height:100%;display:none;
}
#container1{
width: 50vw;height: 50vh; display:none; float:left; position: fixed; top: 0; right: 0%;
}
iframe{
width:100%;
width: 50vw;height: 50vh; display:none; top: 0; right: 0%; position: absolute;
}
}
@media screen and (orientation:landscape) {
@ -52,27 +116,40 @@ h1{
z-index:5;
}
#container1{
width: 50vw;height: 80vh; display:none; float:left; position: fixed; top: 0; right: -10vw;
}
iframe{
max-width:60vw;
width: 50vw; height:100%; max-height: calc(100vh - 100px); display:none; position: fixed; top: 0; right: -10vw;
}
}
.hide{
width: 1px!important;
height: 1px!important;
opacity: 0.5;
}
</style>
</head>
<body>
<div id="container2"></div>
<div id="container1" ></div>
<div id="clean">
<h1>Use VDO.Ninja and Twitch chat at the same time</h1>
<input placeholder="Enter a VDON stream ID or VDON URL" id="viewlink" type="text" />
<input placeholder="Enter the Twitch channel name" id="twitch" type="text" />
<button onclick="loadIframes()" style="display:block;padding:10px;margin:10px;">START</button>
</div>
<div id="container2" ></div>
<div id="container1" class="loading"></div>
<div id="controlbar" >
<button id="hidepreview">Hide Preview</button>
<button id="mutemic">Mute Mic</button>
<button id="mutevideo">Mute Video</button>
<button id="togglesettings">Settings</button>
<button id="hangup">Hangup</button>
</div>
<div id="clean">
<h1>Use VDO.Ninja and Twitch chat at the same time</h1>
VDO.Ninja Stream ID or URL:
<input placeholder="Enter a VDON stream ID or VDON URL" id="viewlink" type="text" />
Twitch Username or URL:
<input placeholder="Enter the Twitch channel name" id="twitch" type="text" />
<button onclick="loadIframes()" style="background-color: #d1fed1;; padding:10px;margin:10px;">START</button>
<button onclick="clearInput()" style="background-color: #f4cccc;margin:10px 0px 10px 10vh;padding:10px;">CLEAR</button>
<br /><br /><br />
<p>
This app lets you publish video/audio via VDO.Ninja at the same time as viewing your Twitch chat.<br /><br />If you have feature requests or suggestions, please report them at https://discord.vdo.ninja in the #feature-request channel.
</p>
</div>
<script>
window.addEventListener("orientationchange", function() {
@ -119,6 +196,28 @@ if (getStorage("twitchChatLink")){
if (getStorage("vdoNinjaTwitchURL")){
document.getElementById("viewlink").value = getStorage("vdoNinjaTwitchURL");
}
function clearInput(){
var confirmit = confirm("Are you sure you want to clear the input fields and local storage?");
if (confirmit){
removeStorage("twitchChatLink");
removeStorage("vdoNinjaTwitchURL");
document.getElementById("viewlink").value = "";
document.getElementById("twitch").value = "";
}
}
var iframe = null;
function sendSelfCommand(action, value=null){
iframe.contentWindow.postMessage({"target":null, "action":action, "value":value}, '*');
}
var injectCSS = `
#controlButtons{
display:none!important;
}
`;
injectCSS = encodeURIComponent(btoa(injectCSS));
function loadIframes(url=false){
@ -129,25 +228,57 @@ function loadIframes(url=false){
document.getElementById("container1").style.display="inline-block";
document.getElementById("container2").style.display="inline-block";
var path = window.location.host+window.location.pathname.split("/").slice(0,-1).join("/");
path = path.replace("/examples","");
if (roomname.startsWith("https://")){
var room1 = roomname;
} else {
var room1 = "https://"+path+"/?push="+roomname+"&webcam&autostart&vd=front&ad=1&transparent&noheader";
var room1 = "https://"+path+"/?push="+roomname+"&webcam&autostart&vd=front&ad=1&transparent&noheader&fullscreen&cleanish&b64css="+injectCSS;
}
var room2 = twitch.startsWith("https://")
? twitch
: `https://www.twitch.tv/embed/${twitch}/chat?parent=${location.hostname}`;
var room2 = twitch.startsWith("https://") ? twitch : `https://www.twitch.tv/embed/${twitch}/chat?darkpopout&parent=${location.hostname}`;
var iframe = document.createElement("iframe");
iframe = document.createElement("iframe");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
iframe.src = room1;
var iframeContainer = document.createElement("div");
iframeContainer.appendChild(iframe);
document.getElementById("container1").appendChild(iframeContainer);
document.getElementById("container1").appendChild(iframe);
//////////// LISTEN FOR EVENTS
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
/// If you have a routing system setup, you could have just one global listener for all iframes instead.
eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
console.warn(e.data);
if ("action" in e.data){
if (e.data.action === "seeding-started"){
document.getElementById("controlbar").style.display="inline-flex";
document.getElementById("container1").classList.remove("loading");
}
if (e.data.action === "settings-menu-state"){
if (e.data.value==true){
togglesettings.dataset.value = "true";
togglesettings.classList.add("pressed");
document.getElementById("container1").classList.add("fullwindow");
} else {
togglesettings.dataset.value = "false";
togglesettings.classList.remove("pressed");
document.getElementById("container1").classList.remove("fullwindow");
}
}
}
});
setStorage("twitchChatLink", room2);
@ -156,13 +287,69 @@ function loadIframes(url=false){
setTimeout(function(){
var iframe = document.createElement("iframe");
iframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
iframe.src = room2;
var iframeContainer = document.createElement("div");
iframeContainer.appendChild(iframe);
document.getElementById("container2").appendChild(iframeContainer);
},3000);
var iframe2 = document.createElement("iframe");
iframe2.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;";
iframe2.src = room2;
document.getElementById("container2").appendChild(iframe2);
},1000);
}
var hangup = document.getElementById("hangup");
hangup.onclick = function(){
iframe.contentWindow.postMessage({"hangup":true}, '*');
}
var togglesettings = document.getElementById("togglesettings");
togglesettings.onclick = function(){
iframe.contentWindow.postMessage({"toggleSettings":"toggle"}, '*');
}
var mutemic = document.getElementById("mutemic");
mutemic.onclick = function(){
if (this.dataset.value!=="false"){
this.dataset.value = "false";
this.classList.add("pressed");
this.innerText = "Un-Mute Mic";
sendSelfCommand("mic",false);
} else {
this.classList.remove("pressed");
this.innerText = "Mute Mic";
this.dataset.value = "true";
sendSelfCommand("mic",true);
}
}
var mutevideo = document.getElementById("mutevideo");
mutevideo.onclick = function(){
if (this.dataset.value!=="false"){
this.dataset.value = "false";
this.classList.add("pressed");
this.innerText = "Un-Mute camera";
sendSelfCommand("camera",false);
} else {
this.classList.remove("pressed");
this.innerText = "Mute Camera";
this.dataset.value = "true";
sendSelfCommand("camera",true);
}
}
var hidepreview = document.getElementById("hidepreview");
hidepreview.onclick = function(){
if (this.dataset.value!=="false"){
this.dataset.value = "false";
this.classList.add("pressed");
this.innerText = "Show Preview";
document.getElementById("container1").classList.add("hide");
document.getElementById("container2").classList.add("fullwindow");
} else {
this.classList.remove("pressed");
this.innerText = "Hide Preview";
this.dataset.value = "true";
document.getElementById("container1").classList.remove("hide");
document.getElementById("container2").classList.remove("fullwindow");
}
}

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=342" />
<link rel="stylesheet" href="./main.css?ver=344" />
<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=662"></script>
<script type="text/javascript" crossorigin="anonymous" src="./webrtc.js?ver=665"></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">
@ -269,7 +269,6 @@
</span>
</span>
<div id="mainmenu" class="row" style="opacity: 0;">
<div id="container-1" title="Add Group Chat to OBS" alt="Add Group Chat to OBS" tabindex="2" role="button" aria-pressed="false" onkeyup="enterPressedClick(event,this);" class="column columnfade pointer rounded card" style=" overflow-y: auto;">
<h2>
@ -900,8 +899,13 @@
<i style="margin-top:30px;font-size:600%;overflow:hidden;" class="largeDarkIcon las la-heartbeat"></i>
</div>
<div id="container-17" class="column columnfade pointer rounded card hidden" style="overflow: hidden;" onclick="window.location = './whip';">
<h2><span data-translate="publish-via-whip">Publish via WHIP</span></h2>
<i style="margin-top:30px;font-size:600%;overflow:hidden;" class="largeDarkIcon las la-broadcast-tower"></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>
<h2><span data-translate="share-whepsrc">Share via WHEP</span></h2>
<i style="margin-top:30px;font-size:560%;overflow:hidden;" class="largeDarkIcon las la-broadcast-tower"></i>
<div class="container-inner">
<br />
@ -927,10 +931,15 @@
</label>
</div>
</div>
</div>
<p></p>
<div id="unexpectedPushLink" class='hidden' style="width:100%;color: white;display:block;font-size:130%;">
<h2>If this page is unexpected, double check your links.</h2>
<b>?push=xxx</b> links are for sending video, while <b>?view=xxx</b> links are for viewing.
</div>
<div id="info" class="fullcolumn columnfade">
<center>
<div class="infoblob" align="left">
@ -2561,11 +2570,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=833"></script>
<script type="text/javascript" crossorigin="anonymous" id="lib-js" src="./lib.js?ver=838"></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=650"></script>
<script type="text/javascript" crossorigin="anonymous" id="main-js" src="./main.js?ver=655"></script>
</body>
</html>

205
lib.js
View File

@ -4239,8 +4239,8 @@ function updateMixerRun(e=false){ // this is the main auto-mixing code. It's a
session.rpcs[i].mutedStateMixer = true;
}
if (!session.hiddenSceneViewBitrate){
session.rpcs[i].videoElement.classList.add("nogb");
}
session.rpcs[i].videoElement.nogb = 2;
}
} else {
if (session.groupAudio){
session.requestRateLimit(0, i, false);
@ -4445,7 +4445,7 @@ function updateMixerRun(e=false){ // this is the main auto-mixing code. It's a
if (session.scene!==false){
session.requestRateLimit(session.hiddenSceneViewBitrate, i, true); // hidden. I dont want it to be super low, for video quality reasons.
if (!session.hiddenSceneViewBitrate){
session.rpcs[i].videoElement.classList.add("nogb");
session.rpcs[i].videoElement.nogb = 2;
}
} else {
session.requestRateLimit(0, i, true); // w/e This is not in OBS, so we just set it as low as possible. Shoudln't exist really unless loading?
@ -4523,6 +4523,13 @@ function updateMixerRun(e=false){ // this is the main auto-mixing code. It's a
session.requestRateLimit(-1, i);
}
}
if (session.rpcs[i].videoElement.nogb==2){
session.rpcs[i].videoElement.nogb = 1;
session.rpcs[i].videoElement.classList.add("nogb");
} else if (session.rpcs[i].videoElement.nogb==1){
session.rpcs[i].videoElement.nogb = 0;
session.rpcs[i].videoElement.classList.remove("nogb");
}
}
}
}
@ -32538,7 +32545,7 @@ function updateIncomingVideoElement(UUID, video=true, audio=true){
if (session.showControls===null){
setTimeout(function(ele){
if (ele){
ele.controls=true;
ele.controls = true;
}
},500, session.rpcs[UUID].videoElement);
}
@ -32777,6 +32784,54 @@ function addAudioPipeline(UUID, track){ // INBOUND AUDIO EFFECTS ; audio tracks
return track;
}
function processMiniInfoUpdate(miniInfo, UUID){
if ("qlr" in miniInfo){
session.rpcs[UUID].stats.info.quality_limitation_reason = miniInfo.qlr;
}
if ("con" in miniInfo){
session.rpcs[UUID].stats.info.conn_type = miniInfo.con;
}
if ("cpu" in miniInfo){
session.rpcs[UUID].stats.info.cpuLimited = miniInfo.cpu;
if (session.rpcs[UUID].signalMeter){
if (miniInfo.cpu){
session.rpcs[UUID].signalMeter.dataset.cpu = "1";
} else if ("cpu" in miniInfo){
session.rpcs[UUID].signalMeter.dataset.cpu = "0";
}
}
}
if ("hw_enc" in miniInfo){
session.rpcs[UUID].stats.info.hardware_video_encoder = miniInfo.hw_enc;
}
if ("bat" in miniInfo){
if (typeof miniInfo.bat == "number"){
session.rpcs[UUID].stats.info.power_level = miniInfo.bat*100;
} else {
session.rpcs[UUID].stats.info.power_level = null;
}
}
if ("chrg" in miniInfo){
session.rpcs[UUID].stats.info.plugged_in = miniInfo.chrg;
}
if (("out" in miniInfo) && ("c" in miniInfo.out)){
session.rpcs[UUID].stats.info.total_outbound_p2p_connections = miniInfo.out.c;
if (session.showConnections && session.rpcs[UUID].connectionDetails){
session.rpcs[UUID].connectionDetails.innerText = "🔗"+session.rpcs[UUID].stats.info.total_outbound_p2p_connections;
session.rpcs[UUID].connectionDetails.dataset.value = session.rpcs[UUID].stats.info.total_outbound_p2p_connections;
}
}
if (session.rpcs[UUID].batteryMeter){
batteryMeterInfoUpdate(UUID);
}
}
function batteryMeterInfoUpdate(UUID){
if (session.rpcs[UUID].stats.info && (session.rpcs[UUID].stats.info.power_level!==null)){
@ -33583,11 +33638,21 @@ function audioMeterGuest(mediaStreamSource, UUID, trackid){
}
if (session.style==3 || session.meterStyle){ // overrides style
if (session.meterStyle==4){
if (session.rpcs[UUID].videoElement){
session.rpcs[UUID].videoElement.dataset.loudness = total;
if (session.rpcs[UUID].videoElement){
if (total>40){
session.rpcs[UUID].videoElement.dataset.speaking = "2";
} else if (total>10){
session.rpcs[UUID].videoElement.dataset.speaking = "1";
} else {
session.rpcs[UUID].videoElement.dataset.speaking = "0";
}
return; // this is cause we are using the data-loudness
if (session.meterStyle==4){
session.rpcs[UUID].videoElement.dataset.loudness = total;
return; // this is cause we are using the data-loudness
}
} else if (session.meterStyle==4){
return;
}
} else if (session.scene!==false){ // if a scene, cancel
return;
@ -34435,6 +34500,108 @@ function resizeWindow(width, height){
},5000);
}
function configureWhipOutSDP(description){ // THIS IS FOR WHIP-OUTPUT; it has
var configs = false;
if (SafariVersion && (SafariVersion<=13) && (iOS || iPad)){
// skip. Not going to try to tinker with older iOS SDPs
} else if ((session.stereo==3) || (session.stereo==5) || (session.stereo==6) || (session.stereo==1)){ // stereo out
configs = {
'stereo': 1,
'useinbandfec': session.noFEC ? 0 : 1,
'maxptime': session.maxptime,
'minptime': session.minptime,
'ptime': session.ptime,
'dtx': session.dtx // "usedtx", if no loud audio, stops sending audio for 400ms. default.
};
log("stereo enabled");
} else if (iOS || iPad){ // iOS doesn't have multichannel, so why even bother
configs = {
'useinbandfec': session.noFEC ? 0 : 1,
'maxptime': session.maxptime,
'minptime': session.minptime,
'ptime': session.ptime,
'dtx': session.dtx // "usedtx", if no loud audio, stops sending audio for 400ms. default.
};
} else if (session.stereo==4){
configs = {
'stereo': 2,
'useinbandfec': session.noFEC ? 0 : 1,
'maxptime': session.maxptime,
'minptime': session.minptime,
'ptime': session.ptime,
'dtx': session.dtx // "usedtx", if no loud audio, stops sending audio for 400ms. default.
};
log("stereo enabled");
} else {
configs = {
'stereo': 0,
'useinbandfec': session.noFEC ? 0 : 1,
'maxptime': session.maxptime,
'minptime': session.minptime,
'ptime': session.ptime,
'dtx': session.dtx // "usedtx", if no loud audio, stops sending audio for 400ms. default.
};
}
if (session.whipOutAudioBitrate){
if (!configs){
configs = {
'maxaveragebitrate': session.whipOutAudioBitrate * 1024,
'cbr': session.cbr
};
} else{
configs.maxaveragebitrate = session.whipOutAudioBitrate * 1024;
configs.cbr = session.cbr;
}
}
if (configs){
description.sdp = CodecsHandler.setOpusAttributes(description.sdp, configs);
}
if (iOS || iPad){ // solves issues with iOS rotation not being correct
if (session.removeOrientationFlag && description.sdp.includes("a=extmap:3 urn:3gpp:video-orientation\r\n")){
description.sdp = description.sdp.replace('a=extmap:3 urn:3gpp:video-orientation\r\n', '');
}
}
if (typeof session.whipOutCodec === "object"){
session.whipOutCodec.reverse().forEach(codec=>{
description.sdp = CodecsHandler.preferCodec(description.sdp, codec);
if (session.whipOutVideoBitrate){
description.sdp = CodecsHandler.setVideoBitrates(description.sdp , {
min: parseInt(session.whipOutVideoBitrate/10) || 1,
max: session.whipOutVideoBitrate || 1
}, codec);
}
});
} else if (session.whipOutCodec){
description.sdp = CodecsHandler.preferCodec(description.sdp, session.whipOutCodec);
if (session.whipOutVideoBitrate){
description.sdp = CodecsHandler.setVideoBitrates(description.sdp , {
min: parseInt(session.whipOutVideoBitrate/10) || 1,
max: session.whipOutVideoBitrate || 1
}, session.whipOutCodec);
}
} else {
description.sdp = CodecsHandler.preferCodec(description.sdp,"h264"); // default
description.sdp = description.sdp.replace(/42001f/gi,"42e01f"); // openh264 set as default.
description.sdp = description.sdp.replace(/420029/gi,"42e01f");
if (session.whipOutVideoBitrate){
description.sdp = CodecsHandler.setVideoBitrates(description.sdp , {
min: parseInt(session.whipOutVideoBitrate/10) || 1,
max: session.whipOutVideoBitrate || 1
}, "h264");
}
}
return description;
}
function whipOut(){
log("whipOut");
var candidates = [];
@ -34570,15 +34737,16 @@ function whipOut(){
warnlog("ON NEGO NEEDED");
warnlog(event);
try {
session.whipOut.createOffer().then(function(offer){
//offer.sdp = CodecsHandler.setOpusAttributes(offer.sdp, {'stereo': 1});
offer.sdp = CodecsHandler.preferCodec(offer.sdp,"h264");
if (!codec){
offer.sdp = offer.sdp.replace(/42001f/gi,"42e01f");
offer.sdp = offer.sdp.replace(/420029/gi,"42e01f");
session.whipOut.createOffer().then(function(description){
try {
description = configureWhipOutSDP(description)
} catch(e){
errorlog(e);
}
warnlog(offer);
return session.whipOut.setLocalDescription(offer);
warnlog(description);
return session.whipOut.setLocalDescription(description);
}).then(function() {
//log(session.whipOut.localDescription);
var sdp = session.whipOut.localDescription.sdp;
@ -34611,6 +34779,11 @@ function whipOut(){
jsep.sdp = this.responseText;
jsep.type = "answer";
try {
jsep = configureWhipOutSDP(jsep)
} catch(e){
errorlog(e);
}
warnlog("Processing answer:");
warnlog(jsep);

View File

@ -1609,7 +1609,8 @@ body.darktheme{
height:100vh;
}
.mainmenuclass {
display: inherit;
display: inline-block;
width:100%;
}
#welcomeImage{
object-fit:cover;
@ -2870,6 +2871,20 @@ video {
background-repeat: no-repeat;
background-position: center;
background-image: var(--video-background-image);
}
video[data-speaking="0"] {
transition: opacity .25s ease-in-out, background-size 0.5s ease;
}
video[data-speaking="1"] {
background-image: var(--video-background-image-talking, var(--video-background-image));
background-size: var(--video-background-image-size-talking, var(--video-background-image-size));
transition: background-size 0.5s ease;
}
video[data-speaking="2"] {
background-image: var(--video-background-image-screaming, var(--video-background-image));
background-size: var(--video-background-image-size-screaming, var(--video-background-image-size));
transition: background-size 0.5s ease;
}
.nogb { background-image: unset !important }

119
main.js
View File

@ -258,15 +258,72 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
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') || "./media/avatar.webp";
var avatarImg = urlParams.get('avatarimg') || urlParams.get('bgimage') || urlParams.get('bgimg') || "./media/avatar1.png";
if (avatarImg){
try {
avatarImg = decodeURIComponent(avatarImg);
} catch(e){}
try {
avatarImg = 'url("'+avatarImg+'")';
document.documentElement.style.setProperty('--video-background-image', avatarImg);
document.documentElement.style.setProperty('--video-background-image-size', "contain");
let fallbackImage = new Image();
fallbackImage.src = avatarImg;
session.style = -1;
fallbackImage.onload = function(){
document.documentElement.style.setProperty('--video-background-image', 'url("'+avatarImg+'")');
if (session.meterStyle!==5){
document.documentElement.style.setProperty('--video-background-image-size', "contain");
}
}
} catch(e){}
}
}
if (urlParams.has('avatarimg2') || urlParams.has('bgimage2') || urlParams.has('bgimg2')) { // URL or data:base64 image. Becomes local to this viewer only. This is like &avatar, but slightly different. Just CSS in this case
var avatarImg2 = urlParams.get('avatarimg2') || urlParams.get('bgimage2') || urlParams.get('bgimg2') || "./media/avatar2.png";
if (avatarImg2){
try {
avatarImg2 = decodeURIComponent(avatarImg2);
} catch(e){}
try {
let fallbackImage2 = new Image();
fallbackImage2.src = avatarImg2;
fallbackImage2.onload = function(){
document.documentElement.style.setProperty('--video-background-image-talking', 'url("'+avatarImg2+'")');
if (session.meterStyle!==5){
document.documentElement.style.setProperty('--video-background-image-size', "contain");
}
}
session.audioEffects = true;
session.meterStyle = 4;
session.style = -1;
if (session.showControls===null){
session.showControls = false;
}
} catch(e){}
}
}
if (urlParams.has('avatarimg3') || urlParams.has('bgimage3') || urlParams.has('bgimg3')) { // URL or data:base64 image. Becomes local to this viewer only. This is like &avatar, but slightly different. Just CSS in this case
var avatarImg3 = urlParams.get('avatarimg3') || urlParams.get('bgimage3') || urlParams.get('bgimg3') || "./media/avatar3.png";
if (avatarImg3){
try {
avatarImg3 = decodeURIComponent(avatarImg3);
} catch(e){}
try {
let fallbackImage3 = new Image();
fallbackImage3.src = avatarImg3;
fallbackImage3.onload = function(){
document.documentElement.style.setProperty('--video-background-image-screaming', 'url("'+avatarImg3+'")');
if (session.meterStyle!==5){
document.documentElement.style.setProperty('--video-background-image-size', "contain");
}
}
session.audioEffects = true;
session.meterStyle = 4;
session.style = -1;
if (session.showControls===null){
session.showControls = false;
}
} catch(e){}
}
}
@ -1244,7 +1301,9 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if (session.scene!==false){
session.disableWebAudio = true;
session.audioEffects = false;
if (session.audioEffects===null){
session.audioEffects = false;
}
session.audioMeterGuest = false;
}
@ -1653,6 +1712,8 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.stereo = 4;
} else if (session.stereo === "2") {
session.stereo = 2;
} else if (session.stereo === "6") {
session.stereo = 6;
} else if (session.stereo === "in") {
session.stereo = 2;
} else {
@ -2217,7 +2278,9 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if (window.obsstudio) {
session.disableWebAudio = true; // default true; might be useful to disable on slow or old computers?
session.audioMeterGuest = false;
session.audioEffects = false;
if (session.audioEffects===null){
session.audioEffects = false;
}
if (window.obsstudio.pluginVersion){
if (macOS){ // if mac, no fix
//session.obsfix = false;
@ -3046,6 +3109,29 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
}
}
if (urlParams.has('whipoutcodec') || urlParams.has('woc')){
session.whipOutCodec = urlParams.get('whipoutcodec') || urlParams.get('woc') || false;
}
if (session.whipOutCodec){
session.whipOutCodec = session.whipOutCodec.toLowerCase();
if (session.whipOutCodec){
session.whipOutCodec = session.whipOutCodec.split(',');
}
}
if (urlParams.has('whipoutaudiobitrate') || urlParams.has('woab')){
session.whipOutAudioBitrate = urlParams.get('whipoutaudiobitrate') || urlParams.get('woab') || false;
if (session.whipOutAudioBitrate ){
session.whipOutAudioBitrate = parseInt(session.whipOutAudioBitrate );
}
}
if (urlParams.has('whipoutvideobitrate') || urlParams.has('wovb')){
session.whipOutVideoBitrate = urlParams.get('whipoutvideobitrate') || urlParams.get('wovb') || false;
if (session.whipOutVideoBitrate){
session.whipOutVideoBitrate = parseInt(session.whipOutVideoBitrate);
}
}
if (urlParams.has('height') || urlParams.has('h')) {
session.height = urlParams.get('height') || urlParams.get('h');
@ -3519,15 +3605,23 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if (urlParams.has('meter') || urlParams.has('meterstyle')){ // same as also adding &style=3
session.meterStyle = urlParams.get('meter') || urlParams.get('meterstyle') || 1;
session.meterStyle = parseInt(session.meterStyle);
session.style=3;
if (session.meterStyle<4){
session.style=3; // black canvas
} else {
session.style = -1; // no canvas
}
session.audioEffects = true;
}
if (session.meterStyle==5){
document.documentElement.style.setProperty('--video-background-image-size-talking', 'auto 35%');
document.documentElement.style.setProperty('--video-background-image-size-screaming', 'auto 45%');
}
if (urlParams.has('directorchat') || urlParams.has('dc')){
session.directorChat = true;
}
if (urlParams.has('style') || urlParams.has('st')) {
session.style = urlParams.get('style') || urlParams.get('st');
@ -3937,6 +4031,15 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
} else {
getById("head1").innerHTML = '<br /><span style="color:#CCC" data-translate="please-select-which-to-share">- Please select which you wish to share</span>';
}
if (!session.cleanOutput){
try {
if (window.obsstudio){
getById("unexpectedPushLink").classList.remove("hidden");
}
} catch(e){}
}
}
}

BIN
media/avatar1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

BIN
media/avatar2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

BIN
media/avatar3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

View File

@ -3,6 +3,7 @@
<title>Mixer app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<script src="./thirdparty/aes.js"></script>
<script src="./thirdparty/jquery/jquery-3.6.0.js?asdf"></script>
<script src="./thirdparty/jquery/jquery-ui.js"></script>
<link rel="stylesheet" href="./thirdparty/jquery/jquery-ui.css">
@ -14,6 +15,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Sora:wght@200;400;700&display=swap" rel="stylesheet">
<style>
:root{
--aspect-ratio: 1.7777777777;
@ -1015,7 +1017,10 @@
<span >
<input type="checkbox" id="toggleLabel" onclick="updateInviteLinks(event)" /><label>Prompt user for a display name</label><br />
<input type="checkbox" id="toggleBroadcast" onclick="updateInviteLinks(event)"/><label>Guest can see other guests and the active layout <small>(higher CPU for guest)</small></label><br />
<input type="checkbox" id="obfuscateInvites" onclick="updateInviteLinks(event)"/><label>Obfuscate the invite links so they cannot be easily modified by guests</label><br />
</span>
<label>Append additional URL params: </label><input size='50' oninput="updateInviteLinks(event)" style='max-width:50%' type="text" id="additionalParams" placeholder='optional URL params here. eg: &showlabels&ruler' /><br />
<i>You can manually customize the invite link further; see the documentation at <a href="https://docs.vdo.ninja" target="_blank">docs.vdo.ninja</a></i>
<br /><br />
<button class='close-btn'>Close</button>
@ -1069,6 +1074,7 @@
}
}
function toggleChat(){
document.getElementById("chatModule").classList.toggle("fadeout");
document.getElementById("chatModuleButton").classList.toggle("hidden");
@ -1158,7 +1164,9 @@
var updateOnSlotChange = true;
var assignSlotToGuest = true;
var toggleLabel = false;
var toggleBroadcast = true;
var toggleBroadcast = false;
var obfuscateInvites = false;
var additionalParams = "";
var messageList = [];
var password = false;
var syncOBS = false;
@ -1167,9 +1175,6 @@
var currentOBSState = false;
if (urlParams.has('password') || urlParams.has('pass') || urlParams.has('pw') || urlParams.has('p')) {
password = urlParams.get('password') || urlParams.get('pass') || urlParams.get('pw') || urlParams.get('p');
}
var aspectRatio = 16/9.0;
var pixelDensity = 720;
@ -1205,6 +1210,14 @@
}
}
}
if (urlParams.has('password') || urlParams.has('pass') || urlParams.has('pw') || urlParams.has('p')) {
password = urlParams.get('password') || urlParams.get('pass') || urlParams.get('pw') || urlParams.get('p') || "";
password = password.trim();
document.getElementById("savedroompassword").classList.add("hidden");
document.getElementById("roompassword").classList.add("hidden");
}
function randomRoomName(){
document.getElementById("roomname").value = generateString(8);
}
@ -1215,9 +1228,12 @@
startRoom();
}
function startRoom(){
var pid = document.getElementById("roompassword").value.trim();
if (pid){
password = pid;
if (password===false){
var pid = document.getElementById("roompassword").value.trim();
if (pid){
password = pid;
}
}
var rid = document.getElementById("roomname").value.trim();
@ -1258,6 +1274,27 @@
sendChatMessage();
}
}
function generateHash(str, length=false){
var buffer = new TextEncoder("utf-8").encode(str);
return crypto.subtle.digest("SHA-256", buffer).then(
function (hash) {
hash = new Uint8Array(hash);
if (length){
hash = hash.slice(0, parseInt(parseInt(length)/2));
}
hash = toHexString(hash);
return hash;
}
);
};
function toHexString(byteArray){
return Array.prototype.map.call(byteArray, function(byte){
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
}).join('');
}
function sendChatMessage(){ // filtered + visual
var msg = document.getElementById('chatInput').value;
@ -1750,6 +1787,29 @@
getById("toggleBroadcast").checked = true;
}
}
if (savedSession.settings && ("obfuscateInvites" in savedSession.settings)){
obfuscateInvites = savedSession.settings.obfuscateInvites;
if (!obfuscateInvites){
getById("obfuscateInvites").value = "off";
getById("obfuscateInvites").checked = false;
getById("obfuscateInvites").removeAttribute('checked');
} else {
getById("obfuscateInvites").value = "on";
getById("obfuscateInvites").checked = true;
}
}
//
if (savedSession.settings && ("additionalParams" in savedSession.settings)){
additionalParams = savedSession.settings.additionalParams;
if (!additionalParams){
getById("additionalParams").value = "";
} else {
getById("additionalParams").value = additionalParams;
}
}
if (savedSession.settings && ("syncOBS" in savedSession.settings)){
syncOBS = savedSession.settings.syncOBS;
if (!syncOBS){
@ -2386,10 +2446,12 @@
var a = document.createElement("a");
a.innerHTML = "Invite Guest Link 📎";
a.href = "./?room="+roomname+"&broadcast"+additional;
//a.href = "./?room="+roomname+"&broadcast"+additional;
a.target = "_blank";
a.id = "mainInviteLink";
a.onclick = function(evt){copyFunction(this, evt);};
document.getElementById("sources").appendChild(a);
updateInviteLinks();
var a = document.createElement("a");
a.innerHTML = "Scene View Link 📎";
@ -2659,13 +2721,15 @@
document.getElementById("inviteOptions").classList.remove("hidden");
updateInviteLinks();
}
var toggleBroadcast = false;
function updateInviteLinks(event=false){
async function updateInviteLinks(event=false){
var additional = "";
if (password){
additional += "&password="+password;
var hash = password.trim();
hash = encodeURIComponent(hash);
hash = await generateHash(hash + location.hostname, 4);
additional += "&hash="+hash;
}
if (document.getElementById("toggleLabel").checked){
@ -2675,23 +2739,50 @@
toggleLabel= false;
}
if (!document.getElementById("toggleBroadcast").checked){
additional += "&broadcast";
if (document.getElementById("toggleBroadcast").checked){
additional += "&layout"; // do not use &broadcast with &layout, else you will get broken results.
toggleBroadcast = true;
} else {
additional += "&layout"; // do not use &broadcast with &layout, else you will get broken results.
additional += "&broadcast";
toggleBroadcast = false;
}
document.querySelectorAll(".roomname").forEach(ele=>{
ele.innerText = roomname;
});
var inviteURL = "https://"+location.hostname+"/?room="+roomname+additional;
if (document.getElementById("additionalParams").value.trim().length){
additionalParams = document.getElementById("additionalParams").value.trim();
if (!additionalParams.startsWith("&")){
additionalParams = "&"+additionalParams;
}
inviteURL += additionalParams;
} else {
additionalParams = "";
}
if (document.getElementById("obfuscateInvites").checked){
obfuscateInvites = true;
inviteURL = processInvite(inviteURL);
} else {
obfuscateInvites = false;
}
document.getElementById("mainInviteLink").href = inviteURL
document.querySelectorAll(".inviteLink").forEach(ele=>{
if (ele.tagName == "A"){
ele.href = "./?room="+roomname+additional;
ele.href = inviteURL
} else if (document.getElementById("obfuscateInvites").checked){
ele.innerHTML = inviteURL;
} else if (ele.tagName == "I"){
ele.innerHTML = "URL + ?room="+roomname+additional;
ele.innerHTML = "URL + ?room="+roomname+additional+additionalParams;
}
ele.onclick = function(evt){copyFunction(this, evt);};
});
@ -2789,6 +2880,89 @@
}
return 0;
}
function processInvite(input){
if (input.startsWith("https://obs.ninja/")){
input = input.replace('https://vdo.ninja/', '');
} else if (input.startsWith("http://obs.ninja/")){
input = input.replace('http://vdo.ninja/', '');
} else if (input.startsWith("obs.ninja/")){
input = input.replace('vdo.ninja/', '');
} else if (input.startsWith("https://vdo.ninja/")){
input = input.replace('https://vdo.ninja/', 'vdo.ninja/');
} else if (input.startsWith("http://vdo.ninja/")){
input = input.replace('http://vdo.ninja/', 'vdo.ninja/');
}
input = input.replace('&view=', '&v=');
input = input.replace('&view&', '&v&');
input = input.replace('?view&', '?v&');
input = input.replace('?view=', '?v=');
input = input.replace('&videobitrate=', '&vb=');
input = input.replace('?videobitrate=', '?vb=');
input = input.replace('&bitrate=', '&vb=');
input = input.replace('?bitrate=', '?vb=');
input = input.replace('?audiodevice=', '?ad=');
input = input.replace('&audiodevice=', '&ad=');
input = input.replace('?label=', '?l=');
input = input.replace('&label=', '&l=');
input = input.replace('?stereo=', '?s=');
input = input.replace('&stereo=', '&s=');
input = input.replace('&stereo&', '&s&');
input = input.replace('?stereo&', '?s&');
input = input.replace('?webcam&', '?wc&');
input = input.replace('&webcam&', '&wc&');
input = input.replace('?remote=', '?rm=');
input = input.replace('&remote=', '&rm=');
input = input.replace('?password=', '?p=');
input = input.replace('&password=', '&p=');
input = input.replace('&maxvideobitrate=', '&mvb=');
input = input.replace('?maxvideobitrate=', '?mvb=');
input = input.replace('&maxbitrate=', '&mvb=');
input = input.replace('?maxbitrate=', '?mvb=');
input = input.replace('&height=', '&h=');
input = input.replace('?height=', '?h=');
input = input.replace('&width=', '&w=');
input = input.replace('?width=', '?w=');
input = input.replace('&quality=', '&q=');
input = input.replace('?quality=', '?q=');
input = input.replace('&cleanoutput=', '&clean=');
input = input.replace('?cleanoutput=', '?clean=');
input = input.replace('&maxviewers=', '&clean=');
input = input.replace('?maxviewers=', '?clean=');
input = input.replace('&framerate=', '&fr=');
input = input.replace('?framerate=', '?fr=');
input = input.replace('&fps=', '&fr=');
input = input.replace('?fps=', '?fr=');
input = input.replace('&permaid=', '&push=');
input = input.replace('?permaid=', '?push=');
input = input.replace('&roomid=', '&r=');
input = input.replace('?roomid=', '?r=');
input = input.replace('&room=', '&r=');
input = input.replace('?room=', '?r=');
return "https://invite.cam/"+CryptoJS.AES.encrypt(input, atob('T0JTTklOSkFGT1JMSUZF')).toString();
}
function drawLayout(layoutOriginal, sceneName=false, obsSceneName = ""){
@ -3815,6 +3989,8 @@
savedSession.settings.assignSlotToGuest = assignSlotToGuest;
savedSession.settings.toggleLabel = toggleLabel;
savedSession.settings.toggleBroadcast = toggleBroadcast;
savedSession.settings.obfuscateInvites = obfuscateInvites;
savedSession.settings.additionalParams = additionalParams;
savedSession.settings.syncOBS = syncOBS;
savedSession.settings.remoteSyncOBS = remoteSyncOBS;
savedSession.settings.aspectRatio = aspectRatio;

View File

@ -273,7 +273,8 @@
var iframeContainer = document.createElement("span");
iframe.allow = "autoplay";
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.
// I've removed &privacy from the view link, and left it just on the push link. This hopefully solves compatibility issues
var srcString = "./?view=" + streamID + "&cleanoutput&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");

File diff suppressed because one or more lines are too long

101
whip.html
View File

@ -79,7 +79,7 @@
width:100%;
background-color: #101520;
}
input.changeText {
.changeText {
font-size: 1em;
align-self: center;
width: 100%;
@ -96,9 +96,12 @@
border-top-right-radius: 0;
}
input.changeText:focus {
.changeText:focus {
outline: none;
}
select.changetext{
padding: .1em;
}
.container{
font-size: min(14px, 2vh);
@ -300,6 +303,14 @@
h3 {
color: #b0e3ff;
}
.hidden{
display:none;
opacity:0;
visibility:none;
width:0;
height:0
}
</style>
</head>
@ -318,10 +329,57 @@
<input type="text" id="changeText1" class="inputfield changeText" placeholder="WHIP Publishing URL" />
<button onclick="gohere1();" class="gobutton" id="gobutton1">GO</button>
</div>
<div class="inputCombo" style="width:350px;margin:10 0 10 auto;">
<input type="password" id="changeText1a" class="inputfield changeText" placeholder="Authentication Bearer Token (optional)" />
<div class="details">⚙️</div>
</div>
<div >
<div class="inputCombo" style="margin: 10px 0px 10px 10px;">
<input type="password" id="changeText1a" class="inputfield changeText" placeholder="🗝️ Authentication Bearer Token (optional)" />
<div class="details">⚙️</div>
</div>
<div class="inputCombo" id="advanced" style="margin: 10px 0px 10px 10px;">
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="audioBitrateGroupFlag" title="Which audio bitrate target would you prefer?" >
<option value="0" selected>🎙Default Audio Bitrate</option>
<option value="500">🎙32-kbps</option>
<option value="2500">🎙64-kbps</option>
<option value="6000">🎙128-kbps</option>
<option value="20000">🎙256-kbps</option>
</select >
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="audioBitrateGroupFlag" title="Which audio bitrate target would you prefer?" >
<option value="cbr" selected>🎙CBR</option>
<option value="vbr">🎙VBR</option>
</select >
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="audioBitrateGroupFlag" title="Which audio bitrate target would you prefer?" >
<option value="0" selected>🎙Denoise</option>
<option value="500">🎙No Denoise</option>
</select >
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="audioBitrateGroupFlag" title="Which audio bitrate target would you prefer?" >
<option value="0" selected>🎙Auto Gain</option>
<option value="500">🎙No Auto Gain</option>
</select >
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="audioBitrateGroupFlag" title="Which audio bitrate target would you prefer?" >
<option value="0" selected>🎙Stereo</option>
<option value="500">🎙Mono</option>
</select >
</div>
<div class="inputCombo" id="advanced2" style="margin: 10px 0px 10px 10px;">
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="bitrateGroupFlag" title="Which video bitrate target would you prefer?" >
<option value="0" selected>🎦Default Video Bitrate</option>
<option value="500">🎦500-kbps</option>
<option value="2500">🎦2500-kbps</option>
<option value="6000">🎦6000-kbps</option>
<option value="20000">🎦20000-kbps</option>
</select >
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="codecGroupFlag" title="Which video codec would you prefer to be used if available?" >
<option value="default" selected>🎦OpenH264</option>
<option value="vp9">🎦VP9</option>
<option value="h264">🎦H264</option>
<option value="vp8">🎦VP8</option>
<option value="av1">🎦AV1</option>
</select >
</div>
</div>
</div>
<div id="urlInput1" class="urlInput" title="Put the link you want to load here">
@ -395,22 +453,35 @@ document.querySelector("#changeText2").value = localStorage.getItem('changeText2
document.querySelector("#changeText3").value = localStorage.getItem('changeText3') || "";
function gohere1(){
if (document.getElementById('changeText1').value && document.getElementById('changeText1a').value){
if (document.getElementById('changeText1').value){
localStorage.setItem('changeText1', document.getElementById('changeText1').value);
localStorage.setItem('changeText1a', document.getElementById('changeText1a').value);
window.location = domain + "?push&whippush=" + encodeURIComponent(document.getElementById('changeText1').value) + "&whippushtoken=" + document.getElementById('changeText1a').value;
} else if (document.getElementById('changeText1').value){
localStorage.setItem('changeText1', document.getElementById('changeText1').value);
localStorage.setItem('changeText1a', "");
window.location = domain + "?push&whippush=" + encodeURIComponent(document.getElementById('changeText1').value);
localStorage.setItem('changeText1a', document.getElementById('changeText1a').value || "");
localStorage.setItem('bitrateGroupFlag', document.getElementById('bitrateGroupFlag').value);
localStorage.setItem('codecGroupFlag', document.getElementById('codecGroupFlag').value);
var bitrate = "";
if (parseInt(document.getElementById('bitrateGroupFlag').value)){
bitrate = "&whipoutvideobitrate="+document.getElementById('bitrateGroupFlag').value;
}
var codec = "";
if (document.getElementById('codecGroupFlag').value!=="default"){
codec = "&whipoutcodec="+document.getElementById('codecGroupFlag').value;
}
if (document.getElementById('changeText1a').value){
window.location = domain + "?push&whippush=" + encodeURIComponent(document.getElementById('changeText1').value) + "&whippushtoken=" + document.getElementById('changeText1a').value + codec + bitrate;
} else {
window.location = domain + "?push&whippush=" + encodeURIComponent(document.getElementById('changeText1').value) + codec + bitrate;
}
}
}
function gohere1t(){
if (document.getElementById('changeText1t').value){
localStorage.setItem('changeText1t', document.getElementById('changeText1t').value);
window.location = domain + "?push&whippush=https%3A%2F%2Fg.webrtc.live-video.net%3A4443%2Fv2%2Foffer&whippushtoken="+ document.getElementById('changeText1t').value;
window.location = domain + "?whipoutvideobitrate=5800&stereo&push&whippush=https%3A%2F%2Fg.webrtc.live-video.net%3A4443%2Fv2%2Foffer&whippushtoken="+ document.getElementById('changeText1t').value;
}
}