Dynamically Swap WebRTC Tracks with replaceTrack
This article explains how developers can dynamically swap audio or
video tracks during an active WebRTC session using the
RTCRtpSender.replaceTrack() method. By utilizing this API,
developers can switch camera sources, transition to screen sharing, or
temporarily mute a stream seamlessly without the overhead of
renegotiating the peer connection. This guide covers the core concepts,
practical implementation steps, and key considerations for deploying
this feature in real-time communication applications.
Why Use replaceTrack()?
Traditionally, changing a media track during a live WebRTC call required removing the old track, adding the new one, and performing a full Session Description Protocol (SDP) offer/answer renegotiation. This process causes noticeable lag, screen flickering, and unnecessary network overhead.
The RTCRtpSender.replaceTrack() method resolves this by
allowing developers to swap the media source directly on the RTP sender.
Because the media pipeline remains intact, the track is swapped
instantly without triggering renegotiation, ensuring a smooth experience
for the end-user.
How to Implement replaceTrack()
To swap a media track, you must access the specific
RTCRtpSender object responsible for transmitting that track
and pass the new media track to its replaceTrack()
method.
Here is a step-by-step implementation:
1. Identify the RTCRtpSender
When you add a track to an RTCPeerConnection, WebRTC
returns an RTCRtpSender object. You can store this
reference or retrieve it from the connection’s active senders list.
// Find the sender responsible for the video track
const senders = peerConnection.getSenders();
const videoSender = senders.find(sender => sender.track && sender.track.kind === 'video');2. Acquire the New Media Track
Obtain the replacement track using getUserMedia or
getDisplayMedia. For example, this snippet requests access
to a secondary camera or a screen share.
async function getNewVideoTrack() {
try {
const newStream = await navigator.mediaDevices.getUserMedia({
video: { deviceId: { exact: "secondary-camera-id" } }
});
return newStream.getVideoTracks()[0];
} catch (error) {
console.error("Failed to acquire new media track:", error);
}
}3. Replace the Track
Call replaceTrack() on the target sender. This method is
asynchronous and returns a Promise that resolves once the track has been
successfully swapped.
async function switchCamera() {
const newTrack = await getNewVideoTrack();
if (videoSender && newTrack) {
try {
// Swap the old track with the new track
await videoSender.replaceTrack(newTrack);
console.log("Track successfully swapped!");
} catch (error) {
console.error("Failed to replace track:", error);
}
}
}Key Considerations for Developers
- Matching Media Kinds: The new track must be of the same kind (audio or video) as the track it is replacing. You cannot replace an audio track with a video track or vice versa.
- Muting and Disabling Tracks: If you pass
nulltoreplaceTrack(), the sender will stop transmitting media entirely without tearing down the connection. This is highly effective for implementing mute or “stop video” features. - Resolution and Aspect Ratio Changes: When swapping video sources (e.g., from a webcam to screen sharing), the video resolution and frame rate will change. WebRTC handles this dynamically, but you must ensure your UI layout can adapt to sudden aspect ratio changes.
- Garbage Collection: Always stop the old track by
calling
oldTrack.stop()after replacing it to release the user’s hardware (like turning off the camera LED light).