Commit 89d62489fc04f3ca7683ea296cf54dbd9f003dc1
Merge branch 'feat/video-component-support-rtsp-protocol' into 'main_dev'
Merge branch 'ww' into 'main_dev' See merge request yunteng/thingskit-view!80
Showing
4 changed files
with
131 additions
and
12 deletions
@@ -19,6 +19,7 @@ | @@ -19,6 +19,7 @@ | ||
19 | "dependencies": { | 19 | "dependencies": { |
20 | "@amap/amap-jsapi-loader": "^1.0.1", | 20 | "@amap/amap-jsapi-loader": "^1.0.1", |
21 | "@amap/amap-jsapi-types": "^0.0.8", | 21 | "@amap/amap-jsapi-types": "^0.0.8", |
22 | + "@fingerprintjs/fingerprintjs": "^3.4.1", | ||
22 | "@types/color": "^3.0.3", | 23 | "@types/color": "^3.0.3", |
23 | "@types/crypto-js": "^4.1.1", | 24 | "@types/crypto-js": "^4.1.1", |
24 | "@types/keymaster": "^1.6.30", | 25 | "@types/keymaster": "^1.6.30", |
@@ -32,6 +33,7 @@ | @@ -32,6 +33,7 @@ | ||
32 | "echarts-liquidfill": "^3.1.0", | 33 | "echarts-liquidfill": "^3.1.0", |
33 | "echarts-stat": "^1.2.0", | 34 | "echarts-stat": "^1.2.0", |
34 | "echarts-wordcloud": "^2.0.0", | 35 | "echarts-wordcloud": "^2.0.0", |
36 | + "flv.js": "^1.6.2", | ||
35 | "gsap": "^3.11.3", | 37 | "gsap": "^3.11.3", |
36 | "highlight.js": "^11.5.0", | 38 | "highlight.js": "^11.5.0", |
37 | "html2canvas": "^1.4.1", | 39 | "html2canvas": "^1.4.1", |
@@ -45,6 +47,7 @@ | @@ -45,6 +47,7 @@ | ||
45 | "screenfull": "^6.0.1", | 47 | "screenfull": "^6.0.1", |
46 | "three": "^0.145.0", | 48 | "three": "^0.145.0", |
47 | "video.js": "^7.20.3", | 49 | "video.js": "^7.20.3", |
50 | + "videojs-flvjs-es6": "^1.0.1", | ||
48 | "vue": "^3.2.31", | 51 | "vue": "^3.2.31", |
49 | "vue-3d-loader": "^2.1.7", | 52 | "vue-3d-loader": "^2.1.7", |
50 | "vue-demi": "^0.13.1", | 53 | "vue-demi": "^0.13.1", |
src/api/external/flvPlay/index.ts
0 → 100644
1 | +import { defHttp } from "@/utils/external/http/axios"; | ||
2 | +import { useGlobSetting } from '@/hooks/external/setting'; | ||
3 | +enum Api { | ||
4 | + OPEN_FLV = '/rtsp/openFlv', | ||
5 | + CLOSE_FLV = '/rtsp/closeFlv' | ||
6 | +} | ||
7 | + | ||
8 | +export const getOpenFlvPlayUrl = (url: string, browserId: string) => { | ||
9 | + const { urlPrefix, apiUrl } = useGlobSetting() | ||
10 | + return `${apiUrl || ''}${urlPrefix || ''}${Api.OPEN_FLV}?url=${encodeURIComponent(url)}&browserId=${browserId}`; | ||
11 | +}; | ||
12 | + | ||
13 | +export const closeFlvPlay = (url: string, browserId: string) => { | ||
14 | + return defHttp.get({ | ||
15 | + url: Api.CLOSE_FLV, | ||
16 | + params: { | ||
17 | + url: encodeURIComponent(url), | ||
18 | + browserId | ||
19 | + } | ||
20 | + }); | ||
21 | +}; |
1 | <template> | 1 | <template> |
2 | <div class="go-content-box" :style="{ width: w + 'px', height: h + 'px' }"> | 2 | <div class="go-content-box" :style="{ width: w + 'px', height: h + 'px' }"> |
3 | - <video | ||
4 | - crossOrigin="anonymous" | ||
5 | - :id="`my-player${index}`" ref="videoRef" class="video-js my-video vjs-theme-city vjs-big-play-centered"> | ||
6 | - <source :src="sourceSrc" /> | 3 | + <video crossOrigin="anonymous" :id="`my-player${index}`" ref="videoRef" |
4 | + class="video-js my-video vjs-theme-city vjs-big-play-centered"> | ||
7 | </video> | 5 | </video> |
8 | </div> | 6 | </div> |
9 | </template> | 7 | </template> |
10 | <script setup lang="ts"> | 8 | <script setup lang="ts"> |
11 | -import { onMounted, ref, onUnmounted, watch } from 'vue' | 9 | +import { onMounted, ref, onUnmounted, watch, unref } from 'vue' |
12 | import videojs from 'video.js' | 10 | import videojs from 'video.js' |
11 | +import 'videojs-flvjs-es6' | ||
13 | import type { VideoJsPlayerOptions } from 'video.js' | 12 | import type { VideoJsPlayerOptions } from 'video.js' |
14 | import 'video.js/dist/video-js.min.css' | 13 | import 'video.js/dist/video-js.min.css' |
14 | +import { getJwtToken, getShareJwtToken } from '@/utils/external/auth' | ||
15 | +import { isShareMode } from '@/views/share/hook' | ||
16 | +import { getOpenFlvPlayUrl, closeFlvPlay } from '@/api/external/flvPlay' | ||
17 | +import { useFingerprint } from '@/utils/external/useFingerprint' | ||
18 | +import { GetResult } from '@fingerprintjs/fingerprintjs' | ||
15 | 19 | ||
16 | const props = defineProps({ | 20 | const props = defineProps({ |
17 | sourceSrc: { | 21 | sourceSrc: { |
@@ -36,40 +40,117 @@ const props = defineProps({ | @@ -36,40 +40,117 @@ const props = defineProps({ | ||
36 | } | 40 | } |
37 | }) | 41 | }) |
38 | 42 | ||
43 | +enum VideoPlayerType { | ||
44 | + m3u8 = 'application/x-mpegURL', | ||
45 | + mp4 = 'video/mp4', | ||
46 | + webm = 'video/webm', | ||
47 | + flv = 'video/x-flv', | ||
48 | +} | ||
49 | + | ||
50 | +const isRtspProtocol = (url: string) => { | ||
51 | + const reg = /^rtsp:\/\//g; | ||
52 | + return reg.test(url); | ||
53 | +}; | ||
54 | + | ||
55 | +const getVideoTypeByUrl = (url = '') => { | ||
56 | + try { | ||
57 | + const { protocol, pathname } = new URL(url) | ||
58 | + | ||
59 | + if (protocol.startsWith('rtsp:')) return VideoPlayerType.flv | ||
60 | + | ||
61 | + const reg = /[^.]\w*$/ | ||
62 | + const mathValue = pathname.match(reg) || [] | ||
63 | + const ext = mathValue[0] as keyof typeof VideoPlayerType || 'webm' | ||
64 | + const type = VideoPlayerType[ext] | ||
65 | + return type ? type : VideoPlayerType.webm | ||
66 | + } catch (error) { | ||
67 | + console.error(error) | ||
68 | + return VideoPlayerType.webm | ||
69 | + } | ||
70 | +}; | ||
71 | + | ||
72 | + | ||
39 | // video标签 | 73 | // video标签 |
40 | const videoRef = ref<HTMLElement | null>(null) | 74 | const videoRef = ref<HTMLElement | null>(null) |
41 | 75 | ||
42 | // video实例对象 | 76 | // video实例对象 |
43 | let videoPlayer: videojs.Player | null = null | 77 | let videoPlayer: videojs.Player | null = null |
44 | 78 | ||
79 | +const fingerprintResult = ref<Nullable<GetResult>>(null) | ||
80 | + | ||
45 | //options配置 | 81 | //options配置 |
46 | -const options: VideoJsPlayerOptions = { | 82 | +const options: VideoJsPlayerOptions & Recordable = { |
47 | language: 'zh-CN', // 设置语言 | 83 | language: 'zh-CN', // 设置语言 |
48 | controls: true, // 是否显示控制条 | 84 | controls: true, // 是否显示控制条 |
49 | preload: 'auto', // 预加载 | 85 | preload: 'auto', // 预加载 |
50 | autoplay: true, // 是否自动播放 | 86 | autoplay: true, // 是否自动播放 |
51 | fluid: false, // 自适应宽高 | 87 | fluid: false, // 自适应宽高 |
52 | poster: props?.avatar || '', | 88 | poster: props?.avatar || '', |
53 | - src: props?.sourceSrc || '', // 要嵌入的视频源的源 URL | 89 | + // src: getSource() || '', // 要嵌入的视频源的源 URL |
90 | + sources: [], | ||
54 | muted: true, | 91 | muted: true, |
55 | userActions: { | 92 | userActions: { |
56 | hotkeys: true | 93 | hotkeys: true |
94 | + }, | ||
95 | + techOrder: ['html5', 'flvjs'], | ||
96 | + flvjs: { | ||
97 | + mediaDataSource: { | ||
98 | + isLive: true, | ||
99 | + cors: true, | ||
100 | + withCredentials: false, | ||
101 | + hasAudio: false, | ||
102 | + autoCleanupSourceBuffer: true, | ||
103 | + autoCleanupMaxBackwardDuration: 60, | ||
104 | + }, | ||
105 | + }, | ||
106 | +} | ||
107 | + | ||
108 | + | ||
109 | +const { getResult } = useFingerprint() | ||
110 | +async function getSource() { | ||
111 | + fingerprintResult.value = await getResult() | ||
112 | + let src = props.sourceSrc || '' | ||
113 | + | ||
114 | + if (isRtspProtocol(props.sourceSrc!)) { | ||
115 | + src = getOpenFlvPlayUrl(src, unref(fingerprintResult)?.visitorId || '') | ||
57 | } | 116 | } |
117 | + | ||
118 | + return [ | ||
119 | + { | ||
120 | + type: getVideoTypeByUrl(props.sourceSrc), | ||
121 | + src | ||
122 | + } | ||
123 | + ] | ||
58 | } | 124 | } |
59 | 125 | ||
60 | // 初始化videojs | 126 | // 初始化videojs |
61 | -const initVideo = () => { | 127 | +const initVideo = async () => { |
62 | if (videoRef.value) { | 128 | if (videoRef.value) { |
63 | - // 创建 video 实例 | ||
64 | - videoPlayer = videojs(videoRef.value, options) | 129 | + // 创建 video 实例 |
130 | + options.sources = await getSource() | ||
131 | + if (options.sources && options.sources.length) { | ||
132 | + | ||
133 | + if (isRtspProtocol(props.sourceSrc || '')) { | ||
134 | + options.flvjs = { | ||
135 | + ...(options.flvjs || {}), | ||
136 | + config: { | ||
137 | + headers: { | ||
138 | + 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`, | ||
139 | + } | ||
140 | + } | ||
141 | + } | ||
142 | + } | ||
143 | + videoPlayer = videojs(videoRef.value, options) | ||
144 | + } | ||
65 | } | 145 | } |
66 | } | 146 | } |
67 | 147 | ||
68 | watch( | 148 | watch( |
69 | () => props.sourceSrc, | 149 | () => props.sourceSrc, |
70 | - (newData: any) => { | 150 | + async (newData: any) => { |
151 | + const result = await getSource() | ||
71 | // props.sourceSrc = newData | 152 | // props.sourceSrc = newData |
72 | - videoPlayer?.src(newData) as any | 153 | + videoPlayer?.src(result) as any |
73 | videoPlayer?.play() | 154 | videoPlayer?.play() |
74 | }, | 155 | }, |
75 | { | 156 | { |
@@ -82,6 +163,9 @@ onMounted(() => { | @@ -82,6 +163,9 @@ onMounted(() => { | ||
82 | }) | 163 | }) |
83 | 164 | ||
84 | onUnmounted(() => { | 165 | onUnmounted(() => { |
166 | + if (props.sourceSrc) { | ||
167 | + closeFlvPlay(props.sourceSrc, unref(fingerprintResult)!.visitorId!) | ||
168 | + } | ||
85 | handleVideoDispose() | 169 | handleVideoDispose() |
86 | }) | 170 | }) |
87 | 171 | ||
@@ -101,6 +185,7 @@ defineExpose({ | @@ -101,6 +185,7 @@ defineExpose({ | ||
101 | display: flex; | 185 | display: flex; |
102 | align-items: center; | 186 | align-items: center; |
103 | justify-content: center; | 187 | justify-content: center; |
188 | + | ||
104 | .my-video { | 189 | .my-video { |
105 | width: 100% !important; | 190 | width: 100% !important; |
106 | height: 100% !important; | 191 | height: 100% !important; |
src/utils/external/useFingerprint.ts
0 → 100644