因爲本身有一個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模塊權限以下。