乐于分享
好东西不私藏

Vue3+Cesium教程–视频纹理源码免费分享(第75篇)

Vue3+Cesium教程–视频纹理源码免费分享(第75篇)

欢迎关注webgis学习,一起探索Cesium高级应用,持续更新中……
视频纹理常常用于把监控视频、无人机回传视频,贴在某个地面区域上,用于指挥调度、态势展示、大屏可视化演示等场景中。先看本文实现的效果:
视频纹理比较简单,即生成一个矩形几何图形,然后将material设置成视频地址即可。
1、新建VideoTexture类,在类构造函数中增加能够播放mp4视频的vedio组件,新建rectangleGraphics矩形图形,并将视频地址作为纹理赋值到其纹理字段中。
import * as Cesium from 'cesium'export type VideoTextureRectangle = {  west: number  south: number  east: number  north: number}export interface VideoTextureCreateOptions {  videoUrl?: string  rectangle?: VideoTextureRectangle  height?: number  heightReference?: Cesium.HeightReference  classificationType?: Cesium.ClassificationType  granularity?: number  loop?: boolean  muted?: boolean  syncWithClock?: boolean}const DEFAULT_RECT: VideoTextureRectangle = {  west: 116.38,  south: 39.9,  east: 116.42,  north: 39.925,}export const DEFAULT_SAMPLE_VIDEO_URL = '/testdata/video/video.mp4'export default class VideoTexture {  private readonly viewer: Cesium.Viewer  private readonly video: HTMLVideoElement  private readonly bounds: Cesium.Rectangle  private entity: Cesium.Entity | undefined  private synchronizer: Cesium.VideoSynchronizer | undefined  constructor(viewer: Cesium.Viewer, options: VideoTextureCreateOptions = {}) {    this.viewer = viewer    const rect = options.rectangle ?? DEFAULT_RECT    this.bounds = Cesium.Rectangle.fromDegrees(      rect.west,      rect.south,      rect.east,      rect.north,    )    const heightReference =      options.heightReference ?? Cesium.HeightReference.CLAMP_TO_GROUND    let rectangleHeight: number | undefined    if (heightReference === Cesium.HeightReference.CLAMP_TO_GROUND) {      rectangleHeight = undefined    } else if (heightReference === Cesium.HeightReference.RELATIVE_TO_GROUND) {      rectangleHeight = options.height ?? 12    } else {      rectangleHeight = options.height ?? 0    }    const classificationType =      options.classificationType ?? Cesium.ClassificationType.TERRAIN    const granularity = options.granularity     const loop = options.loop ?? true    const muted = options.muted ?? true    const url = options.videoUrl ?? DEFAULT_SAMPLE_VIDEO_URL    this.video = document.createElement('video')    this.video.style.display = 'none'    this.video.setAttribute('playsinline''')    this.video.playsInline = true    this.video.crossOrigin = 'anonymous'    this.video.loop = loop    this.video.muted = muted    this.video.preload = 'auto'    this.video.src = url    document.body.appendChild(this.video)    if (options.syncWithClock) {      this.synchronizer = new Cesium.VideoSynchronizer({        clock: viewer.clock,        element: this.video,      })    }    const rectangleGraphics: Cesium.RectangleGraphics.ConstructorOptions = {      coordinates: this.bounds,      heightReference,      classificationType,      granularity,      material: new Cesium.ImageMaterialProperty({        image: this.video,      }),    }    if (rectangleHeight !== undefined) {      rectangleGraphics.height = rectangleHeight    }    this.entity = viewer.entities.add({      rectangle: rectangleGraphics,    })    void this.video.play().catch(() => {})  }  getVideoElement(): HTMLVideoElement {    return this.video  }  getEntity(): Cesium.Entity | undefined {    return this.entity  }  async play(): Promise<void> {    await this.video.play()  }  pause(): void {    this.video.pause()  }  setMuted(muted: boolean): void {    this.video.muted = muted  }  setLoop(loop: boolean): void {    this.video.loop = loop  }  setShow(show: boolean): void {    if (this.entity) this.entity.show = show  }  flyTo(duration = 2.0): void {    this.viewer.camera.flyTo({      destination: this.bounds,      duration,    })  }  /** 更换视频源(会重新 load,创建后调用) */  async setSource(url: string): Promise<void> {    this.pause()    this.video.src = url    this.video.load()    await this.video.play().catch(() => {})  }  destroy(): void {    this.pause()    if (this.synchronizer && !this.synchronizer.isDestroyed()) {      this.synchronizer.destroy()      this.synchronizer = undefined    }    if (this.entity) {      this.viewer.entities.remove(this.entity)      this.entity = undefined    }    this.video.removeAttribute('src')    this.video.load()    this.video.remove()  }}
2、前端vue文件中只需生成一个VideoTexture类对象即可。
const createTexture = () => {  if(!viewer || active.value) return  active.value = new VideoTexture(viewer, {    videoUrl: videoUrl.value || undefined,    muted: muted.value,    looptrue,  })  playing.value = true  active.value.flyTo(2)}
3、视频文件可以选择播放和暂停。
const togglePlay = async () => {  if (!active.valuereturn  const v = active.value.getVideoElement()  if (v.paused) {    await active.value.play()    playing.value = true  } else {    active.value.pause()    playing.value = false  }}
4、视频文件还可以选择是否静音。
  setMuted(mutedboolean): void {    this.video.muted = muted  }
5、 前端页面代码
  <divclass="video-texture-panel">    <divclass="section-title">      <spanclass="title">视频纹理(矩形贴地)</span>    </div>    <divclass="form-row">      <spanclass="label">视频地址</span>      <el-inputv-model="videoUrl"size="small"placeholder="mp4 / m3u8(需浏览器支持)"clearable />    </div>    <divclass="actions">      <el-buttontype="primary"size="small":disabled="!!active" @click="createTexture">        创建视频纹理      </el-button>      <el-buttontype="danger"size="small":disabled="!active" @click="removeTexture">        移除      </el-button>      <el-buttonsize="small":disabled="!active" @click="flyTo">飞到区域</el-button>    </div>    <divv-if="active"class="playback">      <el-buttonsize="small" @click="togglePlay">{{ playing ? '暂停' : '播放' }}</el-button>      <spanclass="muted-label">静音</span>      <el-switchv-model="muted"size="small" @change="onMutedChange" />    </div>  </div>
6、实现效果
7、本系列教程代码采用Vue3+Cesium+Typescript封装编写,读者朋友如需全部源码的话,欢迎在公众号输入栏私信咨询作者。
欢迎关注本公众号,如需其他合作,欢迎私信。