莹石文档接入记录

74 阅读7分钟

前置学习需要先了解一下WebAssembly,定义和目前支持情况。

WebAssembly

WebAssembly(简称Wasm)是一种低级的二进制指令格式,设计用于在Web浏览器中以接近原生代码的速度高效执行。它于2017年由W3C标准化,并得到主流浏览器的全面支持(截至2025年)。以下是其核心特点和应用场景:


核心定义与特点

  1. 高性能

    • 作为编译目标语言,允许将C/C++、Rust等语言编写的代码编译成紧凑的二进制格式,直接在浏览器中运行,性能接近原生程序,尤其适合计算密集型任务(如游戏、图像处理)。
  2. 跨平台与安全

    • 运行在沙箱环境中,无法直接访问系统资源,需通过浏览器API交互,确保安全性。
    • 支持跨浏览器和跨设备运行,无需修改代码。
  3. 与JavaScript互补

    • 不替代JavaScript,而是通过JavaScript API加载Wasm模块,二者协同工作:JavaScript处理逻辑交互,Wasm处理高性能计算。
  4. 多语言支持
    开发者可使用C/C++、Rust、Go等语言编写代码,再编译为Wasm,拓展了Web开发的工具链。


典型应用场景(2025年主流方向)

  • 图形与游戏:3D渲染、物理引擎(如Unity引擎导出至Web端)。
  • 音视频处理:实时编解码、AI驱动的图像识别(如浏览器内视频剪辑工具)。
  • 云原生与边缘计算:在服务端(如Docker替代方案)或物联网设备中运行轻量级沙箱应用。
  • 跨平台应用:通过WebAssembly System Interface(WASI),实现在浏览器外的操作系统级调用。

萤石开发接入步骤

  1. 创建Vue3项目:可能需要使用Vue CLI或Vite初始化项目,这在前面的搜索结果中有提到安装Vue CLI的步骤。
  2. 安装必要的依赖:如axios用于API请求,ezuikit-js用于萤石云播放器。
  3. 获取萤石云的访问凭证:AppKey、AppSecret和设备信息,这需要用户注册萤石云平台。
  4. 在Vue组件中初始化播放器:使用EZUIKit的API,并处理生命周期钩子以确保正确初始化和销毁实例。
  5. 添加控制功能:如云台控制、全屏播放等,参考搜索结果中的代码示例。
  6. 处理安全性和性能优化:如动态获取accessToken,避免硬编码,处理浏览器的自动播放策略。

