Files
gb28181/internal/web/api/gb28181.go
2025-06-14 01:20:46 +08:00

336 lines
10 KiB
Go
Executable File

// 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 := 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
}
// >>> 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)
}