Using async-await with the Spotify Web Playback SDK

Disclaimer: I work at Spotify in the Developer Experience Squad. This is a personal blog post into the exploration of async-await with our new SDK.

Recently, my team at Spotify launched the Web Playback SDK to third-party developers. It is now possible to integrate playback of Spotify content inside of websites and web applications. Read the full announcement here.

With the introduction of the async-await syntax in JavaScript ES6, I decided to write this article to show the power of this new syntax with the Web Playback SDK.

Hello, async-await!

To those of you who have used Promises in JavaScript, this will be easy to explain. If you haven’t used Promises or JavaScript, this article might be slightly more challenging to follow. You might want to play about with our Web Playback SDK demo on Glitch first.

The easy explanation, here’s some pseudo-JavaScript to perform a search and play the first song on Spotify:

function searchAndPlaySong (search_query) {
return searchOnSpotify(search_query).then(results => {
if (results.length > 0) {
return playSong(results[0].spotify_uri);
}
});
}

And let’s sprinkle some async-await salt on it:

async function searchAndPlayFirstSong (search_query) {
let results = await searchOnSpotify(search_query);
if (results.length === 0) return;
return playSong(results[0].spotify_uri);
}

That’s it! They both perform the exact same action. There is nothing new happening here, it is not a new feature, it is nothing more than syntactic sugar for Promises. Instead of wrapping our code in nested Promises, we can write it like above to make it easier to read.

When we write async, it returns a Promise. When we write await, we expect a Promise. It will not run the next line of code until the Promise has been successfully returned. And for error handling, you can simply use JavaScript’s plain-old try {} catch (err) {} for this.

Using async-await with the SDK

Though you don’t need to, I highly recommend reading the Quick Start Guide for the Web Playback SDK to have a better idea of how to setup the SDK inside of your own web app.

Loading the SDK

To those who haven’t used the SDK before, this is the most minimal HTML code you’ll need to get started:

<html>
<body>
<script src="https://sdk.scdn.co/spotify-player.js"></script>
<script>
window.onSpotifyWebPlaybackSDKReady = () => {
console.log("The Web Playback SDK is ready. We have access to Spotify.Player");
console.log(window.Spotify.Player);
};
</script>
</body>
</html>

When the page loads, Spotify automatically loads an external <iframe> containing scripts we want to hide from external developers. Once that has loaded, we make available a window.Spotify object and immediately fire an window.onSpotifyWebPlaybackSDKReady callback to inform the developer.

So, with that in mind, how does this work with async-await?

The first thing we’d want to do, is to create an async method called waitForSpotifyWebPlaybackSDKToLoad which should check if window.Spotify object has either already been defined, or check until window.onSpotifyWebPlaybackSDKReady has been fired:

async function waitForSpotifyWebPlaybackSDKToLoad () {
return new Promise(resolve => {
if (window.Spotify) {
resolve(window.Spotify);
} else {
window.onSpotifyWebPlaybackSDKReady = () => {
resolve(window.Spotify);
};
}
});
};

Then we can use it as follows:

(async () => {
const { Player } = await waitForSpotifyWebPlaybackSDKToLoad();
console.log("The Web Playback SDK has loaded.");
})();

Looks cleaner, with some great abstraction in there.

Instantiating the SDK

At this point, the SDK should load in your browser and you should be able to access to the Player variable inside our async method. We need to pass through our player name, initial volume and access token.

const sdk = new Player({
name: "Web Playback SDK",
volume: 1.0,
getOAuthToken: callback => { callback("access token"); }
});

You might ask: why aren’t we using async-await here? It is because this neither is asynchronous, nor does it use Promises. When we instantiate the Player, our configuration is stored inside of memory and when we call the connect() method later, it will go ahead and perform the request to create the player inside of Spotify.

Connecting the SDK to Spotify

As mentioned, we need to connect our local player to Spotify. This is an asynchronous method as seen by the example provided in the SDK Reference:

sdk.connect().then(connected => {
if (connected) {
// Connection was successful
}
});

There’s some help our async-await friends can help us with here:

let connected = await sdk.connect();
if (connected) {
// Connection was successful
}

Getting Playback State

Once your player has connected to Spotify using connect(), it will appear the user’s “Device List” under “Web Playback SDK” (as we defined earlier in new Player(...)) in any official Spotify client or via the Web API under GET /v1/me/player/devices.

Unless the user has selected your device, it will return a null. Otherwise, the SDK makes it possible to collect the current local playback state. Let’s do that & apply some destructuring:

