Lee Martin
Netmaker
Shania Twain

Building a Spotify Wrapped Inspired Activation for Shania Twain

2023-02-24

Spotify Wrapped has become an annual ritual for the Spotify brand and their users around the world. It is one of the most successful cases of user data storytelling on the web and allows the company to turn play counts and preferences into a cultural moment. Spotify can conduct these sorts of campaigns because they have a huge swath of private data at their disposal but what sort of data can we as artists get access to? And what sort of stories can we tell with this data?

This is something I’ve been interested in ever since the Spotify Platform was opened to developers. On the data side, we can learn about a user’s top 50 tracks and artists in long, medium, and short term timeframes. This might not seem like a ton of information but it allows us to get a simple musical DNA sample of a user and present our own narratives around their habits. I’ve used this data in the past to check a user’s affinity for a particular artist, determine if a user was depressed, and figure out which song off a new album might suit them best.

Naturally, this all leads back to Shania Twain, again. Of course. Kelsey Miller had this wonderful idea to try and create a sort of daily Spotify Wrapped for Shania fans using the listening data we had at our disposal. Our primary story? “What are you the Queen of?” Shania is the undisputed Queen of Country Pop and we thought it would be fun to declare you the queen of whatever genre you stream most on Spotify. In addition to this story, we also determine your “Giddy Up Score” (How often have you been streaming Shania lately?) and “Is Shania Still The One?” (Where does Shania rank in your top artists?) Oh, she’s not there?! That don’t impress me much.

Find out what you are the queen of today at www.queenof.me and read on to learn how the activation came together. Let's go girls.

Spotify Data Stories

A Spotify data campaign such as this all begins with the Top Items endpoint of the API. We'll be calling this to get the top short term tracks and artists for our user and then we'll interpret that data in interesting ways. I love the short term timeframe because it updates every 24 hours which allows us to engage fans in a daily activity. I like to first use axios to create a Spotify client. The user token must be obtained through Spotify authentication.

