在发送方,我们调用 addTrack() 方法加入媒体轨道,开始 ICE 协商。
在接收方,ICE 完成协商后,连接建立,对等端触发 track 事件,我们从事件中获取对等端传输过来的媒体轨道,此时媒体流的传输通道正常建立,可以像本地媒体流一样操作远程媒体流。
addTrack() 方法包含了一个可选参数 streams,表示媒体轨道所属的媒体流。当在发送端同时传入媒体轨道和媒体流时,媒体轨道并不一定来自该媒体流。而在接收端则会自动创建一个媒体流,并将接收到的媒体轨道加入这个媒体流。
如果没有指定可选参数 streams,传入的轨道即为无流轨道。对等端在 track 事件中接收到媒体轨道后,自主创建 MediaStream,并决定将媒体轨道加入哪个媒体流。对于只共享一个媒体流的简单应用来说,这是一种较为常用的做法,应用不需要关注哪个轨道属于哪个流。
下面是发送端的示例代码,使用 getUserMedia() 方法获取音频和视频流,并调用 addTrack 将音视频轨道加入连接,这里没有为轨道指定媒体流。
//无流轨道发送端
async openCall(pc) {
const gumStream = await navigator.mediaDevices.getUserMedia({video: true, audio: true});
for (const track of gumStream.getTracks()) {
pc.addTrack(track);
}
}
下面是对应的接收端代码,在 track 事件处理函数中创建一个新的 MediaStream,将所有媒体轨道加入到这个媒体流中。
//无流轨道接收端
let inboundStream = null;
pc.ontrack = ev => {
if (!inboundStream) {
inboundStream = new MediaStream();
videoElem.srcObject = inboundStream;
}
inboundStream.addTrack(ev.track);
}
也可以为每个轨道都创建一个新的媒体流。
//无流轨道接收端
pc.ontrack = ev => {
let inboundStream = new MediaStream(ev.track);
videoElem.srcObject = inboundStream;
}
如果指定了 streams 参数,传入的轨道即为有流轨道,此时 WebRTC 将在接收端自动创建媒体流,并管理媒体轨道与媒体流的所属关系。
下面代码调用 addTrack 将音视频轨道加入连接,并为轨道指定了媒体流。
//有流轨道发送端
async openCall(pc) {
const gumStream = await navigator.mediaDevices.getUserMedia({video: true, audio: true});
for (const track of gumStream.getTracks()) {
pc.addTrack(track, gumStream);
}
}
下面是对应的接收端示例代码,从 ev 事件中获取第一个媒体流,并将媒体流绑定到视频元素,该媒体流包含发送端加入的所有媒体轨道。
//有流轨道接收端
pc.ontrack = ev => {
videoElem.srcObject = ev.streams[0];
}