@applemusic-like-lyrics/core 0.4.2 → 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 +756 -1459
- 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 +756 -1459
- package/dist/amll-core.mjs.map +1 -1
- package/dist/style.css +0 -164
- package/package.json +87 -69
package/dist/amll-core.mjs
CHANGED
|
@@ -10,6 +10,9 @@ import structuredClone from "@ungap/structured-clone";
|
|
|
10
10
|
import bezier from "bezier-easing";
|
|
11
11
|
//#region src/bg-render/base.ts
|
|
12
12
|
var AbstractBaseRenderer = class {};
|
|
13
|
+
function clamp1(x) {
|
|
14
|
+
return Math.max(1, x);
|
|
15
|
+
}
|
|
13
16
|
var BaseRenderer = class extends AbstractBaseRenderer {
|
|
14
17
|
observer;
|
|
15
18
|
flowSpeed = 1;
|
|
@@ -18,8 +21,8 @@ var BaseRenderer = class extends AbstractBaseRenderer {
|
|
|
18
21
|
super();
|
|
19
22
|
this.canvas = canvas;
|
|
20
23
|
this.observer = new ResizeObserver(() => {
|
|
21
|
-
const width =
|
|
22
|
-
const height =
|
|
24
|
+
const width = clamp1(canvas.clientWidth * window.devicePixelRatio * this.currerntRenderScale);
|
|
25
|
+
const height = clamp1(canvas.clientHeight * window.devicePixelRatio * this.currerntRenderScale);
|
|
23
26
|
this.onResize(width, height);
|
|
24
27
|
});
|
|
25
28
|
this.observer.observe(canvas);
|
|
@@ -207,6 +210,17 @@ function blurImage(imageData, radius, quality) {
|
|
|
207
210
|
}
|
|
208
211
|
}
|
|
209
212
|
//#endregion
|
|
213
|
+
//#region src/utils/clamp.ts
|
|
214
|
+
function clamp(x, min, max) {
|
|
215
|
+
return Math.min(Math.max(x, min), max);
|
|
216
|
+
}
|
|
217
|
+
function clamp01(x) {
|
|
218
|
+
return clamp(x, 0, 1);
|
|
219
|
+
}
|
|
220
|
+
function clampPositive(x) {
|
|
221
|
+
return Math.max(0, x);
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
210
224
|
//#region src/bg-render/mesh-renderer/cp-presets.ts
|
|
211
225
|
/** @internal */
|
|
212
226
|
const p = (cx, cy, x, y, ur = 0, vr = 0, up = 1, vp = 1) => Object.freeze({
|
|
@@ -379,11 +393,8 @@ const CONTROL_POINT_PRESETS = [
|
|
|
379
393
|
* 目的是取代原先大量的预设控制点代码
|
|
380
394
|
*/
|
|
381
395
|
const randomRange = (min, max) => Math.random() * (max - min) + min;
|
|
382
|
-
function clamp$1(x, min, max) {
|
|
383
|
-
return Math.min(Math.max(x, min), max);
|
|
384
|
-
}
|
|
385
396
|
function smoothstep(edge0, edge1, x) {
|
|
386
|
-
const t =
|
|
397
|
+
const t = clamp01((x - edge0) / (edge1 - edge0));
|
|
387
398
|
return t * t * (3 - 2 * t);
|
|
388
399
|
}
|
|
389
400
|
function smoothifyControlPoints(conf, w, h, iterations = 2, factor = .5, factorIterationModifier = .1) {
|
|
@@ -453,7 +464,7 @@ function smoothifyControlPoints(conf, w, h, iterations = 2, factor = .5, factorI
|
|
|
453
464
|
}
|
|
454
465
|
}
|
|
455
466
|
grid = newGrid;
|
|
456
|
-
f =
|
|
467
|
+
f = clamp01(f + factorIterationModifier);
|
|
457
468
|
}
|
|
458
469
|
for (let j = 0; j < h; j++) for (let i = 0; i < w; i++) conf[j * w + i] = grid[j][i];
|
|
459
470
|
}
|
|
@@ -1242,7 +1253,7 @@ var MeshGradientRenderer = class extends BaseRenderer {
|
|
|
1242
1253
|
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
1243
1254
|
this.quadProgram.use();
|
|
1244
1255
|
this.quadProgram.setUniform1i("u_texture", 0);
|
|
1245
|
-
this.quadProgram.setUniform1f("u_alpha", easeInOutSine(
|
|
1256
|
+
this.quadProgram.setUniform1f("u_alpha", easeInOutSine(clamp01(state.alpha)));
|
|
1246
1257
|
gl.activeTexture(gl.TEXTURE0);
|
|
1247
1258
|
gl.bindTexture(gl.TEXTURE_2D, this.fboTexture);
|
|
1248
1259
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadBuffer);
|
|
@@ -1477,7 +1488,7 @@ var PixiRenderer = class extends BaseRenderer {
|
|
|
1477
1488
|
lastContainer = /* @__PURE__ */ new Set();
|
|
1478
1489
|
onTick = (delta) => {
|
|
1479
1490
|
for (const lastContainer of this.lastContainer) {
|
|
1480
|
-
lastContainer.alpha =
|
|
1491
|
+
lastContainer.alpha = clampPositive(lastContainer.alpha - delta / 60);
|
|
1481
1492
|
if (lastContainer.alpha <= 0) {
|
|
1482
1493
|
this.app.stage.removeChild(lastContainer);
|
|
1483
1494
|
this.lastContainer.delete(lastContainer);
|
|
@@ -1716,14 +1727,6 @@ var lyric_player_module_default = {
|
|
|
1716
1727
|
"wordWithRuby": "FmKaba_wordWithRuby"
|
|
1717
1728
|
};
|
|
1718
1729
|
//#endregion
|
|
1719
|
-
//#region src/utils/eq-set.ts
|
|
1720
|
-
const eqSet = (xs, ys) => xs.size === ys.size && [...xs].every((x) => ys.has(x));
|
|
1721
|
-
//#endregion
|
|
1722
|
-
//#region src/utils/is-cjk.ts
|
|
1723
|
-
const isCJK = (char) => {
|
|
1724
|
-
return /^[\p{Unified_Ideograph}\u0800-\u9FFC]+$/u.test(char);
|
|
1725
|
-
};
|
|
1726
|
-
//#endregion
|
|
1727
1730
|
//#region src/utils/optimize-lyric.ts
|
|
1728
1731
|
const DEFAULT_OPTIMIZE_OPTIONS = {
|
|
1729
1732
|
normalizeSpaces: true,
|
|
@@ -1867,6 +1870,145 @@ function optimizeLyricLines(lines, options) {
|
|
|
1867
1870
|
if (config.tryAdvanceStartTime) tryAdvanceStartTime(lines);
|
|
1868
1871
|
}
|
|
1869
1872
|
//#endregion
|
|
1873
|
+
//#region src/lyric-player/dom/interlude-dots.ts
|
|
1874
|
+
function easeInOutBack(x) {
|
|
1875
|
+
const c2 = 1.70158 * 1.525;
|
|
1876
|
+
return x < .5 ? (2 * x) ** 2 * ((c2 + 1) * 2 * x - c2) / 2 : ((2 * x - 2) ** 2 * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2;
|
|
1877
|
+
}
|
|
1878
|
+
function easeOutExpo(x) {
|
|
1879
|
+
return x === 1 ? 1 : 1 - 2 ** (-10 * x);
|
|
1880
|
+
}
|
|
1881
|
+
var InterludeDots = class {
|
|
1882
|
+
element = document.createElement("div");
|
|
1883
|
+
dot0 = document.createElement("span");
|
|
1884
|
+
dot1 = document.createElement("span");
|
|
1885
|
+
dot2 = document.createElement("span");
|
|
1886
|
+
left = 0;
|
|
1887
|
+
top = 0;
|
|
1888
|
+
playing = true;
|
|
1889
|
+
lastStyle = "";
|
|
1890
|
+
currentInterlude;
|
|
1891
|
+
currentTime = 0;
|
|
1892
|
+
targetBreatheDuration = 1500;
|
|
1893
|
+
constructor() {
|
|
1894
|
+
this.element.className = lyric_player_module_default.interludeDots;
|
|
1895
|
+
this.element.appendChild(this.dot0);
|
|
1896
|
+
this.element.appendChild(this.dot1);
|
|
1897
|
+
this.element.appendChild(this.dot2);
|
|
1898
|
+
}
|
|
1899
|
+
getElement() {
|
|
1900
|
+
return this.element;
|
|
1901
|
+
}
|
|
1902
|
+
setTransform(left = this.left, top = this.top) {
|
|
1903
|
+
this.left = left;
|
|
1904
|
+
this.top = top;
|
|
1905
|
+
this.update();
|
|
1906
|
+
}
|
|
1907
|
+
setInterlude(interlude) {
|
|
1908
|
+
this.currentInterlude = interlude;
|
|
1909
|
+
this.currentTime = interlude?.[0] ?? 0;
|
|
1910
|
+
if (interlude) this.element.classList.add(lyric_player_module_default.enabled);
|
|
1911
|
+
else this.element.classList.remove(lyric_player_module_default.enabled);
|
|
1912
|
+
}
|
|
1913
|
+
pause() {
|
|
1914
|
+
this.playing = false;
|
|
1915
|
+
this.element.classList.remove(lyric_player_module_default.playing);
|
|
1916
|
+
}
|
|
1917
|
+
resume() {
|
|
1918
|
+
this.playing = true;
|
|
1919
|
+
this.element.classList.add(lyric_player_module_default.playing);
|
|
1920
|
+
}
|
|
1921
|
+
update(delta = 0) {
|
|
1922
|
+
if (!this.playing) return;
|
|
1923
|
+
this.currentTime += delta;
|
|
1924
|
+
let curStyle = "";
|
|
1925
|
+
curStyle += `transform:translate(${this.left.toFixed(2)}px, ${this.top.toFixed(2)}px)`;
|
|
1926
|
+
if (this.currentInterlude) {
|
|
1927
|
+
const interludeDuration = this.currentInterlude[1] - this.currentInterlude[0];
|
|
1928
|
+
const currentDuration = this.currentTime - this.currentInterlude[0];
|
|
1929
|
+
if (currentDuration <= interludeDuration) {
|
|
1930
|
+
const breatheDuration = interludeDuration / Math.ceil(interludeDuration / this.targetBreatheDuration);
|
|
1931
|
+
let scale = 1;
|
|
1932
|
+
let globalOpacity = 1;
|
|
1933
|
+
scale *= Math.sin(1.5 * Math.PI - currentDuration / breatheDuration * 2) / 20 + 1;
|
|
1934
|
+
if (currentDuration < 2e3) scale *= easeOutExpo(currentDuration / 2e3);
|
|
1935
|
+
if (currentDuration < 500) globalOpacity = 0;
|
|
1936
|
+
else if (currentDuration < 1e3) globalOpacity *= (currentDuration - 500) / 500;
|
|
1937
|
+
if (interludeDuration - currentDuration < 750) scale *= 1 - easeInOutBack((750 - (interludeDuration - currentDuration)) / 750 / 2);
|
|
1938
|
+
if (interludeDuration - currentDuration < 375) globalOpacity *= clamp01((interludeDuration - currentDuration) / 375);
|
|
1939
|
+
const dotsDuration = clampPositive(interludeDuration - 750);
|
|
1940
|
+
scale = clampPositive(scale) * .7;
|
|
1941
|
+
curStyle += ` scale(${scale})`;
|
|
1942
|
+
const dot0Opacity = clamp(.25, currentDuration * 3 / dotsDuration * .75, 1);
|
|
1943
|
+
const dot1Opacity = clamp(.25, (currentDuration - dotsDuration / 3) * 3 / dotsDuration * .75, 1);
|
|
1944
|
+
const dot2Opacity = clamp(.25, (currentDuration - dotsDuration / 3 * 2) * 3 / dotsDuration * .75, 1);
|
|
1945
|
+
this.dot0.style.opacity = `${clamp01(globalOpacity * dot0Opacity)}`;
|
|
1946
|
+
this.dot1.style.opacity = `${clamp01(globalOpacity * dot1Opacity)}`;
|
|
1947
|
+
this.dot2.style.opacity = `${clamp01(globalOpacity * dot2Opacity)}`;
|
|
1948
|
+
} else {
|
|
1949
|
+
curStyle += " scale(0)";
|
|
1950
|
+
this.dot0.style.opacity = "0";
|
|
1951
|
+
this.dot1.style.opacity = "0";
|
|
1952
|
+
this.dot2.style.opacity = "0";
|
|
1953
|
+
}
|
|
1954
|
+
curStyle += ";";
|
|
1955
|
+
if (this.lastStyle !== curStyle) {
|
|
1956
|
+
this.element.setAttribute("style", curStyle);
|
|
1957
|
+
this.lastStyle = curStyle;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
dispose() {
|
|
1962
|
+
this.element.remove();
|
|
1963
|
+
}
|
|
1964
|
+
};
|
|
1965
|
+
//#endregion
|
|
1966
|
+
//#region src/utils/schedule.ts
|
|
1967
|
+
const measureTasks = [];
|
|
1968
|
+
const mutateTasks = [];
|
|
1969
|
+
let scheduled = false;
|
|
1970
|
+
function onFlush() {
|
|
1971
|
+
let tmp = mutateTasks.shift();
|
|
1972
|
+
while (tmp) {
|
|
1973
|
+
try {
|
|
1974
|
+
tmp.resolve(tmp.task());
|
|
1975
|
+
} catch (error) {
|
|
1976
|
+
tmp.reject(error);
|
|
1977
|
+
}
|
|
1978
|
+
tmp = mutateTasks.shift();
|
|
1979
|
+
}
|
|
1980
|
+
tmp = measureTasks.shift();
|
|
1981
|
+
while (tmp) {
|
|
1982
|
+
try {
|
|
1983
|
+
tmp.resolve(tmp.task());
|
|
1984
|
+
} catch (error) {
|
|
1985
|
+
tmp.reject(error);
|
|
1986
|
+
}
|
|
1987
|
+
tmp = measureTasks.shift();
|
|
1988
|
+
}
|
|
1989
|
+
scheduled = false;
|
|
1990
|
+
}
|
|
1991
|
+
function scheduleFlush() {
|
|
1992
|
+
if (!scheduled) {
|
|
1993
|
+
scheduled = true;
|
|
1994
|
+
requestAnimationFrame(onFlush);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
function measure(callback) {
|
|
1998
|
+
const task = {
|
|
1999
|
+
task: callback,
|
|
2000
|
+
resolve: () => {},
|
|
2001
|
+
reject: () => {}
|
|
2002
|
+
};
|
|
2003
|
+
const promise = new Promise((resolve, reject) => {
|
|
2004
|
+
task.resolve = resolve;
|
|
2005
|
+
task.reject = reject;
|
|
2006
|
+
});
|
|
2007
|
+
measureTasks.push(task);
|
|
2008
|
+
scheduleFlush();
|
|
2009
|
+
return promise;
|
|
2010
|
+
}
|
|
2011
|
+
//#endregion
|
|
1870
2012
|
//#region src/utils/derivative.ts
|
|
1871
2013
|
function derivative(f) {
|
|
1872
2014
|
const h = .001;
|
|
@@ -1981,67 +2123,7 @@ function solveSpring(from, velocity, to, delay = 0, params) {
|
|
|
1981
2123
|
};
|
|
1982
2124
|
}
|
|
1983
2125
|
//#endregion
|
|
1984
|
-
//#region src/
|
|
1985
|
-
const measureTasks = [];
|
|
1986
|
-
const mutateTasks = [];
|
|
1987
|
-
let scheduled = false;
|
|
1988
|
-
function onFlush() {
|
|
1989
|
-
let tmp = mutateTasks.shift();
|
|
1990
|
-
while (tmp) {
|
|
1991
|
-
try {
|
|
1992
|
-
tmp.resolve(tmp.task());
|
|
1993
|
-
} catch (error) {
|
|
1994
|
-
tmp.reject(error);
|
|
1995
|
-
}
|
|
1996
|
-
tmp = mutateTasks.shift();
|
|
1997
|
-
}
|
|
1998
|
-
tmp = measureTasks.shift();
|
|
1999
|
-
while (tmp) {
|
|
2000
|
-
try {
|
|
2001
|
-
tmp.resolve(tmp.task());
|
|
2002
|
-
} catch (error) {
|
|
2003
|
-
tmp.reject(error);
|
|
2004
|
-
}
|
|
2005
|
-
tmp = measureTasks.shift();
|
|
2006
|
-
}
|
|
2007
|
-
scheduled = false;
|
|
2008
|
-
}
|
|
2009
|
-
function scheduleFlush() {
|
|
2010
|
-
if (!scheduled) {
|
|
2011
|
-
scheduled = true;
|
|
2012
|
-
requestAnimationFrame(onFlush);
|
|
2013
|
-
}
|
|
2014
|
-
}
|
|
2015
|
-
function measure(callback) {
|
|
2016
|
-
const task = {
|
|
2017
|
-
task: callback,
|
|
2018
|
-
resolve: () => {},
|
|
2019
|
-
reject: () => {}
|
|
2020
|
-
};
|
|
2021
|
-
const promise = new Promise((resolve, reject) => {
|
|
2022
|
-
task.resolve = resolve;
|
|
2023
|
-
task.reject = reject;
|
|
2024
|
-
});
|
|
2025
|
-
measureTasks.push(task);
|
|
2026
|
-
scheduleFlush();
|
|
2027
|
-
return promise;
|
|
2028
|
-
}
|
|
2029
|
-
function mutate(callback) {
|
|
2030
|
-
const task = {
|
|
2031
|
-
task: callback,
|
|
2032
|
-
resolve: () => {},
|
|
2033
|
-
reject: () => {}
|
|
2034
|
-
};
|
|
2035
|
-
const promise = new Promise((resolve, reject) => {
|
|
2036
|
-
task.resolve = resolve;
|
|
2037
|
-
task.reject = reject;
|
|
2038
|
-
});
|
|
2039
|
-
mutateTasks.push(task);
|
|
2040
|
-
scheduleFlush();
|
|
2041
|
-
return promise;
|
|
2042
|
-
}
|
|
2043
|
-
//#endregion
|
|
2044
|
-
//#region src/lyric-player/bottom-line.ts
|
|
2126
|
+
//#region src/lyric-player/base/bottom-line.ts
|
|
2045
2127
|
var BottomLineEl = class {
|
|
2046
2128
|
element = document.createElement("div");
|
|
2047
2129
|
left = 0;
|
|
@@ -2130,107 +2212,423 @@ var BottomLineEl = class {
|
|
|
2130
2212
|
}
|
|
2131
2213
|
};
|
|
2132
2214
|
//#endregion
|
|
2133
|
-
//#region src/lyric-player/
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2215
|
+
//#region src/lyric-player/base/consts.ts
|
|
2216
|
+
/** 歌词中不雅用语的掩码模式 */
|
|
2217
|
+
const MaskObsceneWordsMode = {
|
|
2218
|
+
/** 禁用任何不雅用语掩码 */
|
|
2219
|
+
Disabled: "",
|
|
2220
|
+
/** 完全掩码所有不雅用语 */
|
|
2221
|
+
FullMask: "full-mask",
|
|
2222
|
+
/** 保留首尾字符,屏蔽中间字符 */
|
|
2223
|
+
PartialMask: "partial-mask"
|
|
2224
|
+
};
|
|
2225
|
+
/**
|
|
2226
|
+
* 歌词行的渲染模式
|
|
2227
|
+
* @internal
|
|
2228
|
+
*/
|
|
2229
|
+
const LyricLineRenderMode = {
|
|
2230
|
+
SOLID: 0,
|
|
2231
|
+
GRADIENT: 1
|
|
2232
|
+
};
|
|
2233
|
+
/** 布局对齐锚点 */
|
|
2234
|
+
const LayoutAlignAnchor = {
|
|
2235
|
+
Top: "top",
|
|
2236
|
+
Center: "center",
|
|
2237
|
+
Bottom: "bottom"
|
|
2238
|
+
};
|
|
2239
|
+
//#endregion
|
|
2240
|
+
//#region src/lyric-player/base/layout.ts
|
|
2241
|
+
/**
|
|
2242
|
+
* 根据当前时间与当前目标行,计算当前是否处于某个可展示的间奏区间。
|
|
2243
|
+
*
|
|
2244
|
+
* 仅识别时间轴上的间奏空档,不涉及具体 DOM 元素的创建与摆放。
|
|
2245
|
+
* 若当前不应展示间奏动画,则返回 `undefined`。
|
|
2246
|
+
*/
|
|
2247
|
+
function computeCurrentInterlude(input) {
|
|
2248
|
+
const currentTime = input.currentTime + 20;
|
|
2249
|
+
const currentIndex = input.scrollToIndex;
|
|
2250
|
+
const lines = input.processedLines;
|
|
2251
|
+
const checkGap = (k) => {
|
|
2252
|
+
if (k < -1 || k >= lines.length - 1) return void 0;
|
|
2253
|
+
const prevLine = k === -1 ? null : lines[k];
|
|
2254
|
+
const nextLine = lines[k + 1];
|
|
2255
|
+
const gapStart = prevLine ? prevLine.endTime : 0;
|
|
2256
|
+
const gapEnd = Math.max(gapStart, nextLine.startTime - 250);
|
|
2257
|
+
if (gapEnd - gapStart < 4e3) return;
|
|
2258
|
+
if (gapEnd > currentTime && gapStart < currentTime) return {
|
|
2259
|
+
startTime: Math.max(gapStart, currentTime),
|
|
2260
|
+
endTime: gapEnd,
|
|
2261
|
+
anchorLineIndex: k,
|
|
2262
|
+
isNextDuet: nextLine.isDuet
|
|
2263
|
+
};
|
|
2264
|
+
};
|
|
2265
|
+
return checkGap(currentIndex - 1) || checkGap(currentIndex) || checkGap(currentIndex + 1);
|
|
2137
2266
|
}
|
|
2138
|
-
|
|
2139
|
-
|
|
2267
|
+
/**
|
|
2268
|
+
* 根据当前播放上下文计算歌词纵向滚动动画的弹簧参数。
|
|
2269
|
+
*
|
|
2270
|
+
* 其策略为:
|
|
2271
|
+
* - seeking 或间奏时使用更稳定的固定参数
|
|
2272
|
+
* - 普通播放时根据相邻歌词的时间间隔动态调整 stiffness / damping
|
|
2273
|
+
*/
|
|
2274
|
+
function computeLinePosYSpringParams(input) {
|
|
2275
|
+
const { enabled, processedLines, scrollToIndex, isSeeking, isInterludeActive } = input;
|
|
2276
|
+
if (!enabled || processedLines.length === 0) return { shouldUpdate: false };
|
|
2277
|
+
if (isSeeking || isInterludeActive) return {
|
|
2278
|
+
shouldUpdate: true,
|
|
2279
|
+
params: {
|
|
2280
|
+
stiffness: 90,
|
|
2281
|
+
damping: 15
|
|
2282
|
+
}
|
|
2283
|
+
};
|
|
2284
|
+
const currentLine = processedLines[scrollToIndex];
|
|
2285
|
+
const prevLine = processedLines[scrollToIndex - 1];
|
|
2286
|
+
if (!currentLine || !prevLine) return { shouldUpdate: false };
|
|
2287
|
+
const interval = currentLine.startTime - (prevLine.words[0]?.startTime ?? prevLine.startTime);
|
|
2288
|
+
const MIN_INTERVAL = 100;
|
|
2289
|
+
const MAX_INTERVAL = 800;
|
|
2290
|
+
const clampedInterval = clamp(interval, MIN_INTERVAL, MAX_INTERVAL);
|
|
2291
|
+
const MAX_STIFFNESS = 220;
|
|
2292
|
+
const MIN_STIFFNESS = 170;
|
|
2293
|
+
let ratio = 1 - (clampedInterval - MIN_INTERVAL) / (MAX_INTERVAL - MIN_INTERVAL);
|
|
2294
|
+
ratio = ratio ** .2;
|
|
2295
|
+
const targetStiffness = MIN_STIFFNESS + ratio * (MAX_STIFFNESS - MIN_STIFFNESS);
|
|
2296
|
+
return {
|
|
2297
|
+
shouldUpdate: true,
|
|
2298
|
+
params: {
|
|
2299
|
+
stiffness: targetStiffness,
|
|
2300
|
+
damping: Math.sqrt(targetStiffness) * 2.2
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
2140
2303
|
}
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
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
|
-
|
|
2304
|
+
/**
|
|
2305
|
+
* 计算单行歌词在当前布局中的视觉呈现参数。
|
|
2306
|
+
*
|
|
2307
|
+
* 根据播放状态、缓冲状态、布局模式与间奏信息,
|
|
2308
|
+
* 生成一行歌词最终应使用的 opacity、scale、blur 和 render mode。
|
|
2309
|
+
*/
|
|
2310
|
+
function computeLinePresentation(input) {
|
|
2311
|
+
const { line, lineIndex, scrollToIndex, latestIndex, hasBuffered, hidePassedLines, isPlaying, isNonDynamic, enableScale, enableBlur, isUserScrolling, isCompact, interlude } = input;
|
|
2312
|
+
const isActive = hasBuffered || lineIndex >= scrollToIndex && lineIndex < latestIndex;
|
|
2313
|
+
const blurLevel = computeLineBlur({
|
|
2314
|
+
enableBlur,
|
|
2315
|
+
isUserScrolling,
|
|
2316
|
+
isActive,
|
|
2317
|
+
itemIndex: lineIndex,
|
|
2318
|
+
scrollToIndex,
|
|
2319
|
+
latestIndex,
|
|
2320
|
+
isCompact
|
|
2321
|
+
});
|
|
2322
|
+
let targetOpacity;
|
|
2323
|
+
if (hidePassedLines) if (lineIndex < (interlude ? interlude.anchorLineIndex + 1 : scrollToIndex) && isPlaying) targetOpacity = 1e-4;
|
|
2324
|
+
else if (hasBuffered) targetOpacity = .85;
|
|
2325
|
+
else targetOpacity = isNonDynamic ? .2 : 1;
|
|
2326
|
+
else if (hasBuffered) targetOpacity = .85;
|
|
2327
|
+
else targetOpacity = isNonDynamic ? .2 : 1;
|
|
2328
|
+
const SCALE_ASPECT = enableScale ? 97 : 100;
|
|
2329
|
+
let targetScale = 100;
|
|
2330
|
+
if (!isActive && isPlaying) targetScale = line.isBG ? 75 : SCALE_ASPECT;
|
|
2331
|
+
return {
|
|
2332
|
+
isActive,
|
|
2333
|
+
targetOpacity,
|
|
2334
|
+
targetScale,
|
|
2335
|
+
blurLevel,
|
|
2336
|
+
renderMode: isActive ? LyricLineRenderMode.GRADIENT : LyricLineRenderMode.SOLID
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
/**
|
|
2340
|
+
* 计算一行歌词在当前布局中的模糊等级。
|
|
2341
|
+
*
|
|
2342
|
+
* 越远离当前对齐区域的歌词会得到更高的模糊值;
|
|
2343
|
+
* 活跃行、滚动交互中或关闭模糊效果时返回 `0`。
|
|
2344
|
+
*/
|
|
2345
|
+
function computeLineBlur(input) {
|
|
2346
|
+
const { enableBlur, isUserScrolling, isActive, itemIndex, scrollToIndex, latestIndex, isCompact } = input;
|
|
2347
|
+
if (!enableBlur || isUserScrolling || isActive) return 0;
|
|
2348
|
+
let blurLevel = 1;
|
|
2349
|
+
if (itemIndex < scrollToIndex) blurLevel += Math.abs(scrollToIndex - itemIndex) + 1;
|
|
2350
|
+
else blurLevel += Math.abs(itemIndex - Math.max(scrollToIndex, latestIndex));
|
|
2351
|
+
return isCompact ? blurLevel * .8 : blurLevel;
|
|
2352
|
+
}
|
|
2353
|
+
//#endregion
|
|
2354
|
+
//#region src/lyric-player/base/scroll.ts
|
|
2355
|
+
/**
|
|
2356
|
+
* 将滚动偏移量限制在当前允许的滚动边界内。
|
|
2357
|
+
*
|
|
2358
|
+
* 当手势滚动、滚轮滚动或惯性滚动更新了 {@link PlayerScrollState.scrollOffset}
|
|
2359
|
+
* 后,应调用本函数以避免视图越界。
|
|
2360
|
+
*/
|
|
2361
|
+
function clampPlayerScrollOffset(scrollState) {
|
|
2362
|
+
scrollState.scrollOffset = clamp(scrollState.scrollOffset, scrollState.scrollBoundary.minOffset, scrollState.scrollBoundary.maxOffset);
|
|
2363
|
+
}
|
|
2364
|
+
/**
|
|
2365
|
+
* 重置滚动状态到未发生用户滚动时的初始状态。
|
|
2366
|
+
*
|
|
2367
|
+
* 本函数会清除当前偏移,并结束“已滚动”与“正在滚动”的标记;
|
|
2368
|
+
* **不会清理**外部持有的计时器或事件监听器。
|
|
2369
|
+
*/
|
|
2370
|
+
function resetPlayerScrollState(scrollState) {
|
|
2371
|
+
scrollState.isScrolled = false;
|
|
2372
|
+
scrollState.scrollOffset = 0;
|
|
2373
|
+
scrollState.isUserScrolling = false;
|
|
2374
|
+
}
|
|
2375
|
+
/**
|
|
2376
|
+
* 向指定元素挂载歌词滚动相关的交互处理器。
|
|
2377
|
+
*
|
|
2378
|
+
* 该函数会处理:
|
|
2379
|
+
* - 触摸拖拽滚动
|
|
2380
|
+
* - 触摸结束后的惯性滚动
|
|
2381
|
+
* - 滚轮滚动
|
|
2382
|
+
* - 轻触时的点击透传
|
|
2383
|
+
*
|
|
2384
|
+
* 只更新 {@link PlayerScrollState} 并通过回调通知宿主执行布局或其它副作用,
|
|
2385
|
+
* 不直接依赖具体的播放器类实现。
|
|
2386
|
+
*/
|
|
2387
|
+
function attachPlayerScrollHandlers(element, scrollState, callbacks) {
|
|
2388
|
+
let startScrollY = 0;
|
|
2389
|
+
let startTouchPosY = 0;
|
|
2390
|
+
let startTouchStartX = 0;
|
|
2391
|
+
let startTouchStartY = 0;
|
|
2392
|
+
let lastMoveY = 0;
|
|
2393
|
+
let startScrollTime = 0;
|
|
2394
|
+
let scrollSpeed = 0;
|
|
2395
|
+
let curScrollId = 0;
|
|
2396
|
+
element.addEventListener("touchstart", (evt) => {
|
|
2397
|
+
if (callbacks.onBeginScroll()) {
|
|
2398
|
+
scrollState.isUserScrolling = true;
|
|
2399
|
+
evt.preventDefault();
|
|
2400
|
+
startScrollY = scrollState.scrollOffset;
|
|
2401
|
+
startTouchPosY = evt.touches[0].screenY;
|
|
2402
|
+
lastMoveY = startTouchPosY;
|
|
2403
|
+
startTouchStartX = evt.touches[0].screenX;
|
|
2404
|
+
startTouchStartY = evt.touches[0].screenY;
|
|
2405
|
+
startScrollTime = Date.now();
|
|
2406
|
+
scrollSpeed = 0;
|
|
2407
|
+
callbacks.onLayout(true, true);
|
|
2408
|
+
}
|
|
2409
|
+
});
|
|
2410
|
+
element.addEventListener("touchmove", (evt) => {
|
|
2411
|
+
if (callbacks.onBeginScroll()) {
|
|
2412
|
+
evt.preventDefault();
|
|
2413
|
+
const currentY = evt.touches[0].screenY;
|
|
2414
|
+
const deltaY = currentY - startTouchPosY;
|
|
2415
|
+
scrollState.scrollOffset = startScrollY - deltaY;
|
|
2416
|
+
clampPlayerScrollOffset(scrollState);
|
|
2417
|
+
const now = Date.now();
|
|
2418
|
+
const dt = now - startScrollTime;
|
|
2419
|
+
if (dt > 0) scrollSpeed = (currentY - lastMoveY) / dt;
|
|
2420
|
+
lastMoveY = currentY;
|
|
2421
|
+
startScrollTime = now;
|
|
2422
|
+
callbacks.onLayout(true, true);
|
|
2423
|
+
}
|
|
2424
|
+
});
|
|
2425
|
+
element.addEventListener("touchend", (evt) => {
|
|
2426
|
+
if (callbacks.onBeginScroll()) {
|
|
2427
|
+
evt.preventDefault();
|
|
2428
|
+
const touch = evt.changedTouches[0];
|
|
2429
|
+
const moveX = Math.abs(touch.screenX - startTouchStartX);
|
|
2430
|
+
const moveY = Math.abs(touch.screenY - startTouchStartY);
|
|
2431
|
+
if (moveX < 10 && moveY < 10) {
|
|
2432
|
+
const target = document.elementFromPoint(touch.clientX, touch.clientY);
|
|
2433
|
+
if (target instanceof HTMLElement && callbacks.containsTarget(target)) callbacks.clickTarget(target);
|
|
2434
|
+
scrollState.isUserScrolling = false;
|
|
2435
|
+
callbacks.onEndScroll();
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2438
|
+
startTouchPosY = 0;
|
|
2439
|
+
const scrollId = ++curScrollId;
|
|
2440
|
+
if (Math.abs(scrollSpeed) < .1) scrollSpeed = 0;
|
|
2441
|
+
let lastFrameTime = performance.now();
|
|
2442
|
+
const onScrollFrame = (time) => {
|
|
2443
|
+
if (scrollId !== curScrollId) return;
|
|
2444
|
+
const dt = time - lastFrameTime;
|
|
2445
|
+
lastFrameTime = time;
|
|
2446
|
+
if (dt <= 0 || dt > 100) {
|
|
2447
|
+
requestAnimationFrame(onScrollFrame);
|
|
2448
|
+
return;
|
|
2449
|
+
}
|
|
2450
|
+
if (Math.abs(scrollSpeed) > .05) {
|
|
2451
|
+
scrollState.scrollOffset -= scrollSpeed * dt;
|
|
2452
|
+
clampPlayerScrollOffset(scrollState);
|
|
2453
|
+
const frictionFactor = .95 ** (dt / 16);
|
|
2454
|
+
scrollSpeed *= frictionFactor;
|
|
2455
|
+
callbacks.onLayout(true, true);
|
|
2456
|
+
requestAnimationFrame(onScrollFrame);
|
|
2457
|
+
} else {
|
|
2458
|
+
scrollState.isUserScrolling = false;
|
|
2459
|
+
callbacks.onEndScroll();
|
|
2460
|
+
}
|
|
2461
|
+
};
|
|
2462
|
+
requestAnimationFrame(onScrollFrame);
|
|
2463
|
+
} else scrollState.isUserScrolling = false;
|
|
2464
|
+
});
|
|
2465
|
+
element.addEventListener("wheel", (evt) => {
|
|
2466
|
+
if (callbacks.onBeginScroll()) {
|
|
2467
|
+
evt.preventDefault();
|
|
2468
|
+
if (evt.deltaMode === evt.DOM_DELTA_PIXEL) {
|
|
2469
|
+
scrollState.scrollOffset += evt.deltaY;
|
|
2470
|
+
clampPlayerScrollOffset(scrollState);
|
|
2471
|
+
callbacks.onLayout(true, false);
|
|
2209
2472
|
} else {
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
this.dot2.style.opacity = "0";
|
|
2473
|
+
scrollState.scrollOffset += evt.deltaY * 50;
|
|
2474
|
+
clampPlayerScrollOffset(scrollState);
|
|
2475
|
+
callbacks.onLayout(false, false);
|
|
2214
2476
|
}
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2477
|
+
}
|
|
2478
|
+
}, { passive: false });
|
|
2479
|
+
}
|
|
2480
|
+
//#endregion
|
|
2481
|
+
//#region src/utils/eq-set.ts
|
|
2482
|
+
const eqSet = (xs, ys) => xs.size === ys.size && [...xs].every((x) => ys.has(x));
|
|
2483
|
+
//#endregion
|
|
2484
|
+
//#region src/lyric-player/base/timeline.ts
|
|
2485
|
+
/**
|
|
2486
|
+
* 计算指定时间点的热行/缓冲行状态转移的纯函数。其行为包括:
|
|
2487
|
+
*
|
|
2488
|
+
* - 根据当前时间和已有的热行状态,计算出新的热行状态,并返回应新增的热行 ID 和应移除的热行 ID
|
|
2489
|
+
* - 根据新的热行状态和已有的缓冲行状态,计算出应移除的缓冲行 ID
|
|
2490
|
+
*/
|
|
2491
|
+
function computePlayerTimeState(input) {
|
|
2492
|
+
const { time, processedLines, timelineState: { hotLines, bufferedLines } } = input;
|
|
2493
|
+
const nextHotLines = new Set(hotLines);
|
|
2494
|
+
const addedIds = /* @__PURE__ */ new Set();
|
|
2495
|
+
const removedHotIds = /* @__PURE__ */ new Set();
|
|
2496
|
+
const removedBufferedIds = /* @__PURE__ */ new Set();
|
|
2497
|
+
for (const lastHotId of hotLines) {
|
|
2498
|
+
const line = processedLines[lastHotId];
|
|
2499
|
+
if (!line) {
|
|
2500
|
+
nextHotLines.delete(lastHotId);
|
|
2501
|
+
removedHotIds.add(lastHotId);
|
|
2502
|
+
continue;
|
|
2503
|
+
}
|
|
2504
|
+
if (line.isBG) continue;
|
|
2505
|
+
const nextLine = processedLines[lastHotId + 1];
|
|
2506
|
+
if (nextLine?.isBG) {
|
|
2507
|
+
const nextMainLine = processedLines[lastHotId + 2];
|
|
2508
|
+
const startTime = Math.min(line.startTime, nextLine.startTime);
|
|
2509
|
+
const endTime = Math.min(Math.max(line.endTime, nextMainLine?.startTime ?? Number.MAX_VALUE), Math.max(line.endTime, nextLine.endTime));
|
|
2510
|
+
if (time < startTime || endTime <= time) {
|
|
2511
|
+
nextHotLines.delete(lastHotId);
|
|
2512
|
+
removedHotIds.add(lastHotId);
|
|
2513
|
+
nextHotLines.delete(lastHotId + 1);
|
|
2514
|
+
removedHotIds.add(lastHotId + 1);
|
|
2515
|
+
}
|
|
2516
|
+
} else if (time < line.startTime || line.endTime <= time) {
|
|
2517
|
+
nextHotLines.delete(lastHotId);
|
|
2518
|
+
removedHotIds.add(lastHotId);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
for (let id = 0; id < processedLines.length; id++) {
|
|
2522
|
+
const line = processedLines[id];
|
|
2523
|
+
if (!line || line.isBG) continue;
|
|
2524
|
+
if (line.startTime <= time && line.endTime > time && !nextHotLines.has(id)) {
|
|
2525
|
+
nextHotLines.add(id);
|
|
2526
|
+
addedIds.add(id);
|
|
2527
|
+
if (processedLines[id + 1]?.isBG) {
|
|
2528
|
+
nextHotLines.add(id + 1);
|
|
2529
|
+
addedIds.add(id + 1);
|
|
2219
2530
|
}
|
|
2220
2531
|
}
|
|
2221
2532
|
}
|
|
2222
|
-
|
|
2223
|
-
|
|
2533
|
+
for (const id of bufferedLines) if (!nextHotLines.has(id)) removedBufferedIds.add(id);
|
|
2534
|
+
return {
|
|
2535
|
+
nextHotLines,
|
|
2536
|
+
addedIds,
|
|
2537
|
+
removedHotIds,
|
|
2538
|
+
removedBufferedIds
|
|
2539
|
+
};
|
|
2540
|
+
}
|
|
2541
|
+
/**
|
|
2542
|
+
* 在 seeking 场景下,根据当前时间选出应对齐滚动到的目标行索引。
|
|
2543
|
+
*
|
|
2544
|
+
* 若当前仍存在缓冲行,则优先对齐到最靠前的缓冲行;
|
|
2545
|
+
* 否则对齐到第一条开始时间不小于当前时间的歌词行。
|
|
2546
|
+
*/
|
|
2547
|
+
function pickScrollToIndexForSeek(time, processedLines, bufferedLines) {
|
|
2548
|
+
if (bufferedLines.size > 0) return Math.min(...bufferedLines);
|
|
2549
|
+
const foundIndex = processedLines.findIndex((line) => line.startTime >= time);
|
|
2550
|
+
return foundIndex === -1 ? processedLines.length : foundIndex;
|
|
2551
|
+
}
|
|
2552
|
+
/**
|
|
2553
|
+
* 提交时间线状态转移的纯函数。
|
|
2554
|
+
*
|
|
2555
|
+
* 把一次时间线状态转移写回 {@link PlayerTimelineState},
|
|
2556
|
+
* 并返回一份供宿主执行的副作用应用计划,例如启用/禁用哪些歌词行、
|
|
2557
|
+
* 是否需要重置用户滚动状态、是否需要触发布局。
|
|
2558
|
+
*/
|
|
2559
|
+
function commitPlayerTimeState(input) {
|
|
2560
|
+
const { timelineState, time, processedLines, hasBottomContent, stateResult } = input;
|
|
2561
|
+
const { addedIds, removedHotIds, removedBufferedIds } = stateResult;
|
|
2562
|
+
const { isSeeking } = timelineState;
|
|
2563
|
+
timelineState.currentTime = time;
|
|
2564
|
+
timelineState.hotLines = stateResult.nextHotLines;
|
|
2565
|
+
let shouldLayout = false;
|
|
2566
|
+
let shouldResetScroll = false;
|
|
2567
|
+
const linesToEnable = [];
|
|
2568
|
+
const linesToDisable = /* @__PURE__ */ new Set();
|
|
2569
|
+
if (isSeeking) {
|
|
2570
|
+
timelineState.bufferedLines = new Set([...timelineState.hotLines]);
|
|
2571
|
+
timelineState.scrollToIndex = pickScrollToIndexForSeek(time, processedLines, timelineState.bufferedLines);
|
|
2572
|
+
for (const id of removedHotIds) linesToDisable.add(id);
|
|
2573
|
+
for (const id of timelineState.hotLines) linesToEnable.push(id);
|
|
2574
|
+
for (const id of removedBufferedIds) linesToDisable.add(id);
|
|
2575
|
+
shouldResetScroll = true;
|
|
2576
|
+
shouldLayout = true;
|
|
2577
|
+
} else if (addedIds.size > 0) {
|
|
2578
|
+
for (const id of addedIds) {
|
|
2579
|
+
timelineState.bufferedLines.add(id);
|
|
2580
|
+
linesToEnable.push(id);
|
|
2581
|
+
}
|
|
2582
|
+
for (const id of removedBufferedIds) {
|
|
2583
|
+
timelineState.bufferedLines.delete(id);
|
|
2584
|
+
linesToDisable.add(id);
|
|
2585
|
+
}
|
|
2586
|
+
if (timelineState.bufferedLines.size > 0) timelineState.scrollToIndex = Math.min(...timelineState.bufferedLines);
|
|
2587
|
+
shouldLayout = true;
|
|
2588
|
+
} else if (removedBufferedIds.size > 0 && eqSet(removedBufferedIds, timelineState.bufferedLines)) {
|
|
2589
|
+
for (const id of timelineState.bufferedLines) {
|
|
2590
|
+
if (timelineState.hotLines.has(id)) continue;
|
|
2591
|
+
timelineState.bufferedLines.delete(id);
|
|
2592
|
+
linesToDisable.add(id);
|
|
2593
|
+
}
|
|
2594
|
+
shouldLayout = true;
|
|
2595
|
+
}
|
|
2596
|
+
if (timelineState.bufferedLines.size === 0 && processedLines.length > 0) {
|
|
2597
|
+
if (time >= processedLines[processedLines.length - 1].endTime) {
|
|
2598
|
+
const targetIndex = hasBottomContent ? processedLines.length : processedLines.length - 1;
|
|
2599
|
+
if (timelineState.scrollToIndex !== targetIndex) {
|
|
2600
|
+
timelineState.scrollToIndex = targetIndex;
|
|
2601
|
+
shouldLayout = true;
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2224
2604
|
}
|
|
2225
|
-
|
|
2605
|
+
timelineState.lastCurrentTime = time;
|
|
2606
|
+
return {
|
|
2607
|
+
shouldLayout,
|
|
2608
|
+
shouldResetScroll,
|
|
2609
|
+
linesToEnable,
|
|
2610
|
+
linesToDisable: [...linesToDisable]
|
|
2611
|
+
};
|
|
2612
|
+
}
|
|
2226
2613
|
//#endregion
|
|
2227
|
-
//#region src/lyric-player/base.ts
|
|
2614
|
+
//#region src/lyric-player/base/index.ts
|
|
2228
2615
|
/**
|
|
2229
|
-
*
|
|
2616
|
+
* 歌词播放器的基类,已经包含了有关歌词操作和排版的功能,
|
|
2617
|
+
* 子类需要为其实现对应的显示展示操作
|
|
2230
2618
|
*/
|
|
2231
2619
|
var LyricPlayerBase = class extends EventTarget {
|
|
2232
2620
|
element = document.createElement("div");
|
|
2233
|
-
|
|
2621
|
+
/** 播放时间线状态 */
|
|
2622
|
+
timelineState = {
|
|
2623
|
+
currentTime: 0,
|
|
2624
|
+
lastCurrentTime: 0,
|
|
2625
|
+
hotLines: /* @__PURE__ */ new Set(),
|
|
2626
|
+
bufferedLines: /* @__PURE__ */ new Set(),
|
|
2627
|
+
scrollToIndex: 0,
|
|
2628
|
+
isSeeking: false,
|
|
2629
|
+
isPlaying: true,
|
|
2630
|
+
initialLayoutFinished: false
|
|
2631
|
+
};
|
|
2234
2632
|
/** @internal */
|
|
2235
2633
|
lyricLinesSize = /* @__PURE__ */ new WeakMap();
|
|
2236
2634
|
/** @internal */
|
|
@@ -2238,42 +2636,38 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2238
2636
|
currentLyricLines = [];
|
|
2239
2637
|
processedLines = [];
|
|
2240
2638
|
lyricLinesIndexes = /* @__PURE__ */ new WeakMap();
|
|
2241
|
-
hotLines = /* @__PURE__ */ new Set();
|
|
2242
|
-
bufferedLines = /* @__PURE__ */ new Set();
|
|
2243
2639
|
isNonDynamic = false;
|
|
2244
2640
|
hasDuetLine = false;
|
|
2245
|
-
scrollToIndex = 0;
|
|
2246
2641
|
disableSpring = false;
|
|
2247
|
-
|
|
2642
|
+
layoutState = {
|
|
2643
|
+
interludeDotsSize: [0, 0],
|
|
2644
|
+
targetAlignIndex: 0,
|
|
2645
|
+
lastInterludeState: false,
|
|
2646
|
+
alignAnchor: LayoutAlignAnchor.Center,
|
|
2647
|
+
alignPosition: .35,
|
|
2648
|
+
overscanPx: 300
|
|
2649
|
+
};
|
|
2248
2650
|
interludeDots = new InterludeDots();
|
|
2249
2651
|
bottomLine = new BottomLineEl(this);
|
|
2250
2652
|
enableBlur = true;
|
|
2251
2653
|
enableScale = true;
|
|
2252
|
-
maskObsceneWords =
|
|
2654
|
+
maskObsceneWords = MaskObsceneWordsMode.Disabled;
|
|
2253
2655
|
maskObsceneWordChar = "*";
|
|
2254
2656
|
hidePassedLines = false;
|
|
2255
|
-
|
|
2657
|
+
scrollState = {
|
|
2658
|
+
scrollBoundary: {
|
|
2659
|
+
minOffset: 0,
|
|
2660
|
+
maxOffset: 0
|
|
2661
|
+
},
|
|
2662
|
+
scrollOffset: 0,
|
|
2663
|
+
allowScroll: true,
|
|
2664
|
+
isScrolled: false,
|
|
2665
|
+
isUserScrolling: false
|
|
2666
|
+
};
|
|
2256
2667
|
currentLyricLineObjects = [];
|
|
2257
|
-
isSeeking = false;
|
|
2258
|
-
lastCurrentTime = 0;
|
|
2259
|
-
alignAnchor = "center";
|
|
2260
|
-
alignPosition = .35;
|
|
2261
|
-
scrollOffset = 0;
|
|
2262
2668
|
size = [0, 0];
|
|
2263
|
-
allowScroll = true;
|
|
2264
2669
|
isPageVisible = true;
|
|
2265
2670
|
optimizeOptions = {};
|
|
2266
|
-
initialLayoutFinished = false;
|
|
2267
|
-
/**
|
|
2268
|
-
* 标记用户是否正在进行滚动交互
|
|
2269
|
-
*/
|
|
2270
|
-
isUserScrolling = false;
|
|
2271
|
-
wheelTimeout;
|
|
2272
|
-
/**
|
|
2273
|
-
* 视图额外预渲染(overscan)距离,单位:像素。
|
|
2274
|
-
* 用于决定在视口之外多少距离内也认为是“可见”,以便提前创建/保留行元素。
|
|
2275
|
-
*/
|
|
2276
|
-
overscanPx = 300;
|
|
2277
2671
|
posXSpringParams = {
|
|
2278
2672
|
mass: 1,
|
|
2279
2673
|
damping: 10,
|
|
@@ -2296,13 +2690,12 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2296
2690
|
};
|
|
2297
2691
|
onPageShow = () => {
|
|
2298
2692
|
this.isPageVisible = true;
|
|
2299
|
-
this.setCurrentTime(this.currentTime, true);
|
|
2693
|
+
this.setCurrentTime(this.timelineState.currentTime, true);
|
|
2300
2694
|
};
|
|
2301
2695
|
onPageHide = () => {
|
|
2302
2696
|
this.isPageVisible = false;
|
|
2303
2697
|
};
|
|
2304
2698
|
scrolledHandler;
|
|
2305
|
-
isScrolled = false;
|
|
2306
2699
|
/** @internal */
|
|
2307
2700
|
resizeObserver = new ResizeObserver(((entries) => {
|
|
2308
2701
|
let shouldRelayout = false;
|
|
@@ -2313,8 +2706,8 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2313
2706
|
this.size[1] = rect.height;
|
|
2314
2707
|
shouldRebuildPlayerStyle = true;
|
|
2315
2708
|
} else if (entry.target === this.interludeDots.getElement()) {
|
|
2316
|
-
this.interludeDotsSize[0] = entry.target.clientWidth;
|
|
2317
|
-
this.interludeDotsSize[1] = entry.target.clientHeight;
|
|
2709
|
+
this.layoutState.interludeDotsSize[0] = entry.target.clientWidth;
|
|
2710
|
+
this.layoutState.interludeDotsSize[1] = entry.target.clientHeight;
|
|
2318
2711
|
shouldRelayout = true;
|
|
2319
2712
|
} else if (entry.target === this.bottomLine.getElement()) {
|
|
2320
2713
|
const newSize = [entry.target.clientWidth, entry.target.clientHeight];
|
|
@@ -2339,8 +2732,6 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2339
2732
|
if (shouldRebuildPlayerStyle) this.onResize();
|
|
2340
2733
|
}));
|
|
2341
2734
|
wordFadeWidth = .5;
|
|
2342
|
-
targetAlignIndex = 0;
|
|
2343
|
-
lastInterludeState = false;
|
|
2344
2735
|
constructor(element) {
|
|
2345
2736
|
super();
|
|
2346
2737
|
if (element) this.element = element;
|
|
@@ -2352,114 +2743,27 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2352
2743
|
this.interludeDots.setTransform(0, 200);
|
|
2353
2744
|
window.addEventListener("pageshow", this.onPageShow);
|
|
2354
2745
|
window.addEventListener("pagehide", this.onPageHide);
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
let scrollSpeed = 0;
|
|
2362
|
-
let curScrollId = 0;
|
|
2363
|
-
this.element.addEventListener("touchstart", (evt) => {
|
|
2364
|
-
if (this.beginScrollHandler()) {
|
|
2365
|
-
this.isUserScrolling = true;
|
|
2366
|
-
evt.preventDefault();
|
|
2367
|
-
startScrollY = this.scrollOffset;
|
|
2368
|
-
startTouchPosY = evt.touches[0].screenY;
|
|
2369
|
-
lastMoveY = startTouchPosY;
|
|
2370
|
-
startTouchStartX = evt.touches[0].screenX;
|
|
2371
|
-
startTouchStartY = evt.touches[0].screenY;
|
|
2372
|
-
startScrollTime = Date.now();
|
|
2373
|
-
scrollSpeed = 0;
|
|
2374
|
-
this.calcLayout(true, true);
|
|
2375
|
-
}
|
|
2376
|
-
});
|
|
2377
|
-
this.element.addEventListener("touchmove", (evt) => {
|
|
2378
|
-
if (this.beginScrollHandler()) {
|
|
2379
|
-
evt.preventDefault();
|
|
2380
|
-
const currentY = evt.touches[0].screenY;
|
|
2381
|
-
const deltaY = currentY - startTouchPosY;
|
|
2382
|
-
this.scrollOffset = startScrollY - deltaY;
|
|
2383
|
-
this.limitScrollOffset();
|
|
2384
|
-
const now = Date.now();
|
|
2385
|
-
const dt = now - startScrollTime;
|
|
2386
|
-
if (dt > 0) scrollSpeed = (currentY - lastMoveY) / dt;
|
|
2387
|
-
lastMoveY = currentY;
|
|
2388
|
-
startScrollTime = now;
|
|
2389
|
-
this.calcLayout(true, true);
|
|
2390
|
-
}
|
|
2391
|
-
});
|
|
2392
|
-
this.element.addEventListener("touchend", (evt) => {
|
|
2393
|
-
if (this.beginScrollHandler()) {
|
|
2394
|
-
evt.preventDefault();
|
|
2395
|
-
const touch = evt.changedTouches[0];
|
|
2396
|
-
const moveX = Math.abs(touch.screenX - startTouchStartX);
|
|
2397
|
-
const moveY = Math.abs(touch.screenY - startTouchStartY);
|
|
2398
|
-
if (moveX < 10 && moveY < 10) {
|
|
2399
|
-
const target = document.elementFromPoint(touch.clientX, touch.clientY);
|
|
2400
|
-
if (target && this.element.contains(target)) target.click();
|
|
2401
|
-
this.isUserScrolling = false;
|
|
2402
|
-
this.endScrollHandler();
|
|
2403
|
-
return;
|
|
2404
|
-
}
|
|
2405
|
-
startTouchPosY = 0;
|
|
2406
|
-
const scrollId = ++curScrollId;
|
|
2407
|
-
if (Math.abs(scrollSpeed) < .1) scrollSpeed = 0;
|
|
2408
|
-
let lastFrameTime = performance.now();
|
|
2409
|
-
const onScrollFrame = (time) => {
|
|
2410
|
-
if (scrollId !== curScrollId) return;
|
|
2411
|
-
const dt = time - lastFrameTime;
|
|
2412
|
-
lastFrameTime = time;
|
|
2413
|
-
if (dt <= 0 || dt > 100) {
|
|
2414
|
-
requestAnimationFrame(onScrollFrame);
|
|
2415
|
-
return;
|
|
2416
|
-
}
|
|
2417
|
-
if (Math.abs(scrollSpeed) > .05) {
|
|
2418
|
-
this.scrollOffset -= scrollSpeed * dt;
|
|
2419
|
-
this.limitScrollOffset();
|
|
2420
|
-
const frictionFactor = .95 ** (dt / 16);
|
|
2421
|
-
scrollSpeed *= frictionFactor;
|
|
2422
|
-
this.calcLayout(true, true);
|
|
2423
|
-
requestAnimationFrame(onScrollFrame);
|
|
2424
|
-
} else {
|
|
2425
|
-
this.isUserScrolling = false;
|
|
2426
|
-
this.endScrollHandler();
|
|
2427
|
-
}
|
|
2428
|
-
};
|
|
2429
|
-
requestAnimationFrame(onScrollFrame);
|
|
2430
|
-
} else this.isUserScrolling = false;
|
|
2746
|
+
attachPlayerScrollHandlers(this.element, this.scrollState, {
|
|
2747
|
+
onBeginScroll: () => this.beginScrollHandler(),
|
|
2748
|
+
onEndScroll: () => this.endScrollHandler(),
|
|
2749
|
+
onLayout: (sync, force) => this.calcLayout(sync, force),
|
|
2750
|
+
containsTarget: (target) => this.element.contains(target),
|
|
2751
|
+
clickTarget: (target) => target.click()
|
|
2431
2752
|
});
|
|
2432
|
-
this.element.addEventListener("wheel", (evt) => {
|
|
2433
|
-
if (this.beginScrollHandler()) {
|
|
2434
|
-
evt.preventDefault();
|
|
2435
|
-
if (evt.deltaMode === evt.DOM_DELTA_PIXEL) {
|
|
2436
|
-
this.scrollOffset += evt.deltaY;
|
|
2437
|
-
this.limitScrollOffset();
|
|
2438
|
-
this.calcLayout(true, false);
|
|
2439
|
-
} else {
|
|
2440
|
-
this.scrollOffset += evt.deltaY * 50;
|
|
2441
|
-
this.limitScrollOffset();
|
|
2442
|
-
this.calcLayout(false, false);
|
|
2443
|
-
}
|
|
2444
|
-
}
|
|
2445
|
-
}, { passive: false });
|
|
2446
2753
|
}
|
|
2447
2754
|
beginScrollHandler() {
|
|
2448
|
-
const allowed = this.allowScroll;
|
|
2755
|
+
const allowed = this.scrollState.allowScroll;
|
|
2449
2756
|
if (allowed) {
|
|
2450
|
-
this.isScrolled = true;
|
|
2757
|
+
this.scrollState.isScrolled = true;
|
|
2451
2758
|
clearTimeout(this.scrolledHandler);
|
|
2452
2759
|
this.scrolledHandler = setTimeout(() => {
|
|
2453
|
-
this.isScrolled = false;
|
|
2454
|
-
this.scrollOffset = 0;
|
|
2760
|
+
this.scrollState.isScrolled = false;
|
|
2761
|
+
this.scrollState.scrollOffset = 0;
|
|
2455
2762
|
}, 5e3);
|
|
2456
2763
|
}
|
|
2457
2764
|
return allowed;
|
|
2458
2765
|
}
|
|
2459
2766
|
endScrollHandler() {}
|
|
2460
|
-
limitScrollOffset() {
|
|
2461
|
-
this.scrollOffset = Math.max(Math.min(this.scrollBoundary[1], this.scrollOffset), this.scrollBoundary[0]);
|
|
2462
|
-
}
|
|
2463
2767
|
/**
|
|
2464
2768
|
* 设置文字动画的渐变宽度,单位以歌词行的主文字字体大小的倍数为单位,默认为 0.5,即一个全角字符的一半宽度
|
|
2465
2769
|
*
|
|
@@ -2501,7 +2805,7 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2501
2805
|
return this.wordFadeWidth;
|
|
2502
2806
|
}
|
|
2503
2807
|
setIsSeeking(isSeeking) {
|
|
2504
|
-
this.isSeeking = isSeeking;
|
|
2808
|
+
this.timelineState.isSeeking = isSeeking;
|
|
2505
2809
|
}
|
|
2506
2810
|
/**
|
|
2507
2811
|
* 设置是否隐藏已经播放过的歌词行,默认不隐藏
|
|
@@ -2539,7 +2843,7 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2539
2843
|
const c = char.charAt(0) || "*";
|
|
2540
2844
|
if (this.maskObsceneWordChar === c) return;
|
|
2541
2845
|
this.maskObsceneWordChar = c;
|
|
2542
|
-
if (this.maskObsceneWords !==
|
|
2846
|
+
if (this.maskObsceneWords !== MaskObsceneWordsMode.Disabled) {
|
|
2543
2847
|
this.rebuildLyricLines();
|
|
2544
2848
|
this.calcLayout();
|
|
2545
2849
|
}
|
|
@@ -2554,10 +2858,10 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2554
2858
|
*/
|
|
2555
2859
|
processObsceneWord(word) {
|
|
2556
2860
|
const text = word.word;
|
|
2557
|
-
if (!word.obscene || this.maskObsceneWords ===
|
|
2861
|
+
if (!word.obscene || this.maskObsceneWords === MaskObsceneWordsMode.Disabled) return text;
|
|
2558
2862
|
const maskChar = this.maskObsceneWordChar;
|
|
2559
|
-
if (this.maskObsceneWords ===
|
|
2560
|
-
if (this.maskObsceneWords ===
|
|
2863
|
+
if (this.maskObsceneWords === MaskObsceneWordsMode.FullMask) return text.replace(/\S/g, maskChar);
|
|
2864
|
+
if (this.maskObsceneWords === MaskObsceneWordsMode.PartialMask) {
|
|
2561
2865
|
const trimmed = text.trim();
|
|
2562
2866
|
if (trimmed.length <= 2) return text.replace(/\S/g, maskChar);
|
|
2563
2867
|
const startPos = text.indexOf(trimmed);
|
|
@@ -2575,25 +2879,25 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2575
2879
|
* @param alignAnchor 歌词行对齐方式,详情见函数说明
|
|
2576
2880
|
*/
|
|
2577
2881
|
setAlignAnchor(alignAnchor) {
|
|
2578
|
-
this.alignAnchor = alignAnchor;
|
|
2882
|
+
this.layoutState.alignAnchor = alignAnchor;
|
|
2579
2883
|
}
|
|
2580
2884
|
/**
|
|
2581
2885
|
* 设置默认的歌词行对齐位置,相对于整个歌词播放组件的大小位置,默认为 `0.5`
|
|
2582
2886
|
* @param alignPosition 一个 `[0.0-1.0]` 之间的任意数字,代表组件高度由上到下的比例位置
|
|
2583
2887
|
*/
|
|
2584
2888
|
setAlignPosition(alignPosition) {
|
|
2585
|
-
this.alignPosition = alignPosition;
|
|
2889
|
+
this.layoutState.alignPosition = alignPosition;
|
|
2586
2890
|
}
|
|
2587
2891
|
/**
|
|
2588
2892
|
* 设置 overscan(视图上下额外缓冲渲染区)距离,单位:像素。
|
|
2589
2893
|
* @param px 像素值,默认 300
|
|
2590
2894
|
*/
|
|
2591
2895
|
setOverscanPx(px) {
|
|
2592
|
-
this.overscanPx =
|
|
2896
|
+
this.layoutState.overscanPx = clampPositive(px | 0);
|
|
2593
2897
|
}
|
|
2594
2898
|
/** 获取当前 overscan 像素距离 */
|
|
2595
2899
|
getOverscanPx() {
|
|
2596
|
-
return this.overscanPx;
|
|
2900
|
+
return this.layoutState.overscanPx;
|
|
2597
2901
|
}
|
|
2598
2902
|
/**
|
|
2599
2903
|
* 设置是否使用物理弹簧算法实现歌词动画效果,默认启用
|
|
@@ -2616,34 +2920,6 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2616
2920
|
return !this.disableSpring;
|
|
2617
2921
|
}
|
|
2618
2922
|
/**
|
|
2619
|
-
* 获取当前播放时间里是否处于间奏区间
|
|
2620
|
-
* 如果是则会返回单位为毫秒的始末时间
|
|
2621
|
-
* 否则返回 undefined
|
|
2622
|
-
*
|
|
2623
|
-
* 这个只允许内部调用
|
|
2624
|
-
* @returns [开始时间,结束时间,大概处于的歌词行ID,下一句是否为对唱歌词] 或 undefined 如果不处于间奏区间
|
|
2625
|
-
*/
|
|
2626
|
-
getCurrentInterlude() {
|
|
2627
|
-
const currentTime = this.currentTime + 20;
|
|
2628
|
-
const currentIndex = this.scrollToIndex;
|
|
2629
|
-
const lines = this.processedLines;
|
|
2630
|
-
const checkGap = (k) => {
|
|
2631
|
-
if (k < -1 || k >= lines.length - 1) return void 0;
|
|
2632
|
-
const prevLine = k === -1 ? null : lines[k];
|
|
2633
|
-
const nextLine = lines[k + 1];
|
|
2634
|
-
const gapStart = prevLine ? prevLine.endTime : 0;
|
|
2635
|
-
const gapEnd = Math.max(gapStart, nextLine.startTime - 250);
|
|
2636
|
-
if (gapEnd - gapStart < 4e3) return;
|
|
2637
|
-
if (gapEnd > currentTime && gapStart < currentTime) return [
|
|
2638
|
-
Math.max(gapStart, currentTime),
|
|
2639
|
-
gapEnd,
|
|
2640
|
-
k,
|
|
2641
|
-
nextLine.isDuet
|
|
2642
|
-
];
|
|
2643
|
-
};
|
|
2644
|
-
return checkGap(currentIndex - 1) || checkGap(currentIndex) || checkGap(currentIndex + 1);
|
|
2645
|
-
}
|
|
2646
|
-
/**
|
|
2647
2923
|
* 设置歌词的优化配置项,这些配置项默认全部开启
|
|
2648
2924
|
*
|
|
2649
2925
|
* 注意,如果在 `setLyricLines` 之后修改此配置,需要重新调用 `setLyricLines()` 才能对当前歌词生效
|
|
@@ -2663,9 +2939,9 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2663
2939
|
*/
|
|
2664
2940
|
setLyricLines(lines, initialTime = 0) {
|
|
2665
2941
|
if (process.env.NODE_ENV !== "production") console.log("设置歌词行", lines, initialTime);
|
|
2666
|
-
this.initialLayoutFinished = true;
|
|
2667
|
-
this.lastCurrentTime = initialTime;
|
|
2668
|
-
this.currentTime = initialTime;
|
|
2942
|
+
this.timelineState.initialLayoutFinished = true;
|
|
2943
|
+
this.timelineState.lastCurrentTime = initialTime;
|
|
2944
|
+
this.timelineState.currentTime = initialTime;
|
|
2669
2945
|
this.currentLyricLines = structuredClone(lines);
|
|
2670
2946
|
this.processedLines = structuredClone(this.currentLyricLines);
|
|
2671
2947
|
optimizeLyricLines(this.processedLines, this.optimizeOptions);
|
|
@@ -2677,8 +2953,8 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2677
2953
|
this.hasDuetLine = this.processedLines.some((line) => line.isDuet);
|
|
2678
2954
|
for (const line of this.currentLyricLineObjects) line.dispose();
|
|
2679
2955
|
this.interludeDots.setInterlude(void 0);
|
|
2680
|
-
this.hotLines.clear();
|
|
2681
|
-
this.bufferedLines.clear();
|
|
2956
|
+
this.timelineState.hotLines.clear();
|
|
2957
|
+
this.timelineState.bufferedLines.clear();
|
|
2682
2958
|
this.setCurrentTime(0, true);
|
|
2683
2959
|
if (process.env.NODE_ENV !== "production") console.log("歌词处理完成", this);
|
|
2684
2960
|
}
|
|
@@ -2687,143 +2963,39 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2687
2963
|
* @returns 当前是否在播放
|
|
2688
2964
|
*/
|
|
2689
2965
|
getIsPlaying() {
|
|
2690
|
-
return this.isPlaying;
|
|
2966
|
+
return this.timelineState.isPlaying;
|
|
2691
2967
|
}
|
|
2692
2968
|
/**
|
|
2693
|
-
*
|
|
2694
|
-
*
|
|
2969
|
+
* 设置当前播放进度,此时将会更新内部的歌词进度信息。
|
|
2970
|
+
*
|
|
2971
|
+
* 内部会根据调用间隔和播放进度自动决定如何滚动和显示歌词,所以这个的调用频率越快越准确越好。
|
|
2972
|
+
* 调用完成后,应每帧调用 {@link update} 方法来执行歌词动画效果。**此函数本身不会触发动画效果**。
|
|
2695
2973
|
*
|
|
2696
|
-
* 调用完成后,可以每帧调用 `update` 函数来执行歌词动画效果
|
|
2697
2974
|
* @param time 当前播放进度,单位为毫秒
|
|
2698
2975
|
*/
|
|
2699
2976
|
setCurrentTime(time, isSeek = false) {
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
const nextLine = this.processedLines[lastHotId + 1];
|
|
2710
|
-
if (nextLine?.isBG) {
|
|
2711
|
-
const nextMainLine = this.processedLines[lastHotId + 2];
|
|
2712
|
-
const startTime = Math.min(line.startTime, nextLine?.startTime);
|
|
2713
|
-
const endTime = Math.min(Math.max(line.endTime, nextMainLine?.startTime ?? Number.MAX_VALUE), Math.max(line.endTime, nextLine?.endTime));
|
|
2714
|
-
if (startTime > time || endTime <= time) {
|
|
2715
|
-
this.hotLines.delete(lastHotId);
|
|
2716
|
-
removedHotIds.add(lastHotId);
|
|
2717
|
-
this.hotLines.delete(lastHotId + 1);
|
|
2718
|
-
removedHotIds.add(lastHotId + 1);
|
|
2719
|
-
if (isSeek) {
|
|
2720
|
-
this.currentLyricLineObjects[lastHotId]?.disable();
|
|
2721
|
-
this.currentLyricLineObjects[lastHotId + 1]?.disable();
|
|
2722
|
-
}
|
|
2723
|
-
}
|
|
2724
|
-
} else if (line.startTime > time || line.endTime <= time) {
|
|
2725
|
-
this.hotLines.delete(lastHotId);
|
|
2726
|
-
removedHotIds.add(lastHotId);
|
|
2727
|
-
if (isSeek) this.currentLyricLineObjects[lastHotId]?.disable();
|
|
2728
|
-
}
|
|
2729
|
-
} else {
|
|
2730
|
-
this.hotLines.delete(lastHotId);
|
|
2731
|
-
removedHotIds.add(lastHotId);
|
|
2732
|
-
if (isSeek) this.currentLyricLineObjects[lastHotId]?.disable();
|
|
2733
|
-
}
|
|
2734
|
-
}
|
|
2735
|
-
this.currentLyricLineObjects.forEach((lineObj, id, arr) => {
|
|
2736
|
-
const line = lineObj.getLine();
|
|
2737
|
-
if (!line.isBG && line.startTime <= time && line.endTime > time) {
|
|
2738
|
-
if (isSeek) lineObj.enable(time, this.isPlaying);
|
|
2739
|
-
if (!this.hotLines.has(id)) {
|
|
2740
|
-
this.hotLines.add(id);
|
|
2741
|
-
addedIds.add(id);
|
|
2742
|
-
if (!isSeek) lineObj.enable();
|
|
2743
|
-
if (arr[id + 1]?.getLine()?.isBG) {
|
|
2744
|
-
this.hotLines.add(id + 1);
|
|
2745
|
-
addedIds.add(id + 1);
|
|
2746
|
-
if (isSeek) arr[id + 1].enable(time, this.isPlaying);
|
|
2747
|
-
else arr[id + 1].enable();
|
|
2748
|
-
}
|
|
2749
|
-
}
|
|
2750
|
-
}
|
|
2977
|
+
time = Math.round(time);
|
|
2978
|
+
const { timelineState } = this;
|
|
2979
|
+
timelineState.isSeeking = Boolean(isSeek);
|
|
2980
|
+
timelineState.currentTime = time;
|
|
2981
|
+
if (!timelineState.initialLayoutFinished && !timelineState.isSeeking) return;
|
|
2982
|
+
const stateResult = computePlayerTimeState({
|
|
2983
|
+
time,
|
|
2984
|
+
processedLines: this.processedLines,
|
|
2985
|
+
timelineState
|
|
2751
2986
|
});
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
this.resetScroll();
|
|
2765
|
-
this.calcLayout();
|
|
2766
|
-
} else if (removedIds.size > 0 || addedIds.size > 0) if (removedIds.size === 0 && addedIds.size > 0) {
|
|
2767
|
-
for (const v of addedIds) {
|
|
2768
|
-
this.bufferedLines.add(v);
|
|
2769
|
-
this.currentLyricLineObjects[v]?.enable();
|
|
2770
|
-
}
|
|
2771
|
-
this.scrollToIndex = Math.min(...this.bufferedLines);
|
|
2772
|
-
this.calcLayout();
|
|
2773
|
-
} else if (addedIds.size === 0 && removedIds.size > 0) {
|
|
2774
|
-
if (eqSet(removedIds, this.bufferedLines)) {
|
|
2775
|
-
for (const v of this.bufferedLines) if (!this.hotLines.has(v)) {
|
|
2776
|
-
this.bufferedLines.delete(v);
|
|
2777
|
-
this.currentLyricLineObjects[v]?.disable();
|
|
2778
|
-
}
|
|
2779
|
-
this.calcLayout();
|
|
2780
|
-
}
|
|
2781
|
-
} else {
|
|
2782
|
-
for (const v of addedIds) {
|
|
2783
|
-
this.bufferedLines.add(v);
|
|
2784
|
-
this.currentLyricLineObjects[v]?.enable();
|
|
2785
|
-
}
|
|
2786
|
-
for (const v of removedIds) {
|
|
2787
|
-
this.bufferedLines.delete(v);
|
|
2788
|
-
this.currentLyricLineObjects[v]?.disable();
|
|
2789
|
-
}
|
|
2790
|
-
if (this.bufferedLines.size > 0) this.scrollToIndex = Math.min(...this.bufferedLines);
|
|
2791
|
-
this.calcLayout();
|
|
2792
|
-
}
|
|
2793
|
-
if (this.bufferedLines.size === 0 && this.processedLines.length > 0) {
|
|
2794
|
-
const lastLine = this.processedLines[this.processedLines.length - 1];
|
|
2795
|
-
const hasBottomContent = this.bottomLine.getElement().innerHTML.trim().length > 0;
|
|
2796
|
-
if (time >= lastLine.endTime) {
|
|
2797
|
-
const targetIndex = hasBottomContent ? this.processedLines.length : this.processedLines.length - 1;
|
|
2798
|
-
if (this.scrollToIndex !== targetIndex) {
|
|
2799
|
-
this.scrollToIndex = targetIndex;
|
|
2800
|
-
this.calcLayout();
|
|
2801
|
-
}
|
|
2802
|
-
}
|
|
2803
|
-
}
|
|
2804
|
-
this.lastCurrentTime = time;
|
|
2805
|
-
}
|
|
2806
|
-
updateDynamicSpringParams() {
|
|
2807
|
-
if (!this.getEnableSpring() || this.processedLines.length === 0) return;
|
|
2808
|
-
const currentIndex = this.scrollToIndex;
|
|
2809
|
-
const currentLine = this.processedLines[currentIndex];
|
|
2810
|
-
const prevLine = this.processedLines[currentIndex - 1];
|
|
2811
|
-
if (currentLine && prevLine) {
|
|
2812
|
-
const interval = currentLine.startTime - (prevLine?.words[0]?.startTime ?? prevLine.startTime);
|
|
2813
|
-
const MIN_INTERVAL = 100;
|
|
2814
|
-
const MAX_INTERVAL = 800;
|
|
2815
|
-
const clampedInterval = Math.max(MIN_INTERVAL, Math.min(MAX_INTERVAL, interval));
|
|
2816
|
-
const MAX_STIFFNESS = 220;
|
|
2817
|
-
const MIN_STIFFNESS = 170;
|
|
2818
|
-
let ratio = 1 - (clampedInterval - MIN_INTERVAL) / (MAX_INTERVAL - MIN_INTERVAL);
|
|
2819
|
-
ratio = ratio ** .2;
|
|
2820
|
-
const targetStiffness = MIN_STIFFNESS + ratio * (MAX_STIFFNESS - MIN_STIFFNESS);
|
|
2821
|
-
const targetDamping = Math.sqrt(targetStiffness) * 2.2;
|
|
2822
|
-
this.setLinePosYSpringParams({
|
|
2823
|
-
stiffness: targetStiffness,
|
|
2824
|
-
damping: targetDamping
|
|
2825
|
-
});
|
|
2826
|
-
}
|
|
2987
|
+
const hasBottomContent = this.bottomLine.getElement().innerHTML.trim().length > 0;
|
|
2988
|
+
const commitResult = commitPlayerTimeState({
|
|
2989
|
+
timelineState,
|
|
2990
|
+
time,
|
|
2991
|
+
processedLines: this.processedLines,
|
|
2992
|
+
hasBottomContent,
|
|
2993
|
+
stateResult
|
|
2994
|
+
});
|
|
2995
|
+
for (const id of commitResult.linesToDisable) this.currentLyricLineObjects[id]?.disable();
|
|
2996
|
+
for (const id of commitResult.linesToEnable) this.currentLyricLineObjects[id]?.enable();
|
|
2997
|
+
if (commitResult.shouldResetScroll) this.resetScroll();
|
|
2998
|
+
if (commitResult.shouldLayout) this.calcLayout();
|
|
2827
2999
|
}
|
|
2828
3000
|
/**
|
|
2829
3001
|
* 重新布局定位歌词行的位置,调用完成后再逐帧调用 `update`
|
|
@@ -2843,102 +3015,108 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2843
3015
|
* @param force 是否绕过弹簧效果强制更新位置
|
|
2844
3016
|
*/
|
|
2845
3017
|
async calcLayout(sync = false, force = false) {
|
|
2846
|
-
const interlude =
|
|
3018
|
+
const interlude = computeCurrentInterlude({
|
|
3019
|
+
currentTime: this.timelineState.currentTime,
|
|
3020
|
+
scrollToIndex: this.timelineState.scrollToIndex,
|
|
3021
|
+
processedLines: this.processedLines
|
|
3022
|
+
});
|
|
2847
3023
|
const isInterludeActive = !!interlude;
|
|
2848
|
-
if (this.targetAlignIndex !== this.scrollToIndex || this.lastInterludeState !== isInterludeActive) {
|
|
2849
|
-
this.lastInterludeState = isInterludeActive;
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
damping: 15
|
|
3024
|
+
if (this.layoutState.targetAlignIndex !== this.timelineState.scrollToIndex || this.layoutState.lastInterludeState !== isInterludeActive) {
|
|
3025
|
+
this.layoutState.lastInterludeState = isInterludeActive;
|
|
3026
|
+
const springParams = computeLinePosYSpringParams({
|
|
3027
|
+
enabled: this.getEnableSpring(),
|
|
3028
|
+
processedLines: this.processedLines,
|
|
3029
|
+
scrollToIndex: this.timelineState.scrollToIndex,
|
|
3030
|
+
isSeeking: this.timelineState.isSeeking,
|
|
3031
|
+
isInterludeActive
|
|
2857
3032
|
});
|
|
2858
|
-
|
|
3033
|
+
if (springParams.shouldUpdate && springParams.params) this.setLinePosYSpringParams(springParams.params);
|
|
2859
3034
|
}
|
|
2860
|
-
let curPos = -this.scrollOffset;
|
|
2861
|
-
const targetAlignIndex = this.scrollToIndex;
|
|
3035
|
+
let curPos = -this.scrollState.scrollOffset;
|
|
3036
|
+
const targetAlignIndex = this.timelineState.scrollToIndex;
|
|
2862
3037
|
let isNextDuet = false;
|
|
2863
|
-
if (interlude) isNextDuet = interlude
|
|
3038
|
+
if (interlude) isNextDuet = interlude.isNextDuet;
|
|
2864
3039
|
else this.interludeDots.setInterlude(void 0);
|
|
2865
3040
|
const dotMargin = (this.baseFontSize || 24) * .4;
|
|
2866
|
-
const totalInterludeHeight = this.interludeDotsSize[1] + dotMargin * 2;
|
|
3041
|
+
const totalInterludeHeight = this.layoutState.interludeDotsSize[1] + dotMargin * 2;
|
|
2867
3042
|
if (interlude) {
|
|
2868
|
-
if (interlude
|
|
3043
|
+
if (interlude.anchorLineIndex !== -1) curPos -= totalInterludeHeight;
|
|
2869
3044
|
}
|
|
2870
3045
|
const LINE_HEIGHT_FALLBACK = this.size[1] / 5;
|
|
2871
|
-
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);
|
|
2872
|
-
this.scrollBoundary
|
|
3046
|
+
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);
|
|
3047
|
+
this.scrollState.scrollBoundary.minOffset = -scrollOffset;
|
|
2873
3048
|
curPos -= scrollOffset;
|
|
2874
|
-
curPos += this.size[1] * this.alignPosition;
|
|
3049
|
+
curPos += this.size[1] * this.layoutState.alignPosition;
|
|
2875
3050
|
const curLine = this.currentLyricLineObjects[targetAlignIndex];
|
|
2876
|
-
this.targetAlignIndex = targetAlignIndex;
|
|
3051
|
+
this.layoutState.targetAlignIndex = targetAlignIndex;
|
|
2877
3052
|
const isBottomFocused = targetAlignIndex === this.currentLyricLineObjects.length;
|
|
2878
3053
|
this.bottomLine.setFocused(isBottomFocused);
|
|
2879
3054
|
let targetLineHeight = 0;
|
|
2880
3055
|
if (curLine) targetLineHeight = this.lyricLinesSize.get(curLine)?.[1] ?? LINE_HEIGHT_FALLBACK;
|
|
2881
3056
|
else if (isBottomFocused) targetLineHeight = this.bottomLine.lineSize[1];
|
|
2882
|
-
if (targetLineHeight > 0) switch (this.alignAnchor) {
|
|
2883
|
-
case
|
|
3057
|
+
if (targetLineHeight > 0) switch (this.layoutState.alignAnchor) {
|
|
3058
|
+
case LayoutAlignAnchor.Bottom:
|
|
2884
3059
|
curPos -= targetLineHeight;
|
|
2885
3060
|
break;
|
|
2886
|
-
case
|
|
3061
|
+
case LayoutAlignAnchor.Center:
|
|
2887
3062
|
curPos -= targetLineHeight / 2;
|
|
2888
3063
|
break;
|
|
2889
|
-
case
|
|
3064
|
+
case LayoutAlignAnchor.Top: break;
|
|
2890
3065
|
}
|
|
2891
|
-
const latestIndex = Math.max(...this.bufferedLines);
|
|
3066
|
+
const latestIndex = Math.max(...this.timelineState.bufferedLines);
|
|
2892
3067
|
let delay = 0;
|
|
2893
3068
|
let baseDelay = sync ? 0 : .05;
|
|
2894
3069
|
let setDots = false;
|
|
2895
3070
|
this.currentLyricLineObjects.forEach((lineObj, i) => {
|
|
2896
|
-
const hasBuffered = this.bufferedLines.has(i);
|
|
2897
|
-
const isActive = hasBuffered || i >= this.scrollToIndex && i < latestIndex;
|
|
3071
|
+
const hasBuffered = this.timelineState.bufferedLines.has(i);
|
|
2898
3072
|
const line = lineObj.getLine();
|
|
2899
|
-
const shouldShowDots = interlude && i === interlude
|
|
3073
|
+
const shouldShowDots = interlude && i === interlude.anchorLineIndex + 1;
|
|
2900
3074
|
if (!setDots && shouldShowDots) {
|
|
2901
3075
|
setDots = true;
|
|
2902
3076
|
curPos += dotMargin;
|
|
2903
3077
|
let targetX = 0;
|
|
2904
|
-
if (interlude && isNextDuet) targetX = this.size[0] - this.interludeDotsSize[0];
|
|
3078
|
+
if (interlude && isNextDuet) targetX = this.size[0] - this.layoutState.interludeDotsSize[0];
|
|
2905
3079
|
this.interludeDots.setTransform(targetX, curPos);
|
|
2906
|
-
if (interlude) this.interludeDots.setInterlude([interlude
|
|
2907
|
-
curPos += this.interludeDotsSize[1];
|
|
3080
|
+
if (interlude) this.interludeDots.setInterlude([interlude.startTime, interlude.endTime]);
|
|
3081
|
+
curPos += this.layoutState.interludeDotsSize[1];
|
|
2908
3082
|
curPos += dotMargin;
|
|
2909
3083
|
}
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
3084
|
+
const presentation = computeLinePresentation({
|
|
3085
|
+
line,
|
|
3086
|
+
lineIndex: i,
|
|
3087
|
+
scrollToIndex: this.timelineState.scrollToIndex,
|
|
3088
|
+
latestIndex,
|
|
3089
|
+
hasBuffered,
|
|
3090
|
+
hidePassedLines: this.hidePassedLines,
|
|
3091
|
+
isPlaying: this.timelineState.isPlaying,
|
|
3092
|
+
isNonDynamic: this.isNonDynamic,
|
|
3093
|
+
enableScale: this.enableScale,
|
|
3094
|
+
enableBlur: this.enableBlur,
|
|
3095
|
+
isUserScrolling: this.scrollState.isUserScrolling,
|
|
3096
|
+
isCompact: window.innerWidth <= 1024,
|
|
3097
|
+
interlude
|
|
3098
|
+
});
|
|
3099
|
+
lineObj.setTransform(curPos, presentation.targetScale, presentation.targetOpacity, presentation.blurLevel, force, delay, presentation.renderMode);
|
|
3100
|
+
if (line.isBG && (presentation.isActive || !this.timelineState.isPlaying)) curPos += this.lyricLinesSize.get(lineObj)?.[1] ?? LINE_HEIGHT_FALLBACK;
|
|
2924
3101
|
else if (!line.isBG) curPos += this.lyricLinesSize.get(lineObj)?.[1] ?? LINE_HEIGHT_FALLBACK;
|
|
2925
|
-
if (curPos >= 0 && !this.isSeeking) {
|
|
3102
|
+
if (curPos >= 0 && !this.timelineState.isSeeking) {
|
|
2926
3103
|
if (!line.isBG) delay += baseDelay;
|
|
2927
|
-
if (i >= this.scrollToIndex) baseDelay /= 1.05;
|
|
3104
|
+
if (i >= this.timelineState.scrollToIndex) baseDelay /= 1.05;
|
|
2928
3105
|
}
|
|
2929
3106
|
});
|
|
2930
|
-
this.scrollBoundary
|
|
3107
|
+
this.scrollState.scrollBoundary.maxOffset = curPos + this.scrollState.scrollOffset - this.size[1] / 2;
|
|
2931
3108
|
const bottomIndex = this.currentLyricLineObjects.length;
|
|
2932
|
-
const finalBottomBlur =
|
|
3109
|
+
const finalBottomBlur = computeLineBlur({
|
|
3110
|
+
enableBlur: this.enableBlur,
|
|
3111
|
+
isUserScrolling: this.scrollState.isUserScrolling,
|
|
3112
|
+
isActive: isBottomFocused,
|
|
3113
|
+
itemIndex: bottomIndex,
|
|
3114
|
+
scrollToIndex: this.timelineState.scrollToIndex,
|
|
3115
|
+
latestIndex,
|
|
3116
|
+
isCompact: window.innerWidth <= 1024
|
|
3117
|
+
});
|
|
2933
3118
|
this.bottomLine.setTransform(0, curPos, finalBottomBlur, force, delay);
|
|
2934
3119
|
}
|
|
2935
|
-
calculateBlur(itemIndex, isActive, latestIndex) {
|
|
2936
|
-
if (!this.enableBlur || this.isUserScrolling || isActive) return 0;
|
|
2937
|
-
let blurLevel = 1;
|
|
2938
|
-
if (itemIndex < this.scrollToIndex) blurLevel += Math.abs(this.scrollToIndex - itemIndex) + 1;
|
|
2939
|
-
else blurLevel += Math.abs(itemIndex - Math.max(this.scrollToIndex, latestIndex));
|
|
2940
|
-
return window.innerWidth <= 1024 ? blurLevel * .8 : blurLevel;
|
|
2941
|
-
}
|
|
2942
3120
|
/**
|
|
2943
3121
|
* 设置所有歌词行在横坐标上的弹簧属性,包括重量、弹力和阻力。
|
|
2944
3122
|
*
|
|
@@ -2976,14 +3154,13 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2976
3154
|
for (const lineObj of this.currentLyricLineObjects) if (lineObj.getLine().isBG) lineObj.lineTransforms.scale.updateParams(this.scaleForBGSpringParams);
|
|
2977
3155
|
else lineObj.lineTransforms.scale.updateParams(this.scaleSpringParams);
|
|
2978
3156
|
}
|
|
2979
|
-
isPlaying = true;
|
|
2980
3157
|
/**
|
|
2981
3158
|
* 暂停部分效果演出,目前会暂停播放间奏点的动画,且将背景歌词显示出来
|
|
2982
3159
|
*/
|
|
2983
3160
|
pause() {
|
|
2984
3161
|
this.interludeDots.pause();
|
|
2985
|
-
if (this.isPlaying) {
|
|
2986
|
-
this.isPlaying = false;
|
|
3162
|
+
if (this.timelineState.isPlaying) {
|
|
3163
|
+
this.timelineState.isPlaying = false;
|
|
2987
3164
|
this.calcLayout();
|
|
2988
3165
|
}
|
|
2989
3166
|
}
|
|
@@ -2992,8 +3169,8 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2992
3169
|
*/
|
|
2993
3170
|
resume() {
|
|
2994
3171
|
this.interludeDots.resume();
|
|
2995
|
-
if (!this.isPlaying) {
|
|
2996
|
-
this.isPlaying = true;
|
|
3172
|
+
if (!this.timelineState.isPlaying) {
|
|
3173
|
+
this.timelineState.isPlaying = true;
|
|
2997
3174
|
this.calcLayout();
|
|
2998
3175
|
}
|
|
2999
3176
|
}
|
|
@@ -3026,8 +3203,7 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
3026
3203
|
* 请在用户完成滚动点击跳转歌词时调用本事件再调用 `calcLayout` 以正确滚动到目标位置
|
|
3027
3204
|
*/
|
|
3028
3205
|
resetScroll() {
|
|
3029
|
-
this.
|
|
3030
|
-
this.scrollOffset = 0;
|
|
3206
|
+
resetPlayerScrollState(this.scrollState);
|
|
3031
3207
|
clearTimeout(this.scrolledHandler);
|
|
3032
3208
|
}
|
|
3033
3209
|
/**
|
|
@@ -3046,7 +3222,7 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
3046
3222
|
* @returns 当前播放位置
|
|
3047
3223
|
*/
|
|
3048
3224
|
getCurrentTime() {
|
|
3049
|
-
return this.currentTime;
|
|
3225
|
+
return this.timelineState.currentTime;
|
|
3050
3226
|
}
|
|
3051
3227
|
getElement() {
|
|
3052
3228
|
return this.element;
|
|
@@ -3057,6 +3233,13 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
3057
3233
|
window.removeEventListener("pagehide", this.onPageHide);
|
|
3058
3234
|
}
|
|
3059
3235
|
};
|
|
3236
|
+
//#endregion
|
|
3237
|
+
//#region src/utils/is-cjk.ts
|
|
3238
|
+
const isCJK = (char) => {
|
|
3239
|
+
return /^[\p{Unified_Ideograph}\u0800-\u9FFC]+$/u.test(char);
|
|
3240
|
+
};
|
|
3241
|
+
//#endregion
|
|
3242
|
+
//#region src/lyric-player/base/line.ts
|
|
3060
3243
|
/**
|
|
3061
3244
|
* 所有标准歌词行的基类
|
|
3062
3245
|
* @internal
|
|
@@ -3081,7 +3264,7 @@ var LyricLineBase = class extends EventTarget {
|
|
|
3081
3264
|
*/
|
|
3082
3265
|
static graphemeSegmenter = typeof Intl !== "undefined" && Intl.Segmenter ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
|
|
3083
3266
|
onLineSizeChange(_size) {}
|
|
3084
|
-
setTransform(top = this.top, scale = this.scale, opacity = this.opacity, blur = this.blur, _force = false, delay = 0, _mode =
|
|
3267
|
+
setTransform(top = this.top, scale = this.scale, opacity = this.opacity, blur = this.blur, _force = false, delay = 0, _mode = LyricLineRenderMode.SOLID) {
|
|
3085
3268
|
this.top = top;
|
|
3086
3269
|
this.scale = scale;
|
|
3087
3270
|
this.opacity = opacity;
|
|
@@ -3308,7 +3491,7 @@ var LineBalancer = class {
|
|
|
3308
3491
|
const marginLeft = Number.parseFloat(elStyle.marginLeft) || 0;
|
|
3309
3492
|
const marginRight = Number.parseFloat(elStyle.marginRight) || 0;
|
|
3310
3493
|
childInfos.push({
|
|
3311
|
-
width:
|
|
3494
|
+
width: clampPositive(rect.width + marginLeft + marginRight),
|
|
3312
3495
|
text: el.textContent ?? "",
|
|
3313
3496
|
isSpace: false
|
|
3314
3497
|
});
|
|
@@ -3512,34 +3695,34 @@ function matrix4ToCSS(m, fractionDigits = 4) {
|
|
|
3512
3695
|
}
|
|
3513
3696
|
//#endregion
|
|
3514
3697
|
//#region src/lyric-player/dom/lyric-line.ts
|
|
3515
|
-
const ANIMATION_FRAME_QUANTITY
|
|
3516
|
-
const norNum
|
|
3517
|
-
const EMP_EASING_MID
|
|
3518
|
-
const beginNum
|
|
3519
|
-
const endNum
|
|
3520
|
-
const bezIn
|
|
3521
|
-
const bezOut
|
|
3522
|
-
const makeEmpEasing
|
|
3523
|
-
return (x) => x < mid ? bezIn
|
|
3698
|
+
const ANIMATION_FRAME_QUANTITY = 32;
|
|
3699
|
+
const norNum = (min, max) => (x) => clamp01((x - min) / (max - min));
|
|
3700
|
+
const EMP_EASING_MID = .5;
|
|
3701
|
+
const beginNum = norNum(0, EMP_EASING_MID);
|
|
3702
|
+
const endNum = norNum(EMP_EASING_MID, 1);
|
|
3703
|
+
const bezIn = bezier(.2, .4, .58, 1);
|
|
3704
|
+
const bezOut = bezier(.3, 0, .58, 1);
|
|
3705
|
+
const makeEmpEasing = (mid) => {
|
|
3706
|
+
return (x) => x < mid ? bezIn(beginNum(x)) : 1 - bezOut(endNum(x));
|
|
3524
3707
|
};
|
|
3525
|
-
function generateFadeGradient
|
|
3708
|
+
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))") {
|
|
3526
3709
|
const totalAspect = 2 + width + padding;
|
|
3527
3710
|
const widthInTotal = width / totalAspect;
|
|
3528
3711
|
const leftPos = (1 - widthInTotal) / 2;
|
|
3529
3712
|
return [`linear-gradient(to right,${bright} ${leftPos * 100}%,${dark} ${(leftPos + widthInTotal) * 100}%)`, totalAspect];
|
|
3530
3713
|
}
|
|
3531
|
-
var RawLyricLineMouseEvent
|
|
3714
|
+
var RawLyricLineMouseEvent = class extends MouseEvent {
|
|
3532
3715
|
constructor(line, event) {
|
|
3533
3716
|
super(event.type, event);
|
|
3534
3717
|
this.line = line;
|
|
3535
3718
|
}
|
|
3536
3719
|
};
|
|
3537
|
-
var LyricLineEl
|
|
3720
|
+
var LyricLineEl = class extends LyricLineBase {
|
|
3538
3721
|
element = document.createElement("div");
|
|
3539
3722
|
splittedWords = [];
|
|
3540
3723
|
built = false;
|
|
3541
3724
|
lineSize = [0, 0];
|
|
3542
|
-
renderMode =
|
|
3725
|
+
renderMode = LyricLineRenderMode.SOLID;
|
|
3543
3726
|
currentBrightAlpha = 1;
|
|
3544
3727
|
currentDarkAlpha = .2;
|
|
3545
3728
|
targetBrightAlpha = 1;
|
|
@@ -3580,7 +3763,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3580
3763
|
}
|
|
3581
3764
|
listenersMap = /* @__PURE__ */ new Map();
|
|
3582
3765
|
onMouseEvent = (e) => {
|
|
3583
|
-
const wrapped = new RawLyricLineMouseEvent
|
|
3766
|
+
const wrapped = new RawLyricLineMouseEvent(this, e);
|
|
3584
3767
|
for (const listener of this.listenersMap.get(e.type) ?? []) listener.call(this, wrapped);
|
|
3585
3768
|
if (!this.dispatchEvent(wrapped) || wrapped.defaultPrevented) {
|
|
3586
3769
|
e.preventDefault();
|
|
@@ -3616,30 +3799,28 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3616
3799
|
return true;
|
|
3617
3800
|
}
|
|
3618
3801
|
isEnabled = false;
|
|
3619
|
-
async enable(maskAnimationTime = this.
|
|
3802
|
+
async enable(maskAnimationTime = this.lyricPlayer.getCurrentTime(), shouldPlay = this.lyricPlayer.getIsPlaying()) {
|
|
3620
3803
|
this.isEnabled = true;
|
|
3621
3804
|
this.element.classList.add(lyric_player_module_default.active);
|
|
3622
3805
|
const main = this.element.children[0];
|
|
3623
|
-
const relativeTime =
|
|
3624
|
-
const actualMaskTime = maskAnimationTime === this.lyricLine.startTime ? this.lyricPlayer.getCurrentTime() : maskAnimationTime;
|
|
3625
|
-
const maskRelativeTime = Math.max(0, actualMaskTime - this.lyricLine.startTime);
|
|
3806
|
+
const relativeTime = clampPositive(maskAnimationTime - this.lyricLine.startTime);
|
|
3626
3807
|
for (const word of this.splittedWords) {
|
|
3627
3808
|
for (const a of word.elementAnimations) {
|
|
3628
3809
|
a.currentTime = relativeTime;
|
|
3629
3810
|
a.playbackRate = 1;
|
|
3630
3811
|
const timing = a.effect?.getComputedTiming();
|
|
3631
|
-
const duration = timing?.duration
|
|
3632
|
-
const endTime = (timing?.delay
|
|
3812
|
+
const duration = Number(timing?.duration ?? 0);
|
|
3813
|
+
const endTime = Number(timing?.delay ?? 0) + duration;
|
|
3633
3814
|
if (shouldPlay && relativeTime < endTime) a.play();
|
|
3634
3815
|
else a.pause();
|
|
3635
3816
|
}
|
|
3636
3817
|
for (const a of word.maskAnimations) {
|
|
3637
|
-
const t = Math.min(this.totalDuration,
|
|
3818
|
+
const t = Math.min(this.totalDuration, relativeTime);
|
|
3638
3819
|
a.currentTime = t;
|
|
3639
3820
|
a.playbackRate = 1;
|
|
3640
3821
|
const timing = a.effect?.getComputedTiming();
|
|
3641
|
-
const duration = timing?.duration
|
|
3642
|
-
const endTime = (timing?.delay
|
|
3822
|
+
const duration = Number(timing?.duration ?? 0);
|
|
3823
|
+
const endTime = Number(timing?.delay ?? 0) + duration;
|
|
3643
3824
|
if (shouldPlay && t < endTime) a.play();
|
|
3644
3825
|
else a.pause();
|
|
3645
3826
|
}
|
|
@@ -3649,7 +3830,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3649
3830
|
disable() {
|
|
3650
3831
|
this.isEnabled = false;
|
|
3651
3832
|
this.element.classList.remove(lyric_player_module_default.active);
|
|
3652
|
-
this.renderMode =
|
|
3833
|
+
this.renderMode = LyricLineRenderMode.SOLID;
|
|
3653
3834
|
const main = this.element.children[0];
|
|
3654
3835
|
for (const word of this.splittedWords) {
|
|
3655
3836
|
for (const a of word.elementAnimations) if (a.id === "float-word" || a.id.includes("emphasize-word-float-only")) {
|
|
@@ -3690,7 +3871,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3690
3871
|
setMaskAnimationState(maskAnimationTime = 0) {
|
|
3691
3872
|
const t = maskAnimationTime - this.lyricLine.startTime;
|
|
3692
3873
|
for (const word of this.splittedWords) for (const a of word.maskAnimations) {
|
|
3693
|
-
a.currentTime =
|
|
3874
|
+
a.currentTime = clamp(t, 0, this.totalDuration);
|
|
3694
3875
|
a.playbackRate = 1;
|
|
3695
3876
|
if (t >= 0 && t < this.totalDuration) a.play();
|
|
3696
3877
|
else a.pause();
|
|
@@ -3881,7 +4062,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3881
4062
|
return a;
|
|
3882
4063
|
}
|
|
3883
4064
|
initEmphasizeAnimation(word, characterElements, duration, delay, rubyCharCount) {
|
|
3884
|
-
const de =
|
|
4065
|
+
const de = clampPositive(delay);
|
|
3885
4066
|
let du = Math.max(1e3, duration);
|
|
3886
4067
|
const anchorCharCount = rubyCharCount > 0 ? rubyCharCount : Math.max(1, characterElements.length);
|
|
3887
4068
|
let result = [];
|
|
@@ -3899,12 +4080,12 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3899
4080
|
amount = Math.min(1.2, amount);
|
|
3900
4081
|
blur = Math.min(.8, blur);
|
|
3901
4082
|
const animateDu = Number.isFinite(du) ? du : 0;
|
|
3902
|
-
const empEasing = makeEmpEasing
|
|
4083
|
+
const empEasing = makeEmpEasing(EMP_EASING_MID);
|
|
3903
4084
|
result = characterElements.flatMap((el, i, arr) => {
|
|
3904
4085
|
const wordDe = de + du / 2.5 / anchorCharCount * i;
|
|
3905
4086
|
const result = [];
|
|
3906
|
-
const frames = new Array(ANIMATION_FRAME_QUANTITY
|
|
3907
|
-
const x = (j + 1) / ANIMATION_FRAME_QUANTITY
|
|
4087
|
+
const frames = new Array(ANIMATION_FRAME_QUANTITY).fill(0).map((_, j) => {
|
|
4088
|
+
const x = (j + 1) / ANIMATION_FRAME_QUANTITY;
|
|
3908
4089
|
const transX = empEasing(x);
|
|
3909
4090
|
const glowLevel = empEasing(x) * blur;
|
|
3910
4091
|
const mat = scaleMatrix4(createMatrix4(), 1 + transX * .1 * amount);
|
|
@@ -3929,8 +4110,8 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3929
4110
|
};
|
|
3930
4111
|
glow.pause();
|
|
3931
4112
|
result.push(glow);
|
|
3932
|
-
const floatFrame = new Array(ANIMATION_FRAME_QUANTITY
|
|
3933
|
-
const x = (j + 1) / ANIMATION_FRAME_QUANTITY
|
|
4113
|
+
const floatFrame = new Array(ANIMATION_FRAME_QUANTITY).fill(0).map((_, j) => {
|
|
4114
|
+
const x = (j + 1) / ANIMATION_FRAME_QUANTITY;
|
|
3934
4115
|
let y = Math.sin(x * Math.PI);
|
|
3935
4116
|
if (this.lyricLine.isBG) y *= 2;
|
|
3936
4117
|
return {
|
|
@@ -3989,7 +4170,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3989
4170
|
word.width = wordEl.clientWidth;
|
|
3990
4171
|
word.height = wordEl.clientHeight;
|
|
3991
4172
|
const fadeWidth = word.height * this.lyricPlayer.getWordFadeWidth();
|
|
3992
|
-
const [maskImage, totalAspect] = generateFadeGradient
|
|
4173
|
+
const [maskImage, totalAspect] = generateFadeGradient(fadeWidth / word.width);
|
|
3993
4174
|
const totalAspectStr = `${totalAspect * 100}% 100%`;
|
|
3994
4175
|
if (this.lyricPlayer.supportMaskImage) {
|
|
3995
4176
|
wordEl.style.maskImage = maskImage;
|
|
@@ -4010,12 +4191,12 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4010
4191
|
}
|
|
4011
4192
|
}
|
|
4012
4193
|
generateWebAnimationBasedMaskImage() {
|
|
4013
|
-
const totalFadeDuration = Math.max(this.splittedWords.
|
|
4194
|
+
const totalFadeDuration = Math.max(0, ...this.splittedWords.map((w) => w.endTime), this.lyricLine.endTime) - this.lyricLine.startTime;
|
|
4014
4195
|
this.splittedWords.forEach((word, i) => {
|
|
4015
4196
|
const wordEl = word.mainElement;
|
|
4016
4197
|
if (wordEl) {
|
|
4017
4198
|
const fadeWidth = word.height * this.lyricPlayer.getWordFadeWidth();
|
|
4018
|
-
const [maskImage, totalAspect] = generateFadeGradient
|
|
4199
|
+
const [maskImage, totalAspect] = generateFadeGradient(fadeWidth / (word.width + word.padding * 2));
|
|
4019
4200
|
const totalAspectStr = `${totalAspect * 100}% 100%`;
|
|
4020
4201
|
if (this.lyricPlayer.supportMaskImage) {
|
|
4021
4202
|
wordEl.style.maskImage = maskImage;
|
|
@@ -4030,7 +4211,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4030
4211
|
}
|
|
4031
4212
|
const widthBeforeSelf = this.splittedWords.slice(0, i).reduce((a, b) => a + b.width, 0) + (this.splittedWords[0] ? fadeWidth : 0);
|
|
4032
4213
|
const minOffset = -(word.width + word.padding * 2 + fadeWidth);
|
|
4033
|
-
const clampOffset = (x) =>
|
|
4214
|
+
const clampOffset = (x) => clamp(x, minOffset, 0);
|
|
4034
4215
|
let curPos = -widthBeforeSelf - word.width - word.padding - fadeWidth;
|
|
4035
4216
|
let timeOffset = 0;
|
|
4036
4217
|
const frames = [];
|
|
@@ -4038,7 +4219,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4038
4219
|
let lastTime = 0;
|
|
4039
4220
|
const pushFrame = () => {
|
|
4040
4221
|
const moveOffset = curPos - lastPos;
|
|
4041
|
-
const time =
|
|
4222
|
+
const time = clamp01(timeOffset);
|
|
4042
4223
|
const duration = time - lastTime;
|
|
4043
4224
|
const d = Math.abs(duration / moveOffset);
|
|
4044
4225
|
if (curPos > minOffset && lastPos < minOffset) {
|
|
@@ -4078,7 +4259,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4078
4259
|
lastTimeStamp = curTimeStamp;
|
|
4079
4260
|
}
|
|
4080
4261
|
{
|
|
4081
|
-
const fadeDuration =
|
|
4262
|
+
const fadeDuration = clampPositive(otherWord.endTime - otherWord.startTime);
|
|
4082
4263
|
const rubySegments = this.getRubySegments(otherWord);
|
|
4083
4264
|
const rubyCharCount = rubySegments.reduce((total, ruby) => total + ruby.word.length, 0);
|
|
4084
4265
|
if (rubyCharCount > 0) {
|
|
@@ -4094,7 +4275,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4094
4275
|
timeOffset += rubyStaticDuration / totalFadeDuration;
|
|
4095
4276
|
if (rubyStaticDuration > 0) pushFrame();
|
|
4096
4277
|
lastTimeStamp = rubyStartStamp;
|
|
4097
|
-
const perCharDuration =
|
|
4278
|
+
const perCharDuration = clampPositive(rubyEnd - rubyStart) / ruby.word.length;
|
|
4098
4279
|
for (let rubyCharIndex = 0; rubyCharIndex < ruby.word.length; rubyCharIndex++) {
|
|
4099
4280
|
timeOffset += perCharDuration / totalFadeDuration;
|
|
4100
4281
|
curPos += widthPerChar;
|
|
@@ -4144,10 +4325,10 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4144
4325
|
return this.element;
|
|
4145
4326
|
}
|
|
4146
4327
|
updateMaskAlphaTargets(scale) {
|
|
4147
|
-
const factor =
|
|
4328
|
+
const factor = clamp01((scale - .97) / .03);
|
|
4148
4329
|
const dynamicDarkAlpha = factor * .2 + .2;
|
|
4149
4330
|
const dynamicBrightAlpha = factor * .8 + .2;
|
|
4150
|
-
if (this.renderMode ===
|
|
4331
|
+
if (this.renderMode === LyricLineRenderMode.SOLID) {
|
|
4151
4332
|
this.targetBrightAlpha = dynamicDarkAlpha;
|
|
4152
4333
|
this.targetDarkAlpha = dynamicDarkAlpha;
|
|
4153
4334
|
} else {
|
|
@@ -4169,7 +4350,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4169
4350
|
this.element.style.setProperty("--bright-mask-alpha", this.currentBrightAlpha.toFixed(3));
|
|
4170
4351
|
this.element.style.setProperty("--dark-mask-alpha", this.currentDarkAlpha.toFixed(3));
|
|
4171
4352
|
}
|
|
4172
|
-
setTransform(top = this.top, scale = this.scale, opacity = 1, blur = 0, force = false, delay = 0, mode =
|
|
4353
|
+
setTransform(top = this.top, scale = this.scale, opacity = 1, blur = 0, force = false, delay = 0, mode = LyricLineRenderMode.SOLID) {
|
|
4173
4354
|
super.setTransform(top, scale, opacity, blur, force, delay);
|
|
4174
4355
|
this.renderMode = mode;
|
|
4175
4356
|
const beforeInSight = this.isInSight;
|
|
@@ -4326,7 +4507,7 @@ var DomLyricPlayer = class extends LyricPlayerBase {
|
|
|
4326
4507
|
line.dispose();
|
|
4327
4508
|
}
|
|
4328
4509
|
this.currentLyricLineObjects = this.processedLines.map((line, i) => {
|
|
4329
|
-
const lineEl = new LyricLineEl
|
|
4510
|
+
const lineEl = new LyricLineEl(this, line);
|
|
4330
4511
|
lineEl.addMouseEventListener("click", this.onLineClickedHandler);
|
|
4331
4512
|
lineEl.addMouseEventListener("contextmenu", this.onLineClickedHandler);
|
|
4332
4513
|
this.lyricLinesIndexes.set(lineEl, i);
|
|
@@ -4352,9 +4533,9 @@ var DomLyricPlayer = class extends LyricPlayerBase {
|
|
|
4352
4533
|
for (const line of this.currentLyricLineObjects) line.resume();
|
|
4353
4534
|
}
|
|
4354
4535
|
update(delta = 0) {
|
|
4355
|
-
if (!this.initialLayoutFinished) return;
|
|
4536
|
+
if (!this.timelineState.initialLayoutFinished) return;
|
|
4356
4537
|
super.update(delta);
|
|
4357
|
-
if (!this.supportMaskImage) this.element.style.setProperty("--amll-player-time", `${this.currentTime}`);
|
|
4538
|
+
if (!this.supportMaskImage) this.element.style.setProperty("--amll-player-time", `${this.timelineState.currentTime}`);
|
|
4358
4539
|
if (!this.isPageVisible) return;
|
|
4359
4540
|
const deltaS = delta / 1e3;
|
|
4360
4541
|
for (const line of this.currentLyricLineObjects) line.update(deltaS);
|
|
@@ -4368,890 +4549,6 @@ var DomLyricPlayer = class extends LyricPlayerBase {
|
|
|
4368
4549
|
}
|
|
4369
4550
|
};
|
|
4370
4551
|
//#endregion
|
|
4371
|
-
|
|
4372
|
-
function debounce(cb, wait = 20) {
|
|
4373
|
-
let h;
|
|
4374
|
-
const callable = (...args) => {
|
|
4375
|
-
clearTimeout(h);
|
|
4376
|
-
h = setTimeout(() => cb(...args), wait);
|
|
4377
|
-
};
|
|
4378
|
-
return callable;
|
|
4379
|
-
}
|
|
4380
|
-
//#endregion
|
|
4381
|
-
//#region src/lyric-player/dom-slim/index.module.css
|
|
4382
|
-
var index_module_default = {
|
|
4383
|
-
"active": "KxF9Iq_active",
|
|
4384
|
-
"duet": "KxF9Iq_duet",
|
|
4385
|
-
"enabled": "KxF9Iq_enabled",
|
|
4386
|
-
"hasDuetLine": "KxF9Iq_hasDuetLine",
|
|
4387
|
-
"interludeDots": "KxF9Iq_interludeDots",
|
|
4388
|
-
"lyricBgLine": "KxF9Iq_lyricBgLine",
|
|
4389
|
-
"lyricDuetLine": "KxF9Iq_lyricDuetLine",
|
|
4390
|
-
"lyricLine": "KxF9Iq_lyricLine",
|
|
4391
|
-
"lyricMainLine": "KxF9Iq_lyricMainLine",
|
|
4392
|
-
"lyricSubLine": "KxF9Iq_lyricSubLine",
|
|
4393
|
-
"romanWord": "KxF9Iq_romanWord",
|
|
4394
|
-
"rubyWord": "KxF9Iq_rubyWord",
|
|
4395
|
-
"tmpDisableTransition": "KxF9Iq_tmpDisableTransition",
|
|
4396
|
-
"wordBody": "KxF9Iq_wordBody",
|
|
4397
|
-
"wordWithRuby": "KxF9Iq_wordWithRuby"
|
|
4398
|
-
};
|
|
4399
|
-
//#endregion
|
|
4400
|
-
//#region src/utils/mutex.ts
|
|
4401
|
-
function mutexifyFunction(func) {
|
|
4402
|
-
const awaitingTasks = [];
|
|
4403
|
-
function processNextTask() {
|
|
4404
|
-
const task = awaitingTasks[0];
|
|
4405
|
-
if (!task) return;
|
|
4406
|
-
func(...task.args).then((value) => {
|
|
4407
|
-
task.resolve(value);
|
|
4408
|
-
}).catch((reason) => {
|
|
4409
|
-
task.reject(reason);
|
|
4410
|
-
}).finally(() => {
|
|
4411
|
-
awaitingTasks.shift();
|
|
4412
|
-
if (awaitingTasks.length > 0) processNextTask();
|
|
4413
|
-
});
|
|
4414
|
-
}
|
|
4415
|
-
return ((...args) => {
|
|
4416
|
-
return new Promise((resolve, reject) => {
|
|
4417
|
-
awaitingTasks.push({
|
|
4418
|
-
resolve,
|
|
4419
|
-
reject,
|
|
4420
|
-
args
|
|
4421
|
-
});
|
|
4422
|
-
if (awaitingTasks.length === 1) processNextTask();
|
|
4423
|
-
});
|
|
4424
|
-
});
|
|
4425
|
-
}
|
|
4426
|
-
//#endregion
|
|
4427
|
-
//#region src/lyric-player/dom-slim/lyric-line.ts
|
|
4428
|
-
const ANIMATION_FRAME_QUANTITY = 32;
|
|
4429
|
-
const norNum = (min, max) => (x) => Math.min(1, Math.max(0, (x - min) / (max - min)));
|
|
4430
|
-
const EMP_EASING_MID = .5;
|
|
4431
|
-
const beginNum = norNum(0, EMP_EASING_MID);
|
|
4432
|
-
const endNum = norNum(EMP_EASING_MID, 1);
|
|
4433
|
-
const bezIn = bezier(.2, .4, .58, 1);
|
|
4434
|
-
const bezOut = bezier(.3, 0, .58, 1);
|
|
4435
|
-
const makeEmpEasing = (mid) => {
|
|
4436
|
-
return (x) => x < mid ? bezIn(beginNum(x)) : 1 - bezOut(endNum(x));
|
|
4437
|
-
};
|
|
4438
|
-
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))") {
|
|
4439
|
-
const totalAspect = 2 + width + padding;
|
|
4440
|
-
const widthInTotal = width / totalAspect;
|
|
4441
|
-
const leftPos = (1 - widthInTotal) / 2;
|
|
4442
|
-
return [`linear-gradient(to right,${bright} ${leftPos * 100}%,${dark} ${(leftPos + widthInTotal) * 100}%)`, totalAspect];
|
|
4443
|
-
}
|
|
4444
|
-
var RawLyricLineMouseEvent = class extends MouseEvent {
|
|
4445
|
-
constructor(line, event) {
|
|
4446
|
-
super(event.type, event);
|
|
4447
|
-
this.line = line;
|
|
4448
|
-
}
|
|
4449
|
-
};
|
|
4450
|
-
function getScaleFromTransform(transform) {
|
|
4451
|
-
const match = transform.match(/matrix\(([^)]+)\)/);
|
|
4452
|
-
if (match) {
|
|
4453
|
-
const values = match[1].split(", ");
|
|
4454
|
-
return (Number.parseFloat(values[0]) + Number.parseFloat(values[3])) / 2;
|
|
4455
|
-
}
|
|
4456
|
-
return 1;
|
|
4457
|
-
}
|
|
4458
|
-
var LyricLineEl = class extends LyricLineBase {
|
|
4459
|
-
element = document.createElement("div");
|
|
4460
|
-
splittedWords = [];
|
|
4461
|
-
lineSize = [0, 0];
|
|
4462
|
-
constructor(lyricPlayer, lyricLine = {
|
|
4463
|
-
words: [],
|
|
4464
|
-
translatedLyric: "",
|
|
4465
|
-
romanLyric: "",
|
|
4466
|
-
startTime: 0,
|
|
4467
|
-
endTime: 0,
|
|
4468
|
-
isBG: false,
|
|
4469
|
-
isDuet: false
|
|
4470
|
-
}) {
|
|
4471
|
-
super();
|
|
4472
|
-
this.lyricPlayer = lyricPlayer;
|
|
4473
|
-
this.lyricLine = lyricLine;
|
|
4474
|
-
this.element.setAttribute("class", index_module_default.lyricLine);
|
|
4475
|
-
if (this.lyricLine.isBG) this.element.classList.add(index_module_default.lyricBgLine);
|
|
4476
|
-
if (this.lyricLine.isDuet) this.element.classList.add(index_module_default.lyricDuetLine);
|
|
4477
|
-
this.element.appendChild(document.createElement("div"));
|
|
4478
|
-
this.element.appendChild(document.createElement("div"));
|
|
4479
|
-
this.element.appendChild(document.createElement("div"));
|
|
4480
|
-
const main = this.element.children[0];
|
|
4481
|
-
const trans = this.element.children[1];
|
|
4482
|
-
const roman = this.element.children[2];
|
|
4483
|
-
main.setAttribute("class", index_module_default.lyricMainLine);
|
|
4484
|
-
trans.setAttribute("class", index_module_default.lyricSubLine);
|
|
4485
|
-
roman.setAttribute("class", index_module_default.lyricSubLine);
|
|
4486
|
-
this.rebuildElement();
|
|
4487
|
-
this.rebuildStyle();
|
|
4488
|
-
this.markMaskImageDirty("Initial construction");
|
|
4489
|
-
}
|
|
4490
|
-
listenersMap = /* @__PURE__ */ new Map();
|
|
4491
|
-
onMouseEvent = (e) => {
|
|
4492
|
-
const wrapped = new RawLyricLineMouseEvent(this, e);
|
|
4493
|
-
for (const listener of this.listenersMap.get(e.type) ?? []) listener.call(this, wrapped);
|
|
4494
|
-
if (!this.dispatchEvent(wrapped) || wrapped.defaultPrevented) {
|
|
4495
|
-
e.preventDefault();
|
|
4496
|
-
e.stopPropagation();
|
|
4497
|
-
e.stopImmediatePropagation();
|
|
4498
|
-
}
|
|
4499
|
-
};
|
|
4500
|
-
addMouseEventListener(type, callback, options) {
|
|
4501
|
-
if (callback) {
|
|
4502
|
-
const listeners = this.listenersMap.get(type) ?? /* @__PURE__ */ new Set();
|
|
4503
|
-
if (listeners.size === 0) this.element.addEventListener(type, this.onMouseEvent, options);
|
|
4504
|
-
listeners.add(callback);
|
|
4505
|
-
this.listenersMap.set(type, listeners);
|
|
4506
|
-
}
|
|
4507
|
-
}
|
|
4508
|
-
removeMouseEventListener(type, callback, options) {
|
|
4509
|
-
if (callback) {
|
|
4510
|
-
const listeners = this.listenersMap.get(type);
|
|
4511
|
-
if (listeners) {
|
|
4512
|
-
listeners.delete(callback);
|
|
4513
|
-
if (listeners.size === 0) this.element.removeEventListener(type, this.onMouseEvent, options);
|
|
4514
|
-
}
|
|
4515
|
-
}
|
|
4516
|
-
}
|
|
4517
|
-
areWordsOnSameLine(word1, word2) {
|
|
4518
|
-
if (word1?.mainElement && word2?.mainElement) {
|
|
4519
|
-
const word1el = word1.mainElement;
|
|
4520
|
-
const word2el = word2.mainElement;
|
|
4521
|
-
const rect1 = word1el.getBoundingClientRect();
|
|
4522
|
-
const rect2 = word2el.getBoundingClientRect();
|
|
4523
|
-
return Math.abs(rect1.top - rect2.top) < 10;
|
|
4524
|
-
}
|
|
4525
|
-
return true;
|
|
4526
|
-
}
|
|
4527
|
-
isEnabled = false;
|
|
4528
|
-
async enable(maskAnimationTime = this.lyricLine.startTime) {
|
|
4529
|
-
this.isEnabled = true;
|
|
4530
|
-
this.element.classList.add(index_module_default.active);
|
|
4531
|
-
await this.waitMaskImageUpdated();
|
|
4532
|
-
const main = this.element.children[0];
|
|
4533
|
-
for (const word of this.splittedWords) {
|
|
4534
|
-
for (const a of word.elementAnimations) {
|
|
4535
|
-
a.currentTime = 0;
|
|
4536
|
-
a.playbackRate = 1;
|
|
4537
|
-
a.play();
|
|
4538
|
-
}
|
|
4539
|
-
for (const a of word.maskAnimations) {
|
|
4540
|
-
a.currentTime = Math.min(this.totalDuration, Math.max(0, maskAnimationTime - this.lyricLine.startTime));
|
|
4541
|
-
a.playbackRate = 1;
|
|
4542
|
-
a.play();
|
|
4543
|
-
}
|
|
4544
|
-
}
|
|
4545
|
-
main.classList.add(index_module_default.active);
|
|
4546
|
-
}
|
|
4547
|
-
disable() {
|
|
4548
|
-
this.isEnabled = false;
|
|
4549
|
-
this.element.classList.remove(index_module_default.active);
|
|
4550
|
-
const main = this.element.children[0];
|
|
4551
|
-
for (const word of this.splittedWords) for (const a of word.elementAnimations) if (a.id === "float-word" || a.id.includes("emphasize-word-float-only")) {
|
|
4552
|
-
a.playbackRate = -1;
|
|
4553
|
-
a.play();
|
|
4554
|
-
}
|
|
4555
|
-
main.classList.remove(index_module_default.active);
|
|
4556
|
-
}
|
|
4557
|
-
lastWord;
|
|
4558
|
-
async resume() {
|
|
4559
|
-
await this.waitMaskImageUpdated();
|
|
4560
|
-
if (!this.isEnabled) return;
|
|
4561
|
-
for (const word of this.splittedWords) {
|
|
4562
|
-
for (const a of word.elementAnimations) if (!this.lastWord || this.splittedWords.indexOf(this.lastWord) < this.splittedWords.indexOf(word)) a.play();
|
|
4563
|
-
for (const a of word.maskAnimations) if (!this.lastWord || this.splittedWords.indexOf(this.lastWord) < this.splittedWords.indexOf(word)) a.play();
|
|
4564
|
-
}
|
|
4565
|
-
}
|
|
4566
|
-
async pause() {
|
|
4567
|
-
await this.waitMaskImageUpdated();
|
|
4568
|
-
if (!this.isEnabled) return;
|
|
4569
|
-
for (const word of this.splittedWords) {
|
|
4570
|
-
for (const a of word.elementAnimations) a.pause();
|
|
4571
|
-
for (const a of word.maskAnimations) a.pause();
|
|
4572
|
-
}
|
|
4573
|
-
}
|
|
4574
|
-
setMaskAnimationState(maskAnimationTime = 0) {
|
|
4575
|
-
const t = maskAnimationTime - this.lyricLine.startTime;
|
|
4576
|
-
for (const word of this.splittedWords) for (const a of word.maskAnimations) {
|
|
4577
|
-
a.currentTime = Math.min(this.totalDuration, Math.max(0, t));
|
|
4578
|
-
a.playbackRate = 1;
|
|
4579
|
-
if (t >= 0 && t < this.totalDuration) a.play();
|
|
4580
|
-
else a.pause();
|
|
4581
|
-
}
|
|
4582
|
-
}
|
|
4583
|
-
measureLockMark = false;
|
|
4584
|
-
measureLock = mutexifyFunction(async (callback) => {
|
|
4585
|
-
if (this.measureLockMark) return;
|
|
4586
|
-
this.measureLockMark = true;
|
|
4587
|
-
await callback();
|
|
4588
|
-
this.measureLockMark = false;
|
|
4589
|
-
});
|
|
4590
|
-
getLine() {
|
|
4591
|
-
return this.lyricLine;
|
|
4592
|
-
}
|
|
4593
|
-
show() {
|
|
4594
|
-
this.rebuildStyle();
|
|
4595
|
-
}
|
|
4596
|
-
hide() {}
|
|
4597
|
-
rebuildStyle() {}
|
|
4598
|
-
getRubySegments(word) {
|
|
4599
|
-
return (word.ruby ?? []).filter((ruby) => (ruby?.word?.trim().length ?? 0) > 0);
|
|
4600
|
-
}
|
|
4601
|
-
buildWordElement(word, shouldEmphasize, hasRubyLine, hasRomanLine, displayWord) {
|
|
4602
|
-
const mainWordEl = document.createElement("span");
|
|
4603
|
-
const subElements = [];
|
|
4604
|
-
const romanWord = word.romanWord?.trim() ?? "";
|
|
4605
|
-
let wordContainer = mainWordEl;
|
|
4606
|
-
if (hasRubyLine || hasRomanLine) {
|
|
4607
|
-
wordContainer = document.createElement("div");
|
|
4608
|
-
mainWordEl.appendChild(wordContainer);
|
|
4609
|
-
}
|
|
4610
|
-
if (hasRubyLine) {
|
|
4611
|
-
const rubyWordEl = document.createElement("div");
|
|
4612
|
-
const rubySegments = this.getRubySegments(word);
|
|
4613
|
-
for (const ruby of rubySegments) {
|
|
4614
|
-
const rubyPartEl = document.createElement("span");
|
|
4615
|
-
rubyPartEl.innerText = ruby.word;
|
|
4616
|
-
rubyPartEl.dataset.startTime = String(ruby.startTime);
|
|
4617
|
-
rubyPartEl.dataset.endTime = String(ruby.endTime);
|
|
4618
|
-
rubyWordEl.appendChild(rubyPartEl);
|
|
4619
|
-
}
|
|
4620
|
-
rubyWordEl.classList.add(index_module_default.rubyWord);
|
|
4621
|
-
mainWordEl.classList.add(index_module_default.wordWithRuby);
|
|
4622
|
-
wordContainer.classList.add(index_module_default.wordBody);
|
|
4623
|
-
mainWordEl.insertBefore(rubyWordEl, wordContainer);
|
|
4624
|
-
}
|
|
4625
|
-
if (shouldEmphasize) {
|
|
4626
|
-
mainWordEl.classList.add(index_module_default.emphasize);
|
|
4627
|
-
for (const char of displayWord.trim()) {
|
|
4628
|
-
const charEl = document.createElement("span");
|
|
4629
|
-
charEl.innerText = char;
|
|
4630
|
-
subElements.push(charEl);
|
|
4631
|
-
wordContainer.appendChild(charEl);
|
|
4632
|
-
}
|
|
4633
|
-
} else if (hasRomanLine) {
|
|
4634
|
-
const wordEl = document.createElement("div");
|
|
4635
|
-
wordEl.innerText = displayWord;
|
|
4636
|
-
wordContainer.appendChild(wordEl);
|
|
4637
|
-
} else mainWordEl.innerText = displayWord;
|
|
4638
|
-
if (hasRomanLine) {
|
|
4639
|
-
const romanWordEl = document.createElement("div");
|
|
4640
|
-
romanWordEl.innerText = romanWord.length > 0 ? romanWord : "\xA0";
|
|
4641
|
-
romanWordEl.classList.add(index_module_default.romanWord);
|
|
4642
|
-
wordContainer.appendChild(romanWordEl);
|
|
4643
|
-
}
|
|
4644
|
-
return {
|
|
4645
|
-
mainWordEl,
|
|
4646
|
-
subElements
|
|
4647
|
-
};
|
|
4648
|
-
}
|
|
4649
|
-
rebuildElement() {
|
|
4650
|
-
this.disposeElements();
|
|
4651
|
-
const main = this.element.children[0];
|
|
4652
|
-
const trans = this.element.children[1];
|
|
4653
|
-
const roman = this.element.children[2];
|
|
4654
|
-
if (this.lyricPlayer._getIsNonDynamic()) {
|
|
4655
|
-
main.innerText = this.lyricLine.words.map((w) => this.lyricPlayer.processObsceneWord(w)).join("");
|
|
4656
|
-
trans.innerText = this.lyricLine.translatedLyric;
|
|
4657
|
-
roman.innerText = this.lyricLine.romanLyric;
|
|
4658
|
-
return;
|
|
4659
|
-
}
|
|
4660
|
-
const chunkedWords = chunkAndSplitLyricWords(this.lyricLine.words);
|
|
4661
|
-
const hasRubyLine = this.lyricLine.words.some((word) => (word.ruby?.length ?? 0) > 0);
|
|
4662
|
-
const hasRomanLine = this.lyricLine.words.some((word) => (word.romanWord?.trim().length ?? 0) > 0);
|
|
4663
|
-
main.innerHTML = "";
|
|
4664
|
-
for (const chunk of chunkedWords) if (Array.isArray(chunk)) {
|
|
4665
|
-
if (chunk.length === 0) continue;
|
|
4666
|
-
const merged = chunk.reduce((a, b) => {
|
|
4667
|
-
a.endTime = Math.max(a.endTime, b.endTime);
|
|
4668
|
-
a.startTime = Math.min(a.startTime, b.startTime);
|
|
4669
|
-
a.word += b.word;
|
|
4670
|
-
return a;
|
|
4671
|
-
}, {
|
|
4672
|
-
word: "",
|
|
4673
|
-
romanWord: "",
|
|
4674
|
-
startTime: Number.POSITIVE_INFINITY,
|
|
4675
|
-
endTime: Number.NEGATIVE_INFINITY,
|
|
4676
|
-
wordType: "normal",
|
|
4677
|
-
obscene: false
|
|
4678
|
-
});
|
|
4679
|
-
const emp = chunk.map((word) => LyricLineBase.shouldEmphasize(word)).reduce((a, b) => a || b, LyricLineBase.shouldEmphasize(merged));
|
|
4680
|
-
const wrapperWordEl = document.createElement("span");
|
|
4681
|
-
wrapperWordEl.classList.add(index_module_default.emphasizeWrapper);
|
|
4682
|
-
const characterElements = [];
|
|
4683
|
-
for (const word of chunk) {
|
|
4684
|
-
const { mainWordEl, subElements } = this.buildWordElement(word, emp, hasRubyLine, hasRomanLine, this.lyricPlayer.processObsceneWord(word));
|
|
4685
|
-
if (emp) characterElements.push(...subElements);
|
|
4686
|
-
this.splittedWords.push({
|
|
4687
|
-
...word,
|
|
4688
|
-
mainElement: mainWordEl,
|
|
4689
|
-
subElements,
|
|
4690
|
-
elementAnimations: [],
|
|
4691
|
-
maskAnimations: [],
|
|
4692
|
-
width: 0,
|
|
4693
|
-
height: 0,
|
|
4694
|
-
padding: 0,
|
|
4695
|
-
shouldEmphasize: emp
|
|
4696
|
-
});
|
|
4697
|
-
wrapperWordEl.appendChild(mainWordEl);
|
|
4698
|
-
}
|
|
4699
|
-
if (emp) this.splittedWords[this.splittedWords.length - 1].elementAnimations.push(...this.initEmphasizeAnimation(merged, characterElements, merged.endTime - merged.startTime, merged.startTime - this.lyricLine.startTime));
|
|
4700
|
-
if (merged.word.trimStart() !== merged.word) main.appendChild(document.createTextNode(" "));
|
|
4701
|
-
main.appendChild(wrapperWordEl);
|
|
4702
|
-
if (merged.word.trimEnd() !== merged.word && LyricLineBase.shouldEmphasize(merged)) main.appendChild(document.createTextNode(" "));
|
|
4703
|
-
} else if (chunk.word.trim().length === 0) main.appendChild(document.createTextNode(" "));
|
|
4704
|
-
else {
|
|
4705
|
-
const emp = LyricLineBase.shouldEmphasize(chunk);
|
|
4706
|
-
const { mainWordEl, subElements } = this.buildWordElement(chunk, emp, hasRubyLine, hasRomanLine, this.lyricPlayer.processObsceneWord(chunk).trim());
|
|
4707
|
-
const realWord = {
|
|
4708
|
-
...chunk,
|
|
4709
|
-
mainElement: mainWordEl,
|
|
4710
|
-
subElements,
|
|
4711
|
-
elementAnimations: [],
|
|
4712
|
-
maskAnimations: [],
|
|
4713
|
-
width: 0,
|
|
4714
|
-
height: 0,
|
|
4715
|
-
padding: 0,
|
|
4716
|
-
shouldEmphasize: emp
|
|
4717
|
-
};
|
|
4718
|
-
if (emp) {
|
|
4719
|
-
const duration = Math.abs(realWord.endTime - realWord.startTime);
|
|
4720
|
-
realWord.elementAnimations.push(...this.initEmphasizeAnimation(chunk, subElements, duration, realWord.startTime - this.lyricLine.startTime));
|
|
4721
|
-
}
|
|
4722
|
-
if (chunk.word.trimStart() !== chunk.word) main.appendChild(document.createTextNode(" "));
|
|
4723
|
-
main.appendChild(mainWordEl);
|
|
4724
|
-
if (chunk.word.trimEnd() !== chunk.word) main.appendChild(document.createTextNode(" "));
|
|
4725
|
-
this.splittedWords.push(realWord);
|
|
4726
|
-
}
|
|
4727
|
-
trans.innerText = this.lyricLine.translatedLyric;
|
|
4728
|
-
roman.innerText = this.lyricLine.romanLyric;
|
|
4729
|
-
}
|
|
4730
|
-
initEmphasizeAnimation(word, characterElements, duration, delay) {
|
|
4731
|
-
const de = Math.max(0, delay);
|
|
4732
|
-
let du = Math.max(1e3, duration);
|
|
4733
|
-
let result = [];
|
|
4734
|
-
let amount = du / 2e3;
|
|
4735
|
-
amount = amount > 1 ? Math.sqrt(amount) : amount ** 3;
|
|
4736
|
-
let blur = du / 3e3;
|
|
4737
|
-
blur = blur > 1 ? Math.sqrt(blur) : blur ** 3;
|
|
4738
|
-
amount *= .6;
|
|
4739
|
-
blur *= .5;
|
|
4740
|
-
if (this.lyricLine.words.length > 0 && word.word.includes(this.lyricLine.words[this.lyricLine.words.length - 1].word)) {
|
|
4741
|
-
amount *= 1.6;
|
|
4742
|
-
blur *= 1.5;
|
|
4743
|
-
du *= 1.2;
|
|
4744
|
-
}
|
|
4745
|
-
amount = Math.min(1.2, amount);
|
|
4746
|
-
blur = Math.min(.8, blur);
|
|
4747
|
-
const animateDu = Number.isFinite(du) ? du : 0;
|
|
4748
|
-
const empEasing = makeEmpEasing(EMP_EASING_MID);
|
|
4749
|
-
result = characterElements.flatMap((el, i, arr) => {
|
|
4750
|
-
const wordDe = de + du / 2.5 / arr.length * i;
|
|
4751
|
-
const result = [];
|
|
4752
|
-
const frames = new Array(ANIMATION_FRAME_QUANTITY).fill(0).map((_, j) => {
|
|
4753
|
-
const x = (j + 1) / ANIMATION_FRAME_QUANTITY;
|
|
4754
|
-
const transX = empEasing(x);
|
|
4755
|
-
const glowLevel = empEasing(x) * blur;
|
|
4756
|
-
const mat = scaleMatrix4(createMatrix4(), 1 + transX * .1 * amount);
|
|
4757
|
-
const offsetX = -transX * .03 * amount * (arr.length / 2 - i);
|
|
4758
|
-
const offsetY = -transX * .025 * amount;
|
|
4759
|
-
return {
|
|
4760
|
-
offset: x,
|
|
4761
|
-
transform: `${matrix4ToCSS(mat, 4)} translate(${offsetX}em, ${offsetY}em)`,
|
|
4762
|
-
textShadow: `0 0 ${Math.min(.3, blur * .3)}em rgba(255, 255, 255, ${glowLevel})`
|
|
4763
|
-
};
|
|
4764
|
-
});
|
|
4765
|
-
const glow = el.animate(frames, {
|
|
4766
|
-
duration: animateDu,
|
|
4767
|
-
delay: Number.isFinite(wordDe) ? wordDe : 0,
|
|
4768
|
-
id: `emphasize-word-${el.innerText}-${i}`,
|
|
4769
|
-
iterations: 1,
|
|
4770
|
-
composite: "replace",
|
|
4771
|
-
fill: "both"
|
|
4772
|
-
});
|
|
4773
|
-
glow.onfinish = () => {
|
|
4774
|
-
glow.pause();
|
|
4775
|
-
};
|
|
4776
|
-
glow.pause();
|
|
4777
|
-
result.push(glow);
|
|
4778
|
-
const floatFrame = new Array(ANIMATION_FRAME_QUANTITY).fill(0).map((_, j) => {
|
|
4779
|
-
const x = (j + 1) / ANIMATION_FRAME_QUANTITY;
|
|
4780
|
-
let y = Math.sin(x * Math.PI);
|
|
4781
|
-
if (this.lyricLine.isBG) y *= 2;
|
|
4782
|
-
return {
|
|
4783
|
-
offset: x,
|
|
4784
|
-
transform: `translateY(${-y * .05}em)`
|
|
4785
|
-
};
|
|
4786
|
-
});
|
|
4787
|
-
const float = el.animate(floatFrame, {
|
|
4788
|
-
duration: animateDu * 1.4,
|
|
4789
|
-
delay: Number.isFinite(wordDe) ? wordDe - 400 : 0,
|
|
4790
|
-
id: "emphasize-word-float",
|
|
4791
|
-
iterations: 1,
|
|
4792
|
-
composite: "add",
|
|
4793
|
-
fill: "both"
|
|
4794
|
-
});
|
|
4795
|
-
float.onfinish = () => {
|
|
4796
|
-
float.pause();
|
|
4797
|
-
};
|
|
4798
|
-
float.pause();
|
|
4799
|
-
result.push(float);
|
|
4800
|
-
return result;
|
|
4801
|
-
});
|
|
4802
|
-
return result;
|
|
4803
|
-
}
|
|
4804
|
-
get totalDuration() {
|
|
4805
|
-
return this.lyricLine.endTime - this.lyricLine.startTime;
|
|
4806
|
-
}
|
|
4807
|
-
maskImageDirty = false;
|
|
4808
|
-
markImageDirtyPromiseResolve = /* @__PURE__ */ new Set();
|
|
4809
|
-
markImageDirtyPromise = new Promise((resolve) => {
|
|
4810
|
-
this.markImageDirtyPromiseResolve.add(resolve);
|
|
4811
|
-
});
|
|
4812
|
-
markMaskImageDirty(_debugReason = "") {
|
|
4813
|
-
this.maskImageDirty = true;
|
|
4814
|
-
if (!this.element.classList.contains(index_module_default.dirty)) this.element.classList.add(index_module_default.dirty);
|
|
4815
|
-
const newPromise = Promise.all([this.markImageDirtyPromise, new Promise((resolve) => {
|
|
4816
|
-
this.markImageDirtyPromiseResolve.add(resolve);
|
|
4817
|
-
})]).then(() => {});
|
|
4818
|
-
this.markImageDirtyPromise = newPromise;
|
|
4819
|
-
return newPromise;
|
|
4820
|
-
}
|
|
4821
|
-
waitMaskImageUpdated() {
|
|
4822
|
-
return this.markImageDirtyPromise;
|
|
4823
|
-
}
|
|
4824
|
-
async updateMaskImage() {
|
|
4825
|
-
if (!this.element.checkVisibility({ contentVisibilityAuto: true })) return;
|
|
4826
|
-
this.maskImageDirty = false;
|
|
4827
|
-
await this.measureLock(async () => {
|
|
4828
|
-
await Promise.all(this.splittedWords.map(async (word) => {
|
|
4829
|
-
const el = word.mainElement;
|
|
4830
|
-
if (el) await measure(() => {
|
|
4831
|
-
word.padding = Number.parseFloat(getComputedStyle(el).paddingLeft);
|
|
4832
|
-
word.width = el.clientWidth - word.padding * 2;
|
|
4833
|
-
word.height = el.clientHeight - word.padding * 2;
|
|
4834
|
-
});
|
|
4835
|
-
else {
|
|
4836
|
-
word.width = 0;
|
|
4837
|
-
word.height = 0;
|
|
4838
|
-
word.padding = 0;
|
|
4839
|
-
}
|
|
4840
|
-
if (word.width * word.height === 0) console.warn("Word size is zero");
|
|
4841
|
-
}));
|
|
4842
|
-
await mutate(() => {
|
|
4843
|
-
if (this.lyricPlayer.supportMaskImage) this.generateWebAnimationBasedMaskImage();
|
|
4844
|
-
else this.generateCalcBasedMaskImage();
|
|
4845
|
-
});
|
|
4846
|
-
});
|
|
4847
|
-
for (const resolve of this.markImageDirtyPromiseResolve) {
|
|
4848
|
-
resolve();
|
|
4849
|
-
this.markImageDirtyPromiseResolve.delete(resolve);
|
|
4850
|
-
}
|
|
4851
|
-
await mutate(() => {
|
|
4852
|
-
this.element.classList.remove(index_module_default.dirty);
|
|
4853
|
-
});
|
|
4854
|
-
}
|
|
4855
|
-
generateCalcBasedMaskImage() {
|
|
4856
|
-
for (const word of this.splittedWords) {
|
|
4857
|
-
const wordEl = word.mainElement;
|
|
4858
|
-
if (wordEl) {
|
|
4859
|
-
word.width = wordEl.clientWidth;
|
|
4860
|
-
word.height = wordEl.clientHeight;
|
|
4861
|
-
const fadeWidth = word.height * this.lyricPlayer.getWordFadeWidth();
|
|
4862
|
-
const [maskImage, totalAspect] = generateFadeGradient(fadeWidth / word.width);
|
|
4863
|
-
const totalAspectStr = `${totalAspect * 100}% 100%`;
|
|
4864
|
-
if (this.lyricPlayer.supportMaskImage) {
|
|
4865
|
-
wordEl.style.maskImage = maskImage;
|
|
4866
|
-
wordEl.style.maskRepeat = "no-repeat";
|
|
4867
|
-
wordEl.style.maskOrigin = "left";
|
|
4868
|
-
wordEl.style.maskSize = totalAspectStr;
|
|
4869
|
-
} else {
|
|
4870
|
-
wordEl.style.webkitMaskImage = maskImage;
|
|
4871
|
-
wordEl.style.webkitMaskRepeat = "no-repeat";
|
|
4872
|
-
wordEl.style.webkitMaskOrigin = "left";
|
|
4873
|
-
wordEl.style.webkitMaskSize = totalAspectStr;
|
|
4874
|
-
}
|
|
4875
|
-
const w = word.width + fadeWidth;
|
|
4876
|
-
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`;
|
|
4877
|
-
wordEl.style.maskPosition = maskPos;
|
|
4878
|
-
wordEl.style.webkitMaskPosition = maskPos;
|
|
4879
|
-
}
|
|
4880
|
-
}
|
|
4881
|
-
}
|
|
4882
|
-
generateWebAnimationBasedMaskImage() {
|
|
4883
|
-
const totalFadeDuration = Math.max(this.splittedWords.reduce((pv, w) => Math.max(w.endTime, pv), 0), this.lyricLine.endTime) - this.lyricLine.startTime;
|
|
4884
|
-
this.splittedWords.forEach((word, i) => {
|
|
4885
|
-
const wordEl = word.mainElement;
|
|
4886
|
-
if (wordEl) {
|
|
4887
|
-
const fadeWidth = word.height * this.lyricPlayer.getWordFadeWidth();
|
|
4888
|
-
const [maskImage, totalAspect] = generateFadeGradient(fadeWidth / (word.width + word.padding * 2));
|
|
4889
|
-
const totalAspectStr = `${totalAspect * 100}% 100%`;
|
|
4890
|
-
if (this.lyricPlayer.supportMaskImage) {
|
|
4891
|
-
wordEl.style.maskImage = maskImage;
|
|
4892
|
-
wordEl.style.maskRepeat = "no-repeat";
|
|
4893
|
-
wordEl.style.maskOrigin = "left";
|
|
4894
|
-
wordEl.style.maskSize = totalAspectStr;
|
|
4895
|
-
} else {
|
|
4896
|
-
wordEl.style.webkitMaskImage = maskImage;
|
|
4897
|
-
wordEl.style.webkitMaskRepeat = "no-repeat";
|
|
4898
|
-
wordEl.style.webkitMaskOrigin = "left";
|
|
4899
|
-
wordEl.style.webkitMaskSize = totalAspectStr;
|
|
4900
|
-
}
|
|
4901
|
-
const widthBeforeSelf = this.splittedWords.slice(0, i).reduce((a, b) => a + b.width, 0) + (this.splittedWords[0] ? fadeWidth : 0);
|
|
4902
|
-
const minOffset = -(word.width + word.padding * 2 + fadeWidth);
|
|
4903
|
-
const clampOffset = (x) => Math.max(minOffset, Math.min(0, x));
|
|
4904
|
-
let curPos = -widthBeforeSelf - word.width - word.padding - fadeWidth;
|
|
4905
|
-
let timeOffset = 0;
|
|
4906
|
-
const frames = [];
|
|
4907
|
-
let lastPos = curPos;
|
|
4908
|
-
let lastTime = 0;
|
|
4909
|
-
const pushFrame = () => {
|
|
4910
|
-
const moveOffset = curPos - lastPos;
|
|
4911
|
-
const time = Math.max(0, Math.min(1, timeOffset));
|
|
4912
|
-
const duration = time - lastTime;
|
|
4913
|
-
const d = Math.abs(duration / moveOffset);
|
|
4914
|
-
if (curPos > minOffset && lastPos < minOffset) {
|
|
4915
|
-
const staticTime = Math.abs(lastPos - minOffset) * d;
|
|
4916
|
-
const value = `${clampOffset(lastPos)}px 0`;
|
|
4917
|
-
const frame = {
|
|
4918
|
-
offset: lastTime + staticTime,
|
|
4919
|
-
maskPosition: value
|
|
4920
|
-
};
|
|
4921
|
-
frames.push(frame);
|
|
4922
|
-
}
|
|
4923
|
-
if (curPos > 0 && lastPos < 0) {
|
|
4924
|
-
const staticTime = Math.abs(lastPos) * d;
|
|
4925
|
-
const value = `${clampOffset(curPos)}px 0`;
|
|
4926
|
-
const frame = {
|
|
4927
|
-
offset: lastTime + staticTime,
|
|
4928
|
-
maskPosition: value
|
|
4929
|
-
};
|
|
4930
|
-
frames.push(frame);
|
|
4931
|
-
}
|
|
4932
|
-
const frame = {
|
|
4933
|
-
offset: time,
|
|
4934
|
-
maskPosition: `${clampOffset(curPos)}px 0`
|
|
4935
|
-
};
|
|
4936
|
-
frames.push(frame);
|
|
4937
|
-
lastPos = curPos;
|
|
4938
|
-
lastTime = time;
|
|
4939
|
-
};
|
|
4940
|
-
pushFrame();
|
|
4941
|
-
let lastTimeStamp = 0;
|
|
4942
|
-
this.splittedWords.forEach((otherWord, j) => {
|
|
4943
|
-
{
|
|
4944
|
-
const curTimeStamp = otherWord.startTime - this.lyricLine.startTime;
|
|
4945
|
-
const staticDuration = curTimeStamp - lastTimeStamp;
|
|
4946
|
-
timeOffset += staticDuration / totalFadeDuration;
|
|
4947
|
-
if (staticDuration > 0) pushFrame();
|
|
4948
|
-
lastTimeStamp = curTimeStamp;
|
|
4949
|
-
}
|
|
4950
|
-
{
|
|
4951
|
-
const fadeDuration = otherWord.endTime - otherWord.startTime;
|
|
4952
|
-
const rubySegments = this.getRubySegments(otherWord);
|
|
4953
|
-
const rubyCharCount = rubySegments.reduce((total, ruby) => total + ruby.word.length, 0);
|
|
4954
|
-
if (rubyCharCount > 0) {
|
|
4955
|
-
const widthPerChar = otherWord.width / rubyCharCount;
|
|
4956
|
-
let charIndex = 0;
|
|
4957
|
-
for (const ruby of rubySegments) {
|
|
4958
|
-
const rubyStartTime = Number.isFinite(ruby.startTime) ? ruby.startTime : otherWord.startTime;
|
|
4959
|
-
const rubyEndTime = Number.isFinite(ruby.endTime) ? ruby.endTime : otherWord.endTime;
|
|
4960
|
-
const rubyStart = Math.max(rubyStartTime, otherWord.startTime);
|
|
4961
|
-
const rubyEnd = Math.min(Math.max(rubyEndTime, rubyStart), otherWord.endTime);
|
|
4962
|
-
const rubyStartStamp = rubyStart - this.lyricLine.startTime;
|
|
4963
|
-
const rubyStaticDuration = rubyStartStamp - lastTimeStamp;
|
|
4964
|
-
timeOffset += rubyStaticDuration / totalFadeDuration;
|
|
4965
|
-
if (rubyStaticDuration > 0) pushFrame();
|
|
4966
|
-
lastTimeStamp = rubyStartStamp;
|
|
4967
|
-
const perCharDuration = Math.max(0, rubyEnd - rubyStart) / ruby.word.length;
|
|
4968
|
-
for (let rubyCharIndex = 0; rubyCharIndex < ruby.word.length; rubyCharIndex++) {
|
|
4969
|
-
timeOffset += perCharDuration / totalFadeDuration;
|
|
4970
|
-
curPos += widthPerChar;
|
|
4971
|
-
if (j === 0 && charIndex === 0) curPos += fadeWidth * 1.5;
|
|
4972
|
-
if (j === this.splittedWords.length - 1 && charIndex === rubyCharCount - 1) curPos += fadeWidth * .5;
|
|
4973
|
-
if (perCharDuration > 0) pushFrame();
|
|
4974
|
-
lastTimeStamp += perCharDuration;
|
|
4975
|
-
charIndex++;
|
|
4976
|
-
}
|
|
4977
|
-
}
|
|
4978
|
-
const wordEndStamp = Math.max(otherWord.endTime - this.lyricLine.startTime, lastTimeStamp);
|
|
4979
|
-
const wordTailDuration = wordEndStamp - lastTimeStamp;
|
|
4980
|
-
timeOffset += wordTailDuration / totalFadeDuration;
|
|
4981
|
-
if (wordTailDuration > 0) pushFrame();
|
|
4982
|
-
lastTimeStamp = wordEndStamp;
|
|
4983
|
-
} else {
|
|
4984
|
-
timeOffset += fadeDuration / totalFadeDuration;
|
|
4985
|
-
curPos += otherWord.width;
|
|
4986
|
-
if (j === 0) curPos += fadeWidth * 1.5;
|
|
4987
|
-
if (j === this.splittedWords.length - 1) curPos += fadeWidth * .5;
|
|
4988
|
-
if (fadeDuration > 0) pushFrame();
|
|
4989
|
-
lastTimeStamp += fadeDuration;
|
|
4990
|
-
}
|
|
4991
|
-
}
|
|
4992
|
-
});
|
|
4993
|
-
for (const a of word.maskAnimations) a.cancel();
|
|
4994
|
-
try {
|
|
4995
|
-
const ani = wordEl.animate(frames, {
|
|
4996
|
-
duration: totalFadeDuration || 1,
|
|
4997
|
-
id: `fade-word-${word.word}-${i}`,
|
|
4998
|
-
fill: "both"
|
|
4999
|
-
});
|
|
5000
|
-
ani.pause();
|
|
5001
|
-
word.maskAnimations = [ani];
|
|
5002
|
-
} catch (err) {
|
|
5003
|
-
console.warn("应用渐变动画发生错误", frames, totalFadeDuration, err);
|
|
5004
|
-
}
|
|
5005
|
-
}
|
|
5006
|
-
});
|
|
5007
|
-
}
|
|
5008
|
-
getElement() {
|
|
5009
|
-
return this.element;
|
|
5010
|
-
}
|
|
5011
|
-
setTransform(top = this.top, scale = this.scale, opacity = 1, blur = 0, force = false, delay = 0) {
|
|
5012
|
-
super.setTransform(top, scale, opacity, blur, force, delay);
|
|
5013
|
-
const beforeInSight = this.isInSight;
|
|
5014
|
-
const enableSpring = this.lyricPlayer.getEnableSpring();
|
|
5015
|
-
this.top = top;
|
|
5016
|
-
this.scale = scale;
|
|
5017
|
-
this.delay = delay * 1e3 | 0;
|
|
5018
|
-
const main = this.element.children[0];
|
|
5019
|
-
main.style.opacity = `${opacity}`;
|
|
5020
|
-
if (force || !enableSpring) {
|
|
5021
|
-
if (force) this.element.classList.add(index_module_default.tmpDisableTransition);
|
|
5022
|
-
this.lineTransforms.posY.setPosition(top);
|
|
5023
|
-
this.lineTransforms.scale.setPosition(scale);
|
|
5024
|
-
if (!enableSpring) {
|
|
5025
|
-
const afterInSight = this.isInSight;
|
|
5026
|
-
if (beforeInSight || afterInSight) this.show();
|
|
5027
|
-
else this.hide();
|
|
5028
|
-
} else this.rebuildStyle();
|
|
5029
|
-
if (force) requestAnimationFrame(() => {
|
|
5030
|
-
this.element.classList.remove(index_module_default.tmpDisableTransition);
|
|
5031
|
-
});
|
|
5032
|
-
} else {
|
|
5033
|
-
this.lineTransforms.posY.setTargetPosition(top, delay);
|
|
5034
|
-
this.lineTransforms.scale.setTargetPosition(scale);
|
|
5035
|
-
}
|
|
5036
|
-
}
|
|
5037
|
-
update(delta = 0) {
|
|
5038
|
-
if (!this.lyricPlayer.getEnableSpring()) return;
|
|
5039
|
-
this.lineTransforms.posY.update(delta);
|
|
5040
|
-
this.lineTransforms.scale.update(delta);
|
|
5041
|
-
if (this.isInSight) {
|
|
5042
|
-
this.show();
|
|
5043
|
-
if (this.maskImageDirty) this.updateMaskImage();
|
|
5044
|
-
} else this.hide();
|
|
5045
|
-
if (this.lyricPlayer.getEnableSpring()) {
|
|
5046
|
-
this.element.style.setProperty("--bright-mask-alpha", `${Math.max(0, Math.min(1, this.lineTransforms.scale.getCurrentPosition() / 100 - .97) / .03) * .8 + .2}`);
|
|
5047
|
-
this.element.style.setProperty("--dark-mask-alpha", `${Math.max(0, Math.min(1, this.lineTransforms.scale.getCurrentPosition() / 100 - .97) / .03) * .2 + .2}`);
|
|
5048
|
-
} else {
|
|
5049
|
-
const transform = window.getComputedStyle(this.element).transform;
|
|
5050
|
-
const scale = getScaleFromTransform(transform);
|
|
5051
|
-
this.element.style.setProperty("--bright-mask-alpha", `${Math.max(0, Math.min(1, (scale - .97) / .03)) * .8 + .2}`);
|
|
5052
|
-
this.element.style.setProperty("--dark-mask-alpha", `${Math.max(0, Math.min(1, (scale - .97) / .03)) * .2 + .2}`);
|
|
5053
|
-
}
|
|
5054
|
-
}
|
|
5055
|
-
_getDebugTargetPos() {
|
|
5056
|
-
return `[位移: ${this.top}; 缩放: ${this.scale}; 延时: ${this.delay}]`;
|
|
5057
|
-
}
|
|
5058
|
-
get isInSight() {
|
|
5059
|
-
const t = this.lineTransforms.posY.getCurrentPosition();
|
|
5060
|
-
const h = this.lineSize[1];
|
|
5061
|
-
const b = t + h;
|
|
5062
|
-
return !(t > this.lyricPlayer.size[1] + h || b < -h);
|
|
5063
|
-
}
|
|
5064
|
-
disposeElements() {
|
|
5065
|
-
for (const realWord of this.splittedWords) {
|
|
5066
|
-
for (const a of realWord.elementAnimations) a.cancel();
|
|
5067
|
-
for (const a of realWord.maskAnimations) a.cancel();
|
|
5068
|
-
for (const sub of realWord.subElements) {
|
|
5069
|
-
sub.remove();
|
|
5070
|
-
sub.parentNode?.removeChild(sub);
|
|
5071
|
-
}
|
|
5072
|
-
realWord.elementAnimations = [];
|
|
5073
|
-
realWord.maskAnimations = [];
|
|
5074
|
-
realWord.subElements = [];
|
|
5075
|
-
realWord.mainElement.remove();
|
|
5076
|
-
realWord.mainElement.parentNode?.removeChild(realWord.mainElement);
|
|
5077
|
-
}
|
|
5078
|
-
this.splittedWords = [];
|
|
5079
|
-
}
|
|
5080
|
-
dispose() {
|
|
5081
|
-
this.disposeElements();
|
|
5082
|
-
this.element.remove();
|
|
5083
|
-
}
|
|
5084
|
-
};
|
|
5085
|
-
//#endregion
|
|
5086
|
-
//#region src/lyric-player/dom-slim/index.ts
|
|
5087
|
-
/**
|
|
5088
|
-
* 歌词播放组件,本框架的核心组件
|
|
5089
|
-
*
|
|
5090
|
-
* 尽可能贴切 Apple Music for iPad 的歌词效果设计,且做了力所能及的优化措施
|
|
5091
|
-
*/
|
|
5092
|
-
var DomSlimLyricPlayer = class extends LyricPlayerBase {
|
|
5093
|
-
currentLyricLineObjects = [];
|
|
5094
|
-
debounceCalcLayout = debounce(() => this.calcLayout(true).then(() => this.currentLyricLineObjects.map(async (el, i) => {
|
|
5095
|
-
el.markMaskImageDirty("DomLyricPlayer onResize");
|
|
5096
|
-
await el.waitMaskImageUpdated();
|
|
5097
|
-
if (this.hotLines.has(i)) {
|
|
5098
|
-
el.enable(this.currentTime);
|
|
5099
|
-
el.resume();
|
|
5100
|
-
}
|
|
5101
|
-
})), 1e3);
|
|
5102
|
-
onResize() {
|
|
5103
|
-
const computedStyles = getComputedStyle(this.element);
|
|
5104
|
-
this._baseFontSize = Number.parseFloat(computedStyles.fontSize);
|
|
5105
|
-
const innerWidth = this.element.clientWidth - Number.parseFloat(computedStyles.paddingLeft) - Number.parseFloat(computedStyles.paddingRight);
|
|
5106
|
-
const innerHeight = this.element.clientHeight - Number.parseFloat(computedStyles.paddingTop) - Number.parseFloat(computedStyles.paddingBottom);
|
|
5107
|
-
this.innerSize[0] = innerWidth;
|
|
5108
|
-
this.innerSize[1] = innerHeight;
|
|
5109
|
-
this.rebuildStyle();
|
|
5110
|
-
this.debounceCalcLayout();
|
|
5111
|
-
}
|
|
5112
|
-
supportPlusLighter = CSS.supports("mix-blend-mode", "plus-lighter");
|
|
5113
|
-
supportMaskImage = CSS.supports("mask-image", "none");
|
|
5114
|
-
innerSize = [0, 0];
|
|
5115
|
-
onLineClickedHandler = (e) => {
|
|
5116
|
-
const evt = new LyricLineMouseEvent(this.lyricLinesIndexes.get(e.line) ?? -1, e.line, e);
|
|
5117
|
-
if (!this.dispatchEvent(evt)) {
|
|
5118
|
-
e.preventDefault();
|
|
5119
|
-
e.stopPropagation();
|
|
5120
|
-
e.stopImmediatePropagation();
|
|
5121
|
-
}
|
|
5122
|
-
};
|
|
5123
|
-
/**
|
|
5124
|
-
* 是否为非逐词歌词
|
|
5125
|
-
* @internal
|
|
5126
|
-
*/
|
|
5127
|
-
_getIsNonDynamic() {
|
|
5128
|
-
return this.isNonDynamic;
|
|
5129
|
-
}
|
|
5130
|
-
_baseFontSize = Number.parseFloat(getComputedStyle(this.element).fontSize);
|
|
5131
|
-
get baseFontSize() {
|
|
5132
|
-
return this._baseFontSize;
|
|
5133
|
-
}
|
|
5134
|
-
constructor() {
|
|
5135
|
-
super();
|
|
5136
|
-
this.onResize();
|
|
5137
|
-
this.element.classList.add("amll-lyric-player", "dom-slim");
|
|
5138
|
-
if (this.disableSpring) this.element.classList.add(index_module_default.disableSpring);
|
|
5139
|
-
}
|
|
5140
|
-
rebuildStyle() {
|
|
5141
|
-
const width = this.innerSize[0];
|
|
5142
|
-
const height = this.innerSize[1];
|
|
5143
|
-
this.element.style.setProperty("--amll-lp-width", `${width.toFixed(4)}px`);
|
|
5144
|
-
this.element.style.setProperty("--amll-lp-height", `${height.toFixed(4)}px`);
|
|
5145
|
-
}
|
|
5146
|
-
setWordFadeWidth(value = .5) {
|
|
5147
|
-
super.setWordFadeWidth(value);
|
|
5148
|
-
for (const el of this.currentLyricLineObjects) el.markMaskImageDirty("DomLyricPlayer setWordFadeWidth");
|
|
5149
|
-
}
|
|
5150
|
-
/**
|
|
5151
|
-
* 设置当前播放歌词,要注意传入后这个数组内的信息不得修改,否则会发生错误
|
|
5152
|
-
* @param lines 歌词数组
|
|
5153
|
-
* @param initialTime 初始时间,默认为 0
|
|
5154
|
-
*/
|
|
5155
|
-
setLyricLines(lines, initialTime = 0) {
|
|
5156
|
-
super.setLyricLines(lines, initialTime);
|
|
5157
|
-
if (this.hasDuetLine) this.element.classList.add(index_module_default.hasDuetLine);
|
|
5158
|
-
else this.element.classList.remove(index_module_default.hasDuetLine);
|
|
5159
|
-
for (const line of this.currentLyricLineObjects) {
|
|
5160
|
-
line.removeMouseEventListener("click", this.onLineClickedHandler);
|
|
5161
|
-
line.removeMouseEventListener("contextmenu", this.onLineClickedHandler);
|
|
5162
|
-
line.dispose();
|
|
5163
|
-
}
|
|
5164
|
-
this.currentLyricLineObjects = this.processedLines.map((line, i) => {
|
|
5165
|
-
const lineEl = new LyricLineEl(this, line);
|
|
5166
|
-
lineEl.addMouseEventListener("click", this.onLineClickedHandler);
|
|
5167
|
-
lineEl.addMouseEventListener("contextmenu", this.onLineClickedHandler);
|
|
5168
|
-
this.element.appendChild(lineEl.getElement());
|
|
5169
|
-
this.lyricLinesIndexes.set(lineEl, i);
|
|
5170
|
-
lineEl.markMaskImageDirty("DomLyricPlayer setLyricLines");
|
|
5171
|
-
return lineEl;
|
|
5172
|
-
});
|
|
5173
|
-
this.setLinePosXSpringParams({});
|
|
5174
|
-
this.setLinePosYSpringParams({});
|
|
5175
|
-
this.setLineScaleSpringParams({});
|
|
5176
|
-
this.calcLayout(true).then(() => {
|
|
5177
|
-
this.initialLayoutFinished = true;
|
|
5178
|
-
});
|
|
5179
|
-
}
|
|
5180
|
-
pause() {
|
|
5181
|
-
super.pause();
|
|
5182
|
-
this.interludeDots.pause();
|
|
5183
|
-
for (const line of this.currentLyricLineObjects) line.pause();
|
|
5184
|
-
}
|
|
5185
|
-
resume() {
|
|
5186
|
-
super.resume();
|
|
5187
|
-
this.interludeDots.resume();
|
|
5188
|
-
for (const line of this.currentLyricLineObjects) line.resume();
|
|
5189
|
-
}
|
|
5190
|
-
update(delta = 0) {
|
|
5191
|
-
if (!this.initialLayoutFinished) return;
|
|
5192
|
-
super.update(delta);
|
|
5193
|
-
if (!this.isPageVisible) return;
|
|
5194
|
-
const deltaS = delta / 1e3;
|
|
5195
|
-
for (const line of this.currentLyricLineObjects) line.update(deltaS);
|
|
5196
|
-
}
|
|
5197
|
-
async calcLayout(sync) {
|
|
5198
|
-
await super.calcLayout(sync);
|
|
5199
|
-
const curLine = this.currentLyricLineObjects[this.targetAlignIndex];
|
|
5200
|
-
const curLineEl = curLine.getElement();
|
|
5201
|
-
const curLineVisibility = curLineEl.checkVisibility({ contentVisibilityAuto: true });
|
|
5202
|
-
const playerTop = this.element.getBoundingClientRect().top;
|
|
5203
|
-
if (!curLineVisibility) curLineEl.scrollIntoView({
|
|
5204
|
-
block: "center",
|
|
5205
|
-
behavior: "instant"
|
|
5206
|
-
});
|
|
5207
|
-
const curLineHeight = curLineEl.clientHeight;
|
|
5208
|
-
let scrollToPos = curLineEl.getBoundingClientRect().top - playerTop - this.size[1] * this.alignPosition;
|
|
5209
|
-
if (curLine) switch (this.alignAnchor) {
|
|
5210
|
-
case "bottom":
|
|
5211
|
-
scrollToPos += curLineHeight;
|
|
5212
|
-
break;
|
|
5213
|
-
case "center":
|
|
5214
|
-
scrollToPos += curLineHeight / 2;
|
|
5215
|
-
break;
|
|
5216
|
-
case "top": break;
|
|
5217
|
-
}
|
|
5218
|
-
this.element.scrollBy({
|
|
5219
|
-
top: scrollToPos,
|
|
5220
|
-
behavior: "smooth"
|
|
5221
|
-
});
|
|
5222
|
-
}
|
|
5223
|
-
dispose() {
|
|
5224
|
-
super.dispose();
|
|
5225
|
-
this.element.remove();
|
|
5226
|
-
for (const el of this.currentLyricLineObjects) el.dispose();
|
|
5227
|
-
this.bottomLine.dispose();
|
|
5228
|
-
this.interludeDots.dispose();
|
|
5229
|
-
}
|
|
5230
|
-
};
|
|
5231
|
-
//#endregion
|
|
5232
|
-
//#region src/lyric-player/index.ts
|
|
5233
|
-
/**
|
|
5234
|
-
* 歌词中不雅用语的掩码模式
|
|
5235
|
-
*/
|
|
5236
|
-
var MaskObsceneWordsMode = /* @__PURE__ */ function(MaskObsceneWordsMode) {
|
|
5237
|
-
/** 禁用任何不雅用语掩码 */
|
|
5238
|
-
MaskObsceneWordsMode["Disabled"] = "";
|
|
5239
|
-
/** 完全掩码所有不雅用语 */
|
|
5240
|
-
MaskObsceneWordsMode["FullMask"] = "full-mask";
|
|
5241
|
-
/** 保留首尾字符,屏蔽中间字符 */
|
|
5242
|
-
MaskObsceneWordsMode["PartialMask"] = "partial-mask";
|
|
5243
|
-
return MaskObsceneWordsMode;
|
|
5244
|
-
}(MaskObsceneWordsMode || {});
|
|
5245
|
-
/**
|
|
5246
|
-
* 歌词行的渲染模式
|
|
5247
|
-
* @internal
|
|
5248
|
-
*/
|
|
5249
|
-
var LyricLineRenderMode = /* @__PURE__ */ function(LyricLineRenderMode) {
|
|
5250
|
-
LyricLineRenderMode[LyricLineRenderMode["SOLID"] = 0] = "SOLID";
|
|
5251
|
-
LyricLineRenderMode[LyricLineRenderMode["GRADIENT"] = 1] = "GRADIENT";
|
|
5252
|
-
return LyricLineRenderMode;
|
|
5253
|
-
}(LyricLineRenderMode || {});
|
|
5254
|
-
//#endregion
|
|
5255
|
-
export { AbstractBaseRenderer, BackgroundRender, BaseRenderer, DomLyricPlayer, DomLyricPlayer as LyricPlayer, DomSlimLyricPlayer, LyricLineMouseEvent, LyricLineRenderMode, LyricPlayerBase, MaskObsceneWordsMode, MeshGradientRenderer, PixiRenderer };
|
|
4552
|
+
export { AbstractBaseRenderer, BackgroundRender, BaseRenderer, DomLyricPlayer, DomLyricPlayer as LyricPlayer, LayoutAlignAnchor, LyricLineMouseEvent, LyricLineRenderMode, LyricPlayerBase, MaskObsceneWordsMode, MeshGradientRenderer, PixiRenderer };
|
|
5256
4553
|
|
|
5257
4554
|
//# sourceMappingURL=amll-core.mjs.map
|