Commit 0f31669da86600ef142d2bbe0afea668671de793

Authored by ww
1 parent 5d526eda

feat: 视频配置实现支持flv视频播放

@@ -7,15 +7,18 @@ @@ -7,15 +7,18 @@
7 "cSpell.words": [ 7 "cSpell.words": [
8 "COAP", 8 "COAP",
9 "edrx", 9 "edrx",
10 - "EFENTO", 10 + "EFENTO",
  11 + "flvjs",
11 "inited", 12 "inited",
  13 + "liveui",
12 "MQTT", 14 "MQTT",
13 "notif", 15 "notif",
14 - "PROTOBUF", 16 + "PROTOBUF",
15 "SCADA", 17 "SCADA",
16 "SNMP", 18 "SNMP",
17 "unref", 19 "unref",
18 "vben", 20 "vben",
  21 + "videojs",
19 "VITE", 22 "VITE",
20 "windicss" 23 "windicss"
21 ] 24 ]
@@ -49,6 +49,7 @@ @@ -49,6 +49,7 @@
49 "cropperjs": "^1.5.12", 49 "cropperjs": "^1.5.12",
50 "crypto-js": "^4.1.1", 50 "crypto-js": "^4.1.1",
51 "echarts": "^5.1.2", 51 "echarts": "^5.1.2",
  52 + "flv.js": "^1.6.2",
52 "hls.js": "^1.0.10", 53 "hls.js": "^1.0.10",
53 "intro.js": "^4.1.0", 54 "intro.js": "^4.1.0",
54 "jsoneditor": "^9.7.2", 55 "jsoneditor": "^9.7.2",
@@ -65,6 +66,7 @@ @@ -65,6 +66,7 @@
65 "tinymce": "^5.8.2", 66 "tinymce": "^5.8.2",
66 "vditor": "^3.8.6", 67 "vditor": "^3.8.6",
67 "video.js": "^7.20.3", 68 "video.js": "^7.20.3",
  69 + "videojs-flvjs-es6": "^1.0.1",
