kids-tunes
is a sample Apple MusicKit project that demonstrates how to integrate Apple MusicKit with your iOS, Android or web app. A fully-functional sample app is available for each platform, and includes a platform-specific README with a simple walkthrough of the main features.
This README will provide a brief overview and install steps.
Note: as of 6/3/2019 we’ve experienced periodic latency issues and sporadic errors. You may experience similar issues when working with the MusicKit API.
- Android Lollipop (5.0) or later
- Clone or download this project repo to your local system
- Setup the latest version of Android Studio
- Load the project into Android Studio
- Create a new values resource file named tokens.xml inside app/res/values/ with your Apple Developer Token:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="developer_token">YOUR_DEVELOPER_TOKEN</string>
</resources>
- Compile the project
- Load the APK on your Android device. It will not run correctly on a simulator.
In order for the sample app to work correctly with the Apple Music APIs, you will need to generate your own Apple Music ID and Apple Music Keys. These will be tied to an existing Apple Developer account and an active Apple Music Subscription. Only one Apple Music ID is needed, and can be used across all 3 platforms. A unique Apple Music Key will be needed for each environment (Dev, Prod), but can be used across all 3 platforms. Your app will fail to run without these generated and entered into your sample app.
- Go to your Apple Developer Portal
- Navigate to Certificates, Identifiers & Profiles
- Select Identifiers on the left
- Hit the + to Add a new Identifier
- Select Music IDs from the list and hit Continue
- Follow instructions to generate your Music ID
- Go to your Apple Developer Portal
- Navigate to Keys
- Hit the + to Add a new Key for each Environment you need
- Follow instructions to generate your MusicKit Key, selecting the Music ID you generated in the previous section
- This will be used as your Developer Token in the app
This repository provides a starter project, called Kids Tunes, that will allow a user to listen to children's music from their personal Apple Music account. The main features allow you to:
- Sign-in with your Apple Music Subscription Account
- Pull the top songs from the children's music genre in the Apple Music catalog
- Play/Pause/Rewind/Fast Forward songs
- Mark songs as Favorites and add them to a Favorites playlist
Detailed documentation is available in each platform's repository.
This app was created by Assemble Inc. as a demonstration project for WWDC 2019.
Feel free to submit a PR for review.
The kids-tunes
project has 4 main features that interact with Apple Music: Authentication, Pulling Catalog Items, Music Playback, and Playlist Management.
The Apple Music APIs require authentication before they can be accessed. The app will need an Apple Developer Token, and each user of the app will need to login to the Apple Music service to get their Apple Music Subscription User Token.
Your Apple Developer Token will be used as the Bearer Token for all your calls, and must be present. This should be set in your tokens.xml file.
To get the individual User Token, the user will need to authenticate with their Apple Music Subscription login. The Apple Music app needs to already be installed on the device, and will take care of authentication, after being called from the app. After the user is properly authenticated, the user token is returned and saved for use throughout the rest of the session.
class SignInActivity: AppCompatActivity() {
private var authenticationManager = AuthenticationFactory.createAuthenticationManager(this)
...
private fun signIn() {
val intent = authenticationManager.createIntentBuilder(getString(R.string.developer_token))
.setHideStartScreen(true)
.setStartScreenMessage("Connect with apple music!")
.build()
startActivityForResult(intent, REQUEST_CODE_APPLE_MUSIC_AUTH)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_APPLE_MUSIC_AUTH) {
val result = authenticationManager.handleTokenResult(data)
if (result.isError) {
val error = result.error
Log.e(TAG, "error: $error")
}
else {
saveToken(result.musicUserToken)
startMainActivity()
}
}
else {
super.onActivityResult(requestCode, resultCode, data)
}
}
...
companion object {
private const val REQUEST_CODE_APPLE_MUSIC_AUTH = 1
}
}
The Kids Tunes app is specifically set to only pull music from the Apple Music Catalog that is tagged with the 'children's music' genre. For the sake of this sample app, the genre is hard-coded to 'kids'.
You'll first need to set the bearer token on your call to use your Apple Developer Token.
class LocalMediaProvider(private val context: Context,
private val requestQueue: RequestQueue,
private val appleMusicTokenProvider: AppleMusicTokenProvider) {
private var topTracksMetadata = TreeMap<String, MediaMetadataCompat>()
private val authorizationHeaders: MutableMap<String, String>
get() {
val headers = HashMap<String, String>()
headers["Authorization"] = "Bearer ${appleMusicTokenProvider.developerToken}"
return headers
}
private val musicTokenHeaders: MutableMap<String, String>
get() {
val headers = authorizationHeaders
headers["Music-User-Token"] = appleMusicTokenProvider.userToken
return headers
}
private fun requestTopSongs(result: MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>>) {
val url = "https://api.music.apple.com/v1/catalog/us/charts?types=$MEDIA_TYPE_SONGS&genre=$GENRE_KIDS&limit=100&offset=0"
val request = object: StringRequest(
Method.GET, url,
Response.Listener { response ->
doAsync {
val jsonObject = JsonParser().parse(response).asJsonObject
val songsData = jsonObject.get("results").asJsonObject.get("songs").asJsonArray.get(0).asJsonObject
val songs = songsData.get("data").asJsonArray ?: JsonArray()
val mediaItems = ArrayList<MediaBrowserCompat.MediaItem>()
topTracksMetadata = TreeMap()
songs.forEach {
val song = it as JsonObject
val songPlaybackId = song.get("id").asString
val songAttributes = song.get("attributes").asJsonObject
val songName = songAttributes.get("name").asString
...
fullImageSize.toString()).replace("{h}", fullImageSize.toString())
val metadataBuilder = MediaMetadataCompat.Builder()
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, songPlaybackId)
...
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, fullArtworkUrl)
val metadata = metadataBuilder.build()
topTracksMetadata[songPlaybackId] = metadata
val mediaItem = MediaBrowserCompat.MediaItem(metadata.description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
mediaItems.add(mediaItem)
}
uiThread { result.sendResult(mediaItems) }
}
},
Response.ErrorListener {
val mediaItems = ArrayList<MediaBrowserCompat.MediaItem>()
topTracksMetadata.forEach {
val mediaItem = MediaBrowserCompat.MediaItem(it.value.description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
mediaItems.add(mediaItem)
}
result.sendResult(mediaItems)
})
{
override fun getHeaders(): MutableMap<String, String> {
return authorizationHeaders
}
}
request.retryPolicy = DefaultRetryPolicy(
DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * 2,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)
request.setShouldCache(false)
requestQueue.add(request)
}
companion object {
private const val GENRE_KIDS = 4
private const val MEDIA_TYPE_SONGS = "songs"
}
}
Now that you've pulled the list of all the songs from the API, you'll want to be able to control playing the song. You'll use an instance of com.apple.android.music.playback.controller.MediaPlayerController.
val tokenProvider = object : TokenProvider {
override fun getDeveloperToken(): String? = getString(R.string.developer_token)
override fun getUserToken(): String? = keychainService.fetch(KeychainService.KEY_MUSIC_USER_TOKEN)
}
val mediaPlayerController = MediaPlayerControllerFactory.createLocalController(applicationContext, tokenProvider)
val queueProviderBuilder = CatalogPlaybackQueueItemProvider.Builder()
val tracksIds = queueItems.map { it.description.mediaId }
queueProviderBuilder.items(MediaItemType.SONG, *tracksIds.toTypedArray())
queueProviderBuilder.startItemIndex(queueIndex)
mediaPlayerController.prepare(queueProviderBuilder.build(), true)
val tokenProvider = object : TokenProvider {
override fun getDeveloperToken(): String? = getString(R.string.developer_token)
override fun getUserToken(): String? = keychainService.fetch(KeychainService.KEY_MUSIC_USER_TOKEN)
}
val mediaPlayerController = MediaPlayerControllerFactory.createLocalController(applicationContext, tokenProvider)
val queueProviderBuilder = CatalogPlaybackQueueItemProvider.Builder()
val tracksIds = queueItems.map { it.description.mediaId }
queueProviderBuilder.items(MediaItemType.SONG, *tracksIds.toTypedArray())
queueProviderBuilder.startItemIndex(queueIndex)
mediaPlayerController.prepare(queueProviderBuilder.build(), true)
To see more, visit Apple MusicKit Documentation.