美文网首页
nginx-rtmp-module直播实验

nginx-rtmp-module直播实验

作者: TroyZhang | 来源:发表于2017-03-08 13:01 被阅读3213次

    流程

    流程

    端口规划

    端口 用途
    8000 nginx服务器
    http在线观看视频
    8020 nodejs + express,处理nginx-rtmp-module回调
    1)将rtmp流地址写文件/数据库
    2)生成视频的缩略图
    8040 rtmp传输端口

    编译 安装 nginx + nginx-rtmp-module

    $ ./configure --add-module=/home/troyz/software/nginx-rtmp-module --with-openssl=/home/troyz/software/openssl-OpenSSL_1_0_2 --with-http_ssl_module --with-debug
    $ sudo make
    $ sudo make install
    

    配置文件

      nginx binary file: "/usr/local/nginx/sbin/nginx"
      nginx configuration file: "/usr/local/nginx/conf/nginx.conf"
      nginx error log file: "/usr/local/nginx/logs/error.log"
      nginx http access log file: "/usr/local/nginx/logs/access.log"
    

    rtmp配置 - nginx.conf

    http {
         server{
            listen       8000;
            location /stat {
                rtmp_stat all;
                # NOTE: please copy file `stat.xsl` from `nginx-rtmp-module` to nginx's html folder
                rtmp_stat_stylesheet stat.xsl;
            }
            location /stat.xsl {
                root html;
            }
            location /videos {
                root html;
                add_header Cache-Control no-cache;
                add_header Access-Control-Allow-Origin *;
                add_header Access-Control-Allow-Methods *;
                add_header Access-Control-Allow-Headers *;
            }
         }
    }
    rtmp {
         server {
                listen 8040;
                application live {
                    live on;
                    record all;
                    record_path /usr/local/nginx/html/videos/videos;
                    on_publish http://127.0.0.1:8020/rtmp/push;
                    on_publish_done http://127.0.0.1:8020/rtmp/push_done;
                    notify_method get;
                }
          }
    }
    

    创建文件夹

    // 视频文件保存的路径
    $ mkdir /usr/local/nginx/html/videos/videos
    $ chmod a+w /usr/local/nginx/html/videos/videos
    
    // 缩略图文件保存的路径
    $ mkdir /usr/local/nginx/html/videos/preview
    $ chmod a+w /usr/local/nginx/html/videos/preview
    

    启动nginx

    $ /usr/local/nginx/sbin/nginx -t
    $ /usr/local/nginx/sbin/nginx
    

    stream 列表

    • 首先拷贝文件cp nginx-rtmp-module/stat.xls nginx/html/
    • 访问 http://127.0.0.1:8000/stat
    • 解析<stream>节点下的<name>节点
    • 用处不大,请使用后面的express处理

    安装ffmpeg

    请自行查找相当文档,我在centos6上是源码安装,在mac上是brew安装的。

    Express处理rtmp播放回调

    $ yum install -y nodejs
    $ mkdir /usr/local/nginx/express.js && cd /usr/local/nginx/express.js
    $ npm init
    $ npm install express --save
    $ vim index.js
    
    var fs = require('fs');
    var express = require('express');
    var app = express();
    var exec = require('child_process').exec; 
    
    // 所有的视频地址信息都保存在json文件中
    var filePath = '/usr/local/nginx/html/videos/video_list.json';
    
    var videoList = [];
    
    function saveVideoList()
    {
      fs.writeFile(filePath, JSON.stringify(videoList), function(err){
        if(err) return;
        console.log('save videos successfully');
      });  
    }
    
    // 生成视频的缩略图
    function createPreviewImage(video)
    {
      var previewFilePath = getPreviewFilePath(video);
      var videoFilePath = getVideoFilePath(video);
      fs.stat(previewFilePath, function (err, stats){
        if(stats && stats.isFile()){
          console.log("prefiew file is exists! " + previewFilePath);
        }
        else{
          console.log("prefiew file is not exists! " + previewFilePath + ", let's generate it!");
          var cmdStr = "ffmpeg -i " + videoFilePath + "  -vcodec png -vframes 1 -an -f rawvideo -s 640x480 -ss 00:00:01 -y " + previewFilePath;
          exec(cmdStr, function(err,stdout,stderr){
            if(err){
              console.log("create preview image file error: " + stderr);
            }
            else{
              console.log("create preview image file successfully: " + stdout);
            }
          });
        }
      });
    }
    
    function getVideoFilePath(video)
    {
      return "/usr/local/nginx/html/videos/videos/" + video.name + ".flv";
    }
    
    function getPreviewFilePath(video)
    {
      return "/usr/local/nginx/html/videos/preview/" + video.name + ".png";
    }
    
    function removeVideo(video){
      fs.unlink(getVideoFilePath(video), function(err){});
      fs.unlink(getPreviewFilePath(video), function(err){});
    }
    
    fs.readFile(filePath, 'utf-8', function(err, data){
      if(err) return;
      if(data && data.length > 0)
      {
        videoList = JSON.parse(data);
      }
      videoList = videoList ? videoList : [];
      console.log('data: ' + videoList);
    });
    
    // 当有新的`rtmp`流上传时被`nginx-rtmp-module`调用
    app.get('/rtmp/push', function (req, res) {
      console.log('ok push: ' + JSON.stringify(req.query));
      if(req.query)
      {
        var exist = false;
        for(var i = 0; i < videoList.length; i++)
        {
          var item = videoList[i];
          if(item.app == req.query.app && item.name == req.query.name)
          {
            videoList = videoList.slice(0, i).concat(req.query).concat(videoList.slice(i + 1));
            exist = true;
            break;
          }
        }
        if(!exist)
        {
          videoList.push(req.query);
        }
        saveVideoList();
        // 生成视频的缩略图
        setTimeout(function(){
          createPreviewImage(req.query);
        }, 5000);
      }
      res.send('passed');
    });
    
    // 当`rtmp`流播放结束时被`nginx-rtmp-module`调用(修改json文件中视频的状态字段)
    app.get('/rtmp/push_done', function(req, res){
      console.log('ok push done: ' + JSON.stringify(req.query));
      if(req.query)
      {
        for(var i = 0; i < videoList.length; i++)
        {
          var item = videoList[i];
          if(item.app == req.query.app && item.name == req.query.name)
          {
            videoList = videoList.slice(0, i).concat(req.query).concat(videoList.slice(i + 1));
            saveVideoList();
    
            var filePath = getVideoFilePath(item);
            // remove video file is size is ZERO
            fs.stat(filePath, function (err, stats){
              if(!err && stats){
                if(stats.size <= 0){
                  console.log("remove invalid video file: " + filePath);
                  videoList = videoList.slice(0, i).concat(videoList.slice(i + 1));
                  saveVideoList();
                  removeVideo(item);
                }
              }
            });
            break;
          }
        }
        createPreviewImage(req.query);
      }
      res.send('passed');
    });
    
    // 删除所有视频、缩略图
    app.get('/rtmp/clean', function(req, res){
      for(var i = 0; i < videoList.length; i++)
      {
        var item = videoList[i];
        removeVideo(item);
      }
      videoList = [];
      saveVideoList();
      res.send('passed');
    });
    
    // 生成视频的缩略图
    app.get('/rtmp/g', function(req, res){
      for(var i = 0; i < videoList.length; i++)
      {
        var item = videoList[i];
        createPreviewImage(item);
      }
      res.send('passed');
    });
    
    // 删除某个视频+缩略图
    app.get('/rtmp/delete', function(req, res){
      if(req.query && req.query["name"]){
        for(var i = 0; i < videoList.length; i++)
        {
          var item = videoList[i];
          if(item.name == req.query["name"]){
            removeVideo(item);
            videoList = videoList.slice(0, i).concat(videoList.slice(i + 1));
            saveVideoList();
            break;
          }
        }
      }
      res.send('passed');
    });
    
    var server = app.listen(8020, function () {
      var host = server.address().address;
      var port = server.address().port;
      console.log('app listening at http://%s:%s', host, port);
    });
    

    启动express

    $ npm install -g forever
    
    // 守护进程运行
    $ forever start express.js/index.js
    

    在线观看

    $ vim /usr/local/nginx/html/videos/index.html
    <!DOCTYPE html>
    <html>
    
    <head>
        <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
        <title>flv.js demo</title>
        <link href="//vjs.zencdn.net/5.11/video-js.min.css" rel="stylesheet">
        <script src="//vjs.zencdn.net/5.11/video.min.js"></script>
    
        <style>
            .videoContainer {
                display: block;
                /*width: 1024px;*/
                flex: 1;
                margin-left: auto;
                margin-right: auto;
            }
    
            .urlInput {
                display: block;
                width: 100%;
                margin-left: auto;
                margin-right: auto;
                margin-top: 8px;
                margin-bottom: 8px;
            }
    
            .centeredVideo {
                display: block;
                width: 100%;
                height: 576px;
                margin-left: auto;
                margin-right: auto;
                margin-bottom: auto;
            }
    
            .controls {
                display: none;
                width: 100%;
                text-align: left;
                margin-left: auto;
                margin-right: auto;
            }
            .container{
                display: flex;
                flex-flow: row;
            }
            .left{
                width: 30%;
            }
            .videoList{
                width: 100%;
                display: flex;
                flex-flow: row;
            }
            .leftVideoList{
                flex: 1;
            }
            .rightVideoList{
                flex: 1;
            }
            .videoDiv{
            }
            .emptyVideo{
                width: 20px;
            }
            .active{
                background-color: blue;
                font-weight: bold;
            }
            .normal{
                background-color: gray;
                font-weight: normal;
            }
            .videoList img{
                width: 100%;
                margin-bottom: 20px;
                background-color: black;
            }
            .videoStatus{
                position: absolute;
                margin-top: -50px;
                width: calc((30vw - 20px) / 2.0);
                text-align: center;
                color: white;
                padding-top: 5px;
                padding-bottom: 5px;
                font-size: 12px;
            }
        </style>
    </head>
    
    <body>
    
        <div id="container" class="container">
            <div class="left">
                <h3>Video List</h3>
                <div class="videoList">
                    <div class="leftVideoList">
                        <div v-for="(video, index) in leftVideoList" class="videoDiv" v-on:click="playVideo(video.index)">
                            <image v-bind:src="'preview/' + video.name + '.png'">
                            <div class="videoStatus" v-bind:class="{ active: video.index==selectedIndex, normal: video.index!=selectedIndex }">
                                {{ video.name + (video.call == 'publish' ? '(进行中)' : '(已截止)')}}
                            </div>
                        </div>
                    </div>
                    <div class="emptyVideo"></div>
                    <div class="rightVideoList">
                        <div v-for="(video, index) in rightVideoList" class="videoDiv" v-on:click="playVideo(video.index)">
                            <image v-bind:src="'preview/' + video.name + '.png'">
                            <div class="videoStatus" v-bind:class="{ active: video.index==selectedIndex, normal: video.index!=selectedIndex }">
                                {{ video.name + (video.call == 'publish' ? '(进行中)' : '(已截止)')}}
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div style="width: 70%; height: 576px;position: absolute;top:0;right:0;">
            <video id="videoJsPlayer" name="videoJsPlayer" class="video-js centeredVideo" preload="auto" controls autoplay width="1024px" height="576px">
            </video>
        </div>
        <div style="width: 70%; height: 576px;position: absolute;top:0;right:0;" v-bind:style="{display: selectedIndex==-1?'none':'block'}">
            <video name="flvjsPlayer" autoplay class="centeredVideo" preload="auto" controls autoplay width="1024" height="576">
                Your browser is too old which doesn't support HTML5 video.
            </video>
            <br>
            <div class="controls">
                <button onclick="flv_load()">Load</button>
                <button onclick="flv_start()">Start</button>
                <button onclick="flv_pause()">Pause</button>
                <button onclick="flv_destroy()">Destroy</button>
                <input style="width:100px" type="text" name="seekpoint"/>
                <button onclick="flv_seekto()">SeekTo</button>
            </div>
        </div>
    
        <script src="//cdn.bootcss.com/flv.js/1.1.0/flv.min.js"></script>
        <script src="//cdn.bootcss.com/vue/2.2.1/vue.min.js"></script>
        
        <script>
            function getAllVideoList()
            {
                var xhr = new XMLHttpRequest();
                xhr.open('GET', 'video_list.json', true);
                xhr.onload = function (e) {
                    var videoList = JSON.parse(xhr.response);
                    if(videoList && videoList.length > 0){
                        for(var i = 0; i < videoList.length; i++){
                            videoList[i].index = i;
                        }
                    }
                    app.videoList = videoList;
                }
                xhr.send();
            }
    
            function flv_load() {
                if(app.selectedIndex == -1)
                {
                    return;
                }
                var video = app.videoList[app.selectedIndex];
                console.log(video.name + ".flv" + " isrecording: " + (video.call == 'publish'));
    
                var element = document.getElementsByName('flvjsPlayer')[0];
                player = flvjs.createPlayer({
                    type: 'flv',
                    url: "videos/" + video.name + ".flv",
                    isLive: video.call == 'publish'
                }, {
                    enableWorker: false,
                    lazyLoadMaxDuration: 3 * 60,
                    seekType: 'range',
                });
                player.attachMediaElement(element);
                player.load();
            }
    
            function videoJs_load()
            {
                if(app.selectedIndex == -1)
                {
                    return;
                }
                var video = app.videoList[app.selectedIndex];
                var rtmpUrl = video.tcurl + "/" + video.name;
                var options = {
                    sources: [{
                        src: rtmpUrl,
                        type: 'rtmp/flv'
                    }]
                };
                videojsplayer.poster("preview/" + video.name + ".png");
                if (typeof videojsplayer !== "undefined") {
                    if (videojsplayer != null) {
                        videojsplayer.show();
                        videojsplayer.src({
                            src: rtmpUrl,
                            type: 'rtmp/flv'
                        });
                        videojsplayer.load();
                        videojsplayer.play();
                        return;
                    }
                }
                videojsplayer = videojs('videoJsPlayer', options, function onPlayerReady() {
                  videojs.log('Your player is ready!');
    
                  // In this context, `this` is the player that was created by Video.js.
                  this.play();
    
                  // How about an event listener?
                  this.on('ended', function() {
                    videojs.log('Awww...over so soon?!');
                  });
                });
            }
    
            function destroyFlvPlayer()
            {
                if (typeof player !== "undefined") {
                    if (player != null) {
                        player.unload();
                        player.detachMediaElement();
                        player.destroy();
                        player = null;
                    }
                }
            }
    
            function destroyVideoJsPlayer()
            {
                if (typeof videojsplayer !== "undefined") {
                    if (videojsplayer != null) {
                        videojsplayer.pause();
                        // videojsplayer.hide();
                        // videojsplayer = null;
                    }
                }
            }
    
            function flv_start() {
                player.play();
            }
    
            function flv_pause() {
                player.pause();
            }
    
            function flv_destroy() {
                player.pause();
                player.unload();
                player.detachMediaElement();
                player.destroy();
                player = null;
            }
    
            function flv_seekto() {
                var input = document.getElementsByName('seekpoint')[0];
                player.currentTime = parseFloat(input.value);
            }
    
            function getUrlParam(key, defaultValue) {
                var pageUrl = window.location.search.substring(1);
                var pairs = pageUrl.split('&');
                for (var i = 0; i < pairs.length; i++) {
                    var keyAndValue = pairs[i].split('=');
                    if (keyAndValue[0] === key) {
                        return keyAndValue[1];
                    }
                }
                return defaultValue;
            }
            var app = new Vue({
              el: '#container',
              data: {
                videoList: [],
                selectedIndex: -1,
                isPublishing: false
              },
              computed: {
                leftVideoList: function(){
                    return this.videoList.filter(function (item, index) {
                      return index % 2 === 0
                    })
                },
                rightVideoList: function(){
                    return this.videoList.filter(function (item, index) {
                      return index % 2 === 1
                    })
                }
              },
              methods: {
                playVideo: function(index){
                    if(app.selectedIndex == index){
                        return;
                    }
                    app.selectedIndex = index;
                    var video = app.videoList[index];
                    destroyFlvPlayer();
                    destroyVideoJsPlayer();
                    if(video.call == 'publish_done'){
                        app.isPublishing = false;
                        flv_load();
                    }
                    else if(video.call == 'publish'){
                        app.isPublishing = true;
                        videoJs_load();
                    }
                    else{
                        alert("视频状态:" + video.call);
                    }
                }
              }
            });
            getAllVideoList();
            videojsplayer = videojs('videoJsPlayer', {});
            videojsplayer.hide();
            // document.addEventListener('DOMContentLoaded', function () {
            //     flv_load();
            // });
        </script>
        
    </body>
    
    </html>
    

    ffmpeg rtmp推流

    ffmpeg  -f avfoundation -i "1" -vcodec libx264 -preset ultrafast -acodec libfaac -f flv rtmp://localhost:8040/live/test
    

    浏览器在线观看

    http://127.0.0.1:8000/videos/index.html

    浏览器在线观看

    rtmp推流库

    Library Platform 特点
    LFLiveKit iOS 基本满足需求、可以本地录制
    PLMediaStreamingKit iOS 七牛云,需要将视频推送到七牛云平台
    yasea android 经常花屏
    SopCastComponent android 延时卡顿严重
    librestreaming android 基本满足需求

    rtmp播放库

    Library Platform
    ijkplayer android/iOS

    web在线观看

    Library 特点
    video.js 播放rtmp流,
    播放flv静态视频时有bug,只在左上角显示播放小窗口
    flv.js 播放flv静态视频
    live stream有bug播放不了

    相关文章

      网友评论

          本文标题:nginx-rtmp-module直播实验

          本文链接:https://www.haomeiwen.com/subject/qprbgttx.html