68 "vue": "3.2.31", 70 "vue": "3.2.31",
69 "vue-i18n": "9.1.7", 71 "vue-i18n": "9.1.7",
70 "vue-json-pretty": "^2.0.4", 72 "vue-json-pretty": "^2.0.4",
@@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
4 import 'video.js/dist/video-js.css'; 4 import 'video.js/dist/video-js.css';
5 import { computed, CSSProperties, onMounted, onUnmounted, ref, unref } from 'vue'; 5 import { computed, CSSProperties, onMounted, onUnmounted, ref, unref } from 'vue';
6 import { useDesign } from '/@/hooks/web/useDesign'; 6 import { useDesign } from '/@/hooks/web/useDesign';
7 - 7 + import 'videojs-flvjs-es6';
8 const { prefixCls } = useDesign('basic-video-play'); 8 const { prefixCls } = useDesign('basic-video-play');
9 9
10 const props = defineProps<{ 10 const props = defineProps<{
@@ -21,11 +21,20 @@ @@ -21,11 +21,20 @@
21 21
22 const getOptions = computed(() => { 22 const getOptions = computed(() => {
23 const { options } = props; 23 const { options } = props;
24 - const defaultOptions: VideoJsPlayerOptions = { 24 + const defaultOptions: VideoJsPlayerOptions & Recordable = {
25 language: 'zh', 25 language: 'zh',
26 muted: true, 26 muted: true,
27 liveui: true, 27 liveui: true,
28 controls: true, 28 controls: true,
  29 + techOrder: ['html5', 'flvjs'],
  30 + flvjs: {
  31 + mediaDataSource: {
  32 + isLive: true,
  33 + cors: true,
  34 + hasAudio: false,
  35 + withCredentials: false,
  36 + },
  37 + },
29 }; 38 };
30 return { ...defaultOptions, ...options }; 39 return { ...defaultOptions, ...options };
31 }); 40 });
@@ -58,7 +67,9 @@ @@ -58,7 +67,9 @@
58 <video 67 <video
59 ref="videoPlayEl" 68 ref="videoPlayEl"
60 class="video-js vjs-big-play-centered vjs-show-big-play-button-on-pause !w-full !h-full" 69 class="video-js vjs-big-play-centered vjs-show-big-play-button-on-pause !w-full !h-full"
61 - ></video> 70 + muted
  71 + >
  72 + </video>
62 </div> 73 </div>
63 </template> 74 </template>
64 75
1 -export enum VideoPlayerType {  
2 - m3u8 = 'application/x-mpegURL',  
3 - mp4 = 'video/mp4',  
4 - webm = 'video/webm',  
5 -}  
6 -  
7 -export const getVideoTypeByUrl = (url: string) => {  
8 - const splitExtReg = /(?:.*)(?<=\.)/;  
9 - const type = url.replace(splitExtReg, '');  
10 - /**  
11 - * https://vcsplay.scjtonline.cn:8200/live/HD_1569b634-4789-11eb-ab67-3cd2e55e0b20.m3u8?auth_key=1681179278-0-0-5c54a376f2ca32d05c4a152ee96336e9  
12 - * 如果是这种格式的m3u8,则截取的是这一部分.m3u8?auth_key=1681179278-0-0-5c54a376f2ca32d05c4a152ee96336e9  
13 - */  
14 - if (type.startsWith('m3u8')) return VideoPlayerType.m3u8;  
15 - if (type.startsWith('mp4')) return VideoPlayerType.mp4;  
16 - if (type.startsWith('webm')) return VideoPlayerType.webm;  
17 - return;  
18 -}; 1 +export enum VideoPlayerType {
  2 + m3u8 = 'application/x-mpegURL',
  3 + mp4 = 'video/mp4',
  4 + webm = 'video/webm',
  5 + flv = 'video/x-flv',
  6 +}
  7 +
  8 +export const getVideoTypeByUrl = (url: string) => {
  9 + try {
  10 + const { pathname } = new URL(url);
  11 +
  12 + const reg = /[^.]\w*$/;
  13 + const mathValue = pathname.match(reg) || [];
  14 + const ext = (mathValue[0] as keyof typeof VideoPlayerType) || 'webm';
  15 + const type = VideoPlayerType[ext];
  16 + return type ? type : VideoPlayerType.webm;
  17 + } catch (error) {
  18 + console.error(error);
  19 + return VideoPlayerType.webm;
  20 + }
  21 +};
@@ -11,9 +11,9 @@ @@ -11,9 +11,9 @@
11 @cancel="handleCancel" 11 @cancel="handleCancel"
12 > 12 >
13 <div 13 <div
14 - class="flex items-center justify-center bg-dark-900 w-full h-full min-h-52 video-container" 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" /> 16 + <BasicVideoPlay v-if="showVideo" :options="(options as any)" />
17 </div> 17 </div>
18 </BasicModal> 18 </BasicModal>
19 </div> 19 </div>
@@ -31,7 +31,7 @@ @@ -31,7 +31,7 @@
31 const showVideo = ref(false); 31 const showVideo = ref(false);
32 const options = reactive<VideoJsPlayerOptions>({ 32 const options = reactive<VideoJsPlayerOptions>({
33 width: '100%' as unknown as number, 33 width: '100%' as unknown as number,
34 - height: '100%' as unknown as number, 34 + height: 384 as unknown as number,
35 autoplay: true, 35 autoplay: true,
36 }); 36 });
37 37
1 <script setup lang="ts"> 1 <script setup lang="ts">
2 import { PageWrapper } from '/@/components/Page'; 2 import { PageWrapper } from '/@/components/Page';
3 import OrganizationIdTree from '../../common/organizationIdTree/src/OrganizationIdTree.vue'; 3 import OrganizationIdTree from '../../common/organizationIdTree/src/OrganizationIdTree.vue';
4 - import { onMounted, reactive, ref, unref, watch } from 'vue'; 4 + import { onMounted, reactive, Ref, ref, unref, watch } from 'vue';
5 import { Spin, Button, Pagination, Space, List } from 'ant-design-vue'; 5 import { Spin, Button, Pagination, Space, List } from 'ant-design-vue';
6 import { cameraPage } from '/@/api/camera/cameraManager'; 6 import { cameraPage } from '/@/api/camera/cameraManager';
7 import { CameraRecord } from '/@/api/camera/model/cameraModel'; 7 import { CameraRecord } from '/@/api/camera/model/cameraModel';
@@ -84,12 +84,14 @@ @@ -84,12 +84,14 @@
84 const beforeVideoPlay = async (record: CameraRecordItem) => { 84 const beforeVideoPlay = async (record: CameraRecordItem) => {
85 if (record.accessMode === AccessMode.ManuallyEnter) { 85 if (record.accessMode === AccessMode.ManuallyEnter) {
86 if (record.videoUrl) { 86 if (record.videoUrl) {
  87 + const type = getVideoTypeByUrl(record.videoUrl);
  88 +
87 (record as CameraRecordItem).videoPlayerOptions = { 89 (record as CameraRecordItem).videoPlayerOptions = {
88 ...basicVideoPlayOptions, 90 ...basicVideoPlayOptions,
89 sources: [ 91 sources: [
90 { 92 {
91 src: record.videoUrl, 93 src: record.videoUrl,
92 - type: getVideoTypeByUrl(record.videoUrl), 94 + type,
93 }, 95 },
94 ], 96 ],
95 }; 97 };
@@ -112,7 +114,7 @@ @@ -112,7 +114,7 @@
112 type: getVideoTypeByUrl(url), 114 type: getVideoTypeByUrl(url),
113 }, 115 },
114 ], 116 ],
115 - }, 117 + } as any,
116 isTransform: true, 118 isTransform: true,
117 }; 119 };
118 } 120 }
@@ -134,7 +136,7 @@ @@ -134,7 +136,7 @@
134 getCameraList(); 136 getCameraList();
135 }; 137 };
136 138
137 - const { enter, isFullscreen } = useFullscreen(videoContainer); 139 + const { enter, isFullscreen } = useFullscreen(videoContainer as Ref<HTMLDivElement>);
138 140
139 const handleFullScreen = () => { 141 const handleFullScreen = () => {
140 enter(); 142 enter();
@@ -244,7 +246,7 @@ @@ -244,7 +246,7 @@
244 :loading="loading" 246 :loading="loading"
245 :data-source="cameraList" 247 :data-source="cameraList"
246 class="bg-light-50 w-full h-full dark:bg-dark-900 split-mode-list" 248 class="bg-light-50 w-full h-full dark:bg-dark-900 split-mode-list"
247 - :grid="gridLayout" 249 + :grid="(gridLayout as any)"
248 :style="{ '--height': `${100 / pagination.colNumber}%` }" 250 :style="{ '--height': `${100 / pagination.colNumber}%` }"
249 > 251 >
250 <template #renderItem="{ item }"> 252 <template #renderItem="{ item }">
@@ -316,6 +318,10 @@ @@ -316,6 +318,10 @@
316 .split-mode-list:deep(.ant-row) { 318 .split-mode-list:deep(.ant-row) {
317 width: 100%; 319 width: 100%;
318 height: 100%; 320 height: 100%;
  321 +
  322 + > div {
  323 + height: var(--height);
  324 + }
319 } 325 }
320 326
321 .split-mode-list:deep(.ant-list-item) { 327 .split-mode-list:deep(.ant-list-item) {