Commit 1c906d2d8c2c687b23c7243b562bef9cd3a4166f

Authored by xp.Huang
2 parents 5f3adcb8 6fce6b5f

Merge branch 'perf/video-player-change' into 'main_dev'

perf: 切换视频播放器为xgplaery,支持H265视频编码

See merge request yunteng/thingskit-front!1272
@@ -72,14 +72,16 @@ @@ -72,14 +72,16 @@
72 "sortablejs": "^1.14.0", 72 "sortablejs": "^1.14.0",
73 "tinymce": "^5.8.2", 73 "tinymce": "^5.8.2",
74 "vditor": "^3.8.6", 74 "vditor": "^3.8.6",
75 - "video.js": "^7.20.3",  
76 - "videojs-flvjs-es6": "^1.0.1",  
77 "vue": "3.3.4", 75 "vue": "3.3.4",
78 "vue-i18n": "9.1.7", 76 "vue-i18n": "9.1.7",
79 "vue-json-pretty": "^2.0.4", 77 "vue-json-pretty": "^2.0.4",
80 "vue-router": "^4.0.11", 78 "vue-router": "^4.0.11",
81 "vue-types": "^4.0.3", 79 "vue-types": "^4.0.3",
82 "vue3-grid-layout": "^1.0.0", 80 "vue3-grid-layout": "^1.0.0",
  81 + "xgplayer": "^3.0.14",
  82 + "xgplayer-flv": "^3.0.14",
  83 + "xgplayer-hls": "^3.0.14",
  84 + "xgplayer-mp4": "^3.0.14",
