stream.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. package system
  2. import (
  3. "fmt"
  4. "github.com/gin-gonic/gin"
  5. "go.uber.org/zap"
  6. "net/http"
  7. "os"
  8. "path/filepath"
  9. "server/global"
  10. "server/model/common/response"
  11. "server/service/stream"
  12. )
  13. // StreamApi API接口结构体
  14. type StreamApi struct{}
  15. var (
  16. StreamService = stream.NewStreamService()
  17. // HLS文件存储的相对路径(跨平台兼容)
  18. hlsBaseDir = filepath.Join("..\\", "server", "hls")
  19. )
  20. // StartStream 启动RTSP转HLS流
  21. // @Tags Stream
  22. // @Summary 启动RTSP转HLS流
  23. // @Security ApiKeyAuth
  24. // @accept application/json
  25. // @Produce application/json
  26. // @Param data body StartStreamRequest true "RTSP地址和流ID"
  27. // @Success 200 {object} response.Response{data=string} "返回HLS流地址"
  28. // @Router /stream/start [post]
  29. func (s *StreamApi) StartStream(c *gin.Context) {
  30. var req StartStreamRequest
  31. if err := c.ShouldBindJSON(&req); err != nil {
  32. global.GVA_LOG.Error("参数验证失败", zap.Error(err))
  33. response.FailWithMessage("参数错误: "+err.Error(), c)
  34. return
  35. }
  36. hlsUrl, err := StreamService.StartStream(req.RTSPUrl, req.StreamID)
  37. if err != nil {
  38. global.GVA_LOG.Error("启动流转换失败", zap.Error(err))
  39. response.FailWithMessage(err.Error(), c)
  40. return
  41. }
  42. response.OkWithData(map[string]string{
  43. "hlsUrl": hlsUrl,
  44. }, c)
  45. }
  46. // StopStream 停止流转换
  47. // @Tags Stream
  48. // @Summary 停止流转换
  49. // @Security ApiKeyAuth
  50. // @accept application/json
  51. // @Produce application/json
  52. // @Param data body StopStreamRequest true "流ID"
  53. // @Success 200 {object} response.Response
  54. // @Router /stream/stop [post]
  55. func (s *StreamApi) StopStream(c *gin.Context) {
  56. var req StopStreamRequest
  57. if err := c.ShouldBindJSON(&req); err != nil {
  58. global.GVA_LOG.Error("参数验证失败", zap.Error(err))
  59. response.FailWithMessage("参数错误: "+err.Error(), c)
  60. return
  61. }
  62. if err := StreamService.StopStream(req.StreamID); err != nil {
  63. global.GVA_LOG.Error("停止流转换失败", zap.Error(err))
  64. response.FailWithMessage(err.Error(), c)
  65. return
  66. }
  67. response.OkWithMessage("流已成功停止", c)
  68. }
  69. // PlayHLS 播放HLS流
  70. // @Tags Stream
  71. // @Summary 播放HLS流
  72. // @accept application/json
  73. // @Produce application/x-mpegURL
  74. // @Param streamId path string true "流ID"
  75. // @Param any path string true "m3u8或ts文件名"
  76. // @Success 200 {file} file "返回视频流文件"
  77. // @Router /stream/hls/{streamId}/{any} [get]
  78. func (s *StreamApi) PlayHLS(c *gin.Context) {
  79. streamId := c.Param("streamId")
  80. filePath := c.Param("any")
  81. // 构建完整的文件路径(跨平台兼容)
  82. fullPath := filepath.Join(hlsBaseDir, streamId, filePath)
  83. fmt.Println("fullPath:", fullPath)
  84. // 检查文件是否存在
  85. if _, err := os.Stat(fullPath); err != nil {
  86. global.GVA_LOG.Error("HLS文件不存在", zap.String("path", fullPath), zap.Error(err))
  87. c.JSON(http.StatusNotFound, gin.H{
  88. "code": 404,
  89. "msg": "HLS文件不存在: " + fullPath,
  90. })
  91. return
  92. }
  93. // 设置正确的Content-Type
  94. switch filepath.Ext(fullPath) {
  95. case ".m3u8":
  96. c.Header("Content-Type", "application/x-mpegURL")
  97. case ".ts":
  98. c.Header("Content-Type", "video/MP2T")
  99. default:
  100. c.Header("Content-Type", "application/octet-stream")
  101. }
  102. // 禁用缓存,支持断点续传
  103. c.Header("Cache-Control", "no-cache")
  104. c.Header("Accept-Ranges", "bytes")
  105. // 发送文件内容
  106. c.File(fullPath)
  107. }
  108. // GetStreamList 获取活跃流列表
  109. // @Tags Stream
  110. // @Summary 获取活跃流列表
  111. // @Security ApiKeyAuth
  112. // @accept application/json
  113. // @Produce application/json
  114. // @Success 200 {object} response.Response{data=[]string} "返回活跃流ID列表"
  115. // @Router /stream/list [get]
  116. func (s *StreamApi) GetStreamList(c *gin.Context) {
  117. list := StreamService.GetActiveStreams()
  118. response.OkWithData(list, c)
  119. }
  120. // 请求结构体定义
  121. type StartStreamRequest struct {
  122. RTSPUrl string `json:"rtspUrl" binding:"required"` // RTSP流地址
  123. StreamID string `json:"streamId" binding:"required"` // 流唯一标识
  124. }
  125. type StopStreamRequest struct {
  126. StreamID string `json:"streamId" binding:"required"` // 流唯一标识
  127. }