zhzf/client/src/views/dtsjygl/video.vue

478 lines
12 KiB
Vue
Raw 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>
<div class="jy-media-container">
<!-- 设备选择区域 -->
<div class="device-selector">
<el-select v-model="selectedDevice" placeholder="选择设备" filterable>
<el-option
v-for="device in onlineDevices"
:key="device.hostbody"
:label="`${device.hostname} (${device.hostbody})`"
:value="device.hostbody"
/>
</el-select>
<el-button-group class="control-buttons">
<el-button
type="primary"
:disabled="!selectedDevice || videoLoading"
@click="startVideoCall"
>
{{ videoLoading ? '呼叫中...' : '视频呼叫' }}
</el-button>
<el-button
type="success"
:disabled="!selectedDevice || audioLoading"
@click="startAudioCall"
>
{{ audioLoading ? '呼叫中...' : '音频呼叫' }}
</el-button>
<el-button
type="warning"
:disabled="!isVideoActive || isAudioInVideoActive"
@click="startAudioInVideo"
>
视频中开启音频
</el-button>
<el-button
type="danger"
:disabled="!isVideoActive && !isAudioActive"
@click="stopAllCalls"
>
停止所有
</el-button>
</el-button-group>
</div>
<!-- 视频播放区域 -->
<div class="video-container">
<video
ref="videoPlayer"
controls
autoplay
playsinline
class="video-element"
:class="{ active: isVideoActive }"
/>
<div v-if="!isVideoActive" class="video-placeholder">
<el-icon :size="50"><VideoCamera /></el-icon>
<p>视频未开启</p>
</div>
</div>
<!-- 状态信息 -->
<div class="status-info">
<el-alert
v-if="errorMessage"
:title="errorMessage"
type="error"
show-icon
closable
/>
<el-alert
v-if="callStatus"
:title="callStatus"
:type="callStatusType"
show-icon
/>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { VideoCamera } from '@element-plus/icons-vue'
import axios from 'axios'
// 响应式数据
const selectedDevice = ref('')
const onlineDevices = ref([])
const isVideoActive = ref(false)
const isAudioActive = ref(false)
const isAudioInVideoActive = ref(false)
const videoLoading = ref(false)
const audioLoading = ref(false)
const errorMessage = ref('')
const callStatus = ref('')
const callStatusType = ref('info')
const videoPlayer = ref(null)
const websocket = ref(null)
// WebSocket连接配置
const WS_URL = 'ws://localhost:5000' // 替换为实际WS地址
const API_BASE = '' // 替换为实际API地址
// 获取在线设备列表
const fetchOnlineDevices = async () => {
try {
const response = await axios.post(`${API_BASE}/rest/other/unitjson/gdlist`, {
id: "1001", // 替换为单位ID
bh: "bh",
text: "dname"
}, {
headers: {
'Content-Type': 'application/json',
'Cookie': `PHPSESSID=${localStorage.getItem('sessionId')}`
}
})
onlineDevices.value = response.data.data[0]?.sub || []
} catch (error) {
console.error('获取设备列表失败:', error)
errorMessage.value = `获取设备列表失败: ${error.message}`
}
}
// 初始化WebSocket连接
const initWebSocket = () => {
websocket.value = new WebSocket(WS_URL)
websocket.value.onopen = () => {
console.log('WebSocket连接已建立')
callStatus.value = '实时通讯连接已建立'
callStatusType.value = 'success'
}
websocket.value.onmessage = (event) => {
const data = JSON.parse(event.data)
console.log('WebSocket消息:', data)
// 处理视频流回调
if (data.zk_start_video) {
handleVideoStream(data.zk_start_video)
}
// 处理音频流回调
if (data.zk_start_audio) {
handleAudioStream(data.zk_start_audio)
}
// 处理平台推流回调
if (data.zk_platform_push_video || data.zk_platform_push_audio) {
handlePlatformStream(data)
}
}
websocket.value.onerror = (error) => {
console.error('WebSocket错误:', error)
errorMessage.value = `实时通讯连接错误: ${error.message}`
}
websocket.value.onclose = () => {
console.log('WebSocket连接已关闭')
callStatus.value = '实时通讯连接已断开'
callStatusType.value = 'warning'
}
}
// 开始视频呼叫
const startVideoCall = async () => {
if (!selectedDevice.value) return
videoLoading.value = true
callStatus.value = '正在发起视频呼叫...'
callStatusType.value = 'info'
try {
const response = await axios.post(`${API_BASE}/rest/live/chrome/startLive`, {
hostbody_arr: [selectedDevice.value],
callId: "",
type: ""
}, {
headers: {
'Content-Type': 'application/json',
'Cookie': `PHPSESSID=${localStorage.getItem('sessionId')}`
}
})
if (response.data.code === 200) {
callStatus.value = '视频呼叫已发起,等待设备响应...'
callStatusType.value = 'success'
} else {
throw new Error(response.data.msg)
}
} catch (error) {
console.error('视频呼叫失败:', error)
errorMessage.value = `视频呼叫失败: ${error.message}`
callStatus.value = `视频呼叫失败: ${error.message}`
callStatusType.value = 'error'
} finally {
videoLoading.value = false
}
}
// 开始音频呼叫
const startAudioCall = async () => {
if (!selectedDevice.value) return
audioLoading.value = true
callStatus.value = '正在发起音频呼叫...'
callStatusType.value = 'info'
try {
const response = await axios.post(`${API_BASE}/rest/live/chrome/startAudio`, {
hostbody_arr: [selectedDevice.value],
callId: ""
}, {
headers: {
'Content-Type': 'application/json',
'Cookie': `PHPSESSID=${localStorage.getItem('sessionId')}`
}
})
if (response.data.code === 200) {
callStatus.value = '音频呼叫已发起,等待设备响应...'
callStatusType.value = 'success'
} else {
throw new Error(response.data.msg)
}
} catch (error) {
console.error('音频呼叫失败:', error)
errorMessage.value = `音频呼叫失败: ${error.message}`
callStatus.value = `音频呼叫失败: ${error.message}`
callStatusType.value = 'error'
} finally {
audioLoading.value = false
}
}
// 视频中开启音频
const startAudioInVideo = async () => {
if (!selectedDevice.value || !isVideoActive.value) return
callStatus.value = '正在视频通话中开启音频...'
callStatusType.value = 'info'
try {
const response = await axios.post(`${API_BASE}/rest/live/chrome/startAudioInVideo`, {
hostbody: selectedDevice.value
}, {
headers: {
'Content-Type': 'application/json',
'Cookie': `PHPSESSID=${localStorage.getItem('sessionId')}`
}
})
if (response.data.code === 200) {
isAudioInVideoActive.value = true
callStatus.value = '已在视频通话中开启音频'
callStatusType.value = 'success'
// 处理平台音频流
if (response.data.data.platform_rtsp) {
setupAudioStream(response.data.data.platform_rtsp)
}
} else {
throw new Error(response.data.msg)
}
} catch (error) {
console.error('开启音频失败:', error)
errorMessage.value = `开启音频失败: ${error.message}`
callStatus.value = `开启音频失败: ${error.message}`
callStatusType.value = 'error'
}
}
// 处理视频流
const handleVideoStream = (streamData) => {
if (streamData.hostbody !== selectedDevice.value) return
isVideoActive.value = true
callStatus.value = '视频流已连接'
callStatusType.value = 'success'
// 优先使用WebRTC流回退到RTSP
const streamUrl = streamData.webrtc_url || streamData.rtsp_url
if (streamUrl) {
setupVideoStream(streamUrl)
}
}
// 处理音频流
const handleAudioStream = (streamData) => {
if (streamData.hostbody !== selectedDevice.value) return
isAudioActive.value = true
callStatus.value = '音频流已连接'
callStatusType.value = 'success'
// 优先使用WebRTC流回退到RTSP
const streamUrl = streamData.webrtc_url || streamData.rtsp_url
if (streamUrl) {
setupAudioStream(streamUrl)
}
}
// 处理平台推流
const handlePlatformStream = (data) => {
if (data.zk_platform_push_video && data.zk_platform_push_video.status === 1) {
callStatus.value = '平台视频推流已开始'
callStatusType.value = 'success'
}
if (data.zk_platform_push_audio && data.zk_platform_push_audio.status === 1) {
callStatus.value = '平台音频推流已开始'
callStatusType.value = 'success'
}
}
// 设置视频流
const setupVideoStream = (streamUrl) => {
const video = videoPlayer.value
if (!video) return
// 实际项目中可能需要使用专门的播放器库(如hls.js)处理流媒体
if (streamUrl.startsWith('rtsp://')) {
// RTSP流需要转协议实际项目中应使用转码服务
console.warn('浏览器不支持直接播放RTSP流需要转协议')
errorMessage.value = '浏览器不支持直接播放RTSP流需要后端转协议'
} else {
video.src = streamUrl
video.play().catch(err => {
console.error('视频播放错误:', err)
errorMessage.value = `视频播放错误: ${err.message}`
})
}
}
// 设置音频流
const setupAudioStream = (streamUrl) => {
// 在实际项目中可能需要创建单独的audio元素处理音频流
console.log('音频流地址:', streamUrl)
// 实现逻辑与视频流类似
}
// 停止所有呼叫
const stopAllCalls = async () => {
if (!selectedDevice.value) return
callStatus.value = '正在停止所有呼叫...'
callStatusType.value = 'info'
try {
// 停止视频
if (isVideoActive.value) {
await axios.post(`${API_BASE}/rest/live/chrome/stopLive`, {
hostbody_arr: [selectedDevice.value]
}, {
headers: {
'Content-Type': 'application/json',
'Cookie': `PHPSESSID=${localStorage.getItem('sessionId')}`
}
})
}
// 停止音频
if (isAudioActive.value || isAudioInVideoActive.value) {
await axios.post(`${API_BASE}/rest/live/chrome/stopAudio`, {
hostbody_arr: [selectedDevice.value],
wsChannelId_arr: [] // 实际项目中需要填充正确的wsChannelId
}, {
headers: {
'Content-Type': 'application/json',
'Cookie': `PHPSESSID=${localStorage.getItem('sessionId')}`
}
})
}
// 重置状态
isVideoActive.value = false
isAudioActive.value = false
isAudioInVideoActive.value = false
// 清除视频源
if (videoPlayer.value) {
videoPlayer.value.src = ''
}
callStatus.value = '所有呼叫已停止'
callStatusType.value = 'success'
} catch (error) {
console.error('停止呼叫失败:', error)
errorMessage.value = `停止呼叫失败: ${error.message}`
callStatus.value = `停止呼叫失败: ${error.message}`
callStatusType.value = 'error'
}
}
// 组件生命周期
onMounted(() => {
fetchOnlineDevices()
initWebSocket()
})
onBeforeUnmount(() => {
if (websocket.value) {
websocket.value.close()
}
})
</script>
<style scoped>
.jy-media-container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f5f7fa;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.device-selector {
display: flex;
gap: 10px;
margin-bottom: 20px;
align-items: center;
}
.control-buttons {
margin-left: 10px;
}
.video-container {
position: relative;
width: 100%;
height: 0;
padding-bottom: 56.25%; /* 16:9 比例 */
background-color: #000;
border-radius: 4px;
overflow: hidden;
}
.video-element {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain;
display: none;
}
.video-element.active {
display: block;
}
.video-placeholder {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #fff;
background-color: rgba(0, 0, 0, 0.5);
}
.status-info {
margin-top: 20px;
}
</style>