let state = await sdk.getCurrentState();
if (state == null) {
// Playback isn't on this device yet
} else {
let {
id,
uri: track_uri,
name: track_name,
duration_ms,
artists,
album: {
name: album_name,
uri: album_uri,
images: album_images
}
} = state.track_window.current_track;
  console.log(`You're listening to ${track_name} by ${artists[0].name}!`);
}

This conveniently exposes a bunch of variables for us: id, track_uri, track_name, duration_ms, artists, album_name, album_uri, and album_images. Lovely!

Tip: It is possible to automatically transfer a user’s playback to your player via the Web API with the PUT /v1/me/player endpoint. We won’t cover it to keep things simple.

But, what if we want to wait until the user has selected our device? We can define a method called waitUntilUserHasSelectedPlayer that checks getCurrentState() until it does not return a null:

async function waitUntilUserHasSelectedPlayer (sdk) {
return new Promise(resolve => {
let interval = setInterval(async () => {
let state = await sdk.getCurrentState();
if (state !== null) {
resolve(state);
clearInterval(interval);
}
});
});
};

And then we can call it as follows:

let state = await waitUntilUserHasSelectedPlayer(sdk);

Running SDK Methods

Similar to connect() and getCurrentState(), any method from the SDK Reference (such as pause, resume, skip, etc) can use async-await because they all return Promises. Here’s a few code examples to show you:

Get Volume:

let volume = await sdk.getVolume();
console.log(`Got the volume: ${volume * 100}%`);

Set Volume:

await sdk.setVolume(0.5);
console.log("Updated volume to 50%!");

Pause:

await sdk.pause();
console.log("Paused!");

Resume:

await sdk.resume();
console.log("Resumed!");

Skip Track:

await sdk.nextTrack();
console.log("Skipped track!");

All of the methods are available in the SDK Reference.

Dealing with SDK Events

We have dealt with loading, instantiating, connecting, waiting for user to select our player, and reading from and controlling our player. What about events?

Events are actions that might be asynchronous to what you’re doing. Here’s a few examples:

  • User no longer has a Spotify Premium account
  • An internet connection is lost
  • Error handling
  • Playback state has changed

These actions are asynchronous, but they do not use Promises. They are Event Listeners. Because of this they do not support async-await, but we are free to use them as normal:

sdk.on("player_state_changed", state => {
console.log("Playback State Changed", state);
});

All of the supported events are available in the SDK Reference.

Final Code

We went through a lot of stuff with async-await. Let’s recap:

  • Loading by checking for window.Spotify to be defined by the SDK
  • Instantiating our local player using new Player(...)
  • Connecting to our player using connect()
  • Waiting for the user to select our device
  • Event Handling using existing code from the SDK
  • Check for Local Playback State

What would the code look like? This might help:

window.onSpotifyWebPlaybackSDKReady = () => {};
async function waitForSpotifyWebPlaybackSDKToLoad () {
return new Promise(resolve => {
if (window.Spotify) {
resolve(window.Spotify);
} else {
window.onSpotifyWebPlaybackSDKReady = () => {
resolve(window.Spotify);
};
}
});
};
async function waitUntilUserHasSelectedPlayer (sdk) {
return new Promise(resolve => {
let interval = setInterval(async () => {
let state = await sdk.getCurrentState();
if (state !== null) {
resolve(state);
clearInterval(interval);
}
});
});
};
(async () => {
const { Player } = await waitForSpotifyWebPlaybackSDKToLoad();
const sdk = new Player({
name: "Web Playback SDK",
volume: 1.0,
getOAuthToken: callback => { callback("access token"); }
});
  sdk.on("player_state_changed", state => {
// Update UI with playback state changes
});
  let connected = await sdk.connect();
if (connected) {
let state = await waitUntilUserHasSelectedPlayer(sdk);
await sdk.resume();
await sdk.setVolume(0.5);
    let {
id,
uri: track_uri,
name: track_name,
duration_ms,
artists,
album: {
name: album_name,
uri: album_uri,
images: album_images
}
} = state.track_window.current_track;
    console.log(`You're listening to ${track_name} by ${artists[0].name}!`);
}
})();

That’s the power of async-await! The code is easier to follow now, akin to a list of instructions. The hope is this would make building deep integrations with Spotify more seamless.

Want a demo of async-await? We’ve made one on Glitch. Check it out!

Fallback Support

What’s the support for async-await? Not bad. It has about 73% support in global browsers. Because async-await is only syntactic sugar, we can simply use a popular transpiler such as Babel.js to translate our JavaScript in a way that gracefully has 100% browser coverage.

Final Notes

We’re working on building the best developer experience out there, and I recommend checking out Spotify for Developers experience. If you have any feedback or suggestions, feel free to share it — we’d love to hear from you!

Finally, I can’t await to see what you build with the Spotify Platform!

Happy hacking :)