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,47 @@
# play-from-disk-renegotiation
play-from-disk-renegotiation demonstrates Pion WebRTC's renegotiation abilities.
For a simpler example of playing a file from disk we also have [examples/play-from-disk](/examples/play-from-disk)
## Instructions
### Download play-from-disk-renegotiation
This example requires you to clone the repo since it is serving static HTML.
```
git clone https://github.com/pion/webrtc.git
cd webrtc/examples/play-from-disk-renegotiation
```
### Create IVF named `output.ivf` that contains a VP8, VP9 or AV1 track
To encode video to VP8:
```
ffmpeg -i $INPUT_FILE -c:v libvpx -g 30 -b:v 2M output.ivf
```
alternatively, to encode video to AV1 (Note: AV1 is CPU intensive, you may need to adjust `-cpu-used`):
```
ffmpeg -i $INPUT_FILE -c:v libaom-av1 -cpu-used 8 -g 30 -b:v 2M output.ivf
```
Or to encode video to VP9:
```
ffmpeg -i $INPUT_FILE -c:v libvpx-vp9 -cpu-used 4 -g 30 -b:v 2M output.ivf
```
If you have a VP8, VP9 or AV1 file in a different container you can use `ffmpeg` to mux it into IVF:
```
ffmpeg -i $INPUT_FILE -c:v copy -an output.ivf
```
**Note**: In the `ffmpeg` command, the argument `-b:v 2M` specifies the video bitrate to be 2 megabits per second. We provide this default value to produce decent video quality, but if you experience problems with this configuration (such as dropped frames etc.), you can decrease this. See the [ffmpeg documentation](https://ffmpeg.org/ffmpeg.html#Options) for more information on the format of the value.
### Run play-from-disk-renegotiation
The `output.ivf` you created should be in the same directory as `play-from-disk-renegotiation`. Execute `go run *.go`
### Open the Web UI
Open [http://localhost:8080](http://localhost:8080) and you should have a `Add Track` and `Remove Track` button. Press these to add as many tracks as you want, or to remove as many as you wish.
Congrats, you have used Pion WebRTC! Now start building something cool

View File

@@ -0,0 +1,81 @@
<html>
<!--
SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
SPDX-License-Identifier: MIT
-->
<head>
<title>play-from-disk-renegotiation</title>
</head>
<body>
<button onclick="window.addVideo()"> Add Video </button><br />
<button onclick="window.removeVideo()"> Remove Video </button><br />
<h3> Video </h3>
<div id="remoteVideos"></div> <br />
<h3> Logs </h3>
<div id="logs"></div>
</body>
<script>
let activeVideos = 0
let pc = new RTCPeerConnection({
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
}
]
})
pc.ontrack = function (event) {
var el = document.createElement(event.track.kind)
el.srcObject = event.streams[0]
el.autoplay = true
el.controls = true
event.track.onmute = function(event) {
el.parentNode.removeChild(el);
}
document.getElementById('remoteVideos').appendChild(el)
}
let doSignaling = method => {
pc.createOffer()
.then(offer => {
pc.setLocalDescription(offer)
return fetch(`/${method}`, {
method: 'post',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
body: JSON.stringify(offer)
})
})
.then(res => res.json())
.then(res => pc.setRemoteDescription(res))
.catch(alert)
}
// Create a noop DataChannel. By default PeerConnections do not connect
// if they have no media tracks or DataChannels
pc.createDataChannel('noop')
doSignaling('createPeerConnection')
window.addVideo = () => {
if (pc.getTransceivers().length <= activeVideos) {
pc.addTransceiver('video')
activeVideos++
}
doSignaling('addVideo')
};
window.removeVideo = () => {
doSignaling('removeVideo')
};
</script>
</html>

View File

