Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 130 additions & 50 deletions examples/trickle-ice/index.html
Original file line number Diff line number Diff line change
@@ -1,69 +1,149 @@
<html>
<!--
SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
SPDX-License-Identifier: MIT
-->
<head>
<title>trickle-ice</title>
</head>

<body>
<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 = new RTCPeerConnection({
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
}
]
<!--
SPDX-FileCopyrightText: 2023 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' }]
})

socket.onmessage = e => {
let msg = JSON.parse(e.data)
if (!msg) {
return console.log('failed to parse msg')

// 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)
}
}

let dc = pc.createDataChannel('data')
dc.onmessage = event => {
let el = document.createElement('p')
el.appendChild(document.createTextNode(event.data))
// Start button handler
document.getElementById('startBtn').onclick = () => {
if (offerCreated) return

document.getElementById('inboundDataChannelMessages').appendChild(el);
}
if (!pc) createPeerConnection()

pc.onicecandidate = e => {
if (e.candidate && e.candidate.candidate !== "") {
socket.send(JSON.stringify(e.candidate))
}
}
dc = pc.createDataChannel('data')
setupDataChannel(dc)

pc.oniceconnectionstatechange = () => {
let el = document.createElement('p')
el.appendChild(document.createTextNode(pc.iceConnectionState))
pc.createOffer().then(offer => {
pc.setLocalDescription(offer)
socket.send(JSON.stringify(offer))
offerCreated = true
})
}

document.getElementById('iceConnectionStates').appendChild(el);
// 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)

socket.onopen = () => {
pc.createOffer().then(offer => {
pc.setLocalDescription(offer)
socket.send(JSON.stringify(offer))
})
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>

}

</script>
</html>
54 changes: 40 additions & 14 deletions examples/trickle-ice/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,15 @@ func websocketServer(wsConn *websocket.Conn) {

outbound, marshalErr := json.Marshal(candidate.ToJSON())
if marshalErr != nil {
panic(marshalErr)
fmt.Println("Marshal ICECandidate error:", marshalErr)

return
}

if _, err = wsConn.Write(outbound); err != nil {
panic(err)
fmt.Println("WebSocket write error:", err)

return
}
})

Expand All @@ -51,11 +55,19 @@ func websocketServer(wsConn *websocket.Conn) {
// Send the current time via a DataChannel to the remote peer every 3 seconds
peerConnection.OnDataChannel(func(d *webrtc.DataChannel) {
d.OnOpen(func() {
for range time.Tick(time.Second * 3) {
if err = d.SendText(time.Now().String()); err != nil {
panic(err)
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
}
}
}
}()
})
})

Expand All @@ -64,7 +76,9 @@ func websocketServer(wsConn *websocket.Conn) {
// Read each inbound WebSocket Message
n, err := wsConn.Read(buf)
if err != nil {
panic(err)
fmt.Println(time.Now().Format("15:04:05"), "- WebSocket read error:", err)

return
}

// Unmarshal each inbound WebSocket message
Expand All @@ -78,34 +92,46 @@ func websocketServer(wsConn *websocket.Conn) {
// assume it is not one.
case json.Unmarshal(buf[:n], &offer) == nil && offer.SDP != "":
if err = peerConnection.SetRemoteDescription(offer); err != nil {
panic(err)
fmt.Println("SetRemoteDescription error:", err)

return
}

answer, answerErr := peerConnection.CreateAnswer(nil)
if answerErr != nil {
panic(answerErr)
fmt.Println("CreateAnswer error:", err)

return
}

if err = peerConnection.SetLocalDescription(answer); err != nil {
panic(err)
fmt.Println("SetLocalDescription error:", err)

return
}

outbound, marshalErr := json.Marshal(answer)
if marshalErr != nil {
panic(marshalErr)
fmt.Println("Marshal answer error:", err)

return
}

if _, err = wsConn.Write(outbound); err != nil {
panic(err)
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 {
panic(err)
fmt.Println("AddICECandidate error:", err)

return
}
default:
panic("Unknown message")
fmt.Println("Unknown WebSocket message")
}
}
}
Expand Down
Loading