Voice
Voice requests — Siri on iOS and Google Assistant on Android — arrive through a single handler: onPlayFromSearchRequest. You resolve the structured request into tracks; the library renders the result.
The handler
Register it via registerPlaybackService (from your bootstrap module):
import { getTrackPlayer, registerPlaybackService } from 'react-native-queue-player';
registerPlaybackService(() => ({
onPlayFromSearchRequest: async (request) => {
// request.query is the free-text utterance; structured fields
// (artist, album, song, genre, type, playShuffled, …) are populated
// when the assistant parsed them. request.origin is 'ios-siri' or
// 'android-assistant'.
const tracks = await resolve(request); // -> TrackItem[]
if (request.origin === 'ios-siri') {
// iOS: drive playback yourself, then return the tracks.
const player = getTrackPlayer();
await player.setQueue(tracks);
await player.play();
}
// Android: returning the tracks starts playback automatically.
return tracks; // an empty array signals "no match" (Siri shows its generic failure)
},
}));
Why the platform split: this applies only to voice play-from-search and comes from the platform frameworks. On Android, Media3 applies and plays the list you return. On iOS, the SiriKit INPlayMediaIntent flow has your handler set the queue and start playback before returning, so Siri can report the result. This is specific to resolving a voice request — it does not affect shuffleQueue() or the other transport calls, which behave the same on both platforms. For resume-style intents (request.resumePlayback === true or request.reference === 'currentlyPlaying'), return [] and call player.play() directly.
MediaSearchRequest carries query plus optional type ('artist' | 'album' | 'song' | 'playlist' | 'genre'), artist, album, song, playlist, genre, origin, reference, playShuffled, playbackRepeatMode ('none' | 'one' | 'all'), playbackSpeed, playbackQueueLocation ('now' | 'next' | 'later' — play the results now, or queue them to play next / at the end), releaseDate ({ startYear?, endYear? }, an inclusive year range), and resumePlayback — branch on origin before reading platform-specific fields.
iOS — Siri
Enable the plugin flag:
{ "expo": { "plugins": [["react-native-queue-player", { "siri": true }]] } }
This adds the Siri entitlement, the INPlayMediaIntent Info.plist keys, and an in-app intent handler (the integration uses the in-app application(_:handlerFor:) path — there is no separate Intents extension to create). iOS calls application(_:handlerFor:) only in apps that support multiple scenes, so the plugin also sets UIApplicationSupportsMultipleScenes to true in the scene manifest. It requires Expo SDK 58 or later; run npx expo prebuild --clean after enabling or changing the flag.
Siri can launch the app without its phone UI. The plugin's start-up code then starts React Native without a window on screen, so your handler runs, and moves that same root into the phone window if the user opens the app afterwards. See How the app starts from CarPlay or Siri and its notes (dev-client builds wait for Metro, links, initialProperties, colour scheme). A Siri request that arrives before your handler is registered is held (the latest one, for up to 20 seconds) and delivered when the handler registers, so register it from your bootstrap module.
Then:
- Override the placeholder
NSSiriUsageDescription/NSAppleMusicUsageDescriptionstrings inios.infoPlistwith your own copy. - For a local Xcode build without automatic signing, enable the Siri capability once in the target's Signing & Capabilities pane (EAS Build does this automatically).
Bare React Native: there's no plugin — add the com.apple.developer.siri entitlement + Siri capability, the SiriKit Info.plist keys, and the application(_:handlerFor:) handler in your AppDelegate by hand (see the bare-RN setup table). Set UIApplicationSupportsMultipleScenes to true and add the start-up code from the CarPlay guide's bare React Native section, leaving out the CarPlay scene entry if you don't use CarPlay.
Improve recognition with vocabulary
Seed Siri with the names it should recognize (iOS only; a no-op on Android).
Donation is opt-in and off by default. Turn it on at configure():
await getTrackPlayer().configure({
voiceVocabularyDonationEnabled: true,
});
getTrackPlayer().donateVoiceVocabulary({
artists: ['Pearl Jam', 'Radiohead'],
playlists: ['Chill', 'Focus'],
});
Without the flag, donateVoiceVocabulary does nothing.
:::danger Enable this only with the Siri capability
voiceVocabularyDonationEnabled: true requires the com.apple.developer.siri
entitlement — Xcode's Siri capability — not just NSSiriUsageDescription
in your Info.plist. INVocabulary checks for the entitlement at runtime and
raises an Objective-C exception without it, which cannot be caught from Swift
and terminates the app.
iOS gives a process no way to read its own entitlements, so the library cannot
check this for you — the flag is your assertion that the capability is on the
target. If you are unsure, leave it false.
:::
The value is read once, at the first configure(), and fixed for the life of
the process. The entitlement it stands for is decided when your app is signed,
so a later change could not mean anything; a re-configure() with a different
value is ignored rather than re-arming the crash path above.
Phonetic aliases for hyphenated names
Hyphenated or initialism app names ("RNQP-Demo", "MyMu-Pro") are hard for speech recognition. Add INAlternativeAppNames to your Info.plist so Siri matches spoken aliases (the plugin does not set this for you):
{
"expo": {
"ios": {
"infoPlist": {
"INAlternativeAppNames": [
{ "INAlternativeAppName": "My Music" },
{
"INAlternativeAppName": "Music Pro",
"INAlternativeAppNamePronunciationHint": "My Mu Pro"
}
]
}
}
}
}
Android — Google Assistant
"Play … on YourApp" reaches onPlayFromSearchRequest through the media session when your app owns the active session — no extra setup beyond the handler above.
For richer, app-name-explicit recognition and cold-start launching, add Android App Actions in your app: a shortcuts.xml declaring actions.intent.PLAY_MUSIC, a BROWSABLE deep link, and a route that parses the deep link into a MediaSearchRequest and dispatches it to your resolver. This is configured on the app side; the library does not generate it.
Related
- Setup & manual configuration
- CarPlay / Android Auto — the browse handler model