@@ -0,0 +1,215 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// play-from-disk-renegotiation demonstrates Pion WebRTC's renegotiation abilities.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"time"
"github.com/pion/randutil"
"github.com/pion/webrtc/v4"
"github.com/pion/webrtc/v4/pkg/media"
"github.com/pion/webrtc/v4/pkg/media/ivfreader"
)
var peerConnection *webrtc.PeerConnection //nolint
// doSignaling exchanges all state of the local PeerConnection and is called
// every time a video is added or removed.
func doSignaling(res http.ResponseWriter, req *http.Request) {
var offer webrtc.SessionDescription
if err := json.NewDecoder(req.Body).Decode(&offer); err != nil {
panic(err)
}
if err := peerConnection.SetRemoteDescription(offer); err != nil {
panic(err)
}
// Create channel that is blocked until ICE Gathering is complete
gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
answer, err := peerConnection.CreateAnswer(nil)
if err != nil {
panic(err)
} else if err = peerConnection.SetLocalDescription(answer); 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
response, err := json.Marshal(*peerConnection.LocalDescription())
if err != nil {
panic(err)
}
res.Header().Set("Content-Type", "application/json")
if _, err := res.Write(response); err != nil {
panic(err)
}
}
// Add a single video track.
func createPeerConnection(res http.ResponseWriter, req *http.Request) {
if peerConnection.ConnectionState() != webrtc.PeerConnectionStateNew {
panic(fmt.Sprintf("createPeerConnection called in non-new state (%s)", peerConnection.ConnectionState()))
}
doSignaling(res, req)
fmt.Println("PeerConnection has been created")
}
// Add a single video track.
func addVideo(res http.ResponseWriter, req *http.Request) { //nolint:cyclop
// Open a IVF file and start reading using our IVFReader
file, err := os.Open("output.ivf")
if err != nil {
panic(err)
}
ivf, header, err := ivfreader.NewWith(file)
if err != nil {
panic(err)
}
var mimeType string
switch header.FourCC {
case "VP80":
mimeType = webrtc.MimeTypeVP8
case "VP90":
mimeType = webrtc.MimeTypeVP9
case "AV01":
mimeType = webrtc.MimeTypeAV1
default:
panic(fmt.Sprintf("unsupported codec: %s", header.FourCC))
}
videoTrack, err := webrtc.NewTrackLocalStaticSample(
webrtc.RTPCodecCapability{MimeType: mimeType},
fmt.Sprintf("video-%d", randutil.NewMathRandomGenerator().Uint32()),
fmt.Sprintf("video-%d", randutil.NewMathRandomGenerator().Uint32()),
)
if err != nil {
panic(err)
}
rtpSender, err := peerConnection.AddTrack(videoTrack)
if 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.
go func() {
rtcpBuf := make([]byte, 1500)
for {
if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil {
return
}
}
}()
doSignaling(res, req)
fmt.Println("Video track has been added")
go writeVideoToTrack(ivf, header, videoTrack)
}
// Remove a single sender.
func removeVideo(res http.ResponseWriter, req *http.Request) {
if senders := peerConnection.GetSenders(); len(senders) != 0 {
if err := peerConnection.RemoveTrack(senders[0]); err != nil {
panic(err)
}
}
doSignaling(res, req)
fmt.Println("Video track has been removed")
}
func main() {
var err error
if peerConnection, err = webrtc.NewPeerConnection(webrtc.Configuration{}); err != nil {
panic(err)
}
defer func() {
if cErr := peerConnection.Close(); cErr != nil {
fmt.Printf("cannot close peerConnection: %v\n", cErr)
}
}()
// 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)
}
})
http.Handle("/", http.FileServer(http.Dir(".")))
http.HandleFunc("/createPeerConnection", createPeerConnection)
http.HandleFunc("/addVideo", addVideo)
http.HandleFunc("/removeVideo", removeVideo)
go func() {
fmt.Println("Open http://localhost:8080 to access this demo")
// nolint: gosec
panic(http.ListenAndServe(":8080", nil))
}()
// Block forever
select {}
}
// Read a video file from disk and write it to a webrtc.Track
// When the video has been completely read this exits without error.
func writeVideoToTrack(
ivf *ivfreader.IVFReader, header *ivfreader.IVFFileHeader, track *webrtc.TrackLocalStaticSample,
) {
// Send our video file frame at a time. Pace our sending so we send it at the same speed it should be played back as.
// This isn't required since the video is timestamped, but we will such much higher loss if we send all at once.
//
// It is important to use a time.Ticker instead of time.Sleep because
// * avoids accumulating skew, just calling time.Sleep didn't compensate for the time spent parsing the data
// * works around latency issues with Sleep (see https://github.com/golang/go/issues/44343)
ticker := time.NewTicker(
time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000),
)
defer ticker.Stop()
for ; true; <-ticker.C {
frame, _, err := ivf.ParseNextFrame()
if err != nil {
fmt.Printf("Finish writing video track: %s ", err)
return
}
if err = track.WriteSample(media.Sample{Data: frame, Duration: time.Second}); err != nil {
fmt.Printf("Finish writing video track: %s ", err)
return
}
}
}