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