webwallgl 1.1.0 → 1.3.5
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.
- package/README.en.md +164 -4
- package/README.md +164 -4
- package/package.json +1 -1
- package/types.d.ts +166 -17
- package/webwallgl.d.ts +35 -15
- package/webwallgl.global.js +706 -88
- package/webwallgl.global.js.map +1 -1
- package/webwallgl.global.min.js +37 -37
- package/webwallgl.global.min.js.map +1 -1
- package/webwallgl.min.mjs +37 -37
- package/webwallgl.min.mjs.map +1 -1
- package/webwallgl.mjs +707 -89
- package/webwallgl.mjs.map +1 -1
package/webwallgl.global.js
CHANGED
|
@@ -51,10 +51,24 @@
|
|
|
51
51
|
rt.raf = void 0;
|
|
52
52
|
for (const p of rt.videoPairs ?? []) p.destroy();
|
|
53
53
|
rt.videoPairs = void 0;
|
|
54
|
-
if (rt.sceneCleanup)
|
|
54
|
+
if (rt.sceneCleanup) {
|
|
55
|
+
try {
|
|
56
|
+
rt.sceneCleanup();
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
}
|
|
55
60
|
rt.sceneCleanup = void 0;
|
|
56
61
|
rt.sceneCtl = void 0;
|
|
57
62
|
rt.pointerCtl = void 0;
|
|
63
|
+
rt.mediaCtl = void 0;
|
|
64
|
+
const ds = rt.wallpaperDisposers ?? [];
|
|
65
|
+
rt.wallpaperDisposers = [];
|
|
66
|
+
for (const d of ds) {
|
|
67
|
+
try {
|
|
68
|
+
d();
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
}
|
|
58
72
|
if (rt.renderer) {
|
|
59
73
|
rt.renderer.dispose?.();
|
|
60
74
|
rt.renderer = void 0;
|
|
@@ -96,10 +110,18 @@
|
|
|
96
110
|
rt.canvas = void 0;
|
|
97
111
|
rt.ctx = void 0;
|
|
98
112
|
rt.info = void 0;
|
|
113
|
+
clearSceneDebugGlobals();
|
|
99
114
|
resetFrameMeter(rt);
|
|
100
115
|
}
|
|
101
116
|
function destroyRuntime(rt) {
|
|
102
|
-
|
|
117
|
+
try {
|
|
118
|
+
clear(rt);
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
if (rt.peekRaf !== void 0) {
|
|
122
|
+
cancelAnimationFrame(rt.peekRaf);
|
|
123
|
+
rt.peekRaf = void 0;
|
|
124
|
+
}
|
|
103
125
|
for (const off of rt.disposers.splice(0)) {
|
|
104
126
|
try {
|
|
105
127
|
off();
|
|
@@ -155,6 +177,32 @@
|
|
|
155
177
|
const obj = rt.video ?? rt.img;
|
|
156
178
|
if (obj && obj.isConnected) obj.style.objectPosition = pos;
|
|
157
179
|
}
|
|
180
|
+
const SCENE_DEBUG_GLOBALS = [
|
|
181
|
+
"__scene",
|
|
182
|
+
"__sceneLayers",
|
|
183
|
+
"__textures",
|
|
184
|
+
"__objScripts",
|
|
185
|
+
"__textWidgets",
|
|
186
|
+
"__mediaHooks",
|
|
187
|
+
"__mediaControl",
|
|
188
|
+
"__mediaStats",
|
|
189
|
+
"__mediaSet",
|
|
190
|
+
"__system",
|
|
191
|
+
"__liveSystem",
|
|
192
|
+
"__audioStats",
|
|
193
|
+
"__audioMute",
|
|
194
|
+
"__particleStats",
|
|
195
|
+
"__particleToggle",
|
|
196
|
+
"__pointerStats",
|
|
197
|
+
"__compositeStats",
|
|
198
|
+
"__compositeEnable"
|
|
199
|
+
];
|
|
200
|
+
function clearSceneDebugGlobals() {
|
|
201
|
+
const w = window;
|
|
202
|
+
for (const k of SCENE_DEBUG_GLOBALS) {
|
|
203
|
+
if (k in w) delete w[k];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
158
206
|
function syncCanvasSize(rt, canvas, cfg) {
|
|
159
207
|
const dpr = effectiveDpr(rt, cfg);
|
|
160
208
|
const w = Math.max(1, Math.round((canvas.clientWidth || window.innerWidth || 1) * dpr));
|
|
@@ -3543,6 +3591,11 @@ void main() {
|
|
|
3543
3591
|
const from = usesVarying ? /unprojectedUVs\s*\*=\s*v_PointerScale\s*\*/ : /unprojectedUVs\s*\*=\s*g_PointerScale\s*\*/;
|
|
3544
3592
|
return out.replace(from, "unprojectedUVs *= " + XRAY_FRAG_UV_SCALE + " *");
|
|
3545
3593
|
}
|
|
3594
|
+
const XRAY_SIZE_FALLBACK = 1;
|
|
3595
|
+
function constantFallback(uniformName, declaredDefault) {
|
|
3596
|
+
if (uniformName === "g_PointerScale") return XRAY_SIZE_FALLBACK;
|
|
3597
|
+
return declaredDefault;
|
|
3598
|
+
}
|
|
3546
3599
|
function createRenderer(canvas, opts = {}) {
|
|
3547
3600
|
const gl = canvas.getContext("webgl2", { premultipliedAlpha: false, antialias: false, alpha: false, preserveDrawingBuffer: true });
|
|
3548
3601
|
if (!gl) throw new Error("当前浏览器不支持 WebGL2");
|
|
@@ -3899,7 +3952,8 @@ void main() {
|
|
|
3899
3952
|
}
|
|
3900
3953
|
for (const [matKey, entry] of Object.entries(matMeta || {})) {
|
|
3901
3954
|
if (!constants || !(matKey in constants)) {
|
|
3902
|
-
|
|
3955
|
+
const dflt = constantFallback(entry.uniform, entry.default);
|
|
3956
|
+
if (dflt !== void 0) setConstant(uni, entry.uniform, dflt);
|
|
3903
3957
|
}
|
|
3904
3958
|
}
|
|
3905
3959
|
}
|
|
@@ -5118,9 +5172,11 @@ void main() {
|
|
|
5118
5172
|
__proto__: null,
|
|
5119
5173
|
ALIGN,
|
|
5120
5174
|
XRAY_IDLE_SCREEN_UV,
|
|
5175
|
+
XRAY_SIZE_FALLBACK,
|
|
5121
5176
|
applyColorBlendCPU,
|
|
5122
5177
|
collectGroupDescendantIds,
|
|
5123
5178
|
colorBlendPlan,
|
|
5179
|
+
constantFallback,
|
|
5124
5180
|
createRenderer,
|
|
5125
5181
|
effectFboSize,
|
|
5126
5182
|
layerWantsPreserveBackdrop,
|
|
@@ -11344,19 +11400,84 @@ void main() {
|
|
|
11344
11400
|
dec.close?.();
|
|
11345
11401
|
return { width, height, frames };
|
|
11346
11402
|
}
|
|
11403
|
+
function attachVideoSpectrum(rt, cfg, v) {
|
|
11404
|
+
if (rt.audioBridge) return;
|
|
11405
|
+
const AC = window.AudioContext || window.webkitAudioContext;
|
|
11406
|
+
if (!AC) return;
|
|
11407
|
+
let ctx;
|
|
11408
|
+
let analyser;
|
|
11409
|
+
try {
|
|
11410
|
+
ctx = new AC();
|
|
11411
|
+
const srcNode = ctx.createMediaElementSource(v);
|
|
11412
|
+
analyser = ctx.createAnalyser();
|
|
11413
|
+
analyser.fftSize = 256;
|
|
11414
|
+
analyser.smoothingTimeConstant = 0.75;
|
|
11415
|
+
srcNode.connect(analyser);
|
|
11416
|
+
analyser.connect(ctx.destination);
|
|
11417
|
+
} catch (e) {
|
|
11418
|
+
reportDiag(rt, cfg, `media 频谱接管跳过: ${e?.message ?? e}`);
|
|
11419
|
+
return;
|
|
11420
|
+
}
|
|
11421
|
+
const bins = new Uint8Array(analyser.frequencyBinCount);
|
|
11422
|
+
const left = new Float32Array(64);
|
|
11423
|
+
const right = new Float32Array(64);
|
|
11424
|
+
const bridge = () => {
|
|
11425
|
+
if (ctx.state !== "running") return null;
|
|
11426
|
+
analyser.getByteFrequencyData(bins);
|
|
11427
|
+
const n = Math.min(64, bins.length);
|
|
11428
|
+
for (let i = 0; i < n; i++) {
|
|
11429
|
+
const x = bins[i] / 255;
|
|
11430
|
+
left[i] = x;
|
|
11431
|
+
right[i] = x;
|
|
11432
|
+
}
|
|
11433
|
+
for (let i = n; i < 64; i++) {
|
|
11434
|
+
left[i] = 0;
|
|
11435
|
+
right[i] = 0;
|
|
11436
|
+
}
|
|
11437
|
+
return { left, right };
|
|
11438
|
+
};
|
|
11439
|
+
rt.audioBridge = bridge;
|
|
11440
|
+
if (ctx.state === "suspended") {
|
|
11441
|
+
const kick = () => {
|
|
11442
|
+
void ctx.resume().catch(() => {
|
|
11443
|
+
});
|
|
11444
|
+
window.removeEventListener("pointerdown", kick);
|
|
11445
|
+
window.removeEventListener("keydown", kick);
|
|
11446
|
+
};
|
|
11447
|
+
window.addEventListener("pointerdown", kick, { once: true });
|
|
11448
|
+
window.addEventListener("keydown", kick, { once: true });
|
|
11449
|
+
(rt.wallpaperDisposers ??= []).push(() => {
|
|
11450
|
+
window.removeEventListener("pointerdown", kick);
|
|
11451
|
+
window.removeEventListener("keydown", kick);
|
|
11452
|
+
});
|
|
11453
|
+
}
|
|
11454
|
+
(rt.wallpaperDisposers ??= []).push(() => {
|
|
11455
|
+
if (rt.audioBridge === bridge) rt.audioBridge = null;
|
|
11456
|
+
void ctx.close().catch(() => {
|
|
11457
|
+
});
|
|
11458
|
+
});
|
|
11459
|
+
}
|
|
11347
11460
|
function mountMedia(rt, cfg) {
|
|
11348
11461
|
clear(rt);
|
|
11349
|
-
|
|
11462
|
+
const failHard = (why) => {
|
|
11463
|
+
reportDiag(rt, cfg, `media ${cfg.type} 失败: ${why}`);
|
|
11464
|
+
rt.onError?.(new Error(`媒体壁纸(${cfg.type})${why}`));
|
|
11350
11465
|
rt.fallbackPage?.();
|
|
11466
|
+
};
|
|
11467
|
+
if (!cfg.src) {
|
|
11468
|
+
failHard("缺少资源 URL(cfg.src 为空)");
|
|
11351
11469
|
return;
|
|
11352
11470
|
}
|
|
11353
11471
|
const isVideo = cfg.type === "video";
|
|
11354
11472
|
const isGif = cfg.type === "gif";
|
|
11355
|
-
const
|
|
11473
|
+
const embedded = cfg.canvas instanceof HTMLCanvasElement;
|
|
11474
|
+
const c = embedded ? cfg.canvas : document.createElement("canvas");
|
|
11356
11475
|
const dpr = effectiveDpr(rt, cfg);
|
|
11357
|
-
|
|
11358
|
-
|
|
11359
|
-
c.
|
|
11476
|
+
const vw = c.clientWidth || window.innerWidth || 1;
|
|
11477
|
+
const vh = c.clientHeight || window.innerHeight || 1;
|
|
11478
|
+
c.width = Math.max(1, Math.round(vw * dpr));
|
|
11479
|
+
c.height = Math.max(1, Math.round(vh * dpr));
|
|
11480
|
+
if (!embedded) c.style.cssText = "position:absolute;inset:0;width:100%;height:100%;";
|
|
11360
11481
|
const gl2 = c.getContext("webgl2", {
|
|
11361
11482
|
premultipliedAlpha: false,
|
|
11362
11483
|
antialias: false,
|
|
@@ -11364,13 +11485,32 @@ void main() {
|
|
|
11364
11485
|
preserveDrawingBuffer: true
|
|
11365
11486
|
});
|
|
11366
11487
|
if (!gl2) {
|
|
11367
|
-
reportDiag(rt, cfg, `media ${cfg.type}: WEBGL2_UNAVAILABLE
|
|
11488
|
+
reportDiag(rt, cfg, `media ${cfg.type}: WEBGL2_UNAVAILABLE`);
|
|
11489
|
+
if (embedded) {
|
|
11490
|
+
rt.onError?.(new Error("WEBGL2_UNAVAILABLE"));
|
|
11491
|
+
return;
|
|
11492
|
+
}
|
|
11368
11493
|
if (isVideo) mountVideoDom(rt, cfg);
|
|
11369
11494
|
else mountGifDom(rt, cfg);
|
|
11370
11495
|
return;
|
|
11371
11496
|
}
|
|
11372
|
-
rt.wrap?.appendChild(c);
|
|
11497
|
+
if (!embedded) rt.wrap?.appendChild(c);
|
|
11373
11498
|
rt.canvas = c;
|
|
11499
|
+
if (rt.onSceneInfo) {
|
|
11500
|
+
const hook = rt.onSceneInfo;
|
|
11501
|
+
rt.onSceneInfo = void 0;
|
|
11502
|
+
try {
|
|
11503
|
+
hook({
|
|
11504
|
+
width: vw,
|
|
11505
|
+
height: vh,
|
|
11506
|
+
layerCount: 0,
|
|
11507
|
+
hasModels: false,
|
|
11508
|
+
hasParticles: false,
|
|
11509
|
+
hasText: false
|
|
11510
|
+
});
|
|
11511
|
+
} catch {
|
|
11512
|
+
}
|
|
11513
|
+
}
|
|
11374
11514
|
let disposed = false;
|
|
11375
11515
|
rt.sceneCleanup = () => {
|
|
11376
11516
|
disposed = true;
|
|
@@ -11382,6 +11522,21 @@ void main() {
|
|
|
11382
11522
|
let pauseImpl;
|
|
11383
11523
|
let resumeImpl;
|
|
11384
11524
|
let videoWasPlaying = false;
|
|
11525
|
+
rt.sceneAudio = {
|
|
11526
|
+
setVolume(v) {
|
|
11527
|
+
const vid = rt.video;
|
|
11528
|
+
if (!vid) return;
|
|
11529
|
+
const vol = Math.max(0, Math.min(1, Number(v) || 0));
|
|
11530
|
+
vid.volume = vol;
|
|
11531
|
+
vid.muted = vol <= 0;
|
|
11532
|
+
if (vol > 0 && vid.paused && !rt.paused) {
|
|
11533
|
+
void vid.play().catch((e) => {
|
|
11534
|
+
reportDiag(rt, cfg, `media 取消静音后自动播放被拒绝: ${e?.message ?? e}`);
|
|
11535
|
+
});
|
|
11536
|
+
}
|
|
11537
|
+
},
|
|
11538
|
+
audios: []
|
|
11539
|
+
};
|
|
11385
11540
|
rt.sceneCtl = {
|
|
11386
11541
|
pause() {
|
|
11387
11542
|
pauseImpl?.();
|
|
@@ -11395,8 +11550,7 @@ void main() {
|
|
|
11395
11550
|
const fail = (why) => {
|
|
11396
11551
|
if (disposed) return;
|
|
11397
11552
|
disposed = true;
|
|
11398
|
-
|
|
11399
|
-
rt.fallbackPage?.();
|
|
11553
|
+
failHard(why);
|
|
11400
11554
|
};
|
|
11401
11555
|
void (async () => {
|
|
11402
11556
|
try {
|
|
@@ -11444,6 +11598,7 @@ void main() {
|
|
|
11444
11598
|
});
|
|
11445
11599
|
if (!rt.paused) void v.play().catch(() => {
|
|
11446
11600
|
});
|
|
11601
|
+
attachVideoSpectrum(rt, cfg, v);
|
|
11447
11602
|
reportDiag(rt, cfg, `media video ${mediaW}x${mediaH} → scene 渲染`);
|
|
11448
11603
|
} else {
|
|
11449
11604
|
let decoded = false;
|
|
@@ -11543,6 +11698,14 @@ void main() {
|
|
|
11543
11698
|
peek.x,
|
|
11544
11699
|
peek.y
|
|
11545
11700
|
).then(() => {
|
|
11701
|
+
if (rt.onFirstFrame) {
|
|
11702
|
+
const first = rt.onFirstFrame;
|
|
11703
|
+
rt.onFirstFrame = void 0;
|
|
11704
|
+
try {
|
|
11705
|
+
first();
|
|
11706
|
+
} catch {
|
|
11707
|
+
}
|
|
11708
|
+
}
|
|
11546
11709
|
if (disposed || rt.paused) return;
|
|
11547
11710
|
rt.raf = requestAnimationFrame(renderLoop);
|
|
11548
11711
|
}).catch((e) => fail(String(e.message || e).slice(0, 200)));
|
|
@@ -11625,6 +11788,48 @@ void main() {
|
|
|
11625
11788
|
rt.img = img;
|
|
11626
11789
|
}
|
|
11627
11790
|
const PKG_PATHS = ["scene.pkg", "scenes/scene.pkg", "gifscene.pkg"];
|
|
11791
|
+
const EXT_TYPES = {
|
|
11792
|
+
mp4: "video",
|
|
11793
|
+
webm: "video",
|
|
11794
|
+
mov: "video",
|
|
11795
|
+
m4v: "video",
|
|
11796
|
+
ogv: "video",
|
|
11797
|
+
gif: "gif",
|
|
11798
|
+
png: "image",
|
|
11799
|
+
jpg: "image",
|
|
11800
|
+
jpeg: "image",
|
|
11801
|
+
webp: "image",
|
|
11802
|
+
avif: "image",
|
|
11803
|
+
bmp: "image"
|
|
11804
|
+
};
|
|
11805
|
+
function typeFromMime(mime) {
|
|
11806
|
+
const m = mime.toLowerCase().split(";")[0].trim();
|
|
11807
|
+
if (m === "image/gif") return "gif";
|
|
11808
|
+
if (m.startsWith("video/")) return "video";
|
|
11809
|
+
if (m.startsWith("image/")) return "image";
|
|
11810
|
+
return null;
|
|
11811
|
+
}
|
|
11812
|
+
function sniffMediaType(url) {
|
|
11813
|
+
if (typeof url !== "string" || !url) return null;
|
|
11814
|
+
let path = url;
|
|
11815
|
+
const hash = path.indexOf("#");
|
|
11816
|
+
if (hash >= 0) path = path.slice(0, hash);
|
|
11817
|
+
const q = path.indexOf("?");
|
|
11818
|
+
if (q >= 0) path = path.slice(0, q);
|
|
11819
|
+
const seg = path.split("/").pop() || "";
|
|
11820
|
+
const dot = seg.lastIndexOf(".");
|
|
11821
|
+
if (dot < 0) return null;
|
|
11822
|
+
return EXT_TYPES[seg.slice(dot + 1).toLowerCase()] ?? null;
|
|
11823
|
+
}
|
|
11824
|
+
async function sniffMediaTypeByHead(url, init, signal) {
|
|
11825
|
+
try {
|
|
11826
|
+
const r = await fetch(url, { ...init, method: "HEAD", signal });
|
|
11827
|
+
if (!r.ok) return null;
|
|
11828
|
+
return typeFromMime(r.headers.get("content-type") || "");
|
|
11829
|
+
} catch {
|
|
11830
|
+
return null;
|
|
11831
|
+
}
|
|
11832
|
+
}
|
|
11628
11833
|
function httpSource(baseUrl, init) {
|
|
11629
11834
|
const base = baseUrl.replace(/\/+$/, "");
|
|
11630
11835
|
return {
|
|
@@ -11679,6 +11884,29 @@ void main() {
|
|
|
11679
11884
|
if (signal?.aborted) throw new Error("aborted");
|
|
11680
11885
|
}
|
|
11681
11886
|
return { url: `${base}/${file}` };
|
|
11887
|
+
},
|
|
11888
|
+
/**
|
|
11889
|
+
* 媒体壁纸(video/gif/image)的资源地址:`{base}/{project.file}`。
|
|
11890
|
+
*
|
|
11891
|
+
* 与 webEntry 的区别是**没有默认文件名可兜底**:网页壁纸缺 file 时
|
|
11892
|
+
* index.html 是行业惯例,媒体壁纸的文件名(scene.mp4 / xxx.gif)完全由作者定,
|
|
11893
|
+
* 猜一个只会 404。拿不到 file 就返回 null,让 mount 报「无法解析媒体 URL」,
|
|
11894
|
+
* 而不是发一个必然失败的请求、再把那个 404 当成根因写进错误里。
|
|
11895
|
+
*/
|
|
11896
|
+
async mediaEntry(signal) {
|
|
11897
|
+
let file = "";
|
|
11898
|
+
try {
|
|
11899
|
+
const r = await fetch(`${base}/project.json`, { ...init, signal });
|
|
11900
|
+
if (r.ok) {
|
|
11901
|
+
const project = await r.json();
|
|
11902
|
+
if (project && typeof project.file === "string" && project.file.trim()) {
|
|
11903
|
+
file = project.file.trim().replace(/^\/+/, "");
|
|
11904
|
+
}
|
|
11905
|
+
}
|
|
11906
|
+
} catch {
|
|
11907
|
+
if (signal?.aborted) throw new Error("aborted");
|
|
11908
|
+
}
|
|
11909
|
+
return file ? { url: `${base}/${file}` } : null;
|
|
11682
11910
|
}
|
|
11683
11911
|
};
|
|
11684
11912
|
}
|
|
@@ -11698,6 +11926,43 @@ void main() {
|
|
|
11698
11926
|
project: async () => project ?? null
|
|
11699
11927
|
};
|
|
11700
11928
|
}
|
|
11929
|
+
function mediaSource(urlOrFile, options) {
|
|
11930
|
+
const isBlob = typeof urlOrFile !== "string";
|
|
11931
|
+
const named = urlOrFile;
|
|
11932
|
+
let type = options?.type ?? (isBlob ? typeFromMime(urlOrFile.type || "") : null) ?? sniffMediaType(isBlob ? String(named.name ?? "") : urlOrFile);
|
|
11933
|
+
let headTried = false;
|
|
11934
|
+
let objectUrl = null;
|
|
11935
|
+
const url = () => {
|
|
11936
|
+
if (!isBlob) return urlOrFile;
|
|
11937
|
+
if (!objectUrl) objectUrl = URL.createObjectURL(urlOrFile);
|
|
11938
|
+
return objectUrl;
|
|
11939
|
+
};
|
|
11940
|
+
const key = options?.key ?? (isBlob ? typeof named.name === "string" ? `media:${named.name}:${named.size}:${named.lastModified ?? 0}` : void 0 : `media:${urlOrFile}`);
|
|
11941
|
+
return {
|
|
11942
|
+
key,
|
|
11943
|
+
async scenePkg() {
|
|
11944
|
+
throw new Error("mediaSource 是纯媒体来源,没有 scene.pkg(请改用 httpSource/fileSource/bytesSource)");
|
|
11945
|
+
},
|
|
11946
|
+
// type 为 null 时也如实返回:resolveMountConfig 会再按 mediaEntry 的 URL
|
|
11947
|
+
// 嗅探一次(含 HEAD 兜底),仍认不出才落回 scene 并报错
|
|
11948
|
+
async project() {
|
|
11949
|
+
return type ? { type } : null;
|
|
11950
|
+
},
|
|
11951
|
+
async mediaEntry(signal) {
|
|
11952
|
+
if (!type && !isBlob && !headTried) {
|
|
11953
|
+
headTried = true;
|
|
11954
|
+
type = await sniffMediaTypeByHead(urlOrFile, void 0, signal);
|
|
11955
|
+
}
|
|
11956
|
+
return { url: url(), type: type ?? void 0 };
|
|
11957
|
+
},
|
|
11958
|
+
dispose() {
|
|
11959
|
+
if (objectUrl) {
|
|
11960
|
+
URL.revokeObjectURL(objectUrl);
|
|
11961
|
+
objectUrl = null;
|
|
11962
|
+
}
|
|
11963
|
+
}
|
|
11964
|
+
};
|
|
11965
|
+
}
|
|
11701
11966
|
const LOOP_PREROLL_SEC = 0.5;
|
|
11702
11967
|
const LOOP_HOLD_SEC = 0.04;
|
|
11703
11968
|
const LOOP_SWAP_EPS = 0.08;
|
|
@@ -12978,9 +13243,10 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
12978
13243
|
const audioDriverRef = {
|
|
12979
13244
|
current: null
|
|
12980
13245
|
};
|
|
12981
|
-
let
|
|
13246
|
+
let liveMediaOverride = null;
|
|
13247
|
+
const currentMediaDriver = () => liveMediaOverride ?? rt.mediaSource ?? simMedia;
|
|
12982
13248
|
let windowDriver = simWindow;
|
|
12983
|
-
const audioSim = { enabled: supportsAudioProcessing };
|
|
13249
|
+
const audioSim = { enabled: supportsAudioProcessing && !rt.audioDisabled };
|
|
12984
13250
|
const zero = (n) => new Float32Array(n);
|
|
12985
13251
|
const SILENT_AUDIO = {
|
|
12986
13252
|
left16: zero(16),
|
|
@@ -13079,7 +13345,7 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
13079
13345
|
const shortcuts = system.createShortcutHandler((name) => {
|
|
13080
13346
|
reportDiag(rt, cfg, `openUserShortcut: ${name}`);
|
|
13081
13347
|
});
|
|
13082
|
-
const mediaSim = { enabled:
|
|
13348
|
+
const mediaSim = { enabled: !rt.mediaDisabled, override: null };
|
|
13083
13349
|
const mediaHooks = [];
|
|
13084
13350
|
let lastMediaSnap = null;
|
|
13085
13351
|
liveHold.lastSnap = {
|
|
@@ -13088,7 +13354,7 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
13088
13354
|
if (lastMediaSnap) lastMediaSnap.hasThumbnail = v;
|
|
13089
13355
|
}
|
|
13090
13356
|
};
|
|
13091
|
-
const mediaSnapshot = () =>
|
|
13357
|
+
const mediaSnapshot = () => currentMediaDriver().snapshot;
|
|
13092
13358
|
const registerMediaHook = (sb) => {
|
|
13093
13359
|
if (!sb || !sb.hasMediaHook || mediaHooks.includes(sb)) return;
|
|
13094
13360
|
mediaHooks.push(sb);
|
|
@@ -13133,36 +13399,30 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
13133
13399
|
}
|
|
13134
13400
|
lastMediaSnap = media.cloneMediaSnapshot(mediaSnapshot());
|
|
13135
13401
|
};
|
|
13402
|
+
const callDriver = (name) => {
|
|
13403
|
+
const drv = currentMediaDriver();
|
|
13404
|
+
const fn = drv?.[name];
|
|
13405
|
+
if (typeof fn === "function") {
|
|
13406
|
+
try {
|
|
13407
|
+
fn.call(drv);
|
|
13408
|
+
} catch (e) {
|
|
13409
|
+
reportDiag(rt, cfg, `media ${name} 失败: ${e?.message}`);
|
|
13410
|
+
}
|
|
13411
|
+
}
|
|
13412
|
+
dispatchMediaNow();
|
|
13413
|
+
return mediaSnapshot();
|
|
13414
|
+
};
|
|
13136
13415
|
const mediaControl = {
|
|
13137
13416
|
get snapshot() {
|
|
13138
13417
|
return mediaSnapshot();
|
|
13139
13418
|
},
|
|
13140
|
-
skipNext: () =>
|
|
13141
|
-
|
|
13142
|
-
|
|
13143
|
-
|
|
13144
|
-
|
|
13145
|
-
skipPrevious: () => {
|
|
13146
|
-
mediaDriver.skipPrevious();
|
|
13147
|
-
dispatchMediaNow();
|
|
13148
|
-
return mediaSnapshot();
|
|
13149
|
-
},
|
|
13150
|
-
play: () => {
|
|
13151
|
-
mediaDriver.play();
|
|
13152
|
-
dispatchMediaNow();
|
|
13153
|
-
return mediaSnapshot();
|
|
13154
|
-
},
|
|
13155
|
-
pause: () => {
|
|
13156
|
-
mediaDriver.pause();
|
|
13157
|
-
dispatchMediaNow();
|
|
13158
|
-
return mediaSnapshot();
|
|
13159
|
-
},
|
|
13160
|
-
playPause: () => {
|
|
13161
|
-
mediaDriver.playPause();
|
|
13162
|
-
dispatchMediaNow();
|
|
13163
|
-
return mediaSnapshot();
|
|
13164
|
-
}
|
|
13419
|
+
skipNext: () => callDriver("skipNext"),
|
|
13420
|
+
skipPrevious: () => callDriver("skipPrevious"),
|
|
13421
|
+
play: () => callDriver("play"),
|
|
13422
|
+
pause: () => callDriver("pause"),
|
|
13423
|
+
playPause: () => callDriver("playPause")
|
|
13165
13424
|
};
|
|
13425
|
+
rt.mediaCtl = mediaControl;
|
|
13166
13426
|
window.__mediaControl = mediaControl;
|
|
13167
13427
|
window.__system = {
|
|
13168
13428
|
media: mediaControl,
|
|
@@ -13283,6 +13543,18 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
13283
13543
|
}
|
|
13284
13544
|
}
|
|
13285
13545
|
if (cfg.liveSystem) {
|
|
13546
|
+
const liveSlot = {
|
|
13547
|
+
handle: null,
|
|
13548
|
+
dead: false
|
|
13549
|
+
};
|
|
13550
|
+
(rt.wallpaperDisposers ??= []).push(() => {
|
|
13551
|
+
liveSlot.dead = true;
|
|
13552
|
+
try {
|
|
13553
|
+
liveSlot.handle?.dispose();
|
|
13554
|
+
} catch {
|
|
13555
|
+
}
|
|
13556
|
+
liveSlot.handle = null;
|
|
13557
|
+
});
|
|
13286
13558
|
try {
|
|
13287
13559
|
const uploadLiveArtwork = async (info) => {
|
|
13288
13560
|
try {
|
|
@@ -13323,7 +13595,7 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
13323
13595
|
generated: true
|
|
13324
13596
|
});
|
|
13325
13597
|
}
|
|
13326
|
-
const snap =
|
|
13598
|
+
const snap = currentMediaDriver().snapshot;
|
|
13327
13599
|
if (palette) {
|
|
13328
13600
|
snap.primaryColor = media.mediaVec3(...palette.primary);
|
|
13329
13601
|
snap.secondaryColor = media.mediaVec3(...palette.secondary);
|
|
@@ -13348,39 +13620,48 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
13348
13620
|
void uploadLiveArtwork(info);
|
|
13349
13621
|
}
|
|
13350
13622
|
});
|
|
13351
|
-
|
|
13352
|
-
|
|
13353
|
-
|
|
13354
|
-
|
|
13355
|
-
|
|
13356
|
-
|
|
13357
|
-
|
|
13358
|
-
|
|
13359
|
-
|
|
13360
|
-
|
|
13623
|
+
if (liveSlot.dead) {
|
|
13624
|
+
try {
|
|
13625
|
+
live.dispose();
|
|
13626
|
+
} catch {
|
|
13627
|
+
}
|
|
13628
|
+
live = null;
|
|
13629
|
+
} else {
|
|
13630
|
+
liveSlot.handle = live;
|
|
13631
|
+
liveMediaOverride = live.media;
|
|
13632
|
+
windowDriver = live.windowTitle;
|
|
13633
|
+
liveHold.mediaDriver = live.media;
|
|
13634
|
+
if (live.status().audio === "mic") audioDriverRef.current = live.audio;
|
|
13635
|
+
if (currentMediaDriver().snapshot.hasMedia) {
|
|
13636
|
+
for (const { name, event } of media.diffMediaEvents(null, currentMediaDriver().snapshot)) {
|
|
13637
|
+
for (const sb of mediaHooks) {
|
|
13638
|
+
try {
|
|
13639
|
+
sb.callMedia(name, event);
|
|
13640
|
+
} catch {
|
|
13641
|
+
}
|
|
13361
13642
|
}
|
|
13362
13643
|
}
|
|
13644
|
+
lastMediaSnap = media.cloneMediaSnapshot(currentMediaDriver().snapshot);
|
|
13363
13645
|
}
|
|
13364
|
-
|
|
13646
|
+
const st = live.status();
|
|
13647
|
+
reportDiag(
|
|
13648
|
+
rt,
|
|
13649
|
+
cfg,
|
|
13650
|
+
`liveSystem: audio=${st.audio} media=${st.media} window=${st.window}` + (st.title ? ` title="${st.title}"` : "") + (st.hasArtwork ? " artwork=1" : "")
|
|
13651
|
+
);
|
|
13652
|
+
reportDiag(
|
|
13653
|
+
rt,
|
|
13654
|
+
cfg,
|
|
13655
|
+
`audio: ${audioDriverRef.current ? "live mic" : "simulated"} stream, supportsaudioprocessing=${supportsAudioProcessing}`
|
|
13656
|
+
);
|
|
13657
|
+
window.__system = {
|
|
13658
|
+
media: mediaControl,
|
|
13659
|
+
windowTitle: windowDriver.snapshot,
|
|
13660
|
+
shortcuts: shortcuts.last,
|
|
13661
|
+
live: () => live.status()
|
|
13662
|
+
};
|
|
13663
|
+
window.__liveSystem = () => live.status();
|
|
13365
13664
|
}
|
|
13366
|
-
const st = live.status();
|
|
13367
|
-
reportDiag(
|
|
13368
|
-
rt,
|
|
13369
|
-
cfg,
|
|
13370
|
-
`liveSystem: audio=${st.audio} media=${st.media} window=${st.window}` + (st.title ? ` title="${st.title}"` : "") + (st.hasArtwork ? " artwork=1" : "")
|
|
13371
|
-
);
|
|
13372
|
-
reportDiag(
|
|
13373
|
-
rt,
|
|
13374
|
-
cfg,
|
|
13375
|
-
`audio: ${audioDriverRef.current ? "live mic" : "simulated"} stream, supportsaudioprocessing=${supportsAudioProcessing}`
|
|
13376
|
-
);
|
|
13377
|
-
window.__system = {
|
|
13378
|
-
media: mediaControl,
|
|
13379
|
-
windowTitle: windowDriver.snapshot,
|
|
13380
|
-
shortcuts: shortcuts.last,
|
|
13381
|
-
live: () => live.status()
|
|
13382
|
-
};
|
|
13383
|
-
window.__liveSystem = () => live.status();
|
|
13384
13665
|
} catch (e) {
|
|
13385
13666
|
reportDiag(rt, cfg, `liveSystem: 启动失败,回退模拟源 (${e instanceof Error ? e.message : e})`);
|
|
13386
13667
|
live = null;
|
|
@@ -14712,17 +14993,15 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
14712
14993
|
if (now - lastRender >= interval) {
|
|
14713
14994
|
lastRender = now;
|
|
14714
14995
|
markFrame(rt, now);
|
|
14715
|
-
if (rt.onFirstFrame) {
|
|
14716
|
-
const first = rt.onFirstFrame;
|
|
14717
|
-
rt.onFirstFrame = void 0;
|
|
14718
|
-
first();
|
|
14719
|
-
}
|
|
14720
14996
|
syncCanvasSize(rt, c, rt.cfg);
|
|
14721
14997
|
const t = (now - start - pauseAccum) / 1e3;
|
|
14722
14998
|
inputView.update(pointerSrc.state);
|
|
14723
14999
|
if (mediaSim.enabled) {
|
|
14724
15000
|
if (live?.media) live.media.pump();
|
|
14725
|
-
else
|
|
15001
|
+
else {
|
|
15002
|
+
const drv = currentMediaDriver();
|
|
15003
|
+
if (typeof drv?.update === "function") drv.update(t);
|
|
15004
|
+
}
|
|
14726
15005
|
const snap = mediaSnapshot();
|
|
14727
15006
|
const evts = media.diffMediaEvents(lastMediaSnap, snap);
|
|
14728
15007
|
if (evts.length) {
|
|
@@ -14828,6 +15107,14 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
14828
15107
|
}
|
|
14829
15108
|
const peek = rt.coverAlign;
|
|
14830
15109
|
void renderer.render(scene, textures, c.width, c.height, t, normalizeFit(rt.cfg.fit), peek.x, peek.y).then(() => {
|
|
15110
|
+
if (rt.onFirstFrame) {
|
|
15111
|
+
const first = rt.onFirstFrame;
|
|
15112
|
+
rt.onFirstFrame = void 0;
|
|
15113
|
+
try {
|
|
15114
|
+
first();
|
|
15115
|
+
} catch {
|
|
15116
|
+
}
|
|
15117
|
+
}
|
|
14831
15118
|
if (disposed || rt.paused) return;
|
|
14832
15119
|
try {
|
|
14833
15120
|
dispatchCursor();
|
|
@@ -15148,6 +15435,44 @@ ${escapeScriptClose(opts.seedScript)}
|
|
|
15148
15435
|
}
|
|
15149
15436
|
};
|
|
15150
15437
|
}
|
|
15438
|
+
function bridgeAudioDriver(rt) {
|
|
15439
|
+
const left = new Float32Array(64);
|
|
15440
|
+
const right = new Float32Array(64);
|
|
15441
|
+
return {
|
|
15442
|
+
snapshot() {
|
|
15443
|
+
const src = rt.audioBridge?.();
|
|
15444
|
+
const sl = src?.left;
|
|
15445
|
+
const sr = src?.right;
|
|
15446
|
+
const n = sl && sr ? Math.min(64, sl.length, sr.length) : 0;
|
|
15447
|
+
for (let i = 0; i < n; i++) {
|
|
15448
|
+
left[i] = Math.max(0, Math.min(1, Number(sl[i]) || 0));
|
|
15449
|
+
right[i] = Math.max(0, Math.min(1, Number(sr[i]) || 0));
|
|
15450
|
+
}
|
|
15451
|
+
for (let i = n; i < 64; i++) {
|
|
15452
|
+
left[i] = 0;
|
|
15453
|
+
right[i] = 0;
|
|
15454
|
+
}
|
|
15455
|
+
return { left, right };
|
|
15456
|
+
}
|
|
15457
|
+
};
|
|
15458
|
+
}
|
|
15459
|
+
function liveAudioDriver(handle) {
|
|
15460
|
+
const left = new Float32Array(64);
|
|
15461
|
+
const right = new Float32Array(64);
|
|
15462
|
+
return {
|
|
15463
|
+
tick() {
|
|
15464
|
+
handle.audio.pump();
|
|
15465
|
+
},
|
|
15466
|
+
snapshot() {
|
|
15467
|
+
const s = handle.audio.snapshot;
|
|
15468
|
+
for (let i = 0; i < 64; i++) {
|
|
15469
|
+
left[i] = Math.max(0, Math.min(1, Number(s.left64[i]) || 0));
|
|
15470
|
+
right[i] = Math.max(0, Math.min(1, Number(s.right64[i]) || 0));
|
|
15471
|
+
}
|
|
15472
|
+
return { left, right };
|
|
15473
|
+
}
|
|
15474
|
+
};
|
|
15475
|
+
}
|
|
15151
15476
|
function resolveContainer(rt, cfg) {
|
|
15152
15477
|
if (rt.wrap) return rt.wrap;
|
|
15153
15478
|
const el = cfg.canvas;
|
|
@@ -15462,8 +15787,10 @@ ${escapeScriptClose(opts.seedScript)}
|
|
|
15462
15787
|
}
|
|
15463
15788
|
return media.cloneMediaSnapshot(snap);
|
|
15464
15789
|
}
|
|
15465
|
-
function startAudioPump(rt, driver, frameClock) {
|
|
15790
|
+
function startAudioPump(rt, driver, frameClock, liveHold = { driver: null }) {
|
|
15466
15791
|
if (!driver) return;
|
|
15792
|
+
const bridged = bridgeAudioDriver(rt);
|
|
15793
|
+
const pick = () => rt.audioBridge ? bridged : liveHold.driver ?? driver;
|
|
15467
15794
|
let raf = 0;
|
|
15468
15795
|
let lastPush = 0;
|
|
15469
15796
|
const tick = (now) => {
|
|
@@ -15475,8 +15802,9 @@ ${escapeScriptClose(opts.seedScript)}
|
|
|
15475
15802
|
if (now - lastPush < interval * 0.85) return;
|
|
15476
15803
|
lastPush = now;
|
|
15477
15804
|
try {
|
|
15478
|
-
|
|
15479
|
-
|
|
15805
|
+
const cur = pick();
|
|
15806
|
+
cur.tick?.(now);
|
|
15807
|
+
const snap = cur.snapshot();
|
|
15480
15808
|
const arr = packWebAudioArrayInto(pumpBuffer, snap.left, snap.right);
|
|
15481
15809
|
weShimCall(rt, (w) => w.__wePushAudio?.(arr));
|
|
15482
15810
|
if (frameClock && now - frameClock.last > 200) markFrame(rt, now);
|
|
@@ -15495,6 +15823,7 @@ ${escapeScriptClose(opts.seedScript)}
|
|
|
15495
15823
|
}
|
|
15496
15824
|
function startMediaPump(rt, driver) {
|
|
15497
15825
|
if (!driver) return;
|
|
15826
|
+
const pick = () => rt.mediaSource ?? driver;
|
|
15498
15827
|
let raf = 0;
|
|
15499
15828
|
let lastMedia = null;
|
|
15500
15829
|
let lastTick = 0;
|
|
@@ -15504,8 +15833,9 @@ ${escapeScriptClose(opts.seedScript)}
|
|
|
15504
15833
|
if (now - lastTick < 200) return;
|
|
15505
15834
|
lastTick = now;
|
|
15506
15835
|
try {
|
|
15507
|
-
|
|
15508
|
-
|
|
15836
|
+
const cur = pick();
|
|
15837
|
+
cur.update?.(now / 1e3);
|
|
15838
|
+
lastMedia = pushMediaDiff(rt, lastMedia, cur.snapshot);
|
|
15509
15839
|
} catch {
|
|
15510
15840
|
}
|
|
15511
15841
|
};
|
|
@@ -15600,18 +15930,65 @@ ${escapeScriptClose(opts.seedScript)}
|
|
|
15600
15930
|
}
|
|
15601
15931
|
installWebCtl(rt);
|
|
15602
15932
|
const cfgExt = cfg;
|
|
15603
|
-
const audioDriver = cfgExt._webAudio === null ? null : cfgExt._webAudio ?? defaultAudioDriver();
|
|
15604
|
-
const mediaDriver = cfgExt._webMedia === null ? null : cfgExt._webMedia ?? defaultMediaDriver();
|
|
15933
|
+
const audioDriver = cfgExt._webAudio === null || rt.audioDisabled ? null : cfgExt._webAudio ?? defaultAudioDriver();
|
|
15934
|
+
const mediaDriver = cfgExt._webMedia === null || rt.mediaDisabled ? null : cfgExt._webMedia ?? defaultMediaDriver();
|
|
15605
15935
|
const finishBare = (why) => {
|
|
15606
15936
|
reportDiag(rt, cfg, `网页壁纸 shim 注入失败(${why}),退回裸 iframe`);
|
|
15607
15937
|
attachIframe(rt, cfg, container, entry, { injected: false });
|
|
15608
15938
|
startAudioPump(rt, null);
|
|
15609
15939
|
startMediaPump(rt, null);
|
|
15940
|
+
let beat = 0;
|
|
15941
|
+
const tick = (now) => {
|
|
15942
|
+
if (!rt.iframe) return;
|
|
15943
|
+
beat = requestAnimationFrame(tick);
|
|
15944
|
+
if (rt.paused) return;
|
|
15945
|
+
markFrame(rt, now);
|
|
15946
|
+
};
|
|
15947
|
+
beat = requestAnimationFrame(tick);
|
|
15948
|
+
(rt.wallpaperDisposers ??= []).push(() => cancelAnimationFrame(beat));
|
|
15610
15949
|
};
|
|
15611
15950
|
const frameClock = { last: 0 };
|
|
15951
|
+
const liveHold = { driver: null };
|
|
15612
15952
|
const startPumps = () => {
|
|
15613
|
-
startAudioPump(rt, audioDriver, frameClock);
|
|
15953
|
+
startAudioPump(rt, audioDriver, frameClock, liveHold);
|
|
15614
15954
|
startMediaPump(rt, mediaDriver);
|
|
15955
|
+
if (cfg.liveSystem && audioDriver) {
|
|
15956
|
+
const liveSlot = {
|
|
15957
|
+
handle: null,
|
|
15958
|
+
dead: false
|
|
15959
|
+
};
|
|
15960
|
+
(rt.wallpaperDisposers ??= []).push(() => {
|
|
15961
|
+
liveSlot.dead = true;
|
|
15962
|
+
liveHold.driver = null;
|
|
15963
|
+
try {
|
|
15964
|
+
liveSlot.handle?.dispose();
|
|
15965
|
+
} catch {
|
|
15966
|
+
}
|
|
15967
|
+
liveSlot.handle = null;
|
|
15968
|
+
});
|
|
15969
|
+
void (async () => {
|
|
15970
|
+
try {
|
|
15971
|
+
const live = await startLiveSystem({ origin: location.origin });
|
|
15972
|
+
if (liveSlot.dead) {
|
|
15973
|
+
try {
|
|
15974
|
+
live.dispose();
|
|
15975
|
+
} catch {
|
|
15976
|
+
}
|
|
15977
|
+
return;
|
|
15978
|
+
}
|
|
15979
|
+
liveSlot.handle = live;
|
|
15980
|
+
const st = live.status();
|
|
15981
|
+
if (st.audio === "mic") {
|
|
15982
|
+
liveHold.driver = liveAudioDriver(live);
|
|
15983
|
+
reportDiag(rt, cfg, "liveSystem: 网页壁纸音频改用麦克风");
|
|
15984
|
+
} else {
|
|
15985
|
+
reportDiag(rt, cfg, `liveSystem: 麦克风不可用(${st.audio}),网页壁纸沿用模拟源`);
|
|
15986
|
+
}
|
|
15987
|
+
} catch (e) {
|
|
15988
|
+
reportDiag(rt, cfg, `liveSystem: 启动失败,网页壁纸沿用模拟源 (${e?.message ?? e})`);
|
|
15989
|
+
}
|
|
15990
|
+
})();
|
|
15991
|
+
}
|
|
15615
15992
|
};
|
|
15616
15993
|
void (async () => {
|
|
15617
15994
|
const defaults = await fetchProjectWire(entry);
|
|
@@ -15678,6 +16055,99 @@ ${escapeScriptClose(opts.seedScript)}
|
|
|
15678
16055
|
rt.onUnhandledType?.(cfg);
|
|
15679
16056
|
}
|
|
15680
16057
|
}
|
|
16058
|
+
class Color {
|
|
16059
|
+
x;
|
|
16060
|
+
y;
|
|
16061
|
+
z;
|
|
16062
|
+
constructor(x, y, z) {
|
|
16063
|
+
this.x = Number(x) || 0;
|
|
16064
|
+
this.y = Number(y) || 0;
|
|
16065
|
+
this.z = Number(z) || 0;
|
|
16066
|
+
}
|
|
16067
|
+
add(o) {
|
|
16068
|
+
return new Color(this.x + o.x, this.y + o.y, this.z + o.z);
|
|
16069
|
+
}
|
|
16070
|
+
subtract(o) {
|
|
16071
|
+
return new Color(this.x - o.x, this.y - o.y, this.z - o.z);
|
|
16072
|
+
}
|
|
16073
|
+
multiply(k) {
|
|
16074
|
+
if (typeof k === "number") return new Color(this.x * k, this.y * k, this.z * k);
|
|
16075
|
+
return new Color(this.x * k.x, this.y * k.y, this.z * k.z);
|
|
16076
|
+
}
|
|
16077
|
+
toString() {
|
|
16078
|
+
return `${this.x} ${this.y} ${this.z}`;
|
|
16079
|
+
}
|
|
16080
|
+
}
|
|
16081
|
+
function mediaColor(r, g, b) {
|
|
16082
|
+
if (r instanceof Color) return r;
|
|
16083
|
+
if (Array.isArray(r)) return new Color(r[0] ?? 0, r[1] ?? 0, r[2] ?? 0);
|
|
16084
|
+
if (typeof r === "object" && r !== null) {
|
|
16085
|
+
const o = r;
|
|
16086
|
+
return new Color(o.x ?? 0, o.y ?? 0, o.z ?? 0);
|
|
16087
|
+
}
|
|
16088
|
+
return new Color(r, g ?? 0, b ?? 0);
|
|
16089
|
+
}
|
|
16090
|
+
const DEF_PRIMARY = [0.35, 0.38, 0.45];
|
|
16091
|
+
const DEF_SECONDARY = [0.12, 0.13, 0.17];
|
|
16092
|
+
const DEF_TERTIARY = [0.72, 0.76, 0.84];
|
|
16093
|
+
const DEF_TEXT = [0.95, 0.96, 0.98];
|
|
16094
|
+
function locateLyric(lyrics, position) {
|
|
16095
|
+
if (!Array.isArray(lyrics) || lyrics.length === 0) return { line: "", index: -1 };
|
|
16096
|
+
let idx = -1;
|
|
16097
|
+
for (let i = 0; i < lyrics.length; i++) {
|
|
16098
|
+
const at = Number(lyrics[i]?.[0]);
|
|
16099
|
+
if (Number.isFinite(at) && at <= position) idx = i;
|
|
16100
|
+
else break;
|
|
16101
|
+
}
|
|
16102
|
+
return { line: idx >= 0 ? String(lyrics[idx][1] ?? "") : "", index: idx };
|
|
16103
|
+
}
|
|
16104
|
+
function buildSnapshot(init) {
|
|
16105
|
+
const position = Number(init.position) || 0;
|
|
16106
|
+
const lyrics = Array.isArray(init.lyrics) ? init.lyrics : [];
|
|
16107
|
+
const { line, index } = locateLyric(lyrics, position);
|
|
16108
|
+
const state = init.state !== void 0 ? init.state : init.playing === false ? 2 : init.playing ? 1 : 0;
|
|
16109
|
+
return {
|
|
16110
|
+
hasMedia: init.hasMedia ?? (init.title != null || init.artist != null || !!init.playing),
|
|
16111
|
+
state,
|
|
16112
|
+
title: String(init.title ?? ""),
|
|
16113
|
+
artist: String(init.artist ?? ""),
|
|
16114
|
+
album: String(init.album ?? ""),
|
|
16115
|
+
albumArtist: String(init.albumArtist ?? init.artist ?? ""),
|
|
16116
|
+
position,
|
|
16117
|
+
duration: Number(init.duration) || 0,
|
|
16118
|
+
hasThumbnail: init.hasThumbnail ?? false,
|
|
16119
|
+
primaryColor: mediaColor(init.primaryColor ?? DEF_PRIMARY),
|
|
16120
|
+
secondaryColor: mediaColor(init.secondaryColor ?? DEF_SECONDARY),
|
|
16121
|
+
tertiaryColor: mediaColor(init.tertiaryColor ?? DEF_TERTIARY),
|
|
16122
|
+
textColor: mediaColor(init.textColor ?? DEF_TEXT),
|
|
16123
|
+
highContrastColor: mediaColor(init.highContrastColor ?? init.textColor ?? DEF_TEXT),
|
|
16124
|
+
trackIndex: Number(init.trackIndex) || 0,
|
|
16125
|
+
lyrics,
|
|
16126
|
+
lyricLine: line,
|
|
16127
|
+
lyricIndex: index
|
|
16128
|
+
};
|
|
16129
|
+
}
|
|
16130
|
+
function createMediaSource(init = {}, controls = {}) {
|
|
16131
|
+
let cur = { ...init };
|
|
16132
|
+
let snap = buildSnapshot(cur);
|
|
16133
|
+
return {
|
|
16134
|
+
get snapshot() {
|
|
16135
|
+
return snap;
|
|
16136
|
+
},
|
|
16137
|
+
set(patch) {
|
|
16138
|
+
cur = { ...cur, ...patch };
|
|
16139
|
+
snap = buildSnapshot(cur);
|
|
16140
|
+
},
|
|
16141
|
+
// 宿主的快照由外部事件驱动,不需要按帧自行推进;留空实现满足接口即可
|
|
16142
|
+
update() {
|
|
16143
|
+
},
|
|
16144
|
+
skipNext: controls.skipNext,
|
|
16145
|
+
skipPrevious: controls.skipPrevious,
|
|
16146
|
+
play: controls.play,
|
|
16147
|
+
pause: controls.pause,
|
|
16148
|
+
playPause: controls.playPause
|
|
16149
|
+
};
|
|
16150
|
+
}
|
|
15681
16151
|
function normalizeFitOption(fit) {
|
|
15682
16152
|
if (fit === "fit") return "contain";
|
|
15683
16153
|
if (fit === "fill") return "cover";
|
|
@@ -15687,6 +16157,13 @@ ${escapeScriptClose(opts.seedScript)}
|
|
|
15687
16157
|
const t = project?.type;
|
|
15688
16158
|
return typeof t === "string" && t.toLowerCase() === "web";
|
|
15689
16159
|
}
|
|
16160
|
+
const MEDIA_TYPES = /* @__PURE__ */ new Set(["video", "gif", "image"]);
|
|
16161
|
+
function mediaProjectType(project) {
|
|
16162
|
+
const t = project?.type;
|
|
16163
|
+
if (typeof t !== "string") return null;
|
|
16164
|
+
const lower = t.toLowerCase();
|
|
16165
|
+
return MEDIA_TYPES.has(lower) ? lower : null;
|
|
16166
|
+
}
|
|
15690
16167
|
function ensureSceneCanvas(el) {
|
|
15691
16168
|
if (el instanceof HTMLCanvasElement) return el;
|
|
15692
16169
|
const existing = el.querySelector(":scope > canvas[data-webwallgl]");
|
|
@@ -15729,6 +16206,47 @@ ${escapeScriptClose(opts.seedScript)}
|
|
|
15729
16206
|
if (!url) throw new Error("网页壁纸:无法解析入口 URL(需要 Source.webEntry 或 httpSource)");
|
|
15730
16207
|
return { ...base, type: "web", src: url, source: o.source };
|
|
15731
16208
|
}
|
|
16209
|
+
const mediaType = mediaProjectType(project);
|
|
16210
|
+
if (mediaType) {
|
|
16211
|
+
let url;
|
|
16212
|
+
let entryType;
|
|
16213
|
+
try {
|
|
16214
|
+
const entry = await o.source.mediaEntry?.();
|
|
16215
|
+
url = entry?.url;
|
|
16216
|
+
entryType = entry?.type;
|
|
16217
|
+
} catch {
|
|
16218
|
+
url = void 0;
|
|
16219
|
+
}
|
|
16220
|
+
if (!url && o.source.key) {
|
|
16221
|
+
const file = project && typeof project.file === "string" ? String(project.file).trim().replace(/^\/+/, "") : "";
|
|
16222
|
+
if (file) url = `${o.source.key.replace(/\/+$/, "")}/${file}`;
|
|
16223
|
+
}
|
|
16224
|
+
if (!url) {
|
|
16225
|
+
throw new Error(
|
|
16226
|
+
`媒体壁纸(${mediaType}):无法解析资源 URL(需要 Source.mediaEntry 或 project.file + httpSource)`
|
|
16227
|
+
);
|
|
16228
|
+
}
|
|
16229
|
+
const canvas2 = ensureSceneCanvas(el);
|
|
16230
|
+
const finalType = MEDIA_TYPES.has(String(entryType).toLowerCase()) ? String(entryType).toLowerCase() : mediaType;
|
|
16231
|
+
return { ...base, type: finalType, src: url, canvas: canvas2, source: o.source };
|
|
16232
|
+
}
|
|
16233
|
+
const declaredType = typeof project?.type === "string" ? String(project.type).trim() : "";
|
|
16234
|
+
if (!declaredType && typeof o.source.mediaEntry === "function") {
|
|
16235
|
+
let url;
|
|
16236
|
+
let entryType;
|
|
16237
|
+
try {
|
|
16238
|
+
const entry = await o.source.mediaEntry();
|
|
16239
|
+
url = entry?.url;
|
|
16240
|
+
entryType = entry?.type;
|
|
16241
|
+
} catch {
|
|
16242
|
+
url = void 0;
|
|
16243
|
+
}
|
|
16244
|
+
const sniffed = (MEDIA_TYPES.has(String(entryType).toLowerCase()) ? String(entryType).toLowerCase() : null) ?? (url ? sniffMediaType(url) : null);
|
|
16245
|
+
if (url && sniffed) {
|
|
16246
|
+
const canvas2 = ensureSceneCanvas(el);
|
|
16247
|
+
return { ...base, type: sniffed, src: url, canvas: canvas2, source: o.source };
|
|
16248
|
+
}
|
|
16249
|
+
}
|
|
15732
16250
|
const canvas = ensureSceneCanvas(el);
|
|
15733
16251
|
return { ...base, type: "scene", canvas, source: o.source };
|
|
15734
16252
|
}
|
|
@@ -15768,6 +16286,26 @@ ${escapeScriptClose(opts.seedScript)}
|
|
|
15768
16286
|
rt.onSceneInfo = (info) => {
|
|
15769
16287
|
rt.info = info;
|
|
15770
16288
|
};
|
|
16289
|
+
if ("audio" in o) applyAudio(o.audio ?? null);
|
|
16290
|
+
if ("media" in o) {
|
|
16291
|
+
rt.mediaSource = o.media ?? null;
|
|
16292
|
+
rt.mediaDisabled = o.media === null;
|
|
16293
|
+
}
|
|
16294
|
+
};
|
|
16295
|
+
const applyAudio = (src) => {
|
|
16296
|
+
rt.audioDisabled = src === null;
|
|
16297
|
+
if (!src) {
|
|
16298
|
+
rt.audioBridge = null;
|
|
16299
|
+
return;
|
|
16300
|
+
}
|
|
16301
|
+
rt.audioBridge = () => {
|
|
16302
|
+
try {
|
|
16303
|
+
const s = src.snapshot();
|
|
16304
|
+
return s && s.left && s.right ? s : null;
|
|
16305
|
+
} catch {
|
|
16306
|
+
return null;
|
|
16307
|
+
}
|
|
16308
|
+
};
|
|
15771
16309
|
};
|
|
15772
16310
|
const armFirstFrame = () => {
|
|
15773
16311
|
return new Promise((resolve) => {
|
|
@@ -15824,6 +16362,7 @@ ${escapeScriptClose(opts.seedScript)}
|
|
|
15824
16362
|
setFit(fit) {
|
|
15825
16363
|
rt.cfg.fit = fit;
|
|
15826
16364
|
resetCoverAlign(rt);
|
|
16365
|
+
rt.webRelayout?.();
|
|
15827
16366
|
},
|
|
15828
16367
|
setFps(fps) {
|
|
15829
16368
|
rt.cfg.sceneFps = fps;
|
|
@@ -15847,11 +16386,81 @@ ${escapeScriptClose(opts.seedScript)}
|
|
|
15847
16386
|
getProperties() {
|
|
15848
16387
|
return { ...rt.liveUserProps ?? {} };
|
|
15849
16388
|
},
|
|
16389
|
+
// 宿主频谱源。只存引用,实际每帧拉取在 scene-mount 的渲染循环里。
|
|
16390
|
+
// 与 __wp.setAudioBridge 同纪律:换场景不清空,装一次对之后所有场景生效
|
|
16391
|
+
// (wireOptions 也只在选项里出现 audio 时才覆盖,见那里的说明)。
|
|
16392
|
+
setAudio(src) {
|
|
16393
|
+
currentOptions = { ...currentOptions, audio: src };
|
|
16394
|
+
applyAudio(src);
|
|
16395
|
+
rt.audioDisabled = false;
|
|
16396
|
+
},
|
|
16397
|
+
// 系统媒体源。与 setAudio 同纪律:只存引用、换场景不清空,
|
|
16398
|
+
// scene 与 web 两条装配路径读同一个 rt.mediaSource。
|
|
16399
|
+
// 注意语义与 MountOptions.media:null 不同:这里的 null 按文档是
|
|
16400
|
+
// 「回落内置模拟源」,不是禁用(禁用只在挂载选项里表达)。
|
|
16401
|
+
setMedia(src) {
|
|
16402
|
+
currentOptions = { ...currentOptions, media: src };
|
|
16403
|
+
rt.mediaSource = src ?? null;
|
|
16404
|
+
rt.mediaDisabled = false;
|
|
16405
|
+
},
|
|
16406
|
+
// 媒体控制面。装配后由 mountScene 写入 rt.mediaCtl;未装配(或媒体/网页
|
|
16407
|
+
// 壁纸尚无控制面)时给一个惰性替身,读快照得空、控制方法静默无效 ——
|
|
16408
|
+
// 让调用方能无条件 `wp.media.playPause()` 而不必先判空。
|
|
16409
|
+
get media() {
|
|
16410
|
+
const ctl = rt.mediaCtl;
|
|
16411
|
+
if (ctl) return ctl;
|
|
16412
|
+
const empty = {
|
|
16413
|
+
hasMedia: false,
|
|
16414
|
+
state: 0,
|
|
16415
|
+
title: "",
|
|
16416
|
+
artist: "",
|
|
16417
|
+
album: "",
|
|
16418
|
+
albumArtist: "",
|
|
16419
|
+
position: 0,
|
|
16420
|
+
duration: 0,
|
|
16421
|
+
hasThumbnail: false,
|
|
16422
|
+
primaryColor: mediaColor(0, 0, 0),
|
|
16423
|
+
secondaryColor: mediaColor(0, 0, 0),
|
|
16424
|
+
tertiaryColor: mediaColor(0, 0, 0),
|
|
16425
|
+
textColor: mediaColor(1, 1, 1),
|
|
16426
|
+
highContrastColor: mediaColor(1, 1, 1),
|
|
16427
|
+
trackIndex: 0,
|
|
16428
|
+
lyrics: [],
|
|
16429
|
+
lyricLine: "",
|
|
16430
|
+
lyricIndex: -1
|
|
16431
|
+
};
|
|
16432
|
+
const noop = () => empty;
|
|
16433
|
+
return {
|
|
16434
|
+
get snapshot() {
|
|
16435
|
+
return empty;
|
|
16436
|
+
},
|
|
16437
|
+
skipNext: noop,
|
|
16438
|
+
skipPrevious: noop,
|
|
16439
|
+
play: noop,
|
|
16440
|
+
pause: noop,
|
|
16441
|
+
playPause: noop
|
|
16442
|
+
};
|
|
16443
|
+
},
|
|
16444
|
+
// 外部指针注入。pointerCtl 由 mountScene / mountWeb 各自装配时设置,
|
|
16445
|
+
// 媒体壁纸不设 —— 那时这里静默无效,与整页渲染器的 __wp.pushPointer 一致。
|
|
16446
|
+
pushPointer(u, v, buttons) {
|
|
16447
|
+
rt.pointerCtl?.push({ u, v, buttons });
|
|
16448
|
+
},
|
|
16449
|
+
pointerLeave() {
|
|
16450
|
+
rt.pointerCtl?.leave();
|
|
16451
|
+
},
|
|
15850
16452
|
async load(source) {
|
|
16453
|
+
const prev = currentOptions.source;
|
|
16454
|
+
if (prev && prev !== source) {
|
|
16455
|
+
try {
|
|
16456
|
+
prev.dispose?.();
|
|
16457
|
+
} catch {
|
|
16458
|
+
}
|
|
16459
|
+
}
|
|
15851
16460
|
currentOptions = { ...currentOptions, source };
|
|
15852
16461
|
wireOptions(currentOptions);
|
|
15853
16462
|
const cfg = await resolveMountConfig(boundEl, currentOptions);
|
|
15854
|
-
if (cfg.type
|
|
16463
|
+
if (cfg.type !== "web" && cfg.canvas instanceof HTMLCanvasElement) {
|
|
15855
16464
|
boundEl = cfg.canvas;
|
|
15856
16465
|
}
|
|
15857
16466
|
rt.cfg = cfg;
|
|
@@ -15878,6 +16487,10 @@ ${escapeScriptClose(opts.seedScript)}
|
|
|
15878
16487
|
},
|
|
15879
16488
|
destroy() {
|
|
15880
16489
|
destroyRuntime(rt);
|
|
16490
|
+
try {
|
|
16491
|
+
currentOptions.source?.dispose?.();
|
|
16492
|
+
} catch {
|
|
16493
|
+
}
|
|
15881
16494
|
rt.onDiagnostic = void 0;
|
|
15882
16495
|
rt.onError = void 0;
|
|
15883
16496
|
rt.onFirstFrame = void 0;
|
|
@@ -15905,11 +16518,11 @@ ${escapeScriptClose(opts.seedScript)}
|
|
|
15905
16518
|
currentOptions = o;
|
|
15906
16519
|
wireOptions(o);
|
|
15907
16520
|
const cfg = await resolveMountConfig(el, o);
|
|
15908
|
-
if (cfg.type
|
|
16521
|
+
if (cfg.type !== "web" && cfg.canvas instanceof HTMLCanvasElement) {
|
|
15909
16522
|
boundEl = cfg.canvas;
|
|
15910
16523
|
}
|
|
15911
16524
|
rt.cfg = cfg;
|
|
15912
|
-
rt.paused =
|
|
16525
|
+
rt.paused = false;
|
|
15913
16526
|
rt.info = void 0;
|
|
15914
16527
|
resetCoverAlign(rt);
|
|
15915
16528
|
if (o.properties && Object.keys(o.properties).length) {
|
|
@@ -15923,6 +16536,7 @@ ${escapeScriptClose(opts.seedScript)}
|
|
|
15923
16536
|
} finally {
|
|
15924
16537
|
failure.off();
|
|
15925
16538
|
}
|
|
16539
|
+
if (o.autoplay === false) instance.pause();
|
|
15926
16540
|
if ((o.volume ?? 0) > 0) instance.setVolume(o.volume);
|
|
15927
16541
|
if (o.properties && Object.keys(o.properties).length) instance.setProperties(o.properties);
|
|
15928
16542
|
};
|
|
@@ -15936,10 +16550,14 @@ ${escapeScriptClose(opts.seedScript)}
|
|
|
15936
16550
|
return instance;
|
|
15937
16551
|
}
|
|
15938
16552
|
exports2.bytesSource = bytesSource;
|
|
16553
|
+
exports2.createMediaSource = createMediaSource;
|
|
15939
16554
|
exports2.createScene = createScene;
|
|
15940
16555
|
exports2.fileSource = fileSource;
|
|
15941
16556
|
exports2.httpSource = httpSource;
|
|
16557
|
+
exports2.mediaColor = mediaColor;
|
|
16558
|
+
exports2.mediaSource = mediaSource;
|
|
15942
16559
|
exports2.mount = mount;
|
|
16560
|
+
exports2.sniffMediaType = sniffMediaType;
|
|
15943
16561
|
Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
|
|
15944
16562
|
});
|
|
15945
16563
|
//# sourceMappingURL=webwallgl.global.js.map
|