@@ -11,6 +11,8 @@ public struct WaveformView<Content: View>: View {
1111 private let content : ( WaveformShape ) -> Content
1212
1313 @State private var samples : [ Float ] = [ ]
14+ @State private var rescaleTimer : Timer ?
15+ @State private var currentSize : CGSize = . zero
1416
1517 /**
1618 Creates a new WaveformView which displays a waveform for the audio at `audioURL`.
@@ -39,26 +41,54 @@ public struct WaveformView<Content: View>: View {
3941 public var body : some View {
4042 GeometryReader { geometry in
4143 content ( WaveformShape ( samples: samples, configuration: configuration, renderer: renderer) )
44+ . scaleEffect ( x: scaleDuringResize ( for: geometry) , y: 1 , anchor: . trailing)
4245 . onAppear {
4346 guard samples. isEmpty else { return }
4447 update ( size: geometry. size, url: audioURL, configuration: configuration)
4548 }
46- . modifier ( OnChange ( of: geometry. size, action: { newValue in update ( size: newValue, url: audioURL, configuration: configuration) } ) )
49+ . modifier ( OnChange ( of: geometry. size, action: { newValue in update ( size: newValue, url: audioURL, configuration: configuration, delayed : true ) } ) )
4750 . modifier ( OnChange ( of: audioURL, action: { newValue in update ( size: geometry. size, url: audioURL, configuration: configuration) } ) )
4851 . modifier ( OnChange ( of: configuration, action: { newValue in update ( size: geometry. size, url: audioURL, configuration: newValue) } ) )
4952 }
5053 }
5154
52- private func update( size: CGSize , url: URL , configuration: Waveform . Configuration ) {
53- Task ( priority: priority) {
54- do {
55- let samplesNeeded = Int ( size. width * configuration. scale)
56- let samples = try await WaveformAnalyzer ( ) . samples ( fromAudioAt: url, count: samplesNeeded)
57- await MainActor . run { self . samples = samples }
58- } catch {
59- assertionFailure ( error. localizedDescription)
55+ private func update( size: CGSize , url: URL , configuration: Waveform . Configuration , delayed: Bool = false ) {
56+ rescaleTimer? . invalidate ( )
57+
58+ let updateTask : @Sendable ( Timer ? ) -> Void = { _ in
59+ Task ( priority: . userInitiated) {
60+ do {
61+ let samplesNeeded = Int ( size. width * configuration. scale)
62+ let samples = try await WaveformAnalyzer ( ) . samples ( fromAudioAt: url, count: samplesNeeded)
63+
64+ await MainActor . run {
65+ self . currentSize = size
66+ self . samples = samples
67+ }
68+ } catch {
69+ assertionFailure ( error. localizedDescription)
70+ }
6071 }
6172 }
73+
74+ if delayed {
75+ rescaleTimer = Timer . scheduledTimer ( withTimeInterval: 0.05 , repeats: false , block: updateTask)
76+ RunLoop . main. add ( rescaleTimer!, forMode: . common)
77+ } else {
78+ updateTask ( nil )
79+ }
80+ }
81+
82+ /*
83+ * During resizing, we only visually scale the shape to make it look more seamless,
84+ * before we re-calculate the pixel-perfect re-sampled waveform, which is costly.
85+ * Due to the complex way we need to render the actual waveform based on samples
86+ * available and size to occupy, the re-scaling currently only supports enlarging.
87+ * If we resize to a smaller size, the waveform simply overflows.
88+ */
89+ private func scaleDuringResize( for geometry: GeometryProxy ) -> CGFloat {
90+ guard currentSize != . zero else { return 1 }
91+ return max ( geometry. size. width / currentSize. width, 1 )
6292 }
6393}
6494
0 commit comments