Skip to content

Commit

Permalink
New Example
Browse files Browse the repository at this point in the history
Create WHIP/WHEP example with OBS or the browser
  • Loading branch information
stephanrotolante committed Mar 21, 2024
1 parent 836184c commit beb7460
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 0 deletions.
47 changes: 47 additions & 0 deletions examples/whip-whep/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# whip-whep
whip-whep demonstrates how to use the WHIP/WHEP specifications to exhange SPD descriptions and stream media to a WebRTC client in the browser or OBS

With this example we have pre-made ffmpeg pipelines, but you can use any tool you like!

## Instructions
### Download whip-whep
```
go install github.com/pion/webrtc/v4/examples/whip-whep@latest
```

### Start http server
```
go run main.go
```

### Send RTP to listening socket
Now that you initialzed a client, you can use any software to send H264 encoded rtp packets to port 5004. We also have the pre made examples below

#### ffmpeg
```
ffmpeg -stream_loop -1 -i <your_input_source> -an -c:v libx264 -bsf:v h264_mp4toannexb -r 30 -f rtp udp://127.0.0.1:5004
```

If you wish to send VP8 instead of H264 replace all occurrences of `h264` with VP8 in `main.go` then run

```
ffmpeg -stream_loop -1 -i <your_input_source> -an -c:v libvpx -bsf:v h264_mp4toannexb -r 30 -f rtp udp://127.0.0.1:5004
```

### OBS or Browser
Here are two ways to view data from your local http server

#### Initializing connection with OBS
OBS supports publishing and injest of data via the WHIP/WHEP exchange. Here is a quick demo on how to create a WebRTC connection with you local http server and injest RTP data

<video width="800" height="600" controls>
<source src="/examples/whip-whep/assets/obs_example.mp4" type="video/mp4">
</video>

#### Initializing connection the browser
Since the http server is started, open `http://localhost:8080` in your browser and a new WebRTC connection will be created


For more info on WHIP/WHEP specification, feel free to read some of these great resources:
- https://bloggeek.me/whip-whep-webrtc-live-streaming
- https://www.ietf.org/archive/id/draft-murillo-whep-03.html
Binary file added examples/whip-whep/assets/obs_example.mp4
Binary file not shown.
55 changes: 55 additions & 0 deletions examples/whip-whep/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<html>

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

<body>
<h3> Video </h3>
<video id="videoPlayer" autoplay muted controls style="width: 500"> </video>


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

<script>
let peerConnection = new RTCPeerConnection()
peerConnection.addTransceiver('video', { direction: 'recvonly' })

peerConnection.ontrack = function (event) {
document.getElementById('videoPlayer').srcObject = event.streams[0]
}

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

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

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 => {
console.log(answer)
peerConnection.setRemoteDescription({
sdp: answer,
type: 'answer'
})
})
})
</script>
</html>
136 changes: 136 additions & 0 deletions examples/whip-whep/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// 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 exhange SPD descriptions and stream media to a WebRTC client in the browser or OBS
package main

import (
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"time"

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

const mtuSize = 1600

// nolint:gocognit
func main() {
// Everything below is the Pion WebRTC API! Thanks for using it ❤️.

// Open a UDP Listener for RTP Packets on port 5004
rtpListener, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 5004})
if err != nil {
panic(err)
}

videoTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264}, "video", "pion")
if err != nil {
panic(err)
}

// Serve up html on port 8080
http.Handle("/", http.FileServer(http.Dir(".")))

http.HandleFunc("/whip", func(w http.ResponseWriter, r *http.Request) {
offer, readAllErr := io.ReadAll(r.Body)
if readAllErr != nil {
panic(readAllErr)
}

peerConnection, pcErr := webrtc.NewPeerConnection(webrtc.Configuration{})
if pcErr != nil {
panic(pcErr)
}

rtpSender, addTrackErr := peerConnection.AddTrack(videoTrack)
if addTrackErr != nil {
panic(addTrackErr)
}

go func() {
rtcpBuf := make([]byte, mtuSize)
for {
if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil {
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())

if connectionState == webrtc.ICEConnectionStateFailed {
closeErr := peerConnection.Close()

if closeErr != nil {
panic(closeErr)
}
}
})

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)

answer, createAnsErr := peerConnection.CreateAnswer(nil)
if createAnsErr != nil {
panic(createAnsErr)
} else if err = peerConnection.SetLocalDescription(answer); err != nil {
panic(err)
}

log.Printf("answer %s", answer.SDP)

<-gatherComplete

go func() {
inboundRTPPacket := make([]byte, mtuSize)
for {
n, _, readErr := rtpListener.ReadFrom(inboundRTPPacket)

if readErr != nil {
panic(fmt.Sprintf("error during read: %s", readErr))
}

if _, err = videoTrack.Write(inboundRTPPacket[:n]); err != nil {
if errors.Is(err, io.ErrClosedPipe) {
// The peerConnection has been closed.
return
}
panic(err)
}
}
}()

w.Header().Add("Location", "/whip")
w.WriteHeader(http.StatusCreated)
fmt.Fprint(w, peerConnection.LocalDescription().SDP)
},
)

// Start the server on port 8080
fmt.Println("Server is listening on port 8080")

server := &http.Server{
Addr: ":8080",
ReadHeaderTimeout: 10 * time.Second,
}

err = server.ListenAndServe()
if err != nil {
panic(err)
}
}

0 comments on commit beb7460

Please sign in to comment.