如何在RTSP协议网页无插件直播流媒体视频平台EasyNVR演示系统内做到自定义断流?
我们在对EasyDSS做功能测试的时候,讲过在EasyDSS演示平台上,为了节省资源占用设置的自动停播问题。基于EasyDSS的成功经验,我们在EasyNVR的官网也做了同样一套机制。
分析问题
在EasyNVR的演示平台内设置自动断流机制,限制几分钟后流自动断开,这样客户在浏览的时候就算看了忘了关,系统也会在几分钟就自动断开,耗费流量就会少很多。
解决问题
在获取通过直播链接的时候,在直播链接后面添加一个校验的流的字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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。
针对不同流的特点来进行不同的限制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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流就限制成功了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | //可关闭 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流不同,前端会一直请求这个链接,所以就不用向上面一样限流。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // 检查断流 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平台如何开启“演示”模式》一文了解一下演示平台的机制和作用。
分类:
EasyNVR
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 提示词工程——AI应用必不可少的技术
· 地球OL攻略 —— 某应届生求职总结
· 字符编码:从基础到乱码解决
· SpringCloud带你走进微服务的世界