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,27 @@
# trickle-ice
trickle-ice demonstrates Pion WebRTC's Trickle ICE APIs. ICE is the subsystem WebRTC uses to establish connectivity.
Trickle ICE is the process of sharing addresses as soon as they are gathered. This parallelizes
establishing a connection with a remote peer and starting sessions with TURN servers. Using Trickle ICE
can dramatically reduce the amount of time it takes to establish a WebRTC connection.
Trickle ICE isn't mandatory to use, but highly recommended.
## Instructions
### Download trickle-ice
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/trickle-ice
```
### Run trickle-ice
Execute `go run *.go`
### Open the Web UI
Open [http://localhost:8080](http://localhost:8080). This will automatically start a PeerConnection.
## Note
Congrats, you have used Pion WebRTC! Now start building something cool

View File

@@ -0,0 +1,149 @@
<html>
<!--
SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
SPDX-License-Identifier: MIT
-->
<head>
<title>trickle-ice</title>
<style>
#iceConnectionStates, #inboundDataChannelMessages {
border: 1px solid #ccc;
padding: 10px;
height: 200px;
overflow-y: auto;
font-family: monospace;
background-color: #f9f9f9;
}
.ice-checking { color: orange; }
.ice-connected { color: green; }
.ice-disconnected { color: red; }
.ice-closed { color: gray; }
.data-msg { margin: 2px 0; }
</style>
</head>
<body>
<h3>Controls</h3>
<button id="startBtn">Start</button>
<button id="stopBtn">Stop</button>
<h3> ICE Connection States </h3>
<div id="iceConnectionStates"></div> <br />
<h3> Inbound DataChannel Messages </h3>
<div id="inboundDataChannelMessages"></div>
</body>
<script>
const socket = new WebSocket(`ws://${window.location.host}/websocket`)
let pc = null
let dc = null
let offerCreated = false
function createPeerConnection() {
pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
})
// Handle outgoing ICE candidates
pc.onicecandidate = e => {
if (e.candidate && e.candidate.candidate !== "") {
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify(e.candidate))
} else {
console.warn("WebSocket not open, candidate not sent")
}
}
}
// ICE connection state change handler
pc.oniceconnectionstatechange = () => {
let el = document.createElement('p')
el.appendChild(document.createTextNode(pc.iceConnectionState))
el.className = 'ice-' + pc.iceConnectionState.toLowerCase()
document.getElementById('iceConnectionStates').appendChild(el);
}
// Incoming DataChannel from remote
pc.ondatachannel = event => {
dc = event.channel
setupDataChannel(dc)
}
}
// Setup a DataChannel
function setupDataChannel(channel) {
channel.onopen = () => console.log("DataChannel open")
channel.onmessage = event => {
let el = document.createElement('p')
el.textContent = `${new Date().toLocaleTimeString()} - ${event.data}`
el.className = 'data-msg'
document.getElementById('inboundDataChannelMessages').appendChild(el)
}
channel.onclose = () => console.log("DataChannel closed")
}
// Handle incoming WebSocket messages
socket.onmessage = e => {
try {
let msg = JSON.parse(e.data)
if (!msg) return console.log('failed to parse msg')
if (msg.candidate) {
pc.addIceCandidate(msg)
} else {
pc.setRemoteDescription(msg)
}
} catch (err) {
console.warn("Failed to parse message:", e.data)
}
}
// Start button handler
document.getElementById('startBtn').onclick = () => {
if (offerCreated) return
if (!pc) createPeerConnection()
dc = pc.createDataChannel('data')
setupDataChannel(dc)
pc.createOffer().then(offer => {
pc.setLocalDescription(offer)
socket.send(JSON.stringify(offer))
offerCreated = true
})
}
// Stop button handler
document.getElementById('stopBtn').onclick = () => {
if (dc) {
dc.close()
console.log("DataChannel closed")
dc = null
offerCreated = false
}
if (pc) {
// Show disconnected before closing
let el = document.createElement('p')
el.textContent = `disconnected`
el.className = 'ice-disconnected'
document.getElementById('iceConnectionStates').appendChild(el)
setTimeout(() => {
pc.close()
console.log("PeerConnection closed")
el = document.createElement('p')
el.textContent = `closed`
el.className = 'ice-closed'
document.getElementById('iceConnectionStates').appendChild(el)
pc = null
}, 50) // 50ms delay
}
}
</script>
</html>

