package system import ( "fmt" "github.com/gin-gonic/gin" "go.uber.org/zap" "net/http" "os" "path/filepath" "server/global" "server/model/common/response" "server/service/stream" ) // StreamApi API接口结构体 type StreamApi struct{} var ( StreamService = stream.NewStreamService() // HLS文件存储的相对路径(跨平台兼容) hlsBaseDir = filepath.Join("..\\", "server", "hls") ) // StartStream 启动RTSP转HLS流 // @Tags Stream // @Summary 启动RTSP转HLS流 // @Security ApiKeyAuth // @accept application/json // @Produce application/json // @Param data body StartStreamRequest true "RTSP地址和流ID" // @Success 200 {object} response.Response{data=string} "返回HLS流地址" // @Router /stream/start [post] func (s *StreamApi) StartStream(c *gin.Context) { var req StartStreamRequest if err := c.ShouldBindJSON(&req); err != nil { global.GVA_LOG.Error("参数验证失败", zap.Error(err)) response.FailWithMessage("参数错误: "+err.Error(), c) return } hlsUrl, err := StreamService.StartStream(req.RTSPUrl, req.StreamID) if err != nil { global.GVA_LOG.Error("启动流转换失败", zap.Error(err)) response.FailWithMessage(err.Error(), c) return } response.OkWithData(map[string]string{ "hlsUrl": hlsUrl, }, c) } // StopStream 停止流转换 // @Tags Stream // @Summary 停止流转换 // @Security ApiKeyAuth // @accept application/json // @Produce application/json // @Param data body StopStreamRequest true "流ID" // @Success 200 {object} response.Response // @Router /stream/stop [post] func (s *StreamApi) StopStream(c *gin.Context) { var req StopStreamRequest if err := c.ShouldBindJSON(&req); err != nil { global.GVA_LOG.Error("参数验证失败", zap.Error(err)) response.FailWithMessage("参数错误: "+err.Error(), c) return } if err := StreamService.StopStream(req.StreamID); err != nil { global.GVA_LOG.Error("停止流转换失败", zap.Error(err)) response.FailWithMessage(err.Error(), c) return } response.OkWithMessage("流已成功停止", c) } // PlayHLS 播放HLS流 // @Tags Stream // @Summary 播放HLS流 // @accept application/json // @Produce application/x-mpegURL // @Param streamId path string true "流ID" // @Param any path string true "m3u8或ts文件名" // @Success 200 {file} file "返回视频流文件" // @Router /stream/hls/{streamId}/{any} [get] func (s *StreamApi) PlayHLS(c *gin.Context) { streamId := c.Param("streamId") filePath := c.Param("any") // 构建完整的文件路径(跨平台兼容) fullPath := filepath.Join(hlsBaseDir, streamId, filePath) fmt.Println("fullPath:", fullPath) // 检查文件是否存在 if _, err := os.Stat(fullPath); err != nil { global.GVA_LOG.Error("HLS文件不存在", zap.String("path", fullPath), zap.Error(err)) c.JSON(http.StatusNotFound, gin.H{ "code": 404, "msg": "HLS文件不存在: " + fullPath, }) return } // 设置正确的Content-Type switch filepath.Ext(fullPath) { case ".m3u8": c.Header("Content-Type", "application/x-mpegURL") case ".ts": c.Header("Content-Type", "video/MP2T") default: c.Header("Content-Type", "application/octet-stream") } // 禁用缓存,支持断点续传 c.Header("Cache-Control", "no-cache") c.Header("Accept-Ranges", "bytes") // 发送文件内容 c.File(fullPath) } // GetStreamList 获取活跃流列表 // @Tags Stream // @Summary 获取活跃流列表 // @Security ApiKeyAuth // @accept application/json // @Produce application/json // @Success 200 {object} response.Response{data=[]string} "返回活跃流ID列表" // @Router /stream/list [get] func (s *StreamApi) GetStreamList(c *gin.Context) { list := StreamService.GetActiveStreams() response.OkWithData(list, c) } // 请求结构体定义 type StartStreamRequest struct { RTSPUrl string `json:"rtspUrl" binding:"required"` // RTSP流地址 StreamID string `json:"streamId" binding:"required"` // 流唯一标识 } type StopStreamRequest struct { StreamID string `json:"streamId" binding:"required"` // 流唯一标识 }