const spotify = axios.create({
  baseURL: 'https://api.spotify.com/v1/',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${USER_TOKEN}`
  }
})

Then I can easily get the user's top 50 artists...

let { data: { items: artists }} = await spotify.get('/me/top/artists', {
  params: {
    limit: 50,
    time_range: 'short_term'
  }
})

... and their top 50 tracks.

let { data: { items: artists }} = await spotify.get('/me/top/tracks', {
  params: {
    limit: 50,
    time_range: 'short_term'
  }
})

Now, let's work out each of our data stories.

Queen of Me

I am the Queen of Zydeco

The main story of our activation tells users which genre they are the "Queen of" or rather, which genre fo they stream most? The Spotify platform has many many genres but naturally some are more popular than others. An array of genres can be found on each artist on the platform. In order to determine which genre a user is streaming most, we'll first create an array than emcompasses all genres from all of their top artists. We can then tally up the appearance of all genres and return a sorted scored list.

// Get all genres from top artists
let allGenres = artists.map(artist => artist.genres).flat()

// Initialize scored genres
let scoredGenres = []            

// Loop through all genres
allGenres.forEach(genre => {
  // Get current genre score
  let genreScore = scoredGenres.find(scoredGenre => scoredGenre.id == genre)

  // If current genre score exists
  if (genreScore) {
    // Increment score
    genreScore.value += 1

  } else {
    // Add genre score to array
      scoredGenres.push({
      id: genre,
      value: 1
    })

  }
})

// Return scored genres sorted by most
return scoredGenres.sort((a, b) => a.value - b.value).reverse()

I wish Spotify would augment the genre seeds endpoint to include the popularity of all genres as I would love to build a similar activation which tells a user what the most obscure genre they listen to is.

Giddy Up

Are you ready to Giddy Up?

The "Giddy Up" score is the same fan affinity algorithm I've been using in other campaigns. We go through all of the user's top tracks to check for the appearance and position of Shania Twain's music or collaborations. This gives us a dynamic value of affinity. The only way a user would get a perfect score is if all of their top tracks were from Shania. This has happened on past campaigns. 😅

// Get max tracks
let maxTracks = tracks.length

// Initialize max affinity
let maxAffinity = 0

// Increment max affinity
for (let i = tracks.length; i > tracks.length - maxTracks; i--) {
  // Increment affinity
  maxAffinity += i

}

// Initialize affinity
let affinity = 0

// Loop through tracks
tracks.forEach((track, i) => {
  // If a track's artist matches client
  if (track.artists.map(artist => artist.id).includes(ARTIST_ID)) {
    // Increment affinity
    affinity += tracks.length - i

  }
})

// Return affinity
return affinity / maxAffinity

Still The One

Shania is #10 in your top artists

The final story attempts to tell users if Shania Twain is "Still The One." We can do this simply by checking which position Shania appears in a user's top tracks. If Shania is, in fact, the top artist in a user's recently streamed tracks, she is, indeed, still the one.

// Get index of artist from top artists
return artists.findIndex(artist => artist.id == ARTIST_ID)

You can see how even one line of code, when connected thematically, can be a strong data narrative.

UI/UX

Similar to the user experience of Spotify Wrapped, we're following the known UX techniques of Instagram, TikTok, etc to deliver our data stories to users and encourage them to share them. Building an interface which feels familiar to a user is a powerful tactic which building accessible experiences. This is why I like building camera apps so much. The UI elements we're most interested in is the autoplaying story reveal made popular by Instagram and a simple share picker with one-click sharing to socials.

Autoplay Stories

I've used the autoplaying stories UX before on a Motown project. It consists of a series of progress bars which sit in the header and the revealed stories themselves. The progress bars inform the user of how many stories there are, when a story begins, and how long and how much of a story has elapsed. It also encourages the user to stick around until all stories are revealed. It is certainly a delightful if not addictive piece of UX. Here's a slightly dated dev blog just about building this component. I'd say the biggest different in execution between that past blog's solution and this most recent build is using Greensock instead of Anime. I really love this component and I'd like to work up a really nice version of it in Vue when I get some time. (This is me reminding myself.)

Picker

Once all stories are presented to the user, they land on a sort of outro page which now encourages them to share each of their stories. Since we have three different stories and subsequently three different shareable assets, we need a way for users to look through these to decide which they'd like to share socially. We can do this by building a swipable picker. This is another great piece of known UX which is much easier to put together these days thanks to scroll-snap. Again, this has made an appearance in many of my dev blogs and codepens.

On this particular project, I was able to use Tailwind latest implementation of scroll-snap to simplify the code even further.

#shares-picker{
  @apply gap-5 grid grid-flow-col overflow-x-scroll snap-mandatory snap-x w-full;
  scrollbar-width: none;
}

#shares-picker::-webkit-scrollbar{
  @apply hidden;
}

Web Share

In order to share our story images directly to one of the social platforms, we use the Web Share API. This API is not without its faults but it has been a godsend for these UGC campaigns. I'm going to write a separate post about the composable I've written to handle this but for now, here's a little bit of code.

All of my shareable images were generate dynamically using HTML canvas, so I can easily get a blob of them using the toBlob method. With this blob, I can initialize a new file of my shareable image. I can then check to see if Web Share is supported on this user's browser and if it will be capable of sharing this particular file format. If everything checks out, the Web Share dialogue will appear and the user can choose where they'd like to share it. If not, we can simply download the file to the user's device.

// Initialize file
let file = new File([blob], `queenof.jpg`, {
  type: blob.type
})

// Check that file can be shared
if (navigator.canShare && navigator.canShare({ files: [file] })) {
  // Share file
  navigator.share({
    title: "Shania Twain | Queen of What?",
    description: "Which genre are you the queen of?",
    files: [file]
  })
  .then(() => {
    // Log
    console.log('Share succeeded')

  })
  .catch(error => {
    // Log
    console.error('Share failed')

  })

} else {
  // Generate object URL for blob
  let data = window.URL.createObjectURL(blob)

  // Initialize a tag
  let link = document.createElement('a')

  // Update link param
  link.href = data

  // Upload download param
  link.download = `queenof.jpg`

  // Click link
  link.click()

}

Thanks

What a pleasure to once again work with Kelsey Miller from Republic Records alongwith Robyn Elton and Sarah Delaney from Blackstar Agency on Shania. What’s next? Seeing Shania live in New Orleans later this summer. Shania's latest album, Queen of Me, is out now.