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,41 @@
# insertable-streams
insertable-streams demonstrates how to use insertable streams with Pion.
This example modifies the video with a single-byte XOR cipher before sending, and then
decrypts in Javascript.
insertable-streams allows the browser to process encoded video. You could implement
E2E encryption, add metadata or insert a completely different video feed!
## Instructions
### Create IVF named `output.ivf` that contains a VP8 track
```
ffmpeg -i $INPUT_FILE -g 30 output.ivf
```
### Download insertable-streams
```
go install github.com/pion/webrtc/v4/examples/insertable-streams@latest
```
### Open insertable-streams example page
[jsfiddle.net](https://jsfiddle.net/t5xoaryc/) you should see two text-areas and a 'Start Session' button. You will also have a 'Decrypt' checkbox.
When unchecked the browser will not decrypt the incoming video stream, so it will stop playing or display certificates.
### Run insertable-streams with your browsers SessionDescription as stdin
The `output.ivf` you created should be in the same directory as `insertable-streams`. In the jsfiddle the top textarea is your browser, copy that and:
#### Linux/macOS
Run `echo $BROWSER_SDP | insertable-streams`
#### Windows
1. Paste the SessionDescription into a file.
1. Run `insertable-streams < my_file`
### Input insertable-streams's SessionDescription into your browser
Copy the text that `insertable-streams` just emitted and copy into second text area
### Hit 'Start Session' in jsfiddle, enjoy your video!
A video should start playing in your browser above the input boxes. `insertable-streams` will exit when the file reaches the end.
To stop decrypting the stream uncheck the box and the video will not be viewable.
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: play-from-disk
description: play-from-disk demonstrates how to send video to your browser from a file saved to disk.
authors:
- Sean DuBois

View File

@@ -0,0 +1,22 @@
<!--
SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
SPDX-License-Identifier: MIT
-->
<div id="no-support-banner" style="background-color: red">
<h1> Browser does not support insertable streams </h1>
</div>
Browser base64 Session Description<br />
<textarea id="localSessionDescription" readonly="true"></textarea> <br />
Golang base64 Session Description<br />
<textarea id="remoteSessionDescription"> </textarea> <br/>
<button onclick="window.startSession()"> Start Session </button> Decrypt Video <input type="checkbox" checked="checked" onclick="window.toggleDecryption()"/> <br />
<br />
Video<br />
<video id="remote-video" playsinline autoplay controls style="width: 640; height: 480"></video> <br />
Logs<br />
<div id="div"></div>

View File

@@ -0,0 +1,98 @@
/* eslint-env browser */
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// cipherKey that video is encrypted with
const cipherKey = 0xAA
const pc = new RTCPeerConnection({ encodedInsertableStreams: true, forceEncodedVideoInsertableStreams: true })
const log = msg => {
document.getElementById('div').innerHTML += msg + '<br>'
}
// Offer to receive 1 video
const transceiver = pc.addTransceiver('video')
// The API has seen two iterations, support both
// In the future this will just be `createEncodedStreams`
const receiverStreams = getInsertableStream(transceiver)
// boolean controlled by checkbox to enable/disable encryption
let applyDecryption = true
window.toggleDecryption = () => {
applyDecryption = !applyDecryption
}
// Loop that is called for each video frame
const reader = receiverStreams.readable.getReader()
const writer = receiverStreams.writable.getWriter()
reader.read().then(function processVideo ({ done, value }) {
const decrypted = new DataView(value.data)
if (applyDecryption) {
for (let i = 0; i < decrypted.buffer.byteLength; i++) {
decrypted.setInt8(i, decrypted.getInt8(i) ^ cipherKey)
}
}
value.data = decrypted.buffer
writer.write(value)
return reader.read().then(processVideo)
})
// Fire when remote video arrives
pc.ontrack = function (event) {
document.getElementById('remote-video').srcObject = event.streams[0]
document.getElementById('remote-video').style = ''
}
// Populate SDP field when finished gathering
pc.oniceconnectionstatechange = e => log(pc.iceConnectionState)
pc.onicecandidate = event => {
if (event.candidate === null) {
document.getElementById('localSessionDescription').value = btoa(JSON.stringify(pc.localDescription))
}
}
pc.createOffer().then(d => pc.setLocalDescription(d)).catch(log)
window.startSession = () => {
const sd = document.getElementById('remoteSessionDescription').value
if (sd === '') {
return alert('Session Description must not be empty')
}
try {
pc.setRemoteDescription(JSON.parse(atob(sd)))
} catch (e) {
alert(e)
}
}
// DOM code to show banner if insertable streams not supported
let insertableStreamsSupported = true
const updateSupportBanner = () => {
const el = document.getElementById('no-support-banner')
if (insertableStreamsSupported && el) {
el.style = 'display: none'
}
}
document.addEventListener('DOMContentLoaded', updateSupportBanner)
// Shim to support both versions of API
function getInsertableStream (transceiver) {
let insertableStreams = null
if (transceiver.receiver.createEncodedVideoStreams) {
insertableStreams = transceiver.receiver.createEncodedVideoStreams()
} else if (transceiver.receiver.createEncodedStreams) {
insertableStreams = transceiver.receiver.createEncodedStreams()
}
if (!insertableStreams) {
insertableStreamsSupported = false
updateSupportBanner()
throw new Error('Insertable Streams are not supported')
}
return insertableStreams
}

View File

@@ -0,0 +1,224 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// insertable-streams demonstrates how to use insertable streams with Pion
package main
import (
"bufio"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"strings"
"time"
"github.com/pion/webrtc/v4"
"github.com/pion/webrtc/v4/pkg/media"
"github.com/pion/webrtc/v4/pkg/media/ivfreader"
)
const cipherKey = 0xAA
// nolint:gocognit, cyclop
func main() {
peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: []string{"stun:stun.l.google.com:19302"},
},
},
})
if err != nil {
panic(err)
}
defer func() {
if cErr := peerConnection.Close(); cErr != nil {
fmt.Printf("cannot close peerConnection: %v\n", cErr)
}
}()
// Create a video track
videoTrack, err := webrtc.NewTrackLocalStaticSample(
webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video", "pion",
)
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
}
}
}()
iceConnectedCtx, iceConnectedCtxCancel := context.WithCancel(context.Background())
go func() {
// Open a IVF file and start reading using our IVFReader
file, ivfErr := os.Open("output.ivf")
if ivfErr != nil {
panic(ivfErr)
}
ivf, header, ivfErr := ivfreader.NewWith(file)
if ivfErr != nil {
panic(ivfErr)
}
// Wait for connection established
<-iceConnectedCtx.Done()
// 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 range ticker.C {
frame, _, ivfErr := ivf.ParseNextFrame()
if errors.Is(ivfErr, io.EOF) {
fmt.Printf("All frames parsed and sent")
os.Exit(0)
}
if ivfErr != nil {
panic(ivfErr)
}
// Encrypt video using XOR Cipher
for i := range frame {
frame[i] ^= cipherKey
}
if ivfErr = videoTrack.WriteSample(media.Sample{Data: frame, Duration: time.Second}); ivfErr != nil {
panic(ivfErr)
}
}
}()
// Set the handler for ICE connection state
// This will notify you when the peer has connected/disconnected
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
fmt.Printf("Connection State has changed %s \n", connectionState.String())
if connectionState == webrtc.ICEConnectionStateConnected {
iceConnectedCtxCancel()
}
})
// 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)
}
})
// Wait for the offer to be pasted
offer := webrtc.SessionDescription{}
decode(readUntilNewline(), &offer)
// Set the remote SessionDescription
if err = peerConnection.SetRemoteDescription(offer); err != nil {
panic(err)
}
// Create 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
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
// 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)
}
}