ハイライト
ShazamKit に
SHManagedSessionが追加され、マイク録音とオーディオエンジン設定を自動処理。数行のコードで音声認識が可能になりました。SHLibraryは読み書き削除に対応し、デバイス間同期もサポート。SHSessionは PCM フォーマット対応と複数マッチ返却機能が拡張されました。
主要内容
以前: 音声認識にはオーディオエンジンの手動設定が必要
ShazamKit で音声認識を行うには、以前は AVAudioEngine を手動で設定する必要がありました。tap をインストールして PCM バッファを受信し、オーディオエンジンを準備し、録音権限をリクエストし、録音を開始し、バッファを SHSession に渡す—この一連の流れはオーディオプログラミングに不慣れな開発者には扱いにくく、コード量も多くエラーが起きやすいものでした。
現在: SHManagedSession で数行のコード
iOS 17 では SHManagedSession が導入され、録音関連の作業をすべて自動処理します。インスタンスを作成し、result() を呼ぶか results を反復処理し、返された match/noMatch/error を処理するだけです。
SHManagedSession は 2 つのモードをサポートします:
- 単発認識:
result()を呼び出して 1 件の結果を取得 - 継続認識:
resultsasync sequence を反復処理し、長時間のリスニングに適用
cancel() で録音とマッチングを停止します。prepare() でリソースを事前割り当てし、プリレコーディングを開始することで、次の認識リクエストへの応答を高速化できます。
SHLibrary: 読み書き削除を一体化
以前の SHMediaLibrary はマッチ結果の書き込みのみ可能で、有効な Shazam ID を持つエントリのみ書き込めました。新しい SHLibrary は以下をサポートします:
addItems(_:)でメディア項目を追加itemsで現在のアプリが追加したすべてのエントリを読み取りremoveItems(_:)でエントリを削除
読み取りと削除は本アプリが追加したコンテンツのみ操作でき、他のアプリでのユーザーの Shazam 記録は公開されません。SHLibrary は Observable に準拠し、SwiftUI ビューが自動的に更新されます。
より多くのフォーマット対応と複数マッチ
SHSession の matchStreamingBuffer は以前、特定のサンプルレートの PCM バッファのみサポートしていましたが、現在はほとんどのフォーマット設定とより広いサンプルレート範囲に対応し、ShazamKit がフォーマット変換を自動で行います。
カスタムカタログで複数の参照シグネチャが類似している場合、ShazamKit はマッチ品質順にすべてのマッチ結果を返します。mediaItems で必要なエントリをフィルタリングできます。
詳細
単発認識
(06:46)
import ShazamKit
let managedSession = SHManagedSession()
let result = await managedSession.result()
switch result {
case .match(let match):
print("Match found. MediaItemsCount: \(match.mediaItems.count)")
case .noMatch(_):
print("No match found")
case .error(_, _):
print("An error occurred")
}
キーポイント:
SHManagedSession()がマイク権限と録音を自動処理result()は非同期メソッドで、単一の認識結果を返す- 結果は match、noMatch、error の 3 状態
- Info.plist に
NSMicrophoneUsageDescriptionが必須
継続認識
(07:16)
let managedSession = SHManagedSession()
// 継続的にリッスンし、複数の結果を返す
for await result in managedSession.results {
switch result {
case .match(let match):
print("Match found: \(match.mediaItems.first?.title ?? "")")
case .noMatch(_):
print("No match found")
case .error(let error, _):
print("Error: \(error.localizedDescription)")
}
}
キーポイント:
resultsは async sequence で、長時間リスニングに適しているcancel()を呼ぶと async sequence が終了する- 各マッチ結果は独立して処理され、新しいマッチを継続的に受信できる
完全な Matcher 実装
(08:02)
import Foundation
import ShazamKit
struct MatchResult: Identifiable, Equatable {
let id = UUID()
let match: SHMatch?
}
@MainActor final class Matcher: ObservableObject {
@Published var isMatching = false
@Published var currentMatchResult: MatchResult?
var currentMediaItem: SHMatchedMediaItem? {
currentMatchResult?.match?.mediaItems.first
}
private let session: SHManagedSession
init() {
if let catalog = try? ResourcesProvider.catalog() {
session = SHManagedSession(catalog: catalog)
} else {
session = SHManagedSession()
}
}
func match() async {
isMatching = true
for await result in session.results {
switch result {
case .match(let match):
Task { @MainActor in
self.currentMatchResult = MatchResult(match: match)
}
case .noMatch(_):
print("No match")
endSession()
case .error(let error, _):
print("Error \(error.localizedDescription)")
endSession()
}
stopRecording()
}
}
func stopRecording() {
session.cancel()
}
func endSession() {
isMatching = false
currentMatchResult = MatchResult(match: nil)
}
}
キーポイント:
- カスタムカタログで
SHManagedSession(catalog:)を初期化可能 - 引数なしの初期化はデフォルトの Shazam 音楽カタログを使用
@MainActorと@Publishedで SwiftUI が状態変化に自動応答stopRecording()はcancel()を呼び出して録音を停止
ウォームアップ準備
(10:07)
let managedSession = SHManagedSession()
// 事前にウォームアップし、リソースを割り当てて事前録音を開始
await managedSession.prepare()
// ユーザーが認識ボタンをタップしたとき、より速く応答
let result = await managedSession.result()
キーポイント:
prepare()でマッチングに必要なリソースを事前割り当て- プリレコーディングを開始し、リクエストから結果までの時間を短縮
- オプション—ShazamKit は必要時に自動で呼び出す
SwiftUI で状態変化に応答
(11:39)
struct MatchView: View {
let session: SHManagedSession
var body: some View {
VStack {
Text(session.state == .idle ? "Hear Music?" : "Matching")
if session.state == .matching {
ProgressView()
} else {
Button {
// start match
} label: {
Text("Learn the Dance")
}
}
}
}
}
キーポイント:
session.stateは.idle、.prerecording、.matchingの 3 種類SHManagedSessionはObservableに準拠し、SwiftUI が自動更新- idle 状態でボタン、matching 状態でプログレスバーを表示
Shazam Library の読み書き
(15:23)
import ShazamKit
// メディア項目を Shazam Library に追加
func add(mediaItems: [SHMediaItem]) async throws {
try await SHLibrary.default.addItems(mediaItems)
}
// SwiftUI で読み取る
struct LibraryView: View {
var body: some View {
List(SHLibrary.default.items) { item in
MediaItemView(item: item)
}
}
}
// UI 以外のコンテキストで読み取る
func mostPopularGenre() async -> String? {
let currentItems = await SHLibrary.default.items
let genres = currentItems.flatMap { $0.genres }
return highestOccurringGenre(from: genres)
}
// メディア項目を削除
func remove(mediaItems: [SHMediaItem]) async throws {
try await SHLibrary.default.removeItems(mediaItems)
}
キーポイント:
SHLibrary.defaultはシングルトンでデバイス間同期addItemsで書き込み、itemsで読み取り、removeItemsで削除- 本アプリが追加したエントリのみ操作可能
Observableに準拠し、SwiftUI List が自動更新
複数マッチ結果のフィルタリング
(20:23)
func match(from televisionShowCatalog: SHCustomCatalog) async -> [SHMatchedMediaItem] {
let managedSession = SHManagedSession(catalog: televisionShowCatalog)
let result = await managedSession.result()
if case .match(let match) = result {
// 特定エピソードのメディア項目をフィルタ
let filteredMediaItems = match.mediaItems.filter { $0.title == "Episode 2" }
return filteredMediaItems
}
return []
}
キーポイント:
- カスタムカタログで類似音声は複数マッチを返す
match.mediaItemsはマッチ品質順にソートfilterでメタデータに基づき必要なエントリを選択- カタログ生成時にメタデータを適切に付与し、後のフィルタリングを容易に
重要ポイント
1. 音楽認識ソーシャルアプリ
- 何を作るか: 友人との集まりで再生中の曲を自動認識し、共有プレイリストを生成
- なぜ価値があるか:
SHManagedSessionで数行のコードで認識可能、SHLibraryが全デバイスに自動同期 - 始め方:
SHManagedSessionで継続認識し、マッチ成功後にSHLibrary.default.addItemsで保存、SwiftUI でSHLibrary.default.itemsを表示
2. ダンス/フィットネス追従アプリ
- 何を作るか: 背景音楽を認識し、対応するダンスレッスンやフィットネス指導動画を自動読み込み
- なぜ価値があるか: デモの「ダンスを学ぶ」シナリオがそのまま使える、Managed Session が録音を自動処理
- 始め方:
SHManagedSessionで曲を認識し、match.mediaItems.firstから曲情報を取得、曲 ID で動画リソースを検索
3. テレビライブコンテンツ認識
- 何を作るか: テレビで放送中の番組を認識し、リアルタイムのあらすじや出演者情報を表示
- なぜ価値があるか: カスタムカタログが複数マッチ返却に対応、同じオープニングでも異なるエピソードを区別可能
- 始め方: Shazam CLI でテレビ番組カタログを生成し、各エピソードに異なるメタデータを付与、
filterで現在のエピソードを選択
4. ライブ公演セットリスト生成器
- 何を作るか: コンサートやフェスで演奏された各曲を自動記録し、セットリストを生成
- なぜ価値があるか:
resultsasync sequence で公演全体を継続リスニング可能、SHLibraryで記録を保存 - 始め方:
for await result in session.resultsで継続認識し、重複を除去してタイムスタンプ付きセットリストを生成
関連セッション
- Create custom catalogs at scale with ShazamKit — 大規模カスタム音声カタログの構築
- Add SharePlay to your app — SharePlay 共有体験
- Discover Continuity Camera for tvOS — tvOS の Continuity Camera
コメント
GitHub Issues · utterances