一、前置准备

  1. 环境要求

    • Vue 3.2+
    • Node.js 16+
    • 萤石云账号(需注册并开通开发者权限,获取

    AppKey

    AppSecret

  2. 安装依赖

npm install ezuikit-js axios  # 播放器SDK + HTTP请求库

二、核心步骤

1. 初始化播放器组件
<!-- VideoPlayer.vue -->
<template>
  <div ref="playerContainer" class="video-container"></div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import axios from 'axios';

// 容器引用
const playerContainer = ref(null);
let player = null;

// 动态获取AccessToken(避免硬编码)
const getAccessToken = async () => {
  const response = await axios.post('https://open.ys7.com/api/lapp/token/get', {
    appKey: 'your-app-key',
    appSecret: 'your-app-secret'
  });
  return response.data.data.accessToken;
};

// 初始化播放器
onMounted(async () => {
  const accessToken = await getAccessToken();
  
  player = new EZUIKit.EZUIPlayer({
    id: playerContainer.value,  // 直接绑定DOM元素
    accessToken: accessToken,
    url: 'ezopen://open.ys7.com/设备序列号/1.live',  // 示例设备地址
    autoplay: false,  // 需用户交互后播放
    width: '100%',
    height: '500px',
    controls: true
  });

  // 事件监听
  player.on('play', () => console.log('Playback started'));
  player.on('error', (err) => console.error('Error:', err));
});

// 销毁实例
onUnmounted(() => {
  if (player) player.destroy();
});
</script>

<style scoped>
.video-container {
  margin: 20px auto;
  border: 1px solid #eee;
}
</style>

2. 云台控制扩展
<script setup>
// 在组件内添加控制方法
const controlPTZ = (command, speed = 0.5) => {
  if (!player) return;
  
  player.ptzControl({
    command: command.toUpperCase(),  // LEFT/RIGHT/UP/DOWN/STOP
    speed: Math.min(1, Math.max(0.1, speed))
  });
};
</script>

<template>
  <div>
    <button @click="controlPTZ('left')">左转</button>
    <button @click="controlPTZ('right')">右转</button>
    <button @click="controlPTZ('stop')">停止</button>
  </div>
</template>

三、安全与优化

  1. Token 动态管理

    • 通过后端接口获取Token(前端不应存储 appSecret

    • 使用Vue的 provide/inject全局共享Token

  2. 自动播放策略

    <button @click="player.play()" v-if="!isPlaying">手动启动播放</button>
    
  3. 多实例处理
    同一页面多个摄像头时,需为每个实例分配独立容器:

<div v-for="(device, index) in devices" :key="index">
  <VideoPlayer :device-serial="device.serial" />
</div>

四、常见问题解决

问题解决方案
黑屏无画面检查设备是否在线、url格式是否正确(需包含ezopen://协议头)
控制指令无响应确认设备支持云台功能,检查萤石云控制台的权限设置
内存泄漏确保在onUnmounted中调用player.destroy()
跨域错误在萤石云控制台配置域名白名单(如 https://your-domain.com

账号

AppKeyAppSecretaccessToken
xx27xx9at.xxm3
设备序列号萤石协议规范的url
FQ9862222url: 'ezopen://open.ys7.com/设备序列号/1.live',

需求

使用萤石 ezuikit-js SDK,结合vue3技术,实现PC端控制视频的直播和回放,设备控制、设备查询与管理功能。

  1. 默认展示第一个视频,支持切换1/4/9宫格。
  2. 每个视频可单独控制播放、回放、云台(放大缩小)、截图功能。
  3. 整体操作区切换布局。
  4. 设备查询与管理功能
  5. 设备查询与管理:需要从萤石云API获取设备列表,显示设备状态(在线/离线),并允许用户选择设备来添加到当前布局中。这部分可能需要调用萤石的设备列表接口,并处理分页和筛选。
  6. 性能优化:多视频流同时播放可能影响性能,需要考虑按需加载、非活动窗口暂停播放、分辨率切换等策略。例如,使用Intersection Observer来检测视频元素是否在视口中,从而暂停或恢复播放。
  1. 错误处理与用户反馈:处理视频加载失败、设备离线等情况,给出提示信息。例如,当设备离线时,显示离线状态并禁用控制按钮。

这个视频列表,默认展示一个屏幕展示第一个返回的视频。可选择4个视频在屏幕中间同时展示、也可以选择9个视频在屏幕中间同时展示。

每个视频可以单独控制放大、缩小、播放、回放等的操作区,也有一个整体的操作区,能控制1屏展示1个、4个、9个视频的控制。注意控制分屏后,视频加载的性能。避免卡顿。视频设备离线状态的给出列表说明。

视频列表。一个完整屏幕查看1个,可分屏 4个一屏、9个一屏’

使用萤石 ezuikit-js SDK,结合vue3技术,实现PC端控制视频的直播和回放,设备控制、设备查询与管理功能。操作区功能:开始录像、结束录像、视频截图、全屏、取消全屏、开始对讲、结束对讲

ertc-web-demo: ertc-web react 项目 demo

git clone gitee.com/Ezviz-OpenB…

常见的视频监控系统设计

<template>
  <div
    :id="id"
    class="text-icon-container"
    :class="{
      'can-hover': isButton,
      'not-allow': disabled,
      active: isActive
    }"
    @click="handleClick"
  >
    <i
      :class="{ menuNotClickIcon: menuNotClickIcon }"
      :style="{
        backgroundImage: 'url(' + getAssetUrl(icon) + ')'
      }"
    ></i>
    <span :class="disabled ? 'disabled-text' : ''">{{ text }}</span>
  </div>
</template>

<script setup name="IconText">
  const props = defineProps({
    id: {
      type: String,
      default: ''
    },
    // 文本内容
    text: {
      type: String,
      default: ''
    },
    // 图标
    icon: {
      type: String,
      default: 'recent_view',
      required: true
    },
    // 是否禁用
    disabled: {
      type: Boolean,
      default: false
    },
    // 是否是按钮,若不是按钮则样式有区别
    isButton: {
      type: Boolean,
      default: true
    },
    menuNotClickIcon: {
      type: Boolean,
      default: false // 一级菜单不允许点击icon
    }
  })

  const emits = defineEmits(['click', 'reset'])

  const isActive = ref('')

  let handleOutsideClick = null

  // 处理点击事件
  function handleClick() {
    if (!props.disabled) {
      emits('click')
      isActive.value = 'isActive'
      addOutsideClickListener()
    }
  }

  // 判断是否是一级菜单或三级菜单
  function isMenuClick(event) {
    // 一级菜单的判断条件
    const isFirstLevelMenu = event.target.className === 'menuNotClickIcon'
    console.log('isFirstLevelMenu:', event.target.className)
    // 三级菜单的判断条件
    const isThirdLevelMenu = [
      '电费管理',
      '报表管理',
      '资产管理',
      '配置管理',
      '运行监控',
      '分析诊断',
      '智慧运维',
      '运营中心',
      '系统管理'
    ].includes(event.target.innerText)

    return isFirstLevelMenu || isThirdLevelMenu
  }

  // 添加外部点击监听器
  function addOutsideClickListener() {
    const excludeElement = document.getElementById(props.id)
    handleOutsideClick = (event) => {
      // 如果点击的是一级菜单或三级菜单,则不关闭浮层
      if (isMenuClick(event)) {
        return
      }

      // 先确定可以用于判断的IconText组件元素,给isClickInside赋值
      let isClickInside
      if (
        event.target.parentElement?.id &&
        props.id &&
        event.target.parentElement?.id === props.id
      ) {
        // 判断点击事件的目标元素是否是排除元素本身,或者是否是排除元素的子元素
        isClickInside = excludeElement?.contains(event.target)
        // console.log("isClickInside:", isClickInside);
      }

      // 如果点击发生在排除元素外部,则收起浮层
      if (!isClickInside && !excludeElement?.contains(event.target)) {
        emits('reset')
        isActive.value = ''
        removeOutsideClickListener()
      }
    }
    document.addEventListener('click', handleOutsideClick)
  }

  // 移除外部点击监听器
  function removeOutsideClickListener() {
    document.removeEventListener('click', handleOutsideClick)
  }

  // 组件卸载时移除监听器
  onUnmounted(() => {
    // removeOutsideClickListener()
  })

  const getAssetUrl = (name) => {
    return new URL(`/src/assets/images/${name}.png`, import.meta.url).href
  }
</script>

<style lang="scss" scoped>
  .text-icon-container {
    @apply h-10 leading-10 mr-2.5 py-0 px-4;
    font-size: 1rem; /* 16/16 */
    border-radius: 1.25rem; /* 20/16 */
    &.can-hover {
      cursor: pointer;
      &:hover {
        background: #edf4fc;
      }
    }
    &.not-allow {
      cursor: not-allowed;
      &:hover {
        background: none;
      }
    }
    &.active {
      background: #edf4fc;
    }
    i {
      display: inline-block;
      width: 1.25rem; /* 20/16 */
      height: 1.25rem; /* 20/16 */
      background-repeat: no-repeat;
      background-size: 100% 100%;
      vertical-align: middle;
    }
    span {
      vertical-align: middle;
      margin-left: 0.3125rem; /* 5/16 */
      &.disabled-text {
        color: #a4aab3;
      }
    }
  }
</style>