-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create WHIP/WHEP example with OBS or the browser
- Loading branch information
1 parent
836184c
commit beb7460
Showing
4 changed files
with
238 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |