How to Build Rtmp Service and Develop Live APP with uni-app

Preface:

Because you have an IM application, you decide to add live and short video capabilities in order to improve it.There are currently two ways to do live broadcasting, one is to directly connect with the third party's live service products, the other is to develop the services themselves.So here are also two ways to recommend simple implementation, Aliyun and Tencent cloud and other large-scale products will be unsafe.(Public number replies Live to get source code)

 

Selection:

1. Third party, PHP+Uni-App+LiveQing

2. Develop your own, PHP+Uni-app+Nginx-rtmp-module

Implementation process:

1. Client collects video stream.(turn on camera, video, etc.)

2. Client pushes to rtmp server.

3. rtmp is pushed to a specific port.

4. Other clients will pull the video stream to make it live.

 

1. Third-party methods

This time, the third party recommended a platform called LiveQing, which is a little fast, convenient and fully functional.Running their packages on the server provides on-demand service for short videos as well as live broadcasting of mainstream business scenarios.API calls are also included to create, delete, and statistics live broadcasts through interfaces.But for a fee, the package can only be tried out for one month on a physical machine or a cloud server for free.

1. Find the official website, select rtmp live on-demand streaming media, download a trial to unzip the corresponding system to your own server.

2. The directory is as follows, which willStart.shAuthorization is 777.Then. /Start.shRun the file.

3. Open before runningLiveqing.iniMake settings such as background login password, port number, etc.

4. By default, you need to turn on 10080 and 10085, so you need to use the firewall to let go as follows.

systemctl start firewalld.service    // Open Firewall

firewall-cmd add-port=10080/tcp --permanent
firewall-cmd add-port=10082/tcp --permanent

firewall-cmd --reload               // restart

firewall-cmd --list-ports           // View all ports released

5. Port release, then runStart.shThe following icon appears to indicate success.

6. The browser enters the server's external IP:10080 to enter the control panel.

7.Create a live broadcast, set the name and ID, and select Edit to get the push address.

8. In order to test that you can download an OBS software push to this address locally, as long as the push, the live status will be displayed in the live broadcast and click Edit to get the address of the pull.

9. Also for convenience, you can use VLS software to pull streams or wowza online website to test live streaming.

2. Code implementation

Without a third party, you need to set up rtmp services, configure Nginx, APP video capture push, pull and so on.If it is a large platform, you need to divert clusters, etc.Streaming Media Server Dependent Services,1.nginx The server;2.nginx Server installation requires dependent services OpenSSL, pcre, zlib, c++, gcc, etc. The server environment is Centos7.364-bit.

1. Enter the root directory, where mkdir source #creates the source directory, followed by the source code.cd source enters the directory.

2. Download git, yum-y install git, and then download the required packages over the network.

git clone https://github.com/nginx/nginx.git #Download the source code for nginx from the github server
git clone https://github.com/arut/nginx-rtmp-module.git#Download the source code for the RTMP module
wget https://www.openssl.org/source/openssl-1.1.0.tar.gz#Download OpenSSL Source Package
wget https://ftp.pcre.org/pub/pcre/pcre-8.39.tar.gz#Download the PCRE source package
wget http://www.zlib.net/zlib-1.2.11.tar.gz#Download zlib package source

3. tar-zxvf package name #Unzip individual package sources

4. gcc needs to be installed before compiling nginx and required packages, which can be omitted.

yum -y install gcc 			#Ensure dependent gcc installation
yum -y install gcc-c++ 		#Ensure that dependent c++ is installed

5. Then the cd command enters the nginx directory under source and enters the following command.

./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. Check for success as follows, and make compiles it.

7. make install installation

8. This means that the Nginx compilation and installation is complete, then cd to the root directory, /usr/local/nginx/sbin, if you want to test whether Nginx is accessible.Release port 80 to restart the firewall and enter. /nginx under SBIN to start the Nginx service.Browser access IP address: 80, the following indicates success.

9. Configure the rtmp service in the nginx configuration file, remember that the rtmp service is level with the http service, so we need to start the rtmp service at the same location as the http configuration level.

vi /usr/local/nginx/conf/nginx.conf #Modify Profile
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 of the server;
        }
        application hls  {
            live on;
            hls on;
            hls_path /tmp/hls;
        }
    }
}
/usr/local/nginx/sbin/nginx -s reload  #Modify configuration file to restart nginx service

10. The port of the rtmp service above is 1935, so you also need to let port 1935 go as before, check if the security group of the cloud server will also let go, and restart the firewall.

