Implement SFU example

`sfu` example shows how to build a simple broadcast only SFU

Resolves #63
This commit is contained in:
Sean DuBois
2018-09-22 14:43:45 -07:00
parent 210db45d44
commit 60b771a5ba
8 changed files with 258 additions and 1 deletions

View File

@@ -9,6 +9,7 @@ We've build an extensive collection of examples covering common use-cases. Modif
* [save-to-disk](save-to-disk/README.md): Save video from your Webcam to disk * [save-to-disk](save-to-disk/README.md): Save video from your Webcam to disk
* [data-channels](data-channels/README.md): Use data channels to send text between Pion WebRTC and your browser * [data-channels](data-channels/README.md): Use data channels to send text between Pion WebRTC and your browser
* [data-channels-create](data-channels/README.md): Similar to data channels but now Pion initiates the creation of the data channel. * [data-channels-create](data-channels/README.md): Similar to data channels but now Pion initiates the creation of the data channel.
* [sfu](sfu/README.md): Broadcast a video to many peers, while only requiring the broadcaster to upload once
* [WIP] [pion-to-pion](pion-to-pion/README.md): An example of two Pion instances communicating directly. * [WIP] [pion-to-pion](pion-to-pion/README.md): An example of two Pion instances communicating directly.
All examples can be executed on your local machine. All examples can be executed on your local machine.

View File

@@ -28,5 +28,11 @@
"link": "save-to-disk", "link": "save-to-disk",
"description": "save-to-disk is a simple application that shows how to record your webcam using pion-WebRTC and save to disk.", "description": "save-to-disk is a simple application that shows how to record your webcam using pion-WebRTC and save to disk.",
"type": "browser" "type": "browser"
},
{
"title": "SFU",
"link": "sfu",
"description": "sfu is a pion-WebRTC application that demonstrates how to broadcast a video to many peers, while only requiring the broadcaster to upload once.",
"type": "browser"
} }
] ]

27
examples/sfu/README.md Normal file
View File

@@ -0,0 +1,27 @@
# sfu
sfu is a pion-WebRTC application that demonstrates how to broadcast a video to many peers, while only requiring the broadcaster to upload once.
This could serve as the building block to building conferencing software, and other applications where publishers are bandwidth constrained.
## Instructions
### Download sfu
```
go get github.com/pions/webrtc/examples/sfu
```
### Open sfu example page
[jsfiddle.net](https://jsfiddle.net/5cwx0rns/) You should see two buttons 'Publish a Broadcast' and 'Join a Broadcast'
### Run SFU
#### Linux/macOS
Run `sfu` OR run `main.go` in `github.com/pions/webrtc/examples/sfu`
### Start a publisher
Click `Publish a Broadcast` and paste the SDP into your terminal. The `sfu` application will respond with an offer, paste this into the second input field. Then press `Start Session`
### Join the broadcast
Click `Join a Broadcast` and paste the SDP into your terminal. The `sfu` application will respond with an offer, paste this into the second input field. Then press `Start Session`
You can `Join the broadcast` as many times as you want. The `sfu` Golang application is relaying all traffic, so your browser only has to upload once.
Congrats, you have used pion-WebRTC! Now start building something cool

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,5 @@
---
name: gstreamer-receive
description: Example of using pion-WebRTC to play video using GStreamer
authors:
- Sean DuBois

View File

@@ -0,0 +1,13 @@
<video id="video1" width="160" height="120" autoplay muted></video> <br />
<button class="createSessionButton" onclick="window.createSession(true)"> Publish a Broadcast </button>
<button class="createSessionButton" onclick="window.createSession(false)"> Join a Broadcast </button>
<div id="signalingContainer" style="display: none">
Browser base64 Session Description <textarea id="localSessionDescription" readonly="true"></textarea> <br />
Golang base64 Session Description: <textarea id="remoteSessionDescription"></textarea> <br/>
<button onclick="window.startSession()"> Start Session </button>
</div>
<div id="logs"></div>

View File

@@ -0,0 +1,62 @@
/* eslint-env browser */
var log = msg => {
document.getElementById('logs').innerHTML += msg + '<br>'
}
window.createSession = isPublisher => {
let pc = new RTCPeerConnection({
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
}
]
})
pc.oniceconnectionstatechange = e => log(pc.iceConnectionState)
pc.onicecandidate = event => {
if (event.candidate === null) {
document.getElementById('localSessionDescription').value = btoa(pc.localDescription.sdp)
}
}
if (isPublisher) {
navigator.mediaDevices.getUserMedia({video: true, audio: false})
.then(stream => pc.addStream(document.getElementById('video1').srcObject = stream))
.catch(log)
pc.onnegotiationneeded = e => {
pc.createOffer()
.then(d => pc.setLocalDescription(d))
.catch(log)
}
} else {
pc.createOffer({offerToReceiveVideo: true})
.then(d => pc.setLocalDescription(d))
.catch(log)
pc.ontrack = function (event) {
var el = document.getElementById('video1')
el.srcObject = event.streams[0]
el.autoplay = true
el.controls = true
}
}
window.startSession = () => {
let sd = document.getElementById('remoteSessionDescription').value
if (sd === '') {
return alert('Session Description must not be empty')
}
try {
pc.setRemoteDescription(new RTCSessionDescription({type: 'answer', sdp: atob(sd)}))
} catch (e) {
alert(e)
}
}
let btns = document.getElementsByClassName('createSessionButton')
for (let i = 0; i < btns.length; i++) {
btns[i].style = 'display: none'
}
document.getElementById('signalingContainer').style = 'display: block'
}

