2025-01-11 22:34:36 +08:00
|
|
|
|
package handler
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"VideoStream/proto"
|
2025-01-12 15:18:58 +08:00
|
|
|
|
"VideoStream/service"
|
2025-01-14 15:00:40 +08:00
|
|
|
|
"VideoStream/worker"
|
2025-01-11 22:34:36 +08:00
|
|
|
|
"fmt"
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
2025-01-14 15:00:40 +08:00
|
|
|
|
"github.com/google/uuid"
|
|
|
|
|
|
"github.com/gorilla/websocket"
|
2025-01-11 22:34:36 +08:00
|
|
|
|
"io"
|
2025-01-15 23:31:50 +08:00
|
|
|
|
"log"
|
2025-01-14 15:00:40 +08:00
|
|
|
|
"net/http"
|
|
|
|
|
|
"strconv"
|
2025-01-11 22:34:36 +08:00
|
|
|
|
"time"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-01-14 15:00:40 +08:00
|
|
|
|
var (
|
|
|
|
|
|
upgrader = websocket.Upgrader{
|
|
|
|
|
|
ReadBufferSize: 1024,
|
|
|
|
|
|
WriteBufferSize: 1024,
|
|
|
|
|
|
CheckOrigin: func(r *http.Request) bool {
|
|
|
|
|
|
// 允许所有来源的连接
|
|
|
|
|
|
return true
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-01-11 22:34:36 +08:00
|
|
|
|
func SetUpToolGroup(router *gin.Engine) {
|
|
|
|
|
|
toolGroup := router.Group("/tool")
|
|
|
|
|
|
toolGroup.GET("/video_stream", GetVideoStream)
|
2025-01-14 15:00:40 +08:00
|
|
|
|
toolGroup.GET("/video_real_time", GetRealTimeImage)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 跨域访问:cross origin resource share
|
2025-01-18 20:43:28 +08:00
|
|
|
|
func CorsHandler() gin.HandlerFunc {
|
2025-01-14 15:00:40 +08:00
|
|
|
|
return func(context *gin.Context) {
|
|
|
|
|
|
//method := context.Request.Method
|
|
|
|
|
|
context.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
|
|
|
context.Header("Access-Control-Allow-Origin", "*") // 设置允许访问所有域
|
|
|
|
|
|
context.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")
|
|
|
|
|
|
context.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma,token,openid,opentoken")
|
|
|
|
|
|
context.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar")
|
|
|
|
|
|
context.Header("Access-Control-Max-Age", "172800")
|
|
|
|
|
|
context.Header("Access-Control-Allow-Credentials", "false")
|
|
|
|
|
|
context.Set("content-type", "application/json") //设置返回格式是json
|
|
|
|
|
|
|
|
|
|
|
|
// if method == "OPTIONS" {
|
|
|
|
|
|
// context.JSON(http.StatusOK, gin.H{
|
|
|
|
|
|
// "code":1,
|
|
|
|
|
|
// "message":"error",
|
|
|
|
|
|
// "data":"request error",
|
|
|
|
|
|
// })
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
//处理请求
|
|
|
|
|
|
context.Next()
|
|
|
|
|
|
}
|
2025-01-11 22:34:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type videoStreamReq struct {
|
|
|
|
|
|
ID int `json:"id" form:"id"`
|
|
|
|
|
|
Key string `json:"key" form:"key"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func GetVideoStream(c *gin.Context) {
|
2025-01-14 15:11:45 +08:00
|
|
|
|
id, _ := c.Get("id")
|
|
|
|
|
|
id1 := id.(int)
|
2025-01-18 20:38:35 +08:00
|
|
|
|
deviceIDSTR := c.Query("id")
|
|
|
|
|
|
deviceID, err := strconv.Atoi(deviceIDSTR)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
c.JSON(400, gin.H{"error": "device_id error"})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
key := c.Query("key")
|
2025-01-14 15:11:45 +08:00
|
|
|
|
//校验权限
|
2025-01-18 20:38:35 +08:00
|
|
|
|
device := service.GetDevice(deviceID, id1)
|
2025-01-14 15:11:45 +08:00
|
|
|
|
if device.ID == 0 {
|
|
|
|
|
|
c.JSON(400, gin.H{"error": "device not exist"})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-01-18 20:38:35 +08:00
|
|
|
|
rKey := worker.GetRedis("video_stream_get_stream_key")
|
|
|
|
|
|
if rKey == "" {
|
|
|
|
|
|
rKey = "123456"
|
|
|
|
|
|
}
|
2025-01-14 15:11:45 +08:00
|
|
|
|
|
2025-01-18 20:38:35 +08:00
|
|
|
|
//查看id是否存在
|
|
|
|
|
|
index := -1
|
|
|
|
|
|
for _, device_ := range proto.Config.DeviceInfo {
|
|
|
|
|
|
if device_.ID == deviceID {
|
|
|
|
|
|
index = deviceID
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if index == -1 {
|
|
|
|
|
|
c.JSON(400, gin.H{"error": "id config not exist"})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
//查看key是否正确
|
|
|
|
|
|
if key != rKey {
|
|
|
|
|
|
c.JSON(400, gin.H{"error": "key error"})
|
2025-01-11 22:34:36 +08:00
|
|
|
|
return
|
2025-01-18 20:38:35 +08:00
|
|
|
|
}
|
2025-01-19 15:44:18 +08:00
|
|
|
|
|
|
|
|
|
|
//查看设备是否在获取
|
|
|
|
|
|
isGetting := worker.GetRedis(fmt.Sprintf("device_%d_is_getting", deviceID))
|
|
|
|
|
|
if isGetting != "true" {
|
|
|
|
|
|
c.JSON(400, gin.H{"error": "device is not getting or not exist"})
|
|
|
|
|
|
log.Printf("stream device_id:%d is not getting or not exist", deviceID)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-01-18 20:38:35 +08:00
|
|
|
|
//设备流
|
|
|
|
|
|
c.Stream(func(w io.Writer) bool {
|
|
|
|
|
|
var count int
|
2025-01-18 20:50:33 +08:00
|
|
|
|
errCount := 0
|
2025-01-18 20:38:35 +08:00
|
|
|
|
for {
|
2025-01-18 20:50:33 +08:00
|
|
|
|
if errCount > 10 {
|
|
|
|
|
|
log.Printf("stream device:%d errCount > 10", deviceID)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
2025-01-23 22:49:09 +08:00
|
|
|
|
buf, cnt := service.GetDeviceCurrentFrameV4(deviceID, count)
|
2025-01-19 15:13:51 +08:00
|
|
|
|
if cnt == count || cnt == -1 {
|
2025-01-18 20:38:35 +08:00
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
2025-01-22 15:09:15 +08:00
|
|
|
|
log.Printf("stream device:%d,cnt =%d,count=%d,errCount=%d", deviceID, cnt, count, errCount)
|
2025-01-18 20:50:33 +08:00
|
|
|
|
errCount++
|
2025-01-18 20:38:35 +08:00
|
|
|
|
continue
|
2025-01-11 22:34:36 +08:00
|
|
|
|
}
|
2025-01-23 22:49:09 +08:00
|
|
|
|
count = cnt
|
2025-01-18 20:38:35 +08:00
|
|
|
|
_, err = w.Write([]byte("--frame\r\nContent-Type: image/jpeg\r\n\r\n"))
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
fmt.Printf("写入头部信息错误: %v\n", err)
|
2025-01-23 22:50:45 +08:00
|
|
|
|
buf = nil
|
2025-01-18 20:38:35 +08:00
|
|
|
|
return false
|
2025-01-11 22:34:36 +08:00
|
|
|
|
}
|
2025-01-23 22:49:09 +08:00
|
|
|
|
_, err = w.Write(buf)
|
2025-01-18 20:38:35 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
fmt.Printf("写入帧数据错误: %v\n", err)
|
2025-01-23 22:50:45 +08:00
|
|
|
|
buf = nil
|
2025-01-18 20:38:35 +08:00
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
_, err = w.Write([]byte("\r\n"))
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
fmt.Printf("写入帧结束标记错误: %v\n", err)
|
2025-01-23 22:50:45 +08:00
|
|
|
|
buf = nil
|
2025-01-18 20:38:35 +08:00
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
time.Sleep(50 * time.Millisecond) // 控制帧率,模拟每秒约20帧,可按实际调整
|
2025-01-23 22:49:09 +08:00
|
|
|
|
buf = nil
|
2025-01-18 20:38:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-01-11 22:34:36 +08:00
|
|
|
|
}
|
2025-01-14 15:00:40 +08:00
|
|
|
|
|
|
|
|
|
|
// 发送实时视频流
|
|
|
|
|
|
func GetRealTimeImage(c *gin.Context) {
|
|
|
|
|
|
id, _ := c.Get("id")
|
|
|
|
|
|
id1 := id.(int)
|
2025-01-18 19:59:32 +08:00
|
|
|
|
deviceId := c.Query("device_id")
|
|
|
|
|
|
deviceIdInt, _ := strconv.Atoi(deviceId)
|
|
|
|
|
|
device := service.GetDevice(deviceIdInt, id1)
|
2025-01-14 15:00:40 +08:00
|
|
|
|
if device.ID == 0 {
|
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{"code": 4, "message": "device not found"})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-01-18 19:59:32 +08:00
|
|
|
|
//查看设备是否在获取
|
2025-01-18 20:21:38 +08:00
|
|
|
|
isGetting := worker.GetRedis(fmt.Sprintf("device_%d_is_getting", device.ID))
|
|
|
|
|
|
if isGetting != "true" {
|
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{"code": 4, "message": "device is not getting or not exist"})
|
|
|
|
|
|
log.Printf("device_id:%d is not getting or not exist", deviceIdInt)
|
2025-01-18 19:59:32 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-01-18 20:21:38 +08:00
|
|
|
|
|
2025-01-14 15:00:40 +08:00
|
|
|
|
ws, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
|
|
|
|
|
if err != nil {
|
2025-01-15 23:41:01 +08:00
|
|
|
|
log.Printf("connect wss err:%v", err)
|
2025-01-14 15:00:40 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
worker.SetRedisWithExpire(strconv.Itoa(int(device.ID))+"_is_play", "1", time.Minute*5)
|
2025-01-19 15:44:18 +08:00
|
|
|
|
isGetting = worker.GetRedis(fmt.Sprintf("device_%d_is_getting", device.ID))
|
|
|
|
|
|
if isGetting != "true" {
|
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{"code": 5, "message": "device is not getting or not exist"})
|
|
|
|
|
|
log.Printf("device_id:%d is not getting or not exist", deviceIdInt)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-01-22 14:15:35 +08:00
|
|
|
|
subscribeAndHandleMessagesV3(ws, deviceIdInt)
|
2025-01-14 15:00:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-01-22 14:46:23 +08:00
|
|
|
|
func subscribeAndHandleMessagesV3(ws *websocket.Conn, deviceId int) {
|
2025-01-14 15:00:40 +08:00
|
|
|
|
// 生成唯一连接 uuid
|
|
|
|
|
|
con_id := uuid.New().String()
|
2025-01-22 14:46:23 +08:00
|
|
|
|
online_conn_key := "device_" + strconv.Itoa(deviceId) + "_online_conn_ids"
|
2025-01-14 15:00:40 +08:00
|
|
|
|
// 加入设备在线连接集合
|
|
|
|
|
|
worker.SetRedisSetAddWithExpire(online_conn_key, con_id, time.Minute*5)
|
|
|
|
|
|
//图片计数器
|
|
|
|
|
|
count := 0
|
|
|
|
|
|
//定时器,发送计数器
|
2025-01-23 22:31:04 +08:00
|
|
|
|
tCount := 0
|
2025-01-20 18:52:50 +08:00
|
|
|
|
//计算帧率
|
2025-01-14 15:00:40 +08:00
|
|
|
|
for {
|
|
|
|
|
|
//从service获取当前帧
|
2025-01-23 22:31:04 +08:00
|
|
|
|
buf, c := service.GetDeviceCurrentFrameV4(deviceId, count)
|
2025-01-14 15:00:40 +08:00
|
|
|
|
if c != count {
|
2025-01-23 22:31:04 +08:00
|
|
|
|
count = c
|
|
|
|
|
|
err2 := ws.WriteMessage(websocket.BinaryMessage, buf)
|
|
|
|
|
|
if err2 != nil {
|
|
|
|
|
|
log.Printf("send message to client err:%v", err2)
|
2025-01-19 15:13:51 +08:00
|
|
|
|
worker.SetRedisSetRemove(online_conn_key, con_id)
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
2025-01-23 22:31:04 +08:00
|
|
|
|
c = count
|
2025-01-14 15:00:40 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
//每秒发送一次心跳检测
|
2025-01-23 22:31:04 +08:00
|
|
|
|
if tCount%10 == 0 {
|
2025-01-14 15:00:40 +08:00
|
|
|
|
err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(time.Second))
|
|
|
|
|
|
if err != nil {
|
2025-01-15 23:41:01 +08:00
|
|
|
|
log.Printf("Connection check failed:%v", err)
|
2025-01-14 15:00:40 +08:00
|
|
|
|
worker.SetRedisSetRemove(online_conn_key, con_id)
|
2025-01-18 15:32:10 +08:00
|
|
|
|
break
|
|
|
|
|
|
} else {
|
|
|
|
|
|
log.Printf("Connection check success")
|
2025-01-14 15:00:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-01-23 22:43:20 +08:00
|
|
|
|
buf = nil
|
2025-01-14 15:00:40 +08:00
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
2025-01-23 22:31:04 +08:00
|
|
|
|
tCount++
|
2025-01-14 15:00:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查看是否还有其他连接,没有则设置 is_play 为 0
|
|
|
|
|
|
if worker.IsContainKey(online_conn_key) == false {
|
2025-01-22 14:46:23 +08:00
|
|
|
|
worker.SetRedisWithExpire(strconv.Itoa(deviceId)+"_is_play", "1", time.Minute*5)
|
|
|
|
|
|
log.Printf("device_id: %d has set is_play to 0", deviceId)
|
2025-01-14 15:00:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|