diff --git a/server/djrandom/frontend/static/player.js b/server/djrandom/frontend/static/player.js
index 84ad215d411db9829a13aa0e7f053a20a162a9f7..e0aa3a8e2900e1014bf19868d28ce9c86e27acd8 100644
--- a/server/djrandom/frontend/static/player.js
+++ b/server/djrandom/frontend/static/player.js
@@ -8,14 +8,151 @@ djr.generateRandomId = function() {
   return uuid.join('');
 };
 
+// Backend API stub.
 
-djr.Player = function(selector) {
+djr.Backend = function(userid) {
+  this.userid = userid;
+};
+
+// Read a playlist, calls callback(Playlist).
+djr.Backend.prototype.getPlaylist(uuid, callback, ctx) {
+  $.ajax({url: '/json/playlist/get/' + uuid,
+          dataType: 'json',
+          context: ctx,
+          success: function(data, status, jqxhr) {
+            callback(new djr.PlaylistChunk(data.songs));
+          }
+         });
+};
+
+// Save a playlist (song array).
+djr.Backend.prototype.savePlaylist(uuid, songs) {
+  $.ajax({url: '/json/playlist/save',
+          data: {uuid: uuid,
+                 h: songs.join(',')},
+          type: 'POST'
+         });
+};
+
+// Return the HTML fragment for an array of songs.
+djr.Backend.prototype.getHtmlForSongs(songs, callback, ctx) {
+  $.ajax({url: '/fragment/songs',
+          data: {'h': songs.join(',')},
+          dataType: 'html',
+          type: 'POST',
+          context: ctx || this,
+          success: callback,
+         });
+};
+
+djr.Backend.prototype.nowPlaying = function(song) {
+  $.getJSON('/json/playing/' + song);
+};
+
+
+
+// A Playlist chunk (basically, an array of songs).
+
+djr.PlaylistChunk = function(songs) {
+  this.songs = songs || [];
+};
+
+djr.PlaylistChunk.prototype.hasSong(sha1) {
+  return (this.songs.indexOf(sha1) >= 0);
+};
+
+djr.PlaylistChunk.prototype.removeSong(sha1) {
+  this.songs = $.grep(this.songs, function(a) { return a != sha1; });
+};
+
+
+
+// Playlist.
+
+djr.Playlist = function() {
+  this.uuid = null;
+  this.chunks = [];
+  this.song_map = {};
+  this.chunk_map = {};
+  this.next_chunk_id = 0;
+};
+
+// Return an array with all songs, in order.
+djr.Playlist.prototype.allSongs = function() {
+  var songs = [];
+  $.each(this.chunks, function(idx, chunk_id) {
+    $.each(this.chunk_map[chunk_id].songs, function(idx2, song) {
+      songs.push(song);
+    })});
+  return songs;
+};
+
+// Add a new chunk (only adds unique songs).
+// Returns the chunk ID.
+djr.Playlist.prototype.addChunk = function(playlist_chunk) {
+  chunk_id = this.next_chunk_id++;
+  var songs = [];
+  $.each(playlist_chunk.songs, function(idx, song) {
+    if (this.song_map[song]) {
+      continue;
+    }
+    songs.push(song);
+    this.song_map[song] = chunk_id;
+  });
+  this.chunk_map[chunk_id] = new PlaylistChunk(songs);
+  this.chunks.push(chunk_id);
+  return chunk_id;
+};
+
+// Remove a chunk (by ID).
+djr.Playlist.prototype.removeChunk = function(chunk_id) {
+  $.each(this.chunks[chunk_id].songs, function(idx, song){
+    delete this.song_map[song];
+  });
+  delete this.chunk_map[chunk_id];
+  this.chunks = $.grep(this.chunks, function(cid) { return cid != chunk_id; });
+};
+
+// Return a new Playlist with all the current chunks merged.
+djr.Playlist.prototype.merge = function() {
+  var new_playlist = new djr.Playlist();
+  new_playlist.uuid = this.uuid;
+  new_playlist.addChunk(this.allSongs());
+  return new_playlist;
+};
+
+// Find the next song.
+djr.Playlist.prototype.getNextSong = function(song) {
+  var cur_chunk = this.song_map[song];
+  var chunk_songs = this.chunk_map[cur_chunk].songs;
+
+  var chunk_idx = this.chunks.indexOf(cur_chunk);
+  var idx = chunk_songs.indexOf(song) + 1;
+
+  if (idx >= chunk_songs.length) {
+    idx = 0;
+    chunk_idx++;
+    if (chunk_idx >= this.chunks.length) {
+      chunk_idx = 0;
+    }
+  }
+
+  return this.chunk_map[this.chunks[chunk_idx]].songs[idx];
+};
+
+
+
+
+
+// Player
+
+djr.Player = function(backend, selector) {
+  this.backend = backend;
   this.player = $(selector);
-  this.curPlaylist = Array();
-  this.curPlaylistId = null;
-  this.curIdx = -1;
-  this.curSong = null;
+  this.playlist = new djr.Playlist();
+  this.cur_song = null;
 
+  // Setup the jPlayer interface.
   this.player.jPlayer({
     ready: function() {},
   });
@@ -23,8 +160,102 @@ djr.Player = function(selector) {
                    function () {djr.state.player.nextSong(); });
   this.player.bind($.jPlayer.event.error + '.djr', 
                    function() {djr.state.player.reportError(); });
+
+};
+
+djr.Player.prototype.hideAllChunks = function() {
+  $('.chunk .inner').hide();
+};
+
+// Callback for removechunk click.
+djr.Player.prototype.removeChunkCallback = function() {
+  var chunk_id = $(this).attr('id').substr(6);
+  this.playlist.removeChunk(chunk_id);
+  $(this).remove();
 };
 
+// Search!
+djr.Player.prototype.search = function(query) {
+  this.backend.search(query, function(results) {
+    var songs = [];
+    $.each(results, function(idx, item) {
+      songs.push(item.sha1);
+    });
+
+    var chunk_id = this.playlist.addChunk(new PlaylistChunk(songs));
+
+    var player = this;
+    this.backend.getHtmlForSongs(songs, function(songs_html) {
+      var chunk_div = 'chunk_' + chunk_id;
+      player.hideAllChunks();
+      $('#playlistDiv').append(
+        '<div id=\"' + chunk_div + '\" class=\"chunk\">'
+          + '<div class=\"chunk_title\">' + query + '</div>'
+          + '<div class=\"chunk_inner\">'
+          + songs_html + '</div></div>');
+      $('#' + chunk_div + ' .song_a').click(function() {
+        player.play($(this).attr('id').substr(5));
+      });
+      $('#' + chunk_div + ' .album_a').click(function() {
+        player.search('(album:\"' + $(this).text() + '\")');
+      });
+    }, this);
+  }, this);
+
+};
+
+// Start playing a specific song.
+djr.Player.prototype.play = function(song) {
+  djr.debug('play ' + song);
+
+  this.cur_song = song;
+
+  $('.song').removeClass('playing');
+  $('#song_' + song).addClass('playing');
+
+  var url = '/dl/' + song;
+  this.player.jPlayer('setMedia',
+                      {mp3: url}).jPlayer('play');
+
+  // Report the new song being played.
+  this.backend.nowPlaying(song);
+};
+
+// Start playing the next song.
+djr.Player.prototype.nextSong = function() {
+  this.play(this.playlist.getNextSong(this.cur_song));
+};
+
+
+
+djr = {};
+
+djr.state = {
+  backend: null,
+  player: null,
+  userid: null
+};
+
+djr.init = function (userid) {
+  djr.state.userid = userid;
+  djr.state.backend = new djr.Backend(userid);
+  djr.state.player = new Player(djr.state.backend, '#djr_player');
+};
+
+// Export the player for quick onclick access
+djr.player = function() {
+  return djr.state.player;
+};
+
+// Debugging.
+djr.debug = function(msg) {
+  $('#debug').append(msg + '<br>');
+};
+
+
+
+// ------ OLD --------
+
 // Return the current playlist ID.
 djr.Player.prototype.getCurrentPlaylistID = function() {
   return this.curPlaylistId;
@@ -54,20 +285,6 @@ djr.Player.prototype.setPlaylist = function(hashes) {
   this.curIdx = -1;
 };
 
-// Add songs to the playlist, do not add songs already in there.
-djr.Player.prototype.extendPlaylist = function(hashes) {
-  var i, map = {};
-  for (i = 0; i < this.curPlaylist.length; i++) {
-    map[this.curPlaylist[i]] = 1;
-  }
-  for (i = 0; i < hashes.length; i++) {
-    if (map[hashes[i]]) {
-      continue;
-    }
-    this.curPlaylist.push(hashes[i]);
-    map[hashes[i]] = 1;
-  }
-};
 
 // Save the current playlist.
 djr.Player.prototype.savePlaylist = function() {
@@ -75,11 +292,7 @@ djr.Player.prototype.savePlaylist = function() {
   if (!this.curPlaylistId) {
     return;
   }
-  $.ajax({url: '/json/playlist/save',
-          data: {uuid: this.curPlaylistId,
-                 h: this.curPlaylist.join(',')},
-          type: 'POST'
-         });
+  this.backend.savePlaylist(this.curPlaylistId, this.curPlaylist);
 };
 
 // Load a playlist.
@@ -124,22 +337,6 @@ djr.Player.prototype.nextSong = function() {
   this.playSong(next_sha1);
 };
 
-// Start playing a specific song.
-djr.Player.prototype.playSong = function(sha1) {
-  this.curSong = sha1;
-  this.curIdx = $.inArray(sha1, this.curPlaylist);
-
-  $('.song').removeClass('playing');
-  $('#song_' + sha1).addClass('playing');
-
-  var url = '/dl/' + sha1;
-  djr.debug('playing ' + url);
-  this.player.jPlayer('setMedia',
-                      {mp3: url}).jPlayer('play');
-
-  // Report the new song being played.
-  $.getJSON('/json/playing/' + sha1);
-};
 
 // An error has occurred in the player.
 djr.Player.prototype.reportError = function(event) {
diff --git a/server/djrandom/frontend/templates/_base.html b/server/djrandom/frontend/templates/_base.html
index bf07cd42fadbf9972b7e63820140af123110b173..3512ccb30f4ba1150c23bf6c651b78c7d3fb1281 100644
--- a/server/djrandom/frontend/templates/_base.html
+++ b/server/djrandom/frontend/templates/_base.html
@@ -15,9 +15,7 @@
     <script type="text/javascript"
             src="/static/js/jquery.jplayer.min.js"></script>
     <script type="text/javascript"
-            src="/static/djrandom.js?v=4"></script>
-    <script type="text/javascript"
-            src="/static/player.js?v=4"></script>
+            src="/static/player.js?v=5"></script>
     {% block head %}{% endblock %}
   </head>
   <body>