QQ 咨询
谢谢打赏
微信交流
432

个人如何搭建Rtmp服务结合uni-app开发直播APP
时间: 2020-05-20 09:28:55  作者:北桥苏  阅读:(9)

前言:

    由于自己有一个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 --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、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. 拉流演示代码。