mirror of
https://github.com/pion/webrtc.git
synced 2025-10-15 03:30:46 +08:00
Add sfu example with websocket
sfu example with websocket, support video and audio. easy to use Resolves #507
This commit is contained in:
@@ -92,6 +92,7 @@ Check out the **[contributing wiki](https://github.com/pions/webrtc/wiki/Contrib
|
||||
* [frank](https://github.com/feixiao) - *Building examples on OSX*
|
||||
* [mxmCherry](https://github.com/mxmCherry)
|
||||
* [Alex Browne](https://github.com/albrow) - *JavaScript/WASM bindings*
|
||||
* [adwpc](https://github.com/adwpc) - *SFU example with websocket*
|
||||
|
||||
### License
|
||||
MIT License - see [LICENSE](LICENSE) for full text
|
||||
|
27
examples/sfu-ws/README.md
Normal file
27
examples/sfu-ws/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# sfu-ws
|
||||
sfu-ws 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-ws
|
||||
```
|
||||
go get github.com/pions/webrtc/examples/sfu-ws
|
||||
```
|
||||
|
||||
### Run SFU
|
||||
#### Linux/macOS
|
||||
go build
|
||||
./sfu-ws
|
||||
|
||||
### Start a publisher
|
||||
|
||||
* Click `Publish`
|
||||
|
||||
### Start a Subscriber
|
||||
* Click `Subscribe`
|
||||
|
||||
|
||||
You can start one publisher and many subscriber
|
||||
|
||||
Congrats, you have used pion-WebRTC! Now start building something cool
|
8
examples/sfu-ws/go.mod
Normal file
8
examples/sfu-ws/go.mod
Normal file
@@ -0,0 +1,8 @@
|
||||
module github.com/pions/webrtc/examples/sfu-ws
|
||||
|
||||
require (
|
||||
github.com/gorilla/websocket v1.4.0
|
||||
github.com/pions/rtcp v1.1.0
|
||||
github.com/pions/transport v0.4.0 // indirect
|
||||
github.com/pions/webrtc v1.2.1-0.20190311062446-43e0a64ba287
|
||||
)
|
50
examples/sfu-ws/go.sum
Normal file
50
examples/sfu-ws/go.sum
Normal file
@@ -0,0 +1,50 @@
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/cloudflare/sidh v0.0.0-20181111220428-fc8e6378752b/go.mod h1:o/DcCuWFr9jFzwO+c3y1hhwqKHHKfJ7HvLhWUwRnqfo=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pions/datachannel v1.2.0/go.mod h1:MKPEKJRwX/a9/tyQvcVTUI9szyf8ZuUyZxSA9AVMSro=
|
||||
github.com/pions/dtls v1.2.1/go.mod h1:OgJcO0SqrDdQzqkCTdAp4xCQlbCmwZtGyhbthbq9zIA=
|
||||
github.com/pions/qtls-vendor-extracted v0.0.0-20190210024908-018998217c65/go.mod h1:tSUehzG/8OAT3JvWvnovveLfRMM8NvgfN1LzwSrBX5s=
|
||||
github.com/pions/quic v0.0.1/go.mod h1:q62rRbOZG6Keu45rWWljWZHXmB3H7fKdeJ1KtNcDrNQ=
|
||||
github.com/pions/quic-go v0.7.1-0.20190211221741-ec20a8498576/go.mod h1:YvOsXPS6wXEfRGJobrsWSOBmlN6dkEIg+cUpnSDLkhc=
|
||||
github.com/pions/rtcp v1.1.0 h1:WJarRpNaGE7aScgTXARaNIObMhIbftcbiPuhhqvDtl4=
|
||||
github.com/pions/rtcp v1.1.0/go.mod h1:Q5twXlqiz775Yn37X0cl4lAsfSk8EiHgeNkte59jBY4=
|
||||
github.com/pions/rtp v1.0.1/go.mod h1:GDIt4UYlSz7za4vfaLqihGJJ+yLvgPshnqrF/lm3vcM=
|
||||
github.com/pions/sctp v1.3.0/go.mod h1:GZTG/xApE7wdUFEQq2Rmzgxl/+YaB/L1k8xUl1D5bmo=
|
||||
github.com/pions/sctp v1.4.1/go.mod h1:dAna+Ct/aIIFiGW45yhGzuQjULWD7ni1vjoKHa9DsyU=
|
||||
github.com/pions/sdp/v2 v2.1.0/go.mod h1:KGRBcHfpkgJXjrzKJz2wj/Jf1KWnsHdoIiqtayQ5QmE=
|
||||
github.com/pions/srtp v1.1.0/go.mod h1:GvEoPJmI8sLqyjdPPdTZaCKNQ9e/+9OCCpck01s9uNQ=
|
||||
github.com/pions/stun v0.2.0/go.mod h1:rMdCIsqqnTLC4MOHJE3LNiFQRfIjUDzI1kzx//7oPOM=
|
||||
github.com/pions/transport v0.0.0-20190110151433-e7cbf7d5f464/go.mod h1:HLhzI7I0k8TyiQ99hfRZNRf84lG76eaFnZHnVy/wFnM=
|
||||
github.com/pions/transport v0.1.0/go.mod h1:HLhzI7I0k8TyiQ99hfRZNRf84lG76eaFnZHnVy/wFnM=
|
||||
github.com/pions/transport v0.2.0/go.mod h1:HLhzI7I0k8TyiQ99hfRZNRf84lG76eaFnZHnVy/wFnM=
|
||||
github.com/pions/transport v0.4.0 h1:1N6fluzmj5W/16eFLDsCB18s/xEkjVTek0K4IJz75FU=
|
||||
github.com/pions/transport v0.4.0/go.mod h1:9gvUd8ZeyU4ZX7dhNuUq97mPoekopkd7eCJEyhKwVO0=
|
||||
github.com/pions/webrtc v1.2.1-0.20190311062446-43e0a64ba287 h1:skL8VgjrCGaoMCL+o2cumZ1uyLdRtK12TuLLM5PT30o=
|
||||
github.com/pions/webrtc v1.2.1-0.20190311062446-43e0a64ba287/go.mod h1:H4lYxyochp11YxW5AGmNam36neRoNDfPcqhSFUItaes=
|
||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
47
examples/sfu-ws/main.go
Normal file
47
examples/sfu-ws/main.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/pions/webrtc"
|
||||
)
|
||||
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
// Generate pem file for https
|
||||
genPem()
|
||||
|
||||
// Create a MediaEngine object to configure the supported codec
|
||||
m = webrtc.MediaEngine{}
|
||||
|
||||
// Setup the codecs you want to use.
|
||||
m.RegisterCodec(webrtc.NewRTPVP8Codec(webrtc.DefaultPayloadTypeVP8, 90000))
|
||||
m.RegisterCodec(webrtc.NewRTPOpusCodec(webrtc.DefaultPayloadTypeOpus, 48000))
|
||||
|
||||
// Create the API object with the MediaEngine
|
||||
api = webrtc.NewAPI(webrtc.WithMediaEngine(m))
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
port := flag.String("p", "8443", "https port")
|
||||
flag.Parse()
|
||||
|
||||
// Websocket handle func
|
||||
http.HandleFunc("/ws", room)
|
||||
|
||||
// Html handle func
|
||||
http.HandleFunc("/", web)
|
||||
|
||||
// Support https, so we can test by lan
|
||||
fmt.Println("Web listening :" + *port)
|
||||
panic(http.ListenAndServeTLS(":"+*port, "cert.pem", "key.pem", nil))
|
||||
}
|
180
examples/sfu-ws/room.go
Normal file
180
examples/sfu-ws/room.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pions/rtcp"
|
||||
"github.com/pions/webrtc"
|
||||
)
|
||||
|
||||
// Peer config
|
||||
var peerConnectionConfig = webrtc.Configuration{
|
||||
ICEServers: []webrtc.ICEServer{
|
||||
{
|
||||
URLs: []string{"stun:stun.l.google.com:19302"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
// Media engine
|
||||
m webrtc.MediaEngine
|
||||
|
||||
// API object
|
||||
api *webrtc.API
|
||||
|
||||
// Publisher Peer
|
||||
pubCount int32
|
||||
pubReceiver *webrtc.PeerConnection
|
||||
|
||||
// Local track
|
||||
videoTrack *webrtc.Track
|
||||
audioTrack *webrtc.Track
|
||||
videoTrackLock *sync.RWMutex = new(sync.RWMutex)
|
||||
audioTrackLock *sync.RWMutex = new(sync.RWMutex)
|
||||
|
||||
// Websocket upgrader
|
||||
upgrader = websocket.Upgrader{}
|
||||
)
|
||||
|
||||
const (
|
||||
rtcpPLIInterval = time.Second * 3
|
||||
)
|
||||
|
||||
func room(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Websocket client
|
||||
c, err := upgrader.Upgrade(w, r, nil)
|
||||
checkError(err)
|
||||
defer c.Close()
|
||||
|
||||
// Read sdp from websocket
|
||||
mt, msg, err := c.ReadMessage()
|
||||
checkError(err)
|
||||
|
||||
if atomic.LoadInt32(&pubCount) == 0 {
|
||||
atomic.AddInt32(&pubCount, 1)
|
||||
|
||||
// Create a new RTCPeerConnection
|
||||
pubReceiver, err = api.NewPeerConnection(peerConnectionConfig)
|
||||
checkError(err)
|
||||
|
||||
pubReceiver.OnTrack(func(remoteTrack *webrtc.Track, receiver *webrtc.RTPReceiver) {
|
||||
if remoteTrack.PayloadType() == webrtc.DefaultPayloadTypeVP8 || remoteTrack.PayloadType() == webrtc.DefaultPayloadTypeVP9 || remoteTrack.PayloadType() == webrtc.DefaultPayloadTypeH264 {
|
||||
|
||||
// Create a local video track, all our SFU clients will be fed via this track
|
||||
var err error
|
||||
videoTrackLock.Lock()
|
||||
videoTrack, err = pubReceiver.NewTrack(remoteTrack.PayloadType(), remoteTrack.SSRC(), "video", "pion")
|
||||
videoTrackLock.Unlock()
|
||||
checkError(err)
|
||||
|
||||
// Send a PLI on an interval so that the publisher is pushing a keyframe every rtcpPLIInterval
|
||||
go func() {
|
||||
ticker := time.NewTicker(rtcpPLIInterval)
|
||||
for range ticker.C {
|
||||
checkError(pubReceiver.SendRTCP(&rtcp.PictureLossIndication{MediaSSRC: videoTrack.SSRC()}))
|
||||
}
|
||||
}()
|
||||
|
||||
rtpBuf := make([]byte, 1400)
|
||||
for {
|
||||
i, err := remoteTrack.Read(rtpBuf)
|
||||
checkError(err)
|
||||
videoTrackLock.RLock()
|
||||
_, err = videoTrack.Write(rtpBuf[:i])
|
||||
videoTrackLock.RUnlock()
|
||||
checkError(err)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Create a local audio track, all our SFU clients will be fed via this track
|
||||
var err error
|
||||
audioTrackLock.Lock()
|
||||
audioTrack, err = pubReceiver.NewTrack(remoteTrack.PayloadType(), remoteTrack.SSRC(), "audio", "pion")
|
||||
audioTrackLock.Unlock()
|
||||
checkError(err)
|
||||
|
||||
rtpBuf := make([]byte, 1400)
|
||||
for {
|
||||
i, err := remoteTrack.Read(rtpBuf)
|
||||
checkError(err)
|
||||
audioTrackLock.RLock()
|
||||
_, err = audioTrack.Write(rtpBuf[:i])
|
||||
audioTrackLock.RUnlock()
|
||||
checkError(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Set the remote SessionDescription
|
||||
checkError(pubReceiver.SetRemoteDescription(
|
||||
webrtc.SessionDescription{
|
||||
SDP: string(msg),
|
||||
Type: webrtc.SDPTypeOffer,
|
||||
}))
|
||||
|
||||
// Create answer
|
||||
answer, err := pubReceiver.CreateAnswer(nil)
|
||||
checkError(err)
|
||||
|
||||
// Sets the LocalDescription, and starts our UDP listeners
|
||||
checkError(pubReceiver.SetLocalDescription(answer))
|
||||
|
||||
// Send server sdp to publisher
|
||||
checkError(c.WriteMessage(mt, []byte(answer.SDP)))
|
||||
} else {
|
||||
|
||||
// Create a new PeerConnection
|
||||
subSender, err := api.NewPeerConnection(peerConnectionConfig)
|
||||
checkError(err)
|
||||
|
||||
// Waiting for publisher track finish
|
||||
for {
|
||||
videoTrackLock.RLock()
|
||||
if videoTrack == nil {
|
||||
videoTrackLock.RUnlock()
|
||||
//if videoTrack == nil, waiting..
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
} else {
|
||||
videoTrackLock.RUnlock()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Add local video track
|
||||
videoTrackLock.RLock()
|
||||
_, err = subSender.AddTrack(videoTrack)
|
||||
videoTrackLock.RUnlock()
|
||||
checkError(err)
|
||||
|
||||
// Add local audio track
|
||||
audioTrackLock.RLock()
|
||||
_, err = subSender.AddTrack(audioTrack)
|
||||
audioTrackLock.RUnlock()
|
||||
checkError(err)
|
||||
|
||||
// Set the remote SessionDescription
|
||||
checkError(subSender.SetRemoteDescription(
|
||||
webrtc.SessionDescription{
|
||||
SDP: string(msg),
|
||||
Type: webrtc.SDPTypeOffer,
|
||||
}))
|
||||
|
||||
// Create answer
|
||||
answer, err := subSender.CreateAnswer(nil)
|
||||
checkError(err)
|
||||
|
||||
// Sets the LocalDescription, and starts our UDP listeners
|
||||
checkError(subSender.SetLocalDescription(answer))
|
||||
|
||||
// Send server sdp to subscriber
|
||||
checkError(c.WriteMessage(mt, []byte(answer.SDP)))
|
||||
}
|
||||
}
|
125
examples/sfu-ws/sfu.html
Normal file
125
examples/sfu-ws/sfu.html
Normal file
@@ -0,0 +1,125 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>sfu</title>
|
||||
|
||||
<style type="text/css">
|
||||
.comments {
|
||||
width:100%;/*auto width*/
|
||||
overflow:auto;
|
||||
word-break:break-all;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<video id="video1" width="320" height="240" autoplay muted controls></video> <br />
|
||||
|
||||
|
||||
<button class="sessbtn" onclick="window.createSession(true)">Publish</button>
|
||||
<button class="sessbtn" onclick="window.createSession(false)">Subscribe</button>
|
||||
|
||||
<div id="signalingContainer" style="display: none">
|
||||
Client SDP<textarea class="comments" id="localSDP" readonly="true" rows=10 cols=30 onpropertychange= "this.style.posHeight=this.scrollHeight "></textarea>
|
||||
Server SDP<textarea class="comments" id="remoteSDP" readonly="true" rows=10 cols=30 onpropertychange= "this.style.posHeight=this.scrollHeight "></textarea>
|
||||
<!-- <button onclick="window.startSession()"> Start Session </button> -->
|
||||
</div>
|
||||
|
||||
<div id="logs"></div>
|
||||
<script>
|
||||
var log = msg => {
|
||||
document.getElementById('logs').innerHTML += msg + '<br>'
|
||||
}
|
||||
|
||||
var sock = null;
|
||||
var wsuri = "wss://" + location.host + "/ws";
|
||||
window.onload = function() {
|
||||
sock = new WebSocket(wsuri);
|
||||
sock.onopen = function() {
|
||||
<!-- console.log("websocket connected to " + wsuri); -->
|
||||
}
|
||||
sock.onclose = function(e) {
|
||||
<!-- sock = new WebSocket('wss://' + location.host + '/ws'); -->
|
||||
<!-- console.log("websocket connection closed (" + e.code + ")"); -->
|
||||
}
|
||||
sock.onmessage = function(e) {
|
||||
<!-- console.log("websocket message received: " + e.data); -->
|
||||
document.getElementById('remoteSDP').value = e.data
|
||||
<!-- console.log("startSession"); -->
|
||||
window.startSession()
|
||||
}
|
||||
sock.onerror = function(e) {
|
||||
<!-- console.log("websocket error: " + e.data); -->
|
||||
}
|
||||
};
|
||||
|
||||
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('localSDP').value = pc.localDescription.sdp;
|
||||
<!-- document.getElementById('localSDP').value = pc.localDescription.sdp; -->
|
||||
sock.send(pc.localDescription.sdp);
|
||||
<!-- console.log("send sdp to server:==============\n" + pc.localDescription.sdp); -->
|
||||
}
|
||||
}
|
||||
|
||||
if (isPublisher) {
|
||||
navigator.mediaDevices.getUserMedia({ video: true, audio: true})
|
||||
.then(stream => pc.addStream(document.getElementById('video1').srcObject = stream))
|
||||
.catch(log)
|
||||
pc.onnegotiationneeded = e => {
|
||||
<!-- console.log("Publisher createOffer") -->
|
||||
pc.createOffer()
|
||||
.then(d => pc.setLocalDescription(d))
|
||||
.catch(log)
|
||||
}
|
||||
} else {
|
||||
<!-- console.log("Subcriber createOffer") -->
|
||||
pc.createOffer({ offerToReceiveVideo: true , offerToReceiveAudio: true})
|
||||
.then(d => pc.setLocalDescription(d))
|
||||
.catch(log)
|
||||
|
||||
<!-- console.log("Subcriber ontrack") -->
|
||||
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('remoteSDP').value
|
||||
if (sd === '') {
|
||||
return alert('Session Description must not be empty')
|
||||
}
|
||||
|
||||
try {
|
||||
pc.setRemoteDescription(new RTCSessionDescription({type:'answer', sdp:sd}))
|
||||
} catch (e) {
|
||||
alert(e)
|
||||
}
|
||||
}
|
||||
|
||||
let btns = document.getElementsByClassName('sessbtn')
|
||||
for (let i = 0; i < btns.length; i++) {
|
||||
btns[i].style = 'display: none'
|
||||
}
|
||||
|
||||
document.getElementById('signalingContainer').style = 'display: block'
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
60
examples/sfu-ws/web.go
Normal file
60
examples/sfu-ws/web.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"html/template"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func genPem() {
|
||||
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
checkError(err)
|
||||
|
||||
SNLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
SN, err := rand.Int(rand.Reader, SNLimit)
|
||||
checkError(err)
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: SN,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"test"},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(365 * 24 * time.Hour),
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
template.DNSNames = append(template.DNSNames, "localhost")
|
||||
template.EmailAddresses = append(template.EmailAddresses, "test@test.com")
|
||||
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
|
||||
checkError(err)
|
||||
|
||||
certFile, err := os.Create("cert.pem")
|
||||
checkError(err)
|
||||
pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})
|
||||
certFile.Close()
|
||||
|
||||
keyFile, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
checkError(err)
|
||||
// pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv.(*rsa.PrivateKey))})
|
||||
pem.Encode(keyFile, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)})
|
||||
keyFile.Close()
|
||||
}
|
||||
|
||||
func web(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" {
|
||||
t, _ := template.ParseFiles("sfu.html")
|
||||
checkError(t.Execute(w, nil))
|
||||
}
|
||||
}
|
1
go.mod
1
go.mod
@@ -1,6 +1,7 @@
|
||||
module github.com/pions/webrtc
|
||||
|
||||
require (
|
||||
github.com/gorilla/websocket v1.4.0
|
||||
github.com/pions/datachannel v1.2.0
|
||||
github.com/pions/dtls v1.2.1
|
||||
github.com/pions/quic v0.0.1
|
||||
|
2
go.sum
2
go.sum
@@ -10,6 +10,8 @@ github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
|
110
track_test.go
Normal file
110
track_test.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewVideoTrack(t *testing.T) {
|
||||
m := MediaEngine{}
|
||||
m.RegisterCodec(NewRTPVP8Codec(DefaultPayloadTypeVP8, 90000))
|
||||
api := NewAPI(WithMediaEngine(m))
|
||||
peerConfig := Configuration{
|
||||
ICEServers: []ICEServer{
|
||||
{
|
||||
URLs: []string{"stun:stun.l.google.com:19302"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
peer, _ := api.NewPeerConnection(peerConfig)
|
||||
|
||||
_, err := peer.NewTrack(DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion")
|
||||
if err != nil {
|
||||
t.Error("Failed to new video track")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAudioTrack(t *testing.T) {
|
||||
m := MediaEngine{}
|
||||
m.RegisterCodec(NewRTPOpusCodec(DefaultPayloadTypeOpus, 48000))
|
||||
api := NewAPI(WithMediaEngine(m))
|
||||
peerConfig := Configuration{
|
||||
ICEServers: []ICEServer{
|
||||
{
|
||||
URLs: []string{"stun:stun.l.google.com:19302"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
peer, _ := api.NewPeerConnection(peerConfig)
|
||||
|
||||
_, err := peer.NewTrack(DefaultPayloadTypeOpus, rand.Uint32(), "audio", "pion")
|
||||
if err != nil {
|
||||
t.Error("Failed to new audio track")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewTracks(t *testing.T) {
|
||||
m := MediaEngine{}
|
||||
m.RegisterCodec(NewRTPOpusCodec(DefaultPayloadTypeOpus, 48000))
|
||||
m.RegisterCodec(NewRTPVP8Codec(DefaultPayloadTypeVP8, 90000))
|
||||
api := NewAPI(WithMediaEngine(m))
|
||||
peerConfig := Configuration{
|
||||
ICEServers: []ICEServer{
|
||||
{
|
||||
URLs: []string{"stun:stun.l.google.com:19302"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
peer, _ := api.NewPeerConnection(peerConfig)
|
||||
|
||||
_, err := peer.NewTrack(DefaultPayloadTypeOpus, rand.Uint32(), "audio", "pion")
|
||||
if err != nil {
|
||||
t.Error("Failed to new audio track")
|
||||
}
|
||||
|
||||
_, err = peer.NewTrack(DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion")
|
||||
if err != nil {
|
||||
t.Error("Failed to new video track")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNewTracksWrite(t *testing.T) {
|
||||
m := MediaEngine{}
|
||||
m.RegisterCodec(NewRTPOpusCodec(DefaultPayloadTypeOpus, 48000))
|
||||
m.RegisterCodec(NewRTPVP8Codec(DefaultPayloadTypeVP8, 90000))
|
||||
api := NewAPI(WithMediaEngine(m))
|
||||
peerConfig := Configuration{
|
||||
ICEServers: []ICEServer{
|
||||
{
|
||||
URLs: []string{"stun:stun.l.google.com:19302"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
peer, _ := api.NewPeerConnection(peerConfig)
|
||||
|
||||
videoTrack, err := peer.NewTrack(DefaultPayloadTypeOpus, rand.Uint32(), "audio", "pion")
|
||||
if err != nil {
|
||||
t.Error("Failed to new video track")
|
||||
}
|
||||
|
||||
audioTrack, err := peer.NewTrack(DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion")
|
||||
if err != nil {
|
||||
t.Error("Failed to new audio track")
|
||||
}
|
||||
rtpBuf := make([]byte, 1400)
|
||||
_, err = videoTrack.Write(rtpBuf)
|
||||
if err != nil {
|
||||
t.Error("Failed to write to video track")
|
||||
}
|
||||
|
||||
_, err = audioTrack.Write(rtpBuf)
|
||||
if err != nil {
|
||||
t.Error("Failed to write to audio track")
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user