<返回更多

个人如何搭建Rtmp服务结合uni-app开发直播APP

2020-06-16    
加入收藏

前言:

由于自己有一个IM类的应用,为了完善它所以决定也加上直播和短视频功能。做直播目前有两种方法,一是直接对接第三方的直播服务产品,二是自己搭服务再开发。所以这里也从这两个方法推荐简单的实现方式,阿里云和腾讯云之类的大厂产品就不安利了。

 

选型:

1. 第三方,php+Uni-App+LiveQing

2. 自己开发,PHP+Uni-app+Nginx-rtmp-module

个人如何搭建Rtmp服务结合uni-app开发直播APP

 


个人如何搭建Rtmp服务结合uni-app开发直播APP

 


个人如何搭建Rtmp服务结合uni-app开发直播APP

 

实现流程:

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

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

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

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

 

一、第三方方式

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

1. 找到该官网,选择rtmp直播点播流媒体,下载试用把对应系统解压到自己服务器。

个人如何搭建Rtmp服务结合uni-app开发直播APP

 

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

个人如何搭建Rtmp服务结合uni-app开发直播APP

 

3. 运行前可以打开liveqing.ini进行设置,比如后台登录密码,端口号等。

个人如何搭建Rtmp服务结合uni-app开发直播APP

 

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出现下面图标表示成功。

个人如何搭建Rtmp服务结合uni-app开发直播APP

 

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

个人如何搭建Rtmp服务结合uni-app开发直播APP

 

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

个人如何搭建Rtmp服务结合uni-app开发直播APP

 

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

个人如何搭建Rtmp服务结合uni-app开发直播APP

 

9. 同样为了方便可以使用VLS软件进行拉流或者wowza在线网站测试直播。

个人如何搭建Rtmp服务结合uni-app开发直播APP

 

二、代码实现

不使用第三方的话,就需要搭建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 包名 #解压各个包源码

个人如何搭建Rtmp服务结合uni-app开发直播APP

 

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/
个人如何搭建Rtmp服务结合uni-app开发直播APP

 

6. 检查成功会出现如下,然后make编译一下。

个人如何搭建Rtmp服务结合uni-app开发直播APP

 

7. make install 安装

个人如何搭建Rtmp服务结合uni-app开发直播APP

 

8. 以上操作后表示Nginx编译安装完成,然后cd到根目录,/usr/local/nginx/sbin,如果要测试Nginx是否可以访问。先放行80端口重启防火墙,在sbin下输入./nginx启动Nginx服务。浏览器访问IP地址:80,出现以下表示成功。

个人如何搭建Rtmp服务结合uni-app开发直播APP

 

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地址 端口号,如果出现一下界面说明端口已经通了 。

个人如何搭建Rtmp服务结合uni-app开发直播APP

 

12. 接下来也可以通过OBS推流到该地址,然后用WOWZA拉流进行测试。

rtmp://你的服务器ip:端口(1935)/live #URL填写流的地址
个人如何搭建Rtmp服务结合uni-app开发直播APP

 

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模块权限如下。

个人如何搭建Rtmp服务结合uni-app开发直播APP

 


个人如何搭建Rtmp服务结合uni-app开发直播APP

 


个人如何搭建Rtmp服务结合uni-app开发直播APP

 


个人如何搭建Rtmp服务结合uni-app开发直播APP

 


个人如何搭建Rtmp服务结合uni-app开发直播APP

 


个人如何搭建Rtmp服务结合uni-app开发直播APP
声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>