我有8条虚拟音频线路。我试图将不同的有声视频播放器映射到不同的虚拟线路上,因为我们用它来进行远程解释。我的问题是当一个新的WebRTC出现时,所有的播放器都默认为最新的sinkID。水槽ID的报告他们没有改变,但所有的音频开始流经最近设置的sinkID。有人知道这是浏览器的限制还是什么吗?
这只发生在我在人们连接时按需创建音频播放器时。如果我在每个人都连接后设置ID,就没有问题。 我甚至尝试重新设置所有的sinkID,使其与每个新的连接相同,但音频仍然全部流经最新的音频播放器的SinkID。
我已经附上了所有东西的视图。在附件中,Dave将连接到SinkID的4号线。这工作得很好。如果另一个译员加入Tagalog,他们也可以听到4号线。问题是,如果有人加入让我们说西班牙语,当我设置播放器的水槽ID在西班牙语(3号线),所有的音频是通过3号线的SinkID听到,即使4号线的人的SinkID的仍然设置为4号线。 我想这可能是Chrome的一个BUG。我使用的是OpenTok(tokbox),一些不需要显示的PHP来管理什么语言显示。我已经附上了管理这一切的iFrame代码。
<!DOCTYPE html>
<html>
<head>
<title>IRIS Remote Player</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<script src="../vendor/jquery/jquery.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<link href="/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<script src="https://static.opentok.com/v2/js/opentok.js"></script>
<link href="https://cdn.jsdelivr.net/gh/gitbrent/[email protected]/css/bootstrap4-toggle.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/gh/gitbrent/[email protected]/js/bootstrap4-toggle.min.js"></script>
<meta name="lastLoaded" content="2020-04-21 11:34:57">
<script> var EnglishOn; </script>
<style>
.slow .toggle-group { transition: left 0.7s; -webkit-transition: left 0.7s; }
.fast .toggle-group { transition: left 0.1s; -webkit-transition: left 0.1s; }
.quick .toggle-group { transition: none; -webkit-transition: none; }
</style>
</head>
<body>
<button type="button" class="btn btn-lg btn-danger form-control" id="connect">Connect Remote Interpreters</button>
<div class="container-fluid">
<div class="row live" style="display:none;background:black;color:white;padding-bottom:5px">
<div class="col-sm-2"><input type="checkbox" checked data-width="100%" id="AllAudio" data-toggle="toggle" data-offstyle="danger" data-onstyle="success" data-on="Live" data-off="Break" data-style="quick"></div>
<div class="col-sm-2"><input type="checkbox" checked data-width="100%" id="Admins" data-toggle="toggle" data-offstyle="danger" data-onstyle="success" data-on="Admins Live" data-off="Admins Off" data-style="quick"></div>
</div>
<div class="row live" style="display:none;background:#fff;color:black">
<div class="col-sm-2" id="PL"><input data-style="quick" type="checkbox" class="Interpretation AudioControl SwitchPL" data-width="100%" id="floorPolish" data-lang="PL" data-toggle="toggle" checked data-offstyle="danger" data-onstyle="success" data-on="Polish" data-off="Floor"><br />Floor<br /><audio autoplay controls muted id="englishPolish" class="englishPL"></audio></div><div class="col-sm-2" id="RU"><input data-style="quick" type="checkbox" class="Interpretation AudioControl SwitchRU" data-width="100%" id="floorRussian" data-lang="RU" data-toggle="toggle" checked data-offstyle="danger" data-onstyle="success" data-on="Russian" data-off="Floor"><br />Floor<br /><audio autoplay controls muted id="englishRussian" class="englishRU"></audio></div><div class="col-sm-2" id="ES"><input data-style="quick" type="checkbox" class="Interpretation AudioControl SwitchES" data-width="100%" id="floorSpanish" data-lang="ES" data-toggle="toggle" checked data-offstyle="danger" data-onstyle="success" data-on="Spanish" data-off="Floor"><br />Floor<br /><audio autoplay controls muted id="englishSpanish" class="englishES"></audio></div><div class="col-sm-2" id="TL"><input data-style="quick" type="checkbox" class="Interpretation AudioControl SwitchTL" data-width="100%" id="floorTagalog" data-lang="TL" data-toggle="toggle" checked data-offstyle="danger" data-onstyle="success" data-on="Tagalog" data-off="Floor"><br />Floor<br /><audio autoplay controls muted id="englishTagalog" class="englishTL"></audio></div><div class="col-sm-2" id="EN"><input type="checkbox" data-style="quick" class="AudioControl" data-width="100%" id="floorEnglish" data-toggle="toggle" data-offstyle="danger" data-onstyle="success" data-on="English" data-off="Floor"><br />Floor<br /><audio autoplay controls muted id="englishEnglish" class="englishEN"></audio></div>
</div>
</div>
<script>
var audioSource;
var vars = {};
var floorSource;
$('#connect').click(function() {
$('input:checkbox#Admins').change(function(){
if ($(this).is(':checked')) {
$('.ADMIN').each(function() {
var adminID = $(this).attr("id");
var thisID = adminID.replace("userID", "");
$("#"+thisID).prop('muted', false);
$('#'+thisID).prop('volume', 1);
});
} else {
$('.ADMIN').each(function() {
var adminID = $(this).attr("id");
var thisID = adminID.replace("userID", "");
$("#"+thisID).prop('muted', true);
$('#'+thisID).prop('volume', 0);
});
}
});
$('input:checkbox.AudioControl').change(function(){
var LanguageCode = $(this).attr("data-lang");
var LanguageName = $(this).attr("data-on");
if ($(this).is(':checked')) {
$("#english"+LanguageName).prop('muted', true);
$('#english'+LanguageName).prop('volume', 0);
$("."+LanguageCode).prop('muted', false);
$('.'+LanguageCode).prop('volume', 1);
setTimeout(function(){
if ($('#Admins').is(':checked')) {} else {
$('.ADMIN').each(function() {
var adminID = $(this).attr("id");
var thisID = adminID.replace("userID", "");
$("#"+thisID).prop('muted', true);
$('#'+thisID).prop('volume', 0);
});
}
}, 200);
} else {
//Activate Floor
$("#english"+LanguageName).prop('muted', false);
$('#english'+LanguageName).prop('volume', 1);
$("."+LanguageCode).prop('muted', true);
$('.'+LanguageCode).prop('volume', 0);
setTimeout(function(){
if ($('#Admins').is(':checked')) {} else {
$('.ADMIN').each(function() {
var adminID = $(this).attr("id");
var thisID = adminID.replace("userID", "");
$("#"+thisID).prop('muted', true);
$('#'+thisID).prop('volume', 0);
});
}
}, 200);
}
});
$('input:checkbox#AllAudio').change(function(){
if ($(this).is(':checked')) {
$('.Interpretation').bootstrapToggle('on')
$('#floorEnglish').bootstrapToggle('off')
} else {
$('.AudioControl').bootstrapToggle('off')
}
});
$("#connect").hide();
$("#subscriber").show();
$(".live").show();
playFloor('Polish','PL');
playFloor('Russian','RU');
playFloor('Spanish','ES');
playFloor('Tagalog','TL');
playFloor('English', 'EN');
function playFloor(language, code) {
const audio = document.getElementById('english'+language);
const constraints = {
audio: {deviceId: floorSource},
video: false
};
function handleSuccess(stream) {
const audioTracks = stream.getAudioTracks();
console.log('Got stream with constraints:', constraints);
console.log('Using audio device: ' + audioTracks[0].label);
stream.oninactive = function() {
console.log('Stream ended');
};
window.stream = stream; // make variable available to browser console
audio.srcObject = stream;
}
function handleError(error) {
alert('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);
}
newSinkID = vars[code];
navigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(handleError);
//const MyEnglishAudio = document.getElementById("englishPlayer");
audio.setSinkId(newSinkID);
console.log("Setting Audio to: "+newSinkID);
if(code == "EN") {
$("#englishEnglish").prop('muted', false);
$('#englishEnglish').prop('volume', 1);
console.log("Setting English Volume on "+floorSource);
}
}
function handleRemoteError(error) {
if (error) {
alert(error);
}
}
var apiKey = 'HIDEEN';
var sessionId = 'HIDDEN';
var token = 'HIDDEN';
socket = io.connect('/', {
secure: true,
'reconnection': true,
'reconnectionDelay': 1000,
'reconnectionAttempts': Infinity
});
socket.on('disconnect', function () {
console.log("Disconnected from the server");
//location.reload(true);
});
socket.on('send', function (msg) {
fromSocket = msg.split(" ");
to_meeting_id = fromSocket[0];
to_lang = fromSocket[1];
action = fromSocket[2];
who = fromSocket[3];
if(to_meeting_id == '17') {
console.log("Action by "+who+": "+action+" for "+to_lang);
var info = msg.split(" ");
console.log("0: "+info[0]+" 1: "+info[1]+" 2: "+info[2]+" 3: "+info[3]+" 4:"+info[4]+" 5: "+info[5]+" 6: "+info[6]+" 7: "+info[7]);
if(action == "english") {
$('#floorEnglish').bootstrapToggle('on');
$('.Switch'+info[7]).bootstrapToggle('off');
useraudio = document.getElementById(who);
MynewSinkID = vars['EN'];
//useraudio.setSinkId(MynewSinkID);
useraudio.setSinkId(MynewSinkID)
.then(() => {
console.log('successfully set the audio output device - Note Unmuting '+who);
$("#"+who).prop('muted', false);
$('#'+who).prop('volume', 1);
$('#'+who).css("background-color", "green");
})
.catch((err) => {
console.error('Failed to set the audio output device ', err);
});
}
if(action == "englishOff") {
$('#floorEnglish').bootstrapToggle('off');
$('.Switch'+info[6]).bootstrapToggle('on');
// Set Sink Audio Back
useraudio = document.getElementById(who);
MynewSinkID = vars[info[6]];
//useraudio.setSinkId(MynewSinkID);
useraudio.setSinkId(MynewSinkID)
.then(() => {
console.log('successfully set the audio output device');
$('#'+who).css("background-color", "white");
})
.catch((err) => {
console.error('Failed to set the audio output device ', err);
});
}
if(action == "privacyOn") {
$('#floor'+info[4]).bootstrapToggle('off');
}
if(action == "privacyOff") {
$('#floor'+info[4]).bootstrapToggle('on');
}
}
});
console.log("Starting Session");
var session = OT.initSession(apiKey, sessionId);
var AllUsers = [];
i = 0;
// Subscribe to a newly created stream
session.on('streamCreated', function streamCreated(event) {
console.log("New stream in the session: " + event.stream.streamId);
var subscriberOptions = {
insertMode: 'append',
insertDefaultUI: false,
subscribeToAudio: true,
showControls: true,
width: 400,
height: 100,
subscribeToVideo: false
};
console.log("STARTING LOG OF EVENT");
console.log(event);
console.log("END LOG OF EVENT");
parms = event.stream.connection.data;
//alert(parms)
newparms = parms.split(",");
ConnectingID = newparms[0];
Channel = newparms[1];
subscriber = session.subscribe(event.stream, subscriberOptions, handleRemoteError);
console.log("Connecting to event stream: "+event.stream);
//console.log("My Lang: "+MyLang+" VS "+Channel);
item = {}
item ["Channel"] = Channel;
item ["RemoteUserID"] = ConnectingID;
item ["RemoteStream"] = event.stream;
// See if we have seen this user before. If so, remove them from the array so we can readd them after with new stream info
jQuery.each(AllUsers, function(i, val) {
if(val.RemoteUserID == ConnectingID) // delete index
{
//AllUsers.splice(i, 1); // Remove previous streams by this user
}
});
AllUsers.push(item);
console.log(AllUsers);
subscriber.on('videoElementCreated', (event) => {
//console.log("Info "+event.target.stream.connection.data);
var connectionData = event.target.stream.connection.data;
var info = connectionData.split(",")
console.log("User ID: "+info[0]);
console.log("Lang: "+info[1]);
$('#userID'+info[0]).remove();
$.ajax({
type: 'POST',
timeout: 10000,
url: '/ajax/who.php',
dataType: 'html',
data: { "user_id": info[0] },
cache: false,
error: function (jqXHR, exception) {
var msg = '';
if (jqXHR.status === 0) {
msg = 'Can not reach network.\n Verify you have internet.';
} else if (jqXHR.status == 403) {
msg = 'Your five digit code did not match. ';
} else if (jqXHR.status == 404) {
msg = 'Requested page not found. [404]';
} else if (jqXHR.status == 500) {
msg = 'Internal Server Error [500].';
} else if (exception === 'parsererror') {
msg = 'Requested JSON parse failed.';
} else if (exception === 'timeout') {
msg = 'Time out error.';
} else if (exception === 'abort') {
msg = 'Ajax request aborted.';
} else {
msg = 'Uncaught Error.\n' + jqXHR.responseText;
}
alert("Local Server Request: "+msg);
},
success: function (data) {
$('#'+info[1]).append(data);
document.getElementById(info[1]).appendChild(event.element);
setTimeout(function(){
if (data.indexOf('ADMIN') > -1) {
if ($('#Admins').is(':checked')) {} else {
$("#"+info[0]).prop('muted', true);
$('#'+info[0]).prop('volume', 0);
}
}
}, 200);
}
});
console.log("Video Element Created");
console.log(event);
dynamicSinkID = vars[info[1]];
//alert(dynamicSinkID);
event.element.setAttribute("class", "InterpreationSource "+info[1]);
event.element.setAttribute("data-lang", info[1]);
event.element.setAttribute("id", info[0]);
event.element.setAttribute("controls", "controls");
event.element.setAttribute("width", "300");
event.element.setAttribute("height", "54");
if (typeof event.element.sinkId !== 'undefined') {
event.element.setSinkId(dynamicSinkID)
.then(() => {
console.log('successfully set the audio output device');
})
.catch((err) => {
console.error('Failed to set the audio output device ', err);
});
} else {
console.warn('device does not support setting the audio output');
}
// FIX FOR CHROME BUG SETTING LAST PLAYER CREATED AS THE SINKID FOR All - FAILED
/*
setTimeout(function(){
$('.InterpreationSource').each(function() {
var playerID = $(this).attr("id");
var lang_code = $(this).attr("data-lang"); // Get language of each player
var FixID = vars[lang_code]; // Get SinkID it's supposed to be
var theFix = document.getElementById(playerID);
console.log("Fixing "+playerID+" with "+FixID);
theFix.setSinkId(FixID)
.then(() => {
console.log('successfully set the audio output device - Note Unmuting '+playerID);
})
.catch((err) => {
console.error('Failed to set the audio output device ', err);
});
});
}, 2000);
*/
// END FIX BUG FOR CHROME
});
});
session.connect(token, function (error) {
if(error) {
alert(error);
}
});
});
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
console.log("enumerateDevices() not supported.");
}
// List cameras and microphones.
navigator.mediaDevices.enumerateDevices()
.then(function(devices) {
devices.forEach(function(device) {
//console.log(device.kind + ": " + device.label +" id = " + device.deviceId);
if(device.kind == "audiooutput" && device.label == "Line 5 (Virtual Audio Cable)") {
vars['EN'] = device.deviceId;
console.log(device.kind + ": " + device.label +" id = " + device.deviceId);
}
if(device.kind == "audiooutput" && device.label == "Line 1 (Virtual Audio Cable)") {
vars['PL'] = device.deviceId;
console.log(device.kind + ": " + device.label +" id = " + device.deviceId);
}
if(device.kind == "audiooutput" && device.label == "Line 2 (Virtual Audio Cable)") {
vars['RU'] = device.deviceId;
console.log(device.kind + ": " + device.label +" id = " + device.deviceId);
}
if(device.kind == "audiooutput" && device.label == "Line 3 (Virtual Audio Cable)") {
vars['ES'] = device.deviceId;
console.log(device.kind + ": " + device.label +" id = " + device.deviceId);
}
if(device.kind == "audiooutput" && device.label == "Line 4 (Virtual Audio Cable)") {
vars['TL'] = device.deviceId;
console.log(device.kind + ": " + device.label +" id = " + device.deviceId);
}
if(device.kind == "audioinput" && device.label == "Microphone Array (Synaptics Audio)") {
floorSource = device.deviceId;
console.log(device.kind + ": " + device.label +" id = " + device.deviceId);
}
});
})
.catch(function(err) {
console.log(err.name + ": " + err.message);
});
</script>
</body>
</html>
WebRTC 在内部混合音轨,所有音轨的音频实际上是以单一音频流的形式传送到 Chrome。对音量等属性的更改,是在混音前送入WebRTC应用的。 所以,很不幸的是,这意味着该混音器的输出只能指向单个音频设备。
上述情况适用于和媒体元素。
然而,在Chrome浏览器中有一个可用的变通方法,它涉及到通过WebAudio进行渲染。 要做到这一点,音频仍然需要从混合流中 "拉 "出来,即使它不一定要去一个特定的设备或静音.然后你可以按照这个例子来克隆远程轨道,并通过WebAudio渲染它们。
所以现在我只需要弄清楚如何做到这一点。
所以我想出了如何解决这个问题。我创建了一个函数,并使用以下代码通过WebAudio循环播放WebRTC音频。
function addNewStream(event, dynamicSinkID, lang) {
var mediaStream = event.element.srcObject;
var remote_stream1 = mediaStream.clone();
test_audio_context1 = new AudioContext();
webaudio_source1 = test_audio_context1.createMediaStreamSource(remote_stream1);
webaudio_ms1 = test_audio_context1.createMediaStreamDestination();
webaudio_source1.connect(webaudio_ms1);
test_output_audio1 = new Audio();
test_output_audio1.srcObject = webaudio_ms1.stream;
test_output_audio1.play();
test_output_audio1.setSinkId(dynamicSinkID);
test_output_audio1.setAttribute("controls", "controls");
document.getElementById(lang).appendChild(test_output_audio1);
test_output_audio1.setAttribute("controls", "controls");
test_output_audio1.setAttribute("width", "300");
test_output_audio1.setAttribute("height", "54");
}