目录
前言
uniapp nvue使用live-pusher组件以及腾讯云实现直播推流,使用video组件实现直播拉流观看,不使用第三方原生sdk,完整示例已兼容ios和安卓
效果预览
一、推流使用live-pusher组件
<live-pusher :beauty="beauty" :style="{width: width + 'px',height:height + 'px'}" id="livePusher" :url="url" mode="FHD"></live-pusher>
二、拉流使用video组件
<video id="myVideo" v-if="mark===1" @error="error" :src="url" style="width: 750rpx;" :style="{height : height + 'px'}" :autoplay="true" :controls="false"></video>
三、前端推流核心代码
this.context = uni.createLivePusherContext('livePusher', this);
var secretdate = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'; //这是需要在腾讯云获取直播的密钥
let date = Date.parse(new Date()) / 1000 + 21600;
//这是需要在腾讯云配置推流的地址,这里采用的是flv的视频格式
this.url = 'rtmp://xxxxxx.livepush.myqcloud.com/live/' + this.live_id + '?txSecret=' + md5(secretdate + this.live_id + date.toString(16).toUpperCase()) + '&txTime=' + date.toString(16).toUpperCase();
四、推流完整示例(包含美颜/相机切换/结束直播反馈效果)
<template>
<div>
<view>
<view class="arrow-boxs">
<image class="user-img" src="../../static/images/logo.png"></image>
<view class="jieshao">
<text class="id">账号:{{accountNum}} </text>
</view>
</view>
<view @click="showClose=true" class="arrow-box">
<image class="tui-img" src="../../static/images/tuichu.png"></image>
</view>
<live-pusher :beauty="beauty" :style="{width: width + 'px',height:height + 'px'}" id="livePusher" :url="url" mode="FHD"></live-pusher>
<!-- 这是功能按钮 -->
<view :style="{width: width + 'px'}" class="but">
<!-- <view class="commodyti-box" :style="{width: width*0.3333 + 'px'}">
<view @click="openMark(0)" class="meiyanbut" >
<image class="but-img" src="../../static/img/zhiboshop2.png" style="height: 40rpx;"></image>
</view>
</view> -->
<view class="commodyti-box" :style="{width: width*0.3333 + 'px'}">
<view @click="openmeiyan" class="meiyanbut">
<image class="but-img" v-if="beauty==0" src="../../static/images/meiyan.png"></image>
<image class="but-img" v-else src="../../static/images/selecr-my.png"></image>
</view>
</view>
<view class="commodyti-box" :style="{width: width*0.3333 + 'px'}">
<view @click="switchCamera" class="meiyanbut">
<image class="but-img" v-if="!Camera" src="../../static/images/qiehuan.png"></image>
<image class="but-img" v-else src="../../static/images/select-chang.png"></image>
</view>
</view>
</view>
<view class="model" v-if="showClose">
<view class="model-pup" @click="showClose=false"></view>
<view class="model-body">
<view class="model-cont">
<text>您确定要结束当前直播吗?</text>
</view>
<view class="model-bot">
<view class="model-btn" @click="showClose=false">
<text>取消</text>
</view>
<view class="model-btn1" @click="navback()">
<text style="color: white;">确定</text>
</view>
</view>
</view>
</view>
</view>
</div>
</template>
<script>
import md5 from '@/js_sdk/md5.js';
var app = getApp();
var liveApi = require('../../utils/live.js');
export default {
data() {
return {
type: "",
scrollTop: 0,
Camera: true,
width: '',
height: '',
accountNum: '',
scrollTops: 0,
url: '',
live_id: '', //这是推流id,用于生成推流地址,拉流能用到
context: null,
markact: true,
arr: [],
livegood: [],
beauty: 9, //美颜等级
showClose: false,
toback: false
};
},
onLoad(option) {
let res = uni.getSystemInfoSync()
this.width = res.windowWidth;
this.height = res.windowHeight;
let userInfo = uni.getStorageSync('userInfo');
this.accountNum = userInfo.nickName;
this.live_id = option.liveBroadcastRecordId;
},
onReady() {
uni.showLoading({
title: '准备中'
})
this.context = uni.createLivePusherContext('livePusher', this);
let that = this;
setTimeout(() => {
that.startLive(1);
uni.hideLoading();
}, 1500)
},
onUnload: function() {
},
onHide: function() {
},
onShow() {
this.context = uni.createLivePusherContext('livePusher', this);
var secretdate = 'xxxxxxxxxxxxxxxxxxxx'; //这是需要在腾讯云获取直播的密钥
let date = Date.parse(new Date()) / 1000 + 21600;
//这是需要在腾讯云配置推流的地址,这里采用的是flv的视频格式
this.url = 'rtmp://xxxxxxx.livepush.myqcloud.com/live/' + this.live_id + '?txSecret=' + md5(secretdate + this.live_id + date.toString(16).toUpperCase()) + '&txTime=' + date.toString(16).toUpperCase();
setTimeout(() => {
this.startLive(0);
}, 1000)
app._post(liveApi.startLiveBroadcast, {
liveBroadcastRecordId: this.live_id,
appUserId: app.globalData.userInfo.appUserId
},
function(e) {
console.log(e);
}
)
},
onBackPress(e) {
if (this.toback) {
return false;
}
// 这里可以自定义返回逻辑,比如下面跳转其他页面
this.showClose = true;
// return true 表示禁止默认返回
return true
},
methods: {
navback() {
var that = this;
app._post(liveApi.endLiveBroadcast, {
liveBroadcastRecordId: this.live_id,
appUserId: app.globalData.userInfo.appUserId
},
function(e) {
console.log(e);
that.toback = true;
uni.navigateBack()
}
)
},
zengMark(type) {
this.$refs.popupr.close();
this.type = type;
this.$refs.popupr.open()
},
openmeiyan() {
// this.$refs.popup.open()
if (this.beauty == 0) {
this.beauty = 9
} else {
this.beauty = 0
}
},
startLive(type) {
let self = this;
this.context.start({
success: a => {
// console.log("livePusher.start:" + JSON.stringify(a));
self.markact = false;
},
fail: a => {}
});
},
stopLive() {
this.context.stop({
success: a => {}
});
},
switchCamera() {
this.context.switchCamera({
success: (a) => {
this.Camera = !this.Camera;
console.log("livePusher.switchCamera:" + JSON.stringify(a));
}
});
}
}
}
</script>
<style>
.shop-list-list {
position: relative;
margin-top: 25rpx;
padding-top: 10rpx;
padding-bottom: 10rpx;
align-items: flex-start;
flex-direction: row;
border-bottom-width: 1rpx;
border-style: solid;
border-color: #F7F7F7;
}
.shop-list-but-box {
position: absolute;
bottom: 26px;
right: 10px;
align-items: center;
justify-content: flex-end;
flex-direction: row;
}
.shop-list-but {
color: white;
border-radius: 20px;
font-size: 13px;
background-color: rgba(255, 65, 0, 1);
padding: 10rpx 20rpx;
margin-right: 10px;
}
.shop-list-img {
margin-right: 20rpx;
margin-left: 20rpx;
width: 150rpx;
height: 150rpx;
}
.shop-list-title {
font-size: 16px;
margin-bottom: 10rpx;
color: rgba(51, 51, 51, 1);
/* color: #333333; */
}
.shop-list-price {
font-size: 16px;
color: #FF4100;
line-height: 30px;
}
.scroll-Ys {
background-color: #ffffff;
height: 650rpx;
width: 100%;
}
.scroll-Yss {
background-color: #ffffff;
height: 800rpx;
width: 100%;
}
.shop-list-box {
position: relative;
background-color: white;
width: 100%;
height: 380px;
border-radius: 10rpx;
}
.mark-title {
height: 80rpx;
font-size: 20px;
align-items: flex-start;
justify-content: center;
}
.mark-tui {
/* position:relative; */
height: 20rpx;
font-size: 20px;
line-height: 100rpx;
right: 0;
border-bottom-width: 1rpx;
border-style: solid;
border-color: #F7F7F7;
}
.fanhui {
width: 30rpx;
height: 30rpx;
margin-top: 40rpx;
text-align: center;
margin-right: 25rpx;
}
.zengjia {
width: 40rpx;
height: 40rpx;
margin-left: 30rpx;
margin-top: 34rpx;
margin-bottom: 10px;
}
.add-shop {
font-size: 16px;
color: #333333;
left: 20rpx;
z-index: 10;
margin-left: 5px;
}
.meiyanbut {
height: 80rpx;
width: 80rpx;
flex-direction: row;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.2);
text-align: center;
}
.but-img {
width: 38rpx;
height: 34rpx;
}
.id {
font-size: 14px;
color: white;
}
.text-box {
font-size: 12px;
color: white;
}
.mark-text {
color: white;
font-size: 20px;
}
.mark {
position: fixed;
top: 0px;
background-color: #9FA3A7;
align-items: center;
justify-content: center;
flex-direction: column;
}
.but {
position: fixed;
bottom: 0px;
flex-direction: row;
align-items: center;
justify-content: center;
height: 160rpx;
}
.commodyti-box {
align-items: center;
justify-content: center;
}
.tui {
width: 200px;
height: 200px;
}
.arrow-box {
position: fixed;
top: 80rpx;
right: 30rpx;
z-index: 10;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.2);
height: 80rpx;
width: 80rpx;
align-items: center;
justify-content: center;
}
.tui-img {
width: 36rpx;
height: 36rpx;
}
.arrow-boxs {
padding: 1%;
align-items: center;
flex-direction: row;
position: fixed;
padding-right: 20rpx;
top: 80rpx;
left: 30rpx;
z-index: 10;
border-radius: 30px;
background-color: rgba(0, 0, 0, 0.2);
/* background-color: #FFFFFF; */
}
.user-img {
margin-right: 5px;
width: 35px;
height: 35px;
border-radius: 50%;
}
.jieshao {
flex-direction: column;
}
.jiesu {
padding: 10px 20px 10px 20px;
color: white;
font-size: 16px;
}
.arrow {
width: 15px;
height: 15px;
}
.model {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 10;
}
.model-pup {
background-color: rgba(0, 0, 0, 0.5);
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: 100;
}
.model-body {
width: 550rpx;
padding: 30rpx;
background-color: white;
border-radius: 20rpx;
}
.model-cont {
font-size: 35rpx;
color: #333333;
}
.model-bot {
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-top: 30rpx;
}
.model-btn {
width: 200rpx;
height: 80rpx;
flex-direction: row;
align-items: center;
justify-content: center;
background-color: #F8F8F8;
border-width: 1rpx;
border-color: #E6E6E6;
border-style: solid;
border-radius: 40rpx;
}
.model-btn1 {
width: 200rpx;
height: 80rpx;
flex-direction: row;
align-items: center;
justify-content: center;
background-color: #0462E8;
border-width: 1rpx;
border-color: #E6E6E6;
border-style: solid;
border-radius: 40rpx;
}
</style>
五. 拉流完整示例(包含回放暂停/播放/预告效果)
<template>
<view>
<view class="head-pop">
<view class="arrow-boxs">
<image class="user-img" :src="headImg"></image>
<view class="jieshao">
<text class="id">{{policeName}} </text>
</view>
</view>
<view @click="toShare()" class="share-box">
<image class="share-img" src="../../static/images/toshare.png"></image>
</view>
</view>
<view @click="navback()" class="arrow-box">
<image class="tui-img" src="../../static/images/tuichu.png"></image>
</view>
<text v-if="mark===2" class="text" :style="{width : width + 'px'}">直播即将开始</text>
<text v-if="mark===3" class="text" :style="{width : width + 'px'}">直播已结束</text>
<view style="height: 200rpx;margin: 40rpx;" v-if="mark!==1">
<view class="btn" @click="navback()">
<text style="color: white;">返回</text>
</view>
</view>
<video id="myVideo" v-if="mark===1" @error="error" :src="url" style="width: 750rpx;" :style="{height : height + 'px'}"
:autoplay="true" :controls="false"></video>
<view class="ym-popup" @click="toPause" v-if="showPop">
<view class="ym-popup-bg" v-if="playVideo">
<image src="../../static/images/play.png" style="width: 120rpx;height: 120rpx;" mode=""></image>
</view>
</view>
</view>
</template>
<script>
var app = getApp();
var liveApi = require('../../utils/live.js');
export default {
data() {
return {
height: '',
width: '',
mark: 1,
bottom: 10,
liveBroadcastRecordId: 0,
url: '',
policeName: '用户昵称',
headImg: '../../static/images/logo.png',
showPop: false,
playVideo: false,
}
},
onLoad(option) {
let params = JSON.parse(decodeURIComponent(option.params));
console.log(params);
this.policeName = params.policeName;
this.headImg = params.headImg;
this.liveBroadcastRecordId = params.liveBroadcastRecordId;
if (params.type === 1) {
this.mark = 1;
//这是需要在腾讯云配置拉流的地址,加上
this.url = 'http://xxxx.xxx.xxx/live/' + params.liveBroadcastRecordId + '.flv';
console.log(this.url);
//进入观看回调
app._post(liveApi.watchLiveBroadcast, {
liveBroadcastRecordId: params.liveBroadcastRecordId,
appUserId: app.globalData.userInfo.appUserId
},
function(e) {
console.log(e);
}
)
} else if (params.type === 2) {
this.mark = 2;
} else {
this.showPop = true;
if (params.livePlaybackUrl) {
this.mark = 1;
this.url = params.livePlaybackUrl;
} else {
this.mark = 3;
}
}
let res = uni.getSystemInfoSync()
this.height = res.windowHeight;
this.width = res.windowWidth;
},
methods: {
error(e) {
if (e) {
this.mark = 3;
}
},
navback() {
uni.navigateBack();
},
toPause() {
this.videoContext = uni.createVideoContext('myVideo');
if (this.playVideo) {
this.videoContext.play();
this.playVideo = false;
} else {
this.videoContext.pause();
this.playVideo = true;
}
},
toShare(){
console.log('share')
}
}
}
</script>
<style>
.mark {
align-items: center;
justify-content: center;
flex-direction: column;
background-color: #FFFFFF;
}
.mark-text {
padding-right: 75px;
font-size: 20px;
color: #333333;
}
.mark-texts {
line-height: 20px;
line-height: 10px;
font-size: 14px;
height: 50px;
width: 300px;
color: white;
background-color: #D4237A;
}
.text {
text-align: center;
padding-top: 400px;
padding-bottom: 50px;
}
.arrow-box {
position: fixed;
top: 80rpx;
right: 30rpx;
z-index: 10;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.2);
height: 80rpx;
width: 80rpx;
align-items: center;
justify-content: center;
}
.tui-img {
width: 36rpx;
height: 36rpx;
}
.head-pop{
padding: 1%;
align-items: center;
flex-direction: row;
position: fixed;
padding-right: 20rpx;
top: 80rpx;
left: 30rpx;
z-index: 10;
border-radius: 30px;
}
.arrow-boxs {
align-items: center;
flex-direction: row;
border-radius: 30px;
background-color: rgba(0, 0, 0, .2);
padding-right: 20rpx;
}
.user-img {
margin-right: 5px;
width: 35px;
height: 35px;
border-radius: 50%;
}
.jieshao {
flex-direction: column;
}
.id {
font-size: 14px;
color: white;
}
.btn {
/* display: flex; */
flex-direction: row;
align-items: center;
justify-content: center;
width: 650rpx;
height: 100rpx;
background-color: #0462E8;
border-radius: 50rpx;
}
.ym-popup {
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
}
.ym-popup-bg {
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
background-color: rgba(0, 0, 0, 0.5);
flex-direction: row;
align-items: center;
justify-content: center;
}
.share-box {
margin-left: 30rpx;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.2);
height: 80rpx;
width: 80rpx;
align-items: center;
justify-content: center;
}
.share-img {
width: 40rpx;
height: 40rpx;
}
</style>