- From: Mattias Buelens <notifications@github.com>
- Date: Thu, 02 Dec 2021 02:58:48 -0800
- To: whatwg/streams <streams@noreply.github.com>
- Cc: Subscribed <subscribed@noreply.github.com>
- Message-ID: <whatwg/streams/pull/1193/review/821277372@github.com>
@MattiasBuelens requested changes on this pull request. Thanks for kicking this off! Looks pretty good already. 👍 > + if (!controller.count) + controller.count = 0; It might be a bit confusing to use "expando properties" in these examples. (What if we decide to add `TransformStreamDefaultController.prototype.count` in the future? 😛) For these examples, it's probably better to use a regular variable: ```javascript let count = 0; const frameCountTransform = new TransformStream({ transform: async (videoFrame, controller) => { /* ... */ } }); ``` > +## Example + +Below is an example of JavaScript that shows how this can be used. +The example creates a processing pipe starting with a camera stream and applying two transforms, one for doing a processing for every 30 frame, and one for doing background blur. + +```javascript +// JS transform +const frameCountTransform = new TransformStream({ + transform: async (videoFrame, controller) => { + try { + // videoFrame is under the responsibility of the script and must be closed when no longer needed + controller.enqueue(videoFrame); + // At this point, videoFrame has been transfered within controller.enqueue call. frameCountTransform cannot mutate it. + if (!controller.count) + controller.count = 0; + if (!(i++controller.count % 30) && frameCountTransform.onEach30Frame) `i++controller.count` is a `SyntaxError`. 😬 ```suggestion if (!(++controller.count % 30) && frameCountTransform.onEach30Frame) ``` > +* Add a new 'transfer' type to `ReadableStream`, `WritableStream` and `TransformStream`. + For streams that do not have the 'transfer' type, nothing changes. A `TransformStream` may transform one type of chunks to a different type of chunks. For example, a `TextDecoderStream` accepts `ByteSource`s on its writable end and produces `string`s on its readable end. We should try to support such use cases for ownership-transferring streams too. Therefore, we shouldn't "just" have `type: "transfer"` for `TransformStream`. We have already reserved `readableType` and `writableType` for such use cases, see the [`TransformStream` constructor](https://streams.spec.whatwg.org/#ts-constructor). I suggest we use that: * If `writableType` is `"transfer"`, then the writable end will accept transferable objects. Ownership is transferred into the stream when calling `writer.write(chunk)`, and transferred out when the stream calls `transformer.transform(chunk, controller)`. * If `readableType` is `"transfer"`, then the readable end will produce transferable objects. Ownership is transferred into the stream when calling `controller.enqueue(chunk)`, and transferred out when resolving a `reader.reader()` call. For convenience, we *could* still add `transformer.type`, as a shorthand for setting both `readableType` and `writableType` to the same value. That would make the common case of transforming a `VideoFrame` to another `VideoFrame` easier. Or maybe we want to leave that as a future improvement. > +* Performing realtime transformations of `VideoFrame` objects, for instance taking a camera `MediaStreamTrack` and applying + a background blur effect as a `TransformStream` on each `VideoFrame` of the `MediaStreamTrack`. + +## End-user benefit + +* `VideoFrame` needs specific management and be closed as quickly as possible, without relying on garbage collection. + This is important to not create hangs/stutters in the processing pipeline. By building support for safe patterns + directly in streams, this will allow web developers to optimize `VideoFrame` management, and allow user experience + to be more consistent accross devices. + +## Principles + +The envisioned changes to the streams specification could look like the following: +* Add a new 'transfer' type to `ReadableStream`, `WritableStream` and `TransformStream`. + For streams that do not have the 'transfer' type, nothing changes. +* Streams of the 'transfer' type can only manipulate Transferable or Serializable objects. I believe we have to *require* all objects to be `Serializable`. Otherwise, we cannot call `StructuredSerialize`/`StructuredDeserialize` inside `tee()` to make a "real" clone. (A transferred version of an object is not a clone, since you can no longer use the original.) > + or `WritableStreamDefaultWriter`, create a copy of the object using StructuredSerializeWithTransfer/StructuredDeserializeWithTransfer. + Proceed with the regular stream algorithm by using the copy of the object instead of the object itself. ```suggestion or `WritableStreamDefaultWriter`, create a transferred version of the object using StructuredSerializeWithTransfer/StructuredDeserializeWithTransfer. Proceed with the regular stream algorithm by using the transferred object instead of the object itself. ``` > + directly in streams, this will allow web developers to optimize `VideoFrame` management, and allow user experience + to be more consistent accross devices. + +## Principles + +The envisioned changes to the streams specification could look like the following: +* Add a new 'transfer' type to `ReadableStream`, `WritableStream` and `TransformStream`. + For streams that do not have the 'transfer' type, nothing changes. +* Streams of the 'transfer' type can only manipulate Transferable or Serializable objects. + If a non Transferable or Serializable object is enqueued or written, the object is ignored as if it was never enqueued/written. +* If a Transferable object is enqueueud/written in a 'transfer' type `ReadableStreamDefaultController`, `TransformStreamDefaultController` + or `WritableStreamDefaultWriter`, create a copy of the object using StructuredSerializeWithTransfer/StructuredDeserializeWithTransfer. + Proceed with the regular stream algorithm by using the copy of the object instead of the object itself. +* If a Serializable object is enqueueud/written in a 'transfer' type `ReadableStreamDefaultController`, `TransformStreamDefaultController` + or `WritableStreamDefaultWriter`, create a copy of the object using StructuredSerialize/StructuredDeserialize. + Proceed with the regular stream algorithm by using the copy of the object instead of the object itself. We should also close the original object if it supports `Closeable`. (If it doesn't support it, we just let it be garbage collected as normal.) > +## Principles + +The envisioned changes to the streams specification could look like the following: +* Add a new 'transfer' type to `ReadableStream`, `WritableStream` and `TransformStream`. + For streams that do not have the 'transfer' type, nothing changes. +* Streams of the 'transfer' type can only manipulate Transferable or Serializable objects. + If a non Transferable or Serializable object is enqueued or written, the object is ignored as if it was never enqueued/written. +* If a Transferable object is enqueueud/written in a 'transfer' type `ReadableStreamDefaultController`, `TransformStreamDefaultController` + or `WritableStreamDefaultWriter`, create a copy of the object using StructuredSerializeWithTransfer/StructuredDeserializeWithTransfer. + Proceed with the regular stream algorithm by using the copy of the object instead of the object itself. +* If a Serializable object is enqueueud/written in a 'transfer' type `ReadableStreamDefaultController`, `TransformStreamDefaultController` + or `WritableStreamDefaultWriter`, create a copy of the object using StructuredSerialize/StructuredDeserialize. + Proceed with the regular stream algorithm by using the copy of the object instead of the object itself. +* Introduce a WhatWG streams 'close-able' concept. An object that is 'close-able' defines closing steps. + For instance `VideoFrame` closing steps could be defined using https://www.w3.org/TR/webcodecs/#close-videoframe. + `ArrayBuffer` closing steps could be defined using https://tc39.es/ecma262/#sec-detacharraybuffer. `DetachArrayBuffer` doesn't deallocate the underlying Data Block of the `ArrayBuffer`, since it's also used to *transfer* an `ArrayBuffer` (see [step 5.4 of `StructuredSerializeWithTransfer`](https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializewithtransfer)). However, in our case we know that we won't transfer it to a new `ArrayBuffer`, and thus the Data Block *could* be deallocated immediately. We should make a note that implementations are *allowed* to deallocate the Data Block immediately after closing an `ArrayBuffer` (or they could just wait for garbage collection as normal). > + a background blur effect as a `TransformStream` on each `VideoFrame` of the `MediaStreamTrack`. + +## End-user benefit + +* `VideoFrame` needs specific management and be closed as quickly as possible, without relying on garbage collection. + This is important to not create hangs/stutters in the processing pipeline. By building support for safe patterns + directly in streams, this will allow web developers to optimize `VideoFrame` management, and allow user experience + to be more consistent accross devices. + +## Principles + +The envisioned changes to the streams specification could look like the following: +* Add a new 'transfer' type to `ReadableStream`, `WritableStream` and `TransformStream`. + For streams that do not have the 'transfer' type, nothing changes. +* Streams of the 'transfer' type can only manipulate Transferable or Serializable objects. + If a non Transferable or Serializable object is enqueued or written, the object is ignored as if it was never enqueued/written. Hmm, not sure if I like this "silent error". For readable byte streams, we throw a `TypeError` in `controller.enqueue()` if the given chunk is not an `ArrayBufferView`. Ideally, we'd do something similar for ownership-transferring streams. > +const blurredStream = new MediaStream([getTrackFromReadableStream(blurredVideoFrameStream)]); +// Make use of blurredStream. +... +``` + +## Goals + +* Permit `ReadableStream`, `WritableStream` and `TransformStream` objects to take ownership of objects they manipulate. +* Permit to build a safe and optimal video pipeline using `ReadableStream`, `WritableStream` and `TransformStream` objects that manipulate `VideoFrame` objects. +* Permit both native and JavaScript-based streams of type 'transfer'. +* Permit to optimize streams pipelines of transferable objects like `ArrayBuffer`, `RTCEncodedVideoFrame` or `RTCEncodedAudioFrame`. +* Permit to tee a `ReadableStream` of `VideoFrame` objects without tight coupling between the teed branches. + +## Non-goals + +* Add support for supporting transfer and closing of arbitrary JavaScript objects. ```suggestion * Add support for transferring and closing of arbitrary JavaScript objects. ``` -- You are receiving this because you are subscribed to this thread. Reply to this email directly or view it on GitHub: https://github.com/whatwg/streams/pull/1193#pullrequestreview-821277372
Received on Thursday, 2 December 2021 10:59:01 UTC