mirror of
https://github.com/strukturag/nextcloud-spreed-signaling.git
synced 2023-10-21 07:33:40 -05:00
Switch dependency tracking to Go modules.
With that bump minimum required version to Go 1.13.
This commit is contained in:
+852
@@ -0,0 +1,852 @@
|
||||
/**
|
||||
* Standalone signaling server for the Nextcloud Spreed app.
|
||||
* Copyright (C) 2017 struktur AG
|
||||
*
|
||||
* @author Joachim Bauch <bauch@struktur.de>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contents heavily based on
|
||||
* https://github.com/notedit/janus-go/blob/master/janus.go
|
||||
*
|
||||
* Added error handling and improve functionality.
|
||||
*/
|
||||
package signaling
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/notedit/janus-go"
|
||||
)
|
||||
|
||||
const (
|
||||
/*! \brief Success (no error) */
|
||||
JANUS_OK = 0
|
||||
|
||||
/*! \brief Unauthorized (can only happen when using apisecret/auth token) */
|
||||
JANUS_ERROR_UNAUTHORIZED = 403
|
||||
/*! \brief Unauthorized access to a plugin (can only happen when using auth token) */
|
||||
JANUS_ERROR_UNAUTHORIZED_PLUGIN = 405
|
||||
/*! \brief Unknown/undocumented error */
|
||||
JANUS_ERROR_UNKNOWN = 490
|
||||
/*! \brief Transport related error */
|
||||
JANUS_ERROR_TRANSPORT_SPECIFIC = 450
|
||||
/*! \brief The request is missing in the message */
|
||||
JANUS_ERROR_MISSING_REQUEST = 452
|
||||
/*! \brief The gateway does not suppurt this request */
|
||||
JANUS_ERROR_UNKNOWN_REQUEST = 453
|
||||
/*! \brief The payload is not a valid JSON message */
|
||||
JANUS_ERROR_INVALID_JSON = 454
|
||||
/*! \brief The object is not a valid JSON object as expected */
|
||||
JANUS_ERROR_INVALID_JSON_OBJECT = 455
|
||||
/*! \brief A mandatory element is missing in the message */
|
||||
JANUS_ERROR_MISSING_MANDATORY_ELEMENT = 456
|
||||
/*! \brief The request cannot be handled for this webserver path */
|
||||
JANUS_ERROR_INVALID_REQUEST_PATH = 457
|
||||
/*! \brief The session the request refers to doesn't exist */
|
||||
JANUS_ERROR_SESSION_NOT_FOUND = 458
|
||||
/*! \brief The handle the request refers to doesn't exist */
|
||||
JANUS_ERROR_HANDLE_NOT_FOUND = 459
|
||||
/*! \brief The plugin the request wants to talk to doesn't exist */
|
||||
JANUS_ERROR_PLUGIN_NOT_FOUND = 460
|
||||
/*! \brief An error occurring when trying to attach to a plugin and create a handle */
|
||||
JANUS_ERROR_PLUGIN_ATTACH = 461
|
||||
/*! \brief An error occurring when trying to send a message/request to the plugin */
|
||||
JANUS_ERROR_PLUGIN_MESSAGE = 462
|
||||
/*! \brief An error occurring when trying to detach from a plugin and destroy the related handle */
|
||||
JANUS_ERROR_PLUGIN_DETACH = 463
|
||||
/*! \brief The gateway doesn't support this SDP type
|
||||
* \todo The gateway currently only supports OFFER and ANSWER. */
|
||||
JANUS_ERROR_JSEP_UNKNOWN_TYPE = 464
|
||||
/*! \brief The Session Description provided by the peer is invalid */
|
||||
JANUS_ERROR_JSEP_INVALID_SDP = 465
|
||||
/*! \brief The stream a trickle candidate for does not exist or is invalid */
|
||||
JANUS_ERROR_TRICKE_INVALID_STREAM = 466
|
||||
/*! \brief A JSON element is of the wrong type (e.g., an integer instead of a string) */
|
||||
JANUS_ERROR_INVALID_ELEMENT_TYPE = 467
|
||||
/*! \brief The ID provided to create a new session is already in use */
|
||||
JANUS_ERROR_SESSION_CONFLICT = 468
|
||||
/*! \brief We got an ANSWER to an OFFER we never made */
|
||||
JANUS_ERROR_UNEXPECTED_ANSWER = 469
|
||||
/*! \brief The auth token the request refers to doesn't exist */
|
||||
JANUS_ERROR_TOKEN_NOT_FOUND = 470
|
||||
|
||||
// Error codes of videoroom plugin.
|
||||
JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR = 499
|
||||
JANUS_VIDEOROOM_ERROR_NO_MESSAGE = 421
|
||||
JANUS_VIDEOROOM_ERROR_INVALID_JSON = 422
|
||||
JANUS_VIDEOROOM_ERROR_INVALID_REQUEST = 423
|
||||
JANUS_VIDEOROOM_ERROR_JOIN_FIRST = 424
|
||||
JANUS_VIDEOROOM_ERROR_ALREADY_JOINED = 425
|
||||
JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM = 426
|
||||
JANUS_VIDEOROOM_ERROR_ROOM_EXISTS = 427
|
||||
JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED = 428
|
||||
JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT = 429
|
||||
JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT = 430
|
||||
JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE = 431
|
||||
JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL = 432
|
||||
JANUS_VIDEOROOM_ERROR_UNAUTHORIZED = 433
|
||||
JANUS_VIDEOROOM_ERROR_ALREADY_PUBLISHED = 434
|
||||
JANUS_VIDEOROOM_ERROR_NOT_PUBLISHED = 435
|
||||
JANUS_VIDEOROOM_ERROR_ID_EXISTS = 436
|
||||
JANUS_VIDEOROOM_ERROR_INVALID_SDP = 437
|
||||
)
|
||||
|
||||
var (
|
||||
janusDialer = websocket.Dialer{
|
||||
Subprotocols: []string{"janus-protocol"},
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}
|
||||
)
|
||||
|
||||
var msgtypes = map[string]func() interface{}{
|
||||
"error": func() interface{} { return &janus.ErrorMsg{} },
|
||||
"success": func() interface{} { return &janus.SuccessMsg{} },
|
||||
"detached": func() interface{} { return &janus.DetachedMsg{} },
|
||||
"server_info": func() interface{} { return &InfoMsg{} },
|
||||
"ack": func() interface{} { return &janus.AckMsg{} },
|
||||
"event": func() interface{} { return &janus.EventMsg{} },
|
||||
"webrtcup": func() interface{} { return &janus.WebRTCUpMsg{} },
|
||||
"media": func() interface{} { return &janus.MediaMsg{} },
|
||||
"hangup": func() interface{} { return &janus.HangupMsg{} },
|
||||
"slowlink": func() interface{} { return &janus.SlowLinkMsg{} },
|
||||
"timeout": func() interface{} { return &janus.TimeoutMsg{} },
|
||||
"trickle": func() interface{} { return &TrickleMsg{} },
|
||||
}
|
||||
|
||||
type InfoMsg struct {
|
||||
Name string
|
||||
Version int
|
||||
VersionString string `json:"version_string"`
|
||||
Author string
|
||||
DataChannels bool `json:"data_channels"`
|
||||
IPv6 bool `json:"ipv6"`
|
||||
LocalIP string `json:"local-ip"`
|
||||
ICE_TCP bool `json:"ice-tcp"`
|
||||
FullTrickle bool `json:"full-trickle"`
|
||||
Transports map[string]janus.PluginInfo
|
||||
Plugins map[string]janus.PluginInfo
|
||||
}
|
||||
|
||||
type TrickleMsg struct {
|
||||
Session uint64 `json:"session_id"`
|
||||
Handle uint64 `json:"sender"`
|
||||
Candidate struct {
|
||||
SdpMid string `json:"sdpMid"`
|
||||
SdpMLineIndex int `json:"sdpMLineIndex"`
|
||||
Candidate string `json:"candidate"`
|
||||
|
||||
Completed bool `json:"completed,omitempty"`
|
||||
} `json:"candidate"`
|
||||
}
|
||||
|
||||
func unexpected(request string) error {
|
||||
return fmt.Errorf("Unexpected response received to '%s' request", request)
|
||||
}
|
||||
|
||||
type transaction struct {
|
||||
ch chan interface{}
|
||||
incoming chan interface{}
|
||||
quitChan chan bool
|
||||
}
|
||||
|
||||
func (t *transaction) run() {
|
||||
for {
|
||||
select {
|
||||
case msg := <-t.incoming:
|
||||
t.ch <- msg
|
||||
case <-t.quitChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *transaction) add(msg interface{}) {
|
||||
t.incoming <- msg
|
||||
}
|
||||
|
||||
func (t *transaction) quit() {
|
||||
select {
|
||||
case t.quitChan <- true:
|
||||
default:
|
||||
// Already scheduled to quit.
|
||||
}
|
||||
}
|
||||
|
||||
func newTransaction() *transaction {
|
||||
t := &transaction{
|
||||
ch: make(chan interface{}, 1),
|
||||
incoming: make(chan interface{}, 8),
|
||||
quitChan: make(chan bool, 1),
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func newRequest(method string) (map[string]interface{}, *transaction) {
|
||||
req := make(map[string]interface{}, 8)
|
||||
req["janus"] = method
|
||||
return req, newTransaction()
|
||||
}
|
||||
|
||||
type GatewayListener interface {
|
||||
ConnectionInterrupted()
|
||||
}
|
||||
|
||||
type dummyGatewayListener struct {
|
||||
}
|
||||
|
||||
func (l *dummyGatewayListener) ConnectionInterrupted() {
|
||||
}
|
||||
|
||||
// Gateway represents a connection to an instance of the Janus Gateway.
|
||||
type JanusGateway struct {
|
||||
nextTransaction uint64
|
||||
|
||||
listener GatewayListener
|
||||
|
||||
// Sessions is a map of the currently active sessions to the gateway.
|
||||
Sessions map[uint64]*JanusSession
|
||||
|
||||
// Access to the Sessions map should be synchronized with the Gateway.Lock()
|
||||
// and Gateway.Unlock() methods provided by the embedded sync.Mutex.
|
||||
sync.Mutex
|
||||
|
||||
conn *websocket.Conn
|
||||
transactions map[uint64]*transaction
|
||||
|
||||
closeChan chan bool
|
||||
|
||||
writeMu sync.Mutex
|
||||
}
|
||||
|
||||
// Connect creates a new Gateway instance, connected to the Janus Gateway.
|
||||
// path should be a filesystem path to the Unix Socket that the Unix transport
|
||||
// is bound to.
|
||||
// On success, a new Gateway object will be returned and error will be nil.
|
||||
// func Connect(path string, netType string) (*JanusGateway, error) {
|
||||
// conn, err := net.Dial(netType, path)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// gateway := new(Gateway)
|
||||
// //gateway.conn = conn
|
||||
// gateway.transactions = make(map[uint64]chan interface{})
|
||||
// gateway.Sessions = make(map[uint64]*JanusSession)
|
||||
|
||||
// go gateway.recv()
|
||||
// return gateway, nil
|
||||
// }
|
||||
|
||||
func NewJanusGateway(wsURL string, listener GatewayListener) (*JanusGateway, error) {
|
||||
conn, _, err := janusDialer.Dial(wsURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gateway := new(JanusGateway)
|
||||
gateway.conn = conn
|
||||
gateway.transactions = make(map[uint64]*transaction)
|
||||
gateway.Sessions = make(map[uint64]*JanusSession)
|
||||
gateway.closeChan = make(chan bool)
|
||||
if listener == nil {
|
||||
listener = new(dummyGatewayListener)
|
||||
}
|
||||
gateway.listener = listener
|
||||
|
||||
go gateway.ping()
|
||||
go gateway.recv()
|
||||
return gateway, nil
|
||||
}
|
||||
|
||||
// Close closes the underlying connection to the Gateway.
|
||||
func (gateway *JanusGateway) Close() error {
|
||||
gateway.closeChan <- true
|
||||
gateway.writeMu.Lock()
|
||||
if gateway.conn == nil {
|
||||
gateway.writeMu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
err := gateway.conn.Close()
|
||||
gateway.conn = nil
|
||||
gateway.writeMu.Unlock()
|
||||
gateway.cancelTransactions()
|
||||
return err
|
||||
}
|
||||
|
||||
func (gateway *JanusGateway) cancelTransactions() {
|
||||
msg := &janus.ErrorMsg{
|
||||
Err: janus.ErrorData{
|
||||
Code: 500,
|
||||
Reason: "cancelled",
|
||||
},
|
||||
}
|
||||
gateway.Lock()
|
||||
for _, t := range gateway.transactions {
|
||||
go func(t *transaction) {
|
||||
t.add(msg)
|
||||
t.quit()
|
||||
}(t)
|
||||
}
|
||||
gateway.transactions = make(map[uint64]*transaction)
|
||||
gateway.Unlock()
|
||||
}
|
||||
|
||||
func (gateway *JanusGateway) removeTransaction(id uint64) {
|
||||
gateway.Lock()
|
||||
t, found := gateway.transactions[id]
|
||||
if found {
|
||||
delete(gateway.transactions, id)
|
||||
}
|
||||
gateway.Unlock()
|
||||
if t != nil {
|
||||
t.quit()
|
||||
}
|
||||
}
|
||||
|
||||
func (gateway *JanusGateway) send(msg map[string]interface{}, t *transaction) (uint64, error) {
|
||||
id := atomic.AddUint64(&gateway.nextTransaction, 1)
|
||||
msg["transaction"] = strconv.FormatUint(id, 10)
|
||||
data, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
go t.run()
|
||||
gateway.Lock()
|
||||
gateway.transactions[id] = t
|
||||
gateway.Unlock()
|
||||
|
||||
gateway.writeMu.Lock()
|
||||
if gateway.conn == nil {
|
||||
gateway.writeMu.Unlock()
|
||||
gateway.removeTransaction(id)
|
||||
return 0, fmt.Errorf("not connected")
|
||||
}
|
||||
|
||||
err = gateway.conn.WriteMessage(websocket.TextMessage, data)
|
||||
gateway.writeMu.Unlock()
|
||||
if err != nil {
|
||||
gateway.removeTransaction(id)
|
||||
return 0, err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func passMsg(ch chan interface{}, msg interface{}) {
|
||||
ch <- msg
|
||||
}
|
||||
|
||||
func (gateway *JanusGateway) ping() {
|
||||
ticker := time.NewTicker(time.Second * 30)
|
||||
defer ticker.Stop()
|
||||
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
gateway.writeMu.Lock()
|
||||
if gateway.conn == nil {
|
||||
gateway.writeMu.Unlock()
|
||||
continue
|
||||
}
|
||||
|
||||
err := gateway.conn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(20*time.Second))
|
||||
gateway.writeMu.Unlock()
|
||||
if err != nil {
|
||||
log.Println("Error sending ping to MCU:", err)
|
||||
}
|
||||
case <-gateway.closeChan:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gateway *JanusGateway) recv() {
|
||||
var decodeBuffer bytes.Buffer
|
||||
for {
|
||||
// Read message from Gateway
|
||||
|
||||
// Decode to Msg struct
|
||||
var base janus.BaseMsg
|
||||
|
||||
gateway.writeMu.Lock()
|
||||
conn := gateway.conn
|
||||
gateway.writeMu.Unlock()
|
||||
if conn == nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, reader, err := conn.NextReader()
|
||||
if err != nil {
|
||||
log.Printf("conn.NextReader: %s", err)
|
||||
gateway.writeMu.Lock()
|
||||
gateway.conn = nil
|
||||
gateway.writeMu.Unlock()
|
||||
gateway.cancelTransactions()
|
||||
go gateway.listener.ConnectionInterrupted()
|
||||
return
|
||||
}
|
||||
|
||||
decodeBuffer.Reset()
|
||||
if _, err := decodeBuffer.ReadFrom(reader); err != nil {
|
||||
log.Printf("decodeBuffer.ReadFrom: %s", err)
|
||||
gateway.writeMu.Lock()
|
||||
gateway.conn = nil
|
||||
gateway.writeMu.Unlock()
|
||||
gateway.cancelTransactions()
|
||||
go gateway.listener.ConnectionInterrupted()
|
||||
break
|
||||
}
|
||||
|
||||
data := bytes.NewReader(decodeBuffer.Bytes())
|
||||
decoder := json.NewDecoder(data)
|
||||
decoder.UseNumber()
|
||||
if err := decoder.Decode(&base); err != nil {
|
||||
log.Printf("json.Unmarshal of %s: %s", decodeBuffer.String(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
typeFunc, ok := msgtypes[base.Type]
|
||||
if !ok {
|
||||
log.Printf("Unknown message type received: %s", decodeBuffer.String())
|
||||
continue
|
||||
}
|
||||
|
||||
msg := typeFunc()
|
||||
data = bytes.NewReader(decodeBuffer.Bytes())
|
||||
decoder = json.NewDecoder(data)
|
||||
decoder.UseNumber()
|
||||
if err := decoder.Decode(&msg); err != nil {
|
||||
log.Printf("json.Unmarshal of %s: %s", decodeBuffer.String(), err)
|
||||
continue // Decode error
|
||||
}
|
||||
|
||||
// Pass message on from here
|
||||
if base.ID == "" {
|
||||
// Is this a Handle event?
|
||||
if base.Handle == 0 {
|
||||
// Nope. No idea what's going on...
|
||||
// Error()
|
||||
} else {
|
||||
// Lookup Session
|
||||
gateway.Lock()
|
||||
session := gateway.Sessions[base.Session]
|
||||
gateway.Unlock()
|
||||
if session == nil {
|
||||
log.Printf("Unable to deliver message %s. Session %d gone?", decodeBuffer.String(), base.Session)
|
||||
continue
|
||||
}
|
||||
|
||||
// Lookup Handle
|
||||
session.Lock()
|
||||
handle := session.Handles[base.Handle]
|
||||
session.Unlock()
|
||||
if handle == nil {
|
||||
log.Printf("Unable to deliver message %s. Handle %d gone?", decodeBuffer.String(), base.Handle)
|
||||
continue
|
||||
}
|
||||
|
||||
// Pass msg
|
||||
go passMsg(handle.Events, msg)
|
||||
}
|
||||
} else {
|
||||
id, err := strconv.ParseUint(base.ID, 10, 64)
|
||||
if err != nil {
|
||||
log.Printf("Could not decode transaction id %s: %s", base.ID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Lookup Transaction
|
||||
gateway.Lock()
|
||||
transaction := gateway.transactions[id]
|
||||
gateway.Unlock()
|
||||
if transaction == nil {
|
||||
// Error()
|
||||
continue
|
||||
}
|
||||
|
||||
// Pass msg
|
||||
transaction.add(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func waitForMessage(ctx context.Context, t *transaction) (interface{}, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case msg := <-t.ch:
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Info sends an info request to the Gateway.
|
||||
// On success, an InfoMsg will be returned and error will be nil.
|
||||
func (gateway *JanusGateway) Info(ctx context.Context) (*InfoMsg, error) {
|
||||
req, ch := newRequest("info")
|
||||
id, err := gateway.send(req, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer gateway.removeTransaction(id)
|
||||
|
||||
msg, err := waitForMessage(ctx, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *InfoMsg:
|
||||
return msg, nil
|
||||
case *janus.ErrorMsg:
|
||||
return nil, msg
|
||||
}
|
||||
|
||||
return nil, unexpected("info")
|
||||
}
|
||||
|
||||
// Create sends a create request to the Gateway.
|
||||
// On success, a new Session will be returned and error will be nil.
|
||||
func (gateway *JanusGateway) Create(ctx context.Context) (*JanusSession, error) {
|
||||
req, ch := newRequest("create")
|
||||
id, err := gateway.send(req, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer gateway.removeTransaction(id)
|
||||
|
||||
msg, err := waitForMessage(ctx, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var success *janus.SuccessMsg
|
||||
switch msg := msg.(type) {
|
||||
case *janus.SuccessMsg:
|
||||
success = msg
|
||||
case *janus.ErrorMsg:
|
||||
return nil, msg
|
||||
}
|
||||
|
||||
// Create new session
|
||||
session := new(JanusSession)
|
||||
session.gateway = gateway
|
||||
session.Id = success.Data.ID
|
||||
session.Handles = make(map[uint64]*JanusHandle)
|
||||
|
||||
// Store this session
|
||||
gateway.Lock()
|
||||
gateway.Sessions[session.Id] = session
|
||||
gateway.Unlock()
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
// Session represents a session instance on the Janus Gateway.
|
||||
type JanusSession struct {
|
||||
// Id is the session_id of this session
|
||||
Id uint64
|
||||
|
||||
// Handles is a map of plugin handles within this session
|
||||
Handles map[uint64]*JanusHandle
|
||||
|
||||
// Access to the Handles map should be synchronized with the Session.Lock()
|
||||
// and Session.Unlock() methods provided by the embedded sync.Mutex.
|
||||
sync.Mutex
|
||||
|
||||
gateway *JanusGateway
|
||||
}
|
||||
|
||||
func (session *JanusSession) send(msg map[string]interface{}, t *transaction) (uint64, error) {
|
||||
msg["session_id"] = session.Id
|
||||
return session.gateway.send(msg, t)
|
||||
}
|
||||
|
||||
// Attach sends an attach request to the Gateway within this session.
|
||||
// plugin should be the unique string of the plugin to attach to.
|
||||
// On success, a new Handle will be returned and error will be nil.
|
||||
func (session *JanusSession) Attach(ctx context.Context, plugin string) (*JanusHandle, error) {
|
||||
req, ch := newRequest("attach")
|
||||
req["plugin"] = plugin
|
||||
id, err := session.send(req, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer session.gateway.removeTransaction(id)
|
||||
|
||||
msg, err := waitForMessage(ctx, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var success *janus.SuccessMsg
|
||||
switch msg := msg.(type) {
|
||||
case *janus.SuccessMsg:
|
||||
success = msg
|
||||
case *janus.ErrorMsg:
|
||||
return nil, msg
|
||||
}
|
||||
|
||||
handle := new(JanusHandle)
|
||||
handle.session = session
|
||||
handle.Id = success.Data.ID
|
||||
handle.Events = make(chan interface{}, 8)
|
||||
|
||||
session.Lock()
|
||||
session.Handles[handle.Id] = handle
|
||||
session.Unlock()
|
||||
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
// KeepAlive sends a keep-alive request to the Gateway.
|
||||
// On success, an AckMsg will be returned and error will be nil.
|
||||
func (session *JanusSession) KeepAlive(ctx context.Context) (*janus.AckMsg, error) {
|
||||
req, ch := newRequest("keepalive")
|
||||
id, err := session.send(req, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer session.gateway.removeTransaction(id)
|
||||
|
||||
msg, err := waitForMessage(ctx, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch msg := msg.(type) {
|
||||
case *janus.AckMsg:
|
||||
return msg, nil
|
||||
case *janus.ErrorMsg:
|
||||
return nil, msg
|
||||
}
|
||||
|
||||
return nil, unexpected("keepalive")
|
||||
}
|
||||
|
||||
// Destroy sends a destroy request to the Gateway to tear down this session.
|
||||
// On success, the Session will be removed from the Gateway.Sessions map, an
|
||||
// AckMsg will be returned and error will be nil.
|
||||
func (session *JanusSession) Destroy(ctx context.Context) (*janus.AckMsg, error) {
|
||||
req, ch := newRequest("destroy")
|
||||
id, err := session.send(req, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer session.gateway.removeTransaction(id)
|
||||
|
||||
msg, err := waitForMessage(ctx, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ack *janus.AckMsg
|
||||
switch msg := msg.(type) {
|
||||
case *janus.AckMsg:
|
||||
ack = msg
|
||||
case *janus.ErrorMsg:
|
||||
return nil, msg
|
||||
}
|
||||
|
||||
// Remove this session from the gateway
|
||||
session.gateway.Lock()
|
||||
delete(session.gateway.Sessions, session.Id)
|
||||
session.gateway.Unlock()
|
||||
|
||||
return ack, nil
|
||||
}
|
||||
|
||||
// Handle represents a handle to a plugin instance on the Gateway.
|
||||
type JanusHandle struct {
|
||||
// Id is the handle_id of this plugin handle
|
||||
Id uint64
|
||||
|
||||
// Type // pub or sub
|
||||
Type string
|
||||
|
||||
//User // Userid
|
||||
User string
|
||||
|
||||
// Events is a receive only channel that can be used to receive events
|
||||
// related to this handle from the gateway.
|
||||
Events chan interface{}
|
||||
|
||||
session *JanusSession
|
||||
}
|
||||
|
||||
func (handle *JanusHandle) send(msg map[string]interface{}, t *transaction) (uint64, error) {
|
||||
msg["handle_id"] = handle.Id
|
||||
return handle.session.send(msg, t)
|
||||
}
|
||||
|
||||
// send sync request
|
||||
func (handle *JanusHandle) Request(ctx context.Context, body interface{}) (*janus.SuccessMsg, error) {
|
||||
req, ch := newRequest("message")
|
||||
if body != nil {
|
||||
req["body"] = body
|
||||
}
|
||||
id, err := handle.send(req, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer handle.session.gateway.removeTransaction(id)
|
||||
|
||||
msg, err := waitForMessage(ctx, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch msg := msg.(type) {
|
||||
case *janus.SuccessMsg:
|
||||
return msg, nil
|
||||
case *janus.ErrorMsg:
|
||||
return nil, msg
|
||||
}
|
||||
|
||||
return nil, unexpected("message")
|
||||
}
|
||||
|
||||
// Message sends a message request to a plugin handle on the Gateway.
|
||||
// body should be the plugin data to be passed to the plugin, and jsep should
|
||||
// contain an optional SDP offer/answer to establish a WebRTC PeerConnection.
|
||||
// On success, an EventMsg will be returned and error will be nil.
|
||||
func (handle *JanusHandle) Message(ctx context.Context, body, jsep interface{}) (*janus.EventMsg, error) {
|
||||
req, ch := newRequest("message")
|
||||
if body != nil {
|
||||
req["body"] = body
|
||||
}
|
||||
if jsep != nil {
|
||||
req["jsep"] = jsep
|
||||
}
|
||||
id, err := handle.send(req, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer handle.session.gateway.removeTransaction(id)
|
||||
|
||||
GetMessage: // No tears..
|
||||
msg, err := waitForMessage(ctx, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch msg := msg.(type) {
|
||||
case *janus.AckMsg:
|
||||
goto GetMessage // ..only dreams.
|
||||
case *janus.EventMsg:
|
||||
return msg, nil
|
||||
case *janus.ErrorMsg:
|
||||
return nil, msg
|
||||
}
|
||||
|
||||
return nil, unexpected("message")
|
||||
}
|
||||
|
||||
// Trickle sends a trickle request to the Gateway as part of establishing
|
||||
// a new PeerConnection with a plugin.
|
||||
// candidate should be a single ICE candidate, or a completed object to
|
||||
// signify that all candidates have been sent:
|
||||
// {
|
||||
// "completed": true
|
||||
// }
|
||||
// On success, an AckMsg will be returned and error will be nil.
|
||||
func (handle *JanusHandle) Trickle(ctx context.Context, candidate interface{}) (*janus.AckMsg, error) {
|
||||
req, ch := newRequest("trickle")
|
||||
req["candidate"] = candidate
|
||||
id, err := handle.send(req, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer handle.session.gateway.removeTransaction(id)
|
||||
|
||||
msg, err := waitForMessage(ctx, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch msg := msg.(type) {
|
||||
case *janus.AckMsg:
|
||||
return msg, nil
|
||||
case *janus.ErrorMsg:
|
||||
return nil, msg
|
||||
}
|
||||
|
||||
return nil, unexpected("trickle")
|
||||
}
|
||||
|
||||
// TrickleMany sends a trickle request to the Gateway as part of establishing
|
||||
// a new PeerConnection with a plugin.
|
||||
// candidates should be an array of ICE candidates.
|
||||
// On success, an AckMsg will be returned and error will be nil.
|
||||
func (handle *JanusHandle) TrickleMany(ctx context.Context, candidates interface{}) (*janus.AckMsg, error) {
|
||||
req, ch := newRequest("trickle")
|
||||
req["candidates"] = candidates
|
||||
id, err := handle.send(req, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
handle.session.gateway.removeTransaction(id)
|
||||
|
||||
msg, err := waitForMessage(ctx, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch msg := msg.(type) {
|
||||
case *janus.AckMsg:
|
||||
return msg, nil
|
||||
case *janus.ErrorMsg:
|
||||
return nil, msg
|
||||
}
|
||||
|
||||
return nil, unexpected("trickle")
|
||||
}
|
||||
|
||||
// Detach sends a detach request to the Gateway to remove this handle.
|
||||
// On success, an AckMsg will be returned and error will be nil.
|
||||
func (handle *JanusHandle) Detach(ctx context.Context) (*janus.AckMsg, error) {
|
||||
req, ch := newRequest("detach")
|
||||
id, err := handle.send(req, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer handle.session.gateway.removeTransaction(id)
|
||||
|
||||
msg, err := waitForMessage(ctx, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ack *janus.AckMsg
|
||||
switch msg := msg.(type) {
|
||||
case *janus.AckMsg:
|
||||
ack = msg
|
||||
case *janus.ErrorMsg:
|
||||
return nil, msg
|
||||
}
|
||||
|
||||
// Remove this handle from the session
|
||||
handle.session.Lock()
|
||||
delete(handle.session.Handles, handle.Id)
|
||||
handle.session.Unlock()
|
||||
|
||||
return ack, nil
|
||||
}
|
||||
Reference in New Issue
Block a user