Cassiopeia 谱面嵌入
完整示例 chart.json引用的原创音频 original-tone.wav已提供。下载后保持这两个文件相邻;页面原创示例的简图由示例 rendererAdapter 绘制,使用游戏主题的入口见下文本站谱面。
mountChart 默认组合 Cassiopeia kernel、Vue ChartPlayer、Our Notes plugin、Three renderer 和 Web 音频/输入宿主。它拥有挂载 DOM、媒体、input、renderer 与数据 loader。自定义 renderer 可沿用 kernel/Web host 并呈现作者自己的图形。候选产物安装与 CDN 方法见 JavaScript 嵌入。
1. 保存一个完整可运行播放器
Section titled “1. 保存一个完整可运行播放器”将完整 watch HTML另存为 index.html,再把 chart.json和original-tone.wav保存到同一个文件夹。JSON 是完整的八秒原创 ChartEmbedDocument,WAV 是音量较低的原创测试音。
my-chart/ index.html chart.json original-tone.wav在 my-chart 文件夹中打开终端,运行 python3 -m http.server 8000(Windows 用 py -m http.server 8000),再打开 http://localhost:8000/。Python 安装和编辑器 HTTP 预览方法见快速开始。完成后在终端按 Ctrl+C 关闭服务。
点击加载我的 chart.json,等待已就绪,再点播放。你会看见原创示例的音符以默认游戏画面呈现,测试音提供实际媒体时钟。暂停停止播放,进度滑块按秒跳转。关闭播放器停止媒体并移除画面,之后可以重新加载。加载或播放失败会在画面上方显示原因,修正文件或连接后重试。
托管模块提供默认播放器和配套样式;模块与公开画面素材需要联网,自己的 JSON 和 WAV 则由本地 HTTP 提供。初始例子关闭按键音效,配乐仍正常播放。
<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>观看 Cassiopeia 谱面</title><style>body{max-width:60rem;margin:auto;padding:1rem;font:1rem/1.5 sans-serif;color:CanvasText;background:Canvas}button,input,select{font:inherit}button{min-height:48px;margin:.25rem;padding:.5rem 1rem}label{display:block;margin:.5rem 0}#stage{height:min(70vh,640px);min-height:320px;position:relative}#progress{width:100%}pre{white-space:pre-wrap}</style><p><a href="https://haneoka.org/">Haneoka</a></p><h1>观看 Cassiopeia 谱面</h1><p>将 chart.json 和 original-tone.wav 放在此 HTML 旁边,即可载入八秒原创示例。</p><button id="load-example">加载我的 chart.json</button><label>本站歌曲 ID <input id="song-id" value="100070"></label><label>难度 <select id="difficulty"><option>easy</option><option>normal</option><option>hard</option><option selected>expert</option><option>special</option><option>master</option></select></label><button id="load-game">加载本站歌曲</button><button id="cancel" disabled>取消加载</button><button id="close">关闭播放器</button><p id="status" role="status" aria-live="polite">选择来源后点击加载。</p><button id="play" disabled>播放</button><button id="pause" disabled>暂停</button><label>播放位置 <input id="progress" type="range" min="0" max="0" step="0.1" value="0" disabled><output id="time">0.0 / 0.0 s</output></label><div id="stage"></div><link rel="stylesheet" href="https://docs.haneoka.org/examples/disclosure.css"><section class="docs-disclosure" data-docs-disclosure><button id="state-trigger" class="docs-disclosure__trigger" type="button" aria-expanded="false" aria-controls="state-panel" data-docs-disclosure-trigger disabled><span>当前播放器状态</span><svg class="docs-disclosure__chevron" viewBox="0 0 24 24" aria-hidden="true"><path d="M7.41 8.59 12 13.17l4.59-4.58L18 10l-6 6-6-6z" /></svg></button><div id="state-panel" class="docs-disclosure__panel" role="region" aria-labelledby="state-trigger" data-docs-disclosure-panel hidden><pre id="output"></pre></div></section><script defer src="https://docs.haneoka.org/examples/disclosure.js"></script><script type="module">const by = id => document.getElementById(id);let player, controller, generation = 0;function controls(ready) { for (const id of ["play", "pause", "progress"]) by(id).disabled = !ready; }function loading(value) { for (const id of ["load-example", "load-game"]) by(id).disabled = value; by("cancel").disabled = !value; }function refresh() { if (!player) return; const state = player.snapshot; by("progress").max = String(state.duration || 0); if (document.activeElement !== by("progress")) by("progress").value = String(state.time || 0); by("time").textContent = `${(state.time || 0).toFixed(1)} / ${(state.duration || 0).toFixed(1)} s`; by("output").textContent = JSON.stringify(state, null, 2);}async function dispose() { ++generation; controller?.abort(); controls(false); const old = player; player = undefined; await old?.dispose();}async function load(game) { loading(true); const current = ++generation; const old = player; player = undefined; controller?.abort(); controls(false); try { await old?.dispose(); if (current !== generation) return; controller = new AbortController(); const signal = controller.signal; by("status").textContent = "正在加载谱面与画面素材…"; by("output").textContent = ""; const [sdk, core] = await Promise.all([ import("https://haneoka.org/embed/cassiopeia.js"), import("https://haneoka.org/embed/core.js"), ]); signal.throwIfAborted(); if (current !== generation) return; const songId = by("song-id").value.trim(); if (game && !songId) throw new Error("先填写歌曲 ID。"); const source = game ? sdk.haneokaChartSource({ songId, difficulty: by("difficulty").value }) : core.httpDataSource({ url: new URL("./chart.json", location.href).href }); player = sdk.mountChart(by("stage"), { source, theme: sdk.haneokaChartTheme(), server: "intl", locale: document.documentElement.lang, mode: "watch", volume: 0.3, rate: 1, noteSoundEnabled: false, skin: { noteSkin: "skin001", noteEffectSkin: "effect001", currentQuality: 2, noteSeGroup: 1 }, signal, loadTimeoutMs: 45000, onEvent(event) { if (current !== generation) return; if (event.type === "state" || event.type === "player") refresh(); if (event.type === "error") by("status").textContent = String(event.error?.message ?? event.error); }, }); await player.ready; signal.throwIfAborted(); if (current !== generation) return; controls(true); refresh(); by("status").textContent = "已就绪,点击播放。"; } catch (error) { if (current === generation) { const failed = player; player = undefined; controls(false); await failed?.dispose().catch(() => {}); if (current === generation) by("status").textContent = `操作失败: ${error.message}`; } } finally { if (current === generation) loading(false); }}async function action(operation, label) { const active = player, current = generation; try { if (!active) throw new Error("先加载谱面。"); await operation(active); if (current === generation && player === active) { refresh(); by("status").textContent = label; } } catch (error) { if (current === generation) by("status").textContent = `操作失败: ${error.message}`; }}by("load-example").onclick = () => { void load(false); };by("load-game").onclick = () => { void load(true); };by("play").onclick = () => { void action(active => active.play(), "播放中"); };by("pause").onclick = () => { void action(active => active.pause(), "已暂停"); };by("progress").onchange = () => { void action(active => active.seek(Number(by("progress").value)), "播放位置已改变。"); };by("cancel").onclick = () => { controller?.abort(); player?.cancel(); };by("close").onclick = async () => { by("close").disabled = true; const current = generation + 1; try { await dispose(); if (current !== generation) return; by("status").textContent = "播放器已关闭,可以再次加载。"; } catch (error) { by("status").textContent = `操作失败: ${error.message}`; } finally { if (current === generation) { loading(false); by("close").disabled = false; by("output").textContent = ""; by("progress").value = "0"; by("time").textContent = "0.0 / 0.0 s"; } }};window.addEventListener("pagehide", () => { void dispose().catch(() => {}); }, { once: true });</script></html>2. 加载本站真实歌曲
Section titled “2. 加载本站真实歌曲”在同一网页中保持歌曲 ID 100070、难度 expert,点击加载本站歌曲。等已就绪后点播放。source 读取当前歌曲,解析它返回的谱面文件,使用返回的配乐 URL。其他歌曲 ID 可以从API 入门示例取得;歌曲必须实际提供所选难度。换来源前先关闭当前播放器。
数据 source 和画面 theme 是两个独立 factory。完整 HTML 使用公开浏览器入口:
import { mountChart, haneokaChartSource, haneokaChartTheme } from "https://haneoka.org/embed/cassiopeia.js";const chart = mountChart(container, { source: haneokaChartSource({ songId: "100070", difficulty: "expert" }), theme: haneokaChartTheme(), server: "intl", locale: "zh-CN", mode: "watch",});await chart.ready;// 播放按钮中调用 await chart.play();组件关闭时 await chart.dispose()。这段展示接入点;上面的完整 HTML 已提供 container、按钮、加载状态、取消、错误和清理。它使用轻量素材 quality 2,服务器与文本语言独立,无需账号或 release ID。
3. 每次只改变一个参数
Section titled “3. 每次只改变一个参数”在完整 HTML 的 mountChart 参数中,把 volume: 0.3 改为 0–1 的音量,或把 rate: 1 改为 0.8,刷新网页后重新加载。mode: "watch" 在点击播放后自动演奏;mode: "play" 开启手动谱面输入。加入 settings: { noteSpeed: 9 } 可以调整滚动速度。后面的参数、skin、settings、source 和 handle 表保留完整公开默认值、范围和限制。自带 JSON 使用标准 ChartDocument;其他游戏的原始谱面应先通过其 parser/converter 转换。
后文包导入供已有打包应用参考,npm 发布另行进行。最初的普通 HTML 使用上述托管 ESM;打包应用单独 import @haneoka/embed-cassiopeia/style.css,官方 facade 会挂载匹配样式,并在最后一个实例销毁时移除。
自带谱面与音频
Section titled “自带谱面与音频”ChartEmbedDocument 包含 chart,可选 audio、background、bgmOffsetMs 和 assets。chart 使用公开 ChartDocument 字段:version、bpmChanges、signatureChanges、timeScaleChanges、notes、lines、timeline、durationMs。选择 document 或 source 其中一个。
const handle = mountChart(element, { document: { chart: myChartDocument, audio: "musique/été.wav", bgmOffsetMs: 250 }, assetsBase: "https://my.example/project/", theme: haneokaChartTheme(), locale: "fr-CA", labels: { player: "Partition", pause: "Pause", loading: "Chargement" },});await handle.ready;myChartDocument 是调用方实际准备的标准谱面。音频为时钟:seek(seconds) 使用媒体/呈现秒数,谱面时间减去 bgmOffsetMs,正偏移产生前导。没有音频时为暂停视觉预览。
HTTP 通过 httpDataSource({url,decode}),本地通过 fileDataSource(jsonFile) 与 fileResourceResolver(Map)。作者可使用单语言和 Unicode 素材名。原生 theme 自己还请求公开 API/素材;严格本地 Map 应让合法的公开绝对路径通过,或提供完整自带 theme。自带素材可用 document.assets: OurNotesAssetManifest 或 theme({loader,signal,skin}) factory。
const handle = mountChart(element, { document: { chart: myChartDocument, audio: "audio.ogg" }, assetsBase: "https://my.example/project/", rendererAdapter: { async create({ element, chart, loader, signal }) { const canvas = document.createElement("canvas"); element.append(canvas); const context = canvas.getContext("2d"); const artwork = await loader.resourceUrl("notes.svg", { signal }); return { render(snapshot, options) { drawMyChart(context, chart, snapshot, options, artwork); }, resize(width, height, ratio) { canvas.width = width * ratio; canvas.height = height * ratio; }, dispose() { canvas.remove(); }, }; }, },});drawMyChart 由作者实现。adapter 路径加载 kernel/Web host;自己的 render、resize、dispose 负责图形与 SE。可选 laneAtClientPoint(x,y) 返回 0–23 连续原生 lane,外部为 -1,以接收 play-mode input。createSession 为另一个源游戏提供自己的规则;默认使用当前 Cassiopeia session。
控制、事件和销毁
Section titled “控制、事件和销毁”await chart.setOptions({ rate: 0.8, volume: 0.6, noteSoundVolume: 0.4, settings: { noteSpeed: 9 } });await chart.setSkin({ noteSkin: "skin003", noteEffectSkin: "effect001Simple", noteSeGroup: 4 });chart.seek(12);await chart.play();chart.pause();await chart.dispose();play、setOptions、setSkin、dispose 返回 Promise,pause/seek 同步。ready 表示视觉初始化,不表示媒体完整下载;在用户点击后调用 play 以解锁音频。默认 theme 更换皮肤时在当前媒体位置重新构建并暂停。skin001/002/003 为 Our Notes/Garupa/Honeycomb,effect001/effect001Simple 为效果版本,SE 组 1–4 为 Our Notes/Solid/Wood/Typing。
loadTimeoutMs 默认 30000,只覆盖构造。cancel 和外部 signal 终止 handle;dispose 幂等,加载中也能调用,拆除 DOM、停音频并释放 loader URL。重试先销毁再挂载。snapshot 提供 phase、playing、time、duration。
onEvent/subscribe 提供 load、state、player、error,load 保留 core 每个资源的进度,player 将既有播放器事件及参数转交宿主。subscribe 返回取消监听函数,监听错误与操作结果隔离。
跨域与静态分发
Section titled “跨域与静态分发”assetsBase 解析作者数据资源;theme 的 apiBase/publicBase 独立指定公开 API/原素材根。core 默认 cors/omit,可配置 fetcher、headers、credentials 与每项默认 64 MiB 预算。默认播放器的媒体/贴图/SE 使用既有原生加载器及 anonymous CORS,未经过注入 fetcher/private headers;私人素材可由宿主供应可访问 URL 或 Blob。
把全部延迟 chunk 与模块一起发布,单独链接 style.css。本站图像消费在外站遇到 Referer 403 时,可让数据传输使用 new Request(request,{referrerPolicy:"no-referrer"});原生贴图/音频请求仍由其加载器控制,需按部署的 origin/加载方式确认访问。CSP 应允许模块、素材、媒体以及使用的 blob URL。
宿主沿用已有 Material 控件和加载状态,保持舞台尺寸及触控按钮。Cassiopeia 默认显示原图标与Haneoka链接,图标半透明悬浮,画面保持全尺寸,dispose自动清理;作者素材保持原许可。
本页实际谱面应用
Section titled “本页实际谱面应用”默认八秒原创ChartDocument、轻音量WAV Blob和作者Canvas adapter,通过真实Cassiopeia session/MediaClock驱动。它展示作者集成,不冒充游戏皮肤。HTTP读取实际谱面JSON,file用你选的File;本站模式改用真实song source/native theme。修改options后加载,再play/pause/seek/setOptions,默认theme支持setSkin。
读取本站歌曲和实际谱面,使用默认游戏主题。
示例 JSON 引用的背景和音频仍由示例网址提供;使用自己的资源时,在高级参数填素材目录。
正在准备示例…
高级参数与完整输入(修改后重新加载)
点击才import/create,同页另一个实例先dispose。input/publicSource/nativeTheme/url为示例字段,会转成真实factory;callback不能写JSON,实际adapter在源码链接中。五语显示另保留作者locale输入。
import { mountChart, haneokaChartSource, haneokaChartTheme } from "https://haneoka.org/embed/r-22bfe102f7deb73f/cassiopeia.js";const chart = mountChart(element, { source: haneokaChartSource({songId:"100070",difficulty:"expert"}), theme:haneokaChartTheme(), server:"intl",locale:"en",mode:"watch" });await chart.ready;await chart.play(); // 用户按钮await chart.dispose();实际示例使用稳定官方alias。官方facade在mount注入原包CSS、最后dispose移除;包/bundler方式单独import style.css。r22没有nonce参数,后续nonce接口需取该版本真实receipt/interface。CDN文件为部署build输出,仓库保留源码。
使用本地音乐创作可编辑音符,见谱面创作器教程。其 Project JSON 和带音频备份与下面的播放 ChartDocument 分别使用对应入口。
mountChart 完整参数
Section titled “mountChart 完整参数”container必填浏览器HTMLElement且有效尺寸,非法TypeError、同容器重复Error。runtime严格source/document二选一,否则TypeError。默认composition还需theme或document.assets,自带renderer管理自己的图形。
| 字段 | 类型/默认 | 作用/错误/组合 |
|---|---|---|
| document | ChartEmbedDocument,替代source必填 | notes/lines array和finite durationMs,非法ready TypeError。 |
| source | DataSource<ChartEmbedDocument>,替代document必填 | memory/http/file/custom,load失败拒绝ready。 |
| server | string/undefined,本站intl | registry,与locale独立。 |
| locale | string/en | 作者/UI语言。 |
| assetsBase | 可选HTTP(S)目录/sourcebase | 无query/fragment,非法TypeError。 |
| resolveResource | (key,DataContext)=>URL/string/Blob/Promise | literal键,默认原key;缺资源失败。 |
| fetcher | (Request)=>Promise<Response>,fetch | core/nativebytes;公开Haneoka assets/runtime/embed no-referrer,作者/API保留策略;音乐独立anonymous CORS。 |
| credentials | omit/same-origin/include,omit | core传输,需要origin/credential允许。 |
| headers | HeadersInit,无 | core头,音乐不继承私有header。 |
| maxBytes | 正safe integer/67108864 | 每decodedresponse,非法RangeError;URL/媒体元素另计。 |
| onListenerError | (unknown)=>void,可选 | observer隔离。 |
| theme | (ChartThemeContext)=>manifest或Promise,可选 | 默认呈现,优先于document.assets;factory失败拒绝construct,setSkin需要。 |
| skin | ChartSkin/{} | 下表四字段;作者renderer不消费native皮肤。 |
| rendererAdapter | ChartRendererAdapter,可选 | 选kernel/Webhost作者呈现,自己拥有SE。 |
| createSession | (chart,playbackOptions)=>CassiopeiaSessionPort,可选 | 作者renderer替代规则,默认ChartSession;不传给默认Vue driver。 |
| signal | AbortSignal,可选 | 终止handle,ready后也适用。 |
| loadTimeoutMs | 正safe integer/30000 | 文档与每构造阶段期限,不是player生命周期;非法RangeError/超时TimeoutError。 |
| labels | {player?,pause?,loading?} | 可选string,默认Chart player/Pause/Loading,仅默认Vue。 |
| onEvent | (ChartEmbedEvent)=>void,可选 | 加载前登记。 |
| mode | chart/watch/play,watch | 预览/自动/手动input,变更沿driver reset/pause。 |
| settings | Partial<RenderSettings>+judgementOffsetMs,{} | 下表完整;setOptions合并。 |
| volume | number/.8 | 0–1,初始media超界拒绝,更新clamp,需finite。 |
| rate | number/1 | 正数规范化 .25–4;非法/非正用fallback。 |
| loop | boolean/false | 音乐loop。 |
| noteSoundEnabled | boolean/true | 默认SE,作者renderer自管SE。 |
| noteSoundVolume | number/.7 | 默认SE gain,使用finite0–1。 |
core共同参数见core。无autoplay,ready后用户操作play;construct deadline按阶段,不表示ready已下载完整音乐。
skin、labels 和 factory 全部字段
Section titled “skin、labels 和 factory 全部字段”| skin | 取值/本站默认 | 含义 |
|---|---|---|
| noteSkin | skin001/002/003,001 | Our Notes/Garupa/Honeycomb。 |
| noteEffectSkin | effect001/effect001Simple,前者 | native效果。 |
| noteSeGroup | 1–4,1 | Our Notes/Solid/Wood/Typing。 |
| currentQuality | 0/1/2,2 | high/middle/low素材,与graphicsQuality独立。 |
typed union由theme解析,坏/缺素材拒绝构造。labels.player/pause/loading全为可选string,不选择server或语言。
haneokaChartSource({songId,difficulty,apiBase?})的前两项必填,difficulty为easy/normal/hard/expert/special/master且该曲提供;apiBase 默认 https://haneoka.org/api/v1/。读取返回谱面file并parse/build,再用musicUrl;缺difficulty Error,data/budget/parser保留原因。server从mount。
haneokaChartTheme({apiBase?,publicBase?,server?}={}):API默认上述root,publicBase默认其origin根,server override默认loader.server→intl;与作者assetsBase独立。真实descriptor缺失/歧义Error。ChartThemeContext完整为loader、signal、skin;可以按signal读loader bytes并返回manifest。
settings 全部字段
Section titled “settings 全部字段”以下是public OurNotes插件RenderSettings默认,mount本身只给partial{};固定旧SDK仍保留其plugin版本。未被mount显式校验的项原样传递,使用finite和建议区间,不伪称SDK逐项验证。
| 字段 | 类型/默认 | 呈现/规范化 |
|---|---|---|
| noteSpeed | number/5 | scroll速度。 |
| noteSpeedCurve | number/1.30999994 | offset curve常量。 |
| mirror | boolean/false | 原24lane镜像。 |
| effects | boolean/true | note效果。 |
| noteSize | number/1 | .1–2 clamp。 |
| longAlpha | number/.6 | .1–1。 |
| guideAlpha | number/.6 | .1–1。 |
| graphicsQuality | number/1 | 原0/1/2高/中/低。 |
| guidelineCount | number/6 | lane分隔。 |
| guidelineOpacity | number/.25 | 建议0–1 alpha。 |
| laneOpacity | number/.8 | 建议0–1。 |
| showJudgementLine | boolean/false | 判定线。 |
| showSimultaneousLine | boolean/true | 同时线。 |
| showEaseNote | boolean/false | ease/critical强调。 |
| backgroundBrightness | number/.7 | 建议0–1。 |
| judgeResultPosition | center/lane/none,center | 位置。 |
| noteOverlapLaneBuffer | number/0 | 判定lane buffer。 |
| showFastSlow | boolean/true | 时机提示。 |
| showPerfectFastSlow | boolean/true | 包括Perfect。 |
| showJudgeOffsetMs | boolean/false | ms显示。 |
| alwaysShowFastSlow | boolean/false | 常驻偏好。 |
| scoreRankScores | readonly number[]/无 | C/B/A/S/SS阈值。 |
| judgementOffsetMs | number/0 | ms session offset,作者adapter用setOffset更新。 |
完整document/renderer接口
Section titled “完整document/renderer接口”ChartEmbedDocument.chart必填;audio/background?:string默认无,资源key/URL;bgmOffsetMs?:number默认0,chartTime=mediaMs-offset;assets?:OurNotesAssetManifest在无theme时提供默认图形,rendererAdapter可省略;无audio为暂停预览。
ChartDocument root必填字段:version、bpmChanges、signatureChanges、timeScaleChanges、notes、lines、timeline、durationMs。BPM行tick/beat/timeMs/bpm;signature行tick/beat/timeMs/numerator/denominator;scale行timeMs/scale;timeline skills行index/tick/timeMs,fever行index/startTick/endTick/startTimeMs/endTimeMs,callChanges行index/tick/timeMs/rhythms。line为id/kind:long或guide/critical/noteIds。
note全部字段:id/tick/timeMs/beat/pos/size/laneX/width;operateType/judgementType/judgementAreaOffsetType/direction;critical/judged/visible;lineIds;slideAlong;indexInLine:number/null;easeL/easeR:enum/null。使用公共converter/enums,原创例子包含全部必填。
native manifest root为id、source{game,noteSkin,laneSkin,noteEffectSkin,authoredProfile}、liveQuality{quality,effectRenderingScale,backgroundCameraFps,live2dFps}、noteAtlas、arrowGradient?、lane、particles、slideLineStyle、hud、tmpSdfFont?、noteSeGroup、noteSounds、palette、tiltThresholds[{distance,value}]。它是provider DTO;sprite/particle/audio详细descriptor见完整公开schema,资源交给loader。
rendererAdapter.create({element,chart,loader,signal})同步/Promise返回render(snapshot,options)、resize(width,height,pixelRatio)、dispose()、可选laneAtClientPoint(clientX,clientY)。snapshot复用,只在当次消费;lane连续0–23或外部-1。作者adapter用真实MediaClock/input/session;createSession可以提供自己的publicsession规则,遵循reset/update/input/finish。
handle/events 全部成员
Section titled “handle/events 全部成员”| 成员 | 签名 | 生命周期/错误 |
|---|---|---|
| ready | Promise<void> | 视觉准备,requiredSE失败拒绝,音频可还buffer。 |
| snapshot | phase/playing/time/duration | phase为loading/ready/cancelled/error/disposed,秒。 |
| play | ()=>Promise<void> | 需ready,返回浏览器audio拒绝。 |
| pause | ()=>void | 需ready,state暂停。 |
| seek | (seconds)=>void | finite非负否则RangeError,已知durationclamp,需ready。 |
| setOptions | ChartPlaybackOptions=>Promise<void> | partialsettings合并,其它交driver。 |
| setSkin | ChartSkin=>Promise<void> | ready+默认theme factory,作者/no factory Error;当前位置重建并暂停。 |
| subscribe | listener=>unsubscribe | 未来事件,disposed Error。 |
| cancel | ()=>void | boot/ready都终止,停止player/input/资源。 |
| dispose | 共用Promise<void> | 幂等,包括loading;移DOM/fullscreen监听,停media/renderer/revoke;失败AggregateError。 |
events为load{event}、state{snapshot}、player{name,args}、error{error}。默认转发playing/media-playing/timeupdate/duration/judgement/skill/fever/callchange;timeupdate/duration为秒,judgement含note/judgement/timing/diffMs/judgedAtMs/combo/maxCombo/score/scoreDelta/life。observer异常由onListenerError隔离,source/transport/theme/decoder保留原Error,没有新的错误envelope。同容器重新mount前先dispose。
当前源码的署名背景透明,图标透明度55%,48px角落触控区域悬浮在完整画面上。稳定alias随匹配构建部署采用该行为;固定r22保留旧的预留行布局,显式固定旧版时以该版行为为准。