飞道的博客

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

368人阅读  评论(0)

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

分析问题

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

解决问题

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


  
  1. func wrapURLWithLiveToken(rawURL string, c *gin.Context) (wrapURL string) {
  2. wrapURL = rawURL
  3. demo := utils.Conf().Section( "base_config").Key( "demo").MustBool( false)
  4. if !demo {
  5. return
  6. }
  7. if rawURL == "" {
  8. return
  9. }
  10. _url, err := url.Parse(rawURL)
  11. if err != nil {
  12. return
  13. }
  14. q := _url.Query()
  15. //token := utils.MD5(sessions.Default(c).ID() + rawURL)
  16. token := createRandomString( 8)
  17. q.Set( "token", token)
  18. _url.RawQuery = q.Encode()
  19. wrapURL = _url.String()
  20. liveTokenCache.SetDefault(token, wrapURL)
  21. return
  22. }
  23. func createRandomString(len int) string {
  24. var container string
  25. var str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
  26. b := bytes.NewBufferString(str)
  27. length := b.Len()
  28. bigInt := big.NewInt( int64(length))
  29. for i := 0; i < len; i++ {
  30. randomInt, _ := rand.Int(rand.Reader, bigInt)
  31. container += string(str[randomInt.Int64()])
  32. }
  33. return container
  34. }

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

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

1、ws-flv流


  
  1. func WSFlvHandler() gin.HandlerFunc {
  2. return func(c *gin.Context) {
  3. demo := utils.Conf().Section( "base_config").Key( "demo").MustBool( false)
  4. demoDuration := utils.Conf().Section( "base_config").Key( "demo_duration").MustInt( 180)
  5. flag := false
  6. path := c.Param( "path")
  7. if strings.HasSuffix(path, ".flv") {
  8. target := fmt.Sprintf( "127.0.0.1:%v", dss.GetHTTPPort())
  9. //获取nginx里面的真实流地址
  10. flvUrl := "http://" + target + path
  11. upGrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {
  12. return true
  13. }}
  14. //websocket长连接
  15. ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
  16. if err != nil {
  17. return
  18. }
  19. defer func() {
  20. //fmt.Println("关闭ws-flv长连接")
  21. ws.Close()
  22. }()
  23. if demo {
  24. go func() {
  25. time.Sleep(time.Duration(demoDuration) * time.Second)
  26. flag = true
  27. }()
  28. }
  29. //发送http请求,将视频流数据循环写入到websocket
  30. req, _ := http.NewRequest( "GET", flvUrl, nil)
  31. res, _ := http.DefaultClient.Do(req)
  32. reader := bufio.NewReader(res.Body)
  33. defer res.Body.Close()
  34. //循环遍历
  35. for {
  36. line, err := reader.ReadBytes( ' ')
  37. if err != nil {
  38. return
  39. }
  40. ws.WriteMessage(websocket.BinaryMessage, line)
  41. if flag {
  42. return
  43. }
  44. }
  45. }
  46. c.Next()
  47. }
  48. }

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

2、http-flv流


  
  1. //可关闭 Transport
  2. type ShutDownTransport struct {
  3. Trans *http.Transport
  4. response *http.Response
  5. }
  6. //覆盖上层Transport
  7. func (t *ShutDownTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  8. res, err := t.Trans.RoundTrip(req)
  9. t.response = res
  10. return res, err
  11. }
  12. //实现关闭方法
  13. func (t *ShutDownTransport) ShutDown(d time.Duration) {
  14. time.AfterFunc(d, func() {
  15. res := t.response
  16. if res != nil {
  17. if res.Body != nil {
  18. res.Body.Close()
  19. }
  20. }
  21. })
  22. }
  23. // FlvHandler flv request handler
  24. func FlvHandler() gin.HandlerFunc {
  25. return func(c *gin.Context) {
  26. demo := utils.Conf().Section( "base_config").Key( "demo").MustBool( false)
  27. demoDuration := utils.Conf().Section( "base_config").Key( "demo_duration").MustInt( 180)
  28. path := c.Param( "path")
  29. if strings.HasSuffix(path, ".flv") {
  30. target := fmt.Sprintf( "127.0.0.1:%v", dss.GetHTTPPort())
  31. director := func(req *http.Request) {
  32. req.URL.Scheme = "http"
  33. req.URL.Host = target
  34. req.URL.Path = path
  35. }
  36. modifyRes := func(res *http.Response) (err error) {
  37. res.Header.Del( "Access-Control-Allow-Credentials")
  38. res.Header.Del( "Access-Control-Allow-Headers")
  39. res.Header.Del( "Access-Control-Allow-Methods")
  40. res.Header.Del( "Access-Control-Allow-Origin")
  41. res.Header.Del( "Vary")
  42. res.Header.Del( "Server")
  43. return
  44. }
  45. transport := &ShutDownTransport{
  46. Trans: &http.Transport{
  47. Proxy: http.ProxyFromEnvironment,
  48. DialContext: (&net.Dialer{
  49. Timeout: 30 * time.Second,
  50. KeepAlive: 30 * time.Second,
  51. }).DialContext,
  52. ForceAttemptHTTP2: true,
  53. MaxIdleConns: 100,
  54. IdleConnTimeout: 90 * time.Second,
  55. TLSHandshakeTimeout: 10 * time.Second,
  56. ExpectContinueTimeout: 1 * time.Second,
  57. ResponseHeaderTimeout: 10 * time.Second,
  58. },
  59. }
  60. if demo {
  61. transport.ShutDown(time.Duration(demoDuration) * time.Second)
  62. }
  63. proxy := &httputil.ReverseProxy{
  64. Director: director,
  65. Transport: transport,
  66. ModifyResponse: modifyRes,
  67. }
  68. defer func() {
  69. if p := recover(); p != nil {
  70. log.Println(p)
  71. }
  72. }()
  73. proxy.ServeHTTP(c.Writer, c.Request)
  74. return
  75. }
  76. c.Next()
  77. }
  78. }

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

3、hls流

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


  
  1. // 检查断流
  2. func checkTime() gin. HandlerFunc {
  3. return func(c *gin.Context) {
  4. path := c. Param( "path")
  5. demo := utils. Conf(). Section( "base_config"). Key( "demo"). MustBool( false)
  6. isTS := strings. HasSuffix(path, ".ts")
  7. if demo && !isTS {
  8. token := c. Query( "token")
  9. if token == "" {
  10. c. IndentedJSON( 401, "Token Not Found")
  11. return
  12. }
  13. if _, ok := liveTokenCache. Get(token); !ok {
  14. c. IndentedJSON( 401, "Invalid Token")
  15. return
  16. }
  17. }
  18. c. Next()
  19. }
  20. }

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

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


转载:https://blog.csdn.net/EasyNVR/article/details/108827311
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场