snapshot of https://github.com/pion/webrtc.git (main, 2026-09-15; blobs > 131072B omitted)
Some checks failed
REUSE Compliance Check / lint (push) Failing after 0s
Browser E2E / Test (push) Failing after 3s
Test / test (1.24) (push) Failing after 0s
Test / test-i386 (1.25) (push) Failing after 0s
Test / test-i386 (1.24) (push) Failing after 0s
Test / test-windows (1.25) (push) Failing after 0s
Test / test-windows (1.24) (push) Failing after 0s
Test / test (1.25) (push) Failing after 0s
Examples Tests / Test (push) Failing after 3s
Test / test-macos (1.25) (push) Failing after 0s
Test / test-macos (1.24) (push) Failing after 0s
Go mod tidy / tidy (push) Failing after 0s
Test / test-wasm (push) Failing after 0s
CodeQL / analyze (push) Failing after 1s
Fuzz / fuzz (push) Failing after 0s

This commit is contained in:
upstream-mirror
2026-09-15 20:06:07 +05:00
commit 8acb1a3a29
394 changed files with 78746 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
# simulcast
demonstrates of how to handle incoming track with multiple simulcast rtp streams and show all them back.
The browser will not send higher quality streams unless it has the available bandwidth. You can look at
the bandwidth estimation in `chrome://webrtc-internals`. It is under `VideoBwe` when `Read Stats From: Legacy non-Standard`
is selected.
## Instructions
### Download simulcast
```
go install github.com/pion/webrtc/v4/examples/simulcast@latest
```
### Open simulcast example page
[jsfiddle.net](https://jsfiddle.net/tz4d5bhj/) you should see two text-areas and two buttons: `Copy browser SDP to clipboard`, `Start Session`.
### Run simulcast, with your browsers SessionDescription as stdin
In the jsfiddle the top textarea is your browser's Session Description. Press `Copy browser SDP to clipboard` or copy the base64 string manually.
We will use this value in the next step.
#### Linux/macOS
Run `echo $BROWSER_SDP | simulcast`
#### Windows
1. Paste the SessionDescription into a file.
1. Run `simulcast < my_file`
### Input simulcast's SessionDescription into your browser
Copy the text that `simulcast` just emitted and copy into second text area
### Hit 'Start Session' in jsfiddle, enjoy your video!
Your browser should send a simulcast track to Pion, and then all 3 incoming streams will be relayed back.
Congrats, you have used Pion WebRTC! Now start building something cool

View File

@@ -0,0 +1,8 @@
/*
SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
SPDX-License-Identifier: MIT
*/
textarea {
width: 500px;
min-height: 75px;
}

View File

@@ -0,0 +1,8 @@
---
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
name: simulcast
description: Example of how to have Pion handle incoming track with multiple simulcast rtp streams and show all them back.
authors:
- Simone Gotti

View File

@@ -0,0 +1,27 @@
<!--
SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
SPDX-License-Identifier: MIT
-->
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
Browser base64 Session Description<br />
<textarea id="localSessionDescription" readonly="true"></textarea> <br />
<button onclick="window.copySDP()">
Copy browser SDP to clipboard
</button>
<br />
<br />
Golang base64 Session Description<br />
<textarea id="remoteSessionDescription"></textarea> <br />
<button onclick="window.startSession()"> Start Session </button><br />
<br />
<div>
Browser stream<br />
<video id="browserVideo" width="200" height="200" autoplay muted></video>
</div>
<div id="serverVideos">
Video from server<br />
</div>

View File

@@ -0,0 +1,110 @@
/* eslint-env browser */
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Create peer conn
const pc = new RTCPeerConnection({
iceServers: [{
urls: 'stun:stun.l.google.com:19302'
}]
})
pc.oniceconnectionstatechange = (e) => {
console.log('connection state change', pc.iceConnectionState)
}
pc.onicecandidate = (event) => {
if (event.candidate === null) {
document.getElementById('localSessionDescription').value = btoa(
JSON.stringify(pc.localDescription)
)
}
}
pc.onnegotiationneeded = (e) =>
pc
.createOffer()
.then((d) => pc.setLocalDescription(d))
.catch(console.error)
pc.ontrack = (event) => {
console.log('Got track event', event)
const video = document.createElement('video')
video.srcObject = event.streams[0]
video.autoplay = true
video.width = '500'
const label = document.createElement('div')
label.textContent = event.streams[0].id
document.getElementById('serverVideos').appendChild(label)
document.getElementById('serverVideos').appendChild(video)
}
navigator.mediaDevices
.getUserMedia({
video: {
width: {
ideal: 4096
},
height: {
ideal: 2160
},
frameRate: {
ideal: 60,
min: 10
}
},
audio: false
})
.then((stream) => {
document.getElementById('browserVideo').srcObject = stream
pc.addTransceiver(stream.getVideoTracks()[0], {
direction: 'sendonly',
streams: [stream],
sendEncodings: [
// for firefox order matters... first high resolution, then scaled resolutions...
{
rid: 'f'
},
{
rid: 'h',
scaleResolutionDownBy: 2.0
},
{
rid: 'q',
scaleResolutionDownBy: 4.0
}
]
})
pc.addTransceiver('video')
pc.addTransceiver('video')
pc.addTransceiver('video')
})
window.startSession = () => {
const sd = document.getElementById('remoteSessionDescription').value
if (sd === '') {
return alert('Session Description must not be empty')
}
try {
console.log('answer', JSON.parse(atob(sd)))
pc.setRemoteDescription(JSON.parse(atob(sd)))
} catch (e) {
alert(e)
}
}
window.copySDP = () => {
const browserSDP = document.getElementById('localSessionDescription')
browserSDP.focus()
browserSDP.select()
try {
const successful = document.execCommand('copy')
const msg = successful ? 'successful' : 'unsuccessful'
console.log('Copying SDP was ' + msg)
} catch (err) {
console.log('Unable to copy SDP ' + err)
}
}

245
examples/simulcast/main.go Normal file
View File

@@ -0,0 +1,245 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// simulcast demonstrates of how to handle incoming track with multiple simulcast rtp streams and show all them back.
package main
import (
"bufio"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"strings"
"time"
"github.com/pion/rtcp"
"github.com/pion/webrtc/v4"
)
// nolint:gocognit, cyclop
func main() {
// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
// Prepare the configuration
config := webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: []string{"stun:stun.l.google.com:19302"},
},
},
}
// Create a new RTCPeerConnection
peerConnection, err := webrtc.NewPeerConnection(config)
if err != nil {
panic(err)
}
defer func() {
if cErr := peerConnection.Close(); cErr != nil {
fmt.Printf("cannot close peerConnection: %v\n", cErr)
}
}()
outputTracks := map[string]*webrtc.TrackLocalStaticRTP{}
// Create Track that we send video back to browser on
outputTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8,
}, "video_q", "pion_q")
if err != nil {
panic(err)
}
outputTracks["q"] = outputTrack
outputTrack, err = webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8,
}, "video_h", "pion_h")
if err != nil {
panic(err)
}
outputTracks["h"] = outputTrack
outputTrack, err = webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8,
}, "video_f", "pion_f")
if err != nil {
panic(err)
}
outputTracks["f"] = outputTrack
if _, err = peerConnection.AddTransceiverFromKind(
webrtc.RTPCodecTypeVideo,
webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionRecvonly},
); err != nil {
panic(err)
}
// Add this newly created track to the PeerConnection to send back video
if _, err = peerConnection.AddTransceiverFromTrack(
outputTracks["q"], webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly}); err != nil {
panic(err)
}
if _, err = peerConnection.AddTransceiverFromTrack(
outputTracks["h"],
webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly},
); err != nil {
panic(err)
}
if _, err = peerConnection.AddTransceiverFromTrack(
outputTracks["f"],
webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly},
); err != nil {
panic(err)
}
// Read incoming RTCP packets
// Before these packets are returned they are processed by interceptors. For things
// like NACK this needs to be called.
processRTCP := func(rtpSender *webrtc.RTPSender) {
rtcpBuf := make([]byte, 1500)
for {
if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil {
return
}
}
}
for _, rtpSender := range peerConnection.GetSenders() {
go processRTCP(rtpSender)
}
// Wait for the offer to be pasted
offer := webrtc.SessionDescription{}
decode(readUntilNewline(), &offer)
if err = peerConnection.SetRemoteDescription(offer); err != nil {
panic(err)
}
// Set a handler for when a new remote track starts
peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { //nolint: revive
fmt.Println("Track has started")
// Start reading from all the streams and sending them to the related output track
rid := track.RID()
if track.Kind() == webrtc.RTPCodecTypeVideo {
go func() {
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
for range ticker.C {
fmt.Printf("Sending pli for stream with rid: %q, ssrc: %d\n", track.RID(), track.SSRC())
if writeErr := peerConnection.WriteRTCP(
[]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}},
); writeErr != nil {
fmt.Println(writeErr)
}
}
}()
}
for {
// Read RTP packets being sent to Pion
packet, _, readErr := track.ReadRTP()
if readErr != nil {
panic(readErr)
}
if writeErr := outputTracks[rid].WriteRTP(packet); writeErr != nil && !errors.Is(writeErr, io.ErrClosedPipe) {
panic(writeErr)
}
}
})
// Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected
peerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", state.String())
if state == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
// It may be reconnected using an ICE Restart.
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
fmt.Println("Peer Connection has gone to failed exiting")
os.Exit(0)
}
if state == webrtc.PeerConnectionStateClosed {
// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
fmt.Println("Peer Connection has gone to closed exiting")
os.Exit(0)
}
})
// Create an answer
answer, err := peerConnection.CreateAnswer(nil)
if err != nil {
panic(err)
}
// Create channel that is blocked until ICE Gathering is complete
gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
// Sets the LocalDescription, and starts our UDP listeners
err = peerConnection.SetLocalDescription(answer)
if err != nil {
panic(err)
}
// Block until ICE Gathering is complete, disabling trickle ICE
// we do this because we only can exchange one signaling message
// in a production application you should exchange ICE Candidates via OnICECandidate
<-gatherComplete
// Output the answer in base64 so we can paste it in browser
fmt.Println(encode(peerConnection.LocalDescription()))
// Block forever
select {}
}
// Read from stdin until we get a newline.
func readUntilNewline() (in string) {
var err error
r := bufio.NewReader(os.Stdin)
for {
in, err = r.ReadString('\n')
if err != nil && !errors.Is(err, io.EOF) {
panic(err)
}
if in = strings.TrimSpace(in); len(in) > 0 {
break
}
}
fmt.Println("")
return
}
// JSON encode + base64 a SessionDescription.
func encode(obj *webrtc.SessionDescription) string {
b, err := json.Marshal(obj)
if err != nil {
panic(err)
}
return base64.StdEncoding.EncodeToString(b)
}
// Decode a base64 and unmarshal JSON into a SessionDescription.
func decode(in string, obj *webrtc.SessionDescription) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {
panic(err)
}
if err = json.Unmarshal(b, obj); err != nil {
panic(err)
}
}