ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Phaser 3.60 Video Game Object 全面重构指南:基于 Request Video Frame API 的可靠视频播放

2026/9/19 22:28:04 拓冰建站 浏览量
Phaser 3.60 Video Game Object 全面重构指南:基于 Request Video Frame API 的可靠视频播放 Phaser 3.60 Video Game Object 全面重构指南基于 Request Video Frame API 的可靠视频播放【免费下载链接】phaserPhaser is a fun, free and fast 2D game framework for making HTML5 games for desktop and mobile web browsers, supporting Canvas and WebGL rendering.项目地址: https://gitcode.com/gh_mirrors/ph/phaser本指南对应 Phaser 3.60 变更日志中「New Feature - Video Game Object」章节讲解 Video Game Object 从底层重写后的架构变化、API 演进与破坏性变更。你将掌握如何用新的加载签名播放本地/远程/媒体流视频、如何通过VIDEO_LOCKED等新事件处理浏览器自动播放策略、如何利用 Request Video Frame 回调实现仅在新帧就绪时更新纹理的高效渲染以及如何迁移旧代码到 v3.60 的 API。文末附源码级实现佐证与完整 Bug 修复清单方便排查兼容性问题。一、为什么 v3.60 要重写 Video Game Object在 v3.60 之前Video Game Object 长期存在三类顽固问题RTC/媒体流播放不可靠、切换标签页后视频状态错乱、以及一次性预加载过多视频文件时加载失败。Phaser 团队据此重新审视了当时浏览器视频播放的生态现状决定以Request Video Frame APIrequestVideoFrameCallback简称 RVFC为基石将 Video Game Object 从零重写见 VideoGameObject.md。重构带来的核心收益有两个纹理更新从轮询变为事件驱动旧实现依赖preUpdate方法逐帧检查视频进度再更新源纹理新实现把这一职责全部交给 Request Video Frame 回调——浏览器只有在真正解码出新视频帧时才回调一次Phaser 借此只在需要时刷新纹理。从源码看preUpdate如今仅保留了解锁重试的轮询逻辑Video.js 的 preUpdate不再承担任何纹理更新工作。每个 Video Game Object 独占一个 Video DOM 元素旧版VideoFile加载器会创建 Video DOM 元素并尝试以 blob 方式加载多个视频同时加载时浏览器很快耗尽可用的 Video 元素导致各种错误。v3.60 中加载器只做注入缓存真正的加载与播放由每个 Game Object 自己管理稳定性和可控性大幅提升。二、快速上手新的加载与播放方式2.1 预加载后播放Video Cache 方式在 Scene 的preload中调用this.load.video(key, url, noAudio)create中通过this.add.video(x, y, key)创建function preload () { this.load.video(ripley, assets/aliens.mp4); } function create () { const vid this.add.video(400, 300, ripley); }创建后调用vid.play()即可播放。这里this.add.video由 VideoFactory.js 注册它会将实例加入显示列表并返回用法与创建 Sprite 完全一致。2.2 运行时直接加载 URL不预加载视频并非必须预加载可以创建空对象后随时调用loadURLfunction create () { this.add.video(400, 300).loadURL(assets/aliens.mp4); }2.3 多格式回退与媒体流loadURL与this.load.video都支持传入URL 数组加载时会通过Phaser.Device.Video.getVideoURL自动挑选浏览器支持的第一种格式mp4 / mov / webm 是最常见的组合。加载媒体流则使用loadMediaStream// 多格式回退 this.load.video(intro, [ video/level1.mp4, video/level1.webm, video/level1.mov ]); // 直接播放 MediaStream如 getUserMedia 采集流、WebRTC 流 const stream await navigator.mediaDevices.getUserMedia({ video: true }); this.add.video(400, 300).loadMediaStream(stream);2.4 noAudio自动播放的钥匙浏览器自动播放策略Autoplay Policy限制非常严格带音轨的视频必须等待用户交互后才能播放。noAudio是加载视频时的一个布尔参数// 第三个参数 true 表示该视频没有音轨 this.load.video(pixar, nemo.mp4, true); // loadURL 的第二个参数同理 this.add.video(400, 300).loadURL(assets/aliens.mp4, true);设置noAudio: true后Phaser 会在内部给 Video 元素加上muted、defaultMuted与autoplay属性见 Video.js 的 loadHandler无音轨视频通常可以不经交互立即自动播放。注意即使视频其实含有音轨也可以强行传true视频会立即播放但声音不会响起。如果需要带声音播放就必须把等待用户交互后再播放纳入游戏流程设计。三、核心架构变化加载器与播放器的职责分离3.1 VideoFile 加载器变成轻量缓存注入器v3.60 中VideoFile.js 的load方法不再发起 XHR 请求、不再创建 Video DOM 元素、也不下载视频数据而是解析最终 URL拼接baseURL直接标记为加载完成在onProcess阶段把{ url, noAudio, crossOrigin }组装成小对象注入 Video Cache。真正的内容拉取发生在 Video Game Object 播放时由Video.load(key)从缓存中取出该对象并自行创建专属的video元素。这也是修复一次排队太多视频导致 Loader 加载不结束Fix #4910的根本手段——v3.60 下视频不再被预加载全部由 Game Object 按需管理。3.2 加载与播放回调彻底分离旧版本把加载事件和播放事件的处理混在一起导致标签页切换后视频会错误地重新开始播放。新实现将两套事件处理器彻底拆开源码中均有独立方法加载阶段addLoadEventHandlers监听error、abort、loadedmetadata在loadHandler中注册加载完成后由removeLoadEventHandlers移除Video.js播放阶段addEventHandlers在playSuccess时注册监听ended、playing、seeked、seeking、stalled、suspend、waiting等暂停或停止时由removeEventHandlers移除Video.js。四、用户交互解锁靠 Play Promise 失败驱动的重试机制浏览器可能因为自动播放策略拒绝video.play()。v3.60 的解锁方案非常巧妙检测 Play Promise 是否失败失败即认为处于输入锁定状态然后以retryInterval为间隔反复重试直到用户通过点击、按键等交互解锁Play Promise 成功 resolve 为止。Video Game Object 全程无需监听任何输入事件。机制细节结合 Video.js 与 playErrorcreatePlayPromise调用video.play()并把success/error挂到 Promise 上不支持 Promise 的旧浏览器则回退到legacyPlayHandler若失败且错误名为NotAllowedError设置touchLocked true、playWhenUnlocked true、failedPlayAttempts 1并发出VIDEO_LOCKED事件若错误名为NotSupportedError停止播放并发出VIDEO_UNSUPPORTED可用于检测浏览器是否支持 WebM 等格式其他错误停止播放并发出VIDEO_ERROR锁定状态下preUpdate每帧把增量时间累加到retry达到retryInterval默认 500ms见 Video.js就再次调用createPlayPromise(false)并清零计时一旦解锁成功playSuccess会清除touchLocked并发出VIDEO_UNLOCKED事件。相关属性touchLocked是否锁定、playWhenUnlocked解锁后是否自动播放、failedPlayAttempts累计失败次数只读。vid.on(Phaser.GameObjects.Events.VIDEO_LOCKED, () { // 显示点击开始播放提示 this.startButton.setVisible(true); }); vid.on(Phaser.GameObjects.Events.VIDEO_UNLOCKED, () { this.startButton.setVisible(false); });五、新事件体系一览v3.60 新增或调整了多个事件全部以常量形式定义在 src/gameobjects/events/index.js对应 DOM 事件名分别为complete、created、error、locked、loop、metadata、play、playing、seeked、seeking、stalled、stop、texture、unlocked、unsupported。事件常量触发时机用途VIDEO_LOCKED播放被浏览器输入策略锁定展示请点击以开始播放的 UIVIDEO_UNLOCKED输入解锁、播放成功隐藏解锁提示VIDEO_PLAYING视频开始播放或缺数据后重新播放处理标签页切换后的状态恢复VIDEO_STALLED浏览器因缺数据/数据过多而停滞缓冲中显示缓冲加载动画VIDEO_TEXTURE收到首帧数据并创建纹理获知纹理已就绪saveTexture异步化后的等待点VIDEO_UNSUPPORTED浏览器不支持该视频格式多格式回退或提示用户VIDEO_CREATED首帧回调成功创建视频纹理获知实际视频尺寸VIDEO_PLAY纹理已创建且frameReady置真播放正式开始VIDEO_COMPLETE播放结束ended播完后的收尾逻辑VIDEO_LOOP检测到循环播放循环计数等VIDEO_SEEKING/VIDEO_SEEKED开始/完成 seek进度条状态VIDEO_METADATA元数据可用loadedmetadata获知时长等信息VIDEO_STOP/VIDEO_ERROR停止 / 出错通用控制与异常处理循环检测的实现值得一提requestVideoFrame回调里通过比较当前帧的metadata.mediaTime与上一帧_lastUpdate若currentTime _lastUpdate即判定发生回绕发出VIDEO_LOOPVideo.js。六、方法 API 详解v3.60 之后6.1 播放控制play(loop, markerIn, markerOut)开始播放。内部创建 Request Video Frame 回调与 Play Promise分别处理成功与失败Video.js。markerIn/markerOut用于指定片段播放区间秒。pause()/resume()v3.60 新增的独立方法内部委托给setPaused(true/false)。系统级暂停游戏失焦与代码级暂停被分开记录_systemPaused/_codePaused游戏恢复时globalResume会自动继续播放。stop(emitStopEvent)停止播放、取消 RVFC 回调、移除播放事件处理器。注意stop不会中断未完成的下载需要中断应调用destroy。changeSource(key, autoplay, loop, markerIn, markerOut)复用现有 Video 元素切换片源。因为已解锁的视频元素保持解锁状态切换源后无需再次走解锁流程。6.2 加载方法load(key)从 Video Cache 按 key 取出视频数据url、noAudio、crossOrigin交给loadHandler是 v3.60 的新方法Video.js。loadURL(urls, noAudio, crossOrigin)接受单个 URL 或 URL 数组先经device.video.getVideoURL做格式探测再加载。新增可选参数crossOrigin取值为anonymous或use-credentials用于跨域加载视频时设置crossorigin属性。loadMediaStream(stream, noAudio, crossOrigin)加载媒体流通过srcObject或createObjectURL回退绑定流对象。已移除loadEvent参数。6.3 纹理相关saveTexture(key, flipY)把视频保存为动态纹理供 Sprite 等任何基于纹理的 Game Object 使用。v3.60 起该方法异步化——若纹理尚未创建会返回false需要监听VIDEO_TEXTURE事件或textureready事件后再使用vid.play(); vid.once(Phaser.GameObjects.Events.VIDEO_TEXTURE, (video, texture) { this.add.image(400, 300, doodle); }); vid.saveTexture(doodle);保存的纹理随播放自动更新若纹理将作为 Shader 输入且出现上下颠倒可通过flipY参数调整。snapshot()/snapshotArea(...)把当前帧绘制到CanvasTexture并返回支持指定裁剪区域与缩放尺寸。saveSnapshotTexture(key)将快照纹理注册到全局 Texture Manager供其他对象引用。6.4 定位与进度setCurrentTime(value)按秒 seek也支持2、-2.5这类相对当前时间的字符串。seekTo(value)按 01 的进度比例 seek直播流无 duration不可用。getCurrentTime()/getProgress()/getDuration()分别返回当前时间、进度01无 duration 返回 -1、时长。6.5 音视频控制setMute(value)/isMuted()静音控制并与 Sound Manager 的全局静音联动globalMute处理器。setVolume(value)/getVolume()音量 0.01.0内部用Math.Clamp限制范围Video.js。setPlaybackRate(rate)/getPlaybackRate()播放倍速如 0.5、2.0。setLoop(value)/getLoop()循环开关注意并非所有浏览器对所有编码都支持无缝循环。6.6 片段标记MarkersaddMarker(key, markerIn, markerOut)把一段视频划分成序列playMarker(key, loop)播放该序列removeMarker(key)移除标记。官方源码注释明确提示marker 计时并非逐帧精确制作视频时应为每个序列前后留出足够余量以兼容浏览器 seek 精度差异。6.7 查询状态isPlaying()/isPaused()播放/暂停状态。getVideoKey()当前视频的缓存 key非缓存来源返回空字符串。七、新属性速查v3.60 新增的属性均定义于 Video.js属性类型说明frameReadyboolean纹理是否已创建并填入首帧视频画面isStalledboolean只读视频当前是否停滞缓冲中。DOM 元素发出stalled/suspend/waiting时置真恢复播放时清空。注意停滞不一定是坏事——缓冲区已足够时浏览器也会停滞等待渲染failedPlayAttemptsnumber只读播放失败的累计次数典型原因尚未交互解锁metadataVideoFrameCallbackMetadataRequest Video Frame 回调每次调用时填充的帧元数据对象最常用mediaTime当前帧 PTS 秒数cacheKeystring只读若视频来自 Video Cache 则为对应 key否则为空字符串isSeekingboolean只读当前是否处于 seek 过程seeking置真、seeked置假八、破坏性变更与迁移清单迁移旧代码到 v3.60 必须处理以下变更加载签名简化loadEvent与asBlob两个参数被移除Video 类中所有曾包含它们的方法均已同步更新// v3.60 之前5 个参数 this.load.video(wormhole, wormhole.mp4, loadeddata, false, true); // v3.60 之后只需 key、URL 和 noAudio this.load.video(wormhole, wormhole.mp4, true);retryInterval语义变化现在表示重试调用video.play前等待的毫秒数解锁轮询间隔默认 500ms。retry语义变化从当前重试次数改为两次播放尝试之间累计的增量时间。retryLimit已移除。loadMediaStream/loadURL移除loadEvent参数loadURL首个参数支持 URL 数组两者均新增可选crossOrigin参数。play行为变化内部创建 Request Video Frame 回调与 Play Promise 处理加载成功/失败。方法重命名与移除playHandler→ 拆分为playingHandler处理playing事件与legacyPlayHandler兼容不支持 Promise 的旧浏览器timeUpdateHandler与updateTextyre原文如此即旧的纹理更新方法已移除纹理更新完全由 RVFC 回调接管。saveTexture异步化需监听VIDEO_TEXTURE事件确认纹理就绪。pause/resume新增的独立暂停/恢复方法。VIDEO_TIMEOUT事件已移除。removeVideoElementOnDestroy属性已移除Game Object 销毁时 Video 元素现在总是被移除preDestroy依次执行stop、removeLoadEventHandlers、removeVideoElement见 Video.js。九、v3.60 修复的已知问题本次重构顺带修复了大量历史缺陷均可在 VideoGameObject.md 的 Bug Fixes 一节找到RTC 流不可靠Fix #6130RVFC 方案对实时流更友好切换标签页后已播完的视频会重新播放Fix #5873现在保持停止状态多个 Video Game Object 使用同一 URL 时互相冲突Fix #5458每个对象拥有独立 Video 元素可同时播放同一视频VIDEO_COMPLETE间歇性不触发Fix #6192新回调方案与更严谨的事件处理修复了该问题同时排队过多视频导致 Loader 不结束Fix #4910视频不再预加载彻底规避无资源 key 时setDisplayOrigin产生 NaNFix #5560未加载视频前赋予默认尺寸256×256加载后重置不传noAudio时loadURL不加载、不发VIDEO_CREATEDFix by samme补充了加载事件处理器。此外Device.Video现在会检测x-m4v播放能力并存入Video.m4v属性见 src/device/Video.jsVideoFile加载器会自动利用该探测结果选择可用格式Fix #5719。十、源码与测试导航如果你想深入研读本次重构的实现建议按以下路径展开核心实现src/gameobjects/video/Video.js约 2400 行涵盖加载、播放、事件、纹理、状态查询全部逻辑关键入口loadHandler、requestVideoFrame、play、createPlayPromise、preUpdate工厂与创建器VideoFactory.jsthis.add.video与 VideoCreator.jsthis.make.video支持 VideoConfig 配置对象渲染器VideoWebGLRenderer.js 与 VideoCanvasRenderer.js二者均在无videoTexture时直接跳过渲染加载器src/loader/filetypes/VideoFile.js轻量缓存注入与 VideoFileConfig设备能力探测src/device/Video.jsh264、mov、m4v、webm、vp9、hls与hasRequestVideoFrame等标志以及getVideoURL格式挑选逻辑事件常量src/gameobjects/events/index.js测试tests/gameobjects/video/VideoFactory.test.js 验证了工厂注册与防覆盖行为另有 VideoRender / VideoWebGLRenderer / VideoCanvasRenderer 渲染相关测试。十一、总结与兼容性提示v3.60 的 Video Game Object 重构本质上是把播放状态机从 Phaser 侧迁移到浏览器原生能力上用 Request Video Frame 回调替代轮询、用 Play Promise 失败检测替代监听输入、用按需创建 Video 元素替代预加载。这套架构在移动端尤其受益——WebGL 与 Canvas 两种渲染模式下都只需在纹理层工作纹理上传由 RVFC 驱动性能与稳定性都明显优于旧实现。最后两点兼容性提醒Request Video Frame API 需要较新浏览器支持源码通过device.video.hasRequestVideoFrame标记能力官方为旧浏览器提供了 polyfill而涉及具体自动播放策略、编解码格式支持等平台差异请以目标浏览器实测为准并善用VIDEO_UNSUPPORTED、VIDEO_STALLED、VIDEO_LOCKED三个事件构建健壮的降级体验。完整变更记录可查阅 changelog/v3/3.60/CHANGELOG-v3.60.md。【免费下载链接】phaserPhaser is a fun, free and fast 2D game framework for making HTML5 games for desktop and mobile web browsers, supporting Canvas and WebGL rendering.项目地址: https://gitcode.com/gh_mirrors/ph/phaser创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考