mirror of
https://github.com/lkmio/gb-cms.git
synced 2025-09-27 03:56:08 +08:00
702 lines
20 KiB
Go
702 lines
20 KiB
Go
package main
|
||
|
||
import (
|
||
"context"
|
||
"encoding/binary"
|
||
"fmt"
|
||
"github.com/ghettovoice/gosip/sip"
|
||
"github.com/gorilla/mux"
|
||
"github.com/gorilla/websocket"
|
||
"github.com/lkmio/avformat/librtp"
|
||
"github.com/lkmio/avformat/utils"
|
||
"math"
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
type ApiServer struct {
|
||
router *mux.Router
|
||
upgrader *websocket.Upgrader
|
||
}
|
||
|
||
type InviteParams struct {
|
||
DeviceID string `json:"device_id"`
|
||
ChannelID string `json:"channel_id"`
|
||
StartTime string `json:"start_time"`
|
||
EndTime string `json:"end_time"`
|
||
Setup string `json:"setup"`
|
||
Speed string `json:"speed"`
|
||
streamId StreamID
|
||
}
|
||
|
||
var apiServer *ApiServer
|
||
|
||
func init() {
|
||
apiServer = &ApiServer{
|
||
upgrader: &websocket.Upgrader{
|
||
CheckOrigin: func(r *http.Request) bool {
|
||
return true
|
||
},
|
||
},
|
||
|
||
router: mux.NewRouter(),
|
||
}
|
||
}
|
||
|
||
func withHookParams(f func(streamId StreamID, protocol string, w http.ResponseWriter, req *http.Request)) func(http.ResponseWriter, *http.Request) {
|
||
return func(w http.ResponseWriter, req *http.Request) {
|
||
if "" != req.URL.RawQuery {
|
||
Sugar.Infof("on request %s?%s", req.URL.Path, req.URL.RawQuery)
|
||
}
|
||
|
||
v := struct {
|
||
Stream StreamID `json:"stream"` //Stream id
|
||
Protocol string `json:"protocol"` //推拉流协议
|
||
RemoteAddr string `json:"remote_addr"` //peer地址
|
||
}{}
|
||
|
||
err := HttpDecodeJSONBody(w, req, &v)
|
||
if err != nil {
|
||
w.WriteHeader(http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
f(v.Stream, v.Protocol, w, req)
|
||
}
|
||
}
|
||
|
||
func startApiServer(addr string) {
|
||
apiServer.router.HandleFunc("/api/v1/hook/on_play", withHookParams(apiServer.OnPlay))
|
||
apiServer.router.HandleFunc("/api/v1/hook/on_play_done", withHookParams(apiServer.OnPlayDone))
|
||
apiServer.router.HandleFunc("/api/v1/hook/on_publish", withHookParams(apiServer.OnPublish))
|
||
apiServer.router.HandleFunc("/api/v1/hook/on_publish_done", withHookParams(apiServer.OnPublishDone))
|
||
apiServer.router.HandleFunc("/api/v1/hook/on_idle_timeout", withHookParams(apiServer.OnIdleTimeout))
|
||
apiServer.router.HandleFunc("/api/v1/hook/on_receive_timeout", withHookParams(apiServer.OnReceiveTimeout))
|
||
apiServer.router.HandleFunc("/api/v1/hook/on_record", withHookParams(apiServer.OnReceiveTimeout))
|
||
apiServer.router.HandleFunc("/api/v1/hook/on_started", apiServer.OnStarted)
|
||
|
||
// 统一处理live/playback/download请求
|
||
apiServer.router.HandleFunc("/api/v1/{action}/start", apiServer.OnInvite)
|
||
apiServer.router.HandleFunc("/api/v1/stream/close", apiServer.OnCloseStream) // 释放流(实时/回放/下载), 实际以拉流计数为准, 如果没有客户端拉流, 不等流媒体服务通知空闲超时,立即释放流,否则(还有客户端拉流)不会释放。
|
||
|
||
apiServer.router.HandleFunc("/api/v1/device/list", apiServer.OnDeviceList) // 查询在线设备
|
||
apiServer.router.HandleFunc("/api/v1/record/list", apiServer.OnRecordList) // 查询录像列表
|
||
apiServer.router.HandleFunc("/api/v1/position/sub", apiServer.OnSubscribePosition) // 订阅移动位置
|
||
apiServer.router.HandleFunc("/api/v1/playback/seek", apiServer.OnSeekPlayback) // 回放seek
|
||
apiServer.router.HandleFunc("/api/v1/ptz/control", apiServer.OnPTZControl) // 云台控制
|
||
|
||
apiServer.router.HandleFunc("/api/v1/platform/add", apiServer.OnPlatformAdd) // 添加上级平台
|
||
apiServer.router.HandleFunc("/api/v1/platform/remove", apiServer.OnPlatformRemove) // 删除上级平台
|
||
apiServer.router.HandleFunc("/api/v1/platform/list", apiServer.OnPlatformList) // 上级平台列表
|
||
apiServer.router.HandleFunc("/api/v1/platform/channel/bind", apiServer.OnPlatformChannelBind) // 级联绑定通道
|
||
apiServer.router.HandleFunc("/api/v1/platform/channel/unbind", apiServer.OnPlatformChannelUnbind) // 级联取消绑定通道
|
||
|
||
apiServer.router.HandleFunc("/ws/v1/talk", apiServer.OnWSTalk) // 语音广播/对讲, 主讲音频传输链路
|
||
apiServer.router.HandleFunc("/api/v1/broadcast/invite", apiServer.OnBroadcast) // 发起语音广播
|
||
apiServer.router.HandleFunc("/api/v1/broadcast/hangup", apiServer.OnHangup) // 挂断广播会话
|
||
apiServer.router.HandleFunc("/api/v1/talk", apiServer.OnTalk) // 语音对讲
|
||
apiServer.router.HandleFunc("/broadcast.html", func(writer http.ResponseWriter, request *http.Request) {
|
||
http.ServeFile(writer, request, "./broadcast.html")
|
||
})
|
||
apiServer.router.HandleFunc("/g711.js", func(writer http.ResponseWriter, request *http.Request) {
|
||
http.ServeFile(writer, request, "./g711.js")
|
||
})
|
||
|
||
http.Handle("/", apiServer.router)
|
||
|
||
srv := &http.Server{
|
||
Handler: apiServer.router,
|
||
Addr: addr,
|
||
// Good practice: enforce timeouts for servers you create!
|
||
WriteTimeout: 30 * time.Second,
|
||
ReadTimeout: 30 * time.Second,
|
||
}
|
||
|
||
err := srv.ListenAndServe()
|
||
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
}
|
||
|
||
func (api *ApiServer) OnPlay(streamId StreamID, protocol string, w http.ResponseWriter, r *http.Request) {
|
||
Sugar.Infof("play. protocol:%s stream id:%s", protocol, streamId)
|
||
|
||
// [注意]: windows上使用cmd/power shell推拉流如果要携带多个参数, 请用双引号将与号引起来("&")
|
||
// session_id是为了同一个录像文件, 允许同时点播多个.当然如果实时流支持多路预览, 也是可以的.
|
||
//ffplay -i rtmp://127.0.0.1/34020000001320000001/34020000001310000001
|
||
//ffplay -i http://127.0.0.1:8080/34020000001320000001/34020000001310000001.flv?setup=passive
|
||
//ffplay -i http://127.0.0.1:8080/34020000001320000001/34020000001310000001.m3u8?setup=passive
|
||
//ffplay -i rtsp://test:123456@127.0.0.1/34020000001320000001/34020000001310000001?setup=passive
|
||
|
||
// 回放示例
|
||
//ffplay -i rtmp://127.0.0.1/34020000001320000001/34020000001310000001.session_id_0?setup=passive"&"stream_type=playback"&"start_time=2024-06-18T15:20:56"&"end_time=2024-06-18T15:25:56
|
||
//ffplay -i rtmp://127.0.0.1/34020000001320000001/34020000001310000001.session_id_0?setup=passive&stream_type=playback&start_time=2024-06-18T15:20:56&end_time=2024-06-18T15:25:56
|
||
|
||
// 跳过非国标拉流
|
||
split := strings.Split(string(streamId), "/")
|
||
if len(split) != 2 || len(split[0]) != 20 || len(split[1]) < 20 {
|
||
w.WriteHeader(http.StatusOK)
|
||
return
|
||
}
|
||
|
||
// 已经存在,累加计数
|
||
if stream := StreamManager.Find(streamId); stream != nil {
|
||
stream.IncreaseSinkCount()
|
||
w.WriteHeader(http.StatusOK)
|
||
return
|
||
}
|
||
|
||
deviceId := split[0] //deviceId
|
||
channelId := split[1] //channelId
|
||
if len(channelId) > 20 {
|
||
channelId = channelId[:20]
|
||
}
|
||
|
||
query := r.URL.Query()
|
||
params := InviteParams{
|
||
DeviceID: deviceId,
|
||
ChannelID: channelId,
|
||
StartTime: query.Get("start_time"),
|
||
EndTime: query.Get("end_time"),
|
||
Setup: strings.ToLower(query.Get("setup")),
|
||
Speed: query.Get("speed"),
|
||
streamId: streamId,
|
||
}
|
||
|
||
streamType := strings.ToLower(query.Get("stream_type"))
|
||
var stream *Stream
|
||
var ok bool
|
||
if "playback" == streamType {
|
||
stream, ok = api.DoInvite(InviteTypeLive, params, false, w, r)
|
||
} else if "download" == streamType {
|
||
stream, ok = api.DoInvite(InviteTypeDownload, params, false, w, r)
|
||
} else {
|
||
stream, ok = api.DoInvite(InviteTypeLive, params, false, w, r)
|
||
}
|
||
|
||
if ok {
|
||
stream.IncreaseSinkCount()
|
||
}
|
||
}
|
||
|
||
func (api *ApiServer) OnInvite(w http.ResponseWriter, r *http.Request) {
|
||
v := InviteParams{}
|
||
if err := HttpDecodeJSONBody(w, r, &v); err != nil {
|
||
w.WriteHeader(http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
vars := mux.Vars(r)
|
||
action := strings.ToLower(vars["action"])
|
||
if "playback" == action {
|
||
apiServer.DoInvite(InviteTypePlayback, v, true, w, r)
|
||
} else if "download" == action {
|
||
apiServer.DoInvite(InviteTypeDownload, v, true, w, r)
|
||
} else if "live" == action {
|
||
apiServer.DoInvite(InviteTypeLive, v, true, w, r)
|
||
} else {
|
||
w.WriteHeader(http.StatusNotFound)
|
||
}
|
||
}
|
||
|
||
// DoInvite 处理Invite请求
|
||
// @params sync 是否异步等待流媒体的publish事件(确认收到流), 目前请求流分两种方式,流媒体hook和http接口, hook方式同步等待确认收到流再应答, http接口直接应答成功。
|
||
func (api *ApiServer) DoInvite(inviteType InviteType, params InviteParams, sync bool, w http.ResponseWriter, r *http.Request) (*Stream, bool) {
|
||
device := DeviceManager.Find(params.DeviceID)
|
||
if device == nil {
|
||
Sugar.Warnf("设备离线 id:%s", params.DeviceID)
|
||
w.WriteHeader(http.StatusNotFound)
|
||
return nil, false
|
||
}
|
||
|
||
// 解析时间范围参数
|
||
var startTimeSeconds string
|
||
var endTimeSeconds string
|
||
if InviteTypeLive != inviteType {
|
||
startTime, err := time.ParseInLocation("2006-01-02t15:04:05", params.StartTime, time.Local)
|
||
if err != nil {
|
||
Sugar.Errorf("解析开始时间失败 err:%s start_time:%s", err.Error(), params.StartTime)
|
||
w.WriteHeader(http.StatusBadRequest)
|
||
return nil, false
|
||
}
|
||
|
||
endTime, err := time.ParseInLocation("2006-01-02t15:04:05", params.EndTime, time.Local)
|
||
if err != nil {
|
||
Sugar.Errorf("解析开始时间失败 err:%s start_time:%s", err.Error(), params.EndTime)
|
||
w.WriteHeader(http.StatusBadRequest)
|
||
return nil, false
|
||
}
|
||
|
||
startTimeSeconds = strconv.FormatInt(startTime.Unix(), 10)
|
||
endTimeSeconds = strconv.FormatInt(endTime.Unix(), 10)
|
||
}
|
||
|
||
streamId := params.streamId
|
||
if streamId == "" {
|
||
streamId = GenerateStreamId(inviteType, device.GetID(), params.ChannelID, params.StartTime, params.EndTime)
|
||
}
|
||
|
||
// 解析回放或下载速度参数
|
||
speed, _ := strconv.Atoi(params.Speed)
|
||
speed = int(math.Min(4, float64(speed)))
|
||
stream, ok := device.(*Device).StartStream(inviteType, streamId, params.ChannelID, startTimeSeconds, endTimeSeconds, params.Setup, speed, sync)
|
||
if !ok {
|
||
w.WriteHeader(http.StatusInternalServerError)
|
||
return nil, false
|
||
}
|
||
|
||
// 返回stream id
|
||
response := map[string]string{"stream_id": string(streamId)}
|
||
httpResponseOK(w, response)
|
||
return stream, true
|
||
}
|
||
|
||
func (api *ApiServer) OnCloseStream(w http.ResponseWriter, r *http.Request) {
|
||
v := struct {
|
||
StreamID StreamID `json:"stream_id"`
|
||
}{}
|
||
|
||
err := HttpDecodeJSONBody(w, r, &v)
|
||
if err != nil {
|
||
w.WriteHeader(http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
stream := StreamManager.Find(v.StreamID)
|
||
if stream == nil {
|
||
w.WriteHeader(http.StatusNotFound)
|
||
return
|
||
}
|
||
|
||
if stream.SinkCount() > 0 {
|
||
return
|
||
}
|
||
|
||
CloseStream(v.StreamID)
|
||
}
|
||
|
||
func CloseStream(streamId StreamID) {
|
||
stream := StreamManager.Remove(streamId)
|
||
if stream != nil {
|
||
stream.Close(true)
|
||
}
|
||
}
|
||
|
||
func (api *ApiServer) OnPlayDone(streamId StreamID, protocol string, w http.ResponseWriter, r *http.Request) {
|
||
Sugar.Infof("play done. protocol:%s stream id:%s", protocol, streamId)
|
||
if stream := StreamManager.Find(streamId); stream != nil {
|
||
stream.DecreaseSinkCount()
|
||
}
|
||
|
||
// 与上级级联断开连接
|
||
if protocol == "gb_stream_forward" {
|
||
|
||
}
|
||
|
||
w.WriteHeader(http.StatusOK)
|
||
}
|
||
|
||
func (api *ApiServer) OnPublish(streamId StreamID, protocol string, w http.ResponseWriter, r *http.Request) {
|
||
Sugar.Infof("publish. protocol:%s stream id:%s", protocol, streamId)
|
||
|
||
w.WriteHeader(http.StatusOK)
|
||
stream := StreamManager.Find(streamId)
|
||
if stream != nil {
|
||
stream.publishEvent <- 0
|
||
}
|
||
}
|
||
|
||
func (api *ApiServer) OnPublishDone(streamId StreamID, protocol string, w http.ResponseWriter, r *http.Request) {
|
||
Sugar.Infof("publish done. protocol:%s stream id:%s", protocol, streamId)
|
||
w.WriteHeader(http.StatusOK)
|
||
CloseStream(streamId)
|
||
}
|
||
|
||
func (api *ApiServer) OnIdleTimeout(streamId StreamID, protocol string, w http.ResponseWriter, req *http.Request) {
|
||
Sugar.Infof("publish timeout. protocol:%s stream id:%s", protocol, streamId)
|
||
|
||
if protocol != "rtmp" {
|
||
w.WriteHeader(http.StatusForbidden)
|
||
CloseStream(streamId)
|
||
} else {
|
||
w.WriteHeader(http.StatusOK)
|
||
}
|
||
}
|
||
|
||
func (api *ApiServer) OnReceiveTimeout(streamId StreamID, protocol string, w http.ResponseWriter, req *http.Request) {
|
||
Sugar.Infof("receive timeout. protocol:%s stream id:%s", protocol, streamId)
|
||
|
||
if protocol != "rtmp" {
|
||
w.WriteHeader(http.StatusForbidden)
|
||
CloseStream(streamId)
|
||
} else {
|
||
w.WriteHeader(http.StatusOK)
|
||
}
|
||
}
|
||
|
||
func (api *ApiServer) OnRecord(streamId string, protocol string, w http.ResponseWriter, req *http.Request) {
|
||
Sugar.Infof("receive onrecord. protocol:%s stream id:%s", protocol, streamId)
|
||
w.WriteHeader(http.StatusOK)
|
||
}
|
||
|
||
func (api *ApiServer) OnDeviceList(w http.ResponseWriter, r *http.Request) {
|
||
devices := DeviceManager.AllDevices()
|
||
httpResponseOK(w, devices)
|
||
}
|
||
|
||
func (api *ApiServer) OnRecordList(w http.ResponseWriter, r *http.Request) {
|
||
v := struct {
|
||
DeviceId string `json:"device_id"`
|
||
ChannelId string `json:"channel_id"`
|
||
Timeout int `json:"timeout"`
|
||
StartTime string `json:"start_time"`
|
||
EndTime string `json:"end_time"`
|
||
Type_ string `json:"type"`
|
||
}{}
|
||
|
||
err := HttpDecodeJSONBody(w, r, &v)
|
||
if err != nil {
|
||
w.WriteHeader(http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
device := DeviceManager.Find(v.DeviceId)
|
||
if device == nil {
|
||
httpResponseOK(w, "设备离线")
|
||
return
|
||
}
|
||
|
||
sn := GetSN()
|
||
err = device.QueryRecord(v.ChannelId, v.StartTime, v.EndTime, sn, v.Type_)
|
||
if err != nil {
|
||
httpResponseOK(w, fmt.Sprintf("发送查询录像记录失败 err:%s", err.Error()))
|
||
return
|
||
}
|
||
|
||
var recordList []RecordInfo
|
||
timeout := int(math.Max(math.Min(5, float64(v.Timeout)), 60))
|
||
//设置查询超时时长
|
||
withTimeout, cancelFunc := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
||
|
||
SNManager.AddEvent(sn, func(data interface{}) {
|
||
response := data.(*QueryRecordInfoResponse)
|
||
|
||
if len(response.DeviceList.Devices) > 0 {
|
||
recordList = append(recordList, response.DeviceList.Devices...)
|
||
}
|
||
|
||
//查询完成
|
||
if len(recordList) >= response.SumNum {
|
||
cancelFunc()
|
||
}
|
||
})
|
||
|
||
select {
|
||
case _ = <-withTimeout.Done():
|
||
break
|
||
}
|
||
|
||
httpResponseOK(w, recordList)
|
||
}
|
||
|
||
func (api *ApiServer) OnSubscribePosition(w http.ResponseWriter, r *http.Request) {
|
||
v := struct {
|
||
DeviceID string `json:"device_id"`
|
||
ChannelID string `json:"channel_id"`
|
||
}{}
|
||
|
||
if err := HttpDecodeJSONBody(w, r, &v); err != nil {
|
||
httpResponse2(w, err)
|
||
return
|
||
}
|
||
|
||
device := DeviceManager.Find(v.DeviceID)
|
||
if device == nil {
|
||
return
|
||
}
|
||
|
||
if err := device.SubscribePosition(v.ChannelID); err != nil {
|
||
|
||
}
|
||
|
||
w.WriteHeader(http.StatusOK)
|
||
}
|
||
|
||
func (api *ApiServer) OnSeekPlayback(w http.ResponseWriter, r *http.Request) {
|
||
v := struct {
|
||
StreamId StreamID `json:"stream_id"`
|
||
Seconds int `json:"seconds"`
|
||
}{}
|
||
|
||
if err := HttpDecodeJSONBody(w, r, &v); err != nil {
|
||
httpResponse2(w, err)
|
||
return
|
||
}
|
||
|
||
stream := StreamManager.Find(v.StreamId)
|
||
if stream == nil || stream.DialogRequest == nil {
|
||
return
|
||
}
|
||
|
||
seekRequest := stream.CreateRequestFromDialog(sip.INFO)
|
||
seq, _ := seekRequest.CSeq()
|
||
body := fmt.Sprintf(SeekBodyFormat, seq.SeqNo, v.Seconds)
|
||
seekRequest.SetBody(body, true)
|
||
seekRequest.RemoveHeader(RtspMessageType.Name())
|
||
seekRequest.AppendHeader(&RtspMessageType)
|
||
|
||
SipUA.SendRequest(seekRequest)
|
||
w.WriteHeader(http.StatusOK)
|
||
}
|
||
|
||
func (api *ApiServer) OnPTZControl(w http.ResponseWriter, r *http.Request) {
|
||
|
||
}
|
||
|
||
func (api *ApiServer) OnWSTalk(w http.ResponseWriter, r *http.Request) {
|
||
conn, err := api.upgrader.Upgrade(w, r, nil)
|
||
if err != nil {
|
||
Sugar.Errorf("websocket头检查失败 err:%s", err.Error())
|
||
w.WriteHeader(http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
roomId := utils.RandStringBytes(10)
|
||
room := BroadcastManager.CreateRoom(roomId)
|
||
response := MalformedRequest{200, "ok", map[string]string{
|
||
"room_id": roomId,
|
||
}}
|
||
|
||
conn.WriteJSON(response)
|
||
|
||
rtp := make([]byte, 1500)
|
||
muxer := librtp.NewMuxer(8, 0, 0xFFFFFFFF)
|
||
muxer.SetAllocHandler(func(params interface{}) []byte {
|
||
return rtp[2:]
|
||
})
|
||
muxer.SetWriteHandler(func(data []byte, timestamp uint32, params interface{}) {
|
||
binary.BigEndian.PutUint16(rtp, uint16(len(data)))
|
||
room.DispatchRtpPacket(rtp[:2+len(data)])
|
||
})
|
||
|
||
for {
|
||
_, bytes, err := conn.ReadMessage()
|
||
n := len(bytes)
|
||
if err != nil {
|
||
Sugar.Infof("语音断开连接")
|
||
break
|
||
} else if n < 1 {
|
||
continue
|
||
}
|
||
|
||
count := (n-1)/320 + 1
|
||
for i := 0; i < count; i++ {
|
||
offset := i * 320
|
||
min := int(math.Min(float64(n), 320))
|
||
muxer.Input(bytes[offset:offset+min], uint32(min))
|
||
n -= min
|
||
}
|
||
}
|
||
|
||
Sugar.Infof("主讲websocket断开连接 roomid:%s", roomId)
|
||
muxer.Close()
|
||
|
||
sessions := BroadcastManager.RemoveRoom(roomId)
|
||
for _, session := range sessions {
|
||
session.Close(true)
|
||
}
|
||
}
|
||
|
||
func (api *ApiServer) OnHangup(w http.ResponseWriter, r *http.Request) {
|
||
v := struct {
|
||
DeviceID string `json:"device_id"`
|
||
ChannelID string `json:"channel_id"`
|
||
RoomID string `json:"room_id"`
|
||
}{}
|
||
|
||
if err := HttpDecodeJSONBody(w, r, &v); err != nil {
|
||
httpResponse2(w, err)
|
||
return
|
||
}
|
||
|
||
if session := BroadcastManager.Remove(GenerateSessionId(v.DeviceID, v.ChannelID)); session != nil {
|
||
session.Close(true)
|
||
}
|
||
|
||
httpResponseOK(w, nil)
|
||
}
|
||
|
||
func (api *ApiServer) OnBroadcast(w http.ResponseWriter, r *http.Request) {
|
||
v := struct {
|
||
DeviceID string `json:"device_id"`
|
||
ChannelID string `json:"channel_id"`
|
||
RoomID string `json:"room_id"`
|
||
Type int `json:"type"`
|
||
}{
|
||
Type: int(BroadcastTypeTCP),
|
||
}
|
||
|
||
if err := HttpDecodeJSONBody(w, r, &v); err != nil {
|
||
httpResponse2(w, err)
|
||
return
|
||
}
|
||
|
||
broadcastRoom := BroadcastManager.FindRoom(v.RoomID)
|
||
if broadcastRoom == nil {
|
||
w.WriteHeader(http.StatusNotFound)
|
||
return
|
||
}
|
||
|
||
//全局唯一ID
|
||
sessionId := GenerateSessionId(v.DeviceID, v.ChannelID)
|
||
if BroadcastManager.Find(sessionId) != nil {
|
||
w.WriteHeader(http.StatusForbidden)
|
||
return
|
||
}
|
||
|
||
device := DeviceManager.Find(v.DeviceID)
|
||
if device == nil {
|
||
w.WriteHeader(http.StatusNotFound)
|
||
return
|
||
}
|
||
|
||
sourceId := v.RoomID + utils.RandStringBytes(10)
|
||
session := &BroadcastSession{
|
||
SourceID: sourceId,
|
||
DeviceID: v.DeviceID,
|
||
ChannelID: v.ChannelID,
|
||
RoomId: v.RoomID,
|
||
Type: BroadcastType(v.Type),
|
||
}
|
||
|
||
if BroadcastManager.AddSession(v.RoomID, session) {
|
||
device.Broadcast(sourceId, v.ChannelID)
|
||
httpResponseOK(w, nil)
|
||
} else {
|
||
w.WriteHeader(http.StatusForbidden)
|
||
}
|
||
|
||
select {
|
||
case <-session.Answer:
|
||
break
|
||
case <-r.Context().Done():
|
||
break
|
||
}
|
||
|
||
if !session.Successful {
|
||
Sugar.Errorf("广播失败 session:%s", sessionId)
|
||
BroadcastManager.Remove(sessionId)
|
||
} else {
|
||
Sugar.Infof("广播成功 session:%s", sessionId)
|
||
}
|
||
}
|
||
|
||
func (api *ApiServer) OnTalk(w http.ResponseWriter, r *http.Request) {
|
||
}
|
||
|
||
func (api *ApiServer) OnStarted(w http.ResponseWriter, req *http.Request) {
|
||
Sugar.Infof("lkm启动")
|
||
|
||
streams := StreamManager.PopAll()
|
||
for _, stream := range streams {
|
||
stream.Close(true)
|
||
}
|
||
}
|
||
|
||
func (api *ApiServer) OnPlatformAdd(w http.ResponseWriter, r *http.Request) {
|
||
v := GBPlatformRecord{}
|
||
if err := HttpDecodeJSONBody(w, r, &v); err != nil {
|
||
httpResponse2(w, err)
|
||
return
|
||
}
|
||
|
||
if PlatformManager.ExistPlatform(v.SeverID) || PlatformManager.ExistPlatformWithServerAddr(v.ServerAddr) {
|
||
return
|
||
}
|
||
|
||
platform, err := NewGBPlatform(&v, SipUA)
|
||
if err != nil {
|
||
return
|
||
} else if !PlatformManager.AddPlatform(platform) {
|
||
return
|
||
}
|
||
|
||
platform.Start()
|
||
}
|
||
|
||
func (api *ApiServer) OnPlatformRemove(w http.ResponseWriter, r *http.Request) {
|
||
v := GBPlatformRecord{}
|
||
if err := HttpDecodeJSONBody(w, r, &v); err != nil {
|
||
httpResponse2(w, err)
|
||
return
|
||
}
|
||
|
||
platform := PlatformManager.RemovePlatform(v.SeverID)
|
||
if platform != nil {
|
||
platform.Stop()
|
||
}
|
||
}
|
||
|
||
func (api *ApiServer) OnPlatformList(w http.ResponseWriter, r *http.Request) {
|
||
platforms := PlatformManager.Platforms()
|
||
httpResponseOK(w, platforms)
|
||
}
|
||
|
||
func (api *ApiServer) OnPlatformChannelBind(w http.ResponseWriter, r *http.Request) {
|
||
v := struct {
|
||
ServerID string `json:"server_id"`
|
||
Channels [][2]string `json:"channels"` //二维数组, 索引0-设备ID/索引1-通道ID
|
||
}{}
|
||
|
||
if err := HttpDecodeJSONBody(w, r, &v); err != nil {
|
||
httpResponse2(w, err)
|
||
return
|
||
}
|
||
|
||
platform := PlatformManager.FindPlatform(v.ServerID)
|
||
if platform == nil {
|
||
return
|
||
}
|
||
|
||
var channels []*Channel
|
||
for _, pair := range v.Channels {
|
||
device := DeviceManager.Find(pair[0])
|
||
if device == nil {
|
||
continue
|
||
}
|
||
|
||
channel := device.FindChannel(pair[1])
|
||
if channel == nil {
|
||
continue
|
||
}
|
||
|
||
channels = append(channels, channel)
|
||
}
|
||
|
||
platform.AddChannels(channels)
|
||
}
|
||
|
||
func (api *ApiServer) OnPlatformChannelUnbind(w http.ResponseWriter, r *http.Request) {
|
||
v := struct {
|
||
ServerID string `json:"server_id"`
|
||
Channels [][2]string `json:"channels"` //二维数组, 索引0-设备ID/索引1-通道ID
|
||
}{}
|
||
|
||
if err := HttpDecodeJSONBody(w, r, &v); err != nil {
|
||
httpResponse2(w, err)
|
||
return
|
||
}
|
||
|
||
platform := PlatformManager.FindPlatform(v.ServerID)
|
||
if platform == nil {
|
||
return
|
||
}
|
||
|
||
for _, pair := range v.Channels {
|
||
platform.RemoveChannel(pair[1])
|
||
}
|
||
}
|