@uimaxbai/am-lyrics 1.2.7 → 1.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/AmLyrics.d.ts +13 -0
- package/dist/src/AmLyrics.d.ts.map +1 -1
- package/dist/src/am-lyrics.js +306 -185
- package/dist/src/am-lyrics.js.map +1 -1
- package/dist/src/react.js +306 -185
- package/dist/src/react.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/AmLyrics.ts +367 -211
package/src/AmLyrics.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { css, html, LitElement, svg } from 'lit';
|
|
|
2
2
|
import { property, query, state } from 'lit/decorators.js';
|
|
3
3
|
import { GoogleService } from './GoogleService.js';
|
|
4
4
|
|
|
5
|
-
const VERSION = '1.2.
|
|
5
|
+
const VERSION = '1.2.9';
|
|
6
6
|
const INSTRUMENTAL_THRESHOLD_MS = 7000; // Show dots for gaps >= 7s
|
|
7
7
|
const FETCH_TIMEOUT_MS = 8000; // Timeout for all lyrics fetch requests
|
|
8
8
|
const SEEK_THRESHOLD_MS = 500;
|
|
@@ -1475,12 +1475,32 @@ export class AmLyrics extends LitElement {
|
|
|
1475
1475
|
@state()
|
|
1476
1476
|
private currentSourceIndex = 0;
|
|
1477
1477
|
|
|
1478
|
-
@state()
|
|
1479
1478
|
private isFetchingAlternatives = false;
|
|
1480
1479
|
|
|
1481
|
-
@state()
|
|
1482
1480
|
private hasFetchedAllProviders = false;
|
|
1483
1481
|
|
|
1482
|
+
private _updateFooter() {
|
|
1483
|
+
const footer = this.shadowRoot?.querySelector('.lyrics-footer');
|
|
1484
|
+
if (!footer) return;
|
|
1485
|
+
const switchBtn = footer.querySelector('.source-switch-btn');
|
|
1486
|
+
const svgEl = footer.querySelector('.source-switch-svg');
|
|
1487
|
+
const labelEl = footer.querySelector('.source-switch-label');
|
|
1488
|
+
if (switchBtn) {
|
|
1489
|
+
(switchBtn as HTMLButtonElement).disabled = this.isFetchingAlternatives;
|
|
1490
|
+
}
|
|
1491
|
+
if (svgEl) {
|
|
1492
|
+
svgEl.setAttribute(
|
|
1493
|
+
'style',
|
|
1494
|
+
`margin-right: 4px; ${this.isFetchingAlternatives ? 'animation: spin 1s linear infinite;' : ''}`,
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1497
|
+
if (labelEl) {
|
|
1498
|
+
labelEl.textContent = this.isFetchingAlternatives
|
|
1499
|
+
? 'Switching...'
|
|
1500
|
+
: 'Switch';
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1484
1504
|
private animationFrameId?: number;
|
|
1485
1505
|
|
|
1486
1506
|
private mainWordAnimations: Map<
|
|
@@ -1500,7 +1520,6 @@ export class AmLyrics extends LitElement {
|
|
|
1500
1520
|
|
|
1501
1521
|
private userScrollTimeoutId?: number;
|
|
1502
1522
|
|
|
1503
|
-
@state()
|
|
1504
1523
|
private isUserScrolling = false;
|
|
1505
1524
|
|
|
1506
1525
|
private isProgrammaticScroll = false;
|
|
@@ -1512,6 +1531,33 @@ export class AmLyrics extends LitElement {
|
|
|
1512
1531
|
// Cached DOM elements for animation updates
|
|
1513
1532
|
private cachedLyricsLines: HTMLElement[] = [];
|
|
1514
1533
|
|
|
1534
|
+
// Cached line and gap element maps for fast lookup
|
|
1535
|
+
private lineElementCache = new Map<number, HTMLElement>();
|
|
1536
|
+
|
|
1537
|
+
private gapElementCache = new Map<number, HTMLElement>();
|
|
1538
|
+
|
|
1539
|
+
// Cached gap computation results
|
|
1540
|
+
private cachedAllGaps: Array<{
|
|
1541
|
+
insertBeforeIndex: number;
|
|
1542
|
+
gapStart: number;
|
|
1543
|
+
gapEnd: number;
|
|
1544
|
+
}> = [];
|
|
1545
|
+
|
|
1546
|
+
// Cached isUnsynced flag
|
|
1547
|
+
private cachedIsUnsynced = false;
|
|
1548
|
+
|
|
1549
|
+
// Cached pre-computed line data for render
|
|
1550
|
+
private cachedLineData: Array<{
|
|
1551
|
+
wordGroups: Syllable[][];
|
|
1552
|
+
groupGrowable: boolean[];
|
|
1553
|
+
groupGlowing: boolean[];
|
|
1554
|
+
vwFullText: string[];
|
|
1555
|
+
vwFullDuration: number[];
|
|
1556
|
+
vwCharOffset: number[];
|
|
1557
|
+
vwStartMs: number[];
|
|
1558
|
+
vwEndMs: number[];
|
|
1559
|
+
}> | null = null;
|
|
1560
|
+
|
|
1515
1561
|
// Active line tracking
|
|
1516
1562
|
private activeLineIds: Set<string> = new Set();
|
|
1517
1563
|
|
|
@@ -1602,6 +1648,7 @@ export class AmLyrics extends LitElement {
|
|
|
1602
1648
|
this.currentSourceIndex = 0;
|
|
1603
1649
|
this.isFetchingAlternatives = false;
|
|
1604
1650
|
this.hasFetchedAllProviders = false;
|
|
1651
|
+
this._updateFooter();
|
|
1605
1652
|
try {
|
|
1606
1653
|
const resolvedMetadata = await this.resolveSongMetadata();
|
|
1607
1654
|
// If a newer fetch was triggered while we awaited, bail out
|
|
@@ -1678,6 +1725,7 @@ export class AmLyrics extends LitElement {
|
|
|
1678
1725
|
s.source === 'Tidal' ||
|
|
1679
1726
|
s.source === 'Genius',
|
|
1680
1727
|
);
|
|
1728
|
+
this._updateFooter();
|
|
1681
1729
|
|
|
1682
1730
|
if (collectedSources.length > 0) {
|
|
1683
1731
|
this.availableSources = AmLyrics.mergeAndSortSources(collectedSources);
|
|
@@ -1803,6 +1851,7 @@ export class AmLyrics extends LitElement {
|
|
|
1803
1851
|
|
|
1804
1852
|
if (!this.hasFetchedAllProviders) {
|
|
1805
1853
|
this.isFetchingAlternatives = true;
|
|
1854
|
+
this._updateFooter();
|
|
1806
1855
|
try {
|
|
1807
1856
|
const resolvedMetadata = await this.resolveSongMetadata();
|
|
1808
1857
|
if (resolvedMetadata?.metadata) {
|
|
@@ -1865,6 +1914,7 @@ export class AmLyrics extends LitElement {
|
|
|
1865
1914
|
} finally {
|
|
1866
1915
|
this.hasFetchedAllProviders = true;
|
|
1867
1916
|
this.isFetchingAlternatives = false;
|
|
1917
|
+
this._updateFooter();
|
|
1868
1918
|
}
|
|
1869
1919
|
}
|
|
1870
1920
|
|
|
@@ -3135,15 +3185,11 @@ export class AmLyrics extends LitElement {
|
|
|
3135
3185
|
const linesChanged = !AmLyrics.arraysEqual(newActiveLines, oldActiveLines);
|
|
3136
3186
|
|
|
3137
3187
|
if (linesChanged || isSeek) {
|
|
3138
|
-
// Imperatively manage 'active' class so that scroll-animate and other
|
|
3139
|
-
// imperative classes are never clobbered.
|
|
3140
3188
|
if (this.lyricsContainer) {
|
|
3141
3189
|
// Remove 'active' from lines that are no longer active
|
|
3142
3190
|
for (const lineIndex of oldActiveLines) {
|
|
3143
3191
|
if (!newActiveLines.includes(lineIndex)) {
|
|
3144
|
-
const lineElement = this.
|
|
3145
|
-
`#lyrics-line-${lineIndex}`,
|
|
3146
|
-
) as HTMLElement;
|
|
3192
|
+
const lineElement = this._getLineElement(lineIndex);
|
|
3147
3193
|
if (lineElement) {
|
|
3148
3194
|
lineElement.classList.remove('active');
|
|
3149
3195
|
AmLyrics.resetSyllables(lineElement);
|
|
@@ -3153,12 +3199,10 @@ export class AmLyrics extends LitElement {
|
|
|
3153
3199
|
// Add 'active' to newly active lines
|
|
3154
3200
|
for (const lineIndex of newActiveLines) {
|
|
3155
3201
|
if (!oldActiveLines.includes(lineIndex)) {
|
|
3156
|
-
const lineElement = this.
|
|
3157
|
-
`#lyrics-line-${lineIndex}`,
|
|
3158
|
-
) as HTMLElement;
|
|
3202
|
+
const lineElement = this._getLineElement(lineIndex);
|
|
3159
3203
|
if (lineElement) {
|
|
3160
3204
|
lineElement.classList.add('active');
|
|
3161
|
-
lineElement.classList.remove('pre-active');
|
|
3205
|
+
lineElement.classList.remove('pre-active');
|
|
3162
3206
|
}
|
|
3163
3207
|
}
|
|
3164
3208
|
}
|
|
@@ -3170,17 +3214,13 @@ export class AmLyrics extends LitElement {
|
|
|
3170
3214
|
|
|
3171
3215
|
this.startAnimationFromTime(newTime);
|
|
3172
3216
|
|
|
3173
|
-
// Trigger scroll imperatively (was previously in updated() via @state)
|
|
3174
3217
|
this._handleActiveLineScroll(oldActiveLines, isSeek);
|
|
3175
3218
|
}
|
|
3176
3219
|
|
|
3177
|
-
// YouLyPlus-style syllable animation updates
|
|
3178
3220
|
if (this.lyricsContainer) {
|
|
3179
|
-
// Update syllables in active lines
|
|
3221
|
+
// Update syllables in active lines using cached elements
|
|
3180
3222
|
for (const lineIndex of this.activeLineIndices) {
|
|
3181
|
-
const lineElement = this.
|
|
3182
|
-
`#lyrics-line-${lineIndex}`,
|
|
3183
|
-
) as HTMLElement;
|
|
3223
|
+
const lineElement = this._getLineElement(lineIndex);
|
|
3184
3224
|
if (lineElement) {
|
|
3185
3225
|
AmLyrics.updateSyllablesForLine(lineElement, newTime);
|
|
3186
3226
|
}
|
|
@@ -3193,62 +3233,91 @@ export class AmLyrics extends LitElement {
|
|
|
3193
3233
|
AmLyrics.updateSyllablesForLine(gapLine as HTMLElement, newTime);
|
|
3194
3234
|
});
|
|
3195
3235
|
|
|
3196
|
-
// Imperatively manage gap active state
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3236
|
+
// Imperatively manage gap active state
|
|
3237
|
+
if (this.gapElementCache.size > 0) {
|
|
3238
|
+
for (const [, gap] of this.gapElementCache) {
|
|
3239
|
+
const gapStartTime = parseFloat(
|
|
3240
|
+
gap.getAttribute('data-start-time') || '0',
|
|
3241
|
+
);
|
|
3242
|
+
const gapEndTime = parseFloat(
|
|
3243
|
+
gap.getAttribute('data-end-time') || '0',
|
|
3244
|
+
);
|
|
3245
|
+
const shouldBeActive =
|
|
3246
|
+
newTime >= gapStartTime && newTime < gapEndTime;
|
|
3247
|
+
const isActive = gap.classList.contains('active');
|
|
3248
|
+
const isExiting = gap.classList.contains('gap-exiting');
|
|
3249
|
+
const exitLeadMs = GAP_EXIT_LEAD_MS;
|
|
3250
|
+
const shouldStartExiting =
|
|
3251
|
+
isActive && !isExiting && newTime >= gapEndTime - exitLeadMs;
|
|
3252
|
+
|
|
3253
|
+
if (shouldBeActive && !isActive && !isExiting) {
|
|
3254
|
+
gap.classList.remove('gap-exiting');
|
|
3255
|
+
gap.classList.add('active');
|
|
3256
|
+
const dotSyllables = gap.querySelectorAll('.lyrics-syllable');
|
|
3257
|
+
dotSyllables.forEach(dot => {
|
|
3258
|
+
const dotStart = parseFloat(
|
|
3259
|
+
dot.getAttribute('data-start-time') || '0',
|
|
3260
|
+
);
|
|
3261
|
+
const dotEnd = parseFloat(
|
|
3262
|
+
dot.getAttribute('data-end-time') || '0',
|
|
3263
|
+
);
|
|
3264
|
+
if (newTime > dotEnd) {
|
|
3265
|
+
dot.classList.add('finished');
|
|
3266
|
+
if (!dot.classList.contains('highlight')) {
|
|
3267
|
+
AmLyrics.updateSyllableAnimation(dot as HTMLElement);
|
|
3268
|
+
}
|
|
3269
|
+
} else if (newTime >= dotStart && newTime <= dotEnd) {
|
|
3228
3270
|
AmLyrics.updateSyllableAnimation(dot as HTMLElement);
|
|
3229
3271
|
}
|
|
3230
|
-
}
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3272
|
+
});
|
|
3273
|
+
} else if (shouldStartExiting) {
|
|
3274
|
+
gap.classList.add('gap-exiting');
|
|
3275
|
+
gap.classList.remove('active');
|
|
3276
|
+
setTimeout(() => {
|
|
3277
|
+
gap.classList.remove('gap-exiting');
|
|
3278
|
+
}, GAP_EXIT_LEAD_MS);
|
|
3279
|
+
} else if (isActive && !shouldBeActive) {
|
|
3280
|
+
gap.classList.remove('active');
|
|
3281
|
+
gap.classList.remove('gap-exiting');
|
|
3282
|
+
} else if (isExiting && newTime < gapEndTime - exitLeadMs) {
|
|
3241
3283
|
gap.classList.remove('gap-exiting');
|
|
3242
|
-
}
|
|
3243
|
-
} else if (isActive && !shouldBeActive) {
|
|
3244
|
-
// NEW: Immediate cleanup if we seeked out of valid range
|
|
3245
|
-
gap.classList.remove('active');
|
|
3246
|
-
gap.classList.remove('gap-exiting');
|
|
3247
|
-
} else if (isExiting && newTime < gapEndTime - exitLeadMs) {
|
|
3248
|
-
// NEW: Cleanup exiting state if we seeked backwards before exit window
|
|
3249
|
-
gap.classList.remove('gap-exiting');
|
|
3284
|
+
}
|
|
3250
3285
|
}
|
|
3251
|
-
})
|
|
3286
|
+
} else if (this.lyricsContainer) {
|
|
3287
|
+
// Fallback: no cache yet, use querySelectorAll
|
|
3288
|
+
const allGaps = this.lyricsContainer.querySelectorAll('.lyrics-gap');
|
|
3289
|
+
allGaps.forEach(gap => {
|
|
3290
|
+
const gapStartTime = parseFloat(
|
|
3291
|
+
gap.getAttribute('data-start-time') || '0',
|
|
3292
|
+
);
|
|
3293
|
+
const gapEndTime = parseFloat(
|
|
3294
|
+
gap.getAttribute('data-end-time') || '0',
|
|
3295
|
+
);
|
|
3296
|
+
const shouldBeActive =
|
|
3297
|
+
newTime >= gapStartTime && newTime < gapEndTime;
|
|
3298
|
+
const isActive = gap.classList.contains('active');
|
|
3299
|
+
const isExiting = gap.classList.contains('gap-exiting');
|
|
3300
|
+
const exitLeadMs = GAP_EXIT_LEAD_MS;
|
|
3301
|
+
const shouldStartExiting =
|
|
3302
|
+
isActive && !isExiting && newTime >= gapEndTime - exitLeadMs;
|
|
3303
|
+
|
|
3304
|
+
if (shouldBeActive && !isActive && !isExiting) {
|
|
3305
|
+
gap.classList.remove('gap-exiting');
|
|
3306
|
+
gap.classList.add('active');
|
|
3307
|
+
} else if (shouldStartExiting) {
|
|
3308
|
+
gap.classList.add('gap-exiting');
|
|
3309
|
+
gap.classList.remove('active');
|
|
3310
|
+
setTimeout(() => {
|
|
3311
|
+
gap.classList.remove('gap-exiting');
|
|
3312
|
+
}, GAP_EXIT_LEAD_MS);
|
|
3313
|
+
} else if (isActive && !shouldBeActive) {
|
|
3314
|
+
gap.classList.remove('active');
|
|
3315
|
+
gap.classList.remove('gap-exiting');
|
|
3316
|
+
} else if (isExiting && newTime < gapEndTime - exitLeadMs) {
|
|
3317
|
+
gap.classList.remove('gap-exiting');
|
|
3318
|
+
}
|
|
3319
|
+
});
|
|
3320
|
+
}
|
|
3252
3321
|
|
|
3253
3322
|
// Track instrumental gap state
|
|
3254
3323
|
const currentGap = this.findInstrumentalGapAt(newTime);
|
|
@@ -3271,9 +3340,7 @@ export class AmLyrics extends LitElement {
|
|
|
3271
3340
|
const line = this.lyrics[i];
|
|
3272
3341
|
const timeUntilStart = line.timestamp - newTime;
|
|
3273
3342
|
|
|
3274
|
-
const nextLineEl = this.
|
|
3275
|
-
`#lyrics-line-${i}`,
|
|
3276
|
-
) as HTMLElement;
|
|
3343
|
+
const nextLineEl = this._getLineElement(i);
|
|
3277
3344
|
|
|
3278
3345
|
const isBackToBack = this.activeLineIndices.length > 0;
|
|
3279
3346
|
const leadTime = isBackToBack
|
|
@@ -3281,16 +3348,14 @@ export class AmLyrics extends LitElement {
|
|
|
3281
3348
|
: PRE_SCROLL_LEAD_MS;
|
|
3282
3349
|
|
|
3283
3350
|
if (timeUntilStart > leadTime) {
|
|
3284
|
-
break;
|
|
3351
|
+
break;
|
|
3285
3352
|
}
|
|
3286
3353
|
|
|
3287
3354
|
if (timeUntilStart > 0 && timeUntilStart <= leadTime) {
|
|
3288
|
-
// Time to pre-scroll and pre-activate!
|
|
3289
3355
|
if (nextLineEl) {
|
|
3290
3356
|
preActiveLineIndex = i;
|
|
3291
3357
|
|
|
3292
3358
|
if (!isBackToBack) {
|
|
3293
|
-
// Apply unblur & zoom effect ahead of lyric start only if no line is currently active
|
|
3294
3359
|
nextLineEl.classList.add('pre-active');
|
|
3295
3360
|
}
|
|
3296
3361
|
this.clearPreActiveClasses(i);
|
|
@@ -3316,6 +3381,9 @@ export class AmLyrics extends LitElement {
|
|
|
3316
3381
|
|
|
3317
3382
|
updated(changedProperties: Map<string | number | symbol, unknown>) {
|
|
3318
3383
|
if (changedProperties.has('lyrics')) {
|
|
3384
|
+
this._invalidateCaches();
|
|
3385
|
+
this._ensureLineDataCache();
|
|
3386
|
+
this._updateCachedIsUnsynced();
|
|
3319
3387
|
// Recalculate timing data for accurate animations whenever lyrics change
|
|
3320
3388
|
this._updateCharTimingData();
|
|
3321
3389
|
|
|
@@ -3325,9 +3393,7 @@ export class AmLyrics extends LitElement {
|
|
|
3325
3393
|
if (this.lyricsContainer && this.lyrics) {
|
|
3326
3394
|
const activeLines = this.findActiveLineIndices(this.currentTime);
|
|
3327
3395
|
for (const lineIndex of activeLines) {
|
|
3328
|
-
const lineEl = this.
|
|
3329
|
-
`#lyrics-line-${lineIndex}`,
|
|
3330
|
-
) as HTMLElement;
|
|
3396
|
+
const lineEl = this._getLineElement(lineIndex);
|
|
3331
3397
|
if (lineEl) lineEl.classList.add('active');
|
|
3332
3398
|
}
|
|
3333
3399
|
}
|
|
@@ -3343,7 +3409,7 @@ export class AmLyrics extends LitElement {
|
|
|
3343
3409
|
this.backgroundWordProgress.clear();
|
|
3344
3410
|
this.mainWordAnimations.clear();
|
|
3345
3411
|
this.backgroundWordAnimations.clear();
|
|
3346
|
-
this.
|
|
3412
|
+
this.setUserScrolling(false);
|
|
3347
3413
|
|
|
3348
3414
|
// Cancel any running animations
|
|
3349
3415
|
if (this.animationFrameId) {
|
|
@@ -3402,13 +3468,32 @@ export class AmLyrics extends LitElement {
|
|
|
3402
3468
|
);
|
|
3403
3469
|
if (targetLineIndex === null) return;
|
|
3404
3470
|
|
|
3405
|
-
const targetLine = this.
|
|
3406
|
-
|
|
3407
|
-
) as HTMLElement;
|
|
3471
|
+
const targetLine = this._getLineElement(targetLineIndex);
|
|
3472
|
+
if (!targetLine) return;
|
|
3408
3473
|
|
|
3409
|
-
|
|
3410
|
-
|
|
3474
|
+
// Only scroll snappily when lines are essentially back-to-back.
|
|
3475
|
+
// If there is any noticeable gap between them, scroll slower.
|
|
3476
|
+
let scrollDuration: number | undefined;
|
|
3477
|
+
const prevPrimaryIndex = AmLyrics.getLineIndexFromElement(
|
|
3478
|
+
this.currentPrimaryActiveLine,
|
|
3479
|
+
);
|
|
3480
|
+
if (
|
|
3481
|
+
prevPrimaryIndex !== null &&
|
|
3482
|
+
targetLineIndex > prevPrimaryIndex &&
|
|
3483
|
+
this.lyrics
|
|
3484
|
+
) {
|
|
3485
|
+
const gap =
|
|
3486
|
+
this.lyrics[targetLineIndex].timestamp -
|
|
3487
|
+
this.lyrics[prevPrimaryIndex].endtime;
|
|
3488
|
+
if (gap > 200) {
|
|
3489
|
+
scrollDuration = Math.min(
|
|
3490
|
+
Math.max(gap * 0.6, SCROLL_ANIMATION_DURATION_MS),
|
|
3491
|
+
2000,
|
|
3492
|
+
);
|
|
3493
|
+
}
|
|
3411
3494
|
}
|
|
3495
|
+
|
|
3496
|
+
this.focusLine(targetLine, forceScroll, scrollDuration);
|
|
3412
3497
|
}
|
|
3413
3498
|
|
|
3414
3499
|
private _textWidthCanvas: HTMLCanvasElement | undefined;
|
|
@@ -3429,9 +3514,164 @@ export class AmLyrics extends LitElement {
|
|
|
3429
3514
|
return 0;
|
|
3430
3515
|
}
|
|
3431
3516
|
|
|
3517
|
+
private _rebuildDomCache() {
|
|
3518
|
+
if (!this.lyricsContainer) return;
|
|
3519
|
+
|
|
3520
|
+
this.lineElementCache.clear();
|
|
3521
|
+
this.gapElementCache.clear();
|
|
3522
|
+
|
|
3523
|
+
if (!this.lyrics) return;
|
|
3524
|
+
|
|
3525
|
+
for (let i = 0; i < this.lyrics.length; i += 1) {
|
|
3526
|
+
const lineEl = this.lyricsContainer.querySelector(
|
|
3527
|
+
`#lyrics-line-${i}`,
|
|
3528
|
+
) as HTMLElement | null;
|
|
3529
|
+
if (lineEl) this.lineElementCache.set(i, lineEl);
|
|
3530
|
+
|
|
3531
|
+
const gapEl = this.lyricsContainer.querySelector(
|
|
3532
|
+
`#gap-${i}`,
|
|
3533
|
+
) as HTMLElement | null;
|
|
3534
|
+
if (gapEl) this.gapElementCache.set(i, gapEl);
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
|
|
3538
|
+
private _getLineElement(index: number): HTMLElement | null {
|
|
3539
|
+
const cached = this.lineElementCache.get(index);
|
|
3540
|
+
if (cached) return cached;
|
|
3541
|
+
if (!this.lyricsContainer) return null;
|
|
3542
|
+
const el = this.lyricsContainer.querySelector(
|
|
3543
|
+
`#lyrics-line-${index}`,
|
|
3544
|
+
) as HTMLElement | null;
|
|
3545
|
+
if (el) this.lineElementCache.set(index, el);
|
|
3546
|
+
return el;
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3549
|
+
private _getGapElement(index: number): HTMLElement | null {
|
|
3550
|
+
const cached = this.gapElementCache.get(index);
|
|
3551
|
+
if (cached) return cached;
|
|
3552
|
+
if (!this.lyricsContainer) return null;
|
|
3553
|
+
const el = this.lyricsContainer.querySelector(
|
|
3554
|
+
`#gap-${index}`,
|
|
3555
|
+
) as HTMLElement | null;
|
|
3556
|
+
if (el) this.gapElementCache.set(index, el);
|
|
3557
|
+
return el;
|
|
3558
|
+
}
|
|
3559
|
+
|
|
3560
|
+
private _invalidateCaches() {
|
|
3561
|
+
this.cachedAllGaps = [];
|
|
3562
|
+
this.cachedIsUnsynced = false;
|
|
3563
|
+
this.cachedLineData = null;
|
|
3564
|
+
this.lineElementCache.clear();
|
|
3565
|
+
this.gapElementCache.clear();
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3568
|
+
private _updateCachedIsUnsynced() {
|
|
3569
|
+
this.cachedIsUnsynced =
|
|
3570
|
+
this.lyrics && this.lyrics.length > 0
|
|
3571
|
+
? this.lyrics.every(l => l.timestamp === 0 && l.endtime === 0)
|
|
3572
|
+
: false;
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
private _ensureLineDataCache() {
|
|
3576
|
+
if (this.cachedLineData || !this.lyrics) return;
|
|
3577
|
+
|
|
3578
|
+
this.cachedLineData = this.lyrics.map(line => {
|
|
3579
|
+
const wordGroups: Syllable[][] = [];
|
|
3580
|
+
for (const syllable of line.text) {
|
|
3581
|
+
if (syllable.part && wordGroups.length > 0) {
|
|
3582
|
+
wordGroups[wordGroups.length - 1].push(syllable);
|
|
3583
|
+
} else {
|
|
3584
|
+
wordGroups.push([syllable]);
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
const groupGrowable: boolean[] = new Array(wordGroups.length).fill(false);
|
|
3589
|
+
const groupGlowing: boolean[] = new Array(wordGroups.length).fill(false);
|
|
3590
|
+
const vwFullText: string[] = new Array(wordGroups.length).fill('');
|
|
3591
|
+
const vwFullDuration: number[] = new Array(wordGroups.length).fill(0);
|
|
3592
|
+
const vwCharOffset: number[] = new Array(wordGroups.length).fill(0);
|
|
3593
|
+
const vwStartMs: number[] = new Array(wordGroups.length).fill(0);
|
|
3594
|
+
const vwEndMs: number[] = new Array(wordGroups.length).fill(0);
|
|
3595
|
+
|
|
3596
|
+
let vwStart = 0;
|
|
3597
|
+
while (vwStart < wordGroups.length) {
|
|
3598
|
+
let vwEnd = vwStart;
|
|
3599
|
+
while (vwEnd < wordGroups.length - 1) {
|
|
3600
|
+
const grp = wordGroups[vwEnd];
|
|
3601
|
+
const lastText = grp[grp.length - 1].text;
|
|
3602
|
+
if (/\s$/.test(lastText)) break;
|
|
3603
|
+
vwEnd += 1;
|
|
3604
|
+
}
|
|
3605
|
+
|
|
3606
|
+
const combinedText = wordGroups
|
|
3607
|
+
.slice(vwStart, vwEnd + 1)
|
|
3608
|
+
.flatMap(g => g.map(s => s.text))
|
|
3609
|
+
.join('')
|
|
3610
|
+
.trim();
|
|
3611
|
+
const combinedStart = wordGroups[vwStart][0].timestamp;
|
|
3612
|
+
const lastGrp = wordGroups[vwEnd];
|
|
3613
|
+
const combinedEnd = lastGrp[lastGrp.length - 1].endtime;
|
|
3614
|
+
const combinedDuration = combinedEnd - combinedStart;
|
|
3615
|
+
|
|
3616
|
+
const isCJK =
|
|
3617
|
+
/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/.test(
|
|
3618
|
+
combinedText,
|
|
3619
|
+
);
|
|
3620
|
+
const isRTL =
|
|
3621
|
+
/[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\u0590-\u05FF]/.test(
|
|
3622
|
+
combinedText,
|
|
3623
|
+
);
|
|
3624
|
+
const hasHyphen = combinedText.includes('-');
|
|
3625
|
+
|
|
3626
|
+
const wordLen = combinedText.length;
|
|
3627
|
+
let isGrowableVW =
|
|
3628
|
+
!isCJK && !isRTL && !hasHyphen && wordLen > 0 && wordLen <= 12;
|
|
3629
|
+
if (isGrowableVW) {
|
|
3630
|
+
if (wordLen < 3) {
|
|
3631
|
+
isGrowableVW =
|
|
3632
|
+
combinedDuration >= 1000 && combinedDuration >= wordLen * 500;
|
|
3633
|
+
} else {
|
|
3634
|
+
isGrowableVW =
|
|
3635
|
+
combinedDuration >= 800 && combinedDuration >= wordLen * 180;
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3639
|
+
const isGlowingVW = isGrowableVW;
|
|
3640
|
+
|
|
3641
|
+
let charOff = 0;
|
|
3642
|
+
for (let gi = vwStart; gi <= vwEnd; gi += 1) {
|
|
3643
|
+
groupGrowable[gi] = isGrowableVW;
|
|
3644
|
+
groupGlowing[gi] = isGlowingVW;
|
|
3645
|
+
vwFullText[gi] = combinedText;
|
|
3646
|
+
vwFullDuration[gi] = combinedDuration;
|
|
3647
|
+
vwCharOffset[gi] = charOff;
|
|
3648
|
+
vwStartMs[gi] = combinedStart;
|
|
3649
|
+
vwEndMs[gi] = combinedEnd;
|
|
3650
|
+
const grpText = wordGroups[gi].map(s => s.text).join('');
|
|
3651
|
+
charOff += grpText.replace(/\s/g, '').length;
|
|
3652
|
+
}
|
|
3653
|
+
|
|
3654
|
+
vwStart = vwEnd + 1;
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
return {
|
|
3658
|
+
wordGroups,
|
|
3659
|
+
groupGrowable,
|
|
3660
|
+
groupGlowing,
|
|
3661
|
+
vwFullText,
|
|
3662
|
+
vwFullDuration,
|
|
3663
|
+
vwCharOffset,
|
|
3664
|
+
vwStartMs,
|
|
3665
|
+
vwEndMs,
|
|
3666
|
+
};
|
|
3667
|
+
});
|
|
3668
|
+
}
|
|
3669
|
+
|
|
3432
3670
|
private _updateCharTimingData() {
|
|
3433
3671
|
if (!this.shadowRoot) return;
|
|
3434
3672
|
|
|
3673
|
+
this._rebuildDomCache();
|
|
3674
|
+
|
|
3435
3675
|
// Get the computed font from the first syllable to ensure accuracy
|
|
3436
3676
|
const referenceSyllable = this.shadowRoot.querySelector('.lyrics-syllable');
|
|
3437
3677
|
if (!referenceSyllable) return;
|
|
@@ -3586,6 +3826,15 @@ export class AmLyrics extends LitElement {
|
|
|
3586
3826
|
}
|
|
3587
3827
|
}
|
|
3588
3828
|
|
|
3829
|
+
private setUserScrolling(value: boolean) {
|
|
3830
|
+
this.isUserScrolling = value;
|
|
3831
|
+
if (value) {
|
|
3832
|
+
this.lyricsContainer?.classList.add('user-scrolling');
|
|
3833
|
+
} else {
|
|
3834
|
+
this.lyricsContainer?.classList.remove('user-scrolling');
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3589
3838
|
private handleUserScroll() {
|
|
3590
3839
|
// Ignore programmatic scrolls and click-seek scrolls
|
|
3591
3840
|
if (this.isProgrammaticScroll || this.isClickSeeking) {
|
|
@@ -3593,8 +3842,7 @@ export class AmLyrics extends LitElement {
|
|
|
3593
3842
|
}
|
|
3594
3843
|
|
|
3595
3844
|
// Mark that user is currently scrolling
|
|
3596
|
-
this.
|
|
3597
|
-
this.lyricsContainer?.classList.add('user-scrolling');
|
|
3845
|
+
this.setUserScrolling(true);
|
|
3598
3846
|
|
|
3599
3847
|
// Clear any existing timeout
|
|
3600
3848
|
if (this.userScrollTimeoutId) {
|
|
@@ -3603,7 +3851,7 @@ export class AmLyrics extends LitElement {
|
|
|
3603
3851
|
|
|
3604
3852
|
// Set timeout to re-enable auto-scroll after 2 seconds of no scrolling
|
|
3605
3853
|
this.userScrollTimeoutId = window.setTimeout(() => {
|
|
3606
|
-
this.
|
|
3854
|
+
this.setUserScrolling(false);
|
|
3607
3855
|
this.userScrollTimeoutId = undefined;
|
|
3608
3856
|
|
|
3609
3857
|
// Optionally scroll back to current active line when re-enabling auto-scroll
|
|
@@ -3614,20 +3862,16 @@ export class AmLyrics extends LitElement {
|
|
|
3614
3862
|
}
|
|
3615
3863
|
|
|
3616
3864
|
private findActiveLineIndices(time: number): number[] {
|
|
3617
|
-
if (!this.lyrics) return [];
|
|
3865
|
+
if (!this.lyrics || this.lyrics.length === 0) return [];
|
|
3618
3866
|
const activeLines: number[] = [];
|
|
3867
|
+
|
|
3619
3868
|
for (let i = 0; i < this.lyrics.length; i += 1) {
|
|
3620
3869
|
const line = this.lyrics[i];
|
|
3621
3870
|
let effectiveEndTime = line.endtime;
|
|
3622
3871
|
|
|
3623
|
-
// Extend the "active" highlight window to abut the next line,
|
|
3624
|
-
// leaving a 500ms gap for breathing/scrolling
|
|
3625
3872
|
if (i < this.lyrics.length - 1) {
|
|
3626
3873
|
const nextLineStart = this.lyrics[i + 1].timestamp;
|
|
3627
3874
|
const gapDuration = nextLineStart - line.endtime;
|
|
3628
|
-
|
|
3629
|
-
// If the gap is large enough to trigger the breathing dots,
|
|
3630
|
-
// DO NOT extend the highlight. The text should dim when the dots appear.
|
|
3631
3875
|
if (gapDuration < INSTRUMENTAL_THRESHOLD_MS) {
|
|
3632
3876
|
if (effectiveEndTime < nextLineStart) {
|
|
3633
3877
|
effectiveEndTime = Math.max(effectiveEndTime, nextLineStart - 500);
|
|
@@ -3635,6 +3879,7 @@ export class AmLyrics extends LitElement {
|
|
|
3635
3879
|
}
|
|
3636
3880
|
}
|
|
3637
3881
|
|
|
3882
|
+
if (line.timestamp > time) break;
|
|
3638
3883
|
if (time >= line.timestamp && time <= effectiveEndTime) {
|
|
3639
3884
|
activeLines.push(i);
|
|
3640
3885
|
}
|
|
@@ -3684,6 +3929,7 @@ export class AmLyrics extends LitElement {
|
|
|
3684
3929
|
gapStart: number;
|
|
3685
3930
|
gapEnd: number;
|
|
3686
3931
|
}> {
|
|
3932
|
+
if (this.cachedAllGaps.length > 0) return this.cachedAllGaps;
|
|
3687
3933
|
if (!this.lyrics || this.lyrics.length === 0) return [];
|
|
3688
3934
|
const gaps: Array<{
|
|
3689
3935
|
insertBeforeIndex: number;
|
|
@@ -3708,6 +3954,7 @@ export class AmLyrics extends LitElement {
|
|
|
3708
3954
|
}
|
|
3709
3955
|
}
|
|
3710
3956
|
|
|
3957
|
+
this.cachedAllGaps = gaps;
|
|
3711
3958
|
return gaps;
|
|
3712
3959
|
}
|
|
3713
3960
|
|
|
@@ -3897,7 +4144,7 @@ export class AmLyrics extends LitElement {
|
|
|
3897
4144
|
clearTimeout(this.userScrollTimeoutId);
|
|
3898
4145
|
this.userScrollTimeoutId = undefined;
|
|
3899
4146
|
}
|
|
3900
|
-
this.
|
|
4147
|
+
this.setUserScrolling(false);
|
|
3901
4148
|
|
|
3902
4149
|
// Reset active line tracking to prevent scroll fighting
|
|
3903
4150
|
this.currentPrimaryActiveLine = null;
|
|
@@ -4288,7 +4535,7 @@ export class AmLyrics extends LitElement {
|
|
|
4288
4535
|
|
|
4289
4536
|
this.lyricsContainer.classList.remove('not-focused', 'user-scrolling');
|
|
4290
4537
|
this.isProgrammaticScroll = true;
|
|
4291
|
-
this.
|
|
4538
|
+
this.setUserScrolling(false);
|
|
4292
4539
|
|
|
4293
4540
|
if (this.userScrollTimeoutId) {
|
|
4294
4541
|
clearTimeout(this.userScrollTimeoutId);
|
|
@@ -4888,10 +5135,7 @@ export class AmLyrics extends LitElement {
|
|
|
4888
5135
|
|
|
4889
5136
|
const sourceLabel = this.lyricsSource ?? 'Unavailable';
|
|
4890
5137
|
|
|
4891
|
-
const isUnsynced =
|
|
4892
|
-
this.lyrics && this.lyrics.length > 0
|
|
4893
|
-
? this.lyrics.every(l => l.timestamp === 0 && l.endtime === 0)
|
|
4894
|
-
: false;
|
|
5138
|
+
const isUnsynced = this.cachedIsUnsynced;
|
|
4895
5139
|
|
|
4896
5140
|
const renderContent = () => {
|
|
4897
5141
|
if (this.isLoading) {
|
|
@@ -4977,104 +5221,17 @@ export class AmLyrics extends LitElement {
|
|
|
4977
5221
|
// translation/romanization block for background — it would just duplicate
|
|
4978
5222
|
// the main line's text.
|
|
4979
5223
|
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
if (syllable.part && wordGroups.length > 0) {
|
|
4984
|
-
// Continuation of previous word
|
|
4985
|
-
wordGroups[wordGroups.length - 1].push(syllable);
|
|
4986
|
-
} else {
|
|
4987
|
-
// New word
|
|
4988
|
-
wordGroups.push([syllable]);
|
|
4989
|
-
}
|
|
4990
|
-
}
|
|
5224
|
+
const bgPlacement = hasBackground
|
|
5225
|
+
? AmLyrics.getBackgroundTextPlacement(line)
|
|
5226
|
+
: 'after';
|
|
4991
5227
|
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
const
|
|
4998
|
-
|
|
4999
|
-
);
|
|
5000
|
-
const groupGlowing: boolean[] = new Array(wordGroups.length).fill(
|
|
5001
|
-
false,
|
|
5002
|
-
);
|
|
5003
|
-
// Visual word info for growable char-level glow:
|
|
5004
|
-
// Each group stores the combined visual word's text, duration, and
|
|
5005
|
-
// the char offset of this group within the visual word.
|
|
5006
|
-
const vwFullText: string[] = new Array(wordGroups.length).fill('');
|
|
5007
|
-
const vwFullDuration: number[] = new Array(wordGroups.length).fill(0);
|
|
5008
|
-
const vwCharOffset: number[] = new Array(wordGroups.length).fill(0);
|
|
5009
|
-
const vwStartMs: number[] = new Array(wordGroups.length).fill(0);
|
|
5010
|
-
const vwEndMs: number[] = new Array(wordGroups.length).fill(0);
|
|
5011
|
-
{
|
|
5012
|
-
let vwStart = 0;
|
|
5013
|
-
while (vwStart < wordGroups.length) {
|
|
5014
|
-
let vwEnd = vwStart;
|
|
5015
|
-
while (vwEnd < wordGroups.length - 1) {
|
|
5016
|
-
const grp = wordGroups[vwEnd];
|
|
5017
|
-
const lastText = grp[grp.length - 1].text;
|
|
5018
|
-
if (/\s$/.test(lastText)) break;
|
|
5019
|
-
vwEnd += 1;
|
|
5020
|
-
}
|
|
5021
|
-
|
|
5022
|
-
// Compute combined properties for this visual word
|
|
5023
|
-
const combinedText = wordGroups
|
|
5024
|
-
.slice(vwStart, vwEnd + 1)
|
|
5025
|
-
.flatMap(g => g.map(s => s.text))
|
|
5026
|
-
.join('')
|
|
5027
|
-
.trim();
|
|
5028
|
-
const combinedStart = wordGroups[vwStart][0].timestamp;
|
|
5029
|
-
const lastGrp = wordGroups[vwEnd];
|
|
5030
|
-
const combinedEnd = lastGrp[lastGrp.length - 1].endtime;
|
|
5031
|
-
const combinedDuration = combinedEnd - combinedStart;
|
|
5032
|
-
|
|
5033
|
-
const isCJK =
|
|
5034
|
-
/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/.test(
|
|
5035
|
-
combinedText,
|
|
5036
|
-
);
|
|
5037
|
-
const isRTL =
|
|
5038
|
-
/[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\u0590-\u05FF]/.test(
|
|
5039
|
-
combinedText,
|
|
5040
|
-
);
|
|
5041
|
-
const hasHyphen = combinedText.includes('-');
|
|
5042
|
-
|
|
5043
|
-
const wordLen = combinedText.length;
|
|
5044
|
-
let isGrowableVW =
|
|
5045
|
-
!isCJK && !isRTL && !hasHyphen && wordLen > 0 && wordLen <= 12;
|
|
5046
|
-
if (isGrowableVW) {
|
|
5047
|
-
if (wordLen < 3) {
|
|
5048
|
-
isGrowableVW =
|
|
5049
|
-
combinedDuration >= 1110 && combinedDuration >= wordLen * 550;
|
|
5050
|
-
} else {
|
|
5051
|
-
isGrowableVW =
|
|
5052
|
-
combinedDuration >= 850 && combinedDuration >= wordLen * 200;
|
|
5053
|
-
}
|
|
5054
|
-
}
|
|
5055
|
-
|
|
5056
|
-
// Glow requirement (more strict)
|
|
5057
|
-
const isGlowingVW =
|
|
5058
|
-
isGrowableVW &&
|
|
5059
|
-
combinedDuration >= 1000 &&
|
|
5060
|
-
combinedDuration >= combinedText.length * 250;
|
|
5061
|
-
|
|
5062
|
-
let charOff = 0;
|
|
5063
|
-
for (let gi = vwStart; gi <= vwEnd; gi += 1) {
|
|
5064
|
-
groupGrowable[gi] = isGrowableVW;
|
|
5065
|
-
groupGlowing[gi] = isGlowingVW;
|
|
5066
|
-
vwFullText[gi] = combinedText;
|
|
5067
|
-
vwFullDuration[gi] = combinedDuration;
|
|
5068
|
-
vwCharOffset[gi] = charOff;
|
|
5069
|
-
vwStartMs[gi] = combinedStart;
|
|
5070
|
-
vwEndMs[gi] = combinedEnd;
|
|
5071
|
-
const grpText = wordGroups[gi].map(s => s.text).join('');
|
|
5072
|
-
charOff += grpText.replace(/\s/g, '').length;
|
|
5073
|
-
}
|
|
5074
|
-
|
|
5075
|
-
vwStart = vwEnd + 1;
|
|
5076
|
-
}
|
|
5077
|
-
}
|
|
5228
|
+
const lineData = this.cachedLineData?.[lineIndex];
|
|
5229
|
+
const wordGroups = lineData?.wordGroups ?? [];
|
|
5230
|
+
const groupGrowable = lineData?.groupGrowable ?? [];
|
|
5231
|
+
const groupGlowing = lineData?.groupGlowing ?? [];
|
|
5232
|
+
const vwFullText = lineData?.vwFullText ?? [];
|
|
5233
|
+
const vwFullDuration = lineData?.vwFullDuration ?? [];
|
|
5234
|
+
const vwCharOffset = lineData?.vwCharOffset ?? [];
|
|
5078
5235
|
|
|
5079
5236
|
// Create main vocals using YouLyPlus syllable structure
|
|
5080
5237
|
const mainVocalElement = html`<p class="main-vocal-container">
|
|
@@ -5338,7 +5495,9 @@ export class AmLyrics extends LitElement {
|
|
|
5338
5495
|
}}
|
|
5339
5496
|
>
|
|
5340
5497
|
<div class="lyrics-line-container">
|
|
5341
|
-
${
|
|
5498
|
+
${bgPlacement === 'before' ? backgroundVocalElement : ''}
|
|
5499
|
+
${mainVocalElement}
|
|
5500
|
+
${bgPlacement === 'after' ? backgroundVocalElement : ''}
|
|
5342
5501
|
${translationElement} ${lineRomanizationElement}
|
|
5343
5502
|
</div>
|
|
5344
5503
|
</div>
|
|
@@ -5350,9 +5509,7 @@ export class AmLyrics extends LitElement {
|
|
|
5350
5509
|
<div
|
|
5351
5510
|
class="lyrics-container ${isUnsynced
|
|
5352
5511
|
? 'is-unsynced'
|
|
5353
|
-
: 'blur-inactive-enabled'}
|
|
5354
|
-
? 'user-scrolling'
|
|
5355
|
-
: ''}"
|
|
5512
|
+
: 'blur-inactive-enabled'}"
|
|
5356
5513
|
>
|
|
5357
5514
|
${!this.isLoading && this.lyrics && this.lyrics.length > 0
|
|
5358
5515
|
? html`
|
|
@@ -5467,17 +5624,15 @@ export class AmLyrics extends LitElement {
|
|
|
5467
5624
|
!this.hasFetchedAllProviders
|
|
5468
5625
|
? html`
|
|
5469
5626
|
<button
|
|
5470
|
-
class="download-button"
|
|
5627
|
+
class="download-button source-switch-btn"
|
|
5471
5628
|
title="Switch Lyrics Source"
|
|
5472
5629
|
style="font-family: inherit; font-size: 11px; padding: 2px 6px; border-radius: 4px; border: 1px solid rgba(255, 255, 255, 0.2); background: transparent; cursor: pointer; color: #aaa; display: inline-flex; align-items: center;"
|
|
5473
5630
|
@click=${this.switchSource}
|
|
5474
5631
|
?disabled=${this.isFetchingAlternatives}
|
|
5475
5632
|
>
|
|
5476
5633
|
<svg
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
? 'animation: spin 1s linear infinite;'
|
|
5480
|
-
: ''}"
|
|
5634
|
+
class="source-switch-svg lucide lucide-arrow-down-up-icon lucide-arrow-down-up"
|
|
5635
|
+
style="margin-right: 4px;"
|
|
5481
5636
|
xmlns="http://www.w3.org/2000/svg"
|
|
5482
5637
|
width="12"
|
|
5483
5638
|
height="12"
|
|
@@ -5487,7 +5642,6 @@ export class AmLyrics extends LitElement {
|
|
|
5487
5642
|
stroke-width="2"
|
|
5488
5643
|
stroke-linecap="round"
|
|
5489
5644
|
stroke-linejoin="round"
|
|
5490
|
-
class="lucide lucide-arrow-down-up-icon lucide-arrow-down-up"
|
|
5491
5645
|
>
|
|
5492
5646
|
${this.isFetchingAlternatives
|
|
5493
5647
|
? svg`<path
|
|
@@ -5498,9 +5652,11 @@ export class AmLyrics extends LitElement {
|
|
|
5498
5652
|
><path d="m21 8-4-4-4 4"></path
|
|
5499
5653
|
><path d="M17 4v16"></path>`}
|
|
5500
5654
|
</svg>
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5655
|
+
<span class="source-switch-label"
|
|
5656
|
+
>${this.isFetchingAlternatives
|
|
5657
|
+
? 'Switching...'
|
|
5658
|
+
: 'Switch'}</span
|
|
5659
|
+
>
|
|
5504
5660
|
</button>
|
|
5505
5661
|
`
|
|
5506
5662
|
: ''}
|