Skip to content

Instantly share code, notes, and snippets.

@leewardbound
Last active July 19, 2020 17:04
Show Gist options
  • Save leewardbound/493fe82381df718b1e3f9cb5a034c6f3 to your computer and use it in GitHub Desktop.
Save leewardbound/493fe82381df718b1e3f9cb5a034c6f3 to your computer and use it in GitHub Desktop.
// Package join-from-webcam contains an example of joining an ion instance and
// publishing a stream directly from a webcam device
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/cloudwebrtc/go-protoo/client"
"github.com/cloudwebrtc/go-protoo/logger"
"github.com/cloudwebrtc/go-protoo/peer"
"github.com/cloudwebrtc/go-protoo/transport"
"github.com/google/uuid"
"github.com/pion/mediadevices"
_ "github.com/pion/mediadevices/pkg/driver/camera"
_ "github.com/pion/mediadevices/pkg/driver/microphone"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/opus"
"github.com/pion/mediadevices/pkg/codec/vpx"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/prop"
"github.com/pion/webrtc/v2"
)
const (
address = "wss://ion.streamhuddle.com/ws"
rid = "test"
username = "join-from-webcam"
)
// AnswerJSEP is part of Answer JSON reply
type AnswerJSEP struct {
SDP string `json:"sdp"`
Type string `json:"type"`
}
// Answer is a JSON reply
type Answer struct {
JSEP AnswerJSEP `json:"jsep"`
mid string
}
func main() {
// We make our own mediaEngine so we can place the sender's codecs in it. This because we must use the
// dynamic media type from the sender in our answer. This is not required if we are the offerer
mediaEngine := webrtc.MediaEngine{}
mediaEngine.RegisterDefaultCodecs()
// Create a new RTCPeerConnection
api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine))
peerConnection, err := api.NewPeerConnection(webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: []string{"stun:stun.l.google.com:19302"},
},
},
})
if err != nil {
panic(err)
}
iceConnectedCtx, iceConnectedCtxCancel := context.WithCancel(context.Background())
if iceConnectedCtx != nil {
// do nothing
}
md := mediadevices.NewMediaDevices(peerConnection)
opusParams, err := opus.NewParams()
if err != nil {
panic(err)
}
opusParams.BitRate = 32000 // 32kbps
vp8Params, err := vpx.NewVP8Params()
if err != nil {
panic(err)
}
vp8Params.BitRate = 100000 // 100kbps
s, err := md.GetUserMedia(mediadevices.MediaStreamConstraints{
Audio: func(c *mediadevices.MediaTrackConstraints) {
c.Enabled = true
c.AudioEncoderBuilders = []codec.AudioEncoderBuilder{&opusParams}
},
Video: func(c *mediadevices.MediaTrackConstraints) {
c.FrameFormat = prop.FrameFormat(frame.FormatYUY2)
c.Enabled = true
c.Width = prop.Int(640)
c.Height = prop.Int(480)
c.VideoEncoderBuilders = []codec.VideoEncoderBuilder{&vp8Params}
},
})
if err != nil {
panic(err)
}
for _, tracker := range s.GetTracks() {
t := tracker.Track()
tracker.OnEnded(func(err error) {
fmt.Printf("Track (ID: %s, Label: %s) ended with error: %v\n",
t.ID(), t.Label(), err)
})
_, err = peerConnection.AddTransceiverFromTrack(t,
webrtc.RtpTransceiverInit{
Direction: webrtc.RTPTransceiverDirectionSendonly,
},
)
if err != nil {
panic(err)
}
}
// 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("Connection State has changed %s \n", connectionState.String())
if connectionState == webrtc.ICEConnectionStateConnected {
iceConnectedCtxCancel()
}
})
peerID := uuid.New().String()
client.NewClient(address+"?peer="+peerID, func(con *transport.WebSocketTransport) {
logger.Infof("handleWebSocketOpen")
pr := peer.NewPeer(peerID, con)
handleRequest := func(request peer.Request, accept peer.RespondFunc, reject peer.RejectFunc) {
method := request.Method
logger.Infof("handleRequest => (%s) ", method)
if method == "kick" {
reject(486, "Busy Here")
} else {
accept(nil)
}
}
handleNotification := func(notification peer.Notification) {
logger.Infof("handleNotification => %s", notification.Method)
}
handleClose := func(err transport.TransportErr) {
logger.Infof("handleClose => peer (%s) [%d] %s", pr.ID(), err.Code, err.Text)
}
go func() {
for {
select {
case msg := <-pr.OnNotification:
log.Println(msg)
handleNotification(msg)
case msg := <-pr.OnRequest:
handleRequest(msg.Request, msg.Accept, msg.Reject)
case msg := <-pr.OnClose:
handleClose(msg)
}
}
}()
pr.Request("join", json.RawMessage(`{"rid":"`+rid+`","info":{"name":"`+username+`"}}`),
func(result json.RawMessage) {
logger.Infof("join success: => %s", result)
offer, err := peerConnection.CreateOffer(nil)
if err != nil {
panic(err)
}
publishInfo := map[string]interface{}{
"rid": "test",
"jsep": map[string]interface{}{
"sdp": string(offer.SDP),
"type": "offer",
},
"options": map[string]interface{}{
"codec": "VP8",
"bandwidth": 1024,
},
}
publish, err := json.Marshal(publishInfo)
logger.Infof("Publish Message: %s\n", publish)
pr.Request("publish", publishInfo,
func(result json.RawMessage) {
logger.Infof("publish success: => %s", result)
var answer Answer
json.Unmarshal(result, &answer)
peerConnection.SetRemoteDescription(webrtc.SessionDescription{
Type: webrtc.SDPTypeAnswer,
SDP: answer.JSEP.SDP,
})
},
func(code int, err string) {
logger.Infof("publish reject: %d => %s", code, err)
})
},
func(code int, err string) {
logger.Infof("login reject: %d => %s", code, err)
})
},
)
for {
// wait until end of file and exit
}
}
// Search for Codec PayloadType
//
// Since we are answering we need to match the remote PayloadType
func getPayloadType(m webrtc.MediaEngine, codecType webrtc.RTPCodecType, codecName string) uint8 {
for _, codec := range m.GetCodecsByKind(codecType) {
if codec.Name == codecName {
return codec.PayloadType
}
}
panic(fmt.Sprintf("Remote peer does not support %s", codecName))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment