ShazamKit Tutorial for iOS: Getting Started
Learn how to use ShazamKit to find information about specific audio recordings by matching a segment of that audio against a reference catalog of audio signatures. By Saleh Albuga.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
ShazamKit Tutorial for iOS: Getting Started
30 mins
- Getting Started
- Understanding Shazam’s Matching Mechanism
- Shazam Signatures
- Shazam Catalogs
- Matching Music Against Shazam’s Catalog
- Exploring ShazamKit Sessions
- Displaying the Matched Song
- Testing The App
- Working With Custom Catalogs
- Shazam Signature Files
- Creating a Custom Catalog
- Matching Audio Against a Custom Catalog
- Synchronizing App Content With Audio
- Implementing the Annotations
- Displaying the Synchronized Annotations
- Testing the App
- Where to Go From Here?
Exploring ShazamKit Sessions
There are two steps left before you wire-up the UI. First, you need to implement SHSessionDelegate
to handle matching successes and failures.
Add the following class extension at the end of MatchingHelper.swift:
extension MatchingHelper: SHSessionDelegate {
func session(_ session: SHSession, didFind match: SHMatch) {
DispatchQueue.main.async { [weak self] in
guard let self = self else {
return
}
if let handler = self.matchHandler {
handler(match.mediaItems.first, nil)
// stop capturing audio
}
}
}
}
In this extension, you implement SHSessionDelegate
.
SHSession
calls session(_:didFind:)
when the recorded signature matches a song in the catalog. It has two parameters: The SHSession
it was called from and an SHMatch
object that contains the results.
Here, you check if the matchHandler
is set, and you call it passing the following parameters:
-
The first
SHMatchedMediaItem
of the returnedmediaItems
inSHMatch
: ShazamKit might return multiple matches if the query signature matches multiple songs in the catalog. The matches are ordered by the quality of the match, the first having the highest quality. - An error type: Since this is a success, you pass nil.
You’ll implement this handler block in SwiftUI in the next section.
Right after session(_:didFind:)
, add:
func session(
_ session: SHSession,
didNotFindMatchFor signature: SHSignature,
error: Error?
) {
DispatchQueue.main.async { [weak self] in
guard let self = self else {
return
}
if let handler = self.matchHandler {
handler(nil, error)
// stop capturing audio
}
}
}
session(_:didNotFindMatchFor:error:)
is the delegate method SHSession
calls when there’s no song in the catalog that matches the query signature or when an error that prevents matching occurs. It returns the error that occurred in the third parameter or nil
if there was no match in the Shazam catalog for the query signature. Similar to what you did in session(_:didFind:)
, you call the same handler block and pass in the error.
Finally, to adhere to Apple’s microphone use guidelines and protect user privacy, you need to stop capturing audio when any of the two delegate methods are called.
Add the following method right after match(catalog:)
in the main body of MatchingHelper
:
func stopListening() {
audioEngine.stop()
audioEngine.inputNode.removeTap(onBus: 0)
}
Then, call stopListening()
in both delegate methods above. Replace the following comment:
// stop capturing audio
with:
self.stopListening()
Next, you’ll display the matching result.
Displaying the Matched Song
The final part of your Shazam clone is the UI. Open SongMatchView.swift and check the preview in the canvas:
The view consists of two parts. The top part with the rounded green square is where you’ll show the song info. The bottom part has the Match button that starts the matching process.
First, you need a MatchHelper
object. At the top of SongMatchView
, add:
@State var matcher: MatchingHelper?
Then, at the end of the view struct
, right after body
, add:
func songMatched(item: SHMatchedMediaItem?, error: Error?) {
isListening = false
if error != nil {
status = "Cannot match the audio :("
print(String(describing: error.debugDescription))
} else {
status = "Song matched!"
print("Found song!")
title = item?.title
subtitle = item?.subtitle
artist = item?.artist
coverUrl = item?.artworkURL
}
}
songMatched(item:error:)
is the method that MatchingHelper
calls when it finishes matching. It:
- Sets
isListening
tofalse
. As a result, the UI updates to show the user that the app is not recording anymore and hides the activity indicator. - Checks the
error
parameter. If it isn’t nil, there was an error so it updates the status the user sees and logs the error to the console. - If there was no error, it tells the user it found a match and updates the other properties with the song metadata.
SHMatchedMediaItem
is a subclass of SHMediaItem
. It inherits a media item’s metadata properties for the matched items, like title of the song, artist, genre, artwork URL and video URL.It also has other properties specific to matched items like
frequencySkew
, the difference in frequency between the matched audio and the query audio.
Next, at the end of NavigationView
, add:
.onAppear {
if matcher == nil {
matcher = MatchingHelper(matchHandler: songMatched)
}
}
.onDisappear {
isListening = false
matcher?.stopListening()
status = ""
}
Here you instantiate the MatchHelper
passing the handler you just added when the view appears. When the view disappears, for example, when you switch to another tab, you stop the identification process by calling stopListening()
.
Finally, locate the Match button code, as below:
Button("Match") {
}
.font(.title)
In the button action block, add:
status = "Listening..."
isListening = true
do {
try matcher?.match()
} catch {
status = "Error matching the song"
}
This is where the magic starts. You change status
to tell the user the app is listening and call match()
to start the matching process. When SHSession
returns a result to MatchingHelper
, it calls songMatched(item:error:)
.
Next, you’ll test the app.
Testing The App
After that, set the bundle identifier and signing settings accordingly.
After that, set the bundle identifier and signing settings accordingly.
To try matching a song, build and run the app on an iPhone.
Open the following YouTube link to play the song. Can you guess the song?
Tap Match and bring your iPhone closer to the speakers. A few seconds later, you’ll see the match:
Hooray! The app successfully matched the song.
Working With Custom Catalogs
You learned how to use ShazamKit to match audio against the Shazam catalog. What if you wanted to match your music compositions or video content? ShazamKit’s got you covered.
Now you’ll implement the remaining part of DevCompanion, the Video Content tab. You’ll start by matching audio against a custom catalog you’ll create.
The app will identify the intro video of Your First iOS and SwiftUI App: An App From Scratch video course. This is an amazing free video course for learning the fundamentals of SwiftUI.
Before you do that, you need to learn more about Shazam Signature files.