Index: AndroidManifest.xml =================================================================== --- AndroidManifest.xml (revision 2441) +++ AndroidManifest.xml (working copy) @@ -114,7 +114,8 @@ a:authorities="net.sourceforge.subsonic.androidapp.provider.SearchSuggestionProvider"/> + a:value="net.sourceforge.subsonic.androidapp.activity.QueryReceiverActivity"/> + Index: res/drawable/menu_jukebox.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: res/drawable/menu_jukebox.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: res/drawable-hdpi-v4/menu_jukebox.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: res/drawable-hdpi-v4/menu_jukebox.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: res/layout/button_bar.xml =================================================================== --- res/layout/button_bar.xml (revision 2441) +++ res/layout/button_bar.xml (working copy) @@ -47,6 +47,14 @@ a:layout_weight="1" a:layout_width="0dp" a:layout_height="wrap_content"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file Index: res/menu/jukebox_context.xml =================================================================== --- res/menu/jukebox_context.xml (revision 0) +++ res/menu/jukebox_context.xml (revision 0) @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file Index: res/menu/select_album_context.xml =================================================================== --- res/menu/select_album_context.xml (revision 2441) +++ res/menu/select_album_context.xml (working copy) @@ -15,5 +15,6 @@ a:id="@+id/album_menu_pin" a:title="@string/common.pin" /> + Index: res/menu/select_artist_context.xml =================================================================== --- res/menu/select_artist_context.xml (revision 2441) +++ res/menu/select_artist_context.xml (working copy) @@ -15,5 +15,6 @@ a:id="@+id/artist_menu_pin" a:title="@string/common.pin" /> + Index: res/menu/select_song_context.xml =================================================================== --- res/menu/select_song_context.xml (revision 2441) +++ res/menu/select_song_context.xml (working copy) @@ -15,5 +15,10 @@ a:id="@+id/song_menu_play_last" a:title="@string/common.play_last" /> + + Index: res/values/strings.xml =================================================================== --- res/values/strings.xml (revision 2441) +++ res/values/strings.xml (working copy) @@ -226,6 +226,14 @@ One day left of trial period %d days left of trial period - + + Jukebox + Start + Stop + Next + Previous + Clear + Remove + Add to Jukebox Index: src/net/sourceforge/subsonic/androidapp/activity/JukeboxActivity.java =================================================================== --- src/net/sourceforge/subsonic/androidapp/activity/JukeboxActivity.java (revision 0) +++ src/net/sourceforge/subsonic/androidapp/activity/JukeboxActivity.java (revision 0) @@ -0,0 +1,235 @@ +/* + This file is part of Subsonic. + + Subsonic 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 3 of the License, or + (at your option) any later version. + + Subsonic 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 Subsonic. If not, see . + + Copyright 2009 (C) Sindre Mehus + */ + +package net.sourceforge.subsonic.androidapp.activity; + +import java.util.List; + +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.domain.Jukebox; +import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; +import net.sourceforge.subsonic.androidapp.domain.MusicDirectory.Entry; +import net.sourceforge.subsonic.androidapp.service.MusicService; +import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; +import net.sourceforge.subsonic.androidapp.util.BackgroundTask; +import net.sourceforge.subsonic.androidapp.util.JukeboxSongView; +import net.sourceforge.subsonic.androidapp.util.ProgressListener; +import net.sourceforge.subsonic.androidapp.util.TabActivityBackgroundTask; +import net.sourceforge.subsonic.androidapp.util.Util; +import android.os.Bundle; +import android.view.ContextMenu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.ImageButton; +import android.widget.ListView; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; + +/** + * @author meld0 + * + */ +public class JukeboxActivity extends SubsonicTabActivity implements ProgressListener, OnClickListener, OnSeekBarChangeListener, OnItemClickListener{ + + private ImageButton play, stop, next, prev, shuffle; + private ListView entryList; + private SeekBar gain; + private Jukebox jukebox; + + @Override + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + setContentView(R.layout.jukebox); + + jukebox = new Jukebox(); + + play = (ImageButton) findViewById(R.id.jukebox_start); + stop = (ImageButton) findViewById(R.id.jukebox_stop); + prev = (ImageButton) findViewById(R.id.jukebox_previous); + next = (ImageButton) findViewById(R.id.jukebox_next); + shuffle = (ImageButton) findViewById(R.id.jukebox_shuffle); + entryList = (ListView) findViewById(R.id.jukebox_list); + + gain = (SeekBar) findViewById(R.id.jukebox_seek); + gain.setMax(100); + + play.setOnClickListener(this); + stop.setOnClickListener(this); + next.setOnClickListener(this); + prev.setOnClickListener(this); + shuffle.setOnClickListener(this); + gain.setOnSeekBarChangeListener(this); + entryList.setOnItemClickListener(this); + + registerForContextMenu(entryList); + load(); + } + + @Override + public void updateProgress(int messageId){ + + } + + private void load(){ + load(Jukebox.GET); + } + + private void load(String action){ + load(action, ""); + } + + private void load(final String action, final String additional){ + BackgroundTask task = new TabActivityBackgroundTask(this){ + + protected Boolean doInBackground() throws Throwable{ + boolean update = false; + if (!Util.isOffline(JukeboxActivity.this)) { + MusicService musicService = MusicServiceFactory.getMusicService(JukeboxActivity.this); + jukebox.lastAction = action; + jukebox = musicService.getJukebox(jukebox, JukeboxActivity.this, this, additional); + update = true; + } + return update; + } + + protected void done(Boolean result){ + if (jukebox.isSuccess()) { + if (jukebox.lastAction.equals(Jukebox.SET_GAIN)) { + jukebox.setGain((int) (Double.valueOf(additional.split("=")[1]) * 100)); + gain.setProgress(jukebox.getGain()); + } else if (jukebox.lastAction.equals(Jukebox.REMOVE_ITEM)) ((SongListAdapter) entryList.getAdapter()).remove((Entry) entryList.getAdapter().getItem( + Integer.valueOf(additional.split("=")[1]))); + else if (jukebox.lastAction.equals(Jukebox.CLEAR_PLAYLIST)) ((SongListAdapter) entryList.getAdapter()).clear(); + else if (jukebox.lastAction.equals(Jukebox.SKIP_TO_INDEX)) jukebox.setCurrentIndex(Integer.valueOf(additional.split("=")[1])); + + if (jukebox.isNewPlaylist()) entryList.setAdapter(new SongListAdapter(jukebox.getChildren())); + } + } + }; + task.execute(); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo){ + super.onCreateContextMenu(menu, view, menuInfo); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.jukebox_context, menu); + } + + @Override + public boolean onContextItemSelected(MenuItem menuItem){ + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); + switch (menuItem.getItemId()) { + case R.id.jukebox_remove: + load(Jukebox.REMOVE_ITEM, "index=" + info.position); + break; + case R.id.jukebox_clear: + load(Jukebox.CLEAR_PLAYLIST); + break; + default: + return super.onContextItemSelected(menuItem); + } + return true; + } + + @Override + protected void onResume(){ + super.onResume(); + load(); + } + + @Override + public void onClick(View arg0){ + if (arg0.equals(this.play)) { + load(Jukebox.START_PLAYBACK); + } else if (arg0.equals(this.stop)) { + load(Jukebox.STOP_PLAYBACK); + } else if (arg0.equals(this.prev)) { + loadW(); + load(Jukebox.SKIP_TO_INDEX, "index=" + (jukebox.getCurrentIndex() - 1)); // TODO // MAYBE // BETTER // ? + } else if (arg0.equals(this.next)) { + loadW(); + load(Jukebox.SKIP_TO_INDEX, "index=" + (jukebox.getCurrentIndex() + 1)); + } else if (arg0.equals(this.shuffle)) { + load(Jukebox.SHUFFLE_PLAYLIST); + load(); + } + } + + @Override + public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2){ + } + + @Override + public void onStartTrackingTouch(SeekBar arg0){ + } + + @Override + public void onStopTrackingTouch(SeekBar arg0){ + load(Jukebox.SET_GAIN, "gain=" + ((double) arg0.getProgress() / 100)); + } + + private class SongListAdapter extends ArrayAdapter{ + + public SongListAdapter(List entries){ + super(JukeboxActivity.this, android.R.layout.simple_list_item_1, entries); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent){ + JukeboxSongView view; + if (convertView != null && convertView instanceof JukeboxSongView) { + view = (JukeboxSongView) convertView; + } else { + view = new JukeboxSongView(JukeboxActivity.this); + } + MusicDirectory.Entry entry = getItem(position); + + if (position == jukebox.getCurrentIndex()) { + view.setSong(entry, false, true); + } else view.setSong(entry, false, false); + return view; + } + } + + @Override + public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3){ + load(Jukebox.SKIP_TO_INDEX, "index=" + arg3); + } + + public void loadW(){ + if (!Util.isOffline(JukeboxActivity.this)) { + MusicService musicService = MusicServiceFactory.getMusicService(JukeboxActivity.this); + jukebox.lastAction = Jukebox.GET; + try { + jukebox = musicService.getJukebox(jukebox, JukeboxActivity.this, this, ""); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + +} Index: src/net/sourceforge/subsonic/androidapp/activity/SelectAlbumActivity.java =================================================================== --- src/net/sourceforge/subsonic/androidapp/activity/SelectAlbumActivity.java (revision 2441) +++ src/net/sourceforge/subsonic/androidapp/activity/SelectAlbumActivity.java (working copy) @@ -252,6 +252,9 @@ case R.id.album_menu_pin: downloadRecursively(entry.getId(), true, true, false); break; + case R.id.album_menu_jukebox_add: + addToJukebox(entry.getId()); + break; case R.id.song_menu_play_now: getDownloadService().download(songs, false, true, true); break; @@ -261,6 +264,8 @@ case R.id.song_menu_play_last: getDownloadService().download(songs, false, false, false); break; + case R.id.song_menu_jukebox_add: + addToJukebox(entry); default: return super.onContextItemSelected(menuItem); } Index: src/net/sourceforge/subsonic/androidapp/activity/SelectArtistActivity.java =================================================================== --- src/net/sourceforge/subsonic/androidapp/activity/SelectArtistActivity.java (revision 2441) +++ src/net/sourceforge/subsonic/androidapp/activity/SelectArtistActivity.java (working copy) @@ -210,6 +210,9 @@ case R.id.artist_menu_pin: downloadRecursively(artist.getId(), true, true, false); break; + case R.id.artist_menu_jukebox_add: + addToJukebox(artist.getId()); + break; default: return super.onContextItemSelected(menuItem); } Index: src/net/sourceforge/subsonic/androidapp/activity/SubsonicTabActivity.java =================================================================== --- src/net/sourceforge/subsonic/androidapp/activity/SubsonicTabActivity.java (revision 2441) +++ src/net/sourceforge/subsonic/androidapp/activity/SubsonicTabActivity.java (working copy) @@ -23,6 +23,16 @@ import java.util.LinkedList; import java.util.List; +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; +import net.sourceforge.subsonic.androidapp.service.DownloadService; +import net.sourceforge.subsonic.androidapp.service.DownloadServiceImpl; +import net.sourceforge.subsonic.androidapp.service.MusicService; +import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; +import net.sourceforge.subsonic.androidapp.util.Constants; +import net.sourceforge.subsonic.androidapp.util.ImageLoader; +import net.sourceforge.subsonic.androidapp.util.ModalBackgroundTask; +import net.sourceforge.subsonic.androidapp.util.Util; import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -39,16 +49,6 @@ import android.view.View; import android.view.Window; import android.widget.TextView; -import net.sourceforge.subsonic.androidapp.R; -import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; -import net.sourceforge.subsonic.androidapp.service.DownloadService; -import net.sourceforge.subsonic.androidapp.service.DownloadServiceImpl; -import net.sourceforge.subsonic.androidapp.service.MusicService; -import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; -import net.sourceforge.subsonic.androidapp.util.Constants; -import net.sourceforge.subsonic.androidapp.util.ImageLoader; -import net.sourceforge.subsonic.androidapp.util.ModalBackgroundTask; -import net.sourceforge.subsonic.androidapp.util.Util; /** * @author Sindre Mehus @@ -63,6 +63,7 @@ private View musicButton; private View searchButton; private View playlistButton; + private View jukeboxButton; private View nowPlayingButton; @Override @@ -118,6 +119,14 @@ Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent); } }); + + jukeboxButton = findViewById(R.id.button_bar_jukebox); + jukeboxButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Util.startActivityWithoutTransition(SubsonicTabActivity.this, JukeboxActivity.class); + } + }); nowPlayingButton = findViewById(R.id.button_bar_now_playing); nowPlayingButton.setOnClickListener(new View.OnClickListener() { @@ -225,6 +234,22 @@ int visibility = Util.isOffline(this) ? View.GONE : View.VISIBLE; searchButton.setVisibility(visibility); playlistButton.setVisibility(visibility); + jukeboxButton.setVisibility(visibility); + /* + * The following block would check for valid Permissions + * but it is called too often -> network traffic/ problems ? + * + MusicService musicService = MusicServiceFactory.getMusicService(this); + try { + User user = musicService.getUser(this, null); + if(!user.isJukeboxControl()){ + jukeboxButton.setVisibility(View.GONE); + } + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + */ } public void setProgressVisible(boolean visible) { @@ -363,5 +388,67 @@ } } } + + protected void addToJukebox(String id) { + + List entries = new LinkedList(); + MusicService musicService = MusicServiceFactory.getMusicService(this); + MusicDirectory root; + + try { + root = musicService.getMusicDirectory(id, false, SubsonicTabActivity.this, null); + collectEntries(root, entries); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + + StringBuilder build = new StringBuilder(); + build.append("action=add"); + for(MusicDirectory.Entry entry2: entries){ + build.append("&id="); + build.append(entry2.getId()); + } + try { + musicService.getJukebox(null, this, null, build.toString()); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + + protected void addToJukebox(MusicDirectory.Entry entry) { + try { + MusicService musicService = MusicServiceFactory.getMusicService(this); + musicService.getJukebox(null, this, null, "action=add&id="+entry.getId()); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + private List collectEntries(MusicDirectory parent, List songs){ + for (MusicDirectory.Entry song : parent.getChildren(false, true)) { + if (!song.isVideo()) { + songs.add(song); + } + } + for (MusicDirectory.Entry dir : parent.getChildren(true, false)) { + MusicService musicService = MusicServiceFactory.getMusicService(SubsonicTabActivity.this); + try { + collectEntries(musicService.getMusicDirectory(dir.getId(), false, SubsonicTabActivity.this, null), songs); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + return songs; + } + + + } Index: src/net/sourceforge/subsonic/androidapp/domain/Jukebox.java =================================================================== --- src/net/sourceforge/subsonic/androidapp/domain/Jukebox.java (revision 0) +++ src/net/sourceforge/subsonic/androidapp/domain/Jukebox.java (revision 0) @@ -0,0 +1,113 @@ +/* + This file is part of Subsonic. + + Subsonic 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 3 of the License, or + (at your option) any later version. + + Subsonic 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 Subsonic. If not, see . + + Copyright 2009 (C) Sindre Mehus + */ + +package net.sourceforge.subsonic.androidapp.domain; + +import java.io.Serializable; +import java.util.List; + +/** + * @author meld0 + * + */ +public class Jukebox extends MusicDirectory implements Serializable{ + + public String lastAction; + + //get, start, stop, skip, add, clear, remove, shuffle, setGain + public static final String GET = "get"; + public static final String START_PLAYBACK = "start"; + public static final String STOP_PLAYBACK = "stop"; + public static final String SKIP_TO_INDEX = "skip"; + public static final String ADD_TO_PLAYLIST = "add"; + public static final String CLEAR_PLAYLIST = "clear"; + public static final String REMOVE_ITEM = "remove"; + public static final String SHUFFLE_PLAYLIST = "shuffle"; + public static final String SET_GAIN = "setGain"; + + public Jukebox() { + super(); + } + + public boolean isPlaying() { + return playing; + } + + public void setPlaying(boolean playing) { + this.playing = playing; + } + + public int getGain() { + return gain; + } + + public void setGain(int gain) { + this.gain = gain; + } + + public int getCurrentIndex() { + return currentIndex; + } + + public void setCurrentIndex(int currentIndex) { + this.currentIndex = currentIndex; + } + + public boolean isAvailable() { + return available; + } + + public boolean isSuccess() { + return success; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean isNewPlaylist() { + return newPlaylist; + } + + public void setNewPlaylist(boolean newPlaylist) { + this.newPlaylist = newPlaylist; + } + + private boolean playing = false; + private int gain = 0; + private int currentIndex = 0; + private boolean available = false; + private boolean success = false; + private boolean newPlaylist = false; + + public void addChildren(List children){ + super.addChildren(children); + } + + public void reset(){ + this.available = false; + this.success = false; + this.newPlaylist = false; + } + +} Index: src/net/sourceforge/subsonic/androidapp/domain/MusicDirectory.java =================================================================== --- src/net/sourceforge/subsonic/androidapp/domain/MusicDirectory.java (revision 2441) +++ src/net/sourceforge/subsonic/androidapp/domain/MusicDirectory.java (working copy) @@ -59,6 +59,13 @@ } return result; } + + protected void addChildren(List children){ + if(!children.isEmpty()) { + this.children.clear(); + this.children.addAll(children); + } + } public static class Entry implements Serializable { private String id; Index: src/net/sourceforge/subsonic/androidapp/domain/User.java =================================================================== --- src/net/sourceforge/subsonic/androidapp/domain/User.java (revision 0) +++ src/net/sourceforge/subsonic/androidapp/domain/User.java (revision 0) @@ -0,0 +1,43 @@ +/* + This file is part of Subsonic. + + Subsonic 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 3 of the License, or + (at your option) any later version. + + Subsonic 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 Subsonic. If not, see . + + Copyright 2009 (C) Sindre Mehus + */ + +package net.sourceforge.subsonic.androidapp.domain; + +import java.io.Serializable; + +/** + * @author meld0 + * + */ +public class User implements Serializable{ + + //TODO implement missing user attributes + + private boolean jukeboxControl; + + public boolean isJukeboxControl() { + return jukeboxControl; + } + + public void setJukeboxControl(boolean jukeboxControl) { + this.jukeboxControl = jukeboxControl; + } + + +} Index: src/net/sourceforge/subsonic/androidapp/service/CachedMusicService.java =================================================================== --- src/net/sourceforge/subsonic/androidapp/service/CachedMusicService.java (revision 2441) +++ src/net/sourceforge/subsonic/androidapp/service/CachedMusicService.java (working copy) @@ -21,9 +21,11 @@ import android.content.Context; import android.graphics.Bitmap; import net.sourceforge.subsonic.androidapp.domain.Indexes; +import net.sourceforge.subsonic.androidapp.domain.Jukebox; import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; import net.sourceforge.subsonic.androidapp.domain.MusicFolder; import net.sourceforge.subsonic.androidapp.domain.Playlist; +import net.sourceforge.subsonic.androidapp.domain.User; import net.sourceforge.subsonic.androidapp.domain.Version; import net.sourceforge.subsonic.androidapp.domain.SearchResult; import net.sourceforge.subsonic.androidapp.domain.SearchCritera; @@ -199,4 +201,17 @@ restUrl = newUrl; } } + + @Override + public Jukebox getJukebox(Jukebox jukebox, Context context, + ProgressListener progressListener, String action) throws Exception { + return musicService.getJukebox(jukebox, context, progressListener, action); + } + + @Override + public User getUser(Context context, ProgressListener progressListener) + throws Exception { + return musicService.getUser(context, progressListener); + } + } Index: src/net/sourceforge/subsonic/androidapp/service/MusicService.java =================================================================== --- src/net/sourceforge/subsonic/androidapp/service/MusicService.java (revision 2441) +++ src/net/sourceforge/subsonic/androidapp/service/MusicService.java (working copy) @@ -21,9 +21,11 @@ import android.content.Context; import android.graphics.Bitmap; import net.sourceforge.subsonic.androidapp.domain.Indexes; +import net.sourceforge.subsonic.androidapp.domain.Jukebox; import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; import net.sourceforge.subsonic.androidapp.domain.MusicFolder; import net.sourceforge.subsonic.androidapp.domain.Playlist; +import net.sourceforge.subsonic.androidapp.domain.User; import net.sourceforge.subsonic.androidapp.domain.Version; import net.sourceforge.subsonic.androidapp.domain.SearchResult; import net.sourceforge.subsonic.androidapp.domain.SearchCritera; @@ -74,4 +76,8 @@ Version getLatestVersion(Context context, ProgressListener progressListener) throws Exception; String getVideoUrl(Context context, String id); + + Jukebox getJukebox(Jukebox jukebox, Context context, ProgressListener progressListener , String action) throws Exception; + + User getUser(Context context, ProgressListener progressListener) throws Exception; } \ No newline at end of file Index: src/net/sourceforge/subsonic/androidapp/service/RESTMusicService.java =================================================================== --- src/net/sourceforge/subsonic/androidapp/service/RESTMusicService.java (revision 2441) +++ src/net/sourceforge/subsonic/androidapp/service/RESTMusicService.java (working copy) @@ -28,6 +28,7 @@ import android.util.Log; import net.sourceforge.subsonic.androidapp.R; import net.sourceforge.subsonic.androidapp.domain.Indexes; +import net.sourceforge.subsonic.androidapp.domain.Jukebox; import net.sourceforge.subsonic.androidapp.domain.Lyrics; import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; import net.sourceforge.subsonic.androidapp.domain.MusicFolder; @@ -35,10 +36,12 @@ import net.sourceforge.subsonic.androidapp.domain.SearchCritera; import net.sourceforge.subsonic.androidapp.domain.SearchResult; import net.sourceforge.subsonic.androidapp.domain.ServerInfo; +import net.sourceforge.subsonic.androidapp.domain.User; import net.sourceforge.subsonic.androidapp.domain.Version; import net.sourceforge.subsonic.androidapp.service.parser.AlbumListParser; import net.sourceforge.subsonic.androidapp.service.parser.ErrorParser; import net.sourceforge.subsonic.androidapp.service.parser.IndexesParser; +import net.sourceforge.subsonic.androidapp.service.parser.JukeboxParser; import net.sourceforge.subsonic.androidapp.service.parser.LicenseParser; import net.sourceforge.subsonic.androidapp.service.parser.LyricsParser; import net.sourceforge.subsonic.androidapp.service.parser.MusicDirectoryParser; @@ -48,6 +51,7 @@ import net.sourceforge.subsonic.androidapp.service.parser.RandomSongsParser; import net.sourceforge.subsonic.androidapp.service.parser.SearchResult2Parser; import net.sourceforge.subsonic.androidapp.service.parser.SearchResultParser; +import net.sourceforge.subsonic.androidapp.service.parser.UserParser; import net.sourceforge.subsonic.androidapp.service.parser.VersionParser; import net.sourceforge.subsonic.androidapp.service.ssl.SSLSocketFactory; import net.sourceforge.subsonic.androidapp.service.ssl.TrustSelfSignedStrategy; @@ -195,6 +199,53 @@ Util.close(reader); } } + + public Jukebox getJukebox(Jukebox jukebox, Context context, ProgressListener progressListener, String action) throws Exception { + + List parameterNames = new ArrayList(); + List parameterValues = new ArrayList(); + if(jukebox != null){ + parameterNames.add("action"); + parameterValues.add(jukebox.lastAction); + } + if(!action.isEmpty()){ + String[] names_values = action.split("&"); + for(String name_value: names_values){ + String[] pair = name_value.split("="); + parameterNames.add(pair[0]); + parameterValues.add(pair[1]); + } + } + + Reader reader = getReader(context, progressListener, "jukeboxControl", null, parameterNames, parameterValues); + + try { + return new JukeboxParser(context).parse(jukebox, reader, progressListener); + } finally { + Util.close(reader); + } + } + + public User getUser(Context context, ProgressListener progressListener) throws Exception { + + SharedPreferences prefs = Util.getPreferences(context); + int instance = prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1); + String username = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null); + + List parameterNames = new ArrayList(); + List parameterValues = new ArrayList(); + parameterNames.add("username"); + parameterValues.add(username); + + Reader reader = getReader(context, progressListener, "getUser", null, parameterNames, parameterValues); + + try { + return new UserParser(context).parse(reader, progressListener); + } finally { + Util.close(reader); + } + } + @Override public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception { Index: src/net/sourceforge/subsonic/androidapp/service/parser/AbstractParser.java =================================================================== --- src/net/sourceforge/subsonic/androidapp/service/parser/AbstractParser.java (revision 2441) +++ src/net/sourceforge/subsonic/androidapp/service/parser/AbstractParser.java (working copy) @@ -97,6 +97,11 @@ String s = get(name); return s == null ? null : Long.valueOf(s); } + + protected Double getDouble(String name) { + String s = get(name); + return s == null ? null : Double.valueOf(s); + } protected void init(Reader reader) throws Exception { parser = Xml.newPullParser(); Index: src/net/sourceforge/subsonic/androidapp/service/parser/JukeboxParser.java =================================================================== --- src/net/sourceforge/subsonic/androidapp/service/parser/JukeboxParser.java (revision 0) +++ src/net/sourceforge/subsonic/androidapp/service/parser/JukeboxParser.java (revision 0) @@ -0,0 +1,86 @@ +/* + This file is part of Subsonic. + + Subsonic 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 3 of the License, or + (at your option) any later version. + + Subsonic 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 Subsonic. If not, see . + + Copyright 2009 (C) Sindre Mehus + */ + +package net.sourceforge.subsonic.androidapp.service.parser; + +import java.io.Reader; + +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.domain.Jukebox; +import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; +import net.sourceforge.subsonic.androidapp.util.ProgressListener; + +import org.xmlpull.v1.XmlPullParser; + +import android.content.Context; + +/** + * @author meld0 + * + */ +public class JukeboxParser extends MusicDirectoryEntryParser { + + public JukeboxParser(Context context) { + super(context); + } + + public Jukebox parse(Jukebox jukebox, Reader reader, + ProgressListener progressListener) throws Exception { + updateProgress(progressListener, R.string.parser_reading); + + if (jukebox == null) + jukebox = new Jukebox(); + jukebox.reset(); + init(reader); + jukebox.setAvailable(true); + + MusicDirectory dir = new MusicDirectory(); + + int eventType; + do { + eventType = nextParseEvent(); + if (eventType == XmlPullParser.START_TAG) { + String name = getElementName(); + if ("entry".equals(name)) { + dir.addChild(parseEntry()); + jukebox.setNewPlaylist(true); + } else if ("jukeboxPlaylist".equals(name)) { + jukebox.setPlaying(getBoolean("playing")); + jukebox.setGain((int) (getDouble("gain") * 100)); + jukebox.setCurrentIndex(getInteger("currentIndex")); + } else if ("subsonic-response".equals(name)) { + if (get("status").equals("ok")) + jukebox.setSuccess(true); + } else if ("error".equals(name)) { + handleError(); + } + } + } while (eventType != XmlPullParser.END_DOCUMENT); + + if(jukebox.isNewPlaylist()) { + jukebox.addChildren(dir.getChildren()); + } + + validate(); + updateProgress(progressListener, R.string.parser_reading_done); + + return jukebox; + } + +} Index: src/net/sourceforge/subsonic/androidapp/service/parser/UserParser.java =================================================================== --- src/net/sourceforge/subsonic/androidapp/service/parser/UserParser.java (revision 0) +++ src/net/sourceforge/subsonic/androidapp/service/parser/UserParser.java (revision 0) @@ -0,0 +1,67 @@ +/* + This file is part of Subsonic. + + Subsonic 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 3 of the License, or + (at your option) any later version. + + Subsonic 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 Subsonic. If not, see . + + Copyright 2009 (C) Sindre Mehus + */ + +package net.sourceforge.subsonic.androidapp.service.parser; + +import java.io.Reader; + +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.domain.User; +import net.sourceforge.subsonic.androidapp.util.ProgressListener; + +import org.xmlpull.v1.XmlPullParser; + +import android.content.Context; + +/** + * @author meld0 + * + */ +public class UserParser extends AbstractParser{ + + public UserParser(Context context) { + super(context); + } + + public User parse(Reader reader, ProgressListener progressListener) throws Exception { + updateProgress(progressListener, R.string.parser_reading); + init(reader); + + User user = new User(); + int eventType; + do { + eventType = nextParseEvent(); + if (eventType == XmlPullParser.START_TAG) { + String name = getElementName(); + if ("user".equals(name)) { + user.setJukeboxControl(getBoolean("jukeboxRole")); + } else if ("error".equals(name)) { + handleError(); + } + } + } while (eventType != XmlPullParser.END_DOCUMENT); + + validate(); + updateProgress(progressListener, R.string.parser_reading_done); + + return user; + } + + +} Index: src/net/sourceforge/subsonic/androidapp/util/JukeboxSongView.java =================================================================== --- src/net/sourceforge/subsonic/androidapp/util/JukeboxSongView.java (revision 0) +++ src/net/sourceforge/subsonic/androidapp/util/JukeboxSongView.java (revision 0) @@ -0,0 +1,88 @@ +/* + This file is part of Subsonic. + + Subsonic 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 3 of the License, or + (at your option) any later version. + + Subsonic 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 Subsonic. If not, see . + + Copyright 2009 (C) Sindre Mehus + */ + +package net.sourceforge.subsonic.androidapp.util; + +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CheckedTextView; +import android.widget.LinearLayout; +import android.widget.TextView; + +/** + * @author meld0 + * + */ +public class JukeboxSongView extends LinearLayout{ + + private CheckedTextView checkedTextView; + private TextView titleTextView; + private TextView artistTextView; + private TextView durationTextView; + private TextView statusTextView; + private MusicDirectory.Entry song; + + public JukeboxSongView(Context context) { + super(context); + LayoutInflater.from(context).inflate(R.layout.song_list_item, this, true); + + checkedTextView = (CheckedTextView) findViewById(R.id.song_check); + titleTextView = (TextView) findViewById(R.id.song_title); + artistTextView = (TextView) findViewById(R.id.song_artist); + durationTextView = (TextView) findViewById(R.id.song_duration); + statusTextView = (TextView) findViewById(R.id.song_status); + + } + + public void setSong(MusicDirectory.Entry song, boolean checkable, boolean playing) { + this.song = song; + StringBuilder artist = new StringBuilder(40); + + String bitRate = null; + if (song.getBitRate() != null) { + bitRate = String.format(getContext().getString(R.string.song_details_kbps), song.getBitRate()); + } + + String fileFormat = null; + if (song.getTranscodedSuffix() != null && !song.getTranscodedSuffix().equals(song.getSuffix())) { + fileFormat = String.format("%s > %s", song.getSuffix(), song.getTranscodedSuffix()); + } else { + fileFormat = song.getSuffix(); + } + + artist.append(song.getArtist()).append(" (") + .append(String.format(getContext().getString(R.string.song_details_all), bitRate == null ? "" : bitRate, fileFormat)) + .append(")"); + + titleTextView.setText(song.getTitle()); + artistTextView.setText(artist); + durationTextView.setText(Util.formatDuration(song.getDuration())); + checkedTextView.setVisibility(checkable && !song.isVideo() ? View.VISIBLE : View.GONE); + + if(playing){ + titleTextView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.stat_notify_playing, 0, 0, 0); + }else{ + titleTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + } + } + +}