|
2 | 2 | // Copyright (c) William Entriken and the FDWaveformView contributors |
3 | 3 | // |
4 | 4 |
|
| 5 | +import AVFoundation |
5 | 6 | import FDWaveformView |
6 | 7 | import SwiftUI |
7 | 8 |
|
@@ -120,6 +121,10 @@ struct ContentView: View { |
120 | 121 | @State private var isShowingProfilingAlert = false |
121 | 122 | @State private var profilingStartTime: Date? |
122 | 123 | @State private var aacRenderTime: TimeInterval? |
| 124 | + @State private var audioPlayer: AVAudioPlayer? |
| 125 | + @State private var isPlaying = false |
| 126 | + @State private var playbackTimer: Timer? |
| 127 | + @State private var playbackStartTime: Date? |
123 | 128 |
|
124 | 129 | var body: some View { |
125 | 130 | NavigationView { |
@@ -227,6 +232,21 @@ struct ContentView: View { |
227 | 232 | } |
228 | 233 | } |
229 | 234 | .frame(height: 200) |
| 235 | + |
| 236 | + HStack { |
| 237 | + Button(action: playAudio) { |
| 238 | + Label("Play", systemImage: "play.fill") |
| 239 | + } |
| 240 | + .buttonStyle(.borderedProminent) |
| 241 | + .disabled(audioURL == nil) |
| 242 | + |
| 243 | + Button(action: stopAudio) { |
| 244 | + Label("Stop", systemImage: "stop.fill") |
| 245 | + } |
| 246 | + .buttonStyle(.bordered) |
| 247 | + .disabled(!isPlaying) |
| 248 | + } |
| 249 | + .padding() |
230 | 250 | } |
231 | 251 | .padding() |
232 | 252 | } |
@@ -272,6 +292,50 @@ struct ContentView: View { |
272 | 292 | profilingStartTime = nil |
273 | 293 | } |
274 | 294 | } |
| 295 | + |
| 296 | + private func playAudio() { |
| 297 | + guard let audioURL = audioURL else { return } |
| 298 | + |
| 299 | + do { |
| 300 | + audioPlayer = try AVAudioPlayer(contentsOf: audioURL) |
| 301 | + audioPlayer?.play() |
| 302 | + isPlaying = true |
| 303 | + progress = 0 |
| 304 | + |
| 305 | + // Start playback timer to update progress |
| 306 | + playbackStartTime = Date() |
| 307 | + playbackTimer = Timer.scheduledTimer(withTimeInterval: 0.016, repeats: true) { _ in |
| 308 | + updateProgress() |
| 309 | + } |
| 310 | + } catch { |
| 311 | + NSLog("Failed to play audio: \(error)") |
| 312 | + } |
| 313 | + } |
| 314 | + |
| 315 | + private func stopAudio() { |
| 316 | + audioPlayer?.stop() |
| 317 | + isPlaying = false |
| 318 | + playbackTimer?.invalidate() |
| 319 | + playbackTimer = nil |
| 320 | + playbackStartTime = nil |
| 321 | + } |
| 322 | + |
| 323 | + private func updateProgress() { |
| 324 | + guard isPlaying, let startTime = playbackStartTime, totalSamples > 0 else { return } |
| 325 | + |
| 326 | + let elapsedTime = Date().timeIntervalSince(startTime) |
| 327 | + let sampleRate = 44100 // Submarine.aiff sample rate |
| 328 | + let currentSample = Int(elapsedTime * Double(sampleRate)) |
| 329 | + |
| 330 | + if currentSample < totalSamples { |
| 331 | + withAnimation(.linear(duration: 0.016)) { |
| 332 | + progress = currentSample |
| 333 | + } |
| 334 | + } else { |
| 335 | + // Playback finished |
| 336 | + stopAudio() |
| 337 | + } |
| 338 | + } |
275 | 339 | } |
276 | 340 |
|
277 | 341 | #Preview { |
|
0 commit comments