import Presets from "./presets.js"; import PresetController from "./preset-controller.js"; import BackgroundController from "./background-controller.js"; import SpineController from "./spine-controller.js"; import AudioController from "./audio-controller.js"; // 与 project.json 里属性的默认值保持一致 const DEFAULT_PRESET_ID = "xilian"; const DEFAULT_VOLUME = 0.5; const presets = new PresetController(Presets); const backgroundController = new BackgroundController(); const spineController = new SpineController("spine-container"); const audioController = new AudioController("background-music"); // 唯一的真相来源。WE 可能在 window.load 之前就下发用户属性,所以这里只记录状态; // 渲染时再决定"能不能立刻应用"——只有播放器需要等到 load 之后才存在。 const state = { ready: false, presetId: "", audioSource: "", // 空字符串 = 用当前预设的默认音源 audioVolume: DEFAULT_VOLUME, bgm: "", // 空字符串 = 随预设;否则是某个 preset.js 里 audioChoices 的 id }; let appliedPresetId = ""; // 量出背景图的真实宽高比:立绘层要靠它才能和背景同步缩放。 // 直接读图片,避免在预设里再手写一个容易写错的数字。 function measureImageAspect(url) { return new Promise((resolve) => { if (!url) { resolve(undefined); return; } const probe = new Image(); probe.onload = () => resolve(probe.naturalWidth / probe.naturalHeight); probe.onerror = () => resolve(undefined); probe.src = url; }); } // bgm combo 选中的音源只在本壁纸声明过时才生效。否则回落本壁纸默认音源—— // 选择本身记着不动,切回那份壁纸时用户的选择还在。 function bgmSource(preset) { if (!state.bgm) return ""; const choices = preset.audioChoices || []; const hit = choices.find((choice) => choice.id === state.bgm); return hit ? hit.source : ""; } function render() { const presetId = state.presetId || DEFAULT_PRESET_ID; const preset = presets.get(presetId); if (!preset) return; // 只有壁纸真的换了才碰背景与骨架。否则拖动音量滑块都会重建播放器 // (重建 = 重新下载 3.4 MB 骨架与全部贴图)。 if (appliedPresetId !== presetId) { appliedPresetId = presetId; backgroundController.setImage(preset.backgroundImage); spineController.setConfig(preset.spineConfig); // 播放器还不存在时只是记下来 // 图片解码是异步的,回来时可能已经切到别的预设了,所以要核对一次 measureImageAspect(preset.backgroundImage).then((aspect) => { if (appliedPresetId === presetId) spineController.setImageAspect(aspect); }); } // 音源优先级:用户自填 URL > bgm 里选的(若本壁纸有)> 本壁纸默认音源 audioController.setSource(state.audioSource || bgmSource(preset) || preset.audioOptions.source); audioController.setVolume(state.audioVolume); if (state.ready) audioController.play(); } window.addEventListener("load", function () { state.ready = true; render(); // 先把状态落到各控制器(此时播放器尚不存在,只写配置) spineController.create(); // 再用写好的配置创建播放器 }); // 必须在模块顶层注册:属性事件可能在 window.load 之前到达 window.wallpaperPropertyListener = { applyUserProperties: function (properties) { // 先写状态:无条件记录,与播放器是否存在、是否加载完成无关 if (properties.preset) { const presetId = properties.preset.value; if (presets.has(presetId)) { state.presetId = presetId; } else if (presetId) { console.warn(`忽略未知的预设 id:${presetId}`); } } if (properties.audio_file) { state.audioSource = String(properties.audio_file.value || "").trim(); } if (properties.bgm) { const bgm = String(properties.bgm.value || "").trim(); state.bgm = bgm === "auto" ? "" : bgm; } if (properties.audio_volume) { const volume = Number(properties.audio_volume.value); if (Number.isFinite(volume)) { state.audioVolume = Math.max(0, Math.min(1, volume)); } } render(); }, applyGeneralProperties: function (properties) { // WE 不会替壁纸限流,fps 得自己落实。这个属性与预设无关,不进 state。 // 判 undefined 而不是真值:0 表示不限流,用真值判断会把"取消限流"整个吞掉。 if (properties.fps !== undefined) { spineController.setFps(properties.fps); } }, };