@applemusic-like-lyrics/core 0.5.0 → 0.5.2

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.
@@ -14,6 +14,7 @@ function clamp1(x) {
14
14
  return Math.max(1, x);
15
15
  }
16
16
  var BaseRenderer = class extends AbstractBaseRenderer {
17
+ canvas;
17
18
  observer;
18
19
  flowSpeed = 1;
19
20
  currerntRenderScale = .75;
@@ -538,10 +539,10 @@ function generateControlPoints(width, height, variationFraction = randomRange(.4
538
539
  }
539
540
  //#endregion
540
541
  //#region \0raw:/home/runner/work/applemusic-like-lyrics/applemusic-like-lyrics/packages/core/src/bg-render/mesh-renderer/mesh.frag.glsl
541
- var mesh_frag_default = "precision highp float;\r\n\r\nvarying vec3 v_color;\r\nvarying vec2 v_uv;\r\nuniform sampler2D u_texture;\r\nuniform float u_time;\r\nuniform float u_volume;\r\nuniform float u_alpha;\r\n\r\n// 预计算常量\r\nconst float INV_255 = 1.0 / 255.0;\r\nconst float HALF_INV_255 = 0.5 / 255.0;\r\nconst float GRADIENT_NOISE_A = 52.9829189;\r\nconst vec2 GRADIENT_NOISE_B = vec2(0.06711056, 0.00583715);\r\n\r\n/* Gradient noise from Jorge Jimenez's presentation: */\r\n/* http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare */\r\nfloat gradientNoise(in vec2 uv) {\r\n return fract(GRADIENT_NOISE_A * fract(dot(uv, GRADIENT_NOISE_B)));\r\n}\r\n\r\n// 优化的旋转函数,避免重复计算sin/cos\r\nvec2 rot(vec2 v, float angle) {\r\n float s = sin(angle);\r\n float c = cos(angle);\r\n return vec2(c * v.x - s * v.y, s * v.x + c * v.y);\r\n}\r\n\r\nvoid main() {\r\n // 合并计算以减少指令数\r\n float volumeEffect = u_volume * 2.0;\r\n float timeVolume = u_time + u_volume;\r\n \r\n float dither = INV_255 * gradientNoise(gl_FragCoord.xy) - HALF_INV_255;\r\n vec2 centeredUV = v_uv - vec2(0.2);\r\n vec2 rotatedUV = rot(centeredUV, timeVolume * 2.0);\r\n vec2 finalUV = rotatedUV * max(0.001, 1.0 - volumeEffect) + vec2(0.5);\r\n \r\n vec4 result = texture2D(u_texture, finalUV);\r\n \r\n float alphaVolumeFactor = u_alpha * max(0.5, 1.0 - u_volume * 0.5);\r\n result.rgb *= v_color * alphaVolumeFactor;\r\n result.a *= alphaVolumeFactor;\r\n \r\n result.rgb += vec3(dither);\r\n \r\n float dist = distance(v_uv, vec2(0.5));\r\n float vignette = smoothstep(0.8, 0.3, dist);\r\n float mask = 0.6 + vignette * 0.4;\r\n result.rgb *= mask;\r\n \r\n gl_FragColor = result;\r\n}\r\n";
542
+ var mesh_frag_default = "precision mediump float;\n\nvarying vec3 v_color;\nvarying vec2 v_uv;\nuniform sampler2D u_texture;\nuniform float u_volume;\nuniform float u_alpha;\nuniform float u_sinAngle;\nuniform float u_cosAngle;\n\n// 预计算常量\nconst float INV_255 = 1.0 / 255.0;\nconst float HALF_INV_255 = 0.5 / 255.0;\nconst float GRADIENT_NOISE_A = 52.9829189;\nconst vec2 GRADIENT_NOISE_B = vec2(0.06711056, 0.00583715);\n\nfloat gradientNoise(in vec2 uv) {\n return fract(GRADIENT_NOISE_A * fract(dot(uv, GRADIENT_NOISE_B)));\n}\n\nvoid main() {\n float volumeEffect = u_volume * 2.0;\n\n float dither = INV_255 * gradientNoise(gl_FragCoord.xy) - HALF_INV_255;\n\n vec2 centeredUV = v_uv - vec2(0.2);\n\n vec2 rotatedUV = vec2(\n u_cosAngle * centeredUV.x - u_sinAngle * centeredUV.y,\n u_sinAngle * centeredUV.x + u_cosAngle * centeredUV.y\n );\n\n vec2 finalUV = rotatedUV * max(0.001, 1.0 - volumeEffect) + vec2(0.5);\n \n vec4 result = texture2D(u_texture, finalUV);\n \n float alphaVolumeFactor = u_alpha * max(0.5, 1.0 - u_volume * 0.5);\n result.rgb *= v_color * alphaVolumeFactor;\n result.a *= alphaVolumeFactor;\n \n result.rgb += vec3(dither);\n \n float dist = distance(v_uv, vec2(0.5));\n float vignette = smoothstep(0.8, 0.3, dist);\n float mask = 0.6 + vignette * 0.4;\n result.rgb *= mask;\n \n gl_FragColor = result;\n}\n";
542
543
  //#endregion
543
544
  //#region \0raw:/home/runner/work/applemusic-like-lyrics/applemusic-like-lyrics/packages/core/src/bg-render/mesh-renderer/mesh.vert.glsl
544
- var mesh_vert_default = "precision highp float;\n\nattribute vec2 a_pos;\nattribute vec3 a_color;\nattribute vec2 a_uv;\nvarying vec3 v_color;\nvarying vec2 v_uv;\n\nuniform float u_aspect;\n\nvoid main() {\n v_color = a_color;\n v_uv = a_uv;\n vec2 pos = a_pos;\n if (u_aspect > 1.0) {\n pos.y *= u_aspect;\n } else {\n pos.x /= u_aspect;\n }\n gl_Position = vec4(pos, 0.0, 1.0);\n}\n";
545
+ var mesh_vert_default = "precision mediump float;\n\nattribute vec2 a_pos;\nattribute vec3 a_color;\nattribute vec2 a_uv;\nvarying vec3 v_color;\nvarying vec2 v_uv;\n\nuniform float u_aspect;\n\nvoid main() {\n v_color = a_color;\n v_uv = a_uv;\n vec2 pos = a_pos;\n if (u_aspect > 1.0) {\n pos.y *= u_aspect;\n } else {\n pos.x /= u_aspect;\n }\n gl_Position = vec4(pos, 0.0, 1.0);\n}\n";
545
546
  //#endregion
546
547
  //#region src/bg-render/mesh-renderer/index.ts
547
548
  /**
@@ -572,6 +573,7 @@ function easeInOutSine(x) {
572
573
  return -(Math.cos(Math.PI * x) - 1) / 2;
573
574
  }
574
575
  var GLProgram = class {
576
+ label;
575
577
  gl;
576
578
  program;
577
579
  vertexShader;
@@ -653,6 +655,10 @@ var GLProgram = class {
653
655
  }
654
656
  };
655
657
  var Mesh = class {
658
+ gl;
659
+ attrPos;
660
+ attrColor;
661
+ attrUV;
656
662
  vertexWidth = 0;
657
663
  vertexHeight = 0;
658
664
  vertexBuffer;
@@ -1075,6 +1081,7 @@ var BHPMesh = class extends Mesh {
1075
1081
  }
1076
1082
  };
1077
1083
  var GLTexture = class {
1084
+ gl;
1078
1085
  tex;
1079
1086
  constructor(gl, albumImageData) {
1080
1087
  this.gl = gl;
@@ -1240,11 +1247,14 @@ var MeshGradientRenderer = class extends BaseRenderer {
1240
1247
  gl.clear(gl.COLOR_BUFFER_BIT);
1241
1248
  this.mainProgram.use();
1242
1249
  gl.activeTexture(gl.TEXTURE0);
1243
- this.mainProgram.setUniform1f("u_time", tickTime / 1e4);
1250
+ const uTime = tickTime / 1e4;
1244
1251
  this.mainProgram.setUniform1f("u_aspect", this.manualControl ? 1 : this.canvas.width / this.canvas.height);
1245
1252
  this.mainProgram.setUniform1i("u_texture", 0);
1246
1253
  this.mainProgram.setUniform1f("u_volume", this.volume);
1247
1254
  this.mainProgram.setUniform1f("u_alpha", 1);
1255
+ const angle = (uTime + this.volume) * 2;
1256
+ this.mainProgram.setUniform1f("u_sinAngle", Math.sin(angle));
1257
+ this.mainProgram.setUniform1f("u_cosAngle", Math.cos(angle));
1248
1258
  state.texture.bind();
1249
1259
  state.mesh.bind();
1250
1260
  state.mesh.draw();
@@ -1482,6 +1492,7 @@ var TimedContainer = class extends Container {
1482
1492
  time = 0;
1483
1493
  };
1484
1494
  var PixiRenderer = class extends BaseRenderer {
1495
+ canvas;
1485
1496
  app;
1486
1497
  curContainer;
1487
1498
  staticMode = false;
@@ -1706,8 +1717,11 @@ var BackgroundRender = class BackgroundRender {
1706
1717
  //#region src/styles/lyric-player.module.css
1707
1718
  var lyric_player_module_default = {
1708
1719
  "active": "FmKaba_active",
1720
+ "bgWrapper": "FmKaba_bgWrapper",
1721
+ "bgWrapperActive": "FmKaba_bgWrapperActive",
1722
+ "bgWrapperHidden": "FmKaba_bgWrapperHidden",
1723
+ "bgWrapperTop": "FmKaba_bgWrapperTop",
1709
1724
  "bottomLine": "FmKaba_bottomLine",
1710
- "dirty": "FmKaba_dirty",
1711
1725
  "disableSpring": "FmKaba_disableSpring",
1712
1726
  "duet": "FmKaba_duet",
1713
1727
  "emphasize": "FmKaba_emphasize",
@@ -1718,8 +1732,10 @@ var lyric_player_module_default = {
1718
1732
  "lyricBgLine": "FmKaba_lyricBgLine",
1719
1733
  "lyricDuetLine": "FmKaba_lyricDuetLine",
1720
1734
  "lyricLine": "FmKaba_lyricLine",
1735
+ "lyricLineWrapper": "FmKaba_lyricLineWrapper",
1721
1736
  "lyricMainLine": "FmKaba_lyricMainLine",
1722
1737
  "lyricSubLine": "FmKaba_lyricSubLine",
1738
+ "playing": "FmKaba_playing",
1723
1739
  "romanWord": "FmKaba_romanWord",
1724
1740
  "rubyWord": "FmKaba_rubyWord",
1725
1741
  "tmpDisableTransition": "FmKaba_tmpDisableTransition",
@@ -1820,27 +1836,31 @@ function cleanUnintentionalOverlaps(lines) {
1820
1836
  * 尝试让歌词提前最多 600ms 开始,如果有重叠则尝试最多提前 400ms 或上一行时长的 30%
1821
1837
  */
1822
1838
  function tryAdvanceStartTime(lines) {
1823
- for (let i = lines.length - 1; i >= 0; i--) {
1839
+ const defaultAdvanceAmount = 600;
1840
+ const fallbackAdvanceAmount = 400;
1841
+ const fallbackAdvanceRatio = .3;
1842
+ let prevLineStartTime = 0;
1843
+ let prevLineEndTime = 0;
1844
+ let prevMainGroupStartTime = 0;
1845
+ let prevMainGroupEndTime = 0;
1846
+ let hasPrevLine = false;
1847
+ for (let i = 0; i < lines.length; i++) {
1824
1848
  const line = lines[i];
1825
1849
  if (line.isBG) continue;
1826
- let prevLine = null;
1827
- if (i > 0) {
1828
- let prevIdx = i - 1;
1829
- if (lines[prevIdx].isBG) prevIdx--;
1830
- if (prevIdx >= 0) prevLine = lines[prevIdx];
1831
- }
1850
+ const originalStartTime = line.startTime;
1851
+ const originalEndTime = line.endTime;
1832
1852
  let targetAdvanceAmount = 0;
1833
1853
  let safeBoundary = 0;
1834
- if (prevLine) if (line.startTime >= prevLine.endTime) {
1835
- targetAdvanceAmount = 600;
1836
- safeBoundary = prevLine.endTime;
1854
+ if (hasPrevLine) if (originalStartTime >= prevLineEndTime) {
1855
+ targetAdvanceAmount = defaultAdvanceAmount;
1856
+ safeBoundary = prevMainGroupEndTime;
1837
1857
  } else {
1838
- targetAdvanceAmount = 400;
1839
- const prevDuration = prevLine.endTime - prevLine.startTime;
1840
- safeBoundary = prevLine.startTime + prevDuration * .3;
1858
+ targetAdvanceAmount = fallbackAdvanceAmount;
1859
+ const prevDuration = prevLineEndTime - prevLineStartTime;
1860
+ safeBoundary = prevLineStartTime + prevDuration * fallbackAdvanceRatio;
1841
1861
  }
1842
1862
  else {
1843
- targetAdvanceAmount = 600;
1863
+ targetAdvanceAmount = defaultAdvanceAmount;
1844
1864
  safeBoundary = 0;
1845
1865
  }
1846
1866
  const targetTime = line.startTime - targetAdvanceAmount;
@@ -1848,6 +1868,20 @@ function tryAdvanceStartTime(lines) {
1848
1868
  if (newStartTime < line.startTime) line.startTime = newStartTime;
1849
1869
  const nextLine = lines[i + 1];
1850
1870
  if (nextLine?.isBG) nextLine.startTime = line.startTime;
1871
+ if (hasPrevLine) if (originalStartTime < prevMainGroupEndTime && originalEndTime > prevMainGroupStartTime) {
1872
+ prevMainGroupStartTime = Math.min(prevMainGroupStartTime, originalStartTime);
1873
+ prevMainGroupEndTime = Math.max(prevMainGroupEndTime, originalEndTime);
1874
+ } else {
1875
+ prevMainGroupStartTime = originalStartTime;
1876
+ prevMainGroupEndTime = originalEndTime;
1877
+ }
1878
+ else {
1879
+ prevMainGroupStartTime = originalStartTime;
1880
+ prevMainGroupEndTime = originalEndTime;
1881
+ }
1882
+ prevLineStartTime = originalStartTime;
1883
+ prevLineEndTime = originalEndTime;
1884
+ hasPrevLine = true;
1851
1885
  }
1852
1886
  }
1853
1887
  /**
@@ -2125,6 +2159,7 @@ function solveSpring(from, velocity, to, delay = 0, params) {
2125
2159
  //#endregion
2126
2160
  //#region src/lyric-player/base/bottom-line.ts
2127
2161
  var BottomLineEl = class {
2162
+ lyricPlayer;
2128
2163
  element = document.createElement("div");
2129
2164
  left = 0;
2130
2165
  top = 0;
@@ -2247,19 +2282,19 @@ const LayoutAlignAnchor = {
2247
2282
  function computeCurrentInterlude(input) {
2248
2283
  const currentTime = input.currentTime + 20;
2249
2284
  const currentIndex = input.scrollToIndex;
2250
- const lines = input.processedLines;
2285
+ const groups = input.currentGroups;
2251
2286
  const checkGap = (k) => {
2252
- if (k < -1 || k >= lines.length - 1) return void 0;
2253
- const prevLine = k === -1 ? null : lines[k];
2254
- const nextLine = lines[k + 1];
2255
- const gapStart = prevLine ? prevLine.endTime : 0;
2256
- const gapEnd = Math.max(gapStart, nextLine.startTime - 250);
2257
- if (gapEnd - gapStart < 4e3) return;
2287
+ if (k < -1 || k >= groups.length - 1) return void 0;
2288
+ const prevGroup = k === -1 ? null : groups[k];
2289
+ const nextGroup = groups[k + 1];
2290
+ const gapStart = prevGroup ? prevGroup.endTime : 0;
2291
+ const gapEnd = Math.max(gapStart, nextGroup.startTime - 250);
2292
+ if (gapEnd - gapStart < 4e3) return void 0;
2258
2293
  if (gapEnd > currentTime && gapStart < currentTime) return {
2259
2294
  startTime: Math.max(gapStart, currentTime),
2260
2295
  endTime: gapEnd,
2261
2296
  anchorLineIndex: k,
2262
- isNextDuet: nextLine.isDuet
2297
+ isNextDuet: nextGroup.mainLine.getLine().isDuet
2263
2298
  };
2264
2299
  };
2265
2300
  return checkGap(currentIndex - 1) || checkGap(currentIndex) || checkGap(currentIndex + 1);
@@ -2272,8 +2307,8 @@ function computeCurrentInterlude(input) {
2272
2307
  * - 普通播放时根据相邻歌词的时间间隔动态调整 stiffness / damping
2273
2308
  */
2274
2309
  function computeLinePosYSpringParams(input) {
2275
- const { enabled, processedLines, scrollToIndex, isSeeking, isInterludeActive } = input;
2276
- if (!enabled || processedLines.length === 0) return { shouldUpdate: false };
2310
+ const { enabled, currentGroups, scrollToIndex, isSeeking, isInterludeActive } = input;
2311
+ if (!enabled || currentGroups.length === 0) return { shouldUpdate: false };
2277
2312
  if (isSeeking || isInterludeActive) return {
2278
2313
  shouldUpdate: true,
2279
2314
  params: {
@@ -2281,10 +2316,10 @@ function computeLinePosYSpringParams(input) {
2281
2316
  damping: 15
2282
2317
  }
2283
2318
  };
2284
- const currentLine = processedLines[scrollToIndex];
2285
- const prevLine = processedLines[scrollToIndex - 1];
2286
- if (!currentLine || !prevLine) return { shouldUpdate: false };
2287
- const interval = currentLine.startTime - (prevLine.words[0]?.startTime ?? prevLine.startTime);
2319
+ const currentGroup = currentGroups[scrollToIndex];
2320
+ const prevGroup = currentGroups[scrollToIndex - 1];
2321
+ if (!currentGroup || !prevGroup) return { shouldUpdate: false };
2322
+ const interval = currentGroup.startTime - prevGroup.startTime;
2288
2323
  const MIN_INTERVAL = 100;
2289
2324
  const MAX_INTERVAL = 800;
2290
2325
  const clampedInterval = clamp(interval, MIN_INTERVAL, MAX_INTERVAL);
@@ -2302,38 +2337,33 @@ function computeLinePosYSpringParams(input) {
2302
2337
  };
2303
2338
  }
2304
2339
  /**
2305
- * 计算单行歌词在当前布局中的视觉呈现参数。
2340
+ * 计算一组歌词在当前布局中的视觉呈现参数。
2306
2341
  *
2307
2342
  * 根据播放状态、缓冲状态、布局模式与间奏信息,
2308
- * 生成一行歌词最终应使用的 opacity、scale、blur 和 render mode。
2343
+ * 生成一组歌词最终应使用的活跃状态、不透明度与模糊值。
2309
2344
  */
2310
- function computeLinePresentation(input) {
2311
- const { line, lineIndex, scrollToIndex, latestIndex, hasBuffered, hidePassedLines, isPlaying, isNonDynamic, enableScale, enableBlur, isUserScrolling, isCompact, interlude } = input;
2312
- const isActive = hasBuffered || lineIndex >= scrollToIndex && lineIndex < latestIndex;
2345
+ function computeGroupPresentation(input) {
2346
+ const { groupIndex, scrollToIndex, latestIndex, hasBuffered, hidePassedLines, isPlaying, isNonDynamic, enableBlur, isUserScrolling, isCompact, interlude } = input;
2347
+ const isActive = hasBuffered || groupIndex >= scrollToIndex && groupIndex < latestIndex;
2313
2348
  const blurLevel = computeLineBlur({
2314
2349
  enableBlur,
2315
2350
  isUserScrolling,
2316
2351
  isActive,
2317
- itemIndex: lineIndex,
2352
+ itemIndex: groupIndex,
2318
2353
  scrollToIndex,
2319
2354
  latestIndex,
2320
2355
  isCompact
2321
2356
  });
2322
2357
  let targetOpacity;
2323
- if (hidePassedLines) if (lineIndex < (interlude ? interlude.anchorLineIndex + 1 : scrollToIndex) && isPlaying) targetOpacity = 1e-4;
2358
+ if (hidePassedLines) if (groupIndex < (interlude ? interlude.anchorLineIndex + 1 : scrollToIndex) && isPlaying) targetOpacity = 1e-4;
2324
2359
  else if (hasBuffered) targetOpacity = .85;
2325
2360
  else targetOpacity = isNonDynamic ? .2 : 1;
2326
2361
  else if (hasBuffered) targetOpacity = .85;
2327
2362
  else targetOpacity = isNonDynamic ? .2 : 1;
2328
- const SCALE_ASPECT = enableScale ? 97 : 100;
2329
- let targetScale = 100;
2330
- if (!isActive && isPlaying) targetScale = line.isBG ? 75 : SCALE_ASPECT;
2331
2363
  return {
2332
2364
  isActive,
2333
2365
  targetOpacity,
2334
- targetScale,
2335
- blurLevel,
2336
- renderMode: isActive ? LyricLineRenderMode.GRADIENT : LyricLineRenderMode.SOLID
2366
+ blurLevel
2337
2367
  };
2338
2368
  }
2339
2369
  /**
@@ -2489,50 +2519,29 @@ const eqSet = (xs, ys) => xs.size === ys.size && [...xs].every((x) => ys.has(x))
2489
2519
  * - 根据新的热行状态和已有的缓冲行状态,计算出应移除的缓冲行 ID
2490
2520
  */
2491
2521
  function computePlayerTimeState(input) {
2492
- const { time, processedLines, timelineState: { hotLines, bufferedLines } } = input;
2493
- const nextHotLines = new Set(hotLines);
2522
+ const { time, currentGroups, timelineState: { hotGroups, bufferedGroups } } = input;
2523
+ const nextHotGroups = new Set(hotGroups);
2494
2524
  const addedIds = /* @__PURE__ */ new Set();
2495
2525
  const removedHotIds = /* @__PURE__ */ new Set();
2496
2526
  const removedBufferedIds = /* @__PURE__ */ new Set();
2497
- for (const lastHotId of hotLines) {
2498
- const line = processedLines[lastHotId];
2499
- if (!line) {
2500
- nextHotLines.delete(lastHotId);
2501
- removedHotIds.add(lastHotId);
2502
- continue;
2503
- }
2504
- if (line.isBG) continue;
2505
- const nextLine = processedLines[lastHotId + 1];
2506
- if (nextLine?.isBG) {
2507
- const nextMainLine = processedLines[lastHotId + 2];
2508
- const startTime = Math.min(line.startTime, nextLine.startTime);
2509
- const endTime = Math.min(Math.max(line.endTime, nextMainLine?.startTime ?? Number.MAX_VALUE), Math.max(line.endTime, nextLine.endTime));
2510
- if (time < startTime || endTime <= time) {
2511
- nextHotLines.delete(lastHotId);
2512
- removedHotIds.add(lastHotId);
2513
- nextHotLines.delete(lastHotId + 1);
2514
- removedHotIds.add(lastHotId + 1);
2515
- }
2516
- } else if (time < line.startTime || line.endTime <= time) {
2517
- nextHotLines.delete(lastHotId);
2527
+ for (const lastHotId of hotGroups) {
2528
+ const group = currentGroups[lastHotId];
2529
+ if (!group || time < group.startTime || group.endTime <= time) {
2530
+ nextHotGroups.delete(lastHotId);
2518
2531
  removedHotIds.add(lastHotId);
2519
2532
  }
2520
2533
  }
2521
- for (let id = 0; id < processedLines.length; id++) {
2522
- const line = processedLines[id];
2523
- if (!line || line.isBG) continue;
2524
- if (line.startTime <= time && line.endTime > time && !nextHotLines.has(id)) {
2525
- nextHotLines.add(id);
2534
+ for (let id = 0; id < currentGroups.length; id++) {
2535
+ const group = currentGroups[id];
2536
+ if (!group) continue;
2537
+ if (group.startTime <= time && group.endTime > time && !nextHotGroups.has(id)) {
2538
+ nextHotGroups.add(id);
2526
2539
  addedIds.add(id);
2527
- if (processedLines[id + 1]?.isBG) {
2528
- nextHotLines.add(id + 1);
2529
- addedIds.add(id + 1);
2530
- }
2531
2540
  }
2532
2541
  }
2533
- for (const id of bufferedLines) if (!nextHotLines.has(id)) removedBufferedIds.add(id);
2542
+ for (const id of bufferedGroups) if (!nextHotGroups.has(id)) removedBufferedIds.add(id);
2534
2543
  return {
2535
- nextHotLines,
2544
+ nextHotGroups,
2536
2545
  addedIds,
2537
2546
  removedHotIds,
2538
2547
  removedBufferedIds
@@ -2544,10 +2553,10 @@ function computePlayerTimeState(input) {
2544
2553
  * 若当前仍存在缓冲行,则优先对齐到最靠前的缓冲行;
2545
2554
  * 否则对齐到第一条开始时间不小于当前时间的歌词行。
2546
2555
  */
2547
- function pickScrollToIndexForSeek(time, processedLines, bufferedLines) {
2548
- if (bufferedLines.size > 0) return Math.min(...bufferedLines);
2549
- const foundIndex = processedLines.findIndex((line) => line.startTime >= time);
2550
- return foundIndex === -1 ? processedLines.length : foundIndex;
2556
+ function pickScrollToIndexForSeek(time, currentGroups, bufferedGroups) {
2557
+ if (bufferedGroups.size > 0) return Math.min(...bufferedGroups);
2558
+ const foundIndex = currentGroups.findIndex((group) => group.startTime >= time);
2559
+ return foundIndex === -1 ? currentGroups.length : foundIndex;
2551
2560
  }
2552
2561
  /**
2553
2562
  * 提交时间线状态转移的纯函数。
@@ -2557,45 +2566,45 @@ function pickScrollToIndexForSeek(time, processedLines, bufferedLines) {
2557
2566
  * 是否需要重置用户滚动状态、是否需要触发布局。
2558
2567
  */
2559
2568
  function commitPlayerTimeState(input) {
2560
- const { timelineState, time, processedLines, hasBottomContent, stateResult } = input;
2569
+ const { timelineState, time, currentGroups, hasBottomContent, stateResult } = input;
2561
2570
  const { addedIds, removedHotIds, removedBufferedIds } = stateResult;
2562
2571
  const { isSeeking } = timelineState;
2563
2572
  timelineState.currentTime = time;
2564
- timelineState.hotLines = stateResult.nextHotLines;
2573
+ timelineState.hotGroups = stateResult.nextHotGroups;
2565
2574
  let shouldLayout = false;
2566
2575
  let shouldResetScroll = false;
2567
- const linesToEnable = [];
2568
- const linesToDisable = /* @__PURE__ */ new Set();
2576
+ const groupsToEnable = [];
2577
+ const groupsToDisable = /* @__PURE__ */ new Set();
2569
2578
  if (isSeeking) {
2570
- timelineState.bufferedLines = new Set([...timelineState.hotLines]);
2571
- timelineState.scrollToIndex = pickScrollToIndexForSeek(time, processedLines, timelineState.bufferedLines);
2572
- for (const id of removedHotIds) linesToDisable.add(id);
2573
- for (const id of timelineState.hotLines) linesToEnable.push(id);
2574
- for (const id of removedBufferedIds) linesToDisable.add(id);
2579
+ timelineState.bufferedGroups = new Set([...timelineState.hotGroups]);
2580
+ timelineState.scrollToIndex = pickScrollToIndexForSeek(time, currentGroups, timelineState.bufferedGroups);
2581
+ for (const id of removedHotIds) groupsToDisable.add(id);
2582
+ for (const id of timelineState.hotGroups) groupsToEnable.push(id);
2583
+ for (const id of removedBufferedIds) groupsToDisable.add(id);
2575
2584
  shouldResetScroll = true;
2576
2585
  shouldLayout = true;
2577
2586
  } else if (addedIds.size > 0) {
2578
2587
  for (const id of addedIds) {
2579
- timelineState.bufferedLines.add(id);
2580
- linesToEnable.push(id);
2588
+ timelineState.bufferedGroups.add(id);
2589
+ groupsToEnable.push(id);
2581
2590
  }
2582
2591
  for (const id of removedBufferedIds) {
2583
- timelineState.bufferedLines.delete(id);
2584
- linesToDisable.add(id);
2592
+ timelineState.bufferedGroups.delete(id);
2593
+ groupsToDisable.add(id);
2585
2594
  }
2586
- if (timelineState.bufferedLines.size > 0) timelineState.scrollToIndex = Math.min(...timelineState.bufferedLines);
2595
+ if (timelineState.bufferedGroups.size > 0) timelineState.scrollToIndex = Math.min(...timelineState.bufferedGroups);
2587
2596
  shouldLayout = true;
2588
- } else if (removedBufferedIds.size > 0 && eqSet(removedBufferedIds, timelineState.bufferedLines)) {
2589
- for (const id of timelineState.bufferedLines) {
2590
- if (timelineState.hotLines.has(id)) continue;
2591
- timelineState.bufferedLines.delete(id);
2592
- linesToDisable.add(id);
2597
+ } else if (removedBufferedIds.size > 0 && eqSet(removedBufferedIds, timelineState.bufferedGroups)) {
2598
+ for (const id of timelineState.bufferedGroups) {
2599
+ if (timelineState.hotGroups.has(id)) continue;
2600
+ timelineState.bufferedGroups.delete(id);
2601
+ groupsToDisable.add(id);
2593
2602
  }
2594
2603
  shouldLayout = true;
2595
2604
  }
2596
- if (timelineState.bufferedLines.size === 0 && processedLines.length > 0) {
2597
- if (time >= processedLines[processedLines.length - 1].endTime) {
2598
- const targetIndex = hasBottomContent ? processedLines.length : processedLines.length - 1;
2605
+ if (timelineState.bufferedGroups.size === 0 && currentGroups.length > 0) {
2606
+ if (time >= currentGroups[currentGroups.length - 1].endTime) {
2607
+ const targetIndex = hasBottomContent ? currentGroups.length : currentGroups.length - 1;
2599
2608
  if (timelineState.scrollToIndex !== targetIndex) {
2600
2609
  timelineState.scrollToIndex = targetIndex;
2601
2610
  shouldLayout = true;
@@ -2606,8 +2615,8 @@ function commitPlayerTimeState(input) {
2606
2615
  return {
2607
2616
  shouldLayout,
2608
2617
  shouldResetScroll,
2609
- linesToEnable,
2610
- linesToDisable: [...linesToDisable]
2618
+ groupsToEnable,
2619
+ groupsToDisable: [...groupsToDisable]
2611
2620
  };
2612
2621
  }
2613
2622
  //#endregion
@@ -2622,17 +2631,15 @@ var LyricPlayerBase = class extends EventTarget {
2622
2631
  timelineState = {
2623
2632
  currentTime: 0,
2624
2633
  lastCurrentTime: 0,
2625
- hotLines: /* @__PURE__ */ new Set(),
2626
- bufferedLines: /* @__PURE__ */ new Set(),
2634
+ hotGroups: /* @__PURE__ */ new Set(),
2635
+ bufferedGroups: /* @__PURE__ */ new Set(),
2627
2636
  scrollToIndex: 0,
2628
2637
  isSeeking: false,
2629
2638
  isPlaying: true,
2630
2639
  initialLayoutFinished: false
2631
2640
  };
2632
2641
  /** @internal */
2633
- lyricLinesSize = /* @__PURE__ */ new WeakMap();
2634
- /** @internal */
2635
- lyricLineElementMap = /* @__PURE__ */ new WeakMap();
2642
+ lyricGroupElementMap = /* @__PURE__ */ new WeakMap();
2636
2643
  currentLyricLines = [];
2637
2644
  processedLines = [];
2638
2645
  lyricLinesIndexes = /* @__PURE__ */ new WeakMap();
@@ -2664,10 +2671,13 @@ var LyricPlayerBase = class extends EventTarget {
2664
2671
  isScrolled: false,
2665
2672
  isUserScrolling: false
2666
2673
  };
2667
- currentLyricLineObjects = [];
2674
+ currentLyricGroups = [];
2675
+ lyricGroupSize = /* @__PURE__ */ new WeakMap();
2668
2676
  size = [0, 0];
2669
2677
  isPageVisible = true;
2670
2678
  optimizeOptions = {};
2679
+ /** 是否强制让背景人声行始终后置(即始终在主歌词下方显示,不前置背景人声) */
2680
+ alwaysPostpositionBackground = false;
2671
2681
  posXSpringParams = {
2672
2682
  mass: 1,
2673
2683
  damping: 10,
@@ -2717,13 +2727,13 @@ var LyricPlayerBase = class extends EventTarget {
2717
2727
  shouldRelayout = true;
2718
2728
  }
2719
2729
  } else {
2720
- const lineObj = this.lyricLineElementMap.get(entry.target);
2721
- if (lineObj) {
2730
+ const groupObj = this.lyricGroupElementMap.get(entry.target);
2731
+ if (groupObj) {
2722
2732
  const newSize = [entry.target.clientWidth, entry.target.clientHeight];
2723
- const oldSize = this.lyricLinesSize.get(lineObj) ?? [0, 0];
2733
+ const oldSize = this.lyricGroupSize.get(groupObj) ?? [0, 0];
2724
2734
  if (newSize[0] !== oldSize[0] || newSize[1] !== oldSize[1]) {
2725
- this.lyricLinesSize.set(lineObj, newSize);
2726
- lineObj.onLineSizeChange(newSize);
2735
+ this.lyricGroupSize.set(groupObj, newSize);
2736
+ groupObj.onLineSizeChange(newSize);
2727
2737
  shouldRelayout = true;
2728
2738
  }
2729
2739
  }
@@ -2849,7 +2859,7 @@ var LyricPlayerBase = class extends EventTarget {
2849
2859
  }
2850
2860
  }
2851
2861
  rebuildLyricLines() {
2852
- for (const lineObj of this.currentLyricLineObjects) lineObj.rebuildElement();
2862
+ for (const group of this.currentLyricGroups) group.rebuildAllLines();
2853
2863
  }
2854
2864
  /**
2855
2865
  * 根据当前配置处理不雅用语单词
@@ -2951,11 +2961,11 @@ var LyricPlayerBase = class extends EventTarget {
2951
2961
  break;
2952
2962
  }
2953
2963
  this.hasDuetLine = this.processedLines.some((line) => line.isDuet);
2954
- for (const line of this.currentLyricLineObjects) line.dispose();
2964
+ for (const group of this.currentLyricGroups) group.dispose();
2965
+ this.currentLyricGroups = [];
2955
2966
  this.interludeDots.setInterlude(void 0);
2956
- this.timelineState.hotLines.clear();
2957
- this.timelineState.bufferedLines.clear();
2958
- this.setCurrentTime(0, true);
2967
+ this.timelineState.hotGroups.clear();
2968
+ this.timelineState.bufferedGroups.clear();
2959
2969
  if (process.env.NODE_ENV !== "production") console.log("歌词处理完成", this);
2960
2970
  }
2961
2971
  /**
@@ -2981,19 +2991,19 @@ var LyricPlayerBase = class extends EventTarget {
2981
2991
  if (!timelineState.initialLayoutFinished && !timelineState.isSeeking) return;
2982
2992
  const stateResult = computePlayerTimeState({
2983
2993
  time,
2984
- processedLines: this.processedLines,
2994
+ currentGroups: this.currentLyricGroups,
2985
2995
  timelineState
2986
2996
  });
2987
2997
  const hasBottomContent = this.bottomLine.getElement().innerHTML.trim().length > 0;
2988
2998
  const commitResult = commitPlayerTimeState({
2989
2999
  timelineState,
2990
3000
  time,
2991
- processedLines: this.processedLines,
3001
+ currentGroups: this.currentLyricGroups,
2992
3002
  hasBottomContent,
2993
3003
  stateResult
2994
3004
  });
2995
- for (const id of commitResult.linesToDisable) this.currentLyricLineObjects[id]?.disable();
2996
- for (const id of commitResult.linesToEnable) this.currentLyricLineObjects[id]?.enable();
3005
+ for (const id of commitResult.groupsToDisable) this.currentLyricGroups[id]?.disable();
3006
+ for (const id of commitResult.groupsToEnable) this.currentLyricGroups[id]?.enable();
2997
3007
  if (commitResult.shouldResetScroll) this.resetScroll();
2998
3008
  if (commitResult.shouldLayout) this.calcLayout();
2999
3009
  }
@@ -3018,14 +3028,14 @@ var LyricPlayerBase = class extends EventTarget {
3018
3028
  const interlude = computeCurrentInterlude({
3019
3029
  currentTime: this.timelineState.currentTime,
3020
3030
  scrollToIndex: this.timelineState.scrollToIndex,
3021
- processedLines: this.processedLines
3031
+ currentGroups: this.currentLyricGroups
3022
3032
  });
3023
3033
  const isInterludeActive = !!interlude;
3024
3034
  if (this.layoutState.targetAlignIndex !== this.timelineState.scrollToIndex || this.layoutState.lastInterludeState !== isInterludeActive) {
3025
3035
  this.layoutState.lastInterludeState = isInterludeActive;
3026
3036
  const springParams = computeLinePosYSpringParams({
3027
3037
  enabled: this.getEnableSpring(),
3028
- processedLines: this.processedLines,
3038
+ currentGroups: this.currentLyricGroups,
3029
3039
  scrollToIndex: this.timelineState.scrollToIndex,
3030
3040
  isSeeking: this.timelineState.isSeeking,
3031
3041
  isInterludeActive
@@ -3043,17 +3053,15 @@ var LyricPlayerBase = class extends EventTarget {
3043
3053
  if (interlude.anchorLineIndex !== -1) curPos -= totalInterludeHeight;
3044
3054
  }
3045
3055
  const LINE_HEIGHT_FALLBACK = this.size[1] / 5;
3046
- const scrollOffset = this.currentLyricLineObjects.slice(0, targetAlignIndex).reduce((acc, el) => acc + (el.getLine().isBG && this.timelineState.isPlaying ? 0 : this.lyricLinesSize.get(el)?.[1] ?? LINE_HEIGHT_FALLBACK), 0);
3056
+ const scrollOffset = this.currentLyricGroups.slice(0, targetAlignIndex).reduce((acc, group) => acc + (this.lyricGroupSize.get(group)?.[1] ?? LINE_HEIGHT_FALLBACK), 0);
3047
3057
  this.scrollState.scrollBoundary.minOffset = -scrollOffset;
3048
3058
  curPos -= scrollOffset;
3049
3059
  curPos += this.size[1] * this.layoutState.alignPosition;
3050
- const curLine = this.currentLyricLineObjects[targetAlignIndex];
3060
+ const curGroup = this.currentLyricGroups[targetAlignIndex];
3051
3061
  this.layoutState.targetAlignIndex = targetAlignIndex;
3052
- const isBottomFocused = targetAlignIndex === this.currentLyricLineObjects.length;
3062
+ const isBottomFocused = targetAlignIndex === this.currentLyricGroups.length;
3053
3063
  this.bottomLine.setFocused(isBottomFocused);
3054
- let targetLineHeight = 0;
3055
- if (curLine) targetLineHeight = this.lyricLinesSize.get(curLine)?.[1] ?? LINE_HEIGHT_FALLBACK;
3056
- else if (isBottomFocused) targetLineHeight = this.bottomLine.lineSize[1];
3064
+ const targetLineHeight = curGroup ? this.lyricGroupSize.get(curGroup)?.[1] ?? LINE_HEIGHT_FALLBACK : isBottomFocused ? this.bottomLine.lineSize[1] : 0;
3057
3065
  if (targetLineHeight > 0) switch (this.layoutState.alignAnchor) {
3058
3066
  case LayoutAlignAnchor.Bottom:
3059
3067
  curPos -= targetLineHeight;
@@ -3063,13 +3071,12 @@ var LyricPlayerBase = class extends EventTarget {
3063
3071
  break;
3064
3072
  case LayoutAlignAnchor.Top: break;
3065
3073
  }
3066
- const latestIndex = Math.max(...this.timelineState.bufferedLines);
3074
+ const latestIndex = Math.max(...this.timelineState.bufferedGroups);
3067
3075
  let delay = 0;
3068
3076
  let baseDelay = sync ? 0 : .05;
3069
3077
  let setDots = false;
3070
- this.currentLyricLineObjects.forEach((lineObj, i) => {
3071
- const hasBuffered = this.timelineState.bufferedLines.has(i);
3072
- const line = lineObj.getLine();
3078
+ this.currentLyricGroups.forEach((group, i) => {
3079
+ const hasBuffered = this.timelineState.bufferedGroups.has(i);
3073
3080
  const shouldShowDots = interlude && i === interlude.anchorLineIndex + 1;
3074
3081
  if (!setDots && shouldShowDots) {
3075
3082
  setDots = true;
@@ -3081,31 +3088,28 @@ var LyricPlayerBase = class extends EventTarget {
3081
3088
  curPos += this.layoutState.interludeDotsSize[1];
3082
3089
  curPos += dotMargin;
3083
3090
  }
3084
- const presentation = computeLinePresentation({
3085
- line,
3086
- lineIndex: i,
3091
+ const presentation = computeGroupPresentation({
3092
+ groupIndex: i,
3087
3093
  scrollToIndex: this.timelineState.scrollToIndex,
3088
3094
  latestIndex,
3089
3095
  hasBuffered,
3090
3096
  hidePassedLines: this.hidePassedLines,
3091
3097
  isPlaying: this.timelineState.isPlaying,
3092
3098
  isNonDynamic: this.isNonDynamic,
3093
- enableScale: this.enableScale,
3094
3099
  enableBlur: this.enableBlur,
3095
3100
  isUserScrolling: this.scrollState.isUserScrolling,
3096
3101
  isCompact: window.innerWidth <= 1024,
3097
3102
  interlude
3098
3103
  });
3099
- lineObj.setTransform(curPos, presentation.targetScale, presentation.targetOpacity, presentation.blurLevel, force, delay, presentation.renderMode);
3100
- if (line.isBG && (presentation.isActive || !this.timelineState.isPlaying)) curPos += this.lyricLinesSize.get(lineObj)?.[1] ?? LINE_HEIGHT_FALLBACK;
3101
- else if (!line.isBG) curPos += this.lyricLinesSize.get(lineObj)?.[1] ?? LINE_HEIGHT_FALLBACK;
3104
+ group.setTransform(curPos, force, delay, presentation.isActive, presentation.targetOpacity, presentation.blurLevel);
3105
+ curPos += this.lyricGroupSize.get(group)?.[1] ?? LINE_HEIGHT_FALLBACK;
3102
3106
  if (curPos >= 0 && !this.timelineState.isSeeking) {
3103
- if (!line.isBG) delay += baseDelay;
3107
+ delay += baseDelay;
3104
3108
  if (i >= this.timelineState.scrollToIndex) baseDelay /= 1.05;
3105
3109
  }
3106
3110
  });
3107
3111
  this.scrollState.scrollBoundary.maxOffset = curPos + this.scrollState.scrollOffset - this.size[1] / 2;
3108
- const bottomIndex = this.currentLyricLineObjects.length;
3112
+ const bottomIndex = this.currentLyricGroups.length;
3109
3113
  const finalBottomBlur = computeLineBlur({
3110
3114
  enableBlur: this.enableBlur,
3111
3115
  isUserScrolling: this.scrollState.isUserScrolling,
@@ -3135,7 +3139,10 @@ var LyricPlayerBase = class extends EventTarget {
3135
3139
  ...params
3136
3140
  };
3137
3141
  this.bottomLine.lineTransforms.posY.updateParams(this.posYSpringParams);
3138
- for (const line of this.currentLyricLineObjects) line.lineTransforms.posY.updateParams(this.posYSpringParams);
3142
+ for (const group of this.currentLyricGroups) {
3143
+ group.posY.updateParams(this.posYSpringParams);
3144
+ group.bgSlideY.updateParams(this.posYSpringParams);
3145
+ }
3139
3146
  }
3140
3147
  /**
3141
3148
  * 设置所有歌词行在​缩放大小上的弹簧属性,包括重量、弹力和阻力。
@@ -3151,8 +3158,10 @@ var LyricPlayerBase = class extends EventTarget {
3151
3158
  ...this.scaleForBGSpringParams,
3152
3159
  ...params
3153
3160
  };
3154
- for (const lineObj of this.currentLyricLineObjects) if (lineObj.getLine().isBG) lineObj.lineTransforms.scale.updateParams(this.scaleForBGSpringParams);
3155
- else lineObj.lineTransforms.scale.updateParams(this.scaleSpringParams);
3161
+ for (const group of this.currentLyricGroups) {
3162
+ group.mainLine.lineTransforms.scale.updateParams(this.scaleSpringParams);
3163
+ group.bgLine?.lineTransforms.scale.updateParams(this.scaleForBGSpringParams);
3164
+ }
3156
3165
  }
3157
3166
  /**
3158
3167
  * 暂停部分效果演出,目前会暂停播放间奏点的动画,且将背景歌词显示出来
@@ -3224,6 +3233,23 @@ var LyricPlayerBase = class extends EventTarget {
3224
3233
  getCurrentTime() {
3225
3234
  return this.timelineState.currentTime;
3226
3235
  }
3236
+ /**
3237
+ * 设置是否让背景人声行始终后置显示
3238
+ *
3239
+ * 默认情况下,如果背景歌词开始时间早于主歌词,会在主歌词上方展示;
3240
+ * 如果设置为 `true`,则无论时间顺序如何,背景歌词都会始终在主歌词下方展示
3241
+ * @param enable 是否启用始终后置
3242
+ */
3243
+ setAlwaysPostpositionBackground(enable) {
3244
+ if (this.alwaysPostpositionBackground === enable) return;
3245
+ this.alwaysPostpositionBackground = enable;
3246
+ this.rebuildLyricLines();
3247
+ this.calcLayout();
3248
+ }
3249
+ /** 获取当前是否设置了让背景人声行始终后置显示 */
3250
+ getAlwaysPostpositionBackground() {
3251
+ return this.alwaysPostpositionBackground;
3252
+ }
3227
3253
  getElement() {
3228
3254
  return this.element;
3229
3255
  }
@@ -3234,6 +3260,194 @@ var LyricPlayerBase = class extends EventTarget {
3234
3260
  }
3235
3261
  };
3236
3262
  //#endregion
3263
+ //#region src/lyric-player/base/group.ts
3264
+ var LyricLineGroupBase = class {
3265
+ mainLine;
3266
+ bgLine;
3267
+ posY = new Spring(0);
3268
+ bgSlideY = new Spring(-80);
3269
+ top = 0;
3270
+ delay = 0;
3271
+ isActive = false;
3272
+ opacity = 1;
3273
+ blur = 0;
3274
+ isBgFirst = false;
3275
+ constructor(mainLine, bgLine) {
3276
+ this.mainLine = mainLine;
3277
+ this.bgLine = bgLine;
3278
+ }
3279
+ get startTime() {
3280
+ return this.mainLine.getLine().startTime;
3281
+ }
3282
+ get endTime() {
3283
+ return this.mainLine.getLine().endTime;
3284
+ }
3285
+ onLineSizeChange(size) {
3286
+ this.mainLine.onLineSizeChange(size);
3287
+ this.bgLine?.onLineSizeChange(size);
3288
+ }
3289
+ setTransform(top, force, delay, isActive, opacity, blur) {
3290
+ this.top = top;
3291
+ this.delay = delay;
3292
+ this.isActive = isActive;
3293
+ this.opacity = opacity;
3294
+ this.blur = blur;
3295
+ this.setLineTransformations(force, delay);
3296
+ const enableSpring = this.lyricPlayer.getEnableSpring();
3297
+ const hiddenSlideY = (this.lyricPlayer.getAlwaysPostpositionBackground() ? false : this.isBgFirst) ? 80 : -80;
3298
+ const isPlaying = this.lyricPlayer.getIsPlaying();
3299
+ const targetBgSlideY = isActive || !isPlaying ? 0 : hiddenSlideY;
3300
+ if (force || !enableSpring) {
3301
+ this.posY.setPosition(top);
3302
+ this.bgSlideY.setPosition(targetBgSlideY);
3303
+ this.renderStyles();
3304
+ } else {
3305
+ this.posY.setTargetPosition(top, delay);
3306
+ this.bgSlideY.setTargetPosition(targetBgSlideY, delay);
3307
+ }
3308
+ }
3309
+ setLineTransformations(force, delay) {
3310
+ const enableScale = this.lyricPlayer.getEnableScale();
3311
+ const isPlaying = this.lyricPlayer.getIsPlaying();
3312
+ const renderMode = this.isActive ? LyricLineRenderMode.GRADIENT : LyricLineRenderMode.SOLID;
3313
+ const SCALE_ASPECT = enableScale ? 97 : 100;
3314
+ let mainScale = 100;
3315
+ if (!this.isActive && isPlaying) mainScale = SCALE_ASPECT;
3316
+ this.mainLine.setTransform(mainScale, 1, 0, force, delay, renderMode);
3317
+ let bgScale = 100;
3318
+ if (!this.isActive && isPlaying) bgScale = 75;
3319
+ this.bgLine?.setTransform(bgScale, 1, 0, force, delay, renderMode);
3320
+ }
3321
+ update(delta) {
3322
+ if (this.lyricPlayer.getEnableSpring()) {
3323
+ this.posY.update(delta);
3324
+ this.bgSlideY.update(delta);
3325
+ this.renderStyles();
3326
+ }
3327
+ this.mainLine.update(delta);
3328
+ this.bgLine?.update(delta);
3329
+ }
3330
+ rebuildAllLines() {
3331
+ this.mainLine.rebuildElement();
3332
+ this.bgLine?.rebuildElement();
3333
+ }
3334
+ enable(time, shouldPlay) {
3335
+ this.mainLine.enable(time, shouldPlay);
3336
+ this.bgLine?.enable(time, shouldPlay);
3337
+ }
3338
+ disable() {
3339
+ this.mainLine.disable();
3340
+ this.bgLine?.disable();
3341
+ }
3342
+ dispose() {
3343
+ this.mainLine.dispose();
3344
+ this.bgLine?.dispose();
3345
+ }
3346
+ };
3347
+ //#endregion
3348
+ //#region src/lyric-player/dom/lyric-group.ts
3349
+ var LyricLineGroup = class extends LyricLineGroupBase {
3350
+ lyricPlayer;
3351
+ element;
3352
+ bgWrapper;
3353
+ lastIsActive;
3354
+ constructor(lyricPlayer, mainLine) {
3355
+ super(mainLine);
3356
+ this.lyricPlayer = lyricPlayer;
3357
+ this.element = document.createElement("div");
3358
+ this.element.className = lyric_player_module_default.lyricLineWrapper;
3359
+ this.element.appendChild(mainLine.getElement());
3360
+ this.posY.setPosition(window.innerHeight * 2);
3361
+ lyricPlayer.resizeObserver.observe(this.element);
3362
+ }
3363
+ get isInSight() {
3364
+ const t = this.posY.getCurrentPosition();
3365
+ let h = this.lyricPlayer.lyricGroupSize?.get(this)?.[1];
3366
+ if (h === void 0 || h === 0) h = this.element.clientHeight || 0;
3367
+ const pb = this.lyricPlayer.size[1];
3368
+ const ov = this.lyricPlayer.getOverscanPx();
3369
+ return !(t > pb + h + ov || t < -h - ov);
3370
+ }
3371
+ show() {
3372
+ if (!this.element.parentElement) {
3373
+ const playerEl = this.lyricPlayer.getElement();
3374
+ const groups = this.lyricPlayer.currentLyricGroups;
3375
+ const myIndex = groups.indexOf(this);
3376
+ let referenceNode = null;
3377
+ if (myIndex !== -1) {
3378
+ for (let i = myIndex + 1; i < groups.length; i++) if (groups[i].element.parentElement === playerEl) {
3379
+ referenceNode = groups[i].element;
3380
+ break;
3381
+ }
3382
+ }
3383
+ playerEl.insertBefore(this.element, referenceNode);
3384
+ this.lyricPlayer.resizeObserver.observe(this.element);
3385
+ }
3386
+ this.mainLine.show();
3387
+ this.bgLine?.show();
3388
+ }
3389
+ hide() {
3390
+ if (this.element.parentElement) {
3391
+ this.lyricPlayer.resizeObserver.unobserve(this.element);
3392
+ this.element.remove();
3393
+ this.mainLine.teardownContent();
3394
+ this.bgLine?.teardownContent();
3395
+ }
3396
+ }
3397
+ update(delta) {
3398
+ if (this.isInSight) this.show();
3399
+ else this.hide();
3400
+ super.update(delta);
3401
+ }
3402
+ addBgLine(bgLine) {
3403
+ if (this.bgLine) this.bgLine.dispose();
3404
+ if (this.bgWrapper) this.bgWrapper.remove();
3405
+ this.bgLine = bgLine;
3406
+ const bgStartTime = bgLine.getLine().words[0]?.startTime ?? bgLine.getLine().startTime;
3407
+ const mainStartTime = this.mainLine.getLine().words[0]?.startTime ?? this.mainLine.getLine().startTime;
3408
+ this.isBgFirst = bgStartTime < mainStartTime;
3409
+ if (this.mainLine.getLine().isDuet) bgLine.getElement().classList.add(lyric_player_module_default.lyricDuetLine);
3410
+ this.bgWrapper = document.createElement("div");
3411
+ this.bgWrapper.className = lyric_player_module_default.bgWrapper;
3412
+ this.bgWrapper.appendChild(bgLine.getElement());
3413
+ if (!this.lyricPlayer.getAlwaysPostpositionBackground() && this.isBgFirst) {
3414
+ this.bgWrapper.classList.add(lyric_player_module_default.bgWrapperTop);
3415
+ this.element.insertBefore(this.bgWrapper, this.mainLine.getElement());
3416
+ this.bgSlideY.setPosition(80);
3417
+ } else this.element.appendChild(this.bgWrapper);
3418
+ }
3419
+ renderStyles() {
3420
+ const y = this.posY.getCurrentPosition().toFixed(1);
3421
+ this.element.style.transform = `translateY(${y}px)`;
3422
+ this.element.style.opacity = this.opacity.toString();
3423
+ this.element.style.filter = `blur(${Math.min(5, this.blur)}px)`;
3424
+ if (!this.lyricPlayer.getEnableSpring()) this.element.style.transitionDelay = `${this.delay}ms`;
3425
+ if (this.bgWrapper) {
3426
+ if (this.lastIsActive !== this.isActive) {
3427
+ this.lastIsActive = this.isActive;
3428
+ this.bgWrapper.classList.toggle(lyric_player_module_default.bgWrapperActive, this.isActive);
3429
+ }
3430
+ const slideY = this.bgSlideY.getCurrentPosition();
3431
+ const slideYStr = slideY.toFixed(1);
3432
+ const activeProgress = clamp01(1 - Math.abs(slideY) / 80);
3433
+ const scaleStr = (.8 + activeProgress * .2).toFixed(3);
3434
+ this.bgWrapper.style.transform = `translateY(${slideYStr}%) scale(${scaleStr})`;
3435
+ const shouldBgFirst = !this.lyricPlayer.getAlwaysPostpositionBackground() && this.isBgFirst;
3436
+ if (shouldBgFirst) {
3437
+ const currentMarginTop = -(this.bgWrapper.clientHeight || 0) * (1 - activeProgress);
3438
+ this.bgWrapper.style.marginTop = `${currentMarginTop.toFixed(1)}px`;
3439
+ } else this.bgWrapper.style.marginTop = "";
3440
+ const isHidden = slideYStr === (shouldBgFirst ? "80.0" : "-80.0") && !this.isActive;
3441
+ this.bgWrapper.classList.toggle(lyric_player_module_default.bgWrapperHidden, isHidden);
3442
+ }
3443
+ }
3444
+ dispose() {
3445
+ super.dispose();
3446
+ this.lyricPlayer.resizeObserver.unobserve(this.element);
3447
+ this.element.remove();
3448
+ }
3449
+ };
3450
+ //#endregion
3237
3451
  //#region src/utils/is-cjk.ts
3238
3452
  const isCJK = (char) => {
3239
3453
  return /^[\p{Unified_Ideograph}\u0800-\u9FFC]+$/u.test(char);
@@ -3250,10 +3464,7 @@ var LyricLineBase = class extends EventTarget {
3250
3464
  blur = 0;
3251
3465
  opacity = 1;
3252
3466
  delay = 0;
3253
- lineTransforms = {
3254
- posY: new Spring(0),
3255
- scale: new Spring(100)
3256
- };
3467
+ lineTransforms = { scale: new Spring(100) };
3257
3468
  /**
3258
3469
  * 用于 CJK 词语边界检测的分词器
3259
3470
  */
@@ -3263,9 +3474,7 @@ var LyricLineBase = class extends EventTarget {
3263
3474
  * 用于正确处理 emoji、复合字符等
3264
3475
  */
3265
3476
  static graphemeSegmenter = typeof Intl !== "undefined" && Intl.Segmenter ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
3266
- onLineSizeChange(_size) {}
3267
- setTransform(top = this.top, scale = this.scale, opacity = this.opacity, blur = this.blur, _force = false, delay = 0, _mode = LyricLineRenderMode.SOLID) {
3268
- this.top = top;
3477
+ setTransform(scale = this.scale, opacity = this.opacity, blur = this.blur, _force = false, delay = 0, _mode = LyricLineRenderMode.SOLID) {
3269
3478
  this.scale = scale;
3270
3479
  this.opacity = opacity;
3271
3480
  this.blur = blur;
@@ -3388,6 +3597,7 @@ function getMeasurementContext() {
3388
3597
  * 用于平衡歌词行在换行后的各行长度
3389
3598
  */
3390
3599
  var LineBalancer = class {
3600
+ mainElement;
3391
3601
  isBalancing = false;
3392
3602
  lastBalancedContainerWidth = -1;
3393
3603
  constructor(mainElement) {
@@ -3711,13 +3921,9 @@ function generateFadeGradient(width, padding = 0, bright = "rgba(0,0,0,var(--bri
3711
3921
  const leftPos = (1 - widthInTotal) / 2;
3712
3922
  return [`linear-gradient(to right,${bright} ${leftPos * 100}%,${dark} ${(leftPos + widthInTotal) * 100}%)`, totalAspect];
3713
3923
  }
3714
- var RawLyricLineMouseEvent = class extends MouseEvent {
3715
- constructor(line, event) {
3716
- super(event.type, event);
3717
- this.line = line;
3718
- }
3719
- };
3720
3924
  var LyricLineEl = class extends LyricLineBase {
3925
+ lyricPlayer;
3926
+ lyricLine;
3721
3927
  element = document.createElement("div");
3722
3928
  splittedWords = [];
3723
3929
  built = false;
@@ -3743,12 +3949,9 @@ var LyricLineEl = class extends LyricLineBase {
3743
3949
  super();
3744
3950
  this.lyricPlayer = lyricPlayer;
3745
3951
  this.lyricLine = lyricLine;
3746
- this._prevParentEl = lyricPlayer.getElement();
3747
- lyricPlayer.resizeObserver.observe(this.element);
3748
3952
  this.element.setAttribute("class", lyric_player_module_default.lyricLine);
3749
3953
  if (this.lyricLine.isBG) this.element.classList.add(lyric_player_module_default.lyricBgLine);
3750
3954
  if (this.lyricLine.isDuet) this.element.classList.add(lyric_player_module_default.lyricDuetLine);
3751
- this.lineTransforms.posY.setPosition(window.innerHeight * 2);
3752
3955
  this.element.appendChild(document.createElement("div"));
3753
3956
  this.element.appendChild(document.createElement("div"));
3754
3957
  this.element.appendChild(document.createElement("div"));
@@ -3761,33 +3964,6 @@ var LyricLineEl = class extends LyricLineBase {
3761
3964
  if (LyricLineBase.wordSegmenter) this.balancer = new LineBalancer(main);
3762
3965
  this.rebuildStyle();
3763
3966
  }
3764
- listenersMap = /* @__PURE__ */ new Map();
3765
- onMouseEvent = (e) => {
3766
- const wrapped = new RawLyricLineMouseEvent(this, e);
3767
- for (const listener of this.listenersMap.get(e.type) ?? []) listener.call(this, wrapped);
3768
- if (!this.dispatchEvent(wrapped) || wrapped.defaultPrevented) {
3769
- e.preventDefault();
3770
- e.stopPropagation();
3771
- e.stopImmediatePropagation();
3772
- }
3773
- };
3774
- addMouseEventListener(type, callback, options) {
3775
- if (callback) {
3776
- const listeners = this.listenersMap.get(type) ?? /* @__PURE__ */ new Set();
3777
- if (listeners.size === 0) this.element.addEventListener(type, this.onMouseEvent, options);
3778
- listeners.add(callback);
3779
- this.listenersMap.set(type, listeners);
3780
- }
3781
- }
3782
- removeMouseEventListener(type, callback, options) {
3783
- if (callback) {
3784
- const listeners = this.listenersMap.get(type);
3785
- if (listeners) {
3786
- listeners.delete(callback);
3787
- if (listeners.size === 0) this.element.removeEventListener(type, this.onMouseEvent, options);
3788
- }
3789
- }
3790
- }
3791
3967
  areWordsOnSameLine(word1, word2) {
3792
3968
  if (word1?.mainElement && word2?.mainElement) {
3793
3969
  const word1el = word1.mainElement;
@@ -3880,34 +4056,18 @@ var LyricLineEl = class extends LyricLineBase {
3880
4056
  getLine() {
3881
4057
  return this.lyricLine;
3882
4058
  }
3883
- _prevParentEl;
3884
4059
  lastStyle = "";
3885
4060
  show() {
3886
- if (!this.element.parentElement) {
3887
- this._prevParentEl.appendChild(this.element);
3888
- this.lyricPlayer.resizeObserver.observe(this.element);
3889
- }
3890
4061
  if (!this.built) {
3891
4062
  this.rebuildElement();
3892
4063
  this.built = true;
3893
4064
  this.updateMaskImageSync();
3894
4065
  }
3895
- this.rebuildStyle();
3896
- }
3897
- hide() {
3898
- if (this.element.parentElement) {
3899
- this._prevParentEl.removeChild(this.element);
3900
- this.lyricPlayer.resizeObserver.unobserve(this.element);
3901
- }
3902
- if (this.built) {
3903
- this.disposeElements();
3904
- this.built = false;
3905
- }
3906
4066
  }
3907
4067
  rebuildStyle() {
3908
4068
  let style = "";
3909
- style += `transform:translateY(${this.lineTransforms.posY.getCurrentPosition().toFixed(1)}px) scale(${(this.lineTransforms.scale.getCurrentPosition() / 100).toFixed(4)});`;
3910
- if (!this.lyricPlayer.getEnableSpring() && this.isInSight) style += `transition-delay:${this.delay}ms;`;
4069
+ style += `transform: scale(${(this.lineTransforms.scale.getCurrentPosition() / 100).toFixed(4)});`;
4070
+ if (!this.lyricPlayer.getEnableSpring()) style += `transition-delay:${this.delay}ms;`;
3911
4071
  style += `filter:blur(${Math.min(5, this.blur)}px);`;
3912
4072
  if (style !== this.lastStyle) {
3913
4073
  this.lastStyle = style;
@@ -3920,7 +4080,7 @@ var LyricLineEl = class extends LyricLineBase {
3920
4080
  const trans = this.element.children[1];
3921
4081
  const roman = this.element.children[2];
3922
4082
  if (this.lyricPlayer._getIsNonDynamic()) {
3923
- main.innerText = this.lyricLine.words.map((w) => this.lyricPlayer.processObsceneWord(w)).join("");
4083
+ main.textContent = this.lyricLine.words.map((w) => this.lyricPlayer.processObsceneWord(w)).join("");
3924
4084
  this.setSubLinesText(trans, roman);
3925
4085
  return;
3926
4086
  }
@@ -3933,8 +4093,8 @@ var LyricLineEl = class extends LyricLineBase {
3933
4093
  }
3934
4094
  /** 设置翻译与音译行文本 */
3935
4095
  setSubLinesText(trans, roman) {
3936
- trans.innerText = this.lyricLine.translatedLyric;
3937
- roman.innerText = this.lyricLine.romanLyric;
4096
+ trans.textContent = this.lyricLine.translatedLyric;
4097
+ roman.textContent = this.lyricLine.romanLyric;
3938
4098
  }
3939
4099
  getRubyCharCount(word) {
3940
4100
  return (word.ruby ?? []).reduce((total, ruby) => total + ruby.word.length, 0);
@@ -3952,7 +4112,7 @@ var LyricLineEl = class extends LyricLineBase {
3952
4112
  const rubySegments = this.getRubySegments(word);
3953
4113
  for (const ruby of rubySegments) {
3954
4114
  const rubyPartEl = document.createElement("span");
3955
- rubyPartEl.innerText = ruby.word;
4115
+ rubyPartEl.textContent = ruby.word;
3956
4116
  rubyPartEl.dataset.startTime = String(ruby.startTime);
3957
4117
  rubyPartEl.dataset.endTime = String(ruby.endTime);
3958
4118
  rubyWordEl.appendChild(rubyPartEl);
@@ -3969,24 +4129,24 @@ var LyricLineEl = class extends LyricLineBase {
3969
4129
  const trimmedWord = displayWord.trim();
3970
4130
  if (LyricLineBase.graphemeSegmenter) for (const { segment } of LyricLineBase.graphemeSegmenter.segment(trimmedWord)) {
3971
4131
  const charEl = document.createElement("span");
3972
- charEl.innerText = segment;
4132
+ charEl.textContent = segment;
3973
4133
  subElements.push(charEl);
3974
4134
  wordContainer.appendChild(charEl);
3975
4135
  }
3976
4136
  else for (const segment of Array.from(trimmedWord)) {
3977
4137
  const charEl = document.createElement("span");
3978
- charEl.innerText = segment;
4138
+ charEl.textContent = segment;
3979
4139
  subElements.push(charEl);
3980
4140
  wordContainer.appendChild(charEl);
3981
4141
  }
3982
4142
  } else if (hasRomanLine) {
3983
4143
  const wordEl = document.createElement("div");
3984
- wordEl.innerText = displayWord.trim();
4144
+ wordEl.textContent = displayWord.trim();
3985
4145
  wordContainer.appendChild(wordEl);
3986
- } else if (romanWord.length === 0) wordContainer.innerText = displayWord.trim();
4146
+ } else if (romanWord.length === 0) wordContainer.textContent = displayWord.trim();
3987
4147
  if (hasRomanLine) {
3988
4148
  const romanWordEl = document.createElement("div");
3989
- romanWordEl.innerText = romanWord.length > 0 ? romanWord : "\xA0";
4149
+ romanWordEl.textContent = romanWord.length > 0 ? romanWord : "\xA0";
3990
4150
  romanWordEl.classList.add(lyric_player_module_default.romanWord);
3991
4151
  wordContainer.appendChild(romanWordEl);
3992
4152
  }
@@ -4100,7 +4260,7 @@ var LyricLineEl = class extends LyricLineBase {
4100
4260
  const glow = el.animate(frames, {
4101
4261
  duration: animateDu,
4102
4262
  delay: Number.isFinite(wordDe) ? wordDe : 0,
4103
- id: `emphasize-word-${el.innerText}-${i}`,
4263
+ id: `emphasize-word-${el.textContent}-${i}`,
4104
4264
  iterations: 1,
4105
4265
  composite: "replace",
4106
4266
  fill: "both"
@@ -4350,25 +4510,19 @@ var LyricLineEl = class extends LyricLineBase {
4350
4510
  this.element.style.setProperty("--bright-mask-alpha", this.currentBrightAlpha.toFixed(3));
4351
4511
  this.element.style.setProperty("--dark-mask-alpha", this.currentDarkAlpha.toFixed(3));
4352
4512
  }
4353
- setTransform(top = this.top, scale = this.scale, opacity = 1, blur = 0, force = false, delay = 0, mode = LyricLineRenderMode.SOLID) {
4354
- super.setTransform(top, scale, opacity, blur, force, delay);
4513
+ setTransform(scale = this.scale, opacity = 1, blur = 0, force = false, delay = 0, mode = LyricLineRenderMode.SOLID) {
4514
+ super.setTransform(scale, opacity, blur, force, delay);
4355
4515
  this.renderMode = mode;
4356
- const beforeInSight = this.isInSight;
4357
4516
  const enableSpring = this.lyricPlayer.getEnableSpring();
4358
- this.top = top;
4517
+ this.top = 0;
4359
4518
  this.scale = scale;
4360
4519
  this.delay = delay * 1e3 | 0;
4361
4520
  const main = this.element.children[0];
4362
4521
  main.style.opacity = `${opacity}`;
4363
4522
  if (force || !enableSpring) {
4364
4523
  this.blur = Math.min(32, blur);
4365
- this.lineTransforms.posY.setPosition(top);
4366
4524
  this.lineTransforms.scale.setPosition(scale);
4367
- if (!enableSpring) {
4368
- const afterInSight = this.isInSight;
4369
- if (beforeInSight || afterInSight) this.show();
4370
- else this.hide();
4371
- } else this.rebuildStyle();
4525
+ this.rebuildStyle();
4372
4526
  const currentScale = this.lineTransforms.scale.getCurrentPosition();
4373
4527
  this.updateMaskAlphaTargets(currentScale / 100);
4374
4528
  this.currentBrightAlpha = this.targetBrightAlpha;
@@ -4376,35 +4530,31 @@ var LyricLineEl = class extends LyricLineBase {
4376
4530
  this.element.style.setProperty("--bright-mask-alpha", String(this.currentBrightAlpha));
4377
4531
  this.element.style.setProperty("--dark-mask-alpha", String(this.currentDarkAlpha));
4378
4532
  } else {
4379
- this.lineTransforms.posY.setTargetPosition(top, delay);
4380
4533
  this.lineTransforms.scale.setTargetPosition(scale);
4381
4534
  if (this.blur !== Math.min(5, blur)) {
4382
4535
  this.blur = Math.min(5, blur);
4383
- const roundedBlur = blur.toFixed(3);
4384
- this.element.style.filter = `blur(${roundedBlur}px)`;
4536
+ this.element.style.filter = `blur(${blur.toFixed(3)}px)`;
4385
4537
  }
4386
4538
  }
4387
4539
  }
4388
4540
  update(delta = 0) {
4389
4541
  if (!this.lyricPlayer.getEnableSpring()) return;
4390
- this.lineTransforms.posY.update(delta);
4391
4542
  this.lineTransforms.scale.update(delta);
4392
- if (this.isInSight) this.show();
4393
- else this.hide();
4543
+ this.rebuildStyle();
4544
+ if (!this.built) return;
4394
4545
  const currentScale = this.lineTransforms.scale.getCurrentPosition() / 100;
4395
4546
  this.updateMaskAlphaTargets(currentScale);
4396
4547
  this.applyAlphaToDom(delta);
4397
4548
  }
4549
+ /** @internal */
4398
4550
  _getDebugTargetPos() {
4399
4551
  return `[位移: ${this.top}; 缩放: ${this.scale}; 延时: ${this.delay}]`;
4400
4552
  }
4401
- get isInSight() {
4402
- const t = this.lineTransforms.posY.getCurrentPosition();
4403
- const h = this.lyricPlayer.lyricLinesSize.get(this)?.[1] ?? 0;
4404
- const b = t + h;
4405
- const pb = this.lyricPlayer.size[1];
4406
- const ov = this.lyricPlayer.getOverscanPx();
4407
- return !(t > pb + h + ov || b < -h - ov);
4553
+ teardownContent() {
4554
+ if (this.built) {
4555
+ this.disposeElements();
4556
+ this.built = false;
4557
+ }
4408
4558
  }
4409
4559
  disposeElements() {
4410
4560
  this.balancer?.reset();
@@ -4437,13 +4587,29 @@ var LyricLineEl = class extends LyricLineBase {
4437
4587
  //#endregion
4438
4588
  //#region src/lyric-player/dom/index.ts
4439
4589
  /**
4440
- * 歌词行鼠标相关事件,可以获取到歌词行的索引和歌词行元素
4590
+ * 歌词行鼠标相关事件,可以获取到歌词行的索引、主歌词行以及背景歌词行(如果有)元素
4441
4591
  */
4442
4592
  var LyricLineMouseEvent = class extends MouseEvent {
4443
- constructor(lineIndex, line, event) {
4593
+ lineIndex;
4594
+ line;
4595
+ bgLine;
4596
+ /**
4597
+ * 自定义标志位,用于记录外部是否调用了 `stopPropagation`
4598
+ */
4599
+ isPropagationStopped = false;
4600
+ constructor(lineIndex, line, bgLine, event) {
4444
4601
  super(`line-${event.type}`, event);
4445
4602
  this.lineIndex = lineIndex;
4446
4603
  this.line = line;
4604
+ this.bgLine = bgLine;
4605
+ }
4606
+ stopPropagation() {
4607
+ this.isPropagationStopped = true;
4608
+ super.stopPropagation();
4609
+ }
4610
+ stopImmediatePropagation() {
4611
+ this.isPropagationStopped = true;
4612
+ super.stopImmediatePropagation();
4447
4613
  }
4448
4614
  };
4449
4615
  /**
@@ -4452,7 +4618,8 @@ var LyricLineMouseEvent = class extends MouseEvent {
4452
4618
  * 尽可能贴切 Apple Music for iPad 的歌词效果设计,且做了力所能及的优化措施
4453
4619
  */
4454
4620
  var DomLyricPlayer = class extends LyricPlayerBase {
4455
- currentLyricLineObjects = [];
4621
+ abortController = new AbortController();
4622
+ currentLyricGroups = [];
4456
4623
  onResize() {
4457
4624
  const computedStyles = getComputedStyle(this.element);
4458
4625
  this._baseFontSize = Number.parseFloat(computedStyles.fontSize);
@@ -4461,10 +4628,18 @@ var DomLyricPlayer = class extends LyricPlayerBase {
4461
4628
  supportPlusLighter = CSS.supports("mix-blend-mode", "plus-lighter");
4462
4629
  supportMaskImage = CSS.supports("mask-image", "none");
4463
4630
  innerSize = [0, 0];
4464
- onLineClickedHandler = (e) => {
4465
- const evt = new LyricLineMouseEvent(this.lyricLinesIndexes.get(e.line) ?? -1, e.line, e);
4466
- if (!this.dispatchEvent(evt)) {
4467
- e.preventDefault();
4631
+ onMouseEventHandler = (e) => {
4632
+ const target = e.target;
4633
+ if (!(target instanceof Element)) return;
4634
+ const groupEl = target.closest(`.${lyric_player_module_default.lyricLineWrapper}`);
4635
+ if (!groupEl) return;
4636
+ const group = this.lyricGroupElementMap.get(groupEl);
4637
+ if (!group) return;
4638
+ const mainLine = group.mainLine;
4639
+ const bgLine = group.bgLine;
4640
+ const evt = new LyricLineMouseEvent(this.lyricLinesIndexes.get(mainLine) ?? -1, mainLine, bgLine, e);
4641
+ if (!this.dispatchEvent(evt) || evt.defaultPrevented) e.preventDefault();
4642
+ if (evt.isPropagationStopped) {
4468
4643
  e.stopPropagation();
4469
4644
  e.stopImmediatePropagation();
4470
4645
  }
@@ -4485,11 +4660,16 @@ var DomLyricPlayer = class extends LyricPlayerBase {
4485
4660
  this.onResize();
4486
4661
  this.element.classList.add("amll-lyric-player", "dom");
4487
4662
  if (this.disableSpring) this.element.classList.add(lyric_player_module_default.disableSpring);
4663
+ this.element.addEventListener("click", this.onMouseEventHandler, { signal: this.abortController.signal });
4664
+ this.element.addEventListener("contextmenu", this.onMouseEventHandler, { signal: this.abortController.signal });
4488
4665
  }
4489
4666
  rebuildStyle() {}
4490
4667
  setWordFadeWidth(value = .5) {
4491
4668
  super.setWordFadeWidth(value);
4492
- for (const el of this.currentLyricLineObjects) el.updateMaskImageSync();
4669
+ for (const group of this.currentLyricGroups) {
4670
+ group.mainLine.updateMaskImageSync();
4671
+ group.bgLine?.updateMaskImageSync();
4672
+ }
4493
4673
  }
4494
4674
  /**
4495
4675
  * 设置当前播放歌词,要注意传入后这个数组内的信息不得修改,否则会发生错误
@@ -4501,36 +4681,43 @@ var DomLyricPlayer = class extends LyricPlayerBase {
4501
4681
  if (this.hasDuetLine) this.element.classList.add(lyric_player_module_default.hasDuetLine);
4502
4682
  else this.element.classList.remove(lyric_player_module_default.hasDuetLine);
4503
4683
  if (!this.supportMaskImage) this.element.style.setProperty("--amll-player-time", `${initialTime}`);
4504
- for (const line of this.currentLyricLineObjects) {
4505
- line.removeMouseEventListener("click", this.onLineClickedHandler);
4506
- line.removeMouseEventListener("contextmenu", this.onLineClickedHandler);
4507
- line.dispose();
4508
- }
4509
- this.currentLyricLineObjects = this.processedLines.map((line, i) => {
4684
+ for (const group of this.currentLyricGroups) group.dispose();
4685
+ this.currentLyricGroups = [];
4686
+ let currentGroup = null;
4687
+ for (let i = 0; i < this.processedLines.length; i++) {
4688
+ const line = this.processedLines[i];
4510
4689
  const lineEl = new LyricLineEl(this, line);
4511
- lineEl.addMouseEventListener("click", this.onLineClickedHandler);
4512
- lineEl.addMouseEventListener("contextmenu", this.onLineClickedHandler);
4513
4690
  this.lyricLinesIndexes.set(lineEl, i);
4514
- this.lyricLineElementMap.set(lineEl.getElement(), lineEl);
4515
- return lineEl;
4516
- });
4691
+ if (!line.isBG || !currentGroup) {
4692
+ currentGroup = new LyricLineGroup(this, lineEl);
4693
+ this.currentLyricGroups.push(currentGroup);
4694
+ this.lyricGroupElementMap.set(currentGroup.element, currentGroup);
4695
+ } else currentGroup.addBgLine(lineEl);
4696
+ }
4517
4697
  this.setLinePosXSpringParams({});
4518
4698
  this.setLinePosYSpringParams({});
4519
4699
  this.setLineScaleSpringParams({});
4700
+ this.setCurrentTime(initialTime, true);
4520
4701
  this.calcLayout(true);
4521
4702
  this.update(0);
4522
4703
  }
4523
4704
  pause() {
4524
4705
  super.pause();
4525
- this.element.classList.remove("playing");
4706
+ this.element.classList.remove(lyric_player_module_default.playing);
4526
4707
  this.interludeDots.pause();
4527
- for (const line of this.currentLyricLineObjects) line.pause();
4708
+ for (const group of this.currentLyricGroups) {
4709
+ group.mainLine.pause();
4710
+ group.bgLine?.pause();
4711
+ }
4528
4712
  }
4529
4713
  resume() {
4530
4714
  super.resume();
4531
- this.element.classList.add("playing");
4715
+ this.element.classList.add(lyric_player_module_default.playing);
4532
4716
  this.interludeDots.resume();
4533
- for (const line of this.currentLyricLineObjects) line.resume();
4717
+ for (const group of this.currentLyricGroups) {
4718
+ group.mainLine.resume();
4719
+ group.bgLine?.resume();
4720
+ }
4534
4721
  }
4535
4722
  update(delta = 0) {
4536
4723
  if (!this.timelineState.initialLayoutFinished) return;
@@ -4538,12 +4725,13 @@ var DomLyricPlayer = class extends LyricPlayerBase {
4538
4725
  if (!this.supportMaskImage) this.element.style.setProperty("--amll-player-time", `${this.timelineState.currentTime}`);
4539
4726
  if (!this.isPageVisible) return;
4540
4727
  const deltaS = delta / 1e3;
4541
- for (const line of this.currentLyricLineObjects) line.update(deltaS);
4728
+ for (const group of this.currentLyricGroups) group.update(deltaS);
4542
4729
  }
4543
4730
  dispose() {
4544
4731
  super.dispose();
4732
+ this.abortController.abort();
4545
4733
  this.element.remove();
4546
- for (const el of this.currentLyricLineObjects) el.dispose();
4734
+ for (const group of this.currentLyricGroups) group.dispose();
4547
4735
  this.bottomLine.dispose();
4548
4736
  this.interludeDots.dispose();
4549
4737
  }