@applemusic-like-lyrics/core 0.4.2 → 0.5.1
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 +1048 -1567
- package/dist/amll-core.cjs.map +1 -1
- package/dist/amll-core.d.cts +260 -252
- package/dist/amll-core.d.mts +260 -252
- package/dist/amll-core.mjs +1048 -1567
- package/dist/amll-core.mjs.map +1 -1
- package/dist/style.css +112 -210
- package/package.json +87 -69
package/dist/amll-core.cjs
CHANGED
|
@@ -35,6 +35,9 @@ let bezier_easing = require("bezier-easing");
|
|
|
35
35
|
bezier_easing = __toESM(bezier_easing, 1);
|
|
36
36
|
//#region src/bg-render/base.ts
|
|
37
37
|
var AbstractBaseRenderer = class {};
|
|
38
|
+
function clamp1(x) {
|
|
39
|
+
return Math.max(1, x);
|
|
40
|
+
}
|
|
38
41
|
var BaseRenderer = class extends AbstractBaseRenderer {
|
|
39
42
|
observer;
|
|
40
43
|
flowSpeed = 1;
|
|
@@ -43,8 +46,8 @@ var BaseRenderer = class extends AbstractBaseRenderer {
|
|
|
43
46
|
super();
|
|
44
47
|
this.canvas = canvas;
|
|
45
48
|
this.observer = new ResizeObserver(() => {
|
|
46
|
-
const width =
|
|
47
|
-
const height =
|
|
49
|
+
const width = clamp1(canvas.clientWidth * window.devicePixelRatio * this.currerntRenderScale);
|
|
50
|
+
const height = clamp1(canvas.clientHeight * window.devicePixelRatio * this.currerntRenderScale);
|
|
48
51
|
this.onResize(width, height);
|
|
49
52
|
});
|
|
50
53
|
this.observer.observe(canvas);
|
|
@@ -232,6 +235,17 @@ function blurImage(imageData, radius, quality) {
|
|
|
232
235
|
}
|
|
233
236
|
}
|
|
234
237
|
//#endregion
|
|
238
|
+
//#region src/utils/clamp.ts
|
|
239
|
+
function clamp(x, min, max) {
|
|
240
|
+
return Math.min(Math.max(x, min), max);
|
|
241
|
+
}
|
|
242
|
+
function clamp01(x) {
|
|
243
|
+
return clamp(x, 0, 1);
|
|
244
|
+
}
|
|
245
|
+
function clampPositive(x) {
|
|
246
|
+
return Math.max(0, x);
|
|
247
|
+
}
|
|
248
|
+
//#endregion
|
|
235
249
|
//#region src/bg-render/mesh-renderer/cp-presets.ts
|
|
236
250
|
/** @internal */
|
|
237
251
|
const p = (cx, cy, x, y, ur = 0, vr = 0, up = 1, vp = 1) => Object.freeze({
|
|
@@ -404,11 +418,8 @@ const CONTROL_POINT_PRESETS = [
|
|
|
404
418
|
* 目的是取代原先大量的预设控制点代码
|
|
405
419
|
*/
|
|
406
420
|
const randomRange = (min, max) => Math.random() * (max - min) + min;
|
|
407
|
-
function clamp$1(x, min, max) {
|
|
408
|
-
return Math.min(Math.max(x, min), max);
|
|
409
|
-
}
|
|
410
421
|
function smoothstep(edge0, edge1, x) {
|
|
411
|
-
const t =
|
|
422
|
+
const t = clamp01((x - edge0) / (edge1 - edge0));
|
|
412
423
|
return t * t * (3 - 2 * t);
|
|
413
424
|
}
|
|
414
425
|
function smoothifyControlPoints(conf, w, h, iterations = 2, factor = .5, factorIterationModifier = .1) {
|
|
@@ -478,7 +489,7 @@ function smoothifyControlPoints(conf, w, h, iterations = 2, factor = .5, factorI
|
|
|
478
489
|
}
|
|
479
490
|
}
|
|
480
491
|
grid = newGrid;
|
|
481
|
-
f =
|
|
492
|
+
f = clamp01(f + factorIterationModifier);
|
|
482
493
|
}
|
|
483
494
|
for (let j = 0; j < h; j++) for (let i = 0; i < w; i++) conf[j * w + i] = grid[j][i];
|
|
484
495
|
}
|
|
@@ -1267,7 +1278,7 @@ var MeshGradientRenderer = class extends BaseRenderer {
|
|
|
1267
1278
|
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
1268
1279
|
this.quadProgram.use();
|
|
1269
1280
|
this.quadProgram.setUniform1i("u_texture", 0);
|
|
1270
|
-
this.quadProgram.setUniform1f("u_alpha", easeInOutSine(
|
|
1281
|
+
this.quadProgram.setUniform1f("u_alpha", easeInOutSine(clamp01(state.alpha)));
|
|
1271
1282
|
gl.activeTexture(gl.TEXTURE0);
|
|
1272
1283
|
gl.bindTexture(gl.TEXTURE_2D, this.fboTexture);
|
|
1273
1284
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadBuffer);
|
|
@@ -1502,7 +1513,7 @@ var PixiRenderer = class extends BaseRenderer {
|
|
|
1502
1513
|
lastContainer = /* @__PURE__ */ new Set();
|
|
1503
1514
|
onTick = (delta) => {
|
|
1504
1515
|
for (const lastContainer of this.lastContainer) {
|
|
1505
|
-
lastContainer.alpha =
|
|
1516
|
+
lastContainer.alpha = clampPositive(lastContainer.alpha - delta / 60);
|
|
1506
1517
|
if (lastContainer.alpha <= 0) {
|
|
1507
1518
|
this.app.stage.removeChild(lastContainer);
|
|
1508
1519
|
this.lastContainer.delete(lastContainer);
|
|
@@ -1720,6 +1731,10 @@ var BackgroundRender = class BackgroundRender {
|
|
|
1720
1731
|
//#region src/styles/lyric-player.module.css
|
|
1721
1732
|
var lyric_player_module_default = {
|
|
1722
1733
|
"active": "FmKaba_active",
|
|
1734
|
+
"bgWrapper": "FmKaba_bgWrapper",
|
|
1735
|
+
"bgWrapperActive": "FmKaba_bgWrapperActive",
|
|
1736
|
+
"bgWrapperHidden": "FmKaba_bgWrapperHidden",
|
|
1737
|
+
"bgWrapperTop": "FmKaba_bgWrapperTop",
|
|
1723
1738
|
"bottomLine": "FmKaba_bottomLine",
|
|
1724
1739
|
"dirty": "FmKaba_dirty",
|
|
1725
1740
|
"disableSpring": "FmKaba_disableSpring",
|
|
@@ -1732,8 +1747,10 @@ var lyric_player_module_default = {
|
|
|
1732
1747
|
"lyricBgLine": "FmKaba_lyricBgLine",
|
|
1733
1748
|
"lyricDuetLine": "FmKaba_lyricDuetLine",
|
|
1734
1749
|
"lyricLine": "FmKaba_lyricLine",
|
|
1750
|
+
"lyricLineWrapper": "FmKaba_lyricLineWrapper",
|
|
1735
1751
|
"lyricMainLine": "FmKaba_lyricMainLine",
|
|
1736
1752
|
"lyricSubLine": "FmKaba_lyricSubLine",
|
|
1753
|
+
"playing": "FmKaba_playing",
|
|
1737
1754
|
"romanWord": "FmKaba_romanWord",
|
|
1738
1755
|
"rubyWord": "FmKaba_rubyWord",
|
|
1739
1756
|
"tmpDisableTransition": "FmKaba_tmpDisableTransition",
|
|
@@ -1741,14 +1758,6 @@ var lyric_player_module_default = {
|
|
|
1741
1758
|
"wordWithRuby": "FmKaba_wordWithRuby"
|
|
1742
1759
|
};
|
|
1743
1760
|
//#endregion
|
|
1744
|
-
//#region src/utils/eq-set.ts
|
|
1745
|
-
const eqSet = (xs, ys) => xs.size === ys.size && [...xs].every((x) => ys.has(x));
|
|
1746
|
-
//#endregion
|
|
1747
|
-
//#region src/utils/is-cjk.ts
|
|
1748
|
-
const isCJK = (char) => {
|
|
1749
|
-
return /^[\p{Unified_Ideograph}\u0800-\u9FFC]+$/u.test(char);
|
|
1750
|
-
};
|
|
1751
|
-
//#endregion
|
|
1752
1761
|
//#region src/utils/optimize-lyric.ts
|
|
1753
1762
|
const DEFAULT_OPTIMIZE_OPTIONS = {
|
|
1754
1763
|
normalizeSpaces: true,
|
|
@@ -1842,27 +1851,31 @@ function cleanUnintentionalOverlaps(lines) {
|
|
|
1842
1851
|
* 尝试让歌词提前最多 600ms 开始,如果有重叠则尝试最多提前 400ms 或上一行时长的 30%
|
|
1843
1852
|
*/
|
|
1844
1853
|
function tryAdvanceStartTime(lines) {
|
|
1845
|
-
|
|
1854
|
+
const defaultAdvanceAmount = 600;
|
|
1855
|
+
const fallbackAdvanceAmount = 400;
|
|
1856
|
+
const fallbackAdvanceRatio = .3;
|
|
1857
|
+
let prevLineStartTime = 0;
|
|
1858
|
+
let prevLineEndTime = 0;
|
|
1859
|
+
let prevMainGroupStartTime = 0;
|
|
1860
|
+
let prevMainGroupEndTime = 0;
|
|
1861
|
+
let hasPrevLine = false;
|
|
1862
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1846
1863
|
const line = lines[i];
|
|
1847
1864
|
if (line.isBG) continue;
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
let prevIdx = i - 1;
|
|
1851
|
-
if (lines[prevIdx].isBG) prevIdx--;
|
|
1852
|
-
if (prevIdx >= 0) prevLine = lines[prevIdx];
|
|
1853
|
-
}
|
|
1865
|
+
const originalStartTime = line.startTime;
|
|
1866
|
+
const originalEndTime = line.endTime;
|
|
1854
1867
|
let targetAdvanceAmount = 0;
|
|
1855
1868
|
let safeBoundary = 0;
|
|
1856
|
-
if (
|
|
1857
|
-
targetAdvanceAmount =
|
|
1858
|
-
safeBoundary =
|
|
1869
|
+
if (hasPrevLine) if (originalStartTime >= prevLineEndTime) {
|
|
1870
|
+
targetAdvanceAmount = defaultAdvanceAmount;
|
|
1871
|
+
safeBoundary = prevMainGroupEndTime;
|
|
1859
1872
|
} else {
|
|
1860
|
-
targetAdvanceAmount =
|
|
1861
|
-
const prevDuration =
|
|
1862
|
-
safeBoundary =
|
|
1873
|
+
targetAdvanceAmount = fallbackAdvanceAmount;
|
|
1874
|
+
const prevDuration = prevLineEndTime - prevLineStartTime;
|
|
1875
|
+
safeBoundary = prevLineStartTime + prevDuration * fallbackAdvanceRatio;
|
|
1863
1876
|
}
|
|
1864
1877
|
else {
|
|
1865
|
-
targetAdvanceAmount =
|
|
1878
|
+
targetAdvanceAmount = defaultAdvanceAmount;
|
|
1866
1879
|
safeBoundary = 0;
|
|
1867
1880
|
}
|
|
1868
1881
|
const targetTime = line.startTime - targetAdvanceAmount;
|
|
@@ -1870,6 +1883,20 @@ function tryAdvanceStartTime(lines) {
|
|
|
1870
1883
|
if (newStartTime < line.startTime) line.startTime = newStartTime;
|
|
1871
1884
|
const nextLine = lines[i + 1];
|
|
1872
1885
|
if (nextLine?.isBG) nextLine.startTime = line.startTime;
|
|
1886
|
+
if (hasPrevLine) if (originalStartTime < prevMainGroupEndTime && originalEndTime > prevMainGroupStartTime) {
|
|
1887
|
+
prevMainGroupStartTime = Math.min(prevMainGroupStartTime, originalStartTime);
|
|
1888
|
+
prevMainGroupEndTime = Math.max(prevMainGroupEndTime, originalEndTime);
|
|
1889
|
+
} else {
|
|
1890
|
+
prevMainGroupStartTime = originalStartTime;
|
|
1891
|
+
prevMainGroupEndTime = originalEndTime;
|
|
1892
|
+
}
|
|
1893
|
+
else {
|
|
1894
|
+
prevMainGroupStartTime = originalStartTime;
|
|
1895
|
+
prevMainGroupEndTime = originalEndTime;
|
|
1896
|
+
}
|
|
1897
|
+
prevLineStartTime = originalStartTime;
|
|
1898
|
+
prevLineEndTime = originalEndTime;
|
|
1899
|
+
hasPrevLine = true;
|
|
1873
1900
|
}
|
|
1874
1901
|
}
|
|
1875
1902
|
/**
|
|
@@ -1892,6 +1919,145 @@ function optimizeLyricLines(lines, options) {
|
|
|
1892
1919
|
if (config.tryAdvanceStartTime) tryAdvanceStartTime(lines);
|
|
1893
1920
|
}
|
|
1894
1921
|
//#endregion
|
|
1922
|
+
//#region src/lyric-player/dom/interlude-dots.ts
|
|
1923
|
+
function easeInOutBack(x) {
|
|
1924
|
+
const c2 = 1.70158 * 1.525;
|
|
1925
|
+
return x < .5 ? (2 * x) ** 2 * ((c2 + 1) * 2 * x - c2) / 2 : ((2 * x - 2) ** 2 * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2;
|
|
1926
|
+
}
|
|
1927
|
+
function easeOutExpo(x) {
|
|
1928
|
+
return x === 1 ? 1 : 1 - 2 ** (-10 * x);
|
|
1929
|
+
}
|
|
1930
|
+
var InterludeDots = class {
|
|
1931
|
+
element = document.createElement("div");
|
|
1932
|
+
dot0 = document.createElement("span");
|
|
1933
|
+
dot1 = document.createElement("span");
|
|
1934
|
+
dot2 = document.createElement("span");
|
|
1935
|
+
left = 0;
|
|
1936
|
+
top = 0;
|
|
1937
|
+
playing = true;
|
|
1938
|
+
lastStyle = "";
|
|
1939
|
+
currentInterlude;
|
|
1940
|
+
currentTime = 0;
|
|
1941
|
+
targetBreatheDuration = 1500;
|
|
1942
|
+
constructor() {
|
|
1943
|
+
this.element.className = lyric_player_module_default.interludeDots;
|
|
1944
|
+
this.element.appendChild(this.dot0);
|
|
1945
|
+
this.element.appendChild(this.dot1);
|
|
1946
|
+
this.element.appendChild(this.dot2);
|
|
1947
|
+
}
|
|
1948
|
+
getElement() {
|
|
1949
|
+
return this.element;
|
|
1950
|
+
}
|
|
1951
|
+
setTransform(left = this.left, top = this.top) {
|
|
1952
|
+
this.left = left;
|
|
1953
|
+
this.top = top;
|
|
1954
|
+
this.update();
|
|
1955
|
+
}
|
|
1956
|
+
setInterlude(interlude) {
|
|
1957
|
+
this.currentInterlude = interlude;
|
|
1958
|
+
this.currentTime = interlude?.[0] ?? 0;
|
|
1959
|
+
if (interlude) this.element.classList.add(lyric_player_module_default.enabled);
|
|
1960
|
+
else this.element.classList.remove(lyric_player_module_default.enabled);
|
|
1961
|
+
}
|
|
1962
|
+
pause() {
|
|
1963
|
+
this.playing = false;
|
|
1964
|
+
this.element.classList.remove(lyric_player_module_default.playing);
|
|
1965
|
+
}
|
|
1966
|
+
resume() {
|
|
1967
|
+
this.playing = true;
|
|
1968
|
+
this.element.classList.add(lyric_player_module_default.playing);
|
|
1969
|
+
}
|
|
1970
|
+
update(delta = 0) {
|
|
1971
|
+
if (!this.playing) return;
|
|
1972
|
+
this.currentTime += delta;
|
|
1973
|
+
let curStyle = "";
|
|
1974
|
+
curStyle += `transform:translate(${this.left.toFixed(2)}px, ${this.top.toFixed(2)}px)`;
|
|
1975
|
+
if (this.currentInterlude) {
|
|
1976
|
+
const interludeDuration = this.currentInterlude[1] - this.currentInterlude[0];
|
|
1977
|
+
const currentDuration = this.currentTime - this.currentInterlude[0];
|
|
1978
|
+
if (currentDuration <= interludeDuration) {
|
|
1979
|
+
const breatheDuration = interludeDuration / Math.ceil(interludeDuration / this.targetBreatheDuration);
|
|
1980
|
+
let scale = 1;
|
|
1981
|
+
let globalOpacity = 1;
|
|
1982
|
+
scale *= Math.sin(1.5 * Math.PI - currentDuration / breatheDuration * 2) / 20 + 1;
|
|
1983
|
+
if (currentDuration < 2e3) scale *= easeOutExpo(currentDuration / 2e3);
|
|
1984
|
+
if (currentDuration < 500) globalOpacity = 0;
|
|
1985
|
+
else if (currentDuration < 1e3) globalOpacity *= (currentDuration - 500) / 500;
|
|
1986
|
+
if (interludeDuration - currentDuration < 750) scale *= 1 - easeInOutBack((750 - (interludeDuration - currentDuration)) / 750 / 2);
|
|
1987
|
+
if (interludeDuration - currentDuration < 375) globalOpacity *= clamp01((interludeDuration - currentDuration) / 375);
|
|
1988
|
+
const dotsDuration = clampPositive(interludeDuration - 750);
|
|
1989
|
+
scale = clampPositive(scale) * .7;
|
|
1990
|
+
curStyle += ` scale(${scale})`;
|
|
1991
|
+
const dot0Opacity = clamp(.25, currentDuration * 3 / dotsDuration * .75, 1);
|
|
1992
|
+
const dot1Opacity = clamp(.25, (currentDuration - dotsDuration / 3) * 3 / dotsDuration * .75, 1);
|
|
1993
|
+
const dot2Opacity = clamp(.25, (currentDuration - dotsDuration / 3 * 2) * 3 / dotsDuration * .75, 1);
|
|
1994
|
+
this.dot0.style.opacity = `${clamp01(globalOpacity * dot0Opacity)}`;
|
|
1995
|
+
this.dot1.style.opacity = `${clamp01(globalOpacity * dot1Opacity)}`;
|
|
1996
|
+
this.dot2.style.opacity = `${clamp01(globalOpacity * dot2Opacity)}`;
|
|
1997
|
+
} else {
|
|
1998
|
+
curStyle += " scale(0)";
|
|
1999
|
+
this.dot0.style.opacity = "0";
|
|
2000
|
+
this.dot1.style.opacity = "0";
|
|
2001
|
+
this.dot2.style.opacity = "0";
|
|
2002
|
+
}
|
|
2003
|
+
curStyle += ";";
|
|
2004
|
+
if (this.lastStyle !== curStyle) {
|
|
2005
|
+
this.element.setAttribute("style", curStyle);
|
|
2006
|
+
this.lastStyle = curStyle;
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
dispose() {
|
|
2011
|
+
this.element.remove();
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2014
|
+
//#endregion
|
|
2015
|
+
//#region src/utils/schedule.ts
|
|
2016
|
+
const measureTasks = [];
|
|
2017
|
+
const mutateTasks = [];
|
|
2018
|
+
let scheduled = false;
|
|
2019
|
+
function onFlush() {
|
|
2020
|
+
let tmp = mutateTasks.shift();
|
|
2021
|
+
while (tmp) {
|
|
2022
|
+
try {
|
|
2023
|
+
tmp.resolve(tmp.task());
|
|
2024
|
+
} catch (error) {
|
|
2025
|
+
tmp.reject(error);
|
|
2026
|
+
}
|
|
2027
|
+
tmp = mutateTasks.shift();
|
|
2028
|
+
}
|
|
2029
|
+
tmp = measureTasks.shift();
|
|
2030
|
+
while (tmp) {
|
|
2031
|
+
try {
|
|
2032
|
+
tmp.resolve(tmp.task());
|
|
2033
|
+
} catch (error) {
|
|
2034
|
+
tmp.reject(error);
|
|
2035
|
+
}
|
|
2036
|
+
tmp = measureTasks.shift();
|
|
2037
|
+
}
|
|
2038
|
+
scheduled = false;
|
|
2039
|
+
}
|
|
2040
|
+
function scheduleFlush() {
|
|
2041
|
+
if (!scheduled) {
|
|
2042
|
+
scheduled = true;
|
|
2043
|
+
requestAnimationFrame(onFlush);
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
function measure(callback) {
|
|
2047
|
+
const task = {
|
|
2048
|
+
task: callback,
|
|
2049
|
+
resolve: () => {},
|
|
2050
|
+
reject: () => {}
|
|
2051
|
+
};
|
|
2052
|
+
const promise = new Promise((resolve, reject) => {
|
|
2053
|
+
task.resolve = resolve;
|
|
2054
|
+
task.reject = reject;
|
|
2055
|
+
});
|
|
2056
|
+
measureTasks.push(task);
|
|
2057
|
+
scheduleFlush();
|
|
2058
|
+
return promise;
|
|
2059
|
+
}
|
|
2060
|
+
//#endregion
|
|
1895
2061
|
//#region src/utils/derivative.ts
|
|
1896
2062
|
function derivative(f) {
|
|
1897
2063
|
const h = .001;
|
|
@@ -2006,67 +2172,7 @@ function solveSpring(from, velocity, to, delay = 0, params) {
|
|
|
2006
2172
|
};
|
|
2007
2173
|
}
|
|
2008
2174
|
//#endregion
|
|
2009
|
-
//#region src/
|
|
2010
|
-
const measureTasks = [];
|
|
2011
|
-
const mutateTasks = [];
|
|
2012
|
-
let scheduled = false;
|
|
2013
|
-
function onFlush() {
|
|
2014
|
-
let tmp = mutateTasks.shift();
|
|
2015
|
-
while (tmp) {
|
|
2016
|
-
try {
|
|
2017
|
-
tmp.resolve(tmp.task());
|
|
2018
|
-
} catch (error) {
|
|
2019
|
-
tmp.reject(error);
|
|
2020
|
-
}
|
|
2021
|
-
tmp = mutateTasks.shift();
|
|
2022
|
-
}
|
|
2023
|
-
tmp = measureTasks.shift();
|
|
2024
|
-
while (tmp) {
|
|
2025
|
-
try {
|
|
2026
|
-
tmp.resolve(tmp.task());
|
|
2027
|
-
} catch (error) {
|
|
2028
|
-
tmp.reject(error);
|
|
2029
|
-
}
|
|
2030
|
-
tmp = measureTasks.shift();
|
|
2031
|
-
}
|
|
2032
|
-
scheduled = false;
|
|
2033
|
-
}
|
|
2034
|
-
function scheduleFlush() {
|
|
2035
|
-
if (!scheduled) {
|
|
2036
|
-
scheduled = true;
|
|
2037
|
-
requestAnimationFrame(onFlush);
|
|
2038
|
-
}
|
|
2039
|
-
}
|
|
2040
|
-
function measure(callback) {
|
|
2041
|
-
const task = {
|
|
2042
|
-
task: callback,
|
|
2043
|
-
resolve: () => {},
|
|
2044
|
-
reject: () => {}
|
|
2045
|
-
};
|
|
2046
|
-
const promise = new Promise((resolve, reject) => {
|
|
2047
|
-
task.resolve = resolve;
|
|
2048
|
-
task.reject = reject;
|
|
2049
|
-
});
|
|
2050
|
-
measureTasks.push(task);
|
|
2051
|
-
scheduleFlush();
|
|
2052
|
-
return promise;
|
|
2053
|
-
}
|
|
2054
|
-
function mutate(callback) {
|
|
2055
|
-
const task = {
|
|
2056
|
-
task: callback,
|
|
2057
|
-
resolve: () => {},
|
|
2058
|
-
reject: () => {}
|
|
2059
|
-
};
|
|
2060
|
-
const promise = new Promise((resolve, reject) => {
|
|
2061
|
-
task.resolve = resolve;
|
|
2062
|
-
task.reject = reject;
|
|
2063
|
-
});
|
|
2064
|
-
mutateTasks.push(task);
|
|
2065
|
-
scheduleFlush();
|
|
2066
|
-
return promise;
|
|
2067
|
-
}
|
|
2068
|
-
//#endregion
|
|
2069
|
-
//#region src/lyric-player/bottom-line.ts
|
|
2175
|
+
//#region src/lyric-player/base/bottom-line.ts
|
|
2070
2176
|
var BottomLineEl = class {
|
|
2071
2177
|
element = document.createElement("div");
|
|
2072
2178
|
left = 0;
|
|
@@ -2155,150 +2261,437 @@ var BottomLineEl = class {
|
|
|
2155
2261
|
}
|
|
2156
2262
|
};
|
|
2157
2263
|
//#endregion
|
|
2158
|
-
//#region src/lyric-player/
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2264
|
+
//#region src/lyric-player/base/consts.ts
|
|
2265
|
+
/** 歌词中不雅用语的掩码模式 */
|
|
2266
|
+
const MaskObsceneWordsMode = {
|
|
2267
|
+
/** 禁用任何不雅用语掩码 */
|
|
2268
|
+
Disabled: "",
|
|
2269
|
+
/** 完全掩码所有不雅用语 */
|
|
2270
|
+
FullMask: "full-mask",
|
|
2271
|
+
/** 保留首尾字符,屏蔽中间字符 */
|
|
2272
|
+
PartialMask: "partial-mask"
|
|
2273
|
+
};
|
|
2274
|
+
/**
|
|
2275
|
+
* 歌词行的渲染模式
|
|
2276
|
+
* @internal
|
|
2277
|
+
*/
|
|
2278
|
+
const LyricLineRenderMode = {
|
|
2279
|
+
SOLID: 0,
|
|
2280
|
+
GRADIENT: 1
|
|
2281
|
+
};
|
|
2282
|
+
/** 布局对齐锚点 */
|
|
2283
|
+
const LayoutAlignAnchor = {
|
|
2284
|
+
Top: "top",
|
|
2285
|
+
Center: "center",
|
|
2286
|
+
Bottom: "bottom"
|
|
2287
|
+
};
|
|
2288
|
+
//#endregion
|
|
2289
|
+
//#region src/lyric-player/base/layout.ts
|
|
2290
|
+
/**
|
|
2291
|
+
* 根据当前时间与当前目标行,计算当前是否处于某个可展示的间奏区间。
|
|
2292
|
+
*
|
|
2293
|
+
* 仅识别时间轴上的间奏空档,不涉及具体 DOM 元素的创建与摆放。
|
|
2294
|
+
* 若当前不应展示间奏动画,则返回 `undefined`。
|
|
2295
|
+
*/
|
|
2296
|
+
function computeCurrentInterlude(input) {
|
|
2297
|
+
const currentTime = input.currentTime + 20;
|
|
2298
|
+
const currentIndex = input.scrollToIndex;
|
|
2299
|
+
const groups = input.currentGroups;
|
|
2300
|
+
const checkGap = (k) => {
|
|
2301
|
+
if (k < -1 || k >= groups.length - 1) return void 0;
|
|
2302
|
+
const prevGroup = k === -1 ? null : groups[k];
|
|
2303
|
+
const nextGroup = groups[k + 1];
|
|
2304
|
+
const gapStart = prevGroup ? prevGroup.endTime : 0;
|
|
2305
|
+
const gapEnd = Math.max(gapStart, nextGroup.startTime - 250);
|
|
2306
|
+
if (gapEnd - gapStart < 4e3) return void 0;
|
|
2307
|
+
if (gapEnd > currentTime && gapStart < currentTime) return {
|
|
2308
|
+
startTime: Math.max(gapStart, currentTime),
|
|
2309
|
+
endTime: gapEnd,
|
|
2310
|
+
anchorLineIndex: k,
|
|
2311
|
+
isNextDuet: nextGroup.mainLine.getLine().isDuet
|
|
2312
|
+
};
|
|
2313
|
+
};
|
|
2314
|
+
return checkGap(currentIndex - 1) || checkGap(currentIndex) || checkGap(currentIndex + 1);
|
|
2162
2315
|
}
|
|
2163
|
-
|
|
2164
|
-
|
|
2316
|
+
/**
|
|
2317
|
+
* 根据当前播放上下文计算歌词纵向滚动动画的弹簧参数。
|
|
2318
|
+
*
|
|
2319
|
+
* 其策略为:
|
|
2320
|
+
* - seeking 或间奏时使用更稳定的固定参数
|
|
2321
|
+
* - 普通播放时根据相邻歌词的时间间隔动态调整 stiffness / damping
|
|
2322
|
+
*/
|
|
2323
|
+
function computeLinePosYSpringParams(input) {
|
|
2324
|
+
const { enabled, currentGroups, scrollToIndex, isSeeking, isInterludeActive } = input;
|
|
2325
|
+
if (!enabled || currentGroups.length === 0) return { shouldUpdate: false };
|
|
2326
|
+
if (isSeeking || isInterludeActive) return {
|
|
2327
|
+
shouldUpdate: true,
|
|
2328
|
+
params: {
|
|
2329
|
+
stiffness: 90,
|
|
2330
|
+
damping: 15
|
|
2331
|
+
}
|
|
2332
|
+
};
|
|
2333
|
+
const currentGroup = currentGroups[scrollToIndex];
|
|
2334
|
+
const prevGroup = currentGroups[scrollToIndex - 1];
|
|
2335
|
+
if (!currentGroup || !prevGroup) return { shouldUpdate: false };
|
|
2336
|
+
const interval = currentGroup.startTime - prevGroup.startTime;
|
|
2337
|
+
const MIN_INTERVAL = 100;
|
|
2338
|
+
const MAX_INTERVAL = 800;
|
|
2339
|
+
const clampedInterval = clamp(interval, MIN_INTERVAL, MAX_INTERVAL);
|
|
2340
|
+
const MAX_STIFFNESS = 220;
|
|
2341
|
+
const MIN_STIFFNESS = 170;
|
|
2342
|
+
let ratio = 1 - (clampedInterval - MIN_INTERVAL) / (MAX_INTERVAL - MIN_INTERVAL);
|
|
2343
|
+
ratio = ratio ** .2;
|
|
2344
|
+
const targetStiffness = MIN_STIFFNESS + ratio * (MAX_STIFFNESS - MIN_STIFFNESS);
|
|
2345
|
+
return {
|
|
2346
|
+
shouldUpdate: true,
|
|
2347
|
+
params: {
|
|
2348
|
+
stiffness: targetStiffness,
|
|
2349
|
+
damping: Math.sqrt(targetStiffness) * 2.2
|
|
2350
|
+
}
|
|
2351
|
+
};
|
|
2165
2352
|
}
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2353
|
+
/**
|
|
2354
|
+
* 计算一组歌词在当前布局中的视觉呈现参数。
|
|
2355
|
+
*
|
|
2356
|
+
* 根据播放状态、缓冲状态、布局模式与间奏信息,
|
|
2357
|
+
* 生成一组歌词最终应使用的活跃状态、不透明度与模糊值。
|
|
2358
|
+
*/
|
|
2359
|
+
function computeGroupPresentation(input) {
|
|
2360
|
+
const { groupIndex, scrollToIndex, latestIndex, hasBuffered, hidePassedLines, isPlaying, isNonDynamic, enableBlur, isUserScrolling, isCompact, interlude } = input;
|
|
2361
|
+
const isActive = hasBuffered || groupIndex >= scrollToIndex && groupIndex < latestIndex;
|
|
2362
|
+
const blurLevel = computeLineBlur({
|
|
2363
|
+
enableBlur,
|
|
2364
|
+
isUserScrolling,
|
|
2365
|
+
isActive,
|
|
2366
|
+
itemIndex: groupIndex,
|
|
2367
|
+
scrollToIndex,
|
|
2368
|
+
latestIndex,
|
|
2369
|
+
isCompact
|
|
2370
|
+
});
|
|
2371
|
+
let targetOpacity;
|
|
2372
|
+
if (hidePassedLines) if (groupIndex < (interlude ? interlude.anchorLineIndex + 1 : scrollToIndex) && isPlaying) targetOpacity = 1e-4;
|
|
2373
|
+
else if (hasBuffered) targetOpacity = .85;
|
|
2374
|
+
else targetOpacity = isNonDynamic ? .2 : 1;
|
|
2375
|
+
else if (hasBuffered) targetOpacity = .85;
|
|
2376
|
+
else targetOpacity = isNonDynamic ? .2 : 1;
|
|
2377
|
+
return {
|
|
2378
|
+
isActive,
|
|
2379
|
+
targetOpacity,
|
|
2380
|
+
blurLevel
|
|
2381
|
+
};
|
|
2382
|
+
}
|
|
2383
|
+
/**
|
|
2384
|
+
* 计算一行歌词在当前布局中的模糊等级。
|
|
2385
|
+
*
|
|
2386
|
+
* 越远离当前对齐区域的歌词会得到更高的模糊值;
|
|
2387
|
+
* 活跃行、滚动交互中或关闭模糊效果时返回 `0`。
|
|
2388
|
+
*/
|
|
2389
|
+
function computeLineBlur(input) {
|
|
2390
|
+
const { enableBlur, isUserScrolling, isActive, itemIndex, scrollToIndex, latestIndex, isCompact } = input;
|
|
2391
|
+
if (!enableBlur || isUserScrolling || isActive) return 0;
|
|
2392
|
+
let blurLevel = 1;
|
|
2393
|
+
if (itemIndex < scrollToIndex) blurLevel += Math.abs(scrollToIndex - itemIndex) + 1;
|
|
2394
|
+
else blurLevel += Math.abs(itemIndex - Math.max(scrollToIndex, latestIndex));
|
|
2395
|
+
return isCompact ? blurLevel * .8 : blurLevel;
|
|
2396
|
+
}
|
|
2397
|
+
//#endregion
|
|
2398
|
+
//#region src/lyric-player/base/scroll.ts
|
|
2399
|
+
/**
|
|
2400
|
+
* 将滚动偏移量限制在当前允许的滚动边界内。
|
|
2401
|
+
*
|
|
2402
|
+
* 当手势滚动、滚轮滚动或惯性滚动更新了 {@link PlayerScrollState.scrollOffset}
|
|
2403
|
+
* 后,应调用本函数以避免视图越界。
|
|
2404
|
+
*/
|
|
2405
|
+
function clampPlayerScrollOffset(scrollState) {
|
|
2406
|
+
scrollState.scrollOffset = clamp(scrollState.scrollOffset, scrollState.scrollBoundary.minOffset, scrollState.scrollBoundary.maxOffset);
|
|
2407
|
+
}
|
|
2408
|
+
/**
|
|
2409
|
+
* 重置滚动状态到未发生用户滚动时的初始状态。
|
|
2410
|
+
*
|
|
2411
|
+
* 本函数会清除当前偏移,并结束“已滚动”与“正在滚动”的标记;
|
|
2412
|
+
* **不会清理**外部持有的计时器或事件监听器。
|
|
2413
|
+
*/
|
|
2414
|
+
function resetPlayerScrollState(scrollState) {
|
|
2415
|
+
scrollState.isScrolled = false;
|
|
2416
|
+
scrollState.scrollOffset = 0;
|
|
2417
|
+
scrollState.isUserScrolling = false;
|
|
2418
|
+
}
|
|
2419
|
+
/**
|
|
2420
|
+
* 向指定元素挂载歌词滚动相关的交互处理器。
|
|
2421
|
+
*
|
|
2422
|
+
* 该函数会处理:
|
|
2423
|
+
* - 触摸拖拽滚动
|
|
2424
|
+
* - 触摸结束后的惯性滚动
|
|
2425
|
+
* - 滚轮滚动
|
|
2426
|
+
* - 轻触时的点击透传
|
|
2427
|
+
*
|
|
2428
|
+
* 只更新 {@link PlayerScrollState} 并通过回调通知宿主执行布局或其它副作用,
|
|
2429
|
+
* 不直接依赖具体的播放器类实现。
|
|
2430
|
+
*/
|
|
2431
|
+
function attachPlayerScrollHandlers(element, scrollState, callbacks) {
|
|
2432
|
+
let startScrollY = 0;
|
|
2433
|
+
let startTouchPosY = 0;
|
|
2434
|
+
let startTouchStartX = 0;
|
|
2435
|
+
let startTouchStartY = 0;
|
|
2436
|
+
let lastMoveY = 0;
|
|
2437
|
+
let startScrollTime = 0;
|
|
2438
|
+
let scrollSpeed = 0;
|
|
2439
|
+
let curScrollId = 0;
|
|
2440
|
+
element.addEventListener("touchstart", (evt) => {
|
|
2441
|
+
if (callbacks.onBeginScroll()) {
|
|
2442
|
+
scrollState.isUserScrolling = true;
|
|
2443
|
+
evt.preventDefault();
|
|
2444
|
+
startScrollY = scrollState.scrollOffset;
|
|
2445
|
+
startTouchPosY = evt.touches[0].screenY;
|
|
2446
|
+
lastMoveY = startTouchPosY;
|
|
2447
|
+
startTouchStartX = evt.touches[0].screenX;
|
|
2448
|
+
startTouchStartY = evt.touches[0].screenY;
|
|
2449
|
+
startScrollTime = Date.now();
|
|
2450
|
+
scrollSpeed = 0;
|
|
2451
|
+
callbacks.onLayout(true, true);
|
|
2452
|
+
}
|
|
2453
|
+
});
|
|
2454
|
+
element.addEventListener("touchmove", (evt) => {
|
|
2455
|
+
if (callbacks.onBeginScroll()) {
|
|
2456
|
+
evt.preventDefault();
|
|
2457
|
+
const currentY = evt.touches[0].screenY;
|
|
2458
|
+
const deltaY = currentY - startTouchPosY;
|
|
2459
|
+
scrollState.scrollOffset = startScrollY - deltaY;
|
|
2460
|
+
clampPlayerScrollOffset(scrollState);
|
|
2461
|
+
const now = Date.now();
|
|
2462
|
+
const dt = now - startScrollTime;
|
|
2463
|
+
if (dt > 0) scrollSpeed = (currentY - lastMoveY) / dt;
|
|
2464
|
+
lastMoveY = currentY;
|
|
2465
|
+
startScrollTime = now;
|
|
2466
|
+
callbacks.onLayout(true, true);
|
|
2467
|
+
}
|
|
2468
|
+
});
|
|
2469
|
+
element.addEventListener("touchend", (evt) => {
|
|
2470
|
+
if (callbacks.onBeginScroll()) {
|
|
2471
|
+
evt.preventDefault();
|
|
2472
|
+
const touch = evt.changedTouches[0];
|
|
2473
|
+
const moveX = Math.abs(touch.screenX - startTouchStartX);
|
|
2474
|
+
const moveY = Math.abs(touch.screenY - startTouchStartY);
|
|
2475
|
+
if (moveX < 10 && moveY < 10) {
|
|
2476
|
+
const target = document.elementFromPoint(touch.clientX, touch.clientY);
|
|
2477
|
+
if (target instanceof HTMLElement && callbacks.containsTarget(target)) callbacks.clickTarget(target);
|
|
2478
|
+
scrollState.isUserScrolling = false;
|
|
2479
|
+
callbacks.onEndScroll();
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
startTouchPosY = 0;
|
|
2483
|
+
const scrollId = ++curScrollId;
|
|
2484
|
+
if (Math.abs(scrollSpeed) < .1) scrollSpeed = 0;
|
|
2485
|
+
let lastFrameTime = performance.now();
|
|
2486
|
+
const onScrollFrame = (time) => {
|
|
2487
|
+
if (scrollId !== curScrollId) return;
|
|
2488
|
+
const dt = time - lastFrameTime;
|
|
2489
|
+
lastFrameTime = time;
|
|
2490
|
+
if (dt <= 0 || dt > 100) {
|
|
2491
|
+
requestAnimationFrame(onScrollFrame);
|
|
2492
|
+
return;
|
|
2493
|
+
}
|
|
2494
|
+
if (Math.abs(scrollSpeed) > .05) {
|
|
2495
|
+
scrollState.scrollOffset -= scrollSpeed * dt;
|
|
2496
|
+
clampPlayerScrollOffset(scrollState);
|
|
2497
|
+
const frictionFactor = .95 ** (dt / 16);
|
|
2498
|
+
scrollSpeed *= frictionFactor;
|
|
2499
|
+
callbacks.onLayout(true, true);
|
|
2500
|
+
requestAnimationFrame(onScrollFrame);
|
|
2501
|
+
} else {
|
|
2502
|
+
scrollState.isUserScrolling = false;
|
|
2503
|
+
callbacks.onEndScroll();
|
|
2504
|
+
}
|
|
2505
|
+
};
|
|
2506
|
+
requestAnimationFrame(onScrollFrame);
|
|
2507
|
+
} else scrollState.isUserScrolling = false;
|
|
2508
|
+
});
|
|
2509
|
+
element.addEventListener("wheel", (evt) => {
|
|
2510
|
+
if (callbacks.onBeginScroll()) {
|
|
2511
|
+
evt.preventDefault();
|
|
2512
|
+
if (evt.deltaMode === evt.DOM_DELTA_PIXEL) {
|
|
2513
|
+
scrollState.scrollOffset += evt.deltaY;
|
|
2514
|
+
clampPlayerScrollOffset(scrollState);
|
|
2515
|
+
callbacks.onLayout(true, false);
|
|
2234
2516
|
} else {
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
this.dot2.style.opacity = "0";
|
|
2517
|
+
scrollState.scrollOffset += evt.deltaY * 50;
|
|
2518
|
+
clampPlayerScrollOffset(scrollState);
|
|
2519
|
+
callbacks.onLayout(false, false);
|
|
2239
2520
|
}
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2521
|
+
}
|
|
2522
|
+
}, { passive: false });
|
|
2523
|
+
}
|
|
2524
|
+
//#endregion
|
|
2525
|
+
//#region src/utils/eq-set.ts
|
|
2526
|
+
const eqSet = (xs, ys) => xs.size === ys.size && [...xs].every((x) => ys.has(x));
|
|
2527
|
+
//#endregion
|
|
2528
|
+
//#region src/lyric-player/base/timeline.ts
|
|
2529
|
+
/**
|
|
2530
|
+
* 计算指定时间点的热行/缓冲行状态转移的纯函数。其行为包括:
|
|
2531
|
+
*
|
|
2532
|
+
* - 根据当前时间和已有的热行状态,计算出新的热行状态,并返回应新增的热行 ID 和应移除的热行 ID
|
|
2533
|
+
* - 根据新的热行状态和已有的缓冲行状态,计算出应移除的缓冲行 ID
|
|
2534
|
+
*/
|
|
2535
|
+
function computePlayerTimeState(input) {
|
|
2536
|
+
const { time, currentGroups, timelineState: { hotGroups, bufferedGroups } } = input;
|
|
2537
|
+
const nextHotGroups = new Set(hotGroups);
|
|
2538
|
+
const addedIds = /* @__PURE__ */ new Set();
|
|
2539
|
+
const removedHotIds = /* @__PURE__ */ new Set();
|
|
2540
|
+
const removedBufferedIds = /* @__PURE__ */ new Set();
|
|
2541
|
+
for (const lastHotId of hotGroups) {
|
|
2542
|
+
const group = currentGroups[lastHotId];
|
|
2543
|
+
if (!group || time < group.startTime || group.endTime <= time) {
|
|
2544
|
+
nextHotGroups.delete(lastHotId);
|
|
2545
|
+
removedHotIds.add(lastHotId);
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
for (let id = 0; id < currentGroups.length; id++) {
|
|
2549
|
+
const group = currentGroups[id];
|
|
2550
|
+
if (!group) continue;
|
|
2551
|
+
if (group.startTime <= time && group.endTime > time && !nextHotGroups.has(id)) {
|
|
2552
|
+
nextHotGroups.add(id);
|
|
2553
|
+
addedIds.add(id);
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
for (const id of bufferedGroups) if (!nextHotGroups.has(id)) removedBufferedIds.add(id);
|
|
2557
|
+
return {
|
|
2558
|
+
nextHotGroups,
|
|
2559
|
+
addedIds,
|
|
2560
|
+
removedHotIds,
|
|
2561
|
+
removedBufferedIds
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
/**
|
|
2565
|
+
* 在 seeking 场景下,根据当前时间选出应对齐滚动到的目标行索引。
|
|
2566
|
+
*
|
|
2567
|
+
* 若当前仍存在缓冲行,则优先对齐到最靠前的缓冲行;
|
|
2568
|
+
* 否则对齐到第一条开始时间不小于当前时间的歌词行。
|
|
2569
|
+
*/
|
|
2570
|
+
function pickScrollToIndexForSeek(time, currentGroups, bufferedGroups) {
|
|
2571
|
+
if (bufferedGroups.size > 0) return Math.min(...bufferedGroups);
|
|
2572
|
+
const foundIndex = currentGroups.findIndex((group) => group.startTime >= time);
|
|
2573
|
+
return foundIndex === -1 ? currentGroups.length : foundIndex;
|
|
2574
|
+
}
|
|
2575
|
+
/**
|
|
2576
|
+
* 提交时间线状态转移的纯函数。
|
|
2577
|
+
*
|
|
2578
|
+
* 把一次时间线状态转移写回 {@link PlayerTimelineState},
|
|
2579
|
+
* 并返回一份供宿主执行的副作用应用计划,例如启用/禁用哪些歌词行、
|
|
2580
|
+
* 是否需要重置用户滚动状态、是否需要触发布局。
|
|
2581
|
+
*/
|
|
2582
|
+
function commitPlayerTimeState(input) {
|
|
2583
|
+
const { timelineState, time, currentGroups, hasBottomContent, stateResult } = input;
|
|
2584
|
+
const { addedIds, removedHotIds, removedBufferedIds } = stateResult;
|
|
2585
|
+
const { isSeeking } = timelineState;
|
|
2586
|
+
timelineState.currentTime = time;
|
|
2587
|
+
timelineState.hotGroups = stateResult.nextHotGroups;
|
|
2588
|
+
let shouldLayout = false;
|
|
2589
|
+
let shouldResetScroll = false;
|
|
2590
|
+
const groupsToEnable = [];
|
|
2591
|
+
const groupsToDisable = /* @__PURE__ */ new Set();
|
|
2592
|
+
if (isSeeking) {
|
|
2593
|
+
timelineState.bufferedGroups = new Set([...timelineState.hotGroups]);
|
|
2594
|
+
timelineState.scrollToIndex = pickScrollToIndexForSeek(time, currentGroups, timelineState.bufferedGroups);
|
|
2595
|
+
for (const id of removedHotIds) groupsToDisable.add(id);
|
|
2596
|
+
for (const id of timelineState.hotGroups) groupsToEnable.push(id);
|
|
2597
|
+
for (const id of removedBufferedIds) groupsToDisable.add(id);
|
|
2598
|
+
shouldResetScroll = true;
|
|
2599
|
+
shouldLayout = true;
|
|
2600
|
+
} else if (addedIds.size > 0) {
|
|
2601
|
+
for (const id of addedIds) {
|
|
2602
|
+
timelineState.bufferedGroups.add(id);
|
|
2603
|
+
groupsToEnable.push(id);
|
|
2604
|
+
}
|
|
2605
|
+
for (const id of removedBufferedIds) {
|
|
2606
|
+
timelineState.bufferedGroups.delete(id);
|
|
2607
|
+
groupsToDisable.add(id);
|
|
2608
|
+
}
|
|
2609
|
+
if (timelineState.bufferedGroups.size > 0) timelineState.scrollToIndex = Math.min(...timelineState.bufferedGroups);
|
|
2610
|
+
shouldLayout = true;
|
|
2611
|
+
} else if (removedBufferedIds.size > 0 && eqSet(removedBufferedIds, timelineState.bufferedGroups)) {
|
|
2612
|
+
for (const id of timelineState.bufferedGroups) {
|
|
2613
|
+
if (timelineState.hotGroups.has(id)) continue;
|
|
2614
|
+
timelineState.bufferedGroups.delete(id);
|
|
2615
|
+
groupsToDisable.add(id);
|
|
2616
|
+
}
|
|
2617
|
+
shouldLayout = true;
|
|
2618
|
+
}
|
|
2619
|
+
if (timelineState.bufferedGroups.size === 0 && currentGroups.length > 0) {
|
|
2620
|
+
if (time >= currentGroups[currentGroups.length - 1].endTime) {
|
|
2621
|
+
const targetIndex = hasBottomContent ? currentGroups.length : currentGroups.length - 1;
|
|
2622
|
+
if (timelineState.scrollToIndex !== targetIndex) {
|
|
2623
|
+
timelineState.scrollToIndex = targetIndex;
|
|
2624
|
+
shouldLayout = true;
|
|
2244
2625
|
}
|
|
2245
2626
|
}
|
|
2246
2627
|
}
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2628
|
+
timelineState.lastCurrentTime = time;
|
|
2629
|
+
return {
|
|
2630
|
+
shouldLayout,
|
|
2631
|
+
shouldResetScroll,
|
|
2632
|
+
groupsToEnable,
|
|
2633
|
+
groupsToDisable: [...groupsToDisable]
|
|
2634
|
+
};
|
|
2635
|
+
}
|
|
2251
2636
|
//#endregion
|
|
2252
|
-
//#region src/lyric-player/base.ts
|
|
2637
|
+
//#region src/lyric-player/base/index.ts
|
|
2253
2638
|
/**
|
|
2254
|
-
*
|
|
2639
|
+
* 歌词播放器的基类,已经包含了有关歌词操作和排版的功能,
|
|
2640
|
+
* 子类需要为其实现对应的显示展示操作
|
|
2255
2641
|
*/
|
|
2256
2642
|
var LyricPlayerBase = class extends EventTarget {
|
|
2257
2643
|
element = document.createElement("div");
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2644
|
+
/** 播放时间线状态 */
|
|
2645
|
+
timelineState = {
|
|
2646
|
+
currentTime: 0,
|
|
2647
|
+
lastCurrentTime: 0,
|
|
2648
|
+
hotGroups: /* @__PURE__ */ new Set(),
|
|
2649
|
+
bufferedGroups: /* @__PURE__ */ new Set(),
|
|
2650
|
+
scrollToIndex: 0,
|
|
2651
|
+
isSeeking: false,
|
|
2652
|
+
isPlaying: true,
|
|
2653
|
+
initialLayoutFinished: false
|
|
2654
|
+
};
|
|
2261
2655
|
/** @internal */
|
|
2262
|
-
|
|
2656
|
+
lyricGroupElementMap = /* @__PURE__ */ new WeakMap();
|
|
2263
2657
|
currentLyricLines = [];
|
|
2264
2658
|
processedLines = [];
|
|
2265
2659
|
lyricLinesIndexes = /* @__PURE__ */ new WeakMap();
|
|
2266
|
-
hotLines = /* @__PURE__ */ new Set();
|
|
2267
|
-
bufferedLines = /* @__PURE__ */ new Set();
|
|
2268
2660
|
isNonDynamic = false;
|
|
2269
2661
|
hasDuetLine = false;
|
|
2270
|
-
scrollToIndex = 0;
|
|
2271
2662
|
disableSpring = false;
|
|
2272
|
-
|
|
2663
|
+
layoutState = {
|
|
2664
|
+
interludeDotsSize: [0, 0],
|
|
2665
|
+
targetAlignIndex: 0,
|
|
2666
|
+
lastInterludeState: false,
|
|
2667
|
+
alignAnchor: LayoutAlignAnchor.Center,
|
|
2668
|
+
alignPosition: .35,
|
|
2669
|
+
overscanPx: 300
|
|
2670
|
+
};
|
|
2273
2671
|
interludeDots = new InterludeDots();
|
|
2274
2672
|
bottomLine = new BottomLineEl(this);
|
|
2275
2673
|
enableBlur = true;
|
|
2276
2674
|
enableScale = true;
|
|
2277
|
-
maskObsceneWords =
|
|
2675
|
+
maskObsceneWords = MaskObsceneWordsMode.Disabled;
|
|
2278
2676
|
maskObsceneWordChar = "*";
|
|
2279
2677
|
hidePassedLines = false;
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2678
|
+
scrollState = {
|
|
2679
|
+
scrollBoundary: {
|
|
2680
|
+
minOffset: 0,
|
|
2681
|
+
maxOffset: 0
|
|
2682
|
+
},
|
|
2683
|
+
scrollOffset: 0,
|
|
2684
|
+
allowScroll: true,
|
|
2685
|
+
isScrolled: false,
|
|
2686
|
+
isUserScrolling: false
|
|
2687
|
+
};
|
|
2688
|
+
currentLyricGroups = [];
|
|
2689
|
+
lyricGroupSize = /* @__PURE__ */ new WeakMap();
|
|
2287
2690
|
size = [0, 0];
|
|
2288
|
-
allowScroll = true;
|
|
2289
2691
|
isPageVisible = true;
|
|
2290
2692
|
optimizeOptions = {};
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
* 标记用户是否正在进行滚动交互
|
|
2294
|
-
*/
|
|
2295
|
-
isUserScrolling = false;
|
|
2296
|
-
wheelTimeout;
|
|
2297
|
-
/**
|
|
2298
|
-
* 视图额外预渲染(overscan)距离,单位:像素。
|
|
2299
|
-
* 用于决定在视口之外多少距离内也认为是“可见”,以便提前创建/保留行元素。
|
|
2300
|
-
*/
|
|
2301
|
-
overscanPx = 300;
|
|
2693
|
+
/** 是否强制让背景人声行始终后置(即始终在主歌词下方显示,不前置背景人声) */
|
|
2694
|
+
alwaysPostpositionBackground = false;
|
|
2302
2695
|
posXSpringParams = {
|
|
2303
2696
|
mass: 1,
|
|
2304
2697
|
damping: 10,
|
|
@@ -2321,13 +2714,12 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2321
2714
|
};
|
|
2322
2715
|
onPageShow = () => {
|
|
2323
2716
|
this.isPageVisible = true;
|
|
2324
|
-
this.setCurrentTime(this.currentTime, true);
|
|
2717
|
+
this.setCurrentTime(this.timelineState.currentTime, true);
|
|
2325
2718
|
};
|
|
2326
2719
|
onPageHide = () => {
|
|
2327
2720
|
this.isPageVisible = false;
|
|
2328
2721
|
};
|
|
2329
2722
|
scrolledHandler;
|
|
2330
|
-
isScrolled = false;
|
|
2331
2723
|
/** @internal */
|
|
2332
2724
|
resizeObserver = new ResizeObserver(((entries) => {
|
|
2333
2725
|
let shouldRelayout = false;
|
|
@@ -2338,8 +2730,8 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2338
2730
|
this.size[1] = rect.height;
|
|
2339
2731
|
shouldRebuildPlayerStyle = true;
|
|
2340
2732
|
} else if (entry.target === this.interludeDots.getElement()) {
|
|
2341
|
-
this.interludeDotsSize[0] = entry.target.clientWidth;
|
|
2342
|
-
this.interludeDotsSize[1] = entry.target.clientHeight;
|
|
2733
|
+
this.layoutState.interludeDotsSize[0] = entry.target.clientWidth;
|
|
2734
|
+
this.layoutState.interludeDotsSize[1] = entry.target.clientHeight;
|
|
2343
2735
|
shouldRelayout = true;
|
|
2344
2736
|
} else if (entry.target === this.bottomLine.getElement()) {
|
|
2345
2737
|
const newSize = [entry.target.clientWidth, entry.target.clientHeight];
|
|
@@ -2349,13 +2741,13 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2349
2741
|
shouldRelayout = true;
|
|
2350
2742
|
}
|
|
2351
2743
|
} else {
|
|
2352
|
-
const
|
|
2353
|
-
if (
|
|
2744
|
+
const groupObj = this.lyricGroupElementMap.get(entry.target);
|
|
2745
|
+
if (groupObj) {
|
|
2354
2746
|
const newSize = [entry.target.clientWidth, entry.target.clientHeight];
|
|
2355
|
-
const oldSize = this.
|
|
2747
|
+
const oldSize = this.lyricGroupSize.get(groupObj) ?? [0, 0];
|
|
2356
2748
|
if (newSize[0] !== oldSize[0] || newSize[1] !== oldSize[1]) {
|
|
2357
|
-
this.
|
|
2358
|
-
|
|
2749
|
+
this.lyricGroupSize.set(groupObj, newSize);
|
|
2750
|
+
groupObj.onLineSizeChange(newSize);
|
|
2359
2751
|
shouldRelayout = true;
|
|
2360
2752
|
}
|
|
2361
2753
|
}
|
|
@@ -2364,8 +2756,6 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2364
2756
|
if (shouldRebuildPlayerStyle) this.onResize();
|
|
2365
2757
|
}));
|
|
2366
2758
|
wordFadeWidth = .5;
|
|
2367
|
-
targetAlignIndex = 0;
|
|
2368
|
-
lastInterludeState = false;
|
|
2369
2759
|
constructor(element) {
|
|
2370
2760
|
super();
|
|
2371
2761
|
if (element) this.element = element;
|
|
@@ -2377,114 +2767,27 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2377
2767
|
this.interludeDots.setTransform(0, 200);
|
|
2378
2768
|
window.addEventListener("pageshow", this.onPageShow);
|
|
2379
2769
|
window.addEventListener("pagehide", this.onPageHide);
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
let scrollSpeed = 0;
|
|
2387
|
-
let curScrollId = 0;
|
|
2388
|
-
this.element.addEventListener("touchstart", (evt) => {
|
|
2389
|
-
if (this.beginScrollHandler()) {
|
|
2390
|
-
this.isUserScrolling = true;
|
|
2391
|
-
evt.preventDefault();
|
|
2392
|
-
startScrollY = this.scrollOffset;
|
|
2393
|
-
startTouchPosY = evt.touches[0].screenY;
|
|
2394
|
-
lastMoveY = startTouchPosY;
|
|
2395
|
-
startTouchStartX = evt.touches[0].screenX;
|
|
2396
|
-
startTouchStartY = evt.touches[0].screenY;
|
|
2397
|
-
startScrollTime = Date.now();
|
|
2398
|
-
scrollSpeed = 0;
|
|
2399
|
-
this.calcLayout(true, true);
|
|
2400
|
-
}
|
|
2401
|
-
});
|
|
2402
|
-
this.element.addEventListener("touchmove", (evt) => {
|
|
2403
|
-
if (this.beginScrollHandler()) {
|
|
2404
|
-
evt.preventDefault();
|
|
2405
|
-
const currentY = evt.touches[0].screenY;
|
|
2406
|
-
const deltaY = currentY - startTouchPosY;
|
|
2407
|
-
this.scrollOffset = startScrollY - deltaY;
|
|
2408
|
-
this.limitScrollOffset();
|
|
2409
|
-
const now = Date.now();
|
|
2410
|
-
const dt = now - startScrollTime;
|
|
2411
|
-
if (dt > 0) scrollSpeed = (currentY - lastMoveY) / dt;
|
|
2412
|
-
lastMoveY = currentY;
|
|
2413
|
-
startScrollTime = now;
|
|
2414
|
-
this.calcLayout(true, true);
|
|
2415
|
-
}
|
|
2416
|
-
});
|
|
2417
|
-
this.element.addEventListener("touchend", (evt) => {
|
|
2418
|
-
if (this.beginScrollHandler()) {
|
|
2419
|
-
evt.preventDefault();
|
|
2420
|
-
const touch = evt.changedTouches[0];
|
|
2421
|
-
const moveX = Math.abs(touch.screenX - startTouchStartX);
|
|
2422
|
-
const moveY = Math.abs(touch.screenY - startTouchStartY);
|
|
2423
|
-
if (moveX < 10 && moveY < 10) {
|
|
2424
|
-
const target = document.elementFromPoint(touch.clientX, touch.clientY);
|
|
2425
|
-
if (target && this.element.contains(target)) target.click();
|
|
2426
|
-
this.isUserScrolling = false;
|
|
2427
|
-
this.endScrollHandler();
|
|
2428
|
-
return;
|
|
2429
|
-
}
|
|
2430
|
-
startTouchPosY = 0;
|
|
2431
|
-
const scrollId = ++curScrollId;
|
|
2432
|
-
if (Math.abs(scrollSpeed) < .1) scrollSpeed = 0;
|
|
2433
|
-
let lastFrameTime = performance.now();
|
|
2434
|
-
const onScrollFrame = (time) => {
|
|
2435
|
-
if (scrollId !== curScrollId) return;
|
|
2436
|
-
const dt = time - lastFrameTime;
|
|
2437
|
-
lastFrameTime = time;
|
|
2438
|
-
if (dt <= 0 || dt > 100) {
|
|
2439
|
-
requestAnimationFrame(onScrollFrame);
|
|
2440
|
-
return;
|
|
2441
|
-
}
|
|
2442
|
-
if (Math.abs(scrollSpeed) > .05) {
|
|
2443
|
-
this.scrollOffset -= scrollSpeed * dt;
|
|
2444
|
-
this.limitScrollOffset();
|
|
2445
|
-
const frictionFactor = .95 ** (dt / 16);
|
|
2446
|
-
scrollSpeed *= frictionFactor;
|
|
2447
|
-
this.calcLayout(true, true);
|
|
2448
|
-
requestAnimationFrame(onScrollFrame);
|
|
2449
|
-
} else {
|
|
2450
|
-
this.isUserScrolling = false;
|
|
2451
|
-
this.endScrollHandler();
|
|
2452
|
-
}
|
|
2453
|
-
};
|
|
2454
|
-
requestAnimationFrame(onScrollFrame);
|
|
2455
|
-
} else this.isUserScrolling = false;
|
|
2770
|
+
attachPlayerScrollHandlers(this.element, this.scrollState, {
|
|
2771
|
+
onBeginScroll: () => this.beginScrollHandler(),
|
|
2772
|
+
onEndScroll: () => this.endScrollHandler(),
|
|
2773
|
+
onLayout: (sync, force) => this.calcLayout(sync, force),
|
|
2774
|
+
containsTarget: (target) => this.element.contains(target),
|
|
2775
|
+
clickTarget: (target) => target.click()
|
|
2456
2776
|
});
|
|
2457
|
-
this.element.addEventListener("wheel", (evt) => {
|
|
2458
|
-
if (this.beginScrollHandler()) {
|
|
2459
|
-
evt.preventDefault();
|
|
2460
|
-
if (evt.deltaMode === evt.DOM_DELTA_PIXEL) {
|
|
2461
|
-
this.scrollOffset += evt.deltaY;
|
|
2462
|
-
this.limitScrollOffset();
|
|
2463
|
-
this.calcLayout(true, false);
|
|
2464
|
-
} else {
|
|
2465
|
-
this.scrollOffset += evt.deltaY * 50;
|
|
2466
|
-
this.limitScrollOffset();
|
|
2467
|
-
this.calcLayout(false, false);
|
|
2468
|
-
}
|
|
2469
|
-
}
|
|
2470
|
-
}, { passive: false });
|
|
2471
2777
|
}
|
|
2472
2778
|
beginScrollHandler() {
|
|
2473
|
-
const allowed = this.allowScroll;
|
|
2779
|
+
const allowed = this.scrollState.allowScroll;
|
|
2474
2780
|
if (allowed) {
|
|
2475
|
-
this.isScrolled = true;
|
|
2781
|
+
this.scrollState.isScrolled = true;
|
|
2476
2782
|
clearTimeout(this.scrolledHandler);
|
|
2477
2783
|
this.scrolledHandler = setTimeout(() => {
|
|
2478
|
-
this.isScrolled = false;
|
|
2479
|
-
this.scrollOffset = 0;
|
|
2784
|
+
this.scrollState.isScrolled = false;
|
|
2785
|
+
this.scrollState.scrollOffset = 0;
|
|
2480
2786
|
}, 5e3);
|
|
2481
2787
|
}
|
|
2482
2788
|
return allowed;
|
|
2483
2789
|
}
|
|
2484
2790
|
endScrollHandler() {}
|
|
2485
|
-
limitScrollOffset() {
|
|
2486
|
-
this.scrollOffset = Math.max(Math.min(this.scrollBoundary[1], this.scrollOffset), this.scrollBoundary[0]);
|
|
2487
|
-
}
|
|
2488
2791
|
/**
|
|
2489
2792
|
* 设置文字动画的渐变宽度,单位以歌词行的主文字字体大小的倍数为单位,默认为 0.5,即一个全角字符的一半宽度
|
|
2490
2793
|
*
|
|
@@ -2526,7 +2829,7 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2526
2829
|
return this.wordFadeWidth;
|
|
2527
2830
|
}
|
|
2528
2831
|
setIsSeeking(isSeeking) {
|
|
2529
|
-
this.isSeeking = isSeeking;
|
|
2832
|
+
this.timelineState.isSeeking = isSeeking;
|
|
2530
2833
|
}
|
|
2531
2834
|
/**
|
|
2532
2835
|
* 设置是否隐藏已经播放过的歌词行,默认不隐藏
|
|
@@ -2564,13 +2867,13 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2564
2867
|
const c = char.charAt(0) || "*";
|
|
2565
2868
|
if (this.maskObsceneWordChar === c) return;
|
|
2566
2869
|
this.maskObsceneWordChar = c;
|
|
2567
|
-
if (this.maskObsceneWords !==
|
|
2870
|
+
if (this.maskObsceneWords !== MaskObsceneWordsMode.Disabled) {
|
|
2568
2871
|
this.rebuildLyricLines();
|
|
2569
2872
|
this.calcLayout();
|
|
2570
2873
|
}
|
|
2571
2874
|
}
|
|
2572
2875
|
rebuildLyricLines() {
|
|
2573
|
-
for (const
|
|
2876
|
+
for (const group of this.currentLyricGroups) group.rebuildAllLines();
|
|
2574
2877
|
}
|
|
2575
2878
|
/**
|
|
2576
2879
|
* 根据当前配置处理不雅用语单词
|
|
@@ -2579,10 +2882,10 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2579
2882
|
*/
|
|
2580
2883
|
processObsceneWord(word) {
|
|
2581
2884
|
const text = word.word;
|
|
2582
|
-
if (!word.obscene || this.maskObsceneWords ===
|
|
2885
|
+
if (!word.obscene || this.maskObsceneWords === MaskObsceneWordsMode.Disabled) return text;
|
|
2583
2886
|
const maskChar = this.maskObsceneWordChar;
|
|
2584
|
-
if (this.maskObsceneWords ===
|
|
2585
|
-
if (this.maskObsceneWords ===
|
|
2887
|
+
if (this.maskObsceneWords === MaskObsceneWordsMode.FullMask) return text.replace(/\S/g, maskChar);
|
|
2888
|
+
if (this.maskObsceneWords === MaskObsceneWordsMode.PartialMask) {
|
|
2586
2889
|
const trimmed = text.trim();
|
|
2587
2890
|
if (trimmed.length <= 2) return text.replace(/\S/g, maskChar);
|
|
2588
2891
|
const startPos = text.indexOf(trimmed);
|
|
@@ -2600,25 +2903,25 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2600
2903
|
* @param alignAnchor 歌词行对齐方式,详情见函数说明
|
|
2601
2904
|
*/
|
|
2602
2905
|
setAlignAnchor(alignAnchor) {
|
|
2603
|
-
this.alignAnchor = alignAnchor;
|
|
2906
|
+
this.layoutState.alignAnchor = alignAnchor;
|
|
2604
2907
|
}
|
|
2605
2908
|
/**
|
|
2606
2909
|
* 设置默认的歌词行对齐位置,相对于整个歌词播放组件的大小位置,默认为 `0.5`
|
|
2607
2910
|
* @param alignPosition 一个 `[0.0-1.0]` 之间的任意数字,代表组件高度由上到下的比例位置
|
|
2608
2911
|
*/
|
|
2609
2912
|
setAlignPosition(alignPosition) {
|
|
2610
|
-
this.alignPosition = alignPosition;
|
|
2913
|
+
this.layoutState.alignPosition = alignPosition;
|
|
2611
2914
|
}
|
|
2612
2915
|
/**
|
|
2613
2916
|
* 设置 overscan(视图上下额外缓冲渲染区)距离,单位:像素。
|
|
2614
2917
|
* @param px 像素值,默认 300
|
|
2615
2918
|
*/
|
|
2616
2919
|
setOverscanPx(px) {
|
|
2617
|
-
this.overscanPx =
|
|
2920
|
+
this.layoutState.overscanPx = clampPositive(px | 0);
|
|
2618
2921
|
}
|
|
2619
2922
|
/** 获取当前 overscan 像素距离 */
|
|
2620
2923
|
getOverscanPx() {
|
|
2621
|
-
return this.overscanPx;
|
|
2924
|
+
return this.layoutState.overscanPx;
|
|
2622
2925
|
}
|
|
2623
2926
|
/**
|
|
2624
2927
|
* 设置是否使用物理弹簧算法实现歌词动画效果,默认启用
|
|
@@ -2641,34 +2944,6 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2641
2944
|
return !this.disableSpring;
|
|
2642
2945
|
}
|
|
2643
2946
|
/**
|
|
2644
|
-
* 获取当前播放时间里是否处于间奏区间
|
|
2645
|
-
* 如果是则会返回单位为毫秒的始末时间
|
|
2646
|
-
* 否则返回 undefined
|
|
2647
|
-
*
|
|
2648
|
-
* 这个只允许内部调用
|
|
2649
|
-
* @returns [开始时间,结束时间,大概处于的歌词行ID,下一句是否为对唱歌词] 或 undefined 如果不处于间奏区间
|
|
2650
|
-
*/
|
|
2651
|
-
getCurrentInterlude() {
|
|
2652
|
-
const currentTime = this.currentTime + 20;
|
|
2653
|
-
const currentIndex = this.scrollToIndex;
|
|
2654
|
-
const lines = this.processedLines;
|
|
2655
|
-
const checkGap = (k) => {
|
|
2656
|
-
if (k < -1 || k >= lines.length - 1) return void 0;
|
|
2657
|
-
const prevLine = k === -1 ? null : lines[k];
|
|
2658
|
-
const nextLine = lines[k + 1];
|
|
2659
|
-
const gapStart = prevLine ? prevLine.endTime : 0;
|
|
2660
|
-
const gapEnd = Math.max(gapStart, nextLine.startTime - 250);
|
|
2661
|
-
if (gapEnd - gapStart < 4e3) return;
|
|
2662
|
-
if (gapEnd > currentTime && gapStart < currentTime) return [
|
|
2663
|
-
Math.max(gapStart, currentTime),
|
|
2664
|
-
gapEnd,
|
|
2665
|
-
k,
|
|
2666
|
-
nextLine.isDuet
|
|
2667
|
-
];
|
|
2668
|
-
};
|
|
2669
|
-
return checkGap(currentIndex - 1) || checkGap(currentIndex) || checkGap(currentIndex + 1);
|
|
2670
|
-
}
|
|
2671
|
-
/**
|
|
2672
2947
|
* 设置歌词的优化配置项,这些配置项默认全部开启
|
|
2673
2948
|
*
|
|
2674
2949
|
* 注意,如果在 `setLyricLines` 之后修改此配置,需要重新调用 `setLyricLines()` 才能对当前歌词生效
|
|
@@ -2688,9 +2963,9 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2688
2963
|
*/
|
|
2689
2964
|
setLyricLines(lines, initialTime = 0) {
|
|
2690
2965
|
if (process.env.NODE_ENV !== "production") console.log("设置歌词行", lines, initialTime);
|
|
2691
|
-
this.initialLayoutFinished = true;
|
|
2692
|
-
this.lastCurrentTime = initialTime;
|
|
2693
|
-
this.currentTime = initialTime;
|
|
2966
|
+
this.timelineState.initialLayoutFinished = true;
|
|
2967
|
+
this.timelineState.lastCurrentTime = initialTime;
|
|
2968
|
+
this.timelineState.currentTime = initialTime;
|
|
2694
2969
|
this.currentLyricLines = (0, _ungap_structured_clone.default)(lines);
|
|
2695
2970
|
this.processedLines = (0, _ungap_structured_clone.default)(this.currentLyricLines);
|
|
2696
2971
|
optimizeLyricLines(this.processedLines, this.optimizeOptions);
|
|
@@ -2700,11 +2975,11 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2700
2975
|
break;
|
|
2701
2976
|
}
|
|
2702
2977
|
this.hasDuetLine = this.processedLines.some((line) => line.isDuet);
|
|
2703
|
-
for (const
|
|
2978
|
+
for (const group of this.currentLyricGroups) group.dispose();
|
|
2979
|
+
this.currentLyricGroups = [];
|
|
2704
2980
|
this.interludeDots.setInterlude(void 0);
|
|
2705
|
-
this.
|
|
2706
|
-
this.
|
|
2707
|
-
this.setCurrentTime(0, true);
|
|
2981
|
+
this.timelineState.hotGroups.clear();
|
|
2982
|
+
this.timelineState.bufferedGroups.clear();
|
|
2708
2983
|
if (process.env.NODE_ENV !== "production") console.log("歌词处理完成", this);
|
|
2709
2984
|
}
|
|
2710
2985
|
/**
|
|
@@ -2712,143 +2987,39 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2712
2987
|
* @returns 当前是否在播放
|
|
2713
2988
|
*/
|
|
2714
2989
|
getIsPlaying() {
|
|
2715
|
-
return this.isPlaying;
|
|
2990
|
+
return this.timelineState.isPlaying;
|
|
2716
2991
|
}
|
|
2717
2992
|
/**
|
|
2718
|
-
*
|
|
2719
|
-
*
|
|
2993
|
+
* 设置当前播放进度,此时将会更新内部的歌词进度信息。
|
|
2994
|
+
*
|
|
2995
|
+
* 内部会根据调用间隔和播放进度自动决定如何滚动和显示歌词,所以这个的调用频率越快越准确越好。
|
|
2996
|
+
* 调用完成后,应每帧调用 {@link update} 方法来执行歌词动画效果。**此函数本身不会触发动画效果**。
|
|
2720
2997
|
*
|
|
2721
|
-
* 调用完成后,可以每帧调用 `update` 函数来执行歌词动画效果
|
|
2722
2998
|
* @param time 当前播放进度,单位为毫秒
|
|
2723
2999
|
*/
|
|
2724
3000
|
setCurrentTime(time, isSeek = false) {
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
const nextLine = this.processedLines[lastHotId + 1];
|
|
2735
|
-
if (nextLine?.isBG) {
|
|
2736
|
-
const nextMainLine = this.processedLines[lastHotId + 2];
|
|
2737
|
-
const startTime = Math.min(line.startTime, nextLine?.startTime);
|
|
2738
|
-
const endTime = Math.min(Math.max(line.endTime, nextMainLine?.startTime ?? Number.MAX_VALUE), Math.max(line.endTime, nextLine?.endTime));
|
|
2739
|
-
if (startTime > time || endTime <= time) {
|
|
2740
|
-
this.hotLines.delete(lastHotId);
|
|
2741
|
-
removedHotIds.add(lastHotId);
|
|
2742
|
-
this.hotLines.delete(lastHotId + 1);
|
|
2743
|
-
removedHotIds.add(lastHotId + 1);
|
|
2744
|
-
if (isSeek) {
|
|
2745
|
-
this.currentLyricLineObjects[lastHotId]?.disable();
|
|
2746
|
-
this.currentLyricLineObjects[lastHotId + 1]?.disable();
|
|
2747
|
-
}
|
|
2748
|
-
}
|
|
2749
|
-
} else if (line.startTime > time || line.endTime <= time) {
|
|
2750
|
-
this.hotLines.delete(lastHotId);
|
|
2751
|
-
removedHotIds.add(lastHotId);
|
|
2752
|
-
if (isSeek) this.currentLyricLineObjects[lastHotId]?.disable();
|
|
2753
|
-
}
|
|
2754
|
-
} else {
|
|
2755
|
-
this.hotLines.delete(lastHotId);
|
|
2756
|
-
removedHotIds.add(lastHotId);
|
|
2757
|
-
if (isSeek) this.currentLyricLineObjects[lastHotId]?.disable();
|
|
2758
|
-
}
|
|
2759
|
-
}
|
|
2760
|
-
this.currentLyricLineObjects.forEach((lineObj, id, arr) => {
|
|
2761
|
-
const line = lineObj.getLine();
|
|
2762
|
-
if (!line.isBG && line.startTime <= time && line.endTime > time) {
|
|
2763
|
-
if (isSeek) lineObj.enable(time, this.isPlaying);
|
|
2764
|
-
if (!this.hotLines.has(id)) {
|
|
2765
|
-
this.hotLines.add(id);
|
|
2766
|
-
addedIds.add(id);
|
|
2767
|
-
if (!isSeek) lineObj.enable();
|
|
2768
|
-
if (arr[id + 1]?.getLine()?.isBG) {
|
|
2769
|
-
this.hotLines.add(id + 1);
|
|
2770
|
-
addedIds.add(id + 1);
|
|
2771
|
-
if (isSeek) arr[id + 1].enable(time, this.isPlaying);
|
|
2772
|
-
else arr[id + 1].enable();
|
|
2773
|
-
}
|
|
2774
|
-
}
|
|
2775
|
-
}
|
|
3001
|
+
time = Math.round(time);
|
|
3002
|
+
const { timelineState } = this;
|
|
3003
|
+
timelineState.isSeeking = Boolean(isSeek);
|
|
3004
|
+
timelineState.currentTime = time;
|
|
3005
|
+
if (!timelineState.initialLayoutFinished && !timelineState.isSeeking) return;
|
|
3006
|
+
const stateResult = computePlayerTimeState({
|
|
3007
|
+
time,
|
|
3008
|
+
currentGroups: this.currentLyricGroups,
|
|
3009
|
+
timelineState
|
|
2776
3010
|
});
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
this.resetScroll();
|
|
2790
|
-
this.calcLayout();
|
|
2791
|
-
} else if (removedIds.size > 0 || addedIds.size > 0) if (removedIds.size === 0 && addedIds.size > 0) {
|
|
2792
|
-
for (const v of addedIds) {
|
|
2793
|
-
this.bufferedLines.add(v);
|
|
2794
|
-
this.currentLyricLineObjects[v]?.enable();
|
|
2795
|
-
}
|
|
2796
|
-
this.scrollToIndex = Math.min(...this.bufferedLines);
|
|
2797
|
-
this.calcLayout();
|
|
2798
|
-
} else if (addedIds.size === 0 && removedIds.size > 0) {
|
|
2799
|
-
if (eqSet(removedIds, this.bufferedLines)) {
|
|
2800
|
-
for (const v of this.bufferedLines) if (!this.hotLines.has(v)) {
|
|
2801
|
-
this.bufferedLines.delete(v);
|
|
2802
|
-
this.currentLyricLineObjects[v]?.disable();
|
|
2803
|
-
}
|
|
2804
|
-
this.calcLayout();
|
|
2805
|
-
}
|
|
2806
|
-
} else {
|
|
2807
|
-
for (const v of addedIds) {
|
|
2808
|
-
this.bufferedLines.add(v);
|
|
2809
|
-
this.currentLyricLineObjects[v]?.enable();
|
|
2810
|
-
}
|
|
2811
|
-
for (const v of removedIds) {
|
|
2812
|
-
this.bufferedLines.delete(v);
|
|
2813
|
-
this.currentLyricLineObjects[v]?.disable();
|
|
2814
|
-
}
|
|
2815
|
-
if (this.bufferedLines.size > 0) this.scrollToIndex = Math.min(...this.bufferedLines);
|
|
2816
|
-
this.calcLayout();
|
|
2817
|
-
}
|
|
2818
|
-
if (this.bufferedLines.size === 0 && this.processedLines.length > 0) {
|
|
2819
|
-
const lastLine = this.processedLines[this.processedLines.length - 1];
|
|
2820
|
-
const hasBottomContent = this.bottomLine.getElement().innerHTML.trim().length > 0;
|
|
2821
|
-
if (time >= lastLine.endTime) {
|
|
2822
|
-
const targetIndex = hasBottomContent ? this.processedLines.length : this.processedLines.length - 1;
|
|
2823
|
-
if (this.scrollToIndex !== targetIndex) {
|
|
2824
|
-
this.scrollToIndex = targetIndex;
|
|
2825
|
-
this.calcLayout();
|
|
2826
|
-
}
|
|
2827
|
-
}
|
|
2828
|
-
}
|
|
2829
|
-
this.lastCurrentTime = time;
|
|
2830
|
-
}
|
|
2831
|
-
updateDynamicSpringParams() {
|
|
2832
|
-
if (!this.getEnableSpring() || this.processedLines.length === 0) return;
|
|
2833
|
-
const currentIndex = this.scrollToIndex;
|
|
2834
|
-
const currentLine = this.processedLines[currentIndex];
|
|
2835
|
-
const prevLine = this.processedLines[currentIndex - 1];
|
|
2836
|
-
if (currentLine && prevLine) {
|
|
2837
|
-
const interval = currentLine.startTime - (prevLine?.words[0]?.startTime ?? prevLine.startTime);
|
|
2838
|
-
const MIN_INTERVAL = 100;
|
|
2839
|
-
const MAX_INTERVAL = 800;
|
|
2840
|
-
const clampedInterval = Math.max(MIN_INTERVAL, Math.min(MAX_INTERVAL, interval));
|
|
2841
|
-
const MAX_STIFFNESS = 220;
|
|
2842
|
-
const MIN_STIFFNESS = 170;
|
|
2843
|
-
let ratio = 1 - (clampedInterval - MIN_INTERVAL) / (MAX_INTERVAL - MIN_INTERVAL);
|
|
2844
|
-
ratio = ratio ** .2;
|
|
2845
|
-
const targetStiffness = MIN_STIFFNESS + ratio * (MAX_STIFFNESS - MIN_STIFFNESS);
|
|
2846
|
-
const targetDamping = Math.sqrt(targetStiffness) * 2.2;
|
|
2847
|
-
this.setLinePosYSpringParams({
|
|
2848
|
-
stiffness: targetStiffness,
|
|
2849
|
-
damping: targetDamping
|
|
2850
|
-
});
|
|
2851
|
-
}
|
|
3011
|
+
const hasBottomContent = this.bottomLine.getElement().innerHTML.trim().length > 0;
|
|
3012
|
+
const commitResult = commitPlayerTimeState({
|
|
3013
|
+
timelineState,
|
|
3014
|
+
time,
|
|
3015
|
+
currentGroups: this.currentLyricGroups,
|
|
3016
|
+
hasBottomContent,
|
|
3017
|
+
stateResult
|
|
3018
|
+
});
|
|
3019
|
+
for (const id of commitResult.groupsToDisable) this.currentLyricGroups[id]?.disable();
|
|
3020
|
+
for (const id of commitResult.groupsToEnable) this.currentLyricGroups[id]?.enable();
|
|
3021
|
+
if (commitResult.shouldResetScroll) this.resetScroll();
|
|
3022
|
+
if (commitResult.shouldLayout) this.calcLayout();
|
|
2852
3023
|
}
|
|
2853
3024
|
/**
|
|
2854
3025
|
* 重新布局定位歌词行的位置,调用完成后再逐帧调用 `update`
|
|
@@ -2868,102 +3039,102 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2868
3039
|
* @param force 是否绕过弹簧效果强制更新位置
|
|
2869
3040
|
*/
|
|
2870
3041
|
async calcLayout(sync = false, force = false) {
|
|
2871
|
-
const interlude =
|
|
3042
|
+
const interlude = computeCurrentInterlude({
|
|
3043
|
+
currentTime: this.timelineState.currentTime,
|
|
3044
|
+
scrollToIndex: this.timelineState.scrollToIndex,
|
|
3045
|
+
currentGroups: this.currentLyricGroups
|
|
3046
|
+
});
|
|
2872
3047
|
const isInterludeActive = !!interlude;
|
|
2873
|
-
if (this.targetAlignIndex !== this.scrollToIndex || this.lastInterludeState !== isInterludeActive) {
|
|
2874
|
-
this.lastInterludeState = isInterludeActive;
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
damping: 15
|
|
3048
|
+
if (this.layoutState.targetAlignIndex !== this.timelineState.scrollToIndex || this.layoutState.lastInterludeState !== isInterludeActive) {
|
|
3049
|
+
this.layoutState.lastInterludeState = isInterludeActive;
|
|
3050
|
+
const springParams = computeLinePosYSpringParams({
|
|
3051
|
+
enabled: this.getEnableSpring(),
|
|
3052
|
+
currentGroups: this.currentLyricGroups,
|
|
3053
|
+
scrollToIndex: this.timelineState.scrollToIndex,
|
|
3054
|
+
isSeeking: this.timelineState.isSeeking,
|
|
3055
|
+
isInterludeActive
|
|
2882
3056
|
});
|
|
2883
|
-
|
|
3057
|
+
if (springParams.shouldUpdate && springParams.params) this.setLinePosYSpringParams(springParams.params);
|
|
2884
3058
|
}
|
|
2885
|
-
let curPos = -this.scrollOffset;
|
|
2886
|
-
const targetAlignIndex = this.scrollToIndex;
|
|
3059
|
+
let curPos = -this.scrollState.scrollOffset;
|
|
3060
|
+
const targetAlignIndex = this.timelineState.scrollToIndex;
|
|
2887
3061
|
let isNextDuet = false;
|
|
2888
|
-
if (interlude) isNextDuet = interlude
|
|
3062
|
+
if (interlude) isNextDuet = interlude.isNextDuet;
|
|
2889
3063
|
else this.interludeDots.setInterlude(void 0);
|
|
2890
3064
|
const dotMargin = (this.baseFontSize || 24) * .4;
|
|
2891
|
-
const totalInterludeHeight = this.interludeDotsSize[1] + dotMargin * 2;
|
|
3065
|
+
const totalInterludeHeight = this.layoutState.interludeDotsSize[1] + dotMargin * 2;
|
|
2892
3066
|
if (interlude) {
|
|
2893
|
-
if (interlude
|
|
3067
|
+
if (interlude.anchorLineIndex !== -1) curPos -= totalInterludeHeight;
|
|
2894
3068
|
}
|
|
2895
3069
|
const LINE_HEIGHT_FALLBACK = this.size[1] / 5;
|
|
2896
|
-
const scrollOffset = this.
|
|
2897
|
-
this.scrollBoundary
|
|
3070
|
+
const scrollOffset = this.currentLyricGroups.slice(0, targetAlignIndex).reduce((acc, group) => acc + (this.lyricGroupSize.get(group)?.[1] ?? LINE_HEIGHT_FALLBACK), 0);
|
|
3071
|
+
this.scrollState.scrollBoundary.minOffset = -scrollOffset;
|
|
2898
3072
|
curPos -= scrollOffset;
|
|
2899
|
-
curPos += this.size[1] * this.alignPosition;
|
|
2900
|
-
const
|
|
2901
|
-
this.targetAlignIndex = targetAlignIndex;
|
|
2902
|
-
const isBottomFocused = targetAlignIndex === this.
|
|
3073
|
+
curPos += this.size[1] * this.layoutState.alignPosition;
|
|
3074
|
+
const curGroup = this.currentLyricGroups[targetAlignIndex];
|
|
3075
|
+
this.layoutState.targetAlignIndex = targetAlignIndex;
|
|
3076
|
+
const isBottomFocused = targetAlignIndex === this.currentLyricGroups.length;
|
|
2903
3077
|
this.bottomLine.setFocused(isBottomFocused);
|
|
2904
|
-
|
|
2905
|
-
if (
|
|
2906
|
-
|
|
2907
|
-
if (targetLineHeight > 0) switch (this.alignAnchor) {
|
|
2908
|
-
case "bottom":
|
|
3078
|
+
const targetLineHeight = curGroup ? this.lyricGroupSize.get(curGroup)?.[1] ?? LINE_HEIGHT_FALLBACK : isBottomFocused ? this.bottomLine.lineSize[1] : 0;
|
|
3079
|
+
if (targetLineHeight > 0) switch (this.layoutState.alignAnchor) {
|
|
3080
|
+
case LayoutAlignAnchor.Bottom:
|
|
2909
3081
|
curPos -= targetLineHeight;
|
|
2910
3082
|
break;
|
|
2911
|
-
case
|
|
3083
|
+
case LayoutAlignAnchor.Center:
|
|
2912
3084
|
curPos -= targetLineHeight / 2;
|
|
2913
3085
|
break;
|
|
2914
|
-
case
|
|
3086
|
+
case LayoutAlignAnchor.Top: break;
|
|
2915
3087
|
}
|
|
2916
|
-
const latestIndex = Math.max(...this.
|
|
3088
|
+
const latestIndex = Math.max(...this.timelineState.bufferedGroups);
|
|
2917
3089
|
let delay = 0;
|
|
2918
3090
|
let baseDelay = sync ? 0 : .05;
|
|
2919
3091
|
let setDots = false;
|
|
2920
|
-
this.
|
|
2921
|
-
const hasBuffered = this.
|
|
2922
|
-
const
|
|
2923
|
-
const line = lineObj.getLine();
|
|
2924
|
-
const shouldShowDots = interlude && i === interlude[2] + 1;
|
|
3092
|
+
this.currentLyricGroups.forEach((group, i) => {
|
|
3093
|
+
const hasBuffered = this.timelineState.bufferedGroups.has(i);
|
|
3094
|
+
const shouldShowDots = interlude && i === interlude.anchorLineIndex + 1;
|
|
2925
3095
|
if (!setDots && shouldShowDots) {
|
|
2926
3096
|
setDots = true;
|
|
2927
3097
|
curPos += dotMargin;
|
|
2928
3098
|
let targetX = 0;
|
|
2929
|
-
if (interlude && isNextDuet) targetX = this.size[0] - this.interludeDotsSize[0];
|
|
3099
|
+
if (interlude && isNextDuet) targetX = this.size[0] - this.layoutState.interludeDotsSize[0];
|
|
2930
3100
|
this.interludeDots.setTransform(targetX, curPos);
|
|
2931
|
-
if (interlude) this.interludeDots.setInterlude([interlude
|
|
2932
|
-
curPos += this.interludeDotsSize[1];
|
|
3101
|
+
if (interlude) this.interludeDots.setInterlude([interlude.startTime, interlude.endTime]);
|
|
3102
|
+
curPos += this.layoutState.interludeDotsSize[1];
|
|
2933
3103
|
curPos += dotMargin;
|
|
2934
3104
|
}
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
if (curPos >= 0 && !this.isSeeking) {
|
|
2951
|
-
|
|
2952
|
-
if (i >= this.scrollToIndex) baseDelay /= 1.05;
|
|
3105
|
+
const presentation = computeGroupPresentation({
|
|
3106
|
+
groupIndex: i,
|
|
3107
|
+
scrollToIndex: this.timelineState.scrollToIndex,
|
|
3108
|
+
latestIndex,
|
|
3109
|
+
hasBuffered,
|
|
3110
|
+
hidePassedLines: this.hidePassedLines,
|
|
3111
|
+
isPlaying: this.timelineState.isPlaying,
|
|
3112
|
+
isNonDynamic: this.isNonDynamic,
|
|
3113
|
+
enableBlur: this.enableBlur,
|
|
3114
|
+
isUserScrolling: this.scrollState.isUserScrolling,
|
|
3115
|
+
isCompact: window.innerWidth <= 1024,
|
|
3116
|
+
interlude
|
|
3117
|
+
});
|
|
3118
|
+
group.setTransform(curPos, force, delay, presentation.isActive, presentation.targetOpacity, presentation.blurLevel);
|
|
3119
|
+
curPos += this.lyricGroupSize.get(group)?.[1] ?? LINE_HEIGHT_FALLBACK;
|
|
3120
|
+
if (curPos >= 0 && !this.timelineState.isSeeking) {
|
|
3121
|
+
delay += baseDelay;
|
|
3122
|
+
if (i >= this.timelineState.scrollToIndex) baseDelay /= 1.05;
|
|
2953
3123
|
}
|
|
2954
3124
|
});
|
|
2955
|
-
this.scrollBoundary
|
|
2956
|
-
const bottomIndex = this.
|
|
2957
|
-
const finalBottomBlur =
|
|
3125
|
+
this.scrollState.scrollBoundary.maxOffset = curPos + this.scrollState.scrollOffset - this.size[1] / 2;
|
|
3126
|
+
const bottomIndex = this.currentLyricGroups.length;
|
|
3127
|
+
const finalBottomBlur = computeLineBlur({
|
|
3128
|
+
enableBlur: this.enableBlur,
|
|
3129
|
+
isUserScrolling: this.scrollState.isUserScrolling,
|
|
3130
|
+
isActive: isBottomFocused,
|
|
3131
|
+
itemIndex: bottomIndex,
|
|
3132
|
+
scrollToIndex: this.timelineState.scrollToIndex,
|
|
3133
|
+
latestIndex,
|
|
3134
|
+
isCompact: window.innerWidth <= 1024
|
|
3135
|
+
});
|
|
2958
3136
|
this.bottomLine.setTransform(0, curPos, finalBottomBlur, force, delay);
|
|
2959
3137
|
}
|
|
2960
|
-
calculateBlur(itemIndex, isActive, latestIndex) {
|
|
2961
|
-
if (!this.enableBlur || this.isUserScrolling || isActive) return 0;
|
|
2962
|
-
let blurLevel = 1;
|
|
2963
|
-
if (itemIndex < this.scrollToIndex) blurLevel += Math.abs(this.scrollToIndex - itemIndex) + 1;
|
|
2964
|
-
else blurLevel += Math.abs(itemIndex - Math.max(this.scrollToIndex, latestIndex));
|
|
2965
|
-
return window.innerWidth <= 1024 ? blurLevel * .8 : blurLevel;
|
|
2966
|
-
}
|
|
2967
3138
|
/**
|
|
2968
3139
|
* 设置所有歌词行在横坐标上的弹簧属性,包括重量、弹力和阻力。
|
|
2969
3140
|
*
|
|
@@ -2982,7 +3153,10 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2982
3153
|
...params
|
|
2983
3154
|
};
|
|
2984
3155
|
this.bottomLine.lineTransforms.posY.updateParams(this.posYSpringParams);
|
|
2985
|
-
for (const
|
|
3156
|
+
for (const group of this.currentLyricGroups) {
|
|
3157
|
+
group.posY.updateParams(this.posYSpringParams);
|
|
3158
|
+
group.bgSlideY.updateParams(this.posYSpringParams);
|
|
3159
|
+
}
|
|
2986
3160
|
}
|
|
2987
3161
|
/**
|
|
2988
3162
|
* 设置所有歌词行在缩放大小上的弹簧属性,包括重量、弹力和阻力。
|
|
@@ -2998,17 +3172,18 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
2998
3172
|
...this.scaleForBGSpringParams,
|
|
2999
3173
|
...params
|
|
3000
3174
|
};
|
|
3001
|
-
for (const
|
|
3002
|
-
|
|
3175
|
+
for (const group of this.currentLyricGroups) {
|
|
3176
|
+
group.mainLine.lineTransforms.scale.updateParams(this.scaleSpringParams);
|
|
3177
|
+
group.bgLine?.lineTransforms.scale.updateParams(this.scaleForBGSpringParams);
|
|
3178
|
+
}
|
|
3003
3179
|
}
|
|
3004
|
-
isPlaying = true;
|
|
3005
3180
|
/**
|
|
3006
3181
|
* 暂停部分效果演出,目前会暂停播放间奏点的动画,且将背景歌词显示出来
|
|
3007
3182
|
*/
|
|
3008
3183
|
pause() {
|
|
3009
3184
|
this.interludeDots.pause();
|
|
3010
|
-
if (this.isPlaying) {
|
|
3011
|
-
this.isPlaying = false;
|
|
3185
|
+
if (this.timelineState.isPlaying) {
|
|
3186
|
+
this.timelineState.isPlaying = false;
|
|
3012
3187
|
this.calcLayout();
|
|
3013
3188
|
}
|
|
3014
3189
|
}
|
|
@@ -3017,8 +3192,8 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
3017
3192
|
*/
|
|
3018
3193
|
resume() {
|
|
3019
3194
|
this.interludeDots.resume();
|
|
3020
|
-
if (!this.isPlaying) {
|
|
3021
|
-
this.isPlaying = true;
|
|
3195
|
+
if (!this.timelineState.isPlaying) {
|
|
3196
|
+
this.timelineState.isPlaying = true;
|
|
3022
3197
|
this.calcLayout();
|
|
3023
3198
|
}
|
|
3024
3199
|
}
|
|
@@ -3051,8 +3226,7 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
3051
3226
|
* 请在用户完成滚动点击跳转歌词时调用本事件再调用 `calcLayout` 以正确滚动到目标位置
|
|
3052
3227
|
*/
|
|
3053
3228
|
resetScroll() {
|
|
3054
|
-
this.
|
|
3055
|
-
this.scrollOffset = 0;
|
|
3229
|
+
resetPlayerScrollState(this.scrollState);
|
|
3056
3230
|
clearTimeout(this.scrolledHandler);
|
|
3057
3231
|
}
|
|
3058
3232
|
/**
|
|
@@ -3071,7 +3245,24 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
3071
3245
|
* @returns 当前播放位置
|
|
3072
3246
|
*/
|
|
3073
3247
|
getCurrentTime() {
|
|
3074
|
-
return this.currentTime;
|
|
3248
|
+
return this.timelineState.currentTime;
|
|
3249
|
+
}
|
|
3250
|
+
/**
|
|
3251
|
+
* 设置是否让背景人声行始终后置显示
|
|
3252
|
+
*
|
|
3253
|
+
* 默认情况下,如果背景歌词开始时间早于主歌词,会在主歌词上方展示;
|
|
3254
|
+
* 如果设置为 `true`,则无论时间顺序如何,背景歌词都会始终在主歌词下方展示
|
|
3255
|
+
* @param enable 是否启用始终后置
|
|
3256
|
+
*/
|
|
3257
|
+
setAlwaysPostpositionBackground(enable) {
|
|
3258
|
+
if (this.alwaysPostpositionBackground === enable) return;
|
|
3259
|
+
this.alwaysPostpositionBackground = enable;
|
|
3260
|
+
this.rebuildLyricLines();
|
|
3261
|
+
this.calcLayout();
|
|
3262
|
+
}
|
|
3263
|
+
/** 获取当前是否设置了让背景人声行始终后置显示 */
|
|
3264
|
+
getAlwaysPostpositionBackground() {
|
|
3265
|
+
return this.alwaysPostpositionBackground;
|
|
3075
3266
|
}
|
|
3076
3267
|
getElement() {
|
|
3077
3268
|
return this.element;
|
|
@@ -3082,6 +3273,198 @@ var LyricPlayerBase = class extends EventTarget {
|
|
|
3082
3273
|
window.removeEventListener("pagehide", this.onPageHide);
|
|
3083
3274
|
}
|
|
3084
3275
|
};
|
|
3276
|
+
//#endregion
|
|
3277
|
+
//#region src/lyric-player/base/group.ts
|
|
3278
|
+
var LyricLineGroupBase = class {
|
|
3279
|
+
posY = new Spring(0);
|
|
3280
|
+
bgSlideY = new Spring(-80);
|
|
3281
|
+
top = 0;
|
|
3282
|
+
delay = 0;
|
|
3283
|
+
isActive = false;
|
|
3284
|
+
opacity = 1;
|
|
3285
|
+
blur = 0;
|
|
3286
|
+
isBgFirst = false;
|
|
3287
|
+
constructor(mainLine, bgLine) {
|
|
3288
|
+
this.mainLine = mainLine;
|
|
3289
|
+
this.bgLine = bgLine;
|
|
3290
|
+
}
|
|
3291
|
+
get startTime() {
|
|
3292
|
+
return this.mainLine.getLine().startTime;
|
|
3293
|
+
}
|
|
3294
|
+
get endTime() {
|
|
3295
|
+
return this.mainLine.getLine().endTime;
|
|
3296
|
+
}
|
|
3297
|
+
onLineSizeChange(size) {
|
|
3298
|
+
this.mainLine.onLineSizeChange(size);
|
|
3299
|
+
this.bgLine?.onLineSizeChange(size);
|
|
3300
|
+
}
|
|
3301
|
+
setTransform(top, force, delay, isActive, opacity, blur) {
|
|
3302
|
+
this.top = top;
|
|
3303
|
+
this.delay = delay;
|
|
3304
|
+
this.isActive = isActive;
|
|
3305
|
+
this.opacity = opacity;
|
|
3306
|
+
this.blur = blur;
|
|
3307
|
+
this.setLineTransformations(force, delay);
|
|
3308
|
+
const enableSpring = this.lyricPlayer.getEnableSpring();
|
|
3309
|
+
const hiddenSlideY = (this.lyricPlayer.getAlwaysPostpositionBackground() ? false : this.isBgFirst) ? 80 : -80;
|
|
3310
|
+
const isPlaying = this.lyricPlayer.getIsPlaying();
|
|
3311
|
+
const targetBgSlideY = isActive || !isPlaying ? 0 : hiddenSlideY;
|
|
3312
|
+
if (force || !enableSpring) {
|
|
3313
|
+
this.posY.setPosition(top);
|
|
3314
|
+
this.bgSlideY.setPosition(targetBgSlideY);
|
|
3315
|
+
this.renderStyles();
|
|
3316
|
+
} else {
|
|
3317
|
+
this.posY.setTargetPosition(top, delay);
|
|
3318
|
+
this.bgSlideY.setTargetPosition(targetBgSlideY, delay);
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
setLineTransformations(force, delay) {
|
|
3322
|
+
const enableScale = this.lyricPlayer.getEnableScale();
|
|
3323
|
+
const isPlaying = this.lyricPlayer.getIsPlaying();
|
|
3324
|
+
const renderMode = this.isActive ? LyricLineRenderMode.GRADIENT : LyricLineRenderMode.SOLID;
|
|
3325
|
+
const SCALE_ASPECT = enableScale ? 97 : 100;
|
|
3326
|
+
let mainScale = 100;
|
|
3327
|
+
if (!this.isActive && isPlaying) mainScale = SCALE_ASPECT;
|
|
3328
|
+
this.mainLine.setTransform(mainScale, 1, 0, force, delay, renderMode);
|
|
3329
|
+
let bgScale = 100;
|
|
3330
|
+
if (!this.isActive && isPlaying) bgScale = 75;
|
|
3331
|
+
this.bgLine?.setTransform(bgScale, 1, 0, force, delay, renderMode);
|
|
3332
|
+
}
|
|
3333
|
+
update(delta) {
|
|
3334
|
+
if (this.lyricPlayer.getEnableSpring()) {
|
|
3335
|
+
this.posY.update(delta);
|
|
3336
|
+
this.bgSlideY.update(delta);
|
|
3337
|
+
this.renderStyles();
|
|
3338
|
+
}
|
|
3339
|
+
this.mainLine.update(delta);
|
|
3340
|
+
this.bgLine?.update(delta);
|
|
3341
|
+
}
|
|
3342
|
+
rebuildAllLines() {
|
|
3343
|
+
this.mainLine.rebuildElement();
|
|
3344
|
+
this.bgLine?.rebuildElement();
|
|
3345
|
+
}
|
|
3346
|
+
enable(time, shouldPlay) {
|
|
3347
|
+
this.mainLine.enable(time, shouldPlay);
|
|
3348
|
+
this.bgLine?.enable(time, shouldPlay);
|
|
3349
|
+
}
|
|
3350
|
+
disable() {
|
|
3351
|
+
this.mainLine.disable();
|
|
3352
|
+
this.bgLine?.disable();
|
|
3353
|
+
}
|
|
3354
|
+
dispose() {
|
|
3355
|
+
this.mainLine.dispose();
|
|
3356
|
+
this.bgLine?.dispose();
|
|
3357
|
+
}
|
|
3358
|
+
};
|
|
3359
|
+
//#endregion
|
|
3360
|
+
//#region src/lyric-player/dom/lyric-group.ts
|
|
3361
|
+
var LyricLineGroup = class extends LyricLineGroupBase {
|
|
3362
|
+
element;
|
|
3363
|
+
bgWrapper;
|
|
3364
|
+
lastIsActive;
|
|
3365
|
+
constructor(lyricPlayer, mainLine) {
|
|
3366
|
+
super(mainLine);
|
|
3367
|
+
this.lyricPlayer = lyricPlayer;
|
|
3368
|
+
this.element = document.createElement("div");
|
|
3369
|
+
this.element.className = lyric_player_module_default.lyricLineWrapper;
|
|
3370
|
+
this.element.appendChild(mainLine.getElement());
|
|
3371
|
+
this.posY.setPosition(window.innerHeight * 2);
|
|
3372
|
+
lyricPlayer.resizeObserver.observe(this.element);
|
|
3373
|
+
}
|
|
3374
|
+
get isInSight() {
|
|
3375
|
+
const t = this.posY.getCurrentPosition();
|
|
3376
|
+
let h = this.lyricPlayer.lyricGroupSize?.get(this)?.[1];
|
|
3377
|
+
if (h === void 0 || h === 0) h = this.element.clientHeight || 0;
|
|
3378
|
+
const pb = this.lyricPlayer.size[1];
|
|
3379
|
+
const ov = this.lyricPlayer.getOverscanPx();
|
|
3380
|
+
return !(t > pb + h + ov || t < -h - ov);
|
|
3381
|
+
}
|
|
3382
|
+
show() {
|
|
3383
|
+
if (!this.element.parentElement) {
|
|
3384
|
+
const playerEl = this.lyricPlayer.getElement();
|
|
3385
|
+
const groups = this.lyricPlayer.currentLyricGroups;
|
|
3386
|
+
const myIndex = groups.indexOf(this);
|
|
3387
|
+
let referenceNode = null;
|
|
3388
|
+
if (myIndex !== -1) {
|
|
3389
|
+
for (let i = myIndex + 1; i < groups.length; i++) if (groups[i].element.parentElement === playerEl) {
|
|
3390
|
+
referenceNode = groups[i].element;
|
|
3391
|
+
break;
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
playerEl.insertBefore(this.element, referenceNode);
|
|
3395
|
+
this.lyricPlayer.resizeObserver.observe(this.element);
|
|
3396
|
+
}
|
|
3397
|
+
this.mainLine.show();
|
|
3398
|
+
this.bgLine?.show();
|
|
3399
|
+
}
|
|
3400
|
+
hide() {
|
|
3401
|
+
if (this.element.parentElement) {
|
|
3402
|
+
this.lyricPlayer.resizeObserver.unobserve(this.element);
|
|
3403
|
+
this.element.remove();
|
|
3404
|
+
this.mainLine.teardownContent();
|
|
3405
|
+
this.bgLine?.teardownContent();
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
update(delta) {
|
|
3409
|
+
if (this.isInSight) this.show();
|
|
3410
|
+
else this.hide();
|
|
3411
|
+
super.update(delta);
|
|
3412
|
+
}
|
|
3413
|
+
addBgLine(bgLine) {
|
|
3414
|
+
if (this.bgLine) this.bgLine.dispose();
|
|
3415
|
+
if (this.bgWrapper) this.bgWrapper.remove();
|
|
3416
|
+
this.bgLine = bgLine;
|
|
3417
|
+
const bgStartTime = bgLine.getLine().words[0]?.startTime ?? bgLine.getLine().startTime;
|
|
3418
|
+
const mainStartTime = this.mainLine.getLine().words[0]?.startTime ?? this.mainLine.getLine().startTime;
|
|
3419
|
+
this.isBgFirst = bgStartTime < mainStartTime;
|
|
3420
|
+
if (this.mainLine.getLine().isDuet) bgLine.getElement().classList.add(lyric_player_module_default.lyricDuetLine);
|
|
3421
|
+
this.bgWrapper = document.createElement("div");
|
|
3422
|
+
this.bgWrapper.className = lyric_player_module_default.bgWrapper;
|
|
3423
|
+
this.bgWrapper.appendChild(bgLine.getElement());
|
|
3424
|
+
if (!this.lyricPlayer.getAlwaysPostpositionBackground() && this.isBgFirst) {
|
|
3425
|
+
this.bgWrapper.classList.add(lyric_player_module_default.bgWrapperTop);
|
|
3426
|
+
this.element.insertBefore(this.bgWrapper, this.mainLine.getElement());
|
|
3427
|
+
this.bgSlideY.setPosition(80);
|
|
3428
|
+
} else this.element.appendChild(this.bgWrapper);
|
|
3429
|
+
}
|
|
3430
|
+
renderStyles() {
|
|
3431
|
+
const y = this.posY.getCurrentPosition().toFixed(1);
|
|
3432
|
+
this.element.style.transform = `translateY(${y}px)`;
|
|
3433
|
+
this.element.style.opacity = this.opacity.toString();
|
|
3434
|
+
this.element.style.filter = `blur(${Math.min(5, this.blur)}px)`;
|
|
3435
|
+
if (!this.lyricPlayer.getEnableSpring()) this.element.style.transitionDelay = `${this.delay}ms`;
|
|
3436
|
+
if (this.bgWrapper) {
|
|
3437
|
+
if (this.lastIsActive !== this.isActive) {
|
|
3438
|
+
this.lastIsActive = this.isActive;
|
|
3439
|
+
this.bgWrapper.classList.toggle(lyric_player_module_default.bgWrapperActive, this.isActive);
|
|
3440
|
+
}
|
|
3441
|
+
const slideY = this.bgSlideY.getCurrentPosition();
|
|
3442
|
+
const slideYStr = slideY.toFixed(1);
|
|
3443
|
+
const activeProgress = clamp01(1 - Math.abs(slideY) / 80);
|
|
3444
|
+
const scaleStr = (.8 + activeProgress * .2).toFixed(3);
|
|
3445
|
+
this.bgWrapper.style.transform = `translateY(${slideYStr}%) scale(${scaleStr})`;
|
|
3446
|
+
const shouldBgFirst = !this.lyricPlayer.getAlwaysPostpositionBackground() && this.isBgFirst;
|
|
3447
|
+
if (shouldBgFirst) {
|
|
3448
|
+
const currentMarginTop = -(this.bgWrapper.clientHeight || 0) * (1 - activeProgress);
|
|
3449
|
+
this.bgWrapper.style.marginTop = `${currentMarginTop.toFixed(1)}px`;
|
|
3450
|
+
} else this.bgWrapper.style.marginTop = "";
|
|
3451
|
+
const isHidden = slideYStr === (shouldBgFirst ? "80.0" : "-80.0") && !this.isActive;
|
|
3452
|
+
this.bgWrapper.classList.toggle(lyric_player_module_default.bgWrapperHidden, isHidden);
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
dispose() {
|
|
3456
|
+
super.dispose();
|
|
3457
|
+
this.lyricPlayer.resizeObserver.unobserve(this.element);
|
|
3458
|
+
this.element.remove();
|
|
3459
|
+
}
|
|
3460
|
+
};
|
|
3461
|
+
//#endregion
|
|
3462
|
+
//#region src/utils/is-cjk.ts
|
|
3463
|
+
const isCJK = (char) => {
|
|
3464
|
+
return /^[\p{Unified_Ideograph}\u0800-\u9FFC]+$/u.test(char);
|
|
3465
|
+
};
|
|
3466
|
+
//#endregion
|
|
3467
|
+
//#region src/lyric-player/base/line.ts
|
|
3085
3468
|
/**
|
|
3086
3469
|
* 所有标准歌词行的基类
|
|
3087
3470
|
* @internal
|
|
@@ -3092,10 +3475,7 @@ var LyricLineBase = class extends EventTarget {
|
|
|
3092
3475
|
blur = 0;
|
|
3093
3476
|
opacity = 1;
|
|
3094
3477
|
delay = 0;
|
|
3095
|
-
lineTransforms = {
|
|
3096
|
-
posY: new Spring(0),
|
|
3097
|
-
scale: new Spring(100)
|
|
3098
|
-
};
|
|
3478
|
+
lineTransforms = { scale: new Spring(100) };
|
|
3099
3479
|
/**
|
|
3100
3480
|
* 用于 CJK 词语边界检测的分词器
|
|
3101
3481
|
*/
|
|
@@ -3105,9 +3485,7 @@ var LyricLineBase = class extends EventTarget {
|
|
|
3105
3485
|
* 用于正确处理 emoji、复合字符等
|
|
3106
3486
|
*/
|
|
3107
3487
|
static graphemeSegmenter = typeof Intl !== "undefined" && Intl.Segmenter ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
|
|
3108
|
-
|
|
3109
|
-
setTransform(top = this.top, scale = this.scale, opacity = this.opacity, blur = this.blur, _force = false, delay = 0, _mode = 0) {
|
|
3110
|
-
this.top = top;
|
|
3488
|
+
setTransform(scale = this.scale, opacity = this.opacity, blur = this.blur, _force = false, delay = 0, _mode = LyricLineRenderMode.SOLID) {
|
|
3111
3489
|
this.scale = scale;
|
|
3112
3490
|
this.opacity = opacity;
|
|
3113
3491
|
this.blur = blur;
|
|
@@ -3333,7 +3711,7 @@ var LineBalancer = class {
|
|
|
3333
3711
|
const marginLeft = Number.parseFloat(elStyle.marginLeft) || 0;
|
|
3334
3712
|
const marginRight = Number.parseFloat(elStyle.marginRight) || 0;
|
|
3335
3713
|
childInfos.push({
|
|
3336
|
-
width:
|
|
3714
|
+
width: clampPositive(rect.width + marginLeft + marginRight),
|
|
3337
3715
|
text: el.textContent ?? "",
|
|
3338
3716
|
isSpace: false
|
|
3339
3717
|
});
|
|
@@ -3537,34 +3915,34 @@ function matrix4ToCSS(m, fractionDigits = 4) {
|
|
|
3537
3915
|
}
|
|
3538
3916
|
//#endregion
|
|
3539
3917
|
//#region src/lyric-player/dom/lyric-line.ts
|
|
3540
|
-
const ANIMATION_FRAME_QUANTITY
|
|
3541
|
-
const norNum
|
|
3542
|
-
const EMP_EASING_MID
|
|
3543
|
-
const beginNum
|
|
3544
|
-
const endNum
|
|
3545
|
-
const bezIn
|
|
3546
|
-
const bezOut
|
|
3547
|
-
const makeEmpEasing
|
|
3548
|
-
return (x) => x < mid ? bezIn
|
|
3549
|
-
};
|
|
3550
|
-
function generateFadeGradient
|
|
3551
|
-
const totalAspect = 2 + width + padding;
|
|
3552
|
-
const widthInTotal = width / totalAspect;
|
|
3918
|
+
const ANIMATION_FRAME_QUANTITY = 32;
|
|
3919
|
+
const norNum = (min, max) => (x) => clamp01((x - min) / (max - min));
|
|
3920
|
+
const EMP_EASING_MID = .5;
|
|
3921
|
+
const beginNum = norNum(0, EMP_EASING_MID);
|
|
3922
|
+
const endNum = norNum(EMP_EASING_MID, 1);
|
|
3923
|
+
const bezIn = (0, bezier_easing.default)(.2, .4, .58, 1);
|
|
3924
|
+
const bezOut = (0, bezier_easing.default)(.3, 0, .58, 1);
|
|
3925
|
+
const makeEmpEasing = (mid) => {
|
|
3926
|
+
return (x) => x < mid ? bezIn(beginNum(x)) : 1 - bezOut(endNum(x));
|
|
3927
|
+
};
|
|
3928
|
+
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))") {
|
|
3929
|
+
const totalAspect = 2 + width + padding;
|
|
3930
|
+
const widthInTotal = width / totalAspect;
|
|
3553
3931
|
const leftPos = (1 - widthInTotal) / 2;
|
|
3554
3932
|
return [`linear-gradient(to right,${bright} ${leftPos * 100}%,${dark} ${(leftPos + widthInTotal) * 100}%)`, totalAspect];
|
|
3555
3933
|
}
|
|
3556
|
-
var RawLyricLineMouseEvent
|
|
3934
|
+
var RawLyricLineMouseEvent = class extends MouseEvent {
|
|
3557
3935
|
constructor(line, event) {
|
|
3558
3936
|
super(event.type, event);
|
|
3559
3937
|
this.line = line;
|
|
3560
3938
|
}
|
|
3561
3939
|
};
|
|
3562
|
-
var LyricLineEl
|
|
3940
|
+
var LyricLineEl = class extends LyricLineBase {
|
|
3563
3941
|
element = document.createElement("div");
|
|
3564
3942
|
splittedWords = [];
|
|
3565
3943
|
built = false;
|
|
3566
3944
|
lineSize = [0, 0];
|
|
3567
|
-
renderMode =
|
|
3945
|
+
renderMode = LyricLineRenderMode.SOLID;
|
|
3568
3946
|
currentBrightAlpha = 1;
|
|
3569
3947
|
currentDarkAlpha = .2;
|
|
3570
3948
|
targetBrightAlpha = 1;
|
|
@@ -3585,12 +3963,9 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3585
3963
|
super();
|
|
3586
3964
|
this.lyricPlayer = lyricPlayer;
|
|
3587
3965
|
this.lyricLine = lyricLine;
|
|
3588
|
-
this._prevParentEl = lyricPlayer.getElement();
|
|
3589
|
-
lyricPlayer.resizeObserver.observe(this.element);
|
|
3590
3966
|
this.element.setAttribute("class", lyric_player_module_default.lyricLine);
|
|
3591
3967
|
if (this.lyricLine.isBG) this.element.classList.add(lyric_player_module_default.lyricBgLine);
|
|
3592
3968
|
if (this.lyricLine.isDuet) this.element.classList.add(lyric_player_module_default.lyricDuetLine);
|
|
3593
|
-
this.lineTransforms.posY.setPosition(window.innerHeight * 2);
|
|
3594
3969
|
this.element.appendChild(document.createElement("div"));
|
|
3595
3970
|
this.element.appendChild(document.createElement("div"));
|
|
3596
3971
|
this.element.appendChild(document.createElement("div"));
|
|
@@ -3605,7 +3980,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3605
3980
|
}
|
|
3606
3981
|
listenersMap = /* @__PURE__ */ new Map();
|
|
3607
3982
|
onMouseEvent = (e) => {
|
|
3608
|
-
const wrapped = new RawLyricLineMouseEvent
|
|
3983
|
+
const wrapped = new RawLyricLineMouseEvent(this, e);
|
|
3609
3984
|
for (const listener of this.listenersMap.get(e.type) ?? []) listener.call(this, wrapped);
|
|
3610
3985
|
if (!this.dispatchEvent(wrapped) || wrapped.defaultPrevented) {
|
|
3611
3986
|
e.preventDefault();
|
|
@@ -3641,30 +4016,28 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3641
4016
|
return true;
|
|
3642
4017
|
}
|
|
3643
4018
|
isEnabled = false;
|
|
3644
|
-
async enable(maskAnimationTime = this.
|
|
4019
|
+
async enable(maskAnimationTime = this.lyricPlayer.getCurrentTime(), shouldPlay = this.lyricPlayer.getIsPlaying()) {
|
|
3645
4020
|
this.isEnabled = true;
|
|
3646
4021
|
this.element.classList.add(lyric_player_module_default.active);
|
|
3647
4022
|
const main = this.element.children[0];
|
|
3648
|
-
const relativeTime =
|
|
3649
|
-
const actualMaskTime = maskAnimationTime === this.lyricLine.startTime ? this.lyricPlayer.getCurrentTime() : maskAnimationTime;
|
|
3650
|
-
const maskRelativeTime = Math.max(0, actualMaskTime - this.lyricLine.startTime);
|
|
4023
|
+
const relativeTime = clampPositive(maskAnimationTime - this.lyricLine.startTime);
|
|
3651
4024
|
for (const word of this.splittedWords) {
|
|
3652
4025
|
for (const a of word.elementAnimations) {
|
|
3653
4026
|
a.currentTime = relativeTime;
|
|
3654
4027
|
a.playbackRate = 1;
|
|
3655
4028
|
const timing = a.effect?.getComputedTiming();
|
|
3656
|
-
const duration = timing?.duration
|
|
3657
|
-
const endTime = (timing?.delay
|
|
4029
|
+
const duration = Number(timing?.duration ?? 0);
|
|
4030
|
+
const endTime = Number(timing?.delay ?? 0) + duration;
|
|
3658
4031
|
if (shouldPlay && relativeTime < endTime) a.play();
|
|
3659
4032
|
else a.pause();
|
|
3660
4033
|
}
|
|
3661
4034
|
for (const a of word.maskAnimations) {
|
|
3662
|
-
const t = Math.min(this.totalDuration,
|
|
4035
|
+
const t = Math.min(this.totalDuration, relativeTime);
|
|
3663
4036
|
a.currentTime = t;
|
|
3664
4037
|
a.playbackRate = 1;
|
|
3665
4038
|
const timing = a.effect?.getComputedTiming();
|
|
3666
|
-
const duration = timing?.duration
|
|
3667
|
-
const endTime = (timing?.delay
|
|
4039
|
+
const duration = Number(timing?.duration ?? 0);
|
|
4040
|
+
const endTime = Number(timing?.delay ?? 0) + duration;
|
|
3668
4041
|
if (shouldPlay && t < endTime) a.play();
|
|
3669
4042
|
else a.pause();
|
|
3670
4043
|
}
|
|
@@ -3674,7 +4047,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3674
4047
|
disable() {
|
|
3675
4048
|
this.isEnabled = false;
|
|
3676
4049
|
this.element.classList.remove(lyric_player_module_default.active);
|
|
3677
|
-
this.renderMode =
|
|
4050
|
+
this.renderMode = LyricLineRenderMode.SOLID;
|
|
3678
4051
|
const main = this.element.children[0];
|
|
3679
4052
|
for (const word of this.splittedWords) {
|
|
3680
4053
|
for (const a of word.elementAnimations) if (a.id === "float-word" || a.id.includes("emphasize-word-float-only")) {
|
|
@@ -3715,7 +4088,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3715
4088
|
setMaskAnimationState(maskAnimationTime = 0) {
|
|
3716
4089
|
const t = maskAnimationTime - this.lyricLine.startTime;
|
|
3717
4090
|
for (const word of this.splittedWords) for (const a of word.maskAnimations) {
|
|
3718
|
-
a.currentTime =
|
|
4091
|
+
a.currentTime = clamp(t, 0, this.totalDuration);
|
|
3719
4092
|
a.playbackRate = 1;
|
|
3720
4093
|
if (t >= 0 && t < this.totalDuration) a.play();
|
|
3721
4094
|
else a.pause();
|
|
@@ -3724,34 +4097,18 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3724
4097
|
getLine() {
|
|
3725
4098
|
return this.lyricLine;
|
|
3726
4099
|
}
|
|
3727
|
-
_prevParentEl;
|
|
3728
4100
|
lastStyle = "";
|
|
3729
4101
|
show() {
|
|
3730
|
-
if (!this.element.parentElement) {
|
|
3731
|
-
this._prevParentEl.appendChild(this.element);
|
|
3732
|
-
this.lyricPlayer.resizeObserver.observe(this.element);
|
|
3733
|
-
}
|
|
3734
4102
|
if (!this.built) {
|
|
3735
4103
|
this.rebuildElement();
|
|
3736
4104
|
this.built = true;
|
|
3737
4105
|
this.updateMaskImageSync();
|
|
3738
4106
|
}
|
|
3739
|
-
this.rebuildStyle();
|
|
3740
|
-
}
|
|
3741
|
-
hide() {
|
|
3742
|
-
if (this.element.parentElement) {
|
|
3743
|
-
this._prevParentEl.removeChild(this.element);
|
|
3744
|
-
this.lyricPlayer.resizeObserver.unobserve(this.element);
|
|
3745
|
-
}
|
|
3746
|
-
if (this.built) {
|
|
3747
|
-
this.disposeElements();
|
|
3748
|
-
this.built = false;
|
|
3749
|
-
}
|
|
3750
4107
|
}
|
|
3751
4108
|
rebuildStyle() {
|
|
3752
4109
|
let style = "";
|
|
3753
|
-
style += `transform:
|
|
3754
|
-
if (!this.lyricPlayer.getEnableSpring()
|
|
4110
|
+
style += `transform: scale(${(this.lineTransforms.scale.getCurrentPosition() / 100).toFixed(4)});`;
|
|
4111
|
+
if (!this.lyricPlayer.getEnableSpring()) style += `transition-delay:${this.delay}ms;`;
|
|
3755
4112
|
style += `filter:blur(${Math.min(5, this.blur)}px);`;
|
|
3756
4113
|
if (style !== this.lastStyle) {
|
|
3757
4114
|
this.lastStyle = style;
|
|
@@ -3906,7 +4263,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3906
4263
|
return a;
|
|
3907
4264
|
}
|
|
3908
4265
|
initEmphasizeAnimation(word, characterElements, duration, delay, rubyCharCount) {
|
|
3909
|
-
const de =
|
|
4266
|
+
const de = clampPositive(delay);
|
|
3910
4267
|
let du = Math.max(1e3, duration);
|
|
3911
4268
|
const anchorCharCount = rubyCharCount > 0 ? rubyCharCount : Math.max(1, characterElements.length);
|
|
3912
4269
|
let result = [];
|
|
@@ -3924,12 +4281,12 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3924
4281
|
amount = Math.min(1.2, amount);
|
|
3925
4282
|
blur = Math.min(.8, blur);
|
|
3926
4283
|
const animateDu = Number.isFinite(du) ? du : 0;
|
|
3927
|
-
const empEasing = makeEmpEasing
|
|
4284
|
+
const empEasing = makeEmpEasing(EMP_EASING_MID);
|
|
3928
4285
|
result = characterElements.flatMap((el, i, arr) => {
|
|
3929
4286
|
const wordDe = de + du / 2.5 / anchorCharCount * i;
|
|
3930
4287
|
const result = [];
|
|
3931
|
-
const frames = new Array(ANIMATION_FRAME_QUANTITY
|
|
3932
|
-
const x = (j + 1) / ANIMATION_FRAME_QUANTITY
|
|
4288
|
+
const frames = new Array(ANIMATION_FRAME_QUANTITY).fill(0).map((_, j) => {
|
|
4289
|
+
const x = (j + 1) / ANIMATION_FRAME_QUANTITY;
|
|
3933
4290
|
const transX = empEasing(x);
|
|
3934
4291
|
const glowLevel = empEasing(x) * blur;
|
|
3935
4292
|
const mat = scaleMatrix4(createMatrix4(), 1 + transX * .1 * amount);
|
|
@@ -3954,8 +4311,8 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
3954
4311
|
};
|
|
3955
4312
|
glow.pause();
|
|
3956
4313
|
result.push(glow);
|
|
3957
|
-
const floatFrame = new Array(ANIMATION_FRAME_QUANTITY
|
|
3958
|
-
const x = (j + 1) / ANIMATION_FRAME_QUANTITY
|
|
4314
|
+
const floatFrame = new Array(ANIMATION_FRAME_QUANTITY).fill(0).map((_, j) => {
|
|
4315
|
+
const x = (j + 1) / ANIMATION_FRAME_QUANTITY;
|
|
3959
4316
|
let y = Math.sin(x * Math.PI);
|
|
3960
4317
|
if (this.lyricLine.isBG) y *= 2;
|
|
3961
4318
|
return {
|
|
@@ -4014,7 +4371,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4014
4371
|
word.width = wordEl.clientWidth;
|
|
4015
4372
|
word.height = wordEl.clientHeight;
|
|
4016
4373
|
const fadeWidth = word.height * this.lyricPlayer.getWordFadeWidth();
|
|
4017
|
-
const [maskImage, totalAspect] = generateFadeGradient
|
|
4374
|
+
const [maskImage, totalAspect] = generateFadeGradient(fadeWidth / word.width);
|
|
4018
4375
|
const totalAspectStr = `${totalAspect * 100}% 100%`;
|
|
4019
4376
|
if (this.lyricPlayer.supportMaskImage) {
|
|
4020
4377
|
wordEl.style.maskImage = maskImage;
|
|
@@ -4035,12 +4392,12 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4035
4392
|
}
|
|
4036
4393
|
}
|
|
4037
4394
|
generateWebAnimationBasedMaskImage() {
|
|
4038
|
-
const totalFadeDuration = Math.max(this.splittedWords.
|
|
4395
|
+
const totalFadeDuration = Math.max(0, ...this.splittedWords.map((w) => w.endTime), this.lyricLine.endTime) - this.lyricLine.startTime;
|
|
4039
4396
|
this.splittedWords.forEach((word, i) => {
|
|
4040
4397
|
const wordEl = word.mainElement;
|
|
4041
4398
|
if (wordEl) {
|
|
4042
4399
|
const fadeWidth = word.height * this.lyricPlayer.getWordFadeWidth();
|
|
4043
|
-
const [maskImage, totalAspect] = generateFadeGradient
|
|
4400
|
+
const [maskImage, totalAspect] = generateFadeGradient(fadeWidth / (word.width + word.padding * 2));
|
|
4044
4401
|
const totalAspectStr = `${totalAspect * 100}% 100%`;
|
|
4045
4402
|
if (this.lyricPlayer.supportMaskImage) {
|
|
4046
4403
|
wordEl.style.maskImage = maskImage;
|
|
@@ -4055,7 +4412,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4055
4412
|
}
|
|
4056
4413
|
const widthBeforeSelf = this.splittedWords.slice(0, i).reduce((a, b) => a + b.width, 0) + (this.splittedWords[0] ? fadeWidth : 0);
|
|
4057
4414
|
const minOffset = -(word.width + word.padding * 2 + fadeWidth);
|
|
4058
|
-
const clampOffset = (x) =>
|
|
4415
|
+
const clampOffset = (x) => clamp(x, minOffset, 0);
|
|
4059
4416
|
let curPos = -widthBeforeSelf - word.width - word.padding - fadeWidth;
|
|
4060
4417
|
let timeOffset = 0;
|
|
4061
4418
|
const frames = [];
|
|
@@ -4063,7 +4420,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4063
4420
|
let lastTime = 0;
|
|
4064
4421
|
const pushFrame = () => {
|
|
4065
4422
|
const moveOffset = curPos - lastPos;
|
|
4066
|
-
const time =
|
|
4423
|
+
const time = clamp01(timeOffset);
|
|
4067
4424
|
const duration = time - lastTime;
|
|
4068
4425
|
const d = Math.abs(duration / moveOffset);
|
|
4069
4426
|
if (curPos > minOffset && lastPos < minOffset) {
|
|
@@ -4103,7 +4460,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4103
4460
|
lastTimeStamp = curTimeStamp;
|
|
4104
4461
|
}
|
|
4105
4462
|
{
|
|
4106
|
-
const fadeDuration =
|
|
4463
|
+
const fadeDuration = clampPositive(otherWord.endTime - otherWord.startTime);
|
|
4107
4464
|
const rubySegments = this.getRubySegments(otherWord);
|
|
4108
4465
|
const rubyCharCount = rubySegments.reduce((total, ruby) => total + ruby.word.length, 0);
|
|
4109
4466
|
if (rubyCharCount > 0) {
|
|
@@ -4119,7 +4476,7 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4119
4476
|
timeOffset += rubyStaticDuration / totalFadeDuration;
|
|
4120
4477
|
if (rubyStaticDuration > 0) pushFrame();
|
|
4121
4478
|
lastTimeStamp = rubyStartStamp;
|
|
4122
|
-
const perCharDuration =
|
|
4479
|
+
const perCharDuration = clampPositive(rubyEnd - rubyStart) / ruby.word.length;
|
|
4123
4480
|
for (let rubyCharIndex = 0; rubyCharIndex < ruby.word.length; rubyCharIndex++) {
|
|
4124
4481
|
timeOffset += perCharDuration / totalFadeDuration;
|
|
4125
4482
|
curPos += widthPerChar;
|
|
@@ -4169,10 +4526,10 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4169
4526
|
return this.element;
|
|
4170
4527
|
}
|
|
4171
4528
|
updateMaskAlphaTargets(scale) {
|
|
4172
|
-
const factor =
|
|
4529
|
+
const factor = clamp01((scale - .97) / .03);
|
|
4173
4530
|
const dynamicDarkAlpha = factor * .2 + .2;
|
|
4174
4531
|
const dynamicBrightAlpha = factor * .8 + .2;
|
|
4175
|
-
if (this.renderMode ===
|
|
4532
|
+
if (this.renderMode === LyricLineRenderMode.SOLID) {
|
|
4176
4533
|
this.targetBrightAlpha = dynamicDarkAlpha;
|
|
4177
4534
|
this.targetDarkAlpha = dynamicDarkAlpha;
|
|
4178
4535
|
} else {
|
|
@@ -4194,25 +4551,19 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4194
4551
|
this.element.style.setProperty("--bright-mask-alpha", this.currentBrightAlpha.toFixed(3));
|
|
4195
4552
|
this.element.style.setProperty("--dark-mask-alpha", this.currentDarkAlpha.toFixed(3));
|
|
4196
4553
|
}
|
|
4197
|
-
setTransform(
|
|
4198
|
-
super.setTransform(
|
|
4554
|
+
setTransform(scale = this.scale, opacity = 1, blur = 0, force = false, delay = 0, mode = LyricLineRenderMode.SOLID) {
|
|
4555
|
+
super.setTransform(scale, opacity, blur, force, delay);
|
|
4199
4556
|
this.renderMode = mode;
|
|
4200
|
-
const beforeInSight = this.isInSight;
|
|
4201
4557
|
const enableSpring = this.lyricPlayer.getEnableSpring();
|
|
4202
|
-
this.top =
|
|
4558
|
+
this.top = 0;
|
|
4203
4559
|
this.scale = scale;
|
|
4204
4560
|
this.delay = delay * 1e3 | 0;
|
|
4205
4561
|
const main = this.element.children[0];
|
|
4206
4562
|
main.style.opacity = `${opacity}`;
|
|
4207
4563
|
if (force || !enableSpring) {
|
|
4208
4564
|
this.blur = Math.min(32, blur);
|
|
4209
|
-
this.lineTransforms.posY.setPosition(top);
|
|
4210
4565
|
this.lineTransforms.scale.setPosition(scale);
|
|
4211
|
-
|
|
4212
|
-
const afterInSight = this.isInSight;
|
|
4213
|
-
if (beforeInSight || afterInSight) this.show();
|
|
4214
|
-
else this.hide();
|
|
4215
|
-
} else this.rebuildStyle();
|
|
4566
|
+
this.rebuildStyle();
|
|
4216
4567
|
const currentScale = this.lineTransforms.scale.getCurrentPosition();
|
|
4217
4568
|
this.updateMaskAlphaTargets(currentScale / 100);
|
|
4218
4569
|
this.currentBrightAlpha = this.targetBrightAlpha;
|
|
@@ -4220,21 +4571,18 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4220
4571
|
this.element.style.setProperty("--bright-mask-alpha", String(this.currentBrightAlpha));
|
|
4221
4572
|
this.element.style.setProperty("--dark-mask-alpha", String(this.currentDarkAlpha));
|
|
4222
4573
|
} else {
|
|
4223
|
-
this.lineTransforms.posY.setTargetPosition(top, delay);
|
|
4224
4574
|
this.lineTransforms.scale.setTargetPosition(scale);
|
|
4225
4575
|
if (this.blur !== Math.min(5, blur)) {
|
|
4226
4576
|
this.blur = Math.min(5, blur);
|
|
4227
|
-
|
|
4228
|
-
this.element.style.filter = `blur(${roundedBlur}px)`;
|
|
4577
|
+
this.element.style.filter = `blur(${blur.toFixed(3)}px)`;
|
|
4229
4578
|
}
|
|
4230
4579
|
}
|
|
4231
4580
|
}
|
|
4232
4581
|
update(delta = 0) {
|
|
4233
4582
|
if (!this.lyricPlayer.getEnableSpring()) return;
|
|
4234
|
-
this.lineTransforms.posY.update(delta);
|
|
4235
4583
|
this.lineTransforms.scale.update(delta);
|
|
4236
|
-
|
|
4237
|
-
|
|
4584
|
+
this.rebuildStyle();
|
|
4585
|
+
if (!this.built) return;
|
|
4238
4586
|
const currentScale = this.lineTransforms.scale.getCurrentPosition() / 100;
|
|
4239
4587
|
this.updateMaskAlphaTargets(currentScale);
|
|
4240
4588
|
this.applyAlphaToDom(delta);
|
|
@@ -4242,13 +4590,11 @@ var LyricLineEl$1 = class extends LyricLineBase {
|
|
|
4242
4590
|
_getDebugTargetPos() {
|
|
4243
4591
|
return `[位移: ${this.top}; 缩放: ${this.scale}; 延时: ${this.delay}]`;
|
|
4244
4592
|
}
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
const ov = this.lyricPlayer.getOverscanPx();
|
|
4251
|
-
return !(t > pb + h + ov || b < -h - ov);
|
|
4593
|
+
teardownContent() {
|
|
4594
|
+
if (this.built) {
|
|
4595
|
+
this.disposeElements();
|
|
4596
|
+
this.built = false;
|
|
4597
|
+
}
|
|
4252
4598
|
}
|
|
4253
4599
|
disposeElements() {
|
|
4254
4600
|
this.balancer?.reset();
|
|
@@ -4296,7 +4642,7 @@ var LyricLineMouseEvent = class extends MouseEvent {
|
|
|
4296
4642
|
* 尽可能贴切 Apple Music for iPad 的歌词效果设计,且做了力所能及的优化措施
|
|
4297
4643
|
*/
|
|
4298
4644
|
var DomLyricPlayer = class extends LyricPlayerBase {
|
|
4299
|
-
|
|
4645
|
+
currentLyricGroups = [];
|
|
4300
4646
|
onResize() {
|
|
4301
4647
|
const computedStyles = getComputedStyle(this.element);
|
|
4302
4648
|
this._baseFontSize = Number.parseFloat(computedStyles.fontSize);
|
|
@@ -4333,7 +4679,10 @@ var DomLyricPlayer = class extends LyricPlayerBase {
|
|
|
4333
4679
|
rebuildStyle() {}
|
|
4334
4680
|
setWordFadeWidth(value = .5) {
|
|
4335
4681
|
super.setWordFadeWidth(value);
|
|
4336
|
-
for (const
|
|
4682
|
+
for (const group of this.currentLyricGroups) {
|
|
4683
|
+
group.mainLine.updateMaskImageSync();
|
|
4684
|
+
group.bgLine?.updateMaskImageSync();
|
|
4685
|
+
}
|
|
4337
4686
|
}
|
|
4338
4687
|
/**
|
|
4339
4688
|
* 设置当前播放歌词,要注意传入后这个数组内的信息不得修改,否则会发生错误
|
|
@@ -4345,943 +4694,75 @@ var DomLyricPlayer = class extends LyricPlayerBase {
|
|
|
4345
4694
|
if (this.hasDuetLine) this.element.classList.add(lyric_player_module_default.hasDuetLine);
|
|
4346
4695
|
else this.element.classList.remove(lyric_player_module_default.hasDuetLine);
|
|
4347
4696
|
if (!this.supportMaskImage) this.element.style.setProperty("--amll-player-time", `${initialTime}`);
|
|
4348
|
-
for (const
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4697
|
+
for (const group of this.currentLyricGroups) {
|
|
4698
|
+
const linesToDispose = [group.mainLine, group.bgLine].filter((line) => !!line);
|
|
4699
|
+
for (const line of linesToDispose) {
|
|
4700
|
+
line.removeMouseEventListener("click", this.onLineClickedHandler);
|
|
4701
|
+
line.removeMouseEventListener("contextmenu", this.onLineClickedHandler);
|
|
4702
|
+
}
|
|
4703
|
+
group.dispose();
|
|
4352
4704
|
}
|
|
4353
|
-
this.
|
|
4354
|
-
|
|
4705
|
+
this.currentLyricGroups = [];
|
|
4706
|
+
let currentGroup = null;
|
|
4707
|
+
for (let i = 0; i < this.processedLines.length; i++) {
|
|
4708
|
+
const line = this.processedLines[i];
|
|
4709
|
+
const lineEl = new LyricLineEl(this, line);
|
|
4355
4710
|
lineEl.addMouseEventListener("click", this.onLineClickedHandler);
|
|
4356
4711
|
lineEl.addMouseEventListener("contextmenu", this.onLineClickedHandler);
|
|
4357
4712
|
this.lyricLinesIndexes.set(lineEl, i);
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4713
|
+
if (!line.isBG || !currentGroup) {
|
|
4714
|
+
currentGroup = new LyricLineGroup(this, lineEl);
|
|
4715
|
+
this.currentLyricGroups.push(currentGroup);
|
|
4716
|
+
this.lyricGroupElementMap.set(currentGroup.element, currentGroup);
|
|
4717
|
+
} else currentGroup.addBgLine(lineEl);
|
|
4718
|
+
}
|
|
4361
4719
|
this.setLinePosXSpringParams({});
|
|
4362
4720
|
this.setLinePosYSpringParams({});
|
|
4363
4721
|
this.setLineScaleSpringParams({});
|
|
4722
|
+
this.setCurrentTime(initialTime, true);
|
|
4364
4723
|
this.calcLayout(true);
|
|
4365
4724
|
this.update(0);
|
|
4366
4725
|
}
|
|
4367
4726
|
pause() {
|
|
4368
4727
|
super.pause();
|
|
4369
|
-
this.element.classList.remove(
|
|
4728
|
+
this.element.classList.remove(lyric_player_module_default.playing);
|
|
4370
4729
|
this.interludeDots.pause();
|
|
4371
|
-
for (const
|
|
4730
|
+
for (const group of this.currentLyricGroups) {
|
|
4731
|
+
group.mainLine.pause();
|
|
4732
|
+
group.bgLine?.pause();
|
|
4733
|
+
}
|
|
4372
4734
|
}
|
|
4373
4735
|
resume() {
|
|
4374
4736
|
super.resume();
|
|
4375
|
-
this.element.classList.add(
|
|
4737
|
+
this.element.classList.add(lyric_player_module_default.playing);
|
|
4376
4738
|
this.interludeDots.resume();
|
|
4377
|
-
for (const
|
|
4739
|
+
for (const group of this.currentLyricGroups) {
|
|
4740
|
+
group.mainLine.resume();
|
|
4741
|
+
group.bgLine?.resume();
|
|
4742
|
+
}
|
|
4378
4743
|
}
|
|
4379
4744
|
update(delta = 0) {
|
|
4380
|
-
if (!this.initialLayoutFinished) return;
|
|
4745
|
+
if (!this.timelineState.initialLayoutFinished) return;
|
|
4381
4746
|
super.update(delta);
|
|
4382
|
-
if (!this.supportMaskImage) this.element.style.setProperty("--amll-player-time", `${this.currentTime}`);
|
|
4747
|
+
if (!this.supportMaskImage) this.element.style.setProperty("--amll-player-time", `${this.timelineState.currentTime}`);
|
|
4383
4748
|
if (!this.isPageVisible) return;
|
|
4384
4749
|
const deltaS = delta / 1e3;
|
|
4385
|
-
for (const
|
|
4750
|
+
for (const group of this.currentLyricGroups) group.update(deltaS);
|
|
4386
4751
|
}
|
|
4387
4752
|
dispose() {
|
|
4388
4753
|
super.dispose();
|
|
4389
4754
|
this.element.remove();
|
|
4390
|
-
for (const
|
|
4755
|
+
for (const group of this.currentLyricGroups) group.dispose();
|
|
4391
4756
|
this.bottomLine.dispose();
|
|
4392
4757
|
this.interludeDots.dispose();
|
|
4393
4758
|
}
|
|
4394
4759
|
};
|
|
4395
4760
|
//#endregion
|
|
4396
|
-
//#region src/utils/debounce.ts
|
|
4397
|
-
function debounce(cb, wait = 20) {
|
|
4398
|
-
let h;
|
|
4399
|
-
const callable = (...args) => {
|
|
4400
|
-
clearTimeout(h);
|
|
4401
|
-
h = setTimeout(() => cb(...args), wait);
|
|
4402
|
-
};
|
|
4403
|
-
return callable;
|
|
4404
|
-
}
|
|
4405
|
-
//#endregion
|
|
4406
|
-
//#region src/lyric-player/dom-slim/index.module.css
|
|
4407
|
-
var index_module_default = {
|
|
4408
|
-
"active": "KxF9Iq_active",
|
|
4409
|
-
"duet": "KxF9Iq_duet",
|
|
4410
|
-
"enabled": "KxF9Iq_enabled",
|
|
4411
|
-
"hasDuetLine": "KxF9Iq_hasDuetLine",
|
|
4412
|
-
"interludeDots": "KxF9Iq_interludeDots",
|
|
4413
|
-
"lyricBgLine": "KxF9Iq_lyricBgLine",
|
|
4414
|
-
"lyricDuetLine": "KxF9Iq_lyricDuetLine",
|
|
4415
|
-
"lyricLine": "KxF9Iq_lyricLine",
|
|
4416
|
-
"lyricMainLine": "KxF9Iq_lyricMainLine",
|
|
4417
|
-
"lyricSubLine": "KxF9Iq_lyricSubLine",
|
|
4418
|
-
"romanWord": "KxF9Iq_romanWord",
|
|
4419
|
-
"rubyWord": "KxF9Iq_rubyWord",
|
|
4420
|
-
"tmpDisableTransition": "KxF9Iq_tmpDisableTransition",
|
|
4421
|
-
"wordBody": "KxF9Iq_wordBody",
|
|
4422
|
-
"wordWithRuby": "KxF9Iq_wordWithRuby"
|
|
4423
|
-
};
|
|
4424
|
-
//#endregion
|
|
4425
|
-
//#region src/utils/mutex.ts
|
|
4426
|
-
function mutexifyFunction(func) {
|
|
4427
|
-
const awaitingTasks = [];
|
|
4428
|
-
function processNextTask() {
|
|
4429
|
-
const task = awaitingTasks[0];
|
|
4430
|
-
if (!task) return;
|
|
4431
|
-
func(...task.args).then((value) => {
|
|
4432
|
-
task.resolve(value);
|
|
4433
|
-
}).catch((reason) => {
|
|
4434
|
-
task.reject(reason);
|
|
4435
|
-
}).finally(() => {
|
|
4436
|
-
awaitingTasks.shift();
|
|
4437
|
-
if (awaitingTasks.length > 0) processNextTask();
|
|
4438
|
-
});
|
|
4439
|
-
}
|
|
4440
|
-
return ((...args) => {
|
|
4441
|
-
return new Promise((resolve, reject) => {
|
|
4442
|
-
awaitingTasks.push({
|
|
4443
|
-
resolve,
|
|
4444
|
-
reject,
|
|
4445
|
-
args
|
|
4446
|
-
});
|
|
4447
|
-
if (awaitingTasks.length === 1) processNextTask();
|
|
4448
|
-
});
|
|
4449
|
-
});
|
|
4450
|
-
}
|
|
4451
|
-
//#endregion
|
|
4452
|
-
//#region src/lyric-player/dom-slim/lyric-line.ts
|
|
4453
|
-
const ANIMATION_FRAME_QUANTITY = 32;
|
|
4454
|
-
const norNum = (min, max) => (x) => Math.min(1, Math.max(0, (x - min) / (max - min)));
|
|
4455
|
-
const EMP_EASING_MID = .5;
|
|
4456
|
-
const beginNum = norNum(0, EMP_EASING_MID);
|
|
4457
|
-
const endNum = norNum(EMP_EASING_MID, 1);
|
|
4458
|
-
const bezIn = (0, bezier_easing.default)(.2, .4, .58, 1);
|
|
4459
|
-
const bezOut = (0, bezier_easing.default)(.3, 0, .58, 1);
|
|
4460
|
-
const makeEmpEasing = (mid) => {
|
|
4461
|
-
return (x) => x < mid ? bezIn(beginNum(x)) : 1 - bezOut(endNum(x));
|
|
4462
|
-
};
|
|
4463
|
-
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))") {
|
|
4464
|
-
const totalAspect = 2 + width + padding;
|
|
4465
|
-
const widthInTotal = width / totalAspect;
|
|
4466
|
-
const leftPos = (1 - widthInTotal) / 2;
|
|
4467
|
-
return [`linear-gradient(to right,${bright} ${leftPos * 100}%,${dark} ${(leftPos + widthInTotal) * 100}%)`, totalAspect];
|
|
4468
|
-
}
|
|
4469
|
-
var RawLyricLineMouseEvent = class extends MouseEvent {
|
|
4470
|
-
constructor(line, event) {
|
|
4471
|
-
super(event.type, event);
|
|
4472
|
-
this.line = line;
|
|
4473
|
-
}
|
|
4474
|
-
};
|
|
4475
|
-
function getScaleFromTransform(transform) {
|
|
4476
|
-
const match = transform.match(/matrix\(([^)]+)\)/);
|
|
4477
|
-
if (match) {
|
|
4478
|
-
const values = match[1].split(", ");
|
|
4479
|
-
return (Number.parseFloat(values[0]) + Number.parseFloat(values[3])) / 2;
|
|
4480
|
-
}
|
|
4481
|
-
return 1;
|
|
4482
|
-
}
|
|
4483
|
-
var LyricLineEl = class extends LyricLineBase {
|
|
4484
|
-
element = document.createElement("div");
|
|
4485
|
-
splittedWords = [];
|
|
4486
|
-
lineSize = [0, 0];
|
|
4487
|
-
constructor(lyricPlayer, lyricLine = {
|
|
4488
|
-
words: [],
|
|
4489
|
-
translatedLyric: "",
|
|
4490
|
-
romanLyric: "",
|
|
4491
|
-
startTime: 0,
|
|
4492
|
-
endTime: 0,
|
|
4493
|
-
isBG: false,
|
|
4494
|
-
isDuet: false
|
|
4495
|
-
}) {
|
|
4496
|
-
super();
|
|
4497
|
-
this.lyricPlayer = lyricPlayer;
|
|
4498
|
-
this.lyricLine = lyricLine;
|
|
4499
|
-
this.element.setAttribute("class", index_module_default.lyricLine);
|
|
4500
|
-
if (this.lyricLine.isBG) this.element.classList.add(index_module_default.lyricBgLine);
|
|
4501
|
-
if (this.lyricLine.isDuet) this.element.classList.add(index_module_default.lyricDuetLine);
|
|
4502
|
-
this.element.appendChild(document.createElement("div"));
|
|
4503
|
-
this.element.appendChild(document.createElement("div"));
|
|
4504
|
-
this.element.appendChild(document.createElement("div"));
|
|
4505
|
-
const main = this.element.children[0];
|
|
4506
|
-
const trans = this.element.children[1];
|
|
4507
|
-
const roman = this.element.children[2];
|
|
4508
|
-
main.setAttribute("class", index_module_default.lyricMainLine);
|
|
4509
|
-
trans.setAttribute("class", index_module_default.lyricSubLine);
|
|
4510
|
-
roman.setAttribute("class", index_module_default.lyricSubLine);
|
|
4511
|
-
this.rebuildElement();
|
|
4512
|
-
this.rebuildStyle();
|
|
4513
|
-
this.markMaskImageDirty("Initial construction");
|
|
4514
|
-
}
|
|
4515
|
-
listenersMap = /* @__PURE__ */ new Map();
|
|
4516
|
-
onMouseEvent = (e) => {
|
|
4517
|
-
const wrapped = new RawLyricLineMouseEvent(this, e);
|
|
4518
|
-
for (const listener of this.listenersMap.get(e.type) ?? []) listener.call(this, wrapped);
|
|
4519
|
-
if (!this.dispatchEvent(wrapped) || wrapped.defaultPrevented) {
|
|
4520
|
-
e.preventDefault();
|
|
4521
|
-
e.stopPropagation();
|
|
4522
|
-
e.stopImmediatePropagation();
|
|
4523
|
-
}
|
|
4524
|
-
};
|
|
4525
|
-
addMouseEventListener(type, callback, options) {
|
|
4526
|
-
if (callback) {
|
|
4527
|
-
const listeners = this.listenersMap.get(type) ?? /* @__PURE__ */ new Set();
|
|
4528
|
-
if (listeners.size === 0) this.element.addEventListener(type, this.onMouseEvent, options);
|
|
4529
|
-
listeners.add(callback);
|
|
4530
|
-
this.listenersMap.set(type, listeners);
|
|
4531
|
-
}
|
|
4532
|
-
}
|
|
4533
|
-
removeMouseEventListener(type, callback, options) {
|
|
4534
|
-
if (callback) {
|
|
4535
|
-
const listeners = this.listenersMap.get(type);
|
|
4536
|
-
if (listeners) {
|
|
4537
|
-
listeners.delete(callback);
|
|
4538
|
-
if (listeners.size === 0) this.element.removeEventListener(type, this.onMouseEvent, options);
|
|
4539
|
-
}
|
|
4540
|
-
}
|
|
4541
|
-
}
|
|
4542
|
-
areWordsOnSameLine(word1, word2) {
|
|
4543
|
-
if (word1?.mainElement && word2?.mainElement) {
|
|
4544
|
-
const word1el = word1.mainElement;
|
|
4545
|
-
const word2el = word2.mainElement;
|
|
4546
|
-
const rect1 = word1el.getBoundingClientRect();
|
|
4547
|
-
const rect2 = word2el.getBoundingClientRect();
|
|
4548
|
-
return Math.abs(rect1.top - rect2.top) < 10;
|
|
4549
|
-
}
|
|
4550
|
-
return true;
|
|
4551
|
-
}
|
|
4552
|
-
isEnabled = false;
|
|
4553
|
-
async enable(maskAnimationTime = this.lyricLine.startTime) {
|
|
4554
|
-
this.isEnabled = true;
|
|
4555
|
-
this.element.classList.add(index_module_default.active);
|
|
4556
|
-
await this.waitMaskImageUpdated();
|
|
4557
|
-
const main = this.element.children[0];
|
|
4558
|
-
for (const word of this.splittedWords) {
|
|
4559
|
-
for (const a of word.elementAnimations) {
|
|
4560
|
-
a.currentTime = 0;
|
|
4561
|
-
a.playbackRate = 1;
|
|
4562
|
-
a.play();
|
|
4563
|
-
}
|
|
4564
|
-
for (const a of word.maskAnimations) {
|
|
4565
|
-
a.currentTime = Math.min(this.totalDuration, Math.max(0, maskAnimationTime - this.lyricLine.startTime));
|
|
4566
|
-
a.playbackRate = 1;
|
|
4567
|
-
a.play();
|
|
4568
|
-
}
|
|
4569
|
-
}
|
|
4570
|
-
main.classList.add(index_module_default.active);
|
|
4571
|
-
}
|
|
4572
|
-
disable() {
|
|
4573
|
-
this.isEnabled = false;
|
|
4574
|
-
this.element.classList.remove(index_module_default.active);
|
|
4575
|
-
const main = this.element.children[0];
|
|
4576
|
-
for (const word of this.splittedWords) for (const a of word.elementAnimations) if (a.id === "float-word" || a.id.includes("emphasize-word-float-only")) {
|
|
4577
|
-
a.playbackRate = -1;
|
|
4578
|
-
a.play();
|
|
4579
|
-
}
|
|
4580
|
-
main.classList.remove(index_module_default.active);
|
|
4581
|
-
}
|
|
4582
|
-
lastWord;
|
|
4583
|
-
async resume() {
|
|
4584
|
-
await this.waitMaskImageUpdated();
|
|
4585
|
-
if (!this.isEnabled) return;
|
|
4586
|
-
for (const word of this.splittedWords) {
|
|
4587
|
-
for (const a of word.elementAnimations) if (!this.lastWord || this.splittedWords.indexOf(this.lastWord) < this.splittedWords.indexOf(word)) a.play();
|
|
4588
|
-
for (const a of word.maskAnimations) if (!this.lastWord || this.splittedWords.indexOf(this.lastWord) < this.splittedWords.indexOf(word)) a.play();
|
|
4589
|
-
}
|
|
4590
|
-
}
|
|
4591
|
-
async pause() {
|
|
4592
|
-
await this.waitMaskImageUpdated();
|
|
4593
|
-
if (!this.isEnabled) return;
|
|
4594
|
-
for (const word of this.splittedWords) {
|
|
4595
|
-
for (const a of word.elementAnimations) a.pause();
|
|
4596
|
-
for (const a of word.maskAnimations) a.pause();
|
|
4597
|
-
}
|
|
4598
|
-
}
|
|
4599
|
-
setMaskAnimationState(maskAnimationTime = 0) {
|
|
4600
|
-
const t = maskAnimationTime - this.lyricLine.startTime;
|
|
4601
|
-
for (const word of this.splittedWords) for (const a of word.maskAnimations) {
|
|
4602
|
-
a.currentTime = Math.min(this.totalDuration, Math.max(0, t));
|
|
4603
|
-
a.playbackRate = 1;
|
|
4604
|
-
if (t >= 0 && t < this.totalDuration) a.play();
|
|
4605
|
-
else a.pause();
|
|
4606
|
-
}
|
|
4607
|
-
}
|
|
4608
|
-
measureLockMark = false;
|
|
4609
|
-
measureLock = mutexifyFunction(async (callback) => {
|
|
4610
|
-
if (this.measureLockMark) return;
|
|
4611
|
-
this.measureLockMark = true;
|
|
4612
|
-
await callback();
|
|
4613
|
-
this.measureLockMark = false;
|
|
4614
|
-
});
|
|
4615
|
-
getLine() {
|
|
4616
|
-
return this.lyricLine;
|
|
4617
|
-
}
|
|
4618
|
-
show() {
|
|
4619
|
-
this.rebuildStyle();
|
|
4620
|
-
}
|
|
4621
|
-
hide() {}
|
|
4622
|
-
rebuildStyle() {}
|
|
4623
|
-
getRubySegments(word) {
|
|
4624
|
-
return (word.ruby ?? []).filter((ruby) => (ruby?.word?.trim().length ?? 0) > 0);
|
|
4625
|
-
}
|
|
4626
|
-
buildWordElement(word, shouldEmphasize, hasRubyLine, hasRomanLine, displayWord) {
|
|
4627
|
-
const mainWordEl = document.createElement("span");
|
|
4628
|
-
const subElements = [];
|
|
4629
|
-
const romanWord = word.romanWord?.trim() ?? "";
|
|
4630
|
-
let wordContainer = mainWordEl;
|
|
4631
|
-
if (hasRubyLine || hasRomanLine) {
|
|
4632
|
-
wordContainer = document.createElement("div");
|
|
4633
|
-
mainWordEl.appendChild(wordContainer);
|
|
4634
|
-
}
|
|
4635
|
-
if (hasRubyLine) {
|
|
4636
|
-
const rubyWordEl = document.createElement("div");
|
|
4637
|
-
const rubySegments = this.getRubySegments(word);
|
|
4638
|
-
for (const ruby of rubySegments) {
|
|
4639
|
-
const rubyPartEl = document.createElement("span");
|
|
4640
|
-
rubyPartEl.innerText = ruby.word;
|
|
4641
|
-
rubyPartEl.dataset.startTime = String(ruby.startTime);
|
|
4642
|
-
rubyPartEl.dataset.endTime = String(ruby.endTime);
|
|
4643
|
-
rubyWordEl.appendChild(rubyPartEl);
|
|
4644
|
-
}
|
|
4645
|
-
rubyWordEl.classList.add(index_module_default.rubyWord);
|
|
4646
|
-
mainWordEl.classList.add(index_module_default.wordWithRuby);
|
|
4647
|
-
wordContainer.classList.add(index_module_default.wordBody);
|
|
4648
|
-
mainWordEl.insertBefore(rubyWordEl, wordContainer);
|
|
4649
|
-
}
|
|
4650
|
-
if (shouldEmphasize) {
|
|
4651
|
-
mainWordEl.classList.add(index_module_default.emphasize);
|
|
4652
|
-
for (const char of displayWord.trim()) {
|
|
4653
|
-
const charEl = document.createElement("span");
|
|
4654
|
-
charEl.innerText = char;
|
|
4655
|
-
subElements.push(charEl);
|
|
4656
|
-
wordContainer.appendChild(charEl);
|
|
4657
|
-
}
|
|
4658
|
-
} else if (hasRomanLine) {
|
|
4659
|
-
const wordEl = document.createElement("div");
|
|
4660
|
-
wordEl.innerText = displayWord;
|
|
4661
|
-
wordContainer.appendChild(wordEl);
|
|
4662
|
-
} else mainWordEl.innerText = displayWord;
|
|
4663
|
-
if (hasRomanLine) {
|
|
4664
|
-
const romanWordEl = document.createElement("div");
|
|
4665
|
-
romanWordEl.innerText = romanWord.length > 0 ? romanWord : "\xA0";
|
|
4666
|
-
romanWordEl.classList.add(index_module_default.romanWord);
|
|
4667
|
-
wordContainer.appendChild(romanWordEl);
|
|
4668
|
-
}
|
|
4669
|
-
return {
|
|
4670
|
-
mainWordEl,
|
|
4671
|
-
subElements
|
|
4672
|
-
};
|
|
4673
|
-
}
|
|
4674
|
-
rebuildElement() {
|
|
4675
|
-
this.disposeElements();
|
|
4676
|
-
const main = this.element.children[0];
|
|
4677
|
-
const trans = this.element.children[1];
|
|
4678
|
-
const roman = this.element.children[2];
|
|
4679
|
-
if (this.lyricPlayer._getIsNonDynamic()) {
|
|
4680
|
-
main.innerText = this.lyricLine.words.map((w) => this.lyricPlayer.processObsceneWord(w)).join("");
|
|
4681
|
-
trans.innerText = this.lyricLine.translatedLyric;
|
|
4682
|
-
roman.innerText = this.lyricLine.romanLyric;
|
|
4683
|
-
return;
|
|
4684
|
-
}
|
|
4685
|
-
const chunkedWords = chunkAndSplitLyricWords(this.lyricLine.words);
|
|
4686
|
-
const hasRubyLine = this.lyricLine.words.some((word) => (word.ruby?.length ?? 0) > 0);
|
|
4687
|
-
const hasRomanLine = this.lyricLine.words.some((word) => (word.romanWord?.trim().length ?? 0) > 0);
|
|
4688
|
-
main.innerHTML = "";
|
|
4689
|
-
for (const chunk of chunkedWords) if (Array.isArray(chunk)) {
|
|
4690
|
-
if (chunk.length === 0) continue;
|
|
4691
|
-
const merged = chunk.reduce((a, b) => {
|
|
4692
|
-
a.endTime = Math.max(a.endTime, b.endTime);
|
|
4693
|
-
a.startTime = Math.min(a.startTime, b.startTime);
|
|
4694
|
-
a.word += b.word;
|
|
4695
|
-
return a;
|
|
4696
|
-
}, {
|
|
4697
|
-
word: "",
|
|
4698
|
-
romanWord: "",
|
|
4699
|
-
startTime: Number.POSITIVE_INFINITY,
|
|
4700
|
-
endTime: Number.NEGATIVE_INFINITY,
|
|
4701
|
-
wordType: "normal",
|
|
4702
|
-
obscene: false
|
|
4703
|
-
});
|
|
4704
|
-
const emp = chunk.map((word) => LyricLineBase.shouldEmphasize(word)).reduce((a, b) => a || b, LyricLineBase.shouldEmphasize(merged));
|
|
4705
|
-
const wrapperWordEl = document.createElement("span");
|
|
4706
|
-
wrapperWordEl.classList.add(index_module_default.emphasizeWrapper);
|
|
4707
|
-
const characterElements = [];
|
|
4708
|
-
for (const word of chunk) {
|
|
4709
|
-
const { mainWordEl, subElements } = this.buildWordElement(word, emp, hasRubyLine, hasRomanLine, this.lyricPlayer.processObsceneWord(word));
|
|
4710
|
-
if (emp) characterElements.push(...subElements);
|
|
4711
|
-
this.splittedWords.push({
|
|
4712
|
-
...word,
|
|
4713
|
-
mainElement: mainWordEl,
|
|
4714
|
-
subElements,
|
|
4715
|
-
elementAnimations: [],
|
|
4716
|
-
maskAnimations: [],
|
|
4717
|
-
width: 0,
|
|
4718
|
-
height: 0,
|
|
4719
|
-
padding: 0,
|
|
4720
|
-
shouldEmphasize: emp
|
|
4721
|
-
});
|
|
4722
|
-
wrapperWordEl.appendChild(mainWordEl);
|
|
4723
|
-
}
|
|
4724
|
-
if (emp) this.splittedWords[this.splittedWords.length - 1].elementAnimations.push(...this.initEmphasizeAnimation(merged, characterElements, merged.endTime - merged.startTime, merged.startTime - this.lyricLine.startTime));
|
|
4725
|
-
if (merged.word.trimStart() !== merged.word) main.appendChild(document.createTextNode(" "));
|
|
4726
|
-
main.appendChild(wrapperWordEl);
|
|
4727
|
-
if (merged.word.trimEnd() !== merged.word && LyricLineBase.shouldEmphasize(merged)) main.appendChild(document.createTextNode(" "));
|
|
4728
|
-
} else if (chunk.word.trim().length === 0) main.appendChild(document.createTextNode(" "));
|
|
4729
|
-
else {
|
|
4730
|
-
const emp = LyricLineBase.shouldEmphasize(chunk);
|
|
4731
|
-
const { mainWordEl, subElements } = this.buildWordElement(chunk, emp, hasRubyLine, hasRomanLine, this.lyricPlayer.processObsceneWord(chunk).trim());
|
|
4732
|
-
const realWord = {
|
|
4733
|
-
...chunk,
|
|
4734
|
-
mainElement: mainWordEl,
|
|
4735
|
-
subElements,
|
|
4736
|
-
elementAnimations: [],
|
|
4737
|
-
maskAnimations: [],
|
|
4738
|
-
width: 0,
|
|
4739
|
-
height: 0,
|
|
4740
|
-
padding: 0,
|
|
4741
|
-
shouldEmphasize: emp
|
|
4742
|
-
};
|
|
4743
|
-
if (emp) {
|
|
4744
|
-
const duration = Math.abs(realWord.endTime - realWord.startTime);
|
|
4745
|
-
realWord.elementAnimations.push(...this.initEmphasizeAnimation(chunk, subElements, duration, realWord.startTime - this.lyricLine.startTime));
|
|
4746
|
-
}
|
|
4747
|
-
if (chunk.word.trimStart() !== chunk.word) main.appendChild(document.createTextNode(" "));
|
|
4748
|
-
main.appendChild(mainWordEl);
|
|
4749
|
-
if (chunk.word.trimEnd() !== chunk.word) main.appendChild(document.createTextNode(" "));
|
|
4750
|
-
this.splittedWords.push(realWord);
|
|
4751
|
-
}
|
|
4752
|
-
trans.innerText = this.lyricLine.translatedLyric;
|
|
4753
|
-
roman.innerText = this.lyricLine.romanLyric;
|
|
4754
|
-
}
|
|
4755
|
-
initEmphasizeAnimation(word, characterElements, duration, delay) {
|
|
4756
|
-
const de = Math.max(0, delay);
|
|
4757
|
-
let du = Math.max(1e3, duration);
|
|
4758
|
-
let result = [];
|
|
4759
|
-
let amount = du / 2e3;
|
|
4760
|
-
amount = amount > 1 ? Math.sqrt(amount) : amount ** 3;
|
|
4761
|
-
let blur = du / 3e3;
|
|
4762
|
-
blur = blur > 1 ? Math.sqrt(blur) : blur ** 3;
|
|
4763
|
-
amount *= .6;
|
|
4764
|
-
blur *= .5;
|
|
4765
|
-
if (this.lyricLine.words.length > 0 && word.word.includes(this.lyricLine.words[this.lyricLine.words.length - 1].word)) {
|
|
4766
|
-
amount *= 1.6;
|
|
4767
|
-
blur *= 1.5;
|
|
4768
|
-
du *= 1.2;
|
|
4769
|
-
}
|
|
4770
|
-
amount = Math.min(1.2, amount);
|
|
4771
|
-
blur = Math.min(.8, blur);
|
|
4772
|
-
const animateDu = Number.isFinite(du) ? du : 0;
|
|
4773
|
-
const empEasing = makeEmpEasing(EMP_EASING_MID);
|
|
4774
|
-
result = characterElements.flatMap((el, i, arr) => {
|
|
4775
|
-
const wordDe = de + du / 2.5 / arr.length * i;
|
|
4776
|
-
const result = [];
|
|
4777
|
-
const frames = new Array(ANIMATION_FRAME_QUANTITY).fill(0).map((_, j) => {
|
|
4778
|
-
const x = (j + 1) / ANIMATION_FRAME_QUANTITY;
|
|
4779
|
-
const transX = empEasing(x);
|
|
4780
|
-
const glowLevel = empEasing(x) * blur;
|
|
4781
|
-
const mat = scaleMatrix4(createMatrix4(), 1 + transX * .1 * amount);
|
|
4782
|
-
const offsetX = -transX * .03 * amount * (arr.length / 2 - i);
|
|
4783
|
-
const offsetY = -transX * .025 * amount;
|
|
4784
|
-
return {
|
|
4785
|
-
offset: x,
|
|
4786
|
-
transform: `${matrix4ToCSS(mat, 4)} translate(${offsetX}em, ${offsetY}em)`,
|
|
4787
|
-
textShadow: `0 0 ${Math.min(.3, blur * .3)}em rgba(255, 255, 255, ${glowLevel})`
|
|
4788
|
-
};
|
|
4789
|
-
});
|
|
4790
|
-
const glow = el.animate(frames, {
|
|
4791
|
-
duration: animateDu,
|
|
4792
|
-
delay: Number.isFinite(wordDe) ? wordDe : 0,
|
|
4793
|
-
id: `emphasize-word-${el.innerText}-${i}`,
|
|
4794
|
-
iterations: 1,
|
|
4795
|
-
composite: "replace",
|
|
4796
|
-
fill: "both"
|
|
4797
|
-
});
|
|
4798
|
-
glow.onfinish = () => {
|
|
4799
|
-
glow.pause();
|
|
4800
|
-
};
|
|
4801
|
-
glow.pause();
|
|
4802
|
-
result.push(glow);
|
|
4803
|
-
const floatFrame = new Array(ANIMATION_FRAME_QUANTITY).fill(0).map((_, j) => {
|
|
4804
|
-
const x = (j + 1) / ANIMATION_FRAME_QUANTITY;
|
|
4805
|
-
let y = Math.sin(x * Math.PI);
|
|
4806
|
-
if (this.lyricLine.isBG) y *= 2;
|
|
4807
|
-
return {
|
|
4808
|
-
offset: x,
|
|
4809
|
-
transform: `translateY(${-y * .05}em)`
|
|
4810
|
-
};
|
|
4811
|
-
});
|
|
4812
|
-
const float = el.animate(floatFrame, {
|
|
4813
|
-
duration: animateDu * 1.4,
|
|
4814
|
-
delay: Number.isFinite(wordDe) ? wordDe - 400 : 0,
|
|
4815
|
-
id: "emphasize-word-float",
|
|
4816
|
-
iterations: 1,
|
|
4817
|
-
composite: "add",
|
|
4818
|
-
fill: "both"
|
|
4819
|
-
});
|
|
4820
|
-
float.onfinish = () => {
|
|
4821
|
-
float.pause();
|
|
4822
|
-
};
|
|
4823
|
-
float.pause();
|
|
4824
|
-
result.push(float);
|
|
4825
|
-
return result;
|
|
4826
|
-
});
|
|
4827
|
-
return result;
|
|
4828
|
-
}
|
|
4829
|
-
get totalDuration() {
|
|
4830
|
-
return this.lyricLine.endTime - this.lyricLine.startTime;
|
|
4831
|
-
}
|
|
4832
|
-
maskImageDirty = false;
|
|
4833
|
-
markImageDirtyPromiseResolve = /* @__PURE__ */ new Set();
|
|
4834
|
-
markImageDirtyPromise = new Promise((resolve) => {
|
|
4835
|
-
this.markImageDirtyPromiseResolve.add(resolve);
|
|
4836
|
-
});
|
|
4837
|
-
markMaskImageDirty(_debugReason = "") {
|
|
4838
|
-
this.maskImageDirty = true;
|
|
4839
|
-
if (!this.element.classList.contains(index_module_default.dirty)) this.element.classList.add(index_module_default.dirty);
|
|
4840
|
-
const newPromise = Promise.all([this.markImageDirtyPromise, new Promise((resolve) => {
|
|
4841
|
-
this.markImageDirtyPromiseResolve.add(resolve);
|
|
4842
|
-
})]).then(() => {});
|
|
4843
|
-
this.markImageDirtyPromise = newPromise;
|
|
4844
|
-
return newPromise;
|
|
4845
|
-
}
|
|
4846
|
-
waitMaskImageUpdated() {
|
|
4847
|
-
return this.markImageDirtyPromise;
|
|
4848
|
-
}
|
|
4849
|
-
async updateMaskImage() {
|
|
4850
|
-
if (!this.element.checkVisibility({ contentVisibilityAuto: true })) return;
|
|
4851
|
-
this.maskImageDirty = false;
|
|
4852
|
-
await this.measureLock(async () => {
|
|
4853
|
-
await Promise.all(this.splittedWords.map(async (word) => {
|
|
4854
|
-
const el = word.mainElement;
|
|
4855
|
-
if (el) await measure(() => {
|
|
4856
|
-
word.padding = Number.parseFloat(getComputedStyle(el).paddingLeft);
|
|
4857
|
-
word.width = el.clientWidth - word.padding * 2;
|
|
4858
|
-
word.height = el.clientHeight - word.padding * 2;
|
|
4859
|
-
});
|
|
4860
|
-
else {
|
|
4861
|
-
word.width = 0;
|
|
4862
|
-
word.height = 0;
|
|
4863
|
-
word.padding = 0;
|
|
4864
|
-
}
|
|
4865
|
-
if (word.width * word.height === 0) console.warn("Word size is zero");
|
|
4866
|
-
}));
|
|
4867
|
-
await mutate(() => {
|
|
4868
|
-
if (this.lyricPlayer.supportMaskImage) this.generateWebAnimationBasedMaskImage();
|
|
4869
|
-
else this.generateCalcBasedMaskImage();
|
|
4870
|
-
});
|
|
4871
|
-
});
|
|
4872
|
-
for (const resolve of this.markImageDirtyPromiseResolve) {
|
|
4873
|
-
resolve();
|
|
4874
|
-
this.markImageDirtyPromiseResolve.delete(resolve);
|
|
4875
|
-
}
|
|
4876
|
-
await mutate(() => {
|
|
4877
|
-
this.element.classList.remove(index_module_default.dirty);
|
|
4878
|
-
});
|
|
4879
|
-
}
|
|
4880
|
-
generateCalcBasedMaskImage() {
|
|
4881
|
-
for (const word of this.splittedWords) {
|
|
4882
|
-
const wordEl = word.mainElement;
|
|
4883
|
-
if (wordEl) {
|
|
4884
|
-
word.width = wordEl.clientWidth;
|
|
4885
|
-
word.height = wordEl.clientHeight;
|
|
4886
|
-
const fadeWidth = word.height * this.lyricPlayer.getWordFadeWidth();
|
|
4887
|
-
const [maskImage, totalAspect] = generateFadeGradient(fadeWidth / word.width);
|
|
4888
|
-
const totalAspectStr = `${totalAspect * 100}% 100%`;
|
|
4889
|
-
if (this.lyricPlayer.supportMaskImage) {
|
|
4890
|
-
wordEl.style.maskImage = maskImage;
|
|
4891
|
-
wordEl.style.maskRepeat = "no-repeat";
|
|
4892
|
-
wordEl.style.maskOrigin = "left";
|
|
4893
|
-
wordEl.style.maskSize = totalAspectStr;
|
|
4894
|
-
} else {
|
|
4895
|
-
wordEl.style.webkitMaskImage = maskImage;
|
|
4896
|
-
wordEl.style.webkitMaskRepeat = "no-repeat";
|
|
4897
|
-
wordEl.style.webkitMaskOrigin = "left";
|
|
4898
|
-
wordEl.style.webkitMaskSize = totalAspectStr;
|
|
4899
|
-
}
|
|
4900
|
-
const w = word.width + fadeWidth;
|
|
4901
|
-
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`;
|
|
4902
|
-
wordEl.style.maskPosition = maskPos;
|
|
4903
|
-
wordEl.style.webkitMaskPosition = maskPos;
|
|
4904
|
-
}
|
|
4905
|
-
}
|
|
4906
|
-
}
|
|
4907
|
-
generateWebAnimationBasedMaskImage() {
|
|
4908
|
-
const totalFadeDuration = Math.max(this.splittedWords.reduce((pv, w) => Math.max(w.endTime, pv), 0), this.lyricLine.endTime) - this.lyricLine.startTime;
|
|
4909
|
-
this.splittedWords.forEach((word, i) => {
|
|
4910
|
-
const wordEl = word.mainElement;
|
|
4911
|
-
if (wordEl) {
|
|
4912
|
-
const fadeWidth = word.height * this.lyricPlayer.getWordFadeWidth();
|
|
4913
|
-
const [maskImage, totalAspect] = generateFadeGradient(fadeWidth / (word.width + word.padding * 2));
|
|
4914
|
-
const totalAspectStr = `${totalAspect * 100}% 100%`;
|
|
4915
|
-
if (this.lyricPlayer.supportMaskImage) {
|
|
4916
|
-
wordEl.style.maskImage = maskImage;
|
|
4917
|
-
wordEl.style.maskRepeat = "no-repeat";
|
|
4918
|
-
wordEl.style.maskOrigin = "left";
|
|
4919
|
-
wordEl.style.maskSize = totalAspectStr;
|
|
4920
|
-
} else {
|
|
4921
|
-
wordEl.style.webkitMaskImage = maskImage;
|
|
4922
|
-
wordEl.style.webkitMaskRepeat = "no-repeat";
|
|
4923
|
-
wordEl.style.webkitMaskOrigin = "left";
|
|
4924
|
-
wordEl.style.webkitMaskSize = totalAspectStr;
|
|
4925
|
-
}
|
|
4926
|
-
const widthBeforeSelf = this.splittedWords.slice(0, i).reduce((a, b) => a + b.width, 0) + (this.splittedWords[0] ? fadeWidth : 0);
|
|
4927
|
-
const minOffset = -(word.width + word.padding * 2 + fadeWidth);
|
|
4928
|
-
const clampOffset = (x) => Math.max(minOffset, Math.min(0, x));
|
|
4929
|
-
let curPos = -widthBeforeSelf - word.width - word.padding - fadeWidth;
|
|
4930
|
-
let timeOffset = 0;
|
|
4931
|
-
const frames = [];
|
|
4932
|
-
let lastPos = curPos;
|
|
4933
|
-
let lastTime = 0;
|
|
4934
|
-
const pushFrame = () => {
|
|
4935
|
-
const moveOffset = curPos - lastPos;
|
|
4936
|
-
const time = Math.max(0, Math.min(1, timeOffset));
|
|
4937
|
-
const duration = time - lastTime;
|
|
4938
|
-
const d = Math.abs(duration / moveOffset);
|
|
4939
|
-
if (curPos > minOffset && lastPos < minOffset) {
|
|
4940
|
-
const staticTime = Math.abs(lastPos - minOffset) * d;
|
|
4941
|
-
const value = `${clampOffset(lastPos)}px 0`;
|
|
4942
|
-
const frame = {
|
|
4943
|
-
offset: lastTime + staticTime,
|
|
4944
|
-
maskPosition: value
|
|
4945
|
-
};
|
|
4946
|
-
frames.push(frame);
|
|
4947
|
-
}
|
|
4948
|
-
if (curPos > 0 && lastPos < 0) {
|
|
4949
|
-
const staticTime = Math.abs(lastPos) * d;
|
|
4950
|
-
const value = `${clampOffset(curPos)}px 0`;
|
|
4951
|
-
const frame = {
|
|
4952
|
-
offset: lastTime + staticTime,
|
|
4953
|
-
maskPosition: value
|
|
4954
|
-
};
|
|
4955
|
-
frames.push(frame);
|
|
4956
|
-
}
|
|
4957
|
-
const frame = {
|
|
4958
|
-
offset: time,
|
|
4959
|
-
maskPosition: `${clampOffset(curPos)}px 0`
|
|
4960
|
-
};
|
|
4961
|
-
frames.push(frame);
|
|
4962
|
-
lastPos = curPos;
|
|
4963
|
-
lastTime = time;
|
|
4964
|
-
};
|
|
4965
|
-
pushFrame();
|
|
4966
|
-
let lastTimeStamp = 0;
|
|
4967
|
-
this.splittedWords.forEach((otherWord, j) => {
|
|
4968
|
-
{
|
|
4969
|
-
const curTimeStamp = otherWord.startTime - this.lyricLine.startTime;
|
|
4970
|
-
const staticDuration = curTimeStamp - lastTimeStamp;
|
|
4971
|
-
timeOffset += staticDuration / totalFadeDuration;
|
|
4972
|
-
if (staticDuration > 0) pushFrame();
|
|
4973
|
-
lastTimeStamp = curTimeStamp;
|
|
4974
|
-
}
|
|
4975
|
-
{
|
|
4976
|
-
const fadeDuration = otherWord.endTime - otherWord.startTime;
|
|
4977
|
-
const rubySegments = this.getRubySegments(otherWord);
|
|
4978
|
-
const rubyCharCount = rubySegments.reduce((total, ruby) => total + ruby.word.length, 0);
|
|
4979
|
-
if (rubyCharCount > 0) {
|
|
4980
|
-
const widthPerChar = otherWord.width / rubyCharCount;
|
|
4981
|
-
let charIndex = 0;
|
|
4982
|
-
for (const ruby of rubySegments) {
|
|
4983
|
-
const rubyStartTime = Number.isFinite(ruby.startTime) ? ruby.startTime : otherWord.startTime;
|
|
4984
|
-
const rubyEndTime = Number.isFinite(ruby.endTime) ? ruby.endTime : otherWord.endTime;
|
|
4985
|
-
const rubyStart = Math.max(rubyStartTime, otherWord.startTime);
|
|
4986
|
-
const rubyEnd = Math.min(Math.max(rubyEndTime, rubyStart), otherWord.endTime);
|
|
4987
|
-
const rubyStartStamp = rubyStart - this.lyricLine.startTime;
|
|
4988
|
-
const rubyStaticDuration = rubyStartStamp - lastTimeStamp;
|
|
4989
|
-
timeOffset += rubyStaticDuration / totalFadeDuration;
|
|
4990
|
-
if (rubyStaticDuration > 0) pushFrame();
|
|
4991
|
-
lastTimeStamp = rubyStartStamp;
|
|
4992
|
-
const perCharDuration = Math.max(0, rubyEnd - rubyStart) / ruby.word.length;
|
|
4993
|
-
for (let rubyCharIndex = 0; rubyCharIndex < ruby.word.length; rubyCharIndex++) {
|
|
4994
|
-
timeOffset += perCharDuration / totalFadeDuration;
|
|
4995
|
-
curPos += widthPerChar;
|
|
4996
|
-
if (j === 0 && charIndex === 0) curPos += fadeWidth * 1.5;
|
|
4997
|
-
if (j === this.splittedWords.length - 1 && charIndex === rubyCharCount - 1) curPos += fadeWidth * .5;
|
|
4998
|
-
if (perCharDuration > 0) pushFrame();
|
|
4999
|
-
lastTimeStamp += perCharDuration;
|
|
5000
|
-
charIndex++;
|
|
5001
|
-
}
|
|
5002
|
-
}
|
|
5003
|
-
const wordEndStamp = Math.max(otherWord.endTime - this.lyricLine.startTime, lastTimeStamp);
|
|
5004
|
-
const wordTailDuration = wordEndStamp - lastTimeStamp;
|
|
5005
|
-
timeOffset += wordTailDuration / totalFadeDuration;
|
|
5006
|
-
if (wordTailDuration > 0) pushFrame();
|
|
5007
|
-
lastTimeStamp = wordEndStamp;
|
|
5008
|
-
} else {
|
|
5009
|
-
timeOffset += fadeDuration / totalFadeDuration;
|
|
5010
|
-
curPos += otherWord.width;
|
|
5011
|
-
if (j === 0) curPos += fadeWidth * 1.5;
|
|
5012
|
-
if (j === this.splittedWords.length - 1) curPos += fadeWidth * .5;
|
|
5013
|
-
if (fadeDuration > 0) pushFrame();
|
|
5014
|
-
lastTimeStamp += fadeDuration;
|
|
5015
|
-
}
|
|
5016
|
-
}
|
|
5017
|
-
});
|
|
5018
|
-
for (const a of word.maskAnimations) a.cancel();
|
|
5019
|
-
try {
|
|
5020
|
-
const ani = wordEl.animate(frames, {
|
|
5021
|
-
duration: totalFadeDuration || 1,
|
|
5022
|
-
id: `fade-word-${word.word}-${i}`,
|
|
5023
|
-
fill: "both"
|
|
5024
|
-
});
|
|
5025
|
-
ani.pause();
|
|
5026
|
-
word.maskAnimations = [ani];
|
|
5027
|
-
} catch (err) {
|
|
5028
|
-
console.warn("应用渐变动画发生错误", frames, totalFadeDuration, err);
|
|
5029
|
-
}
|
|
5030
|
-
}
|
|
5031
|
-
});
|
|
5032
|
-
}
|
|
5033
|
-
getElement() {
|
|
5034
|
-
return this.element;
|
|
5035
|
-
}
|
|
5036
|
-
setTransform(top = this.top, scale = this.scale, opacity = 1, blur = 0, force = false, delay = 0) {
|
|
5037
|
-
super.setTransform(top, scale, opacity, blur, force, delay);
|
|
5038
|
-
const beforeInSight = this.isInSight;
|
|
5039
|
-
const enableSpring = this.lyricPlayer.getEnableSpring();
|
|
5040
|
-
this.top = top;
|
|
5041
|
-
this.scale = scale;
|
|
5042
|
-
this.delay = delay * 1e3 | 0;
|
|
5043
|
-
const main = this.element.children[0];
|
|
5044
|
-
main.style.opacity = `${opacity}`;
|
|
5045
|
-
if (force || !enableSpring) {
|
|
5046
|
-
if (force) this.element.classList.add(index_module_default.tmpDisableTransition);
|
|
5047
|
-
this.lineTransforms.posY.setPosition(top);
|
|
5048
|
-
this.lineTransforms.scale.setPosition(scale);
|
|
5049
|
-
if (!enableSpring) {
|
|
5050
|
-
const afterInSight = this.isInSight;
|
|
5051
|
-
if (beforeInSight || afterInSight) this.show();
|
|
5052
|
-
else this.hide();
|
|
5053
|
-
} else this.rebuildStyle();
|
|
5054
|
-
if (force) requestAnimationFrame(() => {
|
|
5055
|
-
this.element.classList.remove(index_module_default.tmpDisableTransition);
|
|
5056
|
-
});
|
|
5057
|
-
} else {
|
|
5058
|
-
this.lineTransforms.posY.setTargetPosition(top, delay);
|
|
5059
|
-
this.lineTransforms.scale.setTargetPosition(scale);
|
|
5060
|
-
}
|
|
5061
|
-
}
|
|
5062
|
-
update(delta = 0) {
|
|
5063
|
-
if (!this.lyricPlayer.getEnableSpring()) return;
|
|
5064
|
-
this.lineTransforms.posY.update(delta);
|
|
5065
|
-
this.lineTransforms.scale.update(delta);
|
|
5066
|
-
if (this.isInSight) {
|
|
5067
|
-
this.show();
|
|
5068
|
-
if (this.maskImageDirty) this.updateMaskImage();
|
|
5069
|
-
} else this.hide();
|
|
5070
|
-
if (this.lyricPlayer.getEnableSpring()) {
|
|
5071
|
-
this.element.style.setProperty("--bright-mask-alpha", `${Math.max(0, Math.min(1, this.lineTransforms.scale.getCurrentPosition() / 100 - .97) / .03) * .8 + .2}`);
|
|
5072
|
-
this.element.style.setProperty("--dark-mask-alpha", `${Math.max(0, Math.min(1, this.lineTransforms.scale.getCurrentPosition() / 100 - .97) / .03) * .2 + .2}`);
|
|
5073
|
-
} else {
|
|
5074
|
-
const transform = window.getComputedStyle(this.element).transform;
|
|
5075
|
-
const scale = getScaleFromTransform(transform);
|
|
5076
|
-
this.element.style.setProperty("--bright-mask-alpha", `${Math.max(0, Math.min(1, (scale - .97) / .03)) * .8 + .2}`);
|
|
5077
|
-
this.element.style.setProperty("--dark-mask-alpha", `${Math.max(0, Math.min(1, (scale - .97) / .03)) * .2 + .2}`);
|
|
5078
|
-
}
|
|
5079
|
-
}
|
|
5080
|
-
_getDebugTargetPos() {
|
|
5081
|
-
return `[位移: ${this.top}; 缩放: ${this.scale}; 延时: ${this.delay}]`;
|
|
5082
|
-
}
|
|
5083
|
-
get isInSight() {
|
|
5084
|
-
const t = this.lineTransforms.posY.getCurrentPosition();
|
|
5085
|
-
const h = this.lineSize[1];
|
|
5086
|
-
const b = t + h;
|
|
5087
|
-
return !(t > this.lyricPlayer.size[1] + h || b < -h);
|
|
5088
|
-
}
|
|
5089
|
-
disposeElements() {
|
|
5090
|
-
for (const realWord of this.splittedWords) {
|
|
5091
|
-
for (const a of realWord.elementAnimations) a.cancel();
|
|
5092
|
-
for (const a of realWord.maskAnimations) a.cancel();
|
|
5093
|
-
for (const sub of realWord.subElements) {
|
|
5094
|
-
sub.remove();
|
|
5095
|
-
sub.parentNode?.removeChild(sub);
|
|
5096
|
-
}
|
|
5097
|
-
realWord.elementAnimations = [];
|
|
5098
|
-
realWord.maskAnimations = [];
|
|
5099
|
-
realWord.subElements = [];
|
|
5100
|
-
realWord.mainElement.remove();
|
|
5101
|
-
realWord.mainElement.parentNode?.removeChild(realWord.mainElement);
|
|
5102
|
-
}
|
|
5103
|
-
this.splittedWords = [];
|
|
5104
|
-
}
|
|
5105
|
-
dispose() {
|
|
5106
|
-
this.disposeElements();
|
|
5107
|
-
this.element.remove();
|
|
5108
|
-
}
|
|
5109
|
-
};
|
|
5110
|
-
//#endregion
|
|
5111
|
-
//#region src/lyric-player/dom-slim/index.ts
|
|
5112
|
-
/**
|
|
5113
|
-
* 歌词播放组件,本框架的核心组件
|
|
5114
|
-
*
|
|
5115
|
-
* 尽可能贴切 Apple Music for iPad 的歌词效果设计,且做了力所能及的优化措施
|
|
5116
|
-
*/
|
|
5117
|
-
var DomSlimLyricPlayer = class extends LyricPlayerBase {
|
|
5118
|
-
currentLyricLineObjects = [];
|
|
5119
|
-
debounceCalcLayout = debounce(() => this.calcLayout(true).then(() => this.currentLyricLineObjects.map(async (el, i) => {
|
|
5120
|
-
el.markMaskImageDirty("DomLyricPlayer onResize");
|
|
5121
|
-
await el.waitMaskImageUpdated();
|
|
5122
|
-
if (this.hotLines.has(i)) {
|
|
5123
|
-
el.enable(this.currentTime);
|
|
5124
|
-
el.resume();
|
|
5125
|
-
}
|
|
5126
|
-
})), 1e3);
|
|
5127
|
-
onResize() {
|
|
5128
|
-
const computedStyles = getComputedStyle(this.element);
|
|
5129
|
-
this._baseFontSize = Number.parseFloat(computedStyles.fontSize);
|
|
5130
|
-
const innerWidth = this.element.clientWidth - Number.parseFloat(computedStyles.paddingLeft) - Number.parseFloat(computedStyles.paddingRight);
|
|
5131
|
-
const innerHeight = this.element.clientHeight - Number.parseFloat(computedStyles.paddingTop) - Number.parseFloat(computedStyles.paddingBottom);
|
|
5132
|
-
this.innerSize[0] = innerWidth;
|
|
5133
|
-
this.innerSize[1] = innerHeight;
|
|
5134
|
-
this.rebuildStyle();
|
|
5135
|
-
this.debounceCalcLayout();
|
|
5136
|
-
}
|
|
5137
|
-
supportPlusLighter = CSS.supports("mix-blend-mode", "plus-lighter");
|
|
5138
|
-
supportMaskImage = CSS.supports("mask-image", "none");
|
|
5139
|
-
innerSize = [0, 0];
|
|
5140
|
-
onLineClickedHandler = (e) => {
|
|
5141
|
-
const evt = new LyricLineMouseEvent(this.lyricLinesIndexes.get(e.line) ?? -1, e.line, e);
|
|
5142
|
-
if (!this.dispatchEvent(evt)) {
|
|
5143
|
-
e.preventDefault();
|
|
5144
|
-
e.stopPropagation();
|
|
5145
|
-
e.stopImmediatePropagation();
|
|
5146
|
-
}
|
|
5147
|
-
};
|
|
5148
|
-
/**
|
|
5149
|
-
* 是否为非逐词歌词
|
|
5150
|
-
* @internal
|
|
5151
|
-
*/
|
|
5152
|
-
_getIsNonDynamic() {
|
|
5153
|
-
return this.isNonDynamic;
|
|
5154
|
-
}
|
|
5155
|
-
_baseFontSize = Number.parseFloat(getComputedStyle(this.element).fontSize);
|
|
5156
|
-
get baseFontSize() {
|
|
5157
|
-
return this._baseFontSize;
|
|
5158
|
-
}
|
|
5159
|
-
constructor() {
|
|
5160
|
-
super();
|
|
5161
|
-
this.onResize();
|
|
5162
|
-
this.element.classList.add("amll-lyric-player", "dom-slim");
|
|
5163
|
-
if (this.disableSpring) this.element.classList.add(index_module_default.disableSpring);
|
|
5164
|
-
}
|
|
5165
|
-
rebuildStyle() {
|
|
5166
|
-
const width = this.innerSize[0];
|
|
5167
|
-
const height = this.innerSize[1];
|
|
5168
|
-
this.element.style.setProperty("--amll-lp-width", `${width.toFixed(4)}px`);
|
|
5169
|
-
this.element.style.setProperty("--amll-lp-height", `${height.toFixed(4)}px`);
|
|
5170
|
-
}
|
|
5171
|
-
setWordFadeWidth(value = .5) {
|
|
5172
|
-
super.setWordFadeWidth(value);
|
|
5173
|
-
for (const el of this.currentLyricLineObjects) el.markMaskImageDirty("DomLyricPlayer setWordFadeWidth");
|
|
5174
|
-
}
|
|
5175
|
-
/**
|
|
5176
|
-
* 设置当前播放歌词,要注意传入后这个数组内的信息不得修改,否则会发生错误
|
|
5177
|
-
* @param lines 歌词数组
|
|
5178
|
-
* @param initialTime 初始时间,默认为 0
|
|
5179
|
-
*/
|
|
5180
|
-
setLyricLines(lines, initialTime = 0) {
|
|
5181
|
-
super.setLyricLines(lines, initialTime);
|
|
5182
|
-
if (this.hasDuetLine) this.element.classList.add(index_module_default.hasDuetLine);
|
|
5183
|
-
else this.element.classList.remove(index_module_default.hasDuetLine);
|
|
5184
|
-
for (const line of this.currentLyricLineObjects) {
|
|
5185
|
-
line.removeMouseEventListener("click", this.onLineClickedHandler);
|
|
5186
|
-
line.removeMouseEventListener("contextmenu", this.onLineClickedHandler);
|
|
5187
|
-
line.dispose();
|
|
5188
|
-
}
|
|
5189
|
-
this.currentLyricLineObjects = this.processedLines.map((line, i) => {
|
|
5190
|
-
const lineEl = new LyricLineEl(this, line);
|
|
5191
|
-
lineEl.addMouseEventListener("click", this.onLineClickedHandler);
|
|
5192
|
-
lineEl.addMouseEventListener("contextmenu", this.onLineClickedHandler);
|
|
5193
|
-
this.element.appendChild(lineEl.getElement());
|
|
5194
|
-
this.lyricLinesIndexes.set(lineEl, i);
|
|
5195
|
-
lineEl.markMaskImageDirty("DomLyricPlayer setLyricLines");
|
|
5196
|
-
return lineEl;
|
|
5197
|
-
});
|
|
5198
|
-
this.setLinePosXSpringParams({});
|
|
5199
|
-
this.setLinePosYSpringParams({});
|
|
5200
|
-
this.setLineScaleSpringParams({});
|
|
5201
|
-
this.calcLayout(true).then(() => {
|
|
5202
|
-
this.initialLayoutFinished = true;
|
|
5203
|
-
});
|
|
5204
|
-
}
|
|
5205
|
-
pause() {
|
|
5206
|
-
super.pause();
|
|
5207
|
-
this.interludeDots.pause();
|
|
5208
|
-
for (const line of this.currentLyricLineObjects) line.pause();
|
|
5209
|
-
}
|
|
5210
|
-
resume() {
|
|
5211
|
-
super.resume();
|
|
5212
|
-
this.interludeDots.resume();
|
|
5213
|
-
for (const line of this.currentLyricLineObjects) line.resume();
|
|
5214
|
-
}
|
|
5215
|
-
update(delta = 0) {
|
|
5216
|
-
if (!this.initialLayoutFinished) return;
|
|
5217
|
-
super.update(delta);
|
|
5218
|
-
if (!this.isPageVisible) return;
|
|
5219
|
-
const deltaS = delta / 1e3;
|
|
5220
|
-
for (const line of this.currentLyricLineObjects) line.update(deltaS);
|
|
5221
|
-
}
|
|
5222
|
-
async calcLayout(sync) {
|
|
5223
|
-
await super.calcLayout(sync);
|
|
5224
|
-
const curLine = this.currentLyricLineObjects[this.targetAlignIndex];
|
|
5225
|
-
const curLineEl = curLine.getElement();
|
|
5226
|
-
const curLineVisibility = curLineEl.checkVisibility({ contentVisibilityAuto: true });
|
|
5227
|
-
const playerTop = this.element.getBoundingClientRect().top;
|
|
5228
|
-
if (!curLineVisibility) curLineEl.scrollIntoView({
|
|
5229
|
-
block: "center",
|
|
5230
|
-
behavior: "instant"
|
|
5231
|
-
});
|
|
5232
|
-
const curLineHeight = curLineEl.clientHeight;
|
|
5233
|
-
let scrollToPos = curLineEl.getBoundingClientRect().top - playerTop - this.size[1] * this.alignPosition;
|
|
5234
|
-
if (curLine) switch (this.alignAnchor) {
|
|
5235
|
-
case "bottom":
|
|
5236
|
-
scrollToPos += curLineHeight;
|
|
5237
|
-
break;
|
|
5238
|
-
case "center":
|
|
5239
|
-
scrollToPos += curLineHeight / 2;
|
|
5240
|
-
break;
|
|
5241
|
-
case "top": break;
|
|
5242
|
-
}
|
|
5243
|
-
this.element.scrollBy({
|
|
5244
|
-
top: scrollToPos,
|
|
5245
|
-
behavior: "smooth"
|
|
5246
|
-
});
|
|
5247
|
-
}
|
|
5248
|
-
dispose() {
|
|
5249
|
-
super.dispose();
|
|
5250
|
-
this.element.remove();
|
|
5251
|
-
for (const el of this.currentLyricLineObjects) el.dispose();
|
|
5252
|
-
this.bottomLine.dispose();
|
|
5253
|
-
this.interludeDots.dispose();
|
|
5254
|
-
}
|
|
5255
|
-
};
|
|
5256
|
-
//#endregion
|
|
5257
|
-
//#region src/lyric-player/index.ts
|
|
5258
|
-
/**
|
|
5259
|
-
* 歌词中不雅用语的掩码模式
|
|
5260
|
-
*/
|
|
5261
|
-
var MaskObsceneWordsMode = /* @__PURE__ */ function(MaskObsceneWordsMode) {
|
|
5262
|
-
/** 禁用任何不雅用语掩码 */
|
|
5263
|
-
MaskObsceneWordsMode["Disabled"] = "";
|
|
5264
|
-
/** 完全掩码所有不雅用语 */
|
|
5265
|
-
MaskObsceneWordsMode["FullMask"] = "full-mask";
|
|
5266
|
-
/** 保留首尾字符,屏蔽中间字符 */
|
|
5267
|
-
MaskObsceneWordsMode["PartialMask"] = "partial-mask";
|
|
5268
|
-
return MaskObsceneWordsMode;
|
|
5269
|
-
}(MaskObsceneWordsMode || {});
|
|
5270
|
-
/**
|
|
5271
|
-
* 歌词行的渲染模式
|
|
5272
|
-
* @internal
|
|
5273
|
-
*/
|
|
5274
|
-
var LyricLineRenderMode = /* @__PURE__ */ function(LyricLineRenderMode) {
|
|
5275
|
-
LyricLineRenderMode[LyricLineRenderMode["SOLID"] = 0] = "SOLID";
|
|
5276
|
-
LyricLineRenderMode[LyricLineRenderMode["GRADIENT"] = 1] = "GRADIENT";
|
|
5277
|
-
return LyricLineRenderMode;
|
|
5278
|
-
}(LyricLineRenderMode || {});
|
|
5279
|
-
//#endregion
|
|
5280
4761
|
exports.AbstractBaseRenderer = AbstractBaseRenderer;
|
|
5281
4762
|
exports.BackgroundRender = BackgroundRender;
|
|
5282
4763
|
exports.BaseRenderer = BaseRenderer;
|
|
5283
4764
|
exports.DomLyricPlayer = DomLyricPlayer;
|
|
5284
|
-
exports.
|
|
4765
|
+
exports.LayoutAlignAnchor = LayoutAlignAnchor;
|
|
5285
4766
|
exports.LyricLineMouseEvent = LyricLineMouseEvent;
|
|
5286
4767
|
exports.LyricLineRenderMode = LyricLineRenderMode;
|
|
5287
4768
|
exports.LyricPlayer = DomLyricPlayer;
|