mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-27 13:22:49 +02:00
fix(player): smooth timed lyrics highlighting
This commit is contained in:
@@ -21,3 +21,73 @@ double syncedLyricsEstimatedOffset({
|
||||
if (index <= 0) return 0;
|
||||
return index * estimatedLineExtent;
|
||||
}
|
||||
|
||||
/// Advances to the final lyric line whose timestamp is already due.
|
||||
int syncedLyricsDueLineIndex({
|
||||
required List<Duration> lineStarts,
|
||||
required int currentIndex,
|
||||
required Duration position,
|
||||
}) {
|
||||
if (lineStarts.isEmpty) return -1;
|
||||
var dueIndex = currentIndex;
|
||||
if (dueIndex < -1) dueIndex = -1;
|
||||
if (dueIndex >= lineStarts.length) dueIndex = lineStarts.length - 1;
|
||||
while (dueIndex + 1 < lineStarts.length &&
|
||||
lineStarts[dueIndex + 1] <= position) {
|
||||
dueIndex++;
|
||||
}
|
||||
return dueIndex;
|
||||
}
|
||||
|
||||
/// Extrapolates the latest player position for the lyrics animation only.
|
||||
///
|
||||
/// The global playback state intentionally updates at a lower frequency to
|
||||
/// avoid rebuilding unrelated UI on every frame. The active lyric line uses
|
||||
/// this value to animate between those updates.
|
||||
Duration interpolatedSyncedLyricsPosition({
|
||||
required Duration anchorPosition,
|
||||
required Duration elapsedSinceAnchor,
|
||||
required bool isPlaying,
|
||||
}) {
|
||||
if (!isPlaying || elapsedSinceAnchor.isNegative) return anchorPosition;
|
||||
return anchorPosition + elapsedSinceAnchor;
|
||||
}
|
||||
|
||||
/// Keeps small timing corrections from making the lyric highlight jump while
|
||||
/// still applying a seek or another large position change immediately.
|
||||
Duration reconcileSyncedLyricsPosition({
|
||||
required Duration predictedPosition,
|
||||
required Duration reportedPosition,
|
||||
Duration seekThreshold = const Duration(milliseconds: 750),
|
||||
}) {
|
||||
final drift = (reportedPosition - predictedPosition).abs();
|
||||
if (drift >= seekThreshold) return reportedPosition;
|
||||
|
||||
const correctionFraction = 0.25;
|
||||
final correction =
|
||||
((reportedPosition - predictedPosition).inMicroseconds *
|
||||
correctionFraction)
|
||||
.round();
|
||||
return predictedPosition + Duration(microseconds: correction);
|
||||
}
|
||||
|
||||
/// Continuous progress for one timed TTML or enhanced LRC segment.
|
||||
double syncedLyricSegmentProgress({
|
||||
required Duration position,
|
||||
required Duration start,
|
||||
required Duration end,
|
||||
}) {
|
||||
if (position <= start) return 0;
|
||||
if (position >= end || end <= start) return 1;
|
||||
return (position - start).inMicroseconds / (end - start).inMicroseconds;
|
||||
}
|
||||
|
||||
/// Horizontal leading edge for a highlight that fills left to right.
|
||||
double syncedLyricsLeftToRightBoundary({
|
||||
required double left,
|
||||
required double right,
|
||||
required double progress,
|
||||
}) {
|
||||
final value = progress.clamp(0.0, 1.0);
|
||||
return left + ((right - left) * value);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user