Published at Addison.Codes on January 16, 2024

How I Built A Spotify "Now Playing" Component

How I built out a "Now Playing" component that will automatically update with what I'm listening to on Spotify.

Making it Personal

I wanted to add some things to my site to make it more personal and pretend I'm back in the Myspace days. I've seen other folks include a Spotify playlist of their favorite tracks and I liked the idea of it. There aren't many artists, albums, or tracks that I love enough to want to show off though, so I wanted to show what I'm currently listening to or have been listening to recently. This also means I'd have to use the Spotify API instead of just embedding a Spotify player - great way to showcase that I'm capable of dealing with API's.

Project Overview

Component Definition: I want a small card that shows my currently playing track on Spotify or, if I'm not listening to anything, a track that I've been listening to a lot recently.

API: We'll need to access the Spotify API to get this data. Getting a user's top tracks and getting your currently playing track are both easy, but authenticating your webapp to have it forever tied to your account involves a few steps.

Connection: Once we receive the data from the API, we'll have to pull it into the frontend!

Component Definition

Since my site is a Next.js site that uses Tailwind for styling. That said, we have three different states this component can be in:

1. Loading

2. Displaying Now Playing

3. Displaying Top Track

Loading

Very basic - just including the basic layout of the component with placeholders for the data.

.tsx

1<div className="p-8 rounded-lg shadow-md bg-primary w-80">
2  <h3 className="mb-6 text-lg font-bold text-center text-black">
3    Loading...
4  </h3>
5  {/* Song Title */}
6  <h2 className="text-2xl font-bold text-center text-accent">
7    <a href={'#'} target="_blank">
8      Song Title
9    </a>
10  </h2>
11  {/* Artist Name */}
12  <p className="text-sm text-center text-black">by Artist</p>
13  <div className="flex justify-center mt-6">
14    <SpotifyLogo />
15  </div>
16</div>

Currently Playing

This includes the result object that we'll be loading data from once we setup the API.

.tsx

1<div className="p-8 rounded-lg shadow-md bg-primary w-80">
2  <h3 className="mb-6 text-lg font-bold text-center text-black">
3    I&apos;m currently hearing this:
4  </h3>
5  {/* Album Cover */}
6  <Image
7    src={result.albumImageUrl}
8    alt={`${result.title} album art`}
9    width={256}
10    height={256}
11    className="w-64 h-64 mx-auto mb-4 rounded-lg shadow-lg shadow-teal-50"
12  />
13  {/* Song Title */}
14  <h2 className="text-2xl font-bold text-center text-accent">
15    <a href={result.songUrl} target="_blank">
16      {result.title}
17    </a>
18  </h2>
19  {/* Artist Name */}
20  <p className="mt-4 text-sm text-center text-black">
21    by {result.artist}
22  </p>
23  <div className="flex flex-col items-center gap-4 mt-4">
24    <SpotifyLogo />
25    <a
26      href="#"
27      className="text-xs text-center text-black underline "
28    >
29      See how this works
30    </a>
31  </div>
32</div>

Top Track

This is almost identical to the currently playing version, just some language changes.

.tsx

1<div className="p-8 rounded-lg shadow-md bg-primary w-80">
2  <h3 className="mb-6 text-lg font-bold text-center text-black">
3    One of my recent favorite tracks:
4  </h3>
5  {/* Album Cover */}
6  <Image
7    src={result.albumImageUrl}
8    alt={`${result.title} album art`}
9    width={256}
10    height={256}
11    className="w-64 h-64 mx-auto mb-4 rounded-lg shadow-lg shadow-teal-50"
12  />
13  {/* Song Title */}
14  <h2 className="text-2xl font-bold text-center text-accent">
15    <a href={result.songUrl} target="_blank">
16      {result.title}
17    </a>
18  </h2>
19  {/* Artist Name */}
20  <p className="mt-4 text-sm text-center text-black">
21    {result.artist}
22  </p>
23  <div className="flex flex-col items-center gap-4 mt-4">
24    <SpotifyLogo />
25    <a
26      href="#"
27      className="text-xs text-center text-black underline "
28    >
29      See how this works
30    </a>
31  </div>
32</div>

API

Authentication

For the API, we need to get our authentication in order, which is somewhat complicated for Spotify. They offer several different methods of authentication for several different purposes. What we need is to create an application that gets access to a single account one time and will stay active/authenticated until the end of time.

You'll want to login to developer.spotify.com with the account that you will be accessing the "now playing" state of. Once logged in, create a new app. Give it a name, description, website, and redirect URI. Use http://localhost:3000 if you are developing locally. For APIs, you can just select the Web API.

Once created, you'll have access to your Client ID and Secret ID - save these, we're gonna use them. First, we'll use the Client ID to authorize some scopes for our application. Simply visit the following the URL in your browser (replace <client_id> with your client id and make sure your local version is running):

