总结
- 延迟比较高大概在5-8秒左右,原因是基于tcp的传输;
- 目前使用情况来看,效果不是太好,假如上传服务断了,没有明显的回调告知;
<template>
<!-- 推送 -->
<div class="live_push">
<x-header :left-options="{backText: '', preventGoBack: true}" @on-click-back="onBack">推流</x-header>
<div id="pusher" class="pusher_wrap"></div>
<!-- <div>
<textarea v-model="url" placeholder="请输入auth_key" style="width:100%;" rows="5"></textarea>
<button @click="handleSetUrl">确定</button>
</div> -->
<footer class="footer_bar" id="parentWebview">
<div @click="handleSwitchMuteStatus">
<svg class="dd_iconfont" aria-hidden="true">
<use xlink:href="#iconaudio"></use>
</svg>
</div>
<div @click="handleSwitchCamera">
<svg class="dd_iconfont" aria-hidden="true">
<use xlink:href="#iconjingtouqiehuan"></use>
</svg>
</div>
<div @click="handlePlayStatus">
<svg class="dd_iconfont" aria-hidden="true">
<use :xlink:href="playStatus === 0 ? '#iconkaishi' : playStatus === 1 ? '#iconbofangzanting' :
playStatus === 2 ? '#iconkaishi' : '#iconbofangzanting'"></use>
</svg>
</div>
<div @click="onBack">
<svg class="dd_iconfont" aria-hidden="true">
<use xlink:href="#icontuichu"></use>
</svg>
</div>
</footer>
</div>
</template>
<script>
import { XHeader } from 'vux';
export default {
components: {
XHeader
},
data() {
return {
playStatus: 0,
muteStatus: true,
pusher: null,
url: 'rtmp://blcs.fnptt.cn/mfs_test/mfs_test_stream?auth_key=1656920156-0-0-6fe9847c28d311a309505f9db6a49e7c'
};
},
mounted() {
this.init();
},
methods: {
init() {
if (!window.plus) return;
this.plusReady();
},
plusReady() {
const pusherHeight = document.body.offsetHeight - 87 + 'px';
const pusherWidth = '100%';
const pusher = new window.plus.video.LivePusher('pusher', {
url: 'rtmp://blcs.fnptt.cn/mfs_test/mfs_test_stream?auth_key=1656920156-0-0-6fe9847c28d311a309505f9db6a49e7c',
top: '47px',
width: pusherWidth,
height: pusherHeight,
position: 'static',
mode: 'HD',
aspect: '9:16',
zindex: 0
});
pusher.setStyles({'zindex': 0});
window.plus.webview.currentWebview().append(pusher);
this.pusher = pusher;
pusher.addEventListener('statechange', (e) => {
e = JSON.stringify(e);
const code = e.detail.code || '';
switch (code) {
case 1001:
console.log(e.type, '已经连接推流服务器');
break;
case 1002:
console.log(e.type, '已经与服务器握手完毕,开始推流');
break;
case 1003:
console.log(e.type, '打开摄像头成功');
break;
case 1004:
console.log(e.type, '录屏启动成功');
break;
case 1005:
console.log(e.type, '推流动态调整分辨率');
break;
case 1006:
console.log(e.type, '推流动态调整码率');
break;
case 1007:
console.log(e.type, '首帧画面采集完成');
break;
case 1008:
console.log(e.type, '编码器启动');
break;
case -1301:
console.log(e.type, '打开摄像头失败');
this.nativeUIToast('打开摄像头失败', 'middle');
break;
case -1302:
console.log(e.type, '打开麦克风失败');
this.nativeUIToast('打开麦克风失败', 'middle');
break;
case -1303:
console.log(e.type, '视频编码失败');
this.nativeUIToast('视频编码失败', 'middle');
break;
case -1304:
console.log(e.type, '音频编码失败');
this.nativeUIToast('音频编码失败', 'middle');
break;
case -1305:
console.log(e.type, '不支持的视频分辨率');
this.nativeUIToast('不支持的视频分辨率', 'middle');
break;
case -1306:
console.log(e.type, '不支持的音频采样率');
this.nativeUIToast('不支持的音频采样率', 'middle');
break;
case -1307:
console.log(e.type, '网络断连,且经多次重连抢救无效,更多重试请自行重启推流');
this.nativeUIToast('网络断连,且经多次重连抢救无效,更多重试请自行重启推流', 'middle');
break;
case -1308:
console.log(e.type, '开始录屏失败,可能是被用户拒绝');
this.nativeUIToast('开始录屏失败,可能是被用户拒绝', 'middle');
break;
case -1309:
console.log(e.type, '录屏失败,不支持的Android系统版本,需要5.0以上的系统');
this.nativeUIToast('录屏失败,不支持的Android系统版本,需要5.0以上的系统', 'middle');
break;
case -1310:
console.log(e.type, '录屏被其他应用打断了');
this.nativeUIToast('录屏被其他应用打断了', 'middle');
break;
case -1311:
console.log(e.type, 'Android Mic打开成功,但是录不到音频数据');
this.nativeUIToast('Android Mic打开成功,但是录不到音频数据', 'middle');
break;
case -1312:
console.log(e.type, '录屏动态切横竖屏失败');
this.nativeUIToast('录屏动态切横竖屏失败', 'middle');
break;
case 1101:
console.log(e.type, '网络状况不佳:上行带宽太小,上传数据受阻');
this.nativeUIToast('网络状况不佳', 'middle');
break;
case 1102:
console.log(e.type, '网络断连, 已启动自动重连');
this.nativeUIToast('网络断连, 已启动自动重连', 'middle');
break;
case 1103:
console.log(e.type, '硬编码启动失败,采用软编码');
break;
case 1104:
console.log(e.type, '视频编码失败');
this.nativeUIToast('视频编码失败', 'middle');
break;
case 1105:
console.log(e.type, '新美颜软编码启动失败,采用老的软编码');
break;
case 1106:
console.log(e.type, '新美颜软编码启动失败,采用老的软编码');
break;
case 3001:
console.log(e.type, 'RTMP -DNS解析失败');
this.nativeUIToast('RTMP -DNS解析失败', 'middle');
break;
case 3002:
console.log(e.type, 'RTMP服务器连接失败');
this.nativeUIToast('RTMP服务器连接失败', 'middle');
break;
case 3003:
console.log(e.type, 'RTMP服务器握手失败');
this.nativeUIToast('RTMP服务器握手失败', 'middle');
break;
case 3004:
console.log(e.type, 'RTMP服务器主动断开,请检查推流地址的合法性或防盗链有效期');
this.nativeUIToast('RTMP服务器主动断开', 'middle');
break;
case 3005:
console.log(e.type, 'RTMP 读/写失败');
this.nativeUIToast('RTMP 读/写失败', 'middle');
break;
default:
console.log(e.type, '未知错误');
this.nativeUIToast('未知错误', 'middle');
break;
};
}, false);
pusher.addEventListener('netstatus', (e) => {
e = JSON.stringify(e);
}, false);
pusher.addEventListener('error', (e) => {
e = JSON.stringify(e);
const code = e.detail.code || '';
switch (code) {
case 1001:
this.nativeUIToast('用户禁止使用摄像头', 'middle');
break;
case 1002:
this.nativeUIToast('用户禁止使用录音', 'middle');
break;
default:
this.nativeUIToast(e.detail.message || '未知错误', 'middle');
break;
};
}, false);
},
nativeUIToast(message) {
window.plus.nativeUI.toast(message, {
type: 'text',
verticalAlign: 'center'
});
},
preview() {
this.pusher.preview();
},
setOptions(obj) {
this.pusher.setOptions({ ...obj });
},
startPusher() {
this.pusher.start(() => {
this.playStatus = 1;
console.log('Start pusher success!');
}, (error) => {
console.log('Start pusher failed: ' + JSON.stringify(error));
this.nativeUIToast('Start pusher failed: ' + JSON.stringify(error), 'middle');
});
},
stopPusher() {
this.pusher.stop();
},
pausePusher() {
this.pusher.pause();
},
resumePusher() {
this.pusher.resume();
},
switchCamera() {
this.pusher.switchCamera();
},
snapshot() {
this.pusher.snapshot(() => {
console.log('Snapshot pusher success!');
}, (error) => {
console.log('Snapshot pusher failed: ' + JSON.stringify(error));
this.nativeUIToast('Snapshot pusher failed: ' + JSON.stringify(error), 'middle');
});
},
close() {
if (!this.pusher) return;
this.pusher.close();
},
onBack() {
if (window.plus) {
window.plus.nativeUI.confirm('确定的退出当前直播吗?', (e) => {
if (e.index === 0) {
this.close();
this.$router.back();
}
}, {
'title': '提示',
'buttons': ['确定', '取消'],
'verticalAlign': 'center'
});
} else {
this.$router.back();
};
},
handlePlayStatus() {
var playStatus = this.playStatus;
switch (playStatus) {
case 0:
this.startPusher();
this.playStatus = 1;
break;
case 1:
this.pausePusher();
this.playStatus = 2;
break;
case 2:
this.resumePusher();
this.playStatus = 3;
break;
case 3:
this.pausePusher();
this.playStatus = 2;
break;
};
},
handleSwitchCamera() {
this.switchCamera();
},
handleSwitchMuteStatus() {
if (this.muteStatus) {
this.setOptions({muted: false});
this.muteStatus = false;
this.nativeUIToast('开启静音模式');
} else {
this.setOptions({muted: true});
this.muteStatus = true;
this.nativeUIToast('开启声音模式');
}
},
handleSetUrl() {
this.setOptions({url: this.url});
}
}
};
</script>
<style lang="scss" scoped>
.live_push{
position: relative;
padding-top: 47px;
height: 100vh;
overflow: hidden;
box-sizing: border-box;
background: #ddd;
.pusher_wrap{
width: 100vw;
height: calc(100vh - 47px);
background-color:#3a3a3a;
position: static;
z-index: 1;
}
.footer_bar{
position: fixed;
bottom: 0;
z-index: 1000;
width: 100%;
color: #fff;
display: flex;
justify-content: space-evenly;
padding: 5px;
z-index: 100px;
.dd_iconfont{
font-size: 30px;
}
}
}
</style>