Commit 6fce6b5fc33bd48520794a99d721746c720c7fc2

Authored by ww
1 parent 99b2eee4

perf: 切换视频播放器为xgplaery

... ... @@ -72,14 +72,16 @@
72 72 "sortablejs": "^1.14.0",
73 73 "tinymce": "^5.8.2",
74 74 "vditor": "^3.8.6",
75   - "video.js": "^7.20.3",
76   - "videojs-flvjs-es6": "^1.0.1",
77 75 "vue": "3.3.4",
78 76 "vue-i18n": "9.1.7",
79 77 "vue-json-pretty": "^2.0.4",
80 78 "vue-router": "^4.0.11",
81 79 "vue-types": "^4.0.3",
82 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 85 "xlsx": "^0.17.0"
84 86 },
85 87 "devDependencies": {
... ... @@ -101,7 +103,6 @@
101 103 "@types/qrcode": "^1.4.1",
102 104 "@types/qs": "^6.9.7",
103 105 "@types/sortablejs": "^1.10.7",
104   - "@types/video.js": "^7.3.49",
105 106 "@typescript-eslint/eslint-plugin": "^4.29.1",
106 107 "@typescript-eslint/parser": "^4.29.1",
107 108 "@vitejs/plugin-legacy": "^1.5.1",
... ...
... ... @@ -109,7 +109,7 @@ export const deleteStreamingMediaRecord = (params: StreamingMediaDeleteParam) =>
109 109 * @returns
110 110 */
111 111 export const getStreamingPlayUrl = (entityId: string) => {
112   - return defHttp.get(
  112 + return defHttp.get<{ data: { url: string } }>(
113 113 {
114 114 url: `${CameraManagerApi.STREAMING_PLAY_GET_URL}/${entityId}`,
115 115 },
... ...
... ... @@ -35,6 +35,7 @@ export interface CameraModel {
35 35 deviceId: string;
36 36 deviceName: string;
37 37 };
  38 + playProtocol?: number;
38 39 }
39 40
40 41 export interface StreamingManageRecord {
... ... @@ -106,4 +107,14 @@ export interface CameraRecord {
106 107 channelNo: string;
107 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 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 80 </BasicTable>
81 81 </PageWrapper>
82 82 <CameraDrawer @register="registerDrawer" @success="handleSuccess" />
83   - <VideoPreviewModal @register="registerModal" />
84   - <VideoModal @register="registerModal1" />
  83 + <VideoModal @register="registerModal" />
85 84 <GBTDrawer @register="registerGBTDrawer" @success="handleGBTSuccess" />
86 85 </div>
87 86 </template>
... ... @@ -103,14 +102,16 @@
103 102 CameraPermission,
104 103 VideoPlatformEnum,
105 104 accessModeConfig,
  105 + getPlayUrl,
106 106 } from './config.data';
107   - import VideoPreviewModal from './DialogPreviewVideo.vue';
108 107 import { useModal } from '/@/components/Modal';
109 108 import { Authority } from '/@/components/Authority';
110 109 import { useBatchDelete } from '/@/hooks/web/useBatchDelete';
111 110 import { Popconfirm } from 'ant-design-vue';
112 111 import { Tag } from 'ant-design-vue';
113 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 116 export default defineComponent({
116 117 components: {
... ... @@ -119,7 +120,6 @@
119 120 BasicTable,
120 121 TableAction,
121 122 CameraDrawer,
122   - VideoPreviewModal,
123 123 VideoModal,
124 124 TableImg,
125 125 Authority,
... ... @@ -131,9 +131,8 @@
131 131 setup(_, { emit }) {
132 132 const searchInfo = reactive<Recordable>({});
133 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 136 // 表格hooks
138 137 const [registerTable, { reload, setProps, clearSelectedRowKeys }] = useTable({
139 138 title: '视频列表',
... ... @@ -197,28 +196,25 @@
197 196 searchInfo.organizationId = organizationId;
198 197 handleSuccess();
199 198 };
200   - const handleViewVideo = (record) => {
201   - const { videoPlatformDTO } = record;
  199 + const handleViewVideo = (record: CameraRecord) => {
  200 + const { videoPlatformDTO, params } = record;
202 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 220 const handleSwitchMode = () => {
... ... @@ -252,7 +248,6 @@
252 248 organizationIdTreeRef,
253 249 handleViewVideo,
254 250 registerModal,
255   - registerModal1,
256 251 AccessMode,
257 252 handleSwitchMode,
258 253 CameraPermission,
... ...
... ... @@ -3,24 +3,21 @@
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, closeFlvPlay, getFlvPlayUrl } from '/@/api/camera/cameraManager';
  6 + import { cameraPage, closeFlvPlay } 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';
10 10 import GBTDrawer from './components/GBTDrawer.vue';
11 11 import { useDrawer } from '/@/components/Drawer';
12   - import { AccessMode, CameraPermission, PageMode } from './config.data';
  12 + import { CameraPermission, getPlayUrl, PageMode } from './config.data';
13 13 import SvgIcon from '/@/components/Icon/src/SvgIcon.vue';
14   - import { getStreamingPlayUrl } from '/@/api/camera/cameraManager';
15 14 import { buildUUID } from '/@/utils/uuid';
16   - import { BasicVideoPlay, getVideoTypeByUrl } from '/@/components/Video';
17   - import { VideoJsPlayerOptions } from 'video.js';
18 15 import { getBoundingClientRect } from '/@/utils/domUtils';
19 16 import { Authority } from '/@/components/Authority';
20 17 import { isRtspProtocol } from '/@/components/Video/src/utils';
21 18 import { useFingerprint } from '/@/utils/useFingerprint';
22 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 22 const props = defineProps({
26 23 mode: {
... ... @@ -29,17 +26,12 @@
29 26 },
30 27 });
31 28 type CameraRecordItem = CameraRecord & {
  29 + placeholder?: boolean;
32 30 canPlay?: boolean;
33 31 isTransform?: boolean;
34 32 withToken?: boolean;
35   - videoPlayerOptions?: VideoJsPlayerOptions;
36 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 37 const emit = defineEmits(['switchMode']);
... ... @@ -88,10 +80,7 @@
88 80
89 81 for (const item of unref(cameraList)) {
90 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 85 } catch (error) {
97 86 } finally {
... ... @@ -100,75 +89,11 @@
100 89 };
101 90
102 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 99 const gridLayout = ref({ gutter: 1, column: 2 });
... ... @@ -316,7 +241,7 @@
316 241 :grid="(gridLayout as any)"
317 242 :style="{ '--height': `${100 / pagination.colNumber}%` }"
318 243 >
319   - <template #renderItem="{ item }">
  244 + <template #renderItem="{ item }: { item: CameraRecordItem }">
320 245 <List.Item>
321 246 <div class="box-border w-full !h-full p-1px">
322 247 <div
... ... @@ -332,10 +257,12 @@
332 257 v-show="!item.isTransform"
333 258 :spinning="!item.isTransform"
334 259 />
335   - <BasicVideoPlay
  260 + <XGPlayer
336 261 v-if="item.isTransform"
337   - :options="item.videoPlayerOptions"
  262 + :url="item.playSourceUrl"
  263 + :stream-type="item.streamType"
338 264 :with-token="item.withToken"
  265 + :config="{ width: '100%', height: '100%' }"
339 266 @on-unmounted="handleCloseFlvPlayUrl(item)"
340 267 />
341 268 <div
... ...
... ... @@ -6,7 +6,6 @@ import { h } from 'vue';
6 6 import SnHelpMessage from './SnHelpMessage.vue';
7 7 import SnHelpMessage1 from './SnHelpMessage1.vue';
8 8 import { OrgTreeSelect } from '../../common/OrgTreeSelect';
9   -import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
10 9 import { createImgPreview } from '/@/components/Preview';
11 10 import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter';
12 11 import { findDictItemByCode } from '/@/api/system/dict';
... ... @@ -15,7 +14,24 @@ import { DataSourceField } from '../../visual/packages/config/common.config';
15 14 import { getMeetTheConditionsDevice } from '/@/api/dataBoard';
16 15 import { useMessage } from '/@/hooks/web/useMessage';
17 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 36 useComponentRegister('OrgTreeSelect', OrgTreeSelect);
21 37
... ... @@ -604,3 +620,37 @@ export const formGBTSchema: QFormSchema[] = [
604 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 1 import { h } from 'vue';
2 2 import { BasicColumn, FormSchema } from '/@/components/Table';
3 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 15 export enum GBT28181_DEVICE_PERMISSION_ENUM {
... ...
... ... @@ -8,7 +8,7 @@
8 8 <a-button type="primary" @click="handleSyncPlay">通道同步</a-button>
9 9 </Authority>
10 10 </template>
11   - <template #hasAudio="{ record }">
  11 + <template #hasAudio="{ record }: { record: VideoChannelItemType }">
12 12 <Switch
13 13 :checked="record.status === 1"
14 14 :loading="record.pendingStatus"
... ... @@ -41,16 +41,22 @@
41 41 </template>
42 42
43 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 50 import { BasicTable, useTable, TableAction } from '/@/components/Table';
46 51 import { Switch } from 'ant-design-vue';
47 52 import { DeviceRecord } from '/@/api/device/model/deviceModel';
48 53 import VideoModal from './videoModal.vue';
49 54 import { useModal } from '/@/components/Modal';
50 55 import { useMessage } from '/@/hooks/web/useMessage';
51   - import { getVideoChannelList } from '/@/api/device/videoChannel';
  56 + import { getVideoChannelList, getVideoControlStart } from '/@/api/device/videoChannel';
52 57 import { stopOnDemandVideoApiGet, syncVideoApiGet } from '/@/api/camera/cameraManager';
53 58 import { Authority } from '/@/components/Authority';
  59 + import { VideoChannelItemType } from '/@/api/device/model/videoChannelModel';
54 60
55 61 const props = defineProps<{ deviceDetail: DeviceRecord }>();
56 62
... ... @@ -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 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 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 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 4 import { QuestionCircleOutlined } from '@ant-design/icons-vue';
12 5 import {
13 6 CaretUpOutlined,
... ... @@ -19,106 +12,49 @@
19 12 ZoomOutOutlined,
20 13 } from '@ant-design/icons-vue';
21 14 import { Button, Slider } from 'ant-design-vue';
22   - import { nextTick } from 'vue';
23 15 import { controlling } from '/@/api/camera/cameraManager';
24 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 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 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 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 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 56 const handleGBTControl = (command: string, action?: number | string) => {
121   - const { tbDeviceId, channelId } = props.GBTOption;
  57 + const { tbDeviceId, channelId } = props.options || {};
122 58 setVideoControl(tbDeviceId, channelId, {
123 59 command,
124 60 horizonSpeed: action,
... ... @@ -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 69 handleGBTControl(action, unref(sliderValue));
158 70 return;
159 71 }
... ... @@ -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 78 handleGBTControl('STOP', unref(sliderValue));
167 79 return;
168 80 }
169 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 83 </script>
184 84
185 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 99 <Tooltip>
201   - <template #title
202   - >长按按钮调用API,由于视频流媒体提供的流,有一定时延,请耐心等待。</template
203   - >
  100 + <template #title>
  101 + 长按按钮调用API,由于视频流媒体提供的流,有一定时延,请耐心等待。
  102 + </template>
204 103 <label class="validate-dot">云台控制</label>
205 104 <QuestionCircleOutlined :style="{ fontSize: '14px', marginLeft: '5px' }" />
206 105 </Tooltip>
... ... @@ -248,7 +147,7 @@
248 147 <Button class="circle" @click="handleClick" />
249 148 </div>
250 149 </div>
251   - <div class="mt-5" v-if="getGBT">
  150 + <div class="mt-5" v-if="options.isGBT">
252 151 <Slider v-model:value="sliderValue" :min="0" :max="255" style="width: 130px" />
253 152 </div>
254 153 <div class="flex justify-center mt-8">
... ... @@ -274,16 +173,6 @@
274 173 </template>
275 174
276 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 176 .child {
288 177 position: absolute;
289 178 width: 3rem;
... ...
1 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 16 </template>
28 17 <script setup lang="ts">
29   - import { ref, reactive } from 'vue';
  18 + import { ref } from 'vue';
30 19 import { BasicModal, useModalInner } from '/@/components/Modal';
31   - import { VideoJsPlayerOptions } from 'video.js';
32 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 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 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 29 try {
70 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 39 } catch (error) {
87 40 } finally {
88 41 setModalProps({ loading: false });
... ... @@ -91,18 +44,6 @@
91 44 );
92 45
93 46 const handleCancel = () => {
94   - showVideo.value = false;
95   - withToken.value = false;
96 47 emit('reloadTable');
97 48 };
98 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 13
14 14 DEVICE_ID = 'deviceId',
15 15 CHANNEL_ID = 'channelId',
  16 + PLAY_PROTOCOL = 'playProtocol',
16 17 }
17 18
18 19 export interface DataSourceValueType {
... ... @@ -129,6 +130,7 @@ export const formSchemas: FormSchema[] = [
129 130 accessMode === AccessMode.GBT28181 ? options.params?.channelNo : null,
130 131 [FormFieldEnum.DEVICE_ID]:
131 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 148 component: 'Input',
147 149 ifShow: false,
148 150 },
  151 + {
  152 + field: FormFieldEnum.PLAY_PROTOCOL,
  153 + label: '播放协议',
  154 + component: 'InputNumber',
  155 + ifShow: false,
  156 + },
149 157 ];
... ...
1 1 <script lang="ts" setup>
2 2 import { ComponentMode, ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 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 8 import { ref } from 'vue';
11 9 import { Spin } from 'ant-design-vue';
12 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 14 const props = defineProps<{
19 15 config: ComponentPropsConfigType<typeof option>;
... ... @@ -21,24 +17,12 @@
21 17
22 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 23 const exampleVideoPlay =
33 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 26 const { getResult } = useFingerprint();
43 27
44 28 const handleGetVideoPlayUrl = async () => {
... ... @@ -46,45 +30,19 @@
46 30 const { config } = props;
47 31 const { option } = config;
48 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 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 46 } finally {
89 47 loading.value = false;
90 48 }
... ... @@ -92,20 +50,8 @@
92 50
93 51 const handleSelectPreview = () => {
94 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 57 onMounted(() => {
... ... @@ -126,11 +72,10 @@
126 72 <template>
127 73 <main class="w-full h-full flex flex-col justify-center items-center p-2">
128 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 80 </Spin>
136 81 </main>
... ...