我们在对EasyDSS做功能测试的时候,讲过在EasyDSS演示平台上,为了节省资源占用设置的自动停播问题。基于EasyDSS的成功经验,我们在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。
针对不同流的特点来进行不同的限制:
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流就限制成功了。
//可关闭 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限流就完成了。
因为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() } }
因为播放器又断流自动重连机制,所以在请求的流链接时先要判断一下,请求的这个流地址是不是系统缓存中,不在系统缓存中就不让播放流了。
只要有演示平台需求的项目都可以通过该调用方法节省公网的浏览和带宽,大家可以参考《EasyGBS平台如何开启“演示”模式》一文了解一下演示平台的机制和作用。