Guest Post: Spotify album visualization using Twitter and PubNub data stream

By ‎@sw1tch‎
Tuesday, 3 February 2015

We recently teamed up with Twitter to offer Twitter streams over PubNub. One of the streams includes a list of @Spotify albums people are listening to on Twitter. Inspired by the @iTunes album artwork screensaver, we created a visualization of the latest album artwork appearing on our Twitter firehouse sample. The result is an ambient background fueled by real listeners.

Guest Post: Spotify album visualization using Twitter and PubNub data stream

Thanks to PubNub, Twitter and Embedly, this app exists entirely on the front end. You can even fork and edit the app in CodePen. Check out the live full window demo.

To create the Album Artwork Visualizer, first create an HTML page for our app to live in.

<html>
  <head>
    <link href='http://fonts.googleapis.com/css?family=Oxygen:400,700,300' rel='stylesheet' type='text/css'>
    <link href='http://cdn.jsdelivr.net/animatecss/3.2.0/animate.min.css' rel='stylesheet' type='text/css'>
  </head>
  <title>Twitter Albums Streams - Powered by PubNub</title>
  <body>
    <div id="output"></div>
    <div id="tweet"></div>
    <a id="brand" href="http://pubnub.com"><img src="https://brandfolder.com/pubnub/assets/xemvalf8" alt="Powered By PubNub Color"></a>
    <script src="http://cdn.pubnub.com/pubnub.min.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js" type="text/javascript"></script>
    <script src="http://cdn.embed.ly/jquery.embedly-3.1.1.min.js" type="text/javascript"></script>
  </body>
</html>

This document serves two purposes. The first is to create our three containers: #output, #tweet and #brand. The second is to include all of our libraries: PubNub, jQuery, Embedly, Google Fonts and Animate.css.

Connecting to the Twitter PubNub stream

We start by defining some global variables.

// store jQuery output containers
var $output = $('#output');
var $tweet = $('#tweet');

// configure # of albums to display
var limit = 60;

Next, we initialize PubNub with our subscribe key and subscribe to the pubnub-twitter-spotify PubNub channel. For now, we just log out the Tweet object to the console.

// initialize PubNub
var pubnub = PUBNUB.init({
  subscribe_key: 'sub-c-78806dd4-42a6-11e4-aed8-02ee2ddab7fe'
});

// render tweets in real time
pubnub.subscribe({
  channel: 'pubnub-twitter-spotify',
  message: function(tweet) { 
    console.log(tweet);
  }
});

The Tweet object sent over our special PubNub channel contains quite a bit of information. For this application, we are only interested in the URLs in the entities property.

urls: [
  {
    url: "http://t.co/ZmTYPcI6Hd",
    expanded_url: "http://spoti.fi/LZPW6x",
    display_url: "spoti.fi/LZPW6x",
    indices: [
      45,
      67
    ]
  }
]

We write a function that finds the specific Spotify link within the Tweet entities. We simply loop through tweet.entities.urls to find it.

// retrieve the last link provided in a tweet
var findLink = function(tweet) {  
  
  var links = tweet.entities.urls; 
  var link = false;
  
  for(var i = 0; i < links.length; i++) {
    if (
      links[i].expanded_url.indexOf('spoti.fi') > 0 ||
      links[i].expanded_url.indexOf('spotify.com') > 0
    ) {
      link = links[i].expanded_url;
    }
  }
  return link;
};

We then create a function to handle Tweets provided by our pubnub.subscribe callback. The handleTweets function will replace the console log in pubnub.subscribe.

// render tweets in real time
pubnub.subscribe({
  channel: 'pubnub-twitter-spotify',
  message: function(tweet) { 
    handleTweets([tweet]);
  }
});

