Commit 158bff00 authored by lichunliang's avatar lichunliang

fix:首页更换video视频播放

parent e48297f3
......@@ -25,10 +25,7 @@
<h3 class="status-title">视频分析任务启动状态</h3>
<div class="status-group">
<div class="status-item" style="height: 300px">
<pie-chart
:option="VideoAnalysisOnlineData"
v-if="isDataLoaded"
/>
<pie-chart :option="VideoAnalysisOnlineData" v-if="isDataLoaded" />
</div>
</div>
</div>
......@@ -42,22 +39,15 @@
</template>
<template v-else>
<span>请选择摄像头和算法</span>
</template></span
>
<el-cascader
v-model="selectVidoe"
@change="handleCascaderChange"
:options="videoList"
:props="{
value: 'value',
label: 'label',
children: 'children',
}"
clearable
></el-cascader>
</template></span>
<el-cascader v-model="selectVidoe" @change="handleCascaderChange" :options="videoList" :props="{
value: 'value',
label: 'label',
children: 'children',
}" clearable></el-cascader>
</h3>
<div class="video-box" ref="videoContainer">
<iframe
<!-- <iframe
:src="iframeSrc"
:style="{
width: width + 'px',
......@@ -65,7 +55,19 @@
border: 'none',
}"
ref="analysisIframe"
></iframe>
></iframe> -->
<div class="video-container">
<video id="videoPlayer" ref="videoPlayer" class="video-js vjs-default-skin" autoplay muted controls
preload="auto"></video>
<div class="video-placeholder" v-if="!isLoaded">
<div class="placeholder-content">
<i class="el-icon-video-camera"></i>
<p>视频加载中...</p>
<div class="loading-spinner"></div>
</div>
</div>
</div>
</div>
</div>
</el-col>
......@@ -75,15 +77,8 @@
<div class="status-container">
<h3 class="status-title both-end">
<span>统计分析(条/天)</span>
<el-date-picker
v-model="pickerDate"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
@change="getData"
>
<el-date-picker v-model="pickerDate" type="daterange" range-separator="至" start-placeholder="开始日期"
end-placeholder="结束日期" value-format="yyyy-MM-dd" @change="getData">
</el-date-picker>
</h3>
<line-chart :chartData="StatisticsData" v-if="isDataLoaded" />
......@@ -96,28 +91,16 @@
<h3 class="status-title both-end">
<span>实时报警</span>
</h3>
<div
class="card-box"
v-for="item of alarmList"
:key="item.logId"
@click="lookAlarm(item)"
>
<div class="card-box" v-for="item of alarmList" :key="item.logId" @click="lookAlarm(item)">
<el-row>
<el-col :span="12">
<dict-tag
:options="dict.type.algorithm_level"
:value="item.alarmLevel"
/>
<dict-tag :options="dict.type.algorithm_level" :value="item.alarmLevel" />
<p>{{ item.algorithmName }}</p>
<p>{{ item.cameraName }}</p>
<p>{{ item.alarmTime }}</p>
</el-col>
<el-col :span="12">
<image-preview
:src="item.alarmImageUrl"
:width="170"
:height="145"
/>
<image-preview :src="item.alarmImageUrl" :width="170" :height="145" />
</el-col>
</el-row>
</div>
......@@ -127,58 +110,36 @@
<el-dialog :visible.sync="dialogVisible" width="60%">
<div slot="title" class="dialog-title">
<span>
<dict-tag
style="display: inline-block; margin-right: 10px"
:options="dict.type.algorithm_level"
:value="currentAlarm.alarmLevel"
/>
<dict-tag style="display: inline-block; margin-right: 10px" :options="dict.type.algorithm_level"
:value="currentAlarm.alarmLevel" />
<span style="margin-right: 20px">{{
(currentAlarm && currentAlarm.alarmMessage) || "报警详情"
}}</span>
<span style="margin-right: 20px">
{{ currentAlarm && currentAlarm.cameraPosition }}</span
>
{{ currentAlarm && currentAlarm.cameraPosition }}</span>
<span style="margin-right: 20px">{{
currentAlarm && currentAlarm.alarmTime
}}</span>
</span>
<el-button
@click="playBack"
:disabled="currentAlarm.videoStatus === 0"
type="text"
class="right-btn"
>
<i
v-if="lookBackVideo === 0"
:class="
currentAlarm.videoStatus === 1
? 'el-icon-video-play'
: 'el-icon-loading'
"
></i>
<el-button @click="playBack" :disabled="currentAlarm.videoStatus === 0" type="text" class="right-btn">
<i v-if="lookBackVideo === 0" :class="currentAlarm.videoStatus === 1
? 'el-icon-video-play'
: 'el-icon-loading'
"></i>
{{
lookBackVideo === 1
? "查看图片"
: currentAlarm.videoStatus === 1
? "查看回放"
: "视频加载中"
? "查看回放"
: "视频加载中"
}}
</el-button>
</div>
<div v-if="currentAlarm">
<img
v-if="lookBackVideo === 0"
:src="currentAlarm.alarmImageUrl"
style="width: 100%; height: 400px"
/>
<video
v-if="lookBackVideo === 1"
:src="currentAlarm.videoBackUrl"
style="width: 100%; height: 400px"
controls
autoplay
></video>
<img v-if="lookBackVideo === 0" :src="currentAlarm.alarmImageUrl" style="width: 100%; height: 400px" />
<video v-if="lookBackVideo === 1" :src="currentAlarm.videoBackUrl" style="width: 100%; height: 400px" controls
autoplay></video>
</div>
</el-dialog>
</div>
......@@ -194,6 +155,10 @@ import {
getVideoPlayUrlWithAlgorithm,
} from "@/api/business/home.js";
import { getAlarmLogList } from "@/api/business/alarmlog.js";
// 播放rtmp视频流
import "video.js/dist/video-js.css";
import videojs from "video.js";
export default {
dicts: ["algorithm_level"],
name: "Index",
......@@ -222,6 +187,9 @@ export default {
height: 0,
resizeObserver: null,
iframeUrl: process.env.VUE_APP_IFRAME_URL,
player: null,
// 加载完成状态
isLoaded: false,
};
},
created() {
......@@ -241,6 +209,27 @@ export default {
this.init();
this.getAlarmLog();
},
mounted() {
// this.$nextTick(() => {
// // 初始化观察器
// this.resizeObserver = new ResizeObserver((entries) => {
// const { width, height } = entries[0].contentRect;
// this.width = width;
// this.height = height;
// // 传递尺寸到iframe内部(需同源)
// this.$refs.analysisIframe.contentWindow.postMessage(
// {
// type: "resize",
// width,
// height,
// },
// "*"
// );
// });
// this.resizeObserver.observe(this.$refs.videoContainer);
// });
this.initVideoPlayer();
},
methods: {
async init() {
// 使用Promise.all等待所有请求完成
......@@ -351,31 +340,76 @@ export default {
this.iframeSrc = `${this.iframeUrl}/view/${value[0]}/${value[1]}`;
}
},
},
mounted() {
this.$nextTick(() => {
// 初始化观察器
this.resizeObserver = new ResizeObserver((entries) => {
const { width, height } = entries[0].contentRect;
this.width = width;
this.height = height;
// 传递尺寸到iframe内部(需同源)
this.$refs.analysisIframe.contentWindow.postMessage(
{
type: "resize",
width,
height,
},
"*"
);
initVideoPlayer() {
this.player = videojs(this.$refs.videoPlayer, {
controls: true,
preload: 'auto',
fluid: true,
responsive: true,
aspectRatio: '16:9',
playbackRates: [0.5, 0.75, 1, 1.25, 1.5, 2],
controlBar: {
children: [
'playToggle',
'volumePanel',
'currentTimeDisplay',
'timeDivider',
'durationDisplay',
'progressControl',
'playbackRateMenuButton',
'qualitySelector',
'fullscreenToggle'
]
},
// 自定义主题
userActions: {
hotkeys: true
}
});
// 添加错误处理
this.player.on('error', (error) => {
console.error('Video player error:', error);
const errorDetails = this.player.error();
console.log('Error details:', errorDetails);
if (errorDetails && errorDetails.code === 4) {
this.$message.error('视频格式不支持或网络连接失败');
} else {
this.$message.error('视频加载失败,请检查网络连接');
}
});
// 添加加载状态处理
this.player.on('loadstart', () => {
console.log('开始加载视频');
this.isLoaded = false;
this.$refs.videoPlayer.classList.add('loading');
});
this.player.on('loadeddata', () => {
console.log('视频数据加载完成');
this.isLoaded = true;
this.$refs.videoPlayer.classList.remove('loading');
});
// 设置视频源
this.player.src({
src: "proxy-iframe/output.m3u8",
// type: "application/x-mpegURL",
});
this.resizeObserver.observe(this.$refs.videoContainer);
});
},
},
beforeDestroy() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
this.resizeObserver = null; // 重要!解除引用
// if (this.resizeObserver) {
// this.resizeObserver.disconnect();
// this.resizeObserver = null; // 重要!解除引用
// }
// 销毁视频播放器
if (this.player) {
this.player.dispose();
this.player = null;
}
},
};
......@@ -383,85 +417,302 @@ export default {
<style lang="scss" scoped>
.app-container {
padding-right: 0;
padding: 24px;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
.fs30 {
font-size: 38px;
}
.ac {
text-align: center;
}
// 响应式设计
@media (max-width: 768px) {
padding: 16px;
.status-container {
padding: 16px;
margin-bottom: 16px;
}
.status-title {
font-size: 16px;
}
.status-item {
padding: 16px;
.count {
font-size: 28px;
}
}
}
.status-container {
padding: 20px;
background: #f8f9fa;
border-radius: 5px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
padding: 24px;
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
margin-bottom: 24px;
border: 1px solid rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
}
&.h100 {
height: 100vh;
}
.status-title {
margin: 0 0 20px 0;
font-size: 16px;
color: #333;
margin: 0 0 24px 0;
font-size: 18px;
font-weight: 600;
color: #2c3e50;
position: relative;
padding-left: 16px;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 20px;
background: linear-gradient(135deg, #409eff, #67c23a);
border-radius: 2px;
}
.title-icon {
margin-right: 8px;
font-size: 20px;
margin-right: 12px;
font-size: 22px;
color: #409eff;
vertical-align: middle;
}
}
.both-end {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-box {
border-radius: 5px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border-radius: 12px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
width: 100%;
padding: 15px;
margin-top: 10px;
padding: 20px;
margin-top: 16px;
cursor: pointer;
background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(255, 255, 255, 0.8);
transition: all 0.3s ease;
backdrop-filter: blur(10px);
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
background: rgba(255, 255, 255, 1);
}
p {
margin: 8px 0;
color: #606266;
font-size: 14px;
line-height: 1.5;
}
}
.status-group {
display: flex;
gap: 20px;
.status-item {
flex: 1;
text-align: center;
padding: 20px;
background: rgba(255, 255, 255, 0.8);
border-radius: 12px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
transition: all 0.3s ease;
border: 1px solid rgba(255, 255, 255, 0.9);
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
.count {
font-size: 32px;
font-weight: bold;
font-size: 36px;
font-weight: 700;
display: block;
margin-bottom: 8px;
margin-bottom: 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
&.online {
color: #67c23a;
background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
&.offline {
color: #f56c6c;
background: linear-gradient(135deg, #f56c6c 0%, #f78989 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
}
.label {
font-size: 14px;
color: #666;
font-size: 15px;
color: #606266;
font-weight: 500;
.svg-icon {
vertical-align: middle;
margin-right: 5px;
font-size: 18px;
margin-right: 8px;
font-size: 20px;
color: #409eff;
}
}
}
}
.video-box {
height: 440px;
width: 100%;
position: relative;
flex-shrink: 0; // 禁止弹性收缩
overflow: hidden; // 添加溢出隐藏
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
background: #000;
iframe {
width: 100%;
height: 100%;
}
.video-container {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
border-radius: 8px;
overflow: hidden;
// 视频播放器样式
.video-js {
width: 100% !important;
height: 100% !important;
border-radius: 8px;
// 自定义视频播放器主题
.vjs-control-bar {
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
border-radius: 0 0 8px 8px;
}
.vjs-progress-control {
.vjs-progress-holder {
height: 4px;
border-radius: 2px;
}
.vjs-play-progress {
background: linear-gradient(90deg, #409eff, #67c23a);
border-radius: 2px;
}
}
.vjs-play-control {
.vjs-icon-placeholder {
color: #409eff;
}
}
.vjs-volume-panel {
.vjs-volume-control {
.vjs-volume-bar {
background: rgba(255, 255, 255, 0.3);
border-radius: 2px;
}
}
}
.vjs-fullscreen-control {
.vjs-icon-placeholder {
color: #409eff;
}
}
}
// 加载状态
&.loading {
.video-js {
opacity: 0.7;
}
}
}
.video-placeholder {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
z-index: 10;
border-radius: 8px;
.placeholder-content {
text-align: center;
color: #fff;
i {
font-size: 64px;
margin-bottom: 20px;
display: block;
opacity: 0.8;
animation: pulse 2s infinite;
}
p {
font-size: 16px;
margin: 0 0 20px 0;
font-weight: 300;
letter-spacing: 1px;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-top: 3px solid #409eff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto;
}
}
}
.loading-overlay,
.error-overlay {
position: absolute;
......@@ -469,23 +720,56 @@ export default {
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
i {
font-size: 40px;
}
&-text {
font-size: 14px;
}
}
.error-overlay {
color: #f56c6c;
}
}
// 动画效果
@keyframes pulse {
0% {
transform: scale(1);
opacity: 0.8;
}
50% {
transform: scale(1.1);
opacity: 1;
}
100% {
transform: scale(1);
opacity: 0.8;
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
}
.dialog-title {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
.right-btn {
margin-right: 40px;
}
......
......@@ -48,12 +48,13 @@ module.exports = {
},
'/proxy-iframe': {
target: 'http://192.168.2.22:5000', // 替换为实际目标地址
// target: 'http://192.168.2.22:5000', // 替换为实际目标地址
target: 'http://192.168.2.23:8080', // 替换为实际目标地址
changeOrigin: true,
pathRewrite: {
'^/proxy-iframe': '' // 替换为实际路径前缀
}
}
},
},
disableHostCheck: true
},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment