Archived
2
0

first commit

This commit is contained in:
Craig
2020-01-13 08:05:38 +00:00
commit 0c8af21fab
95 changed files with 5312 additions and 0 deletions

View File

@ -0,0 +1,22 @@
package webrtc
type dataHeader struct {
Event uint8
Length uint16
}
type dataMouseMove struct {
dataHeader
X int16
Y int16
}
type dataMouseKey struct {
dataHeader
Key uint8
}
type dataKeyboardKey struct {
dataHeader
Key uint16
}

View File

@ -0,0 +1,73 @@
package webrtc
import (
"math/rand"
"net/http"
"github.com/gorilla/websocket"
"github.com/pion/webrtc/v2"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"n.eko.moe/neko/internal/gst"
)
func NewManager(password string) (*WebRTCManager, error) {
engine := webrtc.MediaEngine{}
videoCodec := webrtc.NewRTPVP8Codec(webrtc.DefaultPayloadTypeVP8, 90000)
video, err := webrtc.NewTrack(webrtc.DefaultPayloadTypeVP8, rand.Uint32(), "stream", "stream", videoCodec)
if err != nil {
return nil, err
}
gst.CreatePipeline(webrtc.VP8, []*webrtc.Track{video}, "ximagesrc show-pointer=true use-damage=false ! video/x-raw,framerate=30/1 ! videoconvert").Start()
engine.RegisterCodec(videoCodec)
// ximagesrc xid=0 show-pointer=true ! videoconvert ! queue | videotestsrc
audioCodec := webrtc.NewRTPOpusCodec(webrtc.DefaultPayloadTypeOpus, 48000)
audio, err := webrtc.NewTrack(webrtc.DefaultPayloadTypeOpus, rand.Uint32(), "stream", "stream", audioCodec)
if err != nil {
return nil, err
}
gst.CreatePipeline(webrtc.Opus, []*webrtc.Track{audio}, "pulsesrc device=auto_null.monitor ! audioconvert").Start()
engine.RegisterCodec(audioCodec)
// pulsesrc device=auto_null.monitor ! audioconvert | audiotestsrc
// gst-launch-1.0 -v pulsesrc device=auto_null.monitor ! audioconvert ! vorbisenc ! oggmux ! filesink location=alsasrc.ogg
return &WebRTCManager{
logger: log.With().Str("service", "webrtc").Logger(),
engine: engine,
api: webrtc.NewAPI(webrtc.WithMediaEngine(engine)),
video: video,
audio: audio,
controller: "",
password: password,
sessions: make(map[string]*session),
upgrader: websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
},
config: webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: []string{"stun:stun.l.google.com:19302"},
},
},
SDPSemantics: webrtc.SDPSemanticsUnifiedPlanWithFallback,
},
}, nil
}
type WebRTCManager struct {
logger zerolog.Logger
upgrader websocket.Upgrader
engine webrtc.MediaEngine
api *webrtc.API
config webrtc.Configuration
password string
controller string
sessions map[string]*session
video *webrtc.Track
audio *webrtc.Track
}

View File

@ -0,0 +1,15 @@
package webrtc
type message struct {
Event string `json:"event"`
}
type messageIdentityProvide struct {
message
ID string `json:"id"`
}
type messageSDP struct {
message
SDP string `json:"sdp"`
}

View File

