@pixi-ui-editor/runtime-pixi 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/SceneViewport.js.map +1 -1
  2. package/dist/assets/bitmapFonts.d.ts +2 -0
  3. package/dist/assets/bitmapFonts.d.ts.map +1 -1
  4. package/dist/assets/bitmapFonts.js +3 -1
  5. package/dist/assets/bitmapFonts.js.map +1 -1
  6. package/dist/assets/sounds.d.ts +1 -7
  7. package/dist/assets/sounds.d.ts.map +1 -1
  8. package/dist/assets/sounds.js +7 -90
  9. package/dist/assets/sounds.js.map +1 -1
  10. package/dist/assets/textures.d.ts +10 -0
  11. package/dist/assets/textures.d.ts.map +1 -1
  12. package/dist/assets/textures.js +10 -2
  13. package/dist/assets/textures.js.map +1 -1
  14. package/dist/audio/bus.d.ts +60 -0
  15. package/dist/audio/bus.d.ts.map +1 -0
  16. package/dist/audio/bus.js +38 -0
  17. package/dist/audio/bus.js.map +1 -0
  18. package/dist/audio/director.d.ts +35 -0
  19. package/dist/audio/director.d.ts.map +1 -0
  20. package/dist/audio/director.js +137 -0
  21. package/dist/audio/director.js.map +1 -0
  22. package/dist/controls.js.map +1 -1
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +3 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/layout.js.map +1 -1
  28. package/dist/particles.js.map +1 -1
  29. package/dist/scene.d.ts +6 -2
  30. package/dist/scene.d.ts.map +1 -1
  31. package/dist/scene.js +5 -2
  32. package/dist/scene.js.map +1 -1
  33. package/dist/transformSpace.d.ts +119 -0
  34. package/dist/transformSpace.d.ts.map +1 -0
  35. package/dist/transformSpace.js +487 -0
  36. package/dist/transformSpace.js.map +1 -0
  37. package/dist/views/ButtonNodeView.d.ts +4 -3
  38. package/dist/views/ButtonNodeView.d.ts.map +1 -1
  39. package/dist/views/ButtonNodeView.js +9 -8
  40. package/dist/views/ButtonNodeView.js.map +1 -1
  41. package/dist/views/CheckboxNodeView.d.ts +4 -3
  42. package/dist/views/CheckboxNodeView.d.ts.map +1 -1
  43. package/dist/views/CheckboxNodeView.js +7 -7
  44. package/dist/views/CheckboxNodeView.js.map +1 -1
  45. package/dist/views/ItemTableNodeView.js.map +1 -1
  46. package/dist/views/PaginationNodeView.js.map +1 -1
  47. package/dist/views/ParticleEmitterNodeView.js.map +1 -1
  48. package/dist/views/StagedButtonNodeView.d.ts +4 -3
  49. package/dist/views/StagedButtonNodeView.d.ts.map +1 -1
  50. package/dist/views/StagedButtonNodeView.js +7 -9
  51. package/dist/views/StagedButtonNodeView.js.map +1 -1
  52. package/dist/views/basic.js.map +1 -1
  53. package/dist/views/createNodeView.d.ts +2 -2
  54. package/dist/views/createNodeView.d.ts.map +1 -1
  55. package/dist/views/createNodeView.js +4 -4
  56. package/dist/views/createNodeView.js.map +1 -1
  57. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"SceneViewport.js","sourceRoot":"","sources":["../src/SceneViewport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAwC,MAAM,aAAa,CAAC;AAEtF;;;;;;GAMG;AACH,MAAM,OAAO,aAAc,SAAQ,SAAS;IACjC,iBAAiB,CAAa;IAC9B,OAAO,CAAY;IAE5B,YAAY,OAAkB,EAAE,iBAA6B,EAAE,IAAa;QAC1E,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,iBAAiB,GAAG,EAAE,GAAG,iBAAiB,EAAE,CAAC;QAElD,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,YAAY,GAAG,IAAI,QAAQ,EAAE;iBAChC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,CAAC,MAAM,CAAC;iBAC7D,IAAI,CAAC,QAAQ,CAAC,CAAC;YAClB,YAAY,CAAC,SAAS,GAAG,MAAM,CAAC;YAChC,OAAO,CAAC,IAAI,GAAG,YAAY,CAAC;YAC5B,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QAC9B,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAED,sFAAsF;IACtF,MAAM,CAAC,IAAgB;QACrB,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAAC;QAC/D,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC7B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;QACtC,OAAO,MAAM,CAAC;IAChB,CAAC;CACF","sourcesContent":["import { Container, Graphics } from \"pixi.js\";\r\nimport { fitViewportToHost, type FittedViewport, type LayoutSize } from \"./layout.js\";\r\n\r\n/**\r\n * The single host boundary for a built scene.\r\n *\r\n * Runtime scenes are clipped to the authored reference viewport and can only be fitted with one\r\n * uniform scale. Authoring scenes use the same wrapper without clipping so the editor can expose\r\n * off-viewport content while arranging it.\r\n */\r\nexport class SceneViewport extends Container {\r\n readonly referenceViewport: LayoutSize;\r\n readonly content: Container;\r\n\r\n constructor(content: Container, referenceViewport: LayoutSize, clip: boolean) {\r\n super();\r\n this.content = content;\r\n this.referenceViewport = { ...referenceViewport };\r\n\r\n if (clip) {\r\n const viewportMask = new Graphics()\r\n .rect(0, 0, referenceViewport.width, referenceViewport.height)\r\n .fill(0xffffff);\r\n viewportMask.eventMode = \"none\";\r\n content.mask = viewportMask;\r\n this.addChild(viewportMask);\r\n }\r\n\r\n this.addChild(content);\r\n }\r\n\r\n /** Uniformly contains and centers this authored viewport inside the physical host. */\r\n resize(host: LayoutSize): FittedViewport {\r\n const fitted = fitViewportToHost(this.referenceViewport, host);\r\n this.scale.set(fitted.scale);\r\n this.position.set(fitted.x, fitted.y);\r\n return fitted;\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"SceneViewport.js","sourceRoot":"","sources":["../src/SceneViewport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAwC,MAAM,aAAa,CAAC;AAEtF;;;;;;GAMG;AACH,MAAM,OAAO,aAAc,SAAQ,SAAS;IACjC,iBAAiB,CAAa;IAC9B,OAAO,CAAY;IAE5B,YAAY,OAAkB,EAAE,iBAA6B,EAAE,IAAa;QAC1E,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,iBAAiB,GAAG,EAAE,GAAG,iBAAiB,EAAE,CAAC;QAElD,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,YAAY,GAAG,IAAI,QAAQ,EAAE;iBAChC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,CAAC,MAAM,CAAC;iBAC7D,IAAI,CAAC,QAAQ,CAAC,CAAC;YAClB,YAAY,CAAC,SAAS,GAAG,MAAM,CAAC;YAChC,OAAO,CAAC,IAAI,GAAG,YAAY,CAAC;YAC5B,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QAC9B,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAED,sFAAsF;IACtF,MAAM,CAAC,IAAgB;QACrB,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAAC;QAC/D,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC7B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;QACtC,OAAO,MAAM,CAAC;IAChB,CAAC;CACF","sourcesContent":["import { Container, Graphics } from \"pixi.js\";\nimport { fitViewportToHost, type FittedViewport, type LayoutSize } from \"./layout.js\";\n\n/**\n * The single host boundary for a built scene.\n *\n * Runtime scenes are clipped to the authored reference viewport and can only be fitted with one\n * uniform scale. Authoring scenes use the same wrapper without clipping so the editor can expose\n * off-viewport content while arranging it.\n */\nexport class SceneViewport extends Container {\n readonly referenceViewport: LayoutSize;\n readonly content: Container;\n\n constructor(content: Container, referenceViewport: LayoutSize, clip: boolean) {\n super();\n this.content = content;\n this.referenceViewport = { ...referenceViewport };\n\n if (clip) {\n const viewportMask = new Graphics()\n .rect(0, 0, referenceViewport.width, referenceViewport.height)\n .fill(0xffffff);\n viewportMask.eventMode = \"none\";\n content.mask = viewportMask;\n this.addChild(viewportMask);\n }\n\n this.addChild(content);\n }\n\n /** Uniformly contains and centers this authored viewport inside the physical host. */\n resize(host: LayoutSize): FittedViewport {\n const fitted = fitViewportToHost(this.referenceViewport, host);\n this.scale.set(fitted.scale);\n this.position.set(fitted.x, fitted.y);\n return fitted;\n }\n}\n"]}
@@ -4,6 +4,8 @@ import type { FileUrlResolver } from "./textures.js";
4
4
  type BitmapFontAsset = Extract<Asset, {
5
5
  type: "bitmapFont";
6
6
  }>;
