ios: karaoke vocals On/Soft/Off (1.0.6)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8e5ebb70b1
commit
41b8171df2
4 files changed
+55
-21
No files matched your search
@@ -183,7 +183,7 @@ struct NowPlayingScreen: View {
|
||||
}
|
||||
|
||||
/// Opens a panel, or goes back to the art if it's already open.
|
||||
/// Karaoke: vocals out and lyrics up. If it's on but another panel is showing, brings the lyrics back first.
|
||||
/// Karaoke: vocals down and lyrics up. If it's on but another panel is showing, brings the lyrics back first.
|
||||
private func karaoke() {
|
||||
let pm = g.playback
|
||||
withAnimation(.spring(duration: 0.38, bounce: 0.12)) {
|
||||
@@ -858,6 +858,22 @@ private struct LyricsPanel: View {
|
||||
private var g: AppGraph { .shared }
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
// Soft leaves the singer in quietly as a guide.
|
||||
HStack(spacing: 12) {
|
||||
Text("Vocals").font(.subheadline.weight(.semibold)).foregroundStyle(p.label2)
|
||||
Picker("Vocals", selection: Binding(get: { g.playback.karaokeVocals }, set: { g.playback.setKaraokeVocals($0) })) {
|
||||
ForEach(KaraokeVocals.allCases, id: \.self) { Text(String(describing: $0)).tag($0) }
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
}
|
||||
.padding(.horizontal, 28).padding(.top, 8)
|
||||
content
|
||||
}
|
||||
.task(id: track.ratingKey) { await follow() }
|
||||
}
|
||||
|
||||
@ViewBuilder private var content: some View {
|
||||
Group {
|
||||
if loading {
|
||||
ProgressView()
|
||||
@@ -869,19 +885,20 @@ private struct LyricsPanel: View {
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.task(id: track.ratingKey) {
|
||||
loading = true
|
||||
at = -1
|
||||
lyrics = await LyricsSource.shared.lyrics(track)
|
||||
loading = false
|
||||
guard let l = lyrics, l.synced else { return }
|
||||
while !Task.isCancelled {
|
||||
// A little early, so the line lights up as it's sung rather than after.
|
||||
let pos = g.playback.positionMs() + 250
|
||||
let i = (l.lines.lastIndex { $0.ms <= pos }) ?? -1
|
||||
if i != at { withAnimation(.easeOut(duration: 0.25)) { at = i } }
|
||||
try? await Task.sleep(nanoseconds: 150_000_000)
|
||||
}
|
||||
}
|
||||
|
||||
private func follow() async {
|
||||
loading = true
|
||||
at = -1
|
||||
lyrics = await LyricsSource.shared.lyrics(track)
|
||||
loading = false
|
||||
guard let l = lyrics, l.synced else { return }
|
||||
while !Task.isCancelled {
|
||||
// A little early, so the line lights up as it's sung rather than after.
|
||||
let pos = g.playback.positionMs() + 250
|
||||
let i = (l.lines.lastIndex { $0.ms <= pos }) ?? -1
|
||||
if i != at { withAnimation(.easeOut(duration: 0.25)) { at = i } }
|
||||
try? await Task.sleep(nanoseconds: 150_000_000)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user