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

瀏覽器相容性

另見