RTCPeerConnection: addTrack() 方法

addTrack()RTCPeerConnection 接口的一种方法,它将新的媒体轨道添加到将传输给另一个对等方的轨道集中。

注意:将轨道添加到连接将通过触发 negotiationneeded 事件来触发重新协商。有关详细信息,请参阅 开始协商

语法

js
addTrack(track)
addTrack(track, stream1)
addTrack(track, stream1, stream2)
addTrack(track, stream1, stream2, /* …, */ streamN)

参数

track

一个 MediaStreamTrack 对象,表示要添加到对等连接的媒体轨道。

stream1, …, streamN 可选

一个或多个本地 MediaStream 对象,应将轨道添加到这些对象中。

指定的 track 不一定需要已经存在于任何指定的 stream 中。相反,stream 是在连接的接收端将轨道分组在一起的一种方式,确保它们同步。添加到本地端连接中同一流的任何轨道都将在远程端的同一流中。

返回值

用于传输媒体数据的 RTCRtpSender 对象。

注意:每个 RTCRtpSender 都与一个 RTCRtpReceiver 配对,以构成一个 RTCRtpTransceiver。关联的接收器处于静音状态(表示它无法传递数据包),除非远程对等方将一个或多个流添加到接收器中。

异常

InvalidAccessError DOMException

如果指定的轨道(或其所有底层流)已存在于 RTCPeerConnection 中,则会抛出此异常。

InvalidStateError DOMException

如果 RTCPeerConnection 已关闭,则会抛出此异常。

使用说明

将轨道添加到多个流

track 参数之后,您可以选择指定一个或多个 MediaStream 对象来将轨道添加到其中。只有轨道从一个对等方发送到另一个对等方,而不是流。由于流特定于每个对等方,因此指定一个或多个流意味着另一个对等方将在连接的另一端自动创建相应的流(或流),然后自动将接收到的轨道添加到这些流中。

无流轨道

如果没有指定流,则轨道将是无流的。这完全可以接受,尽管远程对等方将负责决定将轨道插入哪个流(如果有)。在构建许多类型的简单应用程序时,这是一种非常常见的 addTrack() 使用方式,在这些应用程序中,只需要一个流。例如,如果您与远程对等方共享的只是带有音频轨道和视频轨道的单个流,则无需处理管理哪个轨道在哪个流中,因此您可以让转接器为您处理它。

以下是一个示例,显示了一个使用 getUserMedia() 从用户的摄像头和麦克风获取流,然后将流中的每个轨道添加到对等连接的函数,而不为每个轨道指定流

js
async function openCall(pc) {
  const gumStream = await navigator.mediaDevices.getUserMedia({
    video: true,
    audio: true,
  });
  for (const track of gumStream.getTracks()) {
    pc.addTrack(track);
  }
}

结果是将一组轨道发送到远程对等方,没有流关联。远程对等方上的 track 事件的处理程序将负责确定将每个轨道添加到哪个流中,即使这意味着将它们都添加到同一个流中。 ontrack 处理程序可能如下所示

js
let inboundStream = null;

pc.ontrack = (ev) => {
  if (ev.streams && ev.streams[0]) {
    videoElem.srcObject = ev.streams[0];
  } else {
    if (!inboundStream) {
      inboundStream = new MediaStream();
      videoElem.srcObject = inboundStream;
    }
    inboundStream.addTrack(ev.track);
  }
};

在这里,track 事件处理程序将轨道添加到事件指定的第一个流中(如果指定了流)。否则,第一次调用 ontrack 时,将创建一个新流并将其附加到视频元素,然后将轨道添加到新流中。从那时起,新轨道将被添加到该流中。

您也可以为接收到的每个轨道创建一个新流

js
pc.ontrack = (ev) => {
  if (ev.streams && ev.streams[0]) {
    videoElem.srcObject = ev.streams[0];
  } else {
    let inboundStream = new MediaStream(ev.track);
    videoElem.srcObject = inboundStream;
  }
};

将轨道与特定流关联

通过指定流并允许 RTCPeerConnection 为您创建流,流的轨道关联将由 WebRTC 基础结构自动管理。这包括对转接器的 direction 的更改,以及使用 removeTrack() 停止的轨道。

例如,考虑以下函数,应用程序可能会使用它来开始通过 RTCPeerConnection 将设备的摄像头和麦克风输入流传输到远程对等方

js
async function openCall(pc) {
  const gumStream = await navigator.mediaDevices.getUserMedia({
    video: true,
    audio: true,
  });
  for (const track of gumStream.getTracks()) {
    pc.addTrack(track, gumStream);
  }
}

然后,远程对等方可能会使用如下所示的 track 事件处理程序

js
pc.ontrack = ({ streams: [stream] }) => (videoElem.srcObject = stream);

这将视频元素的当前流设置为包含已添加到连接的轨道的流。

重用发送器

此方法返回一个新的 RTCRtpSender 或一个现有的实例以供重用。RTCRtpSender 实例仅在满足以下条件时才能重用

如果满足所有这些条件,则发送器将被重用,这会导致对现有的 RTCRtpSender 及其 RTCRtpTransceiver 进行以下更改

  • RTCRtpSendertrack 属性设置为指定的轨道。
  • 发送器的关联流集被设置为传递给此方法的流列表,stream...
  • 关联的 RTCRtpTransceiver 会更新其 currentDirection 属性以指示它正在发送;如果当前值为 "recvonly",则变为 "sendrecv",如果当前值为 "inactive",则变为 "sendonly"

新的发送器

如果没有现有的发送器可以重用,则会创建一个新的发送器。这也会导致必须存在的关联对象的创建。创建新发送器的过程会导致以下更改

  • 使用指定的轨道和流集创建新的 RTCRtpSender
  • 使用新的 MediaStreamTrack 作为其 track 属性创建一个新的 RTCRtpReceiver(而不是在调用 addTrack() 时作为参数指定的轨道)。此轨道的 kind 属性设置为与作为输入参数提供的轨道的 kind 属性匹配。
  • 创建一个新的 RTCRtpTransceiver 并将其与新的发送器和接收器关联。
  • 新 transceiver 的 direction 属性被设置为 "sendrecv"
  • 将新 transceiver 添加到 RTCPeerConnection 的 transceiver 集合中。

示例

此示例来自文章 信令与视频通话 中的代码,以及相应的示例代码。它来自其中的 handleVideoOfferMsg() 方法,该方法在从远程对等体接收到提议消息时被调用。

js
const mediaConstraints = {
  audio: true, // We want an audio track
  video: true, // And we want a video track
};

const desc = new RTCSessionDescription(sdp);

pc.setRemoteDescription(desc)
  .then(() => navigator.mediaDevices.getUserMedia(mediaConstraints))
  .then((stream) => {
    previewElement.srcObject = stream;

    stream.getTracks().forEach((track) => pc.addTrack(track, stream));
  });

此代码采用从远程对等体接收到的 SDP,并构建一个新的 RTCSessionDescription 传递给 setRemoteDescription()。成功后,它会使用 MediaDevices.getUserMedia() 获取对本地网络摄像头和麦克风的访问权限。

如果成功,则将生成的流指定为 <video> 元素的源,该元素由变量 previewElement 引用。

最后一步是开始将本地视频通过对等连接发送给呼叫者。这是通过迭代 MediaStream.getTracks() 返回的列表中的每个轨道并将其与它们所属的 stream 一起传递给 addTrack() 来完成的。

规范

规范
WebRTC:浏览器中的实时通信
# dom-rtcpeerconnection-addtrack

浏览器兼容性

BCD 表格仅在浏览器中加载

另请参阅