Commit 89d62489fc04f3ca7683ea296cf54dbd9f003dc1

Authored by xp.Huang
2 parents 5fc22f51 e13bf6d3

Merge branch 'feat/video-component-support-rtsp-protocol' into 'main_dev'

Merge branch 'ww' into 'main_dev'

See merge request yunteng/thingskit-view!80
@@ -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",
  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;
  1 +import { load } from '@fingerprintjs/fingerprintjs';
  2 +export const useFingerprint = () => {
  3 + const getResult = async () => {
  4 + const fp = await load();
  5 + const result = await fp.get();
  6 + return result;
  7 + };
  8 +
  9 + return { getResult };
  10 +};