Skip to content

Commit 23c6538

Browse files
authored
Merge pull request #82 from youennf/update-explainer
Update explainer examples to use sender/receiver transform
2 parents 1b0b4c3 + d994df0 commit 23c6538

File tree

1 file changed

+86
-57
lines changed

1 file changed

+86
-57
lines changed

explainer.md

Lines changed: 86 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ some other WG may work on a mechanism for end-to-end keying.
5757

5858
<pre>
5959
const supportsInsertableStreams = window.RTCRtpSender &&
60-
!!RTCRtpSender.prototype.createEncodedStreams;
60+
"transform" in RTCRtpSender.prototype;
6161
</pre>
6262

6363
1. Let an PeerConnection know that it should allow exposing the data flowing through it
@@ -69,18 +69,18 @@ Therefore, we explicitly let the RTCPeerConnection know that we want to use inse
6969
streams. For example:
7070

7171
<pre>
72-
let pc = new RTCPeerConnection({
73-
encodedInsertableStreams: true,
74-
});
72+
const pc = new RTCPeerConnection();
7573
</pre>
7674

7775
2. Set up transform streams that perform some processing on data.
7876

7977
The following example negates every bit in the original data payload
8078
of an encoded frame and adds 4 bytes of padding.
8179

82-
<pre>
83-
let senderTransform = new TransformStream({
80+
<pre>// code in worker.js file
81+
// Sender transform
82+
function createSenderTransform() {
83+
return new TransformStream({
8484
start() {
8585
// Called on startup.
8686
},
@@ -110,54 +110,66 @@ of an encoded frame and adds 4 bytes of padding.
110110
// Called when the stream is about to be closed.
111111
}
112112
});
113+
}
114+
115+
// Receiver transform
116+
function createReceiverTransform() {
117+
return new TransformStream({
118+
start() {},
119+
flush() {},
120+
async transform(encodedFrame, controller) {
121+
// Reconstruct the original frame.
122+
const view = new DataView(encodedFrame.data);
123+
124+
// Ignore the last 4 bytes
125+
const newData = new ArrayBuffer(encodedFrame.data.byteLength - 4);
126+
const newView = new DataView(newData);
127+
128+
// Negate all bits in the incoming frame, ignoring the
129+
// last 4 bytes
130+
for (let i = 0; i < encodedFrame.data.byteLength - 4; ++i)
131+
newView.setInt8(i, ~view.getInt8(i));
132+
133+
encodedFrame.data = newData;
134+
controller.enqueue(encodedFrame);
135+
}
136+
});
137+
}
138+
139+
// Code to instantiate transform and attach them to sender/receiver pipelines.
140+
onrtctransform = (event) => {
141+
let transform;
142+
if (event.transformer.options.name == "senderTransform")
143+
transform = createSenderTransform();
144+
else if (event.transformer.options.name == "receiverTransform")
145+
transform = createReceiverTransform();
146+
else
147+
return;
148+
event.transformer.readable
149+
.pipeThrough(transform)
150+
.pipeTo(event.transformer.writable);
151+
};
113152
</pre>
114153

115154
3. Create a MediaStreamTrack, add it to the RTCPeerConnection and connect the
116155
Transform stream to the track's sender.
117156

118157
<pre>
119-
let stream = await navigator.mediaDevices.getUserMedia({video:true});
120-
let [track] = stream.getTracks();
121-
let videoSender = pc.addTrack(track, stream)
122-
let senderStreams = videoSender.createEncodedStreams();
158+
const worker = new Worker('worker.js');
159+
const stream = await navigator.mediaDevices.getUserMedia({video:true});
160+
const [track] = stream.getTracks();
161+
const videoSender = pc.addTrack(track, stream);
162+
videoSender.transform = new RTCRtpScriptTransform(worker, { name: "senderTransform" });
123163

124164
// Do ICE and offer/answer exchange.
125165

126-
senderStreams.readable
127-
.pipeThrough(senderTransform)
128-
.pipeTo(senderStreams.writable);
129-
</pre>
130-
131166
4. Do the corresponding operations on the receiver side.
132167

133168
<pre>
134-
let pc = new RTCPeerConnection({encodedInsertableStreams: true});
169+
const worker = new Worker('worker.js');
170+
const pc = new RTCPeerConnection({encodedInsertableStreams: true});
135171
pc.ontrack = e => {
136-
let receiverTransform = new TransformStream({
137-
start() {},
138-
flush() {},
139-
async transform(encodedFrame, controller) {
140-
// Reconstruct the original frame.
141-
let view = new DataView(encodedFrame.data);
142-
143-
// Ignore the last 4 bytes
144-
let newData = new ArrayBuffer(encodedFrame.data.byteLength - 4);
145-
let newView = new DataView(newData);
146-
147-
// Negate all bits in the incoming frame, ignoring the
148-
// last 4 bytes
149-
for (let i = 0; i < encodedFrame.data.byteLength - 4; ++i)
150-
newView.setInt8(i, ~view.getInt8(i));
151-
152-
encodedFrame.data = newData;
153-
controller.enqueue(encodedFrame);
154-
},
155-
});
156-
157-
let receiverStreams = e.receiver.createEncodedStreams();
158-
receiverStreams.readable
159-
.pipeThrough(receiverTransform)
160-
.pipeTo(receiverStreams.writable);
172+
e.receiver.transform = new RTCRtpScriptTransform(worker, { name: "receiverTransform" });
161173
}
162174
</pre>
163175

@@ -167,12 +179,6 @@ The following are the IDL modifications proposed by this API.
167179
Future iterations may add additional operations following a similar pattern.
168180

169181
<pre>
170-
// New dictionary.
171-
dictionary RTCInsertableStreams {
172-
ReadableStream readable;
173-
WritableStream writable;
174-
};
175-
176182
// New enum for video frame types. Will eventually re-use the equivalent defined
177183
// by WebCodecs.
178184
enum RTCEncodedVideoFrameType {
@@ -215,18 +221,45 @@ interface RTCEncodedAudioFrame {
215221
RTCAudioFrameMetadata getMetadata();
216222
};
217223

218-
// New field in RTCConfiguration
219-
partial dictionary RTCConfiguration {
220-
boolean encodedInsertableStreams = false;
221-
};
224+
// New methods for RTCRtpSender and RTCRtpReceiver
225+
typedef (SFrameTransform or RTCRtpScriptTransform) RTCRtpTransform;
222226

223227
// New methods for RTCRtpSender and RTCRtpReceiver
224228
partial interface RTCRtpSender {
225-
RTCInsertableStreams createEncodedStreams();
229+
attribute RTCRtpTransform? transform;
226230
};
227231

228232
partial interface RTCRtpReceiver {
229-
RTCInsertableStreams createEncodedStreams();
233+
attribute RTCRtpTransform? transform;
234+
};
235+
236+
[Exposed=(Window,Worker)]
237+
interface SFrameTransform {
238+
constructor(optional SFrameTransformOptions options = {});
239+
Promise<undefined> setEncryptionKey(CryptoKey key, optional unsigned long long keyID);
240+
};
241+
242+
[Exposed=Worker]
243+
interface RTCTransformEvent : Event {
244+
readonly attribute RTCRtpScriptTransformer transformer;
245+
};
246+
247+
partial interface DedicatedWorkerGlobalScope {
248+
attribute EventHandler onrtctransform;
249+
};
250+
251+
// FIXME: We want to expose only in dedicated worker scopes.
252+
[Exposed=Worker]
253+
interface RTCRtpScriptTransformer {
254+
readonly attribute ReadableStream readable;
255+
readonly attribute WritableStream writable;
256+
readonly attribute any options;
257+
};
258+
259+
[Exposed=Window]
260+
interface RTCRtpScriptTransform {
261+
constructor(Worker worker, optional any options);
262+
// FIXME: add messaging methods.
230263
};
231264

232265
</pre>
@@ -277,7 +310,3 @@ This also seemed to involve a significantly larger set of new interfaces, with a
277310
correspondingly larger implementation effort, and would offer less flexibility
278311
in how the processing elements could be implemented.
279312

280-
281-
282-
283-

0 commit comments

Comments
 (0)