因为本身有一个IM类的应用,为了完善它因此决定也加上直播和短视频功能。作直播目前有两种方法,一是直接对接第三方的直播服务产品,二是本身搭服务再开发。因此这里也从这两个方法推荐简单的实现方式,阿里云和腾讯云之类的大厂产品就不安利了。(公众号回复“直播”获取源码)nginx
1. 第三方,PHP+Uni-App+LiveQingc++
2. 本身开发,PHP+Uni-app+Nginx-rtmp-modulegit
1. 客户端采集视频流。(开摄像头,录屏等)github
2. 客户端推流到rtmp服务器上。web
3. rtmp推流到某个特定端口。ajax
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 --permanent firewall-cmd add-port=10082/tcp --permanent firewall-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、一、二、三、四、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模块权限以下。