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