This commit is contained in:
quantulr
2023-12-05 09:38:06 +08:00
parent 9bd8ee2899
commit 419adbcbe3
8 changed files with 562 additions and 396 deletions

View File

@ -1,5 +1,5 @@
<template>
<CameraList/>
<CameraList/>
<!-- <n-config-provider :date-locale="dateZhCN" :locale="zhCN">-->
<!-- <CameraItem-->
<!-- name="测试球机视频"-->
@ -14,30 +14,28 @@
<script>
// import {NConfigProvider, zhCN,dateZhCN} from 'naive-ui'
import CameraList from "./components/CameraList.vue";
import CameraItem from "@/components/CameraItem.vue";
import {useTokenStore} from "@/store/token";
// import List from "./infinite-list-vue.umd.cjs";
export default {
name: "App",
computed: {
zhCN() {
return zhCN
}
},
components: {
CameraItem,
CameraList,
},
mounted() {
const tokenStore = useTokenStore();
tokenStore.fetchToken();
},
name: "App",
computed: {},
components: {
CameraList,
},
created() {
console.log("created")
},
mounted() {
console.log("mounted")
const tokenStore = useTokenStore();
tokenStore.fetchToken();
},
};
</script>
<style>
#app {
background-color: #f6f6f6;
background-color: #f6f6f6;
//width: 100vw; //height: 100vh;
}
</style>

View File

