Files
gb-cms/stack/media_server.go
2025-09-14 19:35:58 +08:00

277 lines
6.3 KiB
Go

package stack
import (
"bytes"
"encoding/json"
"fmt"
"gb-cms/common"
"net"
"net/http"
"net/url"
"strconv"
"time"
)
const (
TransStreamRtmp = iota + 1
TransStreamFlv = 2
TransStreamRtsp = 3
TransStreamHls = 4
TransStreamRtc = 5
TransStreamGBCascaded = 6 // 国标级联转发
TransStreamGBTalk = 7 // 国标广播/对讲转发
TransStreamGBGateway = 8 // 国标网关
)
const (
SourceTypeRtmp = iota + 1
SourceType28181
SourceType1078
SourceTypeGBTalk
)
type SourceDetails struct {
ID string `json:"id"`
Protocol string `json:"protocol"` // 推流协议
Time time.Time `json:"time"` // 推流时间
SinkCount int `json:"sink_count"` // 播放端计数
Bitrate string `json:"bitrate"` // 码率统计
Tracks []string `json:"tracks"` // 每路流编码器ID
Urls []string `json:"urls"` // 拉流地址
}
type SinkDetails struct {
ID string `json:"id"`
Protocol string `json:"protocol"` // 拉流协议
Time time.Time `json:"time"` // 拉流时间
Bitrate string `json:"bitrate"` // 码率统计
Tracks []string `json:"tracks"` // 每路流编码器ID
}
type SDP struct {
SessionName string `json:"session_name,omitempty"` // play/download/playback/talk/broadcast
Addr string `json:"addr,omitempty"` // 连接地址
SSRC string `json:"ssrc,omitempty"`
Setup string `json:"setup,omitempty"` // active/passive
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 {
Source string `json:"source"` // GetSourceID
SDP
}
type GBOffer struct {
SourceSDP
AnswerSetup string `json:"answer_setup,omitempty"` // 希望应答的连接方式
TransStreamProtocol int `json:"trans_stream_protocol,omitempty"`
}
func Send(path string, body interface{}) (*http.Response, error) {
return SendWithUrlParams(path, body, nil)
}
func SendWithUrlParams(path string, body interface{}, values url.Values) (*http.Response, error) {
if values != nil {
params := values.Encode()
if len(params) > 0 {
path = fmt.Sprintf("%s?%s", path, params)
}
}
url := fmt.Sprintf("%s/%s", common.Config.MediaServer, path)
data, err := json.Marshal(body)
if err != nil {
return nil, err
}
client := &http.Client{
Timeout: 10 * time.Second,
}
request, err := http.NewRequest("post", url, bytes.NewBuffer(data))
if err != nil {
return nil, err
}
request.Header.Set("Content-Type", "application/json")
return client.Do(request)
}
func MSCreateGBSource(id, setup string, ssrc string, sessionName string, speed int) (string, uint16, []string, string, error) {
v := &SourceSDP{
Source: id,
SDP: SDP{
Setup: setup,
SSRC: ssrc,
SessionName: sessionName,
Speed: speed,
},
}
response, err := Send("api/v1/gb28181/source/create", v)
if err != nil {
return "", 0, nil, "", err
}
data := &common.Response[struct {
SDP
Urls []string `json:"urls"`
}]{}
if err = common.DecodeJSONBody(response.Body, data); err != nil {
return "", 0, nil, "", err
} else if http.StatusOK != data.Code {
return "", 0, nil, "", fmt.Errorf(data.Msg)
}
host, p, err := net.SplitHostPort(data.Data.Addr)
if err != nil {
return "", 0, nil, "", err
}
port, err := strconv.Atoi(p)
return host, uint16(port), data.Data.Urls, data.Data.SSRC, err
}
func MSConnectGBSource(id, addr string, fileSize int) error {
v := &SourceSDP{
Source: id,
SDP: SDP{
Addr: addr,
FileSize: fileSize,
},
}
_, err := Send("api/v1/gb28181/answer/set", v)
return err
}
func MSCloseSource(id string) error {
v := &struct {
Source string `json:"source"`
}{
Source: id,
}
_, err := Send("api/v1/source/close", v)
return err
}
func MSCloseSink(sourceId string, sinkId string) {
v := struct {
SourceID string `json:"source"`
SinkID string `json:"sink"` // sink id
}{
sourceId, sinkId,
}
_, _ = Send("api/v1/sink/close", v)
}
func MSQuerySourceList() ([]*SourceDetails, error) {
response, err := Send("api/v1/source/list", nil)
if err != nil {
return nil, err
}
data := &common.Response[[]*SourceDetails]{}
if err = common.DecodeJSONBody(response.Body, data); err != nil {
return nil, err
}
return data.Data, err
}
func MSQuerySinkList(source string) ([]*SinkDetails, error) {
id := struct {
Source string `json:"source"`
}{source}
response, err := Send("api/v1/sink/list", id)
if err != nil {
return nil, err
}
data := &common.Response[[]*SinkDetails]{}
if err = common.DecodeJSONBody(response.Body, data); err != nil {
return nil, err
}
return data.Data, err
}
func MSAddForwardSink(protocol int, source, addr, offerSetup, answerSetup, ssrc, sessionName string, values url.Values) (string, uint16, string, string, error) {
offer := &GBOffer{
SourceSDP: SourceSDP{
Source: source,
SDP: SDP{
Addr: addr,
Setup: offerSetup,
SSRC: ssrc,
SessionName: sessionName,
},
},
AnswerSetup: answerSetup,
TransStreamProtocol: protocol,
}
var err error
response, err := SendWithUrlParams("api/v1/sink/add", offer, values)
if err != nil {
return "", 0, "", "", err
}
data := &common.Response[struct {
Sink string `json:"sink"`
SDP
}]{}
if err = common.DecodeJSONBody(response.Body, data); err != nil {
return "", 0, "", "", err
} else if http.StatusOK != data.Code {
return "", 0, "", "", fmt.Errorf(data.Msg)
}
host, p, err := net.SplitHostPort(data.Data.Addr)
if err != nil {
return "", 0, "", "", err
}
port, _ := strconv.Atoi(p)
return host, uint16(port), data.Data.Sink, data.Data.SSRC, nil
}
func MSQueryStreamInfo(header http.Header, queryParams string) (*http.Response, error) {
// 构建目标URL
targetURL := common.Config.MediaServer + "/api/v1/stream/info"
if queryParams != "" {
targetURL += "?" + queryParams
}
// 创建转发请求
proxyReq, err := http.NewRequest("POST", targetURL, nil)
if err != nil {
return nil, err
}
// 复制请求头
for name, values := range header {
for _, value := range values {
proxyReq.Header.Add(name, value)
}
}
// 发送请求
client := &http.Client{
Timeout: 30 * time.Second,
}
return client.Do(proxyReq)
}