ShazamKitで曲を検索してみた🐙
Permission
ShazamKitで曲検索を行う際流れている音楽の詳細を聞きたいはずなのでマイクのパーミッションを許可してあげる必要があります。
Privacy – Microphone Usage Description
func session(_ session: SHSession, didFind match: SHMatch)
一致する曲を見つけた際こちらのデリゲートにイベントが飛んできます。
SHMatchのmediaItemsには配列で返されるのでめちゃくちゃ類似している曲の場合もしかしたら複数返されることがありそうです。似ている曲ソングみたいな動画で複数検出されるか試してみましたが手元では一件しか検出されませんでした。
SHMatchedMediaItemの中でとれるそれぞれのパラメータですがすべてoptionalなので入ってるかしっかり確認が必要です。曲名とアーティスト名が空になることはなかったのですが、アルバムの画像は結構な確率で空になっていることが多かったです。
エラー
func session(_ session: SHSession, didNotFindMatchFor signature: SHSignature, error: Error?)
セッション中のエラーは次のメソッドで受け取ることができます。
func session(_ session: SHSession, didNotFindMatchFor signature: SHSignature, error: Error?)
errorがnilで返された際はShazamKitでの検索結果のが存在しなかった場合です、なんらかのエラーが発生した場合はerrorの中に値が入るそうですが手元で試している感じでは発生しませんでした。
また音楽の再生速度をいじっている場合は一致せずに検索結果が見つからないエラーが帰ってきました。
Identifierを登録していない場合
Optional(Error Domain=com.apple.ShazamKit Code=202 “Please check that you have enabled the ShazamKit App Service for this app identifier” UserInfo={NSDebugDescription=Please check that you have enabled the ShazamKit App Service for this app identifier})
コード
import UIKit
import ShazamKit
class ViewController: UIViewController {
@IBOutlet var titleLabel: UILabel!
@IBOutlet var infoLabel: UILabel!
@IBOutlet var albumImage: UIImageView!
@IBOutlet var searchButton: UIButton!
private let audioEngine = AVAudioEngine()
private let session = SHSession()
private let signatureGenerator = SHSignatureGenerator()
private let audioSession = AVAudioSession.sharedInstance()
private lazy var inputNode = self.audioEngine.inputNode
override func viewDidLoad() {
super.viewDidLoad()
session.delegate = self
}
@IBAction func searchStart(_ sender: Any) {
start()
}
}
extension ViewController {
private func start() {
titleLabel.text = ""
infoLabel.text = ""
albumImage.image = nil
audioSession.requestRecordPermission { [self] granted in
if granted {
self.record()
} else {
DispatchQueue.main.async {
self.titleLabel.text = "パーミッションが許可されていません"
}
}
}
}
private func stop() {
searchButton.isHidden = false
inputNode.removeTap(onBus: AVAudioNodeBus(0))
audioEngine.stop()
}
private func record() {
do {
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
let recordingFormat = self.inputNode.outputFormat(forBus: 0)
self.inputNode.installTap(onBus: 0,
bufferSize: 1024,
format: recordingFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
try! self.signatureGenerator.append(buffer, at: nil)
self.session.matchStreamingBuffer(buffer, at: nil)
}
self.audioEngine.prepare()
try self.audioEngine.start()
DispatchQueue.main.async {
self.searchButton.isHidden = true
self.titleLabel.text = "検索中.."
}
} catch {
self.titleLabel.text = "録音中にエラーが発生しました"
}
}
}
extension ViewController: SHSessionDelegate {
func session(_ session: SHSession, didFind match: SHMatch) {
guard let matchedMediaItem = match.mediaItems.first else {
return
}
DispatchQueue.main.async {
if let title = matchedMediaItem.title {
self.titleLabel.text = "曲名: \(title)"
}
if let artist = matchedMediaItem.artist {
self.infoLabel.text = "アーティスト: \(artist)"
}
if let url = matchedMediaItem.artworkURL {
self.albumImage.image = UIImage(data: try! Data(contentsOf: url))
}
self.stop()
}
}
func session(_ session: SHSession, didNotFindMatchFor signature: SHSignature, error: Error?) {
DispatchQueue.main.async {
if let error = error {
print("error: \(error)")
self.titleLabel.text = "検索中にエラーが発生しました"
} else {
self.titleLabel.text = "該当する曲がみつかりませんでした"
}
self.stop()
}
}
}