// Code generated by godddx, DO AVOID EDIT. package api import ( "fmt" "io" "log/slog" "os" "path/filepath" "strings" "time" "github.com/gin-gonic/gin" "github.com/gowvp/gb28181/internal/core/bz" "github.com/gowvp/gb28181/internal/core/gb28181" "github.com/gowvp/gb28181/internal/core/media" "github.com/gowvp/gb28181/internal/core/sms" "github.com/gowvp/gb28181/pkg/zlm" "github.com/ixugo/goddd/domain/uniqueid" "github.com/ixugo/goddd/pkg/orm" "github.com/ixugo/goddd/pkg/reason" "github.com/ixugo/goddd/pkg/web" ) var ErrDevice = reason.NewError("ErrDevice", "设备错误") const ( coverDir = "cover" ) // TODO: 快照不会删除,只会覆盖,设备删除时也不会删除快照,待实现 func writeCover(dataDir, channelID string, body []byte) error { coverPath := filepath.Join(dataDir, coverDir) os.MkdirAll(coverPath, 0o755) return os.WriteFile(filepath.Join(coverPath, channelID+".jpg"), body, 0o644) } func readCoverPath(dataDir, channelID string) string { coverPath := filepath.Join(dataDir, coverDir) return filepath.Join(coverPath, channelID+".jpg") } func readCover(dataDir, channelID string) ([]byte, error) { return os.ReadFile(readCoverPath(dataDir, channelID)) } type GB28181API struct { gb28181Core gb28181.Core uc *Usecase } func NewGB28181API(core gb28181.Core) GB28181API { return GB28181API{gb28181Core: core} } func NewGB28181Core(store gb28181.Storer, uni uniqueid.Core) gb28181.Core { return gb28181.NewCore(store, uni) } func registerGB28181(g gin.IRouter, api GB28181API, handler ...gin.HandlerFunc) { g.Any("/gb28181/snapshot", func(c *gin.Context) { b, err := io.ReadAll(c.Request.Body) if err != nil { panic(err) } os.WriteFile(orm.GenerateRandomString(10)+".jpg", b, 0o644) c.JSON(200, gin.H{"msg": "ok"}) }) { group := g.Group("/devices", handler...) group.GET("", web.WarpH(api.findDevice)) group.GET("/:id", web.WarpH(api.getDevice)) group.PUT("/:id", web.WarpH(api.editDevice)) group.POST("", web.WarpH(api.addDevice)) group.DELETE("/:id", web.WarpH(api.delDevice)) group.POST("/:id/catalog", web.WarpH(api.queryCatalog)) // 刷新通道 group.GET("/channels", web.WarpH(api.FindChannelsForDevice)) } { group := g.Group("/channels", handler...) group.GET("", web.WarpH(api.findChannel)) group.PUT("/:id", web.WarpH(api.editChannel)) group.POST("/:id/play", web.WarpH(api.play)) group.POST("/:id/snapshot", web.WarpH(api.refreshSnapshot)) // 图像抓拍 group.GET("/:id/snapshot", api.getSnapshot) // 获取图像 // group.GET("/:id", web.WarpH(api.getChannel)) // group.POST("", web.WarpH(api.addChannel)) // group.DELETE("/:id", web.WarpH(api.delChannel)) } } // >>> device >>>>>>>>>>>>>>>>>>>> func (a GB28181API) findDevice(c *gin.Context, in *gb28181.FindDeviceInput) (any, error) { items, total, err := a.gb28181Core.FindDevice(c.Request.Context(), in) return gin.H{"items": items, "total": total}, err } func (a GB28181API) getDevice(c *gin.Context, _ *struct{}) (any, error) { deviceID := c.Param("id") return a.gb28181Core.GetDevice(c.Request.Context(), deviceID) } func (a GB28181API) editDevice(c *gin.Context, in *gb28181.EditDeviceInput) (any, error) { deviceID := c.Param("id") return a.gb28181Core.EditDevice(c.Request.Context(), in, deviceID) } func (a GB28181API) addDevice(c *gin.Context, in *gb28181.AddDeviceInput) (any, error) { return a.gb28181Core.AddDevice(c.Request.Context(), in) } func (a GB28181API) delDevice(c *gin.Context, _ *struct{}) (any, error) { did := c.Param("id") return a.gb28181Core.DelDevice(c.Request.Context(), did) } func (a GB28181API) queryCatalog(c *gin.Context, _ *struct{}) (any, error) { did := c.Param("id") if err := a.uc.SipServer.QueryCatalog(did); err != nil { return nil, ErrDevice.SetMsg(err.Error()) } return gin.H{"msg": "ok"}, nil } func (a GB28181API) FindChannelsForDevice(c *gin.Context, in *gb28181.FindDeviceInput) (any, error) { items, total, err := a.gb28181Core.FindChannelsForDevice(c.Request.Context(), in) return gin.H{"items": items, "total": total}, err } // >>> channel >>>>>>>>>>>>>>>>>>>> func (a GB28181API) findChannel(c *gin.Context, in *gb28181.FindChannelInput) (any, error) { items, total, err := a.gb28181Core.FindChannel(c.Request.Context(), in) return gin.H{"items": items, "total": total}, err } // func (a GB28181API) getChannel(c *gin.Context, _ *struct{}) (any, error) { // channelID := c.Param("id") // return a.gb28181Core.GetChannel(c.Request.Context(), channelID) // } func (a GB28181API) editChannel(c *gin.Context, in *gb28181.EditChannelInput) (any, error) { cid := c.Param("id") return a.gb28181Core.EditChannel(c.Request.Context(), in, cid) } // func (a GB28181API) addChannel(c *gin.Context, in *gb28181.AddChannelInput) (any, error) { // return a.gb28181Core.AddChannel(c.Request.Context(), in) // } // func (a GB28181API) delChannel(c *gin.Context, _ *struct{}) (any, error) { // channelID := c.Param("id") // return a.gb28181Core.DelChannel(c.Request.Context(), channelID) // } func (a GB28181API) play(c *gin.Context, _ *struct{}) (*playOutput, error) { channelID := c.Param("id") var app, appStream, host, stream, session string var svr *sms.MediaServer // 国标逻辑 if strings.HasPrefix(channelID, bz.IDPrefixGBChannel) { // 防止错误的配置,无法收到流 if a.uc.Conf.Media.SDPIP == "127.0.0.1" { return nil, reason.ErrUsedLogic.SetMsg("请先配置流媒体 SDP 收流地址") } // a.uc.SipServer. ch, err := a.gb28181Core.GetChannel(c.Request.Context(), channelID) if err != nil { return nil, err } app = "rtp" appStream = ch.ID svr, err = a.uc.SMSAPI.smsCore.GetMediaServer(c.Request.Context(), sms.DefaultMediaServerID) if err != nil { return nil, err } } else if strings.HasPrefix(channelID, bz.IDPrefixRTMP) { push, err := a.uc.MediaAPI.mediaCore.GetStreamPush(c.Request.Context(), channelID) if err != nil { return nil, err } if push.Status != media.StatusPushing { return nil, reason.ErrNotFound.SetMsg("未推流") } app = push.App appStream = push.Stream svr, err = a.uc.SMSAPI.smsCore.GetMediaServer(c.Request.Context(), push.MediaServerID) if err != nil { return nil, err } if !push.IsAuthDisabled && push.Session != "" { session = "session=" + push.Session } } else if strings.HasPrefix(channelID, bz.IDPrefixRTSP) { proxy, err := a.uc.ProxyAPI.proxyCore.GetStreamProxy(c.Request.Context(), channelID) if err != nil { return nil, err } app = proxy.App appStream = proxy.Stream svr, err = a.uc.SMSAPI.smsCore.GetMediaServer(c.Request.Context(), sms.DefaultMediaServerID) if err != nil { return nil, err } resp, err := a.uc.SMSAPI.smsCore.AddStreamProxy(svr, zlm.AddStreamProxyRequest{ Vhost: "__defaultVhost__", App: proxy.App, Stream: proxy.Stream, URL: proxy.SourceURL, RetryCount: 3, RTPType: proxy.Transport, TimeoutSec: 10, // EnableRTMP: zlm.NewBool(true), // EnableRTSP: zlm.NewBool(true), // EnableHLS: zlm.NewBool(true), // EnableAudio: zlm.NewBool(true), AddMuteAudio: zlm.NewBool(true), // AutoClose: zlm.NewBool(false), }) if err != nil { return nil, reason.ErrServer.SetMsg(err.Error()) } a.uc.ProxyAPI.proxyCore.EditStreamProxyKey(c.Request.Context(), resp.Data.Key, proxy.ID) } else { return nil, reason.ErrNotFound.SetMsg("不支持的播放通道") } stream = app + "/" + appStream host = c.Request.Host if l := strings.Split(c.Request.Host, ":"); len(l) == 2 { host = l[0] } httpPort := a.uc.Conf.Server.HTTP.Port // 播放规则 // https://github.com/zlmediakit/ZLMediaKit/wiki/%E6%92%AD%E6%94%BEurl%E8%A7%84%E5%88%99 out := playOutput{ App: app, Stream: appStream, Items: []streamAddrItem{ { Label: "默认线路", WSFLV: fmt.Sprintf("ws://%s:%d/proxy/sms/%s.live.flv", host, httpPort, stream) + "?" + session, HTTPFLV: fmt.Sprintf("http://%s:%d/proxy/sms/%s.live.flv", host, httpPort, stream) + "?" + session, RTMP: fmt.Sprintf("rtmp://%s:%d/%s", host, svr.Ports.RTMP, stream) + "?" + session, RTSP: fmt.Sprintf("rtsp://%s:%d/%s", host, svr.Ports.RTSP, stream) + "?" + session, WebRTC: fmt.Sprintf("webrtc://%s:%d/proxy/sms/index/api/webrtc?app=%s&stream=%s&type=play", host, httpPort, app, stream) + "&" + session, HLS: fmt.Sprintf("http://%s:%d/proxy/sms/%s/hls.fmp4.m3u8", host, httpPort, stream) + "?" + session, }, { Label: "SSL 线路", WSFLV: fmt.Sprintf("wss://%s:%d/%s.live.flv", host, svr.Ports.HTTP, stream) + session, HTTPFLV: fmt.Sprintf("https://%s:%d/%s.live.flv", host, svr.Ports.HTTP, stream) + session, RTMP: fmt.Sprintf("rtmps://%s:%d/%s", host, svr.Ports.RTMPs, stream) + session, RTSP: fmt.Sprintf("rtsps://%s:%d/%s", host, svr.Ports.RTSPs, stream) + session, WebRTC: fmt.Sprintf("webrtc://%s:%d/index/api/webrtc?app=%s&stream=%s&type=play", host, svr.Ports.HTTPS, app, stream) + "&" + session, HLS: fmt.Sprintf("https://%s:%d/%s/hls.fmp4.m3u8", host, svr.Ports.HTTPS, stream) + "?" + session, }, }, } // 取一张快照 go func() { body, err := a.uc.SMSAPI.smsCore.GetSnapshot(svr, zlm.GetSnapRequest{ URL: out.Items[0].RTSP, TimeoutSec: 10, ExpireSec: 10, }) if err != nil { slog.Error("get snapshot", "err", err) } else { writeCover(a.uc.Conf.ConfigDir, channelID, body) } }() return &out, nil } type refreshSnapshotInput struct { // 指定获取多少秒内创建的快照 WithinSeconds int64 `json:"within_seconds"` // 取快照的链接地址 URL string `json:"url"` } func (a GB28181API) refreshSnapshot(c *gin.Context, in *refreshSnapshotInput) (any, error) { channelID := c.Param("id") path := readCoverPath(a.uc.Conf.ConfigDir, channelID) // 获取文件的修改时间 fileInfo, err := os.Stat(path) if err == nil { if fileInfo.ModTime().Unix() > time.Now().Unix()-in.WithinSeconds { return gin.H{"link": fmt.Sprintf("/api/channels/%s/snapshot", channelID)}, nil } } if in.URL != "" { svr, err := a.uc.SMSAPI.smsCore.GetMediaServer(c.Request.Context(), sms.DefaultMediaServerID) if err != nil { return nil, err } img, err := a.uc.SMSAPI.smsCore.GetSnapshot(svr, zlm.GetSnapRequest{ URL: in.URL, TimeoutSec: 10, ExpireSec: 10, }) if err != nil { slog.Error("get snapshot", "err", err) // return nil, reason.ErrBadRequest.Msg(err.Error()) } else { writeCover(a.uc.Conf.ConfigDir, channelID, img) } } return gin.H{"link": fmt.Sprintf("/channels/%s/snapshot", channelID)}, nil } func (a GB28181API) getSnapshot(c *gin.Context) { channelID := c.Param("id") body, err := readCover(a.uc.Conf.ConfigDir, channelID) if err != nil { reason.ErrNotFound.SetMsg(err.Error()) return } c.Data(200, "image/jpeg", body) }