1
WebRTC
Chairs:
Tuesday November 11, 2025
16:30 - 18:00 JST
TPAC 2025
Kobe Japan
Hybrid meeting
W3C WG IPR Policy
2
W3C meeting rules & etiquette
Meeting : 11 November 2025, 16:30–18:00 JST
This meeting is run under the W3C Code of Ethics and Professional Conduct. Please be respectful, inclusive, and kind.
IRC chat: https://irc.w3.org in channel #webrtc
Reminder to the chairs to ensure that the automated captions have been activated and press record.
3
About Today’s Meeting
4
Virtual Meeting Tips (Zoom)
5
For Discussion Today (Tuesday)
Time control:
6
State of the WG
(Harald)
7
The Web Ecosystem Environment
External Environment
9
Activity since TPAC 2024
10
Implementation activity
11
Things that seem stable (and used)
12
Things that have stabilized
Major new or expanded topics
14
The Transport Exploration
More low-level access to transport desired
Transport Exploration
(Tony and Jan-Ivar)
16
RtcTransport packet-level API
Discussion Group Update
(Tony)
17
RtcTransport - Status Update
18
RtcTransport - Targeted usecases
Basically same as before, but not RTP specific:
Allow Ultra-low latency media streaming over UDP, with strong app control
19
RtcTransport - Examples of usecases
20
RtcTransport - What's been figured out
21
RtcTransport - Things in progress
22
RtcTransport - Conclusion
23
WebTransport over WebRTC ICE?
(Jan-Ivar)
24
WebTransport over WebRTC ICE? (Jan-Ivar)
WebTransport(url, options) solves server ↔ client head-of-line blocking, with browser CC and flow control. Its streams-based API + estimatedSendRate let apps fill send queues efficiently for maximum throughput on the network process.
What if we could leverage this API over WebRTC ICE? It might look like this:
const pc = new RTCPeerConnection();
const wt = pc.createWebTransport(options); // ← new idea
pc.onnegotiationneeded = () => { /* existing webrtc signaling */ };
pc.onicecandidate = () => { /* existing trickle ICE exchange */ };
await wt.ready; // WebTransport resolves promises instead of firing events
Benefit: web developers get the same (send/receive) WebTransport API surface, with nothing to relearn. It just skips the constructor (and fetch). Same semantics
25
WebTransport over WebRTC ICE (Jan-Ivar)
But importantly, such an API can likely be shimmed today on top of createDataChannel() and stats like availableOutgoingBitrate.��Data channels also have send queues and are subject to browser CC.
E.g. datagrams might be sent over unreliable/unordered data channels.�
How well this would perform is the question, but isn’t answered by API.
�The point here is that starting with a new API to solve performance seems like a category mistake. It’s not pinpointing the performance bottlenecks.
Is it JS? Vendor CC? Why would vendors make different decisions per API?
26
Encoder Complexity API
(Erik)
27
Encoder Complexity API
Proposed change (https://github.com/w3c/webrtc-extensions/issues/191):
enum RTCEncodeComplexityMode {
"low",
"normal",
"high",
};
partial dictionary RTCRtpEncodingParameters {
RTCEncodeComplexityMode encodeComplexityMode = "normal";
};
28
Encoder Complexity API
The what:
29
Encoding 1
Encoding 2
GetStats()
setParameters()
Application
Encode Time
QP
Resolution
FPS
Complexity
Encoder Complexity API
Notable caveats:
30
Encoder Complexity API
Raised objections
Ultimately, the application has the best knowledge of the properties of each stream and if power usage is important in this setting or not - and also how that affects remote quality and power usage => Let the application decide!
31
The <usermedia> element
Marian Harbach, Google Chrome
Using gUM and permissions - A balancing act
| User wants a prompt | User does not want a prompt |
Prompt is shown | ✅ | 🤬 |
Prompt is not shown | 😫 | ✅ |
Solution avenues
<usermedia> element
Guaranteeing the signal integrity
Real-life example usage
Links
https://github.com/WICG/PEPC/blob/main/usermedia_element.md
Discussion thread: https://github.com/WICG/PEPC/issues/62
Old explainer: https://github.com/WICG/PEPC/blob/main/explainer.md
Dev instructions: https://github.com/WICG/PEPC/blob/main/HOWTO.md
Spec (WIP): https://wicg.github.io/PEPC/permission-elements.html
Current discussion topics
End of Tuesday Slides
40
Thursday Slides follow
41
42
WebRTC
Chairs:
Thursday November 13, 2025
09:00 - 10:30 JST
TPAC 2025
Kobe Japan
Hybrid meeting
W3C WG IPR Policy
43
W3C meeting rules & etiquette
Meeting : 13 November 2025, 09:00–10:30 JST
This meeting is run under the W3C Code of Ethics and Professional Conduct. Please be respectful, inclusive, and kind.
IRC chat: https://irc.w3.org in channel #webrtc
44
About Today’s Meeting
45
Virtual Meeting Tips (Zoom)
46
For Discussion Today (Thursday)
Time control:
47
WebRTC Event Log uploads
with the Reporting API
(Guido)
48
WebRTC Event Logs
Diagnostic logs that can be generated by libwebrtc
No personally identifiable information or user-generated content
Significant overlap with stats, but structured for offline analysis
49
WebRTC Event Logs
Example use cases:
Binary file format
Open-source tools available to analyze them
50
WebRTC Event Logs
Native applications can generate these logs using native APIs
In Chrome, the user can enable them locally via chrome://webrtc-internals
51
WebRTC Event Logs - Reporting API
We propose an API to allow collecting and uploading with the Reporting API
52
WebRTC Event Logs - Reporting API
Configuration via HTTP Headers
Reporting-Endpoints: main-endpoint="https://reports.example.com/main", default="https://reports.example.com/default"
Content-Security-Policy: script-src 'self'; report-to main-endpoint;
53
WebRTC Event Logs - Reporting API
Some report types also support the ReportingObserver JavaScript API
54
WebRTC Event Logs - Proposal
Define a report type for WebRTC Event logs
Challenge
55
WebRTC Event Logs - Proposal
Applications do not always want to enable logging. Use a JS API to explicitly enable it:
partial interface RTCPeerConnection {
undefined startEventLogging(RTCEventLoggingOptions options);
undefined stopEventLogging();
undefined discardEventLogging();
}
dictionary RTCEventLoggingOptions {
bool upload = true;
}
56
WebRTC Event Logs - Authorization
Let the user decide if event logging is allowed for applications.
User agents can implement a number of strategies:
Since the Reporting API is best effort by design , the proposed JS part does not return errors.
57
Discussion
58
SFrame
(Youenn)
59
SFrame status
SFrame draft presented at IETF 124
60
SFrameTransform API (1/4)
Issues
61
interface mixin SFrameKeyManagement {
Promise<undefined> setEncryptionKey(CryptoKey key, optional CryptoKeyID keyID);
attribute EventHandler onerror;
};
interface SFrameTransform : EventTarget {
constructor(optional SFrameTransformOptions options = {});
};
SFrameTransform includes SFrameKeyManagement;
SFrameTransform API (2/4)
Solution for per-packet/per-frame: introduce an enum!
enum SFrameType{
"per-frame",
"per-packet"
};
�dictionary SFrameTransformOptions {
required SFrameType type;
required SFrameCipherSuite cipherSuite;
};
62
SFrameTransform API (3/4)
Solution for decrypter/encrypter: introduce more interfaces!
interface mixin SFrameEncrypterManagement {
Promise<CryptoKeyID> setEncryptionKey(CryptoKey key, optional CryptoKeyID keyID);
};�SFrameEncrypterStream includes SFrameEncrypterManagement;
�interface mixin SFrameDecrypterManagement {
Promise<undefined> addDecryptionKey(CryptoKey key, CryptoKeyID keyID);
Promise<undefined> removeDecryptionKey(CryptoKeyID keyID);
attribute EventHandler onerror;
};�SFrameDecrypterStream includes SFrameDecrypterManagement;
63
SFrameTransform API (4/4)
64
interface SFrameReceiverTransform : EventTarget {
constructor(SFrameTransformOptions options);
};
SFrameReceiverTransform includes SFrameDecrypterManagement;
�
interface SFrameSenderTransform {
constructor(SFrameTransformOptions options);
};
SFrameSenderTransform includes SFrameEncrypterManagement;
partial interface RTCRtpSender {
attribute (SFrameSenderTransform or RTCRtpScriptTransform)? transform;
};
partial interface RTCRtpReceiver {
attribute (SFrameReceiverTransform or RTCRtpScriptTransform? transform;
};
SFrame + RTCRtpScriptTransform (1/3)
What if I want to use both SFrame native support & RTCRtpScriptTransform?
onrtctransform = transformer => {
const decrypter = new SFrameDecryterStream();
transformer.readable
.pipeThrough(new TransformStream(myMetadataTransform))
.pipeThrough(decrypter)
.pipeTo(writable);
};
65
SFrame + RTCRtpScriptTransform (2/3)
What if I want to use per-packet SFrame with RTCRtpScriptTransform?
// Expose SFrameReceiverTransform and SFrameSenderTransform to worker AND
partial interface RTCRtpScriptTransformer {
attribute (SFrameReceiverTransform or SFrameSenderTransform)? sframe;
};�
onrtctransform = transformer => {
transformer.sframe = new SFrameReceiverTransform({ type: ‘per-packet’, cipherSuite: …’})
transformer.readable
.pipeThrough(new TransformStream(myMetadataTransform))
.pipeTo(transformer.writable);
};
No request so far, just a proof of concept
66
SFrame + RTCRtpScriptTransform (3/3)
What if I want to do my own custom SFrame-in-JS scheme but per-packet?
No request so far, just a proof of concept
67
Discussion
68
Encoded Source update
(Guido)
69
Encoded Source
Last year we presented a proposal for an encode source API for RTCRtpSender
Original use case: Peer-assisted media forwarding
70
Server
Encoded Source (RTCRtpSender)
Similar model to Encoded Transform
More control signals, state and error handling
71
Encoded Source (RTCRtpSender) - IDL
partial interface RTCRtpSender {
Promise<undefined> createEncodedSource(
Worker worker, optional any options, optional sequence<object> transfer);
}
partial interface DedicatedWorkerGlobalScope {
attribute EventHandler onrtcsenderencodedsource;
}
[Exposed=DedicatedWorker] interface RTCRtpSenderEncodedSourceEvent : Event {
readonly attribute RTCRtpSenderEncodedSource encodedSource;
};
72
Encoded Source (RTCRtpSender) - IDL
[Exposed=DedicatedWorker] interface RTCRtpSenderEncodedSource {
// Accepts RTCRtpEncoded{Video|Audio}Frame, rejects on incorrect frames
readonly attribute WritableStream writable;
attribute EventHandler onkeyframerequest;
attribute EventHandler onbandwidthestimate;
readonly attribute any options;
readonly attribute unsigned long long droppedFrames;
readonly attribute double expectedSendQueueTime; // milliseconds
readonly attribute long allocatedBitrate; // bits per second
readonly attribute long availableOutgoingBitrate;
};
73
Encoded Source - Status
No implementation or completed spec
We have identified two additional use cases:
74
Encoded Source update - Receiver
In peer-assisted media delivery media flows through redundant paths
75
Encoded Source update - Receiver
76
RTCRtp
Receiver 1
RTCRtp
Receiver 2
Output
RTCRtp Receiver
P2P Node
Use Encoded Transform to read frames from input receivers
Custom processing to produce output frames (discard duplicates, adjust metadata)
Write to Output Receiver using
RTCRtpReceiverEncodedSource
To media element / web audio
Encoded Source update - Receiver
An encoded source for an RTCRtpReceiver is simpler than for an RTCRtpSender
77
Encoded Source update - Receiver IDL
partial interface RTCRtpReceiver {
Promise<undefined> createEncodedSource(
Worker worker, optional any options, optional sequence<object> transfer);
}
partial interface DedicatedWorkerGlobalScope {
attribute EventHandler onrtcreceiverencodedsource;
}
[Exposed=DedicatedWorker] interface RTCRtpReceiverEncodedSourceEvent : Event {
readonly attribute RTCRtpReceiverEncodedSource encodedSource;
};
[Exposed=DedicatedWorker] interface RTCRtpReceiverEncodedSource {
// Accepts RTCRtpEncoded{Video|Audio}Frame, rejects on incorrect frames
readonly attribute WritableStream writable;
};
78
Encoded Source update - external codecs
Application encodes using WebCodecs or other codec and sends the result over the network using RTCPeerConnection
Clear use case for RTCRtpSenderEncodedSource
Problem: We have no way to create a new encoded frame
Solution: new constructor
Issues: some metadata fields may need to be overwritten by the peer connection
79
Encoded Source update - external codecs
dictionary RTCEncodedVideoFrameOptions {
RTCEncodedVideoFrameMetadata metadata;
ArrayBuffer data;
};
[Exposed=(Window,DedicatedWorker), Serializable]
interface RTCEncodedVideoFrame {
constructor(RTCEncodedVideoFrameOptions options);
constructor(RTCEncodedVideoFrame originalFrame,
optional RTCEncodedVideoFrameOptions options = {});
...
};
80
Discussion
81
Camera intrinsics in MediaTrackSettings
(Rik)
82
Camera intrinsics in MediaTrackSettings
Meta Quest browser gives sessions access to its front facing cameras.
These cameras are offset and rotated wrt the screen and developers need this information to match the camera image with the screen
83
translation
rotation
Camera intrinsics in MediaTrackSettings
Proposal:
dictionary MediaTrackSettings {
…
DOMPointReadOnly lensTranslation;
DOMPointReadOnly lensRotation;
sequence<float> lensCalibration;
sequence<float> lensDistortion;
}
Definition of the parameters will match this spec.
84
Mediacapture-transform and track transfer
(Guido and Jan-Ivar)
85
Track transfer in MSTP/VTG (Guido)
Mediacapture-transform spec requires track transfer for MSTP and VTG
86
Track transfer in MSTP/VTG (Guido)
Proposal
87
Aid transition to MSTP in worker (Jan-Ivar)
Using MediaStreamTrackProcessor requires transferring a MediaStreamTrack to a worker:
� // assume a worker accepting {cmd, value} messages� worker.postMessage({cmd: "track", value: track}, [track]);�
Changes to the source track must go through the worker as well:
� range.oninput = () => worker.postMessage({cmd: "applyConstraints",
value: {height: range.value});
�But right now, websites must straddle spec & nonstandard MSTP on window (transient):
� if (safari) {� range.oninput = () => worker.postMessage({cmd: "applyConstraints",
value: {height: range.value});
} else if (chrome) {
range.oninput = () => track.applyConstraints({height: range.value});
}
88
Aid transition to MSTP in worker (Jan-Ivar)
Can track clones help? No they have independent settings. But what if we had:
// not a proposal� const mirroredTrack = track.clone({mirror: true});�
worker.postMessage({cmd: "track", value: track}, [track]);� range.oninput = () => mirroredTrack.applyConstraints({height: range.value});
stop.onclick = () => mirroredTrack.stop();
mute.onclick = () => mirroredTrack.enabled = !mute.checked;�
Mirrored tracks would share settings. Stop one stops all. With one transferred, this would accept input from worker and main alike, which would solve this.��But adds complexity and invites racy code between worker and main thread.�So not proposing this. A more targeted shim might do…
89
Aid transition to MSTP in worker (Jan-Ivar)
Using mirrorTrack() from https://jan-ivar.github.io/polyfills/mirrortrack.js:
// returns a real track clone that sends {cmd, value} messages to sync up� const mirroredTrack = mirrorTrack(original, {send: msg => worker.postMessage(msg)});
� worker.postMessage({cmd: "track", value: track}, [track]);� range.oninput = () => mirroredTrack.applyConstraints({height: range.value});
stop.onclick = () => mirroredTrack.stop();
mute.onclick = () => mirroredTrack.enabled = !mute.checked;�
A seamless drop-in for a real track, as it is a real track (clone), with synchronous observable state. applyConstraints will succeed/fail reliably on same input.
This still assumes a worker that accepts {cmd:"applyConstraints" | "setEnabled" | "stop"}, but hides browser difference from the rest of the app.
90
Aid transition to MSTP in worker (Jan-Ivar)
Standard example w/fallback (runs in Safari and Chrome): https://jsfiddle.net/jib1/0mxc68s2/ — Excerpt:
� const stream = await navigator.mediaDevices.getUserMedia({video: true});
const [originalTrack] = stream.getVideoTracks(); let track;
try {
// Try standard first + use mirrored control track
track = mirrorTrack(originalTrack, {send: msg => worker.postMessage(msg)});
worker.postMessage({cmd: "track", value: originalTrack}, [originalTrack]);
const {data} = await new Promise(r => worker.onmessage = r);
video.srcObject = new MediaStream([data.track]);
} catch (e) {
if (e.name != "DataCloneError") throw e;
// Fall back to nonstandard
track.stop(); track = originalTrack;
const {readable} = new MediaStreamTrackProcessor({track});
const mstg = new MediaStreamTrackGenerator({kind: "video"});
worker.postMessage({cmd: "streams", value: {readable, writable: mstg.writable}},
{transfer: [readable, mstg.writable]});
video.srcObject = new MediaStream([mstg]);
}� // agnostic code from here on out� mute.onclick = () => { track.enabled = !mute.checked; }
stop.onclick = () => track.stop();
range.oninput = () => track.applyConstraints({height: range.value});
91
Discussion
92
Start of Joint Meeting Thursday Slides
93
94
Media Chairs:
Joint Meeting�Thursday November 13, 2025
13:45 - 15:00 JST
TPAC 2025
Kobe Japan
Hybrid meeting
WebRTC Chairs:
Media + WebRTC
W3C WG IPR Policy
95
W3C meeting rules & etiquette
Meeting : 13 November 2025, 13:45–15:00 JST
This meeting is run under the W3C Code of Ethics and Professional Conduct. Please be respectful, inclusive, and kind.
IRC chat: https://irc.w3.org in channel #mediawg
96
About Today’s Meeting
97
Virtual Meeting Tips (Zoom)
98
Thursday - Joint Meeting Media + WebRTC
Time control:
99
Mediacapture-transform for Audio
(Guido)
100
Mediacapture-transform
Mediacapture-transform allows working with raw media and MediaStreamTracks
Raw media uses interfaces defined in WebCodecs
101
Mediacapture-transform
MediaStreamTrackProcessor provides access to raw media from a MediaStreamTrack on a Worker
const processor = new MediaStreamTrackProcessor({ track: videoTrack });
const reader = processor.readable.getReader();
while (true) {
const frameOrDone = await reader.read();
If (frameOrDone.done) break;
DoSomethingWith(frameOrDone.value);
}
102
Mediacapture-transform
With VideoTrackSource it is possible to produce a track , given frames:
const generator = new VideoTrackGenerator();
connectTrackToSomeSink(generator.track);
const writer = generator.readable.getReader();
while (true) {
const frame = createVideoFrame();
try {
await writer.write(frame);
} catch (e) {
frame.close();
}
}
103
Mediacapture-transform
MSTG and VTG can be used together to produce a transformed track:
const generator = new VideoTrackGenerator();
// generator.track has the output track
const processor = new MediaStreamTrackProcessor({ track: inputTrack });
const transformer = new TransformStream({
async transform(inputFrame, controller) {
const outputFrame = createTransformedFrame(inputFrame);
inputFrame.close();
controller.enqueue(outputFrame);
}
});
await processor.readable.pipeThrough(transformer).pipeTo(generator.writable);
104
Mediacapture-transform for Audio
We have consensus to support video tracks
Chrome supports audio tracks, but no consensus yet
Why or why not audio?
105
Mediacapture-transform for Audio
There are some use cases where the mediacapture-transform model thread is a good fit
Examples:
106
Chrome's experience
107
Chrome's experience
mediacapture-transform usage (audio and video) is roughly 50% of AudioWorklet
108
Mediacapture-transform for Audio
Conclusions
109
Decoder failure API
(Steve)
110
Decoder Fallback and Failure Events #146
Status
Special thanks to
111
Problem
Game streaming platforms like Nvidia GeForce Now, Xbox Cloud Gaming, and many others rely on hardware decoding to deliver low-latency, power efficient interactive gaming experiences.
However, there is currently no reliable way for these applications to detect when decoding falls back to software during a stream without prompting users for camera/mic permissions.
This gap makes it difficult to diagnose performance regressions and provide troubleshooting guidance.
112
Goals
Non-Goals
113
Proposed API
partial interface RTCRtpReceiver {
attribute EventHandler ondecoderstatechange;
attribute EventHandler ondecodererror;
};
114
Decoder State Change Event
interface RTCDecoderStateChangeEvent : Event {
// The media timeline reference when the event occurred.
readonly attribute unsigned long rtpTimestamp;
// Identifies the codec in-use after the state change.
//
// Nullable, allowing implementers to decide when to omit for privacy.
readonly attribute RTCRtpCodecParameters? codecParameters;
// powerEfficientDecoder changes primarily based on hardware/software decoder.
// Aligns with MediaCapabilitiesInfo:
// https://www.w3.org/TR/media-capabilities/#media-capabilities-info
//
// Also available through getStats().
// https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-powerefficientdecoder
//
// // Nullable, allowing implementers to decide when to omit for privacy.
readonly attribute boolean? powerEfficientDecoder;
};
115
Decoder State Change Event
116
const pc = new RTCPeerConnection();
pc.addEventListener('track', (event) => {
event.receiver.addEventListener('decoderstatechange', (ev) => {
// Adapt behavior based on software decoder usage.
if (!ev.powerEfficientDecoder) {
showToast('Playback quality may be reduced'); // Notify the user.
adjustQuality('low'); // Lower resolution or disable heavy post-processing.
// Log telemetry signal with codec and RTP timestamp.
let codecParams = 'unknown';
if (ev.codecParameters) {
codecParams = `${ev.codecParameters.mimeType}|${ev.codecParameters.sdpFmtpLine}`;
}
logMetric(`Fallback: codec=${codecParams}, time=${ev.rtpTimestamp}`);
Decoder Error Event
interface RTCDecoderErrorEvent : RTCDecoderStateChangeEvent {
// The inherited `RTCDecoderStateChangeEvent` members identify the failing codec.
// Provide a description of the error.
readonly attribute unsigned short errorCode;
readonly attribute USVString errorText;
};
117
Media playback quality
(Markus)
118
119
(Markus Handell, Google Meet, Stockholm)
RTC video recap
Requirement: Reproduce captured cadence of frames
120
Oldie goldie
121
Quality Topic | FPS |
Local video tiles | ✅ |
Remote video tiles | ✅ |
Hitch sensitivity | ⛔ |
Video (isochronous) | ✅ |
Screen share (non-isochronous) | ⛔ |
Accuracy | ✅ |
Frame drops | ✅ |
…in receiver getStats()
122
Quality Topic | hFPS |
Local video tiles | ⛔ |
Remote video tiles | ✅ish |
Hitch sensitivity | ✅ |
Video (isochronous) | ✅ |
Screen share (non-isochronous) | ⛔ |
Accuracy | ⛔ish |
Frame drops | ⛔ |
123
Quality Topic | hFPS |
Local video tiles | ✅ |
Remote video tiles | ✅😇 |
Hitch sensitivity | ✅ |
Video (isochronous) | ✅ |
Screen share (non-isochronous) | ⛔ |
Accuracy | ✅🙋 |
Frame drops | ⛔ |
exposed!
124
Quality Topic | RMSE |
Availability: local video tiles | ✅ |
Availability: remote video tiles | ✅ |
Hitch sensitivity | ✅ |
Video (isochronous) | ✅ |
Screen share (non-isochronous) | ✅ |
Accuracy | ✅ |
Frame drops | ⛔ |
exposed!
Remote end: trick to estimate remote capture time/frame duration.�Assumes RTP timestamp avail at time of display.
125
exposed!
90 kHz for all current codecs…
Proposed IDL
interface VideoPlaybackQuality {
readonly attribute DOMHighResTimeStamp creationTime;
readonly attribute unsigned long totalVideoFrames;
readonly attribute unsigned long droppedVideoFrames;
readonly attribute unsigned long corruptedVideoFrames;
// Harmonic framerate
readonly attribute unsigned long presentedFrameDurationSum;
readonly attribute unsigned long presentedFrameDurationSquaredSum;
// Reproduction jitter RMSE
readonly attribute unsigned long presentedFrameErrorSquaredSum;
readonly attribute unsigned long framesWithCaptureTimestamp;
readonly attribute unsigned long presentedFrameErrorSquaredSumRtp;
readonly attribute unsigned long framesWithRtpTimestamp;
}
126
DEMO TIME!
127
WebCodecs and WebRTC Encoded Transform
(Youenn)
128
129
End of Thursday Slides
130