/* * Copyright (C) 2005-2009 Team XBMC * http://xbmc.org * * This Program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This Program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with XBMC Remote; see the file license. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ package org.xbmc.jsonrpc.client; import java.util.ArrayList; import java.util.Iterator; import org.codehaus.jackson.JsonNode; import org.xbmc.api.business.INotifiableManager; import org.xbmc.api.data.IControlClient; import org.xbmc.api.data.IMusicClient; import org.xbmc.api.data.IControlClient.ICurrentlyPlaying; import org.xbmc.api.info.PlayStatus; import org.xbmc.api.object.Album; import org.xbmc.api.object.Artist; import org.xbmc.api.object.Genre; import org.xbmc.api.object.Host; import org.xbmc.api.object.ICoverArt; import org.xbmc.api.object.Song; import org.xbmc.api.type.MediaType; import org.xbmc.api.type.SortType; import org.xbmc.jsonrpc.Connection; import android.graphics.Bitmap; /** * Takes care of every music related stuff, notably the music database. * * @author Team XBMC */ public class MusicClient extends Client implements IMusicClient { public static final String TAG = "MusicClient"; public static final int VIEW_ALBUMS = 1; public static final int VIEW_SONGS = 2; public static final int PLAYLIST_ID = 0; public static final String LIBRARY_TYPE = "songs"; public static final int PLAYLIST_LIMIT = 100; /** * Class constructor needs reference to HTTP client connection * @param connection */ public MusicClient(Connection connection) { super(connection); } /** * Updates host info on the connection. * @param host */ public void setHost(Host host) { mConnection.setHost(host); } /** * Adds an album to the current playlist. * @param album Album * @return True on success, false otherwise. */ public boolean addToPlaylist(INotifiableManager manager, Album album, int sortBy, String sortOrder) { return mConnection.getString(manager, "Playlist.Add", obj().p("playlistid", PLAYLIST_ID).p("item", obj().p("albumid", album.id))).equals("OK"); } /** * Adds all songs from an artist to the current playlist. * @param artist Artist * @return True on success, false otherwise. */ public boolean addToPlaylist(INotifiableManager manager, Artist artist, int sortBy, String sortOrder) { return mConnection.getString(manager, "Playlist.Add", obj().p("playlistid", PLAYLIST_ID).p("item", obj().p("artist", artist.id))).equals("OK"); } /** * Adds all songs from a genre to the current playlist. * @param genre Genre * @return True on success, false otherwise. */ public boolean addToPlaylist(INotifiableManager manager, Genre genre, int sortBy, String sortOrder) { return mConnection.getString(manager, "Playlist.Add", obj().p("playlistid", PLAYLIST_ID).p("item", obj().p("genreid", genre.id))).equals("OK"); } /** * Adds songs of a genre from an artist to the current playlist. * @param artist Artist * @param genre Genre * @return True on success, false otherwise. */ public boolean addToPlaylist(INotifiableManager manager, Artist artist, Genre genre, int sortBy, String sortOrder) { return mConnection.getString(manager, "Playlist.Add", obj().p("playlistid", PLAYLIST_ID).p("item", obj().p("genreid", genre.id).p("artistid", artist.id))).equals("OK"); } /** * Adds a song to the current playlist. * @param song Song to add * @return True on success, false otherwise. */ public boolean addToPlaylist(INotifiableManager manager, Song song) { return mConnection.getString(manager, "Playlist.Add", obj().p("playlistid", PLAYLIST_ID).p("item", obj().p("songid", song.id))).equals("OK"); } /** * Returns how many items are in the playlist. * @return Number of items in the playlist */ public int getPlaylistSize(INotifiableManager manager) { return mConnection.getInt(manager, "Playlist.GetProperties", obj().p("playlistid", PLAYLIST_ID).p(PARAM_PROPERTIES, arr().add("size")), "size"); } /** * Retrieves the currently playing song number in the playlist. * @return Number of items in the playlist */ public int getPlaylistPosition(INotifiableManager manager) { return mConnection.getInt(manager, "Player.GetItem", obj().p("playerid", getActivePlayerId(manager)).p(PARAM_PROPERTIES, arr().add("position")), "position"); } /** * Sets the media at playlist position position to be the next item to be played. * @param position New position, starting with 0. * @return True on success, false otherwise. */ public boolean setPlaylistPosition(INotifiableManager manager, int position) { int playerid = getActivePlayerId(manager); if(playerid == -1) return mConnection.getString(manager, "Player.Open", obj().p("item", obj().p("playlistid", PLAYLIST_ID).p("position", position))).equals("OK"); else return mConnection.getString(manager, "Player.GoTo", obj().p("playerid", getActivePlayerId(manager)).p("position", position)).equals("OK"); } /** * Removes media from the current playlist. It is not possible to remove the media if it is currently being played. * @param position Position to remove, starting with 0. * @return True on success, false otherwise. */ public boolean removeFromPlaylist(INotifiableManager manager, int position) { return mConnection.getString(manager, "Playlist.Remove", obj().p("playlistid", PLAYLIST_ID).p("position", position)).equals("OK"); } /** * Removes media from the current playlist. It is not possible to remove the media if it is currently being played. * @param position Complete path (including filename) of the media to be removed. * @return True on success, false otherwise. */ public boolean removeFromPlaylist(INotifiableManager manager, String path) { JsonNode playlistitems = mConnection.getJson(manager, "Playlist.GetItems", obj().p("playlistid", PLAYLIST_ID).p(PARAM_PROPERTIES, arr().add("position").add("file"))); for (Iterator<JsonNode> i = playlistitems.getElements(); i.hasNext();) { JsonNode jsonItem = (JsonNode)i.next(); if(getString(jsonItem,"file").toLowerCase().equals(path.toLowerCase())) return mConnection.getString(manager, "Playlist.Remove", obj().p("playlistid", PLAYLIST_ID).p("position", getInt(jsonItem,"position"))).equals("OK"); } return false; } /** * Returns the first {@link PLAYLIST_LIMIT} songs of the playlist. * @return Songs in the playlist. */ public ArrayList<String> getPlaylist(INotifiableManager manager) { JsonNode jsonItems = mConnection.getJson(manager, "PlayList.GetItems", obj().p("playlistid", PLAYLIST_ID).p("limits", obj().p("start", 0).p("end", PLAYLIST_LIMIT)).p("properties", arr().add("file"))); final JsonNode jsonSongs = jsonItems.get("items"); final ArrayList<String> files = new ArrayList<String>(); if (jsonSongs != null) { for (Iterator<JsonNode> i = jsonSongs.getElements(); i.hasNext();) { JsonNode jsonSong = (JsonNode)i.next(); files.add(getString(jsonSong, "file")); } } return files; } /** * Clears current playlist * @return True on success, false otherwise. */ public boolean clearPlaylist(INotifiableManager manager) { return mConnection.getString(manager, "Playlist.Clear", obj().p("playlistid", PLAYLIST_ID)).equals("OK"); } /** * Adds a song to the current playlist and plays it. * @param song Song * @return True on success, false otherwise. */ public boolean play(INotifiableManager manager, Song song) { int size = mConnection.getInt(manager, "Playlist.GetProperties", obj().p("playlistid", PLAYLIST_ID), "size"); if(addToPlaylist(manager, song)) return setPlaylistPosition(manager, size); else return false; } public boolean play(INotifiableManager manager){ return mConnection.getString(manager, "Player.Open", obj().p("item", obj().p("playlistid", PLAYLIST_ID).p("position", 0))).equals("OK"); } /** * Plays an album. Playlist is previously cleared. * @param album Album to play * @param sortBy Sort field, see SortType.* * @param sortOrder Sort order, must be either SortType.ASC or SortType.DESC. * @return True on success, false otherwise. */ public boolean play(INotifiableManager manager, Album album, int sortBy, String sortOrder) { if(clearPlaylist(manager)) if(addToPlaylist(manager, album, sortBy, sortOrder)) return play(manager); else return false; else return false; } /** * Plays all songs of a genre. Playlist is previously cleared. * @param genre Genre * @param sortBy Sort field, see SortType.* * @param sortOrder Sort order, must be either SortType.ASC or SortType.DESC. * @return True on success, false otherwise. */ public boolean play(INotifiableManager manager, Genre genre, int sortBy, String sortOrder) { if(clearPlaylist(manager)) if(addToPlaylist(manager, genre, sortBy, sortOrder)) return play(manager); else return false; else return false; } /** * Plays all songs from an artist. Playlist is previously cleared. * @param artist Artist * @param sortBy Sort field, see SortType.* * @param sortOrder Sort order, must be either SortType.ASC or SortType.DESC. * @return True on success, false otherwise. */ public boolean play(INotifiableManager manager, Artist artist, int sortBy, String sortOrder) { if(clearPlaylist(manager)) if(addToPlaylist(manager, artist, sortBy, sortOrder)) return play(manager); else return false; else return false; } /** * Plays songs of a genre from an artist. Playlist is previously cleared. * @param artist Artist * @param genre Genre * @return True on success, false otherwise. */ public boolean play(INotifiableManager manager, Artist artist, Genre genre) { if(clearPlaylist(manager)) if(addToPlaylist(manager, artist, genre, SortType.TITLE, "descending")) return play(manager); else return false; else return false; } /** * Starts playing/showing the next media/image in the current playlist * or, if currently showing a slidshow, the slideshow playlist. * @return True on success, false otherwise. */ public boolean playNext(INotifiableManager manager) { return mConnection.getString(manager, "Player.GoNext", obj().p("playerid", getActivePlayerId(manager))).equals("OK"); } /** * Starts playing/showing the previous media/image in the current playlist * or, if currently showing a slidshow, the slideshow playlist. * @return True on success, false otherwise. */ public boolean playPrev(INotifiableManager manager) { return mConnection.getString(manager, "Player.GoPrevious", obj().p("playerid", getActivePlayerId(manager))).equals("OK"); } /** * Sets the media at playlist position position to be the next item to be * played. Position starts at 0, so SetPlaylistSong(5) sets the position * to the 6th song in the playlist. * @param pos Position * @return true on success, false otherwise. */ public boolean playlistSetSong(INotifiableManager manager, int pos) { return setPlaylistPosition(manager, pos); } /** * Sets current playlist to "0" * @return True on success, false otherwise. */ public boolean setCurrentPlaylist(INotifiableManager manager) { return mConnection.getString(manager, "Player.Open", obj().p("item", obj().p("playlistid", PLAYLIST_ID))).equals("OK"); } /** * Gets all albums from database * @param sortBy Sort field, see SortType.* * @param sortOrder Sort order, must be either SortType.ASC or SortType.DESC. * @return All albums */ public ArrayList<Album> getAlbums(INotifiableManager manager, int SortBy, String sortOrder){ return getAlbums(manager, obj(), SortBy, sortOrder); } private ArrayList<Album> getAlbums(INotifiableManager manager, ObjNode obj, int sortBy, String sortOrder) { obj = sort(obj.p(PARAM_PROPERTIES, arr().add("artist").add("year").add("thumbnail")), sortBy, sortOrder); final ArrayList<Album> albums = new ArrayList<Album>(); final JsonNode result = mConnection.getJson(manager, "AudioLibrary.GetAlbums", obj); final JsonNode jsonAlbums = result.get("albums"); if(jsonAlbums != null){ for (Iterator<JsonNode> i = jsonAlbums.getElements(); i.hasNext();) { JsonNode jsonAlbum = (JsonNode)i.next(); albums.add(new Album( getInt(jsonAlbum, "albumid"), getString(jsonAlbum, "label"), getString(jsonAlbum, "artist"), getInt(jsonAlbum, "year"), getString(jsonAlbum, "thumbnail", "") )); } } return albums; } /** * Gets all albums with given artist IDs * @param artistIDs Array of artist IDs * @return All compilation albums */ public ArrayList<Album> getAlbums(INotifiableManager manager, ArrayList<Integer> artistIDs) { final ArrayList<Album> albums = new ArrayList<Album>(); for(int id : artistIDs){ albums.addAll(getAlbums(manager, obj().p("filter", obj().p("artistid", id)), SortType.TITLE, "descending")); } return albums; } /** * Gets all albums of an artist from database * @param artist Artist * @param sortBy Sort field, see SortType.* * @param sortOrder Sort order, must be either SortType.ASC or SortType.DESC. * @return Albums with an artist */ public ArrayList<Album> getAlbums(INotifiableManager manager, Artist artist, int sortBy, String sortOrder) { return getAlbums(manager, obj().p("filter", obj().p("artistid", artist.id)), sortBy, sortOrder); } /** * Gets all albums of with at least one song in a genre * @param genre Genre * @param sortBy Sort field, see SortType.* * @param sortOrder Sort order, must be either SortType.ASC or SortType.DESC. * @return Albums of a genre */ public ArrayList<Album> getAlbums(INotifiableManager manager, Genre genre, int sortBy, String sortOrder) { return getAlbums(manager, obj().p("filter", obj().p("genreid", genre.id)), sortBy, sortOrder); } /** * Gets all artists from database * @param albumArtistsOnly If set to true, hide artists who appear only on compilations. * @return All albums */ public ArrayList<Artist> getArtists(INotifiableManager manager, ObjNode obj, boolean albumArtistsOnly) { obj.p(PARAM_PROPERTIES, arr().add("thumbnail")).p("albumartistsonly", albumArtistsOnly); obj = sort(obj, SortType.ARTIST, "descending"); final ArrayList<Artist> artists = new ArrayList<Artist>(); final JsonNode result = mConnection.getJson(manager, "AudioLibrary.GetArtists", obj); if(result != null){ final JsonNode jsonArtists = result.get("artists"); for (Iterator<JsonNode> i = jsonArtists.getElements(); i.hasNext();) { JsonNode jsonArtist = (JsonNode)i.next(); artists.add(new Artist( getInt(jsonArtist, "artistid"), getString(jsonArtist, "label"), getString(jsonArtist, "thumbnail", "") )); } } return artists; } public ArrayList<Artist> getArtists(INotifiableManager manager, boolean albumArtistsOnly) { return getArtists(manager, obj(), albumArtistsOnly); } /** * Gets all artists with at least one song of a genre. * @param genre Genre * @param albumArtistsOnly If set to true, hide artists who appear only on compilations. * @return Albums with a genre */ public ArrayList<Artist> getArtists(INotifiableManager manager, Genre genre, boolean albumArtistsOnly) { return getArtists(manager, obj().p("filter", obj().p("genreid", genre.id)), albumArtistsOnly); } /** * Gets all genres from database * @return All genres */ public ArrayList<Genre> getGenres(INotifiableManager manager) { final ArrayList<Genre> genres = new ArrayList<Genre>(); final JsonNode result = mConnection.getJson(manager, "AudioLibrary.GetGenres", sort(obj(), SortType.TITLE, "descending")); final JsonNode jsonGenres = result.get("genres"); for (Iterator<JsonNode> i = jsonGenres.getElements(); i.hasNext();) { JsonNode jsonGenre = (JsonNode)i.next(); genres.add(new Genre( getInt(jsonGenre, "genreid"), getString(jsonGenre, "label") )); } return genres; } /** * Updates the album object with additional data from the albuminfo table * @param album * @return Updated album */ public Album updateAlbumInfo(INotifiableManager manager, Album album) { final JsonNode result = mConnection.getJson(manager, "AudioLibrary.GetAlbumDetails", obj().p("albumid", album.id).p("properties", arr().add("genre").add("albumlabel").add("rating"))); final JsonNode jsonAlbum = result.get("albumdetails"); album.genres = getString(jsonAlbum, "genre"); album.label = getString(jsonAlbum, "albumlabel"); album.rating = getInt(jsonAlbum, "rating"); return album; } /** * Updates the artist object with additional data from the artistinfo table * @param artist * @return Updated artist */ public Artist updateArtistInfo(INotifiableManager manager, Artist artist) { final JsonNode result = mConnection.getJson(manager, "AudioLibrary.GetArtistDetails", obj().p("artistid", artist.id).p("properties", arr().add("born").add("formed").add("mood").add("style").add("description"))); final JsonNode jsonArtist = result.get("artistdetails"); artist.born = getString(jsonArtist, "born"); artist.formed = getString(jsonArtist, "formed"); artist.moods = getString(jsonArtist, "mood"); artist.styles = getString(jsonArtist, "style"); artist.biography = getString(jsonArtist, "description"); return artist; } /** * Returns a list containing tracks of a certain condition. * @param sqlCondition SQL condition which tracks to return * @return Found tracks **/ private ArrayList<Song> getSongs(INotifiableManager manager, ObjNode obj) { final ArrayList<Song> songs = new ArrayList<Song>(); final JsonNode result = mConnection.getJson(manager, "AudioLibrary.GetSongs", obj); final JsonNode jsonAlbums = result.get("songs"); for (Iterator<JsonNode> i = jsonAlbums.getElements(); i.hasNext();) { JsonNode jsonSong = (JsonNode)i.next(); songs.add(new Song( getInt(jsonSong, "songid"), getString(jsonSong, "label"), getString(jsonSong, "artist"), getString(jsonSong, "album"), getInt(jsonSong, "track"), getInt(jsonSong, "duration"), getString(jsonSong, ""), getString(jsonSong, "file"), getString(jsonSong, "thumbnail", "") )); } return songs; } private ArrayList<Song> getSongs(INotifiableManager manager, ObjNode obj, int sortBy, String sortOrder) { return getSongs(manager, sort(obj.p(PARAM_PROPERTIES, arr().add("artist").add("album").add("track").add("duration").add("file").add("thumbnail")), sortBy, sortOrder)); } /** * Returns a list containing all tracks of an album. The list is sorted by filename. * @param album Album * @param sortBy Sort field, see SortType.* * @param sortOrder Sort order, must be either SortType.ASC or SortType.DESC. * @return All tracks of an album */ public ArrayList<Song> getSongs(INotifiableManager manager, Album album, int sortBy, String sortOrder) { return getSongs(manager, obj().p("filter", obj().p("albumid", album.id)), sortBy, sortOrder); } /** * Returns a list containing all tracks of an artist. The list is sorted by album name, filename. * @param artist Artist * @param sortBy Sort field, see SortType.* * @param sortOrder Sort order, must be either SortType.ASC or SortType.DESC. * @return All tracks of the artist */ public ArrayList<Song> getSongs(INotifiableManager manager, Artist artist, int sortBy, String sortOrder) { return getSongs(manager, obj().p("filter", obj().p("artistid", artist.id)), sortBy, sortOrder); } /** * Returns a list containing all tracks of a genre. The list is sorted by artist, album name, filename. * @param genre Genre * @param sortBy Sort field, see SortType.* * @param sortOrder Sort order, must be either SortType.ASC or SortType.DESC. * @return All tracks of the genre */ public ArrayList<Song> getSongs(INotifiableManager manager, Genre genre, int sortBy, String sortOrder) { return getSongs(manager, obj().p("genreid", genre.id), sortBy, sortOrder); } /** * Returns a list containing all tracks of a genre AND and artist. The list is sorted by * artist, album name, filename. * @param genre Genre * @param sortBy Sort field, see SortType.* * @param sortOrder Sort order, must be either SortType.ASC or SortType.DESC. * @return All tracks of the genre */ public ArrayList<Song> getSongs(INotifiableManager manager, Artist artist, Genre genre, int sortBy, String sortOrder) { return getSongs(manager, obj().p("filter", obj().p("artistid", artist.id).p("genreid", genre.id)), sortBy, sortOrder); } /** * Returns a pre-resized album cover. Pre-resizing is done in a way that * the bitmap at least as large as the specified size but not larger than * the double. * @param manager Postback manager * @param cover Cover object * @param size Minmal size to pre-resize to. * @return Thumbnail bitmap */ public Bitmap getCover(INotifiableManager manager, ICoverArt cover, int size) { String url = null; if(Album.getThumbUri(cover) != ""){ final JsonNode dl = mConnection.getJson(manager, "Files.PrepareDownload", obj().p("path", Album.getThumbUri(cover))); if(dl != null){ JsonNode details = dl.get("details"); if(details != null) url = mConnection.getUrl(getString(details, "path")); } } return getCover(manager, cover, size, url); } /** * Returns a list containing all artist IDs that stand for "compilation". * Best case scenario would be only one ID for "Various Artists", though * there are also just "V.A." or "VA" naming conventions. * @return List of compilation artist IDs */ public ArrayList<Integer> getCompilationArtistIDs(INotifiableManager manager) { ArrayList<Artist> artists = getArtists(manager, sort(obj(), SortType.ARTIST, "ascending"), true); ArrayList<Integer> ids = new ArrayList<Integer>(); for(Artist artist : artists){ if(artist.name.toLowerCase().equals("various artists") || artist.name.toLowerCase().equals("v.a.") || artist.name.toLowerCase().equals("va")) ids.add(artist.id); } return ids; } static ICurrentlyPlaying getCurrentlyPlaying(final JsonNode player, final JsonNode item) { return new IControlClient.ICurrentlyPlaying() { private static final long serialVersionUID = 5036994329211476714L; public String getTitle() { return getString(item, "title"); } public int getTime() { return ControlClient.parseTime(player.get("time")); } public int getPlayStatus() { return getInt(player, "speed"); } public int getPlaylistPosition() { return getInt(player, "position"); } //Workarond for bug in Float.valueOf(): http://code.google.com/p/android/issues/detail?id=3156 public float getPercentage() { try{ return getInt(player, "percentage"); } catch (NumberFormatException e) { } return (float)getDouble(player, "percentage"); } public String getFilename() { return getString(item, "file"); } public int getDuration() { return getInt(item, "duration"); } public String getArtist() { return getString(item, "artist"); } public String getAlbum() { return getString(item, "album"); } public int getMediaType() { return MediaType.MUSIC; } public boolean isPlaying() { return getInt(player, "speed") == PlayStatus.PLAYING; } public int getHeight() { return 0; } public int getWidth() { return 0; } }; } }