Building a music recognization app in SwiftUI with ShazamKit

Prerequisite

Creating the project

Adding entitlements

Coding

ContentViewModel.swift

class ContentViewModel: NSObject, ObservableObject { }

Why NSObject?

Why ObservableObject?

Creating a ShazamMedia object

struct ShazamMedia: Decodable {
let title: String?
let subtitle: String?
let artistName: String?
let albumArtURL: URL?
let genres: [String]
}

Back to ContentViewModel

@Published var shazamMedia =  ShazamMedia(title: "Title...",
subtitle: "Subtitle...",
artistName: "Artist Name...",
albumArtURL: URL(string: "https://google.com"),
genres: ["Pop"])
@Published var isRecording = false
private let audioEngine = AVAudioEngine()
private let session = SHSession()
private let signatureGenerator = SHSignatureGenerator()

init

override init() {
super.init()
session.delegate = self
}

Conforming to SHSessionDelegate

extension ContentViewModel: SHSessionDelegate { }

Wiring up the delegates

func session(_ session: SHSession, didFind match: SHMatch) { }
let mediaItems = match.mediaItems
if let firstItem = mediaItems.first {
let _shazamMedia = ShazamMedia(title: firstItem.title,
subtitle: firstItem.subtitle,
artistName: firstItem.artist,
albumArtURL: firstItem.artworkURL,
genres: firstItem.genres)
DispatchQueue.main.async {
self.shazamMedia = _shazamMedia
}
}

Configuring the microphone

Info.plist key

Back to ContentViewModel

guard !audioEngine.isRunning else {
audioEngine.stop()
DispatchQueue.main.async {
self.isRecording = false
}
return
}
let audioSession = AVAudioSession.sharedInstance()audioSession.requestRecordPermission { granted in
guard granted else { return }
try? audioSession.setActive(true, options: .notifyOthersOnDeactivation)
let inputNode = self.audioEngine.inputNode
let recordingFormat = inputNode.outputFormat(forBus: 0)
inputNode.installTap(onBus: 0,
bufferSize: 1024,
format: recordingFormat) { (buffer: AVAudioPCMBuffer,
when: AVAudioTime) in
self.session.matchStreamingBuffer(buffer, at: nil)
}
self.audioEngine.prepare()
do {
try self.audioEngine.start()
} catch (let error) {
assertionFailure(error.localizedDescription)
}
DispatchQueue.main.async {
self.isRecording = true
}
}

Creating the view.

@StateObject private var viewModel = ContentViewModel()
ZStack {
AsyncImage(url: viewModel.shazamMedia.albumArtURL) { image in
image
.resizable()
.scaledToFill()
.blur(radius: 10, opaque: true)
.opacity(0.5)
.edgesIgnoringSafeArea(.all)
} placeholder: {
EmptyView()
}
VStack(alignment: .center) {
Spacer()
AsyncImage(url: viewModel.shazamMedia.albumArtURL) { image in
image
.resizable()
.frame(width: 300, height: 300)
.aspectRatio(contentMode: .fit)
.cornerRadius(10)
} placeholder: {
RoundedRectangle(cornerRadius: 10)
.fill(Color.purple.opacity(0.5))
.frame(width: 300, height: 300)
.cornerRadius(10)
.redacted(reason: .privacy)
}
VStack(alignment: .center) {
Text(viewModel.shazamMedia.title ?? "Title")
.font(.title)
.fontWeight(.semibold)
.multilineTextAlignment(.center)
Text(viewModel.shazamMedia.artistName ?? "Artist Name")
.font(.title2)
.fontWeight(.medium)
.multilineTextAlignment(.center)
}.padding()
Spacer()
Button(action: {viewModel.startOrEndListening()}) {
Text(viewModel.isRecording ? "Listening..." : "Start Shazaming")
.frame(width: 300)
}.buttonStyle(.bordered)
.controlSize(.large)
.controlProminence(.increased)
.shadow(radius: 4)
}
}

--

--

•23 • 3x WWDC Scholar •GSoC 2020 with VLC •iOS Engineer •Airplane and Space Enthusiast

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Swapnanil Dhol

•23 • 3x WWDC Scholar •GSoC 2020 with VLC •iOS Engineer •Airplane and Space Enthusiast