前言

因为本人有一个IM类的利用,为了欠缺它所以决定也加上直播和短视频性能。做直播目前有两种办法,一是间接对接第三方的直播服务产品,二是本人搭服务再开发。所以这里也从这两个办法举荐简略的实现形式,阿里云和腾讯云之类的大厂产品就不安利了。

选型

  1. 第三方,PHP+Uni-App+LiveQing
  2. 本人开发,PHP+Uni-app+Nginx-rtmp-module

实现流程

1.客户端采集视频流。(开摄像头,录屏等)

2.客户端推流到rtmp服务器上。

3.rtmp推流到某个特定端口。

4.其余客户端再对该视频流进行拉流,实现直播。

第三方形式

第三方这次举荐的是一个叫LiveQing的平台,有点是搭建快捷不便,功能完善。在服务器上运行了他们的包后除了能实现支流业务场景的直播,而且还提供短视频的点播服务。还包含API调用,通过接口实现直播的创立,删除,直播数据统计。然而是要免费,该软件包在一台物理机或云服务器上只能收费试用一个月。

1.找到该官网,抉择rtmp直播点播流媒体,下载试用把对应零碎解压到本人服务器。

2.目录如下,将start.sh受权为777。而后./start.sh 运行该文件。

3.运行前能够关上liveqing.ini进行设置,比方后盾登录明码,端口号等。

4.默认须要开启10080和10085,所以须要用防火墙放行,操作如下。

systemctl start firewalld.service    // 开启防火墙firewall-cmd add-port=10080/tcp --permanentfirewall-cmd add-port=10082/tcp --permanentfirewall-cmd --reload               // 重启firewall-cmd --list-ports           // 查看放行的所有端口

5.端口放行,而后在运行start.sh呈现上面图标示意胜利。

6.浏览器输出服务器的外网IP:10080,就能够进入控制面板了。

7.创立一个直播,设置名称和ID,而后抉择编辑获取推流地址。

8.为了测试能够本地下载一个OBS软件推流到该地址,只有一推流,直播状态就会显示直播中并且点击编辑能够获取拉流的地址。

9.同样为了不便能够应用VLS软件进行拉流或者wowza在线网站测试直播。

代码实现

不应用第三方的话,就须要搭建rtmp服务,配置Nginx,APP视频采集推流,拉流等等。如果是大型平台,须要进行分流集群等。流媒体服务器依赖的服务,1.nginx 服务器;2.nginx服务器装置须要依赖的服务 OpenSSL、pcre、zlib、 c++、gcc等,服务器环境是Centos 7.3 64 位。

1.进入根目录,mkdir source #创立源码目录,前面的源码都放在这个目录。cd source进入该目录。

2.下载git,yum -y install git,而后通过网络下载须要的包。

git clone https://github.com/nginx/nginx.git                 #从github服务器上将nginx的源代码下载下来git clone https://github.com/arut/nginx-rtmp-module.git     #将rtmp模块的源码下载下来wget https://www.openssl.org/source/openssl-1.1.0.tar.gz     #下载OpenSSL源码包wget https://ftp.pcre.org/pub/pcre/pcre-8.39.tar.gz         #下载pcre源码包wget http://www.zlib.net/zlib-1.2.11.tar.gz                 #下载zlib包源码

3.tar -zxvf 包名 #解压各个包源码

4.在将nginx和须要的包编译前须要先装置gcc,装置过能够省过。

yum -y install gcc             #确保依赖的gcc装置yum -y install gcc-c++         #确保依赖的c++曾经装置

5.而后cd命令进入source下的nginx目录,输出上面命令。

./auto/configure --prefix=/usr/local/nginx \        --with-pcre=../pcre-8.39 \        --with-openssl=../openssl-1.1.0 \        --with-zlib=../zlib-1.2.11 \        --with-http_v2_module \        --with-http_flv_module \        --with-http_mp4_module \        --add-module=../nginx-rtmp-module/

6.查看胜利会呈现如下,而后make编译一下。

7.make install 装置

8.以上操作后示意Nginx编译装置实现,而后cd到根目录,/usr/local/nginx/sbin,如果要测试Nginx是否能够拜访。先放行80端口重启防火墙,在sbin下输出./nginx启动Nginx服务。浏览器拜访IP地址:80,呈现以下示意胜利。

9.在nginx配置文件中配置rtmp服务,记住rtmp服务是和http服务是平级,所以咱们须要在和http配置平级的地位另起rtmp服务。

