Files
gb-cms/media_server.go
2025-05-31 21:10:04 +08:00

242 lines
5.4 KiB
Go

package main
import (
"bytes"
"encoding/json"
"fmt"
"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
}
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("http://%s/%s", 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) (string, uint16, []string, string, error) {
v := &SourceSDP{
Source: id,
SDP: SDP{
Setup: setup,
SSRC: ssrc,
SessionName: sessionName,
},
}
response, err := Send("api/v1/gb28181/offer/create", v)
if err != nil {
return "", 0, nil, "", err
}
data := &Response[struct {
SDP
Urls []string `json:"urls"`
}]{}
if err = 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) error {
v := &SourceSDP{
Source: id,
SDP: SDP{
Addr: addr,
},
}
_, 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 := &Response[[]*SourceDetails]{}
if err = 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 := &Response[[]*SinkDetails]{}
if err = 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, 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 := &Response[struct {
Sink string `json:"sink"`
Addr string `json:"addr"`
}]{}
if err = 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, nil
}