// this is a function turns an array of tweets into Album objects
var handleTweets = function(tweets) {

  // create an array of links from provided tweets
  var links = [];
  for(var i = 0; i < tweets.length; i++) {
    var link = findLink(tweets[i]);
    
    if(link) {
      links.push(link);

      // maps the link to a Album object
      albums[link] = new Album(tweets[i]);
    }
  }

  // pass the array of links to embedly where album information is retrieved
  $.embedly.extract(links, {key: '513022d2824311e088ae4040f9f86dcd'}).progress(function(data){
    var image = data.images[0];
   
    if(typeof image !== 'undefined' && (image.width / image.height == 1)) { 
      // use the link to render the Album
      albums[data.original_url].render(image);
    }
  });
};

This function accepts an array of tweets and is designed to support batch processing. First, we use the findLink function to parse the tweet object for Spotify streams and then add the returned link to an array.

Using @Embedly to render Spotify albums

Next, we feed the array of Spotify links into our Embedly library. Embedly is an awesome API for extracting content from webpages. In this case, Embedly takes the array of Spotify links and returns the album name and artwork for each one.

Guest Post: Spotify album visualization using Twitter and PubNub data stream

Because we are batch processing the links in async, we don’t actually know which Tweet each Embedly response belongs to.

To solve this, we create an Album object with the data from our PubNub Twitter Stream. We also add it to an associative array using the result from findLink as the key.

// maps the link to a Album object
albums[link] = new Album(tweets[i]);

Embedly provides a property called original_url within the response. We use the original_url to find out which album artwork belongs with which Tweet.

albums[data.original_url].render(image);

The albums array and Album object look like this:

// create a hashtable for Album objects
var albums = {};

// this object represents an album Album and tweet
var Album = function(tweet) {

  var self = this;
  
  self.render = function(image) {
    
    var $html = $('\
      <a class="album" href="http://twitter.com/' + tweet.user.screen_name + '/status/' + tweet.id_str + '" target="_blank"> \
        <img src="' + image.url + '" class="animated flipInY"> \
      </a>');

    $html.hover(function(){
      $tweet.text(tweet.text).show();
    }, function(){
      $tweet.hide();
    });
    
    var num_albums = $('.album');

    if(num_albums.length < limit) {
      // we haven't populated the page yet, append
      $output.append($html);
    } else {
      // take a random album element and replace it with this one 
      var randomel = Math.floor((Math.random() * num_albums.length ) + 1);
      var $replace = $('.album:nth-child(' + randomel + ')');

      $replace.addClass('animated flipOutY').one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function() {
        $(this).replaceWith($html);
      });
    }
  };
};

Notice that the object doesn’t do much before render is called. It simply creates a container we can dump the Tweet into before we call render.

When render is called, we count the number of .album elements on the page. If we’re above the limit defined at the top of the file, we choose to replace an existing album rather than simply appending it. We use animate.css to add some fancy effects. When you mouse over the album artwork, the Tweet text is shown within the #tweet container. Clicking on an album artwork brings you to the exact Tweet that populated that artwork within the stream.

Remember how we designed handleTweets to support an array of Tweets rather than a single Tweet? We use pubnub.history to retrieve the last limit * 1.5 tweets. This returns an array of messages which are exactly the same as those emitted by pubnub.subscribe. When we send the array to handleTweets the page is populated with artwork! We use limit * 1.5 to ensure overpopulation on pageload and account for error.

// populate the page with the last x tweets
pubnub.history({
  channel: 'pubnub-twitter-spotify',
  count: limit * 1.5,
  callback: function(data){
    handleTweets(data[0]);
  },
});

Fork and edit the animations to make your own version on CodePen. Plug it into an app like WebSaver and you’ve got an awesome realtime album screensaver.

@TwitterDev would like to thank Ian Jennings and @PubNub for creating these great demo apps using Twitter data. The NodeJS source code for interfacing with the Twitter streaming/filter API and publishing with the PubNub API is also available in this GitHub Gist. For detailed documentation on the Twitter API, be sure to visit dev.twitter.com.