11. The local computer tests whether 1935 is on or off, you can cmd command telnet server IP address port number, if an interface appears, the port is already open.

12. You can then push through OBS to the address and test with WOWZA pull.

rtmp://Your server ip:port (1935)/live#URL fills in the address of the stream

13. Next, we will demonstrate the push writing of 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">Return</view>				
			</view>
			<view @click="switchCamera" class="buticon martp10">
				<image src="../../static/zhiwen-livepush/reversal.png"></image>	
				<view class="mar10">Flip</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">Beautify</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">Count down</view>
				</picker>	
			</view>

			<view @click="upload" class="buticon martp10" v-if="begin">
				<image src="../../static/zhiwen-livepush/yes.png"></image>	
				<view class="mar10">complete</view>				
			</view>			
		</view>
		
		 
    </view>

</template>

<script>
    export default {
		data() {
			return {
			    begin:false,//start recording
				complete:false,//Recording Completed
				pause:false,//Pause Push Flow
				currentWebview:null,
				pusher:null,
				livepushurl:'rtmp://106.52.216.244:10089/hls/1', //Just modify your own push address here
				logininfokey:'',//Logon authentication encryption string,
				homeworkcont:'',//Job Information
				jiexititle:'',//Job Resolution Title
				index: 0,//timing
				indextu:0,//Whether to turn on the timer
				contTime:'',
				array: [{//Topic Label
						"id": 1,
						"cont": "10 second",
						"time": 10
					}, {
						"id": 2,
						"cont": "20 second",
						"time": 20
					}, {
						"id": 3,
						"cont": "30 second",
						"time": 30
					}, {
						"id": 4,
						"cont": "40 second",
						"time": 40
					},{
						"id": 5,
						"cont": "50 second",
						"time": 50
					},
					{
						"id": 6,
						"cont": "60 second",
						"time": 60
					}],
			}
		},
		 
		onShow() {
			 uni.getNetworkType({
				success: function (res) {
					console.log(res.networkType);
					if(res.networkType != 'wifi'){
						uni.showModal({ //Remind users to update
							title: 'Reminder',
							content: 'Current Non Wifi Network, please note that your traffic is sufficient',
							success: (res) => {
								 
							}
						})
					}
				}
			});
			uni.onNetworkStatusChange(function (res) {
				console.log(res.isConnected);
				console.log(res.networkType);
				if(res.networkType != '4g' && res.networkType != 'wifi'){
					uni.showModal({ //Remind users to update
						title: 'Reminder',
						content: 'Current network quality is poor, please switch to 4 G Network or Wifi network',
						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()//Get webview
        },
		methods: {
			//Count down
			bindPickerChange: function(e) {
			    console.log('picker Send selection changes with a carry value of', e.target.value)
			    this.index = e.target.value
				// this.indexs = e.target.value
				this.contTime=this.array[e.target.value].time
				uni.showToast({
					title: 'Please click the red button to start countdown',
					icon:'none',
					duration: 4000,					 
				});
			},
			
			/**
			 * Return
			 */
			back(){
				uni.showModal({
					title: 'Tips',
					content: 'Video not uploaded after returning needs to be re-recorded',
					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('User Click Cancel');
						}
					}
				});
				
			},
			/**
			 * Get the currently displayed 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()//Create LivePusher Object
			},

			/**
			 * Creating a LivePusher object is a push object
			 */ 
			plusReady(){				
				// Create Live Push Control
				this.pusher =new plus.video.LivePusher('pusher',{
					url:'',
					top:'0',
					left:'0px',
					width: '100%',
					height:  uni.getSystemInfoSync().windowHeight-15 + 'px',				
					position: 'absolute',//Static static layout mode, scrolls with window content if page has scrollbars, absolute layout mode, does not scroll with window content if page has scrollbars; default value is "static"
					beauty:'0',//Beauty 0-off 1-on  
					whiteness:'0',//0, 1, 2, 3, 4, 5, 0 do not use whitening, the higher the value, the whiter the degree.
					aspect:'9:16',					
 				});
				console.log(JSON.stringify(this.pusher))
				console.log(JSON.stringify(this.currentWebview))
				//Append created objects to webview
				this.currentWebview.append(this.pusher);
				// Listen for state change events  
				this.pusher.addEventListener('statechange',(e)=>{
					console.log('statechange: '+JSON.stringify(e));
				}, false);
			},			
			//Beauty
			beautiful(){
				console.log(JSON.stringify(this.pusher))
				this.pusher.options.beauty=1
				this.plusReady()//Create LivePusher Object
			},
			// Start pushing
			startPusher(){
				//Determine whether the countdown starts
				if(this.contTime!=''){
					if(this.indextu!=1){
						this.conttimejs()
					}
				}else{
					this.beginlivepush()
				}
			},
			conttimejs(){
				if(this.contTime!=''){
					this.indextu=1;//Open Timing
					if(this.contTime==1){
						console.log("start")
						this.contTime=""
						this.beginlivepush()
						return false
					}
					this.contTime--
					setTimeout(()=>{
						this.conttimejs()
					},1000)
				}
			},
			beginlivepush() {
				this.indextu=0;//Close Timing
				if(this.begin==false){//Unopened Push Flow
					this.begin=true;//Show Video Brake Picture
					// Set up a Push Server***This needs to be retrieved back-end via ajax
					this.pusher.setOptions({
						url:this.livepushurl //Push Address*******************************Set Push Address Here
					});
					this.pusher.start();//Push Open
					uni.showToast({
						title: 'start recording',
						icon:'none',
						duration: 2000,					 
					});
				}else{
					if(this.pause==true){//Pause Push State
						this.begin=true;//Show Video Brake Picture
						this.pause=false;//Push switch set to default state
						this.pusher.resume();//Restore Push Flow
						uni.showToast({
							title: 'start recording',
							icon:'none',
							duration: 2000,					 
						});
					}else{
						this.begin=false;//Turn off recording of brake pictures
						this.pause=true;//Push pause
						this.pusher.pause();;//Pause Push Flow
						uni.showToast({
							title: 'Pause recording',
							icon:'none',
							duration: 2000,					 
						});
						//Prompt for upload
						this.upload()
						
						
					}

				}
			},
			/**
			 * Switch Camera
			 */ 
			switchCamera() {
				this.pusher.switchCamera();
			},
			/**
			 * Complete recording
			 */
			upload(){
				 uni.showModal({
				 	title: 'Tips',
				 	content: 'Are you sure you want to save it?',
				 	success:(res)=> {
				 		if (res.confirm) {
				 			 console.log('User Click Finish');
							 this.pusher.pause();;//Pause Push Flow
							 this.endlivepush()
							 
							/* setTimeout(()=>{
								 this.endlivepush()
							 },1000) */
				 		} else if (res.cancel) {
				 			console.log('User Click Cancel');
				 		}
				 	}
				 });
			}, 
			//End the push flow, where you need to call the back-end interface to submit the end state to the cloud service provider
			endlivepush(){
					uni.showToast({
					icon:'loading',
					title: 'End...',
					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: 'Video Uploading...',
								duration: 5000
							});
							
							setTimeout(()=>{							
								uni.showToast({
									icon:'none',
									title: 'Upload complete',
									duration: 2000
								});
							},5000)
							setTimeout(()=>{							
								uni.redirectTo({
									url: 'setvideotit?id='+this.homeworkcont.id,
								});
							},7000)
				       },
				       error: (data)=>{
				       	//alert(JSON.stringify(data) +'error')			    
				       }
				   });
			},
			 
		},
		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);
	}
	
	/* Circle that animates (expands outward)  */
	.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;
	}
		
	
	/**
	 * animation
	 */
	@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. Draw demo code.