7
+ /** Exported so callers invalidating a specific asset (e.g. after a Replace) can address the same cache entry. */
8
+ export declare const bitmapFontAssetCacheKey: (asset: BitmapFontAsset) => string;
7
9
  /**
8
10
  * Loads scene bitmap fonts before views are built, mirroring `loadSceneFonts`. Unlike a regular
9
11
  * `FontFace`, PixiJS resolves a `BitmapText`'s font by looking up `Cache` for `${fontFamily}-bitmap`
@@ -1 +1 @@
1
- {"version":3,"file":"bitmapFonts.d.ts","sourceRoot":"","sources":["../../src/assets/bitmapFonts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,eAAe,EAAU,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAoC,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAMrF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,KAAK,eAAe,GAAG,OAAO,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC,CAAC;AAuC9D;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,eAAe,EAAE,KAAK,GAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAAyB,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAYjO;AAED;;;;GAIG;AACH,wBAAsB,0BAA0B,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,eAAe,EAAE,KAAK,GAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAAyB,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAGlO"}
1
+ {"version":3,"file":"bitmapFonts.d.ts","sourceRoot":"","sources":["../../src/assets/bitmapFonts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,eAAe,EAAU,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAoC,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAMrF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,KAAK,eAAe,GAAG,OAAO,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC,CAAC;AAE9D,iHAAiH;AACjH,eAAO,MAAM,uBAAuB,UAAW,eAAe,WAAwD,CAAC;AAsCvH;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,eAAe,EAAE,KAAK,GAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAAyB,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAYjO;AAED;;;;GAIG;AACH,wBAAsB,0BAA0B,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,eAAe,EAAE,KAAK,GAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAAyB,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAGlO"}
@@ -2,7 +2,9 @@ import { BitmapFont, Cache, bitmapFontXMLStringParser } from "pixi.js";
2
2
  import { collectRenderedNodes } from "../scene.js";
3
3
  import { sharedBitmapFontCache } from "./cache.js";
4
4
  import { loadTexture } from "./loadTexture.js";
5
- const keyOf = (asset) => `${asset.files.xml.uri}#${asset.files.texture.uri}`;
5
+ /** Exported so callers invalidating a specific asset (e.g. after a Replace) can address the same cache entry. */
6
+ export const bitmapFontAssetCacheKey = (asset) => `${asset.files.xml.uri}#${asset.files.texture.uri}`;
7
+ const keyOf = bitmapFontAssetCacheKey;
6
8
  /**
7
9
  * The synthetic family every loaded font installs under — never whatever `<info face>` its XML
8
10
  * declares. Two uploaded bitmap fonts could share the same exporter-generated face name, and the
@@ -1 +1 @@
1
- {"version":3,"file":"bitmapFonts.js","sourceRoot":"","sources":["../../src/assets/bitmapFonts.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Asset, ProjectDocument, UINode } from \"@pixi-ui-editor/schema\";\r\nimport { BitmapFont, Cache, bitmapFontXMLStringParser, type Texture } from \"pixi.js\";\r\nimport { collectRenderedNodes } from \"../scene.js\";\r\nimport { sharedBitmapFontCache } from \"./cache.js\";\r\nimport { loadTexture } from \"./loadTexture.js\";\r\n// Type-only: erased at compile time, so this does not create a runtime import cycle even though\r\n// `textures.ts` imports a value (`loadBitmapFontGlyphTexture`) from this file.\r\nimport type { FileUrlResolver } from \"./textures.js\";\r\n\r\ntype BitmapFontAsset = Extract<Asset, { type: \"bitmapFont\" }>;\r\n\r\nconst keyOf = (asset: BitmapFontAsset) => `${asset.files.xml.uri}#${asset.files.texture.uri}`;\r\n/**\r\n * The synthetic family every loaded font installs under — never whatever `<info face>` its XML\r\n * declares. Two uploaded bitmap fonts could share the same exporter-generated face name, and the\r\n * asset id is the only identity PixiJS's `${fontFamily}-bitmap` Cache key is guaranteed not to collide on.\r\n */\r\nconst familyOf = (assetId: string) => `bitmap-font-${assetId}`;\r\nconst fromNodes = (nodes: UINode[]) => nodes.flatMap((node) => node.type === \"bitmap-text\"\r\n ? [node.assetId, node.bitmapTextOverrides?.desktop?.assetId, node.bitmapTextOverrides?.mobile?.assetId].filter((assetId): assetId is string => assetId !== undefined)\r\n : []);\r\n\r\n/**\r\n * Shared by `loadSceneFonts`'s reasoning (see `sharedFontCache`): a loaded `BitmapFont` registers into\r\n * PixiJS's own global `Cache`, so re-parsing the same asset under a caller-scoped cache would still\r\n * collide there. `cache` defaults to that shared map but accepts a `SceneAssetCache.bitmapFonts` from\r\n * the caller, the same override every other scene asset loader takes.\r\n */\r\nfunction loadFont(asset: BitmapFontAsset, resolveFileUrl: FileUrlResolver, cache: Map<string, Promise<BitmapFont | undefined>>): Promise<BitmapFont | undefined> {\r\n const key = keyOf(asset);\r\n let pending = cache.get(key);\r\n if (pending === undefined) {\r\n pending = (async () => {\r\n const xmlUrl = resolveFileUrl(asset.files.xml.uri);\r\n const textureUrl = resolveFileUrl(asset.files.texture.uri);\r\n if (xmlUrl === undefined || textureUrl === undefined) return undefined;\r\n try {\r\n const [xml, texture] = await Promise.all([fetch(xmlUrl).then((response) => response.text()), loadTexture(textureUrl)]);\r\n const font = new BitmapFont({ data: bitmapFontXMLStringParser.parse(xml), textures: [texture] });\r\n Cache.set(`${familyOf(asset.id)}-bitmap`, font);\r\n return font;\r\n } catch (error) { console.warn(`Unable to load bitmap font asset '${asset.id}'. Text using it will not render.`, error); return undefined; }\r\n })();\r\n cache.set(key, pending);\r\n }\r\n return pending;\r\n}\r\n\r\n/**\r\n * Loads scene bitmap fonts before views are built, mirroring `loadSceneFonts`. Unlike a regular\r\n * `FontFace`, PixiJS resolves a `BitmapText`'s font by looking up `Cache` for `${fontFamily}-bitmap`\r\n * (see `BitmapFontManager.getFont`), so loading here means parsing the XML, building one `BitmapFont`\r\n * from the already-fetched texture, and registering it in that cache — there is no URL-based loader\r\n * to delegate to, since our XML and texture live at unrelated asset URIs, not a fixed relative pair.\r\n */\r\nexport async function loadSceneBitmapFonts(document: ProjectDocument, sceneId: string, resolveFileUrl: FileUrlResolver, cache: Map<string, Promise<BitmapFont | undefined>> = sharedBitmapFontCache): Promise<Map<string, string>> {\r\n const scene = document.scenes.find((item) => item.id === sceneId);\r\n if (scene === undefined) throw new Error(`Scene '${sceneId}' does not exist in the project document.`);\r\n const ids = new Set(fromNodes(collectRenderedNodes(document, scene)));\r\n const fonts = new Map<string, string>();\r\n await Promise.all([...ids].map(async (id) => {\r\n const asset = document.assets.find((item) => item.id === id);\r\n if (asset?.type !== \"bitmapFont\") return;\r\n const font = await loadFont(asset, resolveFileUrl, cache);\r\n if (font !== undefined) fonts.set(id, familyOf(asset.id));\r\n }));\r\n return fonts;\r\n}\r\n\r\n/**\r\n * Resolves one glyph's already-cropped `Texture` from a bitmap font asset, for reuse as a plain image\r\n * asset (an Image node, a Button/Checkbox state, ...) — see `BitmapFontAsset.frames`. `BitmapFont`\r\n * builds one `Texture` per character up front (see its constructor), so this is a lookup, not a crop.\r\n */\r\nexport async function loadBitmapFontGlyphTexture(asset: BitmapFontAsset, glyph: string, resolveFileUrl: FileUrlResolver, cache: Map<string, Promise<BitmapFont | undefined>> = sharedBitmapFontCache): Promise<Texture | undefined> {\r\n const font = await loadFont(asset, resolveFileUrl, cache);\r\n return font?.chars[glyph]?.texture;\r\n}\r\n"]}
1
+ {"version":3,"file":"bitmapFonts.js","sourceRoot":"","sources":["../../src/assets/bitmapFonts.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Asset, ProjectDocument, UINode } from \"@pixi-ui-editor/schema\";\r\nimport { BitmapFont, Cache, bitmapFontXMLStringParser, type Texture } from \"pixi.js\";\r\nimport { collectRenderedNodes } from \"../scene.js\";\r\nimport { sharedBitmapFontCache } from \"./cache.js\";\r\nimport { loadTexture } from \"./loadTexture.js\";\r\n// Type-only: erased at compile time, so this does not create a runtime import cycle even though\r\n// `textures.ts` imports a value (`loadBitmapFontGlyphTexture`) from this file.\r\nimport type { FileUrlResolver } from \"./textures.js\";\r\n\r\ntype BitmapFontAsset = Extract<Asset, { type: \"bitmapFont\" }>;\r\n\r\n/** Exported so callers invalidating a specific asset (e.g. after a Replace) can address the same cache entry. */\r\nexport const bitmapFontAssetCacheKey = (asset: BitmapFontAsset) => `${asset.files.xml.uri}#${asset.files.texture.uri}`;\r\nconst keyOf = bitmapFontAssetCacheKey;\r\n/**\r\n * The synthetic family every loaded font installs under — never whatever `<info face>` its XML\r\n * declares. Two uploaded bitmap fonts could share the same exporter-generated face name, and the\r\n * asset id is the only identity PixiJS's `${fontFamily}-bitmap` Cache key is guaranteed not to collide on.\r\n */\r\nconst familyOf = (assetId: string) => `bitmap-font-${assetId}`;\r\nconst fromNodes = (nodes: UINode[]) => nodes.flatMap((node) => node.type === \"bitmap-text\"\r\n ? [node.assetId, node.bitmapTextOverrides?.desktop?.assetId, node.bitmapTextOverrides?.mobile?.assetId].filter((assetId): assetId is string => assetId !== undefined)\r\n : []);\r\n\r\n/**\r\n * Shared by `loadSceneFonts`'s reasoning (see `sharedFontCache`): a loaded `BitmapFont` registers into\r\n * PixiJS's own global `Cache`, so re-parsing the same asset under a caller-scoped cache would still\r\n * collide there. `cache` defaults to that shared map but accepts a `SceneAssetCache.bitmapFonts` from\r\n * the caller, the same override every other scene asset loader takes.\r\n */\r\nfunction loadFont(asset: BitmapFontAsset, resolveFileUrl: FileUrlResolver, cache: Map<string, Promise<BitmapFont | undefined>>): Promise<BitmapFont | undefined> {\r\n const key = keyOf(asset);\r\n let pending = cache.get(key);\r\n if (pending === undefined) {\r\n pending = (async () => {\r\n const xmlUrl = resolveFileUrl(asset.files.xml.uri);\r\n const textureUrl = resolveFileUrl(asset.files.texture.uri);\r\n if (xmlUrl === undefined || textureUrl === undefined) return undefined;\r\n try {\r\n const [xml, texture] = await Promise.all([fetch(xmlUrl).then((response) => response.text()), loadTexture(textureUrl)]);\r\n const font = new BitmapFont({ data: bitmapFontXMLStringParser.parse(xml), textures: [texture] });\r\n Cache.set(`${familyOf(asset.id)}-bitmap`, font);\r\n return font;\r\n } catch (error) { console.warn(`Unable to load bitmap font asset '${asset.id}'. Text using it will not render.`, error); return undefined; }\r\n })();\r\n cache.set(key, pending);\r\n }\r\n return pending;\r\n}\r\n\r\n/**\r\n * Loads scene bitmap fonts before views are built, mirroring `loadSceneFonts`. Unlike a regular\r\n * `FontFace`, PixiJS resolves a `BitmapText`'s font by looking up `Cache` for `${fontFamily}-bitmap`\r\n * (see `BitmapFontManager.getFont`), so loading here means parsing the XML, building one `BitmapFont`\r\n * from the already-fetched texture, and registering it in that cache — there is no URL-based loader\r\n * to delegate to, since our XML and texture live at unrelated asset URIs, not a fixed relative pair.\r\n */\r\nexport async function loadSceneBitmapFonts(document: ProjectDocument, sceneId: string, resolveFileUrl: FileUrlResolver, cache: Map<string, Promise<BitmapFont | undefined>> = sharedBitmapFontCache): Promise<Map<string, string>> {\r\n const scene = document.scenes.find((item) => item.id === sceneId);\r\n if (scene === undefined) throw new Error(`Scene '${sceneId}' does not exist in the project document.`);\r\n const ids = new Set(fromNodes(collectRenderedNodes(document, scene)));\r\n const fonts = new Map<string, string>();\r\n await Promise.all([...ids].map(async (id) => {\r\n const asset = document.assets.find((item) => item.id === id);\r\n if (asset?.type !== \"bitmapFont\") return;\r\n const font = await loadFont(asset, resolveFileUrl, cache);\r\n if (font !== undefined) fonts.set(id, familyOf(asset.id));\r\n }));\r\n return fonts;\r\n}\r\n\r\n/**\r\n * Resolves one glyph's already-cropped `Texture` from a bitmap font asset, for reuse as a plain image\r\n * asset (an Image node, a Button/Checkbox state, ...) — see `BitmapFontAsset.frames`. `BitmapFont`\r\n * builds one `Texture` per character up front (see its constructor), so this is a lookup, not a crop.\r\n */\r\nexport async function loadBitmapFontGlyphTexture(asset: BitmapFontAsset, glyph: string, resolveFileUrl: FileUrlResolver, cache: Map<string, Promise<BitmapFont | undefined>> = sharedBitmapFontCache): Promise<Texture | undefined> {\r\n const font = await loadFont(asset, resolveFileUrl, cache);\r\n return font?.chars[glyph]?.texture;\r\n}\r\n"]}
@@ -1,14 +1,8 @@
1
1
  import type { Sound } from "@pixi/sound";
2
- import type { ProjectDocument, Scene } from "@pixi-ui-editor/schema";
2
+ import type { ProjectDocument } from "@pixi-ui-editor/schema";
3
3
  import type { FileUrlResolver } from "./textures.js";
4
4
  /** Loads every sound asset up front, useful for an editor Preview whose assignments change live. */
5
5
  export declare function loadProjectSounds(document: ProjectDocument, resolveFileUrl: FileUrlResolver, cache?: Map<string, Sound>): Promise<Map<string, Sound>>;
6
6
  /** Loads the background track and button sounds used by one rendered scene. Failed assets stay silent. */
7
7
  export declare function loadSceneSounds(document: ProjectDocument, sceneId: string, resolveFileUrl: FileUrlResolver, cache?: Map<string, Sound>): Promise<Map<string, Sound>>;
8
- export type SceneAudioPlayback = {
9
- update(audio: Scene["audio"], sounds: ReadonlyMap<string, Sound>): void;
10
- stop(): void;
11
- };
12
- /** Owns one scene's looping background track and retries browser-blocked autoplay on first input. */
13
- export declare function createSceneAudioPlayback(): SceneAudioPlayback;
14
8
  //# sourceMappingURL=sounds.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sounds.d.ts","sourceRoot":"","sources":["../../src/assets/sounds.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAkB,MAAM,aAAa,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAGrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAiDrD,oGAAoG;AACpG,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,eAAe,EAC/B,KAAK,GAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAa,GACpC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAE7B;AAED,0GAA0G;AAC1G,wBAAsB,eAAe,CACnC,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,eAAe,EAC/B,KAAK,GAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAa,GACpC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAU7B;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC;IACxE,IAAI,IAAI,IAAI,CAAC;CACd,CAAC;AAEF,qGAAqG;AACrG,wBAAgB,wBAAwB,IAAI,kBAAkB,CA4E7D"}
1
+ {"version":3,"file":"sounds.d.ts","sourceRoot":"","sources":["../../src/assets/sounds.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAG9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAuDrD,oGAAoG;AACpG,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,eAAe,EAC/B,KAAK,GAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAa,GACpC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAE7B;AAED,0GAA0G;AAC1G,wBAAsB,eAAe,CACnC,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,eAAe,EAC/B,KAAK,GAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAa,GACpC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAU7B"}
@@ -18,6 +18,10 @@ async function loadSound(source) {
18
18
  }
19
19
  });
20
20
  }
21
+ // Same reasoning as `imageAssetCacheKey` (textures.ts): `source.uri` survives a Replace unchanged, but
22
+ // `replaceAssetSource` stamps a fresh `source.version`, so the version must be part of the cache key or
23
+ // a replaced sound keeps playing its pre-replace audio forever.
24
+ const soundAssetCacheKey = (asset) => `${asset.source.uri}#${asset.source.version ?? ""}`;
21
25
  async function loadSoundAssets(document, ids, resolveFileUrl, cache = new Map()) {
22
26
  const assetsById = new Map(document.assets.map((asset) => [asset.id, asset]));
23
27
  const sounds = new Map();
@@ -26,7 +30,8 @@ async function loadSoundAssets(document, ids, resolveFileUrl, cache = new Map())
26
30
  if (asset?.type !== "sound")
27
31
  continue;
28
32
  try {
29
- let loaded = cache.get(asset.source.uri);
33
+ const key = soundAssetCacheKey(asset);
34
+ let loaded = cache.get(key);
30
35
  if (loaded === undefined) {
31
36
  const url = resolveFileUrl(asset.source.uri);
32
37
  if (url === undefined)
@@ -35,7 +40,7 @@ async function loadSoundAssets(document, ids, resolveFileUrl, cache = new Map())
35
40
  if (!response.ok)
36
41
  throw new Error(`Unable to fetch '${url}': ${response.status} ${response.statusText}`);
37
42
  loaded = await loadSound(await response.arrayBuffer());
38
- cache.set(asset.source.uri, loaded);
43
+ cache.set(key, loaded);
39
44
  }
40
45
  sounds.set(asset.id, loaded);
41
46
  }
@@ -65,92 +70,4 @@ export async function loadSceneSounds(document, sceneId, resolveFileUrl, cache =
65
70
  }
66
71
  return loadSoundAssets(document, ids, resolveFileUrl, cache);
67
72
  }