.

1https://accounts.spotify.com/authorize?client_id=<client_id>&response_type=code&redirect_uri=http
2%3A%2F%2Flocalhost:3000&scope=user-read-currently-playing%20
3user-top-read

This will redirect you back to your localhost app but the URL will include an authorization code - save that code, we're gonna use it to get a refresh token that our application can use to show that it's allowed to be authenticated forever.

We need to create an API request to https://accounts.spotify.com/api/token, which you can do with curl or Postman or whatever you choose. This request needs to include basic authorization with a code that is your client id and client secret encoded to Base 64 - follow this format: client_id:client_secret when encoding to Base 64. Then the headers grant_type=authorization_code, code=<your authorization code from the previous step>, and redirect_uri=http%3A
%2F%2Flocalhost:3000.

Once you send this request, you'll get back JSON that includes a refresh_token - save that, we're in business. Authentication pain is over!

Code

Finally, we can get the data we need into our application. You'll want to add your client id, secret id, and refresh token to your environment variables to use.

In my Next.js project, I created a SpotifyAPI.ts file as defined here:

.typescript

1import querystring from 'querystring'
2
3const NOW_PLAYING_ENDPOINT = `https://api.spotify.com/v1/me/player/currently-playing`
4const TOP_TRACK_ENDPOINT = `https://api.spotify.com/v1/me/top/tracks?time_range=short_term&limit=1&offset=0`
5const TOKEN_ENDPOINT = `https://accounts.spotify.com/api/token`
6
7const client_id = process.env.NEXT_PUBLIC_SPOTIFY_CLIENT_ID
8const client_secret = process.env.NEXT_PUBLIC_SPOTIFY_CLIENT_SECRET
9const refresh_token = process.env.NEXT_PUBLIC_SPOTIFY_REFRESH_TOKEN
10
11const getAccessToken = async () => {
12  const basic = Buffer.from(`${client_id}:${client_secret}`).toString('base64')
13
14  const response = await fetch(TOKEN_ENDPOINT, {
15    method: 'POST',
16    headers: {
17      Authorization: `Basic ${basic}`,
18      'Content-Type': 'application/x-www-form-urlencoded',
19    },
20    body: querystring.stringify({
21      grant_type: 'refresh_token',
22      refresh_token,
23    }),
24  })
25
26  return response.json()
27}
28
29export const getNowPlaying = async () => {
30  const { access_token } = await getAccessToken()
31
32  return fetch(NOW_PLAYING_ENDPOINT, {
33    headers: {
34      Authorization: `Bearer ${access_token}`,
35    },
36  })
37}
38
39export const getTopTracks = async () => {
40  const { access_token } = await getAccessToken()
41  return fetch(TOP_TRACK_ENDPOINT, {
42    headers: {
43      Authorization: `Bearer ${access_token}`,
44    },
45  })
46}
47
48export default async function getTrackItem() {
49  const response = await getNowPlaying()
50  if (response.status === 204 || response.status > 400) {
51    const response = await getTopTracks()
52    if (response.status === 204 || response.status > 400) {
53      return false
54    }
55    const song = await response.json()
56    const albumImageUrl = song.items[0].album.images[0].url
57    const artist = song.items[0].artists
58      .map((_artist: { name: string }) => _artist.name)
59      .join(', ')
60    const songUrl = song.items[0].external_urls.spotify
61    const title = song.items[0].name
62
63    return {
64      albumImageUrl,
65      artist,
66      songUrl,
67      title,
68    }
69  } else {
70    const song = await response.json()
71    if (song.is_playing === true) {
72      const albumImageUrl = song.item.album.images[0].url
73      const artist = song.item.artists
74        .map((_artist: { name: string }) => _artist.name)
75        .join(', ')
76      const isPlaying = song.is_playing
77      const songUrl = song.item.external_urls.spotify
78      const title = song.item.name
79
80      return {
81        albumImageUrl,
82        artist,
83        isPlaying,
84        songUrl,
85        title,
86      }
87    } else {
88      const response = await getTopTracks()
89      if (response.status === 204 || response.status > 400) {
90        return false
91      }
92
93      const song = await response.json()
94      const albumImageUrl = song.items[0].album.images[0].url
95      const artist = song.items[0].artists
96        .map((_artist: { name: string }) => _artist.name)
97        .join(', ')
98      const songUrl = song.items[0].external_urls.spotify
99      const title = song.items[0].name
100
101      return {
102        albumImageUrl,
103        artist,
104        songUrl,
105        title,
106      }
107    }
108  }
109}
110

getAccessToken()

The first thing we do is create a function for getting our access token that we can use when we make our other requests.

getNowPlaying() and getTopTracks()

These functions will return the result object/array with our data.

getTrackItem()