View File

@@ -0,0 +1,146 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// trickle-ice demonstrates Pion WebRTC's Trickle ICE APIs. ICE is the subsystem WebRTC uses to establish connectivity.
package main
import (
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/pion/webrtc/v4"
"golang.org/x/net/websocket"
)
// websocketServer is called for every new inbound WebSocket
// nolint: gocognit, cyclop
func websocketServer(wsConn *websocket.Conn) {
// Create a new RTCPeerConnection
peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{})
if err != nil {
panic(err)
}
// When Pion gathers a new ICE Candidate send it to the client. This is how
// ice trickle is implemented. Everytime we have a new candidate available we send
// it as soon as it is ready. We don't wait to emit a Offer/Answer until they are
// all available
peerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) {
if candidate == nil {
return
}
outbound, marshalErr := json.Marshal(candidate.ToJSON())
if marshalErr != nil {
fmt.Println("Marshal ICECandidate error:", marshalErr)
return
}
if _, err = wsConn.Write(outbound); err != nil {
fmt.Println("WebSocket write error:", err)
return
}
})
// 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("ICE Connection State has changed: %s\n", connectionState.String())
})
// Send the current time via a DataChannel to the remote peer every 3 seconds
peerConnection.OnDataChannel(func(d *webrtc.DataChannel) {
d.OnOpen(func() {
fmt.Println(time.Now().Format("15:04:05"), "- DataChannel open")
// Periodically send timestamped messages
go func() {
ticker := time.NewTicker(time.Second * 3)
defer ticker.Stop()
for range ticker.C {
if err := d.SendText(time.Now().String()); err != nil {
fmt.Println(time.Now().Format("15:04:05"), "- DataChannel closed, stopping send loop")
return
}
}
}()
})
})
buf := make([]byte, 1500)
for {
// Read each inbound WebSocket Message
n, err := wsConn.Read(buf)
if err != nil {
fmt.Println(time.Now().Format("15:04:05"), "- WebSocket read error:", err)
return
}
// Unmarshal each inbound WebSocket message
var (
candidate webrtc.ICECandidateInit
offer webrtc.SessionDescription
)
switch {
// Attempt to unmarshal as a SessionDescription. If the SDP field is empty
// assume it is not one.
case json.Unmarshal(buf[:n], &offer) == nil && offer.SDP != "":
if err = peerConnection.SetRemoteDescription(offer); err != nil {
fmt.Println("SetRemoteDescription error:", err)
return
}
answer, answerErr := peerConnection.CreateAnswer(nil)
if answerErr != nil {
fmt.Println("CreateAnswer error:", err)
return
}
if err = peerConnection.SetLocalDescription(answer); err != nil {
fmt.Println("SetLocalDescription error:", err)
return
}
outbound, marshalErr := json.Marshal(answer)
if marshalErr != nil {
fmt.Println("Marshal answer error:", err)
return
}
if _, err = wsConn.Write(outbound); err != nil {
fmt.Println("WebSocket write error:", err)
return
}
// Attempt to unmarshal as a ICECandidateInit. If the candidate field is empty
// assume it is not one.
case json.Unmarshal(buf[:n], &candidate) == nil && candidate.Candidate != "":
if err = peerConnection.AddICECandidate(candidate); err != nil {
fmt.Println("AddICECandidate error:", err)
return
}
default:
fmt.Println("Unknown WebSocket message")
}
}
}
func main() {
http.Handle("/", http.FileServer(http.Dir(".")))
http.Handle("/websocket", websocket.Handler(websocketServer))
fmt.Println("Open http://localhost:8080 to access this demo")
// nolint: gosec
panic(http.ListenAndServe(":8080", nil))
}