68
- /** Owns one scene's looping background track and retries browser-blocked autoplay on first input. */
69
- export function createSceneAudioPlayback() {
70
- let assetId;
71
- let currentSound;
72
- let currentInstance;
73
- let currentVolume = 1;
74
- let pendingStart;
75
- const removePendingStart = () => {
76
- if (pendingStart !== undefined && typeof window !== "undefined")
77
- window.removeEventListener("pointerdown", pendingStart);
78
- pendingStart = undefined;
79
- };
80
- const stopCurrent = () => {
81
- removePendingStart();
82
- currentSound?.stop();
83
- currentInstance = undefined;
84
- currentSound = undefined;
85
- assetId = undefined;
86
- };
87
- const audioContextIsSuspended = (sound) => {
88
- const context = sound.context;
89
- return context.audioContext?.state === "suspended";
90
- };
91
- const scheduleRetry = (start) => {
92
- if (typeof window === "undefined" || pendingStart !== undefined)
93
- return;
94
- pendingStart = () => {
95
- pendingStart = undefined;
96
- currentInstance?.stop();
97
- currentInstance = undefined;
98
- const context = currentSound?.context?.audioContext;
99
- if (context?.resume !== undefined)
100
- void context.resume().finally(start);
101
- else
102
- start();
103
- };
104
- window.addEventListener("pointerdown", pendingStart, { once: true });
105
- };
106
- const playback = {
107
- update(audio, sounds) {
108
- const nextAssetId = audio?.backgroundMusicAssetId;
109
- const nextSound = nextAssetId === undefined ? undefined : sounds.get(nextAssetId);
110
- const volume = audio?.volume ?? 1;
111
- currentVolume = volume;
112
- if (nextAssetId === undefined || nextSound === undefined) {
113
- stopCurrent();
114
- return;
115
- }
116
- if (assetId === nextAssetId && currentSound === nextSound) {
117
- if (currentInstance !== undefined)
118
- currentInstance.volume = currentVolume;
119
- return;
120
- }
121
- stopCurrent();
122
- assetId = nextAssetId;
123
- currentSound = nextSound;
124
- const start = () => {
125
- if (assetId !== nextAssetId || currentSound !== nextSound)
126
- return;
127
- removePendingStart();
128
- try {
129
- const result = nextSound.play({ loop: true, volume: currentVolume });
130
- if (result instanceof Promise) {
131
- void result.then((instance) => {
132
- if (assetId === nextAssetId && currentSound === nextSound) {
133
- currentInstance = instance;
134
- instance.volume = currentVolume;
135
- }
136
- else
137
- instance.stop();
138
- }).catch(() => scheduleRetry(start));
139
- }
140
- else {
141
- currentInstance = result;
142
- if (audioContextIsSuspended(nextSound))
143
- scheduleRetry(start);
144
- }
145
- }
146
- catch {
147
- scheduleRetry(start);
148
- }
149
- };
150
- start();
151
- },
152
- stop: stopCurrent,
153
- };
154
- return playback;
155
- }
156
73
  //# sourceMappingURL=sounds.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sounds.js","sourceRoot":"","sources":["../../src/assets/sounds.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Sound, IMediaInstance } from \"@pixi/sound\";\r\nimport type { ProjectDocument, Scene } from \"@pixi-ui-editor/schema\";\r\nimport { collectRenderedNodes } from \"../scene.js\";\r\nimport { collectNodeAssetIds } from \"../views/createNodeView.js\";\r\nimport type { FileUrlResolver } from \"./textures.js\";\r\n\r\nasync function loadSound(source: ArrayBuffer): Promise<Sound> {\r\n const { Sound } = await import(\"@pixi/sound\");\r\n return new Promise((resolve, reject) => {\r\n let created: Sound;\r\n try {\r\n created = Sound.from({\r\n source,\r\n preload: true,\r\n // @pixi/sound's runtime uses the Node-style `null` success value although its declaration\r\n // only mentions Error. Accept both null and undefined or every successful decode is rejected.\r\n loaded: (error, loaded) => error == null ? resolve(loaded ?? created) : reject(error),\r\n });\r\n } catch (error) {\r\n reject(error);\r\n }\r\n });\r\n}\r\n\r\nasync function loadSoundAssets(\r\n document: ProjectDocument,\r\n ids: Iterable<string>,\r\n resolveFileUrl: FileUrlResolver,\r\n cache: Map<string, Sound> = new Map(),\r\n): Promise<Map<string, Sound>> {\r\n const assetsById = new Map(document.assets.map((asset) => [asset.id, asset]));\r\n const sounds = new Map<string, Sound>();\r\n for (const assetId of ids) {\r\n const asset = assetsById.get(assetId);\r\n if (asset?.type !== \"sound\") continue;\r\n try {\r\n let loaded = cache.get(asset.source.uri);\r\n if (loaded === undefined) {\r\n const url = resolveFileUrl(asset.source.uri);\r\n if (url === undefined) continue;\r\n const response = await fetch(url);\r\n if (!response.ok) throw new Error(`Unable to fetch '${url}': ${response.status} ${response.statusText}`);\r\n loaded = await loadSound(await response.arrayBuffer());\r\n cache.set(asset.source.uri, loaded);\r\n }\r\n sounds.set(asset.id, loaded);\r\n } catch (error) {\r\n console.warn(`Unable to load sound for asset '${asset.id}'.`, error);\r\n }\r\n }\r\n return sounds;\r\n}\r\n\r\n/** Loads every sound asset up front, useful for an editor Preview whose assignments change live. */\r\nexport function loadProjectSounds(\r\n document: ProjectDocument,\r\n resolveFileUrl: FileUrlResolver,\r\n cache: Map<string, Sound> = new Map(),\r\n): Promise<Map<string, Sound>> {\r\n return loadSoundAssets(document, document.assets.filter((asset) => asset.type === \"sound\").map((asset) => asset.id), resolveFileUrl, cache);\r\n}\r\n\r\n/** Loads the background track and button sounds used by one rendered scene. Failed assets stay silent. */\r\nexport async function loadSceneSounds(\r\n document: ProjectDocument,\r\n sceneId: string,\r\n resolveFileUrl: FileUrlResolver,\r\n cache: Map<string, Sound> = new Map(),\r\n): Promise<Map<string, Sound>> {\r\n const scene = document.scenes.find((candidate) => candidate.id === sceneId);\r\n if (scene === undefined) throw new Error(`Scene '${sceneId}' does not exist in the project document.`);\r\n const assetsById = new Map(document.assets.map((asset) => [asset.id, asset]));\r\n const ids = new Set<string>();\r\n if (scene.audio?.backgroundMusicAssetId !== undefined) ids.add(scene.audio.backgroundMusicAssetId);\r\n for (const node of collectRenderedNodes(document, scene)) {\r\n for (const assetId of collectNodeAssetIds(node)) if (assetsById.get(assetId)?.type === \"sound\") ids.add(assetId);\r\n }\r\n return loadSoundAssets(document, ids, resolveFileUrl, cache);\r\n}\r\n\r\nexport type SceneAudioPlayback = {\r\n update(audio: Scene[\"audio\"], sounds: ReadonlyMap<string, Sound>): void;\r\n stop(): void;\r\n};\r\n\r\n/** Owns one scene's looping background track and retries browser-blocked autoplay on first input. */\r\nexport function createSceneAudioPlayback(): SceneAudioPlayback {\r\n let assetId: string | undefined;\r\n let currentSound: Sound | undefined;\r\n let currentInstance: IMediaInstance | undefined;\r\n let currentVolume = 1;\r\n let pendingStart: (() => void) | undefined;\r\n\r\n const removePendingStart = () => {\r\n if (pendingStart !== undefined && typeof window !== \"undefined\") window.removeEventListener(\"pointerdown\", pendingStart);\r\n pendingStart = undefined;\r\n };\r\n const stopCurrent = () => {\r\n removePendingStart();\r\n currentSound?.stop();\r\n currentInstance = undefined;\r\n currentSound = undefined;\r\n assetId = undefined;\r\n };\r\n const audioContextIsSuspended = (sound: Sound) => {\r\n const context = sound.context as { audioContext?: { state?: string } };\r\n return context.audioContext?.state === \"suspended\";\r\n };\r\n const scheduleRetry = (start: () => void) => {\r\n if (typeof window === \"undefined\" || pendingStart !== undefined) return;\r\n pendingStart = () => {\r\n pendingStart = undefined;\r\n currentInstance?.stop();\r\n currentInstance = undefined;\r\n const context = (currentSound?.context as { audioContext?: { resume?: () => Promise<void> } } | undefined)?.audioContext;\r\n if (context?.resume !== undefined) void context.resume().finally(start);\r\n else start();\r\n };\r\n window.addEventListener(\"pointerdown\", pendingStart, { once: true });\r\n };\r\n\r\n const playback: SceneAudioPlayback = {\r\n update(audio, sounds) {\r\n const nextAssetId = audio?.backgroundMusicAssetId;\r\n const nextSound = nextAssetId === undefined ? undefined : sounds.get(nextAssetId);\r\n const volume = audio?.volume ?? 1;\r\n currentVolume = volume;\r\n if (nextAssetId === undefined || nextSound === undefined) {\r\n stopCurrent();\r\n return;\r\n }\r\n if (assetId === nextAssetId && currentSound === nextSound) {\r\n if (currentInstance !== undefined) currentInstance.volume = currentVolume;\r\n return;\r\n }\r\n\r\n stopCurrent();\r\n assetId = nextAssetId;\r\n currentSound = nextSound;\r\n const start = () => {\r\n if (assetId !== nextAssetId || currentSound !== nextSound) return;\r\n removePendingStart();\r\n try {\r\n const result = nextSound.play({ loop: true, volume: currentVolume });\r\n if (result instanceof Promise) {\r\n void result.then((instance) => {\r\n if (assetId === nextAssetId && currentSound === nextSound) { currentInstance = instance; instance.volume = currentVolume; }\r\n else instance.stop();\r\n }).catch(() => scheduleRetry(start));\r\n } else {\r\n currentInstance = result;\r\n if (audioContextIsSuspended(nextSound)) scheduleRetry(start);\r\n }\r\n } catch {\r\n scheduleRetry(start);\r\n }\r\n };\r\n start();\r\n },\r\n stop: stopCurrent,\r\n };\r\n return playback;\r\n}\r\n"]}
