如何搭建Rtmp服务结合uniapp开发直播APP

编程

 

选型:

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. 拉流演示代码。

<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服务结合uniapp开发直播APP 的全部内容, 来源链接: utcz.com/z/516609.html

回到顶部