mirror of
https://github.com/sipeed/NanoKVM.git
synced 2026-09-11 00:22:56 -05:00
186 lines
4.5 KiB
Go
186 lines
4.5 KiB
Go
package webrtc
|
|
|
|
import (
|
|
"NanoKVM-Server/config"
|
|
"NanoKVM-Server/middleware"
|
|
"encoding/json"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/gorilla/websocket"
|
|
"github.com/pion/dtls/v3"
|
|
"github.com/pion/webrtc/v4"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
var (
|
|
upgrader = websocket.Upgrader{
|
|
WriteBufferSize: 256 * 1024,
|
|
CheckOrigin: middleware.CheckWebSocketOrigin,
|
|
}
|
|
globalManager *WebRTCManager
|
|
managerOnce sync.Once
|
|
)
|
|
|
|
func getManager() *WebRTCManager {
|
|
managerOnce.Do(func() {
|
|
globalManager = NewWebRTCManager()
|
|
})
|
|
return globalManager
|
|
}
|
|
|
|
func Connect(c *gin.Context) {
|
|
// create WebSocket connection
|
|
wsConn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
|
if err != nil {
|
|
log.Errorf("failed to create h264 websocket: %s", err)
|
|
return
|
|
}
|
|
stopSessionWatcher := middleware.WatchWebSocket(c.Request.Context(), wsConn)
|
|
defer stopSessionWatcher()
|
|
defer func() {
|
|
_ = wsConn.Close()
|
|
log.Debugf("h264 websocket disconnected: %s", c.ClientIP())
|
|
}()
|
|
log.Debugf("h264 websocket connected: %s", c.ClientIP())
|
|
|
|
var zeroTime time.Time
|
|
_ = wsConn.SetReadDeadline(zeroTime)
|
|
|
|
// create video connection
|
|
iceServers := createICEServers()
|
|
|
|
mediaEngine, err := createMediaEngine()
|
|
if err != nil {
|
|
log.Errorf("failed to create h264 media engine: %s", err)
|
|
return
|
|
}
|
|
|
|
videoConn, err := createPeerConnection(iceServers, mediaEngine)
|
|
if err != nil {
|
|
log.Errorf("failed to create h264 video peer connection: %s", err)
|
|
return
|
|
}
|
|
defer func() {
|
|
_ = videoConn.Close()
|
|
log.Debugf("h264 video peer disconnected: %s", c.ClientIP())
|
|
}()
|
|
|
|
// create client
|
|
client := NewClient(wsConn, videoConn)
|
|
if err := client.AddTrack(); err != nil {
|
|
log.Errorf("failed to add track: %s", err)
|
|
return
|
|
}
|
|
|
|
// handle signaling
|
|
signalingHandler := NewSignalingHandler(client)
|
|
defer signalingHandler.Close()
|
|
signalingHandler.RegisterCallbacks()
|
|
if err := sendICEServers(client, iceServers); err != nil {
|
|
log.Errorf("failed to send ICE servers: %s", err)
|
|
return
|
|
}
|
|
|
|
// read and wait
|
|
for {
|
|
message, err := client.ReadMessage()
|
|
if err != nil {
|
|
return
|
|
}
|
|
if message != nil {
|
|
if err := signalingHandler.HandleMessage(message); err != nil {
|
|
log.Errorf("failed to handle signaling message: %s", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func createICEServers() []webrtc.ICEServer {
|
|
var iceServers []webrtc.ICEServer
|
|
|
|
conf := config.GetInstance()
|
|
|
|
if conf.Stun != "" && conf.Stun != "disable" {
|
|
iceServers = append(iceServers, webrtc.ICEServer{
|
|
URLs: []string{"stun:" + conf.Stun},
|
|
})
|
|
}
|
|
|
|
if conf.Turn.TurnAddr != "" && conf.Turn.TurnUser != "" && conf.Turn.TurnCred != "" {
|
|
iceServers = append(iceServers, webrtc.ICEServer{
|
|
URLs: []string{"turn:" + conf.Turn.TurnAddr},
|
|
Username: conf.Turn.TurnUser,
|
|
Credential: conf.Turn.TurnCred,
|
|
})
|
|
}
|
|
|
|
return iceServers
|
|
}
|
|
|
|
type clientICEServer struct {
|
|
URLs []string `json:"urls"`
|
|
Username string `json:"username,omitempty"`
|
|
Credential interface{} `json:"credential,omitempty"`
|
|
}
|
|
|
|
func sendICEServers(client *Client, iceServers []webrtc.ICEServer) error {
|
|
clientServers := make([]clientICEServer, 0, len(iceServers))
|
|
for _, server := range iceServers {
|
|
clientServers = append(clientServers, clientICEServer{
|
|
URLs: server.URLs,
|
|
Username: server.Username,
|
|
Credential: server.Credential,
|
|
})
|
|
}
|
|
|
|
data, err := json.Marshal(clientServers)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return client.WriteMessage("ice-servers", string(data))
|
|
}
|
|
|
|
func createMediaEngine() (*webrtc.MediaEngine, error) {
|
|
mediaEngine := &webrtc.MediaEngine{}
|
|
|
|
if err := mediaEngine.RegisterDefaultCodecs(); err != nil {
|
|
log.Errorf("failed to register default codecs: %s", err)
|
|
return nil, err
|
|
}
|
|
|
|
if err := mediaEngine.RegisterHeaderExtension(
|
|
webrtc.RTPHeaderExtensionCapability{URI: "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay"},
|
|
webrtc.RTPCodecTypeVideo,
|
|
); err != nil {
|
|
log.Errorf("failed to register header extension: %s", err)
|
|
return nil, err
|
|
}
|
|
|
|
return mediaEngine, nil
|
|
}
|
|
|
|
func createPeerConnection(iceServers []webrtc.ICEServer, mediaEngine *webrtc.MediaEngine) (*webrtc.PeerConnection, error) {
|
|
settingEngine := webrtc.SettingEngine{}
|
|
settingEngine.SetSRTPProtectionProfiles(
|
|
dtls.SRTP_AEAD_AES_128_GCM,
|
|
dtls.SRTP_AES128_CM_HMAC_SHA1_80,
|
|
)
|
|
|
|
apiOptions := []func(api *webrtc.API){
|
|
webrtc.WithSettingEngine(settingEngine),
|
|
}
|
|
if mediaEngine != nil {
|
|
apiOptions = append(apiOptions, webrtc.WithMediaEngine(mediaEngine))
|
|
}
|
|
|
|
api := webrtc.NewAPI(apiOptions...)
|
|
|
|
return api.NewPeerConnection(webrtc.Configuration{
|
|
ICEServers: iceServers,
|
|
SDPSemantics: webrtc.SDPSemanticsUnifiedPlan,
|
|
})
|
|
}
|