Commit 5e6df27d254f5e9402ba7649f2f4e51fb621dc4c
1 parent
b6587d76
feat(src/packages): 修改信息下的单个摄像头,如果是rtsp,则调用接口进行播放
Showing
2 changed files
with
171 additions
and
81 deletions
| 1 | 1 | <template> |
| 2 | - <div class="videoPlay"> | |
| 2 | + <div class="go-content-box" :style="{ width: w + 'px', height: h + 'px' }"> | |
| 3 | 3 | <video |
| 4 | - style="object-fit: cover" | |
| 5 | - :poster="poster" | |
| 6 | 4 | crossOrigin="anonymous" |
| 5 | + :id="`my-player`" | |
| 7 | 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 | 9 | </div> |
| 14 | 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 | 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 | 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 | 182 | </script> |
| 92 | -<style> | |
| 93 | -.vjs-poster { | |
| 94 | - background-size: cover !important; | |
| 95 | -} | |
| 96 | -</style> | |
| 183 | + | |
| 97 | 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 | 196 | </style> | ... | ... |
| 1 | 1 | <template> |
| 2 | 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 | 4 | </div> |
| 5 | 5 | </template> |
| 6 | 6 | <script setup lang="ts"> |
| ... | ... | @@ -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 | 21 | const { autoplay, dataset, poster, url } = toRefs(props.chartConfig.option) |
| 22 | 22 | ... | ... |