<template class='fullscreen'>
	<view class='fullscreen'>
		<view v-if="beCalling"  class="backols">
			<view class='becalling-text'>You are invited to start a video chat</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()//Get webview
		},
		onUnload() {
		
		},
		methods: {
				close(){
						 this.pusher.pause();//Pause Push Flow
						this.pusher.close()//Turn off the push control
						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()//Create LivePusher Object
				},
				 
				plusReady(){				
				
					this.pushers =new plus.video.VideoPlayer('video',{
						// src:self.userlist[0].url,
						src:"rtmp://58.200.131.2:1935/livetv/hunantv", //Replace your own pull address here
						top:'0px',
						left:'0px',
						controls:false,
						width: '100%',
						height: uni.getSystemInfoSync().windowHeight-150 + 'px',
						position: 'static'		
					});				 
					this.currentWebview.append(this.pushers);
				 this.pushers.play()

				},
		 
		 
		 /**
			 * Switch Camera
			 */ 
			switchCamera() {
				this.pusher.switchCamera();
			},
			switchaudio() {
				console.log('Clicked');
			}
				
		}	
	}
	
</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%;
	}
	/* called */
	.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 Container */
	.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. The uni-app module permissions are as follows.

 

Tags: Nginx JSON firewall OpenSSL

Posted on Tue, 19 May 2020 13:06:49 -0400 by stevebrett