Commit a0b118cc authored by 李纪文's avatar 李纪文

feat: 增加vms视频回放

parent d84fc0f8
......@@ -15,8 +15,14 @@ group:
**需安装的依赖项: ezuikit-js axios (1.npm i ezuikit-js 2.npm i axios)**
### 视频
<code src="./demos/dmeo1.tsx">
### 视频回放
<code src="./demos/dmeo2.tsx">
## 参数说明
| JessibucaObj 常用参数(对萤石 EZOPEN 协议不生效) | 说明 | 类型 | 默认值 |
......
import { request } from '@wisdom-utils/utils';
const REQUEST_METHOD_GET = 'get';
const REQUEST_METHOD_POST = 'post';
// eslint-disable-next-line no-undef
const baseURI = typeof DUMI_TYPE !== 'undefined' && DUMI_TYPE === 'dumi' ? '/api' : '';
const CommonPath = window?.globalConfig?.hasGateWay ? '/PandaCore/GateWay' : '';
// 录像回放
export function videoPlayback(params) {
return request({
url: `${baseURI}${CommonPath}/pandavms/camera/playback`,
method: REQUEST_METHOD_GET,
params,
});
}
// 根据时间轴时间选定 查看历史视频
export function newPlayback(params) {
return request({
url: `${baseURI}${CommonPath}/pandavms/camera/newpalyback`,
method: REQUEST_METHOD_GET,
params,
});
}
import React, { useRef, useEffect, useState } from 'react';
import Video from '../recVideo';
const Demo2 = (props) => {
const jessibuca = useRef(null);
let JessibucaObj = {
operateBtns: {
// fullscreen: false,
screenshot: false,
},
loadingText: '演示视频加载中',
decoder: '/JessibucaVideo/decoder.js',
};
// 若是在子应用中,则需加上子应用名称,且兼容基座
// 如下:
// let _url = '/civ_energy/JessibucaVideo/decoder.js'
// let JessibucaObj = {
// decoder: window.__POWERED_BY_QIANKUN__ ? '/civbase' + _url : _url
// }
// let VideoInfo = {
// key: '123638446', //'1CEB209F-8BC7-44B5-9F6B-3D8FCB855E76',
// dataRate: `/2`,
// fullUrl: 'ws://172.16.19.19:8080/jessica/1CEB209F-8BC7-44B5-9F6B-3D8FCB855E76',
// useFullUrl: true,
// cameraName: `摄像头s8`,
// appKey: '6c44c8e92d1c4d75a9818756025df550',
// appSecret: '78b7dc88f9f4bf19c2b1aabfdd995244',
// protocol: '萤石EZOPEN',
//};
let VideoParam = {
id: '9745259F-76B5-4ECB-BDD7-8B1B2C5C84CD',
name: '3L00AE9PAJ00034',
protocol: '乐橙云HTTP-FLV',
username: 'lc0f4b952c86c34c4b',
password: 'dfdcae9267bf4964ae09998e16f016',
pandavmsHost: 'ws://192.168.12.154:8080/',
address: '3L00AE9PAJ00034',
};
VideoParam = {
channel: '2',
// dataRate: 'Sub', // Main 主码流 Sub 子码流
name: 'RTSP银河湾五期_通道_2',
id: '46D6E990-8049-4A61-B592-8503BDFAA07A',
username: 'admin',
password: 'zls_1234@abcd',
address: '172.16.19.2',
protocol: 'RTSP',
pandavmsHost: 'ws://192.168.8.30:7000/', // pandavms后端主机地址 eg: ws://172.16.19.19:8080/
hoursRuler: 48,
beginTime: '2024-01-01 00:00:00', // 回放开始时间
endTime: '2024-01-02 23:59:59', // 回放结束时间
};
const [VideoInfo, setVideoInfo] = useState(VideoParam);
useEffect(() => {
// 事件 ,返回视频信息
jessibuca &&
jessibuca.current &&
jessibuca.current.on('videoInfo', function (data) {
console.log('width:', data.width, 'height:', data.width);
});
// 事件 ,播放视频之后的回调
jessibuca &&
jessibuca.current &&
jessibuca.current.on('play', function (data) {
console.log('play:', jessibuca);
});
// 事件 ,暂停视频之后的回调
jessibuca &&
jessibuca.current &&
jessibuca.current.on('pause', function (data) {
console.log('pause:', data);
});
// ******
// 其他事件需查看在线文档
// ******
return () => {
console.log('销毁。。。。。。。。。。。。。。。。。。。。。。。。。');
stopVideo();
};
}, []);
const stopVideo = () => {
jessibuca && jessibuca.current && jessibuca.current.pause && jessibuca.current.pause();
};
const startVideo = () => {
if (jessibuca && jessibuca.current) {
jessibuca.current.play();
// console.log(jessibuca.current.isPlaying());
}
};
const testVideo = () => {
// console.log(jessibuca.current.isPlaying());
setVideoInfo({
channel: '2',
// dataRate: 'Sub', // Main 主码流 Sub 子码流
name: 'RTSP银河湾五期_通道_2',
id: '46D6E990-8049-4A61-B592-8503BDFAA07A',
username: 'admin',
password: 'zls_1234@abcd',
address: '172.16.19.2',
protocol: 'RTSP',
pandavmsHost: 'ws://192.168.8.30:7000/', // pandavms后端主机地址 eg: ws://172.16.19.19:8080/
hoursRuler: 24,
beginTime: '2024-01-01 00:00:00',
endTime: '2024-01-01 23:59:59',
});
};
const destroyVideo = () => {
if (VideoParam.protocol != '萤石EZOPEN')
jessibuca && jessibuca.current && jessibuca.current.destroy();
};
return (
<div>
<button onClick={stopVideo}>暂停</button>
<button onClick={startVideo}>开始</button>
<button onClick={testVideo}>测试</button>
<button onClick={destroyVideo}>销毁</button>
<div style={{ height: '700px' }}>
<Video {...{ JessibucaObj: JessibucaObj, VideoInfo: VideoInfo }} ref={jessibuca} />
</div>
</div>
);
};
export default Demo2;
......@@ -7,7 +7,7 @@
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
// eslint-disable-next-line no-unused-vars
import EZUIKit, { log } from './ezuikit-js/ezuikit';
import EZUIKit, { log } from 'ezuikit-js';
import axios from 'axios';
import React, {
......
const parseNumber = (prop) => parseFloat(prop) || 0;
const getSize = (el) => {
if (el === window || el === document.body) {
return [window.innerWidth, window.innerHeight];
}
let temporary = false;
if (!el.parentNode && document.body) {
temporary = true;
document.body.appendChild(el);
}
const rect = el.getBoundingClientRect();
const styles = getComputedStyle(el);
const height =
(rect.height | 0) +
parseNumber(styles.getPropertyValue('margin-top')) +
parseNumber(styles.getPropertyValue('margin-bottom'));
const width =
(rect.width | 0) +
parseNumber(styles.getPropertyValue('margin-left')) +
parseNumber(styles.getPropertyValue('margin-right'));
if (temporary && document.body) {
document.body.removeChild(el);
}
return [width, height];
};
export default getSize;
import React, { Component } from 'react';
import getSize from './get-size';
export default class RvResponsiveCanvas extends Component {
static defaultProps = {
scale: typeof window !== 'undefined' ? window.devicePixelRatio : 1,
};
state = {
width: 0,
height: 0,
};
$canvas;
componentDidMount() {
window.addEventListener('resize', this.handleResize, false);
this.setSize();
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize, false);
}
handleResize = () => {
this.setSize();
this.props.onResize(this.state.width, this.state.height);
};
setSize = () => {
const parent = this.$canvas.parentElement;
if (!parent) {
return;
}
const [width, height] = getSize(parent);
this.setState({ width, height });
};
setRef = (el) => {
if (!el) {
return;
}
const { canvasRef } = this.props;
this.$canvas = el;
if (typeof canvasRef === 'function') {
canvasRef(el);
}
};
render() {
const { scale, onResize, canvasRef, style, ...props } = this.props;
const { width, height } = this.state;
return (
<canvas
{...props}
ref={this.setRef}
width={width * scale}
height={height * scale}
style={{ ...style, width, height, minWidth: '952px' }}
/>
);
}
}
import { Button, message, Modal } from 'antd';
import { useEffect, useState, useContext, useRef, useImperativeHandle } from 'react';
import TestVideo from '../index';
import classNames from 'classnames';
import { DatePicker, TimePicker, Calendar, theme, ConfigProvider } from 'antd';
import Empty from '@wisdom-components/empty';
import { videoPlayback, newPlayback } from '../apis';
import './index.less';
import moment from 'moment';
import TimeSlider from './TimeSlider';
const RecVideo = (props, ref) => {
const { getPrefixCls } = useContext(ConfigProvider.ConfigContext);
const prefixCls = getPrefixCls('rec-video-view');
const { VideoInfo, JessibucaObj } = props;
const jessibuca = useRef(null);
const [hoursRuler, setHoursRuler] = useState(VideoInfo.hoursRuler || 24);
const [showId, setShowId] = useState(null); //视频ID
const [peridos, setPeridos] = useState([]); //可播放视频时间段
const [playTimestamp, setPlayTimestamp] = useState(null); //当前正在播放时间段
const [minTimestamp, setMinTimestamp] = useState(null); //时间轴最小时间
const [maxTimestamp, setMaxTimestamp] = useState(null); //时间轴最大时间
useEffect(() => {
changeReplayCfg();
}, [props.VideoInfo]);
useImperativeHandle(ref, () => {
// changeVal 就是暴露给父组件的方法, newVal是父组件传递的参数
return jessibuca.current; // _video&& _video.current&&_video.current.jessibuca
});
useEffect(() => {
const { endTime = moment().format('YYYY-MM-DD 23:59:59') } = VideoInfo;
const edTimes = moment(endTime).format('YYYY-MM-DD HH:mm:ss');
if (playTimestamp) {
let momentObj = moment(playTimestamp);
let formattedTime = momentObj.format('YYYY-MM-DD HH:mm:ss');
const edDates = moment(edTimes).format('YYYY-MM-DD HH:mm:ss');
const params = {
id: VideoInfo.id,
startTime: formattedTime,
endTime: edDates,
'site-code': window?.globalConfig?.userInfo?.LocalSite || '',
};
rePaly(params);
}
}, [playTimestamp]);
const changeReplayCfg = () => {
const {
beginTime = moment().format('YYYY-MM-DD 00:00:00'),
endTime = moment().format('YYYY-MM-DD 23:59:59'),
} = VideoInfo;
const hoursPerRuler = calculateHours(beginTime, endTime) || 24;
const stTimes = moment(beginTime).format('YYYY-MM-DD HH:mm:ss');
const edTimes = moment(endTime).format('YYYY-MM-DD HH:mm:ss');
const param = {
id: VideoInfo.id,
startTime: stTimes,
endTime: edTimes,
'site-code': window?.globalConfig?.userInfo?.LocalSite || '',
};
setHoursRuler(hoursPerRuler);
setMinTimestamp(moment(stTimes).valueOf());
setMaxTimestamp(moment(edTimes).valueOf());
getVideoPlBack(param);
};
const getVideoPlBack = (param) => {
videoPlayback(param).then((res) => {
if (res.code === 200) {
setPeridos(
res.data.peridos.map((times, i) => {
let beginTime = moment(times.StartTime.replaceAll('T', ' ').replaceAll('Z', ' '));
let endTime = moment(times.EndTime.replaceAll('T', ' ').replaceAll('Z', ' '));
return {
...times,
idx: i,
beginTime: beginTime.valueOf(),
endTime: endTime.valueOf(),
style: { background: '#637DEC' },
};
}),
);
setShowId(res.data.url);
} else {
message.warn(res.msg);
setShowId(null);
}
});
};
const rePaly = (params) => {
newPlayback(params).then((res) => {
if (res.code === 200) {
setShowId(res.data);
} else {
message.warn(res.msg);
}
});
};
const calculateHours = (time1, time2) => {
const date1 = new Date(time1);
const date2 = new Date(time2);
const diff = Math.abs(date1.getTime() - date2.getTime());
return Math.ceil(diff / (1000 * 60 * 60));
};
return (
<div className={classNames(prefixCls)}>
{showId ? (
<>
<div className={classNames(`${prefixCls}-video`)}>
{showId && (
<TestVideo
VideoInfo={{ ...VideoInfo, id: showId }}
JessibucaObj={{ ...JessibucaObj }}
key={showId}
ref={jessibuca}
/>
)}
</div>
{/* 时间轴 */}
<div className={classNames(`${prefixCls}-time`)}>
{peridos.length ? (
<TimeSlider
minTimestamp={minTimestamp}
key={JSON.stringify(peridos) + (hoursRuler || 24)}
maxTimestamp={maxTimestamp}
hoursPerRuler={hoursRuler || 24}
playTimestamp={playTimestamp ? playTimestamp : peridos[0].beginTime}
playTimestampChange={(time, recordInfo, playOffset) => {
if (recordInfo && playOffset) {
setPlayTimestamp(time);
} else {
message.warn('当前时间节点没有视频可以播放哦!');
}
}}
timecell={peridos}
/>
) : null}
</div>
</>
) : (
<div className={classNames(`${prefixCls}-empty`)}>
<Empty theme={'dark'} description={''} />
</div>
)}
</div>
);
};
export default React.forwardRef(RecVideo);
@root-entry-name: 'default';
@import '~antd/es/style/themes/index.less';
@rec-video-view-prefix-cls: ~'@{ant-prefix}-rec-video-view';
.@{rec-video-view-prefix-cls} {
display: flex;
align-items: center;
flex-direction: column;
height: 100%;
width: 100%;
&-video {
flex: 1;
width: 100%;
}
&-time {
flex: none;
width: 100%;
}
&-empty {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
:global {
.timeslider-canvas-wrap-his {
height: 72px;
width: 100%;
}
}
}
\ No newline at end of file
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