168 lines
3.6 KiB
Go
168 lines
3.6 KiB
Go
package h264
|
|
|
|
import (
|
|
"encoding/json"
|
|
"sync"
|
|
|
|
"github.com/gorilla/websocket"
|
|
"github.com/pion/webrtc/v4"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type Client struct {
|
|
ws *websocket.Conn
|
|
pc *webrtc.PeerConnection
|
|
mutex sync.Mutex
|
|
}
|
|
|
|
type Message struct {
|
|
Event string `json:"event"`
|
|
Data string `json:"data"`
|
|
}
|
|
|
|
// add video track
|
|
func (c *Client) addTrack() {
|
|
videoTrack, err := webrtc.NewTrackLocalStaticSample(
|
|
webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264},
|
|
"video",
|
|
"pion",
|
|
)
|
|
if err != nil {
|
|
log.Errorf("failed to create video track: %s", err)
|
|
return
|
|
}
|
|
|
|
_, err = c.pc.AddTrack(videoTrack)
|
|
if err != nil {
|
|
log.Errorf("failed to add video track: %s", err)
|
|
return
|
|
}
|
|
|
|
trackMap[c.ws] = videoTrack
|
|
}
|
|
|
|
// register callback events
|
|
func (c *Client) register() {
|
|
// new ICE candidate found
|
|
c.pc.OnICECandidate(func(candidate *webrtc.ICECandidate) {
|
|
if candidate == nil {
|
|
return
|
|
}
|
|
|
|
candidateByte, err := json.Marshal(candidate.ToJSON())
|
|
if err != nil {
|
|
log.Errorf("failed to marshal candidate: %s", err)
|
|
return
|
|
}
|
|
|
|
_ = c.sendMessage("candidate", string(candidateByte))
|
|
})
|
|
|
|
// ICE connection state has changed
|
|
c.pc.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
|
|
if state == webrtc.ICEConnectionStateConnected && !isSending {
|
|
// start sending h264 data
|
|
go send()
|
|
isSending = true
|
|
}
|
|
|
|
log.Debugf("ice connection state has changed to %s", state.String())
|
|
})
|
|
}
|
|
|
|
// read websocket message
|
|
func (c *Client) readMessage() {
|
|
message := &Message{}
|
|
|
|
for {
|
|
_, raw, err := c.ws.ReadMessage()
|
|
if err != nil {
|
|
delete(trackMap, c.ws)
|
|
if isSending && len(trackMap) == 0 {
|
|
// stop sending when all websocket connections are closed
|
|
isSending = false
|
|
}
|
|
|
|
log.Debugf("failed to read message: %s", err)
|
|
return
|
|
}
|
|
|
|
if err := json.Unmarshal(raw, &message); err != nil {
|
|
log.Errorf("failed to unmarshal message: %s", err)
|
|
continue
|
|
}
|
|
|
|
log.Debugf("receive message event: %s", message.Event)
|
|
|
|
switch message.Event {
|
|
case "offer":
|
|
offer := webrtc.SessionDescription{}
|
|
if err := json.Unmarshal([]byte(message.Data), &offer); err != nil {
|
|
log.Errorf("failed to unmarshal offer message: %s", err)
|
|
return
|
|
}
|
|
|
|
if err := c.pc.SetRemoteDescription(offer); err != nil {
|
|
log.Errorf("failed to set remote description: %s", err)
|
|
return
|
|
}
|
|
|
|
answer, answerErr := c.pc.CreateAnswer(nil)
|
|
if answerErr != nil {
|
|
log.Errorf("failed to create answer: %s", answerErr)
|
|
return
|
|
}
|
|
|
|
if err := c.pc.SetLocalDescription(answer); err != nil {
|
|
log.Errorf("failed to set local description: %s", err)
|
|
return
|
|
}
|
|
|
|
answerByte, answerByteErr := json.Marshal(answer)
|
|
if answerByteErr != nil {
|
|
log.Errorf("failed to marshal answer: %s", answerByteErr)
|
|
return
|
|
}
|
|
|
|
_ = c.sendMessage("answer", string(answerByte))
|
|
|
|
case "candidate":
|
|
candidate := webrtc.ICECandidateInit{}
|
|
if err := json.Unmarshal([]byte(message.Data), &candidate); err != nil {
|
|
log.Errorf("failed to unmarshal candidate message: %s", err)
|
|
return
|
|
}
|
|
|
|
if err := c.pc.AddICECandidate(candidate); err != nil {
|
|
log.Errorf("failed to add ICE candidate: %s", err)
|
|
return
|
|
}
|
|
|
|
case "heartbeat":
|
|
_ = c.sendMessage("heartbeat", "")
|
|
|
|
default:
|
|
log.Debugf("unhandled message event: %s", message.Event)
|
|
}
|
|
}
|
|
}
|
|
|
|
// send websocket message
|
|
func (c *Client) sendMessage(event string, data string) error {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
message := &Message{
|
|
Event: event,
|
|
Data: data,
|
|
}
|
|
|
|
if err := c.ws.WriteJSON(message); err != nil {
|
|
log.Errorf("failed to send message %s: %s", event, err)
|
|
return err
|
|
}
|
|
|
|
log.Debugf("send message %s", message.Event)
|
|
return nil
|
|
}
|