@applemusic-like-lyrics/core 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,30 +1,28 @@
1
1
  import { Vec2, Vec3 } from "gl-matrix";
2
-
3
- //#region \0rolldown/runtime.js
4
2
  //#endregion
5
3
  //#region src/interfaces.d.ts
6
4
  /**
7
- * 拥有一个 HTML 元素的接口
8
- *
9
- * 可以通过 `getElement` 获取这个类所对应的 HTML 元素实例
10
- */
5
+ * 拥有一个 HTML 元素的接口
6
+ *
7
+ * 可以通过 `getElement` 获取这个类所对应的 HTML 元素实例
8
+ */
11
9
  interface HasElement {
12
10
  /** 获取这个类所对应的 HTML 元素实例 */
13
11
  getElement(): HTMLElement;
14
12
  }
15
13
  /**
16
- * 实现了这个接口的东西需要在使用完毕后
17
- *
18
- * 手动调用 `dispose` 函数来销毁清除占用资源
19
- *
20
- * 以免产生泄露
21
- */
14
+ * 实现了这个接口的东西需要在使用完毕后
15
+ *
16
+ * 手动调用 `dispose` 函数来销毁清除占用资源
17
+ *
18
+ * 以免产生泄露
19
+ */
22
20
  interface Disposable {
23
21
  /**
24
- * 销毁实现了该接口的对象实例,释放占用的资源
25
- *
26
- * 一般情况下,调用本函数后就不可以再调用对象的任何函数了
27
- */
22
+ * 销毁实现了该接口的对象实例,释放占用的资源
23
+ *
24
+ * 一般情况下,调用本函数后就不可以再调用对象的任何函数了
25
+ */
28
26
  dispose(): void;
29
27
  }
30
28
  /** 一个歌词单词 */
@@ -47,9 +45,9 @@ interface LyricWord extends LyricWordBase {
47
45
  /** 一行歌词,存储多个单词 */
48
46
  interface LyricLine {
49
47
  /**
50
- * 该行的所有单词
51
- * 如果是 LyRiC 等只能表达一行歌词的格式,这里就只会有一个单词且通常其始末时间和本结构的 `startTime` 和 `endTime` 相同
52
- */
48
+ * 该行的所有单词
49
+ * 如果是 LyRiC 等只能表达一行歌词的格式,这里就只会有一个单词且通常其始末时间和本结构的 `startTime` 和 `endTime` 相同
50
+ */
53
51
  words: LyricWord[];
54
52
  /** 该行的翻译歌词,将会显示在主歌词行的下方 */
55
53
  translatedLyric: string;
@@ -65,108 +63,114 @@ interface LyricLine {
65
63
  isDuet: boolean;
66
64
  }
67
65
  /**
68
- * 优化歌词行的配置选项
69
- */
66
+ * 优化歌词行的配置选项
67
+ */
70
68
  interface OptimizeLyricOptions {
71
69
  /**
72
- * 规范化歌词中的空格
73
- *
74
- * 将多个连续空格替换为一个空格
75
- * @default true
76
- */
70
+ * 规范化歌词中的空格
71
+ *
72
+ * 将多个连续空格替换为一个空格
73
+ * @default true
74
+ */
77
75
  normalizeSpaces?: boolean;
78
76
  /**
79
- * 是否将行级时间戳强行设为字级时间戳
80
- * @default true
81
- */
77
+ * 是否将行级时间戳强行设为字级时间戳
78
+ * @default true
79
+ */
82
80
  resetLineTimestamps?: boolean;
83
81
  /**
84
- * 把多行背景人声转换为单行背景人声 + 主歌词行的形式
85
- * @default true
86
- */
82
+ * 该选项已不再生效,歌词优化时始终会把多行背景人声转换为
83
+ * 单行背景人声 + 主歌词行的形式
84
+ *
85
+ * @deprecated 现有播放器架构已不再支持多个连续的背景人声行
86
+ */
87
87
  convertExcessiveBackgroundLines?: boolean;
88
88
  /**
89
- * 是否同步主歌词与背景人声的时间
90
- * @default true
91
- */
89
+ * 是否同步主歌词与背景人声的时间
90
+ * @default true
91
+ */
92
92
  syncMainAndBackgroundLines?: boolean;
93
93
  /**
94
- * 清洗非刻意的重叠,以免不必要的多行高亮效果
95
- *
96
- * 如果两行时间轴有重叠的歌词满足下列条件之一:
97
- * * 重叠小于 100ms
98
- * * 重叠时长不足下一行时长的 10%
99
- *
100
- * 则截断上一行歌词的结束时间为下一行歌词的开始时间
101
- * @default true
102
- */
94
+ * 清洗非刻意的重叠,以免不必要的多行高亮效果
95
+ *
96
+ * 重叠**达到** 500ms 时视为有意重叠并予以保留
97
+ *
98
+ * 重叠**不足** 500ms,且满足下列条件之一时视为无意重叠:
99
+ * * 重叠不超过 100ms
100
+ * * 重叠时长不超过下一行时长的 10%
101
+ *
102
+ * 并截断上一行歌词的结束时间为下一行歌词的开始时间
103
+ * @default true
104
+ */
103
105
  cleanUnintentionalOverlaps?: boolean;
104
106
  /**
105
- * 尝试让歌词提前最多 1 秒开始
106
- *
107
- * 有重叠则尝试最多提前 400ms 或上一行时长的 30%
108
- * @default true
109
- */
107
+ * 尝试让歌词提前最多 600ms 开始
108
+ *
109
+ * 与上一行存在重叠时尝试提前 400ms
110
+ *
111
+ * 若重叠时长不足 400ms,则提前重叠时长的 70%
112
+ * @default true
113
+ */
110
114
  tryAdvanceStartTime?: boolean;
111
115
  }
112
116
  //#endregion
113
117
  //#region src/bg-render/base.d.ts
114
118
  declare abstract class AbstractBaseRenderer implements Disposable, HasElement {
115
119
  /**
116
- * 修改背景的流动速度,数字越大越快,默认为 8
117
- * @param speed 背景的流动速度,默认为 8
118
- */
120
+ * 修改背景的流动速度,数字越大越快,默认为 8
121
+ * @param speed 背景的流动速度,默认为 8
122
+ */
119
123
  abstract setFlowSpeed(speed: number): void;
120
124
  /**
121
- * 修改背景的渲染比例,默认是 0.5
122
- *
123
- * 一般情况下这个程度既没有明显瑕疵也不会特别吃性能
124
- * @param scale 背景的渲染比例
125
- */
125
+ * 修改背景的渲染比例,默认是 0.5
126
+ *
127
+ * 一般情况下这个程度既没有明显瑕疵也不会特别吃性能
128
+ * @param scale 背景的渲染比例
129
+ */
126
130
  abstract setRenderScale(scale: number): void;
127
131
  /**
128
- * 是否启用静态模式,即图片在更换后就会保持静止状态并禁用更新,以节省性能
129
- * @param enable 是否启用静态模式
130
- */
132
+ * 是否启用静态模式,即图片在更换后就会保持静止状态并禁用更新,以节省性能
133
+ * @param enable 是否启用静态模式
134
+ */
131
135
  abstract setStaticMode(enable: boolean): void;
132
136
  /**
133
- * 修改背景动画帧率,默认是 30 FPS
134
- *
135
- * 如果设置成 0 则会停止动画
136
- * @param fps 目标帧率,默认 30 FPS
137
- */
137
+ * 修改背景动画帧率,默认是 30 FPS
138
+ *
139
+ * 如果设置成 0 则会停止动画
140
+ * @param fps 目标帧率,默认 30 FPS
141
+ */
138
142
  abstract setFPS(fps: number): void;
139
143
  /**
140
- * 暂停背景动画,画面即便是更新了图片也不会发生变化
141
- */
144
+ * 暂停背景动画,画面即便是更新了图片也不会发生变化
145
+ */
142
146
  abstract pause(): void;
143
147
  /**
144
- * 恢复播放背景动画
145
- */
148
+ * 恢复播放背景动画
149
+ */
146
150
  abstract resume(): void;
147
151
  /**
148
- * 设置背景专辑资源,纹理加载并设置完成后会返回
149
- * @param albumSource 专辑的资源链接,可以是图片或视频链接,抑或是任意 img/video 元素,如果提供字符串链接且为视频则需要指定第二个参数
150
- */
152
+ * 设置背景专辑资源,纹理加载并设置完成后会返回
153
+ * @param albumSource 专辑的资源链接,可以是图片或视频链接,抑或是任意 img/video 元素,如果提供字符串链接且为视频则需要指定第二个参数
154
+ */
151
155
  abstract setAlbum(albumSource: string | HTMLImageElement | HTMLVideoElement, isVideo?: boolean): Promise<void>;
152
156
  /**
153
- * 设置低频的音量大小,范围在 80hz-120hz 之间为宜,取值范围在 [0.0-1.0] 之间
154
- *
155
- * 部分渲染器会根据音量大小调整背景效果(例如根据鼓点跳动)
156
- *
157
- * 如果无法获取到类似的数据,请传入 1.0 作为默认值,或不做任何处理(默认值即 1.0)
158
- * @param volume 低频的音量大小,范围在 50hz-120hz 之间为宜,取值范围在 [0.0-1.0] 之间
159
- */
157
+ * 设置低频的音量大小,范围在 80hz-120hz 之间为宜,取值范围在 [0.0-1.0] 之间
158
+ *
159
+ * 部分渲染器会根据音量大小调整背景效果(例如根据鼓点跳动)
160
+ *
161
+ * 如果无法获取到类似的数据,请传入 1.0 作为默认值,或不做任何处理(默认值即 1.0)
162
+ * @param volume 低频的音量大小,范围在 50hz-120hz 之间为宜,取值范围在 [0.0-1.0] 之间
163
+ */
160
164
  abstract setLowFreqVolume(volume: number): void;
161
165
  /**
162
- * 设置背景是否根据“是否有歌词”这个特征调整自身效果,例如有歌词时会变得更加活跃
163
- *
164
- * 部分渲染器会根据这个特征调整自身效果
165
- *
166
- * 如果不确定是否需要赋值或无法知晓是否包含歌词,请传入 true 或不做任何处理(默认值为 true)
167
- *
168
- * @param hasLyric 是否有歌词,如不确定是否需要赋值,请传入 true 或不做任何处理(默认值为 true)
169
- */
166
+ * 设置背景是否根据“是否有歌词”这个特征调整自身效果,例如有歌词时会变得更加活跃
167
+ *
168
+ * 部分渲染器会根据这个特征调整自身效果
169
+ *
170
+ * 如果不确定是否需要赋值或无法知晓是否包含歌词,请传入 true 或不做任何处理(默认值为 true)
171
+ *
172
+ * @param hasLyric 是否有歌词,如不确定是否需要赋值,请传入 true 或不做任何处理(默认值为 true)
173
+ */
170
174
  abstract setHasLyric(hasLyric: boolean): void;
171
175
  abstract dispose(): void;
172
176
  abstract getElement(): HTMLElement;
@@ -179,47 +183,354 @@ declare abstract class BaseRenderer extends AbstractBaseRenderer {
179
183
  constructor(canvas: HTMLCanvasElement);
180
184
  setRenderScale(scale: number): void;
181
185
  /**
182
- * 当画板元素大小发生变化时此函数会被调用
183
- * 可以在此处重设和渲染器相关的尺寸设置
184
- * 考虑到初始化的时候元素不一定在文档中或出于某些特殊样式状态,尺寸长宽有可能会为 0,请注意进行特判处理
185
- * @param width 画板元素实际的物理像素宽度,有可能为 0
186
- * @param height 画板元素实际的物理像素高度,有可能为 0
187
- */
186
+ * 当画板元素大小发生变化时此函数会被调用
187
+ * 可以在此处重设和渲染器相关的尺寸设置
188
+ * 考虑到初始化的时候元素不一定在文档中或出于某些特殊样式状态,尺寸长宽有可能会为 0,请注意进行特判处理
189
+ * @param width 画板元素实际的物理像素宽度,有可能为 0
190
+ * @param height 画板元素实际的物理像素高度,有可能为 0
191
+ */
188
192
  protected onResize(width: number, height: number): void;
189
193
  /**
190
- * 修改背景的流动速度,数字越大越快,默认为 1
191
- * @param speed 背景的流动速度,默认为 1
192
- */
194
+ * 修改背景的流动速度,数字越大越快,默认为 1
195
+ * @param speed 背景的流动速度,默认为 1
196
+ */
193
197
  setFlowSpeed(speed: number): void;
194
198
  /**
195
- * 是否启用静态模式,即图片在更换后就会保持静止状态并禁用更新,以节省性能
196
- * @param enable 是否启用静态模式
197
- */
199
+ * 是否启用静态模式,即图片在更换后就会保持静止状态并禁用更新,以节省性能
200
+ * @param enable 是否启用静态模式
201
+ */
198
202
  abstract override setStaticMode(enable: boolean): void;
199
203
  /**
200
- * 修改背景动画帧率,默认是 30 FPS
201
- *
202
- * 如果设置成 0 则会停止动画
203
- * @param fps 目标帧率,默认 30 FPS
204
- */
204
+ * 修改背景动画帧率,默认是 30 FPS
205
+ *
206
+ * 如果设置成 0 则会停止动画
207
+ * @param fps 目标帧率,默认 30 FPS
208
+ */
205
209
  abstract override setFPS(fps: number): void;
206
210
  /**
207
- * 暂停背景动画,画面即便是更新了图片也不会发生变化
208
- */
211
+ * 暂停背景动画,画面即便是更新了图片也不会发生变化
212
+ */
209
213
  abstract override pause(): void;
210
214
  /**
211
- * 恢复播放背景动画
212
- */
215
+ * 恢复播放背景动画
216
+ */
213
217
  abstract override resume(): void;
214
218
  /**
215
- * 设置背景专辑资源,纹理加载并设置完成后会返回
216
- * @param albumSource 专辑的资源链接,可以是图片或视频链接,抑或是任意 img/video 元素,如果提供字符串链接且为视频则需要指定第二个参数
217
- */
219
+ * 设置背景专辑资源,纹理加载并设置完成后会返回
220
+ * @param albumSource 专辑的资源链接,可以是图片或视频链接,抑或是任意 img/video 元素,如果提供字符串链接且为视频则需要指定第二个参数
221
+ */
218
222
  abstract override setAlbum(albumSource: string | HTMLImageElement | HTMLVideoElement, isVideo?: boolean): Promise<void>;
223
+ /** 停止监听画板尺寸,供构造失败等尚未接管画板所有权的路径清理 */
224
+ protected disconnectResizeObserver(): void;
219
225
  dispose(): void;
220
226
  override getElement(): HTMLElement;
221
227
  }
222
228
  //#endregion
229
+ //#region src/bg-render/gl-program.d.ts
230
+ /**
231
+ * 着色器程序可用的渲染上下文。
232
+ *
233
+ * 目前的背景渲染器以 WebGL1 为主,但这个封装本身不依赖特定版本,所以两者
234
+ * 都接受。
235
+ */
236
+ type GLRenderingContext = WebGLRenderingContext | WebGL2RenderingContext;
237
+ /**
238
+ * 对 WebGL 着色器程序的一层薄封装,负责编译、链接、缓存 uniform 位置。
239
+ */
240
+ declare class GLProgram implements Disposable {
241
+ private readonly label;
242
+ private gl;
243
+ program: WebGLProgram;
244
+ private vertexShader;
245
+ private fragmentShader;
246
+ readonly attrs: {
247
+ [name: string]: number;
248
+ };
249
+ private uniformLocations;
250
+ constructor(gl: GLRenderingContext, vertexShaderSource: string, fragmentShaderSource: string, label?: string);
251
+ private createShader;
252
+ private createProgram;
253
+ use(): void;
254
+ private notFoundUniforms;
255
+ private warnUniformNotFound;
256
+ /**
257
+ * 取 uniform 位置并缓存。逐帧设置几十个 uniform 时,省下的
258
+ * `getUniformLocation` 调用相当可观。
259
+ */
260
+ private getUniformLocation;
261
+ setUniform1f(name: string, value: number): void;
262
+ setUniform2f(name: string, value1: number, value2: number): void;
263
+ setUniform3f(name: string, value1: number, value2: number, value3: number): void;
264
+ setUniform4f(name: string, value1: number, value2: number, value3: number, value4: number): void;
265
+ setUniform1i(name: string, value: number): void;
266
+ setUniform1fv(name: string, value: Float32Array): void;
267
+ setUniform3fv(name: string, value: Float32Array): void;
268
+ dispose(): void;
269
+ }
270
+ //#endregion
271
+ //#region src/bg-render/palette/types.d.ts
272
+ /**
273
+ * @fileoverview
274
+ * 调色板取色器的公共类型。
275
+ *
276
+ * 移植自 Storyteller-Studios/Impressionist(MIT):
277
+ * - `Impressionist/Abstractions/HSVColor.cs`
278
+ * - `Impressionist/Abstractions/PaletteResult.cs`
279
+ *
280
+ * 颜色一律用 `[x, y, z]` 三元组表示。在 RGB 空间时分量范围是 [0, 255],
281
+ * 与 C# 原实现的 `Vector3` 约定保持一致;在 LAB 空间时则是 L/a/b 的原始取值。
282
+ *
283
+ * @see https://github.com/Storyteller-Studios/Impressionist/blob/master/Impressionist/Abstractions/HSVColor.cs
284
+ * @see https://github.com/Storyteller-Studios/Impressionist/blob/master/Impressionist/Abstractions/PaletteResult.cs
285
+ */
286
+ /** 一个颜色向量,语义随所处色彩空间而定。 */
287
+ type ColorVec3 = [number, number, number];
288
+ /** HSV 颜色,H 取值 [0, 360),S/V 取值 [0, 100]。 */
289
+ interface HSVColor {
290
+ h: number;
291
+ s: number;
292
+ v: number;
293
+ }
294
+ /** 直方图中的一项:一个颜色及其出现次数。 */
295
+ interface ColorCount {
296
+ color: ColorVec3;
297
+ count: number;
298
+ }
299
+ /** 主题色结果。 */
300
+ interface ThemeColorResult {
301
+ /** RGB 空间下的主题色,分量范围 [0, 255]。 */
302
+ color: ColorVec3;
303
+ /** 该主题色是否偏暗(L* <= 50)。 */
304
+ colorIsDark: boolean;
305
+ }
306
+ /** 调色板结果。 */
307
+ interface PaletteResult {
308
+ /** 调色板颜色,RGB 空间,分量范围 [0, 255],长度恒等于请求的数量。 */
309
+ palette: ColorVec3[];
310
+ /** 该调色板整体是否偏暗。 */
311
+ paletteIsDark: boolean;
312
+ /** 生成调色板时顺带算出的主题色。 */
313
+ themeColor: ThemeColorResult;
314
+ }
315
+ /**
316
+ * 调色板的用途,决定取色时的取舍。
317
+ *
318
+ * - `"accent"`:Impressionist 的取向,取强调色用。只保留与主题色明暗一致的候选
319
+ * 色,整套调色板压在明暗轴的一侧,作为强调色时才和界面拉得开对比;亮色封面还
320
+ * 会偏好更聚拢的一支,免得强调色散得没有章法。
321
+ * - `"dominant"`:要封面本身的主色,铺满全屏的背景用。不按明暗筛候选色(否则
322
+ * L* 落在 (40, 60) 的中间调会被整段丢掉,封面的标志色若在这一带就整个消失),
323
+ * 择优时一律取更分散的一支 —— 背景要的是封面的颜色跨度,不是和界面的对比度。
324
+ */
325
+ type PaletteIntent = "accent" | "dominant";
326
+ //#endregion
327
+ //#region src/bg-render/palette/auto.d.ts
328
+ /**
329
+ * 同时跑两种取色算法并择优:暗色调色板偏好更分散的结果,亮色调色板反之偏好
330
+ * 更聚拢的结果(八叉树在亮色上容易退化成一片惨白,所以 0 分散度直接判负)。
331
+ *
332
+ * 但分散度只在两者给出同样多的颜色时才有可比性。调用方要几个色就是几个色,被
333
+ * 重复填充凑满的结果对任何按数量取色的用法都是退化的 —— 拿去做多点渐变会直接
334
+ * 塌成两色平铺 —— 而重复恰恰会拉低分散度,于是在亮色分支里被当成「更聚拢」选
335
+ * 中。所以先比互不相同的颜色数,同数时才轮到原本的分散度规则。
336
+ *
337
+ * 「亮色偏好更聚拢」同样是强调色的取向:强调色散得没有章法不好用,但背景要的正
338
+ * 是封面的颜色跨度,更分散才更像那张封面。所以 `intent` 为 `"dominant"` 时一律
339
+ * 取更分散的一支,不再分明暗两套。见 {@link PaletteIntent}。
340
+ */
341
+ declare function createAutoPalette(sourceColors: readonly ColorCount[], clusterCount: number, ignoreWhite?: boolean, toLab?: boolean, useKMeansPP?: boolean, intent?: PaletteIntent): PaletteResult;
342
+ //#endregion
343
+ //#region src/bg-render/palette/color-utilities.d.ts
344
+ declare function rgbToHsv(color: ColorVec3): HSVColor;
345
+ declare function hsvToRgb(hsv: HSVColor): ColorVec3;
346
+ declare function rgbToXyz(rgb: ColorVec3): ColorVec3;
347
+ declare function xyzToRgb(xyz: ColorVec3): ColorVec3;
348
+ declare function xyzToLab(xyz: ColorVec3): ColorVec3;
349
+ declare function labToXyz(lab: ColorVec3): ColorVec3;
350
+ declare function rgbToLab(rgb: ColorVec3): ColorVec3;
351
+ declare function labToRgb(lab: ColorVec3): ColorVec3;
352
+ declare function channelToLinear(value: number): number;
353
+ /**
354
+ * 把 sRGB 颜色转换到 OkLab。
355
+ *
356
+ * 与本文件其它函数不同,入参与返回的分量取值均为 [0, 1]:OkLab 的矩阵本就定义
357
+ * 在归一化的线性 sRGB 上,没必要为了统一风格多做一次 255 的往返。
358
+ *
359
+ * OkLab 是感知均匀的,在它里面对两个颜色取中点不会像 sRGB 那样发暗发浊,所以
360
+ * 需要混色或者做颜色过渡的地方应当先转到这里来。
361
+ *
362
+ * @see https://bottosson.github.io/posts/oklab/
363
+ */
364
+ declare function srgbToOkLab(rgb: ColorVec3): ColorVec3;
365
+ declare function yToLStar(y: number): number;
366
+ /** 调色板取色时判定「暗色候选」的阈值,比主题色判定更严格。 */
367
+ declare function paletteRgbLStarIsDark(rgb: ColorVec3): boolean;
368
+ /** 调色板取色时判定「亮色候选」的阈值。 */
369
+ declare function paletteRgbLStarIsLight(rgb: ColorVec3): boolean;
370
+ /** 主题色的明暗判定。 */
371
+ declare function rgbLStarIsDark(rgb: ColorVec3): boolean;
372
+ //#endregion
373
+ //#region src/bg-render/palette/histogram.d.ts
374
+ /** 可以用来取色的图像资源。 */
375
+ type HistogramSource = HTMLImageElement | HTMLVideoElement | ImageBitmap | ImageData;
376
+ /**
377
+ * 构建颜色直方图。全透明像素会被跳过,半透明像素按原色计入。
378
+ *
379
+ * @param source 图像资源
380
+ * @param sampleSize 统计前缩放到的最长边像素数,默认 64
381
+ */
382
+ declare function buildColorHistogram(source: HistogramSource, sampleSize?: number): ColorCount[];
383
+ //#endregion
384
+ //#region src/bg-render/palette/kmeans.d.ts
385
+ /** 一个确定性的伪随机数发生器,返回 [0, 1) 区间的浮点数。 */
386
+ type RandomSource = () => number;
387
+ /** 计算主题色,对应 C# 的 `KMeansPaletteGenerator.CreateThemeColor`。 */
388
+ declare function createThemeColor(sourceColors: readonly ColorCount[], ignoreWhite?: boolean, toLab?: boolean, random?: RandomSource): ThemeColorResult;
389
+ /**
390
+ * 生成调色板,对应 C# 的 `KMeansPaletteGenerator.CreatePalette`。
391
+ *
392
+ * `intent` 见 {@link PaletteIntent}:默认的 `"accent"` 沿用 Impressionist 的行为,
393
+ * 铺满全屏的背景应当传 `"dominant"`。
394
+ */
395
+ declare function createKMeansPalette(sourceColors: readonly ColorCount[], clusterCount: number, themeColor: ThemeColorResult, ignoreWhite?: boolean, toLab?: boolean, useKMeansPP?: boolean, intent?: PaletteIntent, random?: RandomSource): PaletteResult;
396
+ //#endregion
397
+ //#region src/bg-render/palette/octtree.d.ts
398
+ /**
399
+ * 生成调色板,对应 C# 的 `OctTreePaletteGenerator.CreatePalette`。
400
+ *
401
+ * `intent` 见 {@link PaletteIntent}:默认的 `"accent"` 沿用 Impressionist 的行为,
402
+ * 铺满全屏的背景应当传 `"dominant"`。
403
+ */
404
+ declare function createOctTreePalette(sourceColors: readonly ColorCount[], clusterCount: number, themeColor?: ThemeColorResult, ignoreWhite?: boolean, intent?: PaletteIntent): PaletteResult;
405
+ //#endregion
406
+ //#region src/bg-render/palette/index.d.ts
407
+ /** 取色算法。 */
408
+ type PaletteAlgorithm = "auto" | "kmeans" | "octtree";
409
+ /** {@link createPaletteFromImage} 的可选项。 */
410
+ interface CreatePaletteOptions {
411
+ /** 取色算法,默认 `"auto"`。 */
412
+ algorithm?: PaletteAlgorithm;
413
+ /** 是否忽略接近纯白的颜色,默认 `false`。 */
414
+ ignoreWhite?: boolean;
415
+ /** K-Means 是否在 LAB 空间聚类,默认 `false`。 */
416
+ toLab?: boolean;
417
+ /** K-Means 是否使用 K-Means++ 初始化,默认 `false`。 */
418
+ useKMeansPP?: boolean;
419
+ /** 调色板的用途,默认 `"accent"`。见 {@link PaletteIntent}。 */
420
+ intent?: PaletteIntent;
421
+ /** 统计直方图前缩放到的最长边像素数,默认 64。 */
422
+ sampleSize?: number;
423
+ }
424
+ /**
425
+ * 从图像资源提取指定数量的主色。
426
+ *
427
+ * 整个流程是同步的:封面会先被缩到 64×64 再统计,因此在主线程上通常只需要几
428
+ * 毫秒,不值得为它单独开一个 Worker。
429
+ *
430
+ * @param source 图像资源,可以是 img/video 元素、`ImageBitmap` 或 `ImageData`
431
+ * @param clusterCount 需要的颜色数量,返回的调色板长度恒等于该值
432
+ */
433
+ declare function createPaletteFromImage(source: HistogramSource, clusterCount: number, options?: CreatePaletteOptions): PaletteResult;
434
+ //#endregion
435
+ //#region src/bg-render/isolation/index.d.ts
436
+ /** Isolation 渲染器的可调选项。 */
437
+ interface IsolationRendererOptions {
438
+ /** 是否启用 lightwave 明度调制,默认 `false`。 */
439
+ lightWave: boolean;
440
+ /** 是否启用屏幕空间抖动,默认 `true`。 */
441
+ dithering: boolean;
442
+ /** 取色算法,默认 `"auto"`。 */
443
+ paletteAlgorithm: PaletteAlgorithm;
444
+ }
445
+ declare class IsolationRenderer extends BaseRenderer {
446
+ /**
447
+ * 新建实例时采用的默认选项。
448
+ *
449
+ * 该对象也可作为配置界面的初始值;实例创建后的调整统一走
450
+ * {@link setOptions}。
451
+ */
452
+ static readonly defaultOptions: Readonly<IsolationRendererOptions>;
453
+ /** 当前环境是否支持该渲染器,选择渲染器前应先问一句。 */
454
+ static isSupported(): boolean;
455
+ private gl;
456
+ private program;
457
+ private quadBuffer;
458
+ private contextLost;
459
+ private _disposed;
460
+ private options;
461
+ private tickHandle;
462
+ private lastTickTime;
463
+ private lastFrameTime;
464
+ private frameTime;
465
+ private maxFPS;
466
+ private paused;
467
+ private staticMode;
468
+ private targetWidth;
469
+ private targetHeight;
470
+ private currentWidth;
471
+ private currentHeight;
472
+ private albumRequestId;
473
+ private albumSource?;
474
+ /**
475
+ * 调色板过渡已经过的毫秒数。
476
+ *
477
+ * 这里刻意不用 `performance.now()`:过渡必须和渲染时钟走同一套时间,否则
478
+ * 暂停、静态模式或限帧的时候过渡进度会和画面对不上。
479
+ */
480
+ private paletteTransitionElapsed;
481
+ /** 过渡起点、终点与当前帧的颜色,均为 OkLab,四个颜色首尾相接。 */
482
+ private readonly fromColors;
483
+ private readonly toColors;
484
+ private readonly colorBuffer;
485
+ /** 取色结果的暂存区,避免每次换封面都新建数组。 */
486
+ private readonly nextColors;
487
+ private readonly randomValues;
488
+ /**
489
+ * 渐变流动参数,依次是波纹频率、波纹幅度、流动速度(已含方向)与渐变轴倾角
490
+ * (弧度)。原实现是在片元着色器里用随机哈希现算的,但它们对整个 draw call
491
+ * 都是常量,挪到 CPU 上由 {@link rollRandomParameters} 随机一次即可 —— 这里
492
+ * 含随机量,若真的逐帧重算,画面会逐帧剧烈跳变。
493
+ */
494
+ private readonly flowParams;
495
+ /** 渐变轴叠加在噪声角度上的抖动,单位弧度,同样是整帧常量。 */
496
+ private angleJitter;
497
+ private readonly paletteOrder;
498
+ constructor(canvas: HTMLCanvasElement);
499
+ private initializeGLResources;
500
+ private readonly onContextLost;
501
+ private readonly onContextRestored;
502
+ /** 重掷整张封面期间保持不变的随机参数,避免画面逐帧跳变。 */
503
+ private rollRandomParameters;
504
+ /** 调整渲染器选项,会立即生效。 */
505
+ setOptions(patch: Partial<IsolationRendererOptions>): void;
506
+ private updatePaletteFromSource;
507
+ private transitionToColors;
508
+ /** 按当前过渡进度就地更新 {@link colorBuffer},不产生任何中间数组。 */
509
+ private updateColorBuffer;
510
+ private checkIfResize;
511
+ private onRedraw;
512
+ private onTick;
513
+ private readonly onTickBinded;
514
+ private requestTick;
515
+ protected override onResize(width: number, height: number): void;
516
+ override setStaticMode(enable: boolean): void;
517
+ override setFPS(fps: number): void;
518
+ override pause(): void;
519
+ override resume(): void;
520
+ private resetFrameClock;
521
+ /**
522
+ * 该次 `setAlbum` 是否还是最新的一次。
523
+ *
524
+ * 与 `MeshGradientRenderer` 不同,这里不看 `contextLost`:取色全在 CPU
525
+ * 上做,上下文丢了也照样能把调色板算完存着,等上下文恢复直接就能画。
526
+ */
527
+ private isCurrentAlbumRequest;
528
+ override setAlbum(albumSource?: string | HTMLImageElement | HTMLVideoElement, isVideo?: boolean): Promise<void>;
529
+ override setLowFreqVolume(_volume: number): void;
530
+ override setHasLyric(_hasLyric: boolean): void;
531
+ override dispose(): void;
532
+ }
533
+ //#endregion
223
534
  //#region src/bg-render/mesh-renderer/index.d.ts
224
535
  declare class ControlPoint {
225
536
  color: Vec3;
@@ -243,7 +554,15 @@ declare class ControlPoint {
243
554
  private updateVTangent;
244
555
  }
245
556
  declare class MeshGradientRenderer extends BaseRenderer {
557
+ /**
558
+ * 当前环境是否支持此渲染器
559
+ */
560
+ static isSupported(): boolean;
246
561
  private gl;
562
+ private contextLost;
563
+ private albumRequestId;
564
+ private albumLoadController?;
565
+ private lastImageData?;
247
566
  private lastFrameTime;
248
567
  private frameTime;
249
568
  private lastTickTime;
@@ -269,6 +588,11 @@ declare class MeshGradientRenderer extends BaseRenderer {
269
588
  private lastFPSUpdate;
270
589
  private currentFPS;
271
590
  private enablePerformanceMonitoring;
591
+ private isCurrentAlbumRequest;
592
+ private initializeGLResources;
593
+ private createMeshState;
594
+ private onContextLost;
595
+ private onContextRestored;
272
596
  setManualControl(enable: boolean): void;
273
597
  setWireFrame(enable: boolean): void;
274
598
  getControlPoint(x: number, y: number): ControlPoint | undefined;
@@ -298,6 +622,10 @@ declare class MeshGradientRenderer extends BaseRenderer {
298
622
  //#region src/bg-render/pixi-renderer.d.ts
299
623
  declare class PixiRenderer extends BaseRenderer {
300
624
  protected override canvas: HTMLCanvasElement;
625
+ /**
626
+ * 当前环境是否支持此渲染器
627
+ */
628
+ static isSupported(): boolean;
301
629
  private app;
302
630
  private curContainer?;
303
631
  private staticMode;
@@ -323,6 +651,13 @@ declare class BackgroundRender<Renderer extends BaseRenderer> implements Abstrac
323
651
  private element;
324
652
  private renderer;
325
653
  constructor(renderer: Renderer, canvas: HTMLCanvasElement);
654
+ /**
655
+ * 获取被包装的渲染器实例。
656
+ *
657
+ * 各个渲染器有自己特有的可调项(例如 {@link IsolationRenderer.setOptions}),
658
+ * 这些项没法通过统一的 `AbstractBaseRenderer` 接口下发,需要拿到实例本体。
659
+ */
660
+ getRenderer(): Renderer;
326
661
  static new<Renderer extends BaseRenderer>(type: {
327
662
  new (canvas: HTMLCanvasElement): Renderer;
328
663
  }): BackgroundRender<Renderer>;
@@ -338,6 +673,54 @@ declare class BackgroundRender<Renderer extends BaseRenderer> implements Abstrac
338
673
  getElement(): HTMLCanvasElement;
339
674
  dispose(): void;
340
675
  }
676
+ //#endregion
677
+ //#region src/utils/time.d.ts
678
+ declare const DURATION_BRAND: unique symbol;
679
+ declare const MEDIA_TIME_BRAND: unique symbol;
680
+ /**
681
+ * 一段时长,内部以毫秒浮点数表示
682
+ */
683
+ type Duration = {
684
+ readonly [DURATION_BRAND]: true;
685
+ };
686
+ /**
687
+ * 媒体时间轴上的一个时间点,内部以毫秒浮点数表示
688
+ *
689
+ * 原点为歌曲起始 0ms
690
+ */
691
+ type MediaTime = {
692
+ readonly [MEDIA_TIME_BRAND]: true;
693
+ };
694
+ declare const Duration: {
695
+ readonly ZERO: Duration;
696
+ readonly fromMillis: (ms: number) => Duration;
697
+ readonly fromSecs: (s: number) => Duration;
698
+ readonly asMillis: (d: Duration) => number;
699
+ readonly asSecsF64: (d: Duration) => number;
700
+ readonly add: (a: Duration, b: Duration) => Duration;
701
+ readonly sub: (a: Duration, b: Duration) => Duration;
702
+ readonly saturatingSub: (a: Duration, b: Duration) => Duration;
703
+ readonly mulF64: (d: Duration, factor: number) => Duration;
704
+ readonly divDuration: (a: Duration, b: Duration) => number;
705
+ readonly min: (a: Duration, b: Duration) => Duration;
706
+ readonly max: (a: Duration, b: Duration) => Duration;
707
+ readonly clampPositive: (d: Duration) => Duration;
708
+ readonly isZero: (d: Duration) => boolean;
709
+ readonly isFinite: (d: Duration) => boolean;
710
+ };
711
+ declare const MediaTime: {
712
+ readonly ZERO: MediaTime;
713
+ readonly fromMillis: (ms: number) => MediaTime;
714
+ readonly asMillis: (t: MediaTime) => number;
715
+ readonly since: (a: MediaTime, b: MediaTime) => Duration;
716
+ readonly saturatingSince: (a: MediaTime, b: MediaTime) => Duration;
717
+ readonly add: (t: MediaTime, d: Duration) => MediaTime;
718
+ readonly sub: (t: MediaTime, d: Duration) => MediaTime;
719
+ readonly min: (a: MediaTime, b: MediaTime) => MediaTime;
720
+ readonly max: (a: MediaTime, b: MediaTime) => MediaTime;
721
+ readonly cmp: (a: MediaTime, b: MediaTime) => number;
722
+ readonly round: (t: MediaTime) => MediaTime;
723
+ };
341
724
  declare namespace spring_d_exports {
342
725
  export { Spring, SpringParams };
343
726
  }
@@ -362,86 +745,85 @@ declare class Spring {
362
745
  private resetSolver;
363
746
  arrived(): boolean;
364
747
  setPosition(targetPosition: number): void;
365
- update(delta?: number): void;
366
- updateParams(params: Partial<SpringParams>, delay?: number): void;
367
- setTargetPosition(targetPosition: number, delay?: number): void;
748
+ update(delta?: Duration): void;
749
+ updateParams(params: Partial<SpringParams>, delay?: Duration): void;
750
+ setTargetPosition(targetPosition: number, delay?: Duration): void;
368
751
  getCurrentPosition(): number;
369
752
  }
370
753
  //#endregion
371
- //#region src/lyric-player/dom/interlude-dots.d.ts
372
- declare class InterludeDots implements HasElement, Disposable {
373
- private element;
374
- private dot0;
375
- private dot1;
376
- private dot2;
377
- private left;
378
- private top;
379
- private playing;
380
- private lastStyle;
381
- private currentInterlude?;
382
- private currentTime;
383
- private targetBreatheDuration;
384
- constructor();
385
- getElement(): HTMLElement;
386
- setTransform(left?: number, top?: number): void;
387
- setInterlude(interlude?: [number, number]): void;
388
- pause(): void;
389
- resume(): void;
390
- update(delta?: number): void;
391
- dispose(): void;
392
- }
393
- //#endregion
394
754
  //#region src/lyric-player/base/bottom-line.d.ts
395
- interface LineTransforms$1 {
396
- posX: Spring;
755
+ /** 底栏的位移动画弹簧 */
756
+ interface BottomLineTransforms {
397
757
  posY: Spring;
398
758
  }
399
- declare class BottomLineEl implements HasElement, Disposable {
400
- private lyricPlayer;
401
- private element;
402
- private left;
403
- private top;
404
- private delay;
759
+ /**
760
+ * 底栏组件的抽象接口
761
+ */
762
+ interface BottomLine extends HasElement, Disposable {
763
+ /**
764
+ * 将底栏放回重建歌词视图时的初始位置
765
+ */
766
+ resetPosition(): void;
767
+ /**
768
+ * 获取供外部插入内容的元素
769
+ */
770
+ getContentElement?(): HTMLElement;
771
+ /**
772
+ * 底栏当前测量得到的尺寸
773
+ *
774
+ * 由播放器的 ResizeObserver 回调写入
775
+ */
405
776
  lineSize: [number, number];
406
- readonly lineTransforms: LineTransforms$1;
407
- private isFocused;
408
- private blur;
409
- constructor(lyricPlayer: LyricPlayerBase);
410
- measureSize(): Promise<[number, number]>;
411
- private lastStyle;
412
- show(): void;
413
- hide(): void;
777
+ /**
778
+ * 底栏的位移弹簧,目标位置与参数由播放器驱动
779
+ */
780
+ readonly lineTransforms: BottomLineTransforms;
781
+ /**
782
+ * 设置底栏是否处于聚焦状态
783
+ *
784
+ * 一般在歌曲播放完毕且底栏有内容时聚焦到底栏并设为 true
785
+ */
414
786
  setFocused(focused: boolean): void;
415
- private rebuildStyle;
416
- getElement(): HTMLElement;
417
- setTransform(left?: number, top?: number, blur?: number, force?: boolean, delay?: number): void;
418
- update(delta?: number): void;
419
- get isInSight(): boolean;
420
- dispose(): void;
787
+ /**
788
+ * 设置底栏的目标位置与模糊值
789
+ * @param top 底栏的 Y 坐标
790
+ * @param blur 底栏的模糊度
791
+ * @param immediate 为 true 时绕过弹簧立刻跳转至目标位置
792
+ * @param delay 弹簧过渡的延迟
793
+ */
794
+ setTransform(top?: number, blur?: number, immediate?: boolean, delay?: Duration): void;
795
+ /**
796
+ * 逐帧推进弹簧动画并应用样式
797
+ * @param delta 距离上一次调用的时长
798
+ */
799
+ update(delta?: Duration): void;
421
800
  }
422
801
  //#endregion
423
802
  //#region src/lyric-player/base/consts.d.ts
424
803
  type ValueOf<T extends Record<PropertyKey, unknown>> = T[keyof T];
425
804
  /** 歌词中不雅用语的掩码模式 */
426
805
  declare const MaskObsceneWordsMode: {
427
- /** 禁用任何不雅用语掩码 */readonly Disabled: ""; /** 完全掩码所有不雅用语 */
428
- readonly FullMask: "full-mask"; /** 保留首尾字符,屏蔽中间字符 */
806
+ /** 禁用任何不雅用语掩码 */
807
+ readonly Disabled: "";
808
+ /** 完全掩码所有不雅用语 */
809
+ readonly FullMask: "full-mask";
810
+ /** 保留首尾字符,屏蔽中间字符 */
429
811
  readonly PartialMask: "partial-mask";
430
812
  };
431
813
  /** 歌词中不雅用语的掩码模式枚举类型,见 {@link MaskObsceneWordsMode} */
432
814
  type MaskObsceneWordsMode = ValueOf<typeof MaskObsceneWordsMode>;
433
815
  /**
434
- * 歌词行的渲染模式
435
- * @internal
436
- */
816
+ * 歌词行的渲染模式
817
+ * @internal
818
+ */
437
819
  declare const LyricLineRenderMode: {
438
820
  readonly SOLID: 0;
439
821
  readonly GRADIENT: 1;
440
822
  };
441
823
  /**
442
- * 歌词行的渲染模式枚举类型,见 {@link LyricLineRenderMode}
443
- * @internal
444
- */
824
+ * 歌词行的渲染模式枚举类型,见 {@link LyricLineRenderMode}
825
+ * @internal
826
+ */
445
827
  type LyricLineRenderMode = ValueOf<typeof LyricLineRenderMode>;
446
828
  /** 布局对齐锚点 */
447
829
  declare const LayoutAlignAnchor: {
@@ -451,30 +833,79 @@ declare const LayoutAlignAnchor: {
451
833
  };
452
834
  /** 布局对齐锚点枚举类型,见 {@link LayoutAlignAnchor} */
453
835
  type LayoutAlignAnchor = ValueOf<typeof LayoutAlignAnchor>;
836
+ /**
837
+ * 触发排版布局更新的原因场景
838
+ */
839
+ declare const LayoutReason: {
840
+ /** 正常播放时间推进 */
841
+ readonly PlaybackTick: "playback-tick";
842
+ /** 容器或窗口尺寸调整 */
843
+ readonly Resize: "resize";
844
+ /** 用户交互挂起开始(触摸/滚轮触发) */
845
+ readonly InteractionStart: "interaction-start";
846
+ /** 连续高频滚动(手指触摸滑动或松手后的 RAF 惯性滑动) */
847
+ readonly ContinuousScroll: "continuous-scroll";
848
+ /** 离散单步滚动(鼠标滚轮单次滚动) */
849
+ readonly DiscreteScroll: "discrete-scroll";
850
+ /** 跳转播放进度 */
851
+ readonly Seek: "seek";
852
+ /** 重新构建歌词视图 */
853
+ readonly RebuildView: "rebuild-view";
854
+ /** 视图结构或样式配置改变 */
855
+ readonly ConfigChange: "config-change";
856
+ };
857
+ /** 触发排版布局更新的原因场景枚举类型,见 {@link LayoutReason} */
858
+ type LayoutReason = ValueOf<typeof LayoutReason>;
859
+ /**
860
+ * 对应各个 LayoutReason 的排版执行策略定义
861
+ */
862
+ interface LayoutStrategy {
863
+ /** 是否禁用阶梯交错动画 */
864
+ disableStagger: boolean;
865
+ /** 是否重置间奏圆点动画 */
866
+ resetInterlude: boolean;
867
+ /**
868
+ * 是否瞬移 Y 轴位置而不经过弹簧动画
869
+ *
870
+ * 一般用于触摸拖动中,避免弹簧动画导致拖动不跟手
871
+ */
872
+ snapPosY: boolean;
873
+ }
874
+ /**
875
+ * 排版原因到排版执行策略的映射字典
876
+ */
877
+ declare const LayoutReasonStrategyMap: Record<LayoutReason, LayoutStrategy>;
878
+ /**
879
+ * 单帧动画时长的钳制上限,用于页面挂起等场景
880
+ *
881
+ * 此时首帧 update 的 delta 携带整个挂起时长,直接透传会导致动画以数秒的时长过冲
882
+ */
883
+ declare const MAX_FRAME_DELTA: Duration;
454
884
  //#endregion
455
885
  //#region src/lyric-player/base/line.d.ts
456
886
  interface LineTransforms {
457
887
  scale: Spring;
458
888
  }
459
889
  /**
460
- * 所有标准歌词行的基类
461
- * @internal
462
- */
890
+ * 所有标准歌词行的基类
891
+ * @internal
892
+ */
463
893
  declare abstract class LyricLineBase extends EventTarget implements Disposable {
464
894
  protected top: number;
465
895
  protected scale: number;
466
896
  protected blur: number;
467
897
  protected opacity: number;
468
- protected delay: number;
898
+ protected delay: Duration;
899
+ protected isUiDirty: boolean;
469
900
  readonly lineTransforms: LineTransforms;
470
901
  /**
471
- * 用于 CJK 词语边界检测的分词器
472
- */
902
+ * 用于 CJK 词语边界检测的分词器
903
+ */
473
904
  static readonly wordSegmenter: Intl.Segmenter | null;
474
905
  /**
475
- * Unicode 标准的全局 Grapheme Cluster 分词器
476
- * 用于正确处理 emoji、复合字符等
477
- */
906
+ * Unicode 标准的全局 Grapheme Cluster 分词器
907
+ * 用于正确处理 emoji、复合字符等
908
+ */
478
909
  static readonly graphemeSegmenter: Intl.Segmenter | null;
479
910
  abstract getLine(): LyricLine;
480
911
  abstract enable(time?: number, shouldPlay?: boolean): void;
@@ -482,20 +913,21 @@ declare abstract class LyricLineBase extends EventTarget implements Disposable {
482
913
  abstract resume(): void;
483
914
  abstract pause(): void;
484
915
  abstract onLineSizeChange(size: [number, number]): void;
485
- setTransform(scale?: number, opacity?: number, blur?: number, _force?: boolean, delay?: number, _mode?: LyricLineRenderMode): void;
916
+ abstract commitChanges(): void;
917
+ setTransform(scale?: number, opacity?: number, blur?: number, delay?: Duration, _mode?: LyricLineRenderMode): void;
486
918
  rebuildElement(): void;
487
919
  /**
488
- * 判定歌词是否可以应用强调辉光效果
489
- *
490
- * 果子在对辉光效果的解释是一种强调(emphasized)效果
491
- *
492
- * 条件是一个单词时长大于等于 1s 且长度小于等于 7
493
- *
494
- * @param word 单词
495
- * @returns 是否可以应用强调辉光效果
496
- */
920
+ * 判定歌词是否可以应用强调辉光效果
921
+ *
922
+ * 果子在对辉光效果的解释是一种强调(emphasized)效果
923
+ *
924
+ * 条件是一个单词时长大于等于 1s 且长度小于等于 7
925
+ *
926
+ * @param word 单词
927
+ * @returns 是否可以应用强调辉光效果
928
+ */
497
929
  static shouldEmphasize(word: LyricWord): boolean;
498
- abstract update(delta?: number): void;
930
+ abstract update(delta?: Duration): void;
499
931
  dispose(): void;
500
932
  }
501
933
  //#endregion
@@ -513,357 +945,1315 @@ declare abstract class LyricLineGroupBase<T extends LyricLineBase = LyricLineBas
513
945
  posY: Spring;
514
946
  bgSlideY: Spring;
515
947
  top: number;
516
- delay: number;
948
+ delay: Duration;
517
949
  isActive: boolean;
518
950
  opacity: number;
519
951
  blur: number;
520
952
  isBgFirst: boolean;
953
+ protected isUiDirty: boolean;
521
954
  constructor(mainLine: T, bgLine?: T | undefined);
522
- get startTime(): number;
523
- get endTime(): number;
955
+ get startTime(): MediaTime;
956
+ get endTime(): MediaTime;
524
957
  onLineSizeChange(size: [number, number]): void;
525
- setTransform(top: number, force: boolean, delay: number, isActive: boolean, opacity: number, blur: number): void;
958
+ onBgSizeChange?(size: [number, number]): void;
959
+ abstract getElement(): Element;
960
+ setTransform(top: number, immediate: boolean, delay: Duration, isActive: boolean, opacity: number, blur: number): void;
526
961
  private setLineTransformations;
527
962
  protected abstract renderStyles(): void;
528
- abstract get isInSight(): boolean;
529
- update(delta: number): void;
963
+ /**
964
+ * 根据当前动画位置判断歌词行是否处于渲染范围内
965
+ *
966
+ * @param includeOverscan 是否包含 overscan 渲染缓冲范围,默认包含;
967
+ * 传入 false 时,仅判断歌词行是否在真实视口范围内
968
+ */
969
+ abstract isInRenderRange(includeOverscan?: boolean): boolean;
970
+ update(delta?: Duration): void;
971
+ commitChanges(): void;
530
972
  rebuildAllLines(): void;
531
973
  enable(time?: number, shouldPlay?: boolean): void;
532
974
  disable(): void;
533
975
  dispose(): void;
534
976
  }
535
977
  //#endregion
978
+ //#region src/lyric-player/base/interlude-dots.d.ts
979
+ /**
980
+ * 间奏点在特定时刻的渲染快照
981
+ */
982
+ interface InterludeDotsSnapshot {
983
+ /**
984
+ * 动画是否仍处于活跃状态
985
+ */
986
+ readonly isActive: boolean;
987
+ /**
988
+ * 三颗圆点各自的不透明度
989
+ */
990
+ readonly dotOpacities: readonly [number, number, number];
991
+ /**
992
+ * 容器缩放值
993
+ */
994
+ readonly scale: number;
995
+ /**
996
+ * 容器整体不透明度
997
+ */
998
+ readonly opacity: number;
999
+ }
1000
+ declare abstract class InterludeDotsBase implements Disposable {
1001
+ private left;
1002
+ private top;
1003
+ readonly posY: Spring;
1004
+ /**
1005
+ * 下一次设置变换位置时是否直接吸附到目标位置
1006
+ *
1007
+ * 演出重建或位置跳变时新旧坐标可能相距很远,走弹簧会看到间奏点从旧位置滑入的残影
1008
+ */
1009
+ private shouldSnapPosY;
1010
+ private currentTime;
1011
+ private playing;
1012
+ private phase;
1013
+ private mode;
1014
+ private fadeElapsedMs;
1015
+ private fadeInitialOpacity;
1016
+ private startTime;
1017
+ private endTime;
1018
+ private anchorTime;
1019
+ private delayEndMs;
1020
+ private bodyEndMs;
1021
+ private totalEndMs;
1022
+ private breathePeriodMs;
1023
+ private segmentMs;
1024
+ private dot3DurationMs;
1025
+ private dot3Target;
1026
+ private readonly mutDotOpacities;
1027
+ private readonly snapshot;
1028
+ /**
1029
+ * 设置间奏区间并锚定演出时间
1030
+ *
1031
+ * @param interlude 间奏起止时间
1032
+ * @param currentTime 当前播放时间,用于把演出重锚到该时刻;未传入时使用间奏起点
1033
+ * @param forceReset 是否强制重建演出,如跳转播放进度或重建歌词视图时
1034
+ * @param anchorLineIndex 间奏锚定的歌词行索引
1035
+ * @returns 本次间奏是否有足够时长显示间奏点
1036
+ */
1037
+ setInterlude(interlude: [MediaTime, MediaTime], currentTime?: MediaTime, forceReset?: boolean, anchorLineIndex?: number): boolean;
1038
+ /**
1039
+ * 清空间奏区间并终止当前演出
1040
+ *
1041
+ * 与 {@link dismiss} 的区别在于本方法会一并抹去区间状态,
1042
+ * 使此后重新进入同一间奏区间时能够重新演出
1043
+ *
1044
+ * @param immediate 是否立即隐藏而非淡出
1045
+ */
1046
+ clearInterlude(immediate?: boolean): void;
1047
+ /**
1048
+ * 结束间奏点演出,默认使用 150ms 淡出
1049
+ * @param immediate 是否立即隐藏
1050
+ */
1051
+ dismiss(immediate?: boolean): void;
1052
+ /**
1053
+ * 设置间奏点的变换位置并立即刷新一次
1054
+ * @param left 横向位置
1055
+ * @param top 纵向位置
1056
+ * @param immediate 是否绕过弹簧直接跳转到目标位置,用于触摸拖动等需要跟手的场景
1057
+ */
1058
+ setTransform(left?: number, top?: number, immediate?: boolean): void;
1059
+ pause(): void;
1060
+ resume(): void;
1061
+ /**
1062
+ * 把演出时钟对齐到指定的媒体时间,由宿主每次推送播放进度时调用
1063
+ */
1064
+ syncClock(time: MediaTime): void;
1065
+ /**
1066
+ * 逐帧推进演出并把当前帧交给子类渲染
1067
+ * @param delta 距离上一次调用的物理时长
1068
+ */
1069
+ update(delta?: Duration): void;
1070
+ /**
1071
+ * 释放演出状态
1072
+ */
1073
+ dispose(): void;
1074
+ /**
1075
+ * 由子类实现的渲染逻辑
1076
+ *
1077
+ * 快照对象会被基类逐帧复用,必须在当前帧内消费完毕,不得保留其引用
1078
+ *
1079
+ * `isActive` 为 `false` 时表示演出已经结束或取消,应当隐藏渲染物
1080
+ *
1081
+ * @param snapshot 当前帧的视觉状态
1082
+ * @param left 由 {@link setTransform} 设置的横向位置
1083
+ * @param top 由 {@link setTransform} 设置的纵向位置经 {@link posY} 平滑后的坐标
1084
+ */
1085
+ protected abstract render(snapshot: Readonly<InterludeDotsSnapshot>, left: number, top: number): void;
1086
+ /**
1087
+ * 进入演出阶段并确定编排方式
1088
+ *
1089
+ * 演出重建后的第一帧位置由外部重新给出,不参与弹簧过渡,
1090
+ * 因此一并重置 {@link shouldSnapPosY}
1091
+ */
1092
+ private enterPerforming;
1093
+ /**
1094
+ * 进入淡出阶段,冻结当前帧的不透明度作为衰减起点
1095
+ */
1096
+ private enterFading;
1097
+ /**
1098
+ * 结束演出,回到既不推进时钟也不渲染的静止状态
1099
+ */
1100
+ private enterIdle;
1101
+ /**
1102
+ * 取消正在进行的淡出
1103
+ *
1104
+ * 只清掉淡出阶段,演出阶段保持原样,随后的收尾仍需据此判断
1105
+ * 是否有可见内容要派发隐藏快照
1106
+ */
1107
+ private cancelFadeOut;
1108
+ /**
1109
+ * 演出被取消时立即隐藏渲染物并清空演出状态
1110
+ */
1111
+ private hidePerformance;
1112
+ /**
1113
+ * 写入三颗圆点当前帧的不透明度
1114
+ *
1115
+ * 最终不透明度由点亮分数映射的透明度与各圆点的错峰入场系数相乘得到
1116
+ *
1117
+ * @param internalMs 距演出开始的经过时间
1118
+ * @param fractions 三颗圆点各自的点亮分数(0~1)
1119
+ */
1120
+ private writeDotOpacities;
1121
+ /**
1122
+ * 物理淡出步进器
1123
+ */
1124
+ private updateFadeOut;
1125
+ /**
1126
+ * 按经过时间求出该时刻的视觉状态
1127
+ *
1128
+ * @remarks 返回的快照对象是内部复用的同一引用,必须在当前帧内消费完毕
1129
+ * @param elapsed 距离本次演出时间锚点的经过时间
1130
+ */
1131
+ private resolveSnapshot;
1132
+ }
1133
+ //#endregion
536
1134
  //#region src/lyric-player/base/layout.d.ts
537
1135
  /**
538
- * 播放器布局状态。
539
- *
540
- * 这部分状态保存布局计算阶段所需的配置项与缓存值,
541
- * 例如对齐方式、间奏点尺寸、上一轮布局命中的目标行等。
542
- * 不描述播放时间线或用户滚动交互,仅记录当前歌词排布。
543
- */
544
- interface PlayerLayoutState {
545
- /** 间奏点元素当前测量得到的尺寸 */
546
- interludeDotsSize: [number, number];
547
- /** 上一轮布局实际对齐的目标歌词行索引 */
548
- targetAlignIndex: number;
549
- /** 上一轮布局时是否处于间奏区间 */
550
- lastInterludeState: boolean;
551
- /** 当前歌词目标行的对齐锚点 */
1136
+ * 布局对齐的静态配置项
1137
+ *
1138
+ * 一般很少改变
1139
+ */
1140
+ interface LayoutConfig {
1141
+ /**
1142
+ * 自动对齐的锚点
1143
+ */
552
1144
  alignAnchor: LayoutAlignAnchor;
553
- /** 当前歌词目标行在播放器高度中的相对对齐位置 */
1145
+ /**
1146
+ * 0.0 - 1.0 的视口相对位置
1147
+ */
1148
+ alignPosition: number;
1149
+ /**
1150
+ * 视口上下额外保留的预渲染距离,单位为像素
1151
+ */
1152
+ overscanPx: number;
1153
+ }
1154
+ /**
1155
+ * 每一帧都可能变化的排版上下文状态
1156
+ */
1157
+ interface LayoutFrameContext {
1158
+ /**
1159
+ * 播放器容器当前总高度
1160
+ */
1161
+ containerHeight: number;
1162
+ /**
1163
+ * 被滚动引擎钳制后的安全滚动量
1164
+ */
1165
+ scrollOffset: number;
1166
+ /**
1167
+ * 当前视口焦点目标
1168
+ */
1169
+ target: FocalTarget;
1170
+ /**
1171
+ * 底栏高度
1172
+ */
1173
+ bottomLineHeight: number;
1174
+ /**
1175
+ * 间奏点状态,若当前排版帧无间奏则为 undefined
1176
+ */
1177
+ interlude?: {
1178
+ /**
1179
+ * 间奏点高度
1180
+ */
1181
+ totalHeight: number;
1182
+ /**
1183
+ * 间奏点应挂在第几行之后
1184
+ * -1 表示首行上方,0 ~ N-1 表示对应行下方
1185
+ **/
1186
+ anchorIndex: number;
1187
+ };
1188
+ }
1189
+ /**
1190
+ * 单行歌词的渲染几何指令
1191
+ */
1192
+ interface RenderInstruction {
1193
+ /**
1194
+ * 当前行歌词的 Y 轴绝对目标坐标
1195
+ */
1196
+ y: number;
1197
+ /**
1198
+ * 当前行歌词的高
1199
+ *
1200
+ * 可能为测量值或估算值
1201
+ */
1202
+ height: number;
1203
+ /**
1204
+ * 当前是否在可视区域(含 Overscan 容差)内,用于剔除渲染
1205
+ */
1206
+ isInViewport: boolean;
1207
+ }
1208
+ /**
1209
+ * 排版计算返回的复合结果
1210
+ */
1211
+ interface LayoutResult {
1212
+ /**
1213
+ * 本次排版计算的有效歌词行数
1214
+ */
1215
+ lineCount: number;
1216
+ /**
1217
+ * 对应所有歌词行的渲染指令池
1218
+ * @remarks 指令池由 LayoutCalculator 复用,遍历时务必以 {@link lineCount} 为界
1219
+ */
1220
+ readonly lineInstructions: ReadonlyArray<RenderInstruction>;
1221
+ /**
1222
+ * 是否需要显示间奏点
1223
+ */
1224
+ hasInterlude: boolean;
1225
+ /**
1226
+ * 如果需要显示间奏点,它的 Y 轴绝对坐标
1227
+ */
1228
+ interludeY: number;
1229
+ /**
1230
+ * 底栏的 Y 轴绝对坐标
1231
+ */
1232
+ bottomLineY: number;
1233
+ /**
1234
+ * 底栏是否在可视范围内,用于剔除渲染
1235
+ */
1236
+ isBottomLineInViewport: boolean;
1237
+ }
1238
+ /**
1239
+ * 决定排版原点焦点的目标
1240
+ */
1241
+ type FocalTarget = {
1242
+ type: "line";
1243
+ index: number;
1244
+ } | {
1245
+ type: "interlude";
1246
+ anchorIndex: number;
1247
+ } | {
1248
+ type: "bottom";
1249
+ };
1250
+ type ResolvedLayoutMetrics = {
1251
+ isValid: boolean;
1252
+ focalTopY: number;
1253
+ anchorOffset: number;
1254
+ interludeTotalHeight: number;
1255
+ activeInterludeAnchor: number | undefined;
1256
+ };
1257
+ type LayoutFrameSession = ResolvedLayoutMetrics & {
1258
+ containerHeight: number;
554
1259
  alignPosition: number;
555
- /** 视口上下额外保留的预渲染距离,单位为像素 */
556
1260
  overscanPx: number;
1261
+ bottomLineHeight: number;
1262
+ };
1263
+ declare class LayoutCalculator {
1264
+ /**
1265
+ * 前缀和缓存
1266
+ *
1267
+ * 长度为歌词行数 + 1
1268
+ *
1269
+ * prefixSums[i] 存储的是第 0 行到第 i-1 行的总高度,不包含间奏点
1270
+ */
1271
+ private prefixSums;
1272
+ private heights;
1273
+ /**
1274
+ * 使用 Uint8Array 作为掩码,1 表示该行经历了真实测量,0 表示该行在使用 fallback 高度
1275
+ */
1276
+ private isMeasured;
1277
+ /**
1278
+ * 渲染指令对象池
1279
+ *
1280
+ * 长度永远只会增加不会减少,避免 GC
1281
+ */
1282
+ private instructionPool;
1283
+ private isPrefixSumDirty;
1284
+ private readonly resolvedMetrics;
1285
+ /**
1286
+ * 全局唯一复用的返回结果实例
1287
+ */
1288
+ private readonly layoutResult;
1289
+ /**
1290
+ * 缓存的歌词行数
1291
+ */
1292
+ private lyricCount;
1293
+ /**
1294
+ * 缓存的所有歌词总高度
1295
+ */
1296
+ private totalLyricHeight;
1297
+ /**
1298
+ * 初始化排版空间结构与高度缓存
1299
+ *
1300
+ * 在加载新歌词时调用
1301
+ *
1302
+ * @param count 歌词总行数
1303
+ * @param defaultHeight 尚未渲染/测量的行的默认回退高度
1304
+ */
1305
+ initHeights(count: number, defaultHeight: number): void;
1306
+ /**
1307
+ * 获取指定索引歌词行的计算高度
1308
+ * @remarks 可能为测量值或估算值
1309
+ * @param index 歌词行索引
1310
+ */
1311
+ getLineHeight(index: number): number;
1312
+ /**
1313
+ * 设置单行歌词的真实测量高度
1314
+ * @param index 歌词行索引
1315
+ * @param height 真实测量的高度
1316
+ */
1317
+ setLineHeight(index: number, height: number): void;
1318
+ /**
1319
+ * 批量更新所有未测量行的回退高度
1320
+ *
1321
+ * 仅在容器 Resize 等会导致回退基准(如 containerHeight / 5)发生变化时调用。
1322
+ * 已被真实测量的行不受影响。
1323
+ *
1324
+ * @param defaultHeight 新的默认回退高度
1325
+ */
1326
+ updateUnmeasuredHeights(defaultHeight: number): void;
1327
+ /**
1328
+ * 解析焦点度量并计算物理滚动边界
1329
+ *
1330
+ * 返回滚动安全闭区间 `{ min, max }` 以及此帧的生命周期会话句柄 {@link LayoutFrameSession}
1331
+ * 供后续使用 `ScrollInteractionEngine.updateBoundary` 钳制 `scrollOffset` 后传给 {@link commit}
1332
+ *
1333
+ * @param ctx 动态帧上下文,包含当前容器尺寸、焦点目标与底栏高度
1334
+ * @param config 布局静态配置,包含对齐锚点、相对位置与 Overscan 容差
1335
+ */
1336
+ beginFrame(ctx: LayoutFrameContext, config: LayoutConfig): {
1337
+ bounds: {
1338
+ min: number;
1339
+ max: number;
1340
+ };
1341
+ session: LayoutFrameSession;
1342
+ };
1343
+ /**
1344
+ * 基于已钳制的 `scrollOffset` 和第一阶段的 {@link LayoutFrameSession} 提交排版并生成指令
1345
+ *
1346
+ * @param session 由 {@link beginFrame} 生成的单帧排版会话
1347
+ * @param scrollOffset 经过边界钳制后的安全滚动偏移量
1348
+ *
1349
+ * @returns 复用的排版结果实例 {@link LayoutResult}
1350
+ */
1351
+ commit(session: LayoutFrameSession, scrollOffset: number): LayoutResult;
1352
+ /**
1353
+ * 解析当前排版帧所对应的间奏点挂载锚点行索引
1354
+ *
1355
+ * @param interlude 当前时间线处于激活状态的间奏点信息
1356
+ * @param focalTarget 当前帧对齐的物理焦点
1357
+ */
1358
+ static resolveInterludeAnchorIndex(interlude: {
1359
+ anchorLineIndex: number;
1360
+ } | undefined, focalTarget: FocalTarget): number | undefined;
1361
+ /**
1362
+ * 如果高度发生过改变,则重新计算前缀和
1363
+ */
1364
+ private ensurePrefixSums;
1365
+ /**
1366
+ * 公共的焦点度量与范围校验逻辑
1367
+ */
1368
+ private resolveLayoutMetrics;
1369
+ /**
1370
+ * 测量对齐目标的几何信息
1371
+ */
1372
+ private updateFocalMetrics;
1373
+ /**
1374
+ * 根据锚点与目标高度计算内部相对偏移
1375
+ */
1376
+ private calculateAnchorOffset;
1377
+ /**
1378
+ * 重置排版结果为安全干净的状态(全部不可见/无底栏)
1379
+ */
1380
+ private resetLayoutResult;
1381
+ }
1382
+ //#endregion
1383
+ //#region src/lyric-player/base/lyric-data-manager.d.ts
1384
+ /**
1385
+ * 所有歌词行数据的配置选项,包括歌词行优化和掩码选项
1386
+ */
1387
+ interface LyricDataConfig {
1388
+ optimizeOptions?: OptimizeLyricOptions;
1389
+ maskMode?: MaskObsceneWordsMode;
1390
+ maskChar?: string;
1391
+ }
1392
+ /**
1393
+ * 歌词数据管理器
1394
+ *
1395
+ * 负责:
1396
+ * 1. 存储原始歌词、进行深拷贝、应用歌词优化、应用歌词掩码
1397
+ * 2. 计算是否为动态歌词 (逐字歌词) 和是否有对唱歌词
1398
+ */
1399
+ declare class LyricDataManager {
1400
+ /**
1401
+ * 原始的歌词行数组
1402
+ */
1403
+ private rawLines;
1404
+ /**
1405
+ * 经过处理后的歌词行数组
1406
+ */
1407
+ private processedLines;
1408
+ /**
1409
+ * 当前处理好的 processedLines 是否已经失效
1410
+ */
1411
+ private isDirty;
1412
+ private optimizeOpts;
1413
+ private maskMode;
1414
+ private maskChar;
1415
+ private isNonDynamic;
1416
+ private hasDuetLine;
1417
+ setOriginalLines(lines: LyricLine[]): void;
1418
+ setConfig(config: LyricDataConfig): void;
1419
+ /**
1420
+ * 获取用户设置的原始歌词行数组,未经过优化和掩码
1421
+ * @returns 原始歌词行数组
1422
+ */
1423
+ getRawLines(): ReadonlyArray<LyricLine>;
1424
+ /**
1425
+ * 获取对原始歌词行数组处理过的的歌词行数组
1426
+ * @returns 经过处理的歌词行数组
1427
+ */
1428
+ getProcessedLines(): ReadonlyArray<LyricLine>;
1429
+ /**
1430
+ * 歌词是否为非动态歌词,又称为逐行歌词
1431
+ */
1432
+ getIsNonDynamic(): boolean;
1433
+ /**
1434
+ * 歌词是否包含对唱歌词
1435
+ */
1436
+ getHasDuetLine(): boolean;
1437
+ getOptimizeOptions(): OptimizeLyricOptions;
1438
+ getMaskMode(): MaskObsceneWordsMode;
1439
+ getMaskChar(): string;
1440
+ private ensurePipeline;
1441
+ private processPipeline;
1442
+ private applyMask;
557
1443
  }
558
1444
  //#endregion
559
1445
  //#region src/lyric-player/base/scroll.d.ts
1446
+ type ScrollInputType = "touch" | "wheel";
1447
+ interface ScrollEngineHooks {
1448
+ /**
1449
+ * 当滚动偏移量发生变化时高频触发
1450
+ *
1451
+ * @param isContinuous 当前是否为连续输入 (触摸或惯性 RAF)
1452
+ *
1453
+ * 一般的使用场景为:
1454
+ * * 若是连续输入,上层应绕过弹簧动画效果直接照着偏移量重新布局;
1455
+ * * 若是低频离散步进 (如滚轮),则上层应保留弹簧效果以便在离散步进之间展示平滑的动画
1456
+ */
1457
+ onScrollUpdate: (isContinuous: boolean) => void;
1458
+ /**
1459
+ * 用户明确产生滑动意图 (手指触摸超过 10px) 或使用滚轮时触发
1460
+ *
1461
+ * 一般在此暂停模糊效果、暂停自动跟随等
1462
+ */
1463
+ onInteractionStart: (type: ScrollInputType) => void;
1464
+ }
1465
+ declare class ScrollInteractionEngine {
1466
+ private container;
1467
+ private hooks;
1468
+ private offset;
1469
+ private minOffset;
1470
+ private maxOffset;
1471
+ private touchState;
1472
+ /**
1473
+ * 记录 Touch 开始时被打断的既有状态
1474
+ */
1475
+ private interruptedState;
1476
+ private inertiaRafId;
1477
+ private scrollEndTime;
1478
+ private isInteracting;
1479
+ private wheelEndTimeoutId;
1480
+ private abortController;
1481
+ constructor(container: HTMLElement, hooks: ScrollEngineHooks);
1482
+ private startInteraction;
1483
+ private endInteraction;
1484
+ private onTouchStart;
1485
+ private onTouchMove;
1486
+ private onTouchCancel;
1487
+ private onTouchEnd;
1488
+ private onWheel;
1489
+ /**
1490
+ * 将给定的偏移量限制在当前的 [minOffset, maxOffset] 边界内
1491
+ */
1492
+ private clampOffset;
1493
+ /**
1494
+ * 滚动结束是否已超过 {@link AUTO_ALIGN_RESUME_DELAY_MS}
1495
+ */
1496
+ get canResumeAutoAlign(): boolean;
1497
+ /**
1498
+ * 更新允许的滚动边界,并返回钳制后的实际 offset
1499
+ *
1500
+ * @description
1501
+ * 在歌词发生排版变化,如页面 `resize`、加载了新歌词、展开背景歌词、歌词行高度改变时,
1502
+ * 计算出当前视口内允许滚动的上限与下限,通过此方法传给滚动引擎以确保滚动不会越界
1503
+ */
1504
+ updateBoundary(min: number, max: number): number;
1505
+ /**
1506
+ * 覆盖当前的滚动偏移量并终止正在进行的惯性动画或等待状态
1507
+ *
1508
+ * @description
1509
+ * 当需要清空全部用户手势带来的临时滚动状态时,例如,用户点击了某行歌词触发了
1510
+ * Seek、歌曲切歌、或者焦点切换时恢复自动对齐,调用此方法重置滚动引擎
1511
+ */
1512
+ resetScroll(targetOffset?: number): void;
1513
+ private clearTimers;
1514
+ dispose(): void;
1515
+ }
1516
+ //#endregion
1517
+ //#region src/lyric-player/base/seek-detector.d.ts
560
1518
  /**
561
- * 播放器滚动状态。
562
- *
563
- * 这部分状态描述用户手势/滚轮滚动产生的临时偏移,以及当前允许滚动的范围。
564
- * 改状态仅记录用户如何把当前视图上下拖动,不决定应该滚动到哪一行,
565
- * 后者由时间线状态与布局计算共同决定。
566
- */
567
- interface PlayerScrollState {
568
- /** 允许的滚动偏移范围 */
569
- scrollBoundary: {
570
- /** 允许的最小偏移量 */minOffset: number; /** 允许的最大偏移量 */
571
- maxOffset: number;
572
- };
573
- /** 当前用户滚动带来的额外偏移量 */
574
- scrollOffset: number;
575
- /** 是否允许用户通过手势或滚轮滚动歌词视图 */
576
- allowScroll: boolean;
577
- /** 是否处于用户滚动过,尚未回归自动对齐的状态 */
578
- isScrolled: boolean;
579
- /** 是否正在进行滚动交互或惯性滚动 */
580
- isUserScrolling: boolean;
1519
+ * 读取单调递增的物理时钟的函数,单位为毫秒
1520
+ *
1521
+ * 默认使用 `performance.now`
1522
+ */
1523
+ type WallClock = () => number;
1524
+ /**
1525
+ * 跳转状态自动推导器
1526
+ *
1527
+ * 让下游使用者只推送播放进度、无需自己判断某次进度变化是否为跳转
1528
+ *
1529
+ * 判定的思路是把本次媒体时钟的推进量与当前播放状态下它应有的推进量比较,
1530
+ * 超出容差的偏离即视为跳转。判定规则为:
1531
+ * - 进度倒退视为跳转
1532
+ * - 播放时应有的推进量是物理时钟的推进量,容差随之按比例放宽
1533
+ * - 暂停时应有的推进量是零,容差只留固定底限
1534
+ */
1535
+ declare class SeekDetector {
1536
+ private readonly now;
1537
+ private lastMediaTime;
1538
+ private lastWallTime;
1539
+ private hasBaseline;
1540
+ /**
1541
+ * @param now 物理时钟读取函数,默认为 `performance.now`
1542
+ */
1543
+ constructor(now?: WallClock);
1544
+ /**
1545
+ * 推导本次进度变化是否为跳转
1546
+ *
1547
+ * @param time 下游推送的当前播放进度
1548
+ * @param isPlaying 当前是否在播放,决定本次推送应有的推进量与容差
1549
+ * @returns 是否应当按跳转处理
1550
+ */
1551
+ detect(time: MediaTime, isPlaying: boolean): boolean;
1552
+ reset(): void;
1553
+ private rebase;
581
1554
  }
582
1555
  //#endregion
583
1556
  //#region src/lyric-player/base/timeline.d.ts
584
1557
  /**
585
- * 播放时间线状态。
586
- *
587
- * 描述播放器在时间轴上的当前位置,当前处于激活状态的歌词组信息
588
- */
589
- interface PlayerTimelineState {
590
- /** 当前播放时间,单位为毫秒 */
591
- currentTime: number;
592
- /** 上一次提交到时间线状态的播放时间,单位为毫秒 */
593
- lastCurrentTime: number;
594
- /** 热行:当前时间 {@link currentTime} 正在命中的组(含主行+可能的背景行) */
595
- hotGroups: Set<number>;
596
- /** 缓冲组:UI 上还保持激活表现的组索引,通常包含热组,和刚结束仍在过渡中的组 */
597
- bufferedGroups: Set<number>;
598
- /** 当前应滚动对齐到的歌词组索引 */
599
- scrollToIndex: number;
600
- /** 是否正在拖拽进度条。若是,更新时丢弃缓冲行,并根据当前时间直接计算热行 */
601
- isSeeking: boolean;
602
- /** 是否处于播放状态 */
603
- isPlaying: boolean;
604
- /** 是否已经完成至少一次初始布局 */
605
- initialLayoutFinished: boolean;
1558
+ * 用于进度计算的最小歌词数据
1559
+ */
1560
+ interface TimeBounds {
1561
+ readonly startTime: MediaTime;
1562
+ readonly endTime: MediaTime;
1563
+ }
1564
+ /**
1565
+ * 当前命中的间奏区间信息
1566
+ */
1567
+ interface PlayerInterlude {
1568
+ /**
1569
+ * 间奏开始时间,即此前全部歌词行中最晚的结束时间
1570
+ */
1571
+ readonly startTime: MediaTime;
1572
+ /**
1573
+ * 间奏结束时间,即间奏后第一行歌词的开始时间
1574
+ */
1575
+ readonly endTime: MediaTime;
1576
+ /**
1577
+ * 间奏点应插入的位置基准
1578
+ *
1579
+ * 即显示顺序上间奏前的最后一行歌词的索引,`-1` 表示第一句之前
1580
+ */
1581
+ readonly anchorLineIndex: number;
1582
+ }
1583
+ /**
1584
+ * 当前播放时间线状态的只读快照
1585
+ *
1586
+ * 用于给 UI 执行排版和计算各种歌词行的效果
1587
+ *
1588
+ * @remarks
1589
+ * 在获取快照后,必须在同一帧内消费完毕,切勿保留其引用,因为下一帧就会被原地覆写
1590
+ */
1591
+ interface TimelineSnapshot {
1592
+ /**
1593
+ * 当前时间推导所依据的绝对播放时间
1594
+ *
1595
+ * 一般用于提供给 UI 层在绘制/排版阶段作为基准时间
1596
+ *
1597
+ * 例如给 InterludeDotsBase 计算播放动画的当前时间戳
1598
+ */
1599
+ readonly currentTime: MediaTime;
1600
+ /**
1601
+ * 当前进度命中的、正在播放的歌词组
1602
+ */
1603
+ readonly playingGroups: ReadonlySet<number>;
1604
+ /**
1605
+ * 当前进度命中的、正在高亮的歌词组
1606
+ *
1607
+ * 高亮的歌词组可能会多于正在播放的,例如一行歌词唱完后不会自行熄灭,而是保持高亮直到下一行开始播放
1608
+ *
1609
+ * 因此多行重叠时先唱完的行会陪着后面的行一起亮,句间空隙内上一行也保持高亮以维持可读性
1610
+ */
1611
+ readonly highlightedGroups: ReadonlySet<number>;
1612
+ /**
1613
+ * 自动滚动应该对齐到哪一行歌词
1614
+ *
1615
+ * 取值始终落在 `[0, 歌词行数 - 1]` 内,没有歌词时为 `0`,因此可以直接用于索引
1616
+ *
1617
+ * @remarks
1618
+ * 仅在 {@link isFocusOnInterlude} 为 `false` 时有效
1619
+ *
1620
+ * 间奏期间此值仍然指向间奏前的那一组歌词,此时应当改为对齐 {@link activeInterlude} 的间奏点
1621
+ */
1622
+ readonly scrollToIndex: number;
1623
+ /**
1624
+ * 处于高亮状态的歌词行中,最靠后的一行
1625
+ *
1626
+ * 例如,如果当前有高亮行索引 `[1, 2, 3]`,则 `latestHighlightedIndex` 为 `3`
1627
+ *
1628
+ * 如果当前没有任何高亮行,被置为 undefined,如首行开始之前、间奏区间内、
1629
+ * 歌曲播放完毕、或已开始的行均为零时长的情况
1630
+ */
1631
+ readonly latestHighlightedIndex?: number;
1632
+ /**
1633
+ * 标识歌曲是否播放完毕,即当前时间已经越过全部歌词行中最晚的结束时间
1634
+ *
1635
+ * 此时所有高亮都已被清空,一般用于展示底栏
1636
+ */
1637
+ readonly isEndOfSong: boolean;
1638
+ /**
1639
+ * 当前命中的间奏区间数据
1640
+ */
1641
+ readonly activeInterlude?: PlayerInterlude;
1642
+ /**
1643
+ * 当前是否应当聚焦在间奏点上
1644
+ */
1645
+ readonly isFocusOnInterlude: boolean;
1646
+ }
1647
+ /**
1648
+ * 时间线增量变化
1649
+ *
1650
+ * 主要用于让 UI 层能够以 $O(1)$ 到 $O(K)$ 的开销知道当前这一帧相比上一帧改变了什么,
1651
+ * 而不需要去遍历或比对全量状态
1652
+ */
1653
+ interface TimelineDiff {
1654
+ /**
1655
+ * 当前帧是否有任何实质性的状态变更,如播放行更替、高亮行新增/移除、间奏状态切换、焦点切换,或发生了跳转
1656
+ *
1657
+ * UI 层接收到 diff 后,检查此标志即可直接跳过后续所有的布局计算
1658
+ */
1659
+ readonly hasChanged: boolean;
1660
+ /**
1661
+ * 标识本次同步的时间轴是否发生了跳转
1662
+ *
1663
+ * 在显式跳转 (`sync` 的 `forceSeek`) 或时间倒退时为 `true`
1664
+ *
1665
+ * 例如跳转时使用更缓慢的弹簧参数 (参见 `spring.ts`),
1666
+ * 以及在非触摸状态下重置滚动坐标系
1667
+ */
1668
+ readonly isTimeJumped: boolean;
1669
+ /**
1670
+ * 在当前时间进度下,最新被命中的、正在播放的歌词索引列表
1671
+ *
1672
+ * 用于通知 UI 哪些歌词行开始播放了
1673
+ */
1674
+ readonly addedPlaying: ReadonlyArray<number>;
1675
+ /**
1676
+ * 在当前时间进度下,刚刚脱离正在播放状态的歌词索引列表,
1677
+ * 即上一帧还在播放、但本帧已不再命中其 `[startTime, endTime)` 的歌词行
1678
+ *
1679
+ * 正常播放时是时间越过了 `endTime`,跳转时也可能是跳到了 `startTime` 之前
1680
+ *
1681
+ * 用于通知 UI 侧哪些歌词行已结束,后续可能会转入高亮行以保持高亮状态
1682
+ */
1683
+ readonly removedPlaying: ReadonlyArray<number>;
1684
+ /**
1685
+ * 在当前帧中,需要变成高亮行的歌词索引列表
1686
+ *
1687
+ * 一般用于 UI 遍历并启用歌词行
1688
+ */
1689
+ readonly addedHighlighted: ReadonlyArray<number>;
1690
+ /**
1691
+ * 在当前帧中,不再是高亮行的歌词行索引列表
1692
+ *
1693
+ * 一般用于 UI 遍历并停用歌词行
1694
+ */
1695
+ readonly removedHighlighted: ReadonlyArray<number>;
1696
+ /**
1697
+ * 标识间奏状态是否发生了切换
1698
+ *
1699
+ * 例如:刚刚进入间奏区间、刚刚离开间奏区间、或从一个间奏区间跳到了另一个间奏区间
1700
+ *
1701
+ * 用于通知 UI 是否需要重新计算间奏点的位置和动画
1702
+ */
1703
+ readonly isInterludeChanged: boolean;
1704
+ /**
1705
+ * 标识自动对齐的目标歌词行索引是否发生了变化
1706
+ *
1707
+ * 用于通知 UI 需要移动到新的歌词行
1708
+ */
1709
+ readonly isScrollToChanged: boolean;
1710
+ /**
1711
+ * 标识歌曲播放完毕状态是否发生了变化
1712
+ */
1713
+ readonly isEndOfSongChanged: boolean;
1714
+ }
1715
+ declare class TimelineController {
1716
+ private lyricBounds;
1717
+ /**
1718
+ * 全部歌词行中最晚的结束时间,用于判定歌曲是否播放完毕
1719
+ */
1720
+ private maxEndTime;
1721
+ /**
1722
+ * 预先计算的间奏区域
1723
+ */
1724
+ private precalculatedInterludes;
1725
+ /**
1726
+ * 保存上次顺序扫描停止的位置,用于避免每次都从头遍历所有歌词,提高性能
1727
+ */
1728
+ private playbackCursor;
1729
+ private interludeCursor;
1730
+ private playingGroupsSet;
1731
+ private highlightedGroupsSet;
1732
+ private nextPlayingSet;
1733
+ private nextHighlightedSet;
1734
+ private addedPlayingIds;
1735
+ private removedPlayingIds;
1736
+ private addedHighlightedIds;
1737
+ private removedHighlightedIds;
1738
+ private expiredHighlightedIds;
1739
+ private readonly snapshot;
1740
+ private readonly diff;
1741
+ /**
1742
+ * 提前设置好歌词的时间数据,内部会根据此数据来进行时间线推导,同时预计算间奏区间
1743
+ *
1744
+ * @param bounds 歌词行的时间边界,**必须按 `startTime` 升序排列**,不按 `startTime`
1745
+ * 排列可能会导致时间推导出现意外情况
1746
+ */
1747
+ setTimeBounds(bounds: readonly TimeBounds[]): void;
1748
+ /**
1749
+ * 获取当前播放时间线状态的只读快照
1750
+ *
1751
+ * 用于给 UI 执行排版和计算各种歌词行的效果
1752
+ *
1753
+ * @remarks 在获取快照后,必须在同一帧内消费完毕,切勿保留其引用,因为下一帧就会被原地覆写
1754
+ * @returns 时间线快照
1755
+ */
1756
+ getSnapshot(): TimelineSnapshot;
1757
+ /**
1758
+ * 将播放进度推进到指定时间,并返回相对上一帧的增量变化
1759
+ *
1760
+ * @remarks
1761
+ * 歌词行的高亮生命周期为:
1762
+ * * 命中 `[startTime, endTime)` 时高亮
1763
+ * * 唱完后不会自行熄灭,而是继续保持高亮
1764
+ *
1765
+ * 直到出现下列任一情况:
1766
+ *
1767
+ * 1. 有新的歌词行开始播放,此时已唱完的行被一起熄灭
1768
+ * 2. 进入间奏区间,此时清空全部高亮并把焦点交给间奏点
1769
+ * 3. 歌曲播放完毕,此时清空全部高亮并设置 {@link TimelineSnapshot.isEndOfSong} 为 true
1770
+ * 4. 发生跳转,此时按跳转后的时间重新推导
1771
+ *
1772
+ * @param time 当前播放时间
1773
+ * @param forceSeek 这次时间变化是否由跳转触发
1774
+ * @returns 相对上一帧的增量变化
1775
+ */
1776
+ sync(time: MediaTime, forceSeek?: boolean): TimelineDiff;
1777
+ /**
1778
+ * 处理正常播放时的时间线推导
1779
+ */
1780
+ private performPlayback;
1781
+ /**
1782
+ * 处理 Seek 时的时间线推导
1783
+ *
1784
+ * 直接按目标时间重建播放与高亮行集合,结果与正常播放到该时刻时一致
1785
+ *
1786
+ * @param time 跳转到的时间
1787
+ * @param dropLingeringWhenIdle 在没有任何行正在播放时,是否丢弃那些已经唱完、
1788
+ * 但在正常播放中仍会保持高亮的行,用于在间奏和播放完时清空高亮行
1789
+ */
1790
+ private performSeek;
1791
+ /**
1792
+ * 把 Seek 重建出的目标集合与上一帧的集合求对称差,输出发生变化的部分
1793
+ */
1794
+ private commitSeekDiff;
1795
+ /**
1796
+ * 立即熄灭当前全部高亮歌词行
1797
+ *
1798
+ * 用于间奏与曲末这两个没有下一行接续、但必须清空高亮的场景
1799
+ */
1800
+ private flushAllHighlighted;
1801
+ /**
1802
+ * 预计算全部间奏区间
1803
+ * @param bounds 按 `startTime` 升序排列的歌词时间边界
1804
+ * @returns 按时间升序排列、互不重叠的间奏区间,供二分查找与游标推进使用
1805
+ */
1806
+ private calculateInterludes;
1807
+ /**
1808
+ * 查找当前时间命中的间奏区间,并顺带推进或重定位间奏游标
1809
+ * @param time 当前播放时间
1810
+ * @param isSeek 当前帧是否为跳转
1811
+ * @returns 命中的间奏区间,未命中时为 undefined
1812
+ */
1813
+ private resolveActiveInterlude;
1814
+ /**
1815
+ * 根据间奏命中情况和当前高亮状态推导是否应当聚焦间奏点
1816
+ *
1817
+ * 处于间奏区域且没有任何歌词高亮时聚焦间奏点,否则交还给歌词行
1818
+ *
1819
+ * @param activeInterlude 当前命中的间奏区间
1820
+ */
1821
+ private updateInterludeFocus;
1822
+ /**
1823
+ * 清空全部推导状态,回到时间原点
1824
+ */
1825
+ private reset;
606
1826
  }
607
1827
  //#endregion
608
1828
  //#region src/lyric-player/base/index.d.ts
609
1829
  /**
610
- * 歌词播放器的基类,已经包含了有关歌词操作和排版的功能,
611
- * 子类需要为其实现对应的显示展示操作
612
- */
1830
+ * 播放器布局状态。
1831
+ *
1832
+ * 记录当前视口动态测量得到的尺寸
1833
+ */
1834
+ interface PlayerLayoutState {
1835
+ /** 间奏点元素当前测量得到的尺寸 */
1836
+ interludeDotsSize: [number, number];
1837
+ }
1838
+ /**
1839
+ * 播放器滚动状态。
1840
+ *
1841
+ * 记录用户的滚动行为
1842
+ */
1843
+ interface PlayerScrollState {
1844
+ /** 是否处于用户滚动过,尚未回归自动对齐的状态 */
1845
+ isAutoAlignSuspended: boolean;
1846
+ isTouchScrolled: boolean;
1847
+ }
1848
+ /**
1849
+ * 歌词播放器的基类,已经包含了有关歌词操作和排版的功能,
1850
+ * 子类需要为其实现对应的显示展示操作
1851
+ */
613
1852
  declare abstract class LyricPlayerBase extends EventTarget implements HasElement, Disposable {
614
1853
  protected element: HTMLElement;
615
1854
  abstract get baseFontSize(): number;
616
- /** 播放时间线状态 */
617
- protected timelineState: PlayerTimelineState;
1855
+ protected isPlaying: boolean;
1856
+ protected timelineController: TimelineController;
1857
+ protected seekDetector: SeekDetector;
1858
+ protected enableAutoSeekDetection: boolean;
1859
+ private hasBottomContent;
1860
+ private bottomLineObserver;
618
1861
  /** @internal */
619
1862
  lyricGroupElementMap: WeakMap<Element, LyricLineGroupBase>;
620
- protected currentLyricLines: LyricLine[];
621
- protected processedLines: LyricLine[];
622
1863
  protected lyricLinesIndexes: WeakMap<LyricLineBase, number>;
623
- protected isNonDynamic: boolean;
624
- protected hasDuetLine: boolean;
625
1864
  protected disableSpring: boolean;
1865
+ protected dataManager: LyricDataManager;
1866
+ protected get processedLines(): ReadonlyArray<LyricLine>;
1867
+ protected get isNonDynamic(): boolean;
1868
+ protected get hasDuetLine(): boolean;
626
1869
  protected layoutState: PlayerLayoutState;
627
- protected interludeDots: InterludeDots;
628
- protected bottomLine: BottomLineEl;
1870
+ protected layoutConfig: LayoutConfig;
1871
+ /** LayoutCalculator 所使用的排版上下文状态 */
1872
+ private frameContext;
1873
+ /**
1874
+ * 逐帧刷新的视觉推导派生值
1875
+ *
1876
+ * 存放会在逐行循环内被反复求值的量,避免逐行重算
1877
+ */
1878
+ private visualFrame;
1879
+ protected interludeDots: InterludeDotsBase & HasElement;
1880
+ protected bottomLine: BottomLine;
629
1881
  protected enableBlur: boolean;
630
1882
  protected enableScale: boolean;
631
- protected maskObsceneWords: MaskObsceneWordsMode;
632
- protected maskObsceneWordChar: string;
633
1883
  protected hidePassedLines: boolean;
1884
+ protected scrollEngine: ScrollInteractionEngine;
634
1885
  protected scrollState: PlayerScrollState;
1886
+ protected layoutCalculator: LayoutCalculator;
1887
+ private focusController;
635
1888
  currentLyricGroups: LyricLineGroupBase[];
636
1889
  lyricGroupSize: WeakMap<LyricLineGroupBase, [number, number]>;
637
1890
  readonly size: [number, number];
638
1891
  protected isPageVisible: boolean;
639
- protected optimizeOptions: OptimizeLyricOptions;
1892
+ /** 默认/回退单行歌词估算高度基准 (containerHeight / 5) */
1893
+ get defaultLineHeight(): number;
1894
+ /**
1895
+ * 获取指定索引歌词行的高度
1896
+ * @remarks 可能为测量值或估算值
1897
+ */
1898
+ getLineHeight(index: number): number;
640
1899
  /** 是否强制让背景人声行始终后置(即始终在主歌词下方显示,不前置背景人声) */
641
1900
  protected alwaysPostpositionBackground: boolean;
642
1901
  protected posXSpringParams: Partial<SpringParams>;
643
1902
  protected posYSpringParams: Partial<SpringParams>;
644
1903
  protected scaleSpringParams: Partial<SpringParams>;
645
1904
  protected scaleForBGSpringParams: Partial<SpringParams>;
1905
+ private lyricGroupIndexMap;
646
1906
  private onPageShow;
647
1907
  private onPageHide;
648
- private scrolledHandler;
1908
+ private onVisibilityChange;
649
1909
  /** @internal */
650
1910
  resizeObserver: ResizeObserver;
651
1911
  protected wordFadeWidth: number;
652
1912
  constructor(element?: HTMLElement);
653
- private beginScrollHandler;
654
- private endScrollHandler;
655
- /**
656
- * 设置文字动画的渐变宽度,单位以歌词行的主文字字体大小的倍数为单位,默认为 0.5,即一个全角字符的一半宽度
657
- *
658
- * 如果要模拟 Apple Music for Android 的效果,可以设置为 1
659
- *
660
- * 如果要模拟 Apple Music for iPad 的效果,可以设置为 0.5
661
- *
662
- * 如果想要近乎禁用渐变效果,可以设置成非常接近 0 的小数(例如 `0.0001` ),但是**不可以为 0**
663
- *
664
- * @param value 需要设置的渐变宽度,单位以歌词行的主文字字体大小的倍数为单位,默认为 0.5
665
- */
1913
+ /**
1914
+ * 设置文字动画的渐变宽度,单位以歌词行的主文字字体大小的倍数为单位,默认为 0.5,即一个全角字符的一半宽度
1915
+ *
1916
+ * 如果要模拟 Apple Music for Android 的效果,可以设置为 1
1917
+ *
1918
+ * 如果要模拟 Apple Music for iPad 的效果,可以设置为 0.5
1919
+ *
1920
+ * 如果想要近乎禁用渐变效果,可以设置成非常接近 0 的小数(例如 `0.0001` ),但是**不可以为 0**
1921
+ *
1922
+ * @param value 需要设置的渐变宽度,单位以歌词行的主文字字体大小的倍数为单位,默认为 0.5
1923
+ */
666
1924
  setWordFadeWidth(value?: number): void;
667
1925
  /**
668
- * 是否启用歌词行缩放效果,默认启用
669
- *
670
- * 如果启用,非选中的歌词行会轻微缩小以凸显当前播放歌词行效果
671
- *
672
- * 此效果对性能影响微乎其微,推荐启用
673
- * @param enable 是否启用歌词行缩放效果
674
- */
1926
+ * 是否启用歌词行缩放效果,默认启用
1927
+ *
1928
+ * 如果启用,非选中的歌词行会轻微缩小以凸显当前播放歌词行效果
1929
+ *
1930
+ * 此效果对性能影响微乎其微,推荐启用
1931
+ * @param enable 是否启用歌词行缩放效果
1932
+ */
675
1933
  setEnableScale(enable?: boolean): void;
676
1934
  /**
677
- * 获取当前是否启用了歌词行缩放效果
678
- * @returns 是否启用歌词行缩放效果
679
- */
1935
+ * 获取当前是否启用了歌词行缩放效果
1936
+ * @returns 是否启用歌词行缩放效果
1937
+ */
680
1938
  getEnableScale(): boolean;
681
1939
  /**
682
- * 获取当前文字动画的渐变宽度,单位以歌词行的主文字字体大小的倍数为单位
683
- * @returns 当前文字动画的渐变宽度,单位以歌词行的主文字字体大小的倍数为单位
684
- */
1940
+ * 获取当前文字动画的渐变宽度,单位以歌词行的主文字字体大小的倍数为单位
1941
+ * @returns 当前文字动画的渐变宽度,单位以歌词行的主文字字体大小的倍数为单位
1942
+ */
685
1943
  getWordFadeWidth(): number;
686
- setIsSeeking(isSeeking: boolean): void;
687
1944
  /**
688
- * 设置是否隐藏已经播放过的歌词行,默认不隐藏
689
- * @param hide 是否隐藏已经播放过的歌词行,默认不隐藏
690
- */
1945
+ * 设置持续性的跳转状态
1946
+ *
1947
+ * @deprecated 此方法已无实际作用,调用它不会产生任何效果,仅为兼容保留,将在未来移除
1948
+ *
1949
+ * 跳转状态现在由每次进度推送逐帧推导,不再存在需要外部显式解除的持续状态
1950
+ *
1951
+ * 跳转会通过以下三条途径被识别,三者同时生效:
1952
+ *
1953
+ * - {@link setCurrentTime} 的 `isSeek` 参数,由调用方明确告知某次进度变化是跳转
1954
+ * - 进度倒退,由内部无条件识别,不受任何开关控制
1955
+ * - 自动推导,由内部比对进度的实际推进量与它应有的推进量识别超量前进,默认启用,
1956
+ * 可通过 {@link setEnableAutoSeekDetection} 关闭
1957
+ */
1958
+ setIsSeeking(_isSeeking: boolean): void;
1959
+ /**
1960
+ * 设置是否自动推导跳转状态,默认启用
1961
+ *
1962
+ * 启用后,即使调用 {@link setCurrentTime} 时没有传入 `isSeek`,
1963
+ * 内部也会在进度前进时比较它的实际推进量与应有的推进量,超量前进即视为跳转
1964
+ *
1965
+ * 应有的推进量取决于当前的播放状态,因此请按 {@link pause} 与 {@link resume}
1966
+ * 的文档正确同步播放状态:
1967
+ * - 播放时以物理时钟的推进量为准,容差随之按比例放宽,以容纳倍速播放与不均匀的推送节奏
1968
+ * - 暂停时进度本不该前进,应有的推进量是零,因此任何超出抖动幅度的前进都会被视为跳转
1969
+ *
1970
+ * 这意味着进度信息的粒度粗于推送间隔时,粒度跳变的那一帧会因超量前进被视为跳转,
1971
+ * 此时应当改善进度来源的精度,或关闭此开关
1972
+ *
1973
+ * 较小的向前跳转可能无法被识别,但一般影响不大
1974
+ *
1975
+ * 此开关只控制上述超量前进的判定。进度倒退不受它控制,无论是否启用推导都会被视为跳转;
1976
+ * 进度保持不变则既不前进也不后退,不会被推导视为跳转
1977
+ *
1978
+ * 推导只会额外识别出跳转,不会否决已显式传入的跳转标志,因此如果你已经在正确传入
1979
+ * 跳转标志了,则一般无需关心此开关。若你的进度来源精度很差而导致超量前进被频繁误判,
1980
+ * 可以选择关闭
1981
+ *
1982
+ * @param enable 是否启用自动推导
1983
+ */
1984
+ setEnableAutoSeekDetection(enable?: boolean): void;
1985
+ /**
1986
+ * 获取当前是否启用了跳转状态的自动推导
1987
+ * @returns 是否启用自动推导
1988
+ */
1989
+ getEnableAutoSeekDetection(): boolean;
1990
+ /**
1991
+ * 设置是否隐藏已经播放过的歌词行,默认不隐藏
1992
+ * @param hide 是否隐藏已经播放过的歌词行,默认不隐藏
1993
+ */
691
1994
  setHidePassedLines(hide: boolean): void;
692
1995
  /**
693
- * 设置是否启用歌词行的模糊效果
694
- * @param enable 是否启用
695
- */
1996
+ * 设置是否启用歌词行的模糊效果
1997
+ * @param enable 是否启用
1998
+ */
696
1999
  setEnableBlur(enable: boolean): void;
697
2000
  /**
698
- * 设置歌词中不雅用语的掩码模式
699
- * @param mode 掩码模式
700
- * @see {@link MaskObsceneWordsMode}
701
- */
2001
+ * 批量更新歌词处理配置,包括优化和掩码设置
2002
+ *
2003
+ * @remarks
2004
+ * 此方法不会自动重建歌词行和刷新视图,
2005
+ * 适用于在渲染前预设配置、批量初始化,或需要手动控制 DOM 刷新时机的场景
2006
+ * @param config 需要更新的配置集合
2007
+ * @see {@link LyricDataConfig}
2008
+ */
2009
+ setLyricProcessConfig(config: LyricDataConfig): void;
2010
+ /**
2011
+ * 批量更新歌词处理配置,包括优化和掩码设置
2012
+ *
2013
+ * 可以调用此方法以避免多次单独设置处理配置导致的多次刷新开销
2014
+ * @remarks 在设置完成后会自动重建歌词行和刷新视图
2015
+ * @param config 需要更新的配置集合
2016
+ * @see {@link LyricDataConfig}
2017
+ */
2018
+ updateLyricProcessConfig(config: LyricDataConfig): void;
2019
+ /**
2020
+ * 设置歌词中不雅用语的掩码模式
2021
+ * @remarks 在设置完成后会自动重建歌词行和刷新视图
2022
+ * @param mode 掩码模式
2023
+ * @see {@link MaskObsceneWordsMode}
2024
+ */
702
2025
  setMaskObsceneWords(mode: MaskObsceneWordsMode): void;
703
2026
  /**
704
- * 设置不雅用语掩码使用的字符,默认为 `*`
705
- * @param char 单个字符,用于替换不雅用语中的字符
706
- */
2027
+ * 设置不雅用语掩码使用的字符,默认为 `*`
2028
+ * @remarks 在设置完成后会自动重建歌词行和刷新视图
2029
+ * @param char 单个字符,用于替换不雅用语中的字符
2030
+ */
707
2031
  setMaskObsceneWordChar(char: string): void;
2032
+ /**
2033
+ * 设置歌词的优化配置项,这些配置项默认全部开启
2034
+ * @remarks 在设置完成后会自动重建歌词行和刷新视图
2035
+ * @param options 优化配置选项
2036
+ * @see {@link OptimizeLyricOptions}
2037
+ */
2038
+ setOptimizeOptions(options: OptimizeLyricOptions): void;
708
2039
  rebuildLyricLines(): void;
709
2040
  /**
710
- * 根据当前配置处理不雅用语单词
711
- * @param word 单词对象
712
- * @internal
713
- */
714
- processObsceneWord(word: LyricWord): string;
715
- /**
716
- * 设置目标歌词行的对齐方式,默认为 `center`
717
- *
718
- * - 设置成 `top` 的话将会向目标歌词行的顶部对齐
719
- * - 设置成 `bottom` 的话将会向目标歌词行的底部对齐
720
- * - 设置成 `center` 的话将会向目标歌词行的垂直中心对齐
721
- * @param alignAnchor 歌词行对齐方式,详情见函数说明
722
- */
2041
+ * 设置目标歌词行的对齐方式,默认为 `center`
2042
+ *
2043
+ * - 设置成 `top` 的话将会向目标歌词行的顶部对齐
2044
+ * - 设置成 `bottom` 的话将会向目标歌词行的底部对齐
2045
+ * - 设置成 `center` 的话将会向目标歌词行的垂直中心对齐
2046
+ * @param alignAnchor 歌词行对齐方式,详情见函数说明
2047
+ */
723
2048
  setAlignAnchor(alignAnchor: LayoutAlignAnchor): void;
724
2049
  /**
725
- * 设置默认的歌词行对齐位置,相对于整个歌词播放组件的大小位置,默认为 `0.5`
726
- * @param alignPosition 一个 `[0.0-1.0]` 之间的任意数字,代表组件高度由上到下的比例位置
727
- */
2050
+ * 设置默认的歌词行对齐位置,相对于整个歌词播放组件的大小位置,默认为 `0.5`
2051
+ * @param alignPosition 一个 `[0.0-1.0]` 之间的任意数字,代表组件高度由上到下的比例位置
2052
+ */
728
2053
  setAlignPosition(alignPosition: number): void;
729
2054
  /**
730
- * 设置 overscan(视图上下额外缓冲渲染区)距离,单位:像素。
731
- * @param px 像素值,默认 300
732
- */
2055
+ * 设置 overscan(视图上下额外缓冲渲染区)距离,单位:像素。
2056
+ * @param px 像素值,默认 300
2057
+ */
733
2058
  setOverscanPx(px: number): void;
734
2059
  /** 获取当前 overscan 像素距离 */
735
2060
  getOverscanPx(): number;
736
2061
  /**
737
- * 设置是否使用物理弹簧算法实现歌词动画效果,默认启用
738
- *
739
- * 如果启用,则会通过弹簧算法实时处理歌词位置,但是需要性能足够强劲的电脑方可流畅运行
740
- *
741
- * 如果不启用,则会回退到基于 `transition` 的过渡效果,对低性能的机器比较友好,但是效果会比较单一
742
- */
2062
+ * 设置是否使用物理弹簧算法实现歌词动画效果,默认启用
2063
+ *
2064
+ * 如果启用,则会通过弹簧算法实时处理歌词位置,但是需要性能足够强劲的电脑方可流畅运行
2065
+ *
2066
+ * 如果不启用,则会回退到基于 `transition` 的过渡效果,对低性能的机器比较友好,但是效果会比较单一
2067
+ */
743
2068
  setEnableSpring(enable?: boolean): void;
744
2069
  /**
745
- * 获取当前是否启用了物理弹簧
746
- * @returns 是否启用物理弹簧
747
- */
2070
+ * 获取当前是否启用了物理弹簧
2071
+ * @returns 是否启用物理弹簧
2072
+ */
748
2073
  getEnableSpring(): boolean;
749
2074
  /**
750
- * 设置歌词的优化配置项,这些配置项默认全部开启
751
- *
752
- * 注意,如果在 `setLyricLines` 之后修改此配置,需要重新调用 `setLyricLines()` 才能对当前歌词生效
753
- * @param options 优化配置选项
754
- * @see {@link OptimizeLyricOptions}
755
- */
756
- setOptimizeOptions(options: OptimizeLyricOptions): void;
757
- /**
758
- * 设置当前播放歌词,要注意传入后这个数组内的信息不得修改,否则会发生错误
759
- * @param lines 歌词数组
760
- * @param initialTime 初始时间,默认为 0
761
- */
2075
+ * 设置当前播放歌词,要注意传入后这个数组内的信息不得修改,否则会发生错误
2076
+ * @param lines 歌词数组
2077
+ * @param initialTime 初始时间,默认为 0
2078
+ * @throws {TypeError} 歌词时间戳不是有限的非负数字
2079
+ * @throws {RangeError} 任一行、单词或注音的开始时间晚于结束时间
2080
+ */
762
2081
  setLyricLines(lines: LyricLine[], initialTime?: number): void;
763
2082
  /**
764
- * 获取当前是否在播放
765
- * @returns 当前是否在播放
766
- */
2083
+ * 获取当前是否在播放
2084
+ * @returns 当前是否在播放
2085
+ */
767
2086
  getIsPlaying(): boolean;
768
2087
  /**
769
- * 设置当前播放进度,此时将会更新内部的歌词进度信息。
770
- *
771
- * 内部会根据调用间隔和播放进度自动决定如何滚动和显示歌词,所以这个的调用频率越快越准确越好。
772
- * 调用完成后,应每帧调用 {@link update} 方法来执行歌词动画效果。**此函数本身不会触发动画效果**。
773
- *
774
- * @param time 当前播放进度,单位为毫秒
775
- */
2088
+ * 设置当前播放进度,此时将会更新内部的歌词进度信息。
2089
+ *
2090
+ * 内部会根据调用间隔和播放进度自动决定应如何滚动和显示歌词,所以此方法的调用频率越快越准确越好。
2091
+ * 调用频率较低或进度细度过粗可能会导致歌词显示延迟或导致自动跳转推导错误。
2092
+ * 调用完成后,应每帧调用 {@link update} 方法来执行歌词动画效果。此函数本身不会触发动画效果。
2093
+ *
2094
+ * 当 `isSeek` 为 `true` 时,将强制按跳转处理,并在下次调用 {@link update} 时触发一系列的行为变更,
2095
+ * 具体请参考 <https://amll.dev/guides/component/seeking>,因此请只在真正跳转时设为 `true`
2096
+ *
2097
+ * @param time 当前播放进度,单位为毫秒,非有限值会被静默忽略
2098
+ * @param isSeek 是否强制按跳转处理,默认交由内部推导
2099
+ * @see {@link setEnableAutoSeekDetection} 自动推导跳转状态的文档
2100
+ * @see https://amll.dev/guides/component/sequence#播放进度
2101
+ */
776
2102
  setCurrentTime(time: number, isSeek?: boolean): void;
777
2103
  /**
778
- * 重新布局定位歌词行的位置,调用完成后再逐帧调用 `update`
779
- * 函数即可让歌词通过动画移动到目标位置。
780
- *
781
- * 函数有一个 `force` 参数,用于指定是否强制修改布局,也就是不经过动画直接调整元素位置和大小。
782
- *
783
- * 此函数还有一个 `reflow` 参数,用于指定是否需要重新计算布局
784
- *
785
- * 因为计算布局必定会导致浏览器重排布局,所以会大幅度影响流畅度和性能,故请只在以下情况下将其​设置为 true:
786
- *
787
- * 1. 歌词页面大小发生改变时(这个组件会自行处理)
788
- * 2. 加载了新的歌词时(不论前后歌词是否完全一样)
789
- * 3. 用户自行跳转了歌曲播放位置(不论距离远近)
790
- *
791
- * @param sync 是否同步执行,通常用于初始化或 Resize 时立即布局
792
- * @param force 是否绕过弹簧效果强制更新位置
793
- */
794
- calcLayout(sync?: boolean, force?: boolean): Promise<void>;
795
- /**
796
- * 设置所有歌词行在横坐标上的弹簧属性,包括重量、弹力和阻力。
797
- *
798
- * @param params 需要设置的弹簧属性,提供的属性将会覆盖原来的属性,未提供的属性将会保持原样
799
- * @deprecated 考虑到横向弹簧效果并不常见,所以这个函数将会在未来的版本中移除
800
- */
2104
+ * 推进时间线并把增量变化应用到视图上
2105
+ * @param mediaTime 当前播放进度
2106
+ * @param isSeek 这次进度变化是否为跳转
2107
+ */
2108
+ private syncTime;
2109
+ /**
2110
+ * 由子类实现的歌词组构建逻辑
2111
+ */
2112
+ protected abstract buildLyricGroups(): void;
2113
+ /**
2114
+ * 由子类实现的间奏点组件创建逻辑
2115
+ *
2116
+ * 在基类构造函数中调用,子类需要返回对应渲染实现的后端
2117
+ *
2118
+ * @remarks 工厂执行时子类字段尚未初始化,实现内不得读取子类实例状态,
2119
+ * 所需资源应由返回的组件自行创建或延迟获取
2120
+ */
2121
+ protected abstract createInterludeDots(): InterludeDotsBase & HasElement;
2122
+ /**
2123
+ * 由子类实现的底栏组件创建逻辑
2124
+ *
2125
+ * 在基类构造函数中调用,子类需要返回对应渲染实现的实例
2126
+ *
2127
+ * @remarks 工厂执行时子类字段尚未初始化,实现内不得读取子类实例状态,
2128
+ * 所需资源应由返回的组件自行创建或延迟获取
2129
+ */
2130
+ protected abstract createBottomLine(): BottomLine;
2131
+ /**
2132
+ * 重新构建歌词行和时间状态
2133
+ *
2134
+ * 一般用于在调用 {@link setLyricProcessConfig} 更新配置后手动刷新视图,
2135
+ * 或在外部样式/DOM 结构发生改变后重置歌词视图
2136
+ *
2137
+ * @param initialTime 重建后对齐的初始时间(毫秒),默认使用当前播放进度
2138
+ */
2139
+ rebuildLyricView(initialTime?: number): void;
2140
+ /**
2141
+ * 更新歌词纵向滚动动画的弹簧参数。
2142
+ *
2143
+ * 其策略为:
2144
+ * - seeking 或间奏时使用更稳定的固定参数
2145
+ * - 普通播放时根据相邻歌词的时间间隔动态调整 stiffness / damping
2146
+ * - 播放完毕时使用中速弹簧参数
2147
+ *
2148
+ * @param isInterludeActive 当前是否命中间奏区间
2149
+ * @param isSeeking 本次同步的时间轴是否发生了跳转
2150
+ * @param isEndOfSong 歌曲是否播放完毕
2151
+ */
2152
+ private updateSpringParams;
2153
+ /**
2154
+ * 重新计算歌词行的几何排版坐标与视觉状态
2155
+ *
2156
+ * 此方法不会触发 DOM 强制重排
2157
+ *
2158
+ * 计算完成后,在每一帧调用 `update()` / `commitChanges()` 即可让歌词平滑移动至目标位置
2159
+ *
2160
+ * @internal 仅供内部和绑定包使用
2161
+ * @param reason 触发排版布局更新的原因场景
2162
+ */
2163
+ calcLayout(reason: LayoutReason): void;
2164
+ /**
2165
+ * 推导某一行是否处于激活状态
2166
+ * @param index 歌词行索引
2167
+ * @param snapshot 当前帧的时间线快照
2168
+ */
2169
+ private resolveIsActive;
2170
+ /**
2171
+ * 推导一行的目标透明度
2172
+ * @param index 歌词行索引
2173
+ * @param isInViewport 该行是否在可视区域内
2174
+ * @param snapshot 当前帧的时间线快照
2175
+ */
2176
+ private resolveOpacity;
2177
+ /**
2178
+ * 按距焦点的行距推导模糊档位
2179
+ * @param index 歌词行索引,底栏传入歌词总行数
2180
+ * @param isFocused 该目标是否为当前焦点,歌词行传 `isActive`,底栏传是否聚焦底栏
2181
+ * @param isInViewport 该目标是否在可视区域内
2182
+ * @param snapshot 当前帧的时间线快照
2183
+ */
2184
+ private resolveBlurLevel;
2185
+ /**
2186
+ * 设置所有歌词行、底栏和间奏点在横坐标上的弹簧属性,包括重量、弹力和阻力。
2187
+ *
2188
+ * @param params 需要设置的弹簧属性,提供的属性将会覆盖原来的属性,未提供的属性将会保持原样
2189
+ * @deprecated 考虑到横向弹簧效果并不常见,所以这个函数将会在未来的版本中移除
2190
+ */
801
2191
  setLinePosXSpringParams(_params?: Partial<SpringParams>): void;
802
2192
  /**
803
- * 设置所有歌词行在​纵坐标上的弹簧属性,包括重量、弹力和阻力。
804
- *
805
- * @param params 需要设置的弹簧属性,提供的属性将会覆盖原来的属性,未提供的属性将会保持原样
806
- */
2193
+ * 设置所有歌词行、底栏和间奏点在​纵坐标上的弹簧属性,包括重量、弹力和阻力。
2194
+ *
2195
+ * @param params 需要设置的弹簧属性,提供的属性将会覆盖原来的属性,未提供的属性将会保持原样
2196
+ */
807
2197
  setLinePosYSpringParams(params?: Partial<SpringParams>): void;
808
2198
  /**
809
- * 设置所有歌词行在​缩放大小上的弹簧属性,包括重量、弹力和阻力。
810
- *
811
- * @param params 需要设置的弹簧属性,提供的属性将会覆盖原来的属性,未提供的属性将会保持原样
812
- */
2199
+ * 设置所有歌词行在​缩放大小上的弹簧属性,包括重量、弹力和阻力。
2200
+ *
2201
+ * @param params 需要设置的弹簧属性,提供的属性将会覆盖原来的属性,未提供的属性将会保持原样
2202
+ */
813
2203
  setLineScaleSpringParams(params?: Partial<SpringParams>): void;
814
2204
  /**
815
- * 暂停部分效果演出,目前会暂停播放间奏点的动画,且将背景歌词显示出来
816
- */
2205
+ * 暂停部分效果演出,目前会暂停播放间奏点的动画,且将背景歌词显示出来
2206
+ */
817
2207
  pause(): void;
818
2208
  /**
819
- * 恢复部分效果演出,目前会恢复播放间奏点的动画
820
- */
2209
+ * 恢复部分效果演出,目前会恢复播放间奏点的动画
2210
+ */
821
2211
  resume(): void;
822
2212
  /**
823
- * 更新动画,这个函数应该被逐帧调用或者在以下情况下调用一次:
824
- *
825
- * 1. 刚刚调用完设置歌词函数的时候
826
- * @param delta 距离上一次被调用到现在的时长,单位为毫秒(可为浮点数)
827
- */
2213
+ * 更新动画,这个函数应该被逐帧调用或者在以下情况下调用一次:
2214
+ *
2215
+ * 1. 刚刚调用完设置歌词函数的时候
2216
+ * @param delta 距离上一次被调用到现在的时长,单位为毫秒(可为浮点数)
2217
+ */
828
2218
  update(delta?: number): void;
829
2219
  protected onResize(): void;
830
2220
  /**
831
- * 获取一个特殊的底栏元素,默认是空白的,可以往内部添加任意元素
832
- *
833
- * 这个元素始终在歌词的底部,可以用于显示歌曲创作者等信息
834
- *
835
- * 但是请勿删除该元素,只能在内部存放元素
836
- *
837
- * @returns 一个元素,可以往内部添加任意元素
838
- */
2221
+ * 获取一个特殊的底栏元素,默认是空白的,可以往内部添加任意元素
2222
+ *
2223
+ * 这个元素始终在歌词的底部,可以用于显示歌曲创作者等信息
2224
+ *
2225
+ * 但是请勿删除该元素,只能在内部存放元素
2226
+ *
2227
+ * @returns 一个元素,可以往内部添加任意元素
2228
+ */
839
2229
  getBottomLineElement(): HTMLElement;
840
2230
  /**
841
- * 重置用户滚动状态
842
- *
843
- * 请在用户完成滚动点击跳转歌词时调用本事件再调用 `calcLayout` 以正确滚动到目标位置
844
- */
2231
+ * 重置用户滚动状态并恢复自动对齐
2232
+ *
2233
+ * 一个典型的使用场景是在用户滚动完毕、但歌词未自动归位时立刻归位
2234
+ */
845
2235
  resetScroll(): void;
846
2236
  /**
847
- * 获取当前歌词数组
848
- *
849
- * 一般和最后调用 `setLyricLines` 给予的参数一样
850
- * @returns 当前歌词数组
851
- */
852
- getLyricLines(): LyricLine[];
853
- /**
854
- * 获取当前歌词的播放位置
855
- *
856
- * 一般和最后调用 `setCurrentTime` 给予的参数一样
857
- * @returns 当前播放位置
858
- */
2237
+ * 获取当前播放的、未经过优化和掩码处理的歌词数组
2238
+ *
2239
+ * 一般和最后调用 `setLyricLines` 给予的参数一样
2240
+ * @returns 当前歌词数组
2241
+ */
2242
+ getLyricLines(): ReadonlyArray<LyricLine>;
2243
+ /**
2244
+ * 获取当前歌词的播放位置
2245
+ *
2246
+ * 一般和最后调用 `setCurrentTime` 给予的参数一样
2247
+ * @returns 当前播放位置
2248
+ */
859
2249
  getCurrentTime(): number;
860
2250
  /**
861
- * 设置是否让背景人声行始终后置显示
862
- *
863
- * 默认情况下,如果背景歌词开始时间早于主歌词,会在主歌词上方展示;
864
- * 如果设置为 `true`,则无论时间顺序如何,背景歌词都会始终在主歌词下方展示
865
- * @param enable 是否启用始终后置
866
- */
2251
+ * 设置是否让背景人声行始终后置显示
2252
+ *
2253
+ * 默认情况下,如果背景歌词开始时间早于主歌词,会在主歌词上方展示;
2254
+ * 如果设置为 `true`,则无论时间顺序如何,背景歌词都会始终在主歌词下方展示
2255
+ * @param enable 是否启用始终后置
2256
+ */
867
2257
  setAlwaysPostpositionBackground(enable: boolean): void;
868
2258
  /** 获取当前是否设置了让背景人声行始终后置显示 */
869
2259
  getAlwaysPostpositionBackground(): boolean;
@@ -871,44 +2261,43 @@ declare abstract class LyricPlayerBase extends EventTarget implements HasElement
871
2261
  dispose(): void;
872
2262
  }
873
2263
  //#endregion
874
- //#region src/lyric-player/dom/lyric-line.d.ts
875
- interface RealWord extends LyricWord {
876
- mainElement: HTMLSpanElement;
877
- subElements: HTMLSpanElement[];
878
- elementAnimations: Animation[];
879
- maskAnimations: Animation[];
880
- width: number;
881
- height: number;
882
- padding: number;
883
- shouldEmphasize: boolean;
2264
+ //#region src/lyric-player/dom/interlude-dots.d.ts
2265
+ declare class InterludeDotsEl extends InterludeDotsBase implements HasElement {
2266
+ private element;
2267
+ private dot0;
2268
+ private dot1;
2269
+ private dot2;
2270
+ private lastStyle;
2271
+ constructor();
2272
+ getElement(): HTMLElement;
2273
+ protected override render(snapshot: Readonly<InterludeDotsSnapshot>, left: number, top: number): void;
2274
+ override dispose(): void;
884
2275
  }
2276
+ //#endregion
2277
+ //#region src/lyric-player/dom/lyric-line.d.ts
885
2278
  declare class LyricLineEl extends LyricLineBase {
886
- private lyricPlayer;
887
- private lyricLine;
888
- private element;
2279
+ private readonly lyricPlayer;
2280
+ private readonly lyricLine;
2281
+ private readonly element;
889
2282
  private splittedWords;
890
2283
  private built;
891
- lineSize: number[];
892
2284
  private renderMode;
893
- private currentBrightAlpha;
894
- private currentDarkAlpha;
895
- private targetBrightAlpha;
896
- private targetDarkAlpha;
897
- /**
898
- * 用于平衡换行、尽量减少各行长度差异的类
899
- */
900
- private balancer?;
2285
+ private maskAnimator?;
2286
+ private lastScaleNum;
2287
+ private readonly lineHasRubyWords;
2288
+ private readonly lineHasRomanWords;
2289
+ /**
2290
+ * 用于平衡换行、尽量减少各行长度差异的类
2291
+ */
2292
+ private readonly balancer?;
901
2293
  constructor(lyricPlayer: DomLyricPlayer, lyricLine?: LyricLine);
902
- areWordsOnSameLine(word1: RealWord, word2: RealWord): boolean;
903
2294
  private isEnabled;
904
2295
  enable(maskAnimationTime?: number, shouldPlay?: boolean): Promise<void>;
905
2296
  disable(): void;
906
2297
  private lastWord?;
907
2298
  resume(): Promise<void>;
908
2299
  pause(): Promise<void>;
909
- setMaskAnimationState(maskAnimationTime?: number): void;
910
2300
  getLine(): LyricLine;
911
- private lastStyle;
912
2301
  show(): void;
913
2302
  private rebuildStyle;
914
2303
  override rebuildElement(): void;
@@ -918,21 +2307,15 @@ declare class LyricLineEl extends LyricLineBase {
918
2307
  private getRubySegments;
919
2308
  private createWord;
920
2309
  private buildWord;
921
- private initFloatAnimation;
922
- private initEmphasizeAnimation;
923
- private get totalDuration();
924
2310
  override onLineSizeChange(_size: [number, number]): void;
925
2311
  updateMaskImageSync(): void;
926
- private generateCalcBasedMaskImage;
927
- private generateWebAnimationBasedMaskImage;
928
2312
  getElement(): HTMLElement;
929
- private updateMaskAlphaTargets;
930
- private applyAlphaToDom;
931
- override setTransform(scale?: number, opacity?: number, blur?: number, force?: boolean, delay?: number, mode?: LyricLineRenderMode): void;
932
- update(delta?: number): void;
2313
+ private setRenderMode;
2314
+ override setTransform(scale?: number, opacity?: number, blur?: number, delay?: Duration, mode?: LyricLineRenderMode): void;
2315
+ update(delta?: Duration): void;
2316
+ override commitChanges(): void;
933
2317
  /** @internal */
934
2318
  _getDebugTargetPos(): string;
935
- teardownContent(): void;
936
2319
  private disposeElements;
937
2320
  override dispose(): void;
938
2321
  }
@@ -943,11 +2326,20 @@ declare class LyricLineGroup extends LyricLineGroupBase<LyricLineEl> {
943
2326
  element: HTMLElement;
944
2327
  bgWrapper?: HTMLElement;
945
2328
  private lastIsActive?;
2329
+ private lastBgHeight;
2330
+ private lastBgIsHidden?;
2331
+ private lastYNum;
2332
+ private lastOpacityNum;
2333
+ private lastBlurNum;
2334
+ private lastBgSlideYNum;
946
2335
  constructor(lyricPlayer: DomLyricPlayer, mainLine: LyricLineEl);
947
- get isInSight(): boolean;
2336
+ getElement(): HTMLElement;
2337
+ isInRenderRange(includeOverscan?: boolean): boolean;
948
2338
  show(): void;
949
2339
  hide(): void;
950
- override update(delta: number): void;
2340
+ override update(delta?: Duration): void;
2341
+ override commitChanges(): void;
2342
+ override onBgSizeChange(size: [number, number]): void;
951
2343
  addBgLine(bgLine: LyricLineEl): void;
952
2344
  protected renderStyles(): void;
953
2345
  override dispose(): void;
@@ -955,24 +2347,24 @@ declare class LyricLineGroup extends LyricLineGroupBase<LyricLineEl> {
955
2347
  //#endregion
956
2348
  //#region src/lyric-player/dom/index.d.ts
957
2349
  /**
958
- * 歌词行鼠标相关事件,可以获取到歌词行的索引、主歌词行以及背景歌词行(如果有)元素
959
- */
2350
+ * 歌词行鼠标相关事件,可以获取到歌词行的索引、主歌词行以及背景歌词行(如果有)元素
2351
+ */
960
2352
  declare class LyricLineMouseEvent extends MouseEvent {
961
2353
  /**
962
- * 歌词行索引
963
- */
2354
+ * 歌词行索引
2355
+ */
964
2356
  readonly lineIndex: number;
965
2357
  /**
966
- * 歌词行元素
967
- */
2358
+ * 歌词行元素
2359
+ */
968
2360
  readonly line: LyricLineBase;
969
2361
  /**
970
- * 背景人声歌词行元素 (如果存在)
971
- */
2362
+ * 背景人声歌词行元素 (如果存在)
2363
+ */
972
2364
  readonly bgLine: LyricLineBase | undefined;
973
2365
  /**
974
- * 自定义标志位,用于记录外部是否调用了 `stopPropagation`
975
- */
2366
+ * 自定义标志位,用于记录外部是否调用了 `stopPropagation`
2367
+ */
976
2368
  isPropagationStopped: boolean;
977
2369
  constructor(lineIndex: number, line: LyricLineBase, bgLine: LyricLineBase | undefined, event: MouseEvent);
978
2370
  override stopPropagation(): void;
@@ -980,10 +2372,10 @@ declare class LyricLineMouseEvent extends MouseEvent {
980
2372
  }
981
2373
  type LyricLineMouseEventListener = (evt: LyricLineMouseEvent) => void;
982
2374
  /**
983
- * 歌词播放组件,本框架的核心组件
984
- *
985
- * 尽可能贴切 Apple Music for iPad 的歌词效果设计,且做了力所能及的优化措施
986
- */
2375
+ * 歌词播放组件,本框架的核心组件
2376
+ *
2377
+ * 尽可能贴切 Apple Music for iPad 的歌词效果设计,且做了力所能及的优化措施
2378
+ */
987
2379
  declare class DomLyricPlayer extends LyricPlayerBase {
988
2380
  private abortController;
989
2381
  override currentLyricGroups: LyricLineGroup[];
@@ -993,26 +2385,32 @@ declare class DomLyricPlayer extends LyricPlayerBase {
993
2385
  readonly innerSize: [number, number];
994
2386
  private readonly onMouseEventHandler;
995
2387
  /**
996
- * 是否为非逐词歌词
997
- * @internal
998
- */
2388
+ * 是否为非逐词歌词
2389
+ * @internal
2390
+ */
999
2391
  _getIsNonDynamic(): boolean;
1000
2392
  private _baseFontSize;
1001
2393
  get baseFontSize(): number;
1002
2394
  constructor();
1003
2395
  private rebuildStyle;
1004
2396
  override setWordFadeWidth(value?: number): void;
1005
- /**
1006
- * 设置当前播放歌词,要注意传入后这个数组内的信息不得修改,否则会发生错误
1007
- * @param lines 歌词数组
1008
- * @param initialTime 初始时间,默认为 0
1009
- */
1010
- override setLyricLines(lines: LyricLine[], initialTime?: number): void;
2397
+ protected override createInterludeDots(): InterludeDotsEl;
2398
+ protected override createBottomLine(): BottomLine;
2399
+ /**
2400
+ * 重新构建歌词行和时间状态
2401
+ *
2402
+ * 一般用于在调用 {@link setLyricProcessConfig} 更新配置后手动刷新视图,
2403
+ * 或在外部样式/DOM 结构发生改变后重置歌词视图
2404
+ *
2405
+ * @param initialTime 重建后对齐的初始时间(毫秒),默认使用当前播放进度
2406
+ */
2407
+ protected override buildLyricGroups(): void;
2408
+ override rebuildLyricView(initialTime?: number): void;
1011
2409
  override pause(): void;
1012
2410
  override resume(): void;
1013
2411
  override update(delta?: number): void;
1014
2412
  override dispose(): void;
1015
2413
  }
1016
2414
  //#endregion
1017
- export { AbstractBaseRenderer, BackgroundRender, BaseRenderer, Disposable, DomLyricPlayer, DomLyricPlayer as LyricPlayer, HasElement, LayoutAlignAnchor, LyricLine, type LyricLineBase, LyricLineMouseEvent, LyricLineMouseEventListener, LyricLineRenderMode, LyricPlayerBase, LyricWord, LyricWordBase, MaskObsceneWordsMode, MeshGradientRenderer, OptimizeLyricOptions, PixiRenderer, type PlayerLayoutState, type PlayerScrollState, type PlayerTimelineState, type spring_d_exports as spring };
2415
+ export { AbstractBaseRenderer, BackgroundRender, BaseRenderer, type BottomLine, type BottomLineTransforms, type ColorCount, type ColorVec3, CreatePaletteOptions, type Disposable, DomLyricPlayer, DomLyricPlayer as LyricPlayer, Duration, GLProgram, type GLRenderingContext, type HSVColor, type HasElement, type HistogramSource, type InterludeDotsBase, type InterludeDotsSnapshot, IsolationRenderer, type IsolationRendererOptions, LayoutAlignAnchor, LayoutReason, LayoutReasonStrategyMap, LayoutStrategy, type LyricDataConfig, type LyricLine, type LyricLineBase, LyricLineMouseEvent, LyricLineMouseEventListener, LyricLineRenderMode, LyricPlayerBase, type LyricWord, type LyricWordBase, MAX_FRAME_DELTA, MaskObsceneWordsMode, MediaTime, MeshGradientRenderer, type OptimizeLyricOptions, PaletteAlgorithm, type PaletteIntent, type PaletteResult, PixiRenderer, type ThemeColorResult, buildColorHistogram, channelToLinear, createAutoPalette, createKMeansPalette, createOctTreePalette, createPaletteFromImage, createThemeColor, hsvToRgb, labToRgb, labToXyz, paletteRgbLStarIsDark, paletteRgbLStarIsLight, rgbLStarIsDark, rgbToHsv, rgbToLab, rgbToXyz, spring_d_exports as spring, srgbToOkLab, xyzToLab, xyzToRgb, yToLStar };
1018
2416
  //# sourceMappingURL=amll-core.d.cts.map