Commit 39b6a248890e3d3692bc65f9860e6e4a0d8c2e3e

Authored by ww
1 parent 2bcdadc2

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

1 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 5 </div>
10 6 </template>
11 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 9 import videojs from 'video.js'
14 10 import 'videojs-flvjs-es6'
15 11 import type { VideoJsPlayerOptions } from 'video.js'
... ... @@ -45,7 +41,7 @@ const isRtspProtocol = (url: string) => {
45 41 }
46 42
47 43 const getVideoTypeByUrl = (url = '') => {
48   - if(!url) return;
  44 + if (!url) return;
49 45 try {
50 46 const { protocol, pathname } = new URL(url)
51 47 if (protocol.startsWith('rtsp:')) return VideoPlayerTypeEnum.flv
... ... @@ -60,11 +56,9 @@ const getVideoTypeByUrl = (url = '') => {
60 56 }
61 57 }
62 58
63   -// video标签
64   -const videoRef = ref<HTMLElement | null>(null)
65   -
66 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 63 const fingerprintResult = ref<Nullable<GetResult>>(null)
70 64
... ... @@ -72,11 +66,11 @@ const fingerprintResult = ref<Nullable<GetResult>>(null)
72 66 const options: VideoJsPlayerOptions & Recordable = {
73 67 language: 'zh-CN', // 设置语言
74 68 controls: true, // 是否显示控制条
75   - autoplay: props.autoPlay ? true : false, // 是否自动播放
  69 + autoplay: !!props.autoPlay, // 是否自动播放
76 70 fluid: false, // 自适应宽高
77 71 poster: props?.avatar || '',
78 72 sources: [],
79   - muted: props.autoPlay ? true : false,
  73 + muted: !!props.autoPlay,
80 74 userActions: {
81 75 hotkeys: true
82 76 },
... ... @@ -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 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 176 watch(
177 177 () => props.autoPlay,
... ... @@ -179,14 +179,14 @@ watch(
179 179 if (newData) {
180 180 handleVideoPlay()
181 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 191 onUnmounted(() => {
192 192 if (props.sourceSrc) {
... ... @@ -195,11 +195,35 @@ onUnmounted(() => {
195 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 228 </script>
205 229
... ...
... ... @@ -46,8 +46,17 @@ export enum AccessMode {
46 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 58 export const option = {
50   - dataset: '',
  59 + dataset: {} as Dataset,
51 60 autoplay: true,
52 61 poster: '',
53 62 sourceType: sourceTypeEnum.CUSTOM,
... ...
... ... @@ -91,7 +91,7 @@ const getOriginationList = async () => {
91 91 }
92 92
93 93 const handleUpdateTreeValue = (value: string) => {
94   - props.optionData.dataset = ''
  94 + props.optionData.dataset = {}
95 95 getVideoLists(value)
96 96 }
97 97
... ... @@ -147,13 +147,23 @@ const handleChecked = (value: string) => {
147 147 const handleSelect = (_: string, e: videoListInterface) => {
148 148 const { accessMode, id, customUrl, channelId, deviceId, value } = e
149 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 169 onMounted(() => {
... ...
... ... @@ -3,12 +3,8 @@
3 3 <n-spin size="medium" :show="showLoading" :content-style="{ background: 'red' }">
4 4 <template #description> 视频正在努力加载中...... </template>
5 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 8 </div>
13 9 </n-spin>
14 10 </div>
... ... @@ -16,8 +12,10 @@
16 12 <script setup lang="ts">
17 13 import { PropType, toRefs, shallowReactive, watch, ref } from 'vue'
18 14 import { CreateComponentType } from '@/packages/index.d'
19   -import { option as configOption } from './config'
  15 +import { AccessMode, Dataset, option as configOption } from './config'
20 16 import { VideoPlay } from './components'
  17 +import { getVideoControlStart } from '@/api/external/flvPlay'
  18 +import { getVideoUrl } from '@/api/external/common'
21 19
22 20 const props = defineProps({
23 21 chartConfig: {
... ... @@ -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 29 const { w, h } = toRefs(props.chartConfig.attr)
32 30
... ... @@ -38,20 +36,57 @@ const option = shallowReactive({
38 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 84 watch(
48 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 92 immediate: true
... ... @@ -70,4 +105,5 @@ watch(
70 105 )
71 106 </script>
72 107
73   -<style lang="scss" scoped></style>
  108 +<style lang="scss" scoped>
  109 +</style>
... ...