diff --git a/internal/core/sms/driver_lalmax.go b/internal/core/sms/driver_lalmax.go index 1c57166..580fa44 100644 --- a/internal/core/sms/driver_lalmax.go +++ b/internal/core/sms/driver_lalmax.go @@ -5,6 +5,7 @@ import ( "fmt" "log/slog" "net" + "strconv" "strings" "github.com/gowvp/gb28181/pkg/lalmax" @@ -25,6 +26,7 @@ type LalmaxDriver struct { // GetStreamLiveAddr implements Driver. func (l *LalmaxDriver) GetStreamLiveAddr(ctx context.Context, ms *MediaServer, httpPrefix string, host string, app string, stream string) StreamLiveAddr { var out StreamLiveAddr + out.Label = "StreamSVR" wsPrefix := strings.Replace(strings.Replace(httpPrefix, "https", "wss", 1), "http", "ws", 1) out.WSFLV = fmt.Sprintf("%s/proxy/sms/%s.flv", wsPrefix, stream) out.HTTPFLV = fmt.Sprintf("%s/proxy/sms/%s.flv", httpPrefix, stream) @@ -101,11 +103,12 @@ func (l *LalmaxDriver) GetSnapshot(ctx context.Context, ms *MediaServer, req *Ge // OpenRTPServer implements Driver. func (l *LalmaxDriver) OpenRTPServer(ctx context.Context, ms *MediaServer, req *zlm.OpenRTPServerRequest) (*zlm.OpenRTPServerResponse, error) { engine := l.withConfig(ms) + resp, err := engine.ApiCtrlStartRtpPub(ctx, lalmax.ApiCtrlStartRtpPubReq{ StreamName: req.StreamID, Port: req.Port, TimeoutMs: PullTimeoutMs, - IsTcpFlag: 0, + IsTcpFlag: int(req.TCPMode), IsWaitKeyFrame: 0, IsTcpActive: false, DebugDumpPacket: "", @@ -131,6 +134,13 @@ func (l *LalmaxDriver) Protocol() string { // Setup implements Driver. func (l *LalmaxDriver) Setup(ctx context.Context, ms *MediaServer, webhookURL string) error { engine := l.withConfig(ms) + + ports := strings.Split(ms.RTPPortRange, "-") + var minPort, maxPort int + if len(ports) == 2 { + minPort, _ = strconv.Atoi(ports[0]) + maxPort, _ = strconv.Atoi(ports[1]) + } if err := engine.SetHttpNotifyConfig(ctx, lalmax.HttpNotifyConfig{ Enable: true, // OnPubStart: webhookURL, @@ -138,6 +148,9 @@ func (l *LalmaxDriver) Setup(ctx context.Context, ms *MediaServer, webhookURL st OnSubStartWithoutStream: fmt.Sprintf("%s/on_stream_not_found", webhookURL), OnStreamChanged: fmt.Sprintf("%s/on_stream_changed", webhookURL), ClientSize: 50, + }, lalmax.MediaConfig{ + ListenPort: minPort, + MultiPortMaxIncrement: maxPort - minPort, }); err != nil { return err } diff --git a/internal/core/sms/driver_zlm.go b/internal/core/sms/driver_zlm.go index c2703d6..d4f2097 100644 --- a/internal/core/sms/driver_zlm.go +++ b/internal/core/sms/driver_zlm.go @@ -18,6 +18,7 @@ type ZLMDriver struct { // GetStreamLiveAddr implements Driver. func (d *ZLMDriver) GetStreamLiveAddr(ctx context.Context, ms *MediaServer, httpPrefix, host, app, stream string) StreamLiveAddr { var out StreamLiveAddr + out.Label = "ZLM" wsPrefix := strings.Replace(strings.Replace(httpPrefix, "https", "wss", 1), "http", "ws", 1) out.WSFLV = fmt.Sprintf("%s/proxy/sms/%s.live.flv", wsPrefix, stream) out.HTTPFLV = fmt.Sprintf("%s/proxy/sms/%s.live.flv", httpPrefix, stream) diff --git a/internal/core/sms/media_server.param.go b/internal/core/sms/media_server.param.go index 1e16001..bf742b2 100755 --- a/internal/core/sms/media_server.param.go +++ b/internal/core/sms/media_server.param.go @@ -37,6 +37,7 @@ type EditMediaServerInput struct { // Ports MediaServerPorts `json:"ports"` // AutoConfig bool `json:"auto_config"` Secret string `json:"secret"` + Type string `json:"type"` // lalmax/zlm // HookAliveInterval int `json:"hook_alive_interval"` // RTPEnable bool `json:"rtpenable"` // Status bool `json:"status"` diff --git a/internal/web/api/sms.go b/internal/web/api/sms.go index 5246b1c..0d65356 100755 --- a/internal/web/api/sms.go +++ b/internal/web/api/sms.go @@ -64,6 +64,7 @@ func (a SmsAPI) editMediaServer(c *gin.Context, in *sms.EditMediaServerInput) (a a.uc.Conf.Media.SDPIP = out.SDPIP a.uc.Conf.Media.Secret = out.Secret a.uc.Conf.Media.WebHookIP = out.HookIP + a.uc.Conf.Media.Type = out.Type if err := conf.WriteConfig(a.uc.Conf, a.uc.Conf.ConfigPath); err != nil { return nil, reason.ErrServer.SetMsg(err.Error()) } diff --git a/pkg/lalmax/config.go b/pkg/lalmax/config.go index 268e365..9d6de05 100644 --- a/pkg/lalmax/config.go +++ b/pkg/lalmax/config.go @@ -404,10 +404,14 @@ type SetServerConfigResponse struct { // if err != nil { // log.Fatal(err) // } -func (e *Engine) SetHttpNotifyConfig(ctx context.Context, config HttpNotifyConfig) error { +func (e *Engine) SetHttpNotifyConfig(ctx context.Context, config HttpNotifyConfig, gb28181 MediaConfig) error { // 使用合并模式,只更新 http_notify 配置 data := map[string]any{ "http_notify": config, + "gb28181": map[string]any{ + "enable": false, + "media_config": gb28181, + }, } return e.setServerConfig(ctx, data, true) } diff --git a/pkg/lalmax/config_test.go b/pkg/lalmax/config_test.go index 08d53c3..efd96b0 100644 --- a/pkg/lalmax/config_test.go +++ b/pkg/lalmax/config_test.go @@ -105,7 +105,10 @@ func TestSetHttpNotifyConfig(t *testing.T) { } // 设置配置 - err := engine.SetHttpNotifyConfig(ctx, notifyConfig) + err := engine.SetHttpNotifyConfig(ctx, notifyConfig, MediaConfig{ + ListenPort: 8080, + MultiPortMaxIncrement: 10, + }) if err != nil { t.Fatalf("SetHttpNotifyConfig 调用失败: %v", err) } diff --git a/pkg/lalmax/rtp.go b/pkg/lalmax/rtp.go index a874c00..dca1a25 100644 --- a/pkg/lalmax/rtp.go +++ b/pkg/lalmax/rtp.go @@ -2,7 +2,7 @@ package lalmax import "context" -const ctrlStartRtpPub = "/api/ctrl/startRtpPub" +const ctrlStartRtpPub = "/api/ctrl/start_rtp_pub" type ApiCtrlStartRtpPubReq struct { StreamName string `json:"stream_name"` diff --git a/pkg/lalmax/rtp_test.go b/pkg/lalmax/rtp_test.go new file mode 100644 index 0000000..52521db --- /dev/null +++ b/pkg/lalmax/rtp_test.go @@ -0,0 +1,65 @@ +package lalmax + +import ( + "context" + "testing" +) + +// TestApiCtrlStartRtpPub 测试启动 RTP 发布 +// 用法示例:go test -v -run TestApiCtrlStartRtpPub ./pkg/lalmax/ +// 前提条件:需要 lalmax 服务运行在 http://localhost:8080 +func TestApiCtrlStartRtpPub(t *testing.T) { + ctx := context.Background() + + // 创建 Engine 实例 + engine := NewEngine() + engine = engine.SetConfig(Config{ + URL: "http://localhost:8080", + Secret: "", // 如果需要密钥,请填写 + }) + + // 构造请求参数 + // 注意:实际使用中可能需要确保流名称对应的流存在或符合预期, + // 这里仅测试接口调用的连通性和基本参数传递。 + req := ApiCtrlStartRtpPubReq{ + StreamName: "test110", // 测试流名称 + Port: 0, // 0 表示让服务器自动分配端口 + PeerPort: 0, // 如不涉及对端被动接收,可填0或按需填写 + TimeoutMs: 10000, // 超时时间 + IsTcpFlag: 0, // 0: UDP, 1: TCP + IsWaitKeyFrame: 1, // 是否等待关键帧 + DebugDumpPacket: "", // 调试抓包文件路径 + IsTcpActive: false, // 是否为 TCP 主动模式 + } + + // 调用 ApiCtrlStartRtpPub 方法 + resp, err := engine.ApiCtrlStartRtpPub(ctx, req) + if err != nil { + t.Fatalf("ApiCtrlStartRtpPub 调用失败: %v", err) + } + + // 验证返回结果不为空 + if resp == nil { + t.Fatal("ApiCtrlStartRtpPub 返回 nil") + } + + // 打印返回结果,用于调试 + t.Logf("响应代码 (Code): %d", resp.Code) + t.Logf("响应描述 (Msg): %s", resp.Msg) + t.Logf("流名称 (StreamName): %s", resp.Data.StreamName) + t.Logf("会话 ID (SessionId): %s", resp.Data.SessionId) + t.Logf("端口 (Port): %d", resp.Data.Port) + + // 简单的校验,确保请求的流名称和返回的一致(如果成功的话) + if resp.Code == 0 { + if resp.Data.StreamName != req.StreamName { + t.Errorf("期望流名称 %s, 实际返回 %s", req.StreamName, resp.Data.StreamName) + } + if resp.Data.Port == 0 && req.Port != 0 { + // 如果请求指定了端口,期望返回该端口(视具体 lal 实现而定,这里仅作一般性检查) + t.Logf("注意:返回端口为 0") + } + } else { + t.Logf("注意:接口返回了非 0 错误码,这在没有实际流的情况下可能是预期的。") + } +}