RTCPeerConnection: addTrack() 方法

Baseline 已广泛支持

此特性已相当成熟,可在许多设备和浏览器版本上使用。自 ⁨2020 年 1 月⁩ 起,所有主流浏览器均已支持。

RTCPeerConnection 接口的 addTrack() 方法会向将要传输到另一方的媒体轨道集添加一条新的媒体轨道。

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

语法

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

参数

track

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

stream1, …, streamN 可选

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

指定的 track 不一定必须已经是指定 streams 中的一个。相反,streams 是在连接的接收端将轨道分组的方式,以确保它们的同步。添加到本地连接同一 stream 的任何轨道,在远程端也将位于同一 stream 中。

返回值

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

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

异常

InvalidAccessError DOMException

如果指定的轨道(或其所有底层 stream)已是 RTCPeerConnection 的一部分,则抛出此异常。

InvalidStateError DOMException

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

用法说明

将轨道添加到多个 stream

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

无 stream 的轨道

如果没有指定 stream,则该轨道是无 stream 的。这是完全可以接受的,尽管最终将由远程对等方决定是否将轨道插入到哪个 stream 中。这是使用 addTrack() 构建许多简单应用程序时非常常见的方式,在这种情况下只需要一个 stream。例如,如果您与远程对等方共享的只是一个包含音频轨道和视频轨道的单个 stream,则无需管理哪个轨道在哪个 stream 中,因此可以放心地让 transceiver 为您处理。

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

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

结果是将一组轨道发送到远程对等方,没有 stream 关联。远程对等方上的 track 事件的处理程序将负责确定将每个轨道添加到哪个 stream,即使这意味着将它们都添加到同一个 stream 中。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 事件处理程序会将轨道添加到事件指定的第一个 stream 中(如果指定了 stream)。否则,第一次调用 ontrack 时,会创建一个新的 stream 并将其附加到 video 元素,然后将轨道添加到新 stream 中。从那时起,新的轨道将被添加到该 stream 中。

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

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;
  }
};

将轨道与特定 stream 关联

通过指定 stream 并允许 RTCPeerConnection 为您创建 stream,stream 的轨道关联将由 WebRTC 基础设施为您自动管理。这包括 transceiver 的 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);

这会将 video 元素的当前 stream 设置为包含已添加到连接的轨道的那一个。

重用发送者

此方法返回一个新的 RTCRtpSender 或一个现有实例以供重用。只有满足以下条件的 RTCRtpSender 实例才兼容重用:

  • 没有轨道已与发送者关联。
  • 与发送者关联的 RTCRtpTransceiver 具有一个 RTCRtpReceiver,该接收器的 track 属性指定了一个 MediaStreamTrack,其 kind 与调用 RTCPeerConnection.addTrack() 时指定的 track 参数的 kind 相同。这确保了 transceiver 只处理音频或视频,而不是两者都处理。
  • RTCRtpTransceiver.currentDirection 属性不等于 "stopped"
  • 正在考虑的 RTCRtpSender 以前从未用于发送数据。如果 transceiver 的 currentDirection 曾经是 "sendrecv""sendonly",则无法重用发送者。

如果满足所有这些条件,则会重用发送者,这将导致对现有 RTCRtpSender 及其 RTCRtpTransceiver 发生以下更改:

  • RTCRtpSendertrack 被设置为指定的轨道。
  • 发送者的关联 stream 集被设置为传递给此方法的 stream 列表,stream1, …, streamN
  • 关联的 RTCRtpTransceivercurrentDirection 被更新以指示它正在发送;如果其当前值为 "recvonly",则变为 "sendrecv";如果其当前值为 "inactive",则变为 "sendonly"

新的发送者

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

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

示例

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

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() 来获取对本地摄像头和麦克风的访问权限。

如果成功,则生成的 stream 被分配为 <video> 元素(由变量 previewElement 引用)的源。

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

规范

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

浏览器兼容性

另见