From 5e9f297a5b9186be66a2c9c9c409038c1d4ad24c Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Wed, 13 Jul 2011 02:21:19 +0200 Subject: [PATCH] Refactor of mhd.js. This is an attempt to refactor the code powering jsmad.org, there is still a lot to do but at least, the source code is now more readable and a bit more extensible. --- index.html | 6 +- mhd.js | 433 ++++++++++++++++++++++++++++++++------------------ officialfm.js | 405 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 683 insertions(+), 161 deletions(-) create mode 100644 officialfm.js diff --git a/index.html b/index.html index 7c33a41..f0e95fb 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,7 @@ jsmad - javascript mp3 decoder - + Fork me on GitHub @@ -25,7 +25,7 @@

or
- +
@@ -74,7 +74,7 @@

jsmad is a pure JavaScript MP3 decoder

- + diff --git a/mhd.js b/mhd.js index d786bf1..102ab42 100644 --- a/mhd.js +++ b/mhd.js @@ -1,159 +1,276 @@ -function onPlayPause() { - console.log("playPause"); -} -function onSeek(percentage) { - console.log("seek " + percentage + "%"); -} - -function onProgress(current, total, preload) { - //console.log("current = " + current + ", total = " + total); - var preloadbar = document.getElementById('preloadbar'); - preloadbar.style.width = (preload * 360) + 'px'; - var progressbar = document.getElementById('progressbar'); - progressbar.style.width = (current / total * 360) + 'px'; -} - -var globalPlayer = null; -var ofm = new OfficialFM('Q5Bd7987TmfsNVOHP9Zt'); -var ofmTrack = null; - -function domReady() { - var playOfm = function () { - var track_id = document.getElementById('ofm').value; - var url = "http://mp3.jsmad.org/mp3s/" + Math.floor(track_id / 1000) + "/" + track_id + ".mp3"; - ofm.track(track_id, function(track) { - ofmTrack = track; - Mad.Player.fromURL(url, usePlayer); - }); - return false; - }; - document.getElementById('ofm').onkeypress = function(ev) { - if(ev.keyCode == 13) { // enter pressed? - playOfm(); - return false; - } - }; - playOfm(); -} - -function usePlayer (player) { - if(globalPlayer) globalPlayer.destroy(); - globalPlayer = player; - if (player.id3) { - var id3 = player.id3.toHash(); - var id3element = document.getElementById('ID3'); - - var id3string = "
"; - var pictures = id3['Attached picture']; - - if(ofmTrack) { - id3string += ""; - } else if (pictures) { - var mime = pictures[0].mime; - var enc = btoa(pictures[0].value); - id3string += ""; - } else { - id3string += ""; - } - - id3string += ""; - id3string += "
"; - - id3string += "
"; - id3string += "
"; - - var artist = ofmTrack? ofmTrack.artist_string: id3['Lead artist/Lead performer/Soloist/Performing group']; - id3string += "

" + (ofmTrack ? ofmTrack.title : id3['Title/Songname/Content description']) + "

"; - id3string += "

" + artist + "

"; - id3string += "
"; - if(id3['Album/Movie/Show title']) { - id3string += "

Album: " + id3['Album/Movie/Show title'] + "

"; - } - if(id3['Track number/Position in set']) { - id3string += "

Track: " + id3['Track number/Position in set'] + "

"; - } - if(id3['Year']) { - id3string += "

Year: " + id3['Year'] + "

"; - } - id3string += "
"; - id3string += "
"; - ofmTrack = null; - - id3element.innerHTML = id3string; - - document.getElementById('playpause').onclick = function () { - player.setPlaying(!player.playing); - return false; - }; - - // musicbrainz + musicmetric queries - $.ajax({ - type: "GET", - url: "http://jsmad.org/musicbrainz/" + escape(artist), - dataType: "xml", - success: function(xml) { - var doc = $(xml); - console.log("real artist name = " + doc.find('artist').children('name').text()); - var artist_id = doc.find('artist').attr('id'); - console.log("musicbrainz artist id = " + artist_id); - - var icons = { - facebook: 'http://facebook.com/favicon.ico', - lastfm: 'http://last.fm/favicon.ico', - myspace: 'http://myspace.com/favicon.ico', - twitter: 'http://twitter.com/favicon.ico', - youtube: 'http://www.youtube.com/favicon.ico', - }; - - $.ajax({ - type: "GET", - url: "http://jsmad.org/musicmetric/musicbrainz:" + artist_id, - dataType: "json", - success: function(json) { - console.log("success? " + json.success); - var up_img = ''; - var down_img = ''; - var equal_img = ''; - - for(var platform in json.response.fans) { - if(!json.response.fans.hasOwnProperty(platform)) continue; - if(platform == "total") continue; - - var fans = json.response.fans[platform]; - var previousFans = parseInt(fans.previous); - var currentFans = parseInt(fans.current); - var totalFans = parseInt(fans.total); - console.log("previous/current fans? " + previousFans + "/" + currentFans); - $('#meta_info').append('

' + (Math.abs(currentFans - previousFans) < 5 ? equal_img : (currentFans > previousFans ? up_img : down_img)) + ' ' + platform + ': ' + (totalFans > 1000000 ? ((Math.floor(totalFans / 100000) / 10.0) + "M") : (totalFans > 1000 ? ((Math.floor(totalFans / 100) / 10.0) + "K") : totalFans)) + ' fans' + '

'); - } - $('#artist_span').append(' ' + json.response.fans.total.total + ' fans'); - } - }); - } - }); - } - - player.onProgress = onProgress; - - player.onPlay = function() { - document.getElementById('playpause').className = 'button pause'; - }; - - player.onPause = function() { - document.getElementById('playpause').className = 'button play'; - }; - - player.createDevice(); -} - -function readFile() { - // uploadData is a form element - // fileChooser is input element of type 'file' - var file = document.forms['uploadData']['fileChooser'].files[0]; - if (!file) { - return; - } - - Mad.Player.fromFile(file, usePlayer); -} +( function() +{ + + var DEBUG = window.MHD_DEBUG || false; + + window.MHD = function( o ) + { + this.options = o; + this.ofm = new OfficialFM( o.ofmAPIKey ); + this.player = null; + this.ofmTrack = null; + + // var globalPlayer = null; + // var ofm = new OfficialFM('Q5Bd7987TmfsNVOHP9Zt'); + // var ofmTrack = null; + }; + + MHD.MP3_URL = 'http://mp3.jsmad.org'; + + MHD.prototype = { + + bindEvents : ( function() + { + var eventsBinded = false; + + return function() + { + if(eventsBinded) return; + + var self = this; + + this.el = {}; + + for(key in this.options.el) + { + this.el[key] = document.querySelector(this.options.el[key]); + } + + this.el.fileChooser.onchange = function( ev ) + { + self.readFile( ev ); + }; + + this.el.trackId.onkeypress = function(ev) + { + // enter pressed ? + if(ev.keyCode == 13) { + + self.playOfm(); + return false; + } + }; + }; + + } )(), + + onPageLoad : function() + { + this.bindEvents(); + this.playOfm(); + }, + + onPlayPause : function() + { + DEBUG && console.log("play/paused pressed."); + }, + + onSeek : function(percentage) + { + DEBUG && console.log("seek " + percentage + "%"); + }, + + onProgress: function( current, total, preload ) + { + DEBUG && console.log("current = " + current + ", total = " + total); + + this.el.preloadBar.style.width = (preload * 360) + 'px'; + this.el.progressBar.style.width = (current / total * 360) + 'px'; + }, + + playOfm : function() + { + var trackId = this.el.trackId.value, + url = MHD.MP3_URL + "/mp3s/" + Math.floor( trackId / 1000 ) + "/" + trackId + ".mp3", + self = this; + + this.ofm.track( trackId, function( track ) + { + self.ofmTrack = track; + Mad.Player.fromURL( url, function( player ) + { + self.usePlayer( player ); + } ); + } ); + }, + + readFile : function() + { + // uploadData is a form element + // fileChooser is input element of type 'file' + var file = document.forms['uploadData']['fileChooser'].files[0], + self = this; + + if (!file) return; + + Mad.Player.fromFile(file, function( player ) + { + self.usePlayer( player ); + }); + }, + + // TODO: Refactor this method. + usePlayer : function(player) + { + var self = this; + + if(this.player) player.destroy(); + + this.player = player; + + if (player.id3) { + + var id3 = player.id3.toHash(), + id3element = self.el.id3, + id3string = "
", + pictures = id3['Attached picture'], + artist = this.ofmTrack ? this.ofmTrack.artist_string : id3['Lead artist/Lead performer/Soloist/Performing group']; + + if(this.ofmTrack) { + + id3string += ""; + + } else if (pictures) { + + var mime = pictures[0].mime, + enc = btoa(pictures[0].value); + + id3string += ""; + + } else { + + id3string += ""; + } + + id3string += ""; + id3string += "
"; + + id3string += "
"; + id3string += "
"; + + id3string += "

" + (this.ofmTrack ? this.ofmTrack.title : id3['Title/Songname/Content description']) + "

"; + id3string += "

" + artist + "

"; + id3string += "
"; + + if(id3['Album/Movie/Show title']) { + + id3string += "

Album: " + id3['Album/Movie/Show title'] + "

"; + } + + if(id3['Track number/Position in set']) { + + id3string += "

Track: " + id3['Track number/Position in set'] + "

"; + } + + if(id3['Year']) { + + id3string += "

Year: " + id3['Year'] + "

"; + } + + id3string += "
"; + id3string += "
"; + + this.ofmTrack = null; + + id3element.innerHTML = id3string; + + // TODO: Make these elements configurable (will be part of the future refactor). + this.el.progressBar = document.getElementById( 'progressbar' ); + this.el.preloadBar = document.getElementById( 'preloadbar' ); + this.el.playPause = document.getElementById( 'playpause' ); + + this.el.playPause.onclick = function( ev ) + { + player.setPlaying(!player.playing); + + return false; + }; + + // musicbrainz + musicmetric queries + /* Disabled for now, as there is no backend to handle this request. + $.ajax({ + type: "GET", + url: "http://jsmad.org/musicbrainz/" + escape(artist), + dataType: "xml", + success: function(xml) { + var doc = $(xml); + console.log("real artist name = " + doc.find('artist').children('name').text()); + var artist_id = doc.find('artist').attr('id'); + console.log("musicbrainz artist id = " + artist_id); + + var icons = { + facebook: 'http://facebook.com/favicon.ico', + lastfm: 'http://last.fm/favicon.ico', + myspace: 'http://myspace.com/favicon.ico', + twitter: 'http://twitter.com/favicon.ico', + youtube: 'http://www.youtube.com/favicon.ico', + }; + + $.ajax({ + type: "GET", + url: "http://jsmad.org/musicmetric/musicbrainz:" + artist_id, + dataType: "json", + success: function(json) { + console.log("success? " + json.success); + var up_img = ''; + var down_img = ''; + var equal_img = ''; + + for(var platform in json.response.fans) { + if(!json.response.fans.hasOwnProperty(platform)) continue; + if(platform == "total") continue; + + var fans = json.response.fans[platform]; + var previousFans = parseInt(fans.previous); + var currentFans = parseInt(fans.current); + var totalFans = parseInt(fans.total); + console.log("previous/current fans? " + previousFans + "/" + currentFans); + $('#meta_info').append('

' + (Math.abs(currentFans - previousFans) < 5 ? equal_img : (currentFans > previousFans ? up_img : down_img)) + ' ' + platform + ': ' + (totalFans > 1000000 ? ((Math.floor(totalFans / 100000) / 10.0) + "M") : (totalFans > 1000 ? ((Math.floor(totalFans / 100) / 10.0) + "K") : totalFans)) + ' fans' + '

'); + } + $('#artist_span').append(' ' + json.response.fans.total.total + ' fans'); + } + }); + } + }); + */ + } + + this.player.onProgress = function( current, total, preload ) + { + self.onProgress(current, total, preload); + } + + this.player.onPlay = function() + { + self.el.playPause.className = 'button pause'; + }; + + this.player.onPause = function() + { + self.el.playPause.className = 'button play'; + }; + + this.player.createDevice(); + } + + }; + + var mhd = new MHD( { + ofmAPIKey : 'Q5Bd7987TmfsNVOHP9Zt', + el : { + // playPause : '#playpause', + // preloadBar : '#preloadbar', + // progressBar: '#progressbar', + fileChooser: '#fileChooser', + trackId : '#ofm', + id3 : '#ID3' + } + } ); + + window.onload = function() + { + mhd.onPageLoad(); + }; + +} )(); \ No newline at end of file diff --git a/officialfm.js b/officialfm.js new file mode 100644 index 0000000..68777c7 --- /dev/null +++ b/officialfm.js @@ -0,0 +1,405 @@ +if(typeof jQuery == 'undefined') { + alert('jQuery must be loaded for officialfm-javascript to work.'); +} + +function OfficialFM(api_key, preload_player) { + this.api_key = api_key; + + if(preload_player) OfficialFM.is_player_loaded(); +} + +OfficialFM.VERSION = '0.0.1'; +OfficialFM.API_URL = 'http://api.official.fm/'; + +OfficialFM.LOADING_TRIES_INTERVAL = 250; /* in ms */ +OfficialFM.MAX_LOADING_TRIES = 30000 / OfficialFM.LOADING_TRIES_INTERVAL; /* try loading for 30 seconds */ + +OfficialFM.player_loading = false; +OfficialFM.loading_tries = 0; + +OfficialFM.is_player_loaded = function () { + if (typeof OfficialFM.Player == "undefined") { + if (!OfficialFM.player_loading) { + OfficialFM.player_loading = true; + $.getScript("https://github.com/officialfm/officialfm-javascript/raw/master/player_api.js"); + } + return false; + } + return true; +} + +OfficialFM.with_player = function (callback) { + if(!callback) return; + + if(OfficialFM.is_player_loaded()) { + callback(); + } else { + if(OfficialFM.loading_tries < OfficialFM.MAX_LOADING_TRIES) { + OfficialFM.loading_tries++; + setTimeout(function () { + OfficialFM.with_player(callback); + }, OfficialFM.LOADING_TRIES_INTERVAL); + } + } +} + +/** + * Internal function used to query the server easily + */ +OfficialFM.prototype.call_api = function(sub_url, callback, other_params) { + var default_params = { + key: this.api_key, + format: 'json' + } + var params = default_params; + for (attrname in other_params) { + if(attrname == 'limit') { + params['api_max_responses'] = other_params[attrname]; + } else if(attrname == 'embed') { + params['api_embed_codes'] = other_params[attrname]; + } else { + params[attrname] = other_params[attrname]; + } + } + + var url = OfficialFM.API_URL + sub_url; + $.ajax({ + url: url, + data: params, + dataType: 'jsonp', + success: callback + }); +} + +/** + * Create an Official.fm player + * + * Valid options: + * - container_id : string : id of the container. ex : 'player_container' + * - type : string : content type : track/playlist (todo : user, dynamic tracklist..) + * - id : content id : track/playlist's id + * - aspect : optional. string : aspect of player : [large], standard, artwork, small, mini. ex : 'artwork' + * - skin : optional. number : skin id (null = default skin). ex : 123 + * - width : optional. string or number : width of player in pixel - or percentage when used with %. (valid only for artwork & small players). ex : '300' or '60%' + * - onReady : listener with no param. Called when player is ready to play. + * - onPlay : listener with 1 param track_id. Called when a track starts playing + * - onPause : listener with 1 param track_id. Called when a track is being paused + * - onProgress : listener with 1 param object {played_seconds, played_percent, total}. Called twice per second minimum. + * - onChangeTrack : listener with 1 param track_id. Called when player switch to another track + * - onChangeTracklist : listener with no param. Called when player switch to another tracklist + * - onTracklistEnd : listener with no param. Called when tracklist's end is reached. + * + * Subsequent calls to player() with the same div_id will replace + * previous instances of the player hosted in the given div + * + */ +OfficialFM.prototype.player = function (options, callback) { + if(!options.hasOwnProperty('container_id')) { + options.container_id = 'player_container'; + } + + if(!options.hasOwnProperty('type')) { + options.type = 'track'; + } + + if(!options.hasOwnProperty('aspect')) { + options.aspect = 'small'; + } + + OfficialFM.with_player(function() { + var _player = OfficialFM.Player.create(options); + /* Hack to add play_track to a player */ + _player.play_track = function(track_id) { + this.play(track_id, OfficialFM.Player.build_feed('track', track_id)); + } + callback(_player); + }); +} + +/** + * Create an Official.fm track player + * + * @param div_id The ID of the div the player will be put in + * @param track_id content id : The ID of the track that should be played + * @param options : + * - callback: function taking the player object as an argument + * - all other options valid in OfficialFM.player() + * + * Subsequent calls to track_player() with the same div_id will replace + * previous instances of the player hosted in the given div + * + * Example: + * ofm.track_player('player_div', 226556) + */ +OfficialFM.prototype.track_player = function (div_id, track_id, options) { + if(!options) options = {}; + options.container_id = div_id; + options.type = 'track'; + options.id = track_id; + if(!options.hasOwnProperty('callback')) options.callback = function() {}; + + this.player(options, options.callback); +} + +/** + * Create an Official.fm playlist player + * + * @param div_id The ID of the div the player will be put in + * @param track_id content id : The ID of the track that should be played + * @param options : + * - callback: function taking the player object as an argument + * - all other options valid in OfficialFM.player() + * + * Subsequent calls to playlist_player() with the same div_id will replace + * previous instances of the player hosted in the given div + */ +OfficialFM.prototype.playlist_player = function (div_id, playlist_id, options) { + if(!options) options = {}; + options.container_id = div_id; + options.type = 'playlist'; + options.id = playlist_id; + if(!options.hasOwnProperty('callback')) options.callback = function() {}; + + this.player(options, options.callback); +} + +/* ==================== users functions ===================== */ + +/** + * Search for users + * + * @param string search_param: a search parameter (eg + name of the user) + * @param int limit (50) limit per page + * @User list + */ +OfficialFM.prototype.users = function (search_term, callback, options) { + this.call_api('search/users/' + encodeURI(search_term), callback, options); +} + +OfficialFM.prototype.each_user = function (search_term, callback, options) { + this.users(search_term, function(users) { $.each(users, callback) }, options); +} + +/** + * Retrieve information about a specific user + * + * @param string user_id: id or login + * @a User object + */ +OfficialFM.prototype.user = function (user_id, callback, options) { + this.call_api('user/' + user_id, function(data) { callback(data[0]); }, {}); +} + +/** + * Retrieve a list of the tracks of this user + * + * @param string user_id: id or login + * @param integer limit (50) limit per page + * @param bool embed (false) should embed codes be included in the response + * @array Track list + */ +OfficialFM.prototype.user_tracks = function (user_id, callback, options) { + this.call_api('user/' + user_id + '/tracks', callback, options); +} + +OfficialFM.prototype.each_user_track = function (search_term, callback, options) { + this.user_tracks(search_term, function(user_tracks) { $.each(user_tracks, callback) }, options); +} + +/** +* Retrieve a list of the playlists of this user +* +* @param string user_id: id or login +* @param integer limit (50) limit per page +* @param bool embed (false) should embed codes be included in the response +* @array Playlist list +*/ +OfficialFM.prototype.user_playlists = function (user_id, callback, options) { + this.call_api('user/' + user_id + '/playlists', callback, options); +} + +OfficialFM.prototype.each_user_playlists = function (search_term, callback, options) { + this.user_playlists(search_term, function(user_playlists) { $.each(user_playlists, callback) }, options); +} + +/** + * Retrieve a list of the subscribers of this user + * + * @param string user_id: id or login + * @param integer limit (50) limit per page + * @array User list + */ +OfficialFM.prototype.user_subscribers = function (user_id, callback, options) { + this.call_api('user/' + user_id + '/subscribers', callback, options); +} + +OfficialFM.prototype.each_user_subscribers = function (search_term, callback, options) { + this.user_subscribers(search_term, function(user_subscribers) { $.each(user_subscribers, callback) }, options); +} + +/** + * Retrieve a list of the subscriptions of this user + * + * @param string user_id: id or login + * @param integer limit (50) limit per page + * @array User list + */ +OfficialFM.prototype.user_subscriptions = function (user_id, callback, options) { + this.call_api('user/' + user_id + '/subscriptions', callback, options); +} + +OfficialFM.prototype.each_user_subscriptions = function (search_term, callback, options) { + this.user_subscriptions(search_term, function(user_subscriptions) { $.each(user_subscriptions, callback) }, options); +} + +/** + * Retrieve a list of the contacts of this user + * + * @param string user_id: id or login + * @param integer limit (50) limit per page + * @param bool embed (false) should embed codes be included in the response + * @array User list + */ +OfficialFM.prototype.user_contacts = function (user_id, callback, options) { + this.call_api('user/' + user_id + '/contacts', callback, options); +} + +OfficialFM.prototype.each_user_contacts = function (search_term, callback, options) { + this.user_contacts(search_term, function(user_contacts) { $.each(user_contacts, callback) }, options); +} + +/* ==================== tracks functions ===================== */ + +/** + * Search for tracks + * + * @param string search_param: a search parameter (eg + name of the track) + * @param integer limit (50) limit per page (optional) + * @array Track list + */ +OfficialFM.prototype.tracks = function (search_term, callback, options) { + this.call_api('search/tracks/' + encodeURI(search_term), callback, options); +} + +OfficialFM.prototype.each_track = function (search_term, callback, options) { + this.tracks(search_term, function(tracks) { $.each(tracks, callback) }, options); +} + +/** + * Retrieve information about a specific track + * + * Note: http://official + fm/developers/simple_api#track_show + * says that api_max_responses is a valid parameter + Why escapes me + + * + * @param string track_id: id + * @param bool embed (false) should embed codes be included in the response + * @array Track + */ +OfficialFM.prototype.track = function (track_id, callback, options) { + this.call_api('track/' + track_id, function(data) { callback(data[0]); }, options); +} + +/** + * Retrieve users that have voted for this track + * + * @param string track_id: id + * @param integer limit (50) limit per page + * @array User list + */ +OfficialFM.prototype.track_votes = function (track_id, callback, options) { + this.call_api('track/' + track_id + '/votes', callback, options); +} + +OfficialFM.prototype.each_track_vote = function (search_term, callback, options) { + this.track_votes(search_term, function(track_votes) { $.each(track_votes, callback) }, options); +} + +/** + * Retrieve 200 tracks of selected chart + * + * @param string charting: 'today', 'week', 'month', 'year' or 'all_time' + * @param string genre: Genre string ('Electronic', 'Rock', 'Jazz', ...) (optional) + * @param string country: ISO country id (CH, FR, UK) (optional) + * @param bool embed (false) should embed codes be included in the response (optional) + * @param integer limit (200) limit per page (optional) + * @array Track list + */ +OfficialFM.prototype.charts = function (charting, callback, options) { + this.call_api('tracks/charts', callback, $.extend({}, options, { charting: charting })); +} + +OfficialFM.prototype.each_chart = function (search_term, callback, options) { + this.charts(search_term, function(charts) { $.each(charts, callback) }, options); +} + +/** + * Retrieve 200 latest tracks + * + * @param string genre: Genre string (Electronic, Rock, Jazz, + + + ) (optional) + * @param string country: ISO country id (CH, FR, UK) (optional) + * @param bool embed (false) should embed codes be included in the response (optional) + * @param integer limit (200) limit per page (optional) + * @array Track list + */ +OfficialFM.prototype.latest = function (callback, options) { + this.call_api('tracks/latest', callback, options); +} + +OfficialFM.prototype.each_latest = function (callback, options) { + this.latest(function(latests) { $.each(latests, callback) }, options); +} + +/* ==================== playlists functions ===================== */ + +/** + * Search for playlists + * + * @param string search_param: a search parameter (eg + name of the playlist) + * @param integer limit (50) limit per page (optional) + * @array Playlist list + */ + OfficialFM.prototype.playlists = function (search_param, callback, options) { + this.call_api('search/playlists/' + encodeURI(search_param), function(result) { + callback($.map(result, OfficialFM.improve_playlist)); + }, options); + } + + OfficialFM.prototype.each_playlist = function (search_term, callback, options) { + this.playlists(search_term, function(playlists) { $.each(playlists, callback) }, options); + } + +/** + * Retrieve information about a specific playlist + * + * @param string playlist_id: id + * @param bool embed (false) should embed codes be included in the response + * @array Playlist + */ + OfficialFM.prototype.playlist = function (playlist_id, callback, options) { + this.call_api('playlist/' + playlist_id, function(result) { + callback(OfficialFM.improve_playlist(result[0])); + }, options); + } + +/** + * Retrieve users that have voted for this playlist + * + * @param string playlist_id: id + * @param integer limit (50) limit per page + * @array User list + */ + OfficialFM.prototype.playlist_votes = function (playlist_id, callback, options) { + this.call_api('playlist/' + playlist_id + '/votes', callback, options); + } + + OfficialFM.prototype.each_playlist_vote = function (search_term, callback, options) { + this.playlist_votes(search_term, function(playlist_votes) { $.each(playlist_votes, callback) }, options); + } + + /* Hack to improve playlist id lists (see issue #4 in sandbox-api) */ + OfficialFM.improve_playlist = function (playlist) { + playlist.running_time = playlist['length']; + /* TODO: actually improve the item list ha*/ + return playlist; + } +