Commit 5e6df27d254f5e9402ba7649f2f4e51fb621dc4c
1 parent
b6587d76
feat(src/packages): 修改信息下的单个摄像头,如果是rtsp,则调用接口进行播放
Showing
2 changed files
with
171 additions
and
81 deletions
1 | <template> | 1 | <template> |
2 | - <div class="videoPlay"> | 2 | + <div class="go-content-box" :style="{ width: w + 'px', height: h + 'px' }"> |
3 | <video | 3 | <video |
4 | - style="object-fit: cover" | ||
5 | - :poster="poster" | ||
6 | crossOrigin="anonymous" | 4 | crossOrigin="anonymous" |
5 | + :id="`my-player`" | ||
7 | ref="videoRef" | 6 | ref="videoRef" |
8 | - class="video-js vjs-default-skin vjs-big-play-centered" | ||
9 | - controls | ||
10 | - > | ||
11 | - <source :src="path" /> | ||
12 | - </video> | 7 | + class="video-js my-video vjs-theme-city vjs-big-play-centered" |
8 | + ></video> | ||
13 | </div> | 9 | </div> |
14 | </template> | 10 | </template> |
15 | -<script lang="ts" setup> | ||
16 | -import { nextTick, onBeforeUnmount, onMounted, ref, watch, toRefs } from 'vue' | ||
17 | -import videojs, { VideoJsPlayer } from 'video.js' | 11 | +<script setup lang="ts"> |
12 | +import { onMounted, ref, onUnmounted, watch, unref } from 'vue' | ||
13 | +import videojs from 'video.js' | ||
14 | +import 'videojs-flvjs-es6' | ||
18 | import type { VideoJsPlayerOptions } from 'video.js' | 15 | import type { VideoJsPlayerOptions } from 'video.js' |
19 | -import 'video.js/dist/video-js.css' | ||
20 | -import zh from 'video.js/dist/lang/zh-CN.json' | ||
21 | - | ||
22 | -const props = withDefaults( | ||
23 | - defineProps<{ | ||
24 | - path: string | ||
25 | - autoPlay?: boolean | ||
26 | - h?: number | ||
27 | - poster?: string | ||
28 | - }>(), | ||
29 | - { autoPlay: false } | ||
30 | -) | 16 | +import 'video.js/dist/video-js.min.css' |
17 | +import { getJwtToken, getShareJwtToken } from '@/utils/external/auth' | ||
18 | +import { isShareMode } from '@/views/share/hook' | ||
19 | +import { getOpenFlvPlayUrl, closeFlvPlay } from '@/api/external/flvPlay' | ||
20 | +import { useFingerprint } from '@/utils/external/useFingerprint' | ||
21 | +import { GetResult } from '@fingerprintjs/fingerprintjs' | ||
31 | 22 | ||
32 | -const videoRef = ref() | ||
33 | - | ||
34 | -let player: VideoJsPlayer | ||
35 | - | ||
36 | -const initPlay = async () => { | ||
37 | - videojs.addLanguage('zh-CN', zh) | ||
38 | - await nextTick() | ||
39 | - const options: VideoJsPlayerOptions = { | ||
40 | - muted: true, | ||
41 | - controls: true, | ||
42 | - autoplay: props.autoPlay, | ||
43 | - src: props?.path, | ||
44 | - poster: props?.poster, | ||
45 | - language: 'zh-CN', | ||
46 | - techOrder: ['html5'], | ||
47 | - preload: 'none' | 23 | +const props = defineProps({ |
24 | + sourceSrc: { | ||
25 | + type: String | ||
26 | + }, | ||
27 | + autoplay: { | ||
28 | + type: Boolean | ||
29 | + }, | ||
30 | + name: { | ||
31 | + type: String | ||
32 | + }, | ||
33 | + avatar: { | ||
34 | + type: String | ||
35 | + }, | ||
36 | + w: { | ||
37 | + type: Number, | ||
38 | + default: 300 | ||
39 | + }, | ||
40 | + h: { | ||
41 | + type: Number, | ||
42 | + default: 300 | ||
48 | } | 43 | } |
49 | - player = videojs(videoRef.value, options, () => { | ||
50 | - videojs.log('Video is reading') | ||
51 | - if (props.autoPlay) { | ||
52 | - player.play() | 44 | +}) |
45 | + | ||
46 | +enum VideoPlayerType { | ||
47 | + m3u8 = 'application/x-mpegURL', | ||
48 | + mp4 = 'video/mp4', | ||
49 | + webm = 'video/webm', | ||
50 | + flv = 'video/x-flv' | ||
51 | +} | ||
52 | + | ||
53 | +const isRtspProtocol = (url: string) => { | ||
54 | + const reg = /^rtsp:\/\//g | ||
55 | + return reg.test(url) | ||
56 | +} | ||
57 | + | ||
58 | +const getVideoTypeByUrl = (url = '') => { | ||
59 | + try { | ||
60 | + const { protocol, pathname } = new URL(url) | ||
61 | + | ||
62 | + if (protocol.startsWith('rtsp:')) return VideoPlayerType.flv | ||
63 | + | ||
64 | + const reg = /[^.]\w*$/ | ||
65 | + const mathValue = pathname.match(reg) || [] | ||
66 | + const ext = (mathValue[0] as keyof typeof VideoPlayerType) || 'webm' | ||
67 | + const type = VideoPlayerType[ext] | ||
68 | + return type ? type : VideoPlayerType.webm | ||
69 | + } catch (error) { | ||
70 | + console.error(error) | ||
71 | + return VideoPlayerType.webm | ||
72 | + } | ||
73 | +} | ||
74 | + | ||
75 | +// video标签 | ||
76 | +const videoRef = ref<HTMLElement | null>(null) | ||
77 | + | ||
78 | +// video实例对象 | ||
79 | +let videoPlayer: videojs.Player | null = null | ||
80 | + | ||
81 | +const fingerprintResult = ref<Nullable<GetResult>>(null) | ||
82 | + | ||
83 | +//options配置 | ||
84 | +const options: VideoJsPlayerOptions & Recordable = { | ||
85 | + language: 'zh-CN', // 设置语言 | ||
86 | + controls: true, // 是否显示控制条 | ||
87 | + preload: 'auto', // 预加载 | ||
88 | + autoplay: true, // 是否自动播放 | ||
89 | + fluid: false, // 自适应宽高 | ||
90 | + poster: props?.avatar || '', | ||
91 | + // src: getSource() || '', // 要嵌入的视频源的源 URL | ||
92 | + sources: [], | ||
93 | + muted: true, | ||
94 | + userActions: { | ||
95 | + hotkeys: true | ||
96 | + }, | ||
97 | + techOrder: ['html5', 'flvjs'], | ||
98 | + flvjs: { | ||
99 | + mediaDataSource: { | ||
100 | + isLive: true, | ||
101 | + cors: true, | ||
102 | + withCredentials: false, | ||
103 | + hasAudio: false | ||
104 | + }, | ||
105 | + config: { | ||
106 | + autoCleanupSourceBuffer: true | ||
53 | } | 107 | } |
54 | - player.on('ended', () => { | ||
55 | - videojs.log('Play end') | ||
56 | - }) | ||
57 | - player.on('error', () => { | ||
58 | - player.errorDisplay.close() | ||
59 | - videojs.log('Play parse error') | ||
60 | - }) | ||
61 | - }) | 108 | + } |
62 | } | 109 | } |
63 | 110 | ||
64 | -onMounted(() => { | ||
65 | - initPlay() | ||
66 | -}) | 111 | +const { getResult } = useFingerprint() |
112 | +async function getSource() { | ||
113 | + fingerprintResult.value = await getResult() | ||
114 | + let src = props.sourceSrc || '' | ||
67 | 115 | ||
68 | -//直接改变路径测试 | ||
69 | -watch( | ||
70 | - () => props.path, | ||
71 | - () => { | ||
72 | - if (props.path && props.autoPlay) { | ||
73 | - try { | ||
74 | - player?.pause() | ||
75 | - player?.load() | ||
76 | - player?.src(props.path) | ||
77 | - player?.play() | ||
78 | - } catch (e) { | ||
79 | - console.log(e) | 116 | + if (isRtspProtocol(props.sourceSrc!)) { |
117 | + src = getOpenFlvPlayUrl(src, unref(fingerprintResult)?.visitorId || '') | ||
118 | + } | ||
119 | + | ||
120 | + return [ | ||
121 | + { | ||
122 | + type: getVideoTypeByUrl(props.sourceSrc), | ||
123 | + src | ||
124 | + } | ||
125 | + ] | ||
126 | +} | ||
127 | + | ||
128 | +// 初始化videojs | ||
129 | +const initVideo = async () => { | ||
130 | + if (videoRef.value) { | ||
131 | + // 创建 video 实例 | ||
132 | + options.sources = await getSource() | ||
133 | + if (options.sources && options.sources.length) { | ||
134 | + if (isRtspProtocol(props.sourceSrc || '')) { | ||
135 | + options.flvjs = { | ||
136 | + ...(options.flvjs || {}), | ||
137 | + config: { | ||
138 | + headers: { | ||
139 | + 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}` | ||
140 | + } | ||
141 | + } | ||
142 | + } | ||
80 | } | 143 | } |
144 | + videoPlayer = videojs(videoRef.value, options) | ||
81 | } | 145 | } |
146 | + } | ||
147 | +} | ||
148 | + | ||
149 | +watch( | ||
150 | + () => props.sourceSrc, | ||
151 | + async (newData: any) => { | ||
152 | + const result = await getSource() | ||
153 | + // props.sourceSrc = newData | ||
154 | + videoPlayer?.src(result) as any | ||
155 | + videoPlayer?.play() | ||
82 | }, | 156 | }, |
83 | { | 157 | { |
84 | immediate: true | 158 | immediate: true |
85 | } | 159 | } |
86 | ) | 160 | ) |
87 | 161 | ||
88 | -onBeforeUnmount(() => { | ||
89 | - player?.dispose() | 162 | +onMounted(() => { |
163 | + initVideo() | ||
164 | +}) | ||
165 | + | ||
166 | +onUnmounted(() => { | ||
167 | + if (props.sourceSrc) { | ||
168 | + closeFlvPlay(props.sourceSrc, unref(fingerprintResult)!.visitorId!) | ||
169 | + } | ||
170 | + handleVideoDispose() | ||
171 | +}) | ||
172 | + | ||
173 | +//播放 | ||
174 | +const handleVideoPlay = () => videoPlayer?.play() | ||
175 | + | ||
176 | +const handleVideoDispose = () => videoPlayer?.dispose() && videoPlayer?.pause() | ||
177 | +//暂停 | ||
178 | +defineExpose({ | ||
179 | + handleVideoPlay, | ||
180 | + handleVideoDispose | ||
90 | }) | 181 | }) |
91 | </script> | 182 | </script> |
92 | -<style> | ||
93 | -.vjs-poster { | ||
94 | - background-size: cover !important; | ||
95 | -} | ||
96 | -</style> | 183 | + |
97 | <style lang="scss" scoped> | 184 | <style lang="scss" scoped> |
98 | -.videoPlay { | ||
99 | - flex: 1; | ||
100 | - height: v-bind('`${h}px`'); | ||
101 | - .video-js { | ||
102 | - height: 100%; | ||
103 | - width: 100%; | 185 | +.go-content-box { |
186 | + display: flex; | ||
187 | + align-items: center; | ||
188 | + justify-content: center; | ||
189 | + | ||
190 | + .my-video { | ||
191 | + width: 100% !important; | ||
192 | + height: 100% !important; | ||
193 | + position: relative; | ||
104 | } | 194 | } |
105 | } | 195 | } |
106 | </style> | 196 | </style> |
1 | <template> | 1 | <template> |
2 | <div> | 2 | <div> |
3 | - <VideoPlay :h="h" :path="option.dataset" :autoPlay="autoplay" :poster="option.poster" /> | 3 | + <VideoPlay :w="w" :h="h" :sourceSrc="option.dataset" :autoPlay="autoplay" :avatar="option.poster" /> |
4 | </div> | 4 | </div> |
5 | </template> | 5 | </template> |
6 | <script setup lang="ts"> | 6 | <script setup lang="ts"> |
@@ -16,7 +16,7 @@ const props = defineProps({ | @@ -16,7 +16,7 @@ const props = defineProps({ | ||
16 | } | 16 | } |
17 | }) | 17 | }) |
18 | 18 | ||
19 | -const { h } = toRefs(props.chartConfig.attr) | 19 | +const { w, h } = toRefs(props.chartConfig.attr) |
20 | 20 | ||
21 | const { autoplay, dataset, poster, url } = toRefs(props.chartConfig.option) | 21 | const { autoplay, dataset, poster, url } = toRefs(props.chartConfig.option) |
22 | 22 |