| | |
| | | } |
| | | |
| | | const props = defineProps<{ |
| | | containerId: string; // 【重要】外部传入唯一DOM ID |
| | | 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 play = () => { if (player) player.play(); }; |
| | | const stop = () => { if (player) player.stop(); }; |
| | | const getOSDTime = () => { |
| | | if (player) |
| | | player.getOSDTime().then((data: any) => { |
| | | console.log("getOSDTime 获取 数据", data); |
| | | }); |
| | | if (player) player.getOSDTime().then((data: any) => console.log("getOSDTime", data)); |
| | | }; |
| | | |
| | | const capturePicture = () => { |
| | | if (player) |
| | | player.capturePicture(`${new Date().getTime()}`).then((data: any) => { |
| | | console.log("capturePicture 获取 数据", data); |
| | | }); |
| | | 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 openSound = () => { if (player) player.openSound(); }; |
| | | const closeSound = () => { if (player) player.closeSound(); }; |
| | | const startSave = () => { |
| | | if (player) |
| | | player.startSave(`${new Date().getTime()}`).then((data: any) => { |
| | | console.log("startSave 获取 数据", data); |
| | | }); |
| | | 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); |
| | | }); |
| | | 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 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; |
| | | } |
| | | }; |
| | | |
| | | let playedOnce = false; |
| | | const init = () => { |
| | | if (player) { |
| | | destroy(); |
| | | } |
| | | 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: true, |
| | | autoplay: shouldAutoplay, |
| | | template: "pcLive", |
| | | height: 360, |
| | | scaleMode: 1, |
| | | staticPath: props.staticPath || "", |
| | | env: { |
| | | domain: "https://open.ys7.com", |
| | | }, |
| | | loggerOptions: { |
| | | level: "INFO", |
| | | name: "ezuikit", |
| | | showTime: true, |
| | | }, |
| | | env: { domain: "https://open.ys7.com" }, |
| | | loggerOptions: { level: "INFO", name: "ezuikit", showTime: true }, |
| | | streamInfoCBType: 1, |
| | | handleError: (err: any) => { |
| | | console.error("handleError", err); |
| | | } |
| | | handleError: (err: any) => console.error("handleError", err), |
| | | }); |
| | | |
| | | player.on(EZUIKitPlayer.EVENTS.firstFrameDisplay, () => { |
| | | if (playedOnce) return; |
| | | playedOnce = true; |
| | | setTimeout(() => { |
| | | player?.pause(); |
| | | }, 1000); |
| | | }); |
| | | // ✅ 首帧后停止拉流(保留画面),用 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.firstFrameDisplay, () => { |
| | | console.warn("firstFrameDisplay "); |
| | | }); |
| | | player.on(EZUIKitPlayer.EVENTS.streamInfoCB, (info: any) => { |
| | | console.warn("streamInfoCB ", info); |
| | | console.warn("streamInfoCB", info); |
| | | }); |
| | | |
| | | inited = true; |
| | | }; |
| | | |
| | | // 监听播放地址/token变化,重新初始化 |
| | | watch([() => props.url, () => props.accessToken], () => { |
| | | init(); |
| | | destroy().then(() => init()); |
| | | }); |
| | | |
| | | onMounted(() => { |
| | | init(); |
| | | }); |
| | | onMounted(() => { init(); }); |
| | | onBeforeUnmount(() => { destroy(); }); |
| | | |
| | | // 组件销毁释放播放器资源 |
| | | onBeforeUnmount(() => { |
| | | destroy(); |
| | | }); |
| | | |
| | | // 对外暴露实例方法 |
| | | defineExpose({ |
| | | play, |
| | | stop, |
| | | openSound, |
| | | closeSound, |
| | | capturePicture, |
| | | startSave, |
| | | stopSave, |
| | | startTalk, |
| | | stopTalk, |
| | | fullscreen, |
| | | getOSDTime, |
| | | destroy |
| | | play, stop, openSound, closeSound, capturePicture, |
| | | startSave, stopSave, startTalk, stopTalk, fullscreen, getOSDTime, destroy |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="ez-player-card"> |
| | | <!-- ID由父组件传入,保证全局唯一 --> |
| | | <div :id="containerId" style="height: 400px;width:100%"></div> |
| | | <!-- |
| | | <div class="player-buttons" style="margin-top:8px;display:flex;gap:6px;flex-wrap:wrap"> |
| | | <button @click="init">init</button> |
| | | <button @click="stop">stop</button> |
| | | <button @click="play">play</button> |
| | | <button @click="openSound">开声音</button> |
| | | <button @click="closeSound">关声音</button> |
| | | <button @click="capturePicture">截图</button> |
| | | <button @click="fullscreen">全屏</button> |
| | | </div> |
| | | --> |
| | | <div :id="containerId" style="height: 360px;width:100%"></div> |
| | | </div> |
| | | </template> |
| | | |