<!-- components/ezuikit-player.vue -->
|
<script lang="ts" setup>
|
defineOptions({
|
name: 'ezuikit-player'
|
});
|
import { EZUIKitPlayer } from "ezuikit-js";
|
import { onMounted, onBeforeUnmount, ref, watch } from "vue";
|
|
interface IPlayer {
|
play: Function;
|
stop: Function;
|
getOSDTime: Function;
|
capturePicture: Function;
|
openSound: Function;
|
closeSound: Function;
|
startSave: Function;
|
stopSave: Function;
|
startTalk: Function;
|
stopTalk: Function;
|
fullscreen: Function;
|
destroy: Function;
|
on: any
|
}
|
|
const props = defineProps<{
|
containerId: string;
|
accessToken: string;
|
url: string;
|
staticPath?: string;
|
/** 首帧后自动停止拉流(保留画面),默认 false */
|
pauseAfterFirstFrame?: boolean;
|
}>();
|
|
let player: IPlayer | null = null;
|
let resizeObserver: ResizeObserver | null = null;
|
let inited = false;
|
|
// 方法暴露给父组件
|
const play = () => { if (player) player.play(); };
|
const stop = () => { if (player) player.stop(); };
|
const getOSDTime = () => {
|
if (player) player.getOSDTime().then((data: any) => console.log("getOSDTime", data));
|
};
|
const capturePicture = () => {
|
if (player) player.capturePicture(`${Date.now()}`).then((data: any) => console.log("capturePicture", data));
|
};
|
const openSound = () => { if (player) player.openSound(); };
|
const closeSound = () => { if (player) player.closeSound(); };
|
const startSave = () => {
|
if (player) player.startSave(`${Date.now()}`).then((data: any) => console.log("startSave", data));
|
};
|
const stopSave = () => {
|
if (player) player.stopSave().then((data: any) => console.log("stopSave", data));
|
};
|
const startTalk = () => { if (player) player.startTalk(); };
|
const stopTalk = () => { if (player) player.stopTalk(); };
|
const fullscreen = () => { if (player) player.fullscreen(); };
|
|
const destroy = async () => {
|
resizeObserver?.disconnect();
|
resizeObserver = null;
|
if (player) {
|
try {
|
await player.stop().catch(() => {});
|
await player.destroy();
|
} catch (e) {
|
console.warn("播放器销毁异常", e);
|
}
|
player = null;
|
inited = false;
|
}
|
};
|
|
const init = () => {
|
if (inited) return;
|
if (player) { player.destroy(); player = null; }
|
if (!props.containerId || !props.url || !props.accessToken) return;
|
|
const el = document.getElementById(props.containerId);
|
if (!el) return;
|
|
// ✅ 容器不可见 → 等可见后再初始化
|
if (!el.offsetParent || el.clientWidth === 0) {
|
resizeObserver?.disconnect();
|
resizeObserver = new ResizeObserver((entries) => {
|
for (const entry of entries) {
|
const t = entry.target as HTMLElement;
|
if (t.clientWidth > 0 && t.clientHeight > 0) {
|
resizeObserver?.disconnect();
|
resizeObserver = null;
|
init();
|
break;
|
}
|
}
|
});
|
resizeObserver.observe(el);
|
return;
|
}
|
|
// ✅ 根据 prop 决定 autoplay
|
const shouldAutoplay = props.pauseAfterFirstFrame ?? false;
|
|
player = new EZUIKitPlayer({
|
id: props.containerId,
|
accessToken: props.accessToken,
|
url: props.url,
|
autoplay: shouldAutoplay,
|
template: "pcLive",
|
height: 360,
|
scaleMode: 1,
|
staticPath: props.staticPath || "",
|
env: { domain: "https://open.ys7.com" },
|
loggerOptions: { level: "INFO", name: "ezuikit", showTime: true },
|
streamInfoCBType: 1,
|
handleError: (err: any) => console.error("handleError", err),
|
});
|
|
// ✅ 首帧后停止拉流(保留画面),用 stop() 不用 pause()
|
if (shouldAutoplay) {
|
let playedOnce = false;
|
player.on(EZUIKitPlayer.EVENTS.firstFrameDisplay, () => {
|
if (playedOnce) return;
|
playedOnce = true;
|
console.warn(`[ezuikit ${props.containerId}] firstFrameDisplay → 1.5s 后 stop 保留画面`);
|
setTimeout(() => {
|
player?.pause();
|
}, 1000);
|
});
|
}
|
|
// 通用事件
|
player.on(EZUIKitPlayer.EVENTS.firstFrameDisplay, () => {
|
console.warn(`[ezuikit ${props.containerId}] firstFrameDisplay ✅`);
|
});
|
player.on(EZUIKitPlayer.EVENTS.videoInfo, (info: any) => {
|
console.warn("videoInfo", info);
|
});
|
player.on(EZUIKitPlayer.EVENTS.audioInfo, (info: any) => {
|
console.warn("audioInfo", info);
|
});
|
player.on(EZUIKitPlayer.EVENTS.streamInfoCB, (info: any) => {
|
console.warn("streamInfoCB", info);
|
});
|
|
inited = true;
|
};
|
|
watch([() => props.url, () => props.accessToken], () => {
|
destroy().then(() => init());
|
});
|
|
onMounted(() => { init(); });
|
onBeforeUnmount(() => { destroy(); });
|
|
defineExpose({
|
play, stop, openSound, closeSound, capturePicture,
|
startSave, stopSave, startTalk, stopTalk, fullscreen, getOSDTime, destroy
|
});
|
</script>
|
|
<template>
|
<div class="ez-player-card">
|
<div :id="containerId" style="height: 360px;width:100%"></div>
|
</div>
|
</template>
|
|
<style scoped>
|
.ez-player-card {
|
border: 0px solid #e4e7ed;
|
border-radius: 6px;
|
padding: 0px;
|
background: #000;
|
}
|
|
/* 手机扩展区里的圆形云台。SDK 用 deepmerge 合并配置时数组合并,单靠删掉 controls 里的 ptz 去不掉 */
|
.ez-player-card :deep(.ez-mobile-ptz) {
|
display: none;
|
}
|
</style>
|