Commit 39b6a248890e3d3692bc65f9860e6e4a0d8c2e3e

Authored by ww
1 parent 2bcdadc2

perf: 优化当摄像头组件逻辑

1 <template> 1 <template>
2 - <div class="go-content-box" :style="{ width: baseSize?.w + 'px', height: baseSize?.h + 'px' }">  
3 - <video  
4 - crossOrigin="anonymous"  
5 - :id="`my-player`"  
6 - ref="videoRef"  
7 - class="video-js my-video vjs-theme-city vjs-big-play-centered"  
8 - ></video> 2 + <div ref="playerContainerElRef" class="go-content-box"
  3 + :style="{ width: baseSize?.w + 'px', height: baseSize?.h + 'px', background: '#000' }">
  4 +
9 </div> 5 </div>
10 </template> 6 </template>
11 <script setup lang="ts"> 7 <script setup lang="ts">
12 -import { onMounted, ref, onUnmounted, watch, unref, PropType, nextTick } from 'vue' 8 +import { ref, onUnmounted, watch, unref, PropType, shallowRef } from 'vue'
13 import videojs from 'video.js' 9 import videojs from 'video.js'
14 import 'videojs-flvjs-es6' 10 import 'videojs-flvjs-es6'
15 import type { VideoJsPlayerOptions } from 'video.js' 11 import type { VideoJsPlayerOptions } from 'video.js'
@@ -45,7 +41,7 @@ const isRtspProtocol = (url: string) => { @@ -45,7 +41,7 @@ const isRtspProtocol = (url: string) => {
45 } 41 }
46 42
47 const getVideoTypeByUrl = (url = '') => { 43 const getVideoTypeByUrl = (url = '') => {
48 - if(!url) return; 44 + if (!url) return;
49 try { 45 try {
50 const { protocol, pathname } = new URL(url) 46 const { protocol, pathname } = new URL(url)
51 if (protocol.startsWith('rtsp:')) return VideoPlayerTypeEnum.flv 47 if (protocol.startsWith('rtsp:')) return VideoPlayerTypeEnum.flv
@@ -60,11 +56,9 @@ const getVideoTypeByUrl = (url = '') => { @@ -60,11 +56,9 @@ const getVideoTypeByUrl = (url = '') => {
60 } 56 }
61 } 57 }
62 58
63 -// video标签  
64 -const videoRef = ref<HTMLElement | null>(null)  
65 -  
66 // video实例对象 59 // video实例对象
67 -let videoPlayer: videojs.Player | null = null 60 +const videoPlayer = shallowRef<videojs.Player | null>(null)
  61 +const playerContainerElRef = shallowRef<HTMLElement | null>(null)
68 62
69 const fingerprintResult = ref<Nullable<GetResult>>(null) 63 const fingerprintResult = ref<Nullable<GetResult>>(null)
70 64
@@ -72,11 +66,11 @@ const fingerprintResult = ref<Nullable<GetResult>>(null) @@ -72,11 +66,11 @@ const fingerprintResult = ref<Nullable<GetResult>>(null)
72 const options: VideoJsPlayerOptions & Recordable = { 66 const options: VideoJsPlayerOptions & Recordable = {
73 language: 'zh-CN', // 设置语言 67 language: 'zh-CN', // 设置语言
74 controls: true, // 是否显示控制条 68 controls: true, // 是否显示控制条
75 - autoplay: props.autoPlay ? true : false, // 是否自动播放 69 + autoplay: !!props.autoPlay, // 是否自动播放
76 fluid: false, // 自适应宽高 70 fluid: false, // 自适应宽高
77 poster: props?.avatar || '', 71 poster: props?.avatar || '',
78 sources: [], 72 sources: [],
79 - muted: props.autoPlay ? true : false, 73 + muted: !!props.autoPlay,
80 userActions: { 74 userActions: {
81 hotkeys: true 75 hotkeys: true
82 }, 76 },
@@ -110,68 +104,74 @@ async function getSource() { @@ -110,68 +104,74 @@ async function getSource() {
110 ] 104 ]
111 } 105 }
112 106
  107 +function createVideoElement() {
  108 + if (!unref(playerContainerElRef)) return
  109 + unref(playerContainerElRef)!.innerHTML = ''
  110 + const video = document.createElement('video')
  111 + video.setAttribute('crossOrigin', 'anonymous')
  112 + video.style.setProperty('width', '100%')
  113 + video.style.setProperty('height', '100%')
  114 + video.classList.add('video-js', 'my-video', 'vjs-theme-city', 'vjs-big-play-centered')
  115 + unref(playerContainerElRef)?.appendChild(video)
  116 + return video
  117 +}
  118 +
113 119
114 // 初始化videojs 120 // 初始化videojs
115 -const initVideo = async () => {  
116 - await nextTick()  
117 - if (videoRef.value) {  
118 - // 创建 video 实例  
119 - options.sources = await getSource()  
120 - if (options.sources && options.sources.length) {  
121 - if (isRtspProtocol(props.sourceSrc || '')) {  
122 - options.flvjs = {  
123 - ...(options.flvjs || {}),  
124 - config: {  
125 - headers: {  
126 - 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`  
127 - }  
128 - } 121 +const createVideoPlayer = async () => {
  122 + if (unref(videoPlayer)) dispose()
  123 +
  124 + options.sources = await getSource()
  125 +
  126 + if (isRtspProtocol(props.sourceSrc || '')) {
  127 + options.flvjs = {
  128 + ...(options.flvjs || {}),
  129 + config: {
  130 + headers: {
  131 + 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`
129 } 132 }
130 } 133 }
131 - if (options.sources.at(-1)?.src){  
132 - videoPlayer = videojs(videoRef.value, options) //fix 修复videojs解决直播延时的问题  
133 - videoPlayer?.on('timeupdate', function () {  
134 - // 计算表最新推流的时间和现在播放器播放推流的时间  
135 - let differTime =  
136 - (videoPlayer as any as videojs.Player)?.buffered()?.end(0) -  
137 - (videoPlayer as any as videojs.Player)?.currentTime() // 差值小于1.5s时根据1倍速进行播放  
138 - if (differTime < 1.5) {  
139 - videoPlayer?.playbackRate(1)  
140 - } // 差值大于1.5s小于10s根据1.2倍速进行播放  
141 - if (differTime < 10 && differTime > 1.5) {  
142 - videoPlayer?.playbackRate(1.2)  
143 - } // 差值大于10s时进行重新加载直播流  
144 - if (differTime > 10) {  
145 - initVideo()  
146 - }  
147 - }) //  
148 - }  
149 } 134 }
150 } 135 }
  136 + const video = createVideoElement()
  137 + if (!video) return
  138 +
  139 + videoPlayer.value = videojs(video!, options) //fix 修复videojs解决直播延时的问题
  140 +
  141 + videoPlayer.value?.on('timeupdate', function () {
  142 + // 计算表最新推流的时间和现在播放器播放推流的时间
  143 + let differTime =
  144 + (unref(videoPlayer))!.buffered()?.end(0) -
  145 + (unref(videoPlayer))!.currentTime() // 差值小于1.5s时根据1倍速进行播放
  146 + if (differTime < 1.5) {
  147 + videoPlayer.value?.playbackRate(1)
  148 + } // 差值大于1.5s小于10s根据1.2倍速进行播放
  149 + if (differTime < 10 && differTime > 1.5) {
  150 + videoPlayer.value?.playbackRate(1.2)
  151 + } // 差值大于10s时进行重新加载直播流
  152 + if (differTime > 10) {
  153 + createVideoPlayer()
  154 + }
  155 + })
151 } 156 }
152 157
153 -watch(  
154 - () => props.sourceSrc,  
155 - async () => {  
156 - (props as any ).sourceSrc = ''  
157 - if(!props.sourceSrc) return;  
158 - await nextTick();  
159 - if(unref(fingerprintResult)!.visitorId!) {  
160 - closeFlvPlay(props.sourceSrc!, unref(fingerprintResult)!.visitorId!)  
161 - }  
162 - videoPlayer?.src('');  
163 - initVideo()  
164 - videoPlayer?.src({  
165 - type: getVideoTypeByUrl(props.sourceSrc),  
166 - src: props.sourceSrc!  
167 - });  
168 - videoPlayer?.load();  
169 - videoPlayer?.play();  
170 - },  
171 - {  
172 - immediate: true  
173 - }  
174 -) 158 +// watch(
  159 +// () => props.sourceSrc,
  160 +// async () => {
  161 +// (props as any ).sourceSrc = ''
  162 +// if(!props.sourceSrc) return;
  163 +// await nextTick();
  164 +// if(unref(fingerprintResult)!.visitorId!) {
  165 +// closeFlvPlay(props.sourceSrc!, unref(fingerprintResult)!.visitorId!)
  166 +// }
  167 +// videoPlayer.value?.src('');
  168 +// createVideoPlayer()
  169 +
  170 +// if (props.sourceSrc) {
  171 +// reloadSource({ src: props.sourceSrc, type: getVideoTypeByUrl(props.sourceSrc) })
  172 +// }
  173 +// }
  174 +// )
