Apple Music JavaScript integration guide
Tags: webdev, javascript, apple
Published: 2020-12-03 21:24:10

We all know  **music streaming**  is booming right now. Because of the sheer convenience, and ease-of-use users have fallen in love with this modern way of listening to music.

But what does it mean for web developers? Well, unless you're Spotify, Apple, Amazon, or similar, your chances of competing in  _"streaming wars"_  are relatively low. But what if your goal is different? What if, instead of a streaming platform itself, you want to make a  **music player**,  **add-on service**, or something like that? In this case, why not let  _"the big players"_  help you?

In today's post, I'd like to walk you through the process of integrating  **Apple Music**  right into your web app with Apple's  **MusicKit JS**. Sounds interesting?

# Why Apple Music?

Before we dive in, I'd like to address the possible question of:

> Why did I choose Apple Music?

You might already know that  **Spotify**  is  **the market leader**. It has by far the largest user base while also having the highest number of paid subscribers (although the latter is much smaller). Apple Music lags behind Spotify in both categories, landing in  [2nd place](https://9to5mac.com/2020/10/29/spotify-subscribers-q3-2020/).

So, if we were choosing based-off user-base, we should go for Spotify. However, there's something more you have to keep in mind. That is the service's  **public API**.

In this aspect, Apple, with its  **MusicKit**, is definitely on top. It allows you to integrate with Apple Music right from your iOS, Android, or web app through easy-to-use libraries.

If you're interested, Spotify does provide similar functionality through its  [**Web Playback SDK**](https://developer.spotify.com/documentation/web-playback-sdk/). Still, it's currently in beta with limited support, works only for the paid subscribers, and requires Spotify's written approval before being used in commercial products. That's far from Apple's offering, but still quite good considering most of (if not all) the other streaming services don't provide such an option at all.

# Preparations

Before getting to work with MusicKit JS, we'll have to do some preparations. That is, get ourselves an  **Apple Music API Token**.

Surprisingly, this can be a bit daunting of a task, especially with Apple's  [pretty convoluted documentation](https://developer.apple.com/documentation/applemusicapi/getting_keys_and_creating_tokens).

First and foremost, you'll need to be a member of the  [**Apple Developer Program**](https://developer.apple.com/programs/)**.**  It'll require elaborate sign-up and a payment of  **100 USD/year**  fee.

With that done, we'll need to go to the Apple Developer dashboard and collect a few details:

1.  From the  _Membership_  tab, get your  **Team ID**.
2.  Next, we'll need a  **MusicKit Identifier**  (one per app):
    -   Go to the  _Certificates, Identifiers & Profiles_  tab.
    -   There, enter the  _Identifiers_  tab.
    -   Click the  _"+"_  button next to the header (if you're in a team, it requires  _Admin_  or  _Account Holder_  permissions to show up).
    -   Enter your App name and "reverse-domain-style" name for the Identifier and hit  _"Continue"_, then  _"Register"_.
3.  With proper Identifier already set up, we can now create a  **Private Key**  to access the MusicKit service:
    -   From  _Certificates, Identifiers & Profiles_, go to the  _Keys_  tab (visible only for Admin or Account Holder)
    -   Click the  _"+"_  button, enter required details and check the  _MusicKit checkbox_  (if you have multiple MusicKit Identifiers, you'll need to select one of them)
    -   Click  _"Confirm"_  and download the  **.p8 key file**  (it's downloadable only once, so keep it safe!)
4.  The file's content is your Private Key, while the file's name should contain the  **Key ID**  as the last ten characters in the file name (right after the underscore). If you changed the filename, Key ID should also be available in the Developer dashboard.

Quite a lot of stuff here, and yet we're still not done.

Only now, with all the details, we can finally generate the  **JSON Web Token**  (JWT) required to access the service.

For that, we'll use some JS.  **NodeJS**  to be specific with  [**jsonwebtoken**  module](https://www.npmjs.com/package/jsonwebtoken), and the following code:

```javascript
const fs = require("fs");
const jwt = require("jsonwebtoken");

const privateKeyPath = "./AuthKey_12345ABCDE.p8";
const privateKey = fs.readFileSync(privateKeyPath).toString();
const teamId = "ABCDE12345";
const keyId = "12345ABCDE";
const token = jwt.sign({}, privateKey, {
  algorithm: "ES256",
  expiresIn: "180d",
  issuer: teamId,
  header: {
    alg: "ES256",
    kid: keyId,
  },
});

console.log(token);
```

With the above code, we generate and output the required JWT token to the console.

On a side-note, how you'll manage your JWT tokens is up to you. In our case, we'll need only a single token for the demonstration purposes, and that's why it's set to be valid for  **180 days**  (max value).

# Integration

Alright, so it was quite a lot of work to get the token, but it'll only get easier from here. Let's finally do some coding!

## Installation

First, we have to "install" the MusicKit JS library. The only  [official way](https://developer.apple.com/documentation/musickitjs/)  to do so is through a  `<script>`  tag pointing to Apple's CDN:

```html
<script src="https://js-cdn.music.apple.com/musickit/v1/musickit.js"></script>
```

Now, to some web devs out there (including me), this might feel a bit dated. To ease-out the development process, you might look for some 3rd-party NPM module-based integration, but I preferred to leave it as is, with some  **TypeScript typings**  on top.

If you're a TypeScript user, typings can help you navigate through any library much easier. Sometimes better than the official documentation!

As for MusicKit JS, there are some typings  [openly available](https://github.com/wsmd/musickit-typescript). They aren't perfect and a bit dated but could be helpful nonetheless.

## Initialization

With the library installed, we should now initialize it. There are two ways to do it - let's call them "the HTML" and "the JS way".

### HTML way

In the first one, you're giving more control to the library by simply letting it know what to do through a couple of  **HTML tags**. For the setup, just add some  `<meta>`  tags to the  `<head>`:

```html
<meta name="apple-music-developer-token" content="DEVELOPER-TOKEN">
<meta name="apple-music-app-name" content="MusicKit Web App">
<meta name="apple-music-app-build" content="1.0.0">
```

After that, the user will need to  **authorize**  with Apple Music, which the library can also handle for you if you simply show it which elements it should attach to through different  `id`s:

```html
<button id="apple-music-authorize"></button>
<button id="apple-music-unauthorize"></button>
```

### JS way

While the HTML setup can be convenient, I prefer a bit more verbose way, where I feel more in control of what's happening. That's why, from this point on, I'll cover JS exclusively, but you can find more about HTML way in the  [official docs](https://developer.apple.com/documentation/musickitjs/adding_musickit_features_using_html).

First, you need to listen to the  `musickitloaded`  on the  `document`  element, to know when the library is  **ready for use**. Personally, I prefer to wrap it in a  **Promise**, to limit the potential of  _"callback hell"_  and achieve cleaner code:

```javascript
const setupMusicKit = new Promise((resolve) => {
  document.addEventListener("musickitloaded", () => {
    const musicKitInstance = window.MusicKit.configure({
      developerToken: "DEVELOPER-TOKEN",
      app: {
        name: "MusicKit Web App",
        build: "1.0.0",
      },
    });
    delete window.MusicKit; // clear global scope
    resolve(musicKitInstance);
  });
});
```

When we know that the library has loaded, we use the  `configure()`  method of the global MusicKit object to create and configure an actual MusicKit instance that we'll use later on (similarly to the  `<meta>`  tags in the HTML setup).

In the code above, it's this instance that I resolve from the promise. I also clean the global scope from the MusicKit object, as it'll no longer be necessary.

To authorize the user, we can use the  `authorize()`  method, which returns a promise for when the process is done.

```javascript
// ...
setupMusicKit.then(async (musicKit) => {
    try {
	await musicKit.authorize(); 
    // await musicKit.unauthorize(); 
    } catch(error) {
    	// Handle cases when authorization fails
    }
})
```

## Usage

At this point, the documentation and optional typings should be your guide. MusicKit JS gives you access to pretty much  **all the functionalities**  you get from the  **Apple Music app**  itself - controlling music playback, searching through Apple Music's catalog, manipulating user's personal library & more!

With that said, let's take a look at a few example use-cases of MusicKit JS possibilities.

### Playing one of the user's playlists

```javascript
// ...
// null indicates fetching all the playlists (within pagination limit)
const playlists = await musicKit.api.library.playlists(null);
const randomIndex = Math.floor(Math.random() * playlists.length);
// pick random playlist
const playlistId = playlists[randomIndex].id;
// set playback queue
await musicKit.setQueue({
  playlist: playlistId,
});
// start playing (playing audio usually requires user interaction)
await musicKit.play();
// ...
``` 

### Controlling music playback

```javascript
// ...
const play = () => {
  return musicKit.play(); // promise
};
const pause = () => {
  appleMusic.pause();
};
const next = () => {
  return appleMusic.skipToNextItem(); // promise
};
const previous = () => {
  appleMusic.skipToPreviousItem(); // promise
};
// ...
```

It's important to note that there seems to be an  **error**  with the MusicKit library while moving to the previous song (`skipToPreviousItem()`). It results in an error being thrown, as well as an  **error dialog**  with a message like:

> A play stop() method was called without a previous play() descriptor

Apart from false warnings, everything should be working fine. So, as it's a library error, your best choice would be to  **hide the dialog**  with some CSS:

```css
#musickit-dialog-scrim,
#musickit-dialog {
  display: none;
}
```

### Searching through Apple Music's catalog

```javascript
// ...
const { songs } = await appleMusic.api.search("Counting Stars", {
  limit: 1,
  types: "songs",
});
if (songs && songs.data[0]) {
  await appleMusic.playNext({ song: resources.songs.data[0].id });
}
// ...
```

When using  `search()`  as well as other querying methods, it's worth knowing that they accept a parameter called  `queryParameters`. It's an object of parameters that are passed to the API call underneath, in line with  [**Apple Music API**  docs](https://developer.apple.com/documentation/applemusicapi)  (which are separate from MusicKit JS docs). Again, Apple's docs are pretty convoluted, so you'll need to find your way around.

# There's more...

So, these are only a few common examples of what you could do with MusicKit JS. It gives you broad access to Apple Music's potential, and we've only scratched the surface. Now you can go explore more and build your own Apple Music integration!

If you've got any questions or got stuck in the docs - feel free to  **leave a comment**  down below, and I'll try to help.

For more web development goodness,  **follow me**  on  [Twitter](https://twitter.com/areknawo),  [Facebook](https://www.facebook.com/areknawoblog), or simply check out [my personal blog](https://areknawo.com). Also, consider checking out my  [YouTube channel](https://www.youtube.com/channel/UCUw6bg379ONlG1E_oxhwi-A/)  (it's making a come-back!) and  **subscribe**  over there. Thanks for reading and have a good listen... sorry, coding!