FenXiNspUniapp/packDetail1/pages/device/index1.vue

2492 lines
94 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page-live">
<view class="video-container">
<live-player id="livePlayer" :autoplay="true" @error="error" @statechange="statechange" mode="RTC" :src="isHD ? videoHDSrc :videoSrc" @tap.stop.prevent="obVideoTap" :muted="!openSound" :class="funllScreen ? 'video-item full-screen' : 'video-item'" @fullscreenchange="fullscreenChange" :object-fit="objectFit">
<!-- loading状态 -->
<cover-view class="video-loading-container" @tap.stop.prevent="onVideoTap" :hidden="videoLoadingStatus === 100">
<cover-image class="video-loading-bg" src="../../static/live/images/live/live_loading_bg.png"></cover-image>
<!-- 停止状态 -->
<cover-view class="video-loaing video-ready" :hidden="videoLoadingStatus !== 0">
<cover-image class="loading-gif" src="../../static/live/images/live/landscape_play.png" @tap="handlePlay"></cover-image>
</cover-view>
<!-- 加载资源中状态 -->
<cover-view class="video-loaing" :hidden="videoLoadingStatus === 0 || videoNetWorkError">
<cover-image class="loading-gif" src="../../static/live/images/live/loading_grey.gif"></cover-image>
<cover-view class="video-loading-text">视频安全传输中...{{ videoLoadingStatus }}%</cover-view>
</cover-view>
</cover-view>
<!-- 开启隐私遮蔽状态 -->
<cover-view class="video-loading-container" :hidden="!(panelStatus == 4)">
<cover-image class="video-loading-bg" src="../../static/live/images/live/live_loading_bg.png"></cover-image>
<cover-view class="video-loaing">
<cover-image class="loading-gif" src="../../static/live/images/live/preview_fail_yinsi.png"></cover-image>
<cover-view class="video-loading-text">已开启隐私遮蔽</cover-view>
</cover-view>
</cover-view>
<!-- 加载资源失败状态 -->
<cover-view class="video-loading-container" :hidden="!videoNetWorkError">
<cover-image class="video-loading-bg" src="../../static/live/images/live/live_loading_bg.png"></cover-image>
<cover-view class="video-loaing">
<cover-image class="loading-gif" src="../../static/live/images/live/preview_fail.png"></cover-image>
<cover-view class="video-loading-text">网络不稳定加载失败</cover-view>
<cover-view class="video-loading-text reTry" @tap="handlePlay">重试</cover-view>
</cover-view>
</cover-view>
<!-- 设备不在线状态 -->
<cover-view class="video-loading-container" :hidden="!deviceOffline">
<cover-image class="video-loading-bg" src="../../static/live/images/live/live_loading_bg.png"></cover-image>
<cover-view class="video-loaing">
<cover-image class="loading-gif" src="../../static/live/images/live/preview_fail_offline.png"></cover-image>
<cover-view class="video-loading-text">设备不在线</cover-view>
<cover-view class="video-loading-text">离线时间{{ deviceOfflineTime }}</cover-view>
</cover-view>
</cover-view>
<!-- 清晰度 -->
<cover-view :class="showHDSelect ? 'hd-select' : 'hd-select hide'">
<cover-view :class="isHD ? 'hd-option active' : 'hd-option'" @tap.stop.prevent="changeVideoHD">高清</cover-view>
<cover-view :class="!isHD ? 'hd-option active' : 'hd-option'" @tap.stop.prevent="changeVideoNormal">标清</cover-view>
</cover-view>
<!-- 竖屏模式 -->
<cover-view :class="(fullScreen ? 'hidden' : '') + ' video-controls-container'"></cover-view>
<cover-view :class="(fullScreen || !showVideoControls ? 'hidden' : '') + ' video-controls-container'">
<cover-image
class="controls-img"
:src="playVideo ? '../../static/live/images/video_icon_stop.png' : '../../static/live/images/video_icon_play.png'"
@tap.stop.prevent="playVideo ? handleStop() : handlePlay()"
></cover-image>
<cover-image
class="controls-img"
:src="!openSound ? '../../static/live/images/video_icon_closesound.png' : '../../static/live/images/video_icon_opensound.png'"
@tap.stop.prevent="handleSound"
></cover-image>
<cover-image
class="controls-img hd"
:src="isHD ? '../../static/live/images/video_icon_hd.png' : '../../static/live/images/video_icon_bq.png'"
@tap.stop.prevent="handleHD"
></cover-image>
<cover-image class="controls-img" @tap.stop.prevent="fullScreenFun" src="../../static/live/images/video_icon_full.png"></cover-image>
</cover-view>
<!-- 横屏模式 -->
<cover-view :class="(!fullScreen ? 'hidden' : '')+' video-back-container'">
<cover-image class="back-img" src="../../static/live/images/nav_icon_back_full.png" @tap.stop.prevent="unfullScreen"></cover-image>
<cover-view class="back-device">{{ deviceName }}</cover-view>
</cover-view>
<cover-view :class="((!fullScreen || !showVideoControls) ? 'hidden' : '')+'video-controls-container'">
<cover-image
class="controls-img"
:src="playVideo ? '../../static/live/images/live_icon_stop_full.png' : '../../static/live/images/live_icon_play_full.png'"
@tap.stop.prevent="playVideo ? 'handleStop' : 'handlePlay'"
></cover-image>
<cover-image
class="controls-img"
:src="!openSound ? '../../static/live/images/live_icon_unsound_full.png' : '../../static/live/images/live_icon_sound_full.png'"
@tap.stop.prevent="handleSound"
></cover-image>
<cover-image class="controls-img" @tap.stop.prevent="ToggleObjectFit" src="../../static/live/images/live_icon_adapt_full.png"></cover-image>
</cover-view>
<cover-view :class="'ptz-limit ' + (ptzLimit ? ptzLimit : 'hidden')"></cover-view>
</live-player>
</view>
<!-- 控制面板 -->
<view :hidden="panelStatus == 1 || panelStatus == 2 || panelStatus == 5">
<view class="panel-container">
<view class="panel-item-container" v-for="(item, index) in list" :key="index">
<view
v-if="item.id != 'capture'"
:class="item.status === 0 ? 'panel-item' : item.status === 1 ? 'panel-item panel-item-active' : 'panel-item panel-item-disable'"
:data-value="item.id"
@tap.stop.prevent="tapPanel"
>
<view class="panel-image-container">
<image class="panel-image" :src="item.status === -1 ? item.abnormalImage : item.imageUrl"></image>
</view>
<view class="panel-name">{{ item.name }}</view>
</view>
<view
v-if="item.id == 'capture'"
:class="playVideo && videoLoadingStatus == 100 ? 'panel-item' : 'panel-item panel-item-disable'"
:data-value="item.id"
@tap.stop.prevent="tapPanel"
>
<view class="panel-image-container">
<image class="panel-image" :src="playVideo && videoLoadingStatus == 100 ? item.imageUrl : item.abnormalImage"></image>
</view>
<view class="panel-name">{{ item.name }}</view>
</view>
</view>
</view>
</view>
<!-- 云台 -->
<view :hidden="panelStatus !== 1" class="ptz-container">
<view class="close">
<image @tap.stop.prevent="handleBackPanel" class="close-img" src="../../static/live/images/yuntai/close.png"></image>
</view>
<view class="ptz-img-container" id="ptz-img-container" @touchstart.stop.prevent="handlePtzTouchStart" @touchend.stop.prevent="handlePtzTouchEnd">
<image class="ptz-img" :src="currentPtzImg"></image>
</view>
</view>
<!-- 语音播报 -->
<view class="voice-container" :hidden="panelStatus !==2">
<view class="close">
<image @tap.stop.prevent="handleBackPanel" class="close-img" src="../../static/live/images/yuntai/close.png"></image>
</view>
<view class="voice-list-title">默认语音</view>
<scroll-view class="scroll-view" :enable-flex="true" :scroll-y="true" style="height: 100px" @scrolltolower="defaultScrollLower">
<view class="voice-list-container" v-for="(item, index) in defaultVoiceList" :key="index">
<view class="voice-list-item" @tap.stop.prevent="playVoice" :data-value="item" data-type="default">
<view class="name">{{ item.voiceName }}</view>
<image
class="gif"
:src="activeDefaultVoiceName == item.voiceName ? '../../static/live/images/voice.gif' : '../../static/live/images/voice_normal.png'"
></image>
</view>
</view>
<view class="list-loading" :hidden="!defaultVoiceListLoading">正在载入更多...</view>
<view class="list-loading" :hidden="(defaultVoiceListLoading || !defaultVoiceNoMore)">已加载全部</view>
</scroll-view>
<view class="voice-list-title">录制语音</view>
<scroll-view class="scroll-view" :enable-flex="true" :scroll-y="true" style="height: 100px" @scrolltolower="customScrollLower">
<view class="voice-list-container" v-for="(item, index) in customVoiceList" :key="index">
<view class="voice-list-item" @tap.stop.prevent="playVoice" :data-value="item" data-type="custom">
<view class="name">{{ item.voiceName }}</view>
<image
class="gif"
:src="activeCustomVoiceName == item.voiceName ? '../../static/live/images/voice.gif' : '../../static/live/images/voice_normal.png'"
></image>
</view>
</view>
<view class="list-loading" :hidden="!customVoiceListLoading">正在载入更多...</view>
<view class="list-loading" :hidden="(customVoiceListLoading || !customVoiceNoMore)">已加载全部</view>
</scroll-view>
<button class="btn primary" :disabled="sendingOnceVoice" @touchstart.stop.prevent="speakStart" @touchend.stop.prevent="speakEnd">
{{ sendingOnceVoice ? '请稍后...' : recoderTime === 60 ? '按住说话' : '松开结束' }}
</button>
</view>
<!-- 对讲 -->
<view :hidden="panelStatus !== 5" class="talk-container">
<view class="talk-container-title">
<image class="closed-icon" src="../../static/live/image/exit.svg" @tap.stop.prevent="exitTalk" />
<view class="status-contant">
<view class="loading" v-if="talkStatus == 1"></view>
<view class="title" v-if="talkStatus == 2">对讲中</view>
</view>
</view>
<view class="talk-container-contant">
<view v-if="talkStatus == 1" class="talk-container-loading"></view>
<image v-if="talkStatus == 2" class="talk-container-talking" src="../../static/live/image/quanshuanggong.png" />
<view v-if="talkStatus == 3" class="talk-container-fail">
<image class="status-image" src="../../static/live/image/quanshuanggong-fail.png" />
<view class="status-title">启动失败</view>
<text class="status-subtitle">请检查你的网络并点击重试</text>
<text class="status-subtitle">再启动对讲</text>
<button class="btn" @tap.stop.prevent="tryTalkAgain">重试</button>
</view>
<view v-if="talkStatus == 4" class="talk-container-fail">
<image class="status-image" src="../../static/live/image/quanshuanggong-fail2.png" />
<view class="status-title">启动失败</view>
<text class="status-subtitle">设备对讲中,请稍后重试</text>
<button class="btn" @tap.stop.prevent="tryTalkAgain">重试</button>
</view>
</view>
<!-- 推流 -->
<view class="push-container" v-if="talkStatus == 2">
<live-pusher
id="livePusher"
class="live-pusher-container"
@error="error"
:url="pushUrl"
mode="RTC"
audio-quality="low"
@statechange="pusherStateChange"
:enable-camera="true"
:enable-mic="openSound"
@tap.stop.prevent="parseEventDynamicCode($event, pushVideo ? 'stopPush' : 'startPush')"
></live-pusher>
</view>
<!-- 拉流 -->
<view class="video-container" :data-playId="item.id" @tap="currentPlayContainerFun">
<live-player id="talk-liveplayer" @error="error" @statechange="statechange" mode="RTC" :src="item.playSrc" class="video-item" autoplay></live-player>
</view>
</view>
<!-- 模态框 -->
<mp-dialog :title="dialogTitle" :show="dialogShow" @buttontap="tapDialogButton" :buttons="buttons">
<view>{{ dialogContent }}</view>
</mp-dialog>
</view>
</template>
<script>
import mpDialog from '../../component/dialog/dialog';
import { DateFormat } from '../../common/utils';
import { OPEN_DOMAIN, PTZ_START, PTZ_STOP } from '../../config/config';
// 生成uuid
const wxuuid = function () {
var s = [];
var hexDigits = '0123456789abcdef';
for (var i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 16), 1);
}
s[14] = '4'; // bits 12-15 of the time_hi_and_version field to 0010
s[19] = hexDigits.substr((s[19] & 3) | 8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
s[8] = s[13] = s[18] = s[23] = '-';
var uuid = s.join('');
return uuid;
};
//index.js
//获取应用实例
const app = getApp();
const recorderManager = uni.getRecorderManager();
const options = {
duration: 60000,
//指定录音的时长,单位 ms最大为10分钟600000默认为1分钟60000
sampleRate: 16000,
//采样率
numberOfChannels: 1,
//录音通道数
format: 'mp3' //音频格式,有效值 aac/mp3
};
var livePlayerContext;
export default{
components: {
mpDialog
},
data(){
return{
scene: 1001,
accessToken: '',
deviceSerial: '',
channelNo: '1',
list: [
{
id: 'talk',
name: '对讲',
status: -1,
imageUrl: '../../static/live/image/talk.svg',
abnormalImage: '../../static/live/image/talk-disabled.svg'
},
{
id: 'ptz',
name: '云台控制',
status: -1,
imageUrl: '../../static/live/image/yuntai.svg',
abnormalImage: '../../static/live/image/yuntai-disabled.svg'
},
{
id: 'voice',
name: '语音播报',
status: -1,
imageUrl: '../../static/live/image/record.svg',
abnormalImage: '../../static/live/image/record-disable.svg'
},
{
id: 'playback',
name: '回看',
status: 0,
imageUrl: '../../static/live/image/backward.svg',
abnormalImage: '../../static/live/image/backward.svg'
},
{
id: 'capture',
name: '截屏',
status: -1,
imageUrl: '../../static/live/image/picture-shoot.svg',
abnormalImage: '../../static/live/image/pictureshoot-disabled.svg'
},
{
id: 'mirror',
name: '镜像翻转',
status: -1,
imageUrl: '../../static/live/image/jingxiang.svg',
abnormalImage: '../../static/live/image/jingxiang-disabled.svg'
},
{
id: 'cover',
name: '镜头遮蔽',
status: -1,
imageUrl: '../../static/live/image/yinsi.svg',
abnormalImage: '../../static/live/image/yinsi-disabled.svg'
}
],
videoSrc:"",
videoHDSrc: "",
panelStatus: 0, //0: 展示面板 1进入云台 2-进入语音播报 3-进入镜像翻转 4-进入镜头遮蔽 5-对讲,
ptzDisabled: true,
voiceDiasbled: true,
mirrorDisabled: true,
mirrorInterval: false,
coverDisabled: true,
coverInterval: false,
showVideoControls: true,
autoHideTimer: undefined,
videoLoadingStatus: 0,
playVideo: false,
videoNetWorkError: false,
objectFit:'contain',
openSound: true,
isHD: true,
showHDSelect: false,
fullScreen: false,
ptzStatus: 0, //0-初始化 1-top noraml 2-downnoraml 3-left normal 4-right normal 5-top noraml 6-down limit 7-left limit 8-right limit
ptzLoading: false,
ptzLimit: '',
deviceOffline: false,
deviceOfflineTime: new Date(),
deviceName: '',
currentPtzImg: '../../static/live/images/yuntai/normal.png',
// 语音播报
activeDefaultVoiceName: '',
activeCustomVoiceName: '',
defaultVoiceList: [], // 默认语音列表
defaultVoiceTotal: 0, // 默认语音总数
defaultVoicePage: 0, // 默认语言当前页
defaultVoiceListLoading: false,
defaultVoiceNoMore: false,
customVoiceList: [], // 默认语音列表
customVoiceTotal: 0, // 默认语音总数
customVoicePage: 0, // 默认语言当前页
customVoiceListLoading: false,
customVoiceListNoMore: false,
recoderTime:60,
recoderTimer: undefined,
sendingOnceVoice: false,
dialogTitle: '',
dialogContent: '',
buttons: [{text: '知道了'}],
dialogShow: false,
pathParam: '',
// 对讲过程
talkStatus: 0, //0-未开始/结束1-加载中2-进行中, 3-启动失败, 4-设备对讲中
token1: '', // 用于邀请设备入会
token2: '', // 用于强制设备退会
websocketUrl: '',
customId: 'isZhangqianwei', // 开发者自定义用户id以此换取clientId
clientId: 993089712, // 注意通讯过程中真实的clientId此处仅供调试无需赋值
roomId: 0, // 注意:需要加入的房间号,需外部提供
joinPassword: '', // 注意:加入房间的密码,需外部提供
vtmHttpsAddress: '',
vtmIp: '', // 此处仅供调试,无需赋值
vtmPort: 8554, // 此处仅供调试,无需赋值
rtmpAddress: '', // push/play地址
rtmpPort: '', // 推流拉流端口地址
lockReconnect: false, // websocket重连
limit: 0,
timer: null,
pushVideo: true, // 推流
pushVideoContext: null,
pushUrl: '', // 推流地址
livePlayerContext: [
], // 播放节点
showVideoControls: false,
videoNetWorkError: false,
playSrc: '', // 播放地址
code:'',
token:''
}
},
onShow() {
console.log('show');
var launchOptions = uni.getEnterOptionsSync();
const pathParam = launchOptions.query.scene;
console.log('pathParam:', pathParam);
this.pathParam=pathParam
if (pathParam) {
this.getWxaInfo();
}
// Do something when show.
this.checkNetWork();
},
onHide() {
// Do something when hide.
console.log('hide');
this.panelStatus=0,
this.pathParam=''
console.log(this.pathParam);
},
onLoad(query) {
var launchOptions = uni.getLaunchOptionsSync();
const { accessToken, deviceSerial, channelNo, scene } = query;
console.log(query,'参数')
this.scene=parseInt(scene, 10) || launchOptions.scene,
this.accessToken=query.accessToken,
console.log(this.accessToken,'token')
this.deviceSerial=query.deviceSerial,
this.channelNo=1,
this.panelStatus=0 // todo -0
this.getPlayUrl();
this.getDeviceInfo();
this.getDeviceCoverInfo();
this.showOneButtonDialog=true
// 录音模块
recorderManager.onStart(() => {
console.log('recorder start');
});
recorderManager.onPause(() => {
console.log('recorder pause');
this.speakEnd();
});
recorderManager.onInterruptionBegin(this.speakEnd);
recorderManager.onStop((res) => {
console.log('recorder stop', res);
const { recoderTime } = this;
const { tempFilePath } = res;
if (recoderTime >= 59) {
this.recoderTime=60
clearTimeout(this.recoderTimer);
return false;
}
this.sendingOnceVoice=true
uni.uploadFile({
url: `${OPEN_DOMAIN}/api/lapp/voice/sendonce`,
//仅为示例,非真实的接口地址
filePath: tempFilePath,
//tempFilePaths[0],
name: 'voiceFile',
formData: {
accessToken: accessToken,
deviceSerial: deviceSerial,
channelNo: channelNo
},
header: {
'content-type': 'amultipart/form-data' // 默认值
},
success: (res) => {
let data = res.data;
if (!data.code) {
data = JSON.parse(data);
}
console.log(data);
if (data.code == 200) {
console.log('发送成功');
} else if (data.code == '111012') {
// 设备正忙
uni.showToast({
title: '语音下发失败',
icon: 'none'
});
} else if (data.code == '20007') {
// 设备正忙
uni.showToast({
title: '设备不在线',
icon: 'none'
});
} else if (data.code == '20008') {
// 设备正忙
uni.showToast({
title: '设备响应超时',
icon: 'none'
});
} else {
uni.showToast({
title: data.msg,
icon: 'none'
});
}
this.recoderTime=60
//do something
},
fail: (res) => {
uni.showToast({
title: '网络异常',
icon: 'none'
});
},
complete: () => {
this.sendingOnceVoice=false
}
});
});
recorderManager.onFrameRecorded((res) => {
const { frameBuffer } = res;
console.log('frameBuffer.byteLength', frameBuffer.byteLength);
});
//视频
livePlayerContext = uni.createLivePlayerContext('livePlayer');
console.log('livePlayerContext', livePlayerContext);
},
/**
* 用户点击右上角分享
*/
onShareAppMessage(res){
const { accessToken, deviceSerial, channelNo } = this;
if (res.from === 'button') {
// 来自页面内转发按钮
console.log(res.target);
this.panelStatus=0
}
return {
title: '小程序',
path: '/packDetail1/pages/device/index1?accessToken=' + accessToken + '&deviceSerial=' + deviceSerial + '&channelNo=' + channelNo + '&scene=1007'
};
},
methods: {
onLaunch() {
console.log(onLaunch);
},
onError(msg) {
console.log(msg);
},
initPannel(moduleId) {},
checkNetWork() {
const _this = this;
uni.getNetworkType({
success(res) {
const networkType = res.networkType;
if (!networkType || networkType === 'none') {
uni.showToast({
title: '当前网络异常',
icon: 'none',
duration: 2000
});
}
}
});
},
getPlayUrl() {
const { accessToken, deviceSerial, channelNo } = this;
var _this = this;
uni.request({
url: `${OPEN_DOMAIN}/api/lapp/v2/live/address/get`,
//仅为示例,并非真实的接口地址
method: 'POST',
data: {
accessToken: accessToken,
deviceSerial: deviceSerial,
// deviceSerial:'J27717024',
channelNo: channelNo,
supportH265:1,
expireTime: 86400,
quality: 2,
protocol: 3
},
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
success: (res) => {
console.log(res.data);
const { list } = this;
if (res.data.code == 200 && res.data.data && res.data.data.url) {
var result = res.data;
if (result.code == 200) {
this.videoSrc=result.data.url
this.videoHDSrc=result.rtmpHd
} else {
list[0].status = -1;
list[1].status = -1;
list[2].status = -1;
list[4].status = -1;
list[5].status = -1;
list[6].status = -1;
_this.list=list,
_this.dialogContent=result.msg,
_this.showVideoControls=false
}
} else if (res.data.code == '20001' || res.data.code == '20002' || res.data.code == '20018' || res.data.code == '10001') {
// 设备不存在 / 不属于用户
this.dialogTitle='获取播放地址失败',
this.dialogContent='该用户不拥有该设备',
this.dialogShow=true
} else {
console.log('获取播放地址失败');
// _this.openPlayUrl();
}
}
});
uni.request({
url: `${OPEN_DOMAIN}/api/lapp/v2/live/address/get`,
//仅为示例,并非真实的接口地址
method: 'POST',
data: {
accessToken: accessToken,
deviceSerial: deviceSerial,
channelNo: channelNo,
expireTime: 86400,
quality: 1,
supportH265:1,
protocol: 3
},
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
success: (res) => {
console.log(res.data);
const { list } = this;
if (res.data.code == 200 && res.data.data && res.data.data.url) {
var result = res.data;
if (result.code == 200) {
//videoSrc: result.rtmp,
this.videoHDSrc=result.data.url
} else {
llist[0].status = -1;
list[1].status = -1;
list[2].status = -1;
list[4].status = -1;
list[5].status = -1;
list[6].status = -1;
_this.list=list,
_this.dialogContent=result.msg,
_this.showVideoControls=false
}
} else {
console.log('获取高清播放地址失败');
// _this.openPlayUrl();
}
}
});
},
/*
* 获取设备基本信息
*/
getDeviceCoverInfo() {
const { accessToken, deviceSerial, channelNo } = this;
console.log(accessToken, deviceSerial, channelNo);
var _this = this;
uni.request({
url: `${OPEN_DOMAIN}/api/lapp/device/scene/switch/status`,
method: 'POST',
data: {
accessToken,
deviceSerial,
channelNo
},
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
success: (res) => {
console.log(res.data);
if (res.data.code == 200 && res.data.data) {
const result = res.data.data;
let list = this.list;
if (result.enable == 1) {
// 当前镜头遮蔽中
list[0].status = -1;
list[1].status = -1;
list[2].status = -1;
list[4].status = -1;
list[5].status = -1;
list[6].status = 1;
this.videoNetWorkError=false,
this.showVideoControls=false,
this.panelStatus=4,
this.videoLoadingStatus=100,
this.list=list
console.log('panelStatus', this.panelStatus);
}
}
},
error: (err) => {
console.log(err);
}
});
},
/*
* 获取设备基本信息
*/
getDeviceInfo() {
const { accessToken, deviceSerial, channelNo, showVideoControls, videoNetWorkError, videoLoadingStatus } = this;
var _this = this;
uni.request({
url: `${OPEN_DOMAIN}/api/lapp/device/info`,
//仅为示例,并非真实的接口地址
method: 'POST',
data: {
accessToken,
deviceSerial,
channelNo
},
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
success: (res) => {
console.log(res.data);
if (res.data.code == 200 && res.data.data) {
let list = this.list;
var result = res.data.data;
_this.deviceName=result.deviceName,
_this.deviceOffline=result.status !== 1,
_this.videoNetWorkError=result.status !== 1 ? false : videoNetWorkError,
_this.showVideoControls=result.status !== 1 ? false : showVideoControls,
_this.deviceOfflineTime=DateFormat(new Date(result.updateTime), 'yyyy-MM-dd hh:mm:ss'),
_this.deviceIsEncrypt=result.isEncrypt,
_this.videoLoadingStatus=result.isEncrypt === 1 || result.status !== 1 ? 100 : videoLoadingStatus
// 配置标题
if (result.deviceName) {
uni.setNavigationBarTitle({
title: result.deviceName
});
}
if (result.status != 1) {
// 设备不在线
list[0].status = -1;
list[1].status = -1;
list[2].status = -1;
list[4].status = -1;
list[5].status = -1;
list[6].status = -1;
_this.list=list,
_this.showVideoControls=false
} else if (result.isEncrypt == 1) {
// 设备被加密
list[0].status = -1;
list[1].status = -1;
list[2].status = -1;
list[4].status = -1;
list[5].status = -1;
list[6].status = -1;
_this.list=list,
_this.dialogTitle='设备被加密',
_this.dialogContent='设备已被加密无法继续查看请前往萤石云app解密。',
_this.dialogShow=true,
_this.showVideoControls=false
} else {
// 获取设备能力
// _this.getDeviceCapacity();
_this.getDeviceCoverInfo();
}
} else if (res.data.code == '20001' || res.data.code == '20002' || res.data.code == '20018') {
// 设备不存在 / 不属于用户
this.dialogTitle='获取播放地址失败',
this.dialogContent='该用户不拥有该设备',
this.dialogShow=true
} else if (res.data.code == 10029) {
uni.showToast({
title: '个人版接口调用超限,请升级企业版',
icon: 'none'
});
} else if (res.data.code == 10002) {
uni.showToast({
title: res.data.msg,
icon: 'none'
});
setTimeout(() => {
this.pageBack();
}, 2000);
} else {
uni.showToast({
title: res.data.msg,
icon: 'none'
});
}
}
});
},
/*
* 获取设备能力集
*/
getDeviceCapacity() {
const { accessToken, deviceSerial, channelNo, playVideo } = this;
var _this = this;
uni.request({
url: `${OPEN_DOMAIN}/api/lapp/device/capacity`,
method: 'POST',
data: {
accessToken,
deviceSerial
},
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
success: (res) => {
console.log(res.data);
if (res.data.code == 200 && res.data.data) {
var result = res.data.data;
let list = this.list;
list[0].status = result.support_talk != (1 || 4) ? -1 : 0;
list[1].status = result.support_ptz == 0 ? -1 : 0;
list[2].status = result.support_talk != 1 ? -1 : 0;
list[5].status = result.ptz_top_bottom_mirror == 0 ? -1 : 0;
list[6].status = result.support_privacy == 0 ? -1 : 0;
if (!playVideo) {
// 非视频播放成功状态下
list[0].status = -1;
list[1].status = -1;
list[4].status = -1;
list[5].status = -1;
}
_this.list=list
} else if (res.data.code == '20002' || res.data.code == '20018') {
// 设备不存在 / 不属于用户
this.dialogTitle='设备被删除',
this.dialogContent='设备已从账号下删除,无法继续查看',
this.dialogShow=true
}
}
});
},
fullScreenFun() {
var _this = this;
livePlayerContext.requestFullScreen({
direction: 90,
success: function () {
_this.fullScreen=true
}
});
console.log('开启全屏');
},
unfullScreen() {
var _this = this;
livePlayerContext.exitFullScreen({
success: function () {
_this.fullScreen=false
}
});
console.log('开启全屏');
},
ToggleObjectFit() {
var objectFit = this.objectFit;
if(objectFit === 'contain'){
this.objectFit = 'fillCrop'
}else{
this.objectFit = 'contain'
}
},
fullscreenChange(event) {
console.log('监听到全屏变化', event);
},
getDefaultVoice: function () {
const { accessToken } = this;
var that = this;
console.info('默认语音: 第' + that.defaultVoicePage + '页');
var that = this;
that.defaultVoiceListLoading=true
uni.request({
url: `${OPEN_DOMAIN}/api/lapp/voice/query`,
//仅为示例,并非真实的接口地址
method: 'POST',
data: {
accessToken: accessToken,
default: 'true',
pageStart: that.defaultVoicePage,
pageSize: 5
},
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
success(res) {
console.log(res.data);
var defaultVoiceList = that.defaultVoiceList;
defaultVoiceList = defaultVoiceList.concat(res.data.data);
if (res.data.data && res.data.data.length > 0) {
that.defaultVoiceList=defaultVoiceList,
that.defaultVoicePage=that.defaultVoicePage + 1
} else {
that.defaultVoiceNoMore=true
}
that.defaultVoiceListLoading=false
},
error(error) {
console.log(error);
that.defaultVoiceListLoading=false
}
});
},
getCustomVoice: function () {
const { accessToken } = this;
var that = this;
console.info('用户自定义语音: 第' + that.defaultVoicePage + '页');
var that = this;
that.customVoiceListLoading=true
uni.request({
url: `${OPEN_DOMAIN}/api/lapp/voice/query`,
//仅为示例,并非真实的接口地址
method: 'POST',
data: {
accessToken: accessToken,
default: false,
pageStart: that.defaultVoicePage,
pageSize: 5
},
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
success(res) {
console.log(res.data);
var customVoiceList = that.customVoiceList;
customVoiceList = customVoiceList.concat(res.data.data);
if (res.data.data && res.data.data.length > 0) {
that.customVoiceList=customVoiceList,
that.customVoicePage=that.customVoicePage + 1
} else {
that.customVoiceNoMore=true
}
that.customVoiceListLoading=false
},
error(error) {
console.log(error);
that.customVoiceListLoading=false
}
});
},
// 滚动至低端事件
defaultScrollLower: function () {
var that = this;
console.info('defaultScrollLower 第' + that.defaultVoicePage + '页');
this.getDefaultVoice();
},
// 滚动至低端事件
customScrollLower: function () {
var that = this;
console.info('customScrollLower 第' + that.defaultVoicePage + '页');
this.getCustomVoice();
},
handlePlay(callback) {
console.log('handelPlay', this.playVideo, this.isHD);
console.log('播放地址:', this.isHD, this.videoHDSrc, this.videoSrc);
let { list } = this;
this.checkNetWork();
livePlayerContext.play({
success: () => {
// playVideo: true,
this.showVideoControls=true,
// videoLoadingStatus: 100,
this.videoNetWorkError=false
if (callback && typeof callback === 'function') {
callback();
}
},
fail: (error) => {
this.checkNetWork();
uni.showToast({
title: '网络异常',
icon: 'none'
});
console.log('开始播放失败');
list[4].status = 0;
this.videoNetWorkError=true,
this.showVideoControls=false,
this.videoLoadingStatus=100,
this.list=[...list]
console.log(this.list,'lisr')
}
});
},
handleStop(callback) {
console.log('stop');
let { list } = this;
livePlayerContext.stop({
success: () => {
list[1].status = -1;
this.playVideo=false
this.videoLoadingStatus=0
this.list=list
this.panelStatus=0
if (callback && typeof callback === 'function') {
callback();
}
},
fail: (error) => {
console.log('停止播放失败');
}
});
},
autoHideControl() {
console.log('showHdSelect', this.showHDSelect);
const _this = this;
clearTimeout(this.autoHideTimer);
this.autoHideTimer = setTimeout(() => {
const { showHDSelect } = _this;
if (!showHDSelect) {
this.showVideoControls=false
}
}, 5000);
},
handleSound(e) {
var openSound = this.openSound;
this.openSound=!openSound
},
handleHD(e) {
var showHDSelect = this.showHDSelect;
console.log('handleHD', showHDSelect);
this.showHDSelect=!showHDSelect
},
changeVideoHD(e) {
var _this = this;
this.showHDSelect=false,
this.isHD=true
this.handleStop(_this.handlePlay);
},
changeVideoNormal(e) {
this.showHDSelect=false,
this.isHD=false
this.handleStop(this.handlePlay);
},
statechange(e) {
console.log('live-player code:', e.detail.code, e.detail);
const { code } = e.detail;
let { videoLoadingStatus, panelStatus } = this;
const { list } = this;
switch (code) {
case 2007:
//启动loading
videoLoadingStatus = 0;
this.playVideo=true,
this.videoLoadingStatus=0
break;
case 2001:
//连接服务器
videoLoadingStatus = 20 + Math.floor(Math.random() * 10 + 1);
console.log(videoLoadingStatus,'进度')
break;
case 2002:
//已经连接 RTMP 服务器,开始拉流
videoLoadingStatus = 40 + Math.floor(Math.random() * 10 + 1);
console.log(videoLoadingStatus,'进度')
break;
case 2008:
// 解码器启动
console.log(videoLoadingStatus,'进度')
break;
case 2009:
//视频分辨率改动
console.log(videoLoadingStatus,'进度')
break;
case 2004:
// 视频播放开始
videoLoadingStatus = 80 + Math.floor(Math.random() * 10 + 1);
console.log(videoLoadingStatus,'进度')
break;
case 2003:
//网络接收到首个视频数据包(IDR)
videoLoadingStatus = 100;
// let {list} = this.data;
list[4].status = 0;
this.playVideo=true,
this.list=[...list]
this.autoHideControl();
this.getDeviceCapacity();
break;
case 2103:
//网络断连, 已启动自动重连(本小程序不自动重连)
// videoLoadingStatus = 100;
// this.handleStop();
// 获取设备状态
// this.getDeviceInfo();
break;
case 3001:
case 3002:
case 3003:
case 3005:
// 播放失败
videoLoadingStatus = 100;
// this.getDeviceInfo();
this.checkNetWork();
this.handleStop(this.playError);
list[5].status = -1;
list[4].status = -1;
// playVideo: false,
this.showVideoControls=false,
this.videoNetWorkError=true,
this.videoLoadingStatus=100,
this.list=list
break;
case -2301:
// 经多次重连抢救无效,更多重试请自行重启播放
videoLoadingStatus = 100;
list[5].status = -1;
list[4].status = -1;
// playVideo: false,
this.showVideoControls=false,
this.videoNetWorkError=true,
this.videoLoadingStatus=100,
this.list=[...list]
break;
}
this.videoLoadingStatus=videoLoadingStatus
},
playError() {
this.showVideoControls=false,
this.videoNetWorkError=true,
this.videoLoadingStatus=100
// this.getPlayUrl();
this.getDeviceInfo();
this.getDeviceCoverInfo();
},
error(e) {
console.log('live-player', e);
console.error('live-player error:', e.detail.errMsg);
if (e.detail.errCode == 10001) {
uni.showToast({
title: '视频直播对讲需要你手机授权微信录音或麦克风权限',
icon: 'none',
duration: 3000
});
}
},
onVideoTap(e) {
console.log('点击视频');
const { deviceOffline, showVideoControls, panelStatus, videoNetWorkError } = this;
if (deviceOffline || panelStatus === 4 || videoNetWorkError) {
return false;
}
if (showVideoControls) {
this.showVideoControls=false
clearTimeout(this.autoHideTimer);
} else {
this.showVideoControls=true
this.autoHideControl();
}
},
tapPanel(event) {
const { accessToken, deviceSerial, channelNo } = this;
var tValue = event.currentTarget.dataset.value;
var list = this.list;
var panelStatus = this.panelStatus;
switch (tValue) {
case 'talk':
if (list[0].status === -1) {
return false;
}
panelStatus = 5;
this.initTalk();
break;
case 'ptz':
if (list[1].status === -1) {
return false;
}
panelStatus = 1;
break;
case 'voice':
if (list[2].status === -1) {
return false;
}
panelStatus = 2;
this.getDefaultVoice();
this.getCustomVoice();
break;
case 'playback':
this.goToLive();
break;
case 'capture':
if (list[4].status === -1) {
return false;
}
this.screenShoot();
break;
case 'mirror':
if (list[5].status === -1) {
return false;
}
if (panelStatus === 3) {
panelStatus = 0;
list[4].status = 0;
list[5].status = 0;
this.sceneMirror(2);
} else {
panelStatus = 3;
this.sceneMirror(2);
}
break;
case 'cover':
if (list[6].status === -1) {
return false;
}
if (panelStatus === 4) {
// 镜头遮蔽中
this.sceneCover(0);
} else {
this.sceneCover(1);
}
break;
default:
panelStatus = 0;
}
this.panelStatus=panelStatus,
this.list=list
},
startRecord(e) {
recorderManager.start(options);
},
stopRecord(e) {
recorderManager.stop();
},
handleBackPanel(event) {
var tValue = event.currentTarget.dataset.value;
this.panelStatus=0
},
handlePtzTouchStart(event) {
// var { offsetLeft, offsetTop } = event.currentTarget;
// var {clientX,clientY} = event.touches[0];
var { ptzStatus, ptzLoading } = this;
// var centerLeft = 104 + offsetLeft;
// var centerTop = 104 + offsetTop;
// var left = clientX - centerLeft;
// var top = clientY - centerTop;
uni.createSelectorQuery()
.in(uni)
.select('#ptz-img-container')
.boundingClientRect((rect) => {
let { clientX, clientY } = event.touches[0];
let rectLeft = rect.left;
let rectTop = rect.top;
var centerLeft = 104 + rectLeft;
var centerTop = 104 + rectTop;
var left = clientX - centerLeft;
var top = clientY - centerTop;
console.log('点击了页面方位pageY', clientY);
console.log('云盘位置top', rect.top);
if (ptzLoading) {
uni.showToast({
title: '操作过于频繁,建议长按转动',
icon: 'none'
});
return false;
}
if (Math.abs(left) > Math.abs(top)) {
if (left > 0) {
this.handlePtzControl(3);
ptzStatus = 4;
} else {
ptzStatus = 3;
this.handlePtzControl(2);
}
} else {
if (top > 0) {
ptzStatus = 2;
this.handlePtzControl(1);
} else {
ptzStatus = 1;
this.handlePtzControl(0);
}
}
this.ptzStatus=ptzStatus
})
.exec();
},
handlePtzTouchEnd(event) {
let { clientX, clientY } = event.changedTouches[0];
const _this = this;
uni.createSelectorQuery()
.in(uni)
.select('#ptz-img-container')
.boundingClientRect((rect) => {
let rectLeft = rect.left;
let rectTop = rect.top;
var centerLeft = 104 + rectLeft;
var centerTop = 104 + rectTop;
var left = clientX - centerLeft;
var top = clientY - centerTop;
if (Math.abs(left) > Math.abs(top)) {
if (left > 0) {
_this.handlePtzControl(3, 'stop');
} else {
_this.handlePtzControl(2, 'stop');
}
} else {
if (top > 0) {
_this.handlePtzControl(1, 'stop');
} else {
_this.handlePtzControl(0, 'stop');
}
}
})
.exec();
this.ptzStatus=0
},
handlePtzControl(position, type) {
const { accessToken, deviceSerial, channelNo, ptzLoading } = this;
let ptzLimit = '';
const ptzTopImgSuccess = '../../static/live/images/yuntai/top.png';
const ptzTopImgFailed = '../../static/live/images/yuntai/top_limit.png';
const ptzDownImgSuccess = '../../static/live/images/yuntai/down.png';
const ptzDownImgFailed = '../../static/live/images/yuntai/down_limit.png';
const ptzLeftImgSuccess = '../../static/live/images/yuntai/left.png';
const ptzLeftImgFailed = '../../static/live/images/yuntai/left_limit.png';
const ptzRightImgSuccess = '../../static/live/images/yuntai/right.png';
const ptzRightImgFailed = '../../static/live/images/yuntai/right_limit.png';
const ptzNormalImg = '../../static/live/images/yuntai/normal.png';
let ptzStatus = this.ptzStatus;
let currentPtzImg = this.currentPtzImg;
var url = `${OPEN_DOMAIN}${PTZ_START}`;
if (type == 'stop') {
url = `${OPEN_DOMAIN}${PTZ_STOP}`;
}
if (ptzLoading && type === 'start') {
uni.showToast({
title: '操作过于频繁,建议长按转动',
icon: 'none'
});
return false;
}
this.ptzLoading=true
uni.request({
url: url,
//仅为示例,并非真实的接口地址
method: 'POST',
data: {
accessToken: accessToken,
deviceSerial: deviceSerial,
channelNo: channelNo,
direction: position,
speed: 1
},
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
success: (res) => {
const code = res.data.code;
console.log('云台控制', res.data);
if (code == 10029) {
uni.showToast({
title: '个人版接口调用超限,请升级企业版',
icon: 'none'
});
} else if (code != 200) {
uni.showToast({
title: res.data.msg,
icon: 'none'
});
}
if (type == 'stop') {
ptzStatus = 0;
currentPtzImg = ptzNormalImg;
} else {
switch (position) {
case 0:
ptzStatus = 1;
currentPtzImg = code == 200 ? ptzTopImgSuccess : ptzTopImgFailed;
ptzLimit = code == 200 ? '' : 'top';
break;
case 1:
ptzStatus = 2;
currentPtzImg = code == 200 ? ptzDownImgSuccess : ptzDownImgFailed;
ptzLimit = code == 200 ? '' : 'down';
break;
case 2:
ptzStatus = 3;
currentPtzImg = code == 200 ? ptzLeftImgSuccess : ptzLeftImgFailed;
ptzLimit = code == 200 ? '' : 'left';
break;
case 3:
ptzStatus = 4;
currentPtzImg = code == 200 ? ptzRightImgSuccess : ptzRightImgFailed;
ptzLimit = code == 200 ? '' : 'right';
break;
default:
ptzStatus = 0;
currentPtzImg = ptzTopImgSuccess;
ptzLimit = '';
}
}
this.ptzStatu=ptzStatus,
this.currentPtzImg=currentPtzImg,
this.ptzLoading=false,
this.ptzLimit=ptzLimit
},
error: (err) => {
this.ptzLoading=false
}
});
},
screenShoot(e) {
const { playVideo, videoLoadingStatus } = this;
if (!playVideo || videoLoadingStatus != 100) {
console.log('非播放状态下点击截图');
return false;
}
console.log('开始截图');
// livePlayerContext.snapshot('raw');
let that = this;
uni.getSetting({
success(res) {
if (res.authSetting['scope.writePhotosAlbum']) {
that.saveImg();
} else if (res.authSetting['scope.writePhotosAlbum'] === undefined) {
uni.authorize({
scope: 'scope.writePhotosAlbum',
success() {
that.saveImg();
},
fail() {
that.authConfirm();
}
});
} else {
that.authConfirm();
}
}
});
},
saveImg() {
livePlayerContext
.snapshot('raw')
.then((data) => {
console.log('data', data);
if (data) {
console.log(data);
uni.saveImageToPhotosAlbum({
filePath: data.tempImagePath,
success(res) {
console.log('保存成功', res);
uni.showToast({
title: '截图已保存至手机相册',
icon: 'none'
});
}
});
}
})
.catch((err) => {
console.log('err', err);
});
},
// 授权拒绝后,再次授权提示弹窗
authConfirm() {
let that = this;
uni.showModal({
content: '您没打开保存图片权限,是否去设置打开?',
confirmText: '确认',
cancelText: '取消',
success: function (res) {
if (res.confirm) {
uni.openSetting({
success: (res) => {
if (res.authSetting['scope.writePhotosAlbum']) {
that.saveImg();
} else {
uni.showToast({
title: '您没有授权,无法保存到相册',
icon: 'none'
});
}
}
});
} else {
uni.showToast({
title: '您没有授权,无法保存到相册',
icon: 'none'
});
}
}
});
},
// 镜头遮蔽
sceneCover(enable) {
var _this = this;
let { deviceSerial, channelNo, accessToken, coverInterval, list, panelStatus, videoNetWorkError } = this;
if (coverInterval) {
uni.showToast({
title: '操作过于频繁',
icon: 'none'
});
return false;
}
this.coverInterval=true
uni.request({
url: `${OPEN_DOMAIN}/api/lapp/device/scene/switch/set`,
method: 'POST',
data: {
accessToken: accessToken,
deviceSerial: deviceSerial,
channelNo: channelNo,
enable: enable
},
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
success: (res) => {
console.log(res.data);
if (res.data.code == 200) {
if (enable == 0) {
list[6].status = 0;
panelStatus = 0;
videoNetWorkError = false;
this.getDeviceCapacity();
this.handlePlay();
} else {
list[0].status = -1;
list[1].status = -1;
list[2].status = -1;
list[4].status = -1;
list[5].status = -1;
panelStatus = 4;
list[6].status = 1;
// this.handleStop();
this.playVideo=false
}
setTimeout(() => {
this.coverInterval=false
}, 5000);
this.videoNetWorkError=videoNetWorkError,
this.list=list,
this.panelStatus=panelStatus
} else {
uni.showToast({
title: res.data.msg,
icon: 'none'
});
this.coverInterval=false,
this.panelStatus=panelStatus
}
},
fail: (res) => {
uni.showToast({
title: '网络异常',
icon: 'none'
});
this.coverInterval=false
}
});
},
// 镜象翻转
sceneMirror(enable) {
const { deviceSerial, channelNo, accessToken, mirrorInterval } = this;
var _this = this;
if (mirrorInterval) {
uni.showToast({
title: '操作过于频繁',
icon: 'none'
});
return false;
}
this.mirrorInterval=true
uni.request({
url: `${OPEN_DOMAIN}/api/lapp/device/ptz/mirror`,
//仅为示例,并非真实的接口地址
method: 'POST',
data: {
accessToken: accessToken,
deviceSerial: deviceSerial,
channelNo: channelNo,
command: enable
},
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
success: (res) => {
console.log(res.data);
if (res.data.code == 200) {
setTimeout(() => {
this.mirrorInterval=false
}, 5000);
} else {
uni.showToast({
title: res.data.msg,
icon: 'none'
});
this.mirrorInterval=false
}
},
fail: (res) => {
uni.showToast({
title: '网络异常',
icon: 'none'
});
this.mirrorInterval=false
}
});
},
playVoice(event) {
const { accessToken, deviceSerial, channelNo } = this;
var { duration, voiceName, fileUrl } = event.currentTarget.dataset.value;
var type = event.currentTarget.dataset.type;
console.log('type', type);
uni.request({
url: `${OPEN_DOMAIN}/api/lapp/voice/send`,
//仅为示例,并非真实的接口地址
method: 'POST',
data: {
accessToken: accessToken,
deviceSerial: deviceSerial,
channelNo: channelNo,
fileUrl: fileUrl
},
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
success: (res) => {
console.log(res.data);
const { list } = this;
if (res.data.code == 200) {
list[2].status = 1;
if(type === 'custom'){
this.activeCustomVoiceName = voiceName
}else{
this.activeCustomVoiceName = ''
}
if(type === 'default'){
this.activeDefaultVoiceName = voiceName
}else{
this.activeDefaultVoiceName = ''
}
this.list=list
setTimeout(() => {
list[2].status = 0;
this.activeCustomVoiceName='',
this.activeDefaultVoiceName='',
this.list=list
}, duration * 1000);
} else if (res.data.code == '111012') {
// 设备正忙
uni.showToast({
title: '语音下发失败',
icon: 'none'
});
} else if (res.data.code == '20007') {
// 设备正忙
uni.showToast({
title: '设备不在线',
icon: 'none'
});
} else if (res.data.code == '20008') {
// 设备正忙
uni.showToast({
title: '设备响应超时',
icon: 'none'
});
} else {
uni.showToast({
title: res.data.msg,
icon: 'none'
});
}
},
fail: (res) => {
uni.showToast({
title: '网络异常',
icon: 'none'
});
}
});
},
speakStart(event) {
let recoderTime = this.recoderTime;
this.recoderTime=59
recorderManager.start(options);
uni.showToast({
icon: 'none',
duration: 60000,
image: '../../static/live/images/voice_talk4.png',
title: '剩余' + recoderTime
});
this.recoderTimer = setInterval(() => {
if (recoderTime > 0) {
--recoderTime;
uni.showToast({
icon: 'none',
duration: 60000,
image: '../../static/live/images/voice_talk4.png',
title: '剩余' + recoderTime
});
this.recoderTime=recoderTime
} else {
clearInterval(this.recoderTimer);
this.speakEnd();
}
// console.log("recoderTime", recoderTime)
}, 1000);
},
speakEnd(event) {
uni.hideToast();
let recoderTime = this.recoderTime;
if (recoderTime >= 59) {
uni.showToast({
title: '时间太短了',
icon: 'none'
});
recorderManager.stop();
clearInterval(this.recoderTimer);
return false;
} else {
uni.hideToast();
recorderManager.stop();
clearInterval(this.recoderTimer);
}
},
tapDialogButton(e) {
this.dialogShow=false
this.pageBack();
},
pageBack() {
const { scene } = this;
console.log('scene', scene);
// wx.showToast({
// title: `code ${scene}`,
// })
if ([1007, 1008, 1036, 1037, 1038, 1044].indexOf(scene) === -1) {
uni.navigateBack({
delta: 1
});
} else {
uni.reLaunch({
url: '/pages/home/home'
});
}
},
// 通过二位码进入直播通过uuid获取accessToken、deviceSerial、channelNo
getWxaInfo: function () {
const { pathParam } = this;
console.log('getWxaInfo:', pathParam);
uni.request({
url: `${OPEN_DOMAIN}/device/getWxaInfo`,
//仅为示例,并非真实的接口地址
method: 'POST',
data: {
uuid: pathParam
},
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
success: (res) => {
console.log('通过uuid获取', res.data);
const res1 = res.data.data;
if (res1) {
this.accessToken=res1.accessToken,
this.deviceSerial=res1.deviceSerial,
this.channelNo=res1.channelNo
this.getPlayUrl();
this.getDeviceInfo();
this.getDeviceCoverInfo();
}
},
fail: (res) => {
console.log('获取accesstoken失败');
// wx.showToast({
// title: '网络异常',
// icon: 'none'
// })
}
});
},
// 返回回放
goToLive() {
const { accessToken, deviceSerial, channelNo } = this;
let url = '/packDetail1/pages/device/playback?accessToken=' + accessToken + '&deviceSerial=' + deviceSerial + '&channelNo=' + channelNo;
uni.navigateTo({
url: url
});
},
// 对讲-------------------
// 退出对讲
exitTalk() {
this.panelStatus=0,
this.talkStatus=0,
this.pushUrl=''
this.websocketClose();
},
// 进入对讲pannel
initTalk() {
// 初始化当前用户id
const uuid = wxuuid();
this.customId=uuid,
this.talkStatus=1
this.startTalk();
},
// 重试
tryTalkAgain() {
// 初始化当前用户id
const uuid = wxuuid();
this.customId=uuid,
this.talkStatus=1
this.websocketClose();
this.startTalk();
},
// 开始对讲-①获取设备能力集
startTalk() {
const { accessToken, deviceSerial } = this;
uni.request({
url: `${OPEN_DOMAIN}/api/lapp/device/capacity`,
method: 'POST',
data: {
accessToken: accessToken,
deviceSerial: deviceSerial
},
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
success: (res) => {
console.log('设备能力集:', res.data);
if (res.data.code == 200) {
const supportTalk = res.data.data.support_talk;
console.log('supportTalk', supportTalk);
if (supportTalk == 1 || 4) {
// 设备能力集中为1/4表示支持全双工
this.getToken();
}
}
},
fail: (err) => {
console.log(err);
}
});
},
// 对讲-②获取两个取流token
getToken() {
const { accessToken } = this;
uni.request({
url: `${OPEN_DOMAIN}/api/user/token`,
method: 'POST',
data: {
accessToken: accessToken,
count: 2,
clientType: '0',
featureCode: '123',
osVersion: '2.3.6',
sdkVersion: 'v.1.0.20140720',
netType: 'UNKNOWN'
},
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
success: (res) => {
console.log('取流token:', res.data);
if (res.data.resultCode == 200) {
const tokens = res.data.tokenArray;
if (tokens && tokens.length == 2) {
this.token1=tokens[0],
this.token2=tokens[1]
// 创建房间,并查询房间信息
this.orderRoom();
}
}
},
fail: (err) => {
console.log(err);
}
});
},
// 对讲-③创建房间并查询房间信息
orderRoom() {
// 检查网络状态
this.checkNetWork();
const { customId, accessToken } = this;
uni.request({
url: `${OPEN_DOMAIN}/api/v3/conference/gen`,
method: 'POST',
data: {
accessToken: accessToken,
customId: customId
},
header: {
'content-type': 'application/x-www-form-urlencoded'
},
success: (res) => {
console.log('预定创建并查询房间信息:', res.data);
const code = res.data.code || res.data.meta.code;
if (code == 200) {
const clientId = res.data.clientId;
const conferenceInfos = res.data.conferenceInfos;
const roomId = conferenceInfos.roomId;
const vtmHttpsAddress = conferenceInfos.vtmHttpsAddress;
const vtmAddress = conferenceInfos.vtmAddress;
let vtmIp = '';
let vtmPort = '';
if (vtmAddress && vtmAddress.length > 0) {
vtmIp = vtmAddress.split(':')[0];
vtmPort = vtmAddress.split(':')[1];
}
this.clientId=clientId,
this.vtmHttpsAddress=vtmHttpsAddress,
this.vtmIp=vtmIp,
this.vtmPort=vtmPort,
this.roomId=roomId
this.advanceDevice();
} else if (code == '10002') {
uni.showToast({
title: 'accessToken过期或异常',
icon: 'none'
});
} else if (code == '404') {
uni.showToast({
title: '未找到房间信息',
icon: 'none'
});
} else {
uni.showToast({
title: '创建房间-' + res.data.meta.message,
icon: 'none'
});
this.talkStatus=3 // 启动失败
}
},
fail: (err) => {
console.log(err);
}
});
},
// 对讲-④邀请设备入会
advanceDevice() {
const { accessToken, roomId, deviceSerial, channelNo, token1 } = this;
uni.request({
url: `${OPEN_DOMAIN}/api/v3/conference/device/invite`,
method: 'POST',
data: {
accessToken: accessToken,
roomId: roomId,
deviceSerial: deviceSerial,
channelNo: channelNo,
mode: 2,
authType: 5,
token: token1
},
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
success: (res) => {
console.log('邀请设备入会:', res.data);
if (res.data.meta.code == 200) {
console.log('邀请设备入会成功,加入模式为对讲模式');
this.getWebsocket();
} else {
if (res.data.meta.code == 80502) {
this.talkStatus=4 // 设备对讲中
} else {
uni.showToast({
title: '邀请设备入会-' + res.data.meta.message,
icon: 'none'
});
this.talkStatus=3 // 启动失败
}
}
},
fail: (err) => {
console.log(err);
this.talkStatus=3 // 启动失败
}
});
},
// 对讲-⑤强制设备退会
exitDevice() {
const { accessToken, roomId, deviceSerial, channelNo, token2 } = this;
uni.request({
url: `${OPEN_DOMAIN}/api/v3/conference/device/kickout`,
method: 'POST',
data: {
accessToken: accessToken,
roomId: roomId,
deviceSerial: deviceSerial,
channelNo: channelNo,
authType: 5,
token: token2
},
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
success: (res) => {
console.log('强制设备退出房间:', res.data);
if (res.data.meta.code == 200) {
console.log('强制设备退出房间成功');
// this.getWebsocket()
} else {
uni.showToast({
title: '强制设备退出房间-' + res.data.meta.message,
icon: 'none'
});
}
},
fail: (err) => {
console.log(err);
}
});
},
// 获取websocket地址
getWebsocket() {
const { vtmHttpsAddress, roomId } = this;
const url = 'https://' + vtmHttpsAddress + '/vtm/rtmpavc/' + roomId; // 注意:此处需配置小程序域名
// const url = 'https://vtmrcgnginx.ys7.com:8555/vtm/rtmpavc/' + roomId;
uni.request({
url: url,
method: 'GET',
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
success: (res) => {
console.log('vtm连接返回', res.data);
if (res.data.retcode == 0 || res.data.retcode == 200) {
const wsurl = 'wss://' + res.data.serverAddr + ':' + res.data.serverPort + '/rtmpavc'; // 注意:此处需配置小程序域名
this.websocketUrl=wsurl,
this.rtmpAddress=res.data.serverAddr
this.socketConfig();
} else if (res.data.retcode == 503) {
uni.showToast({
title: '无可用服务',
icon: 'none'
});
this.talkStatus=3 // 启动失败
this.exitDevice();
} else {
console.log('获取websocket地址失败', res.data.retcode);
uni.showToast({
title: '获取websocket地址失败',
icon: 'none'
});
this.talkStatus=3 // 启动失败
this.exitDevice();
}
},
fail: (err) => {
console.log('获取websocket地址失败', err);
uni.showToast({
title: '获取websocket地址失败',
icon: 'none'
});
this.talkStatus=3 // 启动失败
}
});
},
// 配置socket
socketConfig() {
let that = this;
const { websocketUrl, clientId } = this;
if (Object.keys(app.globalData.socketTask).length <= 0 || app.globalData.socketTask.readyState === 3) {
console.log('开始连接socket');
// socketTask的属性值readyState取下面4个状态值CONNECTING: 0 连接中 、OPEN: 1 已连接 、CLOSING: 2 关闭中 、CLOSED: 3 已关闭
// 直接判断SocketTask对象的属性值readyState如果是1的话就表示连接可用。
console.log('websocketURL:', websocketUrl);
let socketTask = null;
const websocketConnect = new Promise((resolve, reject) => {
socketTask = uni.connectSocket({
url: websocketUrl,
// url: 'wss://test12jsdecoder.ys7.com/?version=0.1&cipherSuites=0&sessionID=',
success: (res) => {
console.log('connectSocket连接中', res);
uni.showToast({
title: 'websocket连接中,请稍候',
icon: 'none'
});
resolve(res);
},
fail: (err) => {
console.log('connectSocket失败', err);
uni.showToast({
title: 'webSocket连接失败',
icon: 'none'
});
this.talkStatus=3 // 启动失败
this.exitDevice();
}
});
});
websocketConnect.then((data) => {
if (data.errMsg == 'connectSocket:ok') {
app.globalData.socketTask = socketTask;
that.initEventHandle();
}
});
}
},
// 初始化websocket事件
initEventHandle() {
const socketTask = app.globalData.socketTask;
const that = this;
const { clientId, roomId, joinPassword, vtmIp, vtmPort, rtmpAddress } = this;
socketTask.onOpen((res) => {
console.log('监听WebSocket开启', res);
// 打开bav会话
let param = {
cmdType: 1000,
vtmIp: vtmIp,
vtmPort: parseInt(vtmPort),
clientId: clientId,
roomId: roomId,
authentication: `roomId=${roomId}&password=${joinPassword}`,
mode: 2
};
if (socketTask.readyState === 1) {
uni.sendSocketMessage({
data: JSON.stringify(param)
});
console.log('websocket发送给服务端的消息:', param);
}
});
socketTask.onMessage((res) => {
console.log('接收到WebSocket消息', res);
if (res.data) {
const result = JSON.parse(res.data);
if (result.cmdType == '1003') {
// 微信小程序推流
const rtmpPort = result.rtmpPort;
if (result.rtmpIp) {
const rtmpIp = result.rtmpIp;
} else {
const rtmpIp = rtmpAddress;
}
const pushUrl = `rtmp://${rtmpIp}:${rtmpPort}/livestream/wechat?roomId=${roomId}&pushClientId=${clientId}`;
console.log('推流地址:', pushUrl);
that.pushUrl=pushUrl,
that.talkStatus=2 // 对讲
var pushcontext = uni.createLivePusherContext('livePusher', this);
that.pushVideoContext=pushcontext
// 开始推流
that.startPush();
} else if (result.cmdType == '1002') {
// 微信小程序拉流
const playClientId = result.playClientId;
const rtmpPort = result.rtmpPort;
if (result.rtmpIp) {
const rtmpIp = result.rtmpIp;
} else {
const rtmpIp = rtmpAddress;
}
const playSrc = `rtmp://${rtmpIp}:${rtmpPort}/livestream/wechat?roomId=${roomId}&pushClientId=${clientId}&playClientId=${playClientId}`;
console.log('拉流地址:', playSrc);
this.talkStatus=2,
// 对讲
this.playSrc=playSrc
const playcontext = uni.createLivePlayerContext('talk-liveplayer', that);
console.log('当前拉流窗口~~~~~', playcontext);
// 播放
that.autoPlay(playcontext);
} else if (result.cmdType == '1005') {
// 某端退出房间
const curPlayClientId = 'paly' + result.playClientId;
uni.showToast({
title: result.playClientId + '将关闭',
icon: 'none'
});
this.talkStatus=3 // 对讲失败
// 对应播放端停止播放
console.log('当前退出窗口', curPlayClientId);
} else if (result.cmdType == '1001') {
const code = result.code;
const msg = result.msg;
console.log('websocket返回的msg', code + '-->' + msg);
let content = '';
switch (code) {
case 0:
break;
case 10001:
content = 'websocket请求参数错误';
break;
case 10002:
content = 'flv转封装失败';
break;
case 10003:
content = 'rtp转封装失败';
break;
case 10004:
content = 'rtmp读写错误 ';
break;
case 10005:
content = 'rtmp接收缓存溢出';
break;
case 6:
content = '连接sts服务失败';
break;
case 11:
content = '无效的房间号';
break;
case 14:
content = 'sts连接vtm失败';
break;
case 17:
content = '房间已满';
break;
case 18:
content = 'auth认证失败';
break;
case 35:
content = '房间号不存在';
break;
}
if (content) {
uni.showToast({
title: content,
icon: 'none',
duration: 300
});
// 关闭websocket
that.websocketClose();
this.talkStatus=3 // 对讲失败
}
}
}
});
socketTask.onClose((res) => {
console.log('WebSocket已关闭', res);
uni.showToast({
title: 'websocket连接已关闭',
icon: 'none'
});
// 监听到websocket关闭,关闭推流
this.stopPush();
// 强制
this.exitDevice();
this.talkStatus=3 // 对讲失败
});
socketTask.onError((err) => {
console.log('WebSocket连接失败', err);
uni.showToast({
title: 'WebSocket连接失败',
icon: 'none'
});
// 强制
this.exitDevice();
this.talkStatus=3 // 对讲失败
});
},
// 关闭websocket
websocketClose() {
// 关闭websocket连接
const socketTask = app.globalData.socketTask;
const that = this;
this.exitDevice();
const { clientId, roomId } = this;
if (Object.keys(socketTask).length === 0) {
return;
}
// 关闭bav会话
let param = {
cmdType: 1004,
// 通知服务端断开websocket
clientId: clientId,
roomId: roomId
};
uni.sendSocketMessage({
data: JSON.stringify(param)
});
console.log('websocket发送给服务端的消息:', param);
socketTask.close({
code: 1000,
success: (res) => {
console.log('websocket已断开连接', res);
}
});
},
// push
pusherStateChange: function (e) {
console.log('>>> live-pusher onPushStateChange:', e.detail.code);
const code = e.detail.code;
let content = '';
switch (code) {
case 1001:
console.log('code', code, '已经连接推流服务器');
break;
case 1002:
console.log('code', code, '已经与服务器握手完毕,开始推流');
break;
case 1003:
console.log('code', code, '打开摄像头成功');
break;
case 1004:
console.log('code', code, '录屏启动成功');
break;
case 1005:
console.log('code', code, '推流动态调整分辨率');
break;
case 1006:
console.log('code', code, '推流动态调整码率');
break;
case 1007:
console.log('code', code, '首帧画面采集完成, 推流成功');
break;
case 1008:
console.log('code', code, '编码器启动');
break;
case -1301:
console.log('code', code, '打开摄像头失败');
break;
case -1302:
console.log('code', code, '打开麦克风失败');
break;
case -1303:
console.log('code', code, '视频编码失败');
break;
case -1304:
console.log('code', code, '音频编码失败');
break;
case -1306:
console.log('code', code, '不支持的音频采样率');
break;
case -1307:
console.log('code', code, '网络断连,且经多次重连抢救无效,更多重试请自行重启推流');
content = '网络出错,推流失败';
break;
case -1308:
console.log('code', code, '开始录屏失败,可能是被用户拒绝');
break;
case -1309:
console.log('code', code, '录屏失败不支持的Android系统版本需要5.0以上的系统');
break;
case -1310:
console.log('code', code, '录屏被其他应用打断了');
break;
case -1311:
console.log('code', code, 'Android Mic打开成功但是录不到音频数据');
break;
case -1312:
console.log('code', code, '录屏动态切横竖屏失败');
break;
case 1101:
console.log('code', code, '网络状况不佳:上行带宽太小,上传数据受阻');
break;
case 1102:
console.log('code', code, '网络断连, 已启动自动重连');
break;
case 1103:
console.log('code', code, '硬编码启动失败,采用软编码');
break;
case 1104:
console.log('code', code, '视频编码失败');
break;
case 1105 || 1106:
console.log('code', code, '新美颜软编码启动失败,采用老的软编码');
break;
case 3001:
console.log('code', code, 'RTMP -DNS解析失败');
break;
case 3002:
console.log('code', code, 'RTMP服务器连接失败');
break;
case 3003:
console.log('code', code, 'RTMP服务器握手失败');
break;
case 3004:
console.log('code', code, 'RTMP服务器主动断开请检查推流地址的合法性或防盗链有效期');
break;
case 3005:
console.log('code', code, 'RTMP 读/写失败');
break;
default:
console.log('未知错误');
break;
}
uni.showToast({
title: content,
icon: 'none'
});
},
stopPush() {
console.log('停止推流');
this.pushVideoContext.stop();
this.pushVideo=false
},
startPush() {
console.log('开始推流,推流地址为:', this.pushUrl);
uni.showToast({
title: '开始推流,推流地址为:'
});
uni.showToast({
title: this.pushUrl
});
this.pushVideoContext.start();
this.pushVideo=true
},
error(e) {
console.log('live-player', e);
console.error('live-player error:', e.detail.errMsg);
if (e.detail.errCode == 10001) {
uni.showToast({
title: '视频播放或录制需要你手机授权微信录音或麦克风权限',
icon: 'none',
duration: 3000
});
}
},
autoPlay(curPlayerContext) {
this.checkNetWork();
curPlayerContext.play({
success: () => {
console.log('播放对讲音频成功');
},
fail: (error) => {
console.log('播放对讲音频失败:', error);
}
});
},
currentPlayContainerFun() {
console.log('占位:函数 _currentPlayContainer 未声明');
}
}
}
</script>
<style lang="scss">
@import './live.css';
</style>