1
+ {"version":3,"file":"sounds.js","sourceRoot":"","sources":["../../src/assets/sounds.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Sound } from \"@pixi/sound\";\r\nimport type { ProjectDocument } from \"@pixi-ui-editor/schema\";\r\nimport { collectRenderedNodes } from \"../scene.js\";\r\nimport { collectNodeAssetIds } from \"../views/createNodeView.js\";\r\nimport type { FileUrlResolver } from \"./textures.js\";\r\n\r\nasync function loadSound(source: ArrayBuffer): Promise<Sound> {\r\n const { Sound } = await import(\"@pixi/sound\");\r\n return new Promise((resolve, reject) => {\r\n let created: Sound;\r\n try {\r\n created = Sound.from({\r\n source,\r\n preload: true,\r\n // @pixi/sound's runtime uses the Node-style `null` success value although its declaration\r\n // only mentions Error. Accept both null and undefined or every successful decode is rejected.\r\n loaded: (error, loaded) => error == null ? resolve(loaded ?? created) : reject(error),\r\n });\r\n } catch (error) {\r\n reject(error);\r\n }\r\n });\r\n}\r\n\r\n// Same reasoning as `imageAssetCacheKey` (textures.ts): `source.uri` survives a Replace unchanged, but\r\n// `replaceAssetSource` stamps a fresh `source.version`, so the version must be part of the cache key or\r\n// a replaced sound keeps playing its pre-replace audio forever.\r\nconst soundAssetCacheKey = (asset: Extract<ProjectDocument[\"assets\"][number], { type: \"sound\" }>): string => `${asset.source.uri}#${asset.source.version ?? \"\"}`;\r\n\r\nasync function loadSoundAssets(\r\n document: ProjectDocument,\r\n ids: Iterable<string>,\r\n resolveFileUrl: FileUrlResolver,\r\n cache: Map<string, Sound> = new Map(),\r\n): Promise<Map<string, Sound>> {\r\n const assetsById = new Map(document.assets.map((asset) => [asset.id, asset]));\r\n const sounds = new Map<string, Sound>();\r\n for (const assetId of ids) {\r\n const asset = assetsById.get(assetId);\r\n if (asset?.type !== \"sound\") continue;\r\n try {\r\n const key = soundAssetCacheKey(asset);\r\n let loaded = cache.get(key);\r\n if (loaded === undefined) {\r\n const url = resolveFileUrl(asset.source.uri);\r\n if (url === undefined) continue;\r\n const response = await fetch(url);\r\n if (!response.ok) throw new Error(`Unable to fetch '${url}': ${response.status} ${response.statusText}`);\r\n loaded = await loadSound(await response.arrayBuffer());\r\n cache.set(key, loaded);\r\n }\r\n sounds.set(asset.id, loaded);\r\n } catch (error) {\r\n console.warn(`Unable to load sound for asset '${asset.id}'.`, error);\r\n }\r\n }\r\n return sounds;\r\n}\r\n\r\n/** Loads every sound asset up front, useful for an editor Preview whose assignments change live. */\r\nexport function loadProjectSounds(\r\n document: ProjectDocument,\r\n resolveFileUrl: FileUrlResolver,\r\n cache: Map<string, Sound> = new Map(),\r\n): Promise<Map<string, Sound>> {\r\n return loadSoundAssets(document, document.assets.filter((asset) => asset.type === \"sound\").map((asset) => asset.id), resolveFileUrl, cache);\r\n}\r\n\r\n/** Loads the background track and button sounds used by one rendered scene. Failed assets stay silent. */\r\nexport async function loadSceneSounds(\r\n document: ProjectDocument,\r\n sceneId: string,\r\n resolveFileUrl: FileUrlResolver,\r\n cache: Map<string, Sound> = new Map(),\r\n): Promise<Map<string, Sound>> {\r\n const scene = document.scenes.find((candidate) => candidate.id === sceneId);\r\n if (scene === undefined) throw new Error(`Scene '${sceneId}' does not exist in the project document.`);\r\n const assetsById = new Map(document.assets.map((asset) => [asset.id, asset]));\r\n const ids = new Set<string>();\r\n if (scene.audio?.backgroundMusicAssetId !== undefined) ids.add(scene.audio.backgroundMusicAssetId);\r\n for (const node of collectRenderedNodes(document, scene)) {\r\n for (const assetId of collectNodeAssetIds(node)) if (assetsById.get(assetId)?.type === \"sound\") ids.add(assetId);\r\n }\r\n return loadSoundAssets(document, ids, resolveFileUrl, cache);\r\n}\r\n"]}
@@ -3,10 +3,20 @@ import { Spritesheet, type BitmapFont, type Texture } from "pixi.js";
3
3
  import { loadTexture } from "./loadTexture.js";
4
4
  export type AssetUrlResolver = (asset: Asset) => string | undefined;
5
5
  export type FileUrlResolver = (uri: string) => string | undefined;
6
+ type ImageAsset = Extract<Asset, {
7
+ type: "image";
8
+ }>;
6
9
  type AtlasAsset = Extract<Asset, {
7
10
  type: "atlas";
8
11
  }>;
9
12
  export { loadTexture };
13
+ /**
14
+ * Cache key for an image asset's loaded `Texture`. `replaceAssetSource` (editor-web `assets.slice.ts`)
15
+ * keeps `source.uri` stable across a Replace (it is derived from the asset's name/id/media type, not its
16
+ * content) but stamps a fresh `source.version`, the same signal `loadSceneFonts` already keys on — so a
17
+ * plain `source.uri` key would keep serving the pre-replace `Texture` forever, invisible to the caller.
18
+ */
19
+ export declare const imageAssetCacheKey: (asset: ImageAsset) => string;
10
20
  /** Loads and parses one atlas's Spritesheet, sharing the parse by the JSON file's URI. */
11
21
  export declare function loadAtlasSpritesheet(asset: AtlasAsset, resolveFileUrl: FileUrlResolver, cache?: Map<string, Spritesheet>): Promise<Spritesheet>;
12
22
  /** Loads textures for image nodes in a scene, resolving atlas frame ids through their Spritesheet. Leaves failed assets for placeholder rendering. */
