@playcraft/build 0.0.15 → 0.0.19

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 (46) hide show
  1. package/dist/analyzers/scene-asset-collector.js +259 -135
  2. package/dist/audio-optimizer.d.ts +70 -0
  3. package/dist/audio-optimizer.js +226 -0
  4. package/dist/base-builder.d.ts +25 -13
  5. package/dist/base-builder.js +69 -29
  6. package/dist/engines/engine-detector.d.ts +13 -4
  7. package/dist/engines/engine-detector.js +74 -10
  8. package/dist/engines/generic-adapter.d.ts +12 -6
  9. package/dist/engines/generic-adapter.js +46 -15
  10. package/dist/engines/index.d.ts +1 -0
  11. package/dist/engines/index.js +1 -0
  12. package/dist/engines/playable-scripts-adapter.d.ts +148 -0
  13. package/dist/engines/playable-scripts-adapter.js +1084 -0
  14. package/dist/engines/playcanvas-adapter.js +3 -0
  15. package/dist/generators/config-generator.js +73 -16
  16. package/dist/index.d.ts +3 -1
  17. package/dist/index.js +3 -1
  18. package/dist/platforms/google.d.ts +9 -0
  19. package/dist/platforms/google.js +68 -7
  20. package/dist/templates/__loading__.js +100 -0
  21. package/dist/templates/__modules__.js +47 -0
  22. package/dist/templates/__settings__.template.js +20 -0
  23. package/dist/templates/__start__.js +332 -0
  24. package/dist/templates/index.html +18 -0
  25. package/dist/templates/logo.png +0 -0
  26. package/dist/templates/manifest.json +1 -0
  27. package/dist/templates/patches/cannon.min.js +28 -0
  28. package/dist/templates/patches/lz4.js +10 -0
  29. package/dist/templates/patches/one-page-http-get.js +20 -0
  30. package/dist/templates/patches/one-page-inline-game-scripts.js +52 -0
  31. package/dist/templates/patches/one-page-mraid-resize-canvas.js +46 -0
  32. package/dist/templates/patches/p2.min.js +27 -0
  33. package/dist/templates/patches/playcraft-no-xhr.js +76 -0
  34. package/dist/templates/playcanvas-stable.min.js +16363 -0
  35. package/dist/templates/styles.css +43 -0
  36. package/dist/types.d.ts +60 -13
  37. package/dist/utils/build-mode-detector.js +2 -0
  38. package/dist/vite/plugin-playcanvas.js +14 -19
  39. package/dist/vite/plugin-source-builder.js +383 -97
  40. package/package.json +7 -4
  41. package/dist/utils/obfuscate.d.ts +0 -42
  42. package/dist/utils/obfuscate.js +0 -216
  43. package/dist/vite/plugin-obfuscate.d.ts +0 -22
  44. package/dist/vite/plugin-obfuscate.js +0 -52
  45. package/dist/vite/plugin-template-minifier.d.ts +0 -20
  46. package/dist/vite/plugin-template-minifier.js +0 -392
