@uimaxbai/am-lyrics 1.2.6 → 1.2.8
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 +323 -241
- package/dist/src/am-lyrics.js.map +1 -1
- package/dist/src/react.js +323 -241
- package/dist/src/react.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/fix.cjs +7 -0
- package/package.json +1 -1
- package/patch.diff +210 -0
- package/patch2.diff +26 -0
- package/src/AmLyrics.ts +393 -298
package/dist/src/am-lyrics.js
CHANGED
|
@@ -319,17 +319,17 @@ class GoogleService {
|
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
-
const VERSION = '1.2.
|
|
322
|
+
const VERSION = '1.2.8';
|
|
323
323
|
const INSTRUMENTAL_THRESHOLD_MS = 7000; // Show dots for gaps >= 7s
|
|
324
324
|
const FETCH_TIMEOUT_MS = 8000; // Timeout for all lyrics fetch requests
|
|
325
325
|
const SEEK_THRESHOLD_MS = 500;
|
|
326
326
|
const PRE_SCROLL_LEAD_MS = 500;
|
|
327
|
-
const PRE_SCROLL_LEAD_SHORT_MS =
|
|
327
|
+
const PRE_SCROLL_LEAD_SHORT_MS = 350;
|
|
328
328
|
const SCROLL_ANIMATION_DURATION_MS = 280;
|
|
329
329
|
const SCROLL_DELAY_INCREMENT_MS = 24;
|
|
330
330
|
const GAP_PULSE_DURATION_MS = 4000;
|
|
331
331
|
const GAP_PULSE_CYCLE_MS = GAP_PULSE_DURATION_MS * 2;
|
|
332
|
-
const GAP_EXIT_LEAD_MS =
|
|
332
|
+
const GAP_EXIT_LEAD_MS = 600;
|
|
333
333
|
const GAP_MIN_SCALE = 0.85;
|
|
334
334
|
/**
|
|
335
335
|
* Fetch with an automatic timeout via AbortSignal.
|
|
@@ -392,6 +392,15 @@ class AmLyrics extends i {
|
|
|
392
392
|
this.isClickSeeking = false;
|
|
393
393
|
// Cached DOM elements for animation updates
|
|
394
394
|
this.cachedLyricsLines = [];
|
|
395
|
+
// Cached line and gap element maps for fast lookup
|
|
396
|
+
this.lineElementCache = new Map();
|
|
397
|
+
this.gapElementCache = new Map();
|
|
398
|
+
// Cached gap computation results
|
|
399
|
+
this.cachedAllGaps = [];
|
|
400
|
+
// Cached isUnsynced flag
|
|
401
|
+
this.cachedIsUnsynced = false;
|
|
402
|
+
// Cached pre-computed line data for render
|
|
403
|
+
this.cachedLineData = null;
|
|
395
404
|
// Active line tracking
|
|
396
405
|
this.activeLineIds = new Set();
|
|
397
406
|
this.currentPrimaryActiveLine = null;
|
|
@@ -483,6 +492,25 @@ class AmLyrics extends i {
|
|
|
483
492
|
get currentTime() {
|
|
484
493
|
return this._currentTime;
|
|
485
494
|
}
|
|
495
|
+
_updateFooter() {
|
|
496
|
+
const footer = this.shadowRoot?.querySelector('.lyrics-footer');
|
|
497
|
+
if (!footer)
|
|
498
|
+
return;
|
|
499
|
+
const switchBtn = footer.querySelector('.source-switch-btn');
|
|
500
|
+
const svgEl = footer.querySelector('.source-switch-svg');
|
|
501
|
+
const labelEl = footer.querySelector('.source-switch-label');
|
|
502
|
+
if (switchBtn) {
|
|
503
|
+
switchBtn.disabled = this.isFetchingAlternatives;
|
|
504
|
+
}
|
|
505
|
+
if (svgEl) {
|
|
506
|
+
svgEl.setAttribute('style', `margin-right: 4px; ${this.isFetchingAlternatives ? 'animation: spin 1s linear infinite;' : ''}`);
|
|
507
|
+
}
|
|
508
|
+
if (labelEl) {
|
|
509
|
+
labelEl.textContent = this.isFetchingAlternatives
|
|
510
|
+
? 'Switching...'
|
|
511
|
+
: 'Switch';
|
|
512
|
+
}
|
|
513
|
+
}
|
|
486
514
|
connectedCallback() {
|
|
487
515
|
super.connectedCallback();
|
|
488
516
|
this.fetchLyrics();
|
|
@@ -530,6 +558,7 @@ class AmLyrics extends i {
|
|
|
530
558
|
this.currentSourceIndex = 0;
|
|
531
559
|
this.isFetchingAlternatives = false;
|
|
532
560
|
this.hasFetchedAllProviders = false;
|
|
561
|
+
this._updateFooter();
|
|
533
562
|
try {
|
|
534
563
|
const resolvedMetadata = await this.resolveSongMetadata();
|
|
535
564
|
// If a newer fetch was triggered while we awaited, bail out
|
|
@@ -582,6 +611,7 @@ class AmLyrics extends i {
|
|
|
582
611
|
collectedSources.some(s => s.source === 'LRCLIB' ||
|
|
583
612
|
s.source === 'Tidal' ||
|
|
584
613
|
s.source === 'Genius');
|
|
614
|
+
this._updateFooter();
|
|
585
615
|
if (collectedSources.length > 0) {
|
|
586
616
|
this.availableSources = AmLyrics.mergeAndSortSources(collectedSources);
|
|
587
617
|
this.currentSourceIndex = 0;
|
|
@@ -692,6 +722,7 @@ class AmLyrics extends i {
|
|
|
692
722
|
return;
|
|
693
723
|
if (!this.hasFetchedAllProviders) {
|
|
694
724
|
this.isFetchingAlternatives = true;
|
|
725
|
+
this._updateFooter();
|
|
695
726
|
try {
|
|
696
727
|
const resolvedMetadata = await this.resolveSongMetadata();
|
|
697
728
|
if (resolvedMetadata?.metadata) {
|
|
@@ -731,6 +762,7 @@ class AmLyrics extends i {
|
|
|
731
762
|
finally {
|
|
732
763
|
this.hasFetchedAllProviders = true;
|
|
733
764
|
this.isFetchingAlternatives = false;
|
|
765
|
+
this._updateFooter();
|
|
734
766
|
}
|
|
735
767
|
}
|
|
736
768
|
if (this.availableSources.length > 1) {
|
|
@@ -1783,13 +1815,11 @@ class AmLyrics extends i {
|
|
|
1783
1815
|
// Reset animation if active lines change or if we skip time.
|
|
1784
1816
|
const linesChanged = !AmLyrics.arraysEqual(newActiveLines, oldActiveLines);
|
|
1785
1817
|
if (linesChanged || isSeek) {
|
|
1786
|
-
// Imperatively manage 'active' class so that scroll-animate and other
|
|
1787
|
-
// imperative classes are never clobbered.
|
|
1788
1818
|
if (this.lyricsContainer) {
|
|
1789
1819
|
// Remove 'active' from lines that are no longer active
|
|
1790
1820
|
for (const lineIndex of oldActiveLines) {
|
|
1791
1821
|
if (!newActiveLines.includes(lineIndex)) {
|
|
1792
|
-
const lineElement = this.
|
|
1822
|
+
const lineElement = this._getLineElement(lineIndex);
|
|
1793
1823
|
if (lineElement) {
|
|
1794
1824
|
lineElement.classList.remove('active');
|
|
1795
1825
|
AmLyrics.resetSyllables(lineElement);
|
|
@@ -1799,10 +1829,10 @@ class AmLyrics extends i {
|
|
|
1799
1829
|
// Add 'active' to newly active lines
|
|
1800
1830
|
for (const lineIndex of newActiveLines) {
|
|
1801
1831
|
if (!oldActiveLines.includes(lineIndex)) {
|
|
1802
|
-
const lineElement = this.
|
|
1832
|
+
const lineElement = this._getLineElement(lineIndex);
|
|
1803
1833
|
if (lineElement) {
|
|
1804
1834
|
lineElement.classList.add('active');
|
|
1805
|
-
lineElement.classList.remove('pre-active');
|
|
1835
|
+
lineElement.classList.remove('pre-active');
|
|
1806
1836
|
}
|
|
1807
1837
|
}
|
|
1808
1838
|
}
|
|
@@ -1811,14 +1841,12 @@ class AmLyrics extends i {
|
|
|
1811
1841
|
}
|
|
1812
1842
|
}
|
|
1813
1843
|
this.startAnimationFromTime(newTime);
|
|
1814
|
-
// Trigger scroll imperatively (was previously in updated() via @state)
|
|
1815
1844
|
this._handleActiveLineScroll(oldActiveLines, isSeek);
|
|
1816
1845
|
}
|
|
1817
|
-
// YouLyPlus-style syllable animation updates
|
|
1818
1846
|
if (this.lyricsContainer) {
|
|
1819
|
-
// Update syllables in active lines
|
|
1847
|
+
// Update syllables in active lines using cached elements
|
|
1820
1848
|
for (const lineIndex of this.activeLineIndices) {
|
|
1821
|
-
const lineElement = this.
|
|
1849
|
+
const lineElement = this._getLineElement(lineIndex);
|
|
1822
1850
|
if (lineElement) {
|
|
1823
1851
|
AmLyrics.updateSyllablesForLine(lineElement, newTime);
|
|
1824
1852
|
}
|
|
@@ -1828,60 +1856,81 @@ class AmLyrics extends i {
|
|
|
1828
1856
|
activeGaps.forEach(gapLine => {
|
|
1829
1857
|
AmLyrics.updateSyllablesForLine(gapLine, newTime);
|
|
1830
1858
|
});
|
|
1831
|
-
// Imperatively manage gap active state
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
// Also ensure the highlight + animation fired so CSS state is correct
|
|
1856
|
-
if (!dot.classList.contains('highlight')) {
|
|
1859
|
+
// Imperatively manage gap active state
|
|
1860
|
+
if (this.gapElementCache.size > 0) {
|
|
1861
|
+
for (const [, gap] of this.gapElementCache) {
|
|
1862
|
+
const gapStartTime = parseFloat(gap.getAttribute('data-start-time') || '0');
|
|
1863
|
+
const gapEndTime = parseFloat(gap.getAttribute('data-end-time') || '0');
|
|
1864
|
+
const shouldBeActive = newTime >= gapStartTime && newTime < gapEndTime;
|
|
1865
|
+
const isActive = gap.classList.contains('active');
|
|
1866
|
+
const isExiting = gap.classList.contains('gap-exiting');
|
|
1867
|
+
const exitLeadMs = GAP_EXIT_LEAD_MS;
|
|
1868
|
+
const shouldStartExiting = isActive && !isExiting && newTime >= gapEndTime - exitLeadMs;
|
|
1869
|
+
if (shouldBeActive && !isActive && !isExiting) {
|
|
1870
|
+
gap.classList.remove('gap-exiting');
|
|
1871
|
+
gap.classList.add('active');
|
|
1872
|
+
const dotSyllables = gap.querySelectorAll('.lyrics-syllable');
|
|
1873
|
+
dotSyllables.forEach(dot => {
|
|
1874
|
+
const dotStart = parseFloat(dot.getAttribute('data-start-time') || '0');
|
|
1875
|
+
const dotEnd = parseFloat(dot.getAttribute('data-end-time') || '0');
|
|
1876
|
+
if (newTime > dotEnd) {
|
|
1877
|
+
dot.classList.add('finished');
|
|
1878
|
+
if (!dot.classList.contains('highlight')) {
|
|
1879
|
+
AmLyrics.updateSyllableAnimation(dot);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
else if (newTime >= dotStart && newTime <= dotEnd) {
|
|
1857
1883
|
AmLyrics.updateSyllableAnimation(dot);
|
|
1858
1884
|
}
|
|
1859
|
-
}
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
// After exit animation completes, remove gap-exiting to collapse
|
|
1871
|
-
setTimeout(() => {
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
else if (shouldStartExiting) {
|
|
1888
|
+
gap.classList.add('gap-exiting');
|
|
1889
|
+
gap.classList.remove('active');
|
|
1890
|
+
setTimeout(() => {
|
|
1891
|
+
gap.classList.remove('gap-exiting');
|
|
1892
|
+
}, GAP_EXIT_LEAD_MS);
|
|
1893
|
+
}
|
|
1894
|
+
else if (isActive && !shouldBeActive) {
|
|
1895
|
+
gap.classList.remove('active');
|
|
1872
1896
|
gap.classList.remove('gap-exiting');
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
gap.classList.remove('active');
|
|
1878
|
-
gap.classList.remove('gap-exiting');
|
|
1879
|
-
}
|
|
1880
|
-
else if (isExiting && newTime < gapEndTime - exitLeadMs) {
|
|
1881
|
-
// NEW: Cleanup exiting state if we seeked backwards before exit window
|
|
1882
|
-
gap.classList.remove('gap-exiting');
|
|
1897
|
+
}
|
|
1898
|
+
else if (isExiting && newTime < gapEndTime - exitLeadMs) {
|
|
1899
|
+
gap.classList.remove('gap-exiting');
|
|
1900
|
+
}
|
|
1883
1901
|
}
|
|
1884
|
-
}
|
|
1902
|
+
}
|
|
1903
|
+
else if (this.lyricsContainer) {
|
|
1904
|
+
// Fallback: no cache yet, use querySelectorAll
|
|
1905
|
+
const allGaps = this.lyricsContainer.querySelectorAll('.lyrics-gap');
|
|
1906
|
+
allGaps.forEach(gap => {
|
|
1907
|
+
const gapStartTime = parseFloat(gap.getAttribute('data-start-time') || '0');
|
|
1908
|
+
const gapEndTime = parseFloat(gap.getAttribute('data-end-time') || '0');
|
|
1909
|
+
const shouldBeActive = newTime >= gapStartTime && newTime < gapEndTime;
|
|
1910
|
+
const isActive = gap.classList.contains('active');
|
|
1911
|
+
const isExiting = gap.classList.contains('gap-exiting');
|
|
1912
|
+
const exitLeadMs = GAP_EXIT_LEAD_MS;
|
|
1913
|
+
const shouldStartExiting = isActive && !isExiting && newTime >= gapEndTime - exitLeadMs;
|
|
1914
|
+
if (shouldBeActive && !isActive && !isExiting) {
|
|
1915
|
+
gap.classList.remove('gap-exiting');
|
|
1916
|
+
gap.classList.add('active');
|
|
1917
|
+
}
|
|
1918
|
+
else if (shouldStartExiting) {
|
|
1919
|
+
gap.classList.add('gap-exiting');
|
|
1920
|
+
gap.classList.remove('active');
|
|
1921
|
+
setTimeout(() => {
|
|
1922
|
+
gap.classList.remove('gap-exiting');
|
|
1923
|
+
}, GAP_EXIT_LEAD_MS);
|
|
1924
|
+
}
|
|
1925
|
+
else if (isActive && !shouldBeActive) {
|
|
1926
|
+
gap.classList.remove('active');
|
|
1927
|
+
gap.classList.remove('gap-exiting');
|
|
1928
|
+
}
|
|
1929
|
+
else if (isExiting && newTime < gapEndTime - exitLeadMs) {
|
|
1930
|
+
gap.classList.remove('gap-exiting');
|
|
1931
|
+
}
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1885
1934
|
// Track instrumental gap state
|
|
1886
1935
|
const currentGap = this.findInstrumentalGapAt(newTime);
|
|
1887
1936
|
if (currentGap) {
|
|
@@ -1899,25 +1948,23 @@ class AmLyrics extends i {
|
|
|
1899
1948
|
for (let i = 0; i < this.lyrics.length; i += 1) {
|
|
1900
1949
|
const line = this.lyrics[i];
|
|
1901
1950
|
const timeUntilStart = line.timestamp - newTime;
|
|
1902
|
-
const nextLineEl = this.
|
|
1951
|
+
const nextLineEl = this._getLineElement(i);
|
|
1903
1952
|
const isBackToBack = this.activeLineIndices.length > 0;
|
|
1904
1953
|
const leadTime = isBackToBack
|
|
1905
1954
|
? PRE_SCROLL_LEAD_SHORT_MS
|
|
1906
1955
|
: PRE_SCROLL_LEAD_MS;
|
|
1907
1956
|
if (timeUntilStart > leadTime) {
|
|
1908
|
-
break;
|
|
1957
|
+
break;
|
|
1909
1958
|
}
|
|
1910
1959
|
if (timeUntilStart > 0 && timeUntilStart <= leadTime) {
|
|
1911
|
-
// Time to pre-scroll and pre-activate!
|
|
1912
1960
|
if (nextLineEl) {
|
|
1913
1961
|
preActiveLineIndex = i;
|
|
1914
1962
|
if (!isBackToBack) {
|
|
1915
|
-
// Apply unblur & zoom effect ahead of lyric start only if no line is currently active
|
|
1916
1963
|
nextLineEl.classList.add('pre-active');
|
|
1917
1964
|
}
|
|
1918
1965
|
this.clearPreActiveClasses(i);
|
|
1919
1966
|
const slowScrollDuration = Math.max(SCROLL_ANIMATION_DURATION_MS, timeUntilStart);
|
|
1920
|
-
this.focusLine(nextLineEl, false, slowScrollDuration
|
|
1967
|
+
this.focusLine(nextLineEl, false, isBackToBack ? 500 : slowScrollDuration);
|
|
1921
1968
|
}
|
|
1922
1969
|
break;
|
|
1923
1970
|
}
|
|
@@ -1928,6 +1975,9 @@ class AmLyrics extends i {
|
|
|
1928
1975
|
}
|
|
1929
1976
|
updated(changedProperties) {
|
|
1930
1977
|
if (changedProperties.has('lyrics')) {
|
|
1978
|
+
this._invalidateCaches();
|
|
1979
|
+
this._ensureLineDataCache();
|
|
1980
|
+
this._updateCachedIsUnsynced();
|
|
1931
1981
|
// Recalculate timing data for accurate animations whenever lyrics change
|
|
1932
1982
|
this._updateCharTimingData();
|
|
1933
1983
|
// Apply 'active' classes imperatively after lyrics first render,
|
|
@@ -1936,7 +1986,7 @@ class AmLyrics extends i {
|
|
|
1936
1986
|
if (this.lyricsContainer && this.lyrics) {
|
|
1937
1987
|
const activeLines = this.findActiveLineIndices(this.currentTime);
|
|
1938
1988
|
for (const lineIndex of activeLines) {
|
|
1939
|
-
const lineEl = this.
|
|
1989
|
+
const lineEl = this._getLineElement(lineIndex);
|
|
1940
1990
|
if (lineEl)
|
|
1941
1991
|
lineEl.classList.add('active');
|
|
1942
1992
|
}
|
|
@@ -1952,7 +2002,7 @@ class AmLyrics extends i {
|
|
|
1952
2002
|
this.backgroundWordProgress.clear();
|
|
1953
2003
|
this.mainWordAnimations.clear();
|
|
1954
2004
|
this.backgroundWordAnimations.clear();
|
|
1955
|
-
this.
|
|
2005
|
+
this.setUserScrolling(false);
|
|
1956
2006
|
// Cancel any running animations
|
|
1957
2007
|
if (this.animationFrameId) {
|
|
1958
2008
|
cancelAnimationFrame(this.animationFrameId);
|
|
@@ -1992,7 +2042,7 @@ class AmLyrics extends i {
|
|
|
1992
2042
|
const targetLineIndex = this.getPrimaryActiveLineIndex(this.activeLineIndices);
|
|
1993
2043
|
if (targetLineIndex === null)
|
|
1994
2044
|
return;
|
|
1995
|
-
const targetLine = this.
|
|
2045
|
+
const targetLine = this._getLineElement(targetLineIndex);
|
|
1996
2046
|
if (targetLine) {
|
|
1997
2047
|
this.focusLine(targetLine, forceScroll);
|
|
1998
2048
|
}
|
|
@@ -2010,9 +2060,144 @@ class AmLyrics extends i {
|
|
|
2010
2060
|
}
|
|
2011
2061
|
return 0;
|
|
2012
2062
|
}
|
|
2063
|
+
_rebuildDomCache() {
|
|
2064
|
+
if (!this.lyricsContainer)
|
|
2065
|
+
return;
|
|
2066
|
+
this.lineElementCache.clear();
|
|
2067
|
+
this.gapElementCache.clear();
|
|
2068
|
+
if (!this.lyrics)
|
|
2069
|
+
return;
|
|
2070
|
+
for (let i = 0; i < this.lyrics.length; i += 1) {
|
|
2071
|
+
const lineEl = this.lyricsContainer.querySelector(`#lyrics-line-${i}`);
|
|
2072
|
+
if (lineEl)
|
|
2073
|
+
this.lineElementCache.set(i, lineEl);
|
|
2074
|
+
const gapEl = this.lyricsContainer.querySelector(`#gap-${i}`);
|
|
2075
|
+
if (gapEl)
|
|
2076
|
+
this.gapElementCache.set(i, gapEl);
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
_getLineElement(index) {
|
|
2080
|
+
const cached = this.lineElementCache.get(index);
|
|
2081
|
+
if (cached)
|
|
2082
|
+
return cached;
|
|
2083
|
+
if (!this.lyricsContainer)
|
|
2084
|
+
return null;
|
|
2085
|
+
const el = this.lyricsContainer.querySelector(`#lyrics-line-${index}`);
|
|
2086
|
+
if (el)
|
|
2087
|
+
this.lineElementCache.set(index, el);
|
|
2088
|
+
return el;
|
|
2089
|
+
}
|
|
2090
|
+
_getGapElement(index) {
|
|
2091
|
+
const cached = this.gapElementCache.get(index);
|
|
2092
|
+
if (cached)
|
|
2093
|
+
return cached;
|
|
2094
|
+
if (!this.lyricsContainer)
|
|
2095
|
+
return null;
|
|
2096
|
+
const el = this.lyricsContainer.querySelector(`#gap-${index}`);
|
|
2097
|
+
if (el)
|
|
2098
|
+
this.gapElementCache.set(index, el);
|
|
2099
|
+
return el;
|
|
2100
|
+
}
|
|
2101
|
+
_invalidateCaches() {
|
|
2102
|
+
this.cachedAllGaps = [];
|
|
2103
|
+
this.cachedIsUnsynced = false;
|
|
2104
|
+
this.cachedLineData = null;
|
|
2105
|
+
this.lineElementCache.clear();
|
|
2106
|
+
this.gapElementCache.clear();
|
|
2107
|
+
}
|
|
2108
|
+
_updateCachedIsUnsynced() {
|
|
2109
|
+
this.cachedIsUnsynced =
|
|
2110
|
+
this.lyrics && this.lyrics.length > 0
|
|
2111
|
+
? this.lyrics.every(l => l.timestamp === 0 && l.endtime === 0)
|
|
2112
|
+
: false;
|
|
2113
|
+
}
|
|
2114
|
+
_ensureLineDataCache() {
|
|
2115
|
+
if (this.cachedLineData || !this.lyrics)
|
|
2116
|
+
return;
|
|
2117
|
+
this.cachedLineData = this.lyrics.map(line => {
|
|
2118
|
+
const wordGroups = [];
|
|
2119
|
+
for (const syllable of line.text) {
|
|
2120
|
+
if (syllable.part && wordGroups.length > 0) {
|
|
2121
|
+
wordGroups[wordGroups.length - 1].push(syllable);
|
|
2122
|
+
}
|
|
2123
|
+
else {
|
|
2124
|
+
wordGroups.push([syllable]);
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
const groupGrowable = new Array(wordGroups.length).fill(false);
|
|
2128
|
+
const groupGlowing = new Array(wordGroups.length).fill(false);
|
|
2129
|
+
const vwFullText = new Array(wordGroups.length).fill('');
|
|
2130
|
+
const vwFullDuration = new Array(wordGroups.length).fill(0);
|
|
2131
|
+
const vwCharOffset = new Array(wordGroups.length).fill(0);
|
|
2132
|
+
const vwStartMs = new Array(wordGroups.length).fill(0);
|
|
2133
|
+
const vwEndMs = new Array(wordGroups.length).fill(0);
|
|
2134
|
+
let vwStart = 0;
|
|
2135
|
+
while (vwStart < wordGroups.length) {
|
|
2136
|
+
let vwEnd = vwStart;
|
|
2137
|
+
while (vwEnd < wordGroups.length - 1) {
|
|
2138
|
+
const grp = wordGroups[vwEnd];
|
|
2139
|
+
const lastText = grp[grp.length - 1].text;
|
|
2140
|
+
if (/\s$/.test(lastText))
|
|
2141
|
+
break;
|
|
2142
|
+
vwEnd += 1;
|
|
2143
|
+
}
|
|
2144
|
+
const combinedText = wordGroups
|
|
2145
|
+
.slice(vwStart, vwEnd + 1)
|
|
2146
|
+
.flatMap(g => g.map(s => s.text))
|
|
2147
|
+
.join('')
|
|
2148
|
+
.trim();
|
|
2149
|
+
const combinedStart = wordGroups[vwStart][0].timestamp;
|
|
2150
|
+
const lastGrp = wordGroups[vwEnd];
|
|
2151
|
+
const combinedEnd = lastGrp[lastGrp.length - 1].endtime;
|
|
2152
|
+
const combinedDuration = combinedEnd - combinedStart;
|
|
2153
|
+
const isCJK = /[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/.test(combinedText);
|
|
2154
|
+
const isRTL = /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\u0590-\u05FF]/.test(combinedText);
|
|
2155
|
+
const hasHyphen = combinedText.includes('-');
|
|
2156
|
+
const wordLen = combinedText.length;
|
|
2157
|
+
let isGrowableVW = !isCJK && !isRTL && !hasHyphen && wordLen > 0 && wordLen <= 12;
|
|
2158
|
+
if (isGrowableVW) {
|
|
2159
|
+
if (wordLen < 3) {
|
|
2160
|
+
isGrowableVW =
|
|
2161
|
+
combinedDuration >= 1110 && combinedDuration >= wordLen * 550;
|
|
2162
|
+
}
|
|
2163
|
+
else {
|
|
2164
|
+
isGrowableVW =
|
|
2165
|
+
combinedDuration >= 850 && combinedDuration >= wordLen * 200;
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
const isGlowingVW = isGrowableVW &&
|
|
2169
|
+
combinedDuration >= 1000 &&
|
|
2170
|
+
combinedDuration >= combinedText.length * 250;
|
|
2171
|
+
let charOff = 0;
|
|
2172
|
+
for (let gi = vwStart; gi <= vwEnd; gi += 1) {
|
|
2173
|
+
groupGrowable[gi] = isGrowableVW;
|
|
2174
|
+
groupGlowing[gi] = isGlowingVW;
|
|
2175
|
+
vwFullText[gi] = combinedText;
|
|
2176
|
+
vwFullDuration[gi] = combinedDuration;
|
|
2177
|
+
vwCharOffset[gi] = charOff;
|
|
2178
|
+
vwStartMs[gi] = combinedStart;
|
|
2179
|
+
vwEndMs[gi] = combinedEnd;
|
|
2180
|
+
const grpText = wordGroups[gi].map(s => s.text).join('');
|
|
2181
|
+
charOff += grpText.replace(/\s/g, '').length;
|
|
2182
|
+
}
|
|
2183
|
+
vwStart = vwEnd + 1;
|
|
2184
|
+
}
|
|
2185
|
+
return {
|
|
2186
|
+
wordGroups,
|
|
2187
|
+
groupGrowable,
|
|
2188
|
+
groupGlowing,
|
|
2189
|
+
vwFullText,
|
|
2190
|
+
vwFullDuration,
|
|
2191
|
+
vwCharOffset,
|
|
2192
|
+
vwStartMs,
|
|
2193
|
+
vwEndMs,
|
|
2194
|
+
};
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2013
2197
|
_updateCharTimingData() {
|
|
2014
2198
|
if (!this.shadowRoot)
|
|
2015
2199
|
return;
|
|
2200
|
+
this._rebuildDomCache();
|
|
2016
2201
|
// Get the computed font from the first syllable to ensure accuracy
|
|
2017
2202
|
const referenceSyllable = this.shadowRoot.querySelector('.lyrics-syllable');
|
|
2018
2203
|
if (!referenceSyllable)
|
|
@@ -2125,21 +2310,29 @@ class AmLyrics extends i {
|
|
|
2125
2310
|
this.scrollToActiveLineYouLy(lineElement, forceScroll, scrollDuration);
|
|
2126
2311
|
}
|
|
2127
2312
|
}
|
|
2313
|
+
setUserScrolling(value) {
|
|
2314
|
+
this.isUserScrolling = value;
|
|
2315
|
+
if (value) {
|
|
2316
|
+
this.lyricsContainer?.classList.add('user-scrolling');
|
|
2317
|
+
}
|
|
2318
|
+
else {
|
|
2319
|
+
this.lyricsContainer?.classList.remove('user-scrolling');
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2128
2322
|
handleUserScroll() {
|
|
2129
2323
|
// Ignore programmatic scrolls and click-seek scrolls
|
|
2130
2324
|
if (this.isProgrammaticScroll || this.isClickSeeking) {
|
|
2131
2325
|
return;
|
|
2132
2326
|
}
|
|
2133
2327
|
// Mark that user is currently scrolling
|
|
2134
|
-
this.
|
|
2135
|
-
this.lyricsContainer?.classList.add('user-scrolling');
|
|
2328
|
+
this.setUserScrolling(true);
|
|
2136
2329
|
// Clear any existing timeout
|
|
2137
2330
|
if (this.userScrollTimeoutId) {
|
|
2138
2331
|
clearTimeout(this.userScrollTimeoutId);
|
|
2139
2332
|
}
|
|
2140
2333
|
// Set timeout to re-enable auto-scroll after 2 seconds of no scrolling
|
|
2141
2334
|
this.userScrollTimeoutId = window.setTimeout(() => {
|
|
2142
|
-
this.
|
|
2335
|
+
this.setUserScrolling(false);
|
|
2143
2336
|
this.userScrollTimeoutId = undefined;
|
|
2144
2337
|
// Optionally scroll back to current active line when re-enabling auto-scroll
|
|
2145
2338
|
if (this.activeLineIndices.length > 0) {
|
|
@@ -2148,25 +2341,23 @@ class AmLyrics extends i {
|
|
|
2148
2341
|
}, 2000);
|
|
2149
2342
|
}
|
|
2150
2343
|
findActiveLineIndices(time) {
|
|
2151
|
-
if (!this.lyrics)
|
|
2344
|
+
if (!this.lyrics || this.lyrics.length === 0)
|
|
2152
2345
|
return [];
|
|
2153
2346
|
const activeLines = [];
|
|
2154
2347
|
for (let i = 0; i < this.lyrics.length; i += 1) {
|
|
2155
2348
|
const line = this.lyrics[i];
|
|
2156
2349
|
let effectiveEndTime = line.endtime;
|
|
2157
|
-
// Extend the "active" highlight window to abut the next line,
|
|
2158
|
-
// leaving a 500ms gap for breathing/scrolling
|
|
2159
2350
|
if (i < this.lyrics.length - 1) {
|
|
2160
2351
|
const nextLineStart = this.lyrics[i + 1].timestamp;
|
|
2161
2352
|
const gapDuration = nextLineStart - line.endtime;
|
|
2162
|
-
// If the gap is large enough to trigger the breathing dots,
|
|
2163
|
-
// DO NOT extend the highlight. The text should dim when the dots appear.
|
|
2164
2353
|
if (gapDuration < INSTRUMENTAL_THRESHOLD_MS) {
|
|
2165
2354
|
if (effectiveEndTime < nextLineStart) {
|
|
2166
2355
|
effectiveEndTime = Math.max(effectiveEndTime, nextLineStart - 500);
|
|
2167
2356
|
}
|
|
2168
2357
|
}
|
|
2169
2358
|
}
|
|
2359
|
+
if (line.timestamp > time)
|
|
2360
|
+
break;
|
|
2170
2361
|
if (time >= line.timestamp && time <= effectiveEndTime) {
|
|
2171
2362
|
activeLines.push(i);
|
|
2172
2363
|
}
|
|
@@ -2206,6 +2397,8 @@ class AmLyrics extends i {
|
|
|
2206
2397
|
* Used by the template to always render gap elements in the DOM.
|
|
2207
2398
|
*/
|
|
2208
2399
|
findAllInstrumentalGaps() {
|
|
2400
|
+
if (this.cachedAllGaps.length > 0)
|
|
2401
|
+
return this.cachedAllGaps;
|
|
2209
2402
|
if (!this.lyrics || this.lyrics.length === 0)
|
|
2210
2403
|
return [];
|
|
2211
2404
|
const gaps = [];
|
|
@@ -2224,6 +2417,7 @@ class AmLyrics extends i {
|
|
|
2224
2417
|
gaps.push({ insertBeforeIndex: i + 1, gapStart, gapEnd });
|
|
2225
2418
|
}
|
|
2226
2419
|
}
|
|
2420
|
+
this.cachedAllGaps = gaps;
|
|
2227
2421
|
return gaps;
|
|
2228
2422
|
}
|
|
2229
2423
|
startAnimationFromTime(time) {
|
|
@@ -2387,7 +2581,7 @@ class AmLyrics extends i {
|
|
|
2387
2581
|
clearTimeout(this.userScrollTimeoutId);
|
|
2388
2582
|
this.userScrollTimeoutId = undefined;
|
|
2389
2583
|
}
|
|
2390
|
-
this.
|
|
2584
|
+
this.setUserScrolling(false);
|
|
2391
2585
|
// Reset active line tracking to prevent scroll fighting
|
|
2392
2586
|
this.currentPrimaryActiveLine = null;
|
|
2393
2587
|
this.lastPrimaryActiveLine = null;
|
|
@@ -2686,7 +2880,7 @@ class AmLyrics extends i {
|
|
|
2686
2880
|
}
|
|
2687
2881
|
this.lyricsContainer.classList.remove('not-focused', 'user-scrolling');
|
|
2688
2882
|
this.isProgrammaticScroll = true;
|
|
2689
|
-
this.
|
|
2883
|
+
this.setUserScrolling(false);
|
|
2690
2884
|
if (this.userScrollTimeoutId) {
|
|
2691
2885
|
clearTimeout(this.userScrollTimeoutId);
|
|
2692
2886
|
this.userScrollTimeoutId = undefined;
|
|
@@ -2723,42 +2917,19 @@ class AmLyrics extends i {
|
|
|
2723
2917
|
// Use a Map to collect animations like YouLyPlus
|
|
2724
2918
|
const charAnimationsMap = new Map();
|
|
2725
2919
|
const styleUpdates = [];
|
|
2726
|
-
// Step 1
|
|
2920
|
+
// Step 1: Grow Pass
|
|
2727
2921
|
if (isGrowable && isFirstSyllable && allWordCharSpans.length > 0) {
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
const
|
|
2731
|
-
allWordCharSpans.forEach(
|
|
2922
|
+
const finalDuration = wordDurationMs;
|
|
2923
|
+
const baseDelayPerChar = finalDuration * 0.09;
|
|
2924
|
+
const growDurationMs = finalDuration * 1.5;
|
|
2925
|
+
allWordCharSpans.forEach(span => {
|
|
2732
2926
|
const horizontalOffset = parseFloat(span.dataset.horizontalOffset || '0');
|
|
2733
2927
|
const maxScale = span.dataset.maxScale || '1.1';
|
|
2734
2928
|
const shadowIntensity = span.dataset.shadowIntensity || '0.6';
|
|
2735
2929
|
const translateYPeak = span.dataset.translateYPeak || '-2';
|
|
2736
|
-
const
|
|
2737
|
-
const
|
|
2738
|
-
|
|
2739
|
-
const parentDuration = parseFloat(parentSyllable.getAttribute('data-duration') || '0');
|
|
2740
|
-
const parentStartTime = parseFloat(parentSyllable.getAttribute('data-start-time') || '0');
|
|
2741
|
-
const startPct = parseFloat(span.dataset.wipeStart || '0');
|
|
2742
|
-
const durationPct = parseFloat(span.dataset.wipeDuration || '0');
|
|
2743
|
-
const relativeStartOffset = Math.max(0, parentStartTime - firstSyllableStartTime);
|
|
2744
|
-
const wipeDelay = relativeStartOffset + parentDuration * startPct;
|
|
2745
|
-
const wipeDuration = parentDuration * durationPct;
|
|
2746
|
-
const useStartAnimation = isFirstInContainer && charIndexInWord === 0;
|
|
2747
|
-
let charWipeAnimation = 'wipe';
|
|
2748
|
-
if (useStartAnimation)
|
|
2749
|
-
charWipeAnimation = isRTL ? 'start-wipe-rtl' : 'start-wipe';
|
|
2750
|
-
else
|
|
2751
|
-
charWipeAnimation = isRTL ? 'wipe-rtl' : 'wipe';
|
|
2752
|
-
// Blend word and syllable durations to let the gradient flow smoothly
|
|
2753
|
-
// while still responding to syllable pacing (no strict exactness, just natural flow)
|
|
2754
|
-
const growDelay = wipeDelay;
|
|
2755
|
-
const growDurationMs = Math.max(600, wordDurationMs * 0.8 + parentDuration * 1.5);
|
|
2756
|
-
animationParts.push(`grow-dynamic ${growDurationMs}ms ease-in-out ${growDelay}ms forwards`);
|
|
2757
|
-
if (wipeDuration > 0) {
|
|
2758
|
-
animationParts.push(`${charWipeAnimation} ${wipeDuration}ms linear ${wipeDelay}ms forwards`);
|
|
2759
|
-
}
|
|
2760
|
-
}
|
|
2761
|
-
charAnimationsMap.set(span, animationParts.join(', '));
|
|
2930
|
+
const syllableCharIndex = parseFloat(span.dataset.syllableCharIndex || '0');
|
|
2931
|
+
const growDelay = baseDelayPerChar * syllableCharIndex;
|
|
2932
|
+
charAnimationsMap.set(span, `grow-dynamic ${growDurationMs}ms ease-in-out ${growDelay}ms forwards`);
|
|
2762
2933
|
styleUpdates.push({
|
|
2763
2934
|
element: span,
|
|
2764
2935
|
property: '--char-offset-x',
|
|
@@ -2781,26 +2952,8 @@ class AmLyrics extends i {
|
|
|
2781
2952
|
});
|
|
2782
2953
|
});
|
|
2783
2954
|
}
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
// If they already have `grow-dynamic`, it means the first syllable correctly took care of BOTH grow and wipe!
|
|
2787
|
-
// Otherwise, they scrubbed directly into this syllable, so let's at least do the wipe.
|
|
2788
|
-
charSpans.forEach(span => {
|
|
2789
|
-
const existingAnimation = charAnimationsMap.get(span) || span.style.animation || '';
|
|
2790
|
-
if (existingAnimation.includes('grow-dynamic'))
|
|
2791
|
-
return;
|
|
2792
|
-
const startPct = parseFloat(span.dataset.wipeStart || '0');
|
|
2793
|
-
const durationPct = parseFloat(span.dataset.wipeDuration || '0');
|
|
2794
|
-
const wipeDelay = syllableDurationMs * startPct;
|
|
2795
|
-
const wipeDuration = syllableDurationMs * durationPct;
|
|
2796
|
-
const charWipeAnimation = isRTL ? 'wipe-rtl' : 'wipe';
|
|
2797
|
-
if (wipeDuration > 0) {
|
|
2798
|
-
charAnimationsMap.set(span, `${charWipeAnimation} ${wipeDuration}ms linear ${wipeDelay}ms forwards`);
|
|
2799
|
-
}
|
|
2800
|
-
});
|
|
2801
|
-
}
|
|
2802
|
-
else if (charSpans.length > 0) {
|
|
2803
|
-
// Per-character wipe for non-growable words (matching YouLyPlus)
|
|
2955
|
+
// Step 2: Wipe Pass
|
|
2956
|
+
if (charSpans.length > 0) {
|
|
2804
2957
|
charSpans.forEach((span, charIndex) => {
|
|
2805
2958
|
const startPct = parseFloat(span.dataset.wipeStart || '0');
|
|
2806
2959
|
const durationPct = parseFloat(span.dataset.wipeDuration || '0');
|
|
@@ -2814,8 +2967,26 @@ class AmLyrics extends i {
|
|
|
2814
2967
|
else {
|
|
2815
2968
|
charWipeAnimation = isRTL ? 'wipe-rtl' : 'wipe';
|
|
2816
2969
|
}
|
|
2970
|
+
const existingAnimation = charAnimationsMap.get(span) || span.style.animation || '';
|
|
2971
|
+
const animationParts = [];
|
|
2972
|
+
if (existingAnimation && existingAnimation.includes('grow-dynamic')) {
|
|
2973
|
+
animationParts.push(existingAnimation.split(',')[0].trim());
|
|
2974
|
+
}
|
|
2975
|
+
if (charIndex > 0) {
|
|
2976
|
+
const arrivalTime = span.dataset.preWipeArrival
|
|
2977
|
+
? parseFloat(span.dataset.preWipeArrival)
|
|
2978
|
+
: wipeDelay;
|
|
2979
|
+
const constantDuration = parseFloat(span.dataset.preWipeDuration || '100');
|
|
2980
|
+
const animDelay = arrivalTime - constantDuration;
|
|
2981
|
+
if (constantDuration > 0) {
|
|
2982
|
+
animationParts.push(`pre-wipe-char ${constantDuration}ms linear ${animDelay}ms forwards`);
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2817
2985
|
if (wipeDuration > 0) {
|
|
2818
|
-
|
|
2986
|
+
animationParts.push(`${charWipeAnimation} ${wipeDuration}ms linear ${wipeDelay}ms forwards`);
|
|
2987
|
+
}
|
|
2988
|
+
if (animationParts.length > 0) {
|
|
2989
|
+
charAnimationsMap.set(span, animationParts.join(', '));
|
|
2819
2990
|
}
|
|
2820
2991
|
});
|
|
2821
2992
|
}
|
|
@@ -3197,9 +3368,7 @@ class AmLyrics extends i {
|
|
|
3197
3368
|
this.style.setProperty('--hover-background-color', this.hoverBackgroundColor);
|
|
3198
3369
|
this.style.setProperty('--highlight-color', this.highlightColor);
|
|
3199
3370
|
const sourceLabel = this.lyricsSource ?? 'Unavailable';
|
|
3200
|
-
const isUnsynced = this.
|
|
3201
|
-
? this.lyrics.every(l => l.timestamp === 0 && l.endtime === 0)
|
|
3202
|
-
: false;
|
|
3371
|
+
const isUnsynced = this.cachedIsUnsynced;
|
|
3203
3372
|
const renderContent = () => {
|
|
3204
3373
|
if (this.isLoading) {
|
|
3205
3374
|
// Render stylized skeleton lines
|
|
@@ -3271,88 +3440,13 @@ class AmLyrics extends i {
|
|
|
3271
3440
|
// as the main vocal, so we intentionally do NOT render a separate
|
|
3272
3441
|
// translation/romanization block for background — it would just duplicate
|
|
3273
3442
|
// the main line's text.
|
|
3274
|
-
|
|
3275
|
-
const wordGroups = [];
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
else {
|
|
3282
|
-
// New word
|
|
3283
|
-
wordGroups.push([syllable]);
|
|
3284
|
-
}
|
|
3285
|
-
}
|
|
3286
|
-
// Pre-compute isGrowable per "visual word": adjacent groups whose text
|
|
3287
|
-
// doesn't end with whitespace form one visual word (e.g. "a"+"live" = "alive").
|
|
3288
|
-
// We evaluate growable on the combined text/duration, then propagate
|
|
3289
|
-
// the result to each individual group so it renders through the
|
|
3290
|
-
// single-syllable path (which supports char-level glow).
|
|
3291
|
-
const groupGrowable = new Array(wordGroups.length).fill(false);
|
|
3292
|
-
const groupGlowing = new Array(wordGroups.length).fill(false);
|
|
3293
|
-
// Visual word info for growable char-level glow:
|
|
3294
|
-
// Each group stores the combined visual word's text, duration, and
|
|
3295
|
-
// the char offset of this group within the visual word.
|
|
3296
|
-
const vwFullText = new Array(wordGroups.length).fill('');
|
|
3297
|
-
const vwFullDuration = new Array(wordGroups.length).fill(0);
|
|
3298
|
-
const vwCharOffset = new Array(wordGroups.length).fill(0);
|
|
3299
|
-
const vwStartMs = new Array(wordGroups.length).fill(0);
|
|
3300
|
-
const vwEndMs = new Array(wordGroups.length).fill(0);
|
|
3301
|
-
{
|
|
3302
|
-
let vwStart = 0;
|
|
3303
|
-
while (vwStart < wordGroups.length) {
|
|
3304
|
-
let vwEnd = vwStart;
|
|
3305
|
-
while (vwEnd < wordGroups.length - 1) {
|
|
3306
|
-
const grp = wordGroups[vwEnd];
|
|
3307
|
-
const lastText = grp[grp.length - 1].text;
|
|
3308
|
-
if (/\s$/.test(lastText))
|
|
3309
|
-
break;
|
|
3310
|
-
vwEnd += 1;
|
|
3311
|
-
}
|
|
3312
|
-
// Compute combined properties for this visual word
|
|
3313
|
-
const combinedText = wordGroups
|
|
3314
|
-
.slice(vwStart, vwEnd + 1)
|
|
3315
|
-
.flatMap(g => g.map(s => s.text))
|
|
3316
|
-
.join('')
|
|
3317
|
-
.trim();
|
|
3318
|
-
const combinedStart = wordGroups[vwStart][0].timestamp;
|
|
3319
|
-
const lastGrp = wordGroups[vwEnd];
|
|
3320
|
-
const combinedEnd = lastGrp[lastGrp.length - 1].endtime;
|
|
3321
|
-
const combinedDuration = combinedEnd - combinedStart;
|
|
3322
|
-
const isCJK = /[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/.test(combinedText);
|
|
3323
|
-
const isRTL = /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\u0590-\u05FF]/.test(combinedText);
|
|
3324
|
-
const hasHyphen = combinedText.includes('-');
|
|
3325
|
-
const wordLen = combinedText.length;
|
|
3326
|
-
let isGrowableVW = !isCJK && !isRTL && !hasHyphen && wordLen > 0 && wordLen <= 12;
|
|
3327
|
-
if (isGrowableVW) {
|
|
3328
|
-
if (wordLen < 3) {
|
|
3329
|
-
isGrowableVW =
|
|
3330
|
-
combinedDuration >= 1110 && combinedDuration >= wordLen * 550;
|
|
3331
|
-
}
|
|
3332
|
-
else {
|
|
3333
|
-
isGrowableVW =
|
|
3334
|
-
combinedDuration >= 850 && combinedDuration >= wordLen * 200;
|
|
3335
|
-
}
|
|
3336
|
-
}
|
|
3337
|
-
// Glow requirement (more strict)
|
|
3338
|
-
const isGlowingVW = isGrowableVW &&
|
|
3339
|
-
combinedDuration >= 1000 &&
|
|
3340
|
-
combinedDuration >= combinedText.length * 250;
|
|
3341
|
-
let charOff = 0;
|
|
3342
|
-
for (let gi = vwStart; gi <= vwEnd; gi += 1) {
|
|
3343
|
-
groupGrowable[gi] = isGrowableVW;
|
|
3344
|
-
groupGlowing[gi] = isGlowingVW;
|
|
3345
|
-
vwFullText[gi] = combinedText;
|
|
3346
|
-
vwFullDuration[gi] = combinedDuration;
|
|
3347
|
-
vwCharOffset[gi] = charOff;
|
|
3348
|
-
vwStartMs[gi] = combinedStart;
|
|
3349
|
-
vwEndMs[gi] = combinedEnd;
|
|
3350
|
-
const grpText = wordGroups[gi].map(s => s.text).join('');
|
|
3351
|
-
charOff += grpText.replace(/\s/g, '').length;
|
|
3352
|
-
}
|
|
3353
|
-
vwStart = vwEnd + 1;
|
|
3354
|
-
}
|
|
3355
|
-
}
|
|
3443
|
+
const lineData = this.cachedLineData?.[lineIndex];
|
|
3444
|
+
const wordGroups = lineData?.wordGroups ?? [];
|
|
3445
|
+
const groupGrowable = lineData?.groupGrowable ?? [];
|
|
3446
|
+
const groupGlowing = lineData?.groupGlowing ?? [];
|
|
3447
|
+
const vwFullText = lineData?.vwFullText ?? [];
|
|
3448
|
+
const vwFullDuration = lineData?.vwFullDuration ?? [];
|
|
3449
|
+
const vwCharOffset = lineData?.vwCharOffset ?? [];
|
|
3356
3450
|
// Create main vocals using YouLyPlus syllable structure
|
|
3357
3451
|
const mainVocalElement = b `<p class="main-vocal-container">
|
|
3358
3452
|
${wordGroups.map((group, groupIdx) => {
|
|
@@ -3586,9 +3680,7 @@ class AmLyrics extends i {
|
|
|
3586
3680
|
<div
|
|
3587
3681
|
class="lyrics-container ${isUnsynced
|
|
3588
3682
|
? 'is-unsynced'
|
|
3589
|
-
: 'blur-inactive-enabled'}
|
|
3590
|
-
? 'user-scrolling'
|
|
3591
|
-
: ''}"
|
|
3683
|
+
: 'blur-inactive-enabled'}"
|
|
3592
3684
|
>
|
|
3593
3685
|
${!this.isLoading && this.lyrics && this.lyrics.length > 0
|
|
3594
3686
|
? b `
|
|
@@ -3703,17 +3795,15 @@ class AmLyrics extends i {
|
|
|
3703
3795
|
!this.hasFetchedAllProviders
|
|
3704
3796
|
? b `
|
|
3705
3797
|
<button
|
|
3706
|
-
class="download-button"
|
|
3798
|
+
class="download-button source-switch-btn"
|
|
3707
3799
|
title="Switch Lyrics Source"
|
|
3708
3800
|
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;"
|
|
3709
3801
|
@click=${this.switchSource}
|
|
3710
3802
|
?disabled=${this.isFetchingAlternatives}
|
|
3711
3803
|
>
|
|
3712
3804
|
<svg
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
? 'animation: spin 1s linear infinite;'
|
|
3716
|
-
: ''}"
|
|
3805
|
+
class="source-switch-svg lucide lucide-arrow-down-up-icon lucide-arrow-down-up"
|
|
3806
|
+
style="margin-right: 4px;"
|
|
3717
3807
|
xmlns="http://www.w3.org/2000/svg"
|
|
3718
3808
|
width="12"
|
|
3719
3809
|
height="12"
|
|
@@ -3723,7 +3813,6 @@ class AmLyrics extends i {
|
|
|
3723
3813
|
stroke-width="2"
|
|
3724
3814
|
stroke-linecap="round"
|
|
3725
3815
|
stroke-linejoin="round"
|
|
3726
|
-
class="lucide lucide-arrow-down-up-icon lucide-arrow-down-up"
|
|
3727
3816
|
>
|
|
3728
3817
|
${this.isFetchingAlternatives
|
|
3729
3818
|
? w `<path
|
|
@@ -3734,9 +3823,11 @@ class AmLyrics extends i {
|
|
|
3734
3823
|
><path d="m21 8-4-4-4 4"></path
|
|
3735
3824
|
><path d="M17 4v16"></path>`}
|
|
3736
3825
|
</svg>
|
|
3737
|
-
|
|
3826
|
+
<span class="source-switch-label"
|
|
3827
|
+
>${this.isFetchingAlternatives
|
|
3738
3828
|
? 'Switching...'
|
|
3739
|
-
: 'Switch'}
|
|
3829
|
+
: 'Switch'}</span
|
|
3830
|
+
>
|
|
3740
3831
|
</button>
|
|
3741
3832
|
`
|
|
3742
3833
|
: ''}
|
|
@@ -4651,8 +4742,8 @@ AmLyrics.styles = i$3 `
|
|
|
4651
4742
|
0.75em 100%,
|
|
4652
4743
|
0% 100%;
|
|
4653
4744
|
background-position:
|
|
4654
|
-
-0.
|
|
4655
|
-
|
|
4745
|
+
-0.75em 0%,
|
|
4746
|
+
-0.375em 0%;
|
|
4656
4747
|
}
|
|
4657
4748
|
100% {
|
|
4658
4749
|
background-size:
|
|
@@ -4746,7 +4837,7 @@ AmLyrics.styles = i$3 `
|
|
|
4746
4837
|
0.75em 100%,
|
|
4747
4838
|
0% 100%;
|
|
4748
4839
|
background-position:
|
|
4749
|
-
-0.
|
|
4840
|
+
-0.75em 0%,
|
|
4750
4841
|
left;
|
|
4751
4842
|
}
|
|
4752
4843
|
to {
|
|
@@ -4754,7 +4845,7 @@ AmLyrics.styles = i$3 `
|
|
|
4754
4845
|
0.75em 100%,
|
|
4755
4846
|
0% 100%;
|
|
4756
4847
|
background-position:
|
|
4757
|
-
-0.
|
|
4848
|
+
-0.375em 0%,
|
|
4758
4849
|
left;
|
|
4759
4850
|
}
|
|
4760
4851
|
}
|
|
@@ -5021,18 +5112,9 @@ __decorate([
|
|
|
5021
5112
|
__decorate([
|
|
5022
5113
|
r()
|
|
5023
5114
|
], AmLyrics.prototype, "currentSourceIndex", void 0);
|
|
5024
|
-
__decorate([
|
|
5025
|
-
r()
|
|
5026
|
-
], AmLyrics.prototype, "isFetchingAlternatives", void 0);
|
|
5027
|
-
__decorate([
|
|
5028
|
-
r()
|
|
5029
|
-
], AmLyrics.prototype, "hasFetchedAllProviders", void 0);
|
|
5030
5115
|
__decorate([
|
|
5031
5116
|
e('.lyrics-container')
|
|
5032
5117
|
], AmLyrics.prototype, "lyricsContainer", void 0);
|
|
5033
|
-
__decorate([
|
|
5034
|
-
r()
|
|
5035
|
-
], AmLyrics.prototype, "isUserScrolling", void 0);
|
|
5036
5118
|
|
|
5037
5119
|
window.customElements.define('am-lyrics', AmLyrics);
|
|
5038
5120
|
//# sourceMappingURL=am-lyrics.js.map
|