This function is what we're going to call from the frontend to get our info. It first gets calls the getNowPlaying() function and determines whether the function returns anything. When calling the getNowPlaying() function it will return an object if something had been playing recently so we then check if the isPlaying attribute is true. This seems a little weird, but if we want it to update in real time, we need this extra step.

If getNowPlaying() either returns nothing OR the object it returns has isPlaying set to false, then we call getTopTracks(), which returns our list of top tracks. The endpoint is defined at the top of the file and can changed to use different parameters to get different data.

In both cases we pull the data from the track that is either playing or is our 'top track' and return it!

Connection

Last but not least, we just have to use useEffect() to call getTrackItem() and update if the result changes. We will set the loading state and the result from calling getTrackItem() with useState() and we're good to go! Here's the whole SpotifyNowPlaying.tsx component:

.tsx

1import Image from 'next/image'
2import { useEffect, useState } from 'react'
3
4import getTrackItem from '~/lib/SpotifyAPI'
5
6import SpotifyLogo from './SpotifyLogo'
7interface NowPlayingResult {
8  isPlaying: boolean
9  title: string
10  albumImageUrl: string
11  songUrl: string
12  artist: string
13}
14
15const SpotifyNowPlaying = () => {
16  const [loading, setLoading] = useState(true)
17  const [result, setResult] = useState<NowPlayingResult>()
18  useEffect(() => {
19    Promise.all([getTrackItem()]).then((results) => {
20      if (results[0]) {
21        setResult(results[0] as NowPlayingResult)
22      }
23      setLoading(false)
24    })
25  }, [result])
26  if (result) {
27    return (
28      <div>
29        {loading && (
30          <div>
31            <div className="p-8 rounded-lg shadow-md bg-primary w-80">
32              <h3 className="mb-6 text-lg font-bold text-center text-black">
33                Loading...
34              </h3>
35              {/* Song Title */}
36              <h2 className="text-2xl font-bold text-center text-accent">
37                <a href={'#'} target="_blank">
38                  Song Title
39                </a>
40              </h2>
41              {/* Artist Name */}
42              <p className="text-sm text-center text-black">by Artist</p>
43              <div className="flex justify-center mt-6">
44                <SpotifyLogo />
45              </div>
46            </div>
47          </div>
48        )}
49        {result.isPlaying ? (
50          <div>
51            <div className="p-8 rounded-lg shadow-md bg-primary w-80">
52              <h3 className="mb-6 text-lg font-bold text-center text-black">
53                I&apos;m currently hearing this:
54              </h3>
55              {/* Album Cover */}
56              <Image
57                src={result.albumImageUrl}
58                alt={`${result.title} album art`}
59                width={256}
60                height={256}
61                className="w-64 h-64 mx-auto mb-4 rounded-lg shadow-lg shadow-teal-50"
62              />
63              {/* Song Title */}
64              <h2 className="text-2xl font-bold text-center text-accent">
65                <a href={result.songUrl} target="_blank">
66                  {result.title}
67                </a>
68              </h2>
69              {/* Artist Name */}
70              <p className="mt-4 text-sm text-center text-black">
71                by {result.artist}
72              </p>
73              <div className="flex flex-col items-center gap-4 mt-4">
74                <SpotifyLogo />
75                <a
76                  href="#"
77                  className="text-xs text-center text-black underline "
78                >
79                  See how this works
80                </a>
81              </div>
82            </div>
83          </div>
84        ) : (
85          <div>
86            <div className="p-8 rounded-lg shadow-md bg-primary w-80">
87              <h3 className="mb-6 text-lg font-bold text-center text-black">
88                One of my recent favorite tracks:
89              </h3>
90              {/* Album Cover */}
91              <Image
92                src={result.albumImageUrl}
93                alt={`${result.title} album art`}
94                width={256}
95                height={256}
96                className="w-64 h-64 mx-auto mb-4 rounded-lg shadow-lg shadow-teal-50"
97              />
98              {/* Song Title */}
99              <h2 className="text-2xl font-bold text-center text-accent">
100                <a href={result.songUrl} target="_blank">
101                  {result.title}
102                </a>
103              </h2>
104              {/* Artist Name */}
105              <p className="mt-4 text-sm text-center text-black">
106                {result.artist}
107              </p>
108              <div className="flex flex-col items-center gap-4 mt-4">
109                <SpotifyLogo />
110                <a
111                  href="#"
112                  className="text-xs text-center text-black underline "
113                >
114                  See how this works
115                </a>
116              </div>
117            </div>
118          </div>
119        )}
120      </div>
121    )
122  }
123}
124
125export default SpotifyNowPlaying

All Done!

When all is said and done, you should end up with a component that looks like this:

It will update in real-time as well, when you start playing something on Spotify or tracks change, it will change!

Of course, you can view the whole repo of this site to see how it's setup in depth.

Thanks for reading!