1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
|
---
title: RTCPeerConnection.addTrack()
slug: Web/API/RTCPeerConnection/addTrack
translation_of: Web/API/RTCPeerConnection/addTrack
---
<div>{{APIRef("WebRTC")}}</div>
<p><span class="seoSummary">{{domxref("RTCPeerConnection")}} 对象的</span><strong><code>addTrack()</code></strong>方法将一个新的媒体音轨添加到一组音轨中,这些音轨将被传输给另一个对等点。</p>
<div class="note">
<p>注意:通过触发一个{{event("negotiationneeded")}}事件,向连接添加一个跟踪将触发重新协商。详情请参见{{SectionOnPage("/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling", "Starting negotiation")}}。</p>
</div>
<h2 id="语法">语法</h2>
<pre class="syntaxbox notranslate"><em>rtpSender</em> = <em>rtcPeerConnection</em>.addTrack(<em>track</em>, <em>stream...</em>);</pre>
<h3 id="参数">参数</h3>
<dl>
<dt><code>track</code></dt>
<dd>一个{{domxref("MediaStreamTrack")}}对象,表示要添加到对等连接的媒体轨道。</dd>
<dt><code>stream...</code> {{optional_inline}}</dt>
<dd>一个或多个本地的{{domxref("MediaStream")}}对象,该轨迹应添加到其中。</dd>
</dl>
<p>指定的<code><strong>track</strong></code>不一定已经是任何指定<code><strong>streams</strong></code>的一部分。相反,<code><strong>streams</strong></code>只是在连接的接收端将轨迹分组在一起的一种方式,以确保它们是同步的。在连接的本地端添加到相同流的任何轨道都将位于远程端相同的流上。</p>
<h3 id="返回值">返回值</h3>
<p>将用于传输媒体数据的{{domxref("RTCRtpSender")}}对象。</p>
<div class="note">
<p>注意:每个<code><strong>RTCRtpSender</strong></code>都与{{domxref("RTCRtpReceiver")}}配对,组成{{domxref("RTCRtpTransceiver")}}。关联的接收方处于静默状态(指示它不能发送数据包),直到或除非远程对等方向接收方添加一个或多个流。</p>
</div>
<h3 id="异常">异常</h3>
<dl>
<dt><code>InvalidAccessError</code></dt>
<dd>指定的轨道(或它的所有底层流)已经是{{domxref("RTCPeerConnection")}}的一部分。</dd>
<dt><code>InvalidStateError</code></dt>
<dd>{{domxref("RTCPeerConnection")}}被关闭。</dd>
</dl>
<h2 id="使用笔记">使用笔记</h2>
<h3 id="向多个流添加轨道">向多个流添加轨道</h3>
<p>在<code><strong>track</strong></code>参数之后,您可以选择指定一个或多个{{domxref("MediaStream")}}对象来添加<code><strong>track</strong></code>。只有轨道从一个点发送到另一个点,而不是一个媒体流。由于流是特定于每个对等点的,因此指定一个或多个流意味着另一个对等点将在连接的另一端自动创建一个相应的流(或多个流),然后自动将接收到的轨道添加到这些流中。</p>
<h4 id="无流承载的轨道">无流承载的轨道</h4>
<p>如果没有指定媒体流,则轨道是无流的。这是完全可以接受的,尽管要由远程对等点决定将轨道插入到哪个流(如果有的话)。当构建一个多类型的简单应用只有一个媒体流时,使用<code><strong>addTrack()</strong></code>是一个非常常用的办法。例如,如果您与远程对等点共享的只是带有音频轨道和视频轨道的单个流,那么您不需要管理流中的哪个轨道,所以您不妨让<strong>transceriver</strong>为您处理它。</p>
<p>下面是一个使用{{domxref("MediaDevices.getUserMedia", "getUserMedia()")}}从用户的摄像机和麦克风获取一个流,然后将流中的每条轨迹添加到对等连接,而不为每条轨迹指定一个流:</p>
<pre class="brush: js notranslate">async openCall(pc) {
const gumStream = await navigator.mediaDevices.getUserMedia(
{video: true, audio: true});
for (const track of gumStream.getTracks()) {
pc.addTrack(track);
}
}</pre>
<p>结果是一组没有流关联的跟踪被发送到远程对等点。远程对等点上的{{event("track")}}事件的处理程序将负责决定将每个跟踪添加到哪个流中,即使这意味着只是将它们全部添加到同一个流中。{{domxref("RTCPeerConnection.ontrack", "ontrack")}}方法如下:</p>
<pre class="brush: js notranslate">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);
}
}</pre>
<p>在这里,如果指定了流,则<code><strong>track</strong></code>事件处理程序将跟踪添加到事件指定的第一个流。否则,在第一次调用<code><strong>ontrack</strong></code>时,将创建一个新流并附加到视频元素,然后将音轨添加到新流中。从那时起,新的堆<strong>track</strong>被添加到这个流中。</p>
<p>你也可以为每个接收到的<strong>track</strong>创建一个新的流:</p>
<pre class="brush: js notranslate">pc.ontrack = ev => {
if (ev.streams && ev.streams[0]) {
videoElem.srcObject = ev.streams[0];
} else {
let inboundStream = new MediaStream(ev.track);
videoElem.srcObject = inboundStream;
}
}</pre>
<h4 id="将track与特定的stream相关联">将<strong>track</strong>与特定的stream相关联</h4>
<p>通过指定一个流并允许{{domxref("RTCPeerConnection")}}为您创建流,流的跟踪关联将由WebRTC基础设施自动为您管理。这包括对收发器的{{domxref("RTCRtpTransceiver.direction","direction")}} 的更改和被停止使用{{domxref("RTCPeerConnection.removeTrack","removeTrack()")}}。</p>
<p>例如,考虑应用程序可能使用的这个函数,通过{{domxref("RTCPeerConnection")}}将设备的摄像头和麦克风输入流化为远程对等点:</p>
<pre class="brush: js notranslate">async openCall(pc) {
const gumStream = await navigator.mediaDevices.getUserMedia(
{video: true, audio: true});
for (const track of gumStream.getTracks()) {
pc.addTrack(track, gumStream);
}
}</pre>
<p>远程对等点然后可以使用一个看起来像这样的{{event("track")}}事件处理程序:</p>
<pre class="brush: js notranslate">pc.ontrack = ({streams: [stream]} => videoElem.srcObject = stream;</pre>
<p>这将把视频元素的当前流设置为包含已添加到连接中的音轨的流。</p>
<h3 id="重用发送方">重用发送方</h3>
<p>这种方法可以返回一个新的<code><strong>RTCRtpSender</strong></code>,或者在非常特殊的情况下,返回一个尚未用于传输数据的现有的兼容发送方。兼容的可重用<code><strong>RTCRtpSender</strong></code>实例满足以下条件:</p>
<ul>
<li>没有与发送方关联的跟踪。</li>
<li>与发送方关联的{domxref("RTCRtpTransceiver")}}有一个{domxref("RTCRtpReceiver")}},它的{{domxref("RTCRtpReceiver.track", "track")}}属性指定了一个{{domxref("MediaStreamTrack")}}它的{{domxref("MediaStreamTrack.kind", "kind")}}与调用<code><strong>RTCPeerConnection.addTrack()</strong></code>时指定的track参数的kind相同。这确保了收发器只能处理音频或视频,而不能同时处理二者。</li>
<li><code><strong>RTCRtpTransceiver</strong></code>的{{domxref("RTCRtpTransceiver.stopped", "stopped")}}属性为<code><strong>false</strong></code>。</li>
<li>正在考虑的<code><strong>RTCRtpSender</strong></code>从未被用于发送数据。如果收发器的{{domxref("RTCRtpTransceiver.currentDirection", "currentDirection")}} 曾经是“<code><strong>sendrecv</strong></code>”或“<code><strong>sendonly</strong></code>”,发送方不能被重用。</li>
</ul>
<p>如果所有这些条件都满足,发送方会被重用,这将导致现有<code><strong>RTCRtpSender</strong></code>和它的<code><strong>RTCRtpTransceiver</strong></code>发生这些变化:</p>
<ul>
<li><code><strong>RTCRtpSender</strong></code>的{{domxref("RTCRtpSender.track", "track")}}被设置为指定的track。</li>
<li>发送方的相关流集被设置为传递到这个方法的流列表,<code><strong>stream…</strong></code></li>
<li>关联的{{domxref("RTCRtpTransceiver")}}更新了它的当前方向,包括发送;如果它的当前值是“<code><strong>recvonly</strong></code>”,它就变成“<code><strong>sendrecv</strong></code>”,如果它的当前值是“<code><strong>inactive</strong></code>”,它就变成“<code><strong>sendonly</strong></code>”。</li>
</ul>
<h3 id="新发送方">新发送方</h3>
<p>如果现有的发送方不存在可重用,则创建一个新的发送方。这也会导致必须存在的关联对象的创建。创建新发送方的过程会导致以下更改:</p>
<ul>
<li>使用指定的<code><strong>track</strong></code>和<code><strong>streams</strong></code>集创建新的<code><strong>RTCRtpSender</strong></code>。</li>
<li>新{{domxref("RTCRtpReceiver")}}被创建,新{{domxref("MediaStreamTrack")}}作为它的{{domxref("RTCRtpReceiver.track", "track")}} 属性(不是调用<code><strong>addTrack()</strong></code>时指定作为参数的track)。这跟踪的{{domxref("MediaStreamTrack.kind", "kind")}}设置为与作为输入参数提供的音轨类型匹配。</li>
<li>将创建一个新的{{domxref("RTCRtpTransceiver")}},并与新的发送方和接收方关联。</li>
<li>新的<code><strong>transceiver</strong></code>的{{domxref("RTCRtpTransceiver.direction", "direction")}}设置为"<code><strong>sendrecv</strong></code>"。</li>
<li>新的<code><strong>transceiver</strong></code>被添加到RTCPeerConnection的收发器集合中。</li>
</ul>
<dl>
</dl>
<h2 id="实例">实例</h2>
<p>这个例子是从文章中给出的<a href="/zh-CN/docs/Web/API/WebRTC_API/Signaling_and_video_calling">Signaling and video calling</a>及其相应的示例代码中提取的。它来自那里的<code><strong>handleVideoOfferMsg()</strong></code>方法,该方法在从远程对等方接收到报价消息时被调用。</p>
<pre class="brush: js notranslate">var mediaConstraints = {
audio: true, // We want an audio track
video: true // ...and we want a video track
};
var desc = new RTCSessionDescription(sdp);
pc.setRemoteDescription(desc).then(function () {
return navigator.mediaDevices.getUserMedia(mediaConstraints);
})
.then(function(stream) {
previewElement.srcObject = stream;
stream.getTracks().forEach(track => pc.addTrack(track, stream));
})</pre>
<p>这段代码获取从远程对等方接收到的SDP,并构造一个新的{{domxref("RTCSessionDescription")}}传递到{{domxref("RTCPeerConnection.setRemoteDescription", "setRemoteDescription()")}}。成功之后,它使用{{domxref(" mediadevic. getusermedia()")}}获得对本地摄像头和麦克风的访问。</p>
<p>如果成功,结果流将被分配为变量<code><strong>previewElement</strong></code>引用的{{HTMLElement("video")}}元素的源。</p>
<p>最后一步是开始通过对等连接向调用者发送本地视频。通过遍历{{domxref("MediaStream.getTracks()")}}返回的列表,并将它们与作为其组件的流一起传递给<code><strong>addTrack()</strong></code>,从而在流中添加每条跟踪。</p>
<h2 id="技术规范">技术规范</h2>
<table class="standard-table">
<thead>
<tr>
<th scope="col">Specification</th>
<th scope="col">Status</th>
<th scope="col">Comment</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ SpecName('WebRTC 1.0', '#dom-rtcpeerconnection-addtrack', 'RTCPeerConnection.addTrack()') }}</td>
<td>{{ Spec2('WebRTC 1.0') }}</td>
<td>Initial specification.</td>
</tr>
</tbody>
</table>
<h2 id="浏览器支持">浏览器支持</h2>
<p>{{Compat("api.RTCPeerConnection.addTrack")}}</p>
<h2 id="参考">参考</h2>
<ul>
<li><a href="/en-US/docs/Web/Guide/API/WebRTC">WebRTC</a></li>
<li><a href="/en-US/docs/Web/API/WebRTC_API/Intro_to_RTP">Introduction to the Real-time Transport Protocol (RTP)</a></li>
<li>{{domxref("RTCPeerConnection.ontrack")}}</li>
<li>{{event("track")}}</li>
</ul>
|