@ -0,0 +1,220 @@
package webrtc
import (
"bytes"
"encoding/binary"
"encoding/json"
"github.com/go-vgo/robotgo"
"github.com/pion/webrtc/v2"
"n.eko.moe/neko/internal/keys"
)
func (manager *WebRTCManager) createPeer(session *session, raw []byte) error {
payload := messageSDP{}
if err := json.Unmarshal(raw, &payload); err != nil {
return err
}
peer, err := manager.api.NewPeerConnection(manager.config)
if err != nil {
return err
}
_, err = peer.AddTrack(manager.video)
if err != nil {
return err
}
_, err = peer.AddTrack(manager.audio)
if err != nil {
return err
}
peer.SetRemoteDescription(webrtc.SessionDescription{
SDP: payload.SDP,
Type: webrtc.SDPTypeOffer,
})
answer, err := peer.CreateAnswer(nil)
if err != nil {
return err
}
if err = peer.SetLocalDescription(answer); err != nil {
return err
}
session.send(messageSDP{
message{Event: "sdp/reply"},
answer.SDP,
})
session.peer = peer
peer.OnDataChannel(func(d *webrtc.DataChannel) {
d.OnMessage(func(msg webrtc.DataChannelMessage) {
if err = manager.onData(session, msg); err != nil {
manager.logger.Warn().Err(err).Msg("onData failed")
}
})
})
peer.OnConnectionStateChange(func(connectionState webrtc.PeerConnectionState) {
switch connectionState {
case webrtc.PeerConnectionStateDisconnected:
case webrtc.PeerConnectionStateFailed:
manager.destroy(session)
break
case webrtc.PeerConnectionStateConnected:
manager.logger.Info().Str("ID", session.id).Msg("Peer connected")
break
}
})
return nil
}
var debounce = map[int]bool{}
func (manager *WebRTCManager) onData(session *session, msg webrtc.DataChannelMessage) error {
if manager.controller != session.id {
return nil
}
header := &dataHeader{}
buffer := bytes.NewBuffer(msg.Data)
byt := make([]byte, 3)
_, err := buffer.Read(byt)
if err != nil {
return err
}
err = binary.Read(bytes.NewBuffer(byt), binary.LittleEndian, header)
if err != nil {
return err
}
buffer = bytes.NewBuffer(msg.Data)
switch header.Event {
case 0x01: // MOUSE_MOVE
payload := &dataMouseMove{}
err := binary.Read(buffer, binary.LittleEndian, payload)
if err != nil {
return err
}
robotgo.Move(int(payload.X), int(payload.Y))
break
case 0x02: // MOUSE_UP
payload := &dataMouseKey{}
err := binary.Read(buffer, binary.LittleEndian, payload)
if err != nil {
return err
}
if key, ok := keys.Mouse[int(payload.Key)]; ok {
if !debounce[int(payload.Key)] {
return nil
}
debounce[int(payload.Key)] = false
robotgo.MouseToggle("up", key)
} else {
manager.logger.Warn().Msgf("Unknown MOUSE_DOWN key: %v", payload.Key)
}
break
case 0x03: // MOUSE_DOWN
payload := &dataMouseKey{}
err := binary.Read(buffer, binary.LittleEndian, payload)
if err != nil {
return err
}
if key, ok := keys.Mouse[int(payload.Key)]; ok {
if debounce[int(payload.Key)] {
return nil
}
debounce[int(payload.Key)] = true
robotgo.MouseToggle("down", key)
} else {
manager.logger.Warn().Msgf("Unknown MOUSE_DOWN key: %v", payload.Key)
}
break
case 0x04: // MOUSE_CLK
payload := &dataMouseKey{}
err := binary.Read(buffer, binary.LittleEndian, payload)
if err != nil {
return err
}
if key, ok := keys.Mouse[int(payload.Key)]; ok {
switch int(payload.Key) {
case keys.MOUSE_WHEEL_DOWN:
robotgo.Scroll(0, -10)
break
case keys.MOUSE_WHEEL_UP:
robotgo.Scroll(0, 10)
break
case keys.MOUSE_WHEEL_LEFT:
robotgo.Scroll(-10, 0)
break
case keys.MOUSE_WHEEL_RIGH:
robotgo.Scroll(10, 0)
break
default:
robotgo.Click(key, false)
}
} else {
manager.logger.Warn().Msgf("Unknown MOUSE_CLK key: %v", payload.Key)
}
break
case 0x05: // KEY_DOWN
payload := &dataKeyboardKey{}
err := binary.Read(buffer, binary.LittleEndian, payload)
if err != nil {
return err
}
if key, ok := keys.Keyboard[int(payload.Key)]; ok {
if debounce[int(payload.Key)] {
return nil
}
debounce[int(payload.Key)] = true
robotgo.KeyToggle(key, "down")
} else {
manager.logger.Warn().Msgf("Unknown KEY_DOWN key: %v", payload.Key)
}
break
case 0x06: // KEY_UP
payload := &dataKeyboardKey{}
err := binary.Read(buffer, binary.LittleEndian, payload)
if err != nil {
return err
}
if key, ok := keys.Keyboard[int(payload.Key)]; ok {
if !debounce[int(payload.Key)] {
return nil
}
debounce[int(payload.Key)] = false
robotgo.KeyToggle(key, "up")
} else {
manager.logger.Warn().Msgf("Unknown KEY_UP key: %v", payload.Key)
}
break
case 0x07: // KEY_CLK
payload := &dataKeyboardKey{}
err := binary.Read(buffer, binary.LittleEndian, payload)
if err != nil {
return err
}
if key, ok := keys.Keyboard[int(payload.Key)]; ok {
robotgo.KeyTap(key)
} else {
manager.logger.Warn().Msgf("Unknown KEY_CLK key: %v", payload.Key)
}
break
}
return nil
}

View File