@@ -1 +1 @@
1
- {"version":3,"file":"textures.d.ts","sourceRoot":"","sources":["../../src/assets/textures.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,KAAK,EAAE,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACjG,OAAO,EAAE,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,OAAO,EAAwB,MAAM,SAAS,CAAC;AAK3F,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,GAAG,SAAS,CAAC;AACpE,MAAM,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;AAClE,KAAK,UAAU,GAAG,OAAO,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAGpD,OAAO,EAAE,WAAW,EAAE,CAAC;AAEvB,0FAA0F;AAC1F,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,UAAU,EACjB,cAAc,EAAE,eAAe,EAC/B,KAAK,GAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAa,GAC1C,OAAO,CAAC,WAAW,CAAC,CAgBtB;AAED,sJAAsJ;AACtJ,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,MAAM,EACf,eAAe,EAAE,gBAAgB,EACjC,cAAc,EAAE,eAAe,EAC/B,KAAK,GAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAa,EACvC,gBAAgB,GAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAa,EACtD,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAAyB,GACpF,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAsD/B"}
1
+ {"version":3,"file":"textures.d.ts","sourceRoot":"","sources":["../../src/assets/textures.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,KAAK,EAAE,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACjG,OAAO,EAAE,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,OAAO,EAAwB,MAAM,SAAS,CAAC;AAK3F,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,GAAG,SAAS,CAAC;AACpE,MAAM,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;AAClE,KAAK,UAAU,GAAG,OAAO,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AACpD,KAAK,UAAU,GAAG,OAAO,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAGpD,OAAO,EAAE,WAAW,EAAE,CAAC;AAEvB;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,UAAW,UAAU,KAAG,MAA6D,CAAC;AAErH,0FAA0F;AAC1F,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,UAAU,EACjB,cAAc,EAAE,eAAe,EAC/B,KAAK,GAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAa,GAC1C,OAAO,CAAC,WAAW,CAAC,CAgBtB;AAED,sJAAsJ;AACtJ,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,MAAM,EACf,eAAe,EAAE,gBAAgB,EACjC,cAAc,EAAE,eAAe,EAC/B,KAAK,GAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAa,EACvC,gBAAgB,GAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAa,EACtD,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAAyB,GACpF,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAuD/B"}
@@ -6,6 +6,13 @@ import { loadBitmapFontGlyphTexture } from "./bitmapFonts.js";
6
6
  import { sharedBitmapFontCache } from "./cache.js";
7
7
  import { loadTexture } from "./loadTexture.js";
8
8
  export { loadTexture };
9
+ /**
10
+ * Cache key for an image asset's loaded `Texture`. `replaceAssetSource` (editor-web `assets.slice.ts`)
11
+ * keeps `source.uri` stable across a Replace (it is derived from the asset's name/id/media type, not its
12
+ * content) but stamps a fresh `source.version`, the same signal `loadSceneFonts` already keys on — so a
13
+ * plain `source.uri` key would keep serving the pre-replace `Texture` forever, invisible to the caller.
14
+ */
15
+ export const imageAssetCacheKey = (asset) => `${asset.source.uri}#${asset.source.version ?? ""}`;
9
16
  /** Loads and parses one atlas's Spritesheet, sharing the parse by the JSON file's URI. */
10
17
  export async function loadAtlasSpritesheet(asset, resolveFileUrl, cache = new Map()) {
11
18
  const cached = cache.get(asset.files.json.uri);
@@ -53,8 +60,9 @@ export async function loadSceneTextures(document, sceneId, resolveAssetUrl, reso
53
60
  if (url === undefined)
54
61
  continue;
55
62
  try {
56
- const texture = cache.get(asset.source.uri) ?? await loadTexture(url);
57
- cache.set(asset.source.uri, texture);
63
+ const key = imageAssetCacheKey(asset);
64
+ const texture = cache.get(key) ?? await loadTexture(url);
65
+ cache.set(key, texture);
58
66
  textures.set(asset.id, texture);
59
67
  }
60
68
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"textures.js","sourceRoot":"","sources":["../../src/assets/textures.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAoC,MAAM,wBAAwB,CAAC;AACjG,OAAO,EAAE,WAAW,EAAuD,MAAM,SAAS,CAAC;AAC3F,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAO/C,OAAO,EAAE,WAAW,EAAE,CAAC;AAEvB,0FAA0F;AAC1F,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,KAAiB,EACjB,cAA+B,EAC/B,KAAK,GAA6B,IAAI,GAAG,EAAE;IAE3C,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/C,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IAExC,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrD,MAAM,UAAU,GAAG,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3D,IAAI,OAAO,KAAK,SAAS,IAAI,UAAU,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,gDAAgD,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;IAErI,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACxC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAA8B,CAAC;QAC9E,WAAW,CAAC,UAAU,CAAC;KACxB,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC7C,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;IACpB,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACvC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,sJAAsJ;AACtJ,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAyB,EACzB,OAAe,EACf,eAAiC,EACjC,cAA+B,EAC/B,KAAK,GAAyB,IAAI,GAAG,EAAE,EACvC,gBAAgB,GAA6B,IAAI,GAAG,EAAE,EACtD,eAAe,GAAiD,qBAAqB;IAErF,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC;IAC5E,IAAI,KAAK,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,UAAU,OAAO,2CAA2C,CAAC,CAAC;IAEvG,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC9E,MAAM,UAAU,GAAG,IAAI,GAAG,EAA4I,CAAC;IACvK,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpC,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAAC,KAAK,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC;gBAAE,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QAAC,CAAC;aAChK,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YAAC,KAAK,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC;gBAAE,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QAAC,CAAC;IACjM,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAmB,CAAC;IAE5C,KAAK,MAAM,IAAI,IAAI,oBAAoB,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC;QACzD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,KAAK,kBAAkB,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACnI,KAAK,MAAM,OAAO,IAAI,CAAC,GAAG,mBAAmB,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YACrH,IAAI,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,SAAS;YAEpC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACtC,IAAI,KAAK,EAAE,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC5B,MAAM,GAAG,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;gBACnC,IAAI,GAAG,KAAK,SAAS;oBAAE,SAAS;gBAChC,IAAI,CAAC;oBACH,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,MAAM,WAAW,CAAC,GAAG,CAAC,CAAC;oBACtE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;oBACrC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;gBAClC,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,OAAO,CAAC,IAAI,CAAC,qCAAqC,KAAK,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;gBACzE,CAAC;gBACD,SAAS;YACX,CAAC;YACD,gHAAgH;YAChH,IAAI,KAAK,KAAK,SAAS;gBAAE,SAAS;YAElC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACtC,IAAI,KAAK,KAAK,SAAS;gBAAE,SAAS;YAClC,IAAI,CAAC;gBACH,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBAC3B,MAAM,KAAK,GAAG,MAAM,oBAAoB,CAAC,KAAK,CAAC,KAAK,EAAE,cAAc,EAAE,gBAAgB,CAAC,CAAC;oBACxF,MAAM,YAAY,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;oBACrD,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;wBAAC,OAAO,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,KAAK,CAAC,EAAE,mBAAmB,KAAK,CAAC,SAAS,IAAI,CAAC,CAAC;wBAAC,SAAS;oBAAC,CAAC;oBAC3H,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;gBACtC,CAAC;qBAAM,CAAC;oBACN,MAAM,YAAY,GAAG,MAAM,0BAA0B,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,SAAS,EAAE,cAAc,EAAE,eAAe,CAAC,CAAC;oBAC1H,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;wBAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,KAAK,CAAC,UAAU,CAAC,EAAE,mBAAmB,KAAK,CAAC,SAAS,IAAI,CAAC,CAAC;wBAAC,SAAS;oBAAC,CAAC;oBACtI,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;gBACtC,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,IAAI,CAAC,qCAAqC,OAAO,IAAI,EAAE,KAAK,CAAC,CAAC;YACxE,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["import { collectEffectAssetIds, type Asset, type ProjectDocument } from \"@pixi-ui-editor/schema\";\r\nimport { Spritesheet, type BitmapFont, type Texture, type SpritesheetData } from \"pixi.js\";\r\nimport { collectRenderedNodes } from \"../scene.js\";\r\nimport { collectNodeAssetIds } from \"../views/createNodeView.js\";\r\nimport { loadBitmapFontGlyphTexture } from \"./bitmapFonts.js\";\r\nimport { sharedBitmapFontCache } from \"./cache.js\";\r\nimport { loadTexture } from \"./loadTexture.js\";\r\n\r\nexport type AssetUrlResolver = (asset: Asset) => string | undefined;\r\nexport type FileUrlResolver = (uri: string) => string | undefined;\r\ntype AtlasAsset = Extract<Asset, { type: \"atlas\" }>;\r\ntype BitmapFontAsset = Extract<Asset, { type: \"bitmapFont\" }>;\r\n\r\nexport { loadTexture };\r\n\r\n/** Loads and parses one atlas's Spritesheet, sharing the parse by the JSON file's URI. */\r\nexport async function loadAtlasSpritesheet(\r\n asset: AtlasAsset,\r\n resolveFileUrl: FileUrlResolver,\r\n cache: Map<string, Spritesheet> = new Map(),\r\n): Promise<Spritesheet> {\r\n const cached = cache.get(asset.files.json.uri);\r\n if (cached !== undefined) return cached;\r\n\r\n const jsonUrl = resolveFileUrl(asset.files.json.uri);\r\n const textureUrl = resolveFileUrl(asset.files.texture.uri);\r\n if (jsonUrl === undefined || textureUrl === undefined) throw new Error(`Unable to resolve file URLs for atlas asset '${asset.id}'.`);\r\n\r\n const [data, texture] = await Promise.all([\r\n fetch(jsonUrl).then((response) => response.json() as Promise<SpritesheetData>),\r\n loadTexture(textureUrl),\r\n ]);\r\n const sheet = new Spritesheet(texture, data);\r\n await sheet.parse();\r\n cache.set(asset.files.json.uri, sheet);\r\n return sheet;\r\n}\r\n\r\n/** Loads textures for image nodes in a scene, resolving atlas frame ids through their Spritesheet. Leaves failed assets for placeholder rendering. */\r\nexport async function loadSceneTextures(\r\n document: ProjectDocument,\r\n sceneId: string,\r\n resolveAssetUrl: AssetUrlResolver,\r\n resolveFileUrl: FileUrlResolver,\r\n cache: Map<string, Texture> = new Map(),\r\n spritesheetCache: Map<string, Spritesheet> = new Map(),\r\n bitmapFontCache: Map<string, Promise<BitmapFont | undefined>> = sharedBitmapFontCache,\r\n): Promise<Map<string, Texture>> {\r\n const scene = document.scenes.find((candidate) => candidate.id === sceneId);\r\n if (scene === undefined) throw new Error(`Scene '${sceneId}' does not exist in the project document.`);\r\n\r\n const assetsById = new Map(document.assets.map((asset) => [asset.id, asset]));\r\n const framesById = new Map<string, { kind: \"atlas\"; atlas: AtlasAsset; frameName: string } | { kind: \"bitmapFont\"; bitmapFont: BitmapFontAsset; frameName: string }>();\r\n for (const asset of document.assets) {\r\n if (asset.type === \"atlas\") { for (const [frameName, frameId] of Object.entries(asset.frames)) framesById.set(frameId, { kind: \"atlas\", atlas: asset, frameName }); }\r\n else if (asset.type === \"bitmapFont\") { for (const [frameName, frameId] of Object.entries(asset.frames ?? {})) framesById.set(frameId, { kind: \"bitmapFont\", bitmapFont: asset, frameName }); }\r\n }\r\n\r\n const textures = new Map<string, Texture>();\r\n\r\n for (const node of collectRenderedNodes(document, scene)) {\r\n const effect = node.type === \"particle-emitter\" ? document.effects.find((candidate) => candidate.id === node.effectId) : undefined;\r\n for (const assetId of [...collectNodeAssetIds(node), ...(effect === undefined ? [] : collectEffectAssetIds(effect))]) {\r\n if (textures.has(assetId)) continue;\r\n\r\n const asset = assetsById.get(assetId);\r\n if (asset?.type === \"image\") {\r\n const url = resolveAssetUrl(asset);\r\n if (url === undefined) continue;\r\n try {\r\n const texture = cache.get(asset.source.uri) ?? await loadTexture(url);\r\n cache.set(asset.source.uri, texture);\r\n textures.set(asset.id, texture);\r\n } catch (error) {\r\n console.warn(`Unable to load texture for asset '${asset.id}'.`, error);\r\n }\r\n continue;\r\n }\r\n // Spine и atlas-ассеты сюда тоже попадают: spine-данные грузит loadSceneSpines, а atlas-фреймы резолвятся ниже.\r\n if (asset !== undefined) continue;\r\n\r\n const frame = framesById.get(assetId);\r\n if (frame === undefined) continue;\r\n try {\r\n if (frame.kind === \"atlas\") {\r\n const sheet = await loadAtlasSpritesheet(frame.atlas, resolveFileUrl, spritesheetCache);\r\n const frameTexture = sheet.textures[frame.frameName];\r\n if (frameTexture === undefined) { console.warn(`Atlas '${frame.atlas.id}' has no frame '${frame.frameName}'.`); continue; }\r\n textures.set(assetId, frameTexture);\r\n } else {\r\n const glyphTexture = await loadBitmapFontGlyphTexture(frame.bitmapFont, frame.frameName, resolveFileUrl, bitmapFontCache);\r\n if (glyphTexture === undefined) { console.warn(`Bitmap font '${frame.bitmapFont.id}' has no glyph '${frame.frameName}'.`); continue; }\r\n textures.set(assetId, glyphTexture);\r\n }\r\n } catch (error) {\r\n console.warn(`Unable to load frame texture for '${assetId}'.`, error);\r\n }\r\n }\r\n }\r\n\r\n return textures;\r\n}\r\n"]}
1
+ {"version":3,"file":"textures.js","sourceRoot":"","sources":["../../src/assets/textures.ts"],"names":[],"mappings":"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","sourcesContent":["import { collectEffectAssetIds, type Asset, type ProjectDocument } from \"@pixi-ui-editor/schema\";\r\nimport { Spritesheet, type BitmapFont, type Texture, type SpritesheetData } from \"pixi.js\";\r\nimport { collectRenderedNodes } from \"../scene.js\";\r\nimport { collectNodeAssetIds } from \"../views/createNodeView.js\";\r\nimport { loadBitmapFontGlyphTexture } from \"./bitmapFonts.js\";\r\nimport { sharedBitmapFontCache } from \"./cache.js\";\r\nimport { loadTexture } from \"./loadTexture.js\";\r\n\r\nexport type AssetUrlResolver = (asset: Asset) => string | undefined;\r\nexport type FileUrlResolver = (uri: string) => string | undefined;\r\ntype ImageAsset = Extract<Asset, { type: \"image\" }>;\r\ntype AtlasAsset = Extract<Asset, { type: \"atlas\" }>;\r\ntype BitmapFontAsset = Extract<Asset, { type: \"bitmapFont\" }>;\r\n\r\nexport { loadTexture };\r\n\r\n/**\r\n * Cache key for an image asset's loaded `Texture`. `replaceAssetSource` (editor-web `assets.slice.ts`)\r\n * keeps `source.uri` stable across a Replace (it is derived from the asset's name/id/media type, not its\r\n * content) but stamps a fresh `source.version`, the same signal `loadSceneFonts` already keys on — so a\r\n * plain `source.uri` key would keep serving the pre-replace `Texture` forever, invisible to the caller.\r\n */\r\nexport const imageAssetCacheKey = (asset: ImageAsset): string => `${asset.source.uri}#${asset.source.version ?? \"\"}`;\r\n\r\n/** Loads and parses one atlas's Spritesheet, sharing the parse by the JSON file's URI. */\r\nexport async function loadAtlasSpritesheet(\r\n asset: AtlasAsset,\r\n resolveFileUrl: FileUrlResolver,\r\n cache: Map<string, Spritesheet> = new Map(),\r\n): Promise<Spritesheet> {\r\n const cached = cache.get(asset.files.json.uri);\r\n if (cached !== undefined) return cached;\r\n\r\n const jsonUrl = resolveFileUrl(asset.files.json.uri);\r\n const textureUrl = resolveFileUrl(asset.files.texture.uri);\r\n if (jsonUrl === undefined || textureUrl === undefined) throw new Error(`Unable to resolve file URLs for atlas asset '${asset.id}'.`);\r\n\r\n const [data, texture] = await Promise.all([\r\n fetch(jsonUrl).then((response) => response.json() as Promise<SpritesheetData>),\r\n loadTexture(textureUrl),\r\n ]);\r\n const sheet = new Spritesheet(texture, data);\r\n await sheet.parse();\r\n cache.set(asset.files.json.uri, sheet);\r\n return sheet;\r\n}\r\n\r\n/** Loads textures for image nodes in a scene, resolving atlas frame ids through their Spritesheet. Leaves failed assets for placeholder rendering. */\r\nexport async function loadSceneTextures(\r\n document: ProjectDocument,\r\n sceneId: string,\r\n resolveAssetUrl: AssetUrlResolver,\r\n resolveFileUrl: FileUrlResolver,\r\n cache: Map<string, Texture> = new Map(),\r\n spritesheetCache: Map<string, Spritesheet> = new Map(),\r\n bitmapFontCache: Map<string, Promise<BitmapFont | undefined>> = sharedBitmapFontCache,\r\n): Promise<Map<string, Texture>> {\r\n const scene = document.scenes.find((candidate) => candidate.id === sceneId);\r\n if (scene === undefined) throw new Error(`Scene '${sceneId}' does not exist in the project document.`);\r\n\r\n const assetsById = new Map(document.assets.map((asset) => [asset.id, asset]));\r\n const framesById = new Map<string, { kind: \"atlas\"; atlas: AtlasAsset; frameName: string } | { kind: \"bitmapFont\"; bitmapFont: BitmapFontAsset; frameName: string }>();\r\n for (const asset of document.assets) {\r\n if (asset.type === \"atlas\") { for (const [frameName, frameId] of Object.entries(asset.frames)) framesById.set(frameId, { kind: \"atlas\", atlas: asset, frameName }); }\r\n else if (asset.type === \"bitmapFont\") { for (const [frameName, frameId] of Object.entries(asset.frames ?? {})) framesById.set(frameId, { kind: \"bitmapFont\", bitmapFont: asset, frameName }); }\r\n }\r\n\r\n const textures = new Map<string, Texture>();\r\n\r\n for (const node of collectRenderedNodes(document, scene)) {\r\n const effect = node.type === \"particle-emitter\" ? document.effects.find((candidate) => candidate.id === node.effectId) : undefined;\r\n for (const assetId of [...collectNodeAssetIds(node), ...(effect === undefined ? [] : collectEffectAssetIds(effect))]) {\r\n if (textures.has(assetId)) continue;\r\n\r\n const asset = assetsById.get(assetId);\r\n if (asset?.type === \"image\") {\r\n const url = resolveAssetUrl(asset);\r\n if (url === undefined) continue;\r\n try {\r\n const key = imageAssetCacheKey(asset);\r\n const texture = cache.get(key) ?? await loadTexture(url);\r\n cache.set(key, texture);\r\n textures.set(asset.id, texture);\r\n } catch (error) {\r\n console.warn(`Unable to load texture for asset '${asset.id}'.`, error);\r\n }\r\n continue;\r\n }\r\n // Spine и atlas-ассеты сюда тоже попадают: spine-данные грузит loadSceneSpines, а atlas-фреймы резолвятся ниже.\r\n if (asset !== undefined) continue;\r\n\r\n const frame = framesById.get(assetId);\r\n if (frame === undefined) continue;\r\n try {\r\n if (frame.kind === \"atlas\") {\r\n const sheet = await loadAtlasSpritesheet(frame.atlas, resolveFileUrl, spritesheetCache);\r\n const frameTexture = sheet.textures[frame.frameName];\r\n if (frameTexture === undefined) { console.warn(`Atlas '${frame.atlas.id}' has no frame '${frame.frameName}'.`); continue; }\r\n textures.set(assetId, frameTexture);\r\n } else {\r\n const glyphTexture = await loadBitmapFontGlyphTexture(frame.bitmapFont, frame.frameName, resolveFileUrl, bitmapFontCache);\r\n if (glyphTexture === undefined) { console.warn(`Bitmap font '${frame.bitmapFont.id}' has no glyph '${frame.frameName}'.`); continue; }\r\n textures.set(assetId, glyphTexture);\r\n }\r\n } catch (error) {\r\n console.warn(`Unable to load frame texture for '${assetId}'.`, error);\r\n }\r\n }\r\n }\r\n\r\n return textures;\r\n}\r\n"]}
@@ -0,0 +1,60 @@
1
+ import type { ProjectDocument } from "@pixi-ui-editor/schema";
2
+ /**
3
+ * Шина звуковых событий рантайма.
4
+ *
5
+ * Рантайм сам по себе ничего не проигрывает: вьюхи и сцена только объявляют, что произошло
6
+ * («нажата кнопка», «сцене нужна фоновая музыка»). Кто и как это озвучит — решает подписчик:
7
+ * встроенный `SoundDirector` (см. `director.ts`) или звуковая система приложения, в которое
8
+ * рантайм встроили.
9
+ *
10
+ * В событиях намеренно нет ни путей, ни mediaType, ни хэндлов `@pixi/sound`: только имя звука из
11
+ * проекта (`name`) и его `assetId`. Имя — то, чем удобно матчить контент со своей картой загрузки;
12
+ * `assetId` идёт рядом, потому что схема не требует уникальности `name` (см. комментарий к `Name`
13
+ * в `@pixi-ui-editor/schema`), и на коллизии именно он остаётся однозначным ключом.
14
+ */
15
+ export type SoundCue = "button.press" | "button.hover" | "checkbox.check" | "checkbox.uncheck";
16
+ /** Ссылка на звук проекта в терминах, понятных чужому загрузчику. */
17
+ export type SoundRef = {
18
+ readonly assetId: string;
19
+ readonly name: string;
20
+ };
21
+ export type AudioEvent =
22
+ /**
23
+ * Разовый звук интерфейса. `sound` отсутствует, если автор не назначил ассет на этот cue: событие
24
+ * всё равно публикуется, чтобы хост мог подставить собственный звук по умолчанию.
25
+ */
26
+ {
27
+ readonly type: "cue";
28
+ readonly cue: SoundCue;
29
+ readonly nodeId: string;
30
+ readonly sound?: SoundRef;
31
+ }
32
+ /** `volume` — авторская громкость сцены, данные, а не приказ: хост волен домножить её на свой мастер. */
33
+ | {
34
+ readonly type: "music";
35
+ readonly action: "play";
36
+ readonly sound: SoundRef;
37
+ readonly loop: boolean;
38
+ readonly volume: number;
39
+ } | {
40
+ readonly type: "music";
41
+ readonly action: "stop";
42
+ };
43
+ export type AudioListener = (event: AudioEvent) => void;
44
+ export interface AudioBus {
45
+ emit(event: AudioEvent): void;
46
+ /** Возвращает функцию отписки. */
47
+ subscribe(listener: AudioListener): () => void;
48
+ }
49
+ export declare function createAudioBus(): AudioBus;
50
+ /** Разрешает id звукового ассета в пару «имя + id»; всё остальное про ассет остаётся внутри рантайма. */
51
+ export declare function soundRefFor(document: ProjectDocument, assetId: string | undefined): SoundRef | undefined;
52
+ /**
53
+ * Публикатор cue, привязанный к документу: вьюхи знают только id назначенного ассета, а имя для
54
+ * события резолвится здесь — один раз построенной картой, а не поиском по `document.assets` на клик.
55
+ */
56
+ export interface SceneAudio {
57
+ cue(cue: SoundCue, nodeId: string, assetId: string | undefined): void;
58
+ }
59
+ export declare function createSceneAudio(document: ProjectDocument, bus: AudioBus): SceneAudio;
60
+ //# sourceMappingURL=bus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bus.d.ts","sourceRoot":"","sources":["../../src/audio/bus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,QAAQ,GAAG,cAAc,GAAG,cAAc,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;AAE/F,qEAAqE;AACrE,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3E,MAAM,MAAM,UAAU;AACpB;;;GAGG;AACD;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAA;CAAE;AACtG,yGAAyG;GACvG;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC9H;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;AAExD,MAAM,WAAW,QAAQ;IACvB,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,kCAAkC;IAClC,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,IAAI,CAAC;CAChD;AAED,wBAAgB,cAAc,IAAI,QAAQ,CAmBzC;AAED,yGAAyG;AACzG,wBAAgB,WAAW,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAIxG;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;CACvE;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,EAAE,QAAQ,GAAG,UAAU,CAQrF"}
@@ -0,0 +1,38 @@
1
+ export function createAudioBus() {
2
+ const listeners = new Set();
3
+ return {
4
+ emit(event) {
5
+ // Копия набора: подписчик вправе отписаться прямо из обработчика, и упавший подписчик не
6
+ // должен глушить остальных — звук никогда не роняет кадр.
7
+ for (const listener of [...listeners]) {
8
+ try {
9
+ listener(event);
10
+ }
11
+ catch (error) {
12
+ console.warn("An audio bus listener threw.", error);
13
+ }
14
+ }
15
+ },
16
+ subscribe(listener) {
17
+ listeners.add(listener);
18
+ return () => { listeners.delete(listener); };
19
+ },
20
+ };
21
+ }
22
+ /** Разрешает id звукового ассета в пару «имя + id»; всё остальное про ассет остаётся внутри рантайма. */
23
+ export function soundRefFor(document, assetId) {
24
+ if (assetId === undefined)
25
+ return undefined;
26
+ const asset = document.assets.find((candidate) => candidate.id === assetId);
27
+ return asset?.type === "sound" ? { assetId: asset.id, name: asset.name } : undefined;
28
+ }
29
+ export function createSceneAudio(document, bus) {
30
+ const namesById = new Map(document.assets.flatMap((asset) => asset.type === "sound" ? [[asset.id, asset.name]] : []));
31
+ return {
32
+ cue(cue, nodeId, assetId) {
33
+ const name = assetId === undefined ? undefined : namesById.get(assetId);
34
+ bus.emit({ type: "cue", cue, nodeId, sound: assetId === undefined || name === undefined ? undefined : { assetId, name } });
35
+ },
36
+ };
37
+ }
38
+ //# sourceMappingURL=bus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bus.js","sourceRoot":"","sources":["../../src/audio/bus.ts"],"names":[],"mappings":"AAsCA,MAAM,UAAU,cAAc;IAC5B,MAAM,SAAS,GAAG,IAAI,GAAG,EAAiB,CAAC;IAC3C,OAAO;QACL,IAAI,CAAC,KAAK;YACR,yFAAyF;YACzF,0DAA0D;YAC1D,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,SAAS,CAAC,EAAE,CAAC;gBACtC,IAAI,CAAC;oBACH,QAAQ,CAAC,KAAK,CAAC,CAAC;gBAClB,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,OAAO,CAAC,IAAI,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;gBACtD,CAAC;YACH,CAAC;QACH,CAAC;QACD,SAAS,CAAC,QAAQ;YAChB,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACxB,OAAO,GAAG,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/C,CAAC;KACF,CAAC;AACJ,CAAC;AAED,yGAAyG;AACzG,MAAM,UAAU,WAAW,CAAC,QAAyB,EAAE,OAA2B;IAChF,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC5C,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC;IAC5E,OAAO,KAAK,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AACvF,CAAC;AAUD,MAAM,UAAU,gBAAgB,CAAC,QAAyB,EAAE,GAAa;IACvE,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,CAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/H,OAAO;QACL,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO;YACtB,MAAM,IAAI,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACxE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;QAC7H,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { ProjectDocument } from \"@pixi-ui-editor/schema\";\r\n\r\n/**\r\n * Шина звуковых событий рантайма.\r\n *\r\n * Рантайм сам по себе ничего не проигрывает: вьюхи и сцена только объявляют, что произошло\r\n * («нажата кнопка», «сцене нужна фоновая музыка»). Кто и как это озвучит — решает подписчик:\r\n * встроенный `SoundDirector` (см. `director.ts`) или звуковая система приложения, в которое\r\n * рантайм встроили.\r\n *\r\n * В событиях намеренно нет ни путей, ни mediaType, ни хэндлов `@pixi/sound`: только имя звука из\r\n * проекта (`name`) и его `assetId`. Имя — то, чем удобно матчить контент со своей картой загрузки;\r\n * `assetId` идёт рядом, потому что схема не требует уникальности `name` (см. комментарий к `Name`\r\n * в `@pixi-ui-editor/schema`), и на коллизии именно он остаётся однозначным ключом.\r\n */\r\nexport type SoundCue = \"button.press\" | \"button.hover\" | \"checkbox.check\" | \"checkbox.uncheck\";\r\n\r\n/** Ссылка на звук проекта в терминах, понятных чужому загрузчику. */\r\nexport type SoundRef = { readonly assetId: string; readonly name: string };\r\n\r\nexport type AudioEvent =\r\n /**\r\n * Разовый звук интерфейса. `sound` отсутствует, если автор не назначил ассет на этот cue: событие\r\n * всё равно публикуется, чтобы хост мог подставить собственный звук по умолчанию.\r\n */\r\n | { readonly type: \"cue\"; readonly cue: SoundCue; readonly nodeId: string; readonly sound?: SoundRef }\r\n /** `volume` — авторская громкость сцены, данные, а не приказ: хост волен домножить её на свой мастер. */\r\n | { readonly type: \"music\"; readonly action: \"play\"; readonly sound: SoundRef; readonly loop: boolean; readonly volume: number }\r\n | { readonly type: \"music\"; readonly action: \"stop\" };\r\n\r\nexport type AudioListener = (event: AudioEvent) => void;\r\n\r\nexport interface AudioBus {\r\n emit(event: AudioEvent): void;\r\n /** Возвращает функцию отписки. */\r\n subscribe(listener: AudioListener): () => void;\r\n}\r\n\r\nexport function createAudioBus(): AudioBus {\r\n const listeners = new Set<AudioListener>();\r\n return {\r\n emit(event) {\r\n // Копия набора: подписчик вправе отписаться прямо из обработчика, и упавший подписчик не\r\n // должен глушить остальных — звук никогда не роняет кадр.\r\n for (const listener of [...listeners]) {\r\n try {\r\n listener(event);\r\n } catch (error) {\r\n console.warn(\"An audio bus listener threw.\", error);\r\n }\r\n }\r\n },\r\n subscribe(listener) {\r\n listeners.add(listener);\r\n return () => { listeners.delete(listener); };\r\n },\r\n };\r\n}\r\n\r\n/** Разрешает id звукового ассета в пару «имя + id»; всё остальное про ассет остаётся внутри рантайма. */\r\nexport function soundRefFor(document: ProjectDocument, assetId: string | undefined): SoundRef | undefined {\r\n if (assetId === undefined) return undefined;\r\n const asset = document.assets.find((candidate) => candidate.id === assetId);\r\n return asset?.type === \"sound\" ? { assetId: asset.id, name: asset.name } : undefined;\r\n}\r\n\r\n/**\r\n * Публикатор cue, привязанный к документу: вьюхи знают только id назначенного ассета, а имя для\r\n * события резолвится здесь — один раз построенной картой, а не поиском по `document.assets` на клик.\r\n */\r\nexport interface SceneAudio {\r\n cue(cue: SoundCue, nodeId: string, assetId: string | undefined): void;\r\n}\r\n\r\nexport function createSceneAudio(document: ProjectDocument, bus: AudioBus): SceneAudio {\r\n const namesById = new Map(document.assets.flatMap((asset) => asset.type === \"sound\" ? [[asset.id, asset.name] as const] : []));\r\n return {\r\n cue(cue, nodeId, assetId) {\r\n const name = assetId === undefined ? undefined : namesById.get(assetId);\r\n bus.emit({ type: \"cue\", cue, nodeId, sound: assetId === undefined || name === undefined ? undefined : { assetId, name } });\r\n },\r\n };\r\n}\r\n"]}
@@ -0,0 +1,35 @@
1
+ import type { Sound } from "@pixi/sound";
2
+ import type { ProjectDocument } from "@pixi-ui-editor/schema";
3
+ import type { FileUrlResolver } from "../assets/textures.js";
4
+ import { type AudioBus } from "./bus.js";
5
+ /**
6
+ * Явная инициализация звукового слоя рантайма.
7
+ *
8
+ * `director: "none"` — рантайм только публикует события в шину и не трогает `@pixi/sound`: звуковые
9
+ * файлы не скачиваются и не декодируются вовсе, всё это делает звуковая система хоста.
10
+ * `director: "builtin"` — те же события дополнительно слушает встроенный директор, который грузит
11
+ * ассеты проекта и играет их сам (так работает Preview редактора и пример `examples/pixi-app`).
12
+ */
13
+ export type RuntimeAudioOptions = {
14
+ readonly director: "none";
15
+ } | {
16
+ readonly director: "builtin";
17
+ /** Как превратить `asset.source.uri` в загружаемый URL — тот же резолвер, что у текстур. */
18
+ readonly resolveFileUrl: FileUrlResolver;
19
+ /** Общий кэш разобранных звуков, обычно `SceneAssetCache.sounds`. */
20
+ readonly cache?: Map<string, Sound>;
21
+ };
22
+ export interface RuntimeAudio extends AudioBus {
23
+ /**
24
+ * Готовит звуки для встроенного директора: со `sceneId` — только те, что нужны сцене, без него —
25
+ * все звуки проекта (редактору так удобнее: назначения меняются на лету). С `director: "none"`
26
+ * ничего не делает.
27
+ */
28
+ load(document: ProjectDocument, sceneId?: string): Promise<void>;
29
+ /** Объявляет активную сцену: публикует её фоновую музыку (или её отсутствие) в шину. */
30
+ setScene(document: ProjectDocument, sceneId: string): void;
31
+ /** Останавливает встроенное воспроизведение и отписывает директора от шины. */
32
+ dispose(): void;
33
+ }
34
+ export declare function createRuntimeAudio(options: RuntimeAudioOptions): RuntimeAudio;
35
+ //# sourceMappingURL=director.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"director.d.ts","sourceRoot":"","sources":["../../src/audio/director.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAkB,MAAM,aAAa,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,OAAO,EAA+B,KAAK,QAAQ,EAAuC,MAAM,UAAU,CAAC;AAmF3G;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC7B;IACE,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;IAC7B,4FAA4F;IAC5F,QAAQ,CAAC,cAAc,EAAE,eAAe,CAAC;IACzC,qEAAqE;IACrE,QAAQ,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;CACrC,CAAC;AAEN,MAAM,WAAW,YAAa,SAAQ,QAAQ;IAC5C;;;;OAIG;IACH,IAAI,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,wFAAwF;IACxF,QAAQ,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3D,+EAA+E;IAC/E,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CA+C7E"}