fixed an issue with advanced audio settings; added meter

This commit is contained in:
Steve Seguin 2020-07-27 04:08:41 -04:00 committed by GitHub
parent dd950b1c4e
commit 2a31e5508e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 799 additions and 275 deletions

View File

@ -81,6 +81,20 @@ $("#lightbox-animations").get(0).sheet.deleteRule(0);
}
});
// multiselect dropdowns
$('#audioSource').on('mousedown touchend focusin focusout', function(e) {
var state = $('.multiselect-trigger').data('state') || 0;
if( state == 0 ) {
// open the dropdown
$('.multiselect-trigger').data('state', '1').addClass('open').removeClass('closed');
$('.multiselect-trigger').find('.chevron').removeClass('bottom');
$('.multiselect-trigger').parent().find('.multiselect-contents').show();
$('.multiselect-trigger').parent().find('.multiselect-contents').find('input[type="checkbox"]').parent().show();;
$('.multiselect-trigger').parent().find('.multiselect-contents').find('input[type="checkbox"]').show();;
}
e.preventDefault();
});
// multiselect dropdowns
$('.multiselect-trigger').on('mousedown touchend focusin focusout', function(e) {
@ -88,14 +102,14 @@ $('.multiselect-trigger').on('mousedown touchend focusin focusout', function(e)
if( state == 0 ) {
// open the dropdown
$(this).data('state', '1').addClass('open').removeClass('closed');
$(this).find('.fa').removeClass('fa-chevron-down').addClass('fa-chevron-up');
$(this).find('.chevron').removeClass('bottom');
$(this).parent().find('.multiselect-contents').show();
$(this).parent().find('.multiselect-contents').find('input[type="checkbox"]').parent().show();;
$(this).parent().find('.multiselect-contents').find('input[type="checkbox"]').show();;
} else {
// close the dropdown
$(this).data('state', '0').addClass('closed').removeClass('open');
$(this).find('.fa').removeClass('fa-chevron-up').addClass('fa-chevron-down');
$(this).find('.chevron').addClass('bottom');
//$(this).parent().find('.multiselect-contents').hide();
//$(this).parent().find('.multiselect-contents').find('input[type="checkbox"]').hide();
$(this).parent().find('.multiselect-contents').find('input[type="checkbox"]').not(":checked").parent().hide();;

71
dual.html Normal file
View File

@ -0,0 +1,71 @@
<html>
<head><title>Dual Input</title>
<style>
body{
padding:0;
margin:0;
}
iframe {
border:0;
margin:0;
padding:0;
display:block;
margin:10px;
width:40%;;
height:40%;
}
#viewlink {
width:400px;
}
#container {
display:block;
padding:0px;
}
input{
padding:5px;
margin:5px;
}
button{
padding:5px;
margin:5px;
}
</style>
<script>
function loadIframe(){
var iframe = document.createElement("iframe");
var iframeContainer = document.createElement("div");
iframe.allow="autoplay";
iframe.src = document.getElementById("viewlink").value;
iframeContainer.appendChild(iframe);
document.getElementById("container").appendChild(iframeContainer);
var button = document.createElement("button");
button.innerHTML = "Close";
button.onclick = function(){iframe.contentWindow.postMessage({"close":true}, '*');iframe.parentNode.parentNode.removeChild(iframeContainer);}
iframeContainer.appendChild(button);
var button = document.createElement("button");
button.innerHTML = "Reload";
button.onclick = function(){iframe.contentWindow.postMessage({"reload":true}, '*');}
iframeContainer.appendChild(button);
}
</script>
</head>
<body>
<input placeholder="Enter an OBS.Ninja Room Link" id="viewlink" />
<button onclick="loadIframe();">ADD</button>
<div id="container">
</div>
</body>
</html>

View File

@ -48,8 +48,8 @@
<span itemprop="thumbnail" itemscope itemtype="http://schema.org/ImageObject">
<link itemprop="url" href="./images/obsNinja_logo_full.png" />
</span>
<script language="javascript" type="text/javascript" src="./thirdparty/CodecsHandler.js?ver=7"></script>
<script language="javascript" type="text/javascript" src="./webrtc.js?ver=9"></script>
<script language="javascript" type="text/javascript" src="./thirdparty/CodecsHandler.js?ver=8"></script>
<script language="javascript" type="text/javascript" src="./webrtc.js?ver=11"></script>
<input id="zoomSlider" type="range" style="display: none;" />
<div id="header">
<a id="logoname" href="./" style="text-decoration: none; color: white; margin: 2px;">
@ -183,10 +183,10 @@
<span data-translate="waiting-for-camera">Waiting for Camera to Load</span>
</button>
<br />
<span style="background-color: #f3f3f3; display: inline-block; padding: 5px 10px; border: 1px solid #ccc; vertical-align: middle;">
<span data-translate="video-source">Video source</span>:
<span class="videoMenu">
<i class="las la-video"></i><span data-translate="video-source"> Video Source </span>
<select id="videoSource" style="background-color: #FFF; padding:5px; display: display:inline-block;vertical-align: middle;"></select>
<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: 170%; vertical-align: middle;" aria-hidden="true"></i>
@ -194,7 +194,7 @@
</span>
<br />
<center>
<span id="videoSettings" style="margin: auto auto; display: none; background-color: #f3f3f3; max-width: 500px; padding: 10px 0; margin: 0 0 5px 0;">
<span id="videoSettings" style="display: none;">
<form id="webcamquality">
<input type="radio" id="fullhd" name="resolution" value="0" />
<label for="fullhd">
@ -217,9 +217,10 @@
</center>
<div class="form-group multiselect">
<a class="form-control multiselect-trigger" tabindex="3">
<div id="audioTitle" style="padding: 5px;" class="title">
<span data-translate="select-audio-source">Select Audio Source</span>:
<i class="las la-chevron-down" aria-hidden="true"></i>
<div id="audioTitle" class="title">
<i class="las la-microphone-alt"></i><span data-translate="select-audio-source"> Audio Source(s) </span>
<i class="chevron bottom" aria-hidden="true"></i>
<div class="meter" id="meter1"></div>
</div>
</a>
<ul id="audioSource" class="multiselect-contents">
@ -231,6 +232,14 @@
</li>
</ul>
</div>
<br />
<span id="headphonesDiv" style="text-align:left; margin:17px 0; max-width: 550px; min-width: 420px; background-color: #f3f3f3; display: none; padding: 10px 10px; border: 1px solid #ccc; vertical-align: middle;">
<div id="audioTitle2" class="title">
<i class="las la-headphones"></i><span data-translate="select-output-source"> Audio Output Destination:</span></div>
<select id="outputSource" style="background-color: #FFF; padding:5px; display: display:inline-block;vertical-align: middle;"></select>
</span>
</div>
<div class="outer close">
<div class="inner">
@ -240,6 +249,7 @@
</div>
</div>
</div>
<div id="container-2" class="column columnfade" style="background-color: #ddd; overflow-y: auto;">
<h2 id="add_screen">
<span data-translate="remote-screenshare-obs">Remote Screenshare into OBS</span>
@ -259,7 +269,7 @@
<i class="las la-cog" 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 0 5px 0; margin: 10px 0 5px 0;">
<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;">
<form id="webcamquality2">
<input type="radio" id="fullhd2" name="resolution2" value="0" />
<label for="fullhd">
@ -281,7 +291,7 @@
<br />
</center>
<p>
<span data-translate="audio-sources">Audio Sources</span>:
<span data-translate="audio-sources">Audio Sources</span>
<br />
<select id="audioSourceScreenshare" multiple style="height: 60px; width: 200px; resize: both; overflow: auto; padding: 5px;" onchange="requestAudioStream();">
<option value="screenshare" selected>
@ -292,6 +302,15 @@
</option>
</select>
</p>
<br />
<span id="headphonesDiv2" style="background-color: #f3f3f3; display: none; padding: 5px 10px; border: 1px solid #ccc; vertical-align: middle;">
<i class="las la-headphones"></i><span data-translate="select-output-source"> Audio Output Destination:</span>:<br />
<select id="outputSourceScreenshare" style="background-color: #FFF; padding:5px; display: display:inline-block;vertical-align: middle;" onclick="requestOutputAudioStream();">
<option value="default">
<span data-translate="default">Default Device</span>
</option>
</select>
</span>
</div>
<div class="outer close">
<div class="inner">
@ -436,8 +455,8 @@
</li>
<br />
Site last updated: <a href="https://www.reddit.com/r/OBSNinja/comments/hhba50/version_8_just_released_see_the_change_log_here/">July 10th, 2020</a>. The previous version can be found at
<a href="https://obs.ninja/v7/">https://obs.ninja/v7/</a> if you are having new issues.
Site last updated: July 20th, 2020. The previous version can be found at
<a href="https://obs.ninja/v8/">https://obs.ninja/v8/</a> if you are having new issues.
<br />
@ -470,6 +489,9 @@
<a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a>
</div>
</div>
<div id="gridlayout"></div>
<div id="controls_blank" style="display: none;">
<center>
@ -596,7 +618,7 @@
// 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" id="main-js" src="./main.js?ver=9"></script>
<script type="text/javascript" src="./animations.js"></script>
<script type="text/javascript" id="main-js" src="./main.js?ver=11"></script>
<script type="text/javascript" src="./animations.js?ver=1"></script>
</body>
</html>

123
main.css
View File

@ -5,10 +5,21 @@
border:0;
}
.meter {
display: inline-block;
width: 100px;
height: 10px;
background: green;
transition: all 52ms linear;
}
#mynetwork {
width: 600px;
height: 400px;
border: 1px solid lightgray;
width: 600px;
height: 400px;
border: 1px solid lightgray;
}
.email {
@ -71,6 +82,29 @@ button {
color:black;
}
.chevron{
padding:0px 6px;
}
.chevron::before {
border-style: solid;
border-width: 0.14em 0.14em 0 0;
content: '';
display: inline-block;
height: 0.32em;
left: 0.15em;
position: relative;
top: .43em;
transform: rotate(-45deg);
vertical-align: top;
width: 0.32em;
}
.chevron.bottom:before {
top: .28em;
transform: rotate(135deg);
}
.pressed {
background: #e3e3e3;
@ -196,10 +230,11 @@ body {
.gowebcam {
font-size:110%;
padding:10px;
border:3px solid #DDDDDD;
cursor:pointer;
background-color:#DDDDDD;
margin: 20px;
padding:10px 50px;
}
.gobutton {
@ -243,6 +278,7 @@ body {
}
.gowebcam {
padding:5px;
margin: 5px;
}
.infoblob {
@ -302,11 +338,44 @@ body {
float:none !important;
padding: 15px 10px 1px 10px !important;
}
input[type=radio]{
b
#videoSettings {
max-width: 100% !important;
min-width: 100% !important;
}
.videoMenu {
max-width: 100% !important;
min-width: 100% !important;
}
div.multiselect {
max-width: 100% !important;
min-width: 100% !important;
}
#headphonesDiv {
max-width: 100% !important;
min-width: 100% !important;
overflow: hidden !important;
}
#outputSource {
width: 100% !important;
}
#audioSourceScreenshare {
max-width: 90% !important;
min-width: 90% !important;
overflow: hidden !important;
}
#videoSettings2 {
max-width: 90% !important;
min-width: 90% !important;
overflow: hidden !important;
}
}
@ -519,7 +588,6 @@ img {
display: block !important;
margin: auto auto !important;
position: relative !important;
transform: translate(0, -50%) !important;
top: 50% !important;
}
@ -758,6 +826,15 @@ video {
background-color: #0066aa !important;
}
#audioTitle{
text-align:left;
padding: 7px 10px;
}
#audioTitle2{
text-align:left;
padding: 0px 10px 10px 1px;
}
.multiselect .multiselect-trigger:hover {
cursor: pointer;
cursor: hand;
@ -774,15 +851,40 @@ video {
border-bottom-right-radius: 4px;
}
#videoSettings {
margin: auto auto;
background-color: #f3f3f3;
width: 420px;
padding: 10px 0;
margin: 0 0 5px 0;
border: 1px solid #ccc;
}
#videoSource {
background-color: #FFF;
display: display:inline-block;
vertical-align: middle;
}
.videoMenu{
background-color: #f3f3f3;
width: 420px;
display: inline-block;
padding: 5px 10px;
border: 1px solid #ccc;
vertical-align: middle;
text-align:left;
}
div.multiselect {
background-color:#FFF;
max-width:550px;
width: 420px;
white-space: nowrap;
overflow:hidden;
min-width:100px;
margin:auto auto;
border: 1px solid #ccc;
border-bottom:0;
display: inline-block;
}
.multiselect .multiselect-contents {
@ -859,6 +961,7 @@ input[type=checkbox]
-o-transform: scale(1.3); /* Opera */
transform: scale(1.3);
padding: 5px;
margin: 0 5px;
}
#screenshare {

