Skip to content

Instantly share code, notes, and snippets.

@leewardbound
Last active July 19, 2020 17:05
Show Gist options
  • Save leewardbound/a32aad8d92ad4ccc6171a24e5876f9ae to your computer and use it in GitHub Desktop.
Save leewardbound/a32aad8d92ad4ccc6171a24e5876f9ae to your computer and use it in GitHub Desktop.
// Package join-from-screenshare contains an example of joining an ion instance
// and publishing the screenshare of your X server
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/codec"
"github.com/pion/mediadevices/pkg/io/video"
_ "github.com/pion/mediadevices/pkg/driver/screen" // This is required to register screen capture adapter
"github.com/pion/mediadevices/pkg/codec/vpx"
"github.com/pion/webrtc/v2"
)
const (
address = "wss://ion.streamhuddle.com/ws"
rid = "test"
username = "join-from-screenshare"
)
// 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)
vp8Params, err := vpx.NewVP8Params()
if err != nil {
panic(err)
}
vp8Params.BitRate = 100000 // 100kbps
s, err := md.GetDisplayMedia(mediadevices.MediaStreamConstraints{
Video: func(c *mediadevices.MediaTrackConstraints) {
c.Enabled = true
c.VideoTransform = video.Scale(-1, 360, nil) // Resize to 360p
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)
}
}
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