vi /usr/local/nginx/conf/nginx.conf #批改配置文件
rtmp  {    server  {        listen 1935;        chunk_size 4096;        application live  {            live on;            record off;        }        application live2  {            live on;            record off;        }        application vod  {            play /var/flvs;        }        application vod_http  {            play http://服务器的ip/vod;        }        application hls  {            live on;            hls on;            hls_path /tmp/hls;        }    }}
/usr/local/nginx/sbin/nginx -s reload  #批改配置文件重启nginx服务

10.下面rtmp服务的端口是1935,所以也须要按之前办法给1935端口放行,查看云服务器的平安组是否也放行,而后再重启防火墙。

11.本地电脑测试1935是否开启,能够cmd命令telnet 服务器IP地址 端口号,如果呈现一下界面阐明端口曾经通了 。

12.接下来也能够通过OBS推流到该地址,而后用WOWZA拉流进行测试。
rtmp://你的服务器ip:端口(1935)/live #URL填写流的地址

13.接下来演示uni-app的推流写法。

<template>    <view class="content">                <view class="butlist">            <view @click="back" class="buticon martp10">                <image src="../../static/zhiwen-livepush/back2.png"></image>                    <view class="mar10">返回</view>                            </view>            <view @click="switchCamera" class="buticon martp10">                <image src="../../static/zhiwen-livepush/reversal.png"></image>                    <view class="mar10">翻转</view>                            </view>            <view class=" buticon" @click="startPusher">                <view class="x_f"></view>                <view :class="begin==true?'givebegin':'give'" >{{contTime}}</view>                <view class="pulse" v-if="begin"></view>            </view>            <view class="buticon martp10">                <image src="../../static/zhiwen-livepush/beautiful.png"></image>                    <view class="mar10">丑化</view>                            </view>            <view   class="buticon martp10" v-if="begin==false">                <picker :value="index" @change="bindPickerChange" :range="array" range-key='cont'>                    <image src="../../static/zhiwen-livepush/countdown.png"></image>                        <view class="mar10">倒计时</view>                </picker>                </view>            <view @click="upload" class="buticon martp10" v-if="begin">                <image src="../../static/zhiwen-livepush/yes.png"></image>                    <view class="mar10">实现</view>                            </view>                    </view>                     </view></template><script>    export default {        data() {            return {                begin:false,//开始录制                complete:false,//录制结束                pause:false,//暂停推流                currentWebview:null,                pusher:null,                livepushurl:'rtmp://106.52.216.244:10089/hls/1',  //这里批改本人的推流地址就能够了                logininfokey:'',//登录验证加密串,                homeworkcont:'',//作业信息                jiexititle:'',//作业解析题目                index: 0,//定时                indextu:0,//是否开启定时                contTime:'',                array: [{//话题标签                        "id": 1,                        "cont": "10秒",                        "time": 10                    }, {                        "id": 2,                        "cont": "20秒",                        "time": 20                    }, {                        "id": 3,                        "cont": "30秒",                        "time": 30                    }, {                        "id": 4,                        "cont": "40秒",                        "time": 40                    },{                        "id": 5,                        "cont": "50秒",                        "time": 50                    },                    {                        "id": 6,                        "cont": "60秒",                        "time": 60                    }],            }        },                 onShow() {             uni.getNetworkType({                success: function (res) {                    console.log(res.networkType);                    if(res.networkType != 'wifi'){                        uni.showModal({ //揭示用户更新                            title: '舒适提醒',                            content: '以后非Wifi网络,请留神您的流量是否够用',                            success: (res) => {                                                             }                        })                    }                }            });            uni.onNetworkStatusChange(function (res) {                console.log(res.isConnected);                console.log(res.networkType);                if(res.networkType != '4g' && res.networkType != 'wifi'){                    uni.showModal({ //揭示用户更新                        title: '舒适提醒',                        content: '以后网络品质差,请切换为4G网络或Wifi网络',                        success: (res) => {                                                     }                    })                }            });        /*     plus.key.addEventListener("backbutton",()=>{                console.log("BackButton Key pressed!" );                //this.back()                return false            }); */        },         onBackPress(){                this.back()                console.log("BackButton Key pressed!" );                return true;         },        onLoad(res) {            console.log(res)            this.jiexititle=res.title            uni.getStorage({                key: 'logininfokey',                success:(res) =>{                    console.log(res.data);                    this.logininfokey=res.data                    console.log(this.logininfokey)                }            });            uni.getStorage({                key: 'clickworkcont',                success:(res) =>{                    console.log(res.data);                    this.homeworkcont=res.data                    //console.log(this.logininfokey)                }            });                        uni.getStorage({                key: 'livepushurl',                success:(res) =>{                    console.log(res.data);                    this.livepushurl=res.data                }            });            console.log(this.livepushurl)            this.getwebview()//获取webview        },        methods: {            //倒计时            bindPickerChange: function(e) {                console.log('picker发送抉择扭转,携带值为', e.target.value)                this.index = e.target.value                // this.indexs = e.target.value                this.contTime=this.array[e.target.value].time                uni.showToast({                    title: '请点击红色按钮,开始进入倒计时',                    icon:'none',                    duration: 4000,                                     });            },                        /**             * 返回             */            back(){                uni.showModal({                    title: '提醒',                    content: '返回后未上传的视频须要从新录制哦',                    success: function (res) {                        if (res.confirm) {                            /* this.currentWebview=null;                            this.pusher=null */                            uni.redirectTo({                                url:'../user/issue'                            })                            //this.currentWebview=null                        } else if (res.cancel) {                            console.log('用户点击勾销');                        }                    }                });                            },            /**             * 获取以后显示的webview             */            getwebview(){                var pages = getCurrentPages();                var page = pages[pages.length - 1];                // #ifdef APP-PLUS                var getcurrentWebview = page.$getAppWebview();                console.log(this.pages)                console.log(this.page)                console.log(JSON.stringify(page.$getAppWebview()))                this.currentWebview=getcurrentWebview;                // #endif                this.plusReady()//创立LivePusher对象            },            /**             * 创立LivePusher对象 即推流对象             */             plusReady(){                                // 创立直播推流控件                this.pusher =new plus.video.LivePusher('pusher',{                    url:'',                    top:'0',                    left:'0px',                    width: '100%',                    height:  uni.getSystemInfoSync().windowHeight-15 + 'px',                                    position: 'absolute',//static动态布局模式,如果页面存在滚动条则随窗口内容滚动,absolute相对布局模式,如果页面存在滚动条不随窗口内容滚动; 默认值为"static"                    beauty:'0',//美颜 0-off  1-on                      whiteness:'0',//0、1、2、3、4、5,0不应用美白,值越大美白水平越大。                    aspect:'9:16',                                     });                console.log(JSON.stringify(this.pusher))                console.log(JSON.stringify(this.currentWebview))                //将创立的对象 追加到webview中                this.currentWebview.append(this.pusher);                // 监听状态变动事件                  this.pusher.addEventListener('statechange',(e)=>{                    console.log('statechange: '+JSON.stringify(e));                }, false);            },                        //美颜            beautiful(){                console.log(JSON.stringify(this.pusher))                this.pusher.options.beauty=1                this.plusReady()//创立LivePusher对象            },            // 开始推流            startPusher(){                //判断是否倒计时开始                if(this.contTime!=''){                    if(this.indextu!=1){                        this.conttimejs()                    }                }else{                    this.beginlivepush()                }            },            conttimejs(){                if(this.contTime!=''){                    this.indextu=1;//开启计时                    if(this.contTime==1){                        console.log("开始")                        this.contTime=""                        this.beginlivepush()                        return false                    }                    this.contTime--                    setTimeout(()=>{                        this.conttimejs()                    },1000)                }            },            beginlivepush() {                this.indextu=0;//敞开计时                if(this.begin==false){//未开启推流                    this.begin=true;//显示录制动画                    // 设置推流服务器  ***此处须要通过ajax向后端获取                    this.pusher.setOptions({                        url:this.livepushurl //推流地址********************************* 此处设置推流地址                    });                    this.pusher.start();//推流开启                    uni.showToast({                        title: '开始录制',                        icon:'none',                        duration: 2000,                                         });                }else{                    if(this.pause==true){//暂停推流状态                        this.begin=true;//显示录制动画                        this.pause=false;//推流开关置为默认状态                        this.pusher.resume();//复原推流                        uni.showToast({                            title: '开始录制',                            icon:'none',                            duration: 2000,                                             });                    }else{                        this.begin=false;//敞开录制动画                        this.pause=true;//推流暂停                        this.pusher.pause();;//暂停推流                        uni.showToast({                            title: '暂停录制',                            icon:'none',                            duration: 2000,                                             });                        //提醒是否上传                        this.upload()                                                                    }                }            },            /**             * 切换摄像头             */             switchCamera() {                this.pusher.switchCamera();            },            /**             * 实现录制             */            upload(){                 uni.showModal({                     title: '提醒',                     content: '确定保留吗',                     success:(res)=> {                         if (res.confirm) {                              console.log('用户点击实现');                             this.pusher.pause();;//暂停推流                             this.endlivepush()                                                         /* setTimeout(()=>{                                 this.endlivepush()                             },1000) */                         } else if (res.cancel) {                             console.log('用户点击勾销');                         }                     }                 });            },             //完结推流,此处须要调用后盾接口向云服务商提交完结状态            endlivepush(){                    uni.showToast({                    icon:'loading',                    title: '完结...',                    duration: 5000                });                return false                uni.request({                        url: "",                                   method: 'POST',                        // dataType:'JSON',                       data:{},                       success:(res)=>{                           console.log(JSON.parse(res.data))                           console.log(JSON.stringify(res.data))                            uni.showToast({                                icon:'loading',                                title: '视频上传中...',                                duration: 5000                            });                                                        setTimeout(()=>{                                                            uni.showToast({                                    icon:'none',                                    title: '上传实现',                                    duration: 2000                                });                            },5000)                            setTimeout(()=>{                                                            uni.redirectTo({                                    url: 'setvideotit?id='+this.homeworkcont.id,                                });                            },7000)                       },                       error: (data)=>{                           //alert(JSON.stringify(data)+'谬误')                                       }                   });            },                     },        components:{                }    }</script><style>.content{        background: #000;        overflow: hidden;    }    .butlist{        height: 140upx;        position: absolute;        bottom: 0;        display: flex;        width: 100%;        justify-content: space-around;        padding-top: 20upx;        border-top: 1px solid #fff;        background: #000;    }    .buticon{        height: 120upx;        width: 120upx;        color: #fff;        position: relative;        text-align: center;        margin-bottom: 20upx;    }    .buticon image{        height: 64upx;        width: 64upx;    }    .buticon .mar10{        margin-top: -20upx;    }    .martp10{        margin-top: 10upx;    }    .give {        width: 90upx;        height: 90upx;        background: #F44336;            border-radius: 50%;        box-shadow: 0 0 22upx 0 rgb(252, 94, 20);          position: absolute;         left:15upx;        top:15upx;             font-size: 44upx;    line-height: 90upx;    }    .givebegin {        width: 60upx;        height: 60upx;        background: #F44336;            border-radius: 20%;        box-shadow: 0 0 22upx 0 rgb(252, 94, 20);          position: absolute;         left:30upx;        top:30upx;     }    .x_f{        /* border: 6upx solid #F44336; */        width: 120upx;        height: 120upx;        background: #fff;        border-radius: 50%;        position: absolute;        text-align: center;        top:0;        left: 0;      box-shadow: 0 0 28upx 0 rgb(251, 99, 24);    }        /* 产生动画(向外扩散变大)的圆圈  */    .pulse {        width: 160upx;        height: 160upx;        position: absolute;        border: 12upx solid #F44336;        border-radius: 100%;        z-index: 1;        opacity: 0;        -webkit-animation: warn 2s ease-out;        animation: warn 2s ease-out;        -webkit-animation-iteration-count: infinite;        animation-iteration-count: infinite;        left: -28upx;        top: -28upx;    }                /**     * 动画     */    @keyframes warn {    0% {        transform: scale(0);        opacity: 0.0;    }    25% {        transform: scale(0);        opacity: 0.1;    }    50% {        transform: scale(0.1);        opacity: 0.3;    }    75% {        transform: scale(0.5);        opacity: 0.5;    }    100% {        transform: scale(1);        opacity: 0.0;    }}         </style>

14.拉流演示代码。

<template class='fullscreen'>    <view class='fullscreen'>        <view v-if="beCalling"  class="backols">            <view class='becalling-text'>对方邀请你开始视频聊天</view>            <view class="butlist2">                <view @click="rejectCallHandler" class="buticon2 martp10">                    <image src="../../static/img/netcall-reject.png"></image>                    </view>                    <view @click="acceptCallHandler" class="buticon2 martp10">                        <image src="../../static/img/netcall-accept.png"></image>                        </view>                </view>        </view>        <view v-else class="butlist">                <view @click="switchaudio" class="buticon martp10">                    <image src="../../static/img/netcall-call-voice.png"></image>                                    </view>                <view @click="switchCamera" class="buticon martp10">                    <image src="../../static/img/netcall-revert-camera.png"></image>                                            </view>                <view @click="close" class="buticon martp10">                    <image src="../../static/img/netcall-reject.png"></image>                    </view>                         </view>    </view>        </template><script>    export default {         data() {             return{                beCalling: true,                videourl:'',                width:'',                currentWebview:null,                pushers:'',                video :''          }        },                onLoad: function (options) {                 this.getwebview()//获取webview        },        onUnload() {                },        methods: {                close(){                         this.pusher.pause();//暂停推流                        this.pusher.close()//敞开推流控件                        uni.switchTab({                            url:''                        })                },                getwebview(){                    var pages = getCurrentPages();                    var page = pages[pages.length - 1];                    // #ifdef APP-PLUS                    var getcurrentWebview = page.$getAppWebview();                    console.log(this.pages)                    console.log(this.page)                    console.log(JSON.stringify(page.$getAppWebview()))                    this.currentWebview=getcurrentWebview;                    // #endif                    this.plusReady()//创立LivePusher对象                },                                 plusReady(){                                                    this.pushers =new plus.video.VideoPlayer('video',{                        // src:self.userlist[0].url,                        src:"rtmp://58.200.131.2:1935/livetv/hunantv", //这里替换本人的拉流地址                        top:'0px',                        left:'0px',                        controls:false,                        width: '100%',                        height: uni.getSystemInfoSync().windowHeight-150 + 'px',                        position: 'static'                            });                                     this.currentWebview.append(this.pushers);                 this.pushers.play()                },                           /**             * 切换摄像头             */             switchCamera() {                this.pusher.switchCamera();            },            switchaudio() {                console.log('点击了');            }                        }        }    </script><style>        .backols{        background: rgba(0, 0, 0, 0.74);    height: 100%;    position: absolute;    width: 100%;    }    uni-page{        background:#000000;    }    .butlist{        height: 140upx;        position: absolute;        bottom: 0;        display: flex;        width: 100%;        justify-content: space-around;        padding-top: 20upx;        border-top: 1px solid #fff;    }    .buticon{        height: 120upx;        width: 120upx;        color: #fff;        position: relative;        text-align: center;        margin-bottom: 20upx;    }    .buticon image{        height: 90upx;        width: 90upx;    }    .buticon .mar10{        margin-top: -20upx;    }    .martp10{        margin-top: 10upx;        }    .becalling-text{        text-align: center;        color: #FFFFFF;        font-size: 28upx;        padding: 60upx;        margin-top: 40%;    }    .butlist2{        height: 140upx;        position: absolute;        bottom: 5%;        display: flex;        width: 100%;        justify-content: space-around;        padding-top: 20upx;         }    .buticon2{        height: 120upx;        width: 120upx;        color: #fff;        position: relative;        text-align: center;        margin-bottom: 20upx;    }    .buticon2 image{        height: 110upx;        width: 110upx;    }      .container {      width: 100%;      height: 100%;    }    /* 被叫 */    .becalling-wrapper {      position: relative;      width:100%;      height:800upx;      background-color:#777;      color:#fff;      font-size:40rpx;    }    .becalling-wrapper .becalling-text {      position: absolute;      top:400rpx;      left:50%;      margin-left:-220rpx;    }    .becalling-wrapper .becalling-button-group {      position: absolute;      width:100%;      box-sizing:border-box;      bottom: 100rpx;      padding: 0 40rpx;      display: flex;      flex-direction: row;      justify-content: space-between;    }    .becalling-button-group .button {      width:220rpx;      height:80rpx;      border-radius:10rpx;      justify-content:center;      display:flex;      align-items:center;      font-size:33rpx;      color:#000;    }    .becalling-button-group .reject-button {      background-color:#f00;    }    .becalling-button-group .accept-button {      background-color:rgb(26, 155, 252);    }        .calling-coverview {      width:100%;      height:100rpx;      background-color:#ccc;      color:#fff;      font-size:40rpx;      text-align:center;      line-height:100rpx;    }    /* 视频容器 */    .video-wrapper {      width: 100%;      height: 100%;      padding-bottom: 100rpx;      box-sizing: border-box;      position: relative;      background-color: #000;    }    .control-wrapper {      width: 100%;      box-sizing: border-box;      position: absolute;      bottom: 0;    }    .calling-voerview {      background-color:#ccc;      color:#fff;      height: 160rpx;      font-size: 40rpx;      text-align: center;      line-height: 160rpx;    }    .control-wrapper {      position: fixed;      bottom: 18px;      left:0;      display: flex;      width: 100%;      box-sizing: border-box;      flex-direction:row;      justify-content: space-between;      padding: 0 42rpx;      height: 200rpx;    }    .control-wrapper .item{      width: 92rpx;      height: 92rpx;      margin-top: 100rpx;    }    .netcall-time-text {      position:absolute;      bottom:160rpx;      width:100%;      height: 40rpx;      color:#fff;      font-size:40rpx;      text-align:center;      left:0;    }            .fullscreen{        display: flex;        background: #000000;        height: 100%;        width: 100%;        position: absolute;    }    </style>

15.uni-app模块权限如下。