Skip to content

Add datachannel whip whep example #3115

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
43 changes: 43 additions & 0 deletions examples/whip-whep-data-channels/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# whip-whep
whip-whep demonstrates using WHIP and WHEP with Pion. Since WHIP+WHEP is standardized signaling you can publish via tools like OBS and GStreamer.
You can then watch it in sub-second time from your browser, or pull the video back into OBS and GStreamer via WHEP.

Further details about the why and how of WHIP+WHEP are below the instructions.

## Instructions

### Download whip-whep

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/whip-whep
```

### Run whip-whep
Execute `go run *.go`

### Publish

You can publish via an tool that supports WHIP or via your browser. To publish via your browser open [http://localhost:8080](http://localhost:8080), and press publish.

To publish via OBS set `Service` to `WHIP` and `Server` to `http://localhost:8080/whip`. The `Bearer Token` can be whatever value you like.


### Subscribe

Once you have started publishing open [http://localhost:8080](http://localhost:8080) and press the subscribe button. You can now view your video you published via
OBS or your browser.

Congrats, you have used Pion WebRTC! Now start building something cool

## Why WHIP/WHEP?

WHIP/WHEP mandates that a Offer is uploaded via HTTP. The server responds with a Answer. With this strong API contract WebRTC support can be added to tools like OBS.

For more info on WHIP/WHEP specification, feel free to read some of these great resources:
- https://webrtchacks.com/webrtc-cracks-the-whip-on-obs/
- https://datatracker.ietf.org/doc/draft-ietf-wish-whip/
- https://datatracker.ietf.org/doc/draft-ietf-wish-whep/
- https://bloggeek.me/whip-whep-webrtc-live-streaming
97 changes: 97 additions & 0 deletions examples/whip-whep-data-channels/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<html>

<!--
SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
SPDX-License-Identifier: MIT
-->
<head>
<title>whip-whep</title>
</head>

<body>
<button onclick="window.doWHIP()">Publish</button>
<button onclick="window.doWHEP()">Subscribe</button>
<br />
Message<br />
<textarea id="message">This is my DataChannel message!</textarea> <br/>
<button onclick="window.sendMessage()">Send Message</button> <br />
<h3> Logs </h3>
<div id="logs"></div>


<h3> ICE Connection States </h3>
<div id="iceConnectionStates"></div> <br />
</body>

<script>
const log = msg => {
document.getElementById('logs').innerHTML += msg + '<br>'
}

let peerConnection = new RTCPeerConnection()

const sendChannel = peerConnection.createDataChannel('foo')
sendChannel.onclose = () => console.log('sendChannel has closed')
sendChannel.onopen = () => console.log('sendChannel has opened')
sendChannel.onmessage = e => log(`Message from DataChannel '${sendChannel.label}' payload '${e.data}'`)

peerConnection.oniceconnectionstatechange = () => {
let el = document.createElement('p')
el.appendChild(document.createTextNode(peerConnection.iceConnectionState))

document.getElementById('iceConnectionStates').appendChild(el);
}

window.doWHEP = () => {

peerConnection.createOffer().then(offer => {
peerConnection.setLocalDescription(offer)

fetch(`/whep`, {
method: 'POST',
body: offer.sdp,
headers: {
Authorization: `Bearer none`,
'Content-Type': 'application/sdp'
}
}).then(r => r.text())
.then(answer => {
peerConnection.setRemoteDescription({
sdp: answer,
type: 'answer'
})
})
})
}

window.doWHIP = () => {
peerConnection.createOffer().then(offer => {
peerConnection.setLocalDescription(offer)

fetch(`/whip`, {
method: 'POST',
body: offer.sdp,
headers: {
Authorization: `Bearer none`,
'Content-Type': 'application/sdp'
}
}).then(r => r.text())
.then(answer => {
peerConnection.setRemoteDescription({
sdp: answer,
type: 'answer'
})
})
})
}

window.sendMessage = () => {
const message = document.getElementById('message').value
if (message === '') {
return alert('Message must not be empty')
}

sendChannel.send(message)
}
</script>
</html>
114 changes: 114 additions & 0 deletions examples/whip-whep-data-channels/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

//go:build !js
// +build !js

// whip-whep demonstrates how to use the WHIP/WHEP specifications to exchange SPD descriptions
// and stream media to a WebRTC client in the browser or OBS.
package main

import (
"fmt"
"io"
"net/http"

"github.com/pion/webrtc/v4"
)

// nolint: gochecknoglobals
var (
peerConnectionConfiguration = webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: []string{"stun:stun.l.google.com:19302"},
},
},
}
)

// nolint:gocognit
func main() {
// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
http.Handle("/", http.FileServer(http.Dir(".")))
http.HandleFunc("/whep", whepHandler)
http.HandleFunc("/whip", whipHandler)

fmt.Println("Open http://localhost:8080 to access this demo")
panic(http.ListenAndServe(":8080", nil)) // nolint: gosec
}

func whipHandler(res http.ResponseWriter, req *http.Request) {
// Read the offer from HTTP Request
offer, err := io.ReadAll(req.Body)
if err != nil {
panic(err)
}

// Create a new RTCPeerConnection
peerConnection, err := webrtc.NewPeerConnection(peerConnectionConfiguration)
if err != nil {
panic(err)
}

// Send answer via HTTP Response
writeAnswer(res, peerConnection, offer, "/whip")
}

func whepHandler(res http.ResponseWriter, req *http.Request) {
// Read the offer from HTTP Request
offer, err := io.ReadAll(req.Body)
if err != nil {
panic(err)
}

// Create a new RTCPeerConnection
peerConnection, err := webrtc.NewPeerConnection(peerConnectionConfiguration)
if err != nil {
panic(err)
}

// Send answer via HTTP Response
writeAnswer(res, peerConnection, offer, "/whep")
}

func writeAnswer(res http.ResponseWriter, peerConnection *webrtc.PeerConnection, offer []byte, path string) {
// 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())

if connectionState == webrtc.ICEConnectionStateFailed {
_ = peerConnection.Close()
}
})

if err := peerConnection.SetRemoteDescription(webrtc.SessionDescription{
Type: webrtc.SDPTypeOffer, SDP: string(offer),
}); err != nil {
panic(err)
}

// Create channel that is blocked until ICE Gathering is complete
gatherComplete := webrtc.GatheringCompletePromise(peerConnection)

// Create answer
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

// WHIP+WHEP expects a Location header and a HTTP Status Code of 201
res.Header().Add("Location", path)
res.WriteHeader(http.StatusCreated)

// Write Answer with Candidates as HTTP Response
fmt.Fprint(res, peerConnection.LocalDescription().SDP) //nolint: errcheck
}
Loading