mirror of
https://github.com/lkmio/gb-cms.git
synced 2025-09-27 03:56:08 +08:00
feat: 适配录像下载接口
This commit is contained in:
179
api.go
179
api.go
@@ -14,15 +14,14 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
"io"
|
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,11 +73,6 @@ type SeekParams struct {
|
|||||||
Seconds int `json:"seconds"`
|
Seconds int `json:"seconds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlatformChannel struct {
|
|
||||||
ServerAddr string `json:"server_addr"`
|
|
||||||
Channels [][2]string `json:"channels"` //二维数组, 索引0-设备ID/索引1-通道ID
|
|
||||||
}
|
|
||||||
|
|
||||||
type BroadcastParams struct {
|
type BroadcastParams struct {
|
||||||
DeviceID string `json:"device_id"`
|
DeviceID string `json:"device_id"`
|
||||||
ChannelID string `json:"channel_id"`
|
ChannelID string `json:"channel_id"`
|
||||||
@@ -92,7 +86,9 @@ type RecordParams struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StreamIDParams struct {
|
type StreamIDParams struct {
|
||||||
StreamID string `json:"streamid"`
|
StreamID common.StreamID `json:"streamid"`
|
||||||
|
Command string `json:"command"`
|
||||||
|
Scale int `json:"scale"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PageQuery struct {
|
type PageQuery struct {
|
||||||
@@ -103,12 +99,6 @@ type PageQuery struct {
|
|||||||
Data interface{} `json:"data"`
|
Data interface{} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PageQueryChannel struct {
|
|
||||||
PageQuery
|
|
||||||
DeviceID string `json:"device_id"`
|
|
||||||
GroupID string `json:"group_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SetMediaTransportReq struct {
|
type SetMediaTransportReq struct {
|
||||||
DeviceID string `json:"serial"`
|
DeviceID string `json:"serial"`
|
||||||
MediaTransport string `json:"media_transport"`
|
MediaTransport string `json:"media_transport"`
|
||||||
@@ -223,15 +213,13 @@ func startApiServer(addr string) {
|
|||||||
apiServer.router.HandleFunc("/api/v1/hook/on_idle_timeout", common.WithJsonParams(apiServer.OnIdleTimeout, &StreamParams{}))
|
apiServer.router.HandleFunc("/api/v1/hook/on_idle_timeout", common.WithJsonParams(apiServer.OnIdleTimeout, &StreamParams{}))
|
||||||
apiServer.router.HandleFunc("/api/v1/hook/on_receive_timeout", common.WithJsonParams(apiServer.OnReceiveTimeout, &StreamParams{}))
|
apiServer.router.HandleFunc("/api/v1/hook/on_receive_timeout", common.WithJsonParams(apiServer.OnReceiveTimeout, &StreamParams{}))
|
||||||
apiServer.router.HandleFunc("/api/v1/hook/on_record", common.WithJsonParams(apiServer.OnRecord, &RecordParams{}))
|
apiServer.router.HandleFunc("/api/v1/hook/on_record", common.WithJsonParams(apiServer.OnRecord, &RecordParams{}))
|
||||||
|
|
||||||
apiServer.router.HandleFunc("/api/v1/hook/on_started", apiServer.OnStarted)
|
apiServer.router.HandleFunc("/api/v1/hook/on_started", apiServer.OnStarted)
|
||||||
|
|
||||||
// 统一处理live/playback/download请求
|
apiServer.router.HandleFunc("/api/v1/stream/start", withVerify(common.WithFormDataParams(apiServer.OnStreamStart, InviteParams{}))) // 实时预览
|
||||||
//apiServer.router.HandleFunc("/api/v1/{action}/start", withVerify(common.WithFormDataParams(apiServer.OnInvite, InviteParams{})))
|
apiServer.router.HandleFunc("/api/v1/stream/stop", withVerify(common.WithFormDataParams(apiServer.OnCloseLiveStream, InviteParams{}))) // 关闭实时预览
|
||||||
|
apiServer.router.HandleFunc("/api/v1/playback/start", withVerify(common.WithFormDataParams(apiServer.OnPlaybackStart, InviteParams{}))) // 回放/下载
|
||||||
apiServer.router.HandleFunc("/api/v1/stream/start", withVerify(common.WithFormDataParams(apiServer.OnStreamStart, InviteParams{}))) // 实时预览
|
apiServer.router.HandleFunc("/api/v1/playback/stop", withVerify(common.WithFormDataParams(apiServer.OnCloseStream, StreamIDParams{}))) // 关闭回放/下载
|
||||||
apiServer.router.HandleFunc("/api/v1/stream/stop", withVerify(common.WithFormDataParams(apiServer.OnCloseStream, InviteParams{})))
|
apiServer.router.HandleFunc("/api/v1/playback/control", withVerify(common.WithFormDataParams(apiServer.OnPlaybackControl, StreamIDParams{}))) // 回放控制
|
||||||
apiServer.router.HandleFunc("/api/v1/playback/start", withVerify(common.WithFormDataParams(apiServer.OnPlaybackStart, InviteParams{}))) // 回放/下载
|
|
||||||
|
|
||||||
apiServer.router.HandleFunc("/api/v1/device/list", withVerify(common.WithQueryStringParams(apiServer.OnDeviceList, QueryDeviceChannel{}))) // 查询设备列表
|
apiServer.router.HandleFunc("/api/v1/device/list", withVerify(common.WithQueryStringParams(apiServer.OnDeviceList, QueryDeviceChannel{}))) // 查询设备列表
|
||||||
apiServer.router.HandleFunc("/api/v1/device/channeltree", withVerify(common.WithQueryStringParams(apiServer.OnDeviceTree, QueryDeviceChannel{}))) // 设备树
|
apiServer.router.HandleFunc("/api/v1/device/channeltree", withVerify(common.WithQueryStringParams(apiServer.OnDeviceTree, QueryDeviceChannel{}))) // 设备树
|
||||||
@@ -242,6 +230,7 @@ func startApiServer(addr string) {
|
|||||||
|
|
||||||
apiServer.router.HandleFunc("/api/v1/playback/recordlist", withVerify(common.WithQueryStringParams(apiServer.OnRecordList, QueryRecordParams{}))) // 查询录像列表
|
apiServer.router.HandleFunc("/api/v1/playback/recordlist", withVerify(common.WithQueryStringParams(apiServer.OnRecordList, QueryRecordParams{}))) // 查询录像列表
|
||||||
apiServer.router.HandleFunc("/api/v1/stream/info", withVerify(apiServer.OnStreamInfo))
|
apiServer.router.HandleFunc("/api/v1/stream/info", withVerify(apiServer.OnStreamInfo))
|
||||||
|
apiServer.router.HandleFunc("/api/v1/playback/streaminfo", withVerify(apiServer.OnStreamInfo))
|
||||||
apiServer.router.HandleFunc("/api/v1/device/session/list", withVerify(common.WithQueryStringParams(apiServer.OnSessionList, QueryDeviceChannel{}))) // 推流列表
|
apiServer.router.HandleFunc("/api/v1/device/session/list", withVerify(common.WithQueryStringParams(apiServer.OnSessionList, QueryDeviceChannel{}))) // 推流列表
|
||||||
apiServer.router.HandleFunc("/api/v1/device/session/stop", withVerify(common.WithFormDataParams(apiServer.OnSessionStop, StreamIDParams{}))) // 关闭流
|
apiServer.router.HandleFunc("/api/v1/device/session/stop", withVerify(common.WithFormDataParams(apiServer.OnSessionStop, StreamIDParams{}))) // 关闭流
|
||||||
apiServer.router.HandleFunc("/api/v1/device/setchannelid", withVerify(common.WithFormDataParams(apiServer.OnCustomChannelSet, CustomChannel{}))) // 关闭流
|
apiServer.router.HandleFunc("/api/v1/device/setchannelid", withVerify(common.WithFormDataParams(apiServer.OnCustomChannelSet, CustomChannel{}))) // 关闭流
|
||||||
@@ -272,7 +261,7 @@ func startApiServer(addr string) {
|
|||||||
|
|
||||||
apiServer.router.HandleFunc("/api/v1/broadcast/invite", common.WithJsonResponse(apiServer.OnBroadcast, &BroadcastParams{Setup: &common.DefaultSetupType})) // 发起语音广播
|
apiServer.router.HandleFunc("/api/v1/broadcast/invite", common.WithJsonResponse(apiServer.OnBroadcast, &BroadcastParams{Setup: &common.DefaultSetupType})) // 发起语音广播
|
||||||
apiServer.router.HandleFunc("/api/v1/broadcast/hangup", common.WithJsonResponse(apiServer.OnHangup, &BroadcastParams{})) // 挂断广播会话
|
apiServer.router.HandleFunc("/api/v1/broadcast/hangup", common.WithJsonResponse(apiServer.OnHangup, &BroadcastParams{})) // 挂断广播会话
|
||||||
apiServer.router.HandleFunc("/api/v1/talk", apiServer.OnTalk) // 语音对讲
|
apiServer.router.HandleFunc("/api/v1/control/ws-talk/{device}/{channel}", withVerify(apiServer.OnTalk)) // 语音对讲
|
||||||
|
|
||||||
apiServer.router.HandleFunc("/api/v1/jt/device/add", common.WithJsonResponse(apiServer.OnVirtualDeviceAdd, &dao.JTDeviceModel{}))
|
apiServer.router.HandleFunc("/api/v1/jt/device/add", common.WithJsonResponse(apiServer.OnVirtualDeviceAdd, &dao.JTDeviceModel{}))
|
||||||
apiServer.router.HandleFunc("/api/v1/jt/device/edit", common.WithJsonResponse(apiServer.OnVirtualDeviceEdit, &dao.JTDeviceModel{}))
|
apiServer.router.HandleFunc("/api/v1/jt/device/edit", common.WithJsonResponse(apiServer.OnVirtualDeviceEdit, &dao.JTDeviceModel{}))
|
||||||
@@ -540,7 +529,7 @@ func (api *ApiServer) OnPlaybackStart(v *InviteParams, w http.ResponseWriter, r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ApiServer) DoStreamStart(v *InviteParams, _ http.ResponseWriter, _ *http.Request, action string) (interface{}, error) {
|
func (api *ApiServer) DoStreamStart(v *InviteParams, w http.ResponseWriter, r *http.Request, action string) (interface{}, error) {
|
||||||
var code int
|
var code int
|
||||||
var stream *dao.StreamModel
|
var stream *dao.StreamModel
|
||||||
var err error
|
var err error
|
||||||
@@ -559,6 +548,17 @@ func (api *ApiServer) DoStreamStart(v *InviteParams, _ http.ResponseWriter, _ *h
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 录像下载, 转发到streaminfo接口
|
||||||
|
if "download" == action {
|
||||||
|
if r.URL.RawQuery == "" {
|
||||||
|
r.URL.RawQuery = "streamid=" + string(v.streamId)
|
||||||
|
} else if r.URL.RawQuery != "" {
|
||||||
|
r.URL.RawQuery += "&streamid=" + string(v.streamId)
|
||||||
|
}
|
||||||
|
common.HttpForwardTo("/api/v1/stream/info", w, r)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
var urls map[string]string
|
var urls map[string]string
|
||||||
urls = make(map[string]string, 10)
|
urls = make(map[string]string, 10)
|
||||||
for _, url := range stream.Urls {
|
for _, url := range stream.Urls {
|
||||||
@@ -671,7 +671,9 @@ func (api *ApiServer) DoInvite(inviteType common.InviteType, params *InviteParam
|
|||||||
|
|
||||||
// 解析回放或下载速度参数
|
// 解析回放或下载速度参数
|
||||||
speed, _ := strconv.Atoi(params.Speed)
|
speed, _ := strconv.Atoi(params.Speed)
|
||||||
speed = int(math.Min(4, float64(speed)))
|
if speed < 1 {
|
||||||
|
speed = 4
|
||||||
|
}
|
||||||
d := stack.Device{device}
|
d := stack.Device{device}
|
||||||
stream, err := d.StartStream(inviteType, params.streamId, params.ChannelID, startTimeSeconds, endTimeSeconds, params.Setup, speed, sync)
|
stream, err := d.StartStream(inviteType, params.streamId, params.ChannelID, startTimeSeconds, endTimeSeconds, params.Setup, speed, sync)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -681,9 +683,14 @@ func (api *ApiServer) DoInvite(inviteType common.InviteType, params *InviteParam
|
|||||||
return http.StatusOK, stream, nil
|
return http.StatusOK, stream, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ApiServer) OnCloseStream(v *InviteParams, w http.ResponseWriter, _ *http.Request) (interface{}, error) {
|
func (api *ApiServer) OnCloseStream(v *StreamIDParams, _ http.ResponseWriter, _ *http.Request) (interface{}, error) {
|
||||||
streamID := common.GenerateStreamID(common.InviteTypePlay, v.DeviceID, v.ChannelID, "", "")
|
stack.CloseStream(v.StreamID, true)
|
||||||
stack.CloseStream(streamID, true)
|
return "OK", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ApiServer) OnCloseLiveStream(v *InviteParams, _ http.ResponseWriter, _ *http.Request) (interface{}, error) {
|
||||||
|
id := common.GenerateStreamID(common.InviteTypePlay, v.DeviceID, v.ChannelID, "", "")
|
||||||
|
stack.CloseStream(id, true)
|
||||||
return "OK", nil
|
return "OK", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1077,8 +1084,73 @@ func (api *ApiServer) OnBroadcast(v *BroadcastParams, _ http.ResponseWriter, r *
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ApiServer) OnTalk(_ http.ResponseWriter, _ *http.Request) {
|
func (api *ApiServer) OnTalk(w http.ResponseWriter, r *http.Request) {
|
||||||
|
//vars := mux.Vars(r)
|
||||||
|
//device := vars["device"]
|
||||||
|
//channel := vars["channel"]
|
||||||
|
format := r.URL.Query().Get("format")
|
||||||
|
|
||||||
|
// 升级HTTP连接到WebSocket
|
||||||
|
conn, err := api.upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Sugar.Errorf("WebSocket升级失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
parse, err := url.Parse(common.Config.MediaServer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 目标WebSocket服务地址
|
||||||
|
targetURL := fmt.Sprintf("ws://%s%s?format=%s", parse.Host, r.URL.Path, format)
|
||||||
|
|
||||||
|
// 连接到目标WebSocket服务
|
||||||
|
targetConn, _, err := websocket.DefaultDialer.Dial(targetURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Sugar.Errorf("连接目标WebSocket失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer targetConn.Close()
|
||||||
|
|
||||||
|
group := sync.WaitGroup{}
|
||||||
|
group.Add(2)
|
||||||
|
|
||||||
|
// 启动两个goroutine双向转发数据
|
||||||
|
// 从客户端转发到目标服务
|
||||||
|
go func() {
|
||||||
|
defer group.Done()
|
||||||
|
for {
|
||||||
|
messageType, p, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
log.Sugar.Debugf("读取客户端消息错误: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := targetConn.WriteMessage(messageType, p); err != nil {
|
||||||
|
log.Sugar.Debugf("写入目标服务消息错误: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 从目标服务转发到客户端
|
||||||
|
go func() {
|
||||||
|
defer group.Done()
|
||||||
|
for {
|
||||||
|
messageType, p, err := targetConn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
log.Sugar.Debugf("读取目标服务消息错误: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := conn.WriteMessage(messageType, p); err != nil {
|
||||||
|
log.Sugar.Debugf("写入客户端消息错误: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
group.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ApiServer) OnStarted(_ http.ResponseWriter, _ *http.Request) {
|
func (api *ApiServer) OnStarted(_ http.ResponseWriter, _ *http.Request) {
|
||||||
@@ -1316,28 +1388,7 @@ func (api *ApiServer) OnCatalogQuery(params *QueryDeviceChannel, _ http.Response
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *ApiServer) OnStreamInfo(w http.ResponseWriter, r *http.Request) {
|
func (api *ApiServer) OnStreamInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
response, err := stack.MSQueryStreamInfo(r.Header, r.URL.RawQuery)
|
common.HttpForwardTo("/api/v1/stream/info", w, r)
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
_, _ = w.Write([]byte(err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer response.Body.Close()
|
|
||||||
|
|
||||||
// 复制响应头
|
|
||||||
for name, values := range response.Header {
|
|
||||||
for _, value := range values {
|
|
||||||
w.Header().Add(name, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置状态码并转发响应体
|
|
||||||
w.WriteHeader(response.StatusCode)
|
|
||||||
_, err = io.Copy(w, response.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Sugar.Errorf("Failed to copy response body: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ApiServer) OnSessionList(q *QueryDeviceChannel, _ http.ResponseWriter, r *http.Request) (interface{}, error) {
|
func (api *ApiServer) OnSessionList(q *QueryDeviceChannel, _ http.ResponseWriter, r *http.Request) (interface{}, error) {
|
||||||
@@ -1406,7 +1457,7 @@ func (api *ApiServer) OnSessionList(q *QueryDeviceChannel, _ http.ResponseWriter
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *ApiServer) OnSessionStop(params *StreamIDParams, w http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (api *ApiServer) OnSessionStop(params *StreamIDParams, w http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
err := stack.MSCloseSource(params.StreamID)
|
err := stack.MSCloseSource(string(params.StreamID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1622,27 +1673,13 @@ func (api *ApiServer) OnCatalogPush(q *SetEnable, w http.ResponseWriter, req *ht
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *ApiServer) OnRecordStart(writer http.ResponseWriter, request *http.Request) {
|
func (api *ApiServer) OnRecordStart(writer http.ResponseWriter, request *http.Request) {
|
||||||
target, _ := url.Parse(fmt.Sprintf("%s/api/v1/record/start", common.Config.MediaServer))
|
common.HttpForwardTo("/api/v1/record/start", writer, request)
|
||||||
proxy := &httputil.ReverseProxy{
|
|
||||||
Director: func(req *http.Request) {
|
|
||||||
req.URL = target
|
|
||||||
req.Host = target.Host
|
|
||||||
req.Header.Set("X-Forwarded-Host", req.Header.Get("Host"))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy.ServeHTTP(writer, request)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ApiServer) OnRecordStop(writer http.ResponseWriter, request *http.Request) {
|
func (api *ApiServer) OnRecordStop(writer http.ResponseWriter, request *http.Request) {
|
||||||
target, _ := url.Parse(fmt.Sprintf("%s/api/v1/record/stop", common.Config.MediaServer))
|
common.HttpForwardTo("/api/v1/record/stop", writer, request)
|
||||||
proxy := &httputil.ReverseProxy{
|
}
|
||||||
Director: func(req *http.Request) {
|
|
||||||
req.URL = target
|
func (api *ApiServer) OnPlaybackControl(params *StreamParams, w http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
req.Host = target.Host
|
return "OK", nil
|
||||||
req.Header.Set("X-Forwarded-Host", req.Header.Get("Host"))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy.ServeHTTP(writer, request)
|
|
||||||
}
|
}
|
||||||
|
54
common/http_proxy.go
Normal file
54
common/http_proxy.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HttpForwardTo(path string, w http.ResponseWriter, r *http.Request) {
|
||||||
|
target, _ := url.Parse(fmt.Sprintf("%s%s", Config.MediaServer, path))
|
||||||
|
proxy := &httputil.ReverseProxy{
|
||||||
|
Director: func(req *http.Request) {
|
||||||
|
req.URL = target
|
||||||
|
req.Host = target.Host
|
||||||
|
req.Header.Set("X-Forwarded-Host", req.Header.Get("Host"))
|
||||||
|
|
||||||
|
// 复制所有原始请求头
|
||||||
|
for name, values := range r.Header {
|
||||||
|
for _, value := range values {
|
||||||
|
req.Header.Add(name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保留原始查询参数
|
||||||
|
req.URL.RawQuery = r.URL.RawQuery
|
||||||
|
|
||||||
|
// 复制请求体(如果有)
|
||||||
|
if r.Body != nil {
|
||||||
|
bodyBytes, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Error reading request body", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.Body.Close()
|
||||||
|
req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
||||||
|
req.ContentLength = int64(len(bodyBytes))
|
||||||
|
req.GetBody = func() (io.ReadCloser, error) {
|
||||||
|
return io.NopCloser(bytes.NewReader(bodyBytes)), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制其他请求属性
|
||||||
|
req.Method = r.Method
|
||||||
|
req.Proto = r.Proto
|
||||||
|
req.ProtoMajor = r.ProtoMajor
|
||||||
|
req.ProtoMinor = r.ProtoMinor
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy.ServeHTTP(w, r)
|
||||||
|
}
|
@@ -2,7 +2,9 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StreamID string // 目前目涉及转码,多路流, 与SourceID相同
|
type StreamID string // 目前目涉及转码,多路流, 与SourceID相同
|
||||||
@@ -24,6 +26,19 @@ func GenerateStreamID(inviteType InviteType, deviceId, channelId string, startTi
|
|||||||
}
|
}
|
||||||
|
|
||||||
streamId = append(streamId, channelId)
|
streamId = append(streamId, channelId)
|
||||||
|
|
||||||
|
// 转换时间戳
|
||||||
|
if startTime != "" {
|
||||||
|
if t, err := time.Parse("2006-01-02T15:04:05", startTime); err == nil {
|
||||||
|
startTime = strconv.FormatInt(t.Unix(), 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if endTime != "" {
|
||||||
|
if t, err := time.Parse("2006-01-02T15:04:05", endTime); err == nil {
|
||||||
|
endTime = strconv.FormatInt(t.Unix(), 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if InviteTypePlayback == inviteType {
|
if InviteTypePlayback == inviteType {
|
||||||
return StreamID(strings.Join(streamId, "/") + ".playback" + "." + startTime + "." + endTime)
|
return StreamID(strings.Join(streamId, "/") + ".playback" + "." + startTime + "." + endTime)
|
||||||
} else if InviteTypeDownload == inviteType {
|
} else if InviteTypeDownload == inviteType {
|
||||||
|
@@ -9,8 +9,8 @@ import (
|
|||||||
"gb-cms/sdp"
|
"gb-cms/sdp"
|
||||||
"github.com/ghettovoice/gosip"
|
"github.com/ghettovoice/gosip"
|
||||||
"github.com/ghettovoice/gosip/sip"
|
"github.com/ghettovoice/gosip/sip"
|
||||||
"math"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ func (d *Device) Invite(inviteType common.InviteType, streamId common.StreamID,
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// 告知流媒体服务创建国标源, 返回收流地址信息
|
// 告知流媒体服务创建国标源, 返回收流地址信息
|
||||||
ip, port, urls, ssrc, msErr := MSCreateGBSource(string(streamId), setup, "", string(inviteType))
|
ip, port, urls, ssrc, msErr := MSCreateGBSource(string(streamId), setup, "", string(inviteType), speed)
|
||||||
if msErr != nil {
|
if msErr != nil {
|
||||||
log.Sugar.Errorf("创建GBSource失败 err: %s", msErr.Error())
|
log.Sugar.Errorf("创建GBSource失败 err: %s", msErr.Error())
|
||||||
return nil, nil, msErr
|
return nil, nil, msErr
|
||||||
@@ -99,7 +99,6 @@ func (d *Device) Invite(inviteType common.InviteType, streamId common.StreamID,
|
|||||||
if common.InviteTypePlayback == inviteType {
|
if common.InviteTypePlayback == inviteType {
|
||||||
inviteRequest, err = d.BuildPlaybackRequest(channelId, ip, port, startTime, stopTime, setup, ssrc)
|
inviteRequest, err = d.BuildPlaybackRequest(channelId, ip, port, startTime, stopTime, setup, ssrc)
|
||||||
} else if common.InviteTypeDownload == inviteType {
|
} else if common.InviteTypeDownload == inviteType {
|
||||||
speed = int(math.Min(4, float64(speed)))
|
|
||||||
inviteRequest, err = d.BuildDownloadRequest(channelId, ip, port, startTime, stopTime, setup, speed, ssrc)
|
inviteRequest, err = d.BuildDownloadRequest(channelId, ip, port, startTime, stopTime, setup, speed, ssrc)
|
||||||
} else {
|
} else {
|
||||||
inviteRequest, err = d.BuildLiveRequest(channelId, ip, port, setup, ssrc)
|
inviteRequest, err = d.BuildLiveRequest(channelId, ip, port, setup, ssrc)
|
||||||
@@ -129,7 +128,7 @@ func (d *Device) Invite(inviteType common.InviteType, streamId common.StreamID,
|
|||||||
recipient.SetPort(&sipPort)
|
recipient.SetPort(&sipPort)
|
||||||
|
|
||||||
log.Sugar.Infof("send ack %s", ackRequest.String())
|
log.Sugar.Infof("send ack %s", ackRequest.String())
|
||||||
|
// 发送ack
|
||||||
err = common.SipStack.Send(ackRequest)
|
err = common.SipStack.Send(ackRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
@@ -149,7 +148,7 @@ func (d *Device) Invite(inviteType common.InviteType, streamId common.StreamID,
|
|||||||
} else if dialogRequest == nil {
|
} else if dialogRequest == nil {
|
||||||
// invite 没有收到任何应答
|
// invite 没有收到任何应答
|
||||||
return nil, nil, fmt.Errorf("invite request timeout")
|
return nil, nil, fmt.Errorf("invite request timeout")
|
||||||
} else if "active" == setup {
|
} else if "active" == setup || common.InviteTypeDownload == inviteType {
|
||||||
// 如果是TCP主动拉流, 还需要将拉流地址告知给流媒体服务
|
// 如果是TCP主动拉流, 还需要将拉流地址告知给流媒体服务
|
||||||
var answer *sdp.SDP
|
var answer *sdp.SDP
|
||||||
answer, err = sdp.Parse(body)
|
answer, err = sdp.Parse(body)
|
||||||
@@ -157,8 +156,20 @@ func (d *Device) Invite(inviteType common.InviteType, streamId common.StreamID,
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 解析下载的文件大小
|
||||||
|
var fileSize int
|
||||||
|
if common.InviteTypeDownload == inviteType {
|
||||||
|
for _, attr := range answer.Attrs {
|
||||||
|
if "filesize" != attr[0] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSize, _ = strconv.Atoi(attr[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", answer.Addr, answer.Video.Port)
|
addr := fmt.Sprintf("%s:%d", answer.Addr, answer.Video.Port)
|
||||||
if err = MSConnectGBSource(string(streamId), addr); err != nil {
|
if err = MSConnectGBSource(string(streamId), addr, fileSize); err != nil {
|
||||||
log.Sugar.Errorf("设置GB28181连接地址失败 err: %s addr: %s", err.Error(), addr)
|
log.Sugar.Errorf("设置GB28181连接地址失败 err: %s addr: %s", err.Error(), addr)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@@ -54,6 +54,10 @@ type SDP struct {
|
|||||||
SSRC string `json:"ssrc,omitempty"`
|
SSRC string `json:"ssrc,omitempty"`
|
||||||
Setup string `json:"setup,omitempty"` // active/passive
|
Setup string `json:"setup,omitempty"` // active/passive
|
||||||
Transport string `json:"transport,omitempty"` // tcp/udp
|
Transport string `json:"transport,omitempty"` // tcp/udp
|
||||||
|
Speed int `json:"speed"`
|
||||||
|
StartTime int `json:"start_time,omitempty"`
|
||||||
|
EndTime int `json:"end_time,omitempty"`
|
||||||
|
FileSize int `json:"file_size,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SourceSDP struct {
|
type SourceSDP struct {
|
||||||
@@ -99,13 +103,14 @@ func SendWithUrlParams(path string, body interface{}, values url.Values) (*http.
|
|||||||
return client.Do(request)
|
return client.Do(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MSCreateGBSource(id, setup string, ssrc string, sessionName string) (string, uint16, []string, string, error) {
|
func MSCreateGBSource(id, setup string, ssrc string, sessionName string, speed int) (string, uint16, []string, string, error) {
|
||||||
v := &SourceSDP{
|
v := &SourceSDP{
|
||||||
Source: id,
|
Source: id,
|
||||||
SDP: SDP{
|
SDP: SDP{
|
||||||
Setup: setup,
|
Setup: setup,
|
||||||
SSRC: ssrc,
|
SSRC: ssrc,
|
||||||
SessionName: sessionName,
|
SessionName: sessionName,
|
||||||
|
Speed: speed,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,11 +139,12 @@ func MSCreateGBSource(id, setup string, ssrc string, sessionName string) (string
|
|||||||
return host, uint16(port), data.Data.Urls, data.Data.SSRC, err
|
return host, uint16(port), data.Data.Urls, data.Data.SSRC, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func MSConnectGBSource(id, addr string) error {
|
func MSConnectGBSource(id, addr string, fileSize int) error {
|
||||||
v := &SourceSDP{
|
v := &SourceSDP{
|
||||||
Source: id,
|
Source: id,
|
||||||
SDP: SDP{
|
SDP: SDP{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
|
FileSize: fileSize,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -37,6 +37,7 @@ const (
|
|||||||
CmdMobilePosition = "MobilePosition"
|
CmdMobilePosition = "MobilePosition"
|
||||||
CmdKeepalive = "Keepalive"
|
CmdKeepalive = "Keepalive"
|
||||||
CmdBroadcast = "Broadcast"
|
CmdBroadcast = "Broadcast"
|
||||||
|
CmdMediaStatus = "MediaStatus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type sipServer struct {
|
type sipServer struct {
|
||||||
@@ -293,6 +294,11 @@ func (s *sipServer) OnMessage(wrapper *SipRequestSource) {
|
|||||||
if CmdKeepalive == cmd {
|
if CmdKeepalive == cmd {
|
||||||
// 下级设备心跳通知
|
// 下级设备心跳通知
|
||||||
ok = s.handler.OnKeepAlive(deviceId, wrapper.req.Source())
|
ok = s.handler.OnKeepAlive(deviceId, wrapper.req.Source())
|
||||||
|
} else if CmdMediaStatus == cmd {
|
||||||
|
// 回放/下载结束
|
||||||
|
ok = true
|
||||||
|
id, _ := wrapper.req.CallID()
|
||||||
|
CloseStreamByCallID(id.Value())
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
@@ -429,6 +435,7 @@ func StartSipServer(id, listenIP, publicIP string, listenPort int) (common.SipSe
|
|||||||
fmt.Sprintf("%s.%s", XmlNameNotify, CmdKeepalive): reflect.TypeOf(BaseMessage{}),
|
fmt.Sprintf("%s.%s", XmlNameNotify, CmdKeepalive): reflect.TypeOf(BaseMessage{}),
|
||||||
fmt.Sprintf("%s.%s", XmlNameNotify, CmdMobilePosition): reflect.TypeOf(BaseMessage{}),
|
fmt.Sprintf("%s.%s", XmlNameNotify, CmdMobilePosition): reflect.TypeOf(BaseMessage{}),
|
||||||
fmt.Sprintf("%s.%s", XmlNameResponse, CmdBroadcast): reflect.TypeOf(BaseMessage{}),
|
fmt.Sprintf("%s.%s", XmlNameResponse, CmdBroadcast): reflect.TypeOf(BaseMessage{}),
|
||||||
|
fmt.Sprintf("%s.%s", XmlNameNotify, CmdMediaStatus): reflect.TypeOf(BaseMessage{}),
|
||||||
}}
|
}}
|
||||||
|
|
||||||
utils.Assert(ua.OnRequest(sip.REGISTER, filterRequest(server.OnRegister)) == nil)
|
utils.Assert(ua.OnRequest(sip.REGISTER, filterRequest(server.OnRegister)) == nil)
|
||||||
|
@@ -107,6 +107,13 @@ func CloseStream(streamId common.StreamID, ms bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CloseStreamByCallID(callId string) {
|
||||||
|
deleteStream, err := dao.Stream.DeleteStreamByCallID(callId)
|
||||||
|
if err == nil {
|
||||||
|
(&Stream{deleteStream}).Close(true, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// CloseStreamSinks 关闭某个流的所有sink
|
// CloseStreamSinks 关闭某个流的所有sink
|
||||||
func CloseStreamSinks(StreamID common.StreamID, bye, ms bool) []*dao.SinkModel {
|
func CloseStreamSinks(StreamID common.StreamID, bye, ms bool) []*dao.SinkModel {
|
||||||
sinks, _ := dao.Sink.DeleteForwardSinksByStreamID(StreamID)
|
sinks, _ := dao.Sink.DeleteForwardSinksByStreamID(StreamID)
|
||||||
|
Reference in New Issue
Block a user