安卓视频监控插件更新,还要回放页面
parent
84c17f60d7
commit
c3aec72e76
|
|
@ -34,7 +34,11 @@
|
|||
"",
|
||||
"<!-- 基础必要权限(如无特殊需求可保留) -->",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>"
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"",
|
||||
"<!-- 麦克风和音频权限(监控语音对讲/录像需要) -->",
|
||||
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>"
|
||||
],
|
||||
"abiFilters" : [ "arm64-v8a" ]
|
||||
},
|
||||
|
|
@ -42,7 +46,8 @@
|
|||
"dSYMs" : false,
|
||||
"privacyDescription" : {
|
||||
"NSCameraUsageDescription" : "需要使用相机扫描条码",
|
||||
"NSPhotoLibraryUsageDescription" : "需要访问相册选择图片扫码"
|
||||
"NSPhotoLibraryUsageDescription" : "需要访问相册选择图片扫码",
|
||||
"NSMicrophoneUsageDescription" : "需要使用麦克风进行监控视频的语音对讲或录制"
|
||||
}
|
||||
},
|
||||
"sdkConfigs" : {}
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -28,6 +28,11 @@
|
|||
<text class="status-text">状态:{{ statusMsg }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 自定义前端录像提示,替代原生 toast 避免崩溃 -->
|
||||
<view class="recording-indicator" v-if="isRecording">
|
||||
<text class="recording-text">正在录像...</text>
|
||||
</view>
|
||||
|
||||
<!-- 设置区域 -->
|
||||
<view class="settings-panel">
|
||||
<view class="setting-row">
|
||||
|
|
@ -61,6 +66,10 @@
|
|||
<button class="btn btn-primary" @click="startPlay"><text class="btn-text">播放</text></button>
|
||||
<button class="btn btn-warn" @click="stopPlay"><text class="btn-text">停止</text></button>
|
||||
</view>
|
||||
<view class="controls">
|
||||
<button class="btn btn-primary" @click="beginRecord"><text class="btn-text">开始录像</text></button>
|
||||
<button class="btn btn-warn" @click="endRecord"><text class="btn-text">停止录像</text></button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
|
@ -70,6 +79,7 @@
|
|||
return {
|
||||
deviceUid: 'HLTY036190SYNSK', // 默认使用你 Demo 里的测试 UID
|
||||
isPlaying: false,
|
||||
isRecording: false, // 是否正在录像
|
||||
statusMsg: '等待操作',
|
||||
currentChannel: 0, // 0对应CH1, 1对应CH2...
|
||||
currentStreamType: 1, // 1为高清(主码流),2为标清(子码流)
|
||||
|
|
@ -87,7 +97,18 @@
|
|||
}
|
||||
},
|
||||
onUnload() {
|
||||
// 页面卸载时停止播放,释放资源
|
||||
// 如果正在录像,必须先停止录像
|
||||
if (this.isRecording) {
|
||||
this.endRecord();
|
||||
}
|
||||
// 页面卸载时停止播放,释放资源,防止占用通道
|
||||
this.stopPlay();
|
||||
},
|
||||
onHide() {
|
||||
// 页面切到后台或跳转到其他页面时,也停止录像和播放,释放通道
|
||||
if (this.isRecording) {
|
||||
this.endRecord();
|
||||
}
|
||||
this.stopPlay();
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -145,15 +166,15 @@
|
|||
this.$refs.ipcVideo.start();
|
||||
} else {
|
||||
console.error('start 方法不存在,可能插件注册失败或版本未生效');
|
||||
uni.showToast({ title: '插件方法未找到', icon: 'none' });
|
||||
// uni.showToast({ title: '插件方法未找到', icon: 'none' }); // 暂时注释掉
|
||||
}
|
||||
} else {
|
||||
uni.showToast({ title: '插件未加载成功', icon: 'none' });
|
||||
// uni.showToast({ title: '插件未加载成功', icon: 'none' }); // 暂时注释掉
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-PLUS
|
||||
uni.showToast({ title: '仅App端支持', icon: 'none' });
|
||||
// uni.showToast({ title: '仅App端支持', icon: 'none' }); // 暂时注释掉
|
||||
// #endif
|
||||
},
|
||||
stopPlay() {
|
||||
|
|
@ -166,6 +187,38 @@
|
|||
}
|
||||
// #endif
|
||||
},
|
||||
beginRecord() {
|
||||
// #ifdef APP-PLUS
|
||||
if (this.$refs.ipcVideo && typeof this.$refs.ipcVideo.startRecord === 'function') {
|
||||
// 将 _doc/ 相对路径转换为手机上的绝对绝对路径
|
||||
const savePath = plus.io.convertLocalFileSystemURL("_doc/camera_record_" + Date.now() + ".mp4");
|
||||
|
||||
this.$refs.ipcVideo.startRecord({
|
||||
path: savePath,
|
||||
width: 1920, // 可选,默认 1920
|
||||
height: 1080, // 可选,默认 1080
|
||||
fps: 15 // 可选,默认 10
|
||||
});
|
||||
|
||||
this.statusMsg = '正在录像...';
|
||||
this.isRecording = true;
|
||||
console.log("正在录像,保存到:", savePath);
|
||||
// uni.showToast({ title: '已开始录像', icon: 'success' }); // 暂时注释掉,使用 isRecording 替代
|
||||
} else {
|
||||
// uni.showToast({ title: '原生插件缺少录像方法', icon: 'none' }); // 暂时注释掉
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
endRecord() {
|
||||
// #ifdef APP-PLUS
|
||||
if (this.$refs.ipcVideo && typeof this.$refs.ipcVideo.stopRecord === 'function') {
|
||||
this.$refs.ipcVideo.stopRecord();
|
||||
this.statusMsg = '录像已保存';
|
||||
this.isRecording = false;
|
||||
// uni.showToast({ title: '录像已保存到 _doc 目录', icon: 'success' }); // 暂时注释掉
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
onVideoStatusChange(e) {
|
||||
try {
|
||||
console.log("视频状态改变:", e);
|
||||
|
|
@ -183,22 +236,22 @@
|
|||
|
||||
// 捕获来自 Java 层主动抛出的原生崩溃错误
|
||||
if (status === 'error' && msg && msg.indexOf('原生启动崩溃') !== -1) {
|
||||
uni.showModal({
|
||||
/* uni.showModal({
|
||||
title: '原生插件崩溃',
|
||||
content: msg,
|
||||
showCancel: false
|
||||
});
|
||||
}); */ // 暂时注释掉
|
||||
this.isPlaying = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理特定的连接错误状态
|
||||
if (status === 'error' || msgParam === 7 || msgParam === 14) {
|
||||
uni.showToast({
|
||||
/* uni.showToast({
|
||||
title: msg ? msg : '摄像机离线,请检查设备网络',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
}); */ // 暂时注释掉
|
||||
// 可以在这里做一些重置操作
|
||||
this.isPlaying = false;
|
||||
} else if (status === 'playing') {
|
||||
|
|
@ -207,18 +260,18 @@
|
|||
} else {
|
||||
// 如果 e.detail 是空对象或者 undefined,提示异常
|
||||
console.error("收到空的状态事件:", e);
|
||||
uni.showToast({
|
||||
/* uni.showToast({
|
||||
title: '收到空状态,原生可能发生异常',
|
||||
icon: 'none'
|
||||
});
|
||||
}); */ // 暂时注释掉
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("解析状态回调时发生前端异常:", err);
|
||||
uni.showModal({
|
||||
/* uni.showModal({
|
||||
title: '前端回调解析异常',
|
||||
content: err.toString(),
|
||||
showCancel: false
|
||||
});
|
||||
}); */ // 暂时注释掉
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -322,4 +375,19 @@
|
|||
color: #ffffff;
|
||||
background-color: #007aff;
|
||||
}
|
||||
.recording-indicator {
|
||||
position: absolute;
|
||||
top: 40rpx;
|
||||
right: 40rpx;
|
||||
background-color: rgba(255, 0, 0, 0.7);
|
||||
padding: 10rpx 20rpx;
|
||||
border-radius: 10rpx;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.recording-text {
|
||||
color: #ffffff;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,353 @@
|
|||
<template>
|
||||
<view class="playback-container">
|
||||
<text class="header">查看回放</text>
|
||||
|
||||
<!-- 视频播放容器 -->
|
||||
<view class="video-wrapper">
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<ry-ipc-video
|
||||
ref="ipcVideo"
|
||||
class="ipc-video"
|
||||
:uid="deviceUid"
|
||||
:channel="currentChannel"
|
||||
:isNvr="isNvr"
|
||||
@onStatus="onVideoStatusChange">
|
||||
</ry-ipc-video>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 非App端提示 -->
|
||||
<!-- #ifndef APP-PLUS -->
|
||||
<view class="not-support-tips">
|
||||
<text class="not-support-tips-text">当前环境不支持查看回放,请使用App</text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
|
||||
<view class="status-text-wrap" v-if="statusMsg">
|
||||
<text class="status-text">状态:{{ statusMsg }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 时间和设备选择面板 -->
|
||||
<view class="settings-panel">
|
||||
<!-- 设备类型选择 -->
|
||||
<view class="setting-row">
|
||||
<text class="setting-label">设备类型:</text>
|
||||
<view class="options-group">
|
||||
<text class="option-btn" :class="!isNvr ? 'active-btn' : ''" @click="changeNvr(false)">单体(IPC)</text>
|
||||
<text class="option-btn" :class="isNvr ? 'active-btn' : ''" @click="changeNvr(true)">主机(NVR)</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 日期选择 -->
|
||||
<view class="setting-row date-control-row">
|
||||
<text class="btn-date" @click="prevDay">前一天</text>
|
||||
<text class="current-date">{{ playDate }}</text>
|
||||
<text class="btn-date" @click="nextDay">后一天</text>
|
||||
</view>
|
||||
|
||||
<!-- 时间选择 -->
|
||||
<view class="setting-row">
|
||||
<text class="setting-label">回放时间:</text>
|
||||
<picker mode="time" :value="playTime" @change="onTimeChange" class="picker">
|
||||
<text class="picker-text">{{ playTime || '请选择时间' }}</text>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="controls">
|
||||
<button class="btn btn-primary" @click="startPlayback"><text class="btn-text">开始回放</text></button>
|
||||
<button class="btn btn-warn" @click="stopPlayback"><text class="btn-text">停止回放</text></button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
deviceUid: 'HLTY036190SYNSK',
|
||||
currentChannel: 0,
|
||||
isNvr: true,
|
||||
playDate: '',
|
||||
playTime: '',
|
||||
statusMsg: '等待操作',
|
||||
isPlaying: false
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
if (options && options.uid) {
|
||||
this.deviceUid = 'HLTY036190SYNSK';
|
||||
}
|
||||
if (options && typeof options.isNvr !== 'undefined') {
|
||||
this.isNvr = options.isNvr === 'true';
|
||||
}
|
||||
if (options && options.channel) {
|
||||
this.currentChannel = parseInt(options.channel);
|
||||
}
|
||||
|
||||
// 初始化默认时间为当前时间
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(now.getDate()).padStart(2, '0');
|
||||
const hours = String(now.getHours()).padStart(2, '0');
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||
|
||||
this.playDate = `${year}-${month}-${day}`;
|
||||
this.playTime = `${hours}:${minutes}`;
|
||||
},
|
||||
onUnload() {
|
||||
// 页面卸载时必须停止回放,释放资源!防止占用通道
|
||||
this.stopPlayback();
|
||||
},
|
||||
onHide() {
|
||||
// 页面隐藏(比如切到后台)时,也停止回放释放通道
|
||||
this.stopPlayback();
|
||||
},
|
||||
methods: {
|
||||
changeNvr(isNvrFlag) {
|
||||
if (this.isNvr === isNvrFlag) return;
|
||||
this.isNvr = isNvrFlag;
|
||||
if (this.isPlaying) {
|
||||
this.stopPlayback();
|
||||
setTimeout(() => { this.startPlayback(); }, 1000);
|
||||
}
|
||||
},
|
||||
prevDay() {
|
||||
// 解决部分环境解析 YYYY-MM-DD 报错,替换为 YYYY/MM/DD
|
||||
let d = new Date(this.playDate.replace(/-/g, '/'));
|
||||
d.setDate(d.getDate() - 1);
|
||||
this.updateDateStr(d);
|
||||
},
|
||||
nextDay() {
|
||||
let d = new Date(this.playDate.replace(/-/g, '/'));
|
||||
d.setDate(d.getDate() + 1);
|
||||
this.updateDateStr(d);
|
||||
},
|
||||
updateDateStr(d) {
|
||||
const year = d.getFullYear();
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(d.getDate()).padStart(2, '0');
|
||||
this.playDate = `${year}-${month}-${day}`;
|
||||
// 如果正在播放,日期改变后重新拉取当天的录像
|
||||
if (this.isPlaying) {
|
||||
this.stopPlayback();
|
||||
setTimeout(() => { this.startPlayback(); }, 500);
|
||||
}
|
||||
},
|
||||
onTimeChange(e) {
|
||||
this.playTime = e.detail.value;
|
||||
// 时间改变后,如果正在播放则重新拉流
|
||||
if (this.isPlaying) {
|
||||
this.stopPlayback();
|
||||
setTimeout(() => { this.startPlayback(); }, 500);
|
||||
}
|
||||
},
|
||||
startPlayback() {
|
||||
// #ifdef APP-PLUS
|
||||
if (!this.playDate || !this.playTime) {
|
||||
uni.showToast({ title: '请先选择回放时间', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.$refs.ipcVideo) {
|
||||
this.statusMsg = '正在请求回放...';
|
||||
const timeStr = `${this.playDate} ${this.playTime}:00`;
|
||||
|
||||
// 转换为 Unix 时间戳 (秒),并确保是纯整数,防止 fastjson 强转 int 报错
|
||||
// replace(/-/g, '/') 是为了兼容 iOS 等环境的时间解析
|
||||
const timeUTC = Math.floor(new Date(timeStr.replace(/-/g, '/')).getTime() / 1000);
|
||||
|
||||
console.log('请求回放时间字符串:', timeStr, ' 时间戳(秒):', timeUTC);
|
||||
|
||||
// 在请求回放前,先调用停止播放,避免直播通道占用带宽
|
||||
if (typeof this.$refs.ipcVideo.stop === 'function') {
|
||||
this.$refs.ipcVideo.stop();
|
||||
}
|
||||
|
||||
if (typeof this.$refs.ipcVideo.startPlayback === 'function') {
|
||||
// 传递一个对象,包含严格为整型的 timeUTC 参数
|
||||
this.$refs.ipcVideo.startPlayback({ timeUTC: timeUTC });
|
||||
this.isPlaying = true;
|
||||
} else {
|
||||
// 容错:如果你原生还没写回放方法,这里给个提示
|
||||
console.error('原生插件没有找到 startPlayback 方法');
|
||||
uni.showToast({ title: '原生插件缺少回放方法', icon: 'none' });
|
||||
}
|
||||
} else {
|
||||
uni.showToast({ title: '插件未加载成功', icon: 'none' });
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-PLUS
|
||||
uni.showToast({ title: '仅App端支持', icon: 'none' });
|
||||
// #endif
|
||||
},
|
||||
stopPlayback() {
|
||||
// #ifdef APP-PLUS
|
||||
if (this.$refs.ipcVideo) {
|
||||
if (typeof this.$refs.ipcVideo.stop === 'function') {
|
||||
this.$refs.ipcVideo.stop();
|
||||
}
|
||||
this.statusMsg = '已发送停止指令';
|
||||
this.isPlaying = false;
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
onVideoStatusChange(e) {
|
||||
try {
|
||||
if (e && e.detail) {
|
||||
const status = e.detail.status;
|
||||
const msg = e.detail.msg;
|
||||
|
||||
if (msg) {
|
||||
this.statusMsg = msg;
|
||||
}
|
||||
|
||||
if (status === 'error' && msg && msg.indexOf('原生启动崩溃') !== -1) {
|
||||
uni.showModal({ title: '原生插件崩溃', content: msg, showCancel: false });
|
||||
this.isPlaying = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (status === 'error') {
|
||||
uni.showToast({ title: msg ? msg : '回放失败', icon: 'none' });
|
||||
this.isPlaying = false;
|
||||
} else if (status === 'playing') {
|
||||
this.isPlaying = true;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("回调解析异常:", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* nvue 样式 */
|
||||
.playback-container {
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.header {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: 20rpx;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.video-wrapper {
|
||||
width: 750rpx;
|
||||
height: 450rpx;
|
||||
background-color: #000000;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.ipc-video {
|
||||
width: 750rpx;
|
||||
height: 450rpx;
|
||||
}
|
||||
.not-support-tips {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.not-support-tips-text {
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.status-text-wrap {
|
||||
align-items: center;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
.status-text {
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #666666;
|
||||
}
|
||||
.settings-panel {
|
||||
padding: 20rpx;
|
||||
margin-top: 20rpx;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.setting-row {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.setting-label {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
width: 140rpx;
|
||||
}
|
||||
.options-group {
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
}
|
||||
.option-btn {
|
||||
padding: 10rpx 20rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666666;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
.active-btn {
|
||||
color: #ffffff;
|
||||
background-color: #007aff;
|
||||
}
|
||||
.date-control-row {
|
||||
justify-content: space-between;
|
||||
padding: 10rpx 0;
|
||||
}
|
||||
.btn-date {
|
||||
font-size: 28rpx;
|
||||
color: #007aff;
|
||||
padding: 10rpx 20rpx;
|
||||
background-color: #f0f8ff;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.current-date {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
.picker {
|
||||
flex: 1;
|
||||
height: 60rpx;
|
||||
justify-content: center;
|
||||
background-color: #f0f0f0;
|
||||
padding-left: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.picker-text {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
.controls {
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
padding-top: 40rpx;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
.btn {
|
||||
width: 300rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 10rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.btn-primary {
|
||||
background-color: #007aff;
|
||||
}
|
||||
.btn-warn {
|
||||
background-color: #e64340;
|
||||
}
|
||||
.btn-text {
|
||||
color: #ffffff;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -67,6 +67,12 @@
|
|||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "playback/playback",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@
|
|||
<text class="item-text">实时监控</text>
|
||||
</view>
|
||||
<view class="grid-item" @click="navigateTo('查看回放')">
|
||||
<view class="item-iconf">
|
||||
<view class="item-iconf" @click="goToPlayback">
|
||||
|
||||
</view>
|
||||
<text class="item-text">查看回放</text>
|
||||
|
|
@ -370,6 +370,20 @@ export default {
|
|||
})
|
||||
// #endif
|
||||
},
|
||||
goToPlayback(){
|
||||
// #ifdef MP
|
||||
uni.showToast({
|
||||
title: '小程序暂不支持查看监控回放请使用app',
|
||||
icon: 'none'
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef MP
|
||||
uni.navigateTo({
|
||||
url: '/package_a/playback/playback'
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
goToAsset() {
|
||||
uni.navigateTo({
|
||||
url: '/package_a/asset/asset'
|
||||
|
|
|
|||
Loading…
Reference in New Issue