如何在RTSP协议网页无插件直播流媒体视频平台EasyNVR内做到自定义断流?

我们在对EasyDSS做功能测试的时候,讲过在EasyDSS演示平台上,为了节省资源占用设置的自动停播问题。基于EasyDSS的成功经验,我们在EasyNVR的官网也做了同样一套机制。

如何在RTSP协议网页无插件直播流媒体视频平台EasyNVR内做到自定义断流?

分析问题

在EasyNVR的演示平台内设置自动断流机制,限制几分钟后流自动断开,这样客户在浏览的时候就算看了忘了关,系统也会在几分钟就自动断开,耗费流量就会少很多。

解决问题

在获取通过直播链接的时候,在直播链接后面添加一个校验的流的字符串。

 

func wrapURLWithLiveToken(rawURL string, c *gin.Context) (wrapURL string) {
   wrapURL = rawURL
   demo := utils.Conf().Section("base_config").Key("demo").MustBool(false)
   if !demo {
      return
   }
   if rawURL == "" {
      return
   }
   _url, err := url.Parse(rawURL)
   if err != nil {
      return
   }
   q := _url.Query()
   //token := utils.MD5(sessions.Default(c).ID() + rawURL)
   token := createRandomString(8)
   q.Set("token", token)
   _url.RawQuery = q.Encode()
   wrapURL = _url.String()
   liveTokenCache.SetDefault(token, wrapURL)
   return
}
 
func createRandomString(len int) string {
   var container string
   var str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
   b := bytes.NewBufferString(str)
   length := b.Len()
   bigInt := big.NewInt(int64(length))
   for i := 0; i < len; i++ {
      randomInt, _ := rand.Int(rand.Reader, bigInt)
      container += string(str[randomInt.Int64()])
   }
   return container
}

这样,直播链接就会有一个校验参数token。

如何在RTSP协议网页无插件直播流媒体视频平台EasyNVR内做到自定义断流?

针对不同流的特点来进行不同的限制:

1、ws-flv流

 

func WSFlvHandler() gin.HandlerFunc {
   return func(c *gin.Context) {
      demo := utils.Conf().Section("base_config").Key("demo").MustBool(false)
      demoDuration := utils.Conf().Section("base_config").Key("demo_duration").MustInt(180)
      flag := false
      path := c.Param("path")
      if strings.HasSuffix(path, ".flv") {
         target := fmt.Sprintf("127.0.0.1:%v", dss.GetHTTPPort())
         //获取nginx里面的真实流地址
         flvUrl := "http://" + target + path
         upGrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {
            return true
         }}
         //websocket长连接
         ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
         if err != nil {
            return
         }
         defer func() {
            //fmt.Println("关闭ws-flv长连接")
            ws.Close()
         }()
         if demo {
            go func() {
               time.Sleep(time.Duration(demoDuration) * time.Second)
               flag = true
            }()
         }
         //发送http请求,将视频流数据循环写入到websocket
         req, _ := http.NewRequest("GET", flvUrl, nil)
         res, _ := http.DefaultClient.Do(req)
         reader := bufio.NewReader(res.Body)
         defer res.Body.Close()
         //循环遍历
         for {
            line, err := reader.ReadBytes(' ')
            if err != nil {
               return
            }
            ws.WriteMessage(websocket.BinaryMessage, line)
            if flag {
               return
            }
         }
      }
      c.Next()
   }
}

至此,ws-flv流就限制成功了。

2、http-flv流

 

//可关闭 Transport
type ShutDownTransport struct {
   Trans    *http.Transport
   response *http.Response
}
 
//覆盖上层Transport
func (t *ShutDownTransport) RoundTrip(req *http.Request) (*http.Response, error) {
   res, err := t.Trans.RoundTrip(req)
   t.response = res
   return res, err
}
 
//实现关闭方法
func (t *ShutDownTransport) ShutDown(d time.Duration) {
   time.AfterFunc(d, func() {
      res := t.response
      if res != nil {
         if res.Body != nil {
            res.Body.Close()
         }
      }
   })
}
 
// FlvHandler flv request handler
func FlvHandler() gin.HandlerFunc {
   return func(c *gin.Context) {
      demo := utils.Conf().Section("base_config").Key("demo").MustBool(false)
      demoDuration := utils.Conf().Section("base_config").Key("demo_duration").MustInt(180)
      path := c.Param("path")
      if strings.HasSuffix(path, ".flv") {
         target := fmt.Sprintf("127.0.0.1:%v", dss.GetHTTPPort())
         director := func(req *http.Request) {
            req.URL.Scheme = "http"
            req.URL.Host = target
            req.URL.Path = path
         }
         modifyRes := func(res *http.Response) (err error) {
            res.Header.Del("Access-Control-Allow-Credentials")
            res.Header.Del("Access-Control-Allow-Headers")
            res.Header.Del("Access-Control-Allow-Methods")
            res.Header.Del("Access-Control-Allow-Origin")
            res.Header.Del("Vary")
            res.Header.Del("Server")
            return
         }
         transport := &ShutDownTransport{
            Trans: &http.Transport{
               Proxy: http.ProxyFromEnvironment,
               DialContext: (&net.Dialer{
                  Timeout:   30 * time.Second,
                  KeepAlive: 30 * time.Second,
               }).DialContext,
               ForceAttemptHTTP2:     true,
               MaxIdleConns:          100,
               IdleConnTimeout:       90 * time.Second,
               TLSHandshakeTimeout:   10 * time.Second,
               ExpectContinueTimeout: 1 * time.Second,
               ResponseHeaderTimeout: 10 * time.Second,
            },
         }
         if demo {
            transport.ShutDown(time.Duration(demoDuration) * time.Second)
         }
         proxy := &httputil.ReverseProxy{
            Director:       director,
            Transport:      transport,
            ModifyResponse: modifyRes,
         }
         defer func() {
            if p := recover(); p != nil {
               log.Println(p)
            }
         }()
         proxy.ServeHTTP(c.Writer, c.Request)
         return
      }
      c.Next()
   }
}

至此,http-flv限流就完成了。

3、hls流

因为hls流和ws-flv、http-flv流不同,前端会一直请求这个链接,所以就不用向上面一样限流。

 

// 检查断流
func checkTime() gin.HandlerFunc {
  return func(c *gin.Context) {
     path := c.Param("path")
     demo := utils.Conf().Section("base_config").Key("demo").MustBool(false)
     isTS := strings.HasSuffix(path, ".ts")
     if demo && !isTS {
        token := c.Query("token")
        if token == "" {
           c.IndentedJSON(401, "Token Not Found")
           return
        }
        if _, ok := liveTokenCache.Get(token); !ok {
           c.IndentedJSON(401, "Invalid Token")
           return
        }
     }
     c.Next()
  }
}

因为播放器又断流自动重连机制,所以在请求的流链接时先要判断一下,请求的这个流地址是不是系统缓存中,不在系统缓存中就不让播放流了。

如何在RTSP协议网页无插件直播流媒体视频平台EasyNVR内做到自定义断流?

只要有演示平台需求的项目都可以通过该调用方法节省公网的浏览和带宽,大家可以参考《EasyGBS平台如何开启“演示”模式》一文了解一下演示平台的机制和作用。

上一篇:ijkplayer、VLC Player、SmartPlayer、ExoPlayer播放器比较


下一篇:RTSP拉流平台EasyNVR、EasyDSS如何自主合并TS文件为MP4格式播放?