83 "xlsx": "^0.17.0" 85 "xlsx": "^0.17.0"
84 }, 86 },
85 "devDependencies": { 87 "devDependencies": {
@@ -101,7 +103,6 @@ @@ -101,7 +103,6 @@
101 "@types/qrcode": "^1.4.1", 103 "@types/qrcode": "^1.4.1",
102 "@types/qs": "^6.9.7", 104 "@types/qs": "^6.9.7",
103 "@types/sortablejs": "^1.10.7", 105 "@types/sortablejs": "^1.10.7",
104 - "@types/video.js": "^7.3.49",  
105 "@typescript-eslint/eslint-plugin": "^4.29.1", 106 "@typescript-eslint/eslint-plugin": "^4.29.1",
106 "@typescript-eslint/parser": "^4.29.1", 107 "@typescript-eslint/parser": "^4.29.1",
107 "@vitejs/plugin-legacy": "^1.5.1", 108 "@vitejs/plugin-legacy": "^1.5.1",
@@ -109,7 +109,7 @@ export const deleteStreamingMediaRecord = (params: StreamingMediaDeleteParam) => @@ -109,7 +109,7 @@ export const deleteStreamingMediaRecord = (params: StreamingMediaDeleteParam) =>
109 * @returns 109 * @returns
110 */ 110 */
111 export const getStreamingPlayUrl = (entityId: string) => { 111 export const getStreamingPlayUrl = (entityId: string) => {
112 - return defHttp.get( 112 + return defHttp.get<{ data: { url: string } }>(
113 { 113 {
114 url: `${CameraManagerApi.STREAMING_PLAY_GET_URL}/${entityId}`, 114 url: `${CameraManagerApi.STREAMING_PLAY_GET_URL}/${entityId}`,
115 }, 115 },
@@ -35,6 +35,7 @@ export interface CameraModel { @@ -35,6 +35,7 @@ export interface CameraModel {
35 deviceId: string; 35 deviceId: string;
36 deviceName: string; 36 deviceName: string;
37 }; 37 };
  38 + playProtocol?: number;
38 } 39 }
39 40
40 export interface StreamingManageRecord { 41 export interface StreamingManageRecord {
@@ -106,4 +107,14 @@ export interface CameraRecord { @@ -106,4 +107,14 @@ export interface CameraRecord {
106 channelNo: string; 107 channelNo: string;
107 deviceId: string; 108 deviceId: string;
108 }; 109 };
  110 + videoPlatformDTO?: VideoPlatformDTO;
  111 +}
  112 +
  113 +interface VideoPlatformDTO {
  114 + enabled: boolean;
  115 + type: number;
  116 + host: string;
  117 + appKey: string;
  118 + appSecret: string;
  119 + ssl: number;
109 } 120 }
1 -import { withInstall } from '/@/utils/index';  
2 -import VideoPlay from './src/VideoPlay.vue'; 1 +export { default as XGPlayer } from './src/XGPlayer.vue';
3 2
4 export { getVideoTypeByUrl } from './src/utils'; 3 export { getVideoTypeByUrl } from './src/utils';
5 -  
6 -export const BasicVideoPlay = withInstall(VideoPlay);  
1 -<script lang="ts" setup>  
2 - import { isNumber } from 'lodash';  
3 - import videoJs, { VideoJsPlayer, VideoJsPlayerOptions } from 'video.js';  
4 - import 'video.js/dist/video-js.css';  
5 - import { computed, CSSProperties, onMounted, onUnmounted, ref, toRaw, unref } from 'vue';  
6 - import { useDesign } from '/@/hooks/web/useDesign';  
7 - import { getJwtToken, getShareJwtToken } from '/@/utils/auth';  
8 - import { isShareMode } from '/@/views/sys/share/hook';  
9 - import 'videojs-flvjs-es6';  
10 - const { prefixCls } = useDesign('basic-video-play');  
11 -  
12 - const props = withDefaults(  
13 - defineProps<{  
14 - options?: VideoJsPlayerOptions;  
15 - withToken?: boolean;  
16 - immediateInitOnMounted?: boolean;  
17 - }>(),  
18 - {  
19 - immediateInitOnMounted: true,  
20 - }  
21 - );  
22 -  
23 - const emit = defineEmits<{  
24 - (event: 'ready', instance?: Nullable<VideoJsPlayer>): void;  
25 - (event: 'onUnmounted'): void;  
26 - }>();  
27 -  
28 - const videoPlayEl = ref<HTMLVideoElement>();  
29 -  
30 - const videoPlayInstance = ref<Nullable<VideoJsPlayer>>();  
31 -  
32 - const getOptions = computed(() => {  
33 - const { options, withToken } = props;  
34 -  
35 - const defaultOptions: VideoJsPlayerOptions & Recordable = {  
36 - language: 'zh',  
37 - muted: true,  
38 - liveui: true,  
39 - controls: true,  
40 - techOrder: ['html5', 'flvjs'],  
41 - flvjs: {  
42 - mediaDataSource: {  
43 - isLive: true,  
44 - cors: true,  
45 - hasAudio: false,  
46 - withCredentials: false,  
47 - },  
48 - config: {  
49 - headers: {  
50 - ...(withToken  
51 - ? {  
52 - 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`,  
53 - }  
54 - : {}),  
55 - },  
56 - autoCleanupSourceBuffer: true,  
57 - },  
58 - },  
59 - };  
60 - return videoJs.mergeOptions(defaultOptions, options);  
61 - });  
62 -  
63 - const getWidthHeight = computed(() => {  
64 - let { width = 300, height = 150 } = unref(getOptions);  
65 - width = isNumber(width) ? (`${width}px` as unknown as number) : width;  
66 - height = isNumber(height) ? (`${height}px` as unknown as number) : height;  
67 - return { width, height } as CSSProperties;  
68 - });  
69 -  
70 - const init = () => {  
71 - if (unref(videoPlayInstance)) unref(videoPlayInstance)?.dispose();  
72 - videoPlayInstance.value = videoJs(unref(videoPlayEl)!, unref(getOptions), () => {  
73 - emit('ready', unref(videoPlayInstance));  
74 - });  
75 - //fix 修复videojs解决直播延时的问题  
76 - videoPlayInstance.value?.on('timeupdate', function () {  
77 - // 计算表最新推流的时间和现在播放器播放推流的时间  
78 - let differTime =  
79 - (videoPlayInstance.value as any as VideoJsPlayer)?.buffered()?.end(0) -  
80 - (videoPlayInstance.value as any as VideoJsPlayer)?.currentTime();  
81 - // 差值小于1.5s时根据1倍速进行播放  
82 - if (differTime < 1.5) {  
83 - videoPlayInstance.value?.playbackRate(1);  
84 - }  
85 - // 差值大于1.5s小于10s根据1.2倍速进行播放  
86 - if (differTime < 10 && differTime > 1.5) {  
87 - videoPlayInstance.value?.playbackRate(1.2);  
88 - }  
89 - // 差值大于10s时进行重新加载直播流  
90 - if (differTime > 10) {  
91 - init();  
92 - }  
93 - });  
94 - //  
95 - };  
96 -  
97 - const customInit = (getOptionsFn: (optios: VideoJsPlayerOptions) => VideoJsPlayerOptions) => {  
98 - return (videoPlayInstance.value = videoJs(  
99 - unref(videoPlayEl)!,  
100 - getOptionsFn(toRaw(unref(getOptions))),  
101 - () => {  
102 - emit('ready', unref(videoPlayInstance));  
103 - }  
104 - ));  
105 - };  
106 -  
107 - onMounted(() => {  
108 - props.immediateInitOnMounted && init();  
109 - });  
110 -  
111 - onUnmounted(() => {  
112 - unref(videoPlayInstance)?.dispose();  
113 - videoPlayInstance.value = null;  
114 - emit('onUnmounted');  
115 - });  
116 -  
117 - defineExpose({  
118 - customInit,  
119 - reloadPlayer: init,  
120 - getInstance: () => unref(videoPlayInstance),  
121 - });  
122 -</script>  
123 -  
124 -<template>  
125 - <div :class="prefixCls" class="!w-full h-full" :style="getWidthHeight" style="min-height: 100%">  
126 - <video  
127 - ref="videoPlayEl"  
128 - class="video-js vjs-big-play-centered vjs-show-big-play-button-on-pause !w-full !h-full"  
129 - muted  
130 - >  
131 - </video>  
132 - </div>  
133 -</template>  
134 -  
135 -<style lang="less">  
136 - @prefix-cls: ~'@{namespace}-basic-video-play';  
137 -  
138 - .@{prefix-cls} {  
139 - .vjs-error-display {  
140 - .vjs-modal-dialog-content::after {  
141 - content: '无法加载视频,原因可能是服务器或网络故障,也可能是格式不支持.';  
142 - }  
143 - }  
144 - }  
145 -</style>  
  1 +<script setup lang="ts">
  2 + import Player, { Events, IError } from 'xgplayer';
  3 + import { FlvPlugin } from 'xgplayer-flv';
  4 + import Mp4Plugin from 'xgplayer-mp4';
  5 + import { HlsPlugin } from 'xgplayer-hls';
  6 + import { onMounted, shallowRef, computed, unref, toRaw, onUnmounted, ref, watch } from 'vue';
  7 + import PresetPlayer from 'xgplayer';
  8 + import { IPlayerOptions } from 'xgplayer/es/player';
  9 + import 'xgplayer/dist/index.min.css';
  10 + import { isShareMode } from '/@/views/sys/share/hook';
  11 + import { getJwtToken, getShareJwtToken } from '/@/utils/auth';
  12 + import { XGPlayerProps } from '/@/components/Video/src/types';
  13 + import { StreamType } from './types';
  14 +
  15 + const props = withDefaults(defineProps<XGPlayerProps>(), {
  16 + streamType: 'auto',
  17 + autoPlay: true,
  18 + config: () => ({}),
  19 + });
  20 +
  21 + const emits = defineEmits<{
  22 + (eventName: 'ready', player: PresetPlayer): void;
  23 + (eventName: 'onUnmounted', player: PresetPlayer): void;
  24 + }>();
  25 +
  26 + function getStreamTypeByUrl(url = ''): StreamType | undefined {
  27 + if (url.endsWith('.m3u8')) return 'hls';
  28 + else if (url.endsWith('.mp4')) return 'mp4';
  29 + else if (url.endsWith('.flv')) {
  30 + return 'flv';
  31 + } else return;
  32 + }
  33 +
  34 + const getPluginByStreamType = (): IPlayerOptions => {
  35 + let { url, withToken } = props;
  36 + let { streamType } = props;
  37 + streamType = streamType === 'auto' ? getStreamTypeByUrl(url)! : streamType;
  38 +
  39 + const liveConfig = {
  40 + targetLatency: 10,
  41 + maxLatency: 20,
  42 + disconnectTime: 0,
  43 + fetchOptions: withToken
  44 + ? {
  45 + headers: {
  46 + 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`,
  47 + },
  48 + }
  49 + : {},
  50 + };
  51 + const config: IPlayerOptions = {
  52 + flv: liveConfig,
  53 + hls: liveConfig,
  54 + };
  55 + switch (streamType) {
  56 + case 'hls':
  57 + config.plugins = [HlsPlugin];
  58 + break;
  59 + case 'mp4':
  60 + config.plugins = [Mp4Plugin];
  61 + break;
  62 + case 'flv':
  63 + config.plugins = [FlvPlugin];
  64 + break;
  65 + }
  66 + return config;
  67 + };
  68 +
  69 + const videoElRef = shallowRef<Nullable<HTMLDivElement>>();
  70 +
  71 + const playerRef = shallowRef<Nullable<PresetPlayer>>();
  72 +
  73 + const propsRef = ref<XGPlayerProps>({});
  74 +
  75 + const getPlayerConfig = computed<IPlayerOptions>(() => {
  76 + const { url, autoPlay, config } = props;
  77 +
  78 + const basicConfig: IPlayerOptions = {
  79 + ...config,
  80 + ...propsRef,
  81 + url,
  82 + lang: 'zh',
  83 + isLive: true,
  84 + autoplay: autoPlay,
  85 + autoplayMuted: autoPlay,
  86 + ...getPluginByStreamType(),
  87 + };
  88 + return basicConfig;
  89 + });
  90 +
  91 + function onDecodeError() {
  92 + console.warn('player happend decode error');
  93 + playerRef.value?.destroy?.();
  94 + initializePlayer();
  95 + }
  96 +
  97 + function initializePlayer() {
  98 + if (unref(playerRef)) {
  99 + playerRef.value?.destroy?.();
  100 + playerRef.value = null;
  101 + }
  102 +
  103 + const config = toRaw(unref(getPlayerConfig));
  104 +
  105 + if (!unref(videoElRef)) return;
  106 +
  107 + const player = (playerRef.value = new Player(Object.assign(config, { el: unref(videoElRef) })));
  108 +
  109 + player.on(Events.READY, () => {
  110 + emits('ready', player);
  111 + });
  112 +
  113 + player.on(Events.ERROR, (error: IError) => {
  114 + // https://h5player.bytedance.com/api/error.html#type
  115 + if (error.errorCode === 5103) {
  116 + onDecodeError();
  117 + }
  118 + });
  119 + }
  120 +
  121 + onMounted(() => {
  122 + initializePlayer();
  123 + });
  124 +
  125 + onUnmounted(() => {
  126 + emits('onUnmounted', unref(playerRef)!);
  127 + playerRef.value?.destroy?.();
  128 + });
  129 +
  130 + watch(
  131 + () => props.url,
  132 + () => {
  133 + initializePlayer();
  134 + }
  135 + );
  136 +
  137 + defineExpose({
  138 + getPlayerInstance: () => unref(playerRef),
  139 + });
  140 +</script>
  141 +
  142 +<template>
  143 + <div ref="videoElRef"></div>
  144 +</template>
  1 +import { IPlayerOptions } from 'xgplayer/es/player';
  2 +
  3 +export type StreamType = 'flv' | 'mp4' | 'hls' | 'auto';
  4 +export interface XGPlayerProps {
  5 + streamType?: StreamType;
  6 + autoPlay?: boolean;
  7 + url?: string;
  8 + withToken?: boolean;
  9 + config?: Omit<IPlayerOptions, 'url'>;
  10 +}
1 -<template>  
2 - <div>  
3 - <BasicModal  
4 - v-bind="$attrs"  
5 - width="55rem"  
6 - destroyOnClose  
7 - :height="heightNum"  
8 - @register="register"  
9 - title="视频预览"  
10 - :showOkBtn="false"  
11 - @cancel="handleCancel"  
12 - >  
13 - <div  
14 - class="flex items-center justify-center bg-dark-900 w-full h-full min-h-96 video-container"  
15 - >  
16 - <BasicVideoPlay  
17 - v-if="showVideo"  
18 - :options="(options as any)"  
19 - :withToken="withToken"  
20 - @on-unmounted="handleCloseFlvPlayUrl"  
21 - />  
22 - </div>  
23 - </BasicModal>  
24 - </div>  
25 -</template>  
26 -<script setup lang="ts">  
27 - import { ref, reactive, unref } from 'vue';  
28 - import { BasicModal, useModalInner } from '/@/components/Modal';  
29 - import type { StreamingManageRecord, CameraModel } from '/@/api/camera/model/cameraModel';  
30 - import { BasicVideoPlay, getVideoTypeByUrl } from '/@/components/Video';  
31 - import { AccessMode } from './config.data';  
32 - import { closeFlvPlay, getFlvPlayUrl, getStreamingPlayUrl } from '/@/api/camera/cameraManager';  
33 - import { isRtspProtocol } from '/@/components/Video/src/utils';  
34 - import { VideoJsPlayerOptions } from 'video.js';  
35 - import { useFingerprint } from '/@/utils/useFingerprint';  
36 - import { GetResult } from '@fingerprintjs/fingerprintjs';  
37 -  
38 - const heightNum = ref(800);  
39 - const showVideo = ref(false);  
40 -  
41 - const playUrl = ref('');  
42 -  
43 - const withToken = ref(false);  
44 -  
45 - const videoId = ref<string>();  
46 -  
47 - const fingerprintResult = ref<Nullable<GetResult>>(null);  
48 -  
49 - const options = reactive<VideoJsPlayerOptions>({  
50 - width: '100%' as unknown as number,  
51 - height: 384 as unknown as number,  
52 - autoplay: true,  
53 - });  
54 -  
55 - const setSources = (url: string, fingerprintResult: GetResult) => {  
56 - const flag = isRtspProtocol(url);  
57 - options.sources = [  
58 - {  
59 - src: flag ? getFlvPlayUrl(url, fingerprintResult.visitorId) : url,  
60 - type: getVideoTypeByUrl(url),  
61 - },  
62 - ];  
63 - };  
64 -  
65 - const { getResult } = useFingerprint();  
66 - const [register] = useModalInner(  
67 - async (data: { record: CameraModel | StreamingManageRecord }) => {  
68 - const { record } = data;  
69 - videoId.value = record.id || '';  
70 - const result = await getResult();  
71 - fingerprintResult.value = result;  
72 - if (record.accessMode === AccessMode.ManuallyEnter) {  
73 - if ((record as CameraModel).videoUrl) {  
74 - if (isRtspProtocol((record as CameraModel).videoUrl)) {  
75 - playUrl.value = (record as CameraModel).videoUrl;  
76 - closeFlvPlay(unref(playUrl), result.visitorId);  
77 - withToken.value = true;  
78 - }  
79 - setSources((record as CameraModel).videoUrl, result);  
80 - }  
81 - } else {  
82 - try {  
83 - const { data: { url } = { url: '' } } = await getStreamingPlayUrl(record.id!);  
84 - setSources(url, result);  
85 - } catch (error) {}  
86 - }  
87 - showVideo.value = true;  
88 - }  
89 - );  
90 -  
91 - const handleCloseFlvPlayUrl = () => {  
92 - if (isRtspProtocol(unref(playUrl))) {  
93 - closeFlvPlay(unref(playUrl)!, unref(fingerprintResult)!.visitorId!);  
94 - }  
95 - };  
96 -  
97 - const handleCancel = () => {  
98 - showVideo.value = false;  
99 - withToken.value = false;  
100 - };  
101 -</script>  
102 -  
103 -<style lang="less" scoped>  
104 - .video-container:deep(.vben-basic-video-play) {  
105 - min-height: 13rem;  
106 - }  
107 -  
108 - .video-container:deep(.video-js) {  
109 - min-height: 13rem;  
110 - }  
111 -</style>  
@@ -80,8 +80,7 @@ @@ -80,8 +80,7 @@
80 </BasicTable> 80 </BasicTable>
81 </PageWrapper> 81 </PageWrapper>
82 <CameraDrawer @register="registerDrawer" @success="handleSuccess" /> 82 <CameraDrawer @register="registerDrawer" @success="handleSuccess" />
83 - <VideoPreviewModal @register="registerModal" />  
84 - <VideoModal @register="registerModal1" /> 83 + <VideoModal @register="registerModal" />
85 <GBTDrawer @register="registerGBTDrawer" @success="handleGBTSuccess" /> 84 <GBTDrawer @register="registerGBTDrawer" @success="handleGBTSuccess" />
86 </div> 85 </div>
87 </template> 86 </template>
@@ -103,14 +102,16 @@ @@ -103,14 +102,16 @@
103 CameraPermission, 102 CameraPermission,
104 VideoPlatformEnum, 103 VideoPlatformEnum,
105 accessModeConfig, 104 accessModeConfig,
  105 + getPlayUrl,
106 } from './config.data'; 106 } from './config.data';
107 - import VideoPreviewModal from './DialogPreviewVideo.vue';  
108 import { useModal } from '/@/components/Modal'; 107 import { useModal } from '/@/components/Modal';
109 import { Authority } from '/@/components/Authority'; 108 import { Authority } from '/@/components/Authority';
110 import { useBatchDelete } from '/@/hooks/web/useBatchDelete'; 109 import { useBatchDelete } from '/@/hooks/web/useBatchDelete';
111 import { Popconfirm } from 'ant-design-vue'; 110 import { Popconfirm } from 'ant-design-vue';
112 import { Tag } from 'ant-design-vue'; 111 import { Tag } from 'ant-design-vue';
113 import VideoModal from '/@/views/device/list/cpns/tabs/VideoChannel/videoModal.vue'; 112 import VideoModal from '/@/views/device/list/cpns/tabs/VideoChannel/videoModal.vue';
  113 + import { VideoCancelModalParamsType } from '/@/views/device/list/cpns/tabs/VideoChannel/config';
  114 + import { CameraRecord } from '/@/api/camera/model/cameraModel';
114 115
115 export default defineComponent({ 116 export default defineComponent({
116 components: { 117 components: {
@@ -119,7 +120,6 @@ @@ -119,7 +120,6 @@
119 BasicTable, 120 BasicTable,
120 TableAction, 121 TableAction,
121 CameraDrawer, 122 CameraDrawer,
122 - VideoPreviewModal,  
123 VideoModal, 123 VideoModal,
124 TableImg, 124 TableImg,
125 Authority, 125 Authority,
@@ -131,9 +131,8 @@ @@ -131,9 +131,8 @@
131 setup(_, { emit }) { 131 setup(_, { emit }) {
132 const searchInfo = reactive<Recordable>({}); 132 const searchInfo = reactive<Recordable>({});
133 const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo); 133 const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);
134 - const [registerModal, { openModal }] = useModal(); //手动输入  
135 134
136 - const [registerModal1, { openModal: openModal1 }] = useModal(); //流媒体获取 135 + const [registerModal, { openModal }] = useModal(); //流媒体获取
137 // 表格hooks 136 // 表格hooks
138 const [registerTable, { reload, setProps, clearSelectedRowKeys }] = useTable({ 137 const [registerTable, { reload, setProps, clearSelectedRowKeys }] = useTable({
139 title: '视频列表', 138 title: '视频列表',
@@ -197,28 +196,25 @@ @@ -197,28 +196,25 @@
197 searchInfo.organizationId = organizationId; 196 searchInfo.organizationId = organizationId;
198 handleSuccess(); 197 handleSuccess();
199 }; 198 };
200 - const handleViewVideo = (record) => {  
201 - const { videoPlatformDTO } = record; 199 + const handleViewVideo = (record: CameraRecord) => {
  200 + const { videoPlatformDTO, params } = record;
202 const { type } = videoPlatformDTO || {}; 201 const { type } = videoPlatformDTO || {};
203 - const commonRecord = {  
204 - isUpdate: true,  
205 - record,  
206 - };  
207 - // 新增一个判断,GBT28181也弹出云台控制界面  
208 - // if (record.accessMode === AccessMode.Streaming && type === VideoPlatformEnum.ISC) {  
209 - // openModal1(true, commonRecord);  
210 - // } else if (record.accessMode === AccessMode.GBT28181) {  
211 - // openModal1(true, { ...commonRecord, ifShowGBT: true });  
212 - // } else openModal(true, commonRecord);  
213 202
214 - if (  
215 - record.accessMode === AccessMode.ManuallyEnter ||  
216 - (record.accessMode === AccessMode.Streaming && type === VideoPlatformEnum.FLUORITE)  
217 - ) {  
218 - openModal(true, commonRecord);  
219 - } else if (record.accessMode === AccessMode.GBT28181) {  
220 - openModal1(true, { ...commonRecord, ifShowGBT: true });  
221 - } else openModal1(true, commonRecord); 203 + openModal(true, {
  204 + record: {
  205 + id: record.id,
  206 + canControl:
  207 + [AccessMode.Streaming, AccessMode.GBT28181].includes(record.accessMode) &&
  208 + type !== VideoPlatformEnum.FLUORITE,
  209 + isGBT: record.accessMode === AccessMode.GBT28181,
  210 + channelId: params?.channelNo,
  211 + tbDeviceId: params?.deviceId,
  212 + getPlayUrl: async () => {
  213 + const result = await getPlayUrl(record);
  214 + return result;
  215 + },
  216 + } as VideoCancelModalParamsType,
  217 + });
222 }; 218 };
223 219
224 const handleSwitchMode = () => { 220 const handleSwitchMode = () => {
@@ -252,7 +248,6 @@ @@ -252,7 +248,6 @@
252 organizationIdTreeRef, 248 organizationIdTreeRef,
253 handleViewVideo, 249 handleViewVideo,
254 registerModal, 250 registerModal,
255 - registerModal1,  
256 AccessMode, 251 AccessMode,
257 handleSwitchMode, 252 handleSwitchMode,
258 CameraPermission, 253 CameraPermission,
@@ -3,24 +3,21 @@ @@ -3,24 +3,21 @@
3 import OrganizationIdTree from '../../common/organizationIdTree/src/OrganizationIdTree.vue'; 3 import OrganizationIdTree from '../../common/organizationIdTree/src/OrganizationIdTree.vue';
4 import { onMounted, reactive, Ref, 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, closeFlvPlay, getFlvPlayUrl } from '/@/api/camera/cameraManager'; 6 + import { cameraPage, closeFlvPlay } from '/@/api/camera/cameraManager';
7 import { CameraRecord } from '/@/api/camera/model/cameraModel'; 7 import { CameraRecord } from '/@/api/camera/model/cameraModel';
8 import { useFullscreen } from '@vueuse/core'; 8 import { useFullscreen } from '@vueuse/core';
9 import CameraDrawer from './CameraDrawer.vue'; 9 import CameraDrawer from './CameraDrawer.vue';
10 import GBTDrawer from './components/GBTDrawer.vue'; 10 import GBTDrawer from './components/GBTDrawer.vue';
11 import { useDrawer } from '/@/components/Drawer'; 11 import { useDrawer } from '/@/components/Drawer';
12 - import { AccessMode, CameraPermission, PageMode } from './config.data'; 12 + import { CameraPermission, getPlayUrl, PageMode } from './config.data';
13 import SvgIcon from '/@/components/Icon/src/SvgIcon.vue'; 13 import SvgIcon from '/@/components/Icon/src/SvgIcon.vue';
14 - import { getStreamingPlayUrl } from '/@/api/camera/cameraManager';  
15 import { buildUUID } from '/@/utils/uuid'; 14 import { buildUUID } from '/@/utils/uuid';
16 - import { BasicVideoPlay, getVideoTypeByUrl } from '/@/components/Video';  
17 - import { VideoJsPlayerOptions } from 'video.js';  
18 import { getBoundingClientRect } from '/@/utils/domUtils'; 15 import { getBoundingClientRect } from '/@/utils/domUtils';
19 import { Authority } from '/@/components/Authority'; 16 import { Authority } from '/@/components/Authority';
20 import { isRtspProtocol } from '/@/components/Video/src/utils'; 17 import { isRtspProtocol } from '/@/components/Video/src/utils';
21 import { useFingerprint } from '/@/utils/useFingerprint'; 18 import { useFingerprint } from '/@/utils/useFingerprint';
22 import { GetResult } from '@fingerprintjs/fingerprintjs'; 19 import { GetResult } from '@fingerprintjs/fingerprintjs';
23 - import { getVideoControlStart } from '/@/api/device/videoChannel'; 20 + import XGPlayer from '/@/components/Video/src/XGPlayer.vue';
24 21
25 const props = defineProps({ 22 const props = defineProps({
26 mode: { 23 mode: {
@@ -29,17 +26,12 @@ @@ -29,17 +26,12 @@
29 }, 26 },
30 }); 27 });
31 type CameraRecordItem = CameraRecord & { 28 type CameraRecordItem = CameraRecord & {
  29 + placeholder?: boolean;
32 canPlay?: boolean; 30 canPlay?: boolean;
33 isTransform?: boolean; 31 isTransform?: boolean;
34 withToken?: boolean; 32 withToken?: boolean;
35 - videoPlayerOptions?: VideoJsPlayerOptions;  
36 playSourceUrl?: string; 33 playSourceUrl?: string;
37 - };  
38 -  
39 - const basicVideoPlayOptions: VideoJsPlayerOptions = {  
40 - width: '100%' as unknown as number,  
41 - height: '100%' as unknown as number,  
42 - autoplay: true, 34 + streamType?: string;
43 }; 35 };
44 36
45 const emit = defineEmits(['switchMode']); 37 const emit = defineEmits(['switchMode']);
@@ -88,10 +80,7 @@ @@ -88,10 +80,7 @@
88 80
89 for (const item of unref(cameraList)) { 81 for (const item of unref(cameraList)) {
90 (item as CameraRecordItem).isTransform = false; 82 (item as CameraRecordItem).isTransform = false;
91 - (item as CameraRecordItem).videoPlayerOptions = {  
92 - ...basicVideoPlayOptions,  
93 - };  
94 - beforeVideoPlay(item as CameraRecordItem, result); 83 + beforeVideoPlay(item as CameraRecordItem);
95 } 84 }
96 } catch (error) { 85 } catch (error) {
97 } finally { 86 } finally {
@@ -100,75 +89,11 @@ @@ -100,75 +89,11 @@
100 }; 89 };
101 90
102 const { getResult } = useFingerprint(); 91 const { getResult } = useFingerprint();
103 - const beforeVideoPlay = async (record: CameraRecordItem, fingerprintResult: GetResult) => {  
104 - if (record.accessMode === AccessMode.ManuallyEnter) {  
105 - if (record.videoUrl) {  
106 - const isFlvPlay = isRtspProtocol(record.videoUrl);  
107 - const type = getVideoTypeByUrl(record.videoUrl);  
108 - record.playSourceUrl = record.videoUrl;  
109 - if (isFlvPlay) {  
110 - // handleFlvPlayerUnload(record, fingerprintResult!.visitorId);  
111 - record.playSourceUrl = getFlvPlayUrl(record.videoUrl, fingerprintResult.visitorId);  
112 - record.withToken = true;  
113 - }  
114 -  
115 - (record as CameraRecordItem).videoPlayerOptions = {  
116 - ...basicVideoPlayOptions,  
117 - sources: [  
118 - {  
119 - src: record.playSourceUrl,  
120 - type,  
121 - },  
122 - ],  
123 - };  
124 - record.isTransform = true;  
125 - }  
126 - }  
127 - if (record.accessMode === AccessMode.GBT28181) {  
128 - try {  
129 - const { params } = record;  
130 - if (params?.channelNo && params.deviceId) {  
131 - const result = await getVideoControlStart({  
132 - deviceId: params.deviceId,  
133 - channelId: params.channelNo,  
134 - });  
135 -  
136 - (record as CameraRecordItem).videoPlayerOptions = {  
137 - ...basicVideoPlayOptions,  
138 - sources: [{ src: result.data.flv, type: getVideoTypeByUrl(result.data.flv) }],  
139 - };  
140 - }  
141 - } finally {  
142 - record.isTransform = true;  
143 - }  
144 - }  
145 - if (record.accessMode === AccessMode.Streaming) {  
146 - try {  
147 - const { data: { url } = { url: '' } } = await getStreamingPlayUrl(record.id!);  
148 - const index = unref(cameraList).findIndex((item) => item.id === record.id);  
149 - if (~index) {  
150 - const oldRecord = unref(cameraList).at(index)!;  
151 - unref(cameraList)[index] = {  
152 - ...oldRecord,  
153 -  
154 - videoPlayerOptions: {  
155 - ...basicVideoPlayOptions,  
156 - sources: [  
157 - {  
158 - src: url,  
159 - type: getVideoTypeByUrl(url),  
160 - },  
161 - ],  
162 - } as any,  
163 - isTransform: true,  
164 - };  
165 - }  
166 - } catch (error) {  
167 - } finally {  
168 - const index = unref(cameraList).findIndex((item) => item.id === record.id);  
169 - if (~index) unref(cameraList)[index].isTransform = true;  
170 - }  
171 - } 92 + const beforeVideoPlay = async (record: CameraRecordItem) => {
  93 + const { url, type } = await getPlayUrl(record);
  94 + record.playSourceUrl = url;
  95 + record.streamType = type;
  96 + record.isTransform = true;
172 }; 97 };
173 98
174 const gridLayout = ref({ gutter: 1, column: 2 }); 99 const gridLayout = ref({ gutter: 1, column: 2 });
@@ -316,7 +241,7 @@ @@ -316,7 +241,7 @@
316 :grid="(gridLayout as any)" 241 :grid="(gridLayout as any)"
317 :style="{ '--height': `${100 / pagination.colNumber}%` }" 242 :style="{ '--height': `${100 / pagination.colNumber}%` }"
318 > 243 >
319 - <template #renderItem="{ item }"> 244 + <template #renderItem="{ item }: { item: CameraRecordItem }">
320 <List.Item> 245 <List.Item>
321 <div class="box-border w-full !h-full p-1px"> 246 <div class="box-border w-full !h-full p-1px">
322 <div 247 <div
@@ -332,10 +257,12 @@ @@ -332,10 +257,12 @@
332 v-show="!item.isTransform" 257 v-show="!item.isTransform"
333 :spinning="!item.isTransform" 258 :spinning="!item.isTransform"
334 /> 259 />
335 - <BasicVideoPlay 260 + <XGPlayer
336 v-if="item.isTransform" 261 v-if="item.isTransform"
337 - :options="item.videoPlayerOptions" 262 + :url="item.playSourceUrl"
  263 + :stream-type="item.streamType"
338 :with-token="item.withToken" 264 :with-token="item.withToken"
  265 + :config="{ width: '100%', height: '100%' }"
339 @on-unmounted="handleCloseFlvPlayUrl(item)" 266 @on-unmounted="handleCloseFlvPlayUrl(item)"
340 /> 267 />
341 <div 268 <div
@@ -6,7 +6,6 @@ import { h } from 'vue'; @@ -6,7 +6,6 @@ import { h } from 'vue';
6 import SnHelpMessage from './SnHelpMessage.vue'; 6 import SnHelpMessage from './SnHelpMessage.vue';
7 import SnHelpMessage1 from './SnHelpMessage1.vue'; 7 import SnHelpMessage1 from './SnHelpMessage1.vue';
8 import { OrgTreeSelect } from '../../common/OrgTreeSelect'; 8 import { OrgTreeSelect } from '../../common/OrgTreeSelect';
9 -import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';  
10 import { createImgPreview } from '/@/components/Preview'; 9 import { createImgPreview } from '/@/components/Preview';
11 import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter'; 10 import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter';
12 import { findDictItemByCode } from '/@/api/system/dict'; 11 import { findDictItemByCode } from '/@/api/system/dict';
@@ -15,7 +14,24 @@ import { DataSourceField } from '../../visual/packages/config/common.config'; @@ -15,7 +14,24 @@ import { DataSourceField } from '../../visual/packages/config/common.config';
15 import { getMeetTheConditionsDevice } from '/@/api/dataBoard'; 14 import { getMeetTheConditionsDevice } from '/@/api/dataBoard';
16 import { useMessage } from '/@/hooks/web/useMessage'; 15 import { useMessage } from '/@/hooks/web/useMessage';
17 import { TransportTypeEnum } from '../../device/profiles/components/TransportDescript/const'; 16 import { TransportTypeEnum } from '../../device/profiles/components/TransportDescript/const';
18 -import { getDeviceChannelList } from '/@/api/camera/cameraManager'; 17 +import {
  18 + getDeviceChannelList,
  19 + getFlvPlayUrl,
  20 + getStreamingPlayUrl,
  21 +} from '/@/api/camera/cameraManager';
  22 +import { CameraRecord } from '/@/api/camera/model/cameraModel';
  23 +import { isRtspProtocol } from '/@/components/Video/src/utils';
  24 +import { useFingerprint } from '/@/utils/useFingerprint';
  25 +import { getVideoControlStart } from '/@/api/device/videoChannel';
  26 +import { StreamType as PlayerStreamType } from '/@/components/Video/src/types';
  27 +
  28 +interface FileItem {
  29 + uid: string;
  30 + name: string;
  31 + status?: string;
  32 + response?: string;
  33 + url?: string;
  34 +}
19 35
20 useComponentRegister('OrgTreeSelect', OrgTreeSelect); 36 useComponentRegister('OrgTreeSelect', OrgTreeSelect);
21 37
@@ -604,3 +620,37 @@ export const formGBTSchema: QFormSchema[] = [ @@ -604,3 +620,37 @@ export const formGBTSchema: QFormSchema[] = [
604 values.allDevice === GBT28181DeviceSelectMethod.PARTIAL, 620 values.allDevice === GBT28181DeviceSelectMethod.PARTIAL,
605 }, 621 },
606 ]; 622 ];
  623 +
  624 +export async function getPlayUrl(
  625 + params: CameraRecord
  626 +): Promise<{ url: string; type: PlayerStreamType }> {
  627 + const { accessMode } = params;
  628 + if (accessMode === AccessMode.ManuallyEnter) {
  629 + const { videoUrl } = params;
  630 + if (params.videoUrl) {
  631 + const isRTSPPlay = isRtspProtocol(videoUrl);
  632 +
  633 + if (isRTSPPlay) {
  634 + const { getResult } = useFingerprint();
  635 + const fingerprint = await getResult();
  636 + return { url: getFlvPlayUrl(videoUrl, fingerprint.visitorId), type: 'flv' };
  637 + } else {
  638 + return { url: videoUrl, type: 'auto' };
  639 + }
  640 + }
  641 + } else if (accessMode === AccessMode.GBT28181) {
  642 + const { deviceId, channelNo } = params?.params || {};
  643 + const result = await getVideoControlStart({ channelId: channelNo!, deviceId: deviceId! });
  644 + return { url: result.data.flv, type: 'flv' };
  645 + } else {
  646 + const { id, playProtocol } = params;
  647 + const result = await getStreamingPlayUrl(id);
  648 + const type: PlayerStreamType =
  649 + playProtocol === FluoriteMideaProtocolEnum.FLV
  650 + ? 'flv'
  651 + : playProtocol === FluoriteMideaProtocolEnum.HLS
  652 + ? 'hls'
  653 + : 'auto';
  654 + return { url: result.data.url, type };
  655 + }
  656 +}
1 import { h } from 'vue'; 1 import { h } from 'vue';
2 import { BasicColumn, FormSchema } from '/@/components/Table'; 2 import { BasicColumn, FormSchema } from '/@/components/Table';
3 import { Tag } from 'ant-design-vue'; 3 import { Tag } from 'ant-design-vue';
4 -import { withInstall } from '/@/utils/index';  
5 -  
6 -import VideoPlay from './video.vue';  
7 -export const Video = withInstall(VideoPlay); 4 +export type VideoCancelModalParamsType = {
  5 + canControl?: boolean;
  6 + isGBT?: boolean;
  7 + tbDeviceId?: string;
  8 + channelId?: string;
  9 + id?: string;
  10 + playerProps?: Recordable;
  11 + getPlayUrl: () => Promise<Record<'url' | 'type', string>>;
  12 +};
8 13
9 //视频通道权限标识枚举 14 //视频通道权限标识枚举
10 export enum GBT28181_DEVICE_PERMISSION_ENUM { 15 export enum GBT28181_DEVICE_PERMISSION_ENUM {
@@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
8 <a-button type="primary" @click="handleSyncPlay">通道同步</a-button> 8 <a-button type="primary" @click="handleSyncPlay">通道同步</a-button>
9 </Authority> 9 </Authority>
10 </template> 10 </template>
11 - <template #hasAudio="{ record }"> 11 + <template #hasAudio="{ record }: { record: VideoChannelItemType }">
12 <Switch 12 <Switch
13 :checked="record.status === 1" 13 :checked="record.status === 1"
14 :loading="record.pendingStatus" 14 :loading="record.pendingStatus"
@@ -41,16 +41,22 @@ @@ -41,16 +41,22 @@
41 </template> 41 </template>
42 42
43 <script lang="ts" setup> 43 <script lang="ts" setup>
44 - import { configColumns, searchFormSchema, GBT28181_DEVICE_PERMISSION_ENUM } from './config'; 44 + import {
  45 + configColumns,
  46 + searchFormSchema,
  47 + GBT28181_DEVICE_PERMISSION_ENUM,
  48 + VideoCancelModalParamsType,
  49 + } from './config';
45 import { BasicTable, useTable, TableAction } from '/@/components/Table'; 50 import { BasicTable, useTable, TableAction } from '/@/components/Table';
46 import { Switch } from 'ant-design-vue'; 51 import { Switch } from 'ant-design-vue';
47 import { DeviceRecord } from '/@/api/device/model/deviceModel'; 52 import { DeviceRecord } from '/@/api/device/model/deviceModel';
48 import VideoModal from './videoModal.vue'; 53 import VideoModal from './videoModal.vue';
49 import { useModal } from '/@/components/Modal'; 54 import { useModal } from '/@/components/Modal';
50 import { useMessage } from '/@/hooks/web/useMessage'; 55 import { useMessage } from '/@/hooks/web/useMessage';
51 - import { getVideoChannelList } from '/@/api/device/videoChannel'; 56 + import { getVideoChannelList, getVideoControlStart } from '/@/api/device/videoChannel';
52 import { stopOnDemandVideoApiGet, syncVideoApiGet } from '/@/api/camera/cameraManager'; 57 import { stopOnDemandVideoApiGet, syncVideoApiGet } from '/@/api/camera/cameraManager';
53 import { Authority } from '/@/components/Authority'; 58 import { Authority } from '/@/components/Authority';
  59 + import { VideoChannelItemType } from '/@/api/device/model/videoChannelModel';
54 60
55 const props = defineProps<{ deviceDetail: DeviceRecord }>(); 61 const props = defineProps<{ deviceDetail: DeviceRecord }>();
56 62
@@ -91,10 +97,20 @@ @@ -91,10 +97,20 @@
91 } 97 }
92 }; 98 };
93 99
94 - const handlePlay = (record: Recordable) => { 100 + const handlePlay = (record: VideoChannelItemType) => {
  101 + const { channelId } = record;
95 openModal(true, { 102 openModal(true, {
96 - record,  
97 - ifShowGBT: true, 103 + record: {
  104 + canControl: true,
  105 + isGBT: true,
  106 + channelId,
  107 + tbDeviceId: props.deviceDetail.tbDeviceId,
  108 + getPlayUrl: async () => {
  109 + const { deviceId, channelId } = record;
  110 + const result = await getVideoControlStart({ deviceId, channelId });
  111 + return { type: 'flv', url: result.data.flv };
  112 + },
  113 + } as VideoCancelModalParamsType,
98 }); 114 });
99 }; 115 };
100 116
1 <script lang="ts" setup> 1 <script lang="ts" setup>
2 - import { isNumber } from 'lodash';  
3 - import videoJs, { VideoJsPlayer, VideoJsPlayerOptions } from 'video.js';  
4 - import 'video.js/dist/video-js.css';  
5 import { Tooltip } from 'ant-design-vue'; 2 import { Tooltip } from 'ant-design-vue';
6 - import { computed, CSSProperties, onMounted, onUnmounted, ref, unref } from 'vue';  
7 - import { useDesign } from '/@/hooks/web/useDesign';  
8 - import { getJwtToken, getShareJwtToken } from '/@/utils/auth';  
9 - import { isShareMode } from '/@/views/sys/share/hook';  
10 - import 'videojs-flvjs-es6'; 3 + import { ref, unref } from 'vue';
11 import { QuestionCircleOutlined } from '@ant-design/icons-vue'; 4 import { QuestionCircleOutlined } from '@ant-design/icons-vue';
12 import { 5 import {
13 CaretUpOutlined, 6 CaretUpOutlined,
@@ -19,106 +12,49 @@ @@ -19,106 +12,49 @@
19 ZoomOutOutlined, 12 ZoomOutOutlined,
20 } from '@ant-design/icons-vue'; 13 } from '@ant-design/icons-vue';
21 import { Button, Slider } from 'ant-design-vue'; 14 import { Button, Slider } from 'ant-design-vue';
22 - import { nextTick } from 'vue';  
23 import { controlling } from '/@/api/camera/cameraManager'; 15 import { controlling } from '/@/api/camera/cameraManager';
24 import { setVideoControl } from '/@/api/device/videoChannel'; 16 import { setVideoControl } from '/@/api/device/videoChannel';
25 -  
26 - const { prefixCls } = useDesign('basic-video-play');  
27 - interface IGbtOption {  
28 - tbDeviceId: string;  
29 - channelId: string;  
30 - } 17 + import XGPlayer from '/@/components/Video/src/XGPlayer.vue';
31 18
32 const props = defineProps<{ 19 const props = defineProps<{
33 - options?: VideoJsPlayerOptions;  
34 - withToken?: boolean;  
35 - isGBT?: boolean;  
36 - GBTOption: IGbtOption;  
37 - }>();  
38 -  
39 - const emit = defineEmits<{  
40 - (event: 'ready', instance?: Nullable<VideoJsPlayer>): void;  
41 - (event: 'onUnmounted'): void;  
42 - }>();  
43 -  
44 - const videoPlayEl = ref<HTMLVideoElement>();  
45 -  
46 - const videoPlayInstance = ref<Nullable<VideoJsPlayer>>();  
47 -  
48 - const getGBT = computed(() => {  
49 - const { isGBT } = props;  
50 - return isGBT;  
51 - });  
52 -  
53 - const getOptions = computed(() => {  
54 - const { options, withToken } = props;  
55 -  
56 - const defaultOptions: VideoJsPlayerOptions & Recordable = {  
57 - language: 'zh',  
58 - muted: true,  
59 - liveui: true,  
60 - controls: true,  
61 - techOrder: ['html5', 'flvjs'],  
62 - flvjs: {  
63 - mediaDataSource: {  
64 - isLive: true,  
65 - cors: true,  
66 - hasAudio: false,  
67 - withCredentials: false,  
68 - },  
69 - config: {  
70 - headers: {  
71 - ...(withToken  
72 - ? {  
73 - 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`,  
74 - }  
75 - : {}),  
76 - },  
77 - autoCleanupSourceBuffer: true,  
78 - },  
79 - }, 20 + playUrl?: string;
  21 + options?: {
  22 + canControl?: boolean;
  23 + isGBT?: boolean;
  24 + tbDeviceId?: string;
  25 + channelId?: string;
  26 + id?: string;
  27 + playerProps?: Recordable;
80 }; 28 };
81 - return videoJs.mergeOptions(defaultOptions, options);  
82 - }); 29 + }>();
83 30
84 - const getWidthHeight = computed(() => {  
85 - let { width = 300, height = 150 } = unref(getOptions);  
86 - width = isNumber(width) ? (`${width}px` as unknown as number) : width;  
87 - height = isNumber(height) ? (`${height}px` as unknown as number) : height;  
88 - return { width, height } as CSSProperties;  
89 - }); 31 + const playerRef = ref<InstanceType<typeof XGPlayer>>();
90 32
91 // 控制云台控制速度 33 // 控制云台控制速度
92 const sliderValue = ref<number>(123); 34 const sliderValue = ref<number>(123);
93 35
94 - const init = () => {  
95 - if (unref(videoPlayInstance)) unref(videoPlayInstance)?.dispose();  
96 - videoPlayInstance.value = videoJs(unref(videoPlayEl)!, unref(getOptions), () => {  
97 - emit('ready', unref(videoPlayInstance));  
98 - });  
99 - }; 36 + const isPlay = ref(false);
100 37
101 //播放/暂停 38 //播放/暂停
102 const handleClick = () => { 39 const handleClick = () => {
103 - // unref(isPlay) ((true,停止),(false,开启))  
104 - if (unref(isPlay)) unref(videoPlayInstance)?.pause();  
105 - else unref(videoPlayInstance)?.play();  
106 - };  
107 -  
108 - const getId = () => {  
109 - const { options } = props || {};  
110 - const { sources }: any = options || {};  
111 - return sources?.[0]?.id; 40 + const playerInstance = unref(playerRef)?.getPlayerInstance();
  41 + if (playerInstance && playerInstance.paused) {
  42 + unref(playerRef)?.getPlayerInstance()?.play();
  43 + isPlay.value = true;
  44 + } else {
  45 + unref(playerRef)?.getPlayerInstance()?.pause();
  46 + isPlay.value = false;
  47 + }
112 }; 48 };
113 49
114 const handleControl = (action: number, direction: string) => { 50 const handleControl = (action: number, direction: string) => {
115 - const organizationId = getId();  
116 - controlling({ cameraIndexCode: organizationId, action, command: direction }); 51 + const { options } = props;
  52 + controlling({ cameraIndexCode: options?.id, action, command: direction });
117 }; 53 };
118 54
119 // 国标控制 55 // 国标控制
120 const handleGBTControl = (command: string, action?: number | string) => { 56 const handleGBTControl = (command: string, action?: number | string) => {
121 - const { tbDeviceId, channelId } = props.GBTOption; 57 + const { tbDeviceId, channelId } = props.options || {};
122 setVideoControl(tbDeviceId, channelId, { 58 setVideoControl(tbDeviceId, channelId, {
123 command, 59 command,
124 horizonSpeed: action, 60 horizonSpeed: action,
@@ -127,33 +63,9 @@ @@ -127,33 +63,9 @@
127 }); 63 });
128 }; 64 };
129 65
130 - const isPlay = ref<Boolean | null | undefined>(false);  
131 -  
132 - onMounted(async () => {  
133 - init();  
134 - await nextTick();  
135 - // isPlay.value = unref(videoPlayInstance)?.paused();  
136 - videoPlayInstance.value?.on('loadedmetadata', () => {});  
137 - videoPlayInstance.value?.on('waiting', () => {  
138 - isPlay.value = false;  
139 - });  
140 - videoPlayInstance.value?.on('play', () => {  
141 - isPlay.value = true;  
142 - });  
143 - videoPlayInstance.value?.on('playing', () => {  
144 - isPlay.value = true;  
145 - });  
146 - videoPlayInstance.value?.on('pause', () => {  
147 - isPlay.value = false;  
148 - });  
149 - videoPlayInstance.value?.on('ended', () => {  
150 - isPlay.value = false;  
151 - });  
152 - });  
153 -  
154 //长按开始 66 //长按开始
155 - const moveStart = (action) => {  
156 - if (unref(getGBT)) { 67 + const moveStart = (action: string) => {
  68 + if (unref(props.options?.isGBT)) {
157 handleGBTControl(action, unref(sliderValue)); 69 handleGBTControl(action, unref(sliderValue));
158 return; 70 return;
159 } 71 }
@@ -161,46 +73,33 @@ @@ -161,46 +73,33 @@
161 }; 73 };
162 74
163 // 长按结束 75 // 长按结束
164 - const moveStop = (action) => {  
165 - if (unref(getGBT)) { 76 + const moveStop = (action: string) => {
  77 + if (unref(props.options?.isGBT)) {
166 handleGBTControl('STOP', unref(sliderValue)); 78 handleGBTControl('STOP', unref(sliderValue));
167 return; 79 return;
168 } 80 }
169 handleControl(1, action); 81 handleControl(1, action);
170 }; 82 };
171 -  
172 - //页面卸载  
173 - onUnmounted(() => {  
174 - unref(videoPlayInstance)?.dispose();  
175 - videoPlayInstance.value = null;  
176 - emit('onUnmounted');  
177 - });  
178 -  
179 - defineExpose({  
180 - reloadPlayer: init,  
181 - getInstance: () => unref(videoPlayInstance),  
182 - });  
183 </script> 83 </script>
184 84
185 <template> 85 <template>
186 - <div  
187 - :class="prefixCls"  
188 - class="!w-full h-full flex"  
189 - :style="getWidthHeight"  
190 - style="min-height: 100%"  
191 - >  
192 - <video  
193 - ref="videoPlayEl"  
194 - class="video-js vjs-big-play-centered vjs-show-big-play-button-on-pause !w-8/10 !h-full"  
195 - muted 86 + <div class="!w-full h-full flex" style="min-height: 100%">
  87 + <XGPlayer
  88 + ref="playerRef"
  89 + v-bind="options?.playerProps"
  90 + :url="playUrl"
  91 + auto-play
  92 + :config="{ fluid: true }"
  93 + />
  94 +
  95 + <div
  96 + v-if="options?.canControl"
  97 + class="!w-2/10 px-4 bg-white flex justify-center items-center flex-col"
196 > 98 >
197 - </video>  
198 -  
199 - <div class="!w-2/10 bg-white flex items-center flex-col">  
200 <Tooltip> 99 <Tooltip>
201 - <template #title  
202 - >长按按钮调用API,由于视频流媒体提供的流,有一定时延,请耐心等待。</template  
203 - > 100 + <template #title>
  101 + 长按按钮调用API,由于视频流媒体提供的流,有一定时延,请耐心等待。
  102 + </template>
204 <label class="validate-dot">云台控制</label> 103 <label class="validate-dot">云台控制</label>
205 <QuestionCircleOutlined :style="{ fontSize: '14px', marginLeft: '5px' }" /> 104 <QuestionCircleOutlined :style="{ fontSize: '14px', marginLeft: '5px' }" />
206 </Tooltip> 105 </Tooltip>
@@ -248,7 +147,7 @@ @@ -248,7 +147,7 @@
248 <Button class="circle" @click="handleClick" /> 147 <Button class="circle" @click="handleClick" />
249 </div> 148 </div>
250 </div> 149 </div>
251 - <div class="mt-5" v-if="getGBT"> 150 + <div class="mt-5" v-if="options.isGBT">
252 <Slider v-model:value="sliderValue" :min="0" :max="255" style="width: 130px" /> 151 <Slider v-model:value="sliderValue" :min="0" :max="255" style="width: 130px" />
253 </div> 152 </div>
254 <div class="flex justify-center mt-8"> 153 <div class="flex justify-center mt-8">
@@ -274,16 +173,6 @@ @@ -274,16 +173,6 @@
274 </template> 173 </template>
275 174
276 <style lang="less" scoped> 175 <style lang="less" scoped>
277 - @prefix-cls: ~'@{namespace}-basic-video-play';  
278 -  
279 - .@{prefix-cls} {  
280 - .vjs-error-display {  
281 - .vjs-modal-dialog-content::after {  
282 - content: '无法加载视频,原因可能是服务器或网络故障,也可能是格式不支持.';  
283 - }  
284 - }  
285 - }  
286 -  
287 .child { 176 .child {
288 position: absolute; 177 position: absolute;
289 width: 3rem; 178 width: 3rem;
1 <template> 1 <template>
2 - <div>  
3 - <BasicModal  
4 - v-bind="$attrs"  
5 - width="60rem"  
6 - destroyOnClose  
7 - :height="heightNum"  
8 - @register="register"  
9 - title="视频预览"  
10 - :showOkBtn="false"  
11 - @cancel="handleCancel"  
12 - >  
13 - <div  
14 - class="flex items-center justify-center w-full h-full min-h-96 video-container bg-dark-50"  
15 - >  
16 - <VideoPlayer  
17 - ref="videoInstance"  
18 - v-if="showVideo"  
19 - :options="(options as VideoJsPlayerOptions)"  
20 - :withToken="withToken"  
21 - :isGBT="isGBT"  
22 - :GBTOption="GBTOption"  
23 - />  
24 - </div>  
25 - </BasicModal>  
26 - </div> 2 + <BasicModal
  3 + v-bind="$attrs"
  4 + width="60rem"
  5 + destroyOnClose
  6 + :height="800"
  7 + @register="register"
  8 + title="视频预览"
  9 + :showOkBtn="false"
  10 + @cancel="handleCancel"
  11 + >
  12 + <div class="flex items-center justify-center w-full h-full min-h-96 video-container bg-dark-50">
  13 + <VideoPlayer :play-url="playUrl" :options="options" />
  14 + </div>
  15 + </BasicModal>
27 </template> 16 </template>
28 <script setup lang="ts"> 17 <script setup lang="ts">
29 - import { ref, reactive } from 'vue'; 18 + import { ref } from 'vue';
30 import { BasicModal, useModalInner } from '/@/components/Modal'; 19 import { BasicModal, useModalInner } from '/@/components/Modal';
31 - import { VideoJsPlayerOptions } from 'video.js';  
32 import VideoPlayer from './video.vue'; 20 import VideoPlayer from './video.vue';
33 - import { getVideoControlStart } from '/@/api/device/videoChannel';  
34 - import { VideoChannelItemType } from '/@/api/device/model/videoChannelModel';  
35 - import { getVideoTypeByUrl } from '/@/components/Video';  
36 - import { AccessMode } from '/@/views/camera/manage/config.data';  
37 - import { getStreamingPlayUrl } from '/@/api/camera/cameraManager';  
38 - 21 + import { VideoCancelModalParamsType } from '/@/views/device/list/cpns/tabs/VideoChannel/config';
39 const emit = defineEmits(['reloadTable', 'register']); 22 const emit = defineEmits(['reloadTable', 'register']);
40 23
41 - const heightNum = ref(800);  
42 - const showVideo = ref(false);  
43 -  
44 - const videoInstance = ref<InstanceType<typeof VideoPlayer>>();  
45 -  
46 - const videoId = ref<string>();  
47 -  
48 - const withToken = ref(false);  
49 -  
50 - const options = reactive<VideoJsPlayerOptions>({  
51 - width: '100%' as unknown as number,  
52 - height: 384 as unknown as number,  
53 - autoplay: true,  
54 - });  
55 - const GBTOption = ref({  
56 - tbDeviceId: '',  
57 - channelId: '',  
58 - });  
59 - const isGBT = ref<boolean>(false);  
60 - 24 + const playUrl = ref();
  25 + const options = ref<VideoCancelModalParamsType>();
61 const [register, { setModalProps }] = useModalInner( 26 const [register, { setModalProps }] = useModalInner(
62 - async (data: { record: VideoChannelItemType }) => {  
63 - const { record, ifShowGBT = false } = data;  
64 - const { params } = record as Recordable;  
65 - videoId.value = record.id || '';  
66 - isGBT.value = ifShowGBT;  
67 - GBTOption.value.tbDeviceId = params?.deviceId ? params?.deviceId : record?.deviceId;  
68 - GBTOption.value.channelId = params?.channelNo ? params?.channelNo : record?.channelId; 27 + async (data: ModalParamsType<VideoCancelModalParamsType>) => {
  28 + const { record } = data;
69 try { 29 try {
70 setModalProps({ loading: true, loadingTip: '视频加载中...' }); 30 setModalProps({ loading: true, loadingTip: '视频加载中...' });
71 - // getStreamingPlayUrl  
72 - let result: any = null;  
73 - if (record.accessMode === AccessMode.GBT28181 || ifShowGBT) {  
74 - result = await getVideoControlStart({  
75 - deviceId: params?.deviceId ? params?.deviceId : record?.deviceId,  
76 - channelId: params?.channelNo ? params?.channelNo : record?.channelId,  
77 - });  
78 - options.sources = [{ src: result.data.flv, type: getVideoTypeByUrl(result.data.flv) }];  
79 - } else {  
80 - result = await getStreamingPlayUrl(record.id);  
81 - options.sources = [  
82 - { src: result.data.url, type: getVideoTypeByUrl(result.data.url), id: record.id },  
83 - ];  
84 - }  
85 - showVideo.value = true; 31 + const { url, type } = await record.getPlayUrl();
  32 +
  33 + playUrl.value = url;
  34 + options.value = record;
  35 + options.value.playerProps = {
  36 + ...(options.value?.playerProps || {}),
  37 + streamType: type,
  38 + };
86 } catch (error) { 39 } catch (error) {
87 } finally { 40 } finally {
88 setModalProps({ loading: false }); 41 setModalProps({ loading: false });
@@ -91,18 +44,6 @@ @@ -91,18 +44,6 @@
91 ); 44 );
92 45
93 const handleCancel = () => { 46 const handleCancel = () => {
94 - showVideo.value = false;  
95 - withToken.value = false;  
96 emit('reloadTable'); 47 emit('reloadTable');
97 }; 48 };
98 </script> 49 </script>
99 -  
100 -<style lang="less" scoped>  
101 - .video-container:deep(.vben-basic-video-play) {  
102 - min-height: 13rem;  
103 - }  
104 -  
105 - .video-container:deep(.video-js) {  
106 - min-height: 13rem;  
107 - }  
108 -</style>  
@@ -13,6 +13,7 @@ export enum FormFieldEnum { @@ -13,6 +13,7 @@ export enum FormFieldEnum {
13 13
14 DEVICE_ID = 'deviceId', 14 DEVICE_ID = 'deviceId',
15 CHANNEL_ID = 'channelId', 15 CHANNEL_ID = 'channelId',
  16 + PLAY_PROTOCOL = 'playProtocol',
16 } 17 }
17 18
18 export interface DataSourceValueType { 19 export interface DataSourceValueType {
@@ -129,6 +130,7 @@ export const formSchemas: FormSchema[] = [ @@ -129,6 +130,7 @@ export const formSchemas: FormSchema[] = [
129 accessMode === AccessMode.GBT28181 ? options.params?.channelNo : null, 130 accessMode === AccessMode.GBT28181 ? options.params?.channelNo : null,
130 [FormFieldEnum.DEVICE_ID]: 131 [FormFieldEnum.DEVICE_ID]:
131 accessMode === AccessMode.GBT28181 ? options.params?.deviceId : null, 132 accessMode === AccessMode.GBT28181 ? options.params?.deviceId : null,
  133 + [FormFieldEnum.PLAY_PROTOCOL]: options?.playProtocol,
132 }); 134 });
133 }, 135 },
134 }; 136 };
@@ -146,4 +148,10 @@ export const formSchemas: FormSchema[] = [ @@ -146,4 +148,10 @@ export const formSchemas: FormSchema[] = [
146 component: 'Input', 148 component: 'Input',
147 ifShow: false, 149 ifShow: false,
148 }, 150 },
  151 + {
  152 + field: FormFieldEnum.PLAY_PROTOCOL,
  153 + label: '播放协议',
  154 + component: 'InputNumber',
  155 + ifShow: false,
  156 + },
149 ]; 157 ];
1 <script lang="ts" setup> 1 <script lang="ts" setup>
2 import { ComponentMode, ComponentPropsConfigType } from '/@/views/visual/packages/index.type'; 2 import { ComponentMode, ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 import { option } from './config'; 3 import { option } from './config';
4 - import { BasicVideoPlay, getVideoTypeByUrl } from '/@/components/Video';  
5 - import { computed, onMounted, onUnmounted, toRaw } from 'vue';  
6 - import { VideoJsPlayerOptions } from 'video.js';  
7 - import { AccessMode } from '/@/views/camera/manage/config.data';  
8 - import { unref } from 'vue';  
9 - import { closeFlvPlay, getFlvPlayUrl, getStreamingPlayUrl } from '/@/api/camera/cameraManager'; 4 + import { XGPlayer } from '/@/components/Video';
  5 + import { onMounted, onUnmounted } from 'vue';
  6 + import { getPlayUrl } from '/@/views/camera/manage/config.data';
  7 + import { closeFlvPlay } from '/@/api/camera/cameraManager';
10 import { ref } from 'vue'; 8 import { ref } from 'vue';
11 import { Spin } from 'ant-design-vue'; 9 import { Spin } from 'ant-design-vue';
12 import { useFingerprint } from '/@/utils/useFingerprint'; 10 import { useFingerprint } from '/@/utils/useFingerprint';
13 - import { isRtspProtocol, VideoPlayerType } from '/@/components/Video/src/utils';  
14 - import { isShareMode } from '/@/views/sys/share/hook';  
15 - import { getJwtToken, getShareJwtToken } from '/@/utils/auth';  
16 - import { getVideoControlStart } from '/@/api/device/videoChannel'; 11 + import { isRtspProtocol } from '/@/components/Video/src/utils';
  12 + import { CameraRecord } from '/@/api/camera/model/cameraModel';
17 13
18 const props = defineProps<{ 14 const props = defineProps<{
19 config: ComponentPropsConfigType<typeof option>; 15 config: ComponentPropsConfigType<typeof option>;
@@ -21,24 +17,12 @@ @@ -21,24 +17,12 @@
21 17
22 const loading = ref(true); 18 const loading = ref(true);
23 19
24 - const basicVideoPlayEl = ref<Nullable<InstanceType<typeof BasicVideoPlay>>>(null);  
25 -  
26 - const withToken = ref(false);  
27 -  
28 - const playSource = ref<Record<'src' | 'type', string>>(  
29 - {} as unknown as Record<'src' | 'type', string>  
30 - ); 20 + const playUrl = ref<string>();
  21 + const playType = ref<string>();
31 22
32 const exampleVideoPlay = 23 const exampleVideoPlay =
33 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm'; 24 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm';
34 25
35 - const getOptions = computed<VideoJsPlayerOptions>(() => {  
36 - return {  
37 - width: '100%' as unknown as number,  
38 - height: '100%' as unknown as number,  
39 - };  
40 - });  
41 -  
42 const { getResult } = useFingerprint(); 26 const { getResult } = useFingerprint();
43 27
44 const handleGetVideoPlayUrl = async () => { 28 const handleGetVideoPlayUrl = async () => {
@@ -46,45 +30,19 @@ @@ -46,45 +30,19 @@
46 const { config } = props; 30 const { config } = props;
47 const { option } = config; 31 const { option } = config;
48 const { videoConfig, uuid } = option || {}; 32 const { videoConfig, uuid } = option || {};
  33 + const { type, url } = await getPlayUrl({
  34 + id: videoConfig?.id,
  35 + accessMode: videoConfig?.accessMode,
  36 + playProtocol: videoConfig?.playProtocol,
  37 + videoUrl: videoConfig?.videoUrl,
  38 + params: {
  39 + deviceId: videoConfig?.deviceId,
  40 + channelNo: videoConfig?.channelId,
  41 + },
  42 + } as unknown as CameraRecord);
  43 + playType.value = type;
  44 + playUrl.value = url;
49 if (!uuid) return; 45 if (!uuid) return;
50 - const { url, id, accessMode, deviceId, channelId } = videoConfig || {};  
51 - let type = getVideoTypeByUrl(url!);  
52 - let playUrl = url;  
53 - if (accessMode === AccessMode.Streaming && id) {  
54 - const { data: { url } = { url: '' } } = await getStreamingPlayUrl(id!);  
55 - playUrl = url;  
56 - playUrl && (type = getVideoTypeByUrl(playUrl!));  
57 - } else if (accessMode === AccessMode.GBT28181 && deviceId && channelId) {  
58 - const {  
59 - data: { flv },  
60 - } = await getVideoControlStart({ channelId, deviceId });  
61 -  
62 - playUrl = flv;  
63 - type = VideoPlayerType.flv;  
64 - }  
65 -  
66 - if (isRtspProtocol(url!)) {  
67 - const result = await getResult();  
68 - const { visitorId } = result;  
69 - playUrl = getFlvPlayUrl(playUrl!, visitorId);  
70 - withToken.value = true;  
71 - }  
72 -  
73 - playSource.value = {  
74 - src: playUrl!,  
75 - type,  
76 - };  
77 -  
78 - const instance = unref(basicVideoPlayEl)?.customInit((options) => {  
79 - if (unref(withToken)) {  
80 - (options as any).flvjs.config.headers = {  
81 - 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`,  
82 - };  
83 - }  
84 - return { ...options, sources: [toRaw(unref(playSource))] } as VideoJsPlayerOptions;  
85 - });  
86 -  
87 - instance?.play();  
88 } finally { 46 } finally {
89 loading.value = false; 47 loading.value = false;
90 } 48 }
@@ -92,20 +50,8 @@ @@ -92,20 +50,8 @@
92 50
93 const handleSelectPreview = () => { 51 const handleSelectPreview = () => {
94 loading.value = false; 52 loading.value = false;
95 - const instance = unref(basicVideoPlayEl)?.customInit((options) => {  
96 - withToken.value = true;  
97 -  
98 - if (unref(withToken)) {  
99 - (options as any).flvjs.config.headers = {  
100 - 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`,  
101 - };  
102 - }  
103 - return {  
104 - ...options,  
105 - sources: [{ type: getVideoTypeByUrl(exampleVideoPlay), src: exampleVideoPlay }],  
106 - } as VideoJsPlayerOptions;  
107 - });  
108 - instance?.play(); 53 + playUrl.value = exampleVideoPlay;
  54 + playType.value = 'auto';
109 }; 55 };
110 56
111 onMounted(() => { 57 onMounted(() => {
@@ -126,11 +72,10 @@ @@ -126,11 +72,10 @@
126 <template> 72 <template>
127 <main class="w-full h-full flex flex-col justify-center items-center p-2"> 73 <main class="w-full h-full flex flex-col justify-center items-center p-2">
128 <Spin :spinning="loading" wrapper-class-name="video-spin"> 74 <Spin :spinning="loading" wrapper-class-name="video-spin">
129 - <BasicVideoPlay  
130 - ref="basicVideoPlayEl"  
131 - :options="getOptions"  
132 - :with-token="withToken"  
133 - :immediateInitOnMounted="false" 75 + <XGPlayer
  76 + :url="playUrl"
  77 + :stream-type="playType"
  78 + :config="{ width: '100%', height: '100%' }"
134 /> 79 />
135 </Spin> 80 </Spin>
136 </main> 81 </main>