175 175
176 watch( 176 watch(
177 () => props.autoPlay, 177 () => props.autoPlay,
@@ -179,14 +179,14 @@ watch( @@ -179,14 +179,14 @@ watch(
179 if (newData) { 179 if (newData) {
180 handleVideoPlay() 180 handleVideoPlay()
181 } else { 181 } else {
182 - videoPlayer?.pause() 182 + videoPlayer.value?.pause()
183 } 183 }
184 }, 184 },
185 ) 185 )
186 186
187 -onMounted(() => {  
188 - initVideo()  
189 -}) 187 +// onMounted(() => {
  188 +// createVideoPlayer()
  189 +// })
190 190
191 onUnmounted(() => { 191 onUnmounted(() => {
192 if (props.sourceSrc) { 192 if (props.sourceSrc) {
@@ -195,11 +195,35 @@ onUnmounted(() => { @@ -195,11 +195,35 @@ onUnmounted(() => {
195 handleVideoDispose() 195 handleVideoDispose()
196 }) 196 })
197 197
  198 +
  199 +function anewInit() {
  200 + createVideoPlayer()
  201 +}
  202 +
  203 +function reloadSource(sources: string | videojs.Tech.SourceObject | videojs.Tech.SourceObject[]) {
  204 + if (!unref(videoPlayer)) return
  205 + unref(videoPlayer)?.pause()
  206 + unref(videoPlayer)?.src(sources)
  207 + unref(videoPlayer)?.load()
  208 + unref(videoPlayer)?.play()
  209 +}
  210 +
  211 +function dispose() {
  212 + unref(videoPlayer)?.dispose()
  213 + videoPlayer.value = null
  214 +}
  215 +
198 //播放 216 //播放
199 -const handleVideoPlay = () => videoPlayer?.play() 217 +const handleVideoPlay = () => videoPlayer.value?.play()
200 218
201 //暂停和销毁 219 //暂停和销毁
202 -const handleVideoDispose = () => videoPlayer?.dispose() && videoPlayer?.pause() 220 +const handleVideoDispose = () => videoPlayer.value?.dispose()
  221 +
  222 +defineExpose({
  223 + dispose,
  224 + reloadSource,
  225 + anewInit,
  226 +})
203 227
204 </script> 228 </script>
205 229
@@ -46,8 +46,17 @@ export enum AccessMode { @@ -46,8 +46,17 @@ export enum AccessMode {
46 GBT28181 = 2 //GBT28181 46 GBT28181 = 2 //GBT28181
47 } 47 }
48 48
  49 +export interface Dataset {
  50 + accessMode: AccessMode
  51 + channelId?: string
  52 + customUrl?: string
  53 + deviceId?: string
  54 + id?: string
  55 + value?: string
  56 +}
  57 +
49 export const option = { 58 export const option = {
50 - dataset: '', 59 + dataset: {} as Dataset,
51 autoplay: true, 60 autoplay: true,
52 poster: '', 61 poster: '',
53 sourceType: sourceTypeEnum.CUSTOM, 62 sourceType: sourceTypeEnum.CUSTOM,
@@ -91,7 +91,7 @@ const getOriginationList = async () => { @@ -91,7 +91,7 @@ const getOriginationList = async () => {
91 } 91 }
92 92
93 const handleUpdateTreeValue = (value: string) => { 93 const handleUpdateTreeValue = (value: string) => {
94 - props.optionData.dataset = '' 94 + props.optionData.dataset = {}
95 getVideoLists(value) 95 getVideoLists(value)
96 } 96 }
97 97
@@ -147,13 +147,23 @@ const handleChecked = (value: string) => { @@ -147,13 +147,23 @@ const handleChecked = (value: string) => {
147 const handleSelect = (_: string, e: videoListInterface) => { 147 const handleSelect = (_: string, e: videoListInterface) => {
148 const { accessMode, id, customUrl, channelId, deviceId, value } = e 148 const { accessMode, id, customUrl, channelId, deviceId, value } = e
149 props.optionData.videoId = value as any 149 props.optionData.videoId = value as any
150 - if (accessMode === AccessMode.Streaming) {  
151 - getVideoUrlById(id)  
152 - } else if (accessMode === AccessMode.GBT28181) {  
153 - getVideoControlList(deviceId, channelId)  
154 - } else {  
155 - getCustomUrl(customUrl) 150 +
  151 + props.optionData.dataset = {
  152 + accessMode,
  153 + id,
  154 + customUrl,
  155 + channelId,
  156 + deviceId,
  157 + value
156 } 158 }
  159 +
  160 + // if (accessMode === AccessMode.Streaming) {
  161 + // getVideoUrlById(id)
  162 + // } else if (accessMode === AccessMode.GBT28181) {
  163 + // getVideoControlList(deviceId, channelId)
  164 + // } else {
  165 + // getCustomUrl(customUrl)
  166 + // }
157 } 167 }
158 168
159 onMounted(() => { 169 onMounted(() => {
@@ -3,12 +3,8 @@ @@ -3,12 +3,8 @@
3 <n-spin size="medium" :show="showLoading" :content-style="{ background: 'red' }"> 3 <n-spin size="medium" :show="showLoading" :content-style="{ background: 'red' }">
4 <template #description> 视频正在努力加载中...... </template> 4 <template #description> 视频正在努力加载中...... </template>
5 <div> 5 <div>
6 - <VideoPlay  
7 - :baseSize="{ w, h }"  
8 - :sourceSrc="option.dataset"  
9 - :autoPlay="option.autoplay"  
10 - :avatar="option.poster"  
11 - /> 6 + <VideoPlay ref="videoPlayerRef" :baseSize="{ w, h }" :sourceSrc="sourceUrl" :autoPlay="option.autoplay"
  7 + :avatar="option.poster" />
12 </div> 8 </div>
13 </n-spin> 9 </n-spin>
14 </div> 10 </div>
@@ -16,8 +12,10 @@ @@ -16,8 +12,10 @@
16 <script setup lang="ts"> 12 <script setup lang="ts">
17 import { PropType, toRefs, shallowReactive, watch, ref } from 'vue' 13 import { PropType, toRefs, shallowReactive, watch, ref } from 'vue'
18 import { CreateComponentType } from '@/packages/index.d' 14 import { CreateComponentType } from '@/packages/index.d'
19 -import { option as configOption } from './config' 15 +import { AccessMode, Dataset, option as configOption } from './config'
20 import { VideoPlay } from './components' 16 import { VideoPlay } from './components'
  17 +import { getVideoControlStart } from '@/api/external/flvPlay'
  18 +import { getVideoUrl } from '@/api/external/common'
21 19
22 const props = defineProps({ 20 const props = defineProps({
23 chartConfig: { 21 chartConfig: {
@@ -26,7 +24,7 @@ const props = defineProps({ @@ -26,7 +24,7 @@ const props = defineProps({
26 } 24 }
27 }) 25 })
28 26
29 -const showLoading = ref(true) 27 +const showLoading = ref(false)
30 28
31 const { w, h } = toRefs(props.chartConfig.attr) 29 const { w, h } = toRefs(props.chartConfig.attr)
32 30
@@ -38,20 +36,57 @@ const option = shallowReactive({ @@ -38,20 +36,57 @@ const option = shallowReactive({
38 autoplay: configOption.autoplay 36 autoplay: configOption.autoplay
39 }) 37 })
40 38
41 -const loadTime = (time: number, status: boolean) => {  
42 - setTimeout(() => {  
43 - showLoading.value = status  
44 - }, time) 39 +const sourceUrl = ref('')
  40 +const videoPlayerRef = ref<InstanceType<typeof VideoPlay> | null>()
  41 +
  42 +// 针对萤石云或者海康威视,根据视频id获取播放流地址
  43 +const getVideoUrlById = async (id: string) => {
  44 + const res = await getVideoUrl(id)
  45 + if (!res) return
  46 + const { url } = res.data
  47 + return url
  48 +}
  49 +
  50 +// 针对gbt28181,根据设备id和通道号获取播放流地址
  51 +const getVideoControlList = async (deviceId: string, channelId: string) => {
  52 + const {
  53 + data: { flv }
  54 + } = await getVideoControlStart({
  55 + deviceId,
  56 + channelId
  57 + })
  58 + return flv
  59 +}
  60 +
  61 +// 针对自定义地址,直接获取地址
  62 +const getCustomUrl = (url: string) => {
  63 + return url
  64 +}
  65 +
  66 +
  67 +async function getPlaySource(params: Dataset) {
  68 + try {
  69 + showLoading.value = true
  70 +
  71 + const { accessMode, id, deviceId, channelId, customUrl } = params
  72 + if (accessMode === AccessMode.Streaming && id) {
  73 + return await getVideoUrlById(id!)
  74 + } else if (accessMode === AccessMode.GBT28181 && deviceId && channelId) {
  75 + return await getVideoControlList(deviceId!, channelId!)
  76 + } else {
  77 + return getCustomUrl(customUrl!)
  78 + }
  79 + } finally {
  80 + showLoading.value = false
  81 + }
45 } 82 }
46 83
47 watch( 84 watch(
48 () => dataset?.value, 85 () => dataset?.value,
49 - (newData: string) => {  
50 - loadTime(800, true)  
51 - if (newData) {  
52 - loadTime(2000, false)  
53 - }  
54 - option.dataset = newData 86 + async (newData) => {
  87 + videoPlayerRef.value?.dispose()
  88 + sourceUrl.value = await getPlaySource(newData)
  89 + videoPlayerRef.value?.anewInit()
55 }, 90 },
56 { 91 {
57 immediate: true 92 immediate: true
@@ -70,4 +105,5 @@ watch( @@ -70,4 +105,5 @@ watch(
70 ) 105 )
71 </script> 106 </script>
72 107
73 -<style lang="scss" scoped></style> 108 +<style lang="scss" scoped>
  109 +</style>