Commit 51190fdd2232d45ba5d9a0c3f98d43b7aec77899

Authored by ww
1 parent 0f31669d

feat: 实现视频组件rtsp协议播放

... ... @@ -8,12 +8,15 @@
8 8 "COAP",
9 9 "edrx",
10 10 "EFENTO",
  11 + "fingerprintjs",
  12 + "flvjs",
11 13 "flvjs",
12 14 "inited",
13 15 "liveui",
14 16 "MQTT",
15 17 "notif",
16   - "PROTOBUF",
  18 + "PROTOBUF",
  19 + "rtsp",
17 20 "SCADA",
18 21 "SNMP",
19 22 "unref",
... ...
... ... @@ -35,6 +35,7 @@
35 35 "gen:iconfont": "esno ./build/generate/iconfont/index.ts"
36 36 },
37 37 "dependencies": {
  38 + "@fingerprintjs/fingerprintjs": "^3.4.1",
38 39 "@iconify/iconify": "^2.0.3",
39 40 "@logicflow/core": "^0.6.9",
40 41 "@logicflow/extension": "^0.6.9",
... ...
... ... @@ -101,3 +101,13 @@ export const getStreamingPlayUrl = (entityId: string) => {
101 101 url: `${CameraManagerApi.STREAMING_PLAY_GET_URL}/${entityId}`,
102 102 });
103 103 };
  104 +
  105 +export const getFlvPlayUrl = (url: string, browserId: string) => {
  106 + return `/api/yt/rtsp/openFlv?url=${encodeURIComponent(url)}&browserId=${browserId}`;
  107 +};
  108 +
  109 +export const closeFlvPlay = (url: string, browserId: string) => {
  110 + return defHttp.get({
  111 + url: `/rtsp/closeFlv?url=${encodeURIComponent(url)}&browserId=${browserId}`,
  112 + });
  113 +};
... ...
... ... @@ -4,15 +4,19 @@
4 4 import 'video.js/dist/video-js.css';
5 5 import { computed, CSSProperties, onMounted, onUnmounted, ref, unref } from 'vue';
6 6 import { useDesign } from '/@/hooks/web/useDesign';
  7 + import { getJwtToken, getShareJwtToken } from '/@/utils/auth';
  8 + import { isShareMode } from '/@/views/sys/share/hook';
