Update package.json and package-lock.json to include flv.js and hls.js dependencies; enhance Vite config with additional API proxy; refactor App.vue and ChinaMap.vue for improved component functionality and layout; optimize MarketEnvironmentMonitor and MarketRealtimeMonitor components for better user experience; update SupplyDemandData and PurchaserAnalysis components for enhanced data presentation.
@ -0,0 +1,54 @@ |
|||||||
|
{ |
||||||
|
"pageSize": 2, |
||||||
|
"autoPlayInterval": 10000, |
||||||
|
"cameras": [ |
||||||
|
{ |
||||||
|
"id": 1, |
||||||
|
"name": "交易大厅主区", |
||||||
|
"resolution": "1920x1080", |
||||||
|
"preview": "/images/monitor/交易大厅主区.jpg", |
||||||
|
"streamUrl": "", |
||||||
|
"status": "online" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 2, |
||||||
|
"name": "牦牛展示区", |
||||||
|
"resolution": "1920x1080", |
||||||
|
"preview": "/images/monitor/牦牛展示区.jpg", |
||||||
|
"streamUrl": "", |
||||||
|
"status": "online" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 3, |
||||||
|
"name": "停车场入口", |
||||||
|
"resolution": "1280x720", |
||||||
|
"preview": "/images/monitor/停车场入口.jpg", |
||||||
|
"streamUrl": "", |
||||||
|
"status": "online" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 4, |
||||||
|
"name": "安全出口", |
||||||
|
"resolution": "1280x720", |
||||||
|
"preview": "/images/monitor/安全出口.jpg", |
||||||
|
"streamUrl": "", |
||||||
|
"status": "offline" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 5, |
||||||
|
"name": "办公区域", |
||||||
|
"resolution": "1920x1080", |
||||||
|
"preview": "/images/monitor/办公区域.jpg", |
||||||
|
"streamUrl": "", |
||||||
|
"status": "error" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"id": 6, |
||||||
|
"name": "仓储区域", |
||||||
|
"resolution": "1280x720", |
||||||
|
"preview": "/images/monitor/仓储区域.jpg", |
||||||
|
"streamUrl": "", |
||||||
|
"status": "online" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 878 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
@ -0,0 +1,76 @@ |
|||||||
|
<template> |
||||||
|
<div class="live-player"> |
||||||
|
<video |
||||||
|
ref="videoRef" |
||||||
|
class="screen-media" |
||||||
|
muted |
||||||
|
autoplay |
||||||
|
playsinline |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref, watch, onMounted, onBeforeUnmount, nextTick } from 'vue' |
||||||
|
import { createLivePlayer, destroyLivePlayer } from '../utils/liveStreamPlayer.js' |
||||||
|
|
||||||
|
const props = defineProps({ |
||||||
|
url: { |
||||||
|
type: String, |
||||||
|
default: '' |
||||||
|
}, |
||||||
|
playerConfig: { |
||||||
|
type: Object, |
||||||
|
default: () => ({}) |
||||||
|
}, |
||||||
|
active: { |
||||||
|
type: Boolean, |
||||||
|
default: true |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
const videoRef = ref(null) |
||||||
|
let playerInstance = null |
||||||
|
|
||||||
|
const teardown = () => { |
||||||
|
destroyLivePlayer(playerInstance) |
||||||
|
playerInstance = null |
||||||
|
} |
||||||
|
|
||||||
|
const setup = async () => { |
||||||
|
teardown() |
||||||
|
if (!props.active || !props.url) { |
||||||
|
return |
||||||
|
} |
||||||
|
await nextTick() |
||||||
|
if (!videoRef.value) { |
||||||
|
return |
||||||
|
} |
||||||
|
playerInstance = createLivePlayer(videoRef.value, props.url, props.playerConfig) |
||||||
|
} |
||||||
|
|
||||||
|
watch( |
||||||
|
() => [props.url, props.active, props.playerConfig], |
||||||
|
setup, |
||||||
|
{ deep: true } |
||||||
|
) |
||||||
|
|
||||||
|
onMounted(setup) |
||||||
|
onBeforeUnmount(teardown) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.live-player { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
|
||||||
|
.screen-media { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
object-fit: cover; |
||||||
|
display: block; |
||||||
|
background: #0a1418; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,123 @@ |
|||||||
|
import Hls from 'hls.js' |
||||||
|
import flvjs from 'flv.js' |
||||||
|
|
||||||
|
const DEFAULT_FORMAT_RULES = [ |
||||||
|
{ type: 'hls', match: '.m3u8', library: 'hls.js' }, |
||||||
|
{ type: 'flv', match: '.flv', library: 'flv.js' }, |
||||||
|
{ type: 'mp4', match: '.mp4', library: 'native' }, |
||||||
|
{ type: 'webm', match: '.webm', library: 'native' } |
||||||
|
] |
||||||
|
|
||||||
|
export function getFormatRules(playerConfig) { |
||||||
|
const rules = playerConfig?.formatRules |
||||||
|
return Array.isArray(rules) && rules.length ? rules : DEFAULT_FORMAT_RULES |
||||||
|
} |
||||||
|
|
||||||
|
export function detectStreamType(url, playerConfig) { |
||||||
|
if (!url) { |
||||||
|
return 'native' |
||||||
|
} |
||||||
|
const lower = ('' + url).toLowerCase() |
||||||
|
const rules = getFormatRules(playerConfig) |
||||||
|
for (const rule of rules) { |
||||||
|
const match = rule.match ? ('' + rule.match).toLowerCase() : '' |
||||||
|
if (match && lower.includes(match)) { |
||||||
|
return rule.type || 'native' |
||||||
|
} |
||||||
|
} |
||||||
|
return 'native' |
||||||
|
} |
||||||
|
|
||||||
|
export function resolveStreamUrl(camera, playerConfig) { |
||||||
|
if (!camera) { |
||||||
|
return '' |
||||||
|
} |
||||||
|
const priority = playerConfig?.urlPriority || ['hdStreamUrl', 'streamUrl', 'playUrl', 'preview'] |
||||||
|
for (const key of priority) { |
||||||
|
const value = camera[key] |
||||||
|
if (value) { |
||||||
|
return value |
||||||
|
} |
||||||
|
} |
||||||
|
return '' |
||||||
|
} |
||||||
|
|
||||||
|
export function createLivePlayer(videoEl, url, playerConfig = {}) { |
||||||
|
if (!videoEl || !url) { |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
const streamType = detectStreamType(url, playerConfig) |
||||||
|
const options = playerConfig?.playerOptions || {} |
||||||
|
|
||||||
|
if (streamType === 'hls') { |
||||||
|
if (videoEl.canPlayType('application/vnd.apple.mpegurl')) { |
||||||
|
videoEl.src = url |
||||||
|
videoEl.play().catch(() => {}) |
||||||
|
return { type: 'native-hls', videoEl } |
||||||
|
} |
||||||
|
if (Hls.isSupported()) { |
||||||
|
const hls = new Hls({ |
||||||
|
enableWorker: true, |
||||||
|
lowLatencyMode: true, |
||||||
|
...(options.hls || {}) |
||||||
|
}) |
||||||
|
hls.loadSource(url) |
||||||
|
hls.attachMedia(videoEl) |
||||||
|
hls.on(Hls.Events.MANIFEST_PARSED, () => { |
||||||
|
videoEl.play().catch(() => {}) |
||||||
|
}) |
||||||
|
hls.on(Hls.Events.ERROR, (_, data) => { |
||||||
|
if (data?.fatal) { |
||||||
|
console.error('HLS 播放失败:', data) |
||||||
|
} |
||||||
|
}) |
||||||
|
return { type: 'hls', instance: hls, videoEl } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (streamType === 'flv' && flvjs.isSupported()) { |
||||||
|
const flvPlayer = flvjs.createPlayer( |
||||||
|
{ |
||||||
|
type: 'flv', |
||||||
|
url, |
||||||
|
isLive: true, |
||||||
|
hasAudio: true, |
||||||
|
hasVideo: true, |
||||||
|
...(options.flv || {}) |
||||||
|
}, |
||||||
|
{ |
||||||
|
enableWorker: true, |
||||||
|
enableStashBuffer: false, |
||||||
|
...(options.flvMedia || {}) |
||||||
|
} |
||||||
|
) |
||||||
|
flvPlayer.attachMediaElement(videoEl) |
||||||
|
flvPlayer.load() |
||||||
|
flvPlayer.play().catch(() => {}) |
||||||
|
return { type: 'flv', instance: flvPlayer, videoEl } |
||||||
|
} |
||||||
|
|
||||||
|
videoEl.src = url |
||||||
|
videoEl.play().catch(() => {}) |
||||||
|
return { type: 'native', videoEl } |
||||||
|
} |
||||||
|
|
||||||
|
export function destroyLivePlayer(player) { |
||||||
|
if (!player) { |
||||||
|
return |
||||||
|
} |
||||||
|
if (player.type === 'hls' && player.instance) { |
||||||
|
player.instance.destroy() |
||||||
|
} else if (player.type === 'flv' && player.instance) { |
||||||
|
player.instance.pause() |
||||||
|
player.instance.unload() |
||||||
|
player.instance.detachMediaElement() |
||||||
|
player.instance.destroy() |
||||||
|
} |
||||||
|
if (player.videoEl) { |
||||||
|
player.videoEl.pause() |
||||||
|
player.videoEl.removeAttribute('src') |
||||||
|
player.videoEl.load() |
||||||
|
} |
||||||
|
} |
||||||