低空智控平台 后端go
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

213 lines
5.8 KiB

package handler
import (
"crypto/subtle"
"encoding/json"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"laic-backend/common"
"laic-backend/service"
"laic-backend/vo"
)
// GetLivePage 直播会话分页列表
func GetLivePage(c *gin.Context) {
var req vo.LivePageReq
_ = c.ShouldBindQuery(&req)
if req.PageNum <= 0 {
req.PageNum = 1
}
if req.PageSize <= 0 {
req.PageSize = 10
}
page, busiErr := service.DefaultLiveService.GetPage(common.GetUserId(c), common.IsAdmin(c), &req)
if busiErr != nil {
common.FailWithBusiError(c, busiErr)
return
}
common.OKWithData(c, page)
}
// CreateLiveSession 创建或加入机巢的唯一活动直播会话,并创建观看租约。
func CreateLiveSession(c *gin.Context) {
dockID := c.Param("dockId")
if dockID == "" {
common.FailWithBusiError(c, common.ErrParam)
return
}
var req vo.LiveSessionCreateReq
_ = c.ShouldBindJSON(&req)
result, busiErr := service.DefaultLiveService.Join(common.GetUserId(c), common.IsAdmin(c), dockID, &req)
if busiErr != nil {
common.FailWithBusiError(c, busiErr)
return
}
common.OKWithData(c, result)
}
// GetLiveSession 获取指定直播会话。
func GetLiveSession(c *gin.Context) {
dockID, sessionID := c.Param("dockId"), c.Param("streamSessionId")
if dockID == "" || sessionID == "" {
common.FailWithBusiError(c, common.ErrParam)
return
}
session, busiErr := service.DefaultLiveService.GetSession(common.GetUserId(c), common.IsAdmin(c), dockID, sessionID)
if busiErr != nil {
common.FailWithBusiError(c, busiErr)
return
}
common.OKWithData(c, session)
}
// HeartbeatLiveSession 续期当前用户的观看租约。
func HeartbeatLiveSession(c *gin.Context) {
dockID, sessionID := c.Param("dockId"), c.Param("streamSessionId")
if dockID == "" || sessionID == "" {
common.FailWithBusiError(c, common.ErrParam)
return
}
result, busiErr := service.DefaultLiveService.Heartbeat(common.GetUserId(c), common.IsAdmin(c), dockID, sessionID)
if busiErr != nil {
common.FailWithBusiError(c, busiErr)
return
}
common.OKWithData(c, result)
}
// LeaveLiveSession 释放当前用户的观看租约。
func LeaveLiveSession(c *gin.Context) {
dockID, sessionID := c.Param("dockId"), c.Param("streamSessionId")
if dockID == "" || sessionID == "" {
common.FailWithBusiError(c, common.ErrParam)
return
}
if busiErr := service.DefaultLiveService.Leave(common.GetUserId(c), common.IsAdmin(c), dockID, sessionID); busiErr != nil {
common.FailWithBusiError(c, busiErr)
return
}
common.OK(c)
}
// StartLive 启动直播推流
func StartLive(c *gin.Context) {
dockID := c.Param("dockId")
if dockID == "" {
common.FailWithBusiError(c, common.ErrParam)
return
}
var req vo.LiveStartReq
_ = c.ShouldBindJSON(&req)
session, busiErr := service.DefaultLiveService.Start(common.GetUserId(c), common.IsAdmin(c), dockID, &req)
if busiErr != nil {
common.FailWithBusiError(c, busiErr)
return
}
common.OKWithData(c, session)
}
func StopLive(c *gin.Context) {
dockID := c.Param("dockId")
if dockID == "" {
common.FailWithBusiError(c, common.ErrParam)
return
}
if busiErr := service.DefaultLiveService.Stop(common.GetUserId(c), common.IsAdmin(c), dockID); busiErr != nil {
common.FailWithBusiError(c, busiErr)
return
}
common.OK(c)
}
// GetLivePlayURL 获取直播播放地址
func GetLivePlayURL(c *gin.Context) {
dockID, sessionID := c.Param("dockId"), c.Query("streamSessionId")
if dockID == "" || sessionID == "" {
common.FailWithBusiError(c, common.ErrParam)
return
}
play, busiErr := service.DefaultLiveService.GetPlayURL(common.GetUserId(c), common.IsAdmin(c), dockID, sessionID)
if busiErr != nil {
common.FailWithBusiError(c, busiErr)
return
}
common.OKWithData(c, play)
}
const maxAliyunCallbackBodyBytes = 64 * 1024
type aliyunLiveCallback struct {
EventType string `json:"eventType"`
AppName string `json:"appName"`
StreamName string `json:"streamName"`
EventTime int64 `json:"eventTime"`
}
func HandleAliyunLiveCallback(c *gin.Context) {
token := common.AppConf.Live.CallbackAuthToken
provided := strings.TrimPrefix(c.GetHeader("Authorization"), "Bearer ")
if token == "" || subtle.ConstantTimeCompare([]byte(provided), []byte(token)) != 1 {
c.Status(http.StatusUnauthorized)
return
}
body, err := io.ReadAll(http.MaxBytesReader(c.Writer, c.Request.Body, maxAliyunCallbackBodyBytes))
if err != nil {
c.Status(http.StatusRequestEntityTooLarge)
return
}
callback, err := parseAliyunLiveCallback(c.GetHeader("Content-Type"), body)
if err != nil || callback.EventType == "" || callback.AppName == "" || callback.StreamName == "" {
c.Status(http.StatusBadRequest)
return
}
if callback.EventTime > 0 && abs(time.Now().Unix()-normalizeAliyunCallbackTime(callback.EventTime)) > 300 {
c.Status(http.StatusUnauthorized)
return
}
if err := service.DefaultLiveService.HandleAliyunCallback(callback.EventType, callback.AppName, callback.StreamName, body); err != nil {
c.Status(http.StatusInternalServerError)
return
}
c.Status(http.StatusNoContent)
}
func parseAliyunLiveCallback(contentType string, body []byte) (aliyunLiveCallback, error) {
var callback aliyunLiveCallback
if strings.HasPrefix(contentType, "application/json") {
err := json.Unmarshal(body, &callback)
return callback, err
}
values, err := url.ParseQuery(string(body))
if err != nil {
return callback, err
}
callback.EventType = values.Get("eventType")
callback.AppName = values.Get("appName")
callback.StreamName = values.Get("streamName")
if value := values.Get("eventTime"); value != "" {
callback.EventTime, err = strconv.ParseInt(value, 10, 64)
}
return callback, err
}
func normalizeAliyunCallbackTime(value int64) int64 {
if value > 1_000_000_000_000 {
return value / 1000
}
return value
}
func abs(value int64) int64 {
if value < 0 {
return -value
}
return value
}