@applemusic-like-lyrics/core 0.4.1 → 0.5.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.
- package/LICENSE +661 -0
- package/README.md +2 -2
- package/dist/amll-core.cjs +803 -1480
- package/dist/amll-core.cjs.map +1 -1
- package/dist/amll-core.d.cts +196 -246
- package/dist/amll-core.d.mts +196 -246
- package/dist/amll-core.mjs +803 -1480
- package/dist/amll-core.mjs.map +1 -1
- package/dist/style.css +5 -165
- package/package.json +87 -69
package/dist/amll-core.cjs
CHANGED
|
@@ -35,6 +35,9 @@ let bezier_easing = require("bezier-easing");
|
|
|
35
35
|
bezier_easing = __toESM(bezier_easing, 1);
|
|
36
36
|
//#region src/bg-render/base.ts
|
|
37
37
|
var AbstractBaseRenderer = class {};
|
|
38
|
+
function clamp1(x) {
|
|
39
|
+
return Math.max(1, x);
|
|
40
|
+
}
|
|
38
41
|
var BaseRenderer = class extends AbstractBaseRenderer {
|
|
39
42
|
observer;
|
|
40
43
|
flowSpeed = 1;
|
|
@@ -43,8 +46,8 @@ var BaseRenderer = class extends AbstractBaseRenderer {
|
|
|
43
46
|
super();
|
|
44
47
|
this.canvas = canvas;
|
|
45
48
|
this.observer = new ResizeObserver(() => {
|
|
46
|
-
const width =
|
|
47
|
-
const height =
|
|
49
|
+
const width = clamp1(canvas.clientWidth * window.devicePixelRatio * this.currerntRenderScale);
|
|
50
|
+
const height = clamp1(canvas.clientHeight * window.devicePixelRatio * this.currerntRenderScale);
|
|
48
51
|
this.onResize(width, height);
|
|
49
52
|
});
|
|
50
53
|
this.observer.observe(canvas);
|
|
@@ -232,6 +235,17 @@ function blurImage(imageData, radius, quality) {
|
|
|
232
235
|
}
|
|
233
236
|
}
|
|
234
237
|
//#endregion
|
|
238
|
+
//#region src/utils/clamp.ts
|
|
239
|
+
function clamp(x, min, max) {
|
|
240
|
+
return Math.min(Math.max(x, min), max);
|
|
241
|
+
}
|
|
242
|
+
function clamp01(x) {
|
|
243
|
+
return clamp(x, 0, 1);
|
|
244
|
+
}
|
|
245
|
+
function clampPositive(x) {
|
|
246
|
+
return Math.max(0, x);
|
|
247
|
+
}
|
|
248
|
+
//#endregion
|
|
235
249
|
//#region src/bg-render/mesh-renderer/cp-presets.ts
|
|
236
250
|
/** @internal */
|
|
237
251
|
const p = (cx, cy, x, y, ur = 0, vr = 0, up = 1, vp = 1) => Object.freeze({
|
|
@@ -404,11 +418,8 @@ const CONTROL_POINT_PRESETS = [
|
|
|
404
418
|
* 目的是取代原先大量的预设控制点代码
|
|
405
419
|
*/
|
|
406
420
|
const randomRange = (min, max) => Math.random() * (max - min) + min;
|
|
407
|
-
function clamp$1(x, min, max) {
|
|
408
|
-
return Math.min(Math.max(x, min), max);
|
|
409
|
-
}
|
|
410
421
|
function smoothstep(edge0, edge1, x) {
|
|
411
|
-
const t =
|
|
422
|
+
const t = clamp01((x - edge0) / (edge1 - edge0));
|
|
412
423
|
return t * t * (3 - 2 * t);
|
|
413
424
|
}
|
|
414
425
|
function smoothifyControlPoints(conf, w, h, iterations = 2, factor = .5, factorIterationModifier = .1) {
|
|
@@ -478,7 +489,7 @@ function smoothifyControlPoints(conf, w, h, iterations = 2, factor = .5, factorI
|
|
|
478
489
|
}
|
|
479
490
|
}
|
|
480
491
|
grid = newGrid;
|
|
481
|
-
f =
|
|
492
|
+
f = clamp01(f + factorIterationModifier);
|
|
482
493
|
}
|
|
483
494
|
for (let j = 0; j < h; j++) for (let i = 0; i < w; i++) conf[j * w + i] = grid[j][i];
|
|
484
495
|
}
|
|
@@ -1267,7 +1278,7 @@ var MeshGradientRenderer = class extends BaseRenderer {
|
|
|
1267
1278
|
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
1268
1279
|
this.quadProgram.use();
|
|
1269
1280
|
this.quadProgram.setUniform1i("u_texture", 0);
|
|
1270
|
-
this.quadProgram.setUniform1f("u_alpha", easeInOutSine(
|
|
1281
|
+
this.quadProgram.setUniform1f("u_alpha", easeInOutSine(clamp01(state.alpha)));
|
|
1271
1282
|
gl.activeTexture(gl.TEXTURE0);
|
|
1272
1283
|
gl.bindTexture(gl.TEXTURE_2D, this.fboTexture);
|
|
1273
1284
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadBuffer);
|
|
@@ -1502,7 +1513,7 @@ var PixiRenderer = class extends BaseRenderer {
|
|
|
1502
1513
|
lastContainer = /* @__PURE__ */ new Set();
|
|
1503
1514
|
onTick = (delta) => {
|
|
1504
1515
|
for (const lastContainer of this.lastContainer) {
|
|
1505
|
-
lastContainer.alpha =
|
|
1516
|
+
lastContainer.alpha = clampPositive(lastContainer.alpha - delta / 60);
|
|
1506
1517
|
if (lastContainer.alpha <= 0) {
|
|
1507
1518
|
this.app.stage.removeChild(lastContainer);
|
|
1508
1519
|
this.lastContainer.delete(lastContainer);
|
|
@@ -1741,14 +1752,6 @@ var lyric_player_module_default = {
|
|
|
1741
1752
|
"wordWithRuby": "FmKaba_wordWithRuby"
|
|
1742
1753
|
};
|
|
1743
1754
|
//#endregion
|
|
1744
|
-
//#region src/utils/eq-set.ts
|
|
1745
|
-
const eqSet = (xs, ys) => xs.size === ys.size && [...xs].every((x) => ys.has(x));
|
|
1746
|
-
//#endregion
|
|
1747
|
-
//#region src/utils/is-cjk.ts
|
|
1748
|
-
const isCJK = (char) => {
|
|
1749
|
-
return /^[\p{Unified_Ideograph}\u0800-\u9FFC]+$/u.test(char);
|
|
1750
|
-
};
|
|
1751
|
-
//#endregion
|
|
1752
1755
|
//#region src/utils/optimize-lyric.ts
|
|
1753
1756
|
const DEFAULT_OPTIMIZE_OPTIONS = {
|
|
1754
1757
|
normalizeSpaces: true,
|
|
@@ -1892,6 +1895,145 @@ function optimizeLyricLines(lines, options) {
|
|
|
1892
1895
|
if (config.tryAdvanceStartTime) tryAdvanceStartTime(lines);
|
|
1893
1896
|
}
|
|
1894
1897
|
//#endregion
|
|
1898
|
+
//#region src/lyric-player/dom/interlude-dots.ts
|
|
1899
|
+
function easeInOutBack(x) {
|
|
1900
|
+
const c2 = 1.70158 * 1.525;
|
|
1901
|
+
return x < .5 ? (2 * x) ** 2 * ((c2 + 1) * 2 * x - c2) / 2 : ((2 * x - 2) ** 2 * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2;
|
|
1902
|
+
}
|
|
1903
|
+
function easeOutExpo(x) {
|
|
1904
|
+
return x === 1 ? 1 : 1 - 2 ** (-10 * x);
|
|
1905
|
+
}
|
|
1906
|
+
var InterludeDots = class {
|
|
1907
|
+
element = document.createElement("div");
|
|
1908
|
+
dot0 = document.createElement("span");
|
|
1909
|
+
dot1 = document.createElement("span");
|
|
1910
|
+
dot2 = document.createElement("span");
|
|
1911
|
+
left = 0;
|
|
1912
|
+
top = 0;
|
|
1913
|
+
playing = true;
|
|
1914
|
+
lastStyle = "";
|
|
1915
|
+
currentInterlude;
|
|
1916
|
+
currentTime = 0;
|
|
1917
|
+
targetBreatheDuration = 1500;
|
|
1918
|
+
constructor() {
|
|
1919
|
+
this.element.className = lyric_player_module_default.interludeDots;
|
|
1920
|
+
this.element.appendChild(this.dot0);
|
|
1921
|
+
this.element.appendChild(this.dot1);
|
|
1922
|
+
this.element.appendChild(this.dot2);
|
|
1923
|
+
}
|
|
1924
|
+
getElement() {
|
|
1925
|
+
return this.element;
|
|
1926
|
+
}
|
|
1927
|
+
setTransform(left = this.left, top = this.top) {
|
|
1928
|
+
this.left = left;
|
|
1929
|
+
this.top = top;
|
|
1930
|
+
this.update();
|
|
1931
|
+
}
|
|
1932
|
+
setInterlude(interlude) {
|
|
1933
|
+
this.currentInterlude = interlude;
|
|
1934
|
+
this.currentTime = interlude?.[0] ?? 0;
|
|
1935
|
+
if (interlude) this.element.classList.add(lyric_player_module_default.enabled);
|
|
1936
|
+
else this.element.classList.remove(lyric_player_module_default.enabled);
|
|
1937
|
+
}
|
|
1938
|
+
pause() {
|
|
1939
|
+
this.playing = false;
|
|
1940
|
+
this.element.classList.remove(lyric_player_module_default.playing);
|
|
1941
|
+
}
|
|
1942
|
+
resume() {
|
|
1943
|
+
this.playing = true;
|
|
1944
|
+
this.element.classList.add(lyric_player_module_default.playing);
|
|
1945
|
+
}
|
|
1946
|
+
update(delta = 0) {
|
|
1947
|
+
if (!this.playing) return;
|
|
1948
|
+
this.currentTime += delta;
|
|
1949
|
+
let curStyle = "";
|
|
1950
|
+
curStyle += `transform:translate(${this.left.toFixed(2)}px, ${this.top.toFixed(2)}px)`;
|
|
1951
|
+
if (this.currentInterlude) {
|
|
1952
|
+
const interludeDuration = this.currentInterlude[1] - this.currentInterlude[0];
|
|
1953
|
+
const currentDuration = this.currentTime - this.currentInterlude[0];
|
|
1954
|
+
if (currentDuration <= interludeDuration) {
|
|
1955
|
+
const breatheDuration = interludeDuration / Math.ceil(interludeDuration / this.targetBreatheDuration);
|
|
1956
|
+
let scale = 1;
|
|
1957
|
+
let globalOpacity = 1;
|
|
1958
|
+
scale *= Math.sin(1.5 * Math.PI - currentDuration / breatheDuration * 2) / 20 + 1;
|
|
1959
|
+
if (currentDuration < 2e3) scale *= easeOutExpo(currentDuration / 2e3);
|
|
1960
|
+
if (currentDuration < 500) globalOpacity = 0;
|
|
1961
|
+
else if (currentDuration < 1e3) globalOpacity *= (currentDuration - 500) / 500;
|
|
1962
|
+
if (interludeDuration - currentDuration < 750) scale *= 1 - easeInOutBack((750 - (interludeDuration - currentDuration)) / 750 / 2);
|
|
1963
|
+
if (interludeDuration - currentDuration < 375) globalOpacity *= clamp01((interludeDuration - currentDuration) / 375);
|
|
1964
|
+
const dotsDuration = clampPositive(interludeDuration - 750);
|
|
1965
|
+
scale = clampPositive(scale) * .7;
|
|
1966
|
+
curStyle += ` scale(${scale})`;
|
|
1967
|
+
const dot0Opacity = clamp(.25, currentDuration * 3 / dotsDuration * .75, 1);
|
|
1968
|
+
const dot1Opacity = clamp(.25, (currentDuration - dotsDuration / 3) * 3 / dotsDuration * .75, 1);
|
|
1969
|
+
const dot2Opacity = clamp(.25, (currentDuration - dotsDuration / 3 * 2) * 3 / dotsDuration * .75, 1);
|
|
1970
|
+
this.dot0.style.opacity = `${clamp01(globalOpacity * dot0Opacity)}`;
|
|
1971
|
+
this.dot1.style.opacity = `${clamp01(globalOpacity * dot1Opacity)}`;
|
|
1972
|
+
this.dot2.style.opacity = `${clamp01(globalOpacity * dot2Opacity)}`;
|
|
1973
|
+
} else {
|
|
1974
|
+
curStyle += " scale(0)";
|
|
1975
|
+
this.dot0.style.opacity = "0";
|
|
1976
|
+
this.dot1.style.opacity = "0";
|
|
1977
|
+
this.dot2.style.opacity = "0";
|
|
1978
|
+
}
|
|
1979
|
+
curStyle += ";";
|
|
1980
|
+
if (this.lastStyle !== curStyle) {
|
|
1981
|
+
this.element.setAttribute("style", curStyle);
|
|
1982
|
+
this.lastStyle = curStyle;
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
dispose() {
|
|
1987
|
+
this.element.remove();
|
|
1988
|
+
}
|
|
1989
|
+
};
|
|
1990
|
+
//#endregion
|
|
1991
|
+
//#region src/utils/schedule.ts
|
|
1992
|
+
const measureTasks = [];
|
|
1993
|
+
const mutateTasks = [];
|
|
1994
|
+
let scheduled = false;
|
|
1995
|
+
function onFlush() {
|
|
1996
|
+
let tmp = mutateTasks.shift();
|
|
1997
|
+
while (tmp) {
|
|
1998
|
+
try {
|
|
1999
|
+
tmp.resolve(tmp.task());
|
|
2000
|
+
} catch (error) {
|
|
2001
|
+
tmp.reject(error);
|
|
2002
|
+
}
|
|
2003
|
+
tmp = mutateTasks.shift();
|
|
2004
|
+
}
|
|
2005
|
+
tmp = measureTasks.shift();
|
|
2006
|
+
while (tmp) {
|
|
2007
|
+
try {
|
|
2008
|
+
tmp.resolve(tmp.task());
|
|
2009
|
+
} catch (error) {
|
|
2010
|
+
tmp.reject(error);
|
|
2011
|
+
}
|
|
2012
|
+
tmp = measureTasks.shift();
|
|
2013
|
+
}
|
|
2014
|
+
scheduled = false;
|
|
2015
|
+
}
|
|
2016
|
+
function scheduleFlush() {
|
|
2017
|
+
if (!scheduled) {
|
|
2018
|
+
scheduled = true;
|
|
2019
|
+
requestAnimationFrame(onFlush);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
function measure(callback) {
|
|
2023
|
+
const task = {
|
|
2024
|
+
task: callback,
|
|
2025
|
+
resolve: () => {},
|
|
2026
|
+
reject: () => {}
|
|
2027
|
+
};
|
|
2028
|
+
const promise = new Promise((resolve, reject) => {
|
|
2029
|
+
task.resolve = resolve;
|
|
2030
|
+
task.reject = reject;
|
|
2031
|
+
});
|
|
2032
|
+
measureTasks.push(task);
|
|
2033
|
+
scheduleFlush();
|
|
2034
|
+
return promise;
|
|
2035
|
+
}
|
|
2036
|
+
//#endregion
|
|
1895
2037
|
//#region src/utils/derivative.ts
|
|
1896
2038
|
function derivative(f) {
|
|
1897
2039
|
const h = .001;
|
|
@@ -2006,67 +2148,7 @@ function solveSpring(from, velocity, to, delay = 0, params) {
|
|
|
2006
2148
|
};
|
|
2007
2149
|
}
|
|
2008
2150
|
//#endregion
|
|
2009
|
-
//#region src/
|
|
2010
|
-
const measureTasks = [];
|
|
2011
|
-
const mutateTasks = [];
|
|
2012
|
-
let scheduled = false;
|
|
2013
|
-
function onFlush() {
|
|
2014
|
-
let tmp = mutateTasks.shift();
|
|
2015
|
-
while (tmp) {
|
|
2016
|
-
try {
|
|
2017
|
-
tmp.resolve(tmp.task());
|
|
2018
|
-
} catch (error) {
|
|
2019
|
-
tmp.reject(error);
|
|
2020
|
-
}
|
|
2021
|
-
tmp = mutateTasks.shift();
|
|
2022
|
-
}
|
|
2023
|
-
tmp = measureTasks.shift();
|
|
2024
|
-
while (tmp) {
|
|
2025
|
-
try {
|
|
2026
|
-
tmp.resolve(tmp.task());
|
|
2027
|
-
} catch (error) {
|
|
2028
|
-
tmp.reject(error);
|
|
2029
|
-
}
|
|
2030
|
-
tmp = measureTasks.shift();
|
|
2031
|
-
}
|
|
2032
|
-
scheduled = false;
|
|
2033
|
-
}
|
|
2034
|
-
function scheduleFlush() {
|
|
2035
|
-
if (!scheduled) {
|
|
2036
|
-
scheduled = true;
|
|
2037
|
-
requestAnimationFrame(onFlush);
|
|
2038
|
-
}
|
|
2039
|
-
}
|
|
2040
|
-
function measure(callback) {
|
|
2041
|
-
const task = {
|
|
2042
|
-
task: callback,
|
|
2043
|
-
resolve: () => {},
|
|
2044
|
-
reject: () => {}
|
|
2045
|
-
};
|
|
2046
|
-
const promise = new Promise((resolve, reject) => {
|
|
2047
|
-
task.resolve = resolve;
|
|
2048
|
-
task.reject = reject;
|
|
2049
|
-
});
|
|
2050
|
-
measureTasks.push(task);
|
|
2051
|
-
scheduleFlush();
|
|
2052
|
-
return promise;
|
|
2053
|
-
}
|
|
2054
|
-
function mutate(callback) {
|
|
2055
|
-
const task = {
|
|
2056
|
-
task: callback,
|
|
2057
|
-
resolve: () => {},
|
|
2058
|
-
reject: () => {}
|
|
2059
|
-
};
|
|
2060
|
-
const promise = new Promise((resolve, reject) => {
|
|
2061
|
-
task.resolve = resolve;
|
|
2062
|
-
task.reject = reject;
|
|
2063
|
-
});
|
|
2064
|
-
mutateTasks.push(task);
|
|
2065
|
-
scheduleFlush();
|
|
2066
|
-
return promise;
|
|
2067
|
-
}
|
|
2068
|
-
//#endregion
|
|
2069
|
-
//#region src/lyric-player/bottom-line.ts
|
|
2151
|
+
//#region src/lyric-player/base/bottom-line.ts
|
|
2070
2152
|
var BottomLineEl = class {
|
|
2071
2153
|
element = document.createElement("div");
|
|
2072
2154
|
left = 0;
|
|
@@ -2155,107 +2237,423 @@ var BottomLineEl = class {
|
|
|
2155
2237
|
}
|
|
2156
2238
|
};
|
|
2157
2239
|
//#endregion
|
|
2158
|
-
//#region src/lyric-player/
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2240
|
+
//#region src/lyric-player/base/consts.ts
|
|
2241
|
+
/** 歌词中不雅用语的掩码模式 */
|
|
2242
|
+
const MaskObsceneWordsMode = {
|
|
2243
|
+
/** 禁用任何不雅用语掩码 */
|
|
2244
|
+
Disabled: "",
|
|
2245
|
+
/** 完全掩码所有不雅用语 */
|
|
2246
|
+
FullMask: "full-mask",
|
|
2247
|
+
/** 保留首尾字符,屏蔽中间字符 */
|
|
2248
|
+
PartialMask: "partial-mask"
|
|
2249
|
+
};
|
|
2250
|
+
/**
|
|
2251
|
+
* 歌词行的渲染模式
|
|
2252
|
+
* @internal
|
|
2253
|
+
*/
|
|
2254
|
+
const LyricLineRenderMode = {
|
|
2255
|
+
SOLID: 0,
|
|
2256
|
+
GRADIENT: 1
|
|
2257
|
+
};
|
|
2258
|
+
/** 布局对齐锚点 */
|
|
2259
|
+
const LayoutAlignAnchor = {
|
|
2260
|
+
Top: "top",
|
|
2261
|
+
Center: "center",
|
|
2262
|
+
Bottom: "bottom"
|
|
2263
|
+
};
|
|
2264
|
+
//#endregion
|
|
2265
|
+
//#region src/lyric-player/base/layout.ts
|
|
2266
|
+
/**
|
|
2267
|
+
* 根据当前时间与当前目标行,计算当前是否处于某个可展示的间奏区间。
|
|
2268
|
+
*
|
|
2269
|
+
* 仅识别时间轴上的间奏空档,不涉及具体 DOM 元素的创建与摆放。
|
|
2270
|
+
* 若当前不应展示间奏动画,则返回 `undefined`。
|
|
2271
|
+
*/
|
|
2272
|
+
function computeCurrentInterlude(input) {
|
|
2273
|
+
const currentTime = input.currentTime + 20;
|
|
2274
|
+
const currentIndex = input.scrollToIndex;
|
|
2275
|
+
const lines = input.processedLines;
|
|
2276
|
+
const checkGap = (k) => {
|
|
2277
|
+
if (k < -1 || k >= lines.length - 1) return void 0;
|
|
2278
|
+
const prevLine = k === -1 ? null : lines[k];
|
|
2279
|
+
const nextLine = lines[k + 1];
|
|
2280
|
+
const gapStart = prevLine ? prevLine.endTime : 0;
|
|
2281
|
+
const gapEnd = Math.max(gapStart, nextLine.startTime - 250);
|
|
2282
|
+
if (gapEnd - gapStart < 4e3) return;
|
|
2283
|
+
if (gapEnd > currentTime && gapStart < currentTime) return {
|
|
2284
|
+
startTime: Math.max(gapStart, currentTime),
|
|
2285
|
+
endTime: gapEnd,
|
|
2286
|
+
anchorLineIndex: k,
|
|
2287
|
+
isNextDuet: nextLine.isDuet
|
|
2288
|
+
};
|
|
2289
|
+
};
|
|
2290
|
+
return checkGap(currentIndex - 1) || checkGap(currentIndex) || checkGap(currentIndex + 1);
|
|
2162
2291
|
}
|
|
2163
|
-
|
|
2164
|
-
|
|
2292
|
+
/**
|
|
2293
|
+
* 根据当前播放上下文计算歌词纵向滚动动画的弹簧参数。
|
|
2294
|
+
*
|
|
2295
|
+
* 其策略为:
|
|
2296
|
+
* - seeking 或间奏时使用更稳定的固定参数
|
|
2297
|
+
* - 普通播放时根据相邻歌词的时间间隔动态调整 stiffness / damping
|
|
2298
|
+
*/
|
|
2299
|
+
function computeLinePosYSpringParams(input) {
|
|
2300
|
+
const { enabled, processedLines, scrollToIndex, isSeeking, isInterludeActive } = input;
|
|
2301
|
+
if (!enabled || processedLines.length === 0) return { shouldUpdate: false };
|
|
2302
|
+
if (isSeeking || isInterludeActive) return {
|
|
2303
|
+
shouldUpdate: true,
|
|
2304
|
+
params: {
|
|
2305
|
+
stiffness: 90,
|
|
2306
|
+
damping: 15
|
|
2307
|
+
}
|
|
2308
|
+
};
|
|
2309
|
+
const currentLine = processedLines[scrollToIndex];
|
|
2310
|
+
const prevLine = processedLines[scrollToIndex - 1];
|
|
2311
|
+
if (!currentLine || !prevLine) return { shouldUpdate: false };
|
|
2312
|
+
const interval = currentLine.startTime - (prevLine.words[0]?.startTime ?? prevLine.startTime);
|
|
2313
|
+
const MIN_INTERVAL = 100;
|
|
2314
|
+
const MAX_INTERVAL = 800;
|
|
2315
|
+
const clampedInterval = clamp(interval, MIN_INTERVAL, MAX_INTERVAL);
|
|
2316
|
+
const MAX_STIFFNESS = 220;
|
|
2317
|
+
const MIN_STIFFNESS = 170;
|
|
2318
|
+
let ratio = 1 - (clampedInterval - MIN_INTERVAL) / (MAX_INTERVAL - MIN_INTERVAL);
|
|
2319
|
+
ratio = ratio ** .2;
|
|
2320
|
+
const targetStiffness = MIN_STIFFNESS + ratio * (MAX_STIFFNESS - MIN_STIFFNESS);
|
|
2321
|
+
return {
|
|
2322
|
+
shouldUpdate: true,
|
|
2323
|
+
params: {
|
|
2324
|
+
stiffness: targetStiffness,
|
|
2325
|
+
damping: Math.sqrt(targetStiffness) * 2.2
|
|
2326
|
+
}
|
|
2327
|
+
};
|
|
2165
2328
|
}
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2329
|
+
/**
|
|
2330
|
+
* 计算单行歌词在当前布局中的视觉呈现参数。
|
|
2331
|
+
*
|
|
2332
|
+
* 根据播放状态、缓冲状态、布局模式与间奏信息,
|
|
2333
|
+
* 生成一行歌词最终应使用的 opacity、scale、blur 和 render mode。
|
|
2334
|
+
*/
|
|
2335
|
+
function computeLinePresentation(input) {
|
|
2336
|
+
const { line, lineIndex, scrollToIndex, latestIndex, hasBuffered, hidePassedLines, isPlaying, isNonDynamic, enableScale, enableBlur, isUserScrolling, isCompact, interlude } = input;
|
|
2337
|
+
const isActive = hasBuffered || lineIndex >= scrollToIndex && lineIndex < latestIndex;
|
|
2338
|
+
const blurLevel = computeLineBlur({
|
|
2339
|
+
enableBlur,
|
|
2340
|
+
isUserScrolling,
|
|
2341
|
+
isActive,
|
|
2342
|
+
itemIndex: lineIndex,
|
|
2343
|
+
scrollToIndex,
|
|
2344
|
+
latestIndex,
|
|
2345
|
+
isCompact
|
|
2346
|
+
});
|
|
2347
|
+
let targetOpacity;
|
|
2348
|
+
if (hidePassedLines) if (lineIndex < (interlude ? interlude.anchorLineIndex + 1 : scrollToIndex) && isPlaying) targetOpacity = 1e-4;
|
|
2349
|
+
else if (hasBuffered) targetOpacity = .85;
|
|
2350
|
+
else targetOpacity = isNonDynamic ? .2 : 1;
|
|
2351
|
+
else if (hasBuffered) targetOpacity = .85;
|
|
2352
|
+
else targetOpacity = isNonDynamic ? .2 : 1;
|
|
2353
|
+
const SCALE_ASPECT = enableScale ? 97 : 100;
|
|
2354
|
+
let targetScale = 100;
|
|
2355
|
+
if (!isActive && isPlaying) targetScale = line.isBG ? 75 : SCALE_ASPECT;
|
|
2356
|
+
return {
|
|
2357
|
+
isActive,
|
|
2358
|
+
targetOpacity,
|
|
2359
|
+
targetScale,
|
|
2360
|
+
blurLevel,
|
|
2361
|
+
renderMode: isActive ? LyricLineRenderMode.GRADIENT : LyricLineRenderMode.SOLID
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
/**
|
|
2365
|
+
* 计算一行歌词在当前布局中的模糊等级。
|
|
2366
|
+
*
|
|
2367
|
+
* 越远离当前对齐区域的歌词会得到更高的模糊值;
|
|
2368
|
+
* 活跃行、滚动交互中或关闭模糊效果时返回 `0`。
|
|
2369
|
+
*/
|
|
2370
|
+
function computeLineBlur(input) {
|
|
2371
|
+
const { enableBlur, isUserScrolling, isActive, itemIndex, scrollToIndex, latestIndex, isCompact } = input;
|
|
2372
|
+
if (!enableBlur || isUserScrolling || isActive) return 0;
|
|
2373
|
+
let blurLevel = 1;
|
|
2374
|
+
if (itemIndex < scrollToIndex) blurLevel += Math.abs(scrollToIndex - itemIndex) + 1;
|
|
2375
|
+
else blurLevel += Math.abs(itemIndex - Math.max(scrollToIndex, latestIndex));
|
|
2376
|
+
return isCompact ? blurLevel * .8 : blurLevel;
|
|
2377
|
+
}
|
|
2378
|
+
//#endregion
|
|
2379
|
+
//#region src/lyric-player/base/scroll.ts
|
|
2380
|
+
/**
|
|
2381
|
+
* 将滚动偏移量限制在当前允许的滚动边界内。
|
|
2382
|
+
*
|
|
2383
|
+
* 当手势滚动、滚轮滚动或惯性滚动更新了 {@link PlayerScrollState.scrollOffset}
|
|
2384
|
+
* 后,应调用本函数以避免视图越界。
|
|
2385
|
+
*/
|
|
2386
|
+
function clampPlayerScrollOffset(scrollState) {
|
|
2387
|
+
scrollState.scrollOffset = clamp(scrollState.scrollOffset, scrollState.scrollBoundary.minOffset, scrollState.scrollBoundary.maxOffset);
|
|
2388
|
+
}
|
|
2389
|
+
/**
|
|
2390
|
+
* 重置滚动状态到未发生用户滚动时的初始状态。
|
|
2391
|
+
*
|
|
2392
|
+
* 本函数会清除当前偏移,并结束“已滚动”与“正在滚动”的标记;
|
|
2393
|
+
* **不会清理**外部持有的计时器或事件监听器。
|
|
2394
|
+
*/
|
|
2395
|
+
function resetPlayerScrollState(scrollState) {
|
|
2396
|
+
scrollState.isScrolled = false;
|
|
2397
|
+
scrollState.scrollOffset = 0;
|
|
2398
|
+
scrollState.isUserScrolling = false;
|
|
2399
|
+
}
|
|
2400
|
+
/**
|
|
2401
|
+
* 向指定元素挂载歌词滚动相关的交互处理器。
|
|
2402
|
+
*
|
|
2403
|
+
* 该函数会处理:
|
|
2404
|
+
* - 触摸拖拽滚动
|
|
2405
|
+
* - 触摸结束后的惯性滚动
|
|
2406
|
+
* - 滚轮滚动
|
|
2407
|
+
* - 轻触时的点击透传
|
|
2408
|
+
*
|
|
2409
|
+
* 只更新 {@link PlayerScrollState} 并通过回调通知宿主执行布局或其它副作用,
|
|
2410
|
+
* 不直接依赖具体的播放器类实现。
|
|
2411
|
+
*/
|
|
2412
|
+
function attachPlayerScrollHandlers(element, scrollState, callbacks) {
|
|
2413
|
+
let startScrollY = 0;
|
|
2414
|
+
let startTouchPosY = 0;
|
|
2415
|
+
let startTouchStartX = 0;
|
|
2416
|
+
let startTouchStartY = 0;
|
|
2417
|
+
let lastMoveY = 0;
|
|
2418
|
+
let startScrollTime = 0;
|
|
2419
|
+
let scrollSpeed = 0;
|
|
2420
|
+
let curScrollId = 0;
|
|
2421
|
+
element.addEventListener("touchstart", (evt) => {
|
|
2422
|
+
if (callbacks.onBeginScroll()) {
|
|
2423
|
+
scrollState.isUserScrolling = true;
|
|
2424
|
+
evt.preventDefault();
|
|
2425
|
+
startScrollY = scrollState.scrollOffset;
|
|
2426
|
+
startTouchPosY = evt.touches[0].screenY;
|
|
2427
|
+
lastMoveY = startTouchPosY;
|
|
2428
|
+
startTouchStartX = evt.touches[0].screenX;
|
|
2429
|
+
startTouchStartY = evt.touches[0].screenY;
|
|
2430
|
+
startScrollTime = Date.now();
|
|
2431
|
+
scrollSpeed = 0;
|
|
2432
|
+
callbacks.onLayout(true, true);
|
|
2433
|
+
}
|
|
2434
|
+
});
|
|
2435
|
+
element.addEventListener("touchmove", (evt) => {
|
|
2436
|
+
if (callbacks.onBeginScroll()) {
|
|
2437
|
+
evt.preventDefault();
|
|
2438
|
+
const currentY = evt.touches[0].screenY;
|
|
2439
|
+
const deltaY = currentY - startTouchPosY;
|
|
2440
|
+
scrollState.scrollOffset = startScrollY - deltaY;
|
|
2441
|
+
clampPlayerScrollOffset(scrollState);
|
|
2442
|
+
const now = Date.now();
|
|
2443
|
+
const dt = now - startScrollTime;
|
|
2444
|
+
if (dt > 0) scrollSpeed = (currentY - lastMoveY) / dt;
|
|
2445
|
+
lastMoveY = currentY;
|
|
2446
|
+
startScrollTime = now;
|
|
2447
|
+
callbacks.onLayout(true, true);
|
|
2448
|
+
}
|
|
2449
|
+
});
|
|
2450
|
+
element.addEventListener("touchend", (evt) => {
|
|
2451
|
+
if (callbacks.onBeginScroll()) {
|
|
2452
|
+
evt.preventDefault();
|
|
2453
|
+
const touch = evt.changedTouches[0];
|
|
2454
|
+
const moveX = Math.abs(touch.screenX - startTouchStartX);
|
|
2455
|
+
const moveY = Math.abs(touch.screenY - startTouchStartY);
|
|
2456
|
+
if (moveX < 10 && moveY < 10) {
|
|
2457
|
+
const target = document.elementFromPoint(touch.clientX, touch.clientY);
|
|
2458
|
+
if (target instanceof HTMLElement && callbacks.containsTarget(target)) callbacks.clickTarget(target);
|
|
2459
|
+
scrollState.isUserScrolling = false;
|
|
2460
|
+
callbacks.onEndScroll();
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
startTouchPosY = 0;
|
|
2464
|
+
const scrollId = ++curScrollId;
|
|
2465
|
+
if (Math.abs(scrollSpeed) < .1) scrollSpeed = 0;
|
|
2466
|
+
let lastFrameTime = performance.now();
|
|
2467
|
+
const onScrollFrame = (time) => {
|
|
2468
|
+
if (scrollId !== curScrollId) return;
|
|
2469
|
+
const dt = time - lastFrameTime;
|
|
2470
|
+
lastFrameTime = time;
|
|
2471
|
+
if (dt <= 0 || dt > 100) {
|
|
2472
|
+
requestAnimationFrame(onScrollFrame);
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
if (Math.abs(scrollSpeed) > .05) {
|
|
2476
|
+
scrollState.scrollOffset -= scrollSpeed * dt;
|
|
2477
|
+
clampPlayerScrollOffset(scrollState);
|
|
2478
|
+
const frictionFactor = .95 ** (dt / 16);
|
|
2479
|
+
scrollSpeed *= frictionFactor;
|
|
2480
|
+
callbacks.onLayout(true, true);
|
|
2481
|
+
requestAnimationFrame(onScrollFrame);
|
|
2482
|
+
} else {
|
|
2483
|
+
scrollState.isUserScrolling = false;
|
|
2484
|
+
callbacks.onEndScroll();
|
|
2485
|
+
}
|
|
2486
|
+
};
|
|
2487
|
+
requestAnimationFrame(onScrollFrame);
|
|
2488
|
+
} else scrollState.isUserScrolling = false;
|
|
2489
|
+
});
|
|
2490
|
+
element.addEventListener("wheel", (evt) => {
|
|
2491
|
+
if (callbacks.onBeginScroll()) {
|
|
2492
|
+
evt.preventDefault();
|
|
2493
|
+
if (evt.deltaMode === evt.DOM_DELTA_PIXEL) {
|
|
2494
|
+
scrollState.scrollOffset += evt.deltaY;
|
|
2495
|
+
clampPlayerScrollOffset(scrollState);
|
|
2496
|
+
callbacks.onLayout(true, false);
|
|
2234
2497
|
} else {
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
this.dot2.style.opacity = "0";
|
|
2498
|
+
scrollState.scrollOffset += evt.deltaY * 50;
|
|
2499
|
+
clampPlayerScrollOffset(scrollState);
|
|
2500
|
+
callbacks.onLayout(false, false);
|
|
2239
2501
|
}
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2502
|
+
}
|
|
2503
|
+
}, { passive: false });
|
|
2504
|
+
}
|
|
2505
|
+
//#endregion
|
|
2506
|
+
//#region src/utils/eq-set.ts
|
|
2507
|
+
const eqSet = (xs, ys) => xs.size === ys.size && [...xs].every((x) => ys.has(x));
|
|
2508
|
+
//#endregion
|
|
2509
|
+
//#region src/lyric-player/base/timeline.ts
|
|
2510
|
+
/**
|
|
2511
|
+
* 计算指定时间点的热行/缓冲行状态转移的纯函数。其行为包括:
|
|
2512
|
+
*
|
|
2513
|
+
* - 根据当前时间和已有的热行状态,计算出新的热行状态,并返回应新增的热行 ID 和应移除的热行 ID
|
|
2514
|
+
* - 根据新的热行状态和已有的缓冲行状态,计算出应移除的缓冲行 ID
|
|
2515
|
+
*/
|
|
2516
|
+
function computePlayerTimeState(input) {
|
|
2517
|
+
const { time, processedLines, timelineState: { hotLines, bufferedLines } } = input;
|
|
2518
|
+
const nextHotLines = new Set(hotLines);
|
|
2519
|
+
const addedIds = /* @__PURE__ */ new Set();
|
|
2520
|
+
const removedHotIds = /* @__PURE__ */ new Set();
|
|
2521
|
+
const removedBufferedIds = /* @__PURE__ */ new Set();
|
|
2522
|
+
for (const lastHotId of hotLines) {
|
|
2523
|
+
const line = processedLines[lastHotId];
|
|
2524
|
+
if (!line) {
|
|
2525
|
+
nextHotLines.delete(lastHotId);
|
|
2526
|
+
removedHotIds.add(lastHotId);
|
|
2527
|
+
continue;
|
|
2528
|
+
}
|
|
2529
|
+
if (line.isBG) continue;
|
|
2530
|
+
const nextLine = processedLines[lastHotId + 1];
|
|
2531
|
+
if (nextLine?.isBG) {
|
|
2532
|
+
const nextMainLine = processedLines[lastHotId + 2];
|
|
2533
|
+
const startTime = Math.min(line.startTime, nextLine.startTime);
|
|
2534
|
+
const endTime = Math.min(Math.max(line.endTime, nextMainLine?.startTime ?? Number.MAX_VALUE), Math.max(line.endTime, nextLine.endTime));
|
|
2535
|
+
if (time < startTime || endTime <= time) {
|
|
2536
|
+
nextHotLines.delete(lastHotId);
|
|
2537
|
+
removedHotIds.add(lastHotId);
|
|
2538
|
+
nextHotLines.delete(lastHotId + 1);
|
|
2539
|
+
removedHotIds.add(lastHotId + 1);
|
|
2540
|
+
}
|
|
2541
|
+
} else if (time < line.startTime || line.endTime <= time) {
|
|
2542
|
+
nextHotLines.delete(lastHotId);
|
|
2543
|
+
removedHotIds.add(lastHotId);
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
for (let id = 0; id < processedLines.length; id++) {
|
|
2547
|
+
const line = processedLines[id];
|
|
2548
|
+
if (!line || line.isBG) continue;
|
|
2549
|
+
if (line.startTime <= time && line.endTime > time && !nextHotLines.has(id)) {
|
|
2550
|
+
nextHotLines.add(id);
|
|
2551
|
+
addedIds.add(id);
|
|
2552
|
+
if (processedLines[id + 1]?.isBG) {
|
|
2553
|
+
nextHotLines.add(id + 1);
|
|
2554
|
+
addedIds.add(id + 1);
|
|
2244
2555
|
}
|
|
2245
2556
|
}
|
|
2246
2557
|
}
|
|
2247
|
-
|
|
2248
|
-
|
|
2558
|
+
for (const id of bufferedLines) if (!nextHotLines.has(id)) removedBufferedIds.add(id);
|
|
2559
|
+
return {
|
|
2560
|
+
nextHotLines,
|
|
2561
|
+
addedIds,
|
|
2562
|
+
removedHotIds,
|
|
2563
|
+
removedBufferedIds
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
/**
|
|
2567
|
+
* 在 seeking 场景下,根据当前时间选出应对齐滚动到的目标行索引。
|
|
2568
|
+
*
|
|
2569
|
+
* 若当前仍存在缓冲行,则优先对齐到最靠前的缓冲行;
|
|
2570
|
+
* 否则对齐到第一条开始时间不小于当前时间的歌词行。
|
|
2571
|
+
*/
|
|
2572
|
+
function pickScrollToIndexForSeek(time, processedLines, bufferedLines) {
|
|
2573
|
+
if (bufferedLines.size > 0) return Math.min(...bufferedLines);
|
|
2574
|
+
const foundIndex = processedLines.findIndex((line) => line.startTime >= time);
|
|
2575
|
+
return foundIndex === -1 ? processedLines.length : foundIndex;
|
|
2576
|
+
}
|
|
2577
|
+
/**
|
|
2578
|
+
* 提交时间线状态转移的纯函数。
|
|
2579
|
+
*
|
|
2580
|
+
* 把一次时间线状态转移写回 {@link PlayerTimelineState},
|
|
2581
|
+
* 并返回一份供宿主执行的副作用应用计划,例如启用/禁用哪些歌词行、
|
|
2582
|
+
* 是否需要重置用户滚动状态、是否需要触发布局。
|
|
2583
|
+
*/
|
|
2584
|
+
function commitPlayerTimeState(input) {
|
|
2585
|
+
const { timelineState, time, processedLines, hasBottomContent, stateResult } = input;
|
|
2586
|
+
const { addedIds, removedHotIds, removedBufferedIds } = stateResult;
|
|
2587
|
+
const { isSeeking } = timelineState;
|
|
2588
|
+
timelineState.currentTime = time;
|
|
2589
|
+
timelineState.hotLines = stateResult.nextHotLines;
|
|
2590
|
+
let shouldLayout = false;
|
|
2591
|
+
let shouldResetScroll = false;
|
|
2592
|
+
const linesToEnable = [];
|
|
2593
|
+
const linesToDisable = /* @__PURE__ */ new Set();
|
|
2594
|
+
if (isSeeking) {
|
|
2595
|
+
timelineState.bufferedLines = new Set([...timelineState.hotLines]);
|
|
2596
|
+
timelineState.scrollToIndex = pickScrollToIndexForSeek(time, processedLines, timelineState.bufferedLines);
|
|
2597
|
+
for (const id of removedHotIds) linesToDisable.add(id);
|
|
2598
|
+
for (const id of timelineState.hotLines) linesToEnable.push(id);
|
|
2599
|
+
for (const id of removedBufferedIds) linesToDisable.add(id);
|
|
2600
|
+
shouldResetScroll = true;
|
|
2601
|
+
shouldLayout = true;
|
|
2602
|
+
} else if (addedIds.size > 0) {
|
|
2603
|
+
for (const id of addedIds) {
|
|
2604
|
+
timelineState.bufferedLines.add(id);
|
|
2605
|
+
linesToEnable.push(id);
|
|
2606
|
+
}
|
|
2607
|
+
for (const id of removedBufferedIds) {
|
|
2608
|
+
timelineState.bufferedLines.delete(id);
|
|
2609
|
+
linesToDisable.add(id);
|
|
2610
|
+
}
|
|
2611
|
+
if (timelineState.bufferedLines.size > 0) timelineState.scrollToIndex = Math.min(...timelineState.bufferedLines);
|
|
2612
|
+
shouldLayout = true;
|
|
2613
|
+
} else if (removedBufferedIds.size > 0 && eqSet(removedBufferedIds, timelineState.bufferedLines)) {
|
|
2614
|
+
for (const id of timelineState.bufferedLines) {
|
|
2615
|
+
if (timelineState.hotLines.has(id)) continue;
|
|
2616
|
+
timelineState.bufferedLines.delete(id);
|
|
2617
|
+
linesToDisable.add(id);
|
|
2618
|
+
}
|
|
2619
|
+
shouldLayout = true;
|
|
2620
|
+
}
|
|
2621
|
+
if (timelineState.bufferedLines.size === 0 && processedLines.length > 0) {
|
|
2622
|
+
if (time >= processedLines[processedLines.length - 1].endTime) {
|
|
2623
|
+
const targetIndex = hasBottomContent ? processedLines.length : processedLines.length - 1;
|
|
2624
|
+
if (timelineState.scrollToIndex !== targetIndex) {
|
|
2625
|
+
timelineState.scrollToIndex = targetIndex;
|
|
2626
|
+
shouldLayout = true;
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2249
2629
|
}
|
|
2250
|
-
|
|
2630
|
+
timelineState.lastCurrentTime = time;
|
|
2631
|
+
return {
|
|
2632
|
+
shouldLayout,
|
|
2633
|
+
shouldResetScroll,
|
|
2634
|
+
linesToEnable,
|
|
2635
|
+
linesToDisable: [...linesToDisable]
|
|
2636
|
+
};
|
|
2637
|
+
}
|
|
2251
2638
|
//#endregion
|
|
2252
|
-
//#region src/lyric-player/base.ts
|
|
2639
|
+
//#region src/lyric-player/base/index.ts
|
|
2253
2640
|
/**
|
|
2254
|
-
*
|
|
2641
|
+
* 歌词播放器的基类,已经包含了有关歌词操作和排版的功能,
|
|
2642
|
+
* 子类需要为其实现对应的显示展示操作
|
|
2255
2643
|
*/
|
|
2256
2644
|
var LyricPlayerBase = class extends EventTarget {
|
|
2257
2645
|
element = document.createElement("div");
|
|
2258
|
-
|
|
2646
|
+
/** 播放时间线状态 */
|
|
2647
|
+
timelineState = {
|
|
2648
|
+
currentTime: 0,
|
|
2649
|
+
lastCurrentTime: 0,
|
|
2650
|
+
hotLines: /* @__PURE__ */ new Set(),
|
|
2651
|
+
bufferedLines: /* @__PURE__ */ new Set(),
|
|
2652
|
+
scrollToIndex: 0,
|
|
2653
|
+
isSeeking: false,
|
|
2654
|
+
isPlaying: true,
|
|
2655
|
+
initialLayoutFinished: false
|
|
2656
|
+
};
|
|
2259
2657
|
/** @internal */
|
|
2260
2658
|
lyricLinesSize = /* @__PURE__ */ new WeakMap();
|
|
2261
2659
|
/** @internal */
|
|
@@ -2263,42 +2661,38 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2263
2661
|
currentLyricLines = [];
|
|
2264
2662
|
processedLines = [];
|
|
2265
2663
|
lyricLinesIndexes = /* @__PURE__ */ new WeakMap();
|
|
2266
|
-
hotLines = /* @__PURE__ */ new Set();
|
|
2267
|
-
bufferedLines = /* @__PURE__ */ new Set();
|
|
2268
2664
|
isNonDynamic = false;
|
|
2269
2665
|
hasDuetLine = false;
|
|
2270
|
-
scrollToIndex = 0;
|
|
2271
2666
|
disableSpring = false;
|
|
2272
|
-
|
|
2667
|
+
layoutState = {
|
|
2668
|
+
interludeDotsSize: [0, 0],
|
|
2669
|
+
targetAlignIndex: 0,
|
|
2670
|
+
lastInterludeState: false,
|
|
2671
|
+
alignAnchor: LayoutAlignAnchor.Center,
|
|
2672
|
+
alignPosition: .35,
|
|
2673
|
+
overscanPx: 300
|
|
2674
|
+
};
|
|
2273
2675
|
interludeDots = new InterludeDots();
|
|
2274
2676
|
bottomLine = new BottomLineEl(this);
|
|
2275
2677
|
enableBlur = true;
|
|
2276
2678
|
enableScale = true;
|
|
2277
|
-
maskObsceneWords =
|
|
2679
|
+
maskObsceneWords = MaskObsceneWordsMode.Disabled;
|
|
2278
2680
|
maskObsceneWordChar = "*";
|
|
2279
2681
|
hidePassedLines = false;
|
|
2280
|
-
|
|
2682
|
+
scrollState = {
|
|
2683
|
+
scrollBoundary: {
|
|
2684
|
+
minOffset: 0,
|
|
2685
|
+
maxOffset: 0
|
|
2686
|
+
},
|
|
2687
|
+
scrollOffset: 0,
|
|
2688
|
+
allowScroll: true,
|
|
2689
|
+
isScrolled: false,
|
|
2690
|
+
isUserScrolling: false
|
|
2691
|
+
};
|
|
2281
2692
|
currentLyricLineObjects = [];
|
|
2282
|
-
isSeeking = false;
|
|
2283
|
-
lastCurrentTime = 0;
|
|
2284
|
-
alignAnchor = "center";
|
|
2285
|
-
alignPosition = .35;
|
|
2286
|
-
scrollOffset = 0;
|
|
2287
2693
|
size = [0, 0];
|
|
2288
|
-
allowScroll = true;
|
|
2289
2694
|
isPageVisible = true;
|
|
2290
2695
|
optimizeOptions = {};
|
|
2291
|
-
initialLayoutFinished = false;
|
|
2292
|
-
/**
|
|
2293
|
-
* 标记用户是否正在进行滚动交互
|
|
2294
|
-
*/
|
|
2295
|
-
isUserScrolling = false;
|
|
2296
|
-
wheelTimeout;
|
|
2297
|
-
/**
|
|
2298
|
-
* 视图额外预渲染(overscan)距离,单位:像素。
|
|
2299
|
-
* 用于决定在视口之外多少距离内也认为是“可见”,以便提前创建/保留行元素。
|
|
2300
|
-
*/
|
|
2301
|
-
overscanPx = 300;
|
|
2302
2696
|
posXSpringParams = {
|
|
2303
2697
|
mass: 1,
|
|
2304
2698
|
damping: 10,
|
|
@@ -2321,13 +2715,12 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2321
2715
|
};
|
|
2322
2716
|
onPageShow = () => {
|
|
2323
2717
|
this.isPageVisible = true;
|
|
2324
|
-
this.setCurrentTime(this.currentTime, true);
|
|
2718
|
+
this.setCurrentTime(this.timelineState.currentTime, true);
|
|
2325
2719
|
};
|
|
2326
2720
|
onPageHide = () => {
|
|
2327
2721
|
this.isPageVisible = false;
|
|
2328
2722
|
};
|
|
2329
2723
|
scrolledHandler;
|
|
2330
|
-
isScrolled = false;
|
|
2331
2724
|
/** @internal */
|
|
2332
2725
|
resizeObserver = new ResizeObserver(((entries) => {
|
|
2333
2726
|
let shouldRelayout = false;
|
|
@@ -2338,8 +2731,8 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2338
2731
|
this.size[1] = rect.height;
|
|
2339
2732
|
shouldRebuildPlayerStyle = true;
|
|
2340
2733
|
} else if (entry.target === this.interludeDots.getElement()) {
|
|
2341
|
-
this.interludeDotsSize[0] = entry.target.clientWidth;
|
|
2342
|
-
this.interludeDotsSize[1] = entry.target.clientHeight;
|
|
2734
|
+
this.layoutState.interludeDotsSize[0] = entry.target.clientWidth;
|
|
2735
|
+
this.layoutState.interludeDotsSize[1] = entry.target.clientHeight;
|
|
2343
2736
|
shouldRelayout = true;
|
|
2344
2737
|
} else if (entry.target === this.bottomLine.getElement()) {
|
|
2345
2738
|
const newSize = [entry.target.clientWidth, entry.target.clientHeight];
|
|
@@ -2364,8 +2757,6 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2364
2757
|
if (shouldRebuildPlayerStyle) this.onResize();
|
|
2365
2758
|
}));
|
|
2366
2759
|
wordFadeWidth = .5;
|
|
2367
|
-
targetAlignIndex = 0;
|
|
2368
|
-
lastInterludeState = false;
|
|
2369
2760
|
constructor(element) {
|
|
2370
2761
|
super();
|
|
2371
2762
|
if (element) this.element = element;
|
|
@@ -2377,114 +2768,27 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2377
2768
|
this.interludeDots.setTransform(0, 200);
|
|
2378
2769
|
window.addEventListener("pageshow", this.onPageShow);
|
|
2379
2770
|
window.addEventListener("pagehide", this.onPageHide);
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
let scrollSpeed = 0;
|
|
2387
|
-
let curScrollId = 0;
|
|
2388
|
-
this.element.addEventListener("touchstart", (evt) => {
|
|
2389
|
-
if (this.beginScrollHandler()) {
|
|
2390
|
-
this.isUserScrolling = true;
|
|
2391
|
-
evt.preventDefault();
|
|
2392
|
-
startScrollY = this.scrollOffset;
|
|
2393
|
-
startTouchPosY = evt.touches[0].screenY;
|
|
2394
|
-
lastMoveY = startTouchPosY;
|
|
2395
|
-
startTouchStartX = evt.touches[0].screenX;
|
|
2396
|
-
startTouchStartY = evt.touches[0].screenY;
|
|
2397
|
-
startScrollTime = Date.now();
|
|
2398
|
-
scrollSpeed = 0;
|
|
2399
|
-
this.calcLayout(true, true);
|
|
2400
|
-
}
|
|
2401
|
-
});
|
|
2402
|
-
this.element.addEventListener("touchmove", (evt) => {
|
|
2403
|
-
if (this.beginScrollHandler()) {
|
|
2404
|
-
evt.preventDefault();
|
|
2405
|
-
const currentY = evt.touches[0].screenY;
|
|
2406
|
-
const deltaY = currentY - startTouchPosY;
|
|
2407
|
-
this.scrollOffset = startScrollY - deltaY;
|
|
2408
|
-
this.limitScrollOffset();
|
|
2409
|
-
const now = Date.now();
|
|
2410
|
-
const dt = now - startScrollTime;
|
|
2411
|
-
if (dt > 0) scrollSpeed = (currentY - lastMoveY) / dt;
|
|
2412
|
-
lastMoveY = currentY;
|
|
2413
|
-
startScrollTime = now;
|
|
2414
|
-
this.calcLayout(true, true);
|
|
2415
|
-
}
|
|
2416
|
-
});
|
|
2417
|
-
this.element.addEventListener("touchend", (evt) => {
|
|
2418
|
-
if (this.beginScrollHandler()) {
|
|
2419
|
-
evt.preventDefault();
|
|
2420
|
-
const touch = evt.changedTouches[0];
|
|
2421
|
-
const moveX = Math.abs(touch.screenX - startTouchStartX);
|
|
2422
|
-
const moveY = Math.abs(touch.screenY - startTouchStartY);
|
|
2423
|
-
if (moveX < 10 && moveY < 10) {
|
|
2424
|
-
const target = document.elementFromPoint(touch.clientX, touch.clientY);
|
|
2425
|
-
if (target && this.element.contains(target)) target.click();
|
|
2426
|
-
this.isUserScrolling = false;
|
|
2427
|
-
this.endScrollHandler();
|
|
2428
|
-
return;
|
|
2429
|
-
}
|
|
2430
|
-
startTouchPosY = 0;
|
|
2431
|
-
const scrollId = ++curScrollId;
|
|
2432
|
-
if (Math.abs(scrollSpeed) < .1) scrollSpeed = 0;
|
|
2433
|
-
let lastFrameTime = performance.now();
|
|
2434
|
-
const onScrollFrame = (time) => {
|
|
2435
|
-
if (scrollId !== curScrollId) return;
|
|
2436
|
-
const dt = time - lastFrameTime;
|
|
2437
|
-
lastFrameTime = time;
|
|
2438
|
-
if (dt <= 0 || dt > 100) {
|
|
2439
|
-
requestAnimationFrame(onScrollFrame);
|
|
2440
|
-
return;
|
|
2441
|
-
}
|
|
2442
|
-
if (Math.abs(scrollSpeed) > .05) {
|
|
2443
|
-
this.scrollOffset -= scrollSpeed * dt;
|
|
2444
|
-
this.limitScrollOffset();
|
|
2445
|
-
const frictionFactor = .95 ** (dt / 16);
|
|
2446
|
-
scrollSpeed *= frictionFactor;
|
|
2447
|
-
this.calcLayout(true, true);
|
|
2448
|
-
requestAnimationFrame(onScrollFrame);
|
|
2449
|
-
} else {
|
|
2450
|
-
this.isUserScrolling = false;
|
|
2451
|
-
this.endScrollHandler();
|
|
2452
|
-
}
|
|
2453
|
-
};
|
|
2454
|
-
requestAnimationFrame(onScrollFrame);
|
|
2455
|
-
} else this.isUserScrolling = false;
|
|
2771
|
+
attachPlayerScrollHandlers(this.element, this.scrollState, {
|
|
2772
|
+
onBeginScroll: () => this.beginScrollHandler(),
|
|
2773
|
+
onEndScroll: () => this.endScrollHandler(),
|
|
2774
|
+
onLayout: (sync, force) => this.calcLayout(sync, force),
|
|
2775
|
+
containsTarget: (target) => this.element.contains(target),
|
|
2776
|
+
clickTarget: (target) => target.click()
|
|
2456
2777
|
});
|
|
2457
|
-
this.element.addEventListener("wheel", (evt) => {
|
|
2458
|
-
if (this.beginScrollHandler()) {
|
|
2459
|
-
evt.preventDefault();
|
|
2460
|
-
if (evt.deltaMode === evt.DOM_DELTA_PIXEL) {
|
|
2461
|
-
this.scrollOffset += evt.deltaY;
|
|
2462
|
-
this.limitScrollOffset();
|
|
2463
|
-
this.calcLayout(true, false);
|
|
2464
|
-
} else {
|
|
2465
|
-
this.scrollOffset += evt.deltaY * 50;
|
|
2466
|
-
this.limitScrollOffset();
|
|
2467
|
-
this.calcLayout(false, false);
|
|
2468
|
-
}
|
|
2469
|
-
}
|
|
2470
|
-
}, { passive: false });
|
|
2471
2778
|
}
|
|
2472
2779
|
beginScrollHandler() {
|
|
2473
|
-
const allowed = this.allowScroll;
|
|
2780
|
+
const allowed = this.scrollState.allowScroll;
|
|
2474
2781
|
if (allowed) {
|
|
2475
|
-
this.isScrolled = true;
|
|
2782
|
+
this.scrollState.isScrolled = true;
|
|
2476
2783
|
clearTimeout(this.scrolledHandler);
|
|
2477
2784
|
this.scrolledHandler = setTimeout(() => {
|
|
2478
|
-
this.isScrolled = false;
|
|
2479
|
-
this.scrollOffset = 0;
|
|
2785
|
+
this.scrollState.isScrolled = false;
|
|
2786
|
+
this.scrollState.scrollOffset = 0;
|
|
2480
2787
|
}, 5e3);
|
|
2481
2788
|
}
|
|
2482
2789
|
return allowed;
|
|
2483
2790
|
}
|
|
2484
2791
|
endScrollHandler() {}
|
|
2485
|
-
limitScrollOffset() {
|
|
2486
|
-
this.scrollOffset = Math.max(Math.min(this.scrollBoundary[1], this.scrollOffset), this.scrollBoundary[0]);
|
|
2487
|
-
}
|
|
2488
2792
|
/**
|
|
2489
2793
|
* 设置文字动画的渐变宽度,单位以歌词行的主文字字体大小的倍数为单位,默认为 0.5,即一个全角字符的一半宽度
|
|
2490
2794
|
*
|
|
@@ -2526,7 +2830,7 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2526
2830
|
return this.wordFadeWidth;
|
|
2527
2831
|
}
|
|
2528
2832
|
setIsSeeking(isSeeking) {
|
|
2529
|
-
this.isSeeking = isSeeking;
|
|
2833
|
+
this.timelineState.isSeeking = isSeeking;
|
|
2530
2834
|
}
|
|
2531
2835
|
/**
|
|
2532
2836
|
* 设置是否隐藏已经播放过的歌词行,默认不隐藏
|
|
@@ -2564,7 +2868,7 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2564
2868
|
const c = char.charAt(0) || "*";
|
|
2565
2869
|
if (this.maskObsceneWordChar === c) return;
|
|
2566
2870
|
this.maskObsceneWordChar = c;
|
|
2567
|
-
if (this.maskObsceneWords !==
|
|
2871
|
+
if (this.maskObsceneWords !== MaskObsceneWordsMode.Disabled) {
|
|
2568
2872
|
this.rebuildLyricLines();
|
|
2569
2873
|
this.calcLayout();
|
|
2570
2874
|
}
|
|
@@ -2579,10 +2883,10 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2579
2883
|
*/
|
|
2580
2884
|
processObsceneWord(word) {
|
|
2581
2885
|
const text = word.word;
|
|
2582
|
-
if (!word.obscene || this.maskObsceneWords ===
|
|
2886
|
+
if (!word.obscene || this.maskObsceneWords === MaskObsceneWordsMode.Disabled) return text;
|
|
2583
2887
|
const maskChar = this.maskObsceneWordChar;
|
|
2584
|
-
if (this.maskObsceneWords ===
|
|
2585
|
-
if (this.maskObsceneWords ===
|
|
2888
|
+
if (this.maskObsceneWords === MaskObsceneWordsMode.FullMask) return text.replace(/\S/g, maskChar);
|
|
2889
|
+
if (this.maskObsceneWords === MaskObsceneWordsMode.PartialMask) {
|
|
2586
2890
|
const trimmed = text.trim();
|
|
2587
2891
|
if (trimmed.length <= 2) return text.replace(/\S/g, maskChar);
|
|
2588
2892
|
const startPos = text.indexOf(trimmed);
|
|
@@ -2600,25 +2904,25 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2600
2904
|
* @param alignAnchor 歌词行对齐方式,详情见函数说明
|
|
2601
2905
|
*/
|
|
2602
2906
|
setAlignAnchor(alignAnchor) {
|
|
2603
|
-
this.alignAnchor = alignAnchor;
|
|
2907
|
+
this.layoutState.alignAnchor = alignAnchor;
|
|
2604
2908
|
}
|
|
2605
2909
|
/**
|
|
2606
2910
|
* 设置默认的歌词行对齐位置,相对于整个歌词播放组件的大小位置,默认为 `0.5`
|
|
2607
2911
|
* @param alignPosition 一个 `[0.0-1.0]` 之间的任意数字,代表组件高度由上到下的比例位置
|
|
2608
2912
|
*/
|
|
2609
2913
|
setAlignPosition(alignPosition) {
|
|
2610
|
-
this.alignPosition = alignPosition;
|
|
2914
|
+
this.layoutState.alignPosition = alignPosition;
|
|
2611
2915
|
}
|
|
2612
2916
|
/**
|
|
2613
2917
|
* 设置 overscan(视图上下额外缓冲渲染区)距离,单位:像素。
|
|
2614
2918
|
* @param px 像素值,默认 300
|
|
2615
2919
|
*/
|
|
2616
2920
|
setOverscanPx(px) {
|
|
2617
|
-
this.overscanPx =
|
|
2921
|
+
this.layoutState.overscanPx = clampPositive(px | 0);
|
|
2618
2922
|
}
|
|
2619
2923
|
/** 获取当前 overscan 像素距离 */
|
|
2620
2924
|
getOverscanPx() {
|
|
2621
|
-
return this.overscanPx;
|
|
2925
|
+
return this.layoutState.overscanPx;
|
|
2622
2926
|
}
|
|
2623
2927
|
/**
|
|
2624
2928
|
* 设置是否使用物理弹簧算法实现歌词动画效果,默认启用
|
|
@@ -2641,34 +2945,6 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2641
2945
|
return !this.disableSpring;
|
|
2642
2946
|
}
|
|
2643
2947
|
/**
|
|
2644
|
-
* 获取当前播放时间里是否处于间奏区间
|
|
2645
|
-
* 如果是则会返回单位为毫秒的始末时间
|
|
2646
|
-
* 否则返回 undefined
|
|
2647
|
-
*
|
|
2648
|
-
* 这个只允许内部调用
|
|
2649
|
-
* @returns [开始时间,结束时间,大概处于的歌词行ID,下一句是否为对唱歌词] 或 undefined 如果不处于间奏区间
|
|
2650
|
-
*/
|
|
2651
|
-
getCurrentInterlude() {
|
|
2652
|
-
const currentTime = this.currentTime + 20;
|
|
2653
|
-
const currentIndex = this.scrollToIndex;
|
|
2654
|
-
const lines = this.processedLines;
|
|
2655
|
-
const checkGap = (k) => {
|
|
2656
|
-
if (k < -1 || k >= lines.length - 1) return void 0;
|
|
2657
|
-
const prevLine = k === -1 ? null : lines[k];
|
|
2658
|
-
const nextLine = lines[k + 1];
|
|
2659
|
-
const gapStart = prevLine ? prevLine.endTime : 0;
|
|
2660
|
-
const gapEnd = Math.max(gapStart, nextLine.startTime - 250);
|
|
2661
|
-
if (gapEnd - gapStart < 4e3) return;
|
|
2662
|
-
if (gapEnd > currentTime && gapStart < currentTime) return [
|
|
2663
|
-
Math.max(gapStart, currentTime),
|
|
2664
|
-
gapEnd,
|
|
2665
|
-
k,
|
|
2666
|
-
nextLine.isDuet
|
|
2667
|
-
];
|
|
2668
|
-
};
|
|
2669
|
-
return checkGap(currentIndex - 1) || checkGap(currentIndex) || checkGap(currentIndex + 1);
|
|
2670
|
-
}
|
|
2671
|
-
/**
|
|
2672
2948
|
* 设置歌词的优化配置项,这些配置项默认全部开启
|
|
2673
2949
|
*
|
|
2674
2950
|
* 注意,如果在 `setLyricLines` 之后修改此配置,需要重新调用 `setLyricLines()` 才能对当前歌词生效
|
|
@@ -2688,9 +2964,9 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2688
2964
|
*/
|
|
2689
2965
|
setLyricLines(lines, initialTime = 0) {
|
|
2690
2966
|
if (process.env.NODE_ENV !== "production") console.log("设置歌词行", lines, initialTime);
|
|
2691
|
-
this.initialLayoutFinished = true;
|
|
2692
|
-
this.lastCurrentTime = initialTime;
|
|
2693
|
-
this.currentTime = initialTime;
|
|
2967
|
+
this.timelineState.initialLayoutFinished = true;
|
|
2968
|
+
this.timelineState.lastCurrentTime = initialTime;
|
|
2969
|
+
this.timelineState.currentTime = initialTime;
|
|
2694
2970
|
this.currentLyricLines = (0, _ungap_structured_clone.default)(lines);
|
|
2695
2971
|
this.processedLines = (0, _ungap_structured_clone.default)(this.currentLyricLines);
|
|
2696
2972
|
optimizeLyricLines(this.processedLines, this.optimizeOptions);
|
|
@@ -2702,8 +2978,8 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2702
2978
|
this.hasDuetLine = this.processedLines.some((line) => line.isDuet);
|
|
2703
2979
|
for (const line of this.currentLyricLineObjects) line.dispose();
|
|
2704
2980
|
this.interludeDots.setInterlude(void 0);
|
|
2705
|
-
this.hotLines.clear();
|
|
2706
|
-
this.bufferedLines.clear();
|
|
2981
|
+
this.timelineState.hotLines.clear();
|
|
2982
|
+
this.timelineState.bufferedLines.clear();
|
|
2707
2983
|
this.setCurrentTime(0, true);
|
|
2708
2984
|
if (process.env.NODE_ENV !== "production") console.log("歌词处理完成", this);
|
|
2709
2985
|
}
|
|
@@ -2712,143 +2988,39 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2712
2988
|
* @returns 当前是否在播放
|
|
2713
2989
|
*/
|
|
2714
2990
|
getIsPlaying() {
|
|
2715
|
-
return this.isPlaying;
|
|
2991
|
+
return this.timelineState.isPlaying;
|
|
2716
2992
|
}
|
|
2717
2993
|
/**
|
|
2718
|
-
*
|
|
2719
|
-
*
|
|
2994
|
+
* 设置当前播放进度,此时将会更新内部的歌词进度信息。
|
|
2995
|
+
*
|
|
2996
|
+
* 内部会根据调用间隔和播放进度自动决定如何滚动和显示歌词,所以这个的调用频率越快越准确越好。
|
|
2997
|
+
* 调用完成后,应每帧调用 {@link update} 方法来执行歌词动画效果。**此函数本身不会触发动画效果**。
|
|
2720
2998
|
*
|
|
2721
|
-
* 调用完成后,可以每帧调用 `update` 函数来执行歌词动画效果
|
|
2722
2999
|
* @param time 当前播放进度,单位为毫秒
|
|
2723
3000
|
*/
|
|
2724
3001
|
setCurrentTime(time, isSeek = false) {
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
const nextLine = this.processedLines[lastHotId + 1];
|
|
2735
|
-
if (nextLine?.isBG) {
|
|
2736
|
-
const nextMainLine = this.processedLines[lastHotId + 2];
|
|
2737
|
-
const startTime = Math.min(line.startTime, nextLine?.startTime);
|
|
2738
|
-
const endTime = Math.min(Math.max(line.endTime, nextMainLine?.startTime ?? Number.MAX_VALUE), Math.max(line.endTime, nextLine?.endTime));
|
|
2739
|
-
if (startTime > time || endTime <= time) {
|
|
2740
|
-
this.hotLines.delete(lastHotId);
|
|
2741
|
-
removedHotIds.add(lastHotId);
|
|
2742
|
-
this.hotLines.delete(lastHotId + 1);
|
|
2743
|
-
removedHotIds.add(lastHotId + 1);
|
|
2744
|
-
if (isSeek) {
|
|
2745
|
-
this.currentLyricLineObjects[lastHotId]?.disable();
|
|
2746
|
-
this.currentLyricLineObjects[lastHotId + 1]?.disable();
|
|
2747
|
-
}
|
|
2748
|
-
}
|
|
2749
|
-
} else if (line.startTime > time || line.endTime <= time) {
|
|
2750
|
-
this.hotLines.delete(lastHotId);
|
|
2751
|
-
removedHotIds.add(lastHotId);
|
|
2752
|
-
if (isSeek) this.currentLyricLineObjects[lastHotId]?.disable();
|
|
2753
|
-
}
|
|
2754
|
-
} else {
|
|
2755
|
-
this.hotLines.delete(lastHotId);
|
|
2756
|
-
removedHotIds.add(lastHotId);
|
|
2757
|
-
if (isSeek) this.currentLyricLineObjects[lastHotId]?.disable();
|
|
2758
|
-
}
|
|
2759
|
-
}
|
|
2760
|
-
this.currentLyricLineObjects.forEach((lineObj, id, arr) => {
|
|
2761
|
-
const line = lineObj.getLine();
|
|
2762
|
-
if (!line.isBG && line.startTime <= time && line.endTime > time) {
|
|
2763
|
-
if (isSeek) lineObj.enable(time, this.isPlaying);
|
|
2764
|
-
if (!this.hotLines.has(id)) {
|
|
2765
|
-
this.hotLines.add(id);
|
|
2766
|
-
addedIds.add(id);
|
|
2767
|
-
if (!isSeek) lineObj.enable();
|
|
2768
|
-
if (arr[id + 1]?.getLine()?.isBG) {
|
|
2769
|
-
this.hotLines.add(id + 1);
|
|
2770
|
-
addedIds.add(id + 1);
|
|
2771
|
-
if (isSeek) arr[id + 1].enable(time, this.isPlaying);
|
|
2772
|
-
else arr[id + 1].enable();
|
|
2773
|
-
}
|
|
2774
|
-
}
|
|
2775
|
-
}
|
|
3002
|
+
time = Math.round(time);
|
|
3003
|
+
const { timelineState } = this;
|
|
3004
|
+
timelineState.isSeeking = Boolean(isSeek);
|
|
3005
|
+
timelineState.currentTime = time;
|
|
3006
|
+
if (!timelineState.initialLayoutFinished && !timelineState.isSeeking) return;
|
|
3007
|
+
const stateResult = computePlayerTimeState({
|
|
3008
|
+
time,
|
|
3009
|
+
processedLines: this.processedLines,
|
|
3010
|
+
timelineState
|
|
2776
3011
|
});
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
this.resetScroll();
|
|
2790
|
-
this.calcLayout();
|
|
2791
|
-
} else if (removedIds.size > 0 || addedIds.size > 0) if (removedIds.size === 0 && addedIds.size > 0) {
|
|
2792
|
-
for (const v of addedIds) {
|
|
2793
|
-
this.bufferedLines.add(v);
|
|
2794
|
-
this.currentLyricLineObjects[v]?.enable();
|
|
2795
|
-
}
|
|
2796
|
-
this.scrollToIndex = Math.min(...this.bufferedLines);
|
|
2797
|
-
this.calcLayout();
|
|
2798
|
-
} else if (addedIds.size === 0 && removedIds.size > 0) {
|
|
2799
|
-
if (eqSet(removedIds, this.bufferedLines)) {
|
|
2800
|
-
for (const v of this.bufferedLines) if (!this.hotLines.has(v)) {
|
|
2801
|
-
this.bufferedLines.delete(v);
|
|
2802
|
-
this.currentLyricLineObjects[v]?.disable();
|
|
2803
|
-
}
|
|
2804
|
-
this.calcLayout();
|
|
2805
|
-
}
|
|
2806
|
-
} else {
|
|
2807
|
-
for (const v of addedIds) {
|
|
2808
|
-
this.bufferedLines.add(v);
|
|
2809
|
-
this.currentLyricLineObjects[v]?.enable();
|
|
2810
|
-
}
|
|
2811
|
-
for (const v of removedIds) {
|
|
2812
|
-
this.bufferedLines.delete(v);
|
|
2813
|
-
this.currentLyricLineObjects[v]?.disable();
|
|
2814
|
-
}
|
|
2815
|
-
if (this.bufferedLines.size > 0) this.scrollToIndex = Math.min(...this.bufferedLines);
|
|
2816
|
-
this.calcLayout();
|
|
2817
|
-
}
|
|
2818
|
-
if (this.bufferedLines.size === 0 && this.processedLines.length > 0) {
|
|
2819
|
-
const lastLine = this.processedLines[this.processedLines.length - 1];
|
|
2820
|
-
const hasBottomContent = this.bottomLine.getElement().innerHTML.trim().length > 0;
|
|
2821
|
-
if (time >= lastLine.endTime) {
|
|
2822
|
-
const targetIndex = hasBottomContent ? this.processedLines.length : this.processedLines.length - 1;
|
|
2823
|
-
if (this.scrollToIndex !== targetIndex) {
|
|
2824
|
-
this.scrollToIndex = targetIndex;
|
|
2825
|
-
this.calcLayout();
|
|
2826
|
-
}
|
|
2827
|
-
}
|
|
2828
|
-
}
|
|
2829
|
-
this.lastCurrentTime = time;
|
|
2830
|
-
}
|
|
2831
|
-
updateDynamicSpringParams() {
|
|
2832
|
-
if (!this.getEnableSpring() || this.processedLines.length === 0) return;
|
|
2833
|
-
const currentIndex = this.scrollToIndex;
|
|
2834
|
-
const currentLine = this.processedLines[currentIndex];
|
|
2835
|
-
const prevLine = this.processedLines[currentIndex - 1];
|
|
2836
|
-
if (currentLine && prevLine) {
|
|
2837
|
-
const interval = currentLine.startTime - (prevLine?.words[0]?.startTime ?? prevLine.startTime);
|
|
2838
|
-
const MIN_INTERVAL = 100;
|
|
2839
|
-
const MAX_INTERVAL = 800;
|
|
2840
|
-
const clampedInterval = Math.max(MIN_INTERVAL, Math.min(MAX_INTERVAL, interval));
|
|
2841
|
-
const MAX_STIFFNESS = 220;
|
|
2842
|
-
const MIN_STIFFNESS = 170;
|
|
2843
|
-
let ratio = 1 - (clampedInterval - MIN_INTERVAL) / (MAX_INTERVAL - MIN_INTERVAL);
|
|
2844
|
-
ratio = ratio ** .2;
|
|
2845
|
-
const targetStiffness = MIN_STIFFNESS + ratio * (MAX_STIFFNESS - MIN_STIFFNESS);
|
|
2846
|
-
const targetDamping = Math.sqrt(targetStiffness) * 2.2;
|
|
2847
|
-
this.setLinePosYSpringParams({
|
|
2848
|
-
stiffness: targetStiffness,
|
|
2849
|
-
damping: targetDamping
|
|
2850
|
-
});
|
|
2851
|
-
}
|
|
3012
|
+
const hasBottomContent = this.bottomLine.getElement().innerHTML.trim().length > 0;
|
|
3013
|
+
const commitResult = commitPlayerTimeState({
|
|
3014
|
+
timelineState,
|
|
3015
|
+
time,
|
|
3016
|
+
processedLines: this.processedLines,
|
|
3017
|
+
hasBottomContent,
|
|
3018
|
+
stateResult
|
|
3019
|
+
});
|
|
3020
|
+
for (const id of commitResult.linesToDisable) this.currentLyricLineObjects[id]?.disable();
|
|
3021
|
+
for (const id of commitResult.linesToEnable) this.currentLyricLineObjects[id]?.enable();
|
|
3022
|
+
if (commitResult.shouldResetScroll) this.resetScroll();
|
|
3023
|
+
if (commitResult.shouldLayout) this.calcLayout();
|
|
2852
3024
|
}
|
|
2853
3025
|
/**
|
|
2854
3026
|
* 重新布局定位歌词行的位置,调用完成后再逐帧调用 `update`
|
|
@@ -2868,102 +3040,108 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2868
3040
|
* @param force 是否绕过弹簧效果强制更新位置
|
|
2869
3041
|
*/
|
|
2870
3042
|
async calcLayout(sync = false, force = false) {
|
|
2871
|
-
const interlude =
|
|
3043
|
+
const interlude = computeCurrentInterlude({
|
|
3044
|
+
currentTime: this.timelineState.currentTime,
|
|
3045
|
+
scrollToIndex: this.timelineState.scrollToIndex,
|
|
3046
|
+
processedLines: this.processedLines
|
|
3047
|
+
});
|
|
2872
3048
|
const isInterludeActive = !!interlude;
|
|
2873
|
-
if (this.targetAlignIndex !== this.scrollToIndex || this.lastInterludeState !== isInterludeActive) {
|
|
2874
|
-
this.lastInterludeState = isInterludeActive;
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
damping: 15
|
|
3049
|
+
if (this.layoutState.targetAlignIndex !== this.timelineState.scrollToIndex || this.layoutState.lastInterludeState !== isInterludeActive) {
|
|
3050
|
+
this.layoutState.lastInterludeState = isInterludeActive;
|
|
3051
|
+
const springParams = computeLinePosYSpringParams({
|
|
3052
|
+
enabled: this.getEnableSpring(),
|
|
3053
|
+
processedLines: this.processedLines,
|
|
3054
|
+
scrollToIndex: this.timelineState.scrollToIndex,
|
|
3055
|
+
isSeeking: this.timelineState.isSeeking,
|
|
3056
|
+
isInterludeActive
|
|
2882
3057
|
});
|
|
2883
|
-
|
|
3058
|
+
if (springParams.shouldUpdate && springParams.params) this.setLinePosYSpringParams(springParams.params);
|
|
2884
3059
|
}
|
|
2885
|
-
let curPos = -this.scrollOffset;
|
|
2886
|
-
const targetAlignIndex = this.scrollToIndex;
|
|
3060
|
+
let curPos = -this.scrollState.scrollOffset;
|
|
3061
|
+
const targetAlignIndex = this.timelineState.scrollToIndex;
|
|
2887
3062
|
let isNextDuet = false;
|
|
2888
|
-
if (interlude) isNextDuet = interlude
|
|
3063
|
+
if (interlude) isNextDuet = interlude.isNextDuet;
|
|
2889
3064
|
else this.interludeDots.setInterlude(void 0);
|
|
2890
3065
|
const dotMargin = (this.baseFontSize || 24) * .4;
|
|
2891
|
-
const totalInterludeHeight = this.interludeDotsSize[1] + dotMargin * 2;
|
|
3066
|
+
const totalInterludeHeight = this.layoutState.interludeDotsSize[1] + dotMargin * 2;
|
|
2892
3067
|
if (interlude) {
|
|
2893
|
-
if (interlude
|
|
3068
|
+
if (interlude.anchorLineIndex !== -1) curPos -= totalInterludeHeight;
|
|
2894
3069
|
}
|
|
2895
3070
|
const LINE_HEIGHT_FALLBACK = this.size[1] / 5;
|
|
2896
|
-
const scrollOffset = this.currentLyricLineObjects.slice(0, targetAlignIndex).reduce((acc, el) => acc + (el.getLine().isBG && this.isPlaying ? 0 : this.lyricLinesSize.get(el)?.[1] ?? LINE_HEIGHT_FALLBACK), 0);
|
|
2897
|
-
this.scrollBoundary
|
|
3071
|
+
const scrollOffset = this.currentLyricLineObjects.slice(0, targetAlignIndex).reduce((acc, el) => acc + (el.getLine().isBG && this.timelineState.isPlaying ? 0 : this.lyricLinesSize.get(el)?.[1] ?? LINE_HEIGHT_FALLBACK), 0);
|
|
3072
|
+
this.scrollState.scrollBoundary.minOffset = -scrollOffset;
|
|
2898
3073
|
curPos -= scrollOffset;
|
|
2899
|
-
curPos += this.size[1] * this.alignPosition;
|
|
3074
|
+
curPos += this.size[1] * this.layoutState.alignPosition;
|
|
2900
3075
|
const curLine = this.currentLyricLineObjects[targetAlignIndex];
|
|
2901
|
-
this.targetAlignIndex = targetAlignIndex;
|
|
3076
|
+
this.layoutState.targetAlignIndex = targetAlignIndex;
|
|
2902
3077
|
const isBottomFocused = targetAlignIndex === this.currentLyricLineObjects.length;
|
|
2903
3078
|
this.bottomLine.setFocused(isBottomFocused);
|
|
2904
3079
|
let targetLineHeight = 0;
|
|
2905
3080
|
if (curLine) targetLineHeight = this.lyricLinesSize.get(curLine)?.[1] ?? LINE_HEIGHT_FALLBACK;
|
|
2906
3081
|
else if (isBottomFocused) targetLineHeight = this.bottomLine.lineSize[1];
|
|
2907
|
-
if (targetLineHeight > 0) switch (this.alignAnchor) {
|
|
2908
|
-
case
|
|
3082
|
+
if (targetLineHeight > 0) switch (this.layoutState.alignAnchor) {
|
|
3083
|
+
case LayoutAlignAnchor.Bottom:
|
|
2909
3084
|
curPos -= targetLineHeight;
|
|
2910
3085
|
break;
|
|
2911
|
-
case
|
|
3086
|
+
case LayoutAlignAnchor.Center:
|
|
2912
3087
|
curPos -= targetLineHeight / 2;
|
|
2913
3088
|
break;
|
|
2914
|
-
case
|
|
3089
|
+
case LayoutAlignAnchor.Top: break;
|
|
2915
3090
|
}
|
|
2916
|
-
const latestIndex = Math.max(...this.bufferedLines);
|
|
3091
|
+
const latestIndex = Math.max(...this.timelineState.bufferedLines);
|
|
2917
3092
|
let delay = 0;
|
|
2918
3093
|
let baseDelay = sync ? 0 : .05;
|
|
2919
3094
|
let setDots = false;
|
|
2920
3095
|
this.currentLyricLineObjects.forEach((lineObj, i) => {
|
|
2921
|
-
const hasBuffered = this.bufferedLines.has(i);
|
|
2922
|
-
const isActive = hasBuffered || i >= this.scrollToIndex && i < latestIndex;
|
|
3096
|
+
const hasBuffered = this.timelineState.bufferedLines.has(i);
|
|
2923
3097
|
const line = lineObj.getLine();
|
|
2924
|
-
const shouldShowDots = interlude && i === interlude
|
|
3098
|
+
const shouldShowDots = interlude && i === interlude.anchorLineIndex + 1;
|
|
2925
3099
|
if (!setDots && shouldShowDots) {
|
|
2926
3100
|
setDots = true;
|
|
2927
3101
|
curPos += dotMargin;
|
|
2928
3102
|
let targetX = 0;
|
|
2929
|
-
if (interlude && isNextDuet) targetX = this.size[0] - this.interludeDotsSize[0];
|
|
3103
|
+
if (interlude && isNextDuet) targetX = this.size[0] - this.layoutState.interludeDotsSize[0];
|
|
2930
3104
|
this.interludeDots.setTransform(targetX, curPos);
|
|
2931
|
-
if (interlude) this.interludeDots.setInterlude([interlude
|
|
2932
|
-
curPos += this.interludeDotsSize[1];
|
|
3105
|
+
if (interlude) this.interludeDots.setInterlude([interlude.startTime, interlude.endTime]);
|
|
3106
|
+
curPos += this.layoutState.interludeDotsSize[1];
|
|
2933
3107
|
curPos += dotMargin;
|
|
2934
3108
|
}
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
3109
|
+
const presentation = computeLinePresentation({
|
|
3110
|
+
line,
|
|
3111
|
+
lineIndex: i,
|
|
3112
|
+
scrollToIndex: this.timelineState.scrollToIndex,
|
|
3113
|
+
latestIndex,
|
|
3114
|
+
hasBuffered,
|
|
3115
|
+
hidePassedLines: this.hidePassedLines,
|
|
3116
|
+
isPlaying: this.timelineState.isPlaying,
|
|
3117
|
+
isNonDynamic: this.isNonDynamic,
|
|
3118
|
+
enableScale: this.enableScale,
|
|
3119
|
+
enableBlur: this.enableBlur,
|
|
3120
|
+
isUserScrolling: this.scrollState.isUserScrolling,
|
|
3121
|
+
isCompact: window.innerWidth <= 1024,
|
|
3122
|
+
interlude
|
|
3123
|
+
});
|
|
3124
|
+
lineObj.setTransform(curPos, presentation.targetScale, presentation.targetOpacity, presentation.blurLevel, force, delay, presentation.renderMode);
|
|
3125
|
+
if (line.isBG && (presentation.isActive || !this.timelineState.isPlaying)) curPos += this.lyricLinesSize.get(lineObj)?.[1] ?? LINE_HEIGHT_FALLBACK;
|
|
2949
3126
|
else if (!line.isBG) curPos += this.lyricLinesSize.get(lineObj)?.[1] ?? LINE_HEIGHT_FALLBACK;
|
|
2950
|
-
if (curPos >= 0 && !this.isSeeking) {
|
|
3127
|
+
if (curPos >= 0 && !this.timelineState.isSeeking) {
|
|
2951
3128
|
if (!line.isBG) delay += baseDelay;
|
|
2952
|
-
if (i >= this.scrollToIndex) baseDelay /= 1.05;
|
|
3129
|
+
if (i >= this.timelineState.scrollToIndex) baseDelay /= 1.05;
|
|
2953
3130
|
}
|
|
2954
3131
|
});
|
|
2955
|
-
this.scrollBoundary
|
|
3132
|
+
this.scrollState.scrollBoundary.maxOffset = curPos + this.scrollState.scrollOffset - this.size[1] / 2;
|
|
2956
3133
|
const bottomIndex = this.currentLyricLineObjects.length;
|
|
2957
|
-
const finalBottomBlur =
|
|
3134
|
+
const finalBottomBlur = computeLineBlur({
|
|
3135
|
+
enableBlur: this.enableBlur,
|
|
3136
|
+
isUserScrolling: this.scrollState.isUserScrolling,
|
|
3137
|
+
isActive: isBottomFocused,
|
|
3138
|
+
itemIndex: bottomIndex,
|
|
3139
|
+
scrollToIndex: this.timelineState.scrollToIndex,
|
|
3140
|
+
latestIndex,
|
|
3141
|
+
isCompact: window.innerWidth <= 1024
|
|
3142
|
+
});
|
|
2958
3143
|
this.bottomLine.setTransform(0, curPos, finalBottomBlur, force, delay);
|
|
2959
3144
|
}
|
|
2960
|
-
calculateBlur(itemIndex, isActive, latestIndex) {
|
|
2961
|
-
if (!this.enableBlur || this.isUserScrolling || isActive) return 0;
|
|
2962
|
-
let blurLevel = 1;
|
|
2963
|
-
if (itemIndex < this.scrollToIndex) blurLevel += Math.abs(this.scrollToIndex - itemIndex) + 1;
|
|
2964
|
-
else blurLevel += Math.abs(itemIndex - Math.max(this.scrollToIndex, latestIndex));
|
|
2965
|
-
return window.innerWidth <= 1024 ? blurLevel * .8 : blurLevel;
|
|
2966
|
-
}
|
|
2967
3145
|
/**
|
|
2968
3146
|
* 设置所有歌词行在横坐标上的弹簧属性,包括重量、弹力和阻力。
|
|
2969
3147
|
*
|
|
@@ -3001,14 +3179,13 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
3001
3179
|
for (const lineObj of this.currentLyricLineObjects) if (lineObj.getLine().isBG) lineObj.lineTransforms.scale.updateParams(this.scaleForBGSpringParams);
|
|
3002
3180
|
else lineObj.lineTransforms.scale.updateParams(this.scaleSpringParams);
|
|
3003
3181
|
}
|
|
3004
|
-
isPlaying = true;
|
|
3005
3182
|
/**
|
|
3006
3183
|
* 暂停部分效果演出,目前会暂停播放间奏点的动画,且将背景歌词显示出来
|
|
3007
3184
|
*/
|
|
3008
3185
|
pause() {
|
|
3009
3186
|
this.interludeDots.pause();
|
|
3010
|
-
if (this.isPlaying) {
|
|
3011
|
-
this.isPlaying = false;
|
|
3187
|
+
if (this.timelineState.isPlaying) {
|
|
3188
|
+
this.timelineState.isPlaying = false;
|
|
3012
3189
|
this.calcLayout();
|
|
3013
3190
|
}
|
|
3014
3191
|
}
|
|
@@ -3017,8 +3194,8 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
3017
3194
|
*/
|
|
3018
3195
|
resume() {
|
|
3019
3196
|
this.interludeDots.resume();
|
|
3020
|
-
if (!this.isPlaying) {
|
|
3021
|
-
this.isPlaying = true;
|
|
3197
|
+
if (!this.timelineState.isPlaying) {
|
|
3198
|
+
this.timelineState.isPlaying = true;
|
|
3022
3199
|
this.calcLayout();
|
|
3023
3200
|
}
|
|
3024
3201
|
}
|
|
@@ -3051,8 +3228,7 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
3051
3228
|
* 请在用户完成滚动点击跳转歌词时调用本事件再调用 `calcLayout` 以正确滚动到目标位置
|
|
3052
3229
|
*/
|
|
3053
3230
|
resetScroll() {
|
|
3054
|
-
this.
|
|
3055
|
-
this.scrollOffset = 0;
|
|
3231
|
+
resetPlayerScrollState(this.scrollState);
|
|
3056
3232
|
clearTimeout(this.scrolledHandler);
|
|
3057
3233
|
}
|
|
3058
3234
|
/**
|
|
@@ -3071,7 +3247,7 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
3071
3247
|
* @returns 当前播放位置
|
|
3072
3248
|
*/
|
|
3073
3249
|
getCurrentTime() {
|
|
3074
|
-
return this.currentTime;
|
|
3250
|
+
return this.timelineState.currentTime;
|
|
3075
3251
|
}
|
|
3076
3252
|
getElement() {
|
|
3077
3253
|
return this.element;
|
|
@@ -3082,6 +3258,13 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
3082
3258
|
window.removeEventListener("pagehide", this.onPageHide);
|
|
3083
3259
|
}
|
|
3084
3260
|
};
|
|
3261
|
+
//#endregion
|
|
3262
|
+
//#region src/utils/is-cjk.ts
|
|
3263
|
+
const isCJK = (char) => {
|
|
3264
|
+
return /^[\p{Unified_Ideograph}\u0800-\u9FFC]+$/u.test(char);
|
|
3265
|
+
};
|
|
3266
|
+
//#endregion
|
|
3267
|
+
//#region src/lyric-player/base/line.ts
|
|
3085
3268
|
/**
|
|
3086
3269
|
* 所有标准歌词行的基类
|
|
3087
3270
|
* @internal
|
|
@@ -3106,7 +3289,7 @@ var LyricLineBase = class extends EventTarget {
|
|
|
3106
3289
|
*/
|
|
3107
3290
|
static graphemeSegmenter = typeof Intl !== "undefined" && Intl.Segmenter ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
|
|
3108
3291
|
onLineSizeChange(_size) {}
|
|
3109
|
-
setTransform(top = this.top, scale = this.scale, opacity = this.opacity, blur = this.blur, _force = false, delay = 0, _mode =
|
|
3292
|
+
setTransform(top = this.top, scale = this.scale, opacity = this.opacity, blur = this.blur, _force = false, delay = 0, _mode = LyricLineRenderMode.SOLID) {
|
|
3110
3293
|
this.top = top;
|
|
3111
3294
|
this.scale = scale;
|
|
3112
3295
|
this.opacity = opacity;
|
|
@@ -3151,6 +3334,13 @@ const NORMAL_BREAK_PENALTY_RATIO = .5;
|
|
|
3151
3334
|
*/
|
|
3152
3335
|
const SPACE_BREAK_REWARD_RATIO = .4;
|
|
3153
3336
|
/**
|
|
3337
|
+
* 在标点符号处断开的奖励比例
|
|
3338
|
+
*
|
|
3339
|
+
* 比空格更高以便优先一点在标点处换行
|
|
3340
|
+
*/
|
|
3341
|
+
const PUNCTUATION_BREAK_REWARD_RATIO = .6;
|
|
3342
|
+
const PUNCTUATION_REGEX = /[,.;:!?,。;:!?、)】》」』’”)[\]}>~…]$/;
|
|
3343
|
+
/**
|
|
3154
3344
|
* 计算平均行长度的断点位置
|
|
3155
3345
|
* @param children 子节点信息
|
|
3156
3346
|
* @param containerWidth 容器可用内容宽度
|
|
@@ -3191,9 +3381,13 @@ function calcBalancedBreaks(children, containerWidth, fullText, segmenter) {
|
|
|
3191
3381
|
else continue;
|
|
3192
3382
|
else lineCost = (containerWidth - w) ** 2;
|
|
3193
3383
|
let breakPenalty = 0;
|
|
3194
|
-
if (j < n)
|
|
3195
|
-
|
|
3196
|
-
|
|
3384
|
+
if (j < n) {
|
|
3385
|
+
const prevChild = children[j - 1];
|
|
3386
|
+
if (PUNCTUATION_REGEX.test(prevChild.text)) breakPenalty = -((containerWidth * PUNCTUATION_BREAK_REWARD_RATIO) ** 2);
|
|
3387
|
+
else if (prevChild.isSpace) breakPenalty = -((containerWidth * SPACE_BREAK_REWARD_RATIO) ** 2);
|
|
3388
|
+
else if (cjkBoundaries.has(charOffsets[j])) breakPenalty = PENALTY_CJK;
|
|
3389
|
+
else breakPenalty = PENALTY_NORMAL;
|
|
3390
|
+
}
|
|
3197
3391
|
const totalCost = lineCost + breakPenalty + dp[j];
|
|
3198
3392
|
if (totalCost < dp[i]) {
|
|
3199
3393
|
dp[i] = totalCost;
|
|
@@ -3218,13 +3412,9 @@ function getMeasurementContext() {
|
|
|
3218
3412
|
/**
|
|
3219
3413
|
* 用于平衡歌词行在换行后的各行长度
|
|
3220
3414
|
*/
|
|
3221
|
-
var LineBalancer = class
|
|
3415
|
+
var LineBalancer = class {
|
|
3222
3416
|
isBalancing = false;
|
|
3223
3417
|
lastBalancedContainerWidth = -1;
|
|
3224
|
-
/**
|
|
3225
|
-
* 防止误差导致的意外换行
|
|
3226
|
-
*/
|
|
3227
|
-
static SAFE_WIDTH_PADDING = 25;
|
|
3228
3418
|
constructor(mainElement) {
|
|
3229
3419
|
this.mainElement = mainElement;
|
|
3230
3420
|
}
|
|
@@ -3251,32 +3441,49 @@ var LineBalancer = class LineBalancer {
|
|
|
3251
3441
|
adapter.resetDOM();
|
|
3252
3442
|
const prevWhiteSpace = this.mainElement.style.whiteSpace;
|
|
3253
3443
|
this.mainElement.style.whiteSpace = "nowrap";
|
|
3444
|
+
const parentElement = this.mainElement.parentElement;
|
|
3445
|
+
let prevTransform = "";
|
|
3446
|
+
let transformChanged = false;
|
|
3447
|
+
if (parentElement) {
|
|
3448
|
+
prevTransform = parentElement.style.transform;
|
|
3449
|
+
if (prevTransform && prevTransform !== "none") {
|
|
3450
|
+
parentElement.style.transform = "none";
|
|
3451
|
+
transformChanged = true;
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
let lockAcquired = false;
|
|
3254
3455
|
try {
|
|
3255
|
-
const
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3456
|
+
const { childInfos, fullText } = adapter.buildChildInfos();
|
|
3457
|
+
let layoutWidth = childInfos.reduce((sum, c) => sum + c.width, 0);
|
|
3458
|
+
if (adapter.needsCalibration) {
|
|
3459
|
+
const range = document.createRange();
|
|
3460
|
+
range.selectNodeContents(this.mainElement);
|
|
3461
|
+
const visualWidth = range.getBoundingClientRect().width;
|
|
3462
|
+
if (layoutWidth > 0 && visualWidth > 0) {
|
|
3463
|
+
const scale = visualWidth / layoutWidth;
|
|
3464
|
+
for (const info of childInfos) info.width *= scale;
|
|
3465
|
+
}
|
|
3466
|
+
layoutWidth = visualWidth;
|
|
3467
|
+
}
|
|
3468
|
+
const safeContainerWidth = Math.max(1, containerWidth);
|
|
3469
|
+
if (layoutWidth <= safeContainerWidth) {
|
|
3260
3470
|
this.lastBalancedContainerWidth = containerWidth;
|
|
3261
3471
|
return;
|
|
3262
3472
|
}
|
|
3263
|
-
const { childInfos, fullText } = adapter.buildChildInfos();
|
|
3264
|
-
const measuredTotal = childInfos.reduce((sum, c) => sum + c.width, 0);
|
|
3265
|
-
if (measuredTotal > 0 && lineWidth > 0) {
|
|
3266
|
-
const scale = lineWidth / measuredTotal;
|
|
3267
|
-
for (const info of childInfos) info.width *= scale;
|
|
3268
|
-
}
|
|
3269
3473
|
const breaks = calcBalancedBreaks(childInfos, safeContainerWidth, fullText, wordSegmenter);
|
|
3270
3474
|
if (breaks.length === 0) {
|
|
3271
3475
|
this.lastBalancedContainerWidth = containerWidth;
|
|
3272
3476
|
return;
|
|
3273
3477
|
}
|
|
3274
3478
|
this.isBalancing = true;
|
|
3479
|
+
lockAcquired = true;
|
|
3275
3480
|
adapter.applyBreaks(breaks, childInfos);
|
|
3276
3481
|
this.lastBalancedContainerWidth = containerWidth;
|
|
3277
3482
|
this.isBalancing = false;
|
|
3278
3483
|
} finally {
|
|
3279
3484
|
this.mainElement.style.whiteSpace = prevWhiteSpace;
|
|
3485
|
+
if (transformChanged && parentElement) parentElement.style.transform = prevTransform;
|
|
3486
|
+
if (lockAcquired) this.isBalancing = false;
|
|
3280
3487
|
}
|
|
3281
3488
|
}
|
|
3282
3489
|
balanceDynamicLineBreaks(containerWidth, wordSegmenter) {
|
|
@@ -3309,7 +3516,7 @@ var LineBalancer = class LineBalancer {
|
|
|
3309
3516
|
const marginLeft = Number.parseFloat(elStyle.marginLeft) || 0;
|
|
3310
3517
|
const marginRight = Number.parseFloat(elStyle.marginRight) || 0;
|
|
3311
3518
|
childInfos.push({
|
|
3312
|
-
width:
|
|
3519
|
+
width: clampPositive(rect.width + marginLeft + marginRight),
|
|
3313
3520
|
text: el.textContent ?? "",
|
|
3314
3521
|
isSpace: false
|
|
3315
3522
|
});
|
|
@@ -3325,7 +3532,8 @@ var LineBalancer = class LineBalancer {
|
|
|
3325
3532
|
const breakIndex = breaks[i];
|
|
3326
3533
|
if (breakIndex >= 0 && breakIndex < infoToNode.length) this.mainElement.insertBefore(document.createElement("br"), infoToNode[breakIndex]);
|
|
3327
3534
|
}
|
|
3328
|
-
}
|
|
3535
|
+
},
|
|
3536
|
+
needsCalibration: false
|
|
3329
3537
|
}, wordSegmenter);
|
|
3330
3538
|
}
|
|
3331
3539
|
balanceNonDynamicLineBreaks(containerWidth, computedStyle, wordSegmenter) {
|
|
@@ -3368,7 +3576,8 @@ var LineBalancer = class LineBalancer {
|
|
|
3368
3576
|
fragment.appendChild(document.createTextNode(childInfos[i].text));
|
|
3369
3577
|
}
|
|
3370
3578
|
this.mainElement.appendChild(fragment);
|
|
3371
|
-
}
|
|
3579
|
+
},
|
|
3580
|
+
needsCalibration: true
|
|
3372
3581
|
}, wordSegmenter);
|
|
3373
3582
|
}
|
|
3374
3583
|
};
|
|
@@ -3511,34 +3720,34 @@ function matrix4ToCSS(m, fractionDigits = 4) {
|
|
|
3511
3720
|
}
|
|
3512
3721
|
//#endregion
|
|
3513
3722
|
//#region src/lyric-player/dom/lyric-line.ts
|
|
3514
|
-
const ANIMATION_FRAME_QUANTITY
|
|
3515
|
-
const norNum
|
|
3516
|
-
const EMP_EASING_MID
|
|
3517
|
-
const beginNum
|
|
3518
|
-
const endNum
|
|
3519
|
-
const bezIn
|
|
3520
|
-
const bezOut
|
|
3521
|
-
const makeEmpEasing
|
|
3522
|
-
return (x) => x < mid ? bezIn
|
|
3723
|
+
const ANIMATION_FRAME_QUANTITY = 32;
|
|
3724
|
+
const norNum = (min, max) => (x) => clamp01((x - min) / (max - min));
|
|
3725
|
+
const EMP_EASING_MID = .5;
|
|
3726
|
+
const beginNum = norNum(0, EMP_EASING_MID);
|
|
3727
|
+
const endNum = norNum(EMP_EASING_MID, 1);
|
|
3728
|
+
const bezIn = (0, bezier_easing.default)(.2, .4, .58, 1);
|
|
3729
|
+
const bezOut = (0, bezier_easing.default)(.3, 0, .58, 1);
|
|
3730
|
+
const makeEmpEasing = (mid) => {
|
|
3731
|
+
return (x) => x < mid ? bezIn(beginNum(x)) : 1 - bezOut(endNum(x));
|
|
3523
3732
|
};
|
|
3524
|
-
function generateFadeGradient
|
|
3733
|
+
function generateFadeGradient(width, padding = 0, bright = "rgba(0,0,0,var(--bright-mask-alpha, 1.0))", dark = "rgba(0,0,0,var(--dark-mask-alpha, 1.0))") {
|
|
3525
3734
|
const totalAspect = 2 + width + padding;
|
|
3526
3735
|
const widthInTotal = width / totalAspect;
|
|
3527
3736
|
const leftPos = (1 - widthInTotal) / 2;
|
|
3528
3737
|
return [`linear-gradient(to right,${bright} ${leftPos * 100}%,${dark} ${(leftPos + widthInTotal) * 100}%)`, totalAspect];
|
|
3529
3738
|
}
|
|
3530
|
-
var RawLyricLineMouseEvent
|
|
3739
|
+
var RawLyricLineMouseEvent = class extends MouseEvent {
|
|
3531
3740
|
constructor(line, event) {
|
|
3532
3741
|
super(event.type, event);
|
|
3533
3742
|
this.line = line;
|
|
3534
3743
|
}
|
|
3535
3744
|
};
|
|
3536
|
-
var LyricLineEl
|
|
3745
|
+
var LyricLineEl = class extends LyricLineBase {
|
|
3537
3746
|
element = document.createElement("div");
|
|
3538
3747
|
splittedWords = [];
|
|
3539
3748
|
built = false;
|
|
3540
3749
|
lineSize = [0, 0];
|
|
3541
|
-
renderMode =
|
|
3750
|
+
renderMode = LyricLineRenderMode.SOLID;
|
|
3542
3751
|
currentBrightAlpha = 1;
|
|
3543
3752
|
currentDarkAlpha = .2;
|
|
3544
3753
|
targetBrightAlpha = 1;
|
|
@@ -3579,7 +3788,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3579
3788
|
}
|
|
3580
3789
|
listenersMap = /* @__PURE__ */ new Map();
|
|
3581
3790
|
onMouseEvent = (e) => {
|
|
3582
|
-
const wrapped = new RawLyricLineMouseEvent
|
|
3791
|
+
const wrapped = new RawLyricLineMouseEvent(this, e);
|
|
3583
3792
|
for (const listener of this.listenersMap.get(e.type) ?? []) listener.call(this, wrapped);
|
|
3584
3793
|
if (!this.dispatchEvent(wrapped) || wrapped.defaultPrevented) {
|
|
3585
3794
|
e.preventDefault();
|
|
@@ -3615,30 +3824,28 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3615
3824
|
return true;
|
|
3616
3825
|
}
|
|
3617
3826
|
isEnabled = false;
|
|
3618
|
-
async enable(maskAnimationTime = this.
|
|
3827
|
+
async enable(maskAnimationTime = this.lyricPlayer.getCurrentTime(), shouldPlay = this.lyricPlayer.getIsPlaying()) {
|
|
3619
3828
|
this.isEnabled = true;
|
|
3620
3829
|
this.element.classList.add(lyric_player_module_default.active);
|
|
3621
3830
|
const main = this.element.children[0];
|
|
3622
|
-
const relativeTime =
|
|
3623
|
-
const actualMaskTime = maskAnimationTime === this.lyricLine.startTime ? this.lyricPlayer.getCurrentTime() : maskAnimationTime;
|
|
3624
|
-
const maskRelativeTime = Math.max(0, actualMaskTime - this.lyricLine.startTime);
|
|
3831
|
+
const relativeTime = clampPositive(maskAnimationTime - this.lyricLine.startTime);
|
|
3625
3832
|
for (const word of this.splittedWords) {
|
|
3626
3833
|
for (const a of word.elementAnimations) {
|
|
3627
3834
|
a.currentTime = relativeTime;
|
|
3628
3835
|
a.playbackRate = 1;
|
|
3629
3836
|
const timing = a.effect?.getComputedTiming();
|
|
3630
|
-
const duration = timing?.duration
|
|
3631
|
-
const endTime = (timing?.delay
|
|
3837
|
+
const duration = Number(timing?.duration ?? 0);
|
|
3838
|
+
const endTime = Number(timing?.delay ?? 0) + duration;
|
|
3632
3839
|
if (shouldPlay && relativeTime < endTime) a.play();
|
|
3633
3840
|
else a.pause();
|
|
3634
3841
|
}
|
|
3635
3842
|
for (const a of word.maskAnimations) {
|
|
3636
|
-
const t = Math.min(this.totalDuration,
|
|
3843
|
+
const t = Math.min(this.totalDuration, relativeTime);
|
|
3637
3844
|
a.currentTime = t;
|
|
3638
3845
|
a.playbackRate = 1;
|
|
3639
3846
|
const timing = a.effect?.getComputedTiming();
|
|
3640
|
-
const duration = timing?.duration
|
|
3641
|
-
const endTime = (timing?.delay
|
|
3847
|
+
const duration = Number(timing?.duration ?? 0);
|
|
3848
|
+
const endTime = Number(timing?.delay ?? 0) + duration;
|
|
3642
3849
|
if (shouldPlay && t < endTime) a.play();
|
|
3643
3850
|
else a.pause();
|
|
3644
3851
|
}
|
|
@@ -3648,7 +3855,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3648
3855
|
disable() {
|
|
3649
3856
|
this.isEnabled = false;
|
|
3650
3857
|
this.element.classList.remove(lyric_player_module_default.active);
|
|
3651
|
-
this.renderMode =
|
|
3858
|
+
this.renderMode = LyricLineRenderMode.SOLID;
|
|
3652
3859
|
const main = this.element.children[0];
|
|
3653
3860
|
for (const word of this.splittedWords) {
|
|
3654
3861
|
for (const a of word.elementAnimations) if (a.id === "float-word" || a.id.includes("emphasize-word-float-only")) {
|
|
@@ -3689,7 +3896,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3689
3896
|
setMaskAnimationState(maskAnimationTime = 0) {
|
|
3690
3897
|
const t = maskAnimationTime - this.lyricLine.startTime;
|
|
3691
3898
|
for (const word of this.splittedWords) for (const a of word.maskAnimations) {
|
|
3692
|
-
a.currentTime =
|
|
3899
|
+
a.currentTime = clamp(t, 0, this.totalDuration);
|
|
3693
3900
|
a.playbackRate = 1;
|
|
3694
3901
|
if (t >= 0 && t < this.totalDuration) a.play();
|
|
3695
3902
|
else a.pause();
|
|
@@ -3880,7 +4087,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3880
4087
|
return a;
|
|
3881
4088
|
}
|
|
3882
4089
|
initEmphasizeAnimation(word, characterElements, duration, delay, rubyCharCount) {
|
|
3883
|
-
const de =
|
|
4090
|
+
const de = clampPositive(delay);
|
|
3884
4091
|
let du = Math.max(1e3, duration);
|
|
3885
4092
|
const anchorCharCount = rubyCharCount > 0 ? rubyCharCount : Math.max(1, characterElements.length);
|
|
3886
4093
|
let result = [];
|
|
@@ -3898,12 +4105,12 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3898
4105
|
amount = Math.min(1.2, amount);
|
|
3899
4106
|
blur = Math.min(.8, blur);
|
|
3900
4107
|
const animateDu = Number.isFinite(du) ? du : 0;
|
|
3901
|
-
const empEasing = makeEmpEasing
|
|
4108
|
+
const empEasing = makeEmpEasing(EMP_EASING_MID);
|
|
3902
4109
|
result = characterElements.flatMap((el, i, arr) => {
|
|
3903
4110
|
const wordDe = de + du / 2.5 / anchorCharCount * i;
|
|
3904
4111
|
const result = [];
|
|
3905
|
-
const frames = new Array(ANIMATION_FRAME_QUANTITY
|
|
3906
|
-
const x = (j + 1) / ANIMATION_FRAME_QUANTITY
|
|
4112
|
+
const frames = new Array(ANIMATION_FRAME_QUANTITY).fill(0).map((_, j) => {
|
|
4113
|
+
const x = (j + 1) / ANIMATION_FRAME_QUANTITY;
|
|
3907
4114
|
const transX = empEasing(x);
|
|
3908
4115
|
const glowLevel = empEasing(x) * blur;
|
|
3909
4116
|
const mat = scaleMatrix4(createMatrix4(), 1 + transX * .1 * amount);
|
|
@@ -3928,8 +4135,8 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3928
4135
|
};
|
|
3929
4136
|
glow.pause();
|
|
3930
4137
|
result.push(glow);
|
|
3931
|
-
const floatFrame = new Array(ANIMATION_FRAME_QUANTITY
|
|
3932
|
-
const x = (j + 1) / ANIMATION_FRAME_QUANTITY
|
|
4138
|
+
const floatFrame = new Array(ANIMATION_FRAME_QUANTITY).fill(0).map((_, j) => {
|
|
4139
|
+
const x = (j + 1) / ANIMATION_FRAME_QUANTITY;
|
|
3933
4140
|
let y = Math.sin(x * Math.PI);
|
|
3934
4141
|
if (this.lyricLine.isBG) y *= 2;
|
|
3935
4142
|
return {
|
|
@@ -3988,7 +4195,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3988
4195
|
word.width = wordEl.clientWidth;
|
|
3989
4196
|
word.height = wordEl.clientHeight;
|
|
3990
4197
|
const fadeWidth = word.height * this.lyricPlayer.getWordFadeWidth();
|
|
3991
|
-
const [maskImage, totalAspect] = generateFadeGradient
|
|
4198
|
+
const [maskImage, totalAspect] = generateFadeGradient(fadeWidth / word.width);
|
|
3992
4199
|
const totalAspectStr = `${totalAspect * 100}% 100%`;
|
|
3993
4200
|
if (this.lyricPlayer.supportMaskImage) {
|
|
3994
4201
|
wordEl.style.maskImage = maskImage;
|
|
@@ -4009,12 +4216,12 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4009
4216
|
}
|
|
4010
4217
|
}
|
|
4011
4218
|
generateWebAnimationBasedMaskImage() {
|
|
4012
|
-
const totalFadeDuration = Math.max(this.splittedWords.
|
|
4219
|
+
const totalFadeDuration = Math.max(0, ...this.splittedWords.map((w) => w.endTime), this.lyricLine.endTime) - this.lyricLine.startTime;
|
|
4013
4220
|
this.splittedWords.forEach((word, i) => {
|
|
4014
4221
|
const wordEl = word.mainElement;
|
|
4015
4222
|
if (wordEl) {
|
|
4016
4223
|
const fadeWidth = word.height * this.lyricPlayer.getWordFadeWidth();
|
|
4017
|
-
const [maskImage, totalAspect] = generateFadeGradient
|
|
4224
|
+
const [maskImage, totalAspect] = generateFadeGradient(fadeWidth / (word.width + word.padding * 2));
|
|
4018
4225
|
const totalAspectStr = `${totalAspect * 100}% 100%`;
|
|
4019
4226
|
if (this.lyricPlayer.supportMaskImage) {
|
|
4020
4227
|
wordEl.style.maskImage = maskImage;
|
|
@@ -4029,7 +4236,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4029
4236
|
}
|
|
4030
4237
|
const widthBeforeSelf = this.splittedWords.slice(0, i).reduce((a, b) => a + b.width, 0) + (this.splittedWords[0] ? fadeWidth : 0);
|
|
4031
4238
|
const minOffset = -(word.width + word.padding * 2 + fadeWidth);
|
|
4032
|
-
const clampOffset = (x) =>
|
|
4239
|
+
const clampOffset = (x) => clamp(x, minOffset, 0);
|
|
4033
4240
|
let curPos = -widthBeforeSelf - word.width - word.padding - fadeWidth;
|
|
4034
4241
|
let timeOffset = 0;
|
|
4035
4242
|
const frames = [];
|
|
@@ -4037,7 +4244,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4037
4244
|
let lastTime = 0;
|
|
4038
4245
|
const pushFrame = () => {
|
|
4039
4246
|
const moveOffset = curPos - lastPos;
|
|
4040
|
-
const time =
|
|
4247
|
+
const time = clamp01(timeOffset);
|
|
4041
4248
|
const duration = time - lastTime;
|
|
4042
4249
|
const d = Math.abs(duration / moveOffset);
|
|
4043
4250
|
if (curPos > minOffset && lastPos < minOffset) {
|
|
@@ -4077,7 +4284,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4077
4284
|
lastTimeStamp = curTimeStamp;
|
|
4078
4285
|
}
|
|
4079
4286
|
{
|
|
4080
|
-
const fadeDuration =
|
|
4287
|
+
const fadeDuration = clampPositive(otherWord.endTime - otherWord.startTime);
|
|
4081
4288
|
const rubySegments = this.getRubySegments(otherWord);
|
|
4082
4289
|
const rubyCharCount = rubySegments.reduce((total, ruby) => total + ruby.word.length, 0);
|
|
4083
4290
|
if (rubyCharCount > 0) {
|
|
@@ -4093,7 +4300,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4093
4300
|
timeOffset += rubyStaticDuration / totalFadeDuration;
|
|
4094
4301
|
if (rubyStaticDuration > 0) pushFrame();
|
|
4095
4302
|
lastTimeStamp = rubyStartStamp;
|
|
4096
|
-
const perCharDuration =
|
|
4303
|
+
const perCharDuration = clampPositive(rubyEnd - rubyStart) / ruby.word.length;
|
|
4097
4304
|
for (let rubyCharIndex = 0; rubyCharIndex < ruby.word.length; rubyCharIndex++) {
|
|
4098
4305
|
timeOffset += perCharDuration / totalFadeDuration;
|
|
4099
4306
|
curPos += widthPerChar;
|
|
@@ -4143,10 +4350,10 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4143
4350
|
return this.element;
|
|
4144
4351
|
}
|
|
4145
4352
|
updateMaskAlphaTargets(scale) {
|
|
4146
|
-
const factor =
|
|
4353
|
+
const factor = clamp01((scale - .97) / .03);
|
|
4147
4354
|
const dynamicDarkAlpha = factor * .2 + .2;
|
|
4148
4355
|
const dynamicBrightAlpha = factor * .8 + .2;
|
|
4149
|
-
if (this.renderMode ===
|
|
4356
|
+
if (this.renderMode === LyricLineRenderMode.SOLID) {
|
|
4150
4357
|
this.targetBrightAlpha = dynamicDarkAlpha;
|
|
4151
4358
|
this.targetDarkAlpha = dynamicDarkAlpha;
|
|
4152
4359
|
} else {
|
|
@@ -4168,7 +4375,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4168
4375
|
this.element.style.setProperty("--bright-mask-alpha", this.currentBrightAlpha.toFixed(3));
|
|
4169
4376
|
this.element.style.setProperty("--dark-mask-alpha", this.currentDarkAlpha.toFixed(3));
|
|
4170
4377
|
}
|
|
4171
|
-
setTransform(top = this.top, scale = this.scale, opacity = 1, blur = 0, force = false, delay = 0, mode =
|
|
4378
|
+
setTransform(top = this.top, scale = this.scale, opacity = 1, blur = 0, force = false, delay = 0, mode = LyricLineRenderMode.SOLID) {
|
|
4172
4379
|
super.setTransform(top, scale, opacity, blur, force, delay);
|
|
4173
4380
|
this.renderMode = mode;
|
|
4174
4381
|
const beforeInSight = this.isInSight;
|
|
@@ -4325,7 +4532,7 @@ var DomLyricPlayer = class extends LyricPlayerBase {
|
|
|
4325
4532
|
line.dispose();
|
|
4326
4533
|
}
|
|
4327
4534
|
this.currentLyricLineObjects = this.processedLines.map((line, i) => {
|
|
4328
|
-
const lineEl = new LyricLineEl
|
|
4535
|
+
const lineEl = new LyricLineEl(this, line);
|
|
4329
4536
|
lineEl.addMouseEventListener("click", this.onLineClickedHandler);
|
|
4330
4537
|
lineEl.addMouseEventListener("contextmenu", this.onLineClickedHandler);
|
|
4331
4538
|
this.lyricLinesIndexes.set(lineEl, i);
|
|
@@ -4351,9 +4558,9 @@ var DomLyricPlayer = class extends LyricPlayerBase {
|
|
|
4351
4558
|
for (const line of this.currentLyricLineObjects) line.resume();
|
|
4352
4559
|
}
|
|
4353
4560
|
update(delta = 0) {
|
|
4354
|
-
if (!this.initialLayoutFinished) return;
|
|
4561
|
+
if (!this.timelineState.initialLayoutFinished) return;
|
|
4355
4562
|
super.update(delta);
|
|
4356
|
-
if (!this.supportMaskImage) this.element.style.setProperty("--amll-player-time", `${this.currentTime}`);
|
|
4563
|
+
if (!this.supportMaskImage) this.element.style.setProperty("--amll-player-time", `${this.timelineState.currentTime}`);
|
|
4357
4564
|
if (!this.isPageVisible) return;
|
|
4358
4565
|
const deltaS = delta / 1e3;
|
|
4359
4566
|
for (const line of this.currentLyricLineObjects) line.update(deltaS);
|
|
@@ -4367,895 +4574,11 @@ var DomLyricPlayer = class extends LyricPlayerBase {
|
|
|
4367
4574
|
}
|
|
4368
4575
|
};
|
|
4369
4576
|
//#endregion
|
|
4370
|
-
//#region src/utils/debounce.ts
|
|
4371
|
-
function debounce(cb, wait = 20) {
|
|
4372
|
-
let h;
|
|
4373
|
-
const callable = (...args) => {
|
|
4374
|
-
clearTimeout(h);
|
|
4375
|
-
h = setTimeout(() => cb(...args), wait);
|
|
4376
|
-
};
|
|
4377
|
-
return callable;
|
|
4378
|
-
}
|
|
4379
|
-
//#endregion
|
|
4380
|
-
//#region src/lyric-player/dom-slim/index.module.css
|
|
4381
|
-
var index_module_default = {
|
|
4382
|
-
"active": "KxF9Iq_active",
|
|
4383
|
-
"duet": "KxF9Iq_duet",
|
|
4384
|
-
"enabled": "KxF9Iq_enabled",
|
|
4385
|
-
"hasDuetLine": "KxF9Iq_hasDuetLine",
|
|
4386
|
-
"interludeDots": "KxF9Iq_interludeDots",
|
|
4387
|
-
"lyricBgLine": "KxF9Iq_lyricBgLine",
|
|
4388
|
-
"lyricDuetLine": "KxF9Iq_lyricDuetLine",
|
|
4389
|
-
"lyricLine": "KxF9Iq_lyricLine",
|
|
4390
|
-
"lyricMainLine": "KxF9Iq_lyricMainLine",
|
|
4391
|
-
"lyricSubLine": "KxF9Iq_lyricSubLine",
|
|
4392
|
-
"romanWord": "KxF9Iq_romanWord",
|
|
4393
|
-
"rubyWord": "KxF9Iq_rubyWord",
|
|
4394
|
-
"tmpDisableTransition": "KxF9Iq_tmpDisableTransition",
|
|
4395
|
-
"wordBody": "KxF9Iq_wordBody",
|
|
4396
|
-
"wordWithRuby": "KxF9Iq_wordWithRuby"
|
|
4397
|
-
};
|
|
4398
|
-
//#endregion
|
|
4399
|
-
//#region src/utils/mutex.ts
|
|
4400
|
-
function mutexifyFunction(func) {
|
|
4401
|
-
const awaitingTasks = [];
|
|
4402
|
-
function processNextTask() {
|
|
4403
|
-
const task = awaitingTasks[0];
|
|
4404
|
-
if (!task) return;
|
|
4405
|
-
func(...task.args).then((value) => {
|
|
4406
|
-
task.resolve(value);
|
|
4407
|
-
}).catch((reason) => {
|
|
4408
|
-
task.reject(reason);
|
|
4409
|
-
}).finally(() => {
|
|
4410
|
-
awaitingTasks.shift();
|
|
4411
|
-
if (awaitingTasks.length > 0) processNextTask();
|
|
4412
|
-
});
|
|
4413
|
-
}
|
|
4414
|
-
return ((...args) => {
|
|
4415
|
-
return new Promise((resolve, reject) => {
|
|
4416
|
-
awaitingTasks.push({
|
|
4417
|
-
resolve,
|
|
4418
|
-
reject,
|
|
4419
|
-
args
|
|
4420
|
-
});
|
|
4421
|
-
if (awaitingTasks.length === 1) processNextTask();
|
|
4422
|
-
});
|
|
4423
|
-
});
|
|
4424
|
-
}
|
|
4425
|
-
//#endregion
|
|
4426
|
-
//#region src/lyric-player/dom-slim/lyric-line.ts
|
|
4427
|
-
const ANIMATION_FRAME_QUANTITY = 32;
|
|
4428
|
-
const norNum = (min, max) => (x) => Math.min(1, Math.max(0, (x - min) / (max - min)));
|
|
4429
|
-
const EMP_EASING_MID = .5;
|
|
4430
|
-
const beginNum = norNum(0, EMP_EASING_MID);
|
|
4431
|
-
const endNum = norNum(EMP_EASING_MID, 1);
|
|
4432
|
-
const bezIn = (0, bezier_easing.default)(.2, .4, .58, 1);
|
|
4433
|
-
const bezOut = (0, bezier_easing.default)(.3, 0, .58, 1);
|
|
4434
|
-
const makeEmpEasing = (mid) => {
|
|
4435
|
-
return (x) => x < mid ? bezIn(beginNum(x)) : 1 - bezOut(endNum(x));
|
|
4436
|
-
};
|
|
4437
|
-
function generateFadeGradient(width, padding = 0, bright = "rgba(0,0,0,var(--bright-mask-alpha, 1.0))", dark = "rgba(0,0,0,var(--dark-mask-alpha, 1.0))") {
|
|
4438
|
-
const totalAspect = 2 + width + padding;
|
|
4439
|
-
const widthInTotal = width / totalAspect;
|
|
4440
|
-
const leftPos = (1 - widthInTotal) / 2;
|
|
4441
|
-
return [`linear-gradient(to right,${bright} ${leftPos * 100}%,${dark} ${(leftPos + widthInTotal) * 100}%)`, totalAspect];
|
|
4442
|
-
}
|
|
4443
|
-
var RawLyricLineMouseEvent = class extends MouseEvent {
|
|
4444
|
-
constructor(line, event) {
|
|
4445
|
-
super(event.type, event);
|
|
4446
|
-
this.line = line;
|
|
4447
|
-
}
|
|
4448
|
-
};
|
|
4449
|
-
function getScaleFromTransform(transform) {
|
|
4450
|
-
const match = transform.match(/matrix\(([^)]+)\)/);
|
|
4451
|
-
if (match) {
|
|
4452
|
-
const values = match[1].split(", ");
|
|
4453
|
-
return (Number.parseFloat(values[0]) + Number.parseFloat(values[3])) / 2;
|
|
4454
|
-
}
|
|
4455
|
-
return 1;
|
|
4456
|
-
}
|
|
4457
|
-
var LyricLineEl = class extends LyricLineBase {
|
|
4458
|
-
element = document.createElement("div");
|
|
4459
|
-
splittedWords = [];
|
|
4460
|
-
lineSize = [0, 0];
|
|
4461
|
-
constructor(lyricPlayer, lyricLine = {
|
|
4462
|
-
words: [],
|
|
4463
|
-
translatedLyric: "",
|
|
4464
|
-
romanLyric: "",
|
|
4465
|
-
startTime: 0,
|
|
4466
|
-
endTime: 0,
|
|
4467
|
-
isBG: false,
|
|
4468
|
-
isDuet: false
|
|
4469
|
-
}) {
|
|
4470
|
-
super();
|
|
4471
|
-
this.lyricPlayer = lyricPlayer;
|
|
4472
|
-
this.lyricLine = lyricLine;
|
|
4473
|
-
this.element.setAttribute("class", index_module_default.lyricLine);
|
|
4474
|
-
if (this.lyricLine.isBG) this.element.classList.add(index_module_default.lyricBgLine);
|
|
4475
|
-
if (this.lyricLine.isDuet) this.element.classList.add(index_module_default.lyricDuetLine);
|
|
4476
|
-
this.element.appendChild(document.createElement("div"));
|
|
4477
|
-
this.element.appendChild(document.createElement("div"));
|
|
4478
|
-
this.element.appendChild(document.createElement("div"));
|
|
4479
|
-
const main = this.element.children[0];
|
|
4480
|
-
const trans = this.element.children[1];
|
|
4481
|
-
const roman = this.element.children[2];
|
|
4482
|
-
main.setAttribute("class", index_module_default.lyricMainLine);
|
|
4483
|
-
trans.setAttribute("class", index_module_default.lyricSubLine);
|
|
4484
|
-
roman.setAttribute("class", index_module_default.lyricSubLine);
|
|
4485
|
-
this.rebuildElement();
|
|
4486
|
-
this.rebuildStyle();
|
|
4487
|
-
this.markMaskImageDirty("Initial construction");
|
|
4488
|
-
}
|
|
4489
|
-
listenersMap = /* @__PURE__ */ new Map();
|
|
4490
|
-
onMouseEvent = (e) => {
|
|
4491
|
-
const wrapped = new RawLyricLineMouseEvent(this, e);
|
|
4492
|
-
for (const listener of this.listenersMap.get(e.type) ?? []) listener.call(this, wrapped);
|
|
4493
|
-
if (!this.dispatchEvent(wrapped) || wrapped.defaultPrevented) {
|
|
4494
|
-
e.preventDefault();
|
|
4495
|
-
e.stopPropagation();
|
|
4496
|
-
e.stopImmediatePropagation();
|
|
4497
|
-
}
|
|
4498
|
-
};
|
|
4499
|
-
addMouseEventListener(type, callback, options) {
|
|
4500
|
-
if (callback) {
|
|
4501
|
-
const listeners = this.listenersMap.get(type) ?? /* @__PURE__ */ new Set();
|
|
4502
|
-
if (listeners.size === 0) this.element.addEventListener(type, this.onMouseEvent, options);
|
|
4503
|
-
listeners.add(callback);
|
|
4504
|
-
this.listenersMap.set(type, listeners);
|
|
4505
|
-
}
|
|
4506
|
-
}
|
|
4507
|
-
removeMouseEventListener(type, callback, options) {
|
|
4508
|
-
if (callback) {
|
|
4509
|
-
const listeners = this.listenersMap.get(type);
|
|
4510
|
-
if (listeners) {
|
|
4511
|
-
listeners.delete(callback);
|
|
4512
|
-
if (listeners.size === 0) this.element.removeEventListener(type, this.onMouseEvent, options);
|
|
4513
|
-
}
|
|
4514
|
-
}
|
|
4515
|
-
}
|
|
4516
|
-
areWordsOnSameLine(word1, word2) {
|
|
4517
|
-
if (word1?.mainElement && word2?.mainElement) {
|
|
4518
|
-
const word1el = word1.mainElement;
|
|
4519
|
-
const word2el = word2.mainElement;
|
|
4520
|
-
const rect1 = word1el.getBoundingClientRect();
|
|
4521
|
-
const rect2 = word2el.getBoundingClientRect();
|
|
4522
|
-
return Math.abs(rect1.top - rect2.top) < 10;
|
|
4523
|
-
}
|
|
4524
|
-
return true;
|
|
4525
|
-
}
|
|
4526
|
-
isEnabled = false;
|
|
4527
|
-
async enable(maskAnimationTime = this.lyricLine.startTime) {
|
|
4528
|
-
this.isEnabled = true;
|
|
4529
|
-
this.element.classList.add(index_module_default.active);
|
|
4530
|
-
await this.waitMaskImageUpdated();
|
|
4531
|
-
const main = this.element.children[0];
|
|
4532
|
-
for (const word of this.splittedWords) {
|
|
4533
|
-
for (const a of word.elementAnimations) {
|
|
4534
|
-
a.currentTime = 0;
|
|
4535
|
-
a.playbackRate = 1;
|
|
4536
|
-
a.play();
|
|
4537
|
-
}
|
|
4538
|
-
for (const a of word.maskAnimations) {
|
|
4539
|
-
a.currentTime = Math.min(this.totalDuration, Math.max(0, maskAnimationTime - this.lyricLine.startTime));
|
|
4540
|
-
a.playbackRate = 1;
|
|
4541
|
-
a.play();
|
|
4542
|
-
}
|
|
4543
|
-
}
|
|
4544
|
-
main.classList.add(index_module_default.active);
|
|
4545
|
-
}
|
|
4546
|
-
disable() {
|
|
4547
|
-
this.isEnabled = false;
|
|
4548
|
-
this.element.classList.remove(index_module_default.active);
|
|
4549
|
-
const main = this.element.children[0];
|
|
4550
|
-
for (const word of this.splittedWords) for (const a of word.elementAnimations) if (a.id === "float-word" || a.id.includes("emphasize-word-float-only")) {
|
|
4551
|
-
a.playbackRate = -1;
|
|
4552
|
-
a.play();
|
|
4553
|
-
}
|
|
4554
|
-
main.classList.remove(index_module_default.active);
|
|
4555
|
-
}
|
|
4556
|
-
lastWord;
|
|
4557
|
-
async resume() {
|
|
4558
|
-
await this.waitMaskImageUpdated();
|
|
4559
|
-
if (!this.isEnabled) return;
|
|
4560
|
-
for (const word of this.splittedWords) {
|
|
4561
|
-
for (const a of word.elementAnimations) if (!this.lastWord || this.splittedWords.indexOf(this.lastWord) < this.splittedWords.indexOf(word)) a.play();
|
|
4562
|
-
for (const a of word.maskAnimations) if (!this.lastWord || this.splittedWords.indexOf(this.lastWord) < this.splittedWords.indexOf(word)) a.play();
|
|
4563
|
-
}
|
|
4564
|
-
}
|
|
4565
|
-
async pause() {
|
|
4566
|
-
await this.waitMaskImageUpdated();
|
|
4567
|
-
if (!this.isEnabled) return;
|
|
4568
|
-
for (const word of this.splittedWords) {
|
|
4569
|
-
for (const a of word.elementAnimations) a.pause();
|
|
4570
|
-
for (const a of word.maskAnimations) a.pause();
|
|
4571
|
-
}
|
|
4572
|
-
}
|
|
4573
|
-
setMaskAnimationState(maskAnimationTime = 0) {
|
|
4574
|
-
const t = maskAnimationTime - this.lyricLine.startTime;
|
|
4575
|
-
for (const word of this.splittedWords) for (const a of word.maskAnimations) {
|
|
4576
|
-
a.currentTime = Math.min(this.totalDuration, Math.max(0, t));
|
|
4577
|
-
a.playbackRate = 1;
|
|
4578
|
-
if (t >= 0 && t < this.totalDuration) a.play();
|
|
4579
|
-
else a.pause();
|
|
4580
|
-
}
|
|
4581
|
-
}
|
|
4582
|
-
measureLockMark = false;
|
|
4583
|
-
measureLock = mutexifyFunction(async (callback) => {
|
|
4584
|
-
if (this.measureLockMark) return;
|
|
4585
|
-
this.measureLockMark = true;
|
|
4586
|
-
await callback();
|
|
4587
|
-
this.measureLockMark = false;
|
|
4588
|
-
});
|
|
4589
|
-
getLine() {
|
|
4590
|
-
return this.lyricLine;
|
|
4591
|
-
}
|
|
4592
|
-
show() {
|
|
4593
|
-
this.rebuildStyle();
|
|
4594
|
-
}
|
|
4595
|
-
hide() {}
|
|
4596
|
-
rebuildStyle() {}
|
|
4597
|
-
getRubySegments(word) {
|
|
4598
|
-
return (word.ruby ?? []).filter((ruby) => (ruby?.word?.trim().length ?? 0) > 0);
|
|
4599
|
-
}
|
|
4600
|
-
buildWordElement(word, shouldEmphasize, hasRubyLine, hasRomanLine, displayWord) {
|
|
4601
|
-
const mainWordEl = document.createElement("span");
|
|
4602
|
-
const subElements = [];
|
|
4603
|
-
const romanWord = word.romanWord?.trim() ?? "";
|
|
4604
|
-
let wordContainer = mainWordEl;
|
|
4605
|
-
if (hasRubyLine || hasRomanLine) {
|
|
4606
|
-
wordContainer = document.createElement("div");
|
|
4607
|
-
mainWordEl.appendChild(wordContainer);
|
|
4608
|
-
}
|
|
4609
|
-
if (hasRubyLine) {
|
|
4610
|
-
const rubyWordEl = document.createElement("div");
|
|
4611
|
-
const rubySegments = this.getRubySegments(word);
|
|
4612
|
-
for (const ruby of rubySegments) {
|
|
4613
|
-
const rubyPartEl = document.createElement("span");
|
|
4614
|
-
rubyPartEl.innerText = ruby.word;
|
|
4615
|
-
rubyPartEl.dataset.startTime = String(ruby.startTime);
|
|
4616
|
-
rubyPartEl.dataset.endTime = String(ruby.endTime);
|
|
4617
|
-
rubyWordEl.appendChild(rubyPartEl);
|
|
4618
|
-
}
|
|
4619
|
-
rubyWordEl.classList.add(index_module_default.rubyWord);
|
|
4620
|
-
mainWordEl.classList.add(index_module_default.wordWithRuby);
|
|
4621
|
-
wordContainer.classList.add(index_module_default.wordBody);
|
|
4622
|
-
mainWordEl.insertBefore(rubyWordEl, wordContainer);
|
|
4623
|
-
}
|
|
4624
|
-
if (shouldEmphasize) {
|
|
4625
|
-
mainWordEl.classList.add(index_module_default.emphasize);
|
|
4626
|
-
for (const char of displayWord.trim()) {
|
|
4627
|
-
const charEl = document.createElement("span");
|
|
4628
|
-
charEl.innerText = char;
|
|
4629
|
-
subElements.push(charEl);
|
|
4630
|
-
wordContainer.appendChild(charEl);
|
|
4631
|
-
}
|
|
4632
|
-
} else if (hasRomanLine) {
|
|
4633
|
-
const wordEl = document.createElement("div");
|
|
4634
|
-
wordEl.innerText = displayWord;
|
|
4635
|
-
wordContainer.appendChild(wordEl);
|
|
4636
|
-
} else mainWordEl.innerText = displayWord;
|
|
4637
|
-
if (hasRomanLine) {
|
|
4638
|
-
const romanWordEl = document.createElement("div");
|
|
4639
|
-
romanWordEl.innerText = romanWord.length > 0 ? romanWord : "\xA0";
|
|
4640
|
-
romanWordEl.classList.add(index_module_default.romanWord);
|
|
4641
|
-
wordContainer.appendChild(romanWordEl);
|
|
4642
|
-
}
|
|
4643
|
-
return {
|
|
4644
|
-
mainWordEl,
|
|
4645
|
-
subElements
|
|
4646
|
-
};
|
|
4647
|
-
}
|
|
4648
|
-
rebuildElement() {
|
|
4649
|
-
this.disposeElements();
|
|
4650
|
-
const main = this.element.children[0];
|
|
4651
|
-
const trans = this.element.children[1];
|
|
4652
|
-
const roman = this.element.children[2];
|
|
4653
|
-
if (this.lyricPlayer._getIsNonDynamic()) {
|
|
4654
|
-
main.innerText = this.lyricLine.words.map((w) => this.lyricPlayer.processObsceneWord(w)).join("");
|
|
4655
|
-
trans.innerText = this.lyricLine.translatedLyric;
|
|
4656
|
-
roman.innerText = this.lyricLine.romanLyric;
|
|
4657
|
-
return;
|
|
4658
|
-
}
|
|
4659
|
-
const chunkedWords = chunkAndSplitLyricWords(this.lyricLine.words);
|
|
4660
|
-
const hasRubyLine = this.lyricLine.words.some((word) => (word.ruby?.length ?? 0) > 0);
|
|
4661
|
-
const hasRomanLine = this.lyricLine.words.some((word) => (word.romanWord?.trim().length ?? 0) > 0);
|
|
4662
|
-
main.innerHTML = "";
|
|
4663
|
-
for (const chunk of chunkedWords) if (Array.isArray(chunk)) {
|
|
4664
|
-
if (chunk.length === 0) continue;
|
|
4665
|
-
const merged = chunk.reduce((a, b) => {
|
|
4666
|
-
a.endTime = Math.max(a.endTime, b.endTime);
|
|
4667
|
-
a.startTime = Math.min(a.startTime, b.startTime);
|
|
4668
|
-
a.word += b.word;
|
|
4669
|
-
return a;
|
|
4670
|
-
}, {
|
|
4671
|
-
word: "",
|
|
4672
|
-
romanWord: "",
|
|
4673
|
-
startTime: Number.POSITIVE_INFINITY,
|
|
4674
|
-
endTime: Number.NEGATIVE_INFINITY,
|
|
4675
|
-
wordType: "normal",
|
|
4676
|
-
obscene: false
|
|
4677
|
-
});
|
|
4678
|
-
const emp = chunk.map((word) => LyricLineBase.shouldEmphasize(word)).reduce((a, b) => a || b, LyricLineBase.shouldEmphasize(merged));
|
|
4679
|
-
const wrapperWordEl = document.createElement("span");
|
|
4680
|
-
wrapperWordEl.classList.add(index_module_default.emphasizeWrapper);
|
|
4681
|
-
const characterElements = [];
|
|
4682
|
-
for (const word of chunk) {
|
|
4683
|
-
const { mainWordEl, subElements } = this.buildWordElement(word, emp, hasRubyLine, hasRomanLine, this.lyricPlayer.processObsceneWord(word));
|
|
4684
|
-
if (emp) characterElements.push(...subElements);
|
|
4685
|
-
this.splittedWords.push({
|
|
4686
|
-
...word,
|
|
4687
|
-
mainElement: mainWordEl,
|
|
4688
|
-
subElements,
|
|
4689
|
-
elementAnimations: [],
|
|
4690
|
-
maskAnimations: [],
|
|
4691
|
-
width: 0,
|
|
4692
|
-
height: 0,
|
|
4693
|
-
padding: 0,
|
|
4694
|
-
shouldEmphasize: emp
|
|
4695
|
-
});
|
|
4696
|
-
wrapperWordEl.appendChild(mainWordEl);
|
|
4697
|
-
}
|
|
4698
|
-
if (emp) this.splittedWords[this.splittedWords.length - 1].elementAnimations.push(...this.initEmphasizeAnimation(merged, characterElements, merged.endTime - merged.startTime, merged.startTime - this.lyricLine.startTime));
|
|
4699
|
-
if (merged.word.trimStart() !== merged.word) main.appendChild(document.createTextNode(" "));
|
|
4700
|
-
main.appendChild(wrapperWordEl);
|
|
4701
|
-
if (merged.word.trimEnd() !== merged.word && LyricLineBase.shouldEmphasize(merged)) main.appendChild(document.createTextNode(" "));
|
|
4702
|
-
} else if (chunk.word.trim().length === 0) main.appendChild(document.createTextNode(" "));
|
|
4703
|
-
else {
|
|
4704
|
-
const emp = LyricLineBase.shouldEmphasize(chunk);
|
|
4705
|
-
const { mainWordEl, subElements } = this.buildWordElement(chunk, emp, hasRubyLine, hasRomanLine, this.lyricPlayer.processObsceneWord(chunk).trim());
|
|
4706
|
-
const realWord = {
|
|
4707
|
-
...chunk,
|
|
4708
|
-
mainElement: mainWordEl,
|
|
4709
|
-
subElements,
|
|
4710
|
-
elementAnimations: [],
|
|
4711
|
-
maskAnimations: [],
|
|
4712
|
-
width: 0,
|
|
4713
|
-
height: 0,
|
|
4714
|
-
padding: 0,
|
|
4715
|
-
shouldEmphasize: emp
|
|
4716
|
-
};
|
|
4717
|
-
if (emp) {
|
|
4718
|
-
const duration = Math.abs(realWord.endTime - realWord.startTime);
|
|
4719
|
-
realWord.elementAnimations.push(...this.initEmphasizeAnimation(chunk, subElements, duration, realWord.startTime - this.lyricLine.startTime));
|
|
4720
|
-
}
|
|
4721
|
-
if (chunk.word.trimStart() !== chunk.word) main.appendChild(document.createTextNode(" "));
|
|
4722
|
-
main.appendChild(mainWordEl);
|
|
4723
|
-
if (chunk.word.trimEnd() !== chunk.word) main.appendChild(document.createTextNode(" "));
|
|
4724
|
-
this.splittedWords.push(realWord);
|
|
4725
|
-
}
|
|
4726
|
-
trans.innerText = this.lyricLine.translatedLyric;
|
|
4727
|
-
roman.innerText = this.lyricLine.romanLyric;
|
|
4728
|
-
}
|
|
4729
|
-
initEmphasizeAnimation(word, characterElements, duration, delay) {
|
|
4730
|
-
const de = Math.max(0, delay);
|
|
4731
|
-
let du = Math.max(1e3, duration);
|
|
4732
|
-
let result = [];
|
|
4733
|
-
let amount = du / 2e3;
|
|
4734
|
-
amount = amount > 1 ? Math.sqrt(amount) : amount ** 3;
|
|
4735
|
-
let blur = du / 3e3;
|
|
4736
|
-
blur = blur > 1 ? Math.sqrt(blur) : blur ** 3;
|
|
4737
|
-
amount *= .6;
|
|
4738
|
-
blur *= .5;
|
|
4739
|
-
if (this.lyricLine.words.length > 0 && word.word.includes(this.lyricLine.words[this.lyricLine.words.length - 1].word)) {
|
|
4740
|
-
amount *= 1.6;
|
|
4741
|
-
blur *= 1.5;
|
|
4742
|
-
du *= 1.2;
|
|
4743
|
-
}
|
|
4744
|
-
amount = Math.min(1.2, amount);
|
|
4745
|
-
blur = Math.min(.8, blur);
|
|
4746
|
-
const animateDu = Number.isFinite(du) ? du : 0;
|
|
4747
|
-
const empEasing = makeEmpEasing(EMP_EASING_MID);
|
|
4748
|
-
result = characterElements.flatMap((el, i, arr) => {
|
|
4749
|
-
const wordDe = de + du / 2.5 / arr.length * i;
|
|
4750
|
-
const result = [];
|
|
4751
|
-
const frames = new Array(ANIMATION_FRAME_QUANTITY).fill(0).map((_, j) => {
|
|
4752
|
-
const x = (j + 1) / ANIMATION_FRAME_QUANTITY;
|
|
4753
|
-
const transX = empEasing(x);
|
|
4754
|
-
const glowLevel = empEasing(x) * blur;
|
|
4755
|
-
const mat = scaleMatrix4(createMatrix4(), 1 + transX * .1 * amount);
|
|
4756
|
-
const offsetX = -transX * .03 * amount * (arr.length / 2 - i);
|
|
4757
|
-
const offsetY = -transX * .025 * amount;
|
|
4758
|
-
return {
|
|
4759
|
-
offset: x,
|
|
4760
|
-
transform: `${matrix4ToCSS(mat, 4)} translate(${offsetX}em, ${offsetY}em)`,
|
|
4761
|
-
textShadow: `0 0 ${Math.min(.3, blur * .3)}em rgba(255, 255, 255, ${glowLevel})`
|
|
4762
|
-
};
|
|
4763
|
-
});
|
|
4764
|
-
const glow = el.animate(frames, {
|
|
4765
|
-
duration: animateDu,
|
|
4766
|
-
delay: Number.isFinite(wordDe) ? wordDe : 0,
|
|
4767
|
-
id: `emphasize-word-${el.innerText}-${i}`,
|
|
4768
|
-
iterations: 1,
|
|
4769
|
-
composite: "replace",
|
|
4770
|
-
fill: "both"
|
|
4771
|
-
});
|
|
4772
|
-
glow.onfinish = () => {
|
|
4773
|
-
glow.pause();
|
|
4774
|
-
};
|
|
4775
|
-
glow.pause();
|
|
4776
|
-
result.push(glow);
|
|
4777
|
-
const floatFrame = new Array(ANIMATION_FRAME_QUANTITY).fill(0).map((_, j) => {
|
|
4778
|
-
const x = (j + 1) / ANIMATION_FRAME_QUANTITY;
|
|
4779
|
-
let y = Math.sin(x * Math.PI);
|
|
4780
|
-
if (this.lyricLine.isBG) y *= 2;
|
|
4781
|
-
return {
|
|
4782
|
-
offset: x,
|
|
4783
|
-
transform: `translateY(${-y * .05}em)`
|
|
4784
|
-
};
|
|
4785
|
-
});
|
|
4786
|
-
const float = el.animate(floatFrame, {
|
|
4787
|
-
duration: animateDu * 1.4,
|
|
4788
|
-
delay: Number.isFinite(wordDe) ? wordDe - 400 : 0,
|
|
4789
|
-
id: "emphasize-word-float",
|
|
4790
|
-
iterations: 1,
|
|
4791
|
-
composite: "add",
|
|
4792
|
-
fill: "both"
|
|
4793
|
-
});
|
|
4794
|
-
float.onfinish = () => {
|
|
4795
|
-
float.pause();
|
|
4796
|
-
};
|
|
4797
|
-
float.pause();
|
|
4798
|
-
result.push(float);
|
|
4799
|
-
return result;
|
|
4800
|
-
});
|
|
4801
|
-
return result;
|
|
4802
|
-
}
|
|
4803
|
-
get totalDuration() {
|
|
4804
|
-
return this.lyricLine.endTime - this.lyricLine.startTime;
|
|
4805
|
-
}
|
|
4806
|
-
maskImageDirty = false;
|
|
4807
|
-
markImageDirtyPromiseResolve = /* @__PURE__ */ new Set();
|
|
4808
|
-
markImageDirtyPromise = new Promise((resolve) => {
|
|
4809
|
-
this.markImageDirtyPromiseResolve.add(resolve);
|
|
4810
|
-
});
|
|
4811
|
-
markMaskImageDirty(_debugReason = "") {
|
|
4812
|
-
this.maskImageDirty = true;
|
|
4813
|
-
if (!this.element.classList.contains(index_module_default.dirty)) this.element.classList.add(index_module_default.dirty);
|
|
4814
|
-
const newPromise = Promise.all([this.markImageDirtyPromise, new Promise((resolve) => {
|
|
4815
|
-
this.markImageDirtyPromiseResolve.add(resolve);
|
|
4816
|
-
})]).then(() => {});
|
|
4817
|
-
this.markImageDirtyPromise = newPromise;
|
|
4818
|
-
return newPromise;
|
|
4819
|
-
}
|
|
4820
|
-
waitMaskImageUpdated() {
|
|
4821
|
-
return this.markImageDirtyPromise;
|
|
4822
|
-
}
|
|
4823
|
-
async updateMaskImage() {
|
|
4824
|
-
if (!this.element.checkVisibility({ contentVisibilityAuto: true })) return;
|
|
4825
|
-
this.maskImageDirty = false;
|
|
4826
|
-
await this.measureLock(async () => {
|
|
4827
|
-
await Promise.all(this.splittedWords.map(async (word) => {
|
|
4828
|
-
const el = word.mainElement;
|
|
4829
|
-
if (el) await measure(() => {
|
|
4830
|
-
word.padding = Number.parseFloat(getComputedStyle(el).paddingLeft);
|
|
4831
|
-
word.width = el.clientWidth - word.padding * 2;
|
|
4832
|
-
word.height = el.clientHeight - word.padding * 2;
|
|
4833
|
-
});
|
|
4834
|
-
else {
|
|
4835
|
-
word.width = 0;
|
|
4836
|
-
word.height = 0;
|
|
4837
|
-
word.padding = 0;
|
|
4838
|
-
}
|
|
4839
|
-
if (word.width * word.height === 0) console.warn("Word size is zero");
|
|
4840
|
-
}));
|
|
4841
|
-
await mutate(() => {
|
|
4842
|
-
if (this.lyricPlayer.supportMaskImage) this.generateWebAnimationBasedMaskImage();
|
|
4843
|
-
else this.generateCalcBasedMaskImage();
|
|
4844
|
-
});
|
|
4845
|
-
});
|
|
4846
|
-
for (const resolve of this.markImageDirtyPromiseResolve) {
|
|
4847
|
-
resolve();
|
|
4848
|
-
this.markImageDirtyPromiseResolve.delete(resolve);
|
|
4849
|
-
}
|
|
4850
|
-
await mutate(() => {
|
|
4851
|
-
this.element.classList.remove(index_module_default.dirty);
|
|
4852
|
-
});
|
|
4853
|
-
}
|
|
4854
|
-
generateCalcBasedMaskImage() {
|
|
4855
|
-
for (const word of this.splittedWords) {
|
|
4856
|
-
const wordEl = word.mainElement;
|
|
4857
|
-
if (wordEl) {
|
|
4858
|
-
word.width = wordEl.clientWidth;
|
|
4859
|
-
word.height = wordEl.clientHeight;
|
|
4860
|
-
const fadeWidth = word.height * this.lyricPlayer.getWordFadeWidth();
|
|
4861
|
-
const [maskImage, totalAspect] = generateFadeGradient(fadeWidth / word.width);
|
|
4862
|
-
const totalAspectStr = `${totalAspect * 100}% 100%`;
|
|
4863
|
-
if (this.lyricPlayer.supportMaskImage) {
|
|
4864
|
-
wordEl.style.maskImage = maskImage;
|
|
4865
|
-
wordEl.style.maskRepeat = "no-repeat";
|
|
4866
|
-
wordEl.style.maskOrigin = "left";
|
|
4867
|
-
wordEl.style.maskSize = totalAspectStr;
|
|
4868
|
-
} else {
|
|
4869
|
-
wordEl.style.webkitMaskImage = maskImage;
|
|
4870
|
-
wordEl.style.webkitMaskRepeat = "no-repeat";
|
|
4871
|
-
wordEl.style.webkitMaskOrigin = "left";
|
|
4872
|
-
wordEl.style.webkitMaskSize = totalAspectStr;
|
|
4873
|
-
}
|
|
4874
|
-
const w = word.width + fadeWidth;
|
|
4875
|
-
const maskPos = `clamp(${-w}px,calc(${-w}px + (var(--amll-player-time) - ${word.startTime})*${w / Math.abs(word.endTime - word.startTime)}px),0px) 0px, left top`;
|
|
4876
|
-
wordEl.style.maskPosition = maskPos;
|
|
4877
|
-
wordEl.style.webkitMaskPosition = maskPos;
|
|
4878
|
-
}
|
|
4879
|
-
}
|
|
4880
|
-
}
|
|
4881
|
-
generateWebAnimationBasedMaskImage() {
|
|
4882
|
-
const totalFadeDuration = Math.max(this.splittedWords.reduce((pv, w) => Math.max(w.endTime, pv), 0), this.lyricLine.endTime) - this.lyricLine.startTime;
|
|
4883
|
-
this.splittedWords.forEach((word, i) => {
|
|
4884
|
-
const wordEl = word.mainElement;
|
|
4885
|
-
if (wordEl) {
|
|
4886
|
-
const fadeWidth = word.height * this.lyricPlayer.getWordFadeWidth();
|
|
4887
|
-
const [maskImage, totalAspect] = generateFadeGradient(fadeWidth / (word.width + word.padding * 2));
|
|
4888
|
-
const totalAspectStr = `${totalAspect * 100}% 100%`;
|
|
4889
|
-
if (this.lyricPlayer.supportMaskImage) {
|
|
4890
|
-
wordEl.style.maskImage = maskImage;
|
|
4891
|
-
wordEl.style.maskRepeat = "no-repeat";
|
|
4892
|
-
wordEl.style.maskOrigin = "left";
|
|
4893
|
-
wordEl.style.maskSize = totalAspectStr;
|
|
4894
|
-
} else {
|
|
4895
|
-
wordEl.style.webkitMaskImage = maskImage;
|
|
4896
|
-
wordEl.style.webkitMaskRepeat = "no-repeat";
|
|
4897
|
-
wordEl.style.webkitMaskOrigin = "left";
|
|
4898
|
-
wordEl.style.webkitMaskSize = totalAspectStr;
|
|
4899
|
-
}
|
|
4900
|
-
const widthBeforeSelf = this.splittedWords.slice(0, i).reduce((a, b) => a + b.width, 0) + (this.splittedWords[0] ? fadeWidth : 0);
|
|
4901
|
-
const minOffset = -(word.width + word.padding * 2 + fadeWidth);
|
|
4902
|
-
const clampOffset = (x) => Math.max(minOffset, Math.min(0, x));
|
|
4903
|
-
let curPos = -widthBeforeSelf - word.width - word.padding - fadeWidth;
|
|
4904
|
-
let timeOffset = 0;
|
|
4905
|
-
const frames = [];
|
|
4906
|
-
let lastPos = curPos;
|
|
4907
|
-
let lastTime = 0;
|
|
4908
|
-
const pushFrame = () => {
|
|
4909
|
-
const moveOffset = curPos - lastPos;
|
|
4910
|
-
const time = Math.max(0, Math.min(1, timeOffset));
|
|
4911
|
-
const duration = time - lastTime;
|
|
4912
|
-
const d = Math.abs(duration / moveOffset);
|
|
4913
|
-
if (curPos > minOffset && lastPos < minOffset) {
|
|
4914
|
-
const staticTime = Math.abs(lastPos - minOffset) * d;
|
|
4915
|
-
const value = `${clampOffset(lastPos)}px 0`;
|
|
4916
|
-
const frame = {
|
|
4917
|
-
offset: lastTime + staticTime,
|
|
4918
|
-
maskPosition: value
|
|
4919
|
-
};
|
|
4920
|
-
frames.push(frame);
|
|
4921
|
-
}
|
|
4922
|
-
if (curPos > 0 && lastPos < 0) {
|
|
4923
|
-
const staticTime = Math.abs(lastPos) * d;
|
|
4924
|
-
const value = `${clampOffset(curPos)}px 0`;
|
|
4925
|
-
const frame = {
|
|
4926
|
-
offset: lastTime + staticTime,
|
|
4927
|
-
maskPosition: value
|
|
4928
|
-
};
|
|
4929
|
-
frames.push(frame);
|
|
4930
|
-
}
|
|
4931
|
-
const frame = {
|
|
4932
|
-
offset: time,
|
|
4933
|
-
maskPosition: `${clampOffset(curPos)}px 0`
|
|
4934
|
-
};
|
|
4935
|
-
frames.push(frame);
|
|
4936
|
-
lastPos = curPos;
|
|
4937
|
-
lastTime = time;
|
|
4938
|
-
};
|
|
4939
|
-
pushFrame();
|
|
4940
|
-
let lastTimeStamp = 0;
|
|
4941
|
-
this.splittedWords.forEach((otherWord, j) => {
|
|
4942
|
-
{
|
|
4943
|
-
const curTimeStamp = otherWord.startTime - this.lyricLine.startTime;
|
|
4944
|
-
const staticDuration = curTimeStamp - lastTimeStamp;
|
|
4945
|
-
timeOffset += staticDuration / totalFadeDuration;
|
|
4946
|
-
if (staticDuration > 0) pushFrame();
|
|
4947
|
-
lastTimeStamp = curTimeStamp;
|
|
4948
|
-
}
|
|
4949
|
-
{
|
|
4950
|
-
const fadeDuration = otherWord.endTime - otherWord.startTime;
|
|
4951
|
-
const rubySegments = this.getRubySegments(otherWord);
|
|
4952
|
-
const rubyCharCount = rubySegments.reduce((total, ruby) => total + ruby.word.length, 0);
|
|
4953
|
-
if (rubyCharCount > 0) {
|
|
4954
|
-
const widthPerChar = otherWord.width / rubyCharCount;
|
|
4955
|
-
let charIndex = 0;
|
|
4956
|
-
for (const ruby of rubySegments) {
|
|
4957
|
-
const rubyStartTime = Number.isFinite(ruby.startTime) ? ruby.startTime : otherWord.startTime;
|
|
4958
|
-
const rubyEndTime = Number.isFinite(ruby.endTime) ? ruby.endTime : otherWord.endTime;
|
|
4959
|
-
const rubyStart = Math.max(rubyStartTime, otherWord.startTime);
|
|
4960
|
-
const rubyEnd = Math.min(Math.max(rubyEndTime, rubyStart), otherWord.endTime);
|
|
4961
|
-
const rubyStartStamp = rubyStart - this.lyricLine.startTime;
|
|
4962
|
-
const rubyStaticDuration = rubyStartStamp - lastTimeStamp;
|
|
4963
|
-
timeOffset += rubyStaticDuration / totalFadeDuration;
|
|
4964
|
-
if (rubyStaticDuration > 0) pushFrame();
|
|
4965
|
-
lastTimeStamp = rubyStartStamp;
|
|
4966
|
-
const perCharDuration = Math.max(0, rubyEnd - rubyStart) / ruby.word.length;
|
|
4967
|
-
for (let rubyCharIndex = 0; rubyCharIndex < ruby.word.length; rubyCharIndex++) {
|
|
4968
|
-
timeOffset += perCharDuration / totalFadeDuration;
|
|
4969
|
-
curPos += widthPerChar;
|
|
4970
|
-
if (j === 0 && charIndex === 0) curPos += fadeWidth * 1.5;
|
|
4971
|
-
if (j === this.splittedWords.length - 1 && charIndex === rubyCharCount - 1) curPos += fadeWidth * .5;
|
|
4972
|
-
if (perCharDuration > 0) pushFrame();
|
|
4973
|
-
lastTimeStamp += perCharDuration;
|
|
4974
|
-
charIndex++;
|
|
4975
|
-
}
|
|
4976
|
-
}
|
|
4977
|
-
const wordEndStamp = Math.max(otherWord.endTime - this.lyricLine.startTime, lastTimeStamp);
|
|
4978
|
-
const wordTailDuration = wordEndStamp - lastTimeStamp;
|
|
4979
|
-
timeOffset += wordTailDuration / totalFadeDuration;
|
|
4980
|
-
if (wordTailDuration > 0) pushFrame();
|
|
4981
|
-
lastTimeStamp = wordEndStamp;
|
|
4982
|
-
} else {
|
|
4983
|
-
timeOffset += fadeDuration / totalFadeDuration;
|
|
4984
|
-
curPos += otherWord.width;
|
|
4985
|
-
if (j === 0) curPos += fadeWidth * 1.5;
|
|
4986
|
-
if (j === this.splittedWords.length - 1) curPos += fadeWidth * .5;
|
|
4987
|
-
if (fadeDuration > 0) pushFrame();
|
|
4988
|
-
lastTimeStamp += fadeDuration;
|
|
4989
|
-
}
|
|
4990
|
-
}
|
|
4991
|
-
});
|
|
4992
|
-
for (const a of word.maskAnimations) a.cancel();
|
|
4993
|
-
try {
|
|
4994
|
-
const ani = wordEl.animate(frames, {
|
|
4995
|
-
duration: totalFadeDuration || 1,
|
|
4996
|
-
id: `fade-word-${word.word}-${i}`,
|
|
4997
|
-
fill: "both"
|
|
4998
|
-
});
|
|
4999
|
-
ani.pause();
|
|
5000
|
-
word.maskAnimations = [ani];
|
|
5001
|
-
} catch (err) {
|
|
5002
|
-
console.warn("应用渐变动画发生错误", frames, totalFadeDuration, err);
|
|
5003
|
-
}
|
|
5004
|
-
}
|
|
5005
|
-
});
|
|
5006
|
-
}
|
|
5007
|
-
getElement() {
|
|
5008
|
-
return this.element;
|
|
5009
|
-
}
|
|
5010
|
-
setTransform(top = this.top, scale = this.scale, opacity = 1, blur = 0, force = false, delay = 0) {
|
|
5011
|
-
super.setTransform(top, scale, opacity, blur, force, delay);
|
|
5012
|
-
const beforeInSight = this.isInSight;
|
|
5013
|
-
const enableSpring = this.lyricPlayer.getEnableSpring();
|
|
5014
|
-
this.top = top;
|
|
5015
|
-
this.scale = scale;
|
|
5016
|
-
this.delay = delay * 1e3 | 0;
|
|
5017
|
-
const main = this.element.children[0];
|
|
5018
|
-
main.style.opacity = `${opacity}`;
|
|
5019
|
-
if (force || !enableSpring) {
|
|
5020
|
-
if (force) this.element.classList.add(index_module_default.tmpDisableTransition);
|
|
5021
|
-
this.lineTransforms.posY.setPosition(top);
|
|
5022
|
-
this.lineTransforms.scale.setPosition(scale);
|
|
5023
|
-
if (!enableSpring) {
|
|
5024
|
-
const afterInSight = this.isInSight;
|
|
5025
|
-
if (beforeInSight || afterInSight) this.show();
|
|
5026
|
-
else this.hide();
|
|
5027
|
-
} else this.rebuildStyle();
|
|
5028
|
-
if (force) requestAnimationFrame(() => {
|
|
5029
|
-
this.element.classList.remove(index_module_default.tmpDisableTransition);
|
|
5030
|
-
});
|
|
5031
|
-
} else {
|
|
5032
|
-
this.lineTransforms.posY.setTargetPosition(top, delay);
|
|
5033
|
-
this.lineTransforms.scale.setTargetPosition(scale);
|
|
5034
|
-
}
|
|
5035
|
-
}
|
|
5036
|
-
update(delta = 0) {
|
|
5037
|
-
if (!this.lyricPlayer.getEnableSpring()) return;
|
|
5038
|
-
this.lineTransforms.posY.update(delta);
|
|
5039
|
-
this.lineTransforms.scale.update(delta);
|
|
5040
|
-
if (this.isInSight) {
|
|
5041
|
-
this.show();
|
|
5042
|
-
if (this.maskImageDirty) this.updateMaskImage();
|
|
5043
|
-
} else this.hide();
|
|
5044
|
-
if (this.lyricPlayer.getEnableSpring()) {
|
|
5045
|
-
this.element.style.setProperty("--bright-mask-alpha", `${Math.max(0, Math.min(1, this.lineTransforms.scale.getCurrentPosition() / 100 - .97) / .03) * .8 + .2}`);
|
|
5046
|
-
this.element.style.setProperty("--dark-mask-alpha", `${Math.max(0, Math.min(1, this.lineTransforms.scale.getCurrentPosition() / 100 - .97) / .03) * .2 + .2}`);
|
|
5047
|
-
} else {
|
|
5048
|
-
const transform = window.getComputedStyle(this.element).transform;
|
|
5049
|
-
const scale = getScaleFromTransform(transform);
|
|
5050
|
-
this.element.style.setProperty("--bright-mask-alpha", `${Math.max(0, Math.min(1, (scale - .97) / .03)) * .8 + .2}`);
|
|
5051
|
-
this.element.style.setProperty("--dark-mask-alpha", `${Math.max(0, Math.min(1, (scale - .97) / .03)) * .2 + .2}`);
|
|
5052
|
-
}
|
|
5053
|
-
}
|
|
5054
|
-
_getDebugTargetPos() {
|
|
5055
|
-
return `[位移: ${this.top}; 缩放: ${this.scale}; 延时: ${this.delay}]`;
|
|
5056
|
-
}
|
|
5057
|
-
get isInSight() {
|
|
5058
|
-
const t = this.lineTransforms.posY.getCurrentPosition();
|
|
5059
|
-
const h = this.lineSize[1];
|
|
5060
|
-
const b = t + h;
|
|
5061
|
-
return !(t > this.lyricPlayer.size[1] + h || b < -h);
|
|
5062
|
-
}
|
|
5063
|
-
disposeElements() {
|
|
5064
|
-
for (const realWord of this.splittedWords) {
|
|
5065
|
-
for (const a of realWord.elementAnimations) a.cancel();
|
|
5066
|
-
for (const a of realWord.maskAnimations) a.cancel();
|
|
5067
|
-
for (const sub of realWord.subElements) {
|
|
5068
|
-
sub.remove();
|
|
5069
|
-
sub.parentNode?.removeChild(sub);
|
|
5070
|
-
}
|
|
5071
|
-
realWord.elementAnimations = [];
|
|
5072
|
-
realWord.maskAnimations = [];
|
|
5073
|
-
realWord.subElements = [];
|
|
5074
|
-
realWord.mainElement.remove();
|
|
5075
|
-
realWord.mainElement.parentNode?.removeChild(realWord.mainElement);
|
|
5076
|
-
}
|
|
5077
|
-
this.splittedWords = [];
|
|
5078
|
-
}
|
|
5079
|
-
dispose() {
|
|
5080
|
-
this.disposeElements();
|
|
5081
|
-
this.element.remove();
|
|
5082
|
-
}
|
|
5083
|
-
};
|
|
5084
|
-
//#endregion
|
|
5085
|
-
//#region src/lyric-player/dom-slim/index.ts
|
|
5086
|
-
/**
|
|
5087
|
-
* 歌词播放组件,本框架的核心组件
|
|
5088
|
-
*
|
|
5089
|
-
* 尽可能贴切 Apple Music for iPad 的歌词效果设计,且做了力所能及的优化措施
|
|
5090
|
-
*/
|
|
5091
|
-
var DomSlimLyricPlayer = class extends LyricPlayerBase {
|
|
5092
|
-
currentLyricLineObjects = [];
|
|
5093
|
-
debounceCalcLayout = debounce(() => this.calcLayout(true).then(() => this.currentLyricLineObjects.map(async (el, i) => {
|
|
5094
|
-
el.markMaskImageDirty("DomLyricPlayer onResize");
|
|
5095
|
-
await el.waitMaskImageUpdated();
|
|
5096
|
-
if (this.hotLines.has(i)) {
|
|
5097
|
-
el.enable(this.currentTime);
|
|
5098
|
-
el.resume();
|
|
5099
|
-
}
|
|
5100
|
-
})), 1e3);
|
|
5101
|
-
onResize() {
|
|
5102
|
-
const computedStyles = getComputedStyle(this.element);
|
|
5103
|
-
this._baseFontSize = Number.parseFloat(computedStyles.fontSize);
|
|
5104
|
-
const innerWidth = this.element.clientWidth - Number.parseFloat(computedStyles.paddingLeft) - Number.parseFloat(computedStyles.paddingRight);
|
|
5105
|
-
const innerHeight = this.element.clientHeight - Number.parseFloat(computedStyles.paddingTop) - Number.parseFloat(computedStyles.paddingBottom);
|
|
5106
|
-
this.innerSize[0] = innerWidth;
|
|
5107
|
-
this.innerSize[1] = innerHeight;
|
|
5108
|
-
this.rebuildStyle();
|
|
5109
|
-
this.debounceCalcLayout();
|
|
5110
|
-
}
|
|
5111
|
-
supportPlusLighter = CSS.supports("mix-blend-mode", "plus-lighter");
|
|
5112
|
-
supportMaskImage = CSS.supports("mask-image", "none");
|
|
5113
|
-
innerSize = [0, 0];
|
|
5114
|
-
onLineClickedHandler = (e) => {
|
|
5115
|
-
const evt = new LyricLineMouseEvent(this.lyricLinesIndexes.get(e.line) ?? -1, e.line, e);
|
|
5116
|
-
if (!this.dispatchEvent(evt)) {
|
|
5117
|
-
e.preventDefault();
|
|
5118
|
-
e.stopPropagation();
|
|
5119
|
-
e.stopImmediatePropagation();
|
|
5120
|
-
}
|
|
5121
|
-
};
|
|
5122
|
-
/**
|
|
5123
|
-
* 是否为非逐词歌词
|
|
5124
|
-
* @internal
|
|
5125
|
-
*/
|
|
5126
|
-
_getIsNonDynamic() {
|
|
5127
|
-
return this.isNonDynamic;
|
|
5128
|
-
}
|
|
5129
|
-
_baseFontSize = Number.parseFloat(getComputedStyle(this.element).fontSize);
|
|
5130
|
-
get baseFontSize() {
|
|
5131
|
-
return this._baseFontSize;
|
|
5132
|
-
}
|
|
5133
|
-
constructor() {
|
|
5134
|
-
super();
|
|
5135
|
-
this.onResize();
|
|
5136
|
-
this.element.classList.add("amll-lyric-player", "dom-slim");
|
|
5137
|
-
if (this.disableSpring) this.element.classList.add(index_module_default.disableSpring);
|
|
5138
|
-
}
|
|
5139
|
-
rebuildStyle() {
|
|
5140
|
-
const width = this.innerSize[0];
|
|
5141
|
-
const height = this.innerSize[1];
|
|
5142
|
-
this.element.style.setProperty("--amll-lp-width", `${width.toFixed(4)}px`);
|
|
5143
|
-
this.element.style.setProperty("--amll-lp-height", `${height.toFixed(4)}px`);
|
|
5144
|
-
}
|
|
5145
|
-
setWordFadeWidth(value = .5) {
|
|
5146
|
-
super.setWordFadeWidth(value);
|
|
5147
|
-
for (const el of this.currentLyricLineObjects) el.markMaskImageDirty("DomLyricPlayer setWordFadeWidth");
|
|
5148
|
-
}
|
|
5149
|
-
/**
|
|
5150
|
-
* 设置当前播放歌词,要注意传入后这个数组内的信息不得修改,否则会发生错误
|
|
5151
|
-
* @param lines 歌词数组
|
|
5152
|
-
* @param initialTime 初始时间,默认为 0
|
|
5153
|
-
*/
|
|
5154
|
-
setLyricLines(lines, initialTime = 0) {
|
|
5155
|
-
super.setLyricLines(lines, initialTime);
|
|
5156
|
-
if (this.hasDuetLine) this.element.classList.add(index_module_default.hasDuetLine);
|
|
5157
|
-
else this.element.classList.remove(index_module_default.hasDuetLine);
|
|
5158
|
-
for (const line of this.currentLyricLineObjects) {
|
|
5159
|
-
line.removeMouseEventListener("click", this.onLineClickedHandler);
|
|
5160
|
-
line.removeMouseEventListener("contextmenu", this.onLineClickedHandler);
|
|
5161
|
-
line.dispose();
|
|
5162
|
-
}
|
|
5163
|
-
this.currentLyricLineObjects = this.processedLines.map((line, i) => {
|
|
5164
|
-
const lineEl = new LyricLineEl(this, line);
|
|
5165
|
-
lineEl.addMouseEventListener("click", this.onLineClickedHandler);
|
|
5166
|
-
lineEl.addMouseEventListener("contextmenu", this.onLineClickedHandler);
|
|
5167
|
-
this.element.appendChild(lineEl.getElement());
|
|
5168
|
-
this.lyricLinesIndexes.set(lineEl, i);
|
|
5169
|
-
lineEl.markMaskImageDirty("DomLyricPlayer setLyricLines");
|
|
5170
|
-
return lineEl;
|
|
5171
|
-
});
|
|
5172
|
-
this.setLinePosXSpringParams({});
|
|
5173
|
-
this.setLinePosYSpringParams({});
|
|
5174
|
-
this.setLineScaleSpringParams({});
|
|
5175
|
-
this.calcLayout(true).then(() => {
|
|
5176
|
-
this.initialLayoutFinished = true;
|
|
5177
|
-
});
|
|
5178
|
-
}
|
|
5179
|
-
pause() {
|
|
5180
|
-
super.pause();
|
|
5181
|
-
this.interludeDots.pause();
|
|
5182
|
-
for (const line of this.currentLyricLineObjects) line.pause();
|
|
5183
|
-
}
|
|
5184
|
-
resume() {
|
|
5185
|
-
super.resume();
|
|
5186
|
-
this.interludeDots.resume();
|
|
5187
|
-
for (const line of this.currentLyricLineObjects) line.resume();
|
|
5188
|
-
}
|
|
5189
|
-
update(delta = 0) {
|
|
5190
|
-
if (!this.initialLayoutFinished) return;
|
|
5191
|
-
super.update(delta);
|
|
5192
|
-
if (!this.isPageVisible) return;
|
|
5193
|
-
const deltaS = delta / 1e3;
|
|
5194
|
-
for (const line of this.currentLyricLineObjects) line.update(deltaS);
|
|
5195
|
-
}
|
|
5196
|
-
async calcLayout(sync) {
|
|
5197
|
-
await super.calcLayout(sync);
|
|
5198
|
-
const curLine = this.currentLyricLineObjects[this.targetAlignIndex];
|
|
5199
|
-
const curLineEl = curLine.getElement();
|
|
5200
|
-
const curLineVisibility = curLineEl.checkVisibility({ contentVisibilityAuto: true });
|
|
5201
|
-
const playerTop = this.element.getBoundingClientRect().top;
|
|
5202
|
-
if (!curLineVisibility) curLineEl.scrollIntoView({
|
|
5203
|
-
block: "center",
|
|
5204
|
-
behavior: "instant"
|
|
5205
|
-
});
|
|
5206
|
-
const curLineHeight = curLineEl.clientHeight;
|
|
5207
|
-
let scrollToPos = curLineEl.getBoundingClientRect().top - playerTop - this.size[1] * this.alignPosition;
|
|
5208
|
-
if (curLine) switch (this.alignAnchor) {
|
|
5209
|
-
case "bottom":
|
|
5210
|
-
scrollToPos += curLineHeight;
|
|
5211
|
-
break;
|
|
5212
|
-
case "center":
|
|
5213
|
-
scrollToPos += curLineHeight / 2;
|
|
5214
|
-
break;
|
|
5215
|
-
case "top": break;
|
|
5216
|
-
}
|
|
5217
|
-
this.element.scrollBy({
|
|
5218
|
-
top: scrollToPos,
|
|
5219
|
-
behavior: "smooth"
|
|
5220
|
-
});
|
|
5221
|
-
}
|
|
5222
|
-
dispose() {
|
|
5223
|
-
super.dispose();
|
|
5224
|
-
this.element.remove();
|
|
5225
|
-
for (const el of this.currentLyricLineObjects) el.dispose();
|
|
5226
|
-
this.bottomLine.dispose();
|
|
5227
|
-
this.interludeDots.dispose();
|
|
5228
|
-
}
|
|
5229
|
-
};
|
|
5230
|
-
//#endregion
|
|
5231
|
-
//#region src/lyric-player/index.ts
|
|
5232
|
-
/**
|
|
5233
|
-
* 歌词中不雅用语的掩码模式
|
|
5234
|
-
*/
|
|
5235
|
-
var MaskObsceneWordsMode = /* @__PURE__ */ function(MaskObsceneWordsMode) {
|
|
5236
|
-
/** 禁用任何不雅用语掩码 */
|
|
5237
|
-
MaskObsceneWordsMode["Disabled"] = "";
|
|
5238
|
-
/** 完全掩码所有不雅用语 */
|
|
5239
|
-
MaskObsceneWordsMode["FullMask"] = "full-mask";
|
|
5240
|
-
/** 保留首尾字符,屏蔽中间字符 */
|
|
5241
|
-
MaskObsceneWordsMode["PartialMask"] = "partial-mask";
|
|
5242
|
-
return MaskObsceneWordsMode;
|
|
5243
|
-
}(MaskObsceneWordsMode || {});
|
|
5244
|
-
/**
|
|
5245
|
-
* 歌词行的渲染模式
|
|
5246
|
-
* @internal
|
|
5247
|
-
*/
|
|
5248
|
-
var LyricLineRenderMode = /* @__PURE__ */ function(LyricLineRenderMode) {
|
|
5249
|
-
LyricLineRenderMode[LyricLineRenderMode["SOLID"] = 0] = "SOLID";
|
|
5250
|
-
LyricLineRenderMode[LyricLineRenderMode["GRADIENT"] = 1] = "GRADIENT";
|
|
5251
|
-
return LyricLineRenderMode;
|
|
5252
|
-
}(LyricLineRenderMode || {});
|
|
5253
|
-
//#endregion
|
|
5254
4577
|
exports.AbstractBaseRenderer = AbstractBaseRenderer;
|
|
5255
4578
|
exports.BackgroundRender = BackgroundRender;
|
|
5256
4579
|
exports.BaseRenderer = BaseRenderer;
|
|
5257
4580
|
exports.DomLyricPlayer = DomLyricPlayer;
|
|
5258
|
-
exports.
|
|
4581
|
+
exports.LayoutAlignAnchor = LayoutAlignAnchor;
|
|
5259
4582
|
exports.LyricLineMouseEvent = LyricLineMouseEvent;
|
|
5260
4583
|
exports.LyricLineRenderMode = LyricLineRenderMode;
|
|
5261
4584
|
exports.LyricPlayer = DomLyricPlayer;
|