如何在RTSP協定網頁無外掛直播串流媒體視訊平臺EasyNVR演示系統內做到自定義斷流?

2020-09-28 11:00:54

我們在對EasyDSS做功能測試的時候,講過在EasyDSS演示平臺上,為了節省資源佔用設定的自動停播問題。基於EasyDSS的成功經驗,我們在EasyNVR的官網也做了同樣一套機制。

NVR10.png

分析問題

在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。

83.png

針對不同流的特點來進行不同的限制:

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

因為播放器又斷流自動重連機制,所以在請求的流連結時先要判斷一下,請求的這個流地址是不是系統快取中,不在系統快取中就不讓播放流了。

NVR7.png

只要有演示平臺需求的專案都可以通過該呼叫方法節省公網的瀏覽和頻寬,大家可以參考《EasyGBS平臺如何開啟「演示」模式》一文了解一下演示平臺的機制和作用。