audio/video retry logic

This commit is contained in:
STeve Seguin 2020-12-02 04:15:45 -05:00
parent 1d93b28595
commit 6bc6736cdf
4 changed files with 138 additions and 104 deletions

View File

@ -119,13 +119,22 @@
<i id="mutevideotoggle" class="toggleSize las la-eye my-float"></i>
</div>
<div id="settingsbutton" title="Settings" onclick="toggleSettings()" class="advanced float" style="cursor: pointer;" alt="Toggle the Settings Menu">
<i id="settingstoggle" class="toggleSize las la-cog my-float"></i>
<i id="settingstoggle" class="toggleSize las la-sliders-h my-float"></i>
</div>
<div id="hangupbutton" title="Hangup the Call" onclick="hangup()" class="advanced float" style="cursor: pointer;" alt="Disconnect and End">
<i class="toggleSize my-float las la-phone rotate225" aria-hidden="true"></i>
</div>
</div>
<span
id="reportbutton"
title="Submit any error logs"
onclick="submitDebugLog();"
style="cursor: pointer; visibility: hidden; display:none;"
>
<i style="float: right; bottom: 0px; cursor: pointer; position: fixed; right: 46px; color: #d9e4eb; padding: 2px; margin: 2px 2px 0 0; font-size: 140%;" class="las la-bug" aria-hidden="true"></i>
</span>
<span
id="helpbutton"
title="Show Help Info"
@ -226,7 +235,7 @@
<select id="videoSource" ></select>
<span id="gear_webcam" style="display: inline-block;" onclick="toggle(document.getElementById('videoSettings'));">
&nbsp;&nbsp;
<i class="las la-cog" style="font-size: 140%; vertical-align: middle;" aria-hidden="true"></i>
<i class="las la-sliders-h" style="font-size: 140%; vertical-align: middle;" aria-hidden="true"></i>
</span>
</span>
<br />
@ -303,7 +312,7 @@
</button>
<span id="gear_screen" style="display: inline-block; cursor: pointer;" onclick="toggle(document.getElementById('videoSettings2'));">
&nbsp;&nbsp;
<i class="las la-cog" style="font-size: 170%; vertical-align: middle;" aria-hidden="true"></i>
<i class="las la-sliders-h" style="font-size: 170%; vertical-align: middle;" aria-hidden="true"></i>
</span>
<center>
<span id="videoSettings2" style="margin: auto auto; display: none; background-color: white; vertical-aligh: middle; border: 3px solid #ccc; max-width: 500px; padding: 10px 10px 5px 10px; margin: 10px 0 5px 0;">
@ -635,7 +644,7 @@
</button>
<button data-action-type="advanced-camera-settings" title="Advanced Settings and Remote Control" onclick="directorSendMessage(this);">
<span data-translate="advanced-camera-settings"><i class="las la-cog"></i> Advanced</span>
<span data-translate="advanced-camera-settings"><i class="las la-sliders-h"></i> Advanced</span>
</button>
<button data-action-type="voice-chat" title="Toggle Voice Chat with this Guest" onclick="directorSendMessage(this);">
<span data-translate="voice-chat"><i class="las la-microphone"></i> Voice Chat</span>
@ -675,8 +684,10 @@
<button id="shareScreenGear" style="width: 135px; padding:20px;text-align:center;" onclick="grabScreen()"><b>Share Screen</b><br /><i style="padding:5px; font-size:300%;" class="las la-desktop"></i></button><br />
<button onclick="toggleSettings()" style="width: 135px; background-color:#EFEFEF;padding:10px 12px 12px 2px;margin: 10px 0px 20px 0"><i class="chevron right" style="font-size:150%;top:3px;position:relative;"></i> <b>Close Settings</b></button>
<button id='advancedOptionsCamera' onclick="this.style.display = 'none'; toggle(getById('popupSelector_constraints_camera'),false,false); " style="display:none; background-color:#EFEFEF;padding:10px 12px 12px 2px;margin: 10px 0px 0px 10px"><i class="chevron bottom" style="font-size:150%;top:3px;position:relative;"></i> <b>Advanced Video</b></button>
<button id='advancedOptionsAudio' onclick="this.style.display = 'none'; toggle(getById('popupSelector_constraints_audio'),false,false); " style="display:none; background-color:#EFEFEF;padding:10px 12px 12px 2px;margin: 10px 0px 0px 10px"><i class="chevron bottom" style="font-size:150%;top:3px;position:relative;"></i> <b>Advanced Audio</b></button>
<button id='advancedOptionsCamera' onclick="this.style.display = 'none'; toggle(getById('popupSelector_constraints_video'),false,false); getById('popupSelector_constraints_loading').style.visibility='visible';" style="display:none; background-color:#EFEFEF;padding:10px 12px 12px 2px;margin: 10px 0px 0px 10px"><i class="chevron bottom" style="font-size:150%;top:3px;position:relative;"></i> <b>Advanced Video</b></button>
<button id='advancedOptionsAudio' onclick="this.style.display = 'none'; toggle(getById('popupSelector_constraints_audio'),false,false); getById('popupSelector_constraints_loading').style.visibility='visible';" style="display:none; background-color:#EFEFEF;padding:10px 12px 12px 2px;margin: 10px 0px 0px 10px"><i class="chevron bottom" style="font-size:150%;top:3px;position:relative;"></i> <b>Advanced Audio</b></button>
<span id="popupSelector_constraints_audio" class="popupSelector_constraints" style="display: none;">
@ -684,6 +695,9 @@
<span id="popupSelector_constraints_video" class="popupSelector_constraints" style="display: none;">
</span>
<span id="popupSelector_constraints_loading" style="display: none; visibility:hidden">
<i class='las la-spinner icn-spinner' style='margin:30px;font-size:400%;color:white;'></i>
</span>
</p>
</div>
<nav id="context-menu" class="context-menu">
@ -807,6 +821,10 @@
<script>
if (window.location.hostname.indexOf("www.obs.ninja") == 0) {
window.location = window.location.href.replace("www.obs.ninja","obs.ninja"); // the session.salt is domain specific; let's consolidate www as a result.
}
var session = WebRTC.Media; // session is a required global variable if configuring manually. Run before loading main.js but after webrtc.js.
session.version = "13.5b";
session.streamID = session.generateStreamID(); // randomly generates a streamID for this session. You can set your own programmatically if needed

