此代码拿走即用
目前只支持单个摄像头开发配置不支持动态获取摄像头列表ip配置
如需多个摄像头ip请修改vite.config.js文件代理改为对应摄像头ip
后续开发中.....
完成摄像头动态IP获取
本地测试直接在vite.config.js中修改对应摄像头IP即可
本次修改针对部署在nginx中
1.大华websdk包下载
2.项目结构图
📦dahuaDemo ┣ 📂public ┃ ┣ 📂module //大华摄像头文件 ┃ ┃ ┣ 📜audioTalkWorker.worker.js ┃ ┃ ┣ 📜audioWorker.worker.js ┃ ┃ ┣ 📜libDecodeSDK.js ┃ ┃ ┣ 📜libDecodeSDK.wasm ┃ ┃ ┣ 📜PlayerControl.js ┃ ┃ ┣ 📜videoWorker.worker.js ┃ ┃ ┗ 📜videoWorkerTrain.worker.js ┃ ┗ 📜vite.svg ┣ 📂src ┃ ┣ 📂assets ┃ ┃ ┗ 📜vue.svg ┃ ┣ 📂components ┃ ┃ ┣ 📜ControlPanel.vue 控制面板 ┃ ┃ ┣ 📜DeviceList.vue 设备列表 ┃ ┃ ┣ 📜PlaybackModal.vue 回放列表模态框 ┃ ┃ ┗ 📜VideoGrid.vue 视频播放区域 ┃ ┣ 📜App.vue ┃ ┣ 📜main.js ┃ ┗ 📜style.css ┣ 📜.env.development ┣ 📜.env.production ┣ 📜.gitignore ┣ 📜index.html ┣ 📜package.json ┣ 📜pnpm-lock.yaml ┣ 📜README.md ┗ 📜vite.config.js
3.代码文件
app.vue
<template>
<div class="ipc-manager">
<!-- 设备列表 -->
<DeviceList
:devices="devices"
:current-device-index="currentDeviceIndex"
@device-click="handleDeviceClick"
@login-all="loginAllDevices"
@logout-all="logoutAllDevices"
/>
<!-- 视频播放区域 -->
<VideoGrid
:grid-type="gridType"
:current-window-index="currentWindowIndex"
:player-instances="playerInstances"
@window-click="handleWindowClick"
/>
<!-- 控制面板 -->
<ControlPanel
:current-device="currentDevice"
:current-window-index="currentWindowIndex"
:is-playing="isPlaying"
:playback-speed="playbackSpeed"
:is-recording="isRecording"
:is-talking="isTalking"
:volume="volume"
:channel="channel"
:stream-type="streamType"
:playback-channel="playbackChannel"
:playback-list="playbackList"
:current-page="currentPage"
:total-pages="totalPages"
@change-grid="handleGridChange"
@change-stream="handleStreamChange"
@change-channel="handleChannelChange"
@start-preview="startPreview"
@stop-preview="stopPreview"
@start-playback="startPlayback"
@stop-playback="stopPlayback"
@toggle-sound="toggleSound"
@toggle-talk="toggleTalk"
@toggle-record="toggleRecord"
@snapshot="captureSnapshot"
@zoom-in="zoomIn"
@zoom-out="zoomOut"
@toggle-fullscreen="toggleFullscreen"
@ptz-control="handlePTZControl"
@change-volume="handleVolumeChange"
@change-speed="handleSpeedChange"
@search-record="searchRecord"
@playback-go="handlePlaybackGo"
@download-record="downloadRecord"
/>
<!-- 回放列表模态框 -->
<PlaybackModal
v-if="showPlaybackModal"
:playback-list="playbackList"
:current-page="currentPage"
:total-pages="totalPages"
@close="showPlaybackModal = false"
@page-change="handlePageChange"
@play="handlePlaybackItemClick"
@download="handleDownloadRecord"
/>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted, watch } from "vue";
import DeviceList from "./components/DeviceList.vue";
import VideoGrid from "./components/VideoGrid.vue";
import ControlPanel from "./components/ControlPanel.vue";
import PlaybackModal from "./components/PlaybackModal.vue";
// 设备相关
const devices = ref([
{
ip: "192.168.0.206",
port: "80",
username: "admin",
password: "bydl666888",
name: "Camera 1",
loggedIn: false,
session: null,
},
]);
const currentDeviceIndex = ref(0);
const currentDevice = computed(() => devices.value[currentDeviceIndex.value]);
// 视频播放相关
const gridType = ref(1); // 1, 2, 3, 4
const currentWindowIndex = ref(0);
const playerInstances = ref(new Array(16).fill(null));
const drawInstances = ref(new Array(16).fill(null));
const ivsInstances = ref(new Array(16).fill(null));
const talkInstances = ref(new Array(16).fill(null));
const recordInstances = ref(new Array(16).fill(null));
// 播放状态
const isPlaying = ref(false);
const isRecording = ref(false);
const isTalking = ref(false);
const playbackSpeed = ref(1);
const volume = ref(50);
// 配置
const channel = ref(0);
const streamType = ref(0);
const playbackChannel = ref(0);
// 回放相关
const playbackList = ref([]);
const currentPage = ref(1);
const totalPages = ref(1);
const playbackOptions = ref(null);
const showPlaybackModal = ref(false);
// 其他
const ChannelPTZCaps = ref([]);
const isStartAll = ref(false);
// 计算属性
const currentPlayer = computed(
() => playerInstances.value[currentWindowIndex.value],
);
// 初始化
onMounted(() => {
init();
addFullscreenListeners();
});
// 清理
onUnmounted(() => {
cleanup();
});
const init = () => {
// 初始化数组
playerInstances.value = new Array(16).fill(null);
drawInstances.value = new Array(16).fill(null);
ivsInstances.value = new Array(16).fill(null);
talkInstances.value = new Array(16).fill(null);
recordInstances.value = new Array(16).fill(null);
};
const addFullscreenListeners = () => {
const events = [
"fullscreenchange",
"webkitfullscreenchange",
"mozfullscreenchange",
"msfullscreenchange",
];
events.forEach((event) => {
document.addEventListener(event, handleFullscreenChange);
});
};
const handleFullscreenChange = () => {
// 处理全屏变化
};
// 设备管理
const handleDeviceClick = async (index) => {
await logoutCurrentDevice();
currentDeviceIndex.value = index;
await loginDevice();
};
const loginDevice = async () => {
const device = currentDevice.value;
try {
//在此次调用大华内置方法设置请求头中的self-targetip字段为摄像头ip+端口
setIP(device.ip + ":" + device.port);
//---------------------------------------------------------------
const res = await RPC.login(device.username, device.password, false);
device.loggedIn = true;
device.session = _getSession();
// 保活
RPC.keepAlive(
300,
60000,
device.session,
`${device.ip}:${device.port}`,
currentWindowIndex.value,
);
// 获取设备能力
await getDeviceCapabilities();
// 开始预览
await startPreview();
} catch (err) {
console.error("Login failed:", err);
handleLoginError(err);
}
};
const logoutCurrentDevice = async () => {
const device = currentDevice.value;
if (!device.session) return;
_setSession(device.session);
try {
await RPC.Global.logout();
// 停止相关播放器
stopWindowPreview(currentWindowIndex.value);
device.loggedIn = false;
device.session = null;
} catch (err) {
console.error("Logout failed:", err);
}
};
const loginAllDevices = async () => {
isStartAll.value = true;
await logoutAllDevices();
currentDeviceIndex.value = 0;
await loginDevice();
};
const logoutAllDevices = async () => {
for (let i = 0; i < devices.value.length; i++) {
if (devices.value[i].session) {
_setSession(devices.value[i].session);
await RPC.Global.logout();
devices.value[i].loggedIn = false;
devices.value[i].session = null;
}
}
// 停止所有窗口的预览
stopAllPreviews();
};
// 视频预览控制
const startPreview = async () => {
const device = currentDevice.value;
// 停止当前窗口的预览
stopWindowPreview(currentWindowIndex.value);
const options = {
wsURL: `ws://${device.ip}:${device.port}/rtspoverwebsocket`,
rtspURL: `rtsp://${device.ip}:${device.port}/cam/realmonitor?channel=${channel.value + 1}&subtype=${streamType.value}&proto=Private3`,
username: device.username,
password: device.password,
lessRateCanvas: true,
wndIndex: currentWindowIndex.value,
h265AccelerationEnabled: true,
};
const player = new PlayerControl(options);
console.log(player);
// 绑定事件
player.on("PlayStart", () => {
isPlaying.value = true;
handlePlayStart();
});
player.on("DecodeStart", (e) => {
initCanvasTools();
});
player.on("Error", (err) => {
console.error("Player error:", err);
isPlaying.value = false;
});
player.on("IvsDraw", (data, index) => {
handleIvsDraw(data, index);
});
player.on("WorkerReady", function () {
player.connect();
});
// 初始化播放器
const canvasElement = document.getElementById(
`h5_canvas_${currentWindowIndex.value}`,
);
const videoElement = document.getElementById(
`h5_video_${currentWindowIndex.value}`,
);
if (canvasElement && videoElement) {
player.init(canvasElement, videoElement);
}
playerInstances.value[currentWindowIndex.value] = player;
// 如果是批量登录,登录下一个设备
if (
isStartAll.value &&
getLoggedInCount() < gridType.value * gridType.value
) {
handleNextWindowLogin();
}
};
const stopWindowPreview = (windowIndex) => {
if (playerInstances.value[windowIndex]) {
playerInstances.value[windowIndex].stop();
playerInstances.value[windowIndex].close();
playerInstances.value[windowIndex] = null;
}
if (talkInstances.val



397

被折叠的 条评论
为什么被折叠?



