This is the reverse of the "stream to browser" path.
The pipeline is:
browser -> WebRtcTrackReceiver -> VideoDecoder -> MultiplexPacketEncoder -> fileIt matters because it proves the receive side is real: not just signalling, not just an open peer connection, but actual browser-originated media getting decoded and written to disk.
The current media-recorder sample:
PeerSessionActivevideoReceiver()That lazy mux open is important. It means the output file takes its real dimensions and pixel format from the stream that actually arrived, instead of from hard-coded guesses.
It also means the sample can survive a browser joining mid-GOP. If the first packets are not decodable yet, it waits for the first usable keyframe instead of creating garbage output.
The session side:
session.IncomingCall += [&](const std::string&) {
session.accept();
};
session.StateChanged += [&](wrtc::PeerSession::State state) {
if (state == wrtc::PeerSession::State::Active)
startRecording();
else if (state == wrtc::PeerSession::State::Ended)
stopRecording();
};The receive path is driven from the receiver emitter:
session->media().videoReceiver().emitter +=
packetSlot(this, &MediaRecorder::onEncodedVideo);Then:
VideoDecoder turns encoded H.264 into decoded framesMultiplexPacketEncoder writes MP4 outputEven though the recorder is receive-only, the session config still declares the codec it is prepared to receive cleanly. The WebRTC layer is now strict about codec setup instead of inventing defaults behind your back.
That is a good thing. It makes negotiation and failure modes honest.
Ended so the file is finalized properly.Those three rules are why the sample is useful instead of just being a demo that "usually works."
Any browser client that can:
can drive this path.
The intended local pairing is with the Symple player stack, but the important part is the protocol shape, not the specific UI.
media-recorder for the runnable sample