diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 5a367df9..920a0f89 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -34,8 +34,6 @@
-
-
diff --git a/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadFile.java b/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadFile.java
index 6d2bfd0d..1c1bbf8e 100644
--- a/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadFile.java
+++ b/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadFile.java
@@ -37,6 +37,7 @@ import com.thejoshwa.ultrasonic.androidapp.util.CacheCleaner;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
+import org.apache.http.Header;
/**
* @author Sindre Mehus
@@ -59,6 +60,7 @@ public class DownloadFile {
private volatile boolean isPlaying = false;
private volatile boolean saveWhenDone = false;
private volatile boolean completeWhenDone = false;
+ private Integer contentLength = null;
public DownloadFile(Context context, MusicDirectory.Entry song, boolean save) {
this.context = context;
@@ -91,6 +93,10 @@ public class DownloadFile {
return song.getBitRate() == null ? 160 : song.getBitRate();
}
+ public Integer getContentLength() {
+ return contentLength;
+ }
+
public synchronized void download() {
FileUtil.createDirectoryForParent(saveFile);
failed = false;
@@ -281,6 +287,17 @@ public class DownloadFile {
if (compare) {
// Attempt partial HTTP GET, appending to the file if it exists.
HttpResponse response = musicService.getDownloadInputStream(context, song, partialFile.length(), bitRate, DownloadTask.this);
+ Header contentLengthHeader = response.getFirstHeader("Content-Length");
+
+ if (contentLengthHeader != null) {
+ String contentLengthString = contentLengthHeader.getValue();
+
+ if (contentLengthString != null) {
+ Log.i(TAG, "Content Length: " + contentLengthString);
+ contentLength = Integer.parseInt(contentLengthString);
+ }
+ }
+
in = response.getEntity().getContent();
boolean partial = response.getStatusLine().getStatusCode() == HttpStatus.SC_PARTIAL_CONTENT;
diff --git a/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadServiceImpl.java b/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadServiceImpl.java
index c7c3bd46..5f4d1be5 100644
--- a/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadServiceImpl.java
+++ b/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadServiceImpl.java
@@ -191,21 +191,6 @@ public class DownloadServiceImpl extends Service implements DownloadService {
audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
- if (mediaPlayer != null) {
- mediaPlayer.release();
- }
-
- mediaPlayer = new MediaPlayer();
- mediaPlayer.setWakeMode(this, PowerManager.PARTIAL_WAKE_LOCK);
- mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
- @Override
- public boolean onError(MediaPlayer mediaPlayer, int what, int more) {
- handleError(new Exception("MediaPlayer error: " + what + " (" + more + ")"));
- return false;
- }
- });
-
notification.flags |= Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT;
notification.contentView = new RemoteViews(this.getPackageName(), R.layout.notification);
Util.linkButtons(this, notification.contentView, false);
@@ -251,6 +236,8 @@ public class DownloadServiceImpl extends Service implements DownloadService {
@Override
public void onDestroy() {
super.onDestroy();
+
+ instance = null;
lifecycleSupport.onDestroy();
mediaPlayer.release();
@@ -287,7 +274,6 @@ public class DownloadServiceImpl extends Service implements DownloadService {
audioManager.unregisterRemoteControlClient(remoteControlClient);
notification = null;
- instance = null;
}
public static DownloadService getInstance() {
@@ -321,14 +307,21 @@ public class DownloadServiceImpl extends Service implements DownloadService {
DownloadFile downloadFile = new DownloadFile(this, song, save);
downloadList.add(getCurrentPlayingIndex() + offset, downloadFile);
offset++;
- }
+ }
revision++;
} else {
+ int size = size();
+ int index = getCurrentPlayingIndex();
+
for (MusicDirectory.Entry song : songs) {
DownloadFile downloadFile = new DownloadFile(this, song, save);
downloadList.add(downloadFile);
}
+
+ if(!autoplay && (size - 1) == index) {
+ setNextPlaying();
+ }
revision++;
}
@@ -357,6 +350,8 @@ public class DownloadServiceImpl extends Service implements DownloadService {
DownloadFile downloadFile = new DownloadFile(this, song, save);
backgroundDownloadList.add(downloadFile);
}
+
+ revision++;
checkDownloads();
lifecycleSupport.serializeDownloadQueue();
@@ -373,6 +368,10 @@ public class DownloadServiceImpl extends Service implements DownloadService {
download(songs, false, false, false, false, newPlaylist);
if (currentPlayingIndex != -1) {
+ while (mediaPlayer == null) {
+ Util.sleepQuietly(50L);
+ }
+
play(currentPlayingIndex, autoPlayStart);
if (currentPlaying != null) {
@@ -607,6 +606,14 @@ public class DownloadServiceImpl extends Service implements DownloadService {
}
synchronized void setNextPlaying() {
+ boolean gaplessPlayback = Util.getGaplessPlaybackPreference(DownloadServiceImpl.this);
+
+ if (!gaplessPlayback) {
+ nextPlaying = null;
+ nextPlayerState = IDLE;
+ return;
+ }
+
int index = getCurrentPlayingIndex();
if (index != -1) {
switch (getRepeatMode()) {
@@ -1312,9 +1319,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
try {
setNextPlayerState(PREPARED);
- boolean gaplessPlayback = Util.getGaplessPlaybackPreference(DownloadServiceImpl.this);
-
- if (gaplessPlayback && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED)) {
mediaPlayer.setNextMediaPlayer(nextMediaPlayer);
nextSetup = true;
}
@@ -1506,7 +1511,8 @@ public class DownloadServiceImpl extends Service implements DownloadService {
DownloadFile downloadFile = backgroundDownloadList.get(i);
if (downloadFile.isWorkDone() && (!downloadFile.shouldSave() || downloadFile.isSaved())) {
// Don't need to keep list like active song list
- backgroundDownloadList.remove(downloadFile);
+ backgroundDownloadList.remove(i);
+ revision++;
i--;
} else {
currentDownloading = downloadFile;
diff --git a/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadServiceLifecycleSupport.java b/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadServiceLifecycleSupport.java
index 4cedd826..baef4dde 100644
--- a/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadServiceLifecycleSupport.java
+++ b/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadServiceLifecycleSupport.java
@@ -24,6 +24,7 @@ import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import android.content.BroadcastReceiver;
@@ -56,6 +57,7 @@ public class DownloadServiceLifecycleSupport {
private PhoneStateListener phoneStateListener;
private boolean externalStorageAvailable= true;
private ReentrantLock lock = new ReentrantLock();
+ private final AtomicBoolean setup = new AtomicBoolean(false);
/**
* This receiver manages the intent that could come from other applications.
@@ -186,6 +188,10 @@ public class DownloadServiceLifecycleSupport {
}
public void serializeDownloadQueue() {
+ if(!setup.get()) {
+ return;
+ }
+
new SerializeTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@@ -307,6 +313,7 @@ public class DownloadServiceLifecycleSupport {
try {
lock.lock();
deserializeDownloadQueueNow();
+ setup.set(true);
} finally {
lock.unlock();
}
diff --git a/src/com/thejoshwa/ultrasonic/androidapp/service/OfflineMusicService.java b/src/com/thejoshwa/ultrasonic/androidapp/service/OfflineMusicService.java
index 2ffeaaca..1f2558b4 100644
--- a/src/com/thejoshwa/ultrasonic/androidapp/service/OfflineMusicService.java
+++ b/src/com/thejoshwa/ultrasonic/androidapp/service/OfflineMusicService.java
@@ -30,6 +30,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Set;
+import java.util.SortedSet;
import java.util.concurrent.TimeUnit;
import android.content.Context;
@@ -47,6 +48,7 @@ import com.thejoshwa.ultrasonic.androidapp.domain.Playlist;
import com.thejoshwa.ultrasonic.androidapp.domain.SearchCriteria;
import com.thejoshwa.ultrasonic.androidapp.domain.SearchResult;
import com.thejoshwa.ultrasonic.androidapp.util.Constants;
+import com.thejoshwa.ultrasonic.androidapp.util.EntryByDiscAndTrackComparator;
import com.thejoshwa.ultrasonic.androidapp.util.FileUtil;
import com.thejoshwa.ultrasonic.androidapp.util.ProgressListener;
import com.thejoshwa.ultrasonic.androidapp.util.Util;
@@ -62,64 +64,89 @@ public class OfflineMusicService extends RESTMusicService {
return true;
}
- @Override
- public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
- List artists = new ArrayList();
- File root = FileUtil.getMusicDirectory(context);
- for (File file : FileUtil.listFiles(root)) {
- if (file.isDirectory()) {
- Artist artist = new Artist();
- artist.setId(file.getPath());
- artist.setName(file.getName());
-
- String artistIndex = "";
-
- try {
- artistIndex = file.getName().substring(0, 1);
- }
- catch (Exception ignored) { }
-
- artist.setIndex(artistIndex);
-
- artists.add(artist);
- }
- }
- return new Indexes(0L, Collections.emptyList(), artists);
- }
+ @Override
+ public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
+ List artists = new ArrayList();
+ File root = FileUtil.getMusicDirectory(context);
+ for (File file : FileUtil.listFiles(root)) {
+ if (file.isDirectory()) {
+ Artist artist = new Artist();
+ artist.setId(file.getPath());
+ artist.setIndex(file.getName().substring(0, 1));
+ artist.setName(file.getName());
+ artists.add(artist);
+ }
+ }
- @Override
- public MusicDirectory getMusicDirectory(String id, String artistName, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
- File dir = new File(id);
- MusicDirectory result = new MusicDirectory();
- result.setName(dir.getName());
+ String ignoredArticlesString = "The El La Los Las Le Les";
+ final String[] ignoredArticles = ignoredArticlesString.split(" ");
- Set names = new HashSet();
+ Collections.sort(artists, new Comparator() {
+ public int compare(Artist lhsArtist, Artist rhsArtist) {
+ String lhs = lhsArtist.getName().toLowerCase();
+ String rhs = rhsArtist.getName().toLowerCase();
- for (File file : FileUtil.listMediaFiles(dir)) {
- String name = getName(file);
- if (name != null & !names.contains(name)) {
- names.add(name);
- result.addChild(createEntry(context, file, name));
- }
- }
- return result;
- }
+ char lhs1 = lhs.charAt(0);
+ char rhs1 = rhs.charAt(0);
- private String getName(File file) {
- String name = file.getName();
- if (file.isDirectory()) {
- return name;
- }
+ if(Character.isDigit(lhs1) && !Character.isDigit(rhs1)) {
+ return 1;
+ } else if(Character.isDigit(rhs1) && !Character.isDigit(lhs1)) {
+ return -1;
+ }
- if (name.endsWith(".partial") || name.contains(".partial.") || name.equals(Constants.ALBUM_ART_FILE)) {
- return null;
- }
+ for(String article: ignoredArticles) {
+ int index = lhs.indexOf(article.toLowerCase() + " ");
+ if(index == 0) {
+ lhs = lhs.substring(article.length() + 1);
+ }
+ index = rhs.indexOf(article.toLowerCase() + " ");
+ if(index == 0) {
+ rhs = rhs.substring(article.length() + 1);
+ }
+ }
- name = name.replace(".complete", "");
- return FileUtil.getBaseName(name);
- }
+ return lhs.compareTo(rhs);
+ }
+ });
- private MusicDirectory.Entry createEntry(Context context, File file, String name) {
+ return new Indexes(0L, Collections.emptyList(), artists);
+ }
+
+ @Override
+ public MusicDirectory getMusicDirectory(String id, String artistName, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
+ File dir = new File(id);
+ MusicDirectory result = new MusicDirectory();
+ result.setName(dir.getName());
+
+ Set names = new HashSet();
+
+ for (File file : FileUtil.listMediaFiles(dir)) {
+ String name = getName(file);
+ if (name != null & !names.contains(name)) {
+ names.add(name);
+ result.addChild(createEntry(context, file, name));
+ }
+ }
+
+ return result;
+ }
+
+ private String getName(File file) {
+ String name = file.getName();
+ if (file.isDirectory()) {
+ return name;
+ }
+
+ if (name.endsWith(".partial") || name.contains(".partial.") || name.equals(Constants.ALBUM_ART_FILE)) {
+ return null;
+ }
+
+ name = name.replace(".complete", "");
+ return FileUtil.getBaseName(name);
+ }
+
+ private MusicDirectory.Entry createEntry(Context context, File file, String name) {
MusicDirectory.Entry entry = new MusicDirectory.Entry();
entry.setIsDirectory(file.isDirectory());
entry.setId(file.getPath());
@@ -277,18 +304,17 @@ public class OfflineMusicService extends RESTMusicService {
throw new OfflineException("Music folders not available in offline mode");
}
- @Override
- public SearchResult search(SearchCriteria criteria, Context context, ProgressListener progressListener) throws Exception {
+ @Override
+ public SearchResult search(SearchCriteria criteria, Context context, ProgressListener progressListener) throws Exception {
List artists = new ArrayList();
List albums = new ArrayList();
List songs = new ArrayList();
- File root = FileUtil.getMusicDirectory(context);
- int closeness;
-
- for (File artistFile : FileUtil.listFiles(root)) {
+ File root = FileUtil.getMusicDirectory(context);
+ int closeness = 0;
+ for (File artistFile : FileUtil.listFiles(root)) {
String artistName = artistFile.getName();
- if (artistFile.isDirectory()) {
- if ((closeness = matchCriteria(criteria, artistName)) > 0) {
+ if (artistFile.isDirectory()) {
+ if((closeness = matchCriteria(criteria, artistName)) > 0) {
Artist artist = new Artist();
artist.setId(artistFile.getPath());
artist.setIndex(artistFile.getName().substring(0, 1));
@@ -296,11 +322,11 @@ public class OfflineMusicService extends RESTMusicService {
artist.setCloseness(closeness);
artists.add(artist);
}
-
+
recursiveAlbumSearch(artistName, artistFile, criteria, context, albums, songs);
- }
- }
-
+ }
+ }
+
Collections.sort(artists, new Comparator() {
public int compare(Artist lhs, Artist rhs) {
if(lhs.getCloseness() == rhs.getCloseness()) {
@@ -340,10 +366,10 @@ public class OfflineMusicService extends RESTMusicService {
}
}
});
-
+
return new SearchResult(artists, albums, songs);
- }
-
+ }
+
private void recursiveAlbumSearch(String artistName, File file, SearchCriteria criteria, Context context, List albums, List songs) {
int closeness;
for(File albumFile : FileUtil.listMediaFiles(file)) {
@@ -382,11 +408,12 @@ public class OfflineMusicService extends RESTMusicService {
}
}
}
+
private int matchCriteria(SearchCriteria criteria, String name) {
String query = criteria.getQuery().toLowerCase();
String[] queryParts = query.split(" ");
String[] nameParts = name.toLowerCase().split(" ");
-
+
int closeness = 0;
for(String queryPart : queryParts) {
for(String namePart : nameParts) {
@@ -395,50 +422,76 @@ public class OfflineMusicService extends RESTMusicService {
}
}
}
-
+
return closeness;
}
- @Override
- public List getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
- List playlists = new ArrayList();
- File root = FileUtil.getPlaylistDirectory();
- for (File file : FileUtil.listFiles(root)) {
- if(FileUtil.isPlaylistFile(file)) {
- String id = file.getName();
- String filename = FileUtil.getBaseName(id);
- Playlist playlist = new Playlist(id, filename);
- playlists.add(playlist);
+ @Override
+ public List getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
+ List playlists = new ArrayList();
+ File root = FileUtil.getPlaylistDirectory();
+ String lastServer = null;
+ boolean removeServer = true;
+ for (File folder : FileUtil.listFiles(root)) {
+ if(folder.isDirectory()) {
+ String server = folder.getName();
+ SortedSet fileList = FileUtil.listFiles(folder);
+ for(File file: fileList) {
+ if(FileUtil.isPlaylistFile(file)) {
+ String id = file.getName();
+ String filename = server + ": " + FileUtil.getBaseName(id);
+ Playlist playlist = new Playlist(server, filename);
+ playlists.add(playlist);
+ }
+ }
+
+ if(!server.equals(lastServer) && fileList.size() > 0) {
+ if(lastServer != null) {
+ removeServer = false;
+ }
+ lastServer = server;
+ }
} else {
// Delete legacy playlist files
try {
- file.delete();
+ folder.delete();
} catch(Exception e) {
- Log.w(TAG, "Failed to delete old playlist file: " + file.getName());
+ Log.w(TAG, "Failed to delete old playlist file: " + folder.getName());
}
}
- }
- return playlists;
- }
+ }
- @Override
- public MusicDirectory getPlaylist(String id, String name, Context context, ProgressListener progressListener) throws Exception {
+ if(removeServer) {
+ for(Playlist playlist: playlists) {
+ playlist.setName(playlist.getName().substring(playlist.getId().length() + 2));
+ }
+ }
+ return playlists;
+ }
+
+ @Override
+ public MusicDirectory getPlaylist(String id, String name, Context context, ProgressListener progressListener) throws Exception {
DownloadService downloadService = DownloadServiceImpl.getInstance();
- if (downloadService == null) {
- return new MusicDirectory();
- }
-
- Reader reader = null;
+ if (downloadService == null) {
+ return new MusicDirectory();
+ }
+
+ Reader reader = null;
BufferedReader buffer = null;
try {
- File playlistFile = FileUtil.getPlaylistFile(name);
+ int firstIndex = name.indexOf(id);
+ if(firstIndex != -1) {
+ name = name.substring(id.length() + 2);
+ }
+
+ File playlistFile = FileUtil.getPlaylistFile(id, name);
reader = new FileReader(playlistFile);
buffer = new BufferedReader(reader);
-
+
MusicDirectory playlist = new MusicDirectory();
String line = buffer.readLine();
- if(!"#EXTM3U".equals(line)) return playlist;
-
+ if(!"#EXTM3U".equals(line)) return playlist;
+
while( (line = buffer.readLine()) != null ){
File entryFile = new File(line);
String entryName = getName(entryFile);
@@ -446,13 +499,13 @@ public class OfflineMusicService extends RESTMusicService {
playlist.addChild(createEntry(context, entryFile, entryName));
}
}
-
+
return playlist;
} finally {
Util.close(buffer);
Util.close(reader);
}
- }
+ }
@Override
public void createPlaylist(String id, String name, List entries, Context context, ProgressListener progressListener) throws Exception {
@@ -568,13 +621,13 @@ public class OfflineMusicService extends RESTMusicService {
throw new OfflineException("Getting Genres not available in offline mode");
}
- private void listFilesRecursively(File parent, List children) {
- for (File file : FileUtil.listMediaFiles(parent)) {
- if (file.isFile()) {
- children.add(file);
- } else {
- listFilesRecursively(file, children);
- }
- }
- }
+ private void listFilesRecursively(File parent, List children) {
+ for (File file : FileUtil.listMediaFiles(parent)) {
+ if (file.isFile()) {
+ children.add(file);
+ } else {
+ listFilesRecursively(file, children);
+ }
+ }
+ }
}
diff --git a/src/com/thejoshwa/ultrasonic/androidapp/service/RESTMusicService.java b/src/com/thejoshwa/ultrasonic/androidapp/service/RESTMusicService.java
index 0e3f4a22..18b2a017 100644
--- a/src/com/thejoshwa/ultrasonic/androidapp/service/RESTMusicService.java
+++ b/src/com/thejoshwa/ultrasonic/androidapp/service/RESTMusicService.java
@@ -469,16 +469,16 @@ public class RESTMusicService implements MusicService {
}
}
- @Override
- public MusicDirectory getPlaylist(String id, String name, Context context, ProgressListener progressListener) throws Exception {
- HttpParams params = new BasicHttpParams();
- HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_PLAYLIST);
+ @Override
+ public MusicDirectory getPlaylist(String id, String name, Context context, ProgressListener progressListener) throws Exception {
+ HttpParams params = new BasicHttpParams();
+ HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_PLAYLIST);
- Reader reader = getReader(context, progressListener, "getPlaylist", params, "id", id);
- try {
+ Reader reader = getReader(context, progressListener, "getPlaylist", params, "id", id);
+ try {
MusicDirectory playlist = new PlaylistParser(context).parse(reader, progressListener);
-
- File playlistFile = FileUtil.getPlaylistFile(name);
+
+ File playlistFile = FileUtil.getPlaylistFile(Util.getServerName(context), name);
FileWriter fw = new FileWriter(playlistFile);
BufferedWriter bw = new BufferedWriter(fw);
try {
@@ -488,7 +488,7 @@ public class RESTMusicService implements MusicService {
if(! new File(filePath).exists()){
String ext = FileUtil.getExtension(filePath);
String base = FileUtil.getBaseName(filePath);
- filePath = base + ".complete." + ext;
+ filePath = base + ".complete." + ext;
}
fw.write(filePath + "\n");
}
@@ -498,67 +498,63 @@ public class RESTMusicService implements MusicService {
bw.close();
fw.close();
}
-
+
return playlist;
- } finally {
- Util.close(reader);
- }
- }
-
- @Override
- public List getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
- Reader reader = getReader(context, progressListener, "getPlaylists", null);
- try {
- return new PlaylistsParser(context).parse(reader, progressListener);
- } finally {
- Util.close(reader);
- }
- }
+ } finally {
+ Util.close(reader);
+ }
+ }
- @Override
- public void createPlaylist(String id, String name, List entries, Context context, ProgressListener progressListener) throws Exception {
- checkServerVersion(context, "1.2", "Creating playlist not supported.");
-
- List parameterNames = new LinkedList();
- List