@@ -57,7 +57,7 @@ some other WG may work on a mechanism for end-to-end keying.
5757
5858<pre >
5959const supportsInsertableStreams = window.RTCRtpSender &&
60- !! RTCRtpSender.prototype.createEncodedStreams ;
60+ "transform" in RTCRtpSender.prototype;
6161</pre >
6262
63631 . 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
6969streams. For example:
7070
7171<pre >
72- let pc = new RTCPeerConnection({
73- encodedInsertableStreams: true,
74- });
72+ const pc = new RTCPeerConnection();
7573</pre >
7674
77752 . Set up transform streams that perform some processing on data.
7876
7977The following example negates every bit in the original data payload
8078of 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
1151543 . Create a MediaStreamTrack, add it to the RTCPeerConnection and connect the
116155Transform 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-
1311664. 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});
135171pc.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.
167179Future 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.
178184enum 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
224228partial interface RTCRtpSender {
225- RTCInsertableStreams createEncodedStreams() ;
229+ attribute RTCRtpTransform? transform ;
226230};
227231
228232partial 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
277310correspondingly larger implementation effort, and would offer less flexibility
278311in how the processing elements could be implemented.
279312
280-
281-
282-
283-
0 commit comments