7 9 import 'videojs-flvjs-es6';
8 10 const { prefixCls } = useDesign('basic-video-play');
9 11
10 12 const props = defineProps<{
11 13 options?: VideoJsPlayerOptions;
  14 + withToken?: boolean;
12 15 }>();
13 16
14 17 const emit = defineEmits<{
15 18 (event: 'ready', instance?: Nullable<VideoJsPlayer>): void;
  19 + (event: 'onUnmounted'): void;
16 20 }>();
17 21
18 22 const videoPlayEl = ref<HTMLVideoElement>();
... ... @@ -20,7 +24,7 @@
20 24 const videoPlayInstance = ref<Nullable<VideoJsPlayer>>();
21 25
22 26 const getOptions = computed(() => {
23   - const { options } = props;
  27 + const { options, withToken } = props;
24 28 const defaultOptions: VideoJsPlayerOptions & Recordable = {
25 29 language: 'zh',
26 30 muted: true,
... ... @@ -34,8 +38,18 @@
34 38 hasAudio: false,
35 39 withCredentials: false,
36 40 },
  41 + config: {
  42 + headers: {
  43 + ...(withToken
  44 + ? {
  45 + 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`,
  46 + }
  47 + : {}),
  48 + },
  49 + },
37 50 },
38 51 };
  52 +
39 53 return { ...defaultOptions, ...options };
40 54 });
41 55
... ... @@ -59,6 +73,7 @@
59 73 onUnmounted(() => {
60 74 unref(videoPlayInstance)?.dispose();
61 75 videoPlayInstance.value = null;
  76 + emit('onUnmounted');
62 77 });
63 78 </script>
64 79
... ...
... ... @@ -5,9 +5,15 @@ export enum VideoPlayerType {
5 5 flv = 'video/x-flv',
6 6 }
7 7
  8 +export const isRtspProtocol = (url: string) => {
  9 + const reg = /^rtsp:\/\//g;
  10 + return reg.test(url);
  11 +};
  12 +
8 13 export const getVideoTypeByUrl = (url: string) => {
9 14 try {
10   - const { pathname } = new URL(url);
  15 + const { protocol, pathname } = new URL(url);
  16 + if (protocol.startsWith('rtsp:')) return VideoPlayerType.flv;
11 17
12 18 const reg = /[^.]\w*$/;
13 19 const mathValue = pathname.match(reg) || [];
... ...
  1 +import { load } from '@fingerprintjs/fingerprintjs';
  2 +export const useFingerprint = () => {
  3 + const getResult = async () => {
  4 + const fp = await load();
  5 + const result = await fp.get();
  6 + return result;
  7 + };
  8 +
  9 + return { getResult };
  10 +};
... ...
... ... @@ -13,56 +13,87 @@
13 13 <div
14 14 class="flex items-center justify-center bg-dark-900 w-full h-full min-h-96 video-container"
15 15 >
16   - <BasicVideoPlay v-if="showVideo" :options="(options as any)" />
  16 + <BasicVideoPlay
  17 + v-if="showVideo"
  18 + :options="(options as any)"
  19 + :withToken="withToken"
  20 + @on-unmounted="handleCloseFlvPlayUrl"
  21 + />
17 22 </div>
18 23 </BasicModal>
19 24 </div>
20 25 </template>
21 26 <script setup lang="ts">
22   - import { ref, reactive } from 'vue';
  27 + import { ref, reactive, unref } from 'vue';
23 28 import { BasicModal, useModalInner } from '/@/components/Modal';
24 29 import type { StreamingManageRecord, CameraModel } from '/@/api/camera/model/cameraModel';
25 30 import { BasicVideoPlay, getVideoTypeByUrl } from '/@/components/Video';
26 31 import { AccessMode } from './config.data';
27   - import { getStreamingPlayUrl } from '/@/api/camera/cameraManager';
  32 + import { closeFlvPlay, getFlvPlayUrl, getStreamingPlayUrl } from '/@/api/camera/cameraManager';
  33 + import { isRtspProtocol } from '/@/components/Video/src/utils';
28 34 import { VideoJsPlayerOptions } from 'video.js';
  35 + import { useFingerprint } from '/@/utils/useFingerprint';
  36 + import { GetResult } from '@fingerprintjs/fingerprintjs';
29 37
30 38 const heightNum = ref(800);
31 39 const showVideo = ref(false);
  40 +
  41 + const playUrl = ref('');
  42 +
  43 + const withToken = ref(false);
  44 +
  45 + const fingerprintResult = ref<Nullable<GetResult>>(null);
  46 +
32 47 const options = reactive<VideoJsPlayerOptions>({
33 48 width: '100%' as unknown as number,
34 49 height: 384 as unknown as number,
35 50 autoplay: true,
36 51 });
37 52
38   - const setSources = (url: string) => {
  53 + const setSources = (url: string, fingerprintResult: GetResult) => {
  54 + const flag = isRtspProtocol(url);
39 55 options.sources = [
40 56 {
41   - src: url,
  57 + src: flag ? getFlvPlayUrl(url, fingerprintResult.visitorId) : url,
42 58 type: getVideoTypeByUrl(url),
43 59 },
44 60 ];
45 61 };
46 62
  63 + const { getResult } = useFingerprint();
47 64 const [register] = useModalInner(
48 65 async (data: { record: CameraModel | StreamingManageRecord }) => {
49 66 const { record } = data;
  67 + const result = await getResult();
  68 + fingerprintResult.value = result;
50 69 if (record.accessMode === AccessMode.ManuallyEnter) {
51 70 if ((record as CameraModel).videoUrl) {
52   - setSources((record as CameraModel).videoUrl);
  71 + if (isRtspProtocol((record as CameraModel).videoUrl)) {
  72 + playUrl.value = (record as CameraModel).videoUrl;
  73 + closeFlvPlay(unref(playUrl), result.visitorId);
  74 + withToken.value = true;
  75 + }
  76 + setSources((record as CameraModel).videoUrl, result);
53 77 }
54 78 } else {
55 79 try {
56 80 const { data: { url } = { url: '' } } = await getStreamingPlayUrl(record.id!);
57   - setSources(url);
  81 + setSources(url, result);
58 82 } catch (error) {}
59 83 }
60 84 showVideo.value = true;
61 85 }
62 86 );
63 87
  88 + const handleCloseFlvPlayUrl = () => {
  89 + if (isRtspProtocol(unref(playUrl))) {
  90 + closeFlvPlay(unref(playUrl)!, unref(fingerprintResult)!.visitorId!);
  91 + }
  92 + };
  93 +
64 94 const handleCancel = () => {
65 95 showVideo.value = false;
  96 + withToken.value = false;
66 97 };
67 98 </script>
68 99
... ...
... ... @@ -3,7 +3,7 @@
3 3 import OrganizationIdTree from '../../common/organizationIdTree/src/OrganizationIdTree.vue';
4 4 import { onMounted, reactive, Ref, ref, unref, watch } from 'vue';
5 5 import { Spin, Button, Pagination, Space, List } from 'ant-design-vue';
6   - import { cameraPage } from '/@/api/camera/cameraManager';
  6 + import { cameraPage, closeFlvPlay, getFlvPlayUrl } from '/@/api/camera/cameraManager';
7 7 import { CameraRecord } from '/@/api/camera/model/cameraModel';
8 8 import { useFullscreen } from '@vueuse/core';
9 9 import CameraDrawer from './CameraDrawer.vue';
... ... @@ -16,11 +16,16 @@
16 16 import { VideoJsPlayerOptions } from 'video.js';
17 17 import { getBoundingClientRect } from '/@/utils/domUtils';
18 18 import { Authority } from '/@/components/Authority';
  19 + import { isRtspProtocol } from '/@/components/Video/src/utils';
  20 + import { useFingerprint } from '/@/utils/useFingerprint';
  21 + import { GetResult } from '@fingerprintjs/fingerprintjs';
19 22
20 23 type CameraRecordItem = CameraRecord & {
21 24 canPlay?: boolean;
22 25 isTransform?: boolean;
  26 + withToken?: boolean;
23 27 videoPlayerOptions?: VideoJsPlayerOptions;
  28 + playSourceUrl?: string;
24 29 };
25 30
26 31 const basicVideoPlayOptions: VideoJsPlayerOptions = {
... ... @@ -43,6 +48,8 @@
43 48 total: 0,
44 49 });
45 50
  51 + const fingerprintResult = ref<Nullable<GetResult>>(null);
  52 +
46 53 // 树形选择器
47 54 const handleSelect = (orgId: string) => {
48 55 organizationId.value = orgId;
... ... @@ -60,12 +67,14 @@
60 67 });
61 68 pagination.total = total;
62 69
  70 + const result = await getResult();
  71 + fingerprintResult.value = result;
63 72 for (const item of items) {
64 73 (item as CameraRecordItem).isTransform = false;
65 74 (item as CameraRecordItem).videoPlayerOptions = {
66 75 ...basicVideoPlayOptions,
67 76 };
68   - beforeVideoPlay(item);
  77 + beforeVideoPlay(item, result);
69 78 }
70 79 if (items.length < pagination.pageSize) {
71 80 const fillArr: any = Array.from({ length: pagination.pageSize - items.length }).map(() => ({
... ... @@ -81,16 +90,24 @@
81 90 }
82 91 };
83 92
84   - const beforeVideoPlay = async (record: CameraRecordItem) => {
  93 + const { getResult } = useFingerprint();
  94 + const beforeVideoPlay = async (record: CameraRecordItem, fingerprintResult: GetResult) => {
85 95 if (record.accessMode === AccessMode.ManuallyEnter) {
86 96 if (record.videoUrl) {
  97 + const isFlvPlay = isRtspProtocol(record.videoUrl);
87 98 const type = getVideoTypeByUrl(record.videoUrl);
  99 + record.playSourceUrl = record.videoUrl;
  100 + if (isFlvPlay) {
  101 + // handleFlvPlayerUnload(record, fingerprintResult!.visitorId);
  102 + record.playSourceUrl = getFlvPlayUrl(record.videoUrl, fingerprintResult.visitorId);
  103 + record.withToken = true;
  104 + }
88 105
89 106 (record as CameraRecordItem).videoPlayerOptions = {
90 107 ...basicVideoPlayOptions,
91 108 sources: [
92 109 {
93   - src: record.videoUrl,
  110 + src: record.playSourceUrl,
94 111 type,
95 112 },
96 113 ],
... ... @@ -106,6 +123,7 @@
106 123 const oldRecord = unref(cameraList).at(index)!;
107 124 unref(cameraList)[index] = {
108 125 ...oldRecord,
  126 +
109 127 videoPlayerOptions: {
110 128 ...basicVideoPlayOptions,
111 129 sources: [
... ... @@ -162,6 +180,12 @@
162 180 });
163 181 };
164 182
  183 + const handleCloseFlvPlayUrl = async (record: CameraRecordItem) => {
  184 + if (isRtspProtocol(record.videoUrl)) {
  185 + closeFlvPlay(record.videoUrl, unref(fingerprintResult)!.visitorId!);
  186 + }
  187 + };
  188 +
165 189 onMounted(() => {
166 190 getCameraList();
167 191 });
... ... @@ -265,7 +289,12 @@
265 289 v-show="!item.isTransform"
266 290 :spinning="!item.isTransform"
267 291 />
268   - <BasicVideoPlay v-if="item.isTransform" :options="item.videoPlayerOptions" />
  292 + <BasicVideoPlay
  293 + v-if="item.isTransform"
  294 + :options="item.videoPlayerOptions"
  295 + :with-token="item.withToken"
  296 + @on-unmounted="handleCloseFlvPlayUrl(item)"
  297 + />
269 298 <div
270 299 class="video-container-mask absolute top-0 left-0 z-50 text-lg w-full text-light-50 flex justify-center items-center"
271 300 style="height: 100%; background-color: rgba(0, 0, 0, 0.5)"
... ...