@@ -259,6 +259,7 @@ function findAssetIds(obj, deps, assets, depth = 0) {
259
259
  * 收集间接依赖(材质 → 纹理,模型 → 材质等)
260
260
  */
261
261
  function collectIndirectDependencies(deps, assets) {
262
+ const scriptNameIndex = buildScriptNameIndex(assets);
262
263
  const visited = new Set();
263
264
  const queue = Array.from(deps.directAssets);
264
265
  while (queue.length > 0) {
@@ -269,122 +270,105 @@ function collectIndirectDependencies(deps, assets) {
269
270
  const asset = assets[assetId];
270
271
  if (!asset)
271
272
  continue;
272
- // 检查不同类型资源的依赖
273
- switch (asset.type) {
274
- case 'material':
275
- // 材质可能引用纹理
276
- findAssetIdsInData(asset.data, deps, assets, queue, visited);
277
- break;
278
- case 'model':
279
- // 模型可能引用材质
280
- findAssetIdsInData(asset.data, deps, assets, queue, visited);
281
- // 检查材质映射
282
- if (asset.data?.mapping) {
283
- for (const [meshName, materialId] of Object.entries(asset.data.mapping)) {
284
- if (typeof materialId === 'number' || typeof materialId === 'string') {
285
- const id = String(materialId);
286
- if (assets[id] && !visited.has(id) && !deps.directAssets.has(id)) {
287
- deps.indirectAssets.add(id);
288
- queue.push(id);
289
- }
290
- }
291
- }
292
- }
293
- break;
294
- case 'cubemap':
295
- // Cubemap 引用 6 个纹理面(textures 数组)
296
- if (asset.data?.textures && Array.isArray(asset.data.textures)) {
297
- for (const textureId of asset.data.textures) {
298
- if (textureId !== null && textureId !== undefined) {
299
- const id = String(textureId);
300
- if (assets[id] && !visited.has(id) && !deps.directAssets.has(id)) {
301
- deps.indirectAssets.add(id);
302
- queue.push(id);
303
- }
273
+ const foundIds = collectAssetDeps(asset, assets, scriptNameIndex);
274
+ for (const id of foundIds) {
275
+ if (!visited.has(id) && !deps.directAssets.has(id) && !deps.indirectAssets.has(id)) {
276
+ deps.indirectAssets.add(id);
277
+ queue.push(id);
278
+ }
279
+ }
280
+ }
281
+ }
282
+ /**
283
+ * 统一的单个资源依赖收集(核心 switch/case)
284
+ * 根据资源类型精确解析其依赖的其他资源 ID。
285
+ * collectIndirectDependencies collectAllTemplatesDependencies 共用。
286
+ */
287
+ function collectAssetDeps(asset, assets, scriptNameIndex) {
288
+ const foundIds = new Set();
289
+ const data = asset.data;
290
+ switch (asset.type) {
291
+ case 'material':
292
+ // Material: precisely scan known texture/asset reference fields
293
+ if (data)
294
+ collectMaterialDeps(data, assets, foundIds);
295
+ break;
296
+ case 'model':
297
+ // Model: mapping of mesh → material ID
298
+ if (data?.mapping && typeof data.mapping === 'object') {
299
+ for (const val of Object.values(data.mapping)) {
300
+ if (typeof val === 'object' && val !== null) {
301
+ const matVal = val.material;
302
+ if (matVal !== null && matVal !== undefined) {
303
+ const id = String(matVal);
304
+ if (assets[id])
305
+ foundIds.add(id);
304
306
  }
305
307
  }
306
- }
307
- // 还要扫描其他可能的数据
308
- findAssetIdsInData(asset.data, deps, assets, queue, visited);
309
- break;
310
- case 'texture':
311
- // 纹理资源通常是叶子节点,但可能引用其他纹理(如合成纹理)
312
- findAssetIdsInData(asset.data, deps, assets, queue, visited);
313
- break;
314
- case 'sprite':
315
- // Sprite 可能引用纹理图集
316
- if (asset.data?.textureAtlasAsset) {
317
- const id = String(asset.data.textureAtlasAsset);
318
- if (assets[id] && !visited.has(id) && !deps.directAssets.has(id)) {
319
- deps.indirectAssets.add(id);
320
- queue.push(id);
321
- }
322
- }
323
- findAssetIdsInData(asset.data, deps, assets, queue, visited);
324
- break;
325
- case 'template':
326
- // 模板资产内部的 entities 可能引用其他资产(字体、纹理、脚本等)
327
- if (asset.data?.entities) {
328
- for (const entity of Object.values(asset.data.entities)) {
329
- findAssetIdsInData(entity, deps, assets, queue, visited);
308
+ else if (typeof val === 'number' || typeof val === 'string') {
309
+ const id = String(val);
310
+ if (assets[id])
311
+ foundIds.add(id);
330
312
  }
331
313
  }
332
- break;
333
- case 'render':
334
- // Render 资源引用 containerAsset (GLB/GLTF 模型)
335
- if (asset.data?.containerAsset) {
336
- const id = String(asset.data.containerAsset);
337
- if (assets[id] && !visited.has(id) && !deps.directAssets.has(id)) {
338
- deps.indirectAssets.add(id);
339
- queue.push(id);
314
+ }
315
+ // Also scan other data fields for additional references
316
+ if (data)
317
+ scanComponentForAssetIds(data, assets, foundIds, 0);
318
+ break;
319
+ case 'cubemap':
320
+ // Cubemap: textures array holds 6 face texture IDs
321
+ if (data?.textures && Array.isArray(data.textures)) {
322
+ for (const textureId of data.textures) {
323
+ if (textureId !== null && textureId !== undefined) {
324
+ const id = String(textureId);
325
+ if (assets[id])
326
+ foundIds.add(id);
340
327
  }
341
328
  }
342
- findAssetIdsInData(asset.data, deps, assets, queue, visited);
343
- break;
344
- case 'container':
345
- // Container 可能引用内部的 render 和 material 资源
346
- // 主要是 GLB/GLTF 文件,它本身是独立的
347
- findAssetIdsInData(asset.data, deps, assets, queue, visited);
348
- break;
349
- }
350
- }
351
- }
352
- /**
353
- * 在资源数据中查找资源 ID
354
- */
355
- function findAssetIdsInData(data, deps, assets, queue, visited) {
356
- if (!data)
357
- return;
358
- // 递归扫描 data 对象
359
- const scan = (obj, depth = 0) => {
360
- if (depth > 10)
361
- return;
362
- if (obj === null || obj === undefined)
363
- return;
364
- if (typeof obj === 'number' || typeof obj === 'string') {
365
- const id = String(obj);
366
- if (assets[id] && !visited.has(id) && !deps.directAssets.has(id)) {
367
- deps.indirectAssets.add(id);
368
- queue.push(id);
369
329
  }
370
- }
371
- else if (Array.isArray(obj)) {
372
- for (const item of obj) {
373
- scan(item, depth + 1);
330
+ break;
331
+ case 'texture':
332
+ // Texture: usually a leaf node, no further deps
333
+ break;
334
+ case 'sprite':
335
+ // Sprite: textureAtlasAsset reference
336
+ if (data?.textureAtlasAsset) {
337
+ const id = String(data.textureAtlasAsset);
338
+ if (assets[id])
339
+ foundIds.add(id);
374
340
  }
375
- }
376
- else if (typeof obj === 'object') {
377
- for (const [key, value] of Object.entries(obj)) {
378
- // 跳过明显不是资源引用的字段
379
- if (key === 'name' || key === 'enabled' || key === 'position' ||
380
- key === 'rotation' || key === 'scale') {
381
- continue;
382
- }
383
- scan(value, depth + 1);
341
+ break;
342
+ case 'textureatlas':
343
+ // TextureAtlas: find associated texture by name + file hash
344
+ findAssociatedTexture(asset, assets, foundIds);
345
+ break;
346
+ case 'template':
347
+ // Template: precisely parse entities for component/script references
348
+ collectTemplateDeps(asset, assets, scriptNameIndex, foundIds);
349
+ break;
350
+ case 'render':
351
+ // Render: containerAsset (GLB/GLTF model)
352
+ if (data?.containerAsset) {
353
+ const id = String(data.containerAsset);
354
+ if (assets[id])
355
+ foundIds.add(id);
384
356
  }
385
- }
386
- };
387
- scan(data);
357
+ if (data)
358
+ scanComponentForAssetIds(data, assets, foundIds, 0);
359
+ break;
360
+ case 'container':
361
+ // Container (GLB/GLTF): may reference render and material assets
362
+ if (data)
363
+ scanComponentForAssetIds(data, assets, foundIds, 0);
364
+ break;
365
+ default:
366
+ // Other types: generic scan
367
+ if (data)
368
+ scanComponentForAssetIds(data, assets, foundIds, 0);
369
+ break;
370
+ }
371
+ return foundIds;
388
372
  }
389
373
  /**
390
374
  * 分析多个场景的资源依赖并合并
@@ -413,7 +397,7 @@ export async function collectScenesAssets(scenes, assets, globalScriptIds) {
413
397
  }
414
398
  // 收集所有 template 资源及其依赖(含 preload=false 的插件模板如 _Guide、_ExplodeBall)
415
399
  // template 通常由 GameplaySystem 等运行时通过 assets.find(name) 按名称动态加载
416
- console.log(`\n📦 收集 Template 资源依赖...`);
400
+ console.log(`\n📦 收集 Template 资源依赖!!!...`);
417
401
  const templateDeps = collectAllTemplatesDependencies(assets);
418
402
  let templateAssetsCount = 0;
419
403
  for (const id of templateDeps) {
@@ -449,14 +433,17 @@ function collectAllTemplatesDependencies(assets) {
449
433
  const result = new Set();
450
434
  const visited = new Set();
451
435
  const queue = [];
452
- // 收集所有 template(不再限制 preload=true,修复 "Failed to find plugin template" 错误)
436
+ // Build script name index for resolving script component references
437
+ const scriptNameIndex = buildScriptNameIndex(assets);
438
+ // Collect all templates (no longer restricted to preload=true)
453
439
  for (const [assetId, asset] of Object.entries(assets)) {
454
440
  if (asset.type === 'template') {
455
441
  result.add(assetId);
456
442
  queue.push(assetId);
457
443
  }
458
444
  }
459
- // 然后递归收集这些 template 的所有依赖
445
+ // BFS: recursively collect dependencies of all templates and their transitive deps
446
+ // Reuses the same collectAssetDeps switch/case as collectIndirectDependencies
460
447
  while (queue.length > 0) {
461
448
  const assetId = queue.shift();
462
449
  if (visited.has(assetId))
@@ -465,58 +452,195 @@ function collectAllTemplatesDependencies(assets) {
465
452
  const asset = assets[assetId];
466
453
  if (!asset)
467
454
  continue;
468
- // 扫描资源数据中引用的其他资源 ID
469
- const foundIds = new Set();
470
- scanForAssetIds(asset, assets, foundIds);
455
+ const foundIds = collectAssetDeps(asset, assets, scriptNameIndex);
456
+ // Only log for template-type assets (no nested logging for transitive deps)
457
+ if (asset.type === 'template' && foundIds.size > 0) {
458
+ const depDetails = [...foundIds].map(id => {
459
+ const dep = assets[id];
460
+ return dep ? `${id}(${dep.type}:${dep.name})` : id;
461
+ });
462
+ console.log(` 📦 template "${asset.name}" (${assetId}) → ${foundIds.size} deps: [${depDetails.join(', ')}]`);
463
+ }
471
464
  for (const id of foundIds) {
472
465
  if (!result.has(id)) {
473
466
  result.add(id);
474
- queue.push(id); // 继续递归收集依赖
467
+ queue.push(id); // continue BFS for transitive deps
475
468
  }
476
469
  }
477
470
  }
478
471
  return result;
479
472
  }
480
473
  /**
481
- * 扫描对象中可能的资源 ID 引用
474
+ * Find the texture asset associated with a textureatlas.
475
+ * In PlayCanvas, a textureatlas and its source texture are separate assets
476
+ * linked by having the same name and the same file hash.
482
477
  */
483
- function scanForAssetIds(obj, assets, foundIds, depth = 0) {
484
- if (depth > 15)
478
+ function findAssociatedTexture(atlasAsset, assets, foundIds) {
479
+ const atlasName = atlasAsset.name;
480
+ const atlasHash = atlasAsset.file?.hash;
481
+ for (const [assetId, asset] of Object.entries(assets)) {
482
+ if (asset.type !== 'texture')
483
+ continue;
484
+ // Match by name and file hash (both should match)
485
+ if (atlasHash && asset.file?.hash === atlasHash) {
486
+ foundIds.add(assetId);
487
+ return;
488
+ }
489
+ // Fallback: match by name if hash is not available
490
+ if (!atlasHash && asset.name === atlasName) {
491
+ foundIds.add(assetId);
492
+ return;
493
+ }
494
+ }
495
+ }
496
+ /**
497
+ * Collect texture dependencies from a PlayCanvas material's data object.
498
+ * Specifically targets known texture/asset reference fields rather than blind scanning,
499
+ * to avoid false positives from color arrays, tiling vectors, etc.
500
+ */
501
+ function collectMaterialDeps(data, assets, foundIds) {
502
+ // Known material fields that hold asset IDs (texture maps and cubemaps)
503
+ const textureFields = [
504
+ 'diffuseMap', 'specularMap', 'metalnessMap', 'glossMap',
505
+ 'emissiveMap', 'opacityMap', 'normalMap', 'heightMap',
506
+ 'aoMap', 'lightMap', 'cubeMap', 'sphereMap',
507
+ 'clearCoatMap', 'clearCoatGlossMap', 'clearCoatNormalMap',
508
+ 'diffuseDetailMap', 'normalDetailMap',
509
+ 'sheenMap', 'sheenGlossMap',
510
+ 'msdfMap', // MSDF font atlas texture
511
+ ];
512
+ for (const field of textureFields) {
513
+ const val = data[field];
514
+ if (val !== null && val !== undefined) {
515
+ const id = String(val);
516
+ if (assets[id])
517
+ foundIds.add(id);
518
+ }
519
+ }
520
+ // Also check cubeMapProjection if it references an asset
521
+ if (data.cubeMapProjectionBox?.asset) {
522
+ const id = String(data.cubeMapProjectionBox.asset);
523
+ if (assets[id])
524
+ foundIds.add(id);
525
+ }
526
+ }
527
+ /**
528
+ * Collect dependencies from a template asset by analyzing its entities structure.
529
+ * Uses precise parsing of PlayCanvas template data instead of blind recursive scanning.
530
+ *
531
+ * Template data structure:
532
+ * asset.data.entities = {
533
+ * "<resource_id>": {
534
+ * name, components: { script: { order, scripts }, element, model, ... },
535
+ * template_id, ...
536
+ * }
537
+ * }
538
+ */
539
+ function collectTemplateDeps(asset, assets, scriptNameIndex, foundIds) {
540
+ const entities = asset.data?.entities;
541
+ if (!entities || typeof entities !== 'object')
542
+ return;
543
+ for (const entity of Object.values(entities)) {
544
+ if (!entity || typeof entity !== 'object')
545
+ continue;
546
+ // 1. Nested template reference (template_id)
547
+ if (entity.template_id) {
548
+ const tid = String(entity.template_id);
549
+ if (assets[tid])
550
+ foundIds.add(tid);
551
+ }
552
+ // 2. Parse components
553
+ if (entity.components && typeof entity.components === 'object') {
554
+ for (const [compName, compData] of Object.entries(entity.components)) {
555
+ if (!compData || typeof compData !== 'object')
556
+ continue;
557
+ if (compName === 'script') {
558
+ // 2a. Script component: resolve script names → asset IDs via index
559
+ collectScriptDepsFromComponent(compData, assets, scriptNameIndex, foundIds);
560
+ }
561
+ else {
562
+ // 2b. Other components (element, model, sound, sprite, etc.):
563
+ // scan for numeric/string asset IDs in their properties
564
+ scanComponentForAssetIds(compData, assets, foundIds, 0);
565
+ }
566
+ }
567
+ }
568
+ }
569
+ }
570
+ /**
571
+ * Resolve script names in a script component to asset IDs,
572
+ * and also scan script attribute values for asset references.
573
+ */
574
+ function collectScriptDepsFromComponent(scriptComponent, assets, scriptNameIndex, foundIds) {
575
+ // Collect script names from order + scripts keys
576
+ const scriptNames = new Set();
577
+ if (Array.isArray(scriptComponent.order)) {
578
+ for (const name of scriptComponent.order) {
579
+ if (typeof name === 'string')
580
+ scriptNames.add(name);
581
+ }
582
+ }
583
+ if (scriptComponent.scripts && typeof scriptComponent.scripts === 'object') {
584
+ for (const name of Object.keys(scriptComponent.scripts)) {
585
+ scriptNames.add(name);
586
+ }
587
+ }
588
+ // Map script names → asset IDs
589
+ for (const scriptName of scriptNames) {
590
+ const assetId = scriptNameIndex.get(scriptName);
591
+ if (assetId && assets[assetId]) {
592
+ foundIds.add(assetId);
593
+ }
594
+ }
595
+ // Scan script attribute values for asset references
596
+ // (e.g. asset-type attributes like "fontAsset", "spriteAsset", etc.)
597
+ if (scriptComponent.scripts && typeof scriptComponent.scripts === 'object') {
598
+ for (const scriptConfig of Object.values(scriptComponent.scripts)) {
599
+ if (scriptConfig?.attributes && typeof scriptConfig.attributes === 'object') {
600
+ scanComponentForAssetIds(scriptConfig.attributes, assets, foundIds, 0);
601
+ }
602
+ }
603
+ }
604
+ }
605
+ /**
606
+ * Recursively scan a component data object for numeric/string asset ID references.
607
+ * Skips known non-asset fields (position, rotation, scale, etc.).
608
+ */
609
+ function scanComponentForAssetIds(obj, assets, foundIds, depth) {
610
+ if (depth > 10)
485
611
  return;
486
612
  if (obj === null || obj === undefined)
487
613
  return;
488
614
  if (typeof obj === 'number') {
489
615
  const id = String(obj);
490
- if (assets[id]) {
616
+ if (assets[id])
491
617
  foundIds.add(id);
492
- }
618
+ return;
493
619
  }
494
- else if (typeof obj === 'string') {
495
- // 直接检查字符串是否是有效的资源 ID(不再限制为纯数字)
496
- if (assets[obj]) {
620
+ if (typeof obj === 'string') {
621
+ if (assets[obj])
497
622
  foundIds.add(obj);
498
- }
623
+ return;
499
624
  }
500
- else if (Array.isArray(obj)) {
501
- // 跳过坐标/颜色数组(通常是 3 4 个小数或小整数)
502
- // 资源 ID 通常是大于 10000 的正整数,不会被误跳过
503
- if (obj.length <= 4 && obj.every(v => typeof v === 'number' && (!Number.isInteger(v) || // 浮点数(坐标/颜色)
504
- (v >= 0 && v < 1000) // 小正整数(颜色分量等)
505
- ))) {
625
+ if (Array.isArray(obj)) {
626
+ // Skip small numeric arrays (likely vectors / colors, not asset IDs)
627
+ if (obj.length <= 4 && obj.every(v => typeof v === 'number' && (!Number.isInteger(v) || (v >= 0 && v < 1000)))) {
506
628
  return;
507
629
  }
508
630
  for (const item of obj) {
509
- scanForAssetIds(item, assets, foundIds, depth + 1);
631
+ scanComponentForAssetIds(item, assets, foundIds, depth + 1);
510
632
  }
633
+ return;
511
634
  }
512
- else if (typeof obj === 'object') {
635
+ if (typeof obj === 'object') {
513
636
  for (const [key, value] of Object.entries(obj)) {
514
- // 跳过明显不是资源引用的字段
515
- if (['name', 'enabled', 'position', 'rotation', 'scale', 'tags',
516
- 'children', 'parent', 'resource_id'].includes(key)) {
637
+ // Skip known non-asset fields
638
+ if (key === 'name' || key === 'enabled' || key === 'position' ||
639
+ key === 'rotation' || key === 'scale' || key === 'tags' ||
640
+ key === 'children' || key === 'parent' || key === 'resource_id') {
517
641
  continue;
518
642
  }
519
- scanForAssetIds(value, assets, foundIds, depth + 1);
643
+ scanComponentForAssetIds(value, assets, foundIds, depth + 1);
520
644
  }
521
645
  }
522
646
  }
@@ -0,0 +1,70 @@
1
+ export interface AudioAssetInfo {
2
+ /** Absolute path to the file */
3
+ filePath: string;
4
+ /** Path relative to build directory */
5
+ relativePath: string;
6
+ /** File size in bytes */
7
+ size: number;
8
+ /** Whether a matching .spectral.json exists */
9
+ hasSpectral: boolean;
10
+ /** Spectral JSON size if available */
11
+ spectralSize?: number;
12
+ /** Savings by switching to spectral */
13
+ potentialSavings?: number;
14
+ }
15
+ export interface AudioOptimizationReport {
16
+ /** All audio files found */
17
+ audioFiles: AudioAssetInfo[];
18
+ /** All .spectral.json files found */
19
+ spectralFiles: string[];
20
+ /** Total size of all MP3/audio files */
21
+ totalAudioSize: number;
22
+ /** Total size if all with spectral were replaced */
23
+ totalSpectralSize: number;
24
+ /** Whether SpectralAudioEngine runtime was injected */
25
+ runtimeInjected: boolean;
26
+ /** Size of injected runtime (bytes) */
27
+ runtimeSize: number;
28
+ /** Net savings from spectral replacement (bytes) */
29
+ netSavings: number;
30
+ }
31
+ export interface AudioOptimizerOptions {
32
+ /** Build output directory to scan */
33
+ buildDir: string;
34
+ /**
35
+ * Whether to auto-inject SpectralAudioEngine runtime when spectral files are present.
36
+ * Defaults to true.
37
+ */
38
+ injectRuntime?: boolean;
39
+ /**
40
+ * Path to an HTML file to inject the runtime into (e.g. index.html).
41
+ * If not provided, runtime injection is skipped even if injectRuntime=true.
42
+ */
43
+ htmlOutputPath?: string;
44
+ /** Verbose logging */
45
+ verbose?: boolean;
46
+ }
47
+ /**
48
+ * AudioOptimizer — Build-time audio analysis and optimization.
49
+ *
50
+ * Scans a build directory for audio files and .spectral.json counterparts.
51
+ * When spectral files are present, optionally injects the SpectralAudioEngine
52
+ * runtime (< 3KB) into the output HTML so the browser can synthesize them.
53
+ *
54
+ * Usage:
55
+ * const optimizer = new AudioOptimizer({ buildDir: './dist', htmlOutputPath: './dist/index.html' });
56
+ * const report = await optimizer.optimize();
57
+ * console.log(report);
58
+ */
59
+ export declare class AudioOptimizer {
60
+ private options;
61
+ constructor(options: AudioOptimizerOptions);
62
+ /**
63
+ * Run the optimizer. Returns a report with audio file details and optimization results.
64
+ */
65
+ optimize(): Promise<AudioOptimizationReport>;
66
+ /**
67
+ * Print a human-readable summary of the optimization report.
68
+ */
69
+ static printReport(report: AudioOptimizationReport): void;
70
+ }