feat: add ShazamService for music recognition

feat/music-streaming
Laurent 1 month ago
parent 66fb024b51
commit 3f788a2f81
  1. 83
      Music/Services/ShazamService.swift

@ -0,0 +1,83 @@
import AVFAudio
import Observation
import ShazamKit
@Observable
final class ShazamService: NSObject, SHSessionDelegate {
var isListening = false
var matchedTitle: String?
var matchedArtist: String?
var errorMessage: String?
private var session: SHSession?
private let audioEngine = AVAudioEngine()
private var timeoutTask: Task<Void, Never>?
func startListening() {
guard !isListening else {
stopListening()
return
}
matchedTitle = nil
matchedArtist = nil
errorMessage = nil
let session = SHSession()
session.delegate = self
self.session = session
let inputNode = audioEngine.inputNode
let format = inputNode.outputFormat(forBus: 0)
inputNode.installTap(onBus: 0, bufferSize: 2048, format: format) { buffer, time in
session.matchStreamingBuffer(buffer, at: time)
}
do {
try audioEngine.start()
isListening = true
timeoutTask = Task { [weak self] in
try? await Task.sleep(for: .seconds(15))
guard !Task.isCancelled else { return }
self?.stopListening()
if self?.matchedTitle == nil {
self?.errorMessage = "No match found. Try again in a quieter environment."
}
}
} catch {
errorMessage = "Could not access microphone."
}
}
func stopListening() {
timeoutTask?.cancel()
timeoutTask = nil
if audioEngine.isRunning {
audioEngine.inputNode.removeTap(onBus: 0)
audioEngine.stop()
}
session = nil
isListening = false
}
func clearResult() {
matchedTitle = nil
matchedArtist = nil
errorMessage = nil
}
nonisolated func session(_ session: SHSession, didFind match: SHMatch) {
Task { @MainActor [weak self] in
guard let item = match.mediaItems.first else { return }
self?.matchedTitle = item.title
self?.matchedArtist = item.artist
self?.stopListening()
}
}
nonisolated func session(_ session: SHSession, didNotFindMatchFor signature: SHSignature) {
// Session continues listening; the 15s timeout handles the "no match" case.
}
}
Loading…
Cancel
Save