feat: light lyrics slightly ahead of the audio

This commit is contained in:
DiheChen committed 2026-11-05 10:55:28 +08:00
1 parent 55be743e8a
commit 659096ed60
4 files changed
+15 -4

No files matched your search

+2
View File
@@ -17,6 +17,8 @@ const yrcWordTag = /\((\d+),(\d+),\d+\)/g
// 逐字歌词没有空白行,要靠行尾和下一行的间隔判断间奏。间隔太短就算作换气:清除高亮再点亮会闪烁。
const interludeGapMs = 3000
const maxTranslationGapMs = 500
// 字亮起和声音同时发生时,看起来反而慢半拍。
export const lyricLeadMs = 100
export function parseLrc(lrc: string): LyricLine[] {
const lines: LyricLine[] = []
@@ -50,6 +50,15 @@ describe('lyricsStore', () => {
expect(lyricsStore.state.index).toBe(1)
})
it('比播放位置提前 100ms 切换到下一行', async () => {
const { lyricsStore, frames, play } = await setup()
play(nowPlaying('1', { lyric: '[00:01.00]一\n[00:02.00]二' }))
frames.emit(1899)
expect(lyricsStore.state.index).toBe(0)
frames.emit(1900)
expect(lyricsStore.state.index).toBe(1)
})
it('同一首歌的新快照不重建歌词行,离开房间后清空', async () => {
const { lyricsStore, play } = await setup()
play(nowPlaying('1'))
+2 -2
View File
@@ -2,7 +2,7 @@ import type { LyricLine } from './lrc'
import { roomStore } from '@/core/room'
import { playerStore } from '@/features/playback'
import { Store } from '@/shared/lib/store'
import { buildLines, lineIndexAt } from './lrc'
import { buildLines, lineIndexAt, lyricLeadMs } from './lrc'
class LyricsStore extends Store<{ lines: LyricLine[], index: number }> {
#track = ''
@@ -28,7 +28,7 @@ class LyricsStore extends Store<{ lines: LyricLine[], index: number }> {
}
#sync(positionMs: number): void {
this.setState({ index: lineIndexAt(this.state.lines, positionMs) })
this.setState({ index: lineIndexAt(this.state.lines, positionMs + lyricLeadMs) })
}
}
+2 -2
View File
@@ -6,7 +6,7 @@ import { playerStore } from '@/features/playback'
import { performanceStore } from '@/features/settings'
import { useStore } from '@/shared/lib/use-store'
import { EmptyState } from '@/shared/ui'
import { wordEmphasis, wordProgress } from './lrc'
import { lyricLeadMs, wordEmphasis, wordProgress } from './lrc'
import { lyricsStore } from './lyrics-store'
import './lyrics.css'
@@ -98,7 +98,7 @@ export function Lyrics() {
const paintWords = (positionMs: number) => {
const smooth = performanceStore.smoothMotion()
for (const item of activeWords) {
const progress = wordProgress(item.word, positionMs)
const progress = wordProgress(item.word, positionMs + lyricLeadMs)
// 不需要流畅动效时按字切换,每帧不用重绘。
const fill = smooth ? progress : Math.ceil(progress)
if (fill !== item.fill) {