类型:开发
描述:
This commit is contained in:
BIN
dist01.zip
BIN
dist01.zip
Binary file not shown.
BIN
dist5.31.zip
BIN
dist5.31.zip
Binary file not shown.
22
package-lock.json
generated
22
package-lock.json
generated
@@ -15,6 +15,7 @@
|
|||||||
"hls.js": "^1.5.18",
|
"hls.js": "^1.5.18",
|
||||||
"jssip": "^3.10.1",
|
"jssip": "^3.10.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"mpegts.js": "^1.8.0",
|
||||||
"pinia": "^2.2.6",
|
"pinia": "^2.2.6",
|
||||||
"pinia-plugin-persistedstate": "^4.2.0",
|
"pinia-plugin-persistedstate": "^4.2.0",
|
||||||
"pubsub-js": "^1.9.5",
|
"pubsub-js": "^1.9.5",
|
||||||
@@ -3201,6 +3202,12 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/es6-promise": {
|
||||||
|
"version": "4.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
|
||||||
|
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
|
||||||
@@ -4272,6 +4279,16 @@
|
|||||||
"ufo": "^1.5.4"
|
"ufo": "^1.5.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mpegts.js": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mpegts.js/-/mpegts.js-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-ZtujqtmTjWgcDDkoOnLvrOKUTO/MKgLHM432zGDI8oPaJ0S+ebPxg1nEpDpLw6I7KmV/GZgUIrfbWi3qqEircg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"es6-promise": "^4.2.5",
|
||||||
|
"webworkify-webpack": "github:xqq/webworkify-webpack"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mrmime": {
|
"node_modules/mrmime": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
|
||||||
@@ -6143,6 +6160,11 @@
|
|||||||
"url": "https://opencollective.com/webpack"
|
"url": "https://opencollective.com/webpack"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/webworkify-webpack": {
|
||||||
|
"version": "2.1.5",
|
||||||
|
"resolved": "git+ssh://git@github.com/xqq/webworkify-webpack.git#24d1e719b4a6cac37a518b2bb10fe124527ef4ef",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"hls.js": "^1.5.18",
|
"hls.js": "^1.5.18",
|
||||||
"jssip": "^3.10.1",
|
"jssip": "^3.10.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"mpegts.js": "^1.8.0",
|
||||||
"pinia": "^2.2.6",
|
"pinia": "^2.2.6",
|
||||||
"pinia-plugin-persistedstate": "^4.2.0",
|
"pinia-plugin-persistedstate": "^4.2.0",
|
||||||
"pubsub-js": "^1.9.5",
|
"pubsub-js": "^1.9.5",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const instance = axios.create({
|
|||||||
timeout: 100000,
|
timeout: 100000,
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: mode == 'dev' ? devToken : proToken,
|
Authorization: mode == 'dev' ? devToken : proToken,
|
||||||
|
'User-Type': '1',
|
||||||
'Content-Type': 'application/json;charset=UTF-8'
|
'Content-Type': 'application/json;charset=UTF-8'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
352
src/components/HlsPlayer/index-bak.vue
Normal file
352
src/components/HlsPlayer/index-bak.vue
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
<template>
|
||||||
|
<div v-show="isActive" class="myVideo-container">
|
||||||
|
<video
|
||||||
|
class="myVideo"
|
||||||
|
ref="videoElement"
|
||||||
|
muted
|
||||||
|
playsinline
|
||||||
|
:controls="false"
|
||||||
|
disablePictureInPicture
|
||||||
|
></video>
|
||||||
|
<!-- <div v-if="loading" class="loading-overlay pointer-events-none">
|
||||||
|
<div class="loading-text">{{ loadingText }}</div>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Hls from 'hls.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'HlsPlayer',
|
||||||
|
props: {
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: '100%'
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: '100%'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
hls: null,
|
||||||
|
video: null,
|
||||||
|
loading: false,
|
||||||
|
loadingText: '加载中...',
|
||||||
|
retryCount: 0,
|
||||||
|
maxRetries: 3,
|
||||||
|
isReady: false,
|
||||||
|
playAttempts: 0,
|
||||||
|
maxPlayAttempts: 3,
|
||||||
|
cleanupTimeout: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
videoStyle() {
|
||||||
|
const style = {}
|
||||||
|
if (this.width) {
|
||||||
|
style.width = typeof this.width === 'number' ? `${this.width}px` : this.width
|
||||||
|
}
|
||||||
|
if (this.height) {
|
||||||
|
style.height = typeof this.height === 'number' ? `${this.height}px` : this.height
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
isActive: {
|
||||||
|
handler(newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.$nextTick(this.initializePlayer)
|
||||||
|
} else {
|
||||||
|
this.immediateCleanup()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
url(newUrl) {
|
||||||
|
// console.log(newUrl,'77777777777777777777777777777777')
|
||||||
|
if (newUrl && this.isActive) {
|
||||||
|
this.initializePlayer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.video = this.$refs.videoElement
|
||||||
|
this.registerVideoEvents()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
registerVideoEvents() {
|
||||||
|
const events = ['canplay', 'waiting', 'playing', 'error', 'stalled', 'suspend']
|
||||||
|
|
||||||
|
events.forEach((event) => {
|
||||||
|
this.video.addEventListener(event, this.handleVideoEvent)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handleVideoEvent(event) {
|
||||||
|
switch (event.type) {
|
||||||
|
case 'canplay':
|
||||||
|
this.isReady = true
|
||||||
|
break
|
||||||
|
case 'waiting':
|
||||||
|
this.showLoadingIndicator('缓冲中...')
|
||||||
|
break
|
||||||
|
case 'playing':
|
||||||
|
this.loading = false
|
||||||
|
break
|
||||||
|
case 'error':
|
||||||
|
this.handlePlaybackError()
|
||||||
|
break
|
||||||
|
case 'stalled':
|
||||||
|
this.showLoadingIndicator('播放卡顿...')
|
||||||
|
break
|
||||||
|
case 'suspend':
|
||||||
|
this.cleanupNetworkResources()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
immediateCleanup() {
|
||||||
|
// 立即停止网络请求
|
||||||
|
if (this.hls) {
|
||||||
|
this.hls.stopLoad()
|
||||||
|
this.hls.detachMedia()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 延迟完全清理以避免播放卡顿
|
||||||
|
this.cleanupTimeout = setTimeout(() => {
|
||||||
|
// 仅在不重新初始化播放器时才调用 fullCleanup
|
||||||
|
if (!this.url || !this.isActive) {
|
||||||
|
this.fullCleanup()
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
},
|
||||||
|
|
||||||
|
fullCleanup() {
|
||||||
|
if (this.hls) {
|
||||||
|
this.hls.destroy()
|
||||||
|
this.hls = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.video) {
|
||||||
|
this.video.pause()
|
||||||
|
this.video.removeAttribute('src')
|
||||||
|
this.video.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = false
|
||||||
|
this.retryCount = 0
|
||||||
|
this.isReady = false
|
||||||
|
this.playAttempts = 0
|
||||||
|
|
||||||
|
if (this.cleanupTimeout) {
|
||||||
|
clearTimeout(this.cleanupTimeout)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
initializePlayer() {
|
||||||
|
if (!this.isActive || !this.url) return
|
||||||
|
|
||||||
|
// 如果是重新初始化播放器,先清理已存在的资源
|
||||||
|
if (this.hls) {
|
||||||
|
this.immediateCleanup()
|
||||||
|
}
|
||||||
|
this.hls = new Hls({
|
||||||
|
// 内存优化配置
|
||||||
|
maxBufferSize: 0, // 降低缓冲区大小(15MB)
|
||||||
|
maxBufferLength: 0.1, // 更小的缓冲窗口
|
||||||
|
liveSyncDuration: 1, // 紧跟直播点
|
||||||
|
liveMaxLatencyDuration: 5, // 最大延迟5秒
|
||||||
|
liveDurationInfinity: true,
|
||||||
|
lowLatencyMode: true, // 启用低延迟模式
|
||||||
|
maxMaxBufferLength: 60,
|
||||||
|
backBufferLength: 1, // 减少保留的缓冲数据
|
||||||
|
manifestLoadingTimeOut: 10000,
|
||||||
|
manifestLoadingMaxRetry: 5,
|
||||||
|
fragLoadingTimeOut: 5000,
|
||||||
|
fragLoadingMaxRetry: 2,
|
||||||
|
enableWorker: true, // 启用Web Worker
|
||||||
|
recycleVideoFrames: true, // 启用帧回收
|
||||||
|
|
||||||
|
startLevel: -1,
|
||||||
|
autoStartLoad: true,
|
||||||
|
maxBufferHole: 2, // 允许更大的时间缺口
|
||||||
|
highBufferWatchdogPeriod: 4, // 延长监控周期
|
||||||
|
nudgeMaxRetry: 5, // 增加微调重试次数
|
||||||
|
nudgeOffset: 0.05, // 微调步长(秒)
|
||||||
|
jumpGaps: false,
|
||||||
|
stallThreshold: 1000
|
||||||
|
})
|
||||||
|
|
||||||
|
this.hls.attachMedia(this.video)
|
||||||
|
this.hls.loadSource(this.url)
|
||||||
|
|
||||||
|
// 事件处理
|
||||||
|
this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
|
this.loading = false
|
||||||
|
this.retryCount = 0
|
||||||
|
this.safePlay()
|
||||||
|
})
|
||||||
|
this.hls.on(Hls.Events.ERROR, (event, data) => {
|
||||||
|
console.log('核心视频错误',data.type)
|
||||||
|
// this.hls.startLoad(); //重连
|
||||||
|
if (data.type === Hls.ErrorTypes.BUFFER_STALLED_ERROR) {
|
||||||
|
console.error('缓冲停滞错误,尝试重新加载视频');
|
||||||
|
this.hls.startLoad(); // 尝试重新加载视频
|
||||||
|
}
|
||||||
|
this.handleHlsError(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.hls.on(Hls.Events.BUFFER_EOS, () => {
|
||||||
|
this.cleanupNetworkResources()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
initVideo() {
|
||||||
|
this.beforeDestroy()
|
||||||
|
this.hls = new Hls({
|
||||||
|
maxBufferLength: 30, // 最大缓冲长度(秒)
|
||||||
|
maxMaxBufferLength: 15, // 缓冲区长度的上限
|
||||||
|
maxBufferSize: 30 * 1000 * 1000 // 最大缓冲大小(字节)
|
||||||
|
})
|
||||||
|
this.hls.attachMedia(this.video)
|
||||||
|
this.hls.loadSource(this.url)
|
||||||
|
|
||||||
|
// 事件处理
|
||||||
|
this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
|
this.loading = false
|
||||||
|
this.retryCount = 0
|
||||||
|
this.safePlay()
|
||||||
|
})
|
||||||
|
this.hls.on(Hls.Events.ERROR, (event, data) => {
|
||||||
|
this.hls.startLoad(); //重连
|
||||||
|
this.handleHlsError(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.hls.on(Hls.Events.BUFFER_EOS, () => {
|
||||||
|
this.cleanupNetworkResources()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cleanupNetworkResources() {
|
||||||
|
// 清理已播放的缓冲数据
|
||||||
|
if (this.hls && this.video) {
|
||||||
|
try {
|
||||||
|
const currentTime = this.video.currentTime
|
||||||
|
this.hls.mediaBuffer = null
|
||||||
|
if (this.hls.bufferTimer) {
|
||||||
|
clearInterval(this.hls.bufferTimer)
|
||||||
|
}
|
||||||
|
this.hls.flushBuffer()
|
||||||
|
this.video.currentTime = currentTime
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Buffer cleanup error:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleHlsError(data) {
|
||||||
|
// console.error('HLS Error:', data)
|
||||||
|
if (data.fatal) {
|
||||||
|
switch (data.type) {
|
||||||
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
||||||
|
this.retryLoad()
|
||||||
|
break
|
||||||
|
case Hls.ErrorTypes.MEDIA_ERROR:
|
||||||
|
this.hls.recoverMediaError()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
this.fullCleanup()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
retryLoad() {
|
||||||
|
if (this.retryCount < this.maxRetries) {
|
||||||
|
this.retryCount++
|
||||||
|
this.showLoadingIndicator(`重试第 ${this.retryCount} 次...`)
|
||||||
|
setTimeout(() => this.initializePlayer(), 2000)
|
||||||
|
} else {
|
||||||
|
this.showLoadingIndicator('视频加载失败')
|
||||||
|
this.fullCleanup()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showLoadingIndicator(text) {
|
||||||
|
this.loading = true
|
||||||
|
this.loadingText = text
|
||||||
|
},
|
||||||
|
|
||||||
|
async safePlay() {
|
||||||
|
if (!this.video || this.playAttempts >= this.maxPlayAttempts) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.playAttempts++
|
||||||
|
if (this.video.readyState >= 2) {
|
||||||
|
await this.video.play()
|
||||||
|
this.playAttempts = 0
|
||||||
|
} else {
|
||||||
|
setTimeout(this.safePlay, 500)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('播放失败:', error)
|
||||||
|
setTimeout(this.safePlay, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 移除所有事件监听
|
||||||
|
if (this.video) {
|
||||||
|
const events = ['canplay', 'waiting', 'playing', 'error', 'stalled', 'suspend']
|
||||||
|
events.forEach((event) => {
|
||||||
|
this.video.removeEventListener(event, this.handleVideoEvent)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.fullCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.myVideo-container {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.myVideo {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
// aspect-ratio: 16/9;
|
||||||
|
/* border: 1px solid #ccc; */
|
||||||
|
// border-radius: vw(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
color: white;
|
||||||
|
padding: vw(10);
|
||||||
|
border-radius: vw(4);
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
<div v-show="isActive" class="myVideo-container">
|
<div v-show="isActive" class="myVideo-container">
|
||||||
<video
|
<video
|
||||||
class="myVideo"
|
class="myVideo"
|
||||||
|
id="video"
|
||||||
ref="videoElement"
|
ref="videoElement"
|
||||||
muted
|
muted
|
||||||
playsinline
|
playsinline
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Hls from 'hls.js'
|
import Hls from 'hls.js'
|
||||||
|
import mpegtsjs from 'mpegts.js'
|
||||||
export default {
|
export default {
|
||||||
name: 'HlsPlayer',
|
name: 'HlsPlayer',
|
||||||
props: {
|
props: {
|
||||||
@@ -162,55 +163,78 @@
|
|||||||
if (this.hls) {
|
if (this.hls) {
|
||||||
this.immediateCleanup()
|
this.immediateCleanup()
|
||||||
}
|
}
|
||||||
this.hls = new Hls({
|
if(this.url.startsWith('ws')){
|
||||||
// 内存优化配置
|
const videoElement = document.getElementById('video')
|
||||||
maxBufferSize: 0, // 降低缓冲区大小(15MB)
|
const player = mpegtsjs.createPlayer({
|
||||||
maxBufferLength: 0.1, // 更小的缓冲窗口
|
url: this.url,
|
||||||
liveSyncDuration: 1, // 紧跟直播点
|
type: 'flv',
|
||||||
liveMaxLatencyDuration: 5, // 最大延迟5秒
|
isLive: true,
|
||||||
liveDurationInfinity: true,
|
hasAudio: false
|
||||||
lowLatencyMode: true, // 启用低延迟模式
|
})
|
||||||
maxMaxBufferLength: 60,
|
player.attachMediaElement(videoElement)
|
||||||
backBufferLength: 1, // 减少保留的缓冲数据
|
player.load()
|
||||||
manifestLoadingTimeOut: 10000,
|
player.play()
|
||||||
manifestLoadingMaxRetry: 5,
|
|
||||||
fragLoadingTimeOut: 5000,
|
|
||||||
fragLoadingMaxRetry: 2,
|
|
||||||
enableWorker: true, // 启用Web Worker
|
|
||||||
recycleVideoFrames: true, // 启用帧回收
|
|
||||||
|
|
||||||
startLevel: -1,
|
// 错误处理和重连机制
|
||||||
autoStartLoad: true,
|
player.on('error', (err) => {
|
||||||
maxBufferHole: 2, // 允许更大的时间缺口
|
// 3 秒后尝试重新加载
|
||||||
highBufferWatchdogPeriod: 4, // 延长监控周期
|
setTimeout(() => {
|
||||||
nudgeMaxRetry: 5, // 增加微调重试次数
|
player.load()
|
||||||
nudgeOffset: 0.05, // 微调步长(秒)
|
player.play()
|
||||||
jumpGaps: false,
|
}, 3000)
|
||||||
stallThreshold: 1000
|
})
|
||||||
})
|
}else{
|
||||||
|
this.hls = new Hls({
|
||||||
|
// 内存优化配置
|
||||||
|
maxBufferSize: 0, // 降低缓冲区大小(15MB)
|
||||||
|
maxBufferLength: 0.1, // 更小的缓冲窗口
|
||||||
|
liveSyncDuration: 1, // 紧跟直播点
|
||||||
|
liveMaxLatencyDuration: 5, // 最大延迟5秒
|
||||||
|
liveDurationInfinity: true,
|
||||||
|
lowLatencyMode: true, // 启用低延迟模式
|
||||||
|
maxMaxBufferLength: 60,
|
||||||
|
backBufferLength: 1, // 减少保留的缓冲数据
|
||||||
|
manifestLoadingTimeOut: 10000,
|
||||||
|
manifestLoadingMaxRetry: 5,
|
||||||
|
fragLoadingTimeOut: 5000,
|
||||||
|
fragLoadingMaxRetry: 2,
|
||||||
|
enableWorker: true, // 启用Web Worker
|
||||||
|
recycleVideoFrames: true, // 启用帧回收
|
||||||
|
|
||||||
this.hls.attachMedia(this.video)
|
startLevel: -1,
|
||||||
this.hls.loadSource(this.url)
|
autoStartLoad: true,
|
||||||
|
maxBufferHole: 2, // 允许更大的时间缺口
|
||||||
|
highBufferWatchdogPeriod: 4, // 延长监控周期
|
||||||
|
nudgeMaxRetry: 5, // 增加微调重试次数
|
||||||
|
nudgeOffset: 0.05, // 微调步长(秒)
|
||||||
|
jumpGaps: false,
|
||||||
|
stallThreshold: 1000
|
||||||
|
})
|
||||||
|
|
||||||
// 事件处理
|
this.hls.attachMedia(this.video)
|
||||||
this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
this.hls.loadSource(this.url)
|
||||||
this.loading = false
|
|
||||||
this.retryCount = 0
|
// 事件处理
|
||||||
this.safePlay()
|
this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
})
|
this.loading = false
|
||||||
this.hls.on(Hls.Events.ERROR, (event, data) => {
|
this.retryCount = 0
|
||||||
console.log('核心视频错误',data.type)
|
this.safePlay()
|
||||||
// this.hls.startLoad(); //重连
|
})
|
||||||
if (data.type === Hls.ErrorTypes.BUFFER_STALLED_ERROR) {
|
this.hls.on(Hls.Events.ERROR, (event, data) => {
|
||||||
console.error('缓冲停滞错误,尝试重新加载视频');
|
console.log('核心视频错误',data.type)
|
||||||
this.hls.startLoad(); // 尝试重新加载视频
|
// this.hls.startLoad(); //重连
|
||||||
}
|
if (data.type === Hls.ErrorTypes.BUFFER_STALLED_ERROR) {
|
||||||
this.handleHlsError(data)
|
console.error('缓冲停滞错误,尝试重新加载视频');
|
||||||
})
|
this.hls.startLoad(); // 尝试重新加载视频
|
||||||
|
}
|
||||||
|
this.handleHlsError(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.hls.on(Hls.Events.BUFFER_EOS, () => {
|
||||||
|
this.cleanupNetworkResources()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
this.hls.on(Hls.Events.BUFFER_EOS, () => {
|
|
||||||
this.cleanupNetworkResources()
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
initVideo() {
|
initVideo() {
|
||||||
this.beforeDestroy()
|
this.beforeDestroy()
|
||||||
@@ -221,7 +245,7 @@
|
|||||||
})
|
})
|
||||||
this.hls.attachMedia(this.video)
|
this.hls.attachMedia(this.video)
|
||||||
this.hls.loadSource(this.url)
|
this.hls.loadSource(this.url)
|
||||||
|
|
||||||
// 事件处理
|
// 事件处理
|
||||||
this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
@@ -232,7 +256,7 @@
|
|||||||
this.hls.startLoad(); //重连
|
this.hls.startLoad(); //重连
|
||||||
this.handleHlsError(data)
|
this.handleHlsError(data)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.hls.on(Hls.Events.BUFFER_EOS, () => {
|
this.hls.on(Hls.Events.BUFFER_EOS, () => {
|
||||||
this.cleanupNetworkResources()
|
this.cleanupNetworkResources()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<div v-if="condShow==0" class="nYong-du">加载中...</div>
|
<div v-if="condShow==0" class="nYong-du">加载中...</div>
|
||||||
<div v-if="condShow==1" class="nYong-du">暂无数据</div>
|
<div v-if="condShow==1" class="nYong-du">暂无数据</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -50,8 +50,8 @@
|
|||||||
|
|
||||||
const { id, setOption,chartVal,dispose ,clearOption} = useEchart()
|
const { id, setOption,chartVal,dispose ,clearOption} = useEchart()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let condShow = ref(0)
|
let condShow = ref(0)
|
||||||
let aIndex = 1
|
let aIndex = 1
|
||||||
var colorList = []
|
var colorList = []
|
||||||
@@ -59,56 +59,45 @@
|
|||||||
watch(
|
watch(
|
||||||
() => props.dataList,
|
() => props.dataList,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
|
|
||||||
aIndex+=1
|
aIndex+=1
|
||||||
|
|
||||||
if(aIndex>=3&&!newVal.length){
|
if(aIndex>=3&&!newVal.length){
|
||||||
condShow.value = 1
|
condShow.value = 1
|
||||||
}
|
}
|
||||||
if (newVal.length > 0) {
|
if (newVal.length > 0) {
|
||||||
|
|
||||||
console.log(colorList.value,'colorList')
|
|
||||||
condShow.value = 2
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
init()
|
init()
|
||||||
// defaultCofig.legend.formatter = (name) => {
|
|
||||||
// let percent = props.dataList.find((item) => item.name == name).value
|
|
||||||
// return name + '\u3000' + `${percent}%`
|
|
||||||
// }
|
|
||||||
// defaultCofig.series[0].data = props.dataList
|
|
||||||
// defaultCofig.series[0].label.formatter = () => {
|
|
||||||
// return `{value|${props.total}}` + '\n' + `{name|${props.label} }`
|
|
||||||
// }
|
|
||||||
// setOption({
|
|
||||||
// ...defaultCofig,
|
|
||||||
// ...props.config
|
|
||||||
// })
|
|
||||||
|
|
||||||
})
|
})
|
||||||
}else{
|
}else{
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
const init = ()=>{
|
const init = ()=>{
|
||||||
|
if(condShow.value===2){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
condShow.value = 2
|
||||||
props.dataList.forEach((item,index)=>{
|
props.dataList.forEach((item,index)=>{
|
||||||
|
|
||||||
if(item.name=='负面'){
|
if(item.name=='负面'){
|
||||||
colorList.push('#d9011b')
|
colorList.push('#d9011b')
|
||||||
}
|
}
|
||||||
else if(item.name=='正面'){
|
else if(item.name=='正面'){
|
||||||
colorList.push('#50F0A6')
|
colorList.push('#50F0A6')
|
||||||
}
|
}
|
||||||
else if(item.name=='中性'){
|
else if(item.name=='中性'){
|
||||||
colorList.push('#2380fb')
|
colorList.push('#2380fb')
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
colorList = ['#3BA272', '#73C0DE','#EE6666','#FAC858','#91CC75','#5470C6','#d9011b','#feae00','#50F0A6']
|
colorList = ['#3BA272', '#73C0DE','#EE6666','#FAC858','#91CC75','#5470C6','#d9011b','#feae00','#50F0A6']
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
var defaultCofig = {
|
var defaultCofig = {
|
||||||
color: colorList,
|
color: colorList,
|
||||||
legend: {
|
legend: {
|
||||||
@@ -167,15 +156,41 @@
|
|||||||
defaultCofig.series[0].label.formatter = () => {
|
defaultCofig.series[0].label.formatter = () => {
|
||||||
return `{value|${props.total}}` + '\n' + `{name|${props.label} }`
|
return `{value|${props.total}}` + '\n' + `{name|${props.label} }`
|
||||||
}
|
}
|
||||||
setOption({
|
const chart = setOption({
|
||||||
...defaultCofig,
|
...defaultCofig,
|
||||||
...props.config
|
...props.config
|
||||||
})
|
})
|
||||||
|
chart.on('legendselectchanged', function (e) {
|
||||||
|
console.log(e,'e')
|
||||||
|
var echartsArr = [];
|
||||||
|
for (let key in e.selected) {
|
||||||
|
if (e.selected[key]) {
|
||||||
|
echartsArr.push(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var echartsNum = 0;
|
||||||
|
props.dataList.forEach(item => {
|
||||||
|
if(echartsArr.includes(item.name)){
|
||||||
|
echartsNum += parseFloat(item.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
defaultCofig.series[0].label.formatter = `{value|${parseInt(echartsNum/100*props.total)}}` + '\n' + `{name|${props.label}}`;
|
||||||
|
console.log(111111)
|
||||||
|
console.log({
|
||||||
|
...defaultCofig,
|
||||||
|
...props.config
|
||||||
|
})
|
||||||
|
console.log(222222)
|
||||||
|
setOption({
|
||||||
|
...defaultCofig,
|
||||||
|
...props.config
|
||||||
|
})
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|
||||||
.nYong-du{
|
.nYong-du{
|
||||||
position:absolute;
|
position:absolute;
|
||||||
left:0;
|
left:0;
|
||||||
@@ -188,6 +203,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -49,10 +49,10 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="action-item">
|
<div class="action-item">
|
||||||
<div class="video-follow" @click="handleCollectAdd(cameraIndexCode, isDiy, index)" v-if="isDiy==0">收藏</div>
|
<div class="video-follow" @click="handleCollectAdd()" v-if="isDayCurr==0">收藏</div>
|
||||||
<div class="video-follow" @click="handleCollectAdd(cameraIndexCode, isDiy, index)" v-else="isDiy==1">取消收藏</div>
|
<div class="video-follow" @click="handleCollectAdd()" v-if="isDayCurr==1">取消收藏</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<img class="close" src="@/assets/images/close.png" @click="handleClose" />
|
<img class="close" src="@/assets/images/close.png" @click="handleClose" />
|
||||||
@@ -103,10 +103,10 @@
|
|||||||
watch(
|
watch(
|
||||||
() =>modelValue.value,
|
() =>modelValue.value,
|
||||||
(val) => {
|
(val) => {
|
||||||
// colletCond.value = props.isCollect
|
colletCond.value = props.isCollect
|
||||||
// isDayCurr.value = props.isDiy
|
isDayCurr.value = props.isDiy
|
||||||
console.log(props.isDiy,'val[0].value')
|
console.log(props.isDiy,'val[0].value')
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true
|
immediate: true
|
||||||
@@ -114,12 +114,12 @@
|
|||||||
)
|
)
|
||||||
const emit = defineEmits(['isDiyChange']);
|
const emit = defineEmits(['isDiyChange']);
|
||||||
// 收藏
|
// 收藏
|
||||||
const handleCollectAdd = async (id, status, index) => {
|
const handleCollectAdd = async () => {
|
||||||
await getColletDiyApi({
|
await getColletDiyApi({
|
||||||
cameraIndexCode:props.cameraIndexCode,
|
cameraIndexCode:props.cameraIndexCode,
|
||||||
isDiy: props.isDiy == 0 ? 1 : 0
|
isDiy: props.isDiy == 0 ? 1 : 0
|
||||||
})
|
})
|
||||||
|
|
||||||
if(props.isDiy==1){
|
if(props.isDiy==1){
|
||||||
isDayCurr.value=0
|
isDayCurr.value=0
|
||||||
// modelValue.value = false
|
// modelValue.value = false
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
}
|
}
|
||||||
emit('isDiyChange', isDayCurr.value);
|
emit('isDiyChange', isDayCurr.value);
|
||||||
pubSub.publish('videoIsDiy',{isDiy:props.isDiy,cameraIndexCode:props.cameraIndexCode} )
|
pubSub.publish('videoIsDiy',{isDiy:props.isDiy,cameraIndexCode:props.cameraIndexCode} )
|
||||||
|
|
||||||
}
|
}
|
||||||
// 关注
|
// 关注
|
||||||
const handleCollect = async (id, status, index) => {
|
const handleCollect = async (id, status, index) => {
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
cameraIndexCode:props.cameraIndexCode,
|
cameraIndexCode:props.cameraIndexCode,
|
||||||
isCollect: colletCond.value == 0 ? 1 : 0
|
isCollect: colletCond.value == 0 ? 1 : 0
|
||||||
})
|
})
|
||||||
|
|
||||||
if (colletCond.value == 0) {
|
if (colletCond.value == 0) {
|
||||||
colletCond.value = 1
|
colletCond.value = 1
|
||||||
} else {
|
} else {
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
modelValue.value = false
|
modelValue.value = false
|
||||||
}
|
}
|
||||||
pubSub.publish('videoCollect', props.cameraIndexCode)
|
pubSub.publish('videoCollect', props.cameraIndexCode)
|
||||||
|
|
||||||
}
|
}
|
||||||
const handleAction = async (e) => {
|
const handleAction = async (e) => {
|
||||||
if (e == STOP) {
|
if (e == STOP) {
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ export function useEchart() {
|
|||||||
const setOption = (params, update = false) => {
|
const setOption = (params, update = false) => {
|
||||||
initChart()
|
initChart()
|
||||||
chart.setOption(params, update)
|
chart.setOption(params, update)
|
||||||
|
return chart;
|
||||||
}
|
}
|
||||||
const clearOption = () => {
|
const clearOption = () => {
|
||||||
console.log('clearooooooooooooooooo')
|
|
||||||
// 将series设置为空数组,可以清空图表内容
|
// 将series设置为空数组,可以清空图表内容
|
||||||
chart.setOption({
|
chart.setOption({
|
||||||
series:[]
|
series:[]
|
||||||
|
|||||||
@@ -10,25 +10,14 @@
|
|||||||
:key="index"
|
:key="index"
|
||||||
@click="handleItem(item)"
|
@click="handleItem(item)"
|
||||||
>
|
>
|
||||||
<HlsPlayer :url="item.hlsUrl" />
|
<video
|
||||||
<div class="item-unfollow" @click.stop="handleUnfollow(item.cameraIndexCode, index)">取消关注</div>
|
|
||||||
<!-- <div>
|
|
||||||
<p class="item-title--primary">
|
|
||||||
{{ item.cameraName || item.cameraIndexCode }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<video
|
|
||||||
class="item-img"
|
class="item-img"
|
||||||
:id="'video' + index"
|
:id="'video' + index"
|
||||||
muted
|
muted
|
||||||
autoplay
|
autoplay
|
||||||
:controls="false"
|
:controls="false"
|
||||||
:src="item.hlsUrl"
|
></video>
|
||||||
controlsList="nodownload"
|
<div class="item-unfollow" @click.stop="handleUnfollow(item.cameraIndexCode, index)">取消关注</div>
|
||||||
>
|
|
||||||
<source src="" type="application/x-mpegURL" />
|
|
||||||
</video>
|
|
||||||
</div> -->
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,6 +39,7 @@
|
|||||||
|
|
||||||
import { useWebSocket } from '@/hooks/socket'
|
import { useWebSocket } from '@/hooks/socket'
|
||||||
import { mode, socketBaseUrl, proSocketBaseUrl } from '@/utils/config'
|
import { mode, socketBaseUrl, proSocketBaseUrl } from '@/utils/config'
|
||||||
|
import mpegtsjs from "mpegts.js";
|
||||||
|
|
||||||
const { dataRes } = useWebSocket(
|
const { dataRes } = useWebSocket(
|
||||||
`${mode == 'dev' ? socketBaseUrl : proSocketBaseUrl}/ws/securityAlerts`
|
`${mode == 'dev' ? socketBaseUrl : proSocketBaseUrl}/ws/securityAlerts`
|
||||||
@@ -73,7 +63,6 @@ let isCollect = ref(0)
|
|||||||
let timer = null
|
let timer = null
|
||||||
let isDiy = ref(0)
|
let isDiy = ref(0)
|
||||||
const handleItem = (item) => {
|
const handleItem = (item) => {
|
||||||
console.log(item,'1111111111111111111111111')
|
|
||||||
src.value = item.hlsUrl
|
src.value = item.hlsUrl
|
||||||
cameraIndexCode.value = item.cameraIndexCode
|
cameraIndexCode.value = item.cameraIndexCode
|
||||||
isCollect.value = item.isCollect
|
isCollect.value = item.isCollect
|
||||||
@@ -82,12 +71,12 @@ let isCollect = ref(0)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const postVideoRemain = () => {
|
const postVideoRemain = () => {
|
||||||
timer = setInterval(() => {
|
// timer = setInterval(() => {
|
||||||
if(!list.value.length) return false;
|
// if(!list.value.length) return false;
|
||||||
postVideoRemainApi({
|
// postVideoRemainApi({
|
||||||
cameraIndexCode: list.value.map((item) => item.cameraIndexCode)
|
// cameraIndexCode: list.value.map((item) => item.cameraIndexCode)
|
||||||
})
|
// })
|
||||||
}, 1500)
|
// }, 1500)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPreviewUrl = async (code) => {
|
const getPreviewUrl = async (code) => {
|
||||||
@@ -104,28 +93,32 @@ let isCollect = ref(0)
|
|||||||
pageSize: 5
|
pageSize: 5
|
||||||
})
|
})
|
||||||
list.value = res.data
|
list.value = res.data
|
||||||
postVideoRemain()
|
|
||||||
if (timer) clearInterval(timer)
|
if (timer) clearInterval(timer)
|
||||||
// console.log(list.value,'list.valuelist.valuelist.valuelist.value')
|
nextTick(() => {
|
||||||
// nextTick(() => {
|
list.value.forEach(async (item, index) => {
|
||||||
// list.value.forEach(async (item, index) => {
|
var videoElement = document.getElementById(`video${index}`)
|
||||||
// var video = document.getElementById(`video${index}`)
|
const player = mpegtsjs.createPlayer({
|
||||||
// const hls = new Hls({
|
url: item.hlsUrl,
|
||||||
// enableWorker: false, // 禁用 Worker 来避免额外的线程
|
type: 'flv',
|
||||||
// enableSoftwareAES: true, // 使用软件解码器以避免硬件解码的额外请求
|
isLive: true,
|
||||||
// cache: true, // 启用缓存
|
hasAudio: false
|
||||||
// maxBufferLength: 10, // 最大缓冲长度(秒)
|
})
|
||||||
// maxMaxBufferLength: 15, // 缓冲区长度的上限
|
player.attachMediaElement(videoElement)
|
||||||
// maxBufferSize: 20 * 1000 * 1000 // 最大缓冲大小(字节)
|
player.load()
|
||||||
// })
|
player.play()
|
||||||
// hls.loadSource(item.hlsUrl)
|
|
||||||
// hls.attachMedia(video)
|
// 错误处理和重连机制
|
||||||
// hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
player.on('error', (err) => {
|
||||||
// video.play()
|
console.error('播放器错误:', err)
|
||||||
// })
|
// 3 秒后尝试重新加载
|
||||||
// hlsRefs.push(hls)
|
setTimeout(() => {
|
||||||
// })
|
player.load()
|
||||||
// })
|
player.play()
|
||||||
|
}, 3000)
|
||||||
|
})
|
||||||
|
hlsRefs.push(player)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
watch(
|
watch(
|
||||||
() => list.value,
|
() => list.value,
|
||||||
@@ -166,9 +159,9 @@ let isCollect = ref(0)
|
|||||||
pubSub.subscribe('videoIsDiy', (msg, data) => {
|
pubSub.subscribe('videoIsDiy', (msg, data) => {
|
||||||
console.log(data,'收藏 ++++++++++++++')
|
console.log(data,'收藏 ++++++++++++++')
|
||||||
getVideoList()
|
getVideoList()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const useScenicStore = defineStore('scenic', () => {
|
|||||||
infoList: [
|
infoList: [
|
||||||
{ name: '游玩舒适度', type: 0, value: '空闲' },
|
{ name: '游玩舒适度', type: 0, value: '空闲' },
|
||||||
// { name: '景区安全', type: 0, value: '安全' },
|
// { name: '景区安全', type: 0, value: '安全' },
|
||||||
{ name: '通景交通', type: 0, value: '通畅' },
|
{ name: '交通拥堵度', type: 0, value: '通畅' },
|
||||||
{ name: '停车场负荷', type: 0, value: '空闲' }
|
{ name: '停车场负荷', type: 0, value: '空闲' }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,19 +7,19 @@
|
|||||||
</div> -->
|
</div> -->
|
||||||
<div class="type-item" v-for="(item,index) in videoList">
|
<div class="type-item" v-for="(item,index) in videoList">
|
||||||
<div class="type-title">{{item.label}}</div>
|
<div class="type-title">{{item.label}}</div>
|
||||||
<draggable
|
<draggable
|
||||||
:data-item-index="index"
|
:data-item-index="index"
|
||||||
class="item-element"
|
class="item-element"
|
||||||
:item-key="item.key"
|
:item-key="item.key"
|
||||||
:list="item.videos"
|
:list="item.videos"
|
||||||
ghost-class="ghost"
|
ghost-class="ghost"
|
||||||
:force-fallback="true" chosen-class="chosenClass" animation="300"
|
:force-fallback="true" chosen-class="chosenClass" animation="300"
|
||||||
@start="onStart" @end="onEnd">
|
@start="onStart" @end="onEnd">
|
||||||
<template #item="{ element }">
|
<template #item="{ element }">
|
||||||
<div class="video-item" :style="{
|
<div class="video-item" :style="{
|
||||||
width:(100/grad)+'%'
|
width:(100/grad)+'%'
|
||||||
}">
|
}">
|
||||||
|
|
||||||
<div class="video-item__inner" @click.stop="handleCamera(element.cameraIndexCode,element,index)">
|
<div class="video-item__inner" @click.stop="handleCamera(element.cameraIndexCode,element,index)">
|
||||||
<div
|
<div
|
||||||
v-if="element.isDiy == 1"
|
v-if="element.isDiy == 1"
|
||||||
@@ -45,12 +45,12 @@
|
|||||||
{{ element.cameraName || element.scenicAreaId }}
|
{{ element.cameraName || element.scenicAreaId }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</draggable>
|
</draggable>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="pagination">
|
<!-- <div class="pagination">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
@@ -93,6 +93,7 @@
|
|||||||
import Hls from 'hls.js'
|
import Hls from 'hls.js'
|
||||||
import emptyIco from '@/assets/images/n-icon.png'
|
import emptyIco from '@/assets/images/n-icon.png'
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
|
import mpegtsjs from "mpegts.js";
|
||||||
const Z00M_IN = 'ZOOM_IN' // 焦距变大
|
const Z00M_IN = 'ZOOM_IN' // 焦距变大
|
||||||
const Z00M_OUT = 'ZOOM_OUT' // 焦距变小
|
const Z00M_OUT = 'ZOOM_OUT' // 焦距变小
|
||||||
const UP = 'UP' // 上转
|
const UP = 'UP' // 上转
|
||||||
@@ -139,17 +140,17 @@
|
|||||||
isDiy.value = val
|
isDiy.value = val
|
||||||
if(!val){
|
if(!val){
|
||||||
show.value = false
|
show.value = false
|
||||||
videoList.value[diyIndex.value].videos = videoList.value[diyIndex.value].videos.filter(item => item.cameraIndexCode !== cameraIndexCode.value);
|
videoList.value[diyIndex.value].videos = videoList.value[diyIndex.value].videos.filter(item => item.cameraIndexCode !== cameraIndexCode.value);
|
||||||
}
|
}
|
||||||
// videoList.value[diyIndex.value].videos.forEach(async (it, i) => {
|
// videoList.value[diyIndex.value].videos.forEach(async (it, i) => {
|
||||||
|
|
||||||
// if(it.cameraIndexCode == cameraIndexCode.value){
|
// if(it.cameraIndexCode == cameraIndexCode.value){
|
||||||
// it.isDiy = val
|
// it.isDiy = val
|
||||||
// }
|
// }
|
||||||
// })
|
// })
|
||||||
}
|
}
|
||||||
const onStart = (res)=>{
|
const onStart = (res)=>{
|
||||||
|
|
||||||
}
|
}
|
||||||
const onEnd = (evt)=>{
|
const onEnd = (evt)=>{
|
||||||
const itemIndex = parseInt(evt.to.getAttribute('data-item-index')); // 当前拖拽的 item 的下标
|
const itemIndex = parseInt(evt.to.getAttribute('data-item-index')); // 当前拖拽的 item 的下标
|
||||||
@@ -157,7 +158,7 @@
|
|||||||
key:videoList.value[itemIndex].key,
|
key:videoList.value[itemIndex].key,
|
||||||
cameraIndexCodes:videoList.value[itemIndex].videos.map((item) => item.cameraIndexCode)
|
cameraIndexCodes:videoList.value[itemIndex].videos.map((item) => item.cameraIndexCode)
|
||||||
}).then((ress)=>{
|
}).then((ress)=>{
|
||||||
|
|
||||||
// getVideCollectCateList()
|
// getVideCollectCateList()
|
||||||
})
|
})
|
||||||
// postVideoRemain()
|
// postVideoRemain()
|
||||||
@@ -179,7 +180,7 @@
|
|||||||
}
|
}
|
||||||
postVideoRemain()
|
postVideoRemain()
|
||||||
// total.value = res.total
|
// total.value = res.total
|
||||||
|
|
||||||
initVideo()
|
initVideo()
|
||||||
}
|
}
|
||||||
// 收藏
|
// 收藏
|
||||||
@@ -189,12 +190,12 @@
|
|||||||
isDiy: status == 0 ? 1 : 0
|
isDiy: status == 0 ? 1 : 0
|
||||||
})
|
})
|
||||||
if (status == 0) {
|
if (status == 0) {
|
||||||
|
|
||||||
element.isDiy = 1
|
element.isDiy = 1
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
videoList.value[index].videos = videoList.value[index].videos.filter(item => item.cameraIndexCode !== id);
|
videoList.value[index].videos = videoList.value[index].videos.filter(item => item.cameraIndexCode !== id);
|
||||||
console.log('取消收藏',)
|
console.log('取消收藏',)
|
||||||
element.isDiy = 0
|
element.isDiy = 0
|
||||||
show.value = false
|
show.value = false
|
||||||
@@ -283,27 +284,49 @@
|
|||||||
clearHlsRefs()
|
clearHlsRefs()
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
videoList.value.forEach(async (it, i) => {
|
videoList.value.forEach(async (it, i) => {
|
||||||
|
|
||||||
it.videos.forEach((item,index)=>{
|
it.videos.forEach((item,index)=>{
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const video = document.getElementById(`monitorVideo${item.cameraIndexCode}`)
|
const video = document.getElementById(`monitorVideo${item.cameraIndexCode}`)
|
||||||
if(item.hlsUrl){
|
if(item.hlsUrl.startsWith('ws')){
|
||||||
const hls = new Hls({
|
const player = mpegtsjs.createPlayer({
|
||||||
maxBufferLength: 10, // 最大缓冲长度(秒)
|
url: item.hlsUrl,
|
||||||
maxMaxBufferLength: 15, // 缓冲区长度的上限
|
type: 'flv',
|
||||||
maxBufferSize: 30 * 1000 * 1000 // 最大缓冲大小(字节)
|
isLive: true,
|
||||||
})
|
hasAudio: false
|
||||||
hls.loadSource(item.hlsUrl)
|
})
|
||||||
hls.attachMedia(video)
|
player.attachMediaElement(video)
|
||||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
player.load()
|
||||||
video.play()
|
player.play()
|
||||||
})
|
|
||||||
hlsRefs.push(hls)
|
// 错误处理和重连机制
|
||||||
}
|
player.on('error', (err) => {
|
||||||
|
// 3 秒后尝试重新加载
|
||||||
|
setTimeout(() => {
|
||||||
|
player.load()
|
||||||
|
player.play()
|
||||||
|
}, 3000)
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
if(item.hlsUrl){
|
||||||
|
const hls = new Hls({
|
||||||
|
maxBufferLength: 10, // 最大缓冲长度(秒)
|
||||||
|
maxMaxBufferLength: 15, // 缓冲区长度的上限
|
||||||
|
maxBufferSize: 30 * 1000 * 1000 // 最大缓冲大小(字节)
|
||||||
|
})
|
||||||
|
hls.loadSource(item.hlsUrl)
|
||||||
|
hls.attachMedia(video)
|
||||||
|
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
|
video.play()
|
||||||
|
})
|
||||||
|
hlsRefs.push(hls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -318,18 +341,18 @@
|
|||||||
)
|
)
|
||||||
// 更新视频
|
// 更新视频
|
||||||
const postVideoRemain = async () => {
|
const postVideoRemain = async () => {
|
||||||
timer = setInterval(() => {
|
// timer = setInterval(() => {
|
||||||
clearInterval(timer)
|
// clearInterval(timer)
|
||||||
videoList.value.forEach((items,index)=>{
|
// videoList.value.forEach((items,index)=>{
|
||||||
setTimeout(()=>{
|
// setTimeout(()=>{
|
||||||
postVideoRemainApi({
|
// postVideoRemainApi({
|
||||||
cameraIndexCode: items.videos.map((item) => item.cameraIndexCode)
|
// cameraIndexCode: items.videos.map((item) => item.cameraIndexCode)
|
||||||
})
|
// })
|
||||||
},1500)
|
// },1500)
|
||||||
|
//
|
||||||
})
|
// })
|
||||||
|
//
|
||||||
}, 1500)
|
// }, 1500)
|
||||||
}
|
}
|
||||||
const getVideoRegions = async () => {
|
const getVideoRegions = async () => {
|
||||||
let res = await getVideoRegionsApi({
|
let res = await getVideoRegionsApi({
|
||||||
@@ -343,7 +366,7 @@
|
|||||||
item.videoResources=item.resourcesList[0].videoResources
|
item.videoResources=item.resourcesList[0].videoResources
|
||||||
})
|
})
|
||||||
regionList.value[0].show = true
|
regionList.value[0].show = true
|
||||||
|
|
||||||
}
|
}
|
||||||
const handleRegions = (e) => {
|
const handleRegions = (e) => {
|
||||||
regionList.value[e].show = !regionList.value[e].show
|
regionList.value[e].show = !regionList.value[e].show
|
||||||
@@ -359,7 +382,7 @@
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (timer) clearInterval(timer)
|
if (timer) clearInterval(timer)
|
||||||
clearHlsRefs()
|
clearHlsRefs()
|
||||||
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style></style>
|
<style></style>
|
||||||
@@ -393,7 +416,7 @@
|
|||||||
}
|
}
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.type-title{
|
.type-title{
|
||||||
position:absolute;
|
position:absolute;
|
||||||
left:vw(-5);
|
left:vw(-5);
|
||||||
@@ -528,7 +551,7 @@
|
|||||||
}
|
}
|
||||||
&-list {
|
&-list {
|
||||||
// gap: vw(3);
|
// gap: vw(3);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-content: flex-start;
|
align-content: flex-start;
|
||||||
@@ -547,7 +570,7 @@
|
|||||||
// border-radius: 5px; /* 滑块的圆角 */
|
// border-radius: 5px; /* 滑块的圆角 */
|
||||||
// }
|
// }
|
||||||
// overflow: auto;
|
// overflow: auto;
|
||||||
|
|
||||||
}
|
}
|
||||||
&-item {
|
&-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -138,7 +138,23 @@
|
|||||||
} else {
|
} else {
|
||||||
params.series[0].data = getSeriesData()
|
params.series[0].data = getSeriesData()
|
||||||
}
|
}
|
||||||
setOption(params)
|
const chart = setOption(params)
|
||||||
|
chart.on('legendselectchanged', function (e) {
|
||||||
|
var echartsArr = [];
|
||||||
|
for (let key in e.selected) {
|
||||||
|
if (e.selected[key]) {
|
||||||
|
echartsArr.push(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var echartsNum = 0;
|
||||||
|
props.list.forEach(item => {
|
||||||
|
if(echartsArr.includes(item.name)){
|
||||||
|
echartsNum += parseInt(item.count)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
params.series[0].label.formatter = `{label|拥堵次数}` + '\n' + `{value|${echartsNum}}`;
|
||||||
|
setOption(params);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {})
|
onMounted(() => {})
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -151,12 +151,28 @@
|
|||||||
params.series[0].label.formatter = formatLabel()
|
params.series[0].label.formatter = formatLabel()
|
||||||
params.series[0].data = getSeriesData()
|
params.series[0].data = getSeriesData()
|
||||||
}
|
}
|
||||||
setOption(params)
|
const chart = setOption(params);
|
||||||
|
chart.on('legendselectchanged', function (e) {
|
||||||
|
var echartsArr = [];
|
||||||
|
for (let key in e.selected) {
|
||||||
|
if (e.selected[key]) {
|
||||||
|
echartsArr.push(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var echartsNum = 0;
|
||||||
|
props.list.forEach(item => {
|
||||||
|
if(echartsArr.includes(item.name)){
|
||||||
|
echartsNum += parseInt(item.count)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
params.series[0].label.formatter = `{value|${echartsNum}}` + '\n' + `{name|${props.subTitle}}`;
|
||||||
|
setOption(params);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|
||||||
.nYong-du{
|
.nYong-du{
|
||||||
position:absolute;
|
position:absolute;
|
||||||
left:0;
|
left:0;
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
:seriesConfig="{ smooth: false, symbol: 'circle' }"
|
:seriesConfig="{ smooth: false, symbol: 'circle' }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="box-4 mr-8 rela">
|
<div class="box-4 mr-8 rela">
|
||||||
<Title1 title="地域分析" />
|
<Title1 title="地域分析" />
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
<span class="label">情感属性:</span>
|
<span class="label">情感属性:</span>
|
||||||
<span class="value">{{ detailsHot.newsEmotion }}</span>
|
<span class="value">{{ detailsHot.newsEmotion }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="m-modal">
|
<div class="m-modal">
|
||||||
<div class="detail-item">
|
<div class="detail-item">
|
||||||
@@ -101,7 +101,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-modal">
|
<div class="m-modal">
|
||||||
|
|
||||||
<div class="detail-item">
|
<div class="detail-item">
|
||||||
<span class="label">原文链接:</span>
|
<span class="label">原文链接:</span>
|
||||||
<a :href="detailsHot.newsUrl" target="_blank" class="link">{{ detailsHot.newsUrl }}</a>
|
<a :href="detailsHot.newsUrl" target="_blank" class="link">{{ detailsHot.newsUrl }}</a>
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
<div class="content" v-html="detailsHot.newsContent"></div>
|
<div class="content" v-html="detailsHot.newsContent"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@@ -229,7 +229,7 @@
|
|||||||
}
|
}
|
||||||
.m-modal{
|
.m-modal{
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
}
|
}
|
||||||
.detail-item {
|
.detail-item {
|
||||||
flex:1;
|
flex:1;
|
||||||
@@ -461,7 +461,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|
||||||
.select-box {
|
.select-box {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
@@ -531,6 +531,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: vh(40);
|
height: vh(40);
|
||||||
|
cursor: pointer;
|
||||||
&:nth-child(2n + 1) {
|
&:nth-child(2n + 1) {
|
||||||
background: rgba(3, 78, 153, 0.3);
|
background: rgba(3, 78, 153, 0.3);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<!-- <div class="header-left__camera" @click="videoShow = true">道路监控</div> -->
|
<!-- <div class="header-left__camera" @click="videoShow = true">道路监控</div> -->
|
||||||
<!-- <div class="header-left__point" @click="videoShow = true">3号点位</div> -->
|
<!-- <div class="header-left__point" @click="videoShow = true">3号点位</div> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="header-status">{{ item.congestLevelText }} </div>
|
<div class="header-status" v-if="item.congestLevel>0">{{ item.congestLevelText }} </div>
|
||||||
</div>
|
</div>
|
||||||
<div class="statistics">
|
<div class="statistics">
|
||||||
<div class="statistics-item">
|
<div class="statistics-item">
|
||||||
|
|||||||
Reference in New Issue
Block a user