November 1, 2021

6 minute read

Listening now: Apple Music

At the bottom of this page you can find the latest song I've listened to on Apple Music. Here's how I made it works.

This whole thing requires that you have an active paid Apple Developer account. It's $100/yr and is not worth it unless you plan on using it for something else besides "current music"

Apple Music is my primary music service. I do listen to Spotify every now and again (their recommendation engine is unmatched), but being an Apple household, it makes sense to mostly use Apple Music.

When I started working on this blog, I took a lot of inspiration from Lee Robinson. He has this little Spotify widget displaying the current song, and while it was easy to adapt and use it, it was not what I wanted. What I wanted was a widget displaying the last song I listened to on Apple Music. Which, apparently, is not trivial.

Apple Music API

For the longest time, I couldn't find a way to use Apple's MusicKit or Music API on the web. Their developer documentation is pretty poor, especially when it comes to the web stuff. At last, I figured that I'm looking for the user listening history, and there's an API endpoint for that.

This endpoint requires two different authorization tokens: the developer token & user music token. With the Developer token, one could query and perform actions within the Music store. And with the user music token, one could query and perform actions within the user's music library. So, how do you get those?

Getting tokens

Now, the deal with the two tokens is that they are totally different, unrelated, yet somewhat connected: You need both in order to fetch music history.

The developer token is a JWT that you build yourself, and sign with a private key you get at the Apple developer portal. More on this here.

The user music token appears to be some kind of opaque token, which you can only get after a user explicitly logs into their Apple Music account and grants your app access to their Apple Music account. So far, I was unable to find a way to generate or obtain this token programmatically. What's worse, this token expires after 6 months (fixed) and there's no way to renew it.

This last bit matters because, in this whole equation, you can generate the developer token yourself. You can obtain the user music token. But from here, you cannot update the user token, so in 6 months you'll have to generate a new one, in the browser, by logging in, manually, as some sort of animal.

Finally, it's a quest to figure out how to get this user token, and crappy docs don't really help. Here's how you do it:

  • Scaffold an HTML page with a <script> tag pointing to the MusicKit script
  • Add the "Sign in with Apple" button to the page (here's how)
  • Deploy the app (to Vercel or Netlify, or wherever, otherwise you'd have to work around a bunch of issues)
  • Log in on this newly deployed page, grant it access to your Apple Music account
  • Open Developer Tools, and in the "Storage" section find the token OR add <script>console.log(response)</script> to the page and just copy the token from the console.

Not very intuitive, but it works. Now you have 6 months to use this token, and after that, you'd need to do the same thing again.

As for the Developer token, I used Auth0's jsonwebtoken library. It allows you to both sign and verifies JWT tokens. I store my private and public keys in environment variables, and sign the token like this:

import jwt from 'jsonwebtoken'

const generateToken = async (): string => {
  const privateKey = process.env.PRIVATE_KEY
  const keyId = process.env.KEY_ID
  const teamId = process.env.TEAM_ID

  const developerToken = jwt.sign({}, 
    `-----BEGIN PRIVATE KEY-----\n` + 
    privateKey + 
    `\n-----END PRIVATE KEY-----`, { 
      algorithm: 'ES256', 
      keyid: keyId, 
      issuer: teamId, 
      expiresIn: '5s'
    });

  return developerToken;
}

That's about it. Now, that you've got both tokens, everything else is pretty straightforward.

Fetching Last Played songs

The API itself is fairly conventional. The only specific thing about it is that it's poorly documented (you don't know for sure what shape the response object will be for different resources) and the fact that you need to supply both Bearer token for Authorization header and a user-music-token as a different header.

With both tokens in hand, it might look something like this:

In response you'll get something like this:

Keep in mind that this shape is not set in stone, and some songs will have all these attributes, some will not (at least that was the case for me).

Showing off

Finally, you'd want to show visitors what you've got. This is the easy part (haha).

I'm a huge fan of SWR - Stale While Revalidate. It's a great way to cache data, refetch stale data, and just generally have a robust way of updating things every now and then without reloading the page or requiring a user action.

Here's the most basic example:

Here I request that SWR refetches new data regularly, refetches it on focus (if a user scrolls away from the Now Playing component, or switches to a different tab and comes back), and refetches data on reconnect (if the user's connection is lost, and reconnects).

The `fetcher` function is a simple wrapper around `fetch` that returns a promise:

I pass the data to the Track component for rendering:

That's about it. The result looks like this:

That's about it. Oh, I've also went ahead and spun up a repo for this specific thing, feel free to check it out: current-apple-music. This one generates an image of the current song playing on Apple Music that you can use in your website or in your Github profile's README ;)

Comments

(Commenting feature coming soon)