aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
m---------DragSortListView0
-rw-r--r--README3
-rw-r--r--subsonic-android/AndroidManifest.xml6
-rw-r--r--subsonic-android/res/layout/album_list_item.xml7
-rw-r--r--subsonic-android/res/layout/download_playlist.xml17
-rw-r--r--subsonic-android/res/layout/select_album.xml15
-rw-r--r--subsonic-android/res/values/styles.xml16
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/activity/DownloadActivity.java69
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/activity/SelectAlbumActivity.java26
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/DownloadFile.java6
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java456
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java33
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/JukeboxService.java2
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/updates/Updater.java1
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/util/CacheCleaner.java4
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/util/ModalBackgroundTask.java6
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/view/UpdateView.java6
18 files changed, 454 insertions, 222 deletions
diff --git a/.gitignore b/.gitignore
index eb01e9aa..686306c4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,5 @@ subsonic-android/.classpath
subsonic-android/.project
subsonic-android/bin/*
subsonic-android/gen/*
-subsonic-android/private/* \ No newline at end of file
+subsonic-android/private/*
+subsonic-android/nbandroid/* \ No newline at end of file
diff --git a/DragSortListView b/DragSortListView
-Subproject c4166a8fb4fd46688061ec915d4fa51de374e0a
+Subproject 76426129c8d212be4fbf8174a2eb6e90ef37985
diff --git a/README b/README
index c9e871cf..b29798ae 100644
--- a/README
+++ b/README
@@ -10,9 +10,6 @@ android update project --path ./
Roadmap of major planned features in rough order that I plan to work on them in (little features get sprinkled in wherever):
-Gapless Playback (may not be possible to get perfect)
- -Media Player chaining (in progress)
- -4.1+: setNextMediaPlayer?
HLS Video
-Display video where album art is currently (double tap to fullscreen)
-Videos can play inline with songs
diff --git a/subsonic-android/AndroidManifest.xml b/subsonic-android/AndroidManifest.xml
index 5ca6cf49..8b6b704d 100644
--- a/subsonic-android/AndroidManifest.xml
+++ b/subsonic-android/AndroidManifest.xml
@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="github.daneren2005.dsub"
android:installLocation="internalOnly"
- android:versionCode="36"
- android:versionName="3.7.4">
+ android:versionCode="38"
+ android:versionName="3.8.0">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
@@ -15,6 +15,8 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.READ_LOGS"/>
+
+ <uses-feature android:name="android.hardware.bluetooth" android:required="false" />
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="16"/>
diff --git a/subsonic-android/res/layout/album_list_item.xml b/subsonic-android/res/layout/album_list_item.xml
index d76f105f..0b84b4f3 100644
--- a/subsonic-android/res/layout/album_list_item.xml
+++ b/subsonic-android/res/layout/album_list_item.xml
@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content">
+ android:id="@id/drag_handle"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
<ImageView
android:id="@+id/album_coverart"
diff --git a/subsonic-android/res/layout/download_playlist.xml b/subsonic-android/res/layout/download_playlist.xml
index dc77826d..515728bb 100644
--- a/subsonic-android/res/layout/download_playlist.xml
+++ b/subsonic-android/res/layout/download_playlist.xml
@@ -20,24 +20,11 @@
android:padding="10dip"/>
<com.mobeta.android.dslv.DragSortListView
- xmlns:dslv="http://schemas.android.com/apk/res/github.daneren2005.dsub"
+ style="@style/DragDropListView"
android:id="@+id/download_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
- android:cacheColorHint="#00000000"
- dslv:drag_enabled="true"
- dslv:collapsed_height="1dp"
- dslv:drag_scroll_start="1.0"
- dslv:max_drag_scroll_speed="2.0"
- dslv:float_alpha="0.6"
- dslv:slide_shuffle_speed="0.3"
- dslv:track_drag_sort="false"
- dslv:use_default_controller="true"
- dslv:drag_handle_id="@id/drag_handle"
- dslv:sort_enabled="true"
- dslv:remove_enabled="false"
- dslv:remove_mode="flingRemove"
- dslv:drag_start_mode="onLongPress"/>
+ android:cacheColorHint="#00000000"/>
</LinearLayout> \ No newline at end of file
diff --git a/subsonic-android/res/layout/select_album.xml b/subsonic-android/res/layout/select_album.xml
index a5802246..b688a95b 100644
--- a/subsonic-android/res/layout/select_album.xml
+++ b/subsonic-android/res/layout/select_album.xml
@@ -19,13 +19,14 @@
android:layout_height="wrap_content"
android:padding="10dip"/>
- <ListView android:id="@+id/select_album_entries"
- android:textFilterEnabled="true"
- android:layout_width="fill_parent"
- android:layout_height="0dip"
- android:layout_weight="1.0"
- android:fastScrollEnabled="true"
- />
+ <com.mobeta.android.dslv.DragSortListView
+ style="@style/DragDropListView"
+ android:id="@+id/select_album_entries"
+ android:textFilterEnabled="true"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1.0"
+ android:fastScrollEnabled="true"/>
<include layout="@layout/button_bar"/>
diff --git a/subsonic-android/res/values/styles.xml b/subsonic-android/res/values/styles.xml
index 7ac30ad1..2d53a8d9 100644
--- a/subsonic-android/res/values/styles.xml
+++ b/subsonic-android/res/values/styles.xml
@@ -25,4 +25,20 @@
<item name="android:background">@drawable/menubar_button</item>
<item name="android:textColor">?android:textColorPrimary</item>
</style>
+
+ <style name="DragDropListView">
+ <item name="drag_enabled">true</item>
+ <item name="collapsed_height">1dp</item>
+ <item name="drag_scroll_start">1.0</item>
+ <item name="max_drag_scroll_speed">2.0</item>
+ <item name="float_alpha">0.6</item>
+ <item name="slide_shuffle_speed">0.3</item>
+ <item name="track_drag_sort">false</item>
+ <item name="use_default_controller">true</item>
+ <item name="drag_handle_id">@id/drag_handle</item>
+ <item name="sort_enabled">true</item>
+ <item name="remove_enabled">false</item>
+ <item name="remove_mode">flingRemove</item>
+ <item name="drag_start_mode">onLongPress</item>
+ </style>
</resources> \ No newline at end of file
diff --git a/subsonic-android/src/github/daneren2005/dsub/activity/DownloadActivity.java b/subsonic-android/src/github/daneren2005/dsub/activity/DownloadActivity.java
index 954a3f02..f487b6e6 100644
--- a/subsonic-android/src/github/daneren2005/dsub/activity/DownloadActivity.java
+++ b/subsonic-android/src/github/daneren2005/dsub/activity/DownloadActivity.java
@@ -83,12 +83,13 @@ import github.daneren2005.dsub.view.AutoRepeatButton;
import java.util.ArrayList;
import java.util.concurrent.ScheduledFuture;
import com.mobeta.android.dslv.*;
+import github.daneren2005.dsub.service.DownloadServiceImpl;
public class DownloadActivity extends SubsonicTabActivity implements OnGestureListener {
private static final String TAG = DownloadActivity.class.getSimpleName();
private static final int DIALOG_SAVE_PLAYLIST = 100;
- private static final int PERCENTAGE_OF_SCREEN_FOR_SWIPE = 5;
+ private static final int PERCENTAGE_OF_SCREEN_FOR_SWIPE = 10;
private static final int COLOR_BUTTON_ENABLED = Color.rgb(51, 181, 229);
private static final int COLOR_BUTTON_DISABLED = Color.rgb(206, 213, 211);
private static final int INCREMENT_TIME = 5000;
@@ -983,50 +984,50 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-
- DownloadService downloadService = getDownloadService();
- if (downloadService == null) {
- return false;
- }
+ DownloadService downloadService = getDownloadService();
+ if (downloadService == null) {
+ return false;
+ }
// Right to Left swipe
if (e1.getX() - e2.getX() > swipeDistance && Math.abs(velocityX) > swipeVelocity) {
- warnIfNetworkOrStorageUnavailable();
- if (downloadService.getCurrentPlayingIndex() < downloadService.size() - 1) {
- downloadService.next();
- onCurrentChanged();
- onProgressChanged();
- }
+ warnIfNetworkOrStorageUnavailable();
+ if (downloadService.getCurrentPlayingIndex() < downloadService.size() - 1) {
+ downloadService.next();
+ onCurrentChanged();
+ onProgressChanged();
+ }
return true;
}
// Left to Right swipe
- if (e2.getX() - e1.getX() > swipeDistance && Math.abs(velocityX) > swipeVelocity) {
- warnIfNetworkOrStorageUnavailable();
- downloadService.previous();
- onCurrentChanged();
- onProgressChanged();
+ else if (e2.getX() - e1.getX() > swipeDistance && Math.abs(velocityX) > swipeVelocity) {
+ warnIfNetworkOrStorageUnavailable();
+ downloadService.previous();
+ onCurrentChanged();
+ onProgressChanged();
return true;
}
- // Top to Bottom swipe
- if (e2.getY() - e1.getY() > swipeDistance && Math.abs(velocityY) > swipeVelocity) {
- warnIfNetworkOrStorageUnavailable();
- downloadService.seekTo(downloadService.getPlayerPosition() + 30000);
- onProgressChanged();
- return true;
- }
-
- // Bottom to Top swipe
- if (e1.getY() - e2.getY() > swipeDistance && Math.abs(velocityY) > swipeVelocity) {
- warnIfNetworkOrStorageUnavailable();
- downloadService.seekTo(downloadService.getPlayerPosition() - 8000);
- onProgressChanged();
- return true;
- }
+ // Top to Bottom swipe
+ else if (e2.getY() - e1.getY() > swipeDistance && Math.abs(velocityY) > swipeVelocity) {
+ warnIfNetworkOrStorageUnavailable();
+ downloadService.pause();
+ onProgressChanged();
+ return true;
+ }
+
+ // Bottom to Top swipe
+ else if (e1.getY() - e2.getY() > swipeDistance && Math.abs(velocityY) > swipeVelocity) {
+ warnIfNetworkOrStorageUnavailable();
+ start();
+ onCurrentChanged();
+ onProgressChanged();
+ return true;
+ }
- return false;
- }
+ return false;
+ }
private void toggleNowPlaying() {
nowPlaying = !nowPlaying;
diff --git a/subsonic-android/src/github/daneren2005/dsub/activity/SelectAlbumActivity.java b/subsonic-android/src/github/daneren2005/dsub/activity/SelectAlbumActivity.java
index 926467ec..5016135c 100644
--- a/subsonic-android/src/github/daneren2005/dsub/activity/SelectAlbumActivity.java
+++ b/subsonic-android/src/github/daneren2005/dsub/activity/SelectAlbumActivity.java
@@ -38,6 +38,7 @@ import github.daneren2005.dsub.R;
import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.service.*;
import github.daneren2005.dsub.util.*;
+import com.mobeta.android.dslv.*;
import java.io.File;
import java.util.*;
@@ -46,7 +47,7 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
private static final String TAG = SelectAlbumActivity.class.getSimpleName();
- private ListView entryList;
+ private DragSortListView entryList;
private View footer;
private View emptyView;
private boolean hideButtons = false;
@@ -54,6 +55,7 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
private Boolean licenseValid;
private boolean showHeader = true;
private EntryAdapter entryAdapter;
+ private List<MusicDirectory.Entry> entries;
/**
* Called when the activity is first created.
@@ -63,7 +65,7 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.select_album);
- entryList = (ListView) findViewById(R.id.select_album_entries);
+ entryList = (DragSortListView) findViewById(R.id.select_album_entries);
footer = LayoutInflater.from(this).inflate(R.layout.select_album_footer, entryList, false);
entryList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
@@ -87,6 +89,20 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
}
}
});
+ entryList.setDropListener(new DragSortListView.DropListener() {
+ @Override
+ public void drop(int from, int to) {
+ int max = entries.size();
+ if(to >= max) {
+ to = max - 1;
+ }
+ else if(to < 0) {
+ to = 0;
+ }
+ entries.add(to, entries.remove(from));
+ entryAdapter.notifyDataSetChanged();
+ }
+ });
moreButton = (Button) footer.findViewById(R.id.select_album_more);
emptyView = findViewById(R.id.select_album_empty);
@@ -387,6 +403,10 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
} else if ("starred".equals(albumListType)) {
setTitle(R.string.main_albums_starred);
}
+
+ if (!"starred".equals(albumListType)) {
+ entryList.setDragEnabled(false);
+ }
new LoadTask() {
@Override
@@ -678,7 +698,7 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
@Override
protected void done(Pair<MusicDirectory, Boolean> result) {
- List<MusicDirectory.Entry> entries = result.getFirst().getChildren();
+ entries = result.getFirst().getChildren();
int songCount = 0;
for (MusicDirectory.Entry entry : entries) {
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/DownloadFile.java b/subsonic-android/src/github/daneren2005/dsub/service/DownloadFile.java
index 59f78252..3fa38bdf 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/DownloadFile.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/DownloadFile.java
@@ -93,6 +93,9 @@ public class DownloadFile {
public synchronized void download() {
FileUtil.createDirectoryForParent(saveFile);
failed = false;
+ if(!partialFile.exists()) {
+ bitRate = Util.getMaxBitrate(context);
+ }
downloadTask = new DownloadTask();
downloadTask.start();
}
@@ -317,6 +320,9 @@ public class DownloadFile {
wifiLock.release();
}
new CacheCleaner(context, DownloadServiceImpl.getInstance()).cleanSpace();
+ if(DownloadServiceImpl.getInstance() != null) {
+ ((DownloadServiceImpl)DownloadServiceImpl.getInstance()).checkDownloads();
+ }
}
}
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java b/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java
index fab4d6a1..ad6591a5 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java
@@ -55,6 +55,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.media.AudioManager;
import android.media.MediaPlayer;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
@@ -84,6 +85,9 @@ public class DownloadServiceImpl extends Service implements DownloadService {
private final IBinder binder = new SimpleServiceBinder<DownloadService>(this);
private MediaPlayer mediaPlayer;
+ private MediaPlayer nextMediaPlayer;
+ private boolean nextSetup = false;
+ private boolean isPartial = true;
private final List<DownloadFile> downloadList = new ArrayList<DownloadFile>();
private final List<DownloadFile> backgroundDownloadList = new ArrayList<DownloadFile>();
private final Handler handler = new Handler();
@@ -95,9 +99,12 @@ public class DownloadServiceImpl extends Service implements DownloadService {
private final Scrobbler scrobbler = new Scrobbler();
private final JukeboxService jukeboxService = new JukeboxService(this);
private DownloadFile currentPlaying;
+ private DownloadFile nextPlaying;
private DownloadFile currentDownloading;
private CancellableTask bufferTask;
+ private CancellableTask nextPlayingTask;
private PlayerState playerState = IDLE;
+ private PlayerState nextPlayerState = IDLE;
private boolean shufflePlay;
private long revision;
private static DownloadService instance;
@@ -119,20 +126,10 @@ public class DownloadServiceImpl extends Service implements DownloadService {
private int timerDuration;
static {
- try {
- EqualizerController.checkAvailable();
- equalizerAvailable = true;
- } catch (Throwable t) {
- equalizerAvailable = false;
- }
- }
- static {
- try {
- VisualizerController.checkAvailable();
- visualizerAvailable = true;
- } catch (Throwable t) {
- visualizerAvailable = false;
- }
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
+ equalizerAvailable = true;
+ visualizerAvailable = true;
+ }
}
@Override
@@ -149,6 +146,17 @@ public class DownloadServiceImpl extends Service implements DownloadService {
return false;
}
});
+
+ nextMediaPlayer = new MediaPlayer();
+ nextMediaPlayer.setWakeMode(this, PowerManager.PARTIAL_WAKE_LOCK);
+
+ nextMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+ @Override
+ public boolean onError(MediaPlayer mediaPlayer, int what, int more) {
+ handleErrorNext(new Exception("MediaPlayer error: " + what + " (" + more + ")"));
+ return false;
+ }
+ });
Util.registerMediaButtonEventReceiver(this);
@@ -191,9 +199,6 @@ public class DownloadServiceImpl extends Service implements DownloadService {
@Override
public void onDestroy() {
super.onDestroy();
- if(proxy != null) {
- proxy.stop();
- }
if(currentPlaying != null) currentPlaying.setPlaying(false);
if(sleepTimer != null){
sleepTimer.cancel();
@@ -201,6 +206,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
}
lifecycleSupport.onDestroy();
mediaPlayer.release();
+ nextMediaPlayer.release();
shufflePlayBuffer.shutdown();
if (equalizerController != null) {
equalizerController.release();
@@ -212,6 +218,13 @@ public class DownloadServiceImpl extends Service implements DownloadService {
mRemoteControl.unregister(this);
mRemoteControl = null;
}
+
+ if(bufferTask != null) {
+ bufferTask.cancel();
+ }
+ if(nextPlayingTask != null) {
+ nextPlayingTask.cancel();
+ }
instance = null;
}
@@ -316,6 +329,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
revision++;
lifecycleSupport.serializeDownloadQueue();
updateJukeboxPlaylist();
+ setNextPlaying();
}
@Override
@@ -326,6 +340,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
@Override
public void setRepeatMode(RepeatMode repeatMode) {
Util.setRepeatMode(this, repeatMode);
+ setNextPlaying();
}
@Override
@@ -423,6 +438,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
lifecycleSupport.serializeDownloadQueue();
}
updateJukeboxPlaylist();
+ setNextPlaying();
}
@Override
@@ -445,6 +461,9 @@ public class DownloadServiceImpl extends Service implements DownloadService {
revision++;
lifecycleSupport.serializeDownloadQueue();
updateJukeboxPlaylist();
+ if(downloadFile == nextPlaying) {
+ setNextPlaying();
+ }
}
@Override
@@ -489,6 +508,37 @@ public class DownloadServiceImpl extends Service implements DownloadService {
Util.hidePlayingNotification(this, this, handler);
}
}
+
+ synchronized void setNextPlaying() {
+ int index = getCurrentPlayingIndex();
+ if (index != -1) {
+ switch (getRepeatMode()) {
+ case OFF:
+ index = index + 1;
+ break;
+ case ALL:
+ index = (index + 1) % size();
+ break;
+ case SINGLE:
+ break;
+ default:
+ break;
+ }
+ }
+
+ if(index < size() && index != -1) {
+ nextPlaying = downloadList.get(index);
+ nextPlayingTask = new CheckCompletionTask(nextPlaying);
+ nextPlayingTask.start();
+ } else {
+ nextPlaying = null;
+ if(nextPlayingTask != null) {
+ nextPlayingTask.cancel();
+ }
+ nextPlayingTask = null;
+ setNextPlayerState(IDLE);
+ }
+ }
@Override
public synchronized int getCurrentPlayingIndex() {
@@ -544,8 +594,10 @@ public class DownloadServiceImpl extends Service implements DownloadService {
reset();
setCurrentPlaying(null, false);
} else {
+ if(nextPlayingTask != null) {
+ nextPlayingTask.cancel();
+ }
setCurrentPlaying(index, start);
- checkDownloads();
if (start) {
if (jukeboxEnabled) {
jukeboxService.skip(getCurrentPlayingIndex(), 0);
@@ -554,12 +606,29 @@ public class DownloadServiceImpl extends Service implements DownloadService {
bufferAndPlay();
}
}
+ checkDownloads();
+ setNextPlaying();
}
}
+
+ private synchronized void playNext(boolean start) {
+ // Swap the media players since nextMediaPlayer is ready to play
+ if(start) {
+ nextMediaPlayer.start();
+ } else {
+ Log.i(TAG, "nextMediaPlayer already playing");
+ }
+ MediaPlayer tmp = mediaPlayer;
+ mediaPlayer = nextMediaPlayer;
+ nextMediaPlayer = tmp;
+ setCurrentPlaying(nextPlaying, true);
+ setPlayerState(PlayerState.STARTED);
+ setupHandlers(currentPlaying, false);
+ setNextPlaying();
+ }
/** Plays or resumes the playback, depending on the current player state. */
- public synchronized void togglePlayPause()
- {
+ public synchronized void togglePlayPause() {
if (playerState == PAUSED || playerState == COMPLETED) {
start();
} else if (playerState == STOPPED || playerState == IDLE) {
@@ -576,6 +645,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
jukeboxService.skip(getCurrentPlayingIndex(), position / 1000);
} else {
mediaPlayer.seekTo(position);
+ cachedPosition = position;
}
} catch (Exception x) {
handleError(x);
@@ -659,10 +729,6 @@ public class DownloadServiceImpl extends Service implements DownloadService {
if (bufferTask != null) {
bufferTask.cancel();
}
- if(proxy != null) {
- proxy.stop();
- proxy = null;
- }
try {
setPlayerState(IDLE);
mediaPlayer.setOnErrorListener(null);
@@ -682,7 +748,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
if (jukeboxEnabled) {
return jukeboxService.getPositionSeconds() * 1000;
} else {
- return mediaPlayer.getCurrentPosition();
+ return cachedPosition;
}
} catch (Exception x) {
handleError(x);
@@ -713,7 +779,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
return playerState;
}
- synchronized void setPlayerState(PlayerState playerState) {
+ public synchronized void setPlayerState(final PlayerState playerState) {
Log.i(TAG, this.playerState.name() + " -> " + playerState.name() + " (" + currentPlaying + ")");
if (playerState == PAUSED) {
@@ -747,28 +813,34 @@ public class DownloadServiceImpl extends Service implements DownloadService {
Runnable runnable = new Runnable() {
@Override
public void run() {
- handler.post(new Runnable() {
- @Override
- public void run() {
- if(mediaPlayer != null && getPlayerState() == STARTED) {
- try {
- cachedPosition = mediaPlayer.getCurrentPosition();
- } catch(Exception e) {
- executorService.shutdown();
- }
- }
+ if(mediaPlayer != null && playerState == STARTED) {
+ try {
+ cachedPosition = mediaPlayer.getCurrentPosition();
+ } catch(Exception e) {
+ executorService.shutdown();
}
- });
+ }
}
};
executorService = Executors.newSingleThreadScheduledExecutor();
executorService.scheduleWithFixedDelay(runnable, 200L, 200L, TimeUnit.MILLISECONDS);
- } else {
+ } else if(playerState != STARTED) {
if(executorService != null && !executorService.isShutdown()) {
executorService.shutdownNow();
}
}
}
+
+ private synchronized void setPlayerStateCompleted() {
+ Log.i(TAG, this.playerState.name() + " -> " + PlayerState.COMPLETED + " (" + currentPlaying + ")");
+ this.playerState = PlayerState.COMPLETED;
+ scrobbler.scrobble(this, currentPlaying, true);
+ }
+
+ private synchronized void setNextPlayerState(PlayerState playerState) {
+ Log.i(TAG, "Next: " + this.nextPlayerState.name() + " -> " + playerState.name() + " (" + nextPlaying + ")");
+ this.nextPlayerState = playerState;
+ }
@Override
public void setSuggestedPlaylistName(String name) {
@@ -839,127 +911,187 @@ public class DownloadServiceImpl extends Service implements DownloadService {
}
private synchronized void bufferAndPlay() {
- reset();
+ if(playerState != PREPARED) {
+ reset();
- bufferTask = new BufferTask(currentPlaying, 0);
- bufferTask.start();
+ bufferTask = new BufferTask(currentPlaying, 0);
+ bufferTask.start();
+ } else {
+ doPlay(currentPlaying, 0, true);
+ }
}
private synchronized void doPlay(final DownloadFile downloadFile, final int position, final boolean start) {
try {
final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile();
- final boolean isPartial = file.equals(downloadFile.getPartialFile());
+ isPartial = file.equals(downloadFile.getPartialFile());
downloadFile.updateModificationDate();
- mediaPlayer.setOnCompletionListener(null);
- mediaPlayer.reset();
- setPlayerState(IDLE);
- mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- String dataSource = file.getPath();
- if(isPartial) {
- if (proxy == null) {
- proxy = new StreamProxy(this);
- proxy.start();
- }
- dataSource = String.format("http://127.0.0.1:%d/%s", proxy.getPort(), dataSource);
- Log.i(TAG, "Data Source: " + dataSource);
- }
- mediaPlayer.setDataSource(dataSource);
- setPlayerState(PREPARING);
- mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
- public void onPrepared(MediaPlayer mediaPlayer) {
- try {
- setPlayerState(PREPARED);
+ if(playerState == PlayerState.PREPARED) {
+ if (start) {
+ mediaPlayer.start();
+ setPlayerState(STARTED);
+ } else {
+ setPlayerState(PAUSED);
+ }
+ } else {
+ mediaPlayer.setOnCompletionListener(null);
+ mediaPlayer.reset();
+ setPlayerState(IDLE);
+ mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ String dataSource = file.getPath();
+ if(isPartial) {
+ if (proxy == null) {
+ proxy = new StreamProxy(this);
+ proxy.start();
+ }
+ dataSource = String.format("http://127.0.0.1:%d/%s", proxy.getPort(), dataSource);
+ Log.i(TAG, "Data Source: " + dataSource);
+ }
+ mediaPlayer.setDataSource(dataSource);
+ setPlayerState(PREPARING);
+
+ mediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
+ public void onBufferingUpdate(MediaPlayer mp, int percent) {
+ Log.i(TAG, "Buffered " + percent + "%");
+ }
+ });
- synchronized (DownloadServiceImpl.this) {
- if (position != 0) {
- Log.i(TAG, "Restarting player from position " + position);
- mediaPlayer.seekTo(position);
- }
- cachedPosition = position;
+ mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+ public void onPrepared(MediaPlayer mediaPlayer) {
+ try {
+ setPlayerState(PREPARED);
- if (start) {
- mediaPlayer.start();
- setPlayerState(STARTED);
- } else {
- setPlayerState(PAUSED);
+ synchronized (DownloadServiceImpl.this) {
+ if (position != 0) {
+ Log.i(TAG, "Restarting player from position " + position);
+ mediaPlayer.seekTo(position);
+ }
+ cachedPosition = position;
+
+ if (start) {
+ mediaPlayer.start();
+ setPlayerState(STARTED);
+ } else {
+ setPlayerState(PAUSED);
+ }
}
+
+ lifecycleSupport.serializeDownloadQueue();
+ } catch (Exception x) {
+ handleError(x);
}
+ }
+ });
+ }
- lifecycleSupport.serializeDownloadQueue();
+ setupHandlers(downloadFile, isPartial);
+
+ if(playerState == PREPARING) {
+ mediaPlayer.prepareAsync();
+ }
+ } catch (Exception x) {
+ handleError(x);
+ }
+ }
+
+ private synchronized void setupNext(final DownloadFile downloadFile) {
+ try {
+ final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile();
+ nextMediaPlayer.setOnCompletionListener(null);
+ nextMediaPlayer.reset();
+ setNextPlayerState(IDLE);
+ nextMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ nextMediaPlayer.setDataSource(file.getPath());
+ setNextPlayerState(PREPARING);
+
+ nextMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+ public void onPrepared(MediaPlayer mediaPlayer) {
+ try {
+ setNextPlayerState(PREPARED);
+ // TODO: Whenever the completing early issue is fixed, remove !isPartial to get gapless playback on streams as well
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && playerState == PlayerState.STARTED && !isPartial) {
+ DownloadServiceImpl.this.mediaPlayer.setNextMediaPlayer(nextMediaPlayer);
+ nextSetup = true;
+ }
} catch (Exception x) {
- handleError(x);
+ handleErrorNext(x);
}
}
});
-
- mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
- @Override
- public void onCompletion(MediaPlayer mediaPlayer) {
-
- // Acquire a temporary wakelock, since when we return from
- // this callback the MediaPlayer will release its wakelock
- // and allow the device to go to sleep.
- wakeLock.acquire(60000);
-
- setPlayerState(COMPLETED);
-
- if (!file.equals(downloadFile.getPartialFile())) {
- onSongCompleted();
- return;
- }
-
- // If file is not completely downloaded, restart the playback from the current position.
- int pos = cachedPosition;
- synchronized (DownloadServiceImpl.this) {
- int duration = downloadFile.getSong().getDuration() == null ? 0 : downloadFile.getSong().getDuration() * 1000;
- Log.i(TAG, "Ending position " + pos + " of " + duration);
- if(downloadFile.isWorkDone()) {
- // Reached the end of the song
- if(Math.abs(duration - pos) < 10000) {
- onSongCompleted();
- // Complete was called early even though file is fully buffered
- } else {
- Log.i(TAG, "Requesting restart from " + pos + " of " + duration);
- reset();
- downloadFile.setPlaying(false);
- doPlay(downloadFile, pos, true);
- downloadFile.setPlaying(true);
- }
- } else {
- Log.i(TAG, "Requesting restart from " + pos + " of " + duration);
- reset();
- bufferTask = new BufferTask(downloadFile, pos);
- bufferTask.start();
- }
- }
- }
- });
- mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+ nextMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
public boolean onError(MediaPlayer mediaPlayer, int what, int extra) {
- Log.w(TAG, "Error on playing file " + "(" + what + ", " + extra + "): " + downloadFile);
- int pos = cachedPosition;
- reset();
- downloadFile.setPlaying(false);
- doPlay(downloadFile, pos, true);
- downloadFile.setPlaying(true);
+ Log.w(TAG, "Error on playing next " + "(" + what + ", " + extra + "): " + downloadFile);
return true;
}
});
- mediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
- @Override
- public void onBufferingUpdate(MediaPlayer mp, int percent) {
- Log.i(TAG, "Buffered " + percent + "%");
- }
- });
-
- mediaPlayer.prepareAsync();
+ nextMediaPlayer.prepareAsync();
} catch (Exception x) {
- handleError(x);
+ handleErrorNext(x);
}
- }
+ }
+
+ private synchronized void setupHandlers(final DownloadFile downloadFile, final boolean isPartial) {
+ mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+ public boolean onError(MediaPlayer mediaPlayer, int what, int extra) {
+ Log.w(TAG, "Error on playing file " + "(" + what + ", " + extra + "): " + downloadFile);
+ int pos = cachedPosition;
+ reset();
+ downloadFile.setPlaying(false);
+ doPlay(downloadFile, pos, true);
+ downloadFile.setPlaying(true);
+ return true;
+ }
+ });
+
+ final int duration = downloadFile.getSong().getDuration() == null ? 0 : downloadFile.getSong().getDuration() * 1000;
+ mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(MediaPlayer mediaPlayer) {
+ setPlayerStateCompleted();
+
+ // Acquire a temporary wakelock, since when we return from
+ // this callback the MediaPlayer will release its wakelock
+ // and allow the device to go to sleep.
+ wakeLock.acquire(60000);
+
+ int pos = cachedPosition;
+ Log.i(TAG, "Ending position " + pos + " of " + duration);
+ if (!isPartial || (downloadFile.isWorkDone() && (Math.abs(duration - pos) < 10000))) {
+ if(nextPlaying != null && nextPlayerState == PlayerState.PREPARED) {
+ if(!nextSetup) {
+ playNext(true);
+ } else {
+ nextSetup = false;
+ playNext(false);
+ }
+ } else {
+ onSongCompleted();
+ }
+ return;
+ }
+
+ // If file is not completely downloaded, restart the playback from the current position.
+ synchronized (DownloadServiceImpl.this) {
+ if(downloadFile.isWorkDone()) {
+ // Complete was called early even though file is fully buffered
+ Log.i(TAG, "Requesting restart from " + pos + " of " + duration);
+ reset();
+ downloadFile.setPlaying(false);
+ doPlay(downloadFile, pos, true);
+ downloadFile.setPlaying(true);
+ } else {
+ Log.i(TAG, "Requesting restart from " + pos + " of " + duration);
+ reset();
+ bufferTask = new BufferTask(downloadFile, pos);
+ bufferTask.start();
+ }
+ }
+ }
+ });
+ }
@Override
public void setSleepTimerDuration(int duration){
@@ -1029,6 +1161,11 @@ public class DownloadServiceImpl extends Service implements DownloadService {
mediaPlayer.reset();
setPlayerState(IDLE);
}
+ private void handleErrorNext(Exception x) {
+ Log.w(TAG, "Next Media player error: " + x, x);
+ nextMediaPlayer.reset();
+ setNextPlayerState(IDLE);
+ }
protected synchronized void checkDownloads() {
if (!Util.isExternalStoragePresent() || !lifecycleSupport.isExternalStorageAvailable()) {
@@ -1046,7 +1183,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
if (downloadList.isEmpty() && backgroundDownloadList.isEmpty()) {
return;
}
-
+
// Need to download current playing?
if (currentPlaying != null && currentPlaying != currentDownloading && !currentPlaying.isWorkDone()) {
// Cancel current download, if necessary.
@@ -1061,6 +1198,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
// Find a suitable target for download.
else if (currentDownloading == null || currentDownloading.isWorkDone() || currentDownloading.isFailed() && (!downloadList.isEmpty() || !backgroundDownloadList.isEmpty())) {
+ currentDownloading = null;
int n = size();
int preloaded = 0;
@@ -1075,6 +1213,9 @@ public class DownloadServiceImpl extends Service implements DownloadService {
currentDownloading = downloadFile;
currentDownloading.download();
cleanupCandidates.add(currentDownloading);
+ if(i == (start + 1)) {
+ setNextPlayerState(DOWNLOADING);
+ }
break;
}
} else if (currentPlaying != downloadFile) {
@@ -1191,7 +1332,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
}
@Override
- public void execute() {
+ public void execute() {
setPlayerState(DOWNLOADING);
while (!bufferComplete()) {
@@ -1216,4 +1357,47 @@ public class DownloadServiceImpl extends Service implements DownloadService {
return "BufferTask (" + downloadFile + ")";
}
}
+
+ private class CheckCompletionTask extends CancellableTask {
+ private final DownloadFile downloadFile;
+ private final File partialFile;
+
+ public CheckCompletionTask(DownloadFile downloadFile) {
+ setNextPlayerState(PlayerState.IDLE);
+ this.downloadFile = downloadFile;
+ if(downloadFile != null) {
+ partialFile = downloadFile.getPartialFile();
+ } else {
+ partialFile = null;
+ }
+ }
+
+ @Override
+ public void execute() {
+ if(downloadFile == null) {
+ return;
+ }
+
+ while (!bufferComplete()) {
+ Util.sleepQuietly(5000L);
+ if (isCancelled()) {
+ return;
+ }
+ }
+
+ // Do something
+ setupNext(downloadFile);
+ }
+
+ private boolean bufferComplete() {
+ boolean completeFileAvailable = downloadFile.isWorkDone();
+ Log.i(TAG, "Buffering next " + partialFile + " (" + partialFile.length() + ")");
+ return completeFileAvailable && (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED);
+ }
+
+ @Override
+ public String toString() {
+ return "CheckCompletionTask (" + downloadFile + ")";
+ }
+ }
}
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java b/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java
index e04fc00c..765e216b 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java
@@ -167,7 +167,7 @@ public class DownloadServiceLifecycleSupport {
public void onDestroy() {
executorService.shutdown();
- serializeDownloadQueue();
+ serializeDownloadQueueNow();
downloadService.clear(false);
downloadService.unregisterReceiver(ejectEventReceiver);
downloadService.unregisterReceiver(headsetEventReceiver);
@@ -184,6 +184,19 @@ public class DownloadServiceLifecycleSupport {
public void serializeDownloadQueue() {
new SerializeTask().execute();
}
+
+ public void serializeDownloadQueueNow() {
+ List<DownloadFile> songs = new ArrayList<DownloadFile>(downloadService.getSongs());
+ State state = new State();
+ for (DownloadFile downloadFile : songs) {
+ state.songs.add(downloadFile.getSong());
+ }
+ state.currentPlayingIndex = downloadService.getCurrentPlayingIndex();
+ state.currentPlayingPosition = downloadService.getPlayerPosition();
+
+ Log.i(TAG, "Serialized currentPlayingIndex: " + state.currentPlayingIndex + ", currentPlayingPosition: " + state.currentPlayingPosition);
+ FileUtil.serialize(downloadService, state, FILENAME_DOWNLOADS_SER);
+ }
private void deserializeDownloadQueue() {
State state = FileUtil.deserialize(downloadService, FILENAME_DOWNLOADS_SER);
@@ -224,7 +237,9 @@ public class DownloadServiceLifecycleSupport {
break;
case RemoteControlClient.FLAG_KEY_MEDIA_PLAY:
case KeyEvent.KEYCODE_MEDIA_PLAY:
- downloadService.start();
+ if(downloadService.getPlayerState() != PlayerState.STARTED) {
+ downloadService.start();
+ }
break;
case RemoteControlClient.FLAG_KEY_MEDIA_PAUSE:
case KeyEvent.KEYCODE_MEDIA_PAUSE:
@@ -274,18 +289,8 @@ public class DownloadServiceLifecycleSupport {
private class SerializeTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
- List<DownloadFile> songs = new ArrayList<DownloadFile>(downloadService.getSongs());
- State state = new State();
- for (DownloadFile downloadFile : songs) {
- state.songs.add(downloadFile.getSong());
- }
- state.currentPlayingIndex = downloadService.getCurrentPlayingIndex();
- state.currentPlayingPosition = downloadService.getPlayerPosition();
-
- Log.i(TAG, "Serialized currentPlayingIndex: " + state.currentPlayingIndex + ", currentPlayingPosition: " + state.currentPlayingPosition);
- FileUtil.serialize(downloadService, state, FILENAME_DOWNLOADS_SER);
-
+ serializeDownloadQueueNow();
return null;
}
}
-} \ No newline at end of file
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/JukeboxService.java b/subsonic-android/src/github/daneren2005/dsub/service/JukeboxService.java
index feeae2a7..96b82336 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/JukeboxService.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/JukeboxService.java
@@ -119,7 +119,9 @@ public class JukeboxService {
// Track change?
Integer index = jukeboxStatus.getCurrentPlayingIndex();
if (index != null && index != -1 && index != downloadService.getCurrentPlayingIndex()) {
+ downloadService.setPlayerState(PlayerState.COMPLETED);
downloadService.setCurrentPlaying(index, true);
+ downloadService.setPlayerState(PlayerState.STARTED);
}
}
diff --git a/subsonic-android/src/github/daneren2005/dsub/updates/Updater.java b/subsonic-android/src/github/daneren2005/dsub/updates/Updater.java
index 69cdb642..3f8876b9 100644
--- a/subsonic-android/src/github/daneren2005/dsub/updates/Updater.java
+++ b/subsonic-android/src/github/daneren2005/dsub/updates/Updater.java
@@ -50,6 +50,7 @@ public class Updater {
if(version > lastVersion) {
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(Constants.LAST_VERSION, version);
+ editor.commit();
Log.i(TAG, "Updating from version " + lastVersion + " to " + version);
for(Updater updater: updaters) {
diff --git a/subsonic-android/src/github/daneren2005/dsub/util/CacheCleaner.java b/subsonic-android/src/github/daneren2005/dsub/util/CacheCleaner.java
index 3b88f00e..c69a595c 100644
--- a/subsonic-android/src/github/daneren2005/dsub/util/CacheCleaner.java
+++ b/subsonic-android/src/github/daneren2005/dsub/util/CacheCleaner.java
@@ -66,6 +66,10 @@ public class CacheCleaner {
}
private long getMinimumDelete(List<File> files) {
+ if(files.size() == 0) {
+ return 0L;
+ }
+
long cacheSizeBytes = Util.getCacheSizeMB(context) * 1024L * 1024L;
long bytesUsedBySubsonic = 0L;
diff --git a/subsonic-android/src/github/daneren2005/dsub/util/ModalBackgroundTask.java b/subsonic-android/src/github/daneren2005/dsub/util/ModalBackgroundTask.java
index 1954c474..8873b62c 100644
--- a/subsonic-android/src/github/daneren2005/dsub/util/ModalBackgroundTask.java
+++ b/subsonic-android/src/github/daneren2005/dsub/util/ModalBackgroundTask.java
@@ -85,7 +85,11 @@ public abstract class ModalBackgroundTask<T> extends BackgroundTask<T> {
getHandler().post(new Runnable() {
@Override
public void run() {
- progressDialog.dismiss();
+ try {
+ progressDialog.dismiss();
+ } catch(Exception e) {
+ Log.w(TAG, "Failed to dismiss dialog");
+ }
done(result);
}
});
diff --git a/subsonic-android/src/github/daneren2005/dsub/view/UpdateView.java b/subsonic-android/src/github/daneren2005/dsub/view/UpdateView.java
index 9cc627cc..9fbaccf6 100644
--- a/subsonic-android/src/github/daneren2005/dsub/view/UpdateView.java
+++ b/subsonic-android/src/github/daneren2005/dsub/view/UpdateView.java
@@ -34,9 +34,9 @@ public class UpdateView extends LinearLayout {
public UpdateView(Context context) {
super(context);
- setLayoutParams(new LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.FILL_PARENT,
- LinearLayout.LayoutParams.WRAP_CONTENT));
+ setLayoutParams(new AbsListView.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
INSTANCES.put(this, null);
int instanceCount = INSTANCES.size();