View File

@ -394,8 +394,23 @@ button.glyphicon-button.active.focus {
}
}
.la-sliders-h {
cursor:pointer;
}
.la-sliders-h {
cursor:pointer;
}
select {
cursor:pointer;
}
input[type='checkbox'] { cursor:pointer; }
input[type='radio'] { cursor:pointer; }
.icn-spinner {
/* animation: spin-animation 3s infinite; */
animation: spin-animation 3s infinite;
display: inline-block;
z-index: 10;
}
@ -818,7 +833,6 @@ label {
/* Add shadows to create the "card" effect */
}
.card {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, .1);
background-color: #ddd;

192
main.js
View File

@ -256,7 +256,10 @@ if (window.obsstudio){
window.onload = function() { // This just keeps people from killing the live stream accidentally. Also give me a headsup that the stream is ending
window.addEventListener("beforeunload", function (e) {
session.ws.close();
try{
session.ws.close();
} catch (e){
}
//setTimeout(function(){session.hangup();},0);
return undefined; // ADDED OCT 29th; get rid of popup. Just close the socket connection if the user is refreshing the page. It's one or the other.
@ -1019,6 +1022,7 @@ if (ln_template){ // checking if manual lanuage override enabled
getById("qos").innerHTML = location.hostname;
getById("logoname").innerHTML = getById("qos").outerHTML;
getById("helpbutton").style.display = "none";
getById("reportbutton").style.display = "none";
}
} else if (location.hostname === "rtc.ninja"){
try{
@ -1029,6 +1033,7 @@ if (ln_template){ // checking if manual lanuage override enabled
getById("logoname").innerHTML = "";
getById("helpbutton").style.display = "none";
getById("helpbutton").style.opacity = 0;
getById("reportbutton").style.display = "none";
getById("mainmenu").style.opacity = 1;
getById("mainmenu").style.margin = "30px 0";
getById("translateButton").style.display = "none";
@ -1079,6 +1084,7 @@ if (ln_template){ // checking if manual lanuage override enabled
getById("qos").innerHTML = location.hostname;
getById("logoname").innerHTML = getById("qos").outerHTML ;
getById("helpbutton").style.display = "none";
getById("reportbutton").style.display = "none";
getById("mainmenu").style.opacity = 1;
}).catch(function(err){
errorlog(err);
@ -1094,6 +1100,7 @@ if (ln_template){ // checking if manual lanuage override enabled
getById("qos").innerHTML = location.hostname;
getById("logoname").innerHTML = getById("qos").outerHTML;
getById("helpbutton").style.display = "none";
getById("reportbutton").style.display = "none";
getById("chatBody").innerHTML = "";
} catch (error){
errorlog(error);
@ -2161,7 +2168,6 @@ function printMyStats(menu){ // see: setupStatsMenu
var scrollLeft = getById("menuStatsBox").scrollLeft;
var scrollTop = getById("menuStatsBox").scrollTop;
log(scrollTop + " " + scrollLeft);
menu.innerHTML="";
session.stats.outbound_connections = Object.keys(session.pcs).length;
@ -2483,7 +2489,6 @@ function toggleSettings(){ // TODO: I need to have this be MUTE, toggle, with vo
enumerateDevices().then(gotDevices2).then(function(){});
getById("popupSelector").style.display="inline-block"
getById("settingstoggle").classList.add("icn-spinner");
getById("settingsbutton").classList.add("float2");
getById("settingsbutton").classList.remove("float");
setTimeout(function(){getById("popupSelector").style.right="0px";},1);
@ -2496,7 +2501,6 @@ function toggleSettings(){ // TODO: I need to have this be MUTE, toggle, with vo
});
getById("popupSelector").style.right="-400px";
getById("settingstoggle").classList.remove("icn-spinner");
getById("settingsbutton").classList.add("float");
getById("settingsbutton").classList.remove("float2");
@ -2792,6 +2796,7 @@ function publishScreen(){
}
getById("controlButtons").style.display="flex";
getById("helpbutton").style.display = "inherit";
getById("reportbutton").style.display = "";
} else {
getById("controlButtons").style.display="none";
}
@ -2855,6 +2860,7 @@ function publishWebcam(btn = false){
}
getById("controlButtons").style.display="flex";
getById("helpbutton").style.display = "inherit";
getById("reportbutton").style.display = "";
} else {
getById("controlButtons").style.display="none";
}
@ -3226,7 +3232,7 @@ function createDirectorCam(vid){
vid.style.height="35px";
vid.style.padding ="0";
getById("press2talk").innerHTML = "<span data-translate='Push-to-Mute'>🔴 Push to Mute</span>";
getById("press2talk").outerHTML += '<button class="grey" style="margin: 10px 15px;" onclick="toggleSettings()"><i class="las la-cog"></i></button>';
getById("press2talk").outerHTML += '<button class="grey" style="margin: 10px 15px;" onclick="toggleSettings()"><i class="las la-sliders-h"></i></button>';
getById("miniPerformer").appendChild(vid);
getById("press2talk").dataset.enabled="true";
session.muted=false;
@ -3997,6 +4003,7 @@ function gotDevices2(deviceInfos){
option.checked = false;
}
option.onchange = function(event){ // make sure to clear 'no audio option' if anything else is selected
log("Audio OPTION HAS CHANGED?");
if (!(CtrlPressed)){
document.querySelectorAll("#audioSource3 input[type='checkbox']").forEach(function(item) {
if (event.currentTarget.value !== item.value){
@ -4035,6 +4042,7 @@ function gotDevices2(deviceInfos){
});
audioInputSelect.onchange = function(){
log("Audio OPTION HAS CHANGED? 2");
activatedPreview=false;
grabAudio("videosource","#audioSource3");
};
@ -4095,8 +4103,10 @@ async function getAudioOnly(selector, trackid=null, override=false){
var audioList = [];
var streams = [];
log("getAudioOnly()");
log(trackid);
for (var i=0; i<audioSelect.length;i++){
if (audioSelect[i].value=="ZZZ"){
log("ZZZ as Audio");
continue;
} else if (trackid==audioSelect[i].value){ // skip already excluded
continue;
@ -4187,6 +4197,62 @@ function applyMirror(mirror, eleName='previewWebcam'){ // true unmirrors as its
}
}
/// Detect system changes; handle change or use for debugging
var audioReconnectTimeout = null;
var videoReconnectTimeout = null;
function reconnectDevices(event){
warnlog("A media device has changed");
errorlog(event);
enumerateDevices().then(gotDevices2).then(function(){
clearTimeout(audioReconnectTimeout);
audioReconnectTimeout = setTimeout(function(){
activatedPreview=false;
grabAudio("videosource","#audioSource3", "default");
},1000);
clearTimeout(videoReconnectTimeout);
videoReconnectTimeout = setTimeout(function(){
activatedPreview=false;
grabVideo(session.quality, "videosource", "select#videoSource3");
},1000);;
var outputSelect = document.querySelector('select#outputSource3');
session.sink = outputSelect.options[outputSelect.selectedIndex].value;
getById("videosource").setSinkId(session.sink).then(() => {
log("New Output Device:"+session.sink);
}).catch(error => {
errorlog(error);
});
for (UUID in session.rpcs){
session.rpcs[UUID].videoElement.setSinkId(session.sink).then(() => {
log("New Output Device for: "+UUID);
}).catch(error => {
errorlog(error);
});
}
});
}
try {
navigator.mediaDevices.ondevicechange = reconnectDevices;
} catch(e){errorlog(e);}
function updateConnectionStatus() {
warnlog("Connection type changed from " + session.stats.network_type + " to " + Connection.effectiveType);
session.stats.network_type = Connection.effectiveType + " / " +Connection.type;
}
try{
var Connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
session.stats.network_type = Connection.effectiveType + " / " +Connection.type;
Connection.addEventListener('change', updateConnectionStatus);
} catch(e){}
async function grabScreen(quality=0, audio=true){
if (!navigator.mediaDevices.getDisplayMedia){
@ -4558,10 +4624,20 @@ async function grabVideo(quality=0, eleName='previewWebcam', selector="select#vi
getById(eleName).controls=true;
}
}
if (getById("popupSelector_constraints")){
getById("popupSelector_constraints").innerHTML = "";
if (getById("popupSelector_constraints_video")){
getById("popupSelector_constraints_video").innerHTML = "";
}
if (getById("popupSelector_constraints_audio")){
getById("popupSelector_constraints_audio").innerHTML = "";
}
if (getById("popupSelector_constraints_loading")){
getById("popupSelector_constraints_loading").style.display="";
}
grabVideoTimer = setTimeout(function(){
if (getById("popupSelector_constraints_loading")){
getById("popupSelector_constraints_loading").style.display="none";
}
if (eleName=="previewWebcam"){
getById(eleName).controls=true;
}
@ -4692,7 +4768,7 @@ async function grabAudio(eleName="previewWebcam", selector="#audioSource", track
for (UUID in session.pcs){
if (session.pcs[UUID].allowAudio==true){ // allow
var sender = session.pcs[UUID].addTrack(track, streams[i]);
//sender.track.onended = tryAgain;
sender.track.onended = tryAgain;
}
}
});
@ -4715,88 +4791,9 @@ async function grabAudio(eleName="previewWebcam", selector="#audioSource", track
}
}
var tryAgainTimer=null;
function tryAgain(event){ // audio or video agnostic track reconnect ------------not actually in use,. maybe out of date
warnlog(event.currentTarget);
if (getById("videosource")==null){ // Don't bother with this if just a preview stream
return;
}
var deviceType = event.currentTarget.kind;
var deviceId= event.currentTarget.id;
if (tryAgainTimer!=null){
clearTimeout(tryAgainTimer);
tryAgainTimer=null;
}
tryAgainTimer = setTimeout(function(){
navigator.mediaDevices.ondevicechange = null; // we only give it 10-seconds to reconnect.
},10000);
navigator.mediaDevices.ondevicechange = function(){
clearTimeout(tryAgainTimer);
tryAgainTimer=null;
navigator.mediaDevices.ondevicechange=null; // clear
if (deviceType=="audio"){
if ((deviceId=="default") && (session.echoCancellation!==false) && (session.autoGainControl!==false) && (session.noiseSuppression!==false)){
var constraint = {audio: true};
} else {
var constraint = {audio: {deviceId: {exact: deviceId}}};
if (session.echoCancellation==false){
constraint.audio.echoCancellation=false;
}
if (session.autoGainControl==false){
constraint.audio.autoGainControl=false;
}
if (session.noiseSuppression==false){
constraint.audio.noiseSuppression=false;
}
}
constraint.video = false;
} else if (deviceType=="video"){
var constraint = {
video: {deviceId: {exact: deviceId}},
audio: false
};
} else {
return; // no idea what this is? fail gently.
}
//warnlog(constraint);
navigator.mediaDevices.getUserMedia(constraint).timeout(3000).then(function (stream){
stream.getTracks().forEach(function(track){
getById("videosource").srcObject.addTrack(track, stream); // add video track to the preview video
session.streamSrc = getById(eleName).srcObject;
toggleMute(true);
for (UUID in session.pcs){
if (session.pcs[UUID].allowAudio==true){ // allow
var senders = session.pcs[UUID].getSenders(); // for any connected peer, update the video they have if connected with a video already.
var added=false;
senders.forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams?
if (sender.track){
if (sender.track.id == track.id){
added=true;
//warnlog(sender.track);
if (sender.track.readyState=="ended"){
sender.replaceTrack(track);
}
}
}
});
if (added==false){
sender = session.pcs[UUID].addTrack(track, stream);
sender.track.onended = tryAgain;
}
}
}
});
}).catch(errorlog); // console error message only
}
log("TRY AGAIN TRIGGERED");
errorlog(event);
}
@ -4963,13 +4960,18 @@ function listAudioSettings(){
getById("popupSelector_constraints_audio").innerHTML = "";
try {
var track0 = session.streamSrc.getAudioTracks();
track0 = track0[0];
if (track0.getCapabilities){
session.cameraConstaints = track0.getCapabilities();
if (track0.length){
track0 = track0[0];
if (track0.getCapabilities){
session.cameraConstaints = track0.getCapabilities();
}
log(session.cameraConstaints);
} else {
warnlog("session.streamSrc contains no audio tracks");
return;
}
log(session.cameraConstaints);
} catch(e){
warnlog("session.streamSrc contains no audio tracks");
errorlog(e);
return;
}
@ -5121,7 +5123,7 @@ function applyAudioHack(track, constraint, value=null){
} else if (value == "false"){
value = false;
}
log("CONTREAIT",constraint);
log(constraint);
var new_constraints = Object.assign(track.getSettings(), {[constraint]:value}, );
new_constraints = {audio: new_constraints, video:false};
log(new_constraints);

File diff suppressed because one or more lines are too long