From 87312075dd414a6f60691464bcd6c6a9bb242a7e Mon Sep 17 00:00:00 2001 From: bbedward Date: Fri, 30 Oct 2026 10:50:50 -0400 Subject: [PATCH] lyrics: distrust the player position after a track change until the probe settles ref #4025 port 2.0 --- quickshell/Modules/DDash/DashMetrics.qml | 2 ++ quickshell/Services/LyricsController.qml | 31 +++++++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/quickshell/Modules/DDash/DashMetrics.qml b/quickshell/Modules/DDash/DashMetrics.qml index 789a6cfdb..c8efc0d1b 100644 --- a/quickshell/Modules/DDash/DashMetrics.qml +++ b/quickshell/Modules/DDash/DashMetrics.qml @@ -224,6 +224,8 @@ Singleton { readonly property int mediaLyricsPositionProbeInterval: 5000 readonly property real mediaLyricsPositionProbeDrift: 1.5 readonly property real mediaLyricsPositionProbeThreshold: 1 + readonly property int mediaLyricsPositionSettleInterval: 1000 + readonly property real mediaLyricsTrackStartSlack: 5 readonly property real lyricsNearOpacity: 0.55 readonly property real lyricsFarOpacity: 0.3 readonly property real lyricsLineHeight: 1.25 diff --git a/quickshell/Services/LyricsController.qml b/quickshell/Services/LyricsController.qml index 7860442c3..0427c4572 100644 --- a/quickshell/Services/LyricsController.qml +++ b/quickshell/Services/LyricsController.qml @@ -57,13 +57,15 @@ QtObject { property real anchorRate: 1 property real positionWall: 0 property var probe: null + property bool positionTrusted: true + property string clockBus: "" property var memo: null onTrackKeyChanged: { cancel(); refreshPending = holdsSong(); if (!refreshPending) - clear(); + restart(); requestedKey = ""; if (enabled) resume(); @@ -172,6 +174,19 @@ QtObject { state = "idle"; } + function restart() { + const switched = clockBus !== "" && clockBus === busName; + clockBus = busName; + clear(); + positionTrusted = !switched; + if (!switched) + return; + anchorPosition = 0; + anchorWall = Date.now(); + anchorPlaying = playing; + anchorRate = rate; + } + function clearContent() { stopClock(); lines = []; @@ -383,6 +398,7 @@ QtObject { if (!line || !Number.isFinite(line.t)) return false; player.position = Math.max(0.1, Math.min(line.t, duration * 0.99)); + positionTrusted = true; reanchor(); return true; } @@ -396,7 +412,7 @@ QtObject { function updatePlaybackClock() { const predicted = currentTime(); const sampled = position + (anchorPlaying ? (Date.now() - positionWall) * anchorRate / 1000 : 0); - const pendingSeek = !stopped && anchorUpdate.running && Math.abs(sampled - predicted) >= DashMetrics.mediaLyricsPositionTolerance; + const pendingSeek = !stopped && anchorUpdate.running && Math.abs(sampled - predicted) >= DashMetrics.mediaLyricsPositionTolerance && reachable(sampled); anchorUpdate.stop(); anchorPosition = pendingSeek ? sampled : predicted; anchorWall = Date.now(); @@ -430,6 +446,7 @@ QtObject { const advanced = previous.position + (wall - previous.wall) * rate / 1000; if (Math.abs(observed - advanced) > DashMetrics.mediaLyricsPositionProbeDrift) return; + positionTrusted = true; if (Math.abs(observed - currentTime()) < DashMetrics.mediaLyricsPositionProbeThreshold) return; anchorPosition = observed; @@ -452,9 +469,15 @@ QtObject { anchorUpdate.start(); } + // the Position Get quickshell issues on a track change can still answer with the old track (#4025) + function reachable(observed) { + return positionTrusted || observed <= currentTime() + DashMetrics.mediaLyricsTrackStartSlack; + } + function reanchor(force = true) { anchorUpdate.stop(); - const observed = player?.position ?? 0; + const sampled = player?.position ?? 0; + const observed = reachable(sampled) ? sampled : currentTime(); if (!force && anchorPlaying && Math.abs(observed - currentTime()) < DashMetrics.mediaLyricsPositionTolerance) return false; anchorPosition = observed; @@ -538,7 +561,7 @@ QtObject { } property Timer positionProbe: Timer { - interval: DashMetrics.mediaLyricsPositionProbeInterval + interval: root.positionTrusted ? DashMetrics.mediaLyricsPositionProbeInterval : DashMetrics.mediaLyricsPositionSettleInterval repeat: true triggeredOnStart: true running: root.enabled && root.synced && root.playing && !root.stopped && root.busName !== "" && DMSService.isConnected