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 |