@ -1,155 +1,166 @@
<template>
<div ref="cameraItemRef" class="camera-item">
<div class="camera-name">
{{ props.name }}
</div>
<!-- <n-image :src="previewImage" class="video-cover" object-fit="contain"></n-image>-->
<img :src="previewImage" alt="预览图" class="video-cover"/>
<!-- <video-->
<!-- v-show="false"-->
<!-- ref="previewVideoRef"-->
<!-- :class="`camera-player ${isFullscreen ? 'fullscreen-video' : ''}`"-->
<!-- ></video>-->
<div class="overlay" @click="openPlayModal">
<span class="iconfont icon-bofang_o" style="font-size: 60px"></span>
</div>
<div v-if="showModal" class="modal-overlay" @click="closePlayModal">
<div ref="cameraItemRef" class="camera-item">
<div class="camera-name">
{{ props.name }}
</div>
<!-- <n-image :src="previewImage" class="video-cover" object-fit="contain"></n-image>-->
<img :src="previewImage" alt="预览图" class="video-cover"/>
<!-- <video-->
<!-- v-show="false"-->
<!-- ref="previewVideoRef"-->
<!-- :class="`camera-player ${isFullscreen ? 'fullscreen-video' : ''}`"-->
<!-- ></video>-->
<div class="overlay" @click="openPlayModal">
<span class="iconfont icon-bofang_o" style="font-size: 60px"></span>
</div>
<div v-if="showModal" class="modal-overlay" @click="closePlayModal">
<span
v-if="isFullscreen && !playbackFlvUrl"
class="quit-fullscreen iconfont icon-quxiaoquanping_o"
@click.stop="exitFullscreen"
v-if="isFullscreen && !playbackFlvUrl"
class="quit-fullscreen iconfont icon-quxiaoquanping_o"
@click.stop="exitFullscreen"
></span>
<n-slider v-if="isFullscreen && playbackFlvUrl" v-model="playbackProcess" :format-tooltip="formatSeconds"
:marks="sliderMarks"
:max="isToday? getNowSeconds()-600 :86399" :min="0" :step="300"
class="time-slider"
reverse
vertical
@update-value="setPlaybackProcess"></n-slider>
<div ref="rotateElementRef" :class="`modal`">
<video
ref="videoElementRef"
:class="`camera-player ${isFullscreen ? 'fullscreen-video' : ''}`"
></video>
<div
:class="`video-overlay ${isFullscreen ? 'fullscreen-video' : ''}`"
@click.stop="switchPlayStatus"
>
<n-popover v-if="isFullscreen" :show="showPopover"
:show-arrow="false"
:style="`transform-origin: center;transform: rotate(90deg);`"
placement="left" trigger="manual">
<template #trigger>
<n-icon class="history-time-range" color="#fff" @click="showPopover =true">
<calendar-outline/>
</n-icon>
</template>
<div>
<n-config-provider :date-locale="dateZhCN" :locale="zhCN">
<n-date-picker v-model="playbackDate" format="yyyy-MM-dd HH:mm:ss" panel type="date"
@update-formatted-value="handleConfirmRange"/>
</n-config-provider>
<n-slider v-if="isFullscreen && playbackFlvUrl" v-model:value="playbackProcess" :format-tooltip="formatSeconds"
:marks="sliderMarks"
:max="isToday? getNowSeconds()-600 :86399" :min="0" :step="300"
class="time-slider"
reverse
vertical
@update-value="setPlaybackProcess"></n-slider>
<div ref="rotateElementRef" :class="`modal`">
<video
ref="videoElementRef"
:class="`camera-player ${isFullscreen ? 'fullscreen-video' : ''}`"
></video>
<div
:class="`video-overlay ${isFullscreen ? 'fullscreen-video' : ''}`"
@click.stop="switchPlayStatus"
>
<n-popover v-if="isFullscreen" :show="showPopover"
:show-arrow="false"
:style="`transform-origin: center;transform: rotate(90deg);`"
placement="left" trigger="manual">
<template #trigger>
<n-icon class="history-time-range" color="#fff" @click="showPopover =true">
<calendar-outline/>
</n-icon>
</template>
<div>
<n-config-provider :date-locale="dateZhCN" :locale="zhCN">
<!-- v-model="playbackDate"-->
<n-date-picker v-model:value="playbackDate" format="yyyy-MM-dd HH:mm:ss" panel type="date"
@update-formatted-value="handleConfirmRange"/>
</n-config-provider>
</div>
</n-popover>
<n-icon v-if="playbackFlvUrl" class="exit-playback" color="#fff"
@click="playbackFlvUrl=null">
<log-out-outline/>
</n-icon>
<div
v-if="isFullscreen && !playbackFlvUrl &&props.name.includes('球机')"
class="arrow-control"
>
<div
:class="`up iconfont icon-up ${pressed.up ? 'pressed' : ''}`"
@touchend="moveCamera('stop')"
@touchstart="moveCamera('up')"
@contextmenu.prevent
></div>
<div
:class="`down iconfont icon-down ${
</div>
</n-popover>
<n-icon v-if="playbackFlvUrl" class="exit-playback" color="#fff"
@click="playbackFlvUrl=null">
<log-out-outline/>
</n-icon>
<!-- -->
<div class="snapshot" v-if="isFullscreen">
<n-icon v-if="!snapshotDownloading" color="#18a058" @click="handleClickGetSnapshot">
<camera-outline/>
</n-icon>
<n-spin v-else size="medium"/>
</div>
<!--right-bottom 控制转动-->
<div
v-if="isFullscreen && !playbackFlvUrl &&props.name.includes('球机')"
class="arrow-control"
>
<div
:class="`up iconfont icon-up ${pressed.up ? 'pressed' : ''}`"
@touchend="moveCamera('stop')"
@touchstart="moveCamera('up')"
@contextmenu.prevent
></div>
<div
:class="`down iconfont icon-down ${
pressed.down ? 'pressed' : ''
}`"
@touchend="moveCamera('stop')"
@touchstart="moveCamera('down')"
@contextmenu.prevent
></div>
<div
:class="`left iconfont icon-left ${
@touchend="moveCamera('stop')"
@touchstart="moveCamera('down')"
@contextmenu.prevent
></div>
<div
:class="`left iconfont icon-left ${
pressed.left ? 'pressed' : ''
}`"
@touchend="moveCamera('stop')"
@touchstart="moveCamera('left')"
@contextmenu.prevent
></div>
<div
:class="`right iconfont icon-right ${
@touchend="moveCamera('stop')"
@touchstart="moveCamera('left')"
@contextmenu.prevent
></div>
<div
:class="`right iconfont icon-right ${
pressed.right ? 'pressed' : ''
}`"
@touchend="moveCamera('stop')"
@touchstart="moveCamera('right')"
@contextmenu.prevent
></div>
</div>
<div
v-if="!isFullscreen"
class="iconfont icon-quanping_o fullscreen"
@click.stop="videoFullscreen"
></div>
<div class="close-button iconfont icon-close" @click="closePlayModal">
</div>
</div>
</div>
@touchend="moveCamera('stop')"
@touchstart="moveCamera('right')"
@contextmenu.prevent
></div>
</div>
<div
v-if="!isFullscreen"
class="iconfont icon-quanping_o fullscreen"
@click.stop="videoFullscreen"
></div>
<div class="close-button iconfont icon-close" @click="closePlayModal">
</div>
</div>
</div>
</div>
</div>
</template>
<script name="camera" setup>
import {nextTick, onMounted, onUnmounted, reactive, ref, toRefs, watch} from "vue";
import {dateZhCN, NConfigProvider, NDatePicker, NIcon, NPopover, NSlider, zhCN} from 'naive-ui'
import {CalendarOutline, LogOutOutline} from '@vicons/ionicons5'
import {dateZhCN, NConfigProvider, NDatePicker, NIcon, NPopover, NSlider, NSpin, zhCN} from 'naive-ui'
import {CalendarOutline, LogOutOutline, CameraOutline} from '@vicons/ionicons5'
import flvjs from "flv.js";
import axios from "axios";
import {useTokenStore} from "@/store/token";
import {debounce} from "lodash-es";
import JSZip from 'jszip'
// eslint-disable-next-line no-undef
const props = defineProps({
name: {
type: String,
},
source: {
type: String,
default: null,
},
name: {
type: String,
},
source: {
type: String,
default: null
// FIXME
// default: "https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-720p.flv",
},
});
const {source} = toRefs(props);
const sliderMarks = {
0: '00:00',
// 3600: '01:00',
7200: '02:00',
// 10800: '03:00',
14400: '04:00',
// 18000: '05:00',
21600: '06:00',
// 25200: '07:00',
28800: '08:00',
// 32400: '09:00',
36000: '10:00',
// 39600: '11:00',
43200: '12:00',
// 46800: '13:00',
50400: '14:00',
// 54000: '15:00',
57600: '16:00',
// 61200: '17:00',
64800: '18:00',
// 68400: '19:00',
72000: '20:00',
// 75600: '21:00',
79200: '22:00',
// 82800: '23:00',
0: '00:00',
// 3600: '01:00',
7200: '02:00',
// 10800: '03:00',
14400: '04:00',
// 18000: '05:00',
21600: '06:00',
// 25200: '07:00',
28800: '08:00',
// 32400: '09:00',
36000: '10:00',
// 39600: '11:00',
43200: '12:00',
// 46800: '13:00',
50400: '14:00',
// 54000: '15:00',
57600: '16:00',
// 61200: '17:00',
64800: '18:00',
// 68400: '19:00',
72000: '20:00',
// 75600: '21:00',
79200: '22:00',
// 82800: '23:00',
}
const playbackDate = ref(null)
@ -163,93 +174,96 @@ const showPopover = ref(false)
let flvPlayer;
const tokenStore = useTokenStore();
onMounted(async () => {
if (!tokenStore.token) {
await tokenStore.fetchToken();
}
const serial = source.value.split("/flv/hls/")[1].split("_")[0];
const url = new URL(source.value);
previewImage.value = `${url.protocol}//${url.host}/api/v1/device/channelsnap?serial=${serial}&token=${tokenStore.token}`;
if (!tokenStore.token) {
await tokenStore.fetchToken();
}
const serial = source.value.split("/flv/hls/")[1].split("_")[0];
const url = new URL(source.value);
previewImage.value = `${url.protocol}//${url.host}/api/v1/device/channelsnap?serial=${serial}&token=${tokenStore.token}`;
});
const openPlayModal = () => {
showModal.value = true;
nextTick(() => {
flvPlayer = flvjs.createPlayer({
type: "flv",
url: props.source,
});
if (flvjs.isSupported()) {
flvPlayer.attachMediaElement(videoElementRef.value);
flvPlayer.load();
flvPlayer.on("ERROR", flvPlayerEventHandler);
flvPlayer.play();
}
showModal.value = true;
nextTick(() => {
flvPlayer = flvjs.createPlayer({
type: "flv",
url: props.source,
});
// document.addEventListener("touchstart", handleZoomStart)
// document.addEventListener("touchend", handleZoomEnd)
// document.addEventListener("touchmove", handleZooming)
// console.log('zoom event bind')
if (flvjs.isSupported()) {
flvPlayer.attachMediaElement(videoElementRef.value);
flvPlayer.load();
flvPlayer.on("ERROR", flvPlayerEventHandler);
flvPlayer.play();
}
});
// document.addEventListener("touchstart", handleZoomStart)
// document.addEventListener("touchend", handleZoomEnd)
// document.addEventListener("touchmove", handleZooming)
// console.log('zoom event bind')
};
const getNowSeconds = () => {
// 获取今天的日期
const today = new Date();
// 获取今天的日期
const today = new Date();
// 将日期设置为零点
today.setHours(0, 0, 0, 0);
today.setHours(0, 0, 0, 0);
// 获取当前时间戳并计算与今天零点的时间差
const now = new Date();
const diff = now.getTime() - today.getTime();
const seconds = Math.floor(diff / 1000);
const now = new Date();
const diff = now.getTime() - today.getTime();
const seconds = Math.floor(diff / 1000);
return seconds
return seconds
}
const closePlayModal = async () => {
if (flvPlayer) {
flvDestroy();
}
showModal.value = false;
/*关闭视频时,如果存在回放,则调用停止接口*/
if (!playbackStreamId.value) {
return
}
if (!tokenStore.token) {
await tokenStore.fetchToken();
}
const serial = source.value.split("/flv/hls/")[1].split("_")[0];
const url = new URL(source.value);
axios.get(
`${url.protocol}//${url.host}/api/v1/playback/stop?serial=${serial}&code=${serial}&streamid=${playbackStreamId.value}&token=${tokenStore.token}`
);
if (flvPlayer) {
flvDestroy();
}
showModal.value = false;
/*关闭视频时,如果存在回放,则调用停止接口*/
if (!playbackStreamId.value) {
return
}
if (!tokenStore.token) {
await tokenStore.fetchToken();
}
playbackDate.value = null
playbackFlvUrl.value = null
playbackProcess.value = 0
const serial = source.value.split("/flv/hls/")[1].split("_")[0];
const url = new URL(source.value);
axios.get(
`${url.protocol}//${url.host}/api/v1/playback/stop?serial=${serial}&code=${serial}&streamid=${playbackStreamId.value}&token=${tokenStore.token}`
);
exitFullscreen()
};
// url: "https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv",
const flvPlayerEventHandler = (e) => {
console.log(e);
console.log(e);
};
// 切换播放状态
const switchPlayStatus = () => {
if (!videoElementRef.value) return;
if (videoElementRef.value.paused) {
flvPlayer.play();
} else {
// flvPlayer.pause();
}
if (!videoElementRef.value) return;
if (videoElementRef.value.paused) {
flvPlayer.play();
} else {
// flvPlayer.pause();
}
};
const scale = ref(0)
/* 进入全屏 */
const videoFullscreen = () => {
const _scale = screen.availWidth / videoElementRef.value.offsetHeight;
scale.value = _scale
videoElementRef.value.style.setProperty("--scale", _scale);
videoElementRef.value.parentNode.style.setProperty("--scale", _scale);
isFullscreen.value = true;
/*进入全屏时,绑定双指缩放事件*/
const _scale = screen.availWidth / videoElementRef.value.offsetHeight;
scale.value = _scale
videoElementRef.value.style.setProperty("--scale", _scale);
videoElementRef.value.parentNode.style.setProperty("--scale", _scale);
isFullscreen.value = true;
/*进入全屏时,绑定双指缩放事件*/
};
/**
@ -258,25 +272,25 @@ const videoFullscreen = () => {
* @return {string}
*/
function formatSeconds(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds - hours * 3600) / 60);
const formattedHours = hours.toString().padStart(2, '0');
const formattedMinutes = minutes.toString().padStart(2, '0');
return `${formattedHours}:${formattedMinutes}`;
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds - hours * 3600) / 60);
const formattedHours = hours.toString().padStart(2, '0');
const formattedMinutes = minutes.toString().padStart(2, '0');
return `${formattedHours}:${formattedMinutes}`;
}
/* 退出全屏 */
const exitFullscreen = () => {
isFullscreen.value = false;
isFullscreen.value = false;
};
/*方向按钮是否按下*/
const pressed = reactive({
left: false,
right: false,
up: false,
down: false,
left: false,
right: false,
up: false,
down: false,
});
/**
@ -285,45 +299,45 @@ const pressed = reactive({
* @return {Promise<void>}
*/
const moveCamera = async (arrow) => {
// event.preventDefault()
if (!tokenStore.token) {
await tokenStore.fetchToken();
}
const serial = source.value.split("/flv/hls/")[1].split("_")[0];
const url = new URL(source.value);
await axios.get(
`${url.protocol}//${url.host}/api/v1/control/ptz?serial=${serial}&code=${serial}&command=${arrow}&token=${tokenStore.token}`
);
if (arrow === "stop") {
pressed.left = false;
pressed.right = false;
pressed.up = false;
pressed.down = false;
} else {
pressed[arrow] = true;
}
// event.preventDefault()
if (!tokenStore.token) {
await tokenStore.fetchToken();
}
const serial = source.value.split("/flv/hls/")[1].split("_")[0];
const url = new URL(source.value);
await axios.get(
`${url.protocol}//${url.host}/api/v1/control/ptz?serial=${serial}&code=${serial}&command=${arrow}&token=${tokenStore.token}`
);
if (arrow === "stop") {
pressed.left = false;
pressed.right = false;
pressed.up = false;
pressed.down = false;
} else {
pressed[arrow] = true;
}
};
function flvDestroy() {
flvPlayer.off("ERROR", flvPlayerEventHandler);
flvPlayer.pause();
flvPlayer.unload();
flvPlayer.detachMediaElement();
flvPlayer.destroy();
flvPlayer = null;
flvPlayer.off("ERROR", flvPlayerEventHandler);
flvPlayer.pause();
flvPlayer.unload();
flvPlayer.detachMediaElement();
flvPlayer.destroy();
flvPlayer = null;
}
function flvCreate(url) {
flvPlayer = flvjs.createPlayer({
type: "flv",
url: url,
});
if (flvjs.isSupported()) {
flvPlayer.attachMediaElement(videoElementRef.value);
flvPlayer.load();
flvPlayer.on("ERROR", flvPlayerEventHandler);
flvPlayer.play();
}
flvPlayer = flvjs.createPlayer({
type: "flv",
url: url,
});
if (flvjs.isSupported()) {
flvPlayer.attachMediaElement(videoElementRef.value);
flvPlayer.load();
flvPlayer.on("ERROR", flvPlayerEventHandler);
flvPlayer.play();
}
}
const playbackProcess = ref(0)
@ -334,37 +348,138 @@ const playbackProcess = ref(0)
* @return {Promise<void>}
*/
const setPlaybackProcess = debounce(async (value) => {
if (!tokenStore.token) {
await tokenStore.fetchToken();
}
const serial = source.value.split("/flv/hls/")[1].split("_")[0];
const url = new URL(source.value);
axios.get(
`${url.protocol}//${url.host}/api/v1/playback/control?serial=${serial}&code=${serial}&streamid=${playbackStreamId.value}&command=play&range=${value}&token=${tokenStore.token}`
);
if (!tokenStore.token) {
await tokenStore.fetchToken();
}
const serial = source.value.split("/flv/hls/")[1].split("_")[0];
const url = new URL(source.value);
axios.get(
`${url.protocol}//${url.host}/api/v1/playback/control?serial=${serial}&code=${serial}&streamid=${playbackStreamId.value}&command=play&range=${value}&token=${tokenStore.token}`
);
}, 500)
onUnmounted(() => {
console.log("destroy player here");
if (flvPlayer) {
flvDestroy();
}
console.log("destroy player here");
if (flvPlayer) {
flvDestroy();
}
});
const formatTime = (_date) => {
// _date.setSeconds(_date.getSeconds() + playbackProcess.value)
const year = _date.getFullYear()
const month = (_date.getMonth() + 1).toString().padStart(2, '0')
const day = _date.getDate().toString().padStart(2, '0')
const hour = _date.getHours().toString().padStart(2, '0')
const minute = _date.getMinutes().toString().padStart(2, '0')
const second = _date.getSeconds().toString().padStart(2, '0')
return `${year}-${month}-${day} ${hour}:${minute}:${second}`
}
/**
* 保存文件
* @param blob
* @param filename
* @returns {Promise<unknown>}
*/
const saveAs = (blob, filename) =>
new Promise((resolve, reject) => {
const reader = new FileReader();
const a = document.createElement("a");
const zip = new JSZip()
zip.file(filename, blob)
zip.generateAsync({type: "blob"}).then((content) => {
reader.readAsDataURL(content);
})
reader.addEventListener("load", () => {
a.href = reader.result;
a.click();
resolve();
});
reader.addEventListener("error", reject);
});
const snapshotDownloading = ref(false)
const handleClickGetSnapshot = async () => {
snapshotDownloading.value = true
try {
await getCameraSnapshot()
} catch (e) {
console.log(e)
}
snapshotDownloading.value = false
}
/**
* 获取摄像头快照
* @returns {Promise<void>}
*/
const getCameraSnapshot = async () => {
if (!tokenStore.token) {
await tokenStore.fetchToken();
}
let stime = null;
let filename;
const serial = source.value.split("/flv/hls/")[1].split("_")[0];
const url = new URL(source.value);
if (playbackFlvUrl.value) {
const _date = new Date(playbackDate.value);
_date.setSeconds(_date.getSeconds() + playbackProcess.value)
const year = _date.getFullYear()
const month = (_date.getMonth() + 1).toString().padStart(2, '0')
const day = _date.getDate().toString().padStart(2, '0')
const hour = _date.getHours().toString().padStart(2, '0')
const minute = _date.getMinutes().toString().padStart(2, '0')
const second = _date.getSeconds().toString().padStart(2, '0')
stime = `${year}${month}${day}${hour}${minute}${second}`
filename = `${serial}_${year}-${month}-${day}-${hour}-${minute}-${second}.jpg`
} else {
filename = `${serial}_${formatTime(new Date()).replace(' ', '-').replace(':', "-")}.jpg`
}
// 如果正在播放回放,则先停止回放
if (playbackFlvUrl.value) {
await axios.get(
`${url.protocol}//${url.host}/api/v1/playback/stop?serial=${serial}&code=${serial}&streamid=${playbackStreamId.value}&token=${tokenStore.token}`
);
playbackStreamId.value = null
}
try {
if (stime === null) {
stime = 'now'
}
const snapshot = await axios.get(
`${url.protocol}//${url.host}/api/v1/device/channelsnap?serial=${serial}&code=${serial}&stime=${stime}&format=jpeg&w=-1&h=-1&token=${tokenStore.token}`,
{
responseType: "blob",
}
);
await saveAs(snapshot.data, filename)
} catch (e) {
console.log(e)
}
if (playbackFlvUrl.value) {
const startDate = new Date(playbackDate.value)
startDate.setSeconds(startDate.getSeconds() + playbackProcess.value)
const starttime = formatTime(startDate)
await startPlayHistory(starttime)
setPlaybackProcess(playbackProcess.value)
}
}
const isToday = ref(false)
const handleConfirmRange = (value) => {
console.log(value)
if (value) {
showPopover.value = false
startPlayHistory(value)
}
const _date = new Date(value)
const now = new Date()
isToday.value = _date.getDate() === now.getDate()
console.log(playbackDate.value)
console.log(value)
if (value) {
showPopover.value = false
startPlayHistory(value)
}
const _date = new Date(value)
const now = new Date()
isToday.value = _date.getDate() === now.getDate()
}
const playbackFlvUrl = ref(null)/*回放视频url*/
const playbackStreamId = ref(null)/*回放id*/
@ -375,41 +490,41 @@ const playbackStreamId = ref(null)/*回放id*/
* @return {Promise<void>}
*/
const startPlayHistory = async (value) => {
const starttime = value.replace(' ', 'T')
if (!tokenStore.token) {
await tokenStore.fetchToken();
}
const serial = source.value.split("/flv/hls/")[1].split("_")[0];
const url = new URL(source.value);
const resp = await axios.get(
`${url.protocol}//${url.host}/api/v1/playback/start?serial=${serial}&code=${serial}&starttime=${starttime}&endtime=${starttime.replace('00:00:00', '23:59:59')}&token=${tokenStore.token}`
);
playbackStreamId.value = resp.data.StreamID
playbackFlvUrl.value = resp.data.FLV
const starttime = value.replace(' ', 'T')
if (!tokenStore.token) {
await tokenStore.fetchToken();
}
const serial = source.value.split("/flv/hls/")[1].split("_")[0];
const url = new URL(source.value);
const endTime = starttime.split('T')[0] + 'T23:59:59'
const resp = await axios.get(
`${url.protocol}//${url.host}/api/v1/playback/start?serial=${serial}&code=${serial}&starttime=${starttime.split('T')[0] + 'T00:00:00'}&endtime=${endTime}&token=${tokenStore.token}`
);
playbackStreamId.value = resp.data.StreamID
playbackFlvUrl.value = resp.data.FLV
}
/*监听回放url,销毁原播放器,如果有值则播放回放,没有则播放原视频*/
watch(playbackFlvUrl, async (url, oldUrl) => {
flvDestroy()
if (url) {
flvCreate(url)
} else {
flvCreate(props.source)
flvDestroy()
if (url !== null) {
flvCreate(url)
} else {
flvCreate(props.source)
}
if (oldUrl && url === null) {
if (!playbackStreamId.value) {
return
}
if (oldUrl) {
if (!playbackStreamId.value) {
return
}
if (!tokenStore.token) {
await tokenStore.fetchToken();
}
const serial = source.value.split("/flv/hls/")[1].split("_")[0];
const _url = new URL(source.value);
axios.get(
`${_url.protocol}//${_url.host}/api/v1/playback/stop?serial=${serial}&code=${serial}&streamid=${playbackStreamId.value}&token=${tokenStore.token}`
);
if (!tokenStore.token) {
await tokenStore.fetchToken();
}
const serial = source.value.split("/flv/hls/")[1].split("_")[0];
const _url = new URL(source.value);
axios.get(
`${_url.protocol}//${_url.host}/api/v1/playback/stop?serial=${serial}&code=${serial}&streamid=${playbackStreamId.value}&token=${tokenStore.token}`
);
}
})
</script>
@ -531,11 +646,19 @@ watch(playbackFlvUrl, async (url, oldUrl) => {
left: 0;
top: 0;
.snapshot {
position: absolute;
left: 20px;
top: 50%;
transform: translateY(-50%);
display: flex;
}
.close-button {
position: absolute;
top: 6px;
right: 6px;
color: #fff;
color: #e7f5ee;
font-size: 48px;
border-radius: 50%;
}
@ -623,16 +746,16 @@ watch(playbackFlvUrl, async (url, oldUrl) => {
</style>
<style>
.n-slider {
background-color: rgb(209 213 219);
background-color: rgb(209 213 219);
}
.n-slider-mark {
transform: translateY(calc(-50% + var(--n-dot-height) / 2)) rotate(90deg) !important;
color: springgreen;
transform: translateY(calc(-50% + var(--n-dot-height) / 2)) rotate(90deg) !important;
color: springgreen;
}
.n-slider-handle-indicator {
transform-origin: center;
transform: rotate(90deg);
transform-origin: center;
transform: rotate(90deg);
}
</style>

View File

@ -95,58 +95,55 @@ const queryParams = reactive({
// const activeIndex = ref(0);
const loadProjectList = async () => {
// const resp = await axios.get(`http://localhost:3000/project-list`);
// TODO:test
// const resp = await axios.get(
// "/api/portal/r/jd?cmd=com.awspaas.user.apps.cmp_screen_getProjectList&sid=b9e99f78-2f6e-4d1c-aea9-4364f4191d30"
// );
// const resp = {
// data: [{
// "SUPERVISORID": "",
// "MAP": "40.763291,111.971932",
// "PROID": "HT6YXM-202303232023030010",
// "PROTYPE": "",
// "UPDATEDATE": 1679559863000,
// "SUPERVISOR": "",
// "DESIGNERID": "",
// "STCV": 800000,
// "PROOWNER": "六院本部/资产运营部/",
// "SURVEY": "",
// "UPDATEUSER": "13088880001",
// "SURVEYID": "",
// "APPENDIX": "",
// "PROCOST": 800000,
// "PRICELEADERID": "13088880004",
// "PRODEPTLEADERID": "13088880003",
// "PROLEADERID": "13088880002",
// "PROLEADER": "建设单位项目管理人员",
// "CONTRACTOR": "",
// "FLOORAREA": 2000,
// "CREATEUSER": "13088880001",
// "ISEND": 0,
// "RSTATUS": "1",
// "LEADERID": "13088880007",
// "PRICELEADER": "工程造价主管部门领导",
// "PROSTATUS": "1",
// "CBDATE": 1677600000000,
// "CONTRACTORID": "",
// "LEADER": "主要领导",
// "PRODEPTLEADER": "建设单位项目管理部门领导",
// "PRONAME": "A区科创园",
// "ASSETLEADER": "固定资产投资主管领导",
// "BINDID": "47856a34-afa4-4a9b-ae28-ba450a4ab798",
// "ORGID": "d8ee92f8-c80f-4741-b92c-cbc60c91d754",
// "PROADDRESS": "内蒙古自治区呼和浩特市赛罕区黄合少镇南地村",
// "DESIGNER": "",
// "PROCESSDEFID": "obj_a48ef53d7a2f4492bf440174e32bef3a",
// "PROPIC": "",
// "CREATEDATE": 1679559863000,
// "ASSETLEADERID": "13088880005",
// "CEDATE": 1703260800000,
// "PROFILE": "",
// "ACCDATE": 1703260800000,
// "ID": "a0fb6df5-48e4-442f-8b9d-da5861c1bf41"q
// }]
// }
// FIXME:test
/*const resp = {
data: [{
"SUPERVISORID": "",
"MAP": "40.763291,111.971932",
"PROID": "HT6YXM-202303232023030010",
"PROTYPE": "",
"UPDATEDATE": 1679559863000,
"SUPERVISOR": "",
"DESIGNERID": "",
"STCV": 800000,
"PROOWNER": "六院本部/资产运营部/",
"SURVEY": "",
"UPDATEUSER": "13088880001",
"SURVEYID": "",
"APPENDIX": "",
"PROCOST": 800000,
"PRICELEADERID": "13088880004",
"PRODEPTLEADERID": "13088880003",
"PROLEADERID": "13088880002",
"PROLEADER": "建设单位项目管理人员",
"CONTRACTOR": "",
"FLOORAREA": 2000,
"CREATEUSER": "13088880001",
"ISEND": 0,
"RSTATUS": "1",
"LEADERID": "13088880007",
"PRICELEADER": "工程造价主管部门领导",
"PROSTATUS": "1",
"CBDATE": 1677600000000,
"CONTRACTORID": "",
"LEADER": "主要领导",
"PRODEPTLEADER": "建设单位项目管理部门领导",
"PRONAME": "A区科创园",
"ASSETLEADER": "固定资产投资主管领导",
"BINDID": "47856a34-afa4-4a9b-ae28-ba450a4ab798",
"ORGID": "d8ee92f8-c80f-4741-b92c-cbc60c91d754",
"PROADDRESS": "内蒙古自治区呼和浩特市赛罕区黄合少镇南地村",
"DESIGNER": "",
"PROCESSDEFID": "obj_a48ef53d7a2f4492bf440174e32bef3a",
"PROPIC": "",
"CREATEDATE": 1679559863000,
"ASSETLEADERID": "13088880005",
"CEDATE": 1703260800000,
"PROFILE": "",
"ACCDATE": 1703260800000,
"ID": "a0fb6df5-48e4-442f-8b9d-da5861c1bf41"
}]
}*/
const resp = await axios.get(
"./jd?cmd=com.awspaas.user.apps.cmp_screen_getProjectList&sid=" + sid
);
@ -164,54 +161,48 @@ const projectClicked = () => {
const loadCameraList = async () => {
cameraLoading.value = true;
// TODO:test
// const resp = await axios(`/api/portal/r/jd`, {
// params: queryParams,
// });
// const resp = {
// data: {
// "code": 0,
// "rows": [{
// "address": "A区-科创园南门内",
// "flvUrl": "http://106.74.152.123:10000/sms/34020000002020000001/flv/hls/34020000001320000009_34020000001320000009.flv",
// "id": "2e627e9a-afe8-4065-9196-423467396401",
// "projectId": "HT6YXM-202303232023030010",
// "projectName": "A区科创园"
// }, {
// "address": "A区科创园东门内",
// "flvUrl": "http://106.74.152.123:10000/sms/34020000002020000001/flv/hls/34020000001320000008_34020000001320000008.flv",
// "id": "7ff7ba05-05e4-48e6-a43a-a0983ff1aa2b",
// "projectId": "HT6YXM-202303232023030010",
// "projectName": "A区科创园"
// }, {
// "address": "A区科创园东塔吊东北角球机",
// "flvUrl": "http://106.74.152.123:10000/sms/34020000002020000001/flv/hls/34020000001320000010_34020000001320000010.flv",
// "id": "e410e322-cceb-40b9-899b-4f3d2e8215eb",
// "projectId": "HT6YXM-202303232023030010",
// "projectName": "A区科创园"
// }, {
// "address": "A区科创园东塔吊西南角球机",
// "flvUrl": "http://106.74.152.123:10000/sms/34020000002020000001/flv/hls/34020000001320000011_34020000001320000011.flv",
// "id": "ff3e0079-068c-49f0-b1fe-3fa8544d91f2",
// "projectId": "HT6YXM-202303232023030010",
// "projectName": "A区科创园"
// }],
// "total": 4
// }
// }
const resp = await axios.get("./jd", {
params: queryParams,
});
// FIXME:test
/*const resp = {
data: {
"code": 0,
"rows": [{
"address": "A区-科创园南门内",
// "flvUrl": "http://106.74.152.123:10000/sms/34020000002020000001/flv/hls/34020000001320000009_34020000001320000009.flv",
flvUrl: "https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-720p.flv",
"id": "2e627e9a-afe8-4065-9196-423467396401",
"projectId": "HT6YXM-202303232023030010",
"projectName": "A区科创园"
}, {
"address": "A区科创园东门内",
"flvUrl": "http://106.74.152.123:10000/sms/34020000002020000001/flv/hls/34020000001320000008_34020000001320000008.flv",
"id": "7ff7ba05-05e4-48e6-a43a-a0983ff1aa2b",
"projectId": "HT6YXM-202303232023030010",
"projectName": "A区科创园"
}, {
"address": "A区科创园东塔吊东北角球机",
"flvUrl": "http://106.74.152.123:10000/sms/34020000002020000001/flv/hls/34020000001320000010_34020000001320000010.flv",
"id": "e410e322-cceb-40b9-899b-4f3d2e8215eb",
"projectId": "HT6YXM-202303232023030010",
"projectName": "A区科创园"
}, {
"address": "A区科创园东塔吊西南角球机",
"flvUrl": "http://106.74.152.123:10000/sms/34020000002020000001/flv/hls/34020000001320000011_34020000001320000011.flv",
"id": "ff3e0079-068c-49f0-b1fe-3fa8544d91f2",
"projectId": "HT6YXM-202303232023030010",
"projectName": "A区科创园"
}],
"total": 4
}
}*/
const resp = await axios.get("./jd", {
params: queryParams,
});
cameraList.value = resp.data.rows;
total.value = resp.data.total;
cameraLoading.value = false;
};
const loadImageList = async () => {
// const resp = await axios.get(
// `./jd?cmd=com.awspaas.user.apps.cmp_photo_list&sid=${sid}&proId=${queryParams.query}`
// ); // 摄像头列表为空时,获取图片列表
// TODO:test
const resp = await axios.get(
`./jd?cmd=com.awspaas.user.apps.cmp_photo_list&sid=${sid}&proId=${queryParams.query}`
); // 摄像头列表为空时,获取图片列表

View File

@ -8,6 +8,7 @@ import "./assets/fonts/iconfont.css";
import { createPinia } from "pinia";
// const Home = { template: "<div>Home</div>" };
// const About = { template: "<div>About</div>" };
console.log('load end')
const pinia = createPinia();
// 2. 定义一些路由
// 每个路由都需要映射到一个组件。
@ -25,5 +26,6 @@ const pinia = createPinia();
// history: createWebHashHistory(),
// routes, // `routes: routes` 的缩写
// });
console.log('create pinia')
createApp(App).use(pinia).mount("#app");
console.log('create app')