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
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
|
|
}
|
|
|