@ -0,0 +1,41 @@
package webrtc
import (
"sync"
"github.com/gorilla/websocket"
"github.com/pion/webrtc/v2"
)
type session struct {
id string
socket *websocket.Conn
peer *webrtc.PeerConnection
mu sync.Mutex
}
func (session *session) send(v interface{}) error {
session.mu.Lock()
defer session.mu.Unlock()
if session.socket != nil {
return session.socket.WriteJSON(v)
}
return nil
}
func (session *session) destroy() error {
if session.peer != nil && session.peer.ConnectionState() == webrtc.PeerConnectionStateConnected {
if err := session.peer.Close(); err != nil {
return err
}
}
if session.socket != nil {
if err := session.socket.Close(); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,222 @@
package webrtc
import (
"encoding/json"
"net/http"
"sync"
"time"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
"n.eko.moe/neko/internal/nanoid"
)
const (
// Send pings to peer with this period. Must be less than pongWait.
pingPeriod = 60 * time.Second
)
func (manager *WebRTCManager) Upgrade(w http.ResponseWriter, r *http.Request) error {
socket, err := manager.upgrader.Upgrade(w, r, nil)
if err != nil {
manager.logger.Error().Err(err).Msg("Failed to upgrade websocket!")
return nil
}
sessionID, ok := manager.authenticate(r)
if ok != true {
manager.logger.Warn().Msg("Authenticatetion failed")
if err = socket.Close(); err != nil {
return err
}
return nil
}
session := &session{
id: sessionID,
socket: socket,
mu: sync.Mutex{},
}
manager.logger.
Info().
Str("ID", sessionID).
Str("RemoteAddr", socket.RemoteAddr().String()).
Msg("Created Session")
manager.sessions[sessionID] = session
defer func() {
manager.destroy(session)
}()
if err = manager.onConnected(session); err != nil {
manager.logger.Error().Err(err).Msg("onConnected failed!")
return nil
}
manager.handleWS(session)
return nil
}
func (manager *WebRTCManager) authenticate(r *http.Request) (sessionID string, ok bool) {
passwords, ok := r.URL.Query()["password"]
if !ok || len(passwords[0]) < 1 {
return "", false
}
if passwords[0] != manager.password {
manager.logger.Warn().Str("Password", passwords[0]).Msg("Wrong password: ")
return "", false
}
id, err := nanoid.NewIDSize(32)
if err != nil {
return "", false
}
return id, true
}
func (manager *WebRTCManager) onConnected(session *session) error {
if err := session.send(messageIdentityProvide{
message: message{Event: "identity/provide"},
ID: session.id,
}); err != nil {
return err
}
return nil
}
func (manager *WebRTCManager) onMessage(session *session, raw []byte) error {
message := message{}
if err := json.Unmarshal(raw, &message); err != nil {
return err
}
switch message.Event {
case "sdp/provide":
return errors.Wrap(manager.createPeer(session, raw), "sdp/provide failed")
case "control/release":
return errors.Wrap(manager.controlRelease(session), "control/release failed")
case "control/request":
return errors.Wrap(manager.controlRequest(session), "control/request failed")
default:
manager.logger.Warn().Msgf("Unknown client method %s", message.Event)
}
return nil
}
func (manager *WebRTCManager) handleWS(session *session) {
bytes := make(chan []byte)
cancel := make(chan struct{})
ticker := time.NewTicker(pingPeriod)
go func() {
defer func() {
ticker.Stop()
manager.logger.Info().Str("RemoteAddr", session.socket.RemoteAddr().String()).Msg("Handle WS ending")
manager.destroy(session)
}()
for {
_, raw, err := session.socket.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
manager.logger.Warn().Err(err).Msg("ReadMessage error")
}
break
}
bytes <- raw
}
}()
for {
select {
case raw := <-bytes:
manager.logger.Info().
Str("ID", session.id).
Str("Message", string(raw)).
Msg("Reading from Websocket")
if err := manager.onMessage(session, raw); err != nil {
manager.logger.Error().Err(err).Msg("onClientMessage has failed")
return
}
case <-cancel:
return
case _ = <-ticker.C:
if err := session.socket.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
}
}
}
func (manager *WebRTCManager) destroy(session *session) {
if manager.controller == session.id {
manager.controller = ""
for id, sess := range manager.sessions {
if id != session.id {
if err := sess.send(message{Event: "control/released"}); err != nil {
manager.logger.Error().Err(err).Msg("session.send has failed")
}
}
}
}
if err := session.destroy(); err != nil {
manager.logger.Error().Err(err).Msg("session.destroy has failed")
}
delete(manager.sessions, session.id)
}
func (manager *WebRTCManager) controlRelease(session *session) error {
if manager.controller == session.id {
manager.controller = ""
if err := session.send(message{Event: "control/release"}); err != nil {
return err
}
for id, sess := range manager.sessions {
if id != session.id {
if err := sess.send(message{Event: "control/released"}); err != nil {
return err
}
}
}
}
return nil
}
func (manager *WebRTCManager) controlRequest(session *session) error {
if manager.controller == "" {
manager.controller = session.id
if err := session.send(message{Event: "control/give"}); err != nil {
return err
}
for id, sess := range manager.sessions {
if id != session.id {
if err := sess.send(message{Event: "control/given"}); err != nil {
return err
}
}
}
} else {
if err := session.send(message{Event: "control/locked"}); err != nil {
return err
}
controller, ok := manager.sessions[manager.controller]
if ok {
controller.send(message{Event: "control/requesting"})
}
}
return nil
}