Browse Source

萤石播放器 88.8%

release_0.0.2
巴林闲侠 2 years ago
parent
commit
6c646018a6
  1. 7
      code/VideoAccess-VCMP/api/app/lib/controllers/camera/index.js
  2. 6
      code/VideoAccess-VCMP/api/app/lib/models/secret_yingshi.js
  3. 5
      code/VideoAccess-VCMP/api/app/lib/schedule/freshYingshiMsg.js
  4. 11
      code/VideoAccess-VCMP/api/app/lib/schedule/index.js
  5. 19
      code/VideoAccess-VCMP/api/app/lib/service/socket.js
  6. 5
      code/VideoAccess-VCMP/api/app/lib/utils/token4yingshi.js
  7. 51
      code/VideoAccess-VCMP/web/client/src/components/textScroll.jsx
  8. 121
      code/VideoAccess-VCMP/web/client/src/components/videoPlayer/videoOperation.jsx
  9. 37
      code/VideoAccess-VCMP/web/client/src/components/videoPlayer/videoOperationCloudControl.jsx
  10. 90
      code/VideoAccess-VCMP/web/client/src/components/videoPlayer/videoOperationHistroyProcess.jsx
  11. 72
      code/VideoAccess-VCMP/web/client/src/components/videoPlayer/videoOperationHistroyTime.jsx
  12. 199
      code/VideoAccess-VCMP/web/client/src/components/videoPlayer/videoPlay.jsx
  13. 10
      code/VideoAccess-VCMP/web/client/src/components/videoPlayer/videoPlay.less
  14. 39
      code/VideoAccess-VCMP/web/client/src/components/videoPlayer/voiceHeader.jsx
  15. 1
      code/VideoAccess-VCMP/web/client/src/layout/index.jsx
  16. 7
      code/VideoAccess-VCMP/web/client/src/sections/auth/actions/auth.js
  17. 16
      code/VideoAccess-VCMP/web/client/src/utils/index.js
  18. 54
      code/VideoAccess-VCMP/web/client/src/utils/videoCloudControl.js

7
code/VideoAccess-VCMP/api/app/lib/controllers/camera/index.js

@ -28,7 +28,10 @@ async function getCameraProject (ctx, next) {
} : {}, } : {},
required: Boolean(nvrId), required: Boolean(nvrId),
attributes: ['id', 'name', 'serialNo'] attributes: ['id', 'name', 'serialNo']
},], }, {
model: models.SecretYingshi,
attributes: ['token']
}],
distinct: true distinct: true
} }
let abilityFind = { let abilityFind = {
@ -197,7 +200,7 @@ async function detail (ctx) {
}, },
include: { include: {
model: models.GbCamera, model: models.GbCamera,
attributes: ['id', 'online'], attributes: ['id', 'online', 'playUrl'],
required: false required: false
} }
}) })

6
code/VideoAccess-VCMP/api/app/lib/models/secret_yingshi.js

@ -56,6 +56,12 @@ module.exports = dc => {
comment: "", comment: "",
indexes: [] indexes: []
}); });
const Camera = dc.models.Camera;
Camera.belongsTo(SecretYingshi, { foreignKey: 'yingshiSecretId', targetKey: 'id' });
SecretYingshi.hasMany(Camera, { foreignKey: 'yingshiSecretId', sourceKey: 'id' });
dc.models.SecretYingshi = SecretYingshi; dc.models.SecretYingshi = SecretYingshi;
return SecretYingshi; return SecretYingshi;
}; };

5
code/VideoAccess-VCMP/api/app/lib/schedule/freshYingshiMsg.js