711
main.js
View File

@ -268,7 +268,6 @@ if (urlParams.has('password')){
if (urlParams.has('stereo') || urlParams.has('s')){ // both peers need this enabled for HD stereo to be on. If just pub, you get no echo/noise cancellation. if just viewer, you get high bitrate mono
log("STEREO ENABLED");
session.stereo = urlParams.get('stereo') || urlParams.get('s');
session.stereo = session.stereo;
if (session.stereo){
session.stereo = session.stereo.toLowerCase();
@ -321,7 +320,6 @@ if (urlParams.has("aec") || urlParams.has("ec")){
}
if (urlParams.has("autogain") || urlParams.has("ag")){
session.autoGainControl = urlParams.get('autogain') || urlParams.get('ag');
@ -375,6 +373,13 @@ if (urlParams.has('audiobitrate') || urlParams.has('ab')){ // both peers need th
if (urlParams.has('streamid') || urlParams.has('view') || urlParams.has('v') || urlParams.has('pull')){ // the streams we want to view; if set, but let blank, we will request no streams to watch.
session.view = urlParams.get('streamid') || urlParams.get('view') || urlParams.get('v') || urlParams.get('pull'); // this value can be comma seperated for multiple streams to pull
getById("headphonesDiv").style.display="inline-block";
getById("headphonesDiv2").style.display="inline-block";
if (session.view.split(",").length>1){
session.view_set = session.view.split(",");
}
}
if (urlParams.has('icefilter')){
@ -396,14 +401,27 @@ if (urlParams.has('obsoff') || urlParams.has('oo')){
session.disableOBS = true;
}
if (urlParams.has('noaudio') || urlParams.has('na')){
log("disable audio playback");
session.audio = false;
if (urlParams.has('novideo') || urlParams.has('nv') || urlParams.has('hidevideo')){
if (session.novideo===""){
session.novideo=[];
} else {
session.novideo = urlParams.get('novideo') || urlParams.get('nv') || urlParams.has('hidevideo');
session.novideo = session.novideo.split(",");
}
log("disable video playback");
log(session.novideo);
}
if (urlParams.has('novideo') || urlParams.has('nv')){
log("disable video playback");
session.video = false;
if (urlParams.has('noaudio') || urlParams.has('na') || urlParams.has('hideaudio')){
if (session.noaudio==""){
session.noaudio=[];
} else {
session.noaudio = urlParams.get('noaudio') || urlParams.get('na') || urlParams.has('hideaudio');
session.noaudio = session.noaudio.split(",");
}
log("disable audio playback");
}
@ -429,6 +447,7 @@ if (urlParams.has('nocursor')){
}
if (urlParams.has('codec')){
log("CODEC CHANGED");
session.codec = urlParams.get('codec');
@ -557,7 +576,14 @@ function changeLg(lang){
if (urlParams.has('videobitrate') || urlParams.has('bitrate') || urlParams.has('vb')){
session.bitrate = urlParams.get('videobitrate') || urlParams.get('bitrate') || urlParams.get('vb');
session.bitrate = parseInt(session.bitrate);
if ((session.view_set) && (session.bitrate.split(",").length>1)){
session.bitrate_set = session.bitrate.split(",");
session.bitrate = parseInt(session.bitrate_set[0]);
} else {
session.bitrate = parseInt(session.bitrate);
}
if (session.bitrate<1){session.bitrate=false;}
log("BITRATE ENABLED");
log(session.bitrate);
@ -734,6 +760,7 @@ session.connect();
var url = window.location.pathname;
var filename = url.substring(url.lastIndexOf('/')+1);
if (filename.split(".").length==1){
if (filename.length<2){
filename=false;
@ -788,6 +815,8 @@ if ( (session.roomid) || (urlParams.has('roomid')) || (urlParams.has('r')) || (u
roomid = roomid.replace(/[\W_]+/g,"_");
session.roomid = roomid;
getById("headphonesDiv2").style.display="inline-block";
getById("headphonesDiv").style.display="inline-block";
getById("info").innerHTML = "";
getById("info").style.color="#CCC";
getById("videoname1").value = roomid;
@ -1100,7 +1129,7 @@ function publishScreen(){
var constraints = window.constraints = {
audio: {echoCancellation: session.echoCancellation, autoGainControl: session.autoGainControl, noiseSuppression: session.noiseSuppression },
video: {width: width, height: height, cursor: "never", mediaSource: "browser"}
video: {width: width, height: height, mediaSource: "screen"}
};
if (!(urlParams.has("denoise"))){
@ -1113,12 +1142,20 @@ function publishScreen(){
constraints.audio.echoCancellation = false; // the defaults for screen publishing should be off.
}
if (session.framerate){
constraints.video.frameRate = session.framerate;
}
var audioSelect = document.querySelector('select#audioSourceScreenshare');
var outputSelect = document.querySelector('select#outputSourceScreenshare');
session.sink = outputSelect.options[outputSelect.selectedIndex].value;
log("Session SInk: "+session.sink);
if (session.sink=="default"){session.sink=false;}
log("*");
session.publishScreen(constraints, title, audioSelect).then((res)=>{
if (res==false){return;} // no screen selected
log("streamID is: "+session.streamID);
@ -1186,6 +1223,99 @@ function publishWebcam(){
}
var audioContext = null;
var meter = null;
var mediaStreamSource = null;
var drawLoopLimiter = null;
function volumeStream(stream) {
log("gostream");
if (meter){
meter.shutdown;
}
if (stream.getAudioTracks().length){
window.AudioContext = window.AudioContext || window.webkitAudioContext;
audioContext = new AudioContext();
mediaStreamSource = audioContext.createMediaStreamSource(stream);
meter = createAudioMeter(audioContext);
mediaStreamSource.connect(meter);
clearInterval(drawLoopLimiter);
drawLoopLimiter = setTimeout(function(){drawLoop();},1)
}
}
function drawLoop( time ) {
log("draw volume");
if (!document.getElementById("meter1")){
return
}
if (meter.clipping){
getById("meter1").style.width = "100px";
getById("meter1").style.background = "red";
} else {
if ((100-meter.volume*100*4)<=1){
getById("meter1").style.width = "100px";
getById("meter1").style.background = "green";
} else if ((100-meter.volume*100*4)<100){
getById("meter1").style.width = (meter.volume*100*4)+"px";
getById("meter1").style.background = "green";
} else {
getById("meter1").style.width = "0px";
}
}
clearInterval(drawLoopLimiter);
drawLoopLimiter = setTimeout(function(){drawLoop();},50)
}
function createAudioMeter(audioContext,clipLevel,averaging,clipLag) {
var processor = audioContext.createScriptProcessor(512);
processor.onaudioprocess = volumeAudioProcess;
processor.clipping = false;
processor.lastClip = 0;
processor.volume = 0;
processor.clipLevel = clipLevel || 0.95;
processor.averaging = averaging || 0.90;
processor.clipLag = clipLag || 750;
processor.connect(audioContext.destination);
processor.checkClipping = function(){
if (!this.clipping)
return false;
if ((this.lastClip + this.clipLag) < window.performance.now())
this.clipping = false;
return this.clipping;
};
processor.shutdown = function(){
this.disconnect();
this.onaudioprocess = null;
};
return processor;
}
function volumeAudioProcess( event ) {
var buf = event.inputBuffer.getChannelData(0);
var bufLength = buf.length;
var sum = 0;
var x;
for (var i=0; i<bufLength; i++) {
x = buf[i];
if (Math.abs(x)>=this.clipLevel) {
this.clipping = true;
this.lastClip = window.performance.now();
} else {
this.clipping = false;
}
sum += x * x ;
}
var rms = Math.pow(sum / bufLength,0.3);
this.volume = Math.max(rms, this.volume*this.averaging);
}
function joinRoom(roomname, maxbitrate=false){
roomname = roomname.replace(/[^0-9a-z]/gi, '');
if (roomname.length){
@ -1309,7 +1439,7 @@ function createRoom(roomname=false){
function toggle(ele, tog=false) {
var x = ele;
if (x.style.display === "none") {
x.style.display = "block";
x.style.display = "inline-block";
} else {
x.style.display = "none";
}
@ -1364,9 +1494,52 @@ function enumerateDevices() {
}
}
function requestOutputAudioStream(){
try {
warnlog("GET USER MEDIA");
return navigator.mediaDevices.getUserMedia({audio:true, video:false }).then(function(stream1){ // Apple needs thi to happen before I can access EnumerateDevices.
log("get media sources; request audio stream");
return enumerateDevices().then(function(deviceInfos){
stream1.getTracks().forEach(function(track) { // We don't want to keep it without audio; so we are going to try to add audio now.
track.stop(); // I need to do this after the enumeration step, else it breaks firefox's labels
});
const audioOutputSelect = document.querySelector('#outputSourceScreenshare');
audioOutputSelect.remove(0);
audioOutputSelect.removeAttribute("onclick");
for (let i = 0; i !== deviceInfos.length; ++i) {
const deviceInfo = deviceInfos[i];
if (deviceInfo==null){continue;}
const option = document.createElement('option');
option.value = deviceInfo.deviceId;
if (deviceInfo.kind === 'audiooutput') {
const option = document.createElement('option');
option.value = deviceInfo.deviceId || "default";
if (option.value == session.sink){
option.selected = true;
}
option.text = deviceInfo.label || `Speaker ${audioOutputSelect.length + 1}`;
audioOutputSelect.appendChild(option);
} else {
log('Some other kind of source/device: ', deviceInfo);
}
}
});
});
} catch (e){
if (window.isSecureContext) {
alert("An error has occured when trying to access the webcam. The reason is not known.");
} else {
alert("Error acessing webcam.\n\nWebsite is loaded in an insecure context.\n\nPlease see: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia");
}
}
}
function requestAudioStream(){
try {
return navigator.mediaDevices.getUserMedia({audio:true, video:false }).then(function(stream1){ // Apple needs thi to happen before I can access EnumerateDevices.
warnlog("GET USER MEDIA");
return navigator.mediaDevices.getUserMedia({audio:true, video:false }).then(function(stream1){ // Apple needs thi to happen before I can access EnumerateDevices.
log("get media sources; request audio stream");
return enumerateDevices().then(function(deviceInfos){
stream1.getTracks().forEach(function(track) { // We don't want to keep it without audio; so we are going to try to add audio now.
@ -1376,16 +1549,7 @@ function requestAudioStream(){
const audioInputSelect = document.querySelector('select#audioSourceScreenshare');
audioInputSelect.remove(1);
audioInputSelect.removeAttribute("onchange");
//var temp = {};
//for (let i = 0; i !== deviceInfos.length; ++i) { // getting rid of duplicates. This is a bit useless; I need to revisit.
// if (deviceInfos[i].kind === 'audioinput') {
// if (deviceInfos[i].deviceId in temp){
// deviceInfos[i] = null;
// } else {
// temp[deviceInfos[i].deviceId]=true;
// }
// }
//}
for (let i = 0; i !== deviceInfos.length; ++i) {
const deviceInfo = deviceInfos[i];
@ -1393,7 +1557,7 @@ function requestAudioStream(){
const option = document.createElement('option');
option.value = deviceInfo.deviceId;
if (deviceInfo.kind === 'audioinput') {
option.text = deviceInfo.label || `microphone ${audioInputSelect.length + 1}`;
option.text = deviceInfo.label || `Microphone ${audioInputSelect.length + 1}`;
audioInputSelect.appendChild(option);
} else {
log('Some other kind of source/device: ', deviceInfo);
@ -1420,25 +1584,16 @@ function gotDevices(deviceInfos) { // https://github.com/webrtc/samples/blob/gh-
try{
const audioInputSelect = document.querySelector('#audioSource');
const videoSelect = document.querySelector('select#videoSource');
const audioOutputSelect = document.querySelector('#outputSource');
const selectors = [ videoSelect];
// Handles being called several times to update labels. Preserve values.
const values = selectors.map(select => select.value);
selectors.forEach(select => {
while (select.firstChild) {
select.removeChild(select.firstChild);
}
});
//var temp = {};
//for (let i = 0; i !== deviceInfos.length; ++i) {
// if (deviceInfos[i].kind === 'audioinput') {
// if (deviceInfos[i].deviceId in temp){
// deviceInfos[i] = null;
// } else {
// temp[deviceInfos[i].deviceId]=true;
// }
// }
//}
var counter = 1;
for (let i = 0; i !== deviceInfos.length; ++i) {
const deviceInfo = deviceInfos[i];
@ -1497,16 +1652,26 @@ function gotDevices(deviceInfos) { // https://github.com/webrtc/samples/blob/gh-
option.value = deviceInfo.deviceId || "default";
option.text = deviceInfo.label || `camera ${videoSelect.length + 1}`;
videoSelect.appendChild(option);
} else if (deviceInfo.kind === 'audiooutput'){
const option = document.createElement('option');
option.value = deviceInfo.deviceId || "default";
if (option.value == session.sink){
option.selected = true;
}
option.text = deviceInfo.label || `Speaker ${outputSelect.length + 1}`;
audioOutputSelect.appendChild(option);
} else {
log('Some other kind of source/device: ', deviceInfo);
}
}
if (audioOutputSelect.childNodes.length==0){
const option = document.createElement('option');
option.value = "default";
option.text = "System Default";
audioOutputSelect.appendChild(option);
}
//var option = document.createElement('option');
//option.text = "Disable Audio";
//option.value = "ZZZ";
//audioInputSelect.appendChild(option); // NO AUDIO OPTION
option = document.createElement('option');
option.text = "Disable Video";
@ -1519,7 +1684,6 @@ function gotDevices(deviceInfos) { // https://github.com/webrtc/samples/blob/gh-
}
});
//audioInputSelect.selectedIndex = 0;
} catch (e){
errorlog(e);
}
@ -1628,12 +1792,8 @@ function getUserMediaVideoParams(resolutionFallbackLevel, isSafariBrowser) {
}
}
function grabVideo(quality=0, audioEnable=false){
if( activatedPreview == true){log("activated preview return 2");return;}
activatedPreview = true;
log("trying with quality:"+quality);
var videoSelect = document.querySelector('select#videoSource');
function changeVideo(deviceID="default", quality=0){
var sq=0;
if (session.quality>2){ // 1080, 720, and 360p
@ -1657,79 +1817,186 @@ function grabVideo(quality=0, audioEnable=false){
quality=1;
}
}
var constraints = {
audio: false,
video: getUserMediaVideoParams(quality, iOS)
};
//enumerateDevices().then(gotDevices).then(function(){
if ((iOS) || (iPad)){
constraints.video.deviceId = deviceID; // iPhone 6s compatible ?
} else {
constraints.video.deviceId = deviceID; // NDI Compatible
}
//}
var audio = false;
var streams = [];
if ((videoSelect.value == "ZZZ") || (audioEnable==true)){ // if there is no video, or if manually set to audio ready, then do this step.
if (session.width){
constraints.video.width = {exact: session.width};
}
if (session.height){
constraints.video.height = {exact: session.height};
}
if (session.framerate){
constraints.video.frameRate = {exact: session.framerate};
} else if (session.maxframerate){
constraints.video.frameRate = {max: session.maxframerate};
}
warnlog(constraints);
navigator.mediaDevices.getUserMedia(constraints).then(function(stream){
var audioSelect = document.querySelector('#audioSource').querySelectorAll("input");
var audioList = [];
for (var i=0; i<audioSelect.length;i++){
if (audioSelect[i].value=="ZZZ"){
continue;
}
if (audioSelect[i].checked){
audioList.push(audioSelect[i]);
log("adding tracks");
session.streamSrc.getVideoTracks().forEach((track) => {
track.stop();
session.streamSrc.removeTrack(track);
});
stream.getVideoTracks().forEach((track) => {
session.streamSrc.addTrack(track);
});
}).catch(function(e){
errorlog(e);
if (e.name === "OverconstrainedError"){
errorlog(e.message);
log("Resolution or framerate didn't work");
} else if (e.name === "NotReadableError"){
if (iOS){
alert("An error occured. Upgrading to at least iOS 13.4 should fix this glitch from happening again");
} else {
alert("Error Listing Media Devices.\n\nThe default Camera may already be in use with another app. Typically webcams can only be accessed by one program at a time.\n\nThe selected device may also not be supported.");
}
return;
} else if (e.name === "NavigatorUserMediaError"){
alert("Unknown error: 'NavigatorUserMediaError'");
return;
} else {
errorlog("An unknown camera error occured");
}
});
}
async function getAudioOnly(){
var audioSelect = document.querySelector('#audioSource').querySelectorAll("input");
var audioList = [];
var streams = [];
for (var i=0; i<audioSelect.length;i++){
if (audioSelect[i].value=="ZZZ"){
continue;
}
if (audioSelect[i].checked){
audioList.push(audioSelect[i]);
}
}
for (var i=0; i<audioList.length;i++){
for (var i=1; i<audioList.length;i++){
if ((audioList[i].value=="default") && session.echoCancellation && session.autoGainControl && session.noiseSuppression){
var constraint = {audio: true};
} else if (session.echoCancellation && session.autoGainControl && session.noiseSuppression){ // Just trying to avoid problems with some systems that don't support these features
var constraint = {audio: {deviceId: {exact: audioList[i].value}}};
} else {
var constraint = {audio: {deviceId: {exact: audioList[i].value}}};
constraint.audio.echoCancellation = session.echoCancellation;
constraint.audio.autoGainControl = session.autoGainControl;
constraint.audio.noiseSuppression = session.noiseSuppression;
navigator.mediaDevices.getUserMedia(constraint).then(function (stream2){
streams.push(stream2);
}).catch(errorlog);
}
if (audioList.length){
audio = {deviceId: {exact: audioList[0].value}};
audio.echoCancellation = session.echoCancellation;
audio.autoGainControl = session.autoGainControl;
audio.noiseSuppression = session.noiseSuppression;
}
}
constraint.video = false;
warnlog(constraint);
var stream = await navigator.mediaDevices.getUserMedia(constraint).then(function (stream2){
log("pushing stream2");
return stream2;
}).catch(errorlog); // More error reporting maybe?
if (stream){
streams.push(stream);
}
}
return streams;
}
function applyMirror(mirror){
if (mirror){
if (session.mirrored && session.flipped){
getById('previewWebcam').style.transform = " scaleX(-1) scaleY(-1)";
} else if (session.mirrored){
getById('previewWebcam').style.transform = "scaleX(-1)";
} else if (session.flipped){
getById('previewWebcam').style.transform = "scaleY(-1) scaleX(1)";
} else {
getById('previewWebcam').style.transform = "scaleX(1)";
}
} else {
if (session.mirrored && session.flipped){
getById('previewWebcam').style.transform = " scaleX(1) scaleY(-1)";
} else if (session.mirrored){
getById('previewWebcam').style.transform = "scaleX(1)";
} else if (session.flipped){
getById('previewWebcam').style.transform = "scaleY(-1) scaleX(-1)";
} else {
getById('previewWebcam').style.transform = "scaleX(-1)";
}
}
}
async function grabVideo(quality=0){
if( activatedPreview == true){log("activated preview return 2");return;}
activatedPreview = true;
try {
log("Resetting Stream");
var oldstream = getById('previewWebcam').srcObject;
if (oldstream){
oldstream.getVideoTracks().forEach(function(track) {
track.stop();
oldstream.removeTrack(track);
});
} else {
getById('previewWebcam').srcObject = new MediaStream();
}
} catch(e){
errorlog(e);
}
var videoSelect = document.querySelector('select#videoSource');
var mirror=false;
if (videoSelect.value == "ZZZ"){ // without video. Nice and quick
var constraints = {
audio: audio,
video: false
};
log(constraints);
navigator.mediaDevices.getUserMedia(constraints).then(function(stream){
log("adding additional audio tracks");
for (var i=0; i<streams.length;i++){
streams[i].getAudioTracks().forEach(function(track){
stream.addTrack(track);
log(track);
});
if (videoSelect.value == "ZZZ"){ // if there is no video, or if manually set to audio ready, then do this step.
// revisit
applyMirror(mirror);
var gowebcam = getById("gowebcam");
gowebcam.disabled =false;
gowebcam.style.backgroundColor = "#3C3";
gowebcam.style.color = "black";
gowebcam.style.fontWeight="bold";
gowebcam.innerHTML = "START";
} else {
var sq=0;
if (session.quality>2){ // 1080, 720, and 360p
sq = 2; // hacking my own code. TODO: ugly, so I need to revisit this.
} else {
sq = session.quality;
}
if (sq!==false){
if (quality>sq){
quality=sq; // override the user's setting
}
streams = null;
getById('previewWebcam').srcObject = stream; // set the preview window and run with it
var gowebcam = getById("gowebcam");
gowebcam.disabled =false;
gowebcam.style.backgroundColor = "#3C3";
gowebcam.style.color = "black";
gowebcam.style.fontWeight="bold";
gowebcam.innerHTML = "PRESS WHEN READY!";
}).catch(function(e){
errorlog(e);
alert("Error: Media stream creation failed.");
});
} else { // with video
}
if (iOS){ // iOS will not work correctly at 1080p; likely a h264 codec issue.
if (quality==0){
quality=1;
}
} else if (iPad){
if (quality==0){
quality=1;
}
}
var constraints = {
audio: audio,
audio: false,
video: getUserMediaVideoParams(quality, iOS)
};
if ((iOS) || (iPad)){
@ -1737,6 +2004,19 @@ function grabVideo(quality=0, audioEnable=false){
} else {
constraints.video.deviceId = videoSelect.value; // NDI Compatible
}
log(videoSelect.options[videoSelect.selectedIndex].text);
if (videoSelect.options[videoSelect.selectedIndex].text.startsWith("OBS-Camera")){
mirror=true;
} else if (videoSelect.options[videoSelect.selectedIndex].text.includes(" back")){
mirror=true;
} else if (videoSelect.options[videoSelect.selectedIndex].text.startsWith("Back Camera")){
mirror=true;
} else {
mirror=false;
}
if (session.width){
constraints.video.width = {exact: session.width};
}
@ -1749,94 +2029,116 @@ function grabVideo(quality=0, audioEnable=false){
constraints.video.frameRate = {max: session.maxframerate};
}
log(constraints);
setTimeout(()=>{
try {
log("Trying Constraints");
var oldstream = getById('previewWebcam').srcObject;
if (oldstream){
oldstream.getTracks().forEach(function(track) {
track.stop();
});
}
} catch(e){
errorlog(e);
}
navigator.mediaDevices.getUserMedia(constraints).then(function(stream){
if (audioEnable == false){
stream.getTracks().forEach(function(track) { // We don't want to keep it without audio; so we are going to try to add audio now.
track.stop();
});
log("GOT IT BUT WITH NO AUDIO");
activatedPreview = false;
grabVideo(quality,true);
} else {
log("adding tracks");
for (var i=0; i<streams.length;i++){
streams[i].getAudioTracks().forEach(function(track){
stream.addTrack(track);
log(track);
});
}
streams = null;
getById('previewWebcam').srcObject = stream; // set the preview window and run with it
var gowebcam = getById("gowebcam");
gowebcam.disabled =false;
gowebcam.style.backgroundColor = "#3C3";
gowebcam.style.color = "black";
gowebcam.style.fontWeight="bold";
gowebcam.innerHTML = "PRESS WHEN READY!";
// Once crbug.com/711524 is fixed, we won't need to wait anymore. This is
// currently needed because capabilities can only be retrieved after the
// device starts streaming. This happens after and asynchronously w.r.t.
// getUserMedia() returns.
setTimeout(function(){dragElement(getById('previewWebcam'));},1000); // focus
log("DONE - found stream");
}
}).catch(function(e){
activatedPreview = false;
errorlog(e);
if (e.name === "OverconstrainedError"){
errorlog(e.message);
log("Resolution or framerate didn't work");
} else if (e.name === "NotReadableError"){
if (iOS){
alert("An error occured. Upgrading to at least iOS 13.4 should fix this glitch from happening again");
} else {
alert("Error Listing Media Devices.\n\nThe default Camera may already be in use with another app. Typically webcams can only be accessed by one program at a time.\n\nThe selected device may also not be supported.");
}
getById('gowebcam').innerHTML="Problem with Camera";
activatedPreview=true;
return;
} else if (e.name === "NavigatorUserMediaError"){
getById('gowebcam').innerHTML="Problem with Camera";
alert("Unknown error: 'NavigatorUserMediaError'");
return;
} else {
errorlog("An unknown camera error occured");
}
if (quality<=9){
grabVideo(quality+1);
} else {
errorlog("********Camera failed to work");
activatedPreview=true;
getById('gowebcam').innerHTML="Problem with Camera";
alert("Camera failed to load. \n\nPlease make sure it is not already in use by another application.\n\nPlease make sure you have accepted the camera permissions.");
}
warnlog(constraints);
navigator.mediaDevices.getUserMedia(constraints).then(function(stream){
log("adding video tracks");
volumeStream(stream);
stream.getVideoTracks().forEach(function(track){
getById('previewWebcam').srcObject.addTrack(track);
});
},1);
applyMirror(mirror);
var gowebcam = getById("gowebcam");
gowebcam.disabled =false;
gowebcam.style.backgroundColor = "#3C3";
gowebcam.style.color = "black";
gowebcam.style.fontWeight="bold";
gowebcam.innerHTML = "START";
// Once crbug.com/711524 is fixed, we won't need to wait anymore. This is
// currently needed because capabilities can only be retrieved after the
// device starts streaming. This happens after and asynchronously w.r.t.
// getUserMedia() returns.
setTimeout(function(){dragElement(getById('previewWebcam'));},1000); // focus
log("DONE - found stream");
}).catch(function(e){
activatedPreview = false;
errorlog(e);
if (e.name === "OverconstrainedError"){
errorlog(e.message);
log("Resolution or framerate didn't work");
} else if (e.name === "NotReadableError"){
if (iOS){
alert("An error occured. Closing existing tabs in Safari may solve this issue.");
} else {
alert("Error Listing Media Devices.\n\nThe default Camera may already be in use with another app. Typically webcams can only be accessed by one program at a time.\n\nThe selected device may also not be supported.");
}
getById('gowebcam').innerHTML="Problem with Camera";
activatedPreview=true;
return;
} else if (e.name === "NavigatorUserMediaError"){
getById('gowebcam').innerHTML="Problem with Camera";
alert("Unknown error: 'NavigatorUserMediaError'");
return;
} else {
errorlog("An unknown camera error occured");
}
if (quality<=9){
grabVideo(quality+1);
} else {
errorlog("********Camera failed to work");
activatedPreview=true;
getById('gowebcam').innerHTML="Problem with Camera";
alert("Camera failed to load. \n\nPlease make sure it is not already in use by another application.\n\nPlease make sure you have accepted the camera permissions.");
}
});
}
}
async function grabAudio(){
if( activatedPreview == true){log("activated preview return 2");return;}
activatedPreview = true;
try {
log("Resetting Audio Streams");
var oldstream = getById('previewWebcam').srcObject;
if (oldstream){
oldstream.getAudioTracks().forEach(function(track) {
track.stop();
oldstream.removeTrack(track);
});
} else { // if no stream exists
getById('previewWebcam').srcObject = new MediaStream();
}
} catch(e){
errorlog(e);
}
var videoSelect = document.querySelector('select#videoSource');
// if there is no video, or if manually set to audio ready, then do this step.
var mirror=false; // revisit
applyMirror(mirror);
var streams = await getAudioOnly();
log("adding additional audio tracks");
for (var i=0; i<streams.length;i++){
streams[i].getAudioTracks().forEach(function(track){
getById('previewWebcam').srcObject.addTrack(track);
log(track);
});
}
log(getById('previewWebcam').srcObject);
if (streams.length){
volumeStream(getById('previewWebcam').srcObject);
}
var gowebcam = getById("gowebcam");
gowebcam.disabled =false;
gowebcam.style.backgroundColor = "#3C3";
gowebcam.style.color = "black";
gowebcam.style.fontWeight="bold";
gowebcam.innerHTML = "START";
}
function enterPressed(event, callback){
// Number 13 is the "Enter" key on the keyboard
@ -1968,10 +2270,16 @@ function dragElement(elmnt) {
}
function setupWebcamSelection(){
function setupWebcamSelection(stream){
log("setup webcam");
try {
return enumerateDevices().then(gotDevices).then(function(){
stream.getTracks().forEach(function(track) { // We don't want to keep it without audio; so we are going to try to add audio now.
track.stop(); // I need to do this after the enumeration step, else it breaks firefox's labels
stream.removeTrack(track);
});
log("enumerated");
if (parseInt(getById("webcamquality").elements.namedItem("resolution").value)==3){
session.maxframerate = 30;
@ -1986,6 +2294,7 @@ function setupWebcamSelection(){
var audioSelect = document.querySelector('#audioSource');
var videoSelect = document.querySelector('select#videoSource');
var outputSelect = document.querySelector('select#outputSource');
audioSelect.onchange = function(){
@ -1997,7 +2306,7 @@ function setupWebcamSelection(){
log("AUDIO source CHANGED");
activatedPreview=false;
grabVideo(parseInt(getById("webcamquality").elements.namedItem("resolution").value));
grabAudio();
};
videoSelect.onchange = function(){
@ -2011,6 +2320,19 @@ function setupWebcamSelection(){
activatedPreview=false;
grabVideo(parseInt(getById("webcamquality").elements.namedItem("resolution").value));
};
outputSelect.onchange = function(){
session.sink = outputSelect.options[outputSelect.selectedIndex].value;
if (session.sink=="default"){session.sink=false;} else {
getById("previewWebcam").setSinkId(session.sink).then(() => {
log("New Output Device:"+session.sink);
}).catch(error => {
errorlog(error);
setTimeout(function(){alert("Failed to change audio output destination.");},1);
});
}
}
getById("webcamquality").onchange = function(){
var gowebcam = getById("gowebcam");
gowebcam.disabled = true;
@ -2030,6 +2352,8 @@ function setupWebcamSelection(){
activatedPreview = false;
grabVideo(parseInt(getById("webcamquality").elements.namedItem("resolution").value));
activatedPreview = false;
grabAudio();
}).catch(e => {errorlog(e);})
} catch (e){errorlog(e);}
@ -2072,6 +2396,7 @@ function previewWebcam(){
log("old stream found");
oldstream.getTracks().forEach(function(track) {
track.stop();
oldstream.removeTrack(track);
log("stopping old track");
});
}
@ -2079,15 +2404,11 @@ function previewWebcam(){
} catch (e){
errorlog(e);
}
try {
navigator.mediaDevices.getUserMedia({audio:true, video:true }).timeout(15000).then(function(stream){ // Apple needs thi to happen before I can access EnumerateDevices.
log("got first stream");
setupWebcamSelection().then(()=>{
log("Got second stream");
stream.getTracks().forEach(function(track) { // We don't want to keep it without audio; so we are going to try to add audio now.
track.stop(); // I need to do this after the enumeration step, else it breaks firefox's labels
});
});
setupWebcamSelection(stream);
}).catch(function(err){
errorlog(err); /* handle the error */
if (err.name == "NotFoundError" || err.name == "DevicesNotFoundError") {
@ -2107,7 +2428,7 @@ function previewWebcam(){
setTimeout(function(){alert(err);},1);
}
errorlog("trying to list webcam again");
setupWebcamSelection();
setupWebcamSelection(stream);
});
} catch (e){
if (window.isSecureContext) {

View File

@ -20,6 +20,7 @@ Copyright (c) 2012-2020 [Muaz Khan](https://github.com/muaz-khan)
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Sourced from: https://cdn.webrtc-experiment.com/CodecsHandler.js
// *FILE HAS BEEN HEAVILY MODIFIED BY STEVE SEGUIN. ALL RIGHTS RESERVED WHERE APPLICABLE *
@ -171,10 +172,12 @@ var CodecsHandler = (function() {
function getVideoBitrates(sdp) {
var defaultBitrate = 2500;
var sdpLines = sdp.split('\r\n');
var mLineIndex = findLine(sdpLines, 'm=', 'video');
if (mLineIndex === null) {
return 2500;
return defaultBitrate;
}
var videoMLine = sdpLines[mLineIndex];
var pattern = new RegExp('m=video\\s\\d+\\s[A-Z/]+\\s');
@ -189,7 +192,7 @@ var CodecsHandler = (function() {
}
if (!codecPayload) {
return 2500;
return defaultBitrate;
}
var rtxIndex = findLine(sdpLines, 'a=rtpmap', 'rtx/90000');
@ -199,7 +202,7 @@ var CodecsHandler = (function() {
}
if (!rtxIndex) {
return 2500;
return defaultBitrate;
}
var rtxFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + rtxPayload.toString());
@ -208,7 +211,7 @@ var CodecsHandler = (function() {
var maxBitrate = parseInt(sdpLines[rtxFmtpLineIndex].split("x-google-max-bitrate=")[1].split(";")[0]);
var minBitrate = parseInt(sdpLines[rtxFmtpLineIndex].split("x-google-min-bitrate=")[1].split(";")[0]);
} catch(e){
return 2500;
return defaultBitrate;
}
if (minBitrate>maxBitrate){
@ -217,7 +220,7 @@ var CodecsHandler = (function() {
if (maxBitrate<1){maxBitrate=1;}
return maxBitrate
} else {
return 2500;
return defaultBitrate;
}
@ -275,7 +278,7 @@ var CodecsHandler = (function() {
var rtxFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + rtxPayload.toString());
if (rtxFmtpLineIndex !== null) {
var appendrtxNext = '\r\n';
appendrtxNext += 'a=fmtp:' + codecPayload + ' x-google-min-bitrate=' + (xgoogle_min_bitrate || '228') + '; x-google-max-bitrate=' + (xgoogle_max_bitrate || '228');
appendrtxNext += 'a=fmtp:' + codecPayload + ' x-google-min-bitrate=' + (xgoogle_min_bitrate || '2500') + '; x-google-max-bitrate=' + (xgoogle_max_bitrate || '2500');
sdpLines[rtxFmtpLineIndex] = sdpLines[rtxFmtpLineIndex].concat(appendrtxNext);
sdp = sdpLines.join('\r\n');
}
@ -283,12 +286,11 @@ var CodecsHandler = (function() {
return sdp;
}
function setOpusAttributes(sdp, params) {
function setOpusAttributes(sdp, params) {
params = params || {};
var sdpLines = sdp.split('\r\n');
// Opus
var opusIndex = findLine(sdpLines, 'a=rtpmap', 'opus/48000');
var opusPayload;
if (opusIndex) {
@ -305,65 +307,58 @@ var CodecsHandler = (function() {
}
var appendOpusNext = '';
appendOpusNext += '; stereo=' + (typeof params.stereo != 'undefined' ? params.stereo : '1');
appendOpusNext += '; sprop-stereo=' + (typeof params['sprop-stereo'] != 'undefined' ? params['sprop-stereo'] : '1');
// Please see https://tools.ietf.org/html/rfc7587 for more details on OPUS settings
if (typeof params.maxptime != 'undefined') { // max packet size in milliseconds
appendOpusNext += ';maxptime:' + params.maxptime; // 3, 5, 10, 20, 40, 60 and the default is 120. (20 is minimum recommended for webrtc)
}
if (typeof params.ptime != 'undefined') { // packet size; webrtc doesn't support less than 10 or 20 I think.
appendOpusNext += ';ptime:' + params.ptime;
}
if (typeof params.stereo != 'undefined'){
if (params.stereo==0){
appendOpusNext += ';stereo=0;sprop-stereo=0'; // defaults to 0
} else if (params.stereo==1){
appendOpusNext += ';stereo=1;sprop-stereo=1'; // defaults to 0
} else if (params.stereo==2){
sdpLines[opusIndex] = sdpLines[opusIndex].replace("opus/48000/2", "multiopus/48000/6");
appendOpusNext += ';channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2'; // Multi-channel 5.1 audio
}
}
if (typeof params.maxaveragebitrate != 'undefined') {
appendOpusNext += '; maxaveragebitrate=' + (params.maxaveragebitrate || 128 * 1024 * 8);
appendOpusNext += ';maxaveragebitrate=' + params.maxaveragebitrate; // default 2500 (kbps)
}
if (typeof params.maxplaybackrate != 'undefined') {
appendOpusNext += '; maxplaybackrate=' + (params.maxplaybackrate || 128 * 1024 * 8);
appendOpusNext += ';maxplaybackrate=' + params.maxplaybackrate; // Default should be 48000 (hz) , 8000 to 48000 are valid options
}
if (typeof params.cbr != 'undefined') {
appendOpusNext += '; cbr=' + (typeof params.cbr != 'undefined' ? params.cbr : '1');
appendOpusNext += ';cbr=' + params.cbr; // default is 0 (vbr)
}
if (typeof params.useinbandfec != 'undefined') {
appendOpusNext += '; useinbandfec=' + params.useinbandfec;
//if (typeof params.useinbandfec != 'undefined') { // useful for handling packet loss
// appendOpusNext += '; useinbandfec=' + params.useinbandfec; // Defaults to 0
//}
if (typeof params.usedtx != 'undefined') { // Default is 0
appendOpusNext += ';usedtx=' + params.usedtx; // if decoder prefers the use of DTX.
}
if (typeof params.usedtx != 'undefined') {
appendOpusNext += '; usedtx=' + params.usedtx;
}
if (typeof params.maxptime != 'undefined') {
appendOpusNext += '\r\na=maxptime:' + params.maxptime;
}
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].concat(appendOpusNext);
sdp = sdpLines.join('\r\n');
return sdp;
}
// forceStereoAudio => via webrtcexample.com
// requires getUserMedia => echoCancellation:false
function forceStereoAudio(sdp) {
var sdpLines = sdp.split('\r\n');
var fmtpLineIndex = null;
for (var i = 0; i < sdpLines.length; i++) {
if (sdpLines[i].search('opus/48000') !== -1) {
var opusPayload = extractSdp(sdpLines[i], /:(\d+) opus\/48000/i);
break;
}
}
for (var i = 0; i < sdpLines.length; i++) {
if (sdpLines[i].search('a=fmtp') !== -1) {
var payload = extractSdp(sdpLines[i], /a=fmtp:(\d+)/);
if (payload === opusPayload) {
fmtpLineIndex = i;
break;
}
}
}
if (fmtpLineIndex === null) return sdp;
sdpLines[fmtpLineIndex] = sdpLines[fmtpLineIndex].concat('; stereo=1; sprop-stereo=1');
sdp = sdpLines.join('\r\n');
return sdp;
}
return {
disableNACK: disableNACK,
@ -378,9 +373,7 @@ var CodecsHandler = (function() {
return setOpusAttributes(sdp, params);
},
preferCodec: preferCodec,
forceStereoAudio: forceStereoAudio
preferCodec: preferCodec
};
})();

File diff suppressed because one or more lines are too long