Commit 158bff00 authored by lichunliang's avatar lichunliang

fix:首页更换video视频播放

parent e48297f3
...@@ -25,10 +25,7 @@ ...@@ -25,10 +25,7 @@
<h3 class="status-title">视频分析任务启动状态</h3> <h3 class="status-title">视频分析任务启动状态</h3>
<div class="status-group"> <div class="status-group">
<div class="status-item" style="height: 300px"> <div class="status-item" style="height: 300px">
<pie-chart <pie-chart :option="VideoAnalysisOnlineData" v-if="isDataLoaded" />
:option="VideoAnalysisOnlineData"
v-if="isDataLoaded"
/>
</div> </div>
</div> </div>
</div> </div>
...@@ -42,22 +39,15 @@ ...@@ -42,22 +39,15 @@
</template> </template>
<template v-else> <template v-else>
<span>请选择摄像头和算法</span> <span>请选择摄像头和算法</span>
</template></span </template></span>
> <el-cascader v-model="selectVidoe" @change="handleCascaderChange" :options="videoList" :props="{
<el-cascader
v-model="selectVidoe"
@change="handleCascaderChange"
:options="videoList"
:props="{
value: 'value', value: 'value',
label: 'label', label: 'label',
children: 'children', children: 'children',
}" }" clearable></el-cascader>
clearable
></el-cascader>
</h3> </h3>
<div class="video-box" ref="videoContainer"> <div class="video-box" ref="videoContainer">
<iframe <!-- <iframe
:src="iframeSrc" :src="iframeSrc"
:style="{ :style="{
width: width + 'px', width: width + 'px',
...@@ -65,7 +55,19 @@ ...@@ -65,7 +55,19 @@
border: 'none', border: 'none',
}" }"
ref="analysisIframe" 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>
</div> </div>
</el-col> </el-col>
...@@ -75,15 +77,8 @@ ...@@ -75,15 +77,8 @@
<div class="status-container"> <div class="status-container">
<h3 class="status-title both-end"> <h3 class="status-title both-end">
<span>统计分析(条/天)</span> <span>统计分析(条/天)</span>
<el-date-picker <el-date-picker v-model="pickerDate" type="daterange" range-separator="至" start-placeholder="开始日期"
v-model="pickerDate" end-placeholder="结束日期" value-format="yyyy-MM-dd" @change="getData">
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
@change="getData"
>
</el-date-picker> </el-date-picker>
</h3> </h3>
<line-chart :chartData="StatisticsData" v-if="isDataLoaded" /> <line-chart :chartData="StatisticsData" v-if="isDataLoaded" />
...@@ -96,28 +91,16 @@ ...@@ -96,28 +91,16 @@
<h3 class="status-title both-end"> <h3 class="status-title both-end">
<span>实时报警</span> <span>实时报警</span>
</h3> </h3>
<div <div class="card-box" v-for="item of alarmList" :key="item.logId" @click="lookAlarm(item)">
class="card-box"
v-for="item of alarmList"
:key="item.logId"
@click="lookAlarm(item)"
>
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<dict-tag <dict-tag :options="dict.type.algorithm_level" :value="item.alarmLevel" />
:options="dict.type.algorithm_level"
:value="item.alarmLevel"
/>
<p>{{ item.algorithmName }}</p> <p>{{ item.algorithmName }}</p>
<p>{{ item.cameraName }}</p> <p>{{ item.cameraName }}</p>
<p>{{ item.alarmTime }}</p> <p>{{ item.alarmTime }}</p>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<image-preview <image-preview :src="item.alarmImageUrl" :width="170" :height="145" />
:src="item.alarmImageUrl"
:width="170"
:height="145"
/>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
...@@ -127,36 +110,23 @@ ...@@ -127,36 +110,23 @@
<el-dialog :visible.sync="dialogVisible" width="60%"> <el-dialog :visible.sync="dialogVisible" width="60%">
<div slot="title" class="dialog-title"> <div slot="title" class="dialog-title">
<span> <span>
<dict-tag <dict-tag style="display: inline-block; margin-right: 10px" :options="dict.type.algorithm_level"
style="display: inline-block; margin-right: 10px" :value="currentAlarm.alarmLevel" />
:options="dict.type.algorithm_level"
:value="currentAlarm.alarmLevel"
/>
<span style="margin-right: 20px">{{ <span style="margin-right: 20px">{{
(currentAlarm && currentAlarm.alarmMessage) || "报警详情" (currentAlarm && currentAlarm.alarmMessage) || "报警详情"
}}</span> }}</span>
<span style="margin-right: 20px"> <span style="margin-right: 20px">
{{ currentAlarm && currentAlarm.cameraPosition }}</span {{ currentAlarm && currentAlarm.cameraPosition }}</span>
>
<span style="margin-right: 20px">{{ <span style="margin-right: 20px">{{
currentAlarm && currentAlarm.alarmTime currentAlarm && currentAlarm.alarmTime
}}</span> }}</span>
</span> </span>
<el-button <el-button @click="playBack" :disabled="currentAlarm.videoStatus === 0" type="text" class="right-btn">
@click="playBack" <i v-if="lookBackVideo === 0" :class="currentAlarm.videoStatus === 1
:disabled="currentAlarm.videoStatus === 0"
type="text"
class="right-btn"
>
<i
v-if="lookBackVideo === 0"
:class="
currentAlarm.videoStatus === 1
? 'el-icon-video-play' ? 'el-icon-video-play'
: 'el-icon-loading' : 'el-icon-loading'
" "></i>
></i>
{{ {{
lookBackVideo === 1 lookBackVideo === 1
? "查看图片" ? "查看图片"
...@@ -167,18 +137,9 @@ ...@@ -167,18 +137,9 @@
</el-button> </el-button>
</div> </div>
<div v-if="currentAlarm"> <div v-if="currentAlarm">
<img <img v-if="lookBackVideo === 0" :src="currentAlarm.alarmImageUrl" style="width: 100%; height: 400px" />
v-if="lookBackVideo === 0" <video v-if="lookBackVideo === 1" :src="currentAlarm.videoBackUrl" style="width: 100%; height: 400px" controls
:src="currentAlarm.alarmImageUrl" autoplay></video>
style="width: 100%; height: 400px"
/>
<video
v-if="lookBackVideo === 1"
:src="currentAlarm.videoBackUrl"
style="width: 100%; height: 400px"
controls
autoplay
></video>
</div> </div>
</el-dialog> </el-dialog>
</div> </div>
...@@ -194,6 +155,10 @@ import { ...@@ -194,6 +155,10 @@ import {
getVideoPlayUrlWithAlgorithm, getVideoPlayUrlWithAlgorithm,
} from "@/api/business/home.js"; } from "@/api/business/home.js";
import { getAlarmLogList } from "@/api/business/alarmlog.js"; import { getAlarmLogList } from "@/api/business/alarmlog.js";
// 播放rtmp视频流
import "video.js/dist/video-js.css";
import videojs from "video.js";
export default { export default {
dicts: ["algorithm_level"], dicts: ["algorithm_level"],
name: "Index", name: "Index",
...@@ -222,6 +187,9 @@ export default { ...@@ -222,6 +187,9 @@ export default {
height: 0, height: 0,
resizeObserver: null, resizeObserver: null,
iframeUrl: process.env.VUE_APP_IFRAME_URL, iframeUrl: process.env.VUE_APP_IFRAME_URL,
player: null,
// 加载完成状态
isLoaded: false,
}; };
}, },
created() { created() {
...@@ -241,6 +209,27 @@ export default { ...@@ -241,6 +209,27 @@ export default {
this.init(); this.init();
this.getAlarmLog(); 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: { methods: {
async init() { async init() {
// 使用Promise.all等待所有请求完成 // 使用Promise.all等待所有请求完成
...@@ -351,31 +340,76 @@ export default { ...@@ -351,31 +340,76 @@ export default {
this.iframeSrc = `${this.iframeUrl}/view/${value[0]}/${value[1]}`; this.iframeSrc = `${this.iframeUrl}/view/${value[0]}/${value[1]}`;
} }
}, },
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'
]
}, },
mounted() { // 自定义主题
this.$nextTick(() => { userActions: {
// 初始化观察器 hotkeys: true
this.resizeObserver = new ResizeObserver((entries) => { }
const { width, height } = entries[0].contentRect; });
this.width = width;
this.height = height; // 添加错误处理
// 传递尺寸到iframe内部(需同源) this.player.on('error', (error) => {
this.$refs.analysisIframe.contentWindow.postMessage( console.error('Video player error:', error);
{ const errorDetails = this.player.error();
type: "resize", console.log('Error details:', errorDetails);
width,
height, if (errorDetails && errorDetails.code === 4) {
}, this.$message.error('视频格式不支持或网络连接失败');
"*" } else {
); this.$message.error('视频加载失败,请检查网络连接');
}
}); });
this.resizeObserver.observe(this.$refs.videoContainer);
// 添加加载状态处理
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",
});
},
}, },
beforeDestroy() { beforeDestroy() {
if (this.resizeObserver) { // if (this.resizeObserver) {
this.resizeObserver.disconnect(); // this.resizeObserver.disconnect();
this.resizeObserver = null; // 重要!解除引用 // this.resizeObserver = null; // 重要!解除引用
// }
// 销毁视频播放器
if (this.player) {
this.player.dispose();
this.player = null;
} }
}, },
}; };
...@@ -383,85 +417,302 @@ export default { ...@@ -383,85 +417,302 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.app-container { .app-container {
padding-right: 0; padding: 24px;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
.fs30 { .fs30 {
font-size: 38px; font-size: 38px;
} }
.ac { .ac {
text-align: center; text-align: center;
} }
// 响应式设计
@media (max-width: 768px) {
padding: 16px;
.status-container { .status-container {
padding: 20px; padding: 16px;
background: #f8f9fa; margin-bottom: 16px;
border-radius: 5px; }
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
margin-bottom: 20px; .status-title {
font-size: 16px;
}
.status-item {
padding: 16px;
.count {
font-size: 28px;
}
}
}
.status-container {
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 { &.h100 {
height: 100vh; height: 100vh;
} }
.status-title { .status-title {
margin: 0 0 20px 0; margin: 0 0 24px 0;
font-size: 16px; font-size: 18px;
color: #333; 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 { .title-icon {
margin-right: 8px; margin-right: 12px;
font-size: 20px; font-size: 22px;
color: #409eff; color: #409eff;
vertical-align: middle;
} }
} }
.both-end { .both-end {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.card-box { .card-box {
border-radius: 5px; border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
width: 100%; width: 100%;
padding: 15px; padding: 20px;
margin-top: 10px; margin-top: 16px;
cursor: pointer; 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 { .status-group {
display: flex; display: flex;
gap: 20px; gap: 20px;
.status-item { .status-item {
flex: 1; flex: 1;
text-align: center; 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 { .count {
font-size: 32px; font-size: 36px;
font-weight: bold; font-weight: 700;
display: block; 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 { &.online {
color: #67c23a; background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
} }
&.offline { &.offline {
color: #f56c6c; background: linear-gradient(135deg, #f56c6c 0%, #f78989 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
} }
} }
.label { .label {
font-size: 14px; font-size: 15px;
color: #666; color: #606266;
font-weight: 500;
.svg-icon { .svg-icon {
vertical-align: middle; vertical-align: middle;
margin-right: 5px; margin-right: 8px;
font-size: 18px; font-size: 20px;
color: #409eff;
} }
} }
} }
} }
.video-box { .video-box {
height: 440px; height: 440px;
width: 100%; width: 100%;
position: relative; position: relative;
flex-shrink: 0; // 禁止弹性收缩 flex-shrink: 0; // 禁止弹性收缩
overflow: hidden; // 添加溢出隐藏 overflow: hidden; // 添加溢出隐藏
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
background: #000;
iframe { iframe {
width: 100%; width: 100%;
height: 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, .loading-overlay,
.error-overlay { .error-overlay {
position: absolute; position: absolute;
...@@ -469,23 +720,56 @@ export default { ...@@ -469,23 +720,56 @@ export default {
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
text-align: center; text-align: center;
i { i {
font-size: 40px; font-size: 40px;
} }
&-text { &-text {
font-size: 14px; font-size: 14px;
} }
} }
.error-overlay { .error-overlay {
color: #f56c6c; 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 { .dialog-title {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
width: 100%; width: 100%;
.right-btn { .right-btn {
margin-right: 40px; margin-right: 40px;
} }
......
...@@ -48,12 +48,13 @@ module.exports = { ...@@ -48,12 +48,13 @@ module.exports = {
}, },
'/proxy-iframe': { '/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, changeOrigin: true,
pathRewrite: { pathRewrite: {
'^/proxy-iframe': '' // 替换为实际路径前缀 '^/proxy-iframe': '' // 替换为实际路径前缀
} }
} },
}, },
disableHostCheck: true 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