ShazamKit for NativePHP Mobile#
Identify songs from ambient audio using Apple's ShazamKit — on both iOS and Android — from a NativePHP Mobile app, no JavaScript required.
Overview#
The ShazamKit API arms the device microphone, matches ambient audio against Shazam's catalog, and reports the result — a song match, no match, or an error — as an event a few seconds later. On iOS, a match can also be saved to the user's synced Shazam library.
Installation#
composer require thelemon2020/nativephp-shazamkitphp artisan native:plugin:register thelemon2020/nativephp-shazamkitphp artisan native:plugin:validate
Don't run native:install/native:run yet — an Android build will fail without the AAR below, and neither platform will actually work without the manual setup below. Do both first, then jump to Build.
One-time manual setup (per platform)#
Both of these need Account Holder or Admin on your Apple Developer account, and can't be done from code or CI — they're account-level settings.
iOS — enable the ShazamKit capability on your App ID:
- Certificates, Identifiers & Profiles → Identifiers
- + → App IDs → Continue → register or select your app's App ID
- App Services tab → check ShazamKit → Continue → Register
Android — create a Media ID + private key (this is what produces the three values LocalTokenResolver needs):
- Certificates, Identifiers & Profiles → Identifiers → + → Media IDs → Continue
- Enter a description and a reverse-domain string (e.g.
com.yourcompany.yourapp.media) → Continue - Enable ShazamKit on this Media ID → Register → Done
- Keys section → create a private key with the media services you need → associate it with the Media ID you just created
- Download the
.p8file — one-time only, Apple won't let you re-download it, so save it immediately - Note the Key ID shown after creation, and your Team ID from the Membership page
Team ID → SHAZAMKIT_TEAM_ID, Key ID → SHAZAMKIT_KEY_ID, the .p8 file's contents → SHAZAMKIT_PRIVATE_KEY — all three in your app's .env (see Developer token below for the exact format).
Android SDK download (required before an Android build will compile)#
Unlike iOS (a system framework, nothing to install), Apple distributes the ShazamKit Android SDK as a manually-downloaded AAR — there's no Maven coordinate to depend on, so this can't be automated, and every app that uses this plugin needs its own copy:
- Visit developer.apple.com/shazamkit/android (signed in with your own Apple Developer account) and download
shazamkit-android-release.aar. - Place it at
shazamkit-android-release.aarin the root of your app (the one requiring this plugin — not the plugin's own vendor checkout). That's the default forshazamkit.android_aar_path; setSHAZAMKIT_ANDROID_AAR_PATHin your.envif you'd rather keep it somewhere else. - Move on to Build — the plugin's
copy_assetshook copies the AAR into the generated Android project from that path and declares it as a Gradle dependency automatically when you runnative:install. Without the file present, the hook prints a warning and skips both steps, and the Android build will fail to resolvecom.shazam.shazamkit.
Build#
With the manual setup done, the SHAZAMKIT_* env vars set, and the AAR in place:
php artisan native:install --force
The plugin's copy_assets hook wires the AAR into the generated Android project at this point. Rebuild the same way (native:install --force or native:run) any time you change the AAR's location, or after composer update wipes and re-fetches the plugin.
Usage#
PHP (Livewire/Blade)#
use Thelemon2020\NativephpShazamkit\Facades\ShazamKit; // Arms the mic and starts matching. Returns only whether the session// started — the actual result arrives later as an event (see Events below).ShazamKit::startListening(); // Cancel an in-progress listen. Call this when the user navigates away// from the screen — a session left running keeps the mic (and battery) active.ShazamKit::stopListening(); // iOS only — re-save the last match to the user's Shazam library without// listening again. See the AddedToLibrary / LibraryError events below.ShazamKit::addToLibrary();
JavaScript (Vue/React/Inertia)#
For Inertia + Vue/React apps (or any JS-driven frontend), import directly from this package instead of going through Livewire. Every facade method above is also available as a named export (startListening, stopListening, addToLibrary) if you'd rather not import the whole object.
import { shazamKit } from '@thelemon2020/nativephp-shazamkit'; // Arms the mic and starts matching. Resolves once the session has// started — not once a match is found.await shazamKit.startListening(); // Cancel an in-progress listen.await shazamKit.stopListening(); // iOS only — re-save the last match without listening again.await shazamKit.addToLibrary();
Events#
Matching takes a few seconds, so results don't come back from startListening() directly — they arrive later as one of six events.
In PHP, listen with NativePHP's #[On(...)] attribute (Native\Mobile\Attributes\On). In JavaScript, use NativePHP's core On/Off functions (from #nativephp, installed as part of the core JS library) with this plugin's own ShazamKitEvents constants — the same pattern the core Camera/Microphone/etc. plugins use, so if your app already listens for other native events, this is nothing new.
MatchFound#
Fired when a catalog match is found. On iOS, the match is also written to the user's Shazam library immediately afterward — see AddedToLibrary below.
Payload:
string $titlestring $artist?string $artworkUrl?string $appleMusicUrl?string $isrc?string $shazamId
PHP
use Native\Mobile\Attributes\On;use Thelemon2020\NativephpShazamkit\Events\MatchFound; #[On(MatchFound::class)]public function onMatchFound(string $title, string $artist, ?string $artworkUrl, ?string $appleMusicUrl, ?string $isrc, ?string $shazamId){ // ...}
JavaScript
import { On } from '#nativephp';import { ShazamKitEvents } from '@thelemon2020/nativephp-shazamkit'; On(ShazamKitEvents.MatchFound, (payload) => { // { title, artist, artworkUrl, appleMusicUrl, isrc, shazamId }});
NoMatch#
Fired when listening completes with nothing recognized. No payload.
use Native\Mobile\Attributes\On;use Thelemon2020\NativephpShazamkit\Events\NoMatch; #[On(NoMatch::class)]public function onNoMatch() { /* ... */ }
ListeningError#
Fired when the listen itself fails (not "no match" — an actual error).
Payload:
string $reason— currently always"native_error"; neither platform's native code distinguishes an offline/network-drop failure from any other yet?string $message
use Native\Mobile\Attributes\On;use Thelemon2020\NativephpShazamkit\Events\ListeningError; #[On(ListeningError::class)]public function onListeningError(string $reason, ?string $message) { /* ... */ }
PermissionDenied#
Fired when the user denies the microphone permission prompt. No payload — deep-link to Settings rather than showing a generic error.
use Native\Mobile\Attributes\On;use Thelemon2020\NativephpShazamkit\Events\PermissionDenied; #[On(PermissionDenied::class)]public function onPermissionDenied() { /* ... */ }
AddedToLibrary (iOS only)#
Fired after a match is written to the user's synced Shazam library. There is no Shazam login step and no system permission prompt for this write — Apple still requires the app to tell the user, which is what this event is for.
Android's ShazamKit SDK has no library API at all (the developer token authenticates the app, not a person), so addToLibrary() just resolves false on Android without ever firing this event or LibraryError. No payload.
use Native\Mobile\Attributes\On;use Thelemon2020\NativephpShazamkit\Events\AddedToLibrary; #[On(AddedToLibrary::class)]public function onAddedToLibrary() { /* "Saved to your Shazam library" */ }
import { On } from '#nativephp';import { ShazamKitEvents } from '@thelemon2020/nativephp-shazamkit'; On(ShazamKitEvents.AddedToLibrary, () => { // "Saved to your Shazam library"});
LibraryError (iOS only)#
Fired when the Shazam-library write itself fails.
Payload:
string $reason—"no_match"(addToLibrary()called with nothing to save) or"native_error"(the write failed)?string $message
use Native\Mobile\Attributes\On;use Thelemon2020\NativephpShazamkit\Events\LibraryError; #[On(LibraryError::class)]public function onLibraryError(string $reason, ?string $message) { /* "Couldn't save that" */ }
import { On } from '#nativephp';import { ShazamKitEvents } from '@thelemon2020/nativephp-shazamkit'; On(ShazamKitEvents.LibraryError, (payload) => { // "Couldn't save that" — payload.reason is "no_match" or "native_error"});
Developer token (Android only)#
iOS confirms your app via the ShazamKit App ID capability and needs no runtime token. Android's ShazamKit SDK does — a signed JWT built from an Apple Media Services private key.
By default, the plugin signs this token itself — no backend required. Publish the config, set three env vars from a key you generate in your own Apple Developer account, and you're done:
php artisan vendor:publish --tag=shazamkit-config
SHAZAMKIT_TEAM_ID=...SHAZAMKIT_KEY_ID=...SHAZAMKIT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----..."
Your private key ships inside the app. That's the same tradeoff every mobile app already makes for a client-embedded API credential — it isn't a NativePHP-specific weakness (a key baked into compiled Swift/Kotlin is just as extractable), and Apple scopes ShazamKit developer tokens to your own Team ID, so a leaked one only ever spends your own quota. For most apps this is a perfectly reasonable default, not a shortcut you need to graduate away from.
If you'd rather keep the key off-device entirely — e.g. you already run a backend and want tighter control — implement the resolver interface yourself and bind it over the default. This is an optional extra layer, not something the plugin requires:
use Thelemon2020\NativephpShazamkit\Contracts\DeveloperTokenResolver; class MyBackendTokenResolver implements DeveloperTokenResolver{ public function resolve(): string { // Call your own authenticated backend endpoint, which holds the // Apple Media Services key server-side and signs the JWT there. return Http::withToken(config('services.mybackend.token')) ->get(config('services.mybackend.url').'/shazamkit/developer-token') ->throw() ->json('token'); }}
// In your app's own service provider — Laravel registers app providers// after package-discovered providers like this plugin's, so this// overrides the default binding:$this->app->singleton(DeveloperTokenResolver::class, MyBackendTokenResolver::class);
License#
Proprietary — see LICENSE. Available for purchase and use exclusively through the NativePHP Plugin Marketplace.