142
examples/sfu/main.go Normal file
View File

@@ -0,0 +1,142 @@
package main
import (
"fmt"
"os"
"sync"
"time"
"bufio"
"encoding/base64"
"github.com/pions/webrtc"
"github.com/pions/webrtc/pkg/media"
"github.com/pions/webrtc/pkg/media/samplebuilder"
"github.com/pions/webrtc/pkg/rtcp"
"github.com/pions/webrtc/pkg/rtp/codecs"
)
var peerConnectionConfig = webrtc.RTCConfiguration{
IceServers: []webrtc.RTCIceServer{
{
URLs: []string{"stun:stun.l.google.com:19302"},
},
},
}
func check(err error) {
if err != nil {
panic(err)
}
}
func mustReadStdin(reader *bufio.Reader) string {
rawSd, err := reader.ReadString('\n')
check(err)
fmt.Println("")
sd, err := base64.StdEncoding.DecodeString(rawSd)
check(err)
return string(sd)
}
const (
rtcpPLIInterval = time.Second * 3
)
func main() {
reader := bufio.NewReader(os.Stdin)
sd := mustReadStdin(reader)
fmt.Println("")
/* Everything below is the pion-WebRTC API, thanks for using it! */
// Only support VP8, this makes our proxying code simpler
webrtc.RegisterCodec(webrtc.NewRTCRtpVP8Codec(webrtc.DefaultPayloadTypeVP8, 90000))
// Create a new RTCPeerConnection
peerConnection, err := webrtc.New(peerConnectionConfig)
check(err)
outboundSamples := []chan<- media.RTCSample{}
var outboundSamplesLock sync.RWMutex
// Set a handler for when a new remote track starts, this just distributes all our packets
// to connected peers
peerConnection.OnTrack = func(track *webrtc.RTCTrack) {
// Send a PLI on an interval so that the publisher is pushing a keyframe every rtcpPLIInterval
// This is a temporary fix until we implement incoming RTCP events, then we would push a PLI only when a viewer requests it
go func() {
ticker := time.NewTicker(rtcpPLIInterval)
for {
select {
case <-ticker.C:
err := peerConnection.SendRTCP(&rtcp.PictureLossIndication{MediaSSRC: track.Ssrc})
if err != nil {
fmt.Println(err)
}
}
}
}()
// Transform RTP packets into samples, allowing us to distribute incoming packets from the publisher to everyone who has joined the broadcast
builder := samplebuilder.New(256, &codecs.VP8Packet{})
for {
outboundSamplesLock.RLock()
builder.Push(<-track.Packets)
for s := builder.Pop(); s != nil; s = builder.Pop() {
for _, outChan := range outboundSamples {
outChan <- *s
}
}
outboundSamplesLock.RUnlock()
}
}
// Set the remote SessionDescription
check(peerConnection.SetRemoteDescription(webrtc.RTCSessionDescription{
Type: webrtc.RTCSdpTypeOffer,
Sdp: string(sd),
}))
// Sets the LocalDescription, and starts our UDP listeners
answer, err := peerConnection.CreateAnswer(nil)
check(err)
// Get the LocalDescription and take it to base64 so we can paste in browser
fmt.Println(base64.StdEncoding.EncodeToString([]byte(answer.Sdp)))
for {
fmt.Println("")
fmt.Println("Paste an SDP to start sendonly peer connection")
recvOnlyOffer := mustReadStdin(reader)
// Create a new RTCPeerConnection
peerConnection, err := webrtc.New(peerConnectionConfig)
check(err)
// Create a single VP8 Track to send videa
vp8Track, err := peerConnection.NewRTCTrack(webrtc.DefaultPayloadTypeVP8, "video", "pion2")
check(err)
_, err = peerConnection.AddTrack(vp8Track)
check(err)
outboundSamplesLock.Lock()
outboundSamples = append(outboundSamples, vp8Track.Samples)
outboundSamplesLock.Unlock()
// Set the remote SessionDescription
check(peerConnection.SetRemoteDescription(webrtc.RTCSessionDescription{
Type: webrtc.RTCSdpTypeOffer,
Sdp: string(recvOnlyOffer),
}))
// Sets the LocalDescription, and starts our UDP listeners
answer, err := peerConnection.CreateAnswer(nil)
check(err)
// Get the LocalDescription and take it to base64 so we can paste in browser
fmt.Println(base64.StdEncoding.EncodeToString([]byte(answer.Sdp)))
}
}