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

478 lines
12 KiB
Vue
Raw Normal View History

<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>