diff --git a/index.bs b/index.bs
index 9f03733..4b54fab 100644
--- a/index.bs
+++ b/index.bs
@@ -117,14 +117,20 @@ Failing to release them (or waiting for garbage collection) can cause the source
### Interface definition ### {#track-processor-interface}
+[Exposed=(Window,DedicatedWorker), Transferable]
+interface MediaStreamTrackHandle {
+ constructor(MediaStreamTrack track);
+};
+
[Exposed=DedicatedWorker]
interface MediaStreamTrackProcessor {
constructor(MediaStreamTrackProcessorInit init);
readonly attribute ReadableStream readable;
};
+typedef (MediaStreamTrack or MediaStreamTrackHandle) MediaStreamTrackOrHandle;
dictionary MediaStreamTrackProcessorInit {
- required MediaStreamTrack track;
+ required MediaStreamTrackOrHandle track;
[EnforceRange] unsigned short maxBufferSize;
};
@@ -158,8 +164,11 @@ application that have not yet been handled.
MediaStreamTrackProcessor(|init|)
-1. If |init|.{{MediaStreamTrackProcessorInit/track}} is not a valid {{MediaStreamTrack}},
+1. If |init|.{{MediaStreamTrackProcessorInit/track}} is a {{MediaStreamTrack}} that is not valid,
throw a {{TypeError}}.
+1. If |init|.{{MediaStreamTrackProcessorInit/track}} is a {{MediaStreamTrackHandle}}, run the following substeps:
+ 1. If |init|.{{MediaStreamTrackProcessorInit/track}}.`[[transferable]]` is false, throw a {{ TypeError}}.
+ 1. If |init|.{{MediaStreamTrackProcessorInit/track}}.`[[track]]` is referencing a non valid {{MediaStreamTrack}}, throw a {{TypeError}}.
1. Let |maxBufferSize| be 1.
1. If |init|.{{MediaStreamTrackProcessorInit/maxBufferSize}} has an integer value greater than 1, run the following substeps:
1. Set |maxBufferSize| to |init|.{{MediaStreamTrackProcessorInit/maxBufferSize}}.
@@ -179,7 +188,7 @@ application that have not yet been handled.
### Attributes ### {#attributes-processor}
- readable
-- Allows reading the frames delivered by the {{MediaStreamTrack}} stored
+
- Allows reading the frames delivered by the {{MediaStreamTrack}} or {{MediaStreamTrackHandle}} stored
in the `[[track]]` internal slot. This attribute is created the first time it is invoked
according to the following steps:
1. Initialize [=this=].{{MediaStreamTrackProcessor/readable}} to be a new {{ReadableStream}}.
@@ -201,15 +210,16 @@ The maybeReadFrame algorithm is given a |processor| as input. It is d
The processorCancel algorithm is given a |processor| as input.
It is defined by running the following steps:
1. Run the [=processorClose=] algorithm with |processor| as parameter.
-3. Return [=a promise resolved with=] undefined.
+1. Return [=a promise resolved with=] undefined.
The processorClose algorithm is given a |processor| as input.
It is defined by running the following steps:
1. If |processor|.`[[isClosed]]` is true, abort these steps.
-2. Disconnect |processor| from |processor|.`[[track]]`. The mechanism to do this is UA specific and the result is that |processor| is no longer a sink of |processor|.`[[track]]`.
-3. [$ReadableStreamDefaultControllerClose|Close$] |processor|.{{MediaStreamTrackProcessor/readable}}.[=ReadableStream/[[controller]]=].
-4. [=list/Empty=] |processor|.`[[queue]]`.
-5. Set |processor|.`[[isClosed]]` to true.
+1. Disconnect |processor| from |processor|.`[[track]]`. The mechanism to do this is UA specific and the result is that |processor| is no longer a sink of |processor|.`[[track]]`, or the {{MediaStreamTrack}} referenced by it.
+1. Set |processor|.`[[track]]` to null.
+1. [$ReadableStreamDefaultControllerClose|Close$] |processor|.{{MediaStreamTrackProcessor/readable}}.[=ReadableStream/[[controller]]=].
+1. [=list/Empty=] |processor|.`[[queue]]`.
+1. Set |processor|.`[[isClosed]]` to true.
@@ -240,6 +250,35 @@ When the `[[track]]` of a {{MediaStreamTrackProcessor}} |processor|
[=track|ends=], the [=processorClose=] algorithm must be
executed with |processor| as parameter.
+### MediaStreamTrackHandle ### {#mediastreamtrackhandle}
+
+The {{MediaStreamTrackHandle}} interface represents a proxy to a {{MediaStreamTrack}} object that is useful for making a DedicatedWorkerGlobalScope {{MediaStreamTrackProcessor}} a sink to a {{MediaStreamTrack}} living in another context.
+
+A {{MediaStreamTrackHandle}} has the following slots:
+1. A `[[transferable]]` internal slot that is used to ensure that once the handle object instance has been transferred, that instance cannot be transferred again.
+1. A `[[track]]` internal slot that is a weak reference to the proxied {{MediaStreamTrack}} object.
+
+### Constructor ### {#mediastreamtrackhandle-constructor}
+
+ MediaStreamTrackHandle(|track|)
+
+1. Let |handle| be a new {{MediaStreamTrackHandle}} object.
+1. Set |handle|.`[[transferable]]` to true.
+1. Set |handle|.`[[track]]` to an internal reference to |track|.
+1. Return |handle|.
+
+The {{MediaStreamTrackHandle}} [=transfer steps=], given |value| and |dataHolder|, are:
+1. If |value|.`[[transferable]]` is false, throw a {{DataCloneError}} DOMException.
+1. Set |value|.`[[transferable]]` to false.
+1. Set |dataHolder|.`[[track]]` to |handle|.`[[track]]`.
+1. Set |value|.`[[track]]` to null.
+
+The {{MediaStreamTrackHandle}} [=transfer-receiving steps=], given |dataHolder| and |handle|, are:
+1. Set |handle|.`[[track]]` to |dataHolder|.`[[track]]`.
+
+The {{MediaStreamTrackHandle}} can increase the lifetime of its proxied {{MediaStreamTrack}}.
+From a garbage collection point of view, it is as if a {{MediaStreamTrackHandle}} holds a strong reference to its {{MediaStreamTrack}}.
+Also, a {{MediaStreamTrack}} MUST not be garbage collected if it is referenced from a data holder that is being transferred.
## VideoTrackGenerator ## {#video-track-generator}
A {{VideoTrackGenerator}} allows the creation of a video source for a
@@ -497,22 +536,22 @@ effects on video elements.
const stream = await navigator.mediaDevices.getUserMedia({video:true});
const videoBefore = document.getElementById('video-before');
const videoAfter = document.getElementById('video-after');
-videoBefore.srcObject = stream.clone();
+videoBefore.srcObject = stream;
-const [track] = stream.getVideoTracks();
+const trackHandle = new MediaStreamTrackHandle(stream.getVideoTracks()[0]);
const worker = new Worker('worker.js');
-worker.postMessage({track}, [track]);
+worker.postMessage({trackHandle}, [trackHandle]);
const {data} = await new Promise(r => worker.onmessage);
videoAfter.srcObject = new MediaStream([data.track]);
// worker.js
-self.onmessage = async ({data: {track}}) => {
+self.onmessage = async ({data: {trackHandle}}) => {
const source = new VideoTrackGenerator();
parent.postMessage({track: source.track}, [source.track]);
- const {readable} = new MediaStreamTrackProcessor({track});
+ const {readable} = new MediaStreamTrackProcessor({track: trackHandle});
const transformer = new TransformStream({
async transform(frame, controller) {
const facePosition = await detectFace(frame);