@ -118,11 +118,8 @@ module.exports = function (app, opts) {
const freshYingshiPlayUrl = schedule.scheduleJob( const freshYingshiPlayUrl = schedule.scheduleJob(
// '0 0 4 */1 *', // '0 0 4 */1 *',
'*/45 * * * *', '*/30 * * * *',
async () => { async () => {
const protocolMap = {
}
try { try {
const { models } = app.fs.dc const { models } = app.fs.dc
const { token4yingshi, getYingshiPlayUrl } = app.fs.utils const { token4yingshi, getYingshiPlayUrl } = app.fs.utils

11
code/VideoAccess-VCMP/api/app/lib/schedule/index.js

@ -1,6 +1,17 @@
'use strict'; 'use strict';
const fs = require('fs'); const fs = require('fs');
const nodeSchedule = require('node-schedule');
const schedule = ({
interval, immediate
}, callback) => {
const j = nodeSchedule.scheduleJob(interval, callback);
if (immediate) {
j.run();
}
return j;
}
// 将定时任务汇集未来可根据需要选取操作 // 将定时任务汇集未来可根据需要选取操作
module.exports = async function (app, opts) { module.exports = async function (app, opts) {

19
code/VideoAccess-VCMP/api/app/lib/service/socket.js

@ -14,13 +14,20 @@ module.exports = async function factory (app, opts) {
const { connected } = app.socket.sockets const { connected } = app.socket.sockets
const roomId = 'ROOM_' + Math.random() const roomId = 'ROOM_' + Math.random()
if (connected) { // if (connected) {
for (let c in connected) { // for (let c in connected) {
connected[c].join(roomId) // connected[c].join(roomId)
} // }
app.socket.to(roomId).emit('TEST', { someProperty: `【星域 ROOM:${roomId}】呼叫自然选择号!!!`, }) // app.socket.to(roomId).emit('TEST', { someProperty: `【星域 ROOM:${roomId}】呼叫自然选择号!!!`, })
} // }
// app.socket.emit('TEST', { someProperty: '【广播】呼叫青铜时代号!!!', }) // app.socket.emit('TEST', { someProperty: '【广播】呼叫青铜时代号!!!', })
app.socket.emit('CAMERA_ONLINE', {
ipctype: 'yingshi',
online: Math.random() > 0.5 ? 'ON' : 'OFF',
gbId: Math.floor(Math.random() * 100),
name: 'cameraName'
})
}, 3000) }, 3000)
} }

5
code/VideoAccess-VCMP/api/app/lib/utils/token4yingshi.js

@ -119,15 +119,14 @@ module.exports = function (app, opts) {
if (playUrlRes.code == 200) { if (playUrlRes.code == 200) {
playUrl.liveUrl[quality][protocol] = playUrlRes.data.url playUrl.liveUrl[quality][protocol] = playUrlRes.data.url
} else { } else {
return null // return null
} }
} }
} }
for (let type in typeMap) { for (let type in typeMap) {
try { try {
// TODO 这里404 const playUrlRes = await app.fs.yingshiRequest.post('lapp/v2/live/address/get', {
const playUrlRes = await app.fs.yingshiRequest.post('lapp/v2/replay/address/get', {
query: { query: {
accessToken: token, accessToken: token,
deviceSerial: deviceSerial, deviceSerial: deviceSerial,

51
code/VideoAccess-VCMP/web/client/src/components/textScroll.jsx

@ -5,34 +5,41 @@ import './textScroll.less'
function TextScroll (props) { function TextScroll (props) {
const { content, duration } = props const { content, duration } = props
const [showContent, setShowContent] = useState('1231231') const [showContent, setShowContent] = useState('1231231')
const [timer, setTimer] = useState(null)
const showIndex = useRef(0)
useEffect(() => { useEffect(() => {
let repeatTime = moment() if (content.length) {
let refreshTime = moment() let repeatTime = moment()
const scroll = () => { let refreshTime = moment()
let contentParent = document.getElementById('marquee_box') const scroll = () => {
document.getElementById('contentPMakeUp').style.width = contentParent.clientWidth + 'px' let contentParent = document.getElementById('marquee_box')
// document.getElementById('contentPMakeUp').style.width = contentParent.clientWidth + 'px'
if (moment().diff(refreshTime) > 1000 / 60) { //
const contentP = document.getElementById('contentP') if (moment().diff(refreshTime) > 1000 / 60) {
// const contentP = document.getElementById('contentP')
if (moment().diff(repeatTime) > 1000 * 1.5) { //
contentP.style.visibility = 'visible' if (moment().diff(repeatTime) > 1000 * 1.5) {
contentP.style.visibility = 'visible'
}
//
if (moment().diff(repeatTime) > 1000 * 3) {
contentParent.scrollLeft = contentParent.scrollLeft + 1
}
//
if (contentParent.scrollLeft >= contentP.clientWidth + 24) {
contentParent.scrollLeft = 0
repeatTime = moment()
setShowContent(content[showIndex.current])
showIndex.current = (showIndex.current + 1) % content.length
contentP.style.visibility = 'hidden'
}
refreshTime = moment()
} }
if (moment().diff(repeatTime) > 1000 * 3) { window.requestAnimationFrame(scroll)
contentParent.scrollLeft = contentParent.scrollLeft + 1
}
if (contentParent.scrollLeft >= contentP.clientWidth + 24) {
contentParent.scrollLeft = 0
repeatTime = moment()
setShowContent('asdasd' + Math.random())
contentP.style.visibility = 'hidden'
}
refreshTime = moment()
} }
window.requestAnimationFrame(scroll) window.requestAnimationFrame(scroll)
} }
window.requestAnimationFrame(scroll)
}, []) }, [])
return ( return (

121
code/VideoAccess-VCMP/web/client/src/components/videoPlayer/videoOperation.jsx

@ -6,12 +6,22 @@ import VideoOperationTalk from './videoOperationTalk'
import VideoOperationSpeed from './videoOperationSpeed' import VideoOperationSpeed from './videoOperationSpeed'
import VideoOperationVoice from './videoOperationVoice' import VideoOperationVoice from './videoOperationVoice'
import VideoOperationHistroyTime from './videoOperationHistroyTime' import VideoOperationHistroyTime from './videoOperationHistroyTime'
import VideoOperationHistroyProcess from "./videoOperationHistroyProcess";
import { IconPause, IconPlay } from '@douyinfe/semi-icons'; import { IconPause, IconPlay } from '@douyinfe/semi-icons';
import './videoPlay.less'; import './videoPlay.less';
const timeFormat = 'YYYY-MM-DD HH:mm:ss'
const VideoOperation = ({ const VideoOperation = ({
operationState, operation, voiceDisY, setVoiceDisY, operationState, operation,
resolution, setResolution voiceDisY, setVoiceDisY,
processDisX, setProcessDisX,
isAdjustProcess, setIsAdjustProcess,
resolution, setResolution,
histroyTime, setHistroyTime,
histroyBegain,
play, pause, isPlaying,
videoObj
}) => { }) => {
const [showTimeSelect, setShowTimeSelect] = useState(false) const [showTimeSelect, setShowTimeSelect] = useState(false)
@ -25,68 +35,87 @@ const VideoOperation = ({
setResolution(resolution == 'sd' ? 'hd' : 'sd') setResolution(resolution == 'sd' ? 'hd' : 'sd')
} }
const histroySelected = operationState && operationState.histroy.select
return ( return (
<> <>
{ {
operationState ? operationState ?
operationState.control.select ? operationState.control.select ?
<VideoOperationCloudControl /> : <VideoOperationCloudControl videoObj={videoObj} /> :
operationState.talk.select ? operationState.talk.select ?
<VideoOperationTalk /> : <VideoOperationTalk /> :
'' : '' '' : ''
} }
{ {
showTimeSelect ? showTimeSelect ?
<VideoOperationHistroyTime close={() => { setShowTimeSelect(false) }} /> <VideoOperationHistroyTime close={() => { setShowTimeSelect(false) }} histroyTime={histroyTime} setHistroyTime={setHistroyTime} setProcessDisX={setProcessDisX} />
: '' : ''
} }
{/* 下方操作 */} {/* 下方操作 */}
<div style={{ <div style={{
height: 42, lineHeight: '42px', background: 'linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.8) 100%)', padding: '0 12px', height: histroySelected ? 44 : 42, lineHeight: `${histroySelected ? 44 : 42}px`,
display: 'flex', justifyContent: 'space-between', background: 'linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.8) 100%)', padding: '0 12px',
position: 'absolute', bottom: 0, width: '100%', zIndex: 99, color: '#fff' position: 'absolute', bottom: 0, width: '100%', zIndex: 99, color: '#fff',
}}> }}>
{ {
operationState ? histroySelected && histroyTime.length ?
operationState.histroy.select ? <VideoOperationHistroyProcess
<> processDisX={processDisX} setProcessDisX={setProcessDisX} histroyTime={histroyTime}
<div style={{ display: 'flex', alignItems: 'center' }}> isAdjustProcess={isAdjustProcess} setIsAdjustProcess={setIsAdjustProcess}
<IconPause style={{ cursor: 'pointer' }} /> /> : ''
{/* <IconPlay style={{ cursor: 'pointer' }} /> */}
<span style={{ marginLeft: 12 }}>{moment().format('YYYY-MM-DD HH:mm:ss')}/{moment().format('YYYY-MM-DD HH:mm:ss')}</span>
</div>
<div style={{ display: 'flex', alignItems: 'center' }}>
<VideoOperationVoice voiceDisY={voiceDisY} setVoiceDisY={setVoiceDisY} />
<VideoOperationSpeed butStyle={butStyle} />
<div style={butStyle} onClick={() => {
setShowTimeSelect(!showTimeSelect)
}}>时间设置</div>
</div>
</>
:
<>
<div style={{ display: 'flex', alignItems: 'center' }}>
{
operationState ?
operation.map(p => {
return <img
src={`/assets/images/background/video-icon-${p.key}-${operationState[p.key].select ? 'select' : 'unselect'}.png`}
height={18}
style={{ marginRight: 24, cursor: 'pointer' }}
onClick={p.click}
/>
}) : ''
}
</div>
<div style={{ display: 'flex', alignItems: 'center' }}>
{
resolution == 'sd' ?
<div style={butStyle} onClick={changeResolution}>标清</div> :
<div style={butStyle} onClick={changeResolution}>高清</div>
}
</div>
</> : ''
} }
<div style={{
display: 'flex', justifyContent: 'space-between',
}}>
{
operationState ?
histroySelected ?
<>
<div style={{ display: 'flex', alignItems: 'center' }}>
{
isPlaying ?
<IconPause style={{ cursor: 'pointer' }} onClick={pause} />
: <IconPlay style={{ cursor: 'pointer' }} onClick={play} />
}
<span style={{ marginLeft: 12 }}>
{histroyTime.length ? `${moment(histroyTime[0]).format(timeFormat)} / ${moment(histroyTime[1]).format(timeFormat)}` : ''}
</span>
</div>
<div style={{ display: 'flex', alignItems: 'center' }}>
<VideoOperationVoice voiceDisY={voiceDisY} setVoiceDisY={setVoiceDisY} />
<VideoOperationSpeed butStyle={butStyle} />
<div style={butStyle} onClick={() => {
setShowTimeSelect(!showTimeSelect)
}}>时间设置</div>
</div>
</>
:
<>
<div style={{ display: 'flex', alignItems: 'center' }}>
{
operationState ?
operation.map(p => {
return <img
src={`/assets/images/background/video-icon-${p.key}-${operationState[p.key].select ? 'select' : 'unselect'}.png`}
height={18}
style={{ marginRight: 24, cursor: 'pointer' }}
onClick={p.click}
/>
}) : ''
}
</div>
<div style={{ display: 'flex', alignItems: 'center', userSelect: 'none' }}>
{
videoObj.playUrlSd && videoObj.playUrlHd ?
resolution == 'sd' ?
<div style={butStyle} onClick={changeResolution}>标清</div> :
<div style={butStyle} onClick={changeResolution}>高清</div>
: ''
}
</div>
</> : ''
}
</div>
</div> </div>
</> </>
) )

37
code/VideoAccess-VCMP/web/client/src/components/videoPlayer/videoOperationCloudControl.jsx

@ -1,8 +1,19 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { ysptz } from '$utils'
import './videoPlay.less'; import './videoPlay.less';
const VideoOperationCloudControl = ({ }) => { const VideoOperationCloudControl = ({
videoObj,
}) => {
const cloudControl = (ac) => {
if (videoObj.type == 'yingshi') {
ysptz(ac, videoObj)
} else {
}
}
return ( return (
<div style={{ <div style={{
@ -15,13 +26,17 @@ const VideoOperationCloudControl = ({ }) => {
}}> }}>
{ {
[{ [{
style: { top: 12, left: (148 - 24) / 2, } style: { top: 12, left: (148 - 24) / 2, },
click: () => { cloudControl('up') }
}, { }, {
style: { right: 12, top: (148 - 24) / 2, } style: { right: 12, top: (148 - 24) / 2, },
click: () => { cloudControl('right') },
}, { }, {
style: { bottom: 12, left: (148 - 24) / 2, } style: { bottom: 12, left: (148 - 24) / 2, },
click: () => { cloudControl('down') },
}, { }, {
style: { left: 12, top: (148 - 24) / 2, } style: { left: 12, top: (148 - 24) / 2 },
click: () => { cloudControl('left') },
}].map((s, i) => { }].map((s, i) => {
return ( return (
<img <img
@ -30,6 +45,7 @@ const VideoOperationCloudControl = ({ }) => {
height: 24, width: 24, display: 'inline-block', transform: `rotate(${i * 90}deg)`, height: 24, width: 24, display: 'inline-block', transform: `rotate(${i * 90}deg)`,
position: 'absolute' position: 'absolute'
}, s.style)} }, s.style)}
onClick={s.click}
/> />
) )
}) })
@ -41,8 +57,8 @@ const VideoOperationCloudControl = ({ }) => {
</div> </div>
{ {
[ [
[{ n: '+' }, { n: '焦距' }, { n: '-' }], [{ n: '+', click: () => { cloudControl('focus_in') }, }, { n: '焦距' }, { n: '-', click: () => { cloudControl('focus_out') }, }],
[{ n: '+' }, { n: '缩放' }, { n: '-' }] [{ n: '+', click: () => { cloudControl('zoom_in') }, }, { n: '缩放' }, { n: '-', click: () => { cloudControl('zoom_out') }, }],
].map(s => { ].map(s => {
return ( return (
<div style={{ <div style={{
@ -52,7 +68,10 @@ const VideoOperationCloudControl = ({ }) => {
{ {
s.map((m, mi) => { s.map((m, mi) => {
return ( return (
<div style={{ textAlign: 'center', display: 'inline-block', cursor: mi != 1 ? 'pointer' : 'auto' }}>{m.n}</div> <div
style={{ textAlign: 'center', display: 'inline-block', cursor: mi != 1 ? 'pointer' : 'auto' }}
onClick={() => { m.click ? m.click() : null }}
>{m.n}</div>
) )
}) })
} }
@ -60,7 +79,7 @@ const VideoOperationCloudControl = ({ }) => {
) )
}) })
} }
</div> </div >
) )
} }

90
code/VideoAccess-VCMP/web/client/src/components/videoPlayer/videoOperationHistroyProcess.jsx

@ -0,0 +1,90 @@
import React, { useState, useEffect, useRef } from "react";
import { connect } from "react-redux";
import moment from 'moment'
import './videoPlay.less';
const timeFormat = 'MM-DD HH:mm:ss'
const videoOperationHistroyProcess = ({ processDisX, setProcessDisX, histroyTime, isAdjustProcess, setIsAdjustProcess, }) => {
const [timeRangeS, setTimeRangeS] = useState(0)
const [processDisXRatio, setProcessDisXRatio] = useState(0)
useEffect(() => {
if (histroyTime.length) {
setTimeRangeS(Math.abs(moment(histroyTime[0]).diff(moment(histroyTime[1]), 'seconds')))
}
}, [histroyTime])
return (
<div >
<div style={{ height: 2, backgroundColor: '#ffffff7F' }}>
<div style={{ height: 2, backgroundColor: '#2F53EA', width: processDisX }}></div>
<div
style={{
height: 9, width: 9, borderRadius: '100%', backgroundColor: '#fff',
position: 'relative', top: -6.5, cursor: 'pointer',
}}
id='process_point'
className="video_process_but"
onMouseDown={(ev) => {
setIsAdjustProcess(true)
ev.stopPropagation();
ev.preventDefault();
document.onmouseup = function () {
setIsAdjustProcess(false)
document.onmousemove = null;
document.onmouseup = null;
};
let oevent = ev;
let distanceX = oevent.clientX
let prev = Date.now();
const point = document.getElementById('process_point')
const parentWidth = point.parentElement.offsetWidth
document.onmousemove = function (ev) {
ev.stopPropagation();
ev.preventDefault();
//
let now = Date.now();
if (now - prev >= 6) {
let oevent = ev;
let x = processDisX + oevent.clientX - distanceX
if (x < 0) {
x = 0
} else if (x > parentWidth) {
x = parentWidth
}
console.log(parentWidth, x);
setProcessDisX(x)
setProcessDisXRatio(x / parentWidth)
point.style.left = x + 'px';
prev = Date.now();
}
};
}}
>
{/* <div
className={"video_process_time"}
style={{
backgroundColor: '#0000007F', position: 'absolute',
top: -32, height: 24, lineHeight: '24px', textAlign: 'center', width: 94, left: -38
}}>
{moment(histroyTime[0]).add(timeRangeS * processDisXRatio, 'seconds').format(timeFormat)}
</div> */}
</div>
</div>
</div>
)
}
function mapStateToProps (state) {
const { auth } = state;
return {
user: auth.user,
};
}
export default connect(mapStateToProps)(videoOperationHistroyProcess);

72
code/VideoAccess-VCMP/web/client/src/components/videoPlayer/videoOperationHistroyTime.jsx

@ -1,9 +1,23 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { DatePicker, Button, Space } from '@douyinfe/semi-ui'; import { DatePicker, Toast, ToastFactory, Space } from '@douyinfe/semi-ui';
import './videoPlay.less'; import './videoPlay.less';
import moment from "moment";
const VideoOperationHistroyTime = ({ close }) => { const timeFormat = 'YYYY-MM-DD HH:mm:ss'
const VideoOperationHistroyTime = ({ close, histroyTime, setHistroyTime, setProcessDisX }) => {
const [selectedTimeRange, setSelectedTimeRange] = useState(histroyTime)
useEffect(() => {
if (!selectedTimeRange.length) {
setSelectedTimeRange([moment().subtract(72, 'hours').format(timeFormat), moment().format(timeFormat)])
}
}, [])
const ToastInCustomContainer = ToastFactory.create({
getPopupContainer: () => document.getElementById('vcmp_videoplay'),
});
return ( return (
<div style={{ <div style={{
@ -15,20 +29,62 @@ const VideoOperationHistroyTime = ({ close }) => {
<DatePicker <DatePicker
density='compact' density='compact'
type="dateTimeRange" type="dateTimeRange"
defaultPickerValue={[new Date('2022-08-08 00:00'), new Date('2022-08-09 12:00')]} value={selectedTimeRange}
onChange={console.log} onChange={(t, timeRange) => {
setSelectedTimeRange(timeRange)
}}
style={{ width: '100%' }} style={{ width: '100%' }}
// disabledDate={(date, options) => {
// const { rangeStart, rangeEnd } = options;
// // console.log(date, options);
// if (!rangeStart && !rangeEnd) {
// return false
// }
// if (rangeStart) {
// return Math.abs(moment(date).diff(moment(rangeStart), 'days')) > 3
// }
// if (rangeEnd) {
// return Math.abs(moment(date).diff(moment(rangeEnd), 'days')) > 3
// }
// }}
// disabledTime={(date, type) => {
// console.log(date, type);
// }}
/> />
</div> </div>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', }}>
<span style={{ color: '#FF7100' }}> <span style={{ color: '#FF7100' }}>
<img src="/assets/images/background/warning.png" height={14} style={{ position: 'relative', top: 2 }} /> <img src="/assets/images/background/warning.png" height={14} style={{ position: 'relative', top: 2, marginRight: 2 }} />
最长时间跨度不超过72小时 最长时间跨度不超过72小时
</span> </span>
<span> <span>
<Space> <Space>
<Button theme='light' type='tertiary' onClick={() => { close() }}>取消</Button> <div
<Button theme='solid' type='primary' onClick={() => { close() }}>播放</Button> onClick={() => {
close()
}}
style={{
cursor: 'pointer', height: 32, width: 56, lineHeight: '32px', borderRadius: 2,
textAlign: 'center', background: '#fff', color: 'rgba(0, 0, 0, 0.65)'
}}
>取消</div>
<div
onClick={() => {
if (selectedTimeRange.length == 2 && selectedTimeRange.every(t => t)) {
if (Math.abs(moment(selectedTimeRange[0]).diff(moment(selectedTimeRange[1]), 'hours')) > 72) {
ToastInCustomContainer.destroyAll()
return ToastInCustomContainer.error('所选时间超过 72 小时')
}
setHistroyTime(selectedTimeRange)
setProcessDisX(0)
close()
}
}}
style={{
cursor: 'pointer', height: 32, width: 56, lineHeight: '32px', borderRadius: 2,
textAlign: 'center', background: '#1859C1', color: '#fff'
}}
>播放</div>
</Space> </Space>
</span> </span>
</div> </div>

199
code/VideoAccess-VCMP/web/client/src/components/videoPlayer/videoPlay.jsx

@ -1,28 +1,48 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import screenfull from 'screenfull'; import screenfull from 'screenfull';
import moment from "moment";
import VideoHeader from './voiceHeader' import VideoHeader from './voiceHeader'
import VideoOperation from './videoOperation' import VideoOperation from './videoOperation'
import './videoPlay.less'; import './videoPlay.less';
const timeFormat = 'YYYY-MM-DD HH:mm:ss'
const yingshiUrl = 'https://open.ys7.com/ezopen/h5/iframe'
const VideoPlay = ({ const VideoPlay = ({
height, width, type, containerId = 'myPlayer', yingshiToken, height, width, containerId = 'myPlayer',
// playUrl, // playUrl,
name,
videoObj = {
type: 'yingshi',
audio: true,
yingshiToken: 'at.3j6eyqbn0g5xvcut73v0rzdu1nh0gnxx-4ua03m82o9-12u1t9g-rtzndpyds',
playUrlSd: 'ezopen://open.ys7.com/G75922040/1.live',
// playUrl: 'ws://221.230.55.27:8081/jessica/34020000001110000077/34020000001310000003',
playUrlHd: 'ezopen://open.ys7.com/G75922040/1.hd.live',
replayUrl: 'ezopen://open.ys7.com/G75922040/1.rec',
},
}) => { }) => {
const [jessibuca, setjessibuca] = useState(null) const [jessibuca, setjessibuca] = useState(null)
const [playUrl, setPlayUrl] = useState(
// 'http://flv.bdplay.nodemedia.cn/live/bbb.flv' // TEST
'ws://221.230.55.27:8081/jessica/34020000001110000077/34020000001310000001',
// 'ws://221.230.55.27:8081/jessica/34020000001110000077/34020000001310000003'
)
const [isPlaying, setIsPlaying] = useState(false) const [isPlaying, setIsPlaying] = useState(false)
const [operationState, setoperationState] = useState() const [operationState, setoperationState] = useState()
const [voiceDisY, setVoiceDisY] = useState(0) const [voiceDisY, setVoiceDisY] = useState(0)
const [processDisX, setProcessDisX] = useState(0)
const [isAdjustProcess, setIsAdjustProcess] = useState(false)
const [histroyTime, setHistroyTime] = useState([])
const [histroyBegain, setHistroyBegain] = useState()
const [resolution, setResolution] = useState('sd') // sd hd const [resolution, setResolution] = useState('sd') // sd hd
const operationRef = useRef(null)
const processChangeTimeoutRef = useRef(null)
const changeSelectState = (key) => { const changeSelectState = (key) => {
const nextOperationState = JSON.parse(JSON.stringify(operationState)) const nextOperationState = JSON.parse(JSON.stringify(operationRef.current))
if (key == 'histroy' && nextOperationState.histroy.select) {
//
setProcessDisX(0)
setHistroyTime([])
}
for (let k in nextOperationState) { for (let k in nextOperationState) {
if (k == key) { if (k == key) {
nextOperationState[k].select = !nextOperationState[k].select nextOperationState[k].select = !nextOperationState[k].select
@ -30,26 +50,29 @@ const VideoPlay = ({
nextOperationState[k].select = false nextOperationState[k].select = false
} }
} }
return nextOperationState operationRef.current = nextOperationState
if (operationRef.current.histroy.select && histroyTime.length == 0) {
setHistroyTime([moment().subtract(72, 'hours').format(timeFormat), moment().format(timeFormat)])
}
setoperationState(nextOperationState)
} }
const operation = [{ const operation = [{
key: 'control', key: 'control',
click: () => { click: () => {
let nextOperationState = changeSelectState('control') changeSelectState('control')
setoperationState(nextOperationState)
} }
}, { }, {
key: 'talk', key: 'talk',
click: () => { click: () => {
let nextOperationState = changeSelectState('talk') changeSelectState('talk')
setoperationState(nextOperationState)
} }
}, { }, {
key: 'fullScreen', key: 'fullScreen',
click: () => { click: () => {
let nextOperationState = changeSelectState('fullScreen')
setoperationState(nextOperationState) changeSelectState('fullScreen')
let player = document.getElementById('vcmp_videoplay') let player = document.getElementById('vcmp_videoplay')
if (screenfull.isEnabled) { if (screenfull.isEnabled) {
screenfull.toggle(player); screenfull.toggle(player);
@ -58,10 +81,11 @@ const VideoPlay = ({
}, { }, {
key: 'histroy', key: 'histroy',
click: () => { click: () => {
let nextOperationState = changeSelectState('histroy')
setoperationState(nextOperationState) changeSelectState('histroy')
} }
},] },]
useEffect(() => { useEffect(() => {
create() create()
let nextOperationState = {} let nextOperationState = {}
@ -71,44 +95,110 @@ const VideoPlay = ({
} }
} }
setoperationState(nextOperationState) setoperationState(nextOperationState)
operationRef.current = nextOperationState
//
screenfull.on('change', () => {
if (screenfull.isFullscreen && operationRef.current && !operationRef.current['fullScreen'].select) {
changeSelectState('fullScreen')
}
if (!screenfull.isFullscreen && operationRef.current && operationRef.current['fullScreen'].select) {
changeSelectState('fullScreen')
}
});
// ifream
// const listenYingshiMessage = async (e) => {
// const { data, origin } = e
// console.log(e);
// if (origin !== 'https://open.ys7.com') return
// if (data.type == 'stop' && data.code == 1) {
// setIsPlaying(false)
// }
// if (data.type == 'stop' && data.code == 1) {
// setIsPlaying(false)
// }
// }
// if (videoObj.type == 'yingshi') {
// window.addEventListener('message', listenYingshiMessage);
// }
// return () => {
// window.removeEventListener('message', listenYingshiMessage);
// }
}, []) }, [])
useEffect(() => {
if (histroyTime.length) {
setHistroyBegain(histroyTime[0])
document.getElementById('process_point').style.left = 0 + 'px'; //
} else {
setHistroyBegain(null)
}
}, [histroyTime])
useEffect(() => {
if (operationState && operationState.histroy.select) {
if (isAdjustProcess) {
if (processChangeTimeoutRef.current) {
clearTimeout(processChangeTimeoutRef.current)
}
processChangeTimeoutRef.current = setTimeout(() => {
setHistroyBegain(
moment(histroyTime[0])
.add(
Math.abs(moment(histroyTime[0]).diff(moment(histroyTime[1]), 'seconds')) * (processDisX / document.getElementById('process_point').parentElement.offsetWidth),
'second'
)
.format(timeFormat)
)
}, 300)
}
}
}, [processDisX])
const create = () => { const create = () => {
let $container = document.getElementById('container'); if (videoObj.type != 'yingshi') {
const jessibuca = new window.Jessibuca({ let $container = document.getElementById('container');
container: $container, const jessibuca = new window.Jessibuca({
videoBuffer: 0.2, // container: $container,
isResize: false, videoBuffer: 0.2, //
text: "", isResize: false,
loadingText: "加载中", text: "",
debug: true, loadingText: "加载中",
showBandwidth: true, // debug: true,
operateBtns: { showBandwidth: true, //
fullscreen: true, operateBtns: {
screenshot: true, fullscreen: true,
play: true, screenshot: true,
audio: true, play: true,
}, audio: true,
forceNoOffscreen: false, },
isNotMute: false, forceNoOffscreen: false,
}); isNotMute: false,
setjessibuca(jessibuca) });
play() setjessibuca(jessibuca)
play()
}
} }
useEffect(() => { const yingshiOperation = (operation) => {
play() let a = document.getElementById(containerId).contentWindow.postMessage(operation, yingshiUrl)
}, [jessibuca]) console.log(a);
setIsPlaying(operation == 'play')
}
const play = () => { const play = () => {
if (jessibuca && playUrl) { if (videoObj.type == 'yingshi') {
jessibuca.play(playUrl); yingshiOperation('play')
} else if (jessibuca && videoObj.playUrlSd) {
jessibuca.play(videoObj.playUrlSd);
setIsPlaying(true) setIsPlaying(true)
} }
} }
const pause = () => { const pause = () => {
if (jessibuca) { if (videoObj.type == 'yingshi') {
yingshiOperation('stop')
} else if (jessibuca) {
jessibuca.pause(); jessibuca.pause();
setIsPlaying(false) setIsPlaying(false)
} }
@ -119,20 +209,27 @@ const VideoPlay = ({
<div className="vcmp_videoplay" style={{ height: height || '100%', width: width || '100%' }}> <div className="vcmp_videoplay" style={{ height: height || '100%', width: width || '100%' }}>
<div id="vcmp_videoplay" style={{ position: 'relative', display: 'flex', height: '100%', width: '100%' }}> <div id="vcmp_videoplay" style={{ position: 'relative', display: 'flex', height: '100%', width: '100%' }}>
{/* 顶部信息 */} {/* 顶部信息 */}
<VideoHeader operationState={operationState} changeSelectState={changeSelectState} setoperationState={setoperationState} /> <VideoHeader
operationState={operationState} changeSelectState={changeSelectState}
setoperationState={setoperationState} name={name}
showTime={histroyBegain}
/>
{/* 视频内容 */} {/* 视频内容 */}
{ {
type == 'yingshi' ? videoObj.type == 'yingshi' ?
<iframe <iframe
frameBorder="0" frameBorder="0"
id={containerId} id={containerId}
src={`https://open.ys7.com/ezopen/h5/iframe?audio=${'audio' ? '1' : '0'}&url=${playUrl}&autoplay=${1}&accessToken=${yingshiToken}`} src={
`${yingshiUrl}?audio=${videoObj.audio ? '1' : '0'}&url=${operationState && operationState.histroy.select && histroyBegain ? `${videoObj.replayUrl}?begin=${moment(histroyBegain).format("YYYYMMDDHHmmss")}&end=${moment(histroyTime[1]).format("YYYYMMDDHHmmss")}` : resolution == 'sd' ? videoObj.playUrlSd : videoObj.playUrlHd}&autoplay=${'1'}&accessToken=${videoObj.yingshiToken}`
}
// https://open.ys7.com/doc/zh/book/index/live_proto.html // https://open.ys7.com/doc/zh/book/index/live_proto.html
// {width: 400px;height: 300px;} // {width: 400px;height: 300px;}
width={'100%'} width={'100%'}
height={'100%'} height={'100%'}
allowFullScreen allowFullScreen
wmode="transparent" wmode="transparent"
style={{ pointerEvents: 'none' }}
/> : /> :
<div id="container" <div id="container"
style={{ style={{
@ -142,11 +239,19 @@ const VideoPlay = ({
</div> </div>
} }
{/* 云控 对讲 对应操作内容 */} {/* 下方操作栏 */}
<VideoOperation <VideoOperation
operationState={operationState} operation={operation} operationState={operationState} operation={operation}
voiceDisY={voiceDisY} setVoiceDisY={setVoiceDisY} voiceDisY={voiceDisY} setVoiceDisY={setVoiceDisY}
processDisX={processDisX} setProcessDisX={setProcessDisX}
isAdjustProcess={isAdjustProcess} setIsAdjustProcess={setIsAdjustProcess}
resolution={resolution} setResolution={setResolution} resolution={resolution} setResolution={setResolution}
histroyTime={histroyTime} setHistroyTime={setHistroyTime}
histroyBegain={histroyBegain}
play={play}
pause={pause}
isPlaying={isPlaying}
videoObj={videoObj}
/> />
</div> </div>

10
code/VideoAccess-VCMP/web/client/src/components/videoPlayer/videoPlay.less

@ -27,3 +27,13 @@
.video_voice_options { .video_voice_options {
display: none; display: none;
} }
.video_process_but:hover {
.video_process_time {
display: block;
}
}
.video_process_time {
display: none;
}

39
code/VideoAccess-VCMP/web/client/src/components/videoPlayer/voiceHeader.jsx

@ -6,7 +6,38 @@ import { IconReply } from '@douyinfe/semi-icons';
import TextScroll from '../textScroll' import TextScroll from '../textScroll'
import './videoPlay.less'; import './videoPlay.less';
const VideoHeader = ({ operationState, changeSelectState, setoperationState }) => { const timeFormat = 'YYYY-MM-DD HH:mm:ss'
const VideoHeader = ({ operationState, changeSelectState, setoperationState, name, showTime }) => {
const time = useRef(moment(showTime || undefined))
const upTimeInterval = useRef(null)
const [showTime_, setShowTime] = useState(time.current.format(timeFormat))
const updateTime = () => {
time.current = moment(showTime || undefined)
if (upTimeInterval.current) {
clearInterval(upTimeInterval.current)
}
const upTime = () => {
time.current.add(1, 's')
setShowTime(time.current.format(timeFormat))
}
upTime()
upTimeInterval.current = setInterval(upTime, 1000)
}
useEffect(() => {
updateTime()
return () => {
if (upTimeInterval.current) {
clearInterval(upTimeInterval.current)
}
}
}, [])
useEffect(() => {
updateTime()
}, [showTime])
return ( return (
<div style={{ <div style={{
@ -19,19 +50,17 @@ const VideoHeader = ({ operationState, changeSelectState, setoperationState }) =
backgroundImage: 'url(/assets/images/background/videoPlayBg.png)', backgroundImage: 'url(/assets/images/background/videoPlayBg.png)',
backgroundSize: '100% 100%', backgroundSize: '100% 100%',
backgroundRepeat: 'no-repeat', backgroundRepeat: 'no-repeat',
// textAlign: 'center',
padding: '0 12px' padding: '0 12px'
}}> }}>
{ {
operationState && operationState.histroy.select ? operationState && operationState.histroy.select ?
<> <>
<IconReply style={{ marginRight: 12, cursor: 'pointer' }} onClick={() => { <IconReply style={{ marginRight: 12, cursor: 'pointer' }} onClick={() => {
let nextOperationState = changeSelectState('histroy') changeSelectState('histroy')
setoperationState(nextOperationState)
}} /> }} />
</> : '' </> : ''
} }
{moment().format('YYYY-MM-DD HH:mm:ss')} xxxxxxx {showTime_} {name}
</Col> </Col>
<Col span={15} style={{}}> <Col span={15} style={{}}>
<div style={{ paddingRight: 12 }}> <div style={{ paddingRight: 12 }}>

1
code/VideoAccess-VCMP/web/client/src/layout/index.jsx

@ -154,6 +154,7 @@ const Root = props => {
// IOT system cross // IOT system cross
window.addEventListener('message', async function (e) { // message window.addEventListener('message', async function (e) { // message
const { data } = e const { data } = e
console.log(e);
if (data && data.action) { if (data && data.action) {
if (data.action == 'initUser') { if (data.action == 'initUser') {
await store.dispatch(actions.auth.initAuth(data.user)) await store.dispatch(actions.auth.initAuth(data.user))

7
code/VideoAccess-VCMP/web/client/src/sections/auth/actions/auth.js

@ -64,9 +64,10 @@ export function login (username, password) {
export const LOGOUT = 'LOGOUT'; export const LOGOUT = 'LOGOUT';
export function logout () { export function logout () {
const user = JSON.parse(sessionStorage.getItem('user')) const user = JSON.parse(sessionStorage.getItem('user'))
AxyRequest.post(ApiTable.logout, { user && user.token ?
token: user.token AxyRequest.post(ApiTable.logout, {
}); token: user.token
}) : null;
sessionStorage.removeItem('user'); sessionStorage.removeItem('user');
return { return {
type: LOGOUT type: LOGOUT

16
code/VideoAccess-VCMP/web/client/src/utils/index.js

@ -2,14 +2,18 @@
import { isAuthorized } from './func'; import { isAuthorized } from './func';
import { AuthorizationCode } from './authCode'; import { AuthorizationCode } from './authCode';
import { ApiTable, RouteTable, AuthRequest, AxyRequest } from './webapi' import { ApiTable, RouteTable, AuthRequest, AxyRequest } from './webapi'
import { YS_PTZ_DIRECTION, ysptz } from './videoCloudControl';
export { export {
isAuthorized, isAuthorized,
AuthorizationCode, AuthorizationCode,
ApiTable, ApiTable,
RouteTable, RouteTable,
AuthRequest, AuthRequest,
AxyRequest, AxyRequest,
YS_PTZ_DIRECTION,
ysptz,
} }

54
code/VideoAccess-VCMP/web/client/src/utils/videoCloudControl.js

@ -0,0 +1,54 @@
'use strict';
import superagent from "superagent"
// 操作命令:0-上,1-下,2-左,3-右,4-左上,5-左下,6-右上,7-右下,8-放大,9-缩小,10-近焦距,11-远焦距
export const YS_PTZ_DIRECTION = {
up: 0,
down: 1,
left: 2,
right: 3,
up_left: 4,
down_left: 5,
up_right: 6,
down_right: 7,
zoom_in: 9,
zoom_out: 8,
focus_in: 10,
focus_out: 11
}
export function ysptz (ac, { serialNo, yingshiToken, channelNo }) {
let params = {
accessToken: yingshiToken,
deviceSerial: serialNo,
channelNo: channelNo || 1,
direction: YS_PTZ_DIRECTION[ac],
speed: 2
}
let startReqBody = Object.assign({}, params, { speed: 1 })
let stopReqBody = params
let requestUrl = `https://open.ys7.com/api/lapp/device/ptz/`
superagent
.post(requestUrl + 'start')
.send(startReqBody)
.set('Content-Type', 'application/x-www-form-urlencoded')
.then((res) => {
let resBody = res.body
if (resBody.code != '200') {
// message.error(resBody.msg)
} else {
setTimeout(_ => {
superagent
.post(requestUrl + 'stop')
.send(stopReqBody)
.set('Content-Type', 'application/x-www-form-urlencoded')
.then((tRes) => {
let resBody2 = tRes.body
if (resBody2.code != '200') {
// message.error('操作失败!')
}
})
}, 500)
}
})
}
Loading…
Cancel
Save