mirror of
				https://github.com/ossrs/srs.git
				synced 2025-03-09 15:49:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1077 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1077 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// The MIT License (MIT)
 | 
						|
//
 | 
						|
// # Copyright (c) 2021 Winlin
 | 
						|
//
 | 
						|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
 | 
						|
// this software and associated documentation files (the "Software"), to deal in
 | 
						|
// the Software without restriction, including without limitation the rights to
 | 
						|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 | 
						|
// the Software, and to permit persons to whom the Software is furnished to do so,
 | 
						|
// subject to the following conditions:
 | 
						|
//
 | 
						|
// The above copyright notice and this permission notice shall be included in all
 | 
						|
// copies or substantial portions of the Software.
 | 
						|
//
 | 
						|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
						|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 | 
						|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 | 
						|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 | 
						|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 | 
						|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 | 
						|
package janus
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"encoding/json"
 | 
						|
	"fmt"
 | 
						|
	"github.com/ossrs/go-oryx-lib/errors"
 | 
						|
	"github.com/ossrs/go-oryx-lib/logger"
 | 
						|
	"io/ioutil"
 | 
						|
	"net/http"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
type publisherInfo struct {
 | 
						|
	AudioCodec string `json:"audio_codec"`
 | 
						|
	Display    string `json:"display"`
 | 
						|
	ID         uint64 `json:"id"`
 | 
						|
	Talking    bool   `json:"talking"`
 | 
						|
	VideoCodec string `json:"video_codec"`
 | 
						|
}
 | 
						|
 | 
						|
func (v publisherInfo) String() string {
 | 
						|
	return fmt.Sprintf("%v(codec:%v/%v,id:%v,talk:%v)",
 | 
						|
		v.Display, v.VideoCodec, v.AudioCodec, v.ID, v.Talking)
 | 
						|
}
 | 
						|
 | 
						|
type janusReply struct {
 | 
						|
	transactionID string
 | 
						|
	replies       chan []byte
 | 
						|
}
 | 
						|
 | 
						|
func newJanusReply(tid string) *janusReply {
 | 
						|
	return &janusReply{
 | 
						|
		transactionID: tid,
 | 
						|
		replies:       make(chan []byte, 1),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type janusHandle struct {
 | 
						|
	api *janusAPI
 | 
						|
	// The ID created by API.
 | 
						|
	handleID    uint64
 | 
						|
	publisherID uint64
 | 
						|
}
 | 
						|
 | 
						|
type janusAPI struct {
 | 
						|
	// For example, http://localhost:8088/janus
 | 
						|
	r string
 | 
						|
	// The ID created by API.
 | 
						|
	sessionID uint64 // By Create().
 | 
						|
	privateID uint64 // By JoinAsPublisher().
 | 
						|
	// The handles, key is handleID, value is *janusHandle
 | 
						|
	handles sync.Map
 | 
						|
	// The callbacks.
 | 
						|
	onDetached    func(sender, sessionID uint64)
 | 
						|
	onWebrtcUp    func(sender, sessionID uint64)
 | 
						|
	onMedia       func(sender, sessionID uint64, mtype string, receiving bool)
 | 
						|
	onSlowLink    func(sender, sessionID uint64, media string, lost uint64, uplink bool)
 | 
						|
	onPublisher   func(sender, sessionID uint64, publishers []publisherInfo)
 | 
						|
	onUnPublished func(sender, sessionID, id uint64)
 | 
						|
	onLeave       func(sender, sessionID, id uint64)
 | 
						|
	// The context for polling.
 | 
						|
	pollingCtx    context.Context
 | 
						|
	pollingCancel context.CancelFunc
 | 
						|
	wg            sync.WaitGroup
 | 
						|
	// The replies of polling key is transactionID, value is janusReply.
 | 
						|
	replies sync.Map
 | 
						|
}
 | 
						|
 | 
						|
func newJanusAPI(r string) *janusAPI {
 | 
						|
	v := &janusAPI{r: r}
 | 
						|
	if !strings.HasSuffix(r, "/") {
 | 
						|
		v.r += "/"
 | 
						|
	}
 | 
						|
	v.onDetached = func(sender, sessionID uint64) {
 | 
						|
	}
 | 
						|
	v.onWebrtcUp = func(sender, sessionID uint64) {
 | 
						|
	}
 | 
						|
	v.onMedia = func(sender, sessionID uint64, mtype string, receiving bool) {
 | 
						|
	}
 | 
						|
	v.onSlowLink = func(sender, sessionID uint64, media string, lost uint64, uplink bool) {
 | 
						|
	}
 | 
						|
	v.onPublisher = func(sender, sessionID uint64, publishers []publisherInfo) {
 | 
						|
	}
 | 
						|
	v.onUnPublished = func(sender, sessionID, id uint64) {
 | 
						|
	}
 | 
						|
	v.onLeave = func(sender, sessionID, id uint64) {
 | 
						|
	}
 | 
						|
	return v
 | 
						|
}
 | 
						|
 | 
						|
func (v *janusAPI) Close() error {
 | 
						|
	v.pollingCancel()
 | 
						|
	v.wg.Wait()
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (v *janusAPI) Create(ctx context.Context) error {
 | 
						|
	v.pollingCtx, v.pollingCancel = context.WithCancel(ctx)
 | 
						|
 | 
						|
	api := v.r
 | 
						|
 | 
						|
	reqBody := struct {
 | 
						|
		Janus       string `json:"janus"`
 | 
						|
		Transaction string `json:"transaction"`
 | 
						|
	}{
 | 
						|
		"create", newTransactionID(),
 | 
						|
	}
 | 
						|
 | 
						|
	b, err := json.Marshal(reqBody)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "Marshal body %v", reqBody)
 | 
						|
	}
 | 
						|
	logger.Tf(ctx, "Request url api=%v with %v", api, string(b))
 | 
						|
 | 
						|
	req, err := http.NewRequest("POST", api, strings.NewReader(string(b)))
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "HTTP request %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	res, err := http.DefaultClient.Do(req.WithContext(ctx))
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "Do HTTP request %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	b2, err := ioutil.ReadAll(res.Body)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "Read response for %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	s2 := escapeJSON(string(b2))
 | 
						|
	logger.Tf(ctx, "Response from %v is %v", api, s2)
 | 
						|
 | 
						|
	resBody := struct {
 | 
						|
		Janus       string `json:"janus"`
 | 
						|
		Transaction string `json:"transaction"`
 | 
						|
		Data        struct {
 | 
						|
			ID uint64 `json:"id"`
 | 
						|
		} `json:"data"`
 | 
						|
	}{}
 | 
						|
	if err := json.Unmarshal([]byte(s2), &resBody); err != nil {
 | 
						|
		return errors.Wrapf(err, "Marshal %v", s2)
 | 
						|
	}
 | 
						|
	if resBody.Janus != "success" {
 | 
						|
		return errors.Errorf("Server fail code=%v %v", resBody.Janus, s2)
 | 
						|
	}
 | 
						|
 | 
						|
	v.sessionID = resBody.Data.ID
 | 
						|
	logger.Tf(ctx, "Parse create sessionID=%v", v.sessionID)
 | 
						|
 | 
						|
	v.wg.Add(1)
 | 
						|
	go func() {
 | 
						|
		defer v.wg.Done()
 | 
						|
		defer v.pollingCancel()
 | 
						|
 | 
						|
		for v.pollingCtx.Err() == nil {
 | 
						|
			if err := v.polling(v.pollingCtx); err != nil {
 | 
						|
				if v.pollingCtx.Err() != context.Canceled {
 | 
						|
					logger.Wf(ctx, "polling err %+v", err)
 | 
						|
				}
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (v *janusAPI) AttachPlugin(ctx context.Context) (handleID uint64, err error) {
 | 
						|
	api := fmt.Sprintf("%v%v", v.r, v.sessionID)
 | 
						|
 | 
						|
	reqBody := struct {
 | 
						|
		Janus       string `json:"janus"`
 | 
						|
		OpaqueID    string `json:"opaque_id"`
 | 
						|
		Plugin      string `json:"plugin"`
 | 
						|
		Transaction string `json:"transaction"`
 | 
						|
	}{
 | 
						|
		"attach", newTransactionID(),
 | 
						|
		"janus.plugin.videoroom", newTransactionID(),
 | 
						|
	}
 | 
						|
 | 
						|
	b, err := json.Marshal(reqBody)
 | 
						|
	if err != nil {
 | 
						|
		return 0, errors.Wrapf(err, "Marshal body %v", reqBody)
 | 
						|
	}
 | 
						|
	logger.Tf(ctx, "Request url api=%v with %v", api, string(b))
 | 
						|
 | 
						|
	req, err := http.NewRequest("POST", api, strings.NewReader(string(b)))
 | 
						|
	if err != nil {
 | 
						|
		return 0, errors.Wrapf(err, "HTTP request %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	res, err := http.DefaultClient.Do(req.WithContext(ctx))
 | 
						|
	if err != nil {
 | 
						|
		return 0, errors.Wrapf(err, "Do HTTP request %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	b2, err := ioutil.ReadAll(res.Body)
 | 
						|
	if err != nil {
 | 
						|
		return 0, errors.Wrapf(err, "Read response for %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	s2 := escapeJSON(string(b2))
 | 
						|
	logger.Tf(ctx, "Response from %v is %v", api, s2)
 | 
						|
 | 
						|
	resBody := struct {
 | 
						|
		Janus       string `json:"janus"`
 | 
						|
		SessionID   uint64 `json:"session_id"`
 | 
						|
		Transaction string `json:"transaction"`
 | 
						|
		Data        struct {
 | 
						|
			ID uint64 `json:"id"`
 | 
						|
		} `json:"data"`
 | 
						|
	}{}
 | 
						|
	if err := json.Unmarshal([]byte(s2), &resBody); err != nil {
 | 
						|
		return 0, errors.Wrapf(err, "Marshal %v", s2)
 | 
						|
	}
 | 
						|
	if resBody.Janus != "success" {
 | 
						|
		return 0, errors.Errorf("Server fail code=%v %v", resBody.Janus, s2)
 | 
						|
	}
 | 
						|
 | 
						|
	h := &janusHandle{}
 | 
						|
	h.handleID = resBody.Data.ID
 | 
						|
	h.api = v
 | 
						|
	v.handles.Store(h.handleID, h)
 | 
						|
	logger.Tf(ctx, "Parse create handleID=%v", h.handleID)
 | 
						|
 | 
						|
	return h.handleID, nil
 | 
						|
}
 | 
						|
 | 
						|
func (v *janusAPI) DetachPlugin(ctx context.Context, handleID uint64) error {
 | 
						|
	handler := v.loadHandler(handleID)
 | 
						|
	api := fmt.Sprintf("%v%v/%v", v.r, v.sessionID, handler.handleID)
 | 
						|
 | 
						|
	reqBody := struct {
 | 
						|
		Janus       string `json:"janus"`
 | 
						|
		Transaction string `json:"transaction"`
 | 
						|
	}{
 | 
						|
		"detach", newTransactionID(),
 | 
						|
	}
 | 
						|
 | 
						|
	b, err := json.Marshal(reqBody)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "Marshal body %v", reqBody)
 | 
						|
	}
 | 
						|
	logger.Tf(ctx, "Request url api=%v with %v", api, string(b))
 | 
						|
 | 
						|
	req, err := http.NewRequest("POST", api, strings.NewReader(string(b)))
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "HTTP request %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	res, err := http.DefaultClient.Do(req.WithContext(ctx))
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "Do HTTP request %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	b2, err := ioutil.ReadAll(res.Body)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "Read response for %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	s2 := escapeJSON(string(b2))
 | 
						|
	logger.Tf(ctx, "Response from %v is %v", api, s2)
 | 
						|
 | 
						|
	ackBody := struct {
 | 
						|
		Janus       string `json:"janus"`
 | 
						|
		SessionID   uint64 `json:"session_id"`
 | 
						|
		Transaction string `json:"transaction"`
 | 
						|
	}{}
 | 
						|
	if err := json.Unmarshal([]byte(s2), &ackBody); err != nil {
 | 
						|
		return errors.Wrapf(err, "Marshal %v", s2)
 | 
						|
	}
 | 
						|
	if ackBody.Janus != "success" {
 | 
						|
		return errors.Errorf("Server fail code=%v %v", ackBody.Janus, s2)
 | 
						|
	}
 | 
						|
	logger.Tf(ctx, "Detach tid=%v done", reqBody.Transaction)
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (v *janusAPI) loadHandler(handleID uint64) *janusHandle {
 | 
						|
	if h, ok := v.handles.Load(handleID); !ok {
 | 
						|
		return nil
 | 
						|
	} else {
 | 
						|
		return h.(*janusHandle)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (v *janusAPI) JoinAsPublisher(ctx context.Context, handleID uint64, room int, display string) error {
 | 
						|
	handler := v.loadHandler(handleID)
 | 
						|
	api := fmt.Sprintf("%v%v/%v", v.r, v.sessionID, handler.handleID)
 | 
						|
 | 
						|
	reqBodyBody := struct {
 | 
						|
		Request string `json:"request"`
 | 
						|
		PType   string `json:"ptype"`
 | 
						|
		Room    int    `json:"room"`
 | 
						|
		Display string `json:"display"`
 | 
						|
	}{
 | 
						|
		"join", "publisher", room, display,
 | 
						|
	}
 | 
						|
	reqBody := struct {
 | 
						|
		Janus       string      `json:"janus"`
 | 
						|
		Transaction string      `json:"transaction"`
 | 
						|
		Body        interface{} `json:"body"`
 | 
						|
	}{
 | 
						|
		"message", newTransactionID(), reqBodyBody,
 | 
						|
	}
 | 
						|
 | 
						|
	reply := newJanusReply(reqBody.Transaction)
 | 
						|
	v.replies.Store(reqBody.Transaction, reply)
 | 
						|
 | 
						|
	b, err := json.Marshal(reqBody)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "Marshal body %v", reqBody)
 | 
						|
	}
 | 
						|
	logger.Tf(ctx, "Request url api=%v with %v", api, string(b))
 | 
						|
 | 
						|
	req, err := http.NewRequest("POST", api, strings.NewReader(string(b)))
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "HTTP request %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	res, err := http.DefaultClient.Do(req.WithContext(ctx))
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "Do HTTP request %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	b2, err := ioutil.ReadAll(res.Body)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "Read response for %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	s2 := escapeJSON(string(b2))
 | 
						|
	logger.Tf(ctx, "Response from %v is %v", api, s2)
 | 
						|
 | 
						|
	ackBody := struct {
 | 
						|
		Janus       string `json:"janus"`
 | 
						|
		SessionID   uint64 `json:"session_id"`
 | 
						|
		Transaction string `json:"transaction"`
 | 
						|
	}{}
 | 
						|
	if err := json.Unmarshal([]byte(s2), &ackBody); err != nil {
 | 
						|
		return errors.Wrapf(err, "Marshal %v", s2)
 | 
						|
	}
 | 
						|
	if ackBody.Janus != "ack" {
 | 
						|
		return errors.Errorf("Server fail code=%v %v", ackBody.Janus, s2)
 | 
						|
	}
 | 
						|
	logger.Tf(ctx, "Response tid=%v ack", reply.transactionID)
 | 
						|
 | 
						|
	// Reply from polling.
 | 
						|
	var s3 string
 | 
						|
	select {
 | 
						|
	case <-ctx.Done():
 | 
						|
		return ctx.Err()
 | 
						|
	case b3 := <-reply.replies:
 | 
						|
		s3 = escapeJSON(string(b3))
 | 
						|
		logger.Tf(ctx, "Async response tid=%v, reply=%v", reply.transactionID, s3)
 | 
						|
	}
 | 
						|
	resBody := struct {
 | 
						|
		Janus       string `json:"janus"`
 | 
						|
		Session     uint64 `json:"session_id"`
 | 
						|
		Transaction string `json:"transaction"`
 | 
						|
		Sender      uint64 `json:"sender"`
 | 
						|
		PluginData  struct {
 | 
						|
			Plugin string `json:"plugin"`
 | 
						|
			Data   struct {
 | 
						|
				VideoRoom   string          `json:"videoroom"`
 | 
						|
				Room        int             `json:"room"`
 | 
						|
				Description string          `json:"description"`
 | 
						|
				ID          uint64          `json:"id"`
 | 
						|
				PrivateID   uint64          `json:"private_id"`
 | 
						|
				Publishers  []publisherInfo `json:"publishers"`
 | 
						|
			} `json:"data"`
 | 
						|
		} `json:"plugindata"`
 | 
						|
	}{}
 | 
						|
	if err := json.Unmarshal([]byte(s3), &resBody); err != nil {
 | 
						|
		return errors.Wrapf(err, "Marshal %v", s3)
 | 
						|
	}
 | 
						|
 | 
						|
	plugin := resBody.PluginData.Data
 | 
						|
	if resBody.Janus != "event" || plugin.VideoRoom != "joined" {
 | 
						|
		return errors.Errorf("Server fail janus=%v, plugin=%v %v", resBody.Janus, plugin.VideoRoom, s3)
 | 
						|
	}
 | 
						|
 | 
						|
	handler.publisherID = plugin.ID
 | 
						|
	v.privateID = plugin.PrivateID
 | 
						|
	logger.Tf(ctx, "Join as publisher room=%v, display=%v, tid=%v ok, event=%v, plugin=%v, id=%v, private=%v, publishers=%v",
 | 
						|
		room, display, reply.transactionID, resBody.Janus, plugin.VideoRoom, handler.publisherID, plugin.PrivateID, len(plugin.Publishers))
 | 
						|
 | 
						|
	if len(plugin.Publishers) > 0 {
 | 
						|
		v.onPublisher(resBody.Sender, resBody.Session, plugin.Publishers)
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (v *janusAPI) UnPublish(ctx context.Context, handleID uint64) error {
 | 
						|
	handler := v.loadHandler(handleID)
 | 
						|
	api := fmt.Sprintf("%v%v/%v", v.r, v.sessionID, handler.handleID)
 | 
						|
 | 
						|
	reqBodyBody := struct {
 | 
						|
		Request string `json:"request"`
 | 
						|
	}{
 | 
						|
		"unpublish",
 | 
						|
	}
 | 
						|
	reqBody := struct {
 | 
						|
		Janus       string      `json:"janus"`
 | 
						|
		Transaction string      `json:"transaction"`
 | 
						|
		Body        interface{} `json:"body"`
 | 
						|
	}{
 | 
						|
		"message", newTransactionID(), reqBodyBody,
 | 
						|
	}
 | 
						|
 | 
						|
	b, err := json.Marshal(reqBody)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "Marshal body %v", reqBody)
 | 
						|
	}
 | 
						|
	logger.Tf(ctx, "Request url api=%v with %v", api, string(b))
 | 
						|
 | 
						|
	req, err := http.NewRequest("POST", api, strings.NewReader(string(b)))
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "HTTP request %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	res, err := http.DefaultClient.Do(req.WithContext(ctx))
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "Do HTTP request %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	b2, err := ioutil.ReadAll(res.Body)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "Read response for %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	s2 := escapeJSON(string(b2))
 | 
						|
	logger.Tf(ctx, "Response from %v is %v", api, s2)
 | 
						|
 | 
						|
	ackBody := struct {
 | 
						|
		Janus       string `json:"janus"`
 | 
						|
		SessionID   uint64 `json:"session_id"`
 | 
						|
		Transaction string `json:"transaction"`
 | 
						|
	}{}
 | 
						|
	if err := json.Unmarshal([]byte(s2), &ackBody); err != nil {
 | 
						|
		return errors.Wrapf(err, "Marshal %v", s2)
 | 
						|
	}
 | 
						|
	if ackBody.Janus != "ack" {
 | 
						|
		return errors.Errorf("Server fail code=%v %v", ackBody.Janus, s2)
 | 
						|
	}
 | 
						|
	logger.Tf(ctx, "UnPublish tid=%v done", reqBody.Transaction)
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (v *janusAPI) Publish(ctx context.Context, handleID uint64, offer string) (answer string, err error) {
 | 
						|
	handler := v.loadHandler(handleID)
 | 
						|
	api := fmt.Sprintf("%v%v/%v", v.r, v.sessionID, handler.handleID)
 | 
						|
 | 
						|
	reqBodyBody := struct {
 | 
						|
		Request string `json:"request"`
 | 
						|
		Video   bool   `json:"video"`
 | 
						|
		Audio   bool   `json:"audio"`
 | 
						|
	}{
 | 
						|
		"configure", true, true,
 | 
						|
	}
 | 
						|
	jsepBody := struct {
 | 
						|
		Type string `json:"type"`
 | 
						|
		SDP  string `json:"sdp"`
 | 
						|
	}{
 | 
						|
		"offer", offer,
 | 
						|
	}
 | 
						|
	reqBody := struct {
 | 
						|
		Janus       string      `json:"janus"`
 | 
						|
		Transaction string      `json:"transaction"`
 | 
						|
		Body        interface{} `json:"body"`
 | 
						|
		JSEP        interface{} `json:"jsep"`
 | 
						|
	}{
 | 
						|
		"message", newTransactionID(), reqBodyBody, jsepBody,
 | 
						|
	}
 | 
						|
 | 
						|
	reply := newJanusReply(reqBody.Transaction)
 | 
						|
	v.replies.Store(reqBody.Transaction, reply)
 | 
						|
 | 
						|
	b, err := json.Marshal(reqBody)
 | 
						|
	if err != nil {
 | 
						|
		return "", errors.Wrapf(err, "Marshal body %v", reqBody)
 | 
						|
	}
 | 
						|
	logger.Tf(ctx, "Request url api=%v with %v", api, string(b))
 | 
						|
 | 
						|
	req, err := http.NewRequest("POST", api, strings.NewReader(string(b)))
 | 
						|
	if err != nil {
 | 
						|
		return "", errors.Wrapf(err, "HTTP request %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	res, err := http.DefaultClient.Do(req.WithContext(ctx))
 | 
						|
	if err != nil {
 | 
						|
		return "", errors.Wrapf(err, "Do HTTP request %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	b2, err := ioutil.ReadAll(res.Body)
 | 
						|
	if err != nil {
 | 
						|
		return "", errors.Wrapf(err, "Read response for %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	s2 := escapeJSON(string(b2))
 | 
						|
	logger.Tf(ctx, "Response from %v is %v", api, s2)
 | 
						|
 | 
						|
	ackBody := struct {
 | 
						|
		Janus       string `json:"janus"`
 | 
						|
		SessionID   uint64 `json:"session_id"`
 | 
						|
		Transaction string `json:"transaction"`
 | 
						|
	}{}
 | 
						|
	if err := json.Unmarshal([]byte(s2), &ackBody); err != nil {
 | 
						|
		return "", errors.Wrapf(err, "Marshal %v", s2)
 | 
						|
	}
 | 
						|
	if ackBody.Janus != "ack" {
 | 
						|
		return "", errors.Errorf("Server fail code=%v %v", ackBody.Janus, s2)
 | 
						|
	}
 | 
						|
	logger.Tf(ctx, "Response tid=%v ack", reply.transactionID)
 | 
						|
 | 
						|
	// Reply from polling.
 | 
						|
	var s3 string
 | 
						|
	select {
 | 
						|
	case <-ctx.Done():
 | 
						|
		return "", ctx.Err()
 | 
						|
	case b3 := <-reply.replies:
 | 
						|
		s3 = escapeJSON(string(b3))
 | 
						|
		logger.Tf(ctx, "Async response tid=%v, reply=%v", reply.transactionID, s3)
 | 
						|
	}
 | 
						|
	resBody := struct {
 | 
						|
		Janus       string `json:"janus"`
 | 
						|
		Session     uint64 `json:"session_id"`
 | 
						|
		Transaction string `json:"transaction"`
 | 
						|
		Sender      uint64 `json:"sender"`
 | 
						|
		PluginData  struct {
 | 
						|
			Plugin string `json:"plugin"`
 | 
						|
			Data   struct {
 | 
						|
				VideoRoom  string `json:"videoroom"`
 | 
						|
				Room       int    `json:"room"`
 | 
						|
				Configured string `json:"configured"`
 | 
						|
				AudioCodec string `json:"audio_codec"`
 | 
						|
				VideoCodec string `json:"video_codec"`
 | 
						|
			} `json:"data"`
 | 
						|
		} `json:"plugindata"`
 | 
						|
		JSEP struct {
 | 
						|
			Type string `json:"type"`
 | 
						|
			SDP  string `json:"sdp"`
 | 
						|
		} `json:"jsep"`
 | 
						|
	}{}
 | 
						|
	if err := json.Unmarshal([]byte(s3), &resBody); err != nil {
 | 
						|
		return "", errors.Wrapf(err, "Marshal %v", s3)
 | 
						|
	}
 | 
						|
 | 
						|
	plugin := resBody.PluginData.Data
 | 
						|
	jsep := resBody.JSEP
 | 
						|
	if resBody.Janus != "event" || plugin.VideoRoom != "event" {
 | 
						|
		return "", errors.Errorf("Server fail janus=%v, plugin=%v %v", resBody.Janus, plugin.VideoRoom, s3)
 | 
						|
	}
 | 
						|
	logger.Tf(ctx, "Configure publisher offer=%vB, tid=%v ok, event=%v, plugin=%v, answer=%vB",
 | 
						|
		len(offer), reply.transactionID, resBody.Janus, plugin.VideoRoom, len(jsep.SDP))
 | 
						|
 | 
						|
	return jsep.SDP, nil
 | 
						|
}
 | 
						|
 | 
						|
func (v *janusAPI) JoinAsSubscribe(ctx context.Context, handleID uint64, room int, publisher *publisherInfo) (offer string, err error) {
 | 
						|
	handler := v.loadHandler(handleID)
 | 
						|
	api := fmt.Sprintf("%v%v/%v", v.r, v.sessionID, handler.handleID)
 | 
						|
 | 
						|
	reqBodyBody := struct {
 | 
						|
		Request   string `json:"request"`
 | 
						|
		PType     string `json:"ptype"`
 | 
						|
		Room      int    `json:"room"`
 | 
						|
		Feed      uint64 `json:"feed"`
 | 
						|
		PrivateID uint64 `json:"private_id"`
 | 
						|
	}{
 | 
						|
		"join", "subscriber", room, publisher.ID, v.privateID,
 | 
						|
	}
 | 
						|
	reqBody := struct {
 | 
						|
		Janus       string      `json:"janus"`
 | 
						|
		Transaction string      `json:"transaction"`
 | 
						|
		Body        interface{} `json:"body"`
 | 
						|
	}{
 | 
						|
		"message", newTransactionID(), reqBodyBody,
 | 
						|
	}
 | 
						|
 | 
						|
	reply := newJanusReply(reqBody.Transaction)
 | 
						|
	v.replies.Store(reqBody.Transaction, reply)
 | 
						|
 | 
						|
	b, err := json.Marshal(reqBody)
 | 
						|
	if err != nil {
 | 
						|
		return "", errors.Wrapf(err, "Marshal body %v", reqBody)
 | 
						|
	}
 | 
						|
	logger.Tf(ctx, "Request url api=%v with %v", api, string(b))
 | 
						|
 | 
						|
	req, err := http.NewRequest("POST", api, strings.NewReader(string(b)))
 | 
						|
	if err != nil {
 | 
						|
		return "", errors.Wrapf(err, "HTTP request %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	res, err := http.DefaultClient.Do(req.WithContext(ctx))
 | 
						|
	if err != nil {
 | 
						|
		return "", errors.Wrapf(err, "Do HTTP request %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	b2, err := ioutil.ReadAll(res.Body)
 | 
						|
	if err != nil {
 | 
						|
		return "", errors.Wrapf(err, "Read response for %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	s2 := escapeJSON(string(b2))
 | 
						|
	logger.Tf(ctx, "Response from %v is %v", api, s2)
 | 
						|
 | 
						|
	ackBody := struct {
 | 
						|
		Janus       string `json:"janus"`
 | 
						|
		SessionID   uint64 `json:"session_id"`
 | 
						|
		Transaction string `json:"transaction"`
 | 
						|
	}{}
 | 
						|
	if err := json.Unmarshal([]byte(s2), &ackBody); err != nil {
 | 
						|
		return "", errors.Wrapf(err, "Marshal %v", s2)
 | 
						|
	}
 | 
						|
	if ackBody.Janus != "ack" {
 | 
						|
		return "", errors.Errorf("Server fail code=%v %v", ackBody.Janus, s2)
 | 
						|
	}
 | 
						|
	logger.Tf(ctx, "Response tid=%v ack", reply.transactionID)
 | 
						|
 | 
						|
	// Reply from polling.
 | 
						|
	var s3 string
 | 
						|
	select {
 | 
						|
	case <-ctx.Done():
 | 
						|
		return "", ctx.Err()
 | 
						|
	case b3 := <-reply.replies:
 | 
						|
		s3 = escapeJSON(string(b3))
 | 
						|
		logger.Tf(ctx, "Async response tid=%v, reply=%v", reply.transactionID, s3)
 | 
						|
	}
 | 
						|
	resBody := struct {
 | 
						|
		Janus       string `json:"janus"`
 | 
						|
		Session     uint64 `json:"session_id"`
 | 
						|
		Transaction string `json:"transaction"`
 | 
						|
		Sender      uint64 `json:"sender"`
 | 
						|
		PluginData  struct {
 | 
						|
			Plugin string `json:"plugin"`
 | 
						|
			Data   struct {
 | 
						|
				VideoRoom string `json:"videoroom"`
 | 
						|
				Room      int    `json:"room"`
 | 
						|
				ID        uint64 `json:"id"`
 | 
						|
				Display   string `json:"display"`
 | 
						|
			} `json:"data"`
 | 
						|
		} `json:"plugindata"`
 | 
						|
		JSEP struct {
 | 
						|
			Type string `json:"type"`
 | 
						|
			SDP  string `json:"sdp"`
 | 
						|
		} `json:"jsep"`
 | 
						|
	}{}
 | 
						|
	if err := json.Unmarshal([]byte(s3), &resBody); err != nil {
 | 
						|
		return "", errors.Wrapf(err, "Marshal %v", s3)
 | 
						|
	}
 | 
						|
 | 
						|
	plugin := resBody.PluginData.Data
 | 
						|
	jsep := resBody.JSEP
 | 
						|
	if resBody.Janus != "event" || plugin.VideoRoom != "attached" {
 | 
						|
		return "", errors.Errorf("Server fail janus=%v, plugin=%v %v", resBody.Janus, plugin.VideoRoom, s3)
 | 
						|
	}
 | 
						|
	logger.Tf(ctx, "Join as subscriber room=%v, tid=%v ok, event=%v, plugin=%v, offer=%vB",
 | 
						|
		room, reply.transactionID, resBody.Janus, plugin.VideoRoom, len(jsep.SDP))
 | 
						|
 | 
						|
	return jsep.SDP, nil
 | 
						|
}
 | 
						|
 | 
						|
func (v *janusAPI) Subscribe(ctx context.Context, handleID uint64, room int, answer string) error {
 | 
						|
	handler := v.loadHandler(handleID)
 | 
						|
	api := fmt.Sprintf("%v%v/%v", v.r, v.sessionID, handler.handleID)
 | 
						|
 | 
						|
	reqBodyBody := struct {
 | 
						|
		Request string `json:"request"`
 | 
						|
		Room    int    `json:"room"`
 | 
						|
	}{
 | 
						|
		"start", room,
 | 
						|
	}
 | 
						|
	jsepBody := struct {
 | 
						|
		Type string `json:"type"`
 | 
						|
		SDP  string `json:"sdp"`
 | 
						|
	}{
 | 
						|
		"answer", answer,
 | 
						|
	}
 | 
						|
	reqBody := struct {
 | 
						|
		Janus       string      `json:"janus"`
 | 
						|
		Transaction string      `json:"transaction"`
 | 
						|
		Body        interface{} `json:"body"`
 | 
						|
		JSEP        interface{} `json:"jsep"`
 | 
						|
	}{
 | 
						|
		"message", newTransactionID(), reqBodyBody, jsepBody,
 | 
						|
	}
 | 
						|
 | 
						|
	reply := newJanusReply(reqBody.Transaction)
 | 
						|
	v.replies.Store(reqBody.Transaction, reply)
 | 
						|
 | 
						|
	b, err := json.Marshal(reqBody)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "Marshal body %v", reqBody)
 | 
						|
	}
 | 
						|
	logger.Tf(ctx, "Request url api=%v with %v", api, string(b))
 | 
						|
 | 
						|
	req, err := http.NewRequest("POST", api, strings.NewReader(string(b)))
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "HTTP request %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	res, err := http.DefaultClient.Do(req.WithContext(ctx))
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "Do HTTP request %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	b2, err := ioutil.ReadAll(res.Body)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "Read response for %v", string(b))
 | 
						|
	}
 | 
						|
 | 
						|
	s2 := escapeJSON(string(b2))
 | 
						|
	logger.Tf(ctx, "Response from %v is %v", api, s2)
 | 
						|
 | 
						|
	ackBody := struct {
 | 
						|
		Janus       string `json:"janus"`
 | 
						|
		SessionID   uint64 `json:"session_id"`
 | 
						|
		Transaction string `json:"transaction"`
 | 
						|
	}{}
 | 
						|
	if err := json.Unmarshal([]byte(s2), &ackBody); err != nil {
 | 
						|
		return errors.Wrapf(err, "Marshal %v", s2)
 | 
						|
	}
 | 
						|
	if ackBody.Janus != "ack" {
 | 
						|
		return errors.Errorf("Server fail code=%v %v", ackBody.Janus, s2)
 | 
						|
	}
 | 
						|
	logger.Tf(ctx, "Response tid=%v ack", reply.transactionID)
 | 
						|
 | 
						|
	// Reply from polling.
 | 
						|
	var s3 string
 | 
						|
	select {
 | 
						|
	case <-ctx.Done():
 | 
						|
		return ctx.Err()
 | 
						|
	case b3 := <-reply.replies:
 | 
						|
		s3 = escapeJSON(string(b3))
 | 
						|
		logger.Tf(ctx, "Async response tid=%v, reply=%v", reply.transactionID, s3)
 | 
						|
	}
 | 
						|
	resBody := struct {
 | 
						|
		Janus       string `json:"janus"`
 | 
						|
		Session     uint64 `json:"session_id"`
 | 
						|
		Transaction string `json:"transaction"`
 | 
						|
		Sender      uint64 `json:"sender"`
 | 
						|
		PluginData  struct {
 | 
						|
			Plugin string `json:"plugin"`
 | 
						|
			Data   struct {
 | 
						|
				VideoRoom string `json:"videoroom"`
 | 
						|
				Room      int    `json:"room"`
 | 
						|
				Started   string `json:"started"`
 | 
						|
			} `json:"data"`
 | 
						|
		} `json:"plugindata"`
 | 
						|
	}{}
 | 
						|
	if err := json.Unmarshal([]byte(s3), &resBody); err != nil {
 | 
						|
		return errors.Wrapf(err, "Marshal %v", s3)
 | 
						|
	}
 | 
						|
 | 
						|
	plugin := resBody.PluginData.Data
 | 
						|
	if resBody.Janus != "event" || plugin.VideoRoom != "event" || plugin.Started != "ok" {
 | 
						|
		return errors.Errorf("Server fail janus=%v, plugin=%v, started=%v %v", resBody.Janus, plugin.VideoRoom, plugin.Started, s3)
 | 
						|
	}
 | 
						|
	logger.Tf(ctx, "Start subscribe answer=%vB, tid=%v ok, event=%v, plugin=%v, started=%v",
 | 
						|
		len(answer), reply.transactionID, resBody.Janus, plugin.VideoRoom, plugin.Started)
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (v *janusAPI) polling(ctx context.Context) error {
 | 
						|
	api := fmt.Sprintf("%v%v?rid=%v&maxev=1", v.r, v.sessionID,
 | 
						|
		uint64(time.Duration(time.Now().UnixNano())/time.Millisecond))
 | 
						|
	logger.Tf(ctx, "Polling: Request url api=%v", api)
 | 
						|
 | 
						|
	req, err := http.NewRequest("GET", api, nil)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "HTTP request %v", api)
 | 
						|
	}
 | 
						|
 | 
						|
	res, err := http.DefaultClient.Do(req.WithContext(ctx))
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "Do HTTP request %v", api)
 | 
						|
	}
 | 
						|
 | 
						|
	b2, err := ioutil.ReadAll(res.Body)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrapf(err, "Read response for %v", api)
 | 
						|
	}
 | 
						|
 | 
						|
	s2 := escapeJSON(string(b2))
 | 
						|
	logger.Tf(ctx, "Polling: Response from %v is %v", api, s2)
 | 
						|
 | 
						|
	if len(b2) == 0 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	replyID := struct {
 | 
						|
		Janus       string `json:"janus"`
 | 
						|
		Transaction string `json:"transaction"`
 | 
						|
	}{}
 | 
						|
	if err := json.Unmarshal([]byte(s2), &replyID); err != nil {
 | 
						|
		return errors.Wrapf(err, "Marshal %v", s2)
 | 
						|
	}
 | 
						|
 | 
						|
	switch replyID.Janus {
 | 
						|
	case "event":
 | 
						|
		if r, ok := v.replies.Load(replyID.Transaction); !ok {
 | 
						|
			if err := v.handleCall(replyID.Janus, s2); err != nil {
 | 
						|
				logger.Wf(ctx, "Polling: Handle call %v fail %v, err %+v", replyID.Janus, s2, err)
 | 
						|
			}
 | 
						|
		} else if r2, ok := r.(*janusReply); !ok {
 | 
						|
			logger.Wf(ctx, "Polling: Ignore tid=%v reply %v", replyID.Transaction, s2)
 | 
						|
		} else {
 | 
						|
			select {
 | 
						|
			case <-ctx.Done():
 | 
						|
				return ctx.Err()
 | 
						|
			case r2.replies <- b2:
 | 
						|
				logger.Tf(ctx, "Polling: Reply tid=%v ok, %v", replyID.Transaction, s2)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	case "keepalive":
 | 
						|
		return nil
 | 
						|
	case "webrtcup", "media", "slowlink", "detached":
 | 
						|
		if err := v.handleCall(replyID.Janus, s2); err != nil {
 | 
						|
			logger.Wf(ctx, "Polling: Handle call %v fail %v, err %+v", replyID.Janus, s2, err)
 | 
						|
		}
 | 
						|
	default:
 | 
						|
		logger.Wf(ctx, "Polling: Unknown janus=%v %v", replyID.Janus, s2)
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (v *janusAPI) handleCall(janus string, s string) error {
 | 
						|
	type callHeader struct {
 | 
						|
		Sender    uint64 `json:"sender"`
 | 
						|
		SessionID uint64 `json:"session_id"`
 | 
						|
	}
 | 
						|
 | 
						|
	switch janus {
 | 
						|
	case "detached":
 | 
						|
		/*{
 | 
						|
			"janus": "detached",
 | 
						|
			"sender": 4201795482244652,
 | 
						|
			"session_id": 373403124722380
 | 
						|
		}*/
 | 
						|
		r := callHeader{}
 | 
						|
		if err := json.Unmarshal([]byte(s), &r); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		v.onDetached(r.Sender, r.SessionID)
 | 
						|
	case "webrtcup":
 | 
						|
		/*{
 | 
						|
			"janus": "webrtcup",
 | 
						|
			"sender": 7698695982180732,
 | 
						|
			"session_id": 2403223275773854
 | 
						|
		}*/
 | 
						|
		r := callHeader{}
 | 
						|
		if err := json.Unmarshal([]byte(s), &r); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		v.onWebrtcUp(r.Sender, r.SessionID)
 | 
						|
	case "media":
 | 
						|
		/*{
 | 
						|
			"janus": "media",
 | 
						|
			"receiving": true,
 | 
						|
			"sender": 7698695982180732,
 | 
						|
			"session_id": 2403223275773854,
 | 
						|
			"type": "audio"
 | 
						|
		}*/
 | 
						|
		r := struct {
 | 
						|
			callHeader
 | 
						|
			Type      string `json:"type"`
 | 
						|
			Receiving bool   `json:"receiving"`
 | 
						|
		}{}
 | 
						|
		if err := json.Unmarshal([]byte(s), &r); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		v.onMedia(r.Sender, r.SessionID, r.Type, r.Receiving)
 | 
						|
	case "slowlink":
 | 
						|
		/*{
 | 
						|
			"janus": "slowlink",
 | 
						|
			"lost": 4294902988,
 | 
						|
			"media": "video",
 | 
						|
			"sender": 562229074390269,
 | 
						|
			"session_id": 156116325213625,
 | 
						|
			"uplink": false
 | 
						|
		}*/
 | 
						|
		r := struct {
 | 
						|
			callHeader
 | 
						|
			Lost   uint64 `json:"lost"`
 | 
						|
			Media  string `json:"media"`
 | 
						|
			Uplink bool   `json:"uplink"`
 | 
						|
		}{}
 | 
						|
		if err := json.Unmarshal([]byte(s), &r); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		v.onSlowLink(r.Sender, r.SessionID, r.Media, r.Lost, r.Uplink)
 | 
						|
	case "event":
 | 
						|
		if strings.Contains(s, "publishers") {
 | 
						|
			/*{
 | 
						|
				"janus": "event",
 | 
						|
				"plugindata": {
 | 
						|
					"data": {
 | 
						|
						"publishers": [{
 | 
						|
							"audio_codec": "opus",
 | 
						|
							"display": "test",
 | 
						|
							"id": 2805536617160145,
 | 
						|
							"talking": false,
 | 
						|
							"video_codec": "h264"
 | 
						|
						}],
 | 
						|
						"room": 2345,
 | 
						|
						"videoroom": "event"
 | 
						|
					},
 | 
						|
					"plugin": "janus.plugin.videoroom"
 | 
						|
				},
 | 
						|
				"sender": 2156044968631669,
 | 
						|
				"session_id": 6696376606446844
 | 
						|
			}*/
 | 
						|
			r := struct {
 | 
						|
				callHeader
 | 
						|
				PluginData struct {
 | 
						|
					Data struct {
 | 
						|
						Publishers []publisherInfo `json:"publishers"`
 | 
						|
						Room       int             `json:"room"`
 | 
						|
						VideoRoom  string          `json:"videoroom"`
 | 
						|
					} `json:"data"`
 | 
						|
					Plugin string `json:"plugin"`
 | 
						|
				} `json:"plugindata"`
 | 
						|
			}{}
 | 
						|
			if err := json.Unmarshal([]byte(s), &r); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			v.onPublisher(r.Sender, r.SessionID, r.PluginData.Data.Publishers)
 | 
						|
		} else if strings.Contains(s, "unpublished") {
 | 
						|
			/*{
 | 
						|
				"janus": "event",
 | 
						|
				"plugindata": {
 | 
						|
					"data": {
 | 
						|
						"room": 2345,
 | 
						|
						"unpublished": 2805536617160145,
 | 
						|
						"videoroom": "event"
 | 
						|
					},
 | 
						|
					"plugin": "janus.plugin.videoroom"
 | 
						|
				},
 | 
						|
				"sender": 2156044968631669,
 | 
						|
				"session_id": 6696376606446844
 | 
						|
			}*/
 | 
						|
			r := struct {
 | 
						|
				callHeader
 | 
						|
				PluginData struct {
 | 
						|
					Data struct {
 | 
						|
						Room        int    `json:"room"`
 | 
						|
						UnPublished uint64 `json:"unpublished"`
 | 
						|
						VideoRoom   string `json:"videoroom"`
 | 
						|
					} `json:"data"`
 | 
						|
					Plugin string `json:"plugin"`
 | 
						|
				} `json:"plugindata"`
 | 
						|
			}{}
 | 
						|
			if err := json.Unmarshal([]byte(s), &r); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			v.onUnPublished(r.Sender, r.SessionID, r.PluginData.Data.UnPublished)
 | 
						|
		} else if strings.Contains(s, "leaving") {
 | 
						|
			/*{
 | 
						|
				"janus": "event",
 | 
						|
				"plugindata": {
 | 
						|
					"data": {
 | 
						|
						"leaving": 2805536617160145,
 | 
						|
						"room": 2345,
 | 
						|
						"videoroom": "event"
 | 
						|
					},
 | 
						|
					"plugin": "janus.plugin.videoroom"
 | 
						|
				},
 | 
						|
				"sender": 2156044968631669,
 | 
						|
				"session_id": 6696376606446844
 | 
						|
			}*/
 | 
						|
			r := struct {
 | 
						|
				callHeader
 | 
						|
				PluginData struct {
 | 
						|
					Data struct {
 | 
						|
						Leaving   uint64 `json:"leaving"`
 | 
						|
						Room      int    `json:"room"`
 | 
						|
						VideoRoom string `json:"videoroom"`
 | 
						|
					} `json:"data"`
 | 
						|
					Plugin string `json:"plugin"`
 | 
						|
				} `json:"plugindata"`
 | 
						|
			}{}
 | 
						|
			if err := json.Unmarshal([]byte(s), &r); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			v.onLeave(r.Sender, r.SessionID, r.PluginData.Data.Leaving)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (v *janusAPI) DiscoverPublisher(ctx context.Context, room int, display string, timeout time.Duration) (*publisherInfo, error) {
 | 
						|
	var publisher *publisherInfo
 | 
						|
	discoverCtx, discoverCancel := context.WithCancel(context.Background())
 | 
						|
 | 
						|
	ov := v.onPublisher
 | 
						|
	defer func() {
 | 
						|
		v.onPublisher = ov
 | 
						|
	}()
 | 
						|
	v.onPublisher = func(sender, sessionID uint64, publishers []publisherInfo) {
 | 
						|
		for _, p := range publishers {
 | 
						|
			if p.Display == display {
 | 
						|
				publisher = &p
 | 
						|
				discoverCancel()
 | 
						|
				logger.Tf(ctx, "Publisher discovered %v", p)
 | 
						|
				return
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	go func() {
 | 
						|
		if err := func() error {
 | 
						|
			publishHandleID, err := v.AttachPlugin(ctx)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			defer v.DetachPlugin(ctx, publishHandleID)
 | 
						|
 | 
						|
			if err := v.JoinAsPublisher(ctx, publishHandleID, room, fmt.Sprintf("sub-%v", display)); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			<-discoverCtx.Done()
 | 
						|
			return nil
 | 
						|
		}(); err != nil {
 | 
						|
			logger.Ef(ctx, "join err %+v", err)
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	select {
 | 
						|
	case <-ctx.Done():
 | 
						|
		return nil, ctx.Err()
 | 
						|
	case <-discoverCtx.Done():
 | 
						|
	case <-time.After(timeout):
 | 
						|
		discoverCancel()
 | 
						|
	}
 | 
						|
	if publisher == nil {
 | 
						|
		return nil, errors.Errorf("no publisher for room=%v, display=%v, session=%v",
 | 
						|
			room, display, v.sessionID)
 | 
						|
	}
 | 
						|
 | 
						|
	return publisher, nil
 | 
						|
}
 |