live media info api

This commit is contained in:
madao
2025-02-07 00:10:22 +08:00
parent 3aefddd1e2
commit a9f0ab7b7a
11 changed files with 315 additions and 25 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,13 +1,14 @@
package flvadmin
import (
"errors"
"io"
"sync"
"time"
"github.com/deepch/vdk/av"
"github.com/hkmadao/rtsp2rtmp/src/rtsp2rtmp/flvadmin/httpflvmanage"
"github.com/hkmadao/rtsp2rtmp/src/rtsp2rtmp/web/common"
"github.com/hkmadao/rtsp2rtmp/src/rtsp2rtmp/web/dto/vo/ext/live"
)
var hfas *HttpFlvAdmin
@@ -31,6 +32,10 @@ func (hfa *HttpFlvAdmin) AddHttpFlvManager(
) {
hfm := httpflvmanage.NewHttpFlvManager(pktStream, code, codecs)
hfa.hfms.Store(code, hfm)
go func() {
<-hfm.GetDone()
hfa.hfms.Delete(code)
}()
}
func (hfa *HttpFlvAdmin) StopWrite(code string) {
@@ -50,22 +55,26 @@ func (hfa *HttpFlvAdmin) StartWrite(code string) {
}
}
//添加播放者
// 添加播放者
func (hfa *HttpFlvAdmin) AddHttpFlvPlayer(
playerDone <-chan int,
pulseInterval time.Duration,
code string,
writer io.Writer,
) (<-chan int, error) {
) (<-chan int, *common.Rtmp2FlvCustomError) {
v, b := hfa.hfms.Load(code)
if b {
hfm := v.(*httpflvmanage.HttpFlvManager)
return hfm.AddHttpFlvPlayer(playerDone, pulseInterval, writer)
flvPlayerDone, err := hfm.AddHttpFlvPlayer(playerDone, pulseInterval, writer)
if err != nil {
return flvPlayerDone, common.InternalError(err)
}
return flvPlayerDone, nil
}
return nil, errors.New("camera no connection")
return nil, common.CustomError("camera no connection")
}
//更新sps、pps等信息
// 更新sps、pps等信息
func (hfa *HttpFlvAdmin) UpdateCodecs(code string, codecs []av.CodecData) {
rfw, ok := hfa.hfms.Load(code)
if ok {
@@ -73,3 +82,28 @@ func (hfa *HttpFlvAdmin) UpdateCodecs(code string, codecs []av.CodecData) {
rfw.SetCodecs(codecs)
}
}
func (hfa *HttpFlvAdmin) GetLiveInfo(code string) (*live.LiveMediaInfo, error) {
liveMediaInfo := &live.LiveMediaInfo{
HasAudio: false,
OnlineStatus: false,
AnchorName: code,
}
rfw, ok := hfa.hfms.Load(code)
if ok {
rfw := rfw.(*httpflvmanage.HttpFlvManager)
liveMediaInfo.HasAudio = hasAudio(rfw.GetCodecs())
liveMediaInfo.OnlineStatus = true
}
return liveMediaInfo, nil
}
func hasAudio(streams []av.CodecData) bool {
for _, stream := range streams {
if stream.Type().IsAudio() {
return true
}
}
return false
}

View File

@@ -100,6 +100,8 @@ func commandRes(commandMessage tcpclientcommon.CommandMessage) {
startRtmpPush(commandMessage)
case "stopPushRtmp":
stopRtmpPush(commandMessage)
case "getLiveMediaInfo":
getLiveMediaInfo(commandMessage)
default:
logs.Error("unsupport commandType: %s", commandMessage.MessageType)
}

View File

@@ -0,0 +1,82 @@
package tcpclient
import (
"encoding/json"
"fmt"
"github.com/beego/beego/v2/core/logs"
"github.com/hkmadao/rtsp2rtmp/src/rtsp2rtmp/flvadmin"
"github.com/hkmadao/rtsp2rtmp/src/rtsp2rtmp/tcpclient/tcpclientcommon"
"github.com/hkmadao/rtsp2rtmp/src/rtsp2rtmp/web/common"
"github.com/hkmadao/rtsp2rtmp/src/rtsp2rtmp/web/dto/vo/ext/live"
base_service "github.com/hkmadao/rtsp2rtmp/src/rtsp2rtmp/web/service/base"
)
func getLiveMediaInfo(commandMessage tcpclientcommon.CommandMessage) {
conn, err := tcpclientcommon.ConnectAndResRegister("getLiveMediaInfo", commandMessage.MessageId)
if err != nil {
logs.Error("getLiveMediaInfo connect to server error: %v", err)
return
}
defer conn.Close()
paramStr := commandMessage.Param
rtmpPushParam := RtmpPushParam{}
err = json.Unmarshal([]byte(paramStr), &rtmpPushParam)
if err != nil {
logs.Error("getLiveMediaInfo message format error: %v", err)
result := common.ErrorResult(fmt.Sprintf("getLiveMediaInfo message format error: %v", err))
_, err = tcpclientcommon.WriteResult(result, conn)
if err != nil {
logs.Error(err)
return
}
return
}
condition := common.GetEqualCondition("code", rtmpPushParam.CameraCode)
camera, err := base_service.CameraFindOneByCondition(condition)
if err != nil {
logs.Error("CameraFindOneByCondition error: %v", err)
result := common.ErrorResult("CameraFindOneByCondition error")
_, err = tcpclientcommon.WriteResult(result, conn)
if err != nil {
logs.Error(err)
return
}
return
}
if !camera.RtmpPushStatus {
liveMediaInfo := live.LiveMediaInfo{
HasAudio: false,
OnlineStatus: false,
AnchorName: camera.Code,
}
result := common.SuccessResultData(liveMediaInfo)
_, err = tcpclientcommon.WriteResult(result, conn)
if err != nil {
logs.Error(err)
return
}
}
liveMediaInfo, err := flvadmin.GetSingleHttpFlvAdmin().GetLiveInfo(camera.Code)
if err != nil {
logs.Error("getLiveInfo error : %v", err)
result := common.ErrorResult("getLiveInfo error")
_, err = tcpclientcommon.WriteResult(result, conn)
if err != nil {
logs.Error(err)
return
}
return
}
result := common.SuccessResultData(liveMediaInfo)
_, err = tcpclientcommon.WriteResult(result, conn)
if err != nil {
logs.Error(err)
return
}
}

View File

@@ -14,7 +14,7 @@ import (
)
type CommandMessage struct {
// "cameraAq" "historyVideoPage" "flvFileMediaInfo" "flvPlay" "flvFetchMoreData" "startPushRtmp" "stopPushRtmp"
// "cameraAq" "historyVideoPage" "flvFileMediaInfo" "flvPlay" "flvFetchMoreData" "startPushRtmp" "stopPushRtmp" "getLiveMediaInfo"
MessageType string `json:"messageType"`
Param string `json:"param"`
MessageId string `json:"messageId"`
@@ -25,7 +25,7 @@ type RegisterInfo struct {
ClientCode string `json:"clientCode"`
DateStr string `json:"dateStr"`
Sign string `json:"sign"`
// "keepChannel" "cameraOnline" "cameraOffline" "cameraAq" "historyVideoPage" "flvFileMediaInfo" "flvPlay" "flvFetchMoreData" "startPushRtmp" "stopPushRtmp"
// "keepChannel" "cameraOnline" "cameraOffline" "cameraAq" "historyVideoPage" "flvFileMediaInfo" "flvPlay" "flvFetchMoreData" "startPushRtmp" "stopPushRtmp" "getLiveMediaInfo"
ConnType string `json:"connType"`
MessageId string `json:"messageId"`
CameraCode string `json:"cameraCode"`

View File

@@ -1,5 +1,7 @@
package common
import "errors"
type AppResult struct {
Status uint32 `json:"status"`
Message string `json:"message"`
@@ -31,6 +33,10 @@ type DeleteRefErrorMessageVO struct {
RefClassName string `json:"refClassName"`
}
func (appResult *AppResult) IsFailed() bool {
return appResult.Status != 0
}
func ErrorResult(msg string) AppResult {
return AppResult{Status: 1, Message: msg}
}
@@ -46,3 +52,27 @@ func SuccessResultMsg(msg string) AppResult {
func SuccessResultWithMsg(msg string, data interface{}) AppResult {
return AppResult{Status: 0, Message: msg, Data: data}
}
type Rtmp2FlvCustomError struct {
kind int
err error
}
func CustomError(errMsg string) *Rtmp2FlvCustomError {
return &Rtmp2FlvCustomError{kind: 0, err: errors.New(errMsg)}
}
func InternalError(err error) *Rtmp2FlvCustomError {
if err == nil {
panic("err param is nil")
}
return &Rtmp2FlvCustomError{kind: 1, err: err}
}
func (ce *Rtmp2FlvCustomError) IsCustomError() bool {
return ce.kind == 0
}
func (ce *Rtmp2FlvCustomError) Error() string {
return ce.err.Error()
}

View File

@@ -14,7 +14,7 @@ import (
base_service "github.com/hkmadao/rtsp2rtmp/src/rtsp2rtmp/web/service/base"
)
func CameraRecordFileDuration(ctx *gin.Context) {
func CameraRecordFileMediaInfo(ctx *gin.Context) {
defer func() {
if result := recover(); result != nil {
logs.Error("system painc : %v \nstack : %v", result, string(debug.Stack()))

View File

@@ -13,6 +13,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/hkmadao/rtsp2rtmp/src/rtsp2rtmp/flvadmin"
"github.com/hkmadao/rtsp2rtmp/src/rtsp2rtmp/flvadmin/fileflvmanager/fileflvreader"
"github.com/hkmadao/rtsp2rtmp/src/rtsp2rtmp/utils"
"github.com/hkmadao/rtsp2rtmp/src/rtsp2rtmp/web/common"
"github.com/hkmadao/rtsp2rtmp/src/rtsp2rtmp/web/dto/vo/ext/flv_file"
base_service "github.com/hkmadao/rtsp2rtmp/src/rtsp2rtmp/web/service/base"
@@ -20,6 +21,106 @@ import (
var playerMap sync.Map
func HttpFlvPlayMediaInfo(ctx *gin.Context) {
defer func() {
if result := recover(); result != nil {
logs.Error("system painc : %v \nstack : %v", result, string(debug.Stack()))
}
}()
ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*")
ctx.Writer.Header().Set("Connection", "keep-alive")
method, ok := ctx.Params.Get("method")
if !ok || method == "" {
logs.Error("path param method is rquired")
result := common.ErrorResult("path param method is rquired")
ctx.JSON(http.StatusOK, result)
return
}
code, ok := ctx.Params.Get("code")
if !ok || code == "" {
logs.Error("path param code is rquired")
result := common.ErrorResult("path param code is rquired")
ctx.JSON(http.StatusOK, result)
return
}
authCode, ok := ctx.Params.Get("authCode.flv")
if !ok || authCode == "" {
logs.Error("path param authCode is rquired")
result := common.ErrorResult("path param authCode is rquired")
ctx.JSON(http.StatusOK, result)
return
}
authCode = utils.ReverseString(strings.Replace(utils.ReverseString(authCode), utils.ReverseString(".flv"), "", 1))
conditon := common.GetEqualCondition("code", code)
camera, err := base_service.CameraFindOneByCondition(conditon)
if err != nil {
logs.Error("camera query error : %v", err)
result := common.ErrorResult("internal error")
ctx.JSON(http.StatusOK, result)
return
}
if !(method == "temp" || method == "permanent") {
logs.Error("method error : %s", method)
result := common.ErrorResult("internal error")
ctx.JSON(http.StatusOK, result)
return
}
if method == "temp" {
var filters = []common.EqualFilter{{Name: "cameraId", Value: camera.Id}, {Name: "authCode", Value: authCode}}
condition := common.GetEqualConditions(filters)
exists, err := base_service.CameraShareExistsByCondition(condition)
if err != nil {
logs.Error("cameraShareExistsByCondition error : %v", err)
result := common.ErrorResult("internal error")
ctx.JSON(http.StatusOK, result)
return
}
if !exists {
logs.Error("camera [%s] AuthCodeTemp expired : %s", camera.Code, authCode)
result := common.ErrorResult("auth error")
ctx.JSON(http.StatusOK, result)
return
}
cs, err := base_service.CameraShareFindOneByCondition(condition)
if err != nil {
logs.Error("CameraShareSelectOne error : %v", err)
result := common.ErrorResult("internal error")
ctx.JSON(http.StatusOK, result)
return
}
if time.Now().Before(cs.StartTime) || time.Now().After(cs.Deadline) {
logs.Error("camera [%s] AuthCodeTemp expired : %s", camera.Code, authCode)
result := common.ErrorResult("auth error")
ctx.JSON(http.StatusOK, result)
return
}
}
if method == "permanent" && authCode != camera.PlayAuthCode {
logs.Error("AuthCodePermanent error : %s", authCode)
result := common.ErrorResult("auth error")
ctx.JSON(http.StatusBadRequest, result)
return
}
liveMediaInfo, err := flvadmin.GetSingleHttpFlvAdmin().GetLiveInfo(camera.Code)
if err != nil {
logs.Error("getLiveInfo error : %v", err)
result := common.ErrorResult("internal error")
ctx.JSON(http.StatusOK, result)
return
}
result := common.SuccessResultData(liveMediaInfo)
ctx.JSON(http.StatusOK, result)
}
func HttpFlvPlay(ctx *gin.Context) {
defer func() {
if result := recover(); result != nil {
@@ -28,15 +129,28 @@ func HttpFlvPlay(ctx *gin.Context) {
}()
ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*")
ctx.Writer.Header().Set("Connection", "keep-alive")
uri := strings.TrimSuffix(strings.TrimLeft(ctx.Request.RequestURI, "/"), ".flv")
uris := strings.Split(uri, "/")
if len(uris) < 3 || uris[0] != "live" {
http.Error(ctx.Writer, "invalid path", http.StatusBadRequest)
method, ok := ctx.Params.Get("method")
if !ok || method == "" {
logs.Error("path param method is rquired")
http.Error(ctx.Writer, "path param method is rquired", http.StatusBadRequest)
return
}
method := uris[1]
code := uris[2]
authCode := uris[3]
code, ok := ctx.Params.Get("code")
if !ok || code == "" {
logs.Error("path param code is rquired")
http.Error(ctx.Writer, "path param code is rquired", http.StatusBadRequest)
return
}
authCode, ok := ctx.Params.Get("authCode.flv")
if !ok || authCode == "" {
logs.Error("path param authCode is rquired")
http.Error(ctx.Writer, "path param authCode is rquired", http.StatusBadRequest)
return
}
authCode = utils.ReverseString(strings.Replace(utils.ReverseString(authCode), utils.ReverseString(".flv"), "", 1))
conditon := common.GetEqualCondition("code", code)
camera, err := base_service.CameraFindOneByCondition(conditon)
@@ -55,8 +169,22 @@ func HttpFlvPlay(ctx *gin.Context) {
if method == "temp" {
var filters = []common.EqualFilter{{Name: "cameraId", Value: camera.Id}, {Name: "authCode", Value: authCode}}
condition := common.GetEqualConditions(filters)
cs, err := base_service.CameraShareFindOneByCondition(condition)
exists, err := base_service.CameraShareExistsByCondition(condition)
if err != nil {
logs.Error("cameraShareExistsByCondition error : %v", err)
result := common.ErrorResult("internal error")
ctx.JSON(http.StatusBadRequest, result)
return
}
if !exists {
logs.Error("camera [%s] AuthCodeTemp expired : %s", camera.Code, authCode)
result := common.ErrorResult("auth error")
ctx.JSON(http.StatusBadRequest, result)
return
}
cs, err := base_service.CameraShareFindOneByCondition(condition)
if err != nil {
logs.Error("CameraShareSelectOne error : %v", err)
result := common.ErrorResult("internal error")
@@ -83,9 +211,15 @@ func HttpFlvPlay(ctx *gin.Context) {
playerDone := make(chan int)
defer close(playerDone)
const timeout = 10 * time.Second
flvPlayerDone, err := flvadmin.GetSingleHttpFlvAdmin().AddHttpFlvPlayer(playerDone, timeout/2, code, ctx.Writer)
if err != nil {
logs.Error("camera [%s] add player error : %s", code, err)
flvPlayerDone, addHttpFlvPlayerErr := flvadmin.GetSingleHttpFlvAdmin().AddHttpFlvPlayer(playerDone, timeout/2, code, ctx.Writer)
if addHttpFlvPlayerErr != nil {
logs.Error("camera [%s] add player error : %v", code, addHttpFlvPlayerErr)
if addHttpFlvPlayerErr.IsCustomError() {
result := common.ErrorResult(addHttpFlvPlayerErr.Error())
ctx.JSON(http.StatusBadRequest, result)
return
}
result := common.ErrorResult("internal error")
ctx.JSON(http.StatusBadRequest, result)
return
@@ -94,7 +228,7 @@ func HttpFlvPlay(ctx *gin.Context) {
logs.Info("player [%s] addr [%s] exit", code, ctx.Request.RemoteAddr)
}
func HttpFlvVODFileDuration(ctx *gin.Context) {
func HttpFlvVODFileMediaInfo(ctx *gin.Context) {
defer func() {
if result := recover(); result != nil {
logs.Error("system painc : %v \nstack : %v", result, string(debug.Stack()))

View File

@@ -0,0 +1,7 @@
package live
type LiveMediaInfo struct {
HasAudio bool `json:"hasAudio"`
OnlineStatus bool `json:"onlineStatus"`
AnchorName string `json:"anchorName"`
}

View File

@@ -42,8 +42,9 @@ func (w *web) webRun() {
router.POST("/login", ext_controller.Login)
router.POST("/logout", ext_controller.Logout)
router.GET("/live/getMediaInfo/:method/:code/:authCode.flv", ext_controller.HttpFlvPlayMediaInfo)
router.GET("/live/:method/:code/:authCode.flv", ext_controller.HttpFlvPlay)
router.GET("/vod/getDuration/:fileName", ext_controller.HttpFlvVODFileDuration)
router.GET("/vod/getMediaInfo/:fileName", ext_controller.HttpFlvVODFileMediaInfo)
router.GET("/vod/start/:fileName", ext_controller.HttpFlvVODStart)
router.GET("/vod/fetch", ext_controller.HttpFlvVODFetch)
router.GET("/vod/getFileList", ext_controller.CameraGetRecordFiles)
@@ -99,7 +100,7 @@ func (w *web) webRun() {
router.GET("/cameraRecord/getByIds", base_controller.CameraRecordGetByIds)
router.POST("/cameraRecord/aq", base_controller.CameraRecordAq)
router.POST("/cameraRecord/aqPage", base_controller.CameraRecordAqPage)
router.GET("/cameraRecord/getDuration/:idCameraRecord", ext_controller.CameraRecordFileDuration)
router.GET("/cameraRecord/getMediaInfo/:idCameraRecord", ext_controller.CameraRecordFileMediaInfo)
router.GET("/cameraRecord/start/:idCameraRecord", ext_controller.CameraRecordFilePlay)
router.GET("/cameraRecord/fetch", ext_controller.CameraRecordFileFetch)