diff options
Diffstat (limited to 'src/github')
8 files changed, 299 insertions, 16 deletions
diff --git a/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java b/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java index 9e6608cf..b8a1006b 100644 --- a/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java +++ b/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java @@ -42,6 +42,7 @@ import github.daneren2005.dsub.domain.ArtistInfo; import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.ServerInfo;
import github.daneren2005.dsub.domain.Share;
+import github.daneren2005.dsub.service.DownloadService;
import github.daneren2005.dsub.util.ImageLoader;
import github.daneren2005.dsub.view.AlbumGridAdapter;
import github.daneren2005.dsub.view.EntryAdapter;
@@ -227,8 +228,9 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter if(!ServerInfo.isMadsonic(context)) {
menu.removeItem(R.id.menu_top_tracks);
}
- if(!ServerInfo.checkServerVersion(context, "1.11")) {
+ if(!ServerInfo.checkServerVersion(context, "1.11") || !ServerInfo.isStockSubsonic(context)) {
menu.removeItem(R.id.menu_similar_artists);
+ menu.removeItem(R.id.menu_radio);
}
} else {
if(podcastId == null) {
@@ -320,6 +322,9 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter case R.id.menu_similar_artists:
showSimilarArtists(id);
return true;
+ case R.id.menu_radio:
+ startArtistRadio(id);
+ return true;
}
return super.onOptionsItemSelected(item);
@@ -1268,6 +1273,26 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter replaceFragment(fragment, true);
}
+ private void startArtistRadio(final String artistId) {
+ new LoadingTask<Void>(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ DownloadService downloadService = getDownloadService();
+ downloadService.setArtistRadio(artistId);
+ if(downloadService.size() == 0) {
+ Log.e(TAG, "Failed to create artist radio");
+ throw new Exception("Failed to create artist radio");
+ }
+ return null;
+ }
+
+ @Override
+ protected void done(Void result) {
+ Util.startActivityWithoutTransition(context, DownloadActivity.class);
+ }
+ }.execute();
+ }
+
private View createHeader() {
View header = entryList.findViewById(R.id.select_album_header);
boolean add = false;
diff --git a/src/github/daneren2005/dsub/service/CachedMusicService.java b/src/github/daneren2005/dsub/service/CachedMusicService.java index 124ae615..09347d1d 100644 --- a/src/github/daneren2005/dsub/service/CachedMusicService.java +++ b/src/github/daneren2005/dsub/service/CachedMusicService.java @@ -466,6 +466,11 @@ public class CachedMusicService implements MusicService { } @Override + public MusicDirectory getRandomSongs(int size, String artistId, Context context, ProgressListener progressListener) throws Exception { + return musicService.getRandomSongs(size, artistId, context, progressListener); + } + + @Override public MusicDirectory getStarredList(Context context, ProgressListener progressListener) throws Exception { MusicDirectory dir = musicService.getStarredList(context, progressListener); diff --git a/src/github/daneren2005/dsub/service/DownloadService.java b/src/github/daneren2005/dsub/service/DownloadService.java index 8c6016ba..5726b696 100644 --- a/src/github/daneren2005/dsub/service/DownloadService.java +++ b/src/github/daneren2005/dsub/service/DownloadService.java @@ -41,6 +41,7 @@ import github.daneren2005.dsub.domain.RemoteControlState; import github.daneren2005.dsub.domain.RepeatMode; import github.daneren2005.dsub.domain.ServerInfo; import github.daneren2005.dsub.receiver.MediaButtonIntentReceiver; +import github.daneren2005.dsub.util.ArtistRadioBuffer; import github.daneren2005.dsub.util.Notifications; import github.daneren2005.dsub.util.SilentBackgroundTask; import github.daneren2005.dsub.util.Constants; @@ -100,6 +101,9 @@ public class DownloadService extends Service { public static final int REWIND = 10000; private static final double DELETE_CUTOFF = 0.84; private static final int REQUIRED_ALBUM_MATCHES = 4; + private static final int SHUFFLE_MODE_NONE = 0; + private static final int SHUFFLE_MODE_ALL = 1; + private static final int SHUFFLE_MODE_ARTIST = 2; private RemoteControlClientHelper mRemoteControl; @@ -116,6 +120,7 @@ public class DownloadService extends Service { private Handler mediaPlayerHandler; private final DownloadServiceLifecycleSupport lifecycleSupport = new DownloadServiceLifecycleSupport(this); private ShufflePlayBuffer shufflePlayBuffer; + private ArtistRadioBuffer artistRadioBuffer; private final LruCache<MusicDirectory.Entry, DownloadFile> downloadFileCache = new LruCache<MusicDirectory.Entry, DownloadFile>(100); private final List<DownloadFile> cleanupCandidates = new ArrayList<DownloadFile>(); @@ -131,6 +136,7 @@ public class DownloadService extends Service { private PlayerState nextPlayerState = IDLE; private boolean removePlayed; private boolean shufflePlay; + private boolean artistRadio; private long revision; private static DownloadService instance; private String suggestedPlaylistName; @@ -229,6 +235,7 @@ public class DownloadService extends Service { instance = this; lifecycleSupport.onCreate(); shufflePlayBuffer = new ShufflePlayBuffer(this); + artistRadioBuffer = new ArtistRadioBuffer(this); } @Override @@ -313,6 +320,7 @@ public class DownloadService extends Service { } public synchronized void download(List<MusicDirectory.Entry> songs, boolean save, boolean autoplay, boolean playNext, boolean shuffle, int start, int position) { setShufflePlayEnabled(false); + setArtistRadio(null); int offset = 1; boolean noNetwork = !Util.isOffline(this) && !Util.isNetworkConnected(this); boolean warnNetwork = false; @@ -355,7 +363,7 @@ public class DownloadService extends Service { } revision++; } - updateJukeboxPlaylist(); + updateRemotePlaylist(); if(shuffle) { shuffle(); @@ -406,7 +414,7 @@ public class DownloadService extends Service { lifecycleSupport.serializeDownloadQueue(); } - private void updateJukeboxPlaylist() { + private void updateRemotePlaylist() { if (remoteState != LOCAL && remoteController != null) { remoteController.updatePlaylist(); } @@ -422,12 +430,17 @@ public class DownloadService extends Service { if(prefs.getBoolean(Constants.PREFERENCES_KEY_REMOVE_PLAYED, false)) { removePlayed = true; } - boolean startShufflePlay = prefs.getBoolean(Constants.PREFERENCES_KEY_SHUFFLE_MODE, false); + int startShufflePlay = prefs.getInt(Constants.PREFERENCES_KEY_SHUFFLE_MODE, SHUFFLE_MODE_NONE); download(songs, false, false, false, false); - if(startShufflePlay) { - shufflePlay = true; + if(startShufflePlay != SHUFFLE_MODE_NONE) { + if(startShufflePlay == SHUFFLE_MODE_ALL) { + shufflePlay = true; + } else { + artistRadio = true; + artistRadioBuffer.restoreArtist(prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_MODE_EXTRA, null)); + } SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(Constants.PREFERENCES_KEY_SHUFFLE_MODE, true); + editor.putInt(Constants.PREFERENCES_KEY_SHUFFLE_MODE, startShufflePlay); editor.commit(); } if (currentPlayingIndex != -1) { @@ -470,7 +483,7 @@ public class DownloadService extends Service { checkDownloads(); } SharedPreferences.Editor editor = Util.getPreferences(this).edit(); - editor.putBoolean(Constants.PREFERENCES_KEY_SHUFFLE_MODE, enabled); + editor.putInt(Constants.PREFERENCES_KEY_SHUFFLE_MODE, enabled ? SHUFFLE_MODE_ALL : SHUFFLE_MODE_NONE); editor.commit(); } @@ -478,6 +491,20 @@ public class DownloadService extends Service { return shufflePlay; } + public void setArtistRadio(String artistId) { + if(artistId == null) { + artistRadio = false; + } else { + artistRadio = true; + artistRadioBuffer.setArtist(artistId); + } + + SharedPreferences.Editor editor = Util.getPreferences(this).edit(); + editor.putInt(Constants.PREFERENCES_KEY_SHUFFLE_MODE, (artistId != null) ? SHUFFLE_MODE_ARTIST : SHUFFLE_MODE_NONE); + editor.putString(Constants.PREFERENCES_KEY_SHUFFLE_MODE_EXTRA, artistId); + editor.commit(); + } + public synchronized void shuffle() { Collections.shuffle(downloadList); currentPlayingIndex = downloadList.indexOf(currentPlaying); @@ -488,7 +515,7 @@ public class DownloadService extends Service { } revision++; lifecycleSupport.serializeDownloadQueue(); - updateJukeboxPlaylist(); + updateRemotePlaylist(); setNextPlaying(); } @@ -575,7 +602,7 @@ public class DownloadService extends Service { } } lifecycleSupport.serializeDownloadQueue(); - updateJukeboxPlaylist(); + updateRemotePlaylist(); } public void setOnline(final boolean online) { @@ -587,6 +614,9 @@ public class DownloadService extends Service { if(shufflePlay) { setShufflePlayEnabled(false); } + if(artistRadio) { + setArtistRadio(null); + } lifecycleSupport.post(new Runnable() { @Override @@ -645,7 +675,7 @@ public class DownloadService extends Service { if (serialize) { lifecycleSupport.serializeDownloadQueue(); } - updateJukeboxPlaylist(); + updateRemotePlaylist(); setNextPlaying(); if(proxy != null) { proxy.stop(); @@ -675,7 +705,7 @@ public class DownloadService extends Service { backgroundDownloadList.remove(downloadFile); revision++; lifecycleSupport.serializeDownloadQueue(); - updateJukeboxPlaylist(); + updateRemotePlaylist(); if(downloadFile == nextPlaying) { setNextPlaying(); } @@ -1742,7 +1772,7 @@ public class DownloadService extends Service { list.add(to, movedSong); currentPlayingIndex = downloadList.indexOf(currentPlaying); if(remoteState != LOCAL && mainList) { - updateJukeboxPlaylist(); + updateRemotePlaylist(); } else if(mainList && (movedSong == nextPlaying || movedSong == currentPlaying || (currentPlayingIndex + 1) == to)) { // Moving next playing, current playing, or moving a song to be next playing setNextPlaying(); @@ -1787,6 +1817,9 @@ public class DownloadService extends Service { if (shufflePlay) { checkShufflePlay(); } + if(artistRadio) { + checkArtistRadio(); + } if (!Util.isNetworkConnected(this, true) || Util.isOffline(this)) { return; @@ -1913,7 +1946,48 @@ public class DownloadService extends Service { currentPlayingIndex = downloadList.indexOf(currentPlaying); if (revisionBefore != revision) { - updateJukeboxPlaylist(); + updateRemotePlaylist(); + } + + if (wasEmpty && !downloadList.isEmpty()) { + play(0); + } + } + + private synchronized void checkArtistRadio() { + // Get users desired random playlist size + SharedPreferences prefs = Util.getPreferences(this); + int listSize = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_RANDOM_SIZE, "20")); + boolean wasEmpty = downloadList.isEmpty(); + + long revisionBefore = revision; + + // First, ensure that list is at least 20 songs long. + int size = size(); + if (size < listSize) { + for (MusicDirectory.Entry song : artistRadioBuffer.get(listSize - size)) { + DownloadFile downloadFile = new DownloadFile(this, song, false); + downloadList.add(downloadFile); + revision++; + } + } + + int currIndex = currentPlaying == null ? 0 : getCurrentPlayingIndex(); + + // Only shift playlist if playing song #5 or later. + if (currIndex > 4) { + int songsToShift = currIndex - 2; + for (MusicDirectory.Entry song : artistRadioBuffer.get(songsToShift)) { + downloadList.add(new DownloadFile(this, song, false)); + downloadList.get(0).cancelDownload(); + downloadList.remove(0); + revision++; + } + } + currentPlayingIndex = downloadList.indexOf(currentPlaying); + + if (revisionBefore != revision) { + updateRemotePlaylist(); } if (wasEmpty && !downloadList.isEmpty()) { diff --git a/src/github/daneren2005/dsub/service/MusicService.java b/src/github/daneren2005/dsub/service/MusicService.java index 854a0aa4..2c5a7f2a 100644 --- a/src/github/daneren2005/dsub/service/MusicService.java +++ b/src/github/daneren2005/dsub/service/MusicService.java @@ -93,6 +93,7 @@ public interface MusicService { MusicDirectory getAlbumList(String type, String extra, int size, int offset, Context context, ProgressListener progressListener) throws Exception; + MusicDirectory getRandomSongs(int size, String artistId, Context context, ProgressListener progressListener) throws Exception; MusicDirectory getRandomSongs(int size, String folder, String genre, String startYear, String endYear, Context context, ProgressListener progressListener) throws Exception; String getCoverArtUrl(Context context, MusicDirectory.Entry entry) throws Exception; diff --git a/src/github/daneren2005/dsub/service/OfflineMusicService.java b/src/github/daneren2005/dsub/service/OfflineMusicService.java index 254592da..c0fc89fe 100644 --- a/src/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/src/github/daneren2005/dsub/service/OfflineMusicService.java @@ -531,7 +531,12 @@ public class OfflineMusicService implements MusicService { throw new OfflineException(ERRORMSG); } - @Override + @Override + public MusicDirectory getRandomSongs(int size, String artistId, Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException(ERRORMSG); + } + + @Override public String getVideoUrl(int maxBitrate, Context context, String id) { return null; } diff --git a/src/github/daneren2005/dsub/service/RESTMusicService.java b/src/github/daneren2005/dsub/service/RESTMusicService.java index 8db42677..715d07da 100644 --- a/src/github/daneren2005/dsub/service/RESTMusicService.java +++ b/src/github/daneren2005/dsub/service/RESTMusicService.java @@ -571,6 +571,28 @@ public class RESTMusicService implements MusicService { } @Override + public MusicDirectory getRandomSongs(int size, String artistId, Context context, ProgressListener progressListener) throws Exception { + checkServerVersion(context, "1.11", "Artist radio is not supported"); + + List<String> names = new ArrayList<String>(); + List<Object> values = new ArrayList<Object>(); + + names.add("id"); + names.add("count"); + + values.add(artistId); + values.add(size); + + int instance = getInstance(context); + Reader reader = getReader(context, progressListener, Util.isTagBrowsing(context, instance) ? "getSimilarSongs2" : "getSimilarSongs", null, names, values); + try { + return new RandomSongsParser(context, instance).parse(reader, progressListener); + } finally { + Util.close(reader); + } + } + + @Override public MusicDirectory getStarredList(Context context, ProgressListener progressListener) throws Exception { Reader reader = getReader(context, progressListener, Util.isTagBrowsing(context, getInstance(context)) ? "getStarred2" : "getStarred", null); try { diff --git a/src/github/daneren2005/dsub/util/ArtistRadioBuffer.java b/src/github/daneren2005/dsub/util/ArtistRadioBuffer.java new file mode 100644 index 00000000..829478f7 --- /dev/null +++ b/src/github/daneren2005/dsub/util/ArtistRadioBuffer.java @@ -0,0 +1,150 @@ +/* + 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 <http://www.gnu.org/licenses/>. + Copyright 2014 (C) Scott Jackson +*/ + +package github.daneren2005.dsub.util; + +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import github.daneren2005.dsub.domain.MusicDirectory; +import github.daneren2005.dsub.service.DownloadService; +import github.daneren2005.dsub.service.MusicService; +import github.daneren2005.dsub.service.MusicServiceFactory; + +public class ArtistRadioBuffer { + private static final String TAG = ArtistRadioBuffer.class.getSimpleName(); + + private ScheduledExecutorService executorService; + private Runnable runnable; + private final ArrayList<MusicDirectory.Entry> buffer = new ArrayList<MusicDirectory.Entry>(); + private int lastCount = -1; + private DownloadService context; + private boolean awaitingResults = false; + private int capacity; + private int refillThreshold; + + private String artistId; + + public ArtistRadioBuffer(DownloadService context) { + this.context = context; + + executorService = Executors.newSingleThreadScheduledExecutor(); + runnable = new Runnable() { + @Override + public void run() { + refill(); + } + }; + + // Calculate out the capacity and refill threshold based on the user's random size preference + int shuffleListSize = Integer.parseInt(Util.getPreferences(context).getString(Constants.PREFERENCES_KEY_RANDOM_SIZE, "20")); + // ex: default 20 -> 50 + capacity = shuffleListSize * 5 / 2; + capacity = Math.min(500, capacity); + + // ex: default 20 -> 40 + refillThreshold = capacity * 4 / 5; + } + + public void setArtist(String artistId) { + if(!Util.equals(this.artistId, artistId)) { + buffer.clear(); + } + + context.clear(); + this.artistId = artistId; + awaitingResults = true; + refill(); + } + public void restoreArtist(String artistId) { + this.artistId = artistId; + awaitingResults = false; + restart(); + } + + public List<MusicDirectory.Entry> get(int size) { + // Make sure fetcher is running if needed + restart(); + + List<MusicDirectory.Entry> result = new ArrayList<MusicDirectory.Entry>(size); + synchronized (buffer) { + while (!buffer.isEmpty() && result.size() < size) { + result.add(buffer.remove(buffer.size() - 1)); + } + } + Log.i(TAG, "Taking " + result.size() + " songs from shuffle play buffer. " + buffer.size() + " remaining."); + if(result.isEmpty()) { + awaitingResults = true; + } + return result; + } + + public void shutdown() { + executorService.shutdown(); + } + + private void restart() { + synchronized(buffer) { + if(buffer.size() <= refillThreshold && lastCount != 0 && executorService.isShutdown()) { + executorService = Executors.newSingleThreadScheduledExecutor(); + executorService.scheduleWithFixedDelay(runnable, 0, 10, TimeUnit.SECONDS); + } + } + } + + private void refill() { + if (buffer != null && (buffer.size() > refillThreshold || (!Util.isNetworkConnected(context) && !Util.isOffline(context)) || lastCount == 0)) { + executorService.shutdown(); + return; + } + + try { + MusicService service = MusicServiceFactory.getMusicService(context); + + // Get capacity based + int n = capacity - buffer.size(); + MusicDirectory songs = service.getRandomSongs(n, artistId, context, null); + + synchronized (buffer) { + lastCount = 0; + for(MusicDirectory.Entry entry: songs.getChildren()) { + if(!buffer.contains(entry)) { + buffer.add(entry); + lastCount++; + } + } + Log.i(TAG, "Refilled artist radio buffer with " + lastCount + " songs."); + } + } catch (Exception x) { + // Give it one more try before quitting + if(lastCount != -2) { + lastCount = -2; + } else if(lastCount == -2) { + lastCount = 0; + } + Log.w(TAG, "Failed to refill artist radio buffer.", x); + } + + if(awaitingResults) { + awaitingResults = false; + context.checkDownloads(); + } + } +} diff --git a/src/github/daneren2005/dsub/util/Constants.java b/src/github/daneren2005/dsub/util/Constants.java index 7298c7b4..d97fe8d0 100644 --- a/src/github/daneren2005/dsub/util/Constants.java +++ b/src/github/daneren2005/dsub/util/Constants.java @@ -113,7 +113,8 @@ public final class Constants { public static final String PREFERENCES_KEY_PERSISTENT_NOTIFICATION = "persistentNotification"; public static final String PREFERENCES_KEY_GAPLESS_PLAYBACK = "gaplessPlayback"; public static final String PREFERENCES_KEY_REMOVE_PLAYED = "removePlayed"; - public static final String PREFERENCES_KEY_SHUFFLE_MODE = "shuffleMode"; + public static final String PREFERENCES_KEY_SHUFFLE_MODE = "shuffleMode2"; + public static final String PREFERENCES_KEY_SHUFFLE_MODE_EXTRA = "shuffleModeExtra"; public static final String PREFERENCES_KEY_CHAT_REFRESH = "chatRefreshRate"; public static final String PREFERENCES_KEY_CHAT_ENABLED = "chatEnabled"; public static final String PREFERENCES_KEY_VIDEO_PLAYER = "videoPlayer"; |