wangrong
2026-08-23 fc55533e7c2e41263dc06270966e287755c975e2
修复站点地图摄像头播放
4个文件已修改
198 ■■■■■ 已修改文件
src/modules/ezuikit/components/ezuikit-player.vue 187 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/maps/views/site.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/monitor/views/device.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/report/views/dailysite.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/ezuikit/components/ezuikit-player.vue
@@ -23,181 +23,144 @@
}
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>
src/modules/maps/views/site.vue
@@ -62,11 +62,12 @@
                    <div v-for="dev in cameras" :key="dev.id" class="cam">
                        <div class="cam__name">{{ dev.iotName || dev.iotCode }}</div>
                        <ezuikit-player
                            v-if="ysToken"
                            :key="'p_' + dev.id + '_' + ysToken"
                            v-if="tab === 'live' && ysToken"
                            :key="'p_' + dev.id"
                            :container-id="'site_player_' + dev.id"
                            :access-token="ysToken"
                            :url="`ezopen://open.ys7.com/${dev.iotCode}/1.hd.live`"
                            :url="`ezopen://open.ys7.com/${dev.iotCode}/1.hd.live`"
                            :pauseAfterFirstFrame="true"
                        />
                    </div>
                </el-tab-pane>
src/modules/monitor/views/device.vue
@@ -13,7 +13,7 @@
          <!-- <div class="title">{{ dev.iotName }}</div> -->
          <ezuikit-player v-if="ysToken" :key="'p_' + dev.id + '_' + ysToken" :container-id="'player_' + dev.id"
            :access-token="ysToken" :url="`ezopen://open.ys7.com/${dev.iotCode}/1.hd.live`"
            :pause-at-first-frame="true" />
            :pauseAfterFirstFrame="true" />
        </div>
      </div>
    </div>
src/modules/report/views/dailysite.vue
@@ -6,7 +6,7 @@
            <!-- 新增按钮 -->
            <!--cl-add-btn /-->
            <!-- 删除按钮 -->
            <!--cl-multi-delete-btn /-->// ...只展示部分字段... //
            <!--cl-multi-delete-btn /-->
            <cl-flex1 />
            <!-- 条件搜索 -->
            <cl-search ref="Search" />