@@ -57,7 +57,7 @@ some other WG may work on a mechanism for end-to-end keying.
57
57
58
58
<pre >
59
59
const supportsInsertableStreams = window.RTCRtpSender &&
60
- !! RTCRtpSender.prototype.createEncodedStreams ;
60
+ "transform" in RTCRtpSender.prototype;
61
61
</pre >
62
62
63
63
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
69
69
streams. For example:
70
70
71
71
<pre >
72
- let pc = new RTCPeerConnection({
73
- encodedInsertableStreams: true,
74
- });
72
+ const pc = new RTCPeerConnection();
75
73
</pre >
76
74
77
75
2 . Set up transform streams that perform some processing on data.
78
76
79
77
The following example negates every bit in the original data payload
80
78
of an encoded frame and adds 4 bytes of padding.
81
79
82
- <pre >
83
- let senderTransform = new TransformStream({
80
+ <pre >// code in worker.js file
81
+ // Sender transform
82
+ function createSenderTransform() {
83
+ return new TransformStream({
84
84
start() {
85
85
// Called on startup.
86
86
},
@@ -110,54 +110,66 @@ of an encoded frame and adds 4 bytes of padding.
110
110
// Called when the stream is about to be closed.
111
111
}
112
112
});
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
+ };
113
152
</pre >
114
153
115
154
3 . Create a MediaStreamTrack, add it to the RTCPeerConnection and connect the
116
155
Transform stream to the track's sender.
117
156
118
157
<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" });
123
163
124
164
// Do ICE and offer/answer exchange.
125
165
126
- senderStreams.readable
127
- .pipeThrough(senderTransform)
128
- .pipeTo(senderStreams.writable);
129
- </pre >
130
-
131
166
4. Do the corresponding operations on the receiver side.
132
167
133
168
<pre >
134
- let pc = new RTCPeerConnection({encodedInsertableStreams: true});
169
+ const worker = new Worker('worker.js');
170
+ const pc = new RTCPeerConnection({encodedInsertableStreams: true});
135
171
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" });
161
173
}
162
174
</pre >
163
175
@@ -167,12 +179,6 @@ The following are the IDL modifications proposed by this API.
167
179
Future iterations may add additional operations following a similar pattern.
168
180
169
181
<pre >
170
- // New dictionary.
171
- dictionary RTCInsertableStreams {
172
- ReadableStream readable;
173
- WritableStream writable;
174
- };
175
-
176
182
// New enum for video frame types. Will eventually re-use the equivalent defined
177
183
// by WebCodecs.
178
184
enum RTCEncodedVideoFrameType {
@@ -215,18 +221,45 @@ interface RTCEncodedAudioFrame {
215
221
RTCAudioFrameMetadata getMetadata();
216
222
};
217
223
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;
222
226
223
227
// New methods for RTCRtpSender and RTCRtpReceiver
224
228
partial interface RTCRtpSender {
225
- RTCInsertableStreams createEncodedStreams() ;
229
+ attribute RTCRtpTransform? transform ;
226
230
};
227
231
228
232
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.
230
263
};
231
264
232
265
</pre >
@@ -277,7 +310,3 @@ This also seemed to involve a significantly larger set of new interfaces, with a
277
310
correspondingly larger implementation effort, and would offer less flexibility
278
311
in how the processing elements could be implemented.
279
312
280
-
281
-
282
-
283
-
0 commit comments