如何在WebRTC的MediaStream中添加Track

如何在WebRTC的MediaStream中添加Track,webrtc,microphone,audiotrack,mediastream,Webrtc,Microphone,Audiotrack,Mediastream,我正在使用webrtc与同行进行通信。我不想在旧生成的流中添加新曲目,因为我不想让用户在音频通信期间切换麦克风。我使用的代码是 让“pc”作为进行音频通信的peerConnection对象&“newStream”是从getUserMedia函数使用新选择的麦克风设备生成的新媒体流 var localStreams = pc.getLocalStreams()[0]; localStreams.removeTrack(localStreams.get

我正在使用webrtc与同行进行通信。我不想在旧生成的流中添加新曲目,因为我不想让用户在音频通信期间切换麦克风。我使用的代码是

让“pc”作为进行音频通信的peerConnection对象&“newStream”是从getUserMedia函数使用新选择的麦克风设备生成的新媒体流

            var localStreams = pc.getLocalStreams()[0];
            localStreams.removeTrack(localStreams.getAudioTracks()[0]);


            var audioTrack = newStream.getAudioTracks()[0];
            localStreams.addTrack(audioTrack);
他们是否认为新添加的磁道可以在不再次提供整个SDP的情况下开始连接到先前连接的另一个对等方

在交换媒体设备的情况下,例如,当对等设备之间已经建立连接时,使用麦克风的优化方式是什么?

更新:底部附近的工作示例

这在很大程度上取决于您目前使用的浏览器,这是由于一个不断发展的规范

在和Firefox中,对等连接现在基本上是基于跟踪的,不依赖于本地流关联。您有
var sender=pc.addTrack(track,stream)
pc.removeTrack(sender)
,甚至还有
sender.replaceTrack(track)
,后者根本不涉及重新协商

在Chrome中,您仍然只有
pc.addStream
pc.removeStream
,从本地流中删除一个曲目会导致停止发送,但将其添加回并不起作用。我很幸运地删除了整个流并将其重新添加到对等连接中,然后进行了重新协商

不幸的是,在这里使用没有帮助,因为
addTrack
很难填充

重新谈判 重新谈判不是重新开始。您所需要的只是:

pc.onnegotiationneeded = e => pc.createOffer()
  .then(offer => pc.setLocalDescription(offer))
  .then(() => signalingChannel.send(JSON.stringify({sdp: pc.localDescription})));
  .catch(failed);
一旦您添加了这个,对等连接将在需要时使用您的信令通道自动重新协商。这甚至取代了对
createOffer
和您现在正在做的朋友的呼叫,这是一个净胜利

有了这个功能,您可以在实时连接期间添加/删除曲目,而且它应该“正常工作”

如果这还不够顺畅,您甚至可以
pc.createDataChannel(“yourOwnSignalingChannel”)

例子 以下是所有这些(在Chrome中使用)的示例:

var-config={iceServers:[{url:stun:stun.l.google.com:19302“}]};
var signalingDelayMs=0;
var dc、sc、pc=新RTPeerConnection(配置),live=false;
pc.onaddstream=e=>v2.srcObject=e.stream;
pc.ondatachannel=e=>dc?scInit(sc=e.channel):dcInit(dc=e.channel);
var streams=[];
var haveGum=navigator.mediaDevices.getUserMedia({fake:true,video:true})
。然后(stream=>streams[1]=stream)
.then(()=>navigator.mediaDevices.getUserMedia({video:true}))
。然后(stream=>v1.srcObject=streams[0]=stream);
pc.oniceConnectionState更改=()=>更新(pc.iceConnectionState);
var谈判;//铬处理
pc.onGotiationNeeded=()=>{
如果(谈判)返回;
谈判=正确;
pc.createOffer().then(d=>pc.setLocalDescription(d))
.then(()=>live&&sc.send(JSON.stringify({sdp:pc.localDescription})))
.渔获物(原木);
};
pc.onSignalingState更改=()=>协商=pc.signalingState!=“稳定”;
函数scInit(){
sc.onmessage=e=>wait(signalingDelayMs)。然后(()=>{
var msg=JSON.parse(e.data);
如果(msg.sdp){
var desc=新的RTCSessionDescription(JSON.parse(e.data).sdp);
如果(描述类型=“报价”){
pc.setRemoteDescription(desc).然后(()=>pc.createAnswer())
。然后(答案=>pc.setLocalDescription(答案))。然后(()=>{
sc.send(JSON.stringify({sdp:pc.localDescription}));
}).渔获物(原木);
}否则{
pc.setRemoteDescription(desc).catch(log);
}
}else if(msg.candidate){
pc.addIceCandidate(新的RTCIceCandidate(msg.candidate)).catch(log);
}
}).渔获物(原木);
}
函数dcInit(){
dc.onopen=()=>{
live=true;更新(“聊天:”;Chat.disabled=false;Chat.select();
};
dc.onmessage=e=>log(e.data);
}
函数createOffer(){
button.disabled=true;
pc.onicecandidate=e=>{
如果(现场){
sc.send(JSON.stringify({“候选者”:e.candidate}));
}如果(!e.候选者){
offer.value=pc.localDescription.sdp;
offer.select();
answer.placeholder=“在此处粘贴答案”;
}
};
dcInit(dc=pc.createDataChannel(“聊天”);
scInit(sc=pc.createDataChannel(“信令”);
};
offer.onkeypress=e=>{
如果(e.keyCode!=13 | | pc.signalingState!=“稳定”)返回;
button.disabled=offer.disabled=true;
var obj={type:“offer”,sdp:offer.value};
pc.setRemoteDescription(新的RTCSessionDescription(obj))
。然后(()=>pc.createAnswer())。然后(d=>pc.setLocalDescription(d))
.渔获物(原木);
pc.onicecandidate=e=>{
如果(如候选人)返回;
如果(!live){
回答:焦点();
answer.value=pc.localDescription.sdp;
回答。选择();
}否则{
sc.send(JSON.stringify({“候选者”:e.candidate}));
}
};
};
answer.onkeypress=e=>{
如果(e.keyCode!=13 | | pc.signingstate!=“拥有本地报价”)返回;
答案:disabled=true;
var obj={type:“answer”,sdp:answer.value};
pc.setRemoteDescription(新的RTCSessionDescription(obj)).catch(日志);
};
chat.onkeypress=e=>{
如果(e.keyCode!=13)返回;
dc.send(chat.value);
日志(“>”+聊天记录值);
chat.value=“”;
};
函数addTrack(){
pc.addStream(streams[0]);
flipButton.disabled=假;
removeAddButton.disabled=false;
}
var翻转=0;
函数翻转(){
pc.getSenders()[0].replaceTrack(流[flipped=1-flipped].getVideoTracks()[0])
.渔获物(原木);
}
函数removeAdd(){
如果(电脑中的“removeTrack”){
pc.removeTrack(pc.getSenders()[0]);
pc.addStream(streams[flipped=1-flipped]);
}否则{
pc.removeStream(流[翻转]);
pc.addStream(streams[flipped=1-flipped]);
}
}
var wait=ms=>newpromise(resolve=>setTimeout(resolve,ms));
var update=msg=>div2.innerHTML=msg;
变量日志