WaveformView.swift 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
  1. import SwiftUI
  2. /// Canvas-based waveform visualization with playback progress.
  3. struct WaveformView: View {
  4. @Environment(PlayerViewModel.self) private var playerVM
  5. @EnvironmentObject private var theme: AppTheme
  6. @State private var isDragging = false
  7. @State private var dragProgress: Double = 0
  8. var body: some View {
  9. GeometryReader { geo in
  10. Canvas { context, size in
  11. let samples = playerVM.waveformSamples
  12. guard !samples.isEmpty else { return }
  13. let barWidth = size.width / CGFloat(samples.count)
  14. let midY = size.height / 2
  15. let progress = isDragging ? dragProgress : playerVM.progress
  16. for (index, sample) in samples.enumerated() {
  17. let x = CGFloat(index) * barWidth
  18. let isPlayed = Double(index) / Double(samples.count) < progress
  19. let topHeight = CGFloat(sample.max) * midY
  20. let bottomHeight = CGFloat(-sample.min) * midY
  21. let rect = CGRect(
  22. x: x,
  23. y: midY - topHeight,
  24. width: max(barWidth - 0.5, 0.5),
  25. height: topHeight + bottomHeight
  26. )
  27. let color = isPlayed ? theme.seekbarForeground : theme.seekbarBackground
  28. context.fill(Path(rect), with: .color(color))
  29. }
  30. // Playhead line
  31. let playheadX = progress * Double(size.width)
  32. let playheadRect = CGRect(x: playheadX - 0.5, y: 0, width: 1, height: size.height)
  33. context.fill(Path(playheadRect), with: .color(theme.accent))
  34. }
  35. .gesture(
  36. DragGesture(minimumDistance: 0)
  37. .onChanged { value in
  38. isDragging = true
  39. dragProgress = max(0, min(1, Double(value.location.x / geo.size.width)))
  40. }
  41. .onEnded { value in
  42. let prog = max(0, min(1, Double(value.location.x / geo.size.width)))
  43. playerVM.seekToProgress(prog)
  44. isDragging = false
  45. }
  46. )
  47. }
  48. .clipShape(RoundedRectangle(cornerRadius: 6))
  49. .contentShape(Rectangle())
  50. }
  51. }