1. Introduction
The [WEBRTC-NV-USE-CASES] document describes several functions that can only be achieved by access to media (requirements N20-N22), including, but not limited to:
-
Funny Hats
-
Machine Learning
-
Virtual Reality Gaming
These use cases further require that processing can be done in worker threads (requirement N23-N24).
Furthermore, the "trusted JavaScript cloud conferencing" use case requires such processing to be done on encoded media, not just the raw media.
This specification gives an interface inspired by [WEB-CODECS] to provide access to such functionality while retaining the setup flow of RTCPeerConnection.
This iteration of the specification provides access to encoded media, which is the output of the encoder part of a codec and the input to the decoder part of a codec.
2. Terminology
3. Specification
The Streams definition doesn’t use WebIDL much, but the WebRTC spec does. This specification shows the IDL extensions for WebRTC.
It
uses
an
additional
API
on
RTCRtpSender
and
RTCRtpReceiver
to
insert
the
processing
into
the
pipeline.
// New dictionarydictionary {
RTCInsertableStreams ReadableStream ;
readable WritableStream ; };
writable typedef (SFrameTransform or RTCRtpScriptTransform ); // New methods for RTCRtpSender and RTCRtpReceiver
RTCRtpTransform partial interface RTCRtpSender {attribute RTCRtpTransform ?transform ; };partial interface RTCRtpReceiver {attribute RTCRtpTransform ?transform ; };
3.1. Extension operation
At
the
time
when
a
codec
is
initialized
as
part
of
the
encoder,
and
the
corresponding
flag
is
set
in
the
RTCPeerConnection
's
RTCConfiguration
argument,
ensure
that
the
codec
is
disabled
and
produces
no
output.
3.1.1. Stream creation
Each
RTCRtpSender
or
RTCRtpReceiver
has
its
own
media
thread
on
which
media
flows,
from
a
source
on
which
to
read
encoded
data
to
a
sink
on
which
to
write
encoded
data.
The
source
is
the
packetizer
for
RTCRtpSender
and
the
decoder
for
RTCRtpReceiver
.
It
operates
in
the
media
thread
and
is
modeled
as
a
ReadableStream
.
The
sink
is
the
encoder
for
RTCRtpSender
and
the
depacketizer
for
RTCRtpReceiver
.
It
operates
in
the
media
thread
and
is
modeled
as
a
WritableStream
.
At
construction
of
each
RTCRtpSender
or
RTCRtpReceiver
,
run
the
following
steps:
-
Initialize this .
[[transform]]
tonull.null
. -
Initialize this .
[[readable]][[lastReceivedFrameTimestamp]] Initialize this .
[[pipeToController]]
tonull
.Initialize this .
[[source]]
to a newReadableStream
.-
Set up this .
[[readable]][[source]][[readable]][[source]]readEncodedDatareadSourceData algorithm given this as parameter. -
Set this . [[readable]] . [[owner]] to this .Initialize this .[[writable]][[sink]]WritableStream
. -
Set up this .
[[writable]][[sink]]writeEncodedDatawriteSinkData given this as parameter. -
Set this . [[writable]] . [[owner]] to this . Initialize this . [[pipeToController]] to null. Initialize this . [[lastReceivedFrameTimestamp]]Queue a task tozero.run the following step:-
Queue a task on this 's media thread to run the following steps:
-
If this .
[[pipeToController]]
is not null, abort these steps. -
Set this .
[[pipeToController]]
to a newAbortController
. -
Call pipeTo with this .
[[readable]][[source]][[writable]][[sink]][[pipeToController]]
.signal.
-
-
3.1.2. Stream processing
The
readEncodedData
readSourceData
algorithm
is
given
a
rtcObject
as
parameter.
It
is
defined
by
running
runs
the
following
steps:
steps
in
the
media
thread
:
-
Wait for a frame to be produced by rtcObject ’s encoder if it is a
RTCRtpSender
or rtcObject ’s packetizer if it is aRTCRtpReceiver
. -
Let frame be the newly produced frame.
-
Set frame .
[[owner]]
to rtcObject . -
Enqueue frame in rtcObject .
[[readable]][[source]]
The
writeEncodedData
writeSinkData
algorithm
is
given
a
rtcObject
as
parameter
and
a
frame
as
input.
It
is
defined
by
running
runs
the
following
steps:
steps
in
the
media
thread
:
-
If frame .
[[owner]]
is not equal to rtcObject , abort these steps and return a promise resolved with undefined. A processor cannot create frames, or move frames between streams. -
If the frame ’s
timestamp
is equal to or larger than rtcObject .[[lastReceivedFrameTimestamp]]
, abort these steps and return a promise resolved with undefined. A processor cannot reorder frames, although it may delay them or drop them. -
Set rtcObject .
[[lastReceivedFrameTimestamp]]
to the frame ’stimestamp
. -
Enqueue the frame for processing as if it came directly from the encoded data source, by running one of the following
steps:steps in the media thread :-
If rtcObject is a
RTCRtpSender
, enqueue it to rtcObject ’spacketizer, to be processed in parallel .packetizer. -
If rtcObject is a
RTCRtpReceiver
, enqueue it to rtcObject ’sdecoder, to be processed in parallel .decoder.
-
-
Return a promise resolved with undefined.
3.2. Extension attribute
A
RTCRtpTransform
has
two
private
slots
called
[[readable]]
and
[[writable]]
.
The
transform
getter
steps
are:
-
Return this .
[[transform]]
.
The
transform
setter
steps
are:
-
Let transform be the argument to the setter.
-
Let checkedTransform set toIf transformif itis notnull
, run the following steps:or to an identityIf transform
stream.[[readable]]
is locked , throwotherwise.aTypeError
.-
Let reader be the result of gettingGet areaderwriter forcheckedTransformtransform .[[readable]][[writable]] -
Let writer be the result of gettingGet awriterreader forcheckedTransformtransform .[[writable]][[readable]]
-
InitializeQueue a task in this 's media thread to run the following steps:let
newPipeToControllerpipeToControllertobe a newAbortController
.-
If thisLet internalTransform be an identity transform stream .[[pipeToController]] is not null, run the following steps: -
If transform is an
AddSFrameTransformthe chainalgorithmstreamto this . [[pipeToController]] .signal.given transform . -
If transform is an
signal abortRTCRtpScriptTransformthis . [[pipeToController]] .signal. -
Else, runRun the chain transform algorithmsteps. Setwith this., internalTransform .[[pipeToController]][[readable]][[writable]]
toandnewPipeToControllerpipeToController .
-
Set this .
[[transform]]
to transform .
The
chain
transform
algorithm
,
given
rtcObject
,
readable
,
writable
and
pipeToController
,
runs
these
steps
are
defined
as:
in
rtcObject’s
media
thread
:
-
If
newPipeToControllerpipeToController’saborted flag is true, abort these steps. -
ReleaseIfreaderrtcObject .[[pipeToController]]
is notnull
, run the following steps:-
ReleaseAdd the chain transform algorithm withwriterrtcObject , readable , writable and pipeToController , to rtcObject .[[pipeToController]]
.signal. -
Assert that newPipeToController is the same object assignal abort rtcObject .[[pipeToController]]
.signal..
-
-
Else run the following steps:
-
Call pipeTo with rtcObject .
[[readable]][[source]]checkedTransform . [[writable]] ,writable , preventClose equal to false, preventAbort equal to false, preventCancel equal to true andnewPipeToControllerpipeToController .signal. -
Call pipeTo with
checkedTransform . [[readable]] ,readable , rtcObject .[[writable]][[sink]]newPipeToControllerpipeToController .signal.
-
-
Set rtcObject .
[[pipeToController]]
to pipeToController .
This algorithm is defined so that transforms can be updated dynamically. There is no guarantee on which frame will happen the switch from the previous transform to the new transform. If a new transform overwrites an old transform, all frames will go either through the old or the new transform from the source to the sink.
If
a
web
application
sets
the
transform
synchronously
at
creation
of
the
RTCRtpSender
(for
instance
when
calling
addTrack),
the
transform
will
receive
the
first
frame
generated
by
the
RTCRtpSender
's
encoder.
Similarly,
if
a
web
application
sets
the
transform
synchronously
at
creation
of
the
RTCRtpReceiver
(for
instance
when
calling
addTrack,
or
at
track
event
handler),
the
transform
will
receive
the
first
full
frame
generated
by
the
RTCRtpReceiver
's
packetizer.
4. SFrameTransform
enum {
SFrameTransformRole ,
"encrypt" };
"decrypt" dictionary {
SFrameTransformOptions SFrameTransformRole = "encrypt"; };
role typedef [EnforceRange ]unsigned long long ;
SmallCryptoKeyID typedef (SmallCryptoKeyID or bigint ); [
CryptoKeyID Exposed =(Window ,DedicatedWorker )]interface {
SFrameTransform constructor (optional SFrameTransformOptions = {});
options Promise <undefined >setEncryptionKey (CryptoKey ,
key optional CryptoKeyID ); };
keyID ;SFrameTransform includes GenericTransformStream ;
The
new
SFrameTransform(
options
)
constructor
steps
are:
-
Set this .
[[role]]
to options ["role
"]. Let sframeTransform be a SFrame transform stream given this .
Set this .
[[readable]]
to sframeTransform .[[readable]]
.Set this .
[[writable]]
to sframeTransform .[[writable]]
.
4.1. Algorithms
A SFrame transform stream given transform is created by running the following steps:
Let transformAlgorithm be an algorithm which takes a frame as input and runs the SFrame transform algorithm with
thistransform and frame .-
Set
thistransform .[[transform]][[sframeTransform]]TransformStream
. -
Set up
this .transform .[[transform]][[sframeTransform]] -
Let options be the method’s first argument. Set this . [[role]] to options [" role "]. Set this . [[readable]] to this . [[transform]] . [[readable]] . Set this . [[writable]] toReturnthissframeTransform .[[transform]] . [[writable]] .
The
SFrame
transform
algorithm,
algorithm
,
given
sframe
sframeTransform
as
a
SFrameTransform
object
and
frame
,
runs
these
steps:
-
Let role be
sframesframeTransform .[[role]]
. -
If frame .
[[owner]]
is aRTCRtpSender
, set role to 'encrypt'. -
If frame .
[[owner]]
is aRTCRtpReceiver
, set role to 'decrypt'. -
Let data be undefined.
-
If frame is a
BufferSource
, set data to frame . -
If frame is a
RTCEncodedAudioFrame
, set data to frame .data
-
If frame is a
RTCEncodedVideoFrame
, set data to frame .data
-
If data is undefined, abort these steps.
-
Let buffer be the result of running the SFrame algorithm with data and role as parameters. This algorithm is defined by the SFrame specification and returns an
ArrayBuffer
. -
If frame is a
BufferSource
, set frame to buffer . -
If frame is a
RTCEncodedAudioFrame
, set frame .data
to buffer . -
If frame is a
RTCEncodedVideoFrame
, set frame .data
to buffer . -
Enqueue frame in
sframesframeTransform .[[transform]]
.
4.2. Methods
The
setEncryptionKey(
key
,
keyID
)
method
steps
are:
-
Let promise be a new promise .
-
If keyID is a
bigint
which cannot be represented as a integer between 0 and 2 64 -1 inclusive, reject promise with aRangeError
exception. -
Otherwise, in parallel , run the following steps:
-
Set key with its optional keyID as key material to use for the SFrame transform
algorithm,algorithm , as defined by the SFrame specification . -
If setting the key material fails, reject promise with an
InvalidModificationError
exception and abort these steps. -
Resolve promise with undefined.
-
-
Return promise .
5. RTCRtpScriptTransform
// New enum for video frame types. Will eventually re-use the equivalent defined // by WebCodecs.enum {
RTCEncodedVideoFrameType ,
"empty" ,
"key" , };
"delta" dictionary {
RTCEncodedVideoFrameMetadata long long ;
frameId sequence <long long >;
dependencies unsigned short ;
width unsigned short ;
height long ;
spatialIndex long ;
temporalIndex long ;
synchronizationSource sequence <long >; }; // New interfaces to define encoded video and audio frames. Will eventually // re-use or extend the equivalent defined in WebCodecs. [
contributingSources Exposed =(Window ,DedicatedWorker )]interface {
RTCEncodedVideoFrame readonly attribute RTCEncodedVideoFrameType ;
type readonly attribute unsigned long long ;
timestamp attribute ArrayBuffer ;
data RTCEncodedVideoFrameMetadata (); };
getMetadata dictionary {
RTCEncodedAudioFrameMetadata long ;
synchronizationSource sequence <long >; }; [
contributingSources Exposed =(Window ,DedicatedWorker )]interface {
RTCEncodedAudioFrame readonly attribute unsigned long long ;
timestamp attribute ArrayBuffer ;
data RTCEncodedAudioFrameMetadata (); }; // New interfaces to expose JavaScript-based transforms. [
getMetadata Exposed =DedicatedWorker ]interface :
RTCTransformEvent Event {readonly attribute RTCRtpScriptTransformer ; };
transformer partial interface DedicatedWorkerGlobalScope {attribute EventHandler ; }; [
onrtctransform Exposed =DedicatedWorker ]interface {
RTCRtpScriptTransformer ; ;readonly attribute ReadableStream readable ;readonly attribute WritableStream writable ;readonly attribute any options ; }; [Exposed =Window ]interface {
RTCRtpScriptTransform constructor (Worker ,
worker optional any ,
options optional sequence <object >); };
transfer
5.1. Operations
The
new
RTCRtpScriptTransform(
worker
,
options
,
transfer
)
constructor
steps
are:
-
Set
t1 to an identity transform stream . Set t2 to an identity transform stream . Set this . [[writable]] to t1 . [[writable]] . Setthis .[[readable]][[worker]]t2worker .[[readable]] . -
Let serializedOptions be the result of StructuredSerializeWithTransfer ( options , transfer ).
-
Let serializedReadable be the result of StructuredSerializeWithTransfer ( t1 . [[readable]] , « t1 . [[readable]] »). Let serializedWritable be the result of StructuredSerializeWithTransfer ( t2 . [[writable]] , « t2 . [[writable]] »).Queue a task on the DOM manipulation task source worker ’s global scope to run the following steps:-
Let
transformerOptionstransformer bethe result of StructuredDeserializea newRTCRtpScriptTransformer
.( serializedOptions , the current Realm). -
Let
readabletransformerOptions be the result of StructuredDeserialize (serializedReadableserializedOptions , the current Realm). -
LetSetwritable be the result of StructuredDeserialize (transformer .[[options]]
toserializedWritable , the current Realm).transformerOptions . -
Let transformer
be a new RTCRtpScriptTransformer ..[[t1]]
to an identity transform stream . -
SetLet transformer .[[options]][[t2]]transformerOptions .an identity transform stream . -
Set transformer .
[[readable]]
toreadabletransformer .[[t1]]
.[[readable]]
. -
Set transformer .
[[writable]]
towritabletransformer .[[t2]]
.[[writable]]
. -
Let event be the result of creating an event with
RTCTransformEvent
. -
Set event .type attribute to "rtctransform".
-
Set event .transformer to transformer .
-
Set this .
[[transformer]]
to transformer . Dispatch event
onto worker ’s global scope.
-
// FIXME: Describe error handling (worker closing flag true at RTCRtpScriptTransform creation time. And worker being terminated while transform is processing data).
A script transform stream given transform is created by running the following steps:
Set t1 to an identity transform stream .
Set t2 to an identity transform stream .
Set scriptTransform .
[[writable]]
to t1 .[[writable]]
.Set scriptTransform .
[[readable]]
to t2 .[[readable]]
.Let serializedReadable be the result of StructuredSerializeWithTransfer ( t1 .
[[readable]]
, « t1 .[[readable]]
»).Let serializedWritable be the result of StructuredSerializeWithTransfer ( t2 .
[[writable]]
, « t2 .[[writable]]
»).Queue a task on the DOM manipulation task source transform .
[[worker]]
's global scope to run the following steps:Let readable be the result of StructuredDeserialize ( serializedReadable , the current Realm).
Let writable be the result of StructuredDeserialize ( serializedWritable , the current Realm).
Call pipeTo with readable , transform .
[[transformer]]
.[[t1]]
.[[writable]]
.Call pipeTo with transform .
[[transformer]]
.[[t2]]
.[[readable]]
and writable .
Return scriptTransform .
5.2. Attributes
A
RTCRtpScriptTransformer
has
three
private
slots
called
[[options]]
,
[[readable]]
and
[[writable]]
.
The
options
getter
steps
are:
-
Return this .
[[options]]
.
The
readable
getter
steps
are:
-
Return this .
[[readable]]
.
The
writable
getter
steps
are:
-
Return this .
[[writable]]
.
6. Privacy and security considerations
This API gives Javascript access to the content of media streams. This is also available from other sources, such as Canvas and WebAudio.
However, streams that are isolated (as specified in [WEBRTC-IDENTITY] ) or tainted with another origin, cannot be accessed using this API, since that would break the isolation rule.
The API will allow access to some aspects of timing information that are otherwise unavailable, which allows some fingerprinting surface.
The API will give access to encoded media, which means that the JS application will have full control over what’s delivered to internal components like the packetizer or the decoder. This may require additional care with auditing how data is handled inside these components.
For instance, packetizers may expect to see data only from trusted encoders, and may not be audited for reception of data from untrusted sources.
7. Examples
See the explainer document .