aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorScott Jackson <daneren2005@gmail.com>2015-03-11 17:25:04 -0700
committerScott Jackson <daneren2005@gmail.com>2015-03-11 17:25:04 -0700
commitd29f9f7073c4a74c072cf7894b8314e793cea9ac (patch)
tree47480cadb1978f0a80d3f893e1c4c86dcc8c21fd /src
parentec16b487769752f7bc4c32567bebfc23537fe3c9 (diff)
downloaddsub-d29f9f7073c4a74c072cf7894b8314e793cea9ac.tar.gz
dsub-d29f9f7073c4a74c072cf7894b8314e793cea9ac.tar.bz2
dsub-d29f9f7073c4a74c072cf7894b8314e793cea9ac.zip
#466 Added support for next playing in RemoteController, implement for DLNA players that support SetNextAvTransportURI
Diffstat (limited to 'src')
-rw-r--r--src/github/daneren2005/dsub/service/DLNAController.java317
-rw-r--r--src/github/daneren2005/dsub/service/DownloadService.java53
-rw-r--r--src/github/daneren2005/dsub/service/RemoteController.java8
3 files changed, 251 insertions, 127 deletions
diff --git a/src/github/daneren2005/dsub/service/DLNAController.java b/src/github/daneren2005/dsub/service/DLNAController.java
index 5daf0910..036b111e 100644
--- a/src/github/daneren2005/dsub/service/DLNAController.java
+++ b/src/github/daneren2005/dsub/service/DLNAController.java
@@ -19,6 +19,7 @@ import android.content.SharedPreferences;
import android.os.Looper;
import android.util.Log;
+import org.fourthline.cling.controlpoint.ActionCallback;
import org.fourthline.cling.controlpoint.ControlPoint;
import org.fourthline.cling.controlpoint.SubscriptionCallback;
import org.fourthline.cling.model.action.ActionInvocation;
@@ -30,6 +31,7 @@ import org.fourthline.cling.model.meta.Service;
import org.fourthline.cling.model.meta.StateVariable;
import org.fourthline.cling.model.state.StateVariableValue;
import org.fourthline.cling.model.types.ServiceType;
+import org.fourthline.cling.model.types.UnsignedIntegerFourBytes;
import org.fourthline.cling.support.avtransport.callback.GetPositionInfo;
import org.fourthline.cling.support.avtransport.callback.Pause;
import org.fourthline.cling.support.avtransport.callback.Play;
@@ -65,6 +67,7 @@ import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.PlayerState;
import github.daneren2005.dsub.util.Constants;
import github.daneren2005.dsub.util.FileUtil;
+import github.daneren2005.dsub.util.Pair;
import github.daneren2005.dsub.util.Util;
import github.daneren2005.serverproxy.FileProxy;
import github.daneren2005.serverproxy.ServerProxy;
@@ -79,6 +82,7 @@ public class DLNAController extends RemoteController {
ControlPoint controlPoint;
SubscriptionCallback callback;
boolean supportsSeek = false;
+ boolean supportsSetupNext = false;
private ServerProxy proxy;
String rootLocation = "";
@@ -87,6 +91,8 @@ public class DLNAController extends RemoteController {
final AtomicLong lastUpdate = new AtomicLong();
int currentPosition = 0;
String currentPlayingURI;
+ String nextPlayingURI;
+ DownloadFile nextPlaying;
boolean running = true;
boolean hasDuration = false;
Runnable searchDLNA = new Runnable() {
@@ -108,6 +114,7 @@ public class DLNAController extends RemoteController {
SharedPreferences prefs = Util.getPreferences(downloadService);
rootLocation = prefs.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, null);
+ nextSupported = true;
}
@Override
@@ -131,6 +138,10 @@ public class DLNAController extends RemoteController {
}
}
}
+ Action setupNextAction = genaSubscription.getService().getAction("SetNextAVTransportURI");
+ if(setupNextAction != null) {
+ supportsSetupNext = true;
+ }
startSong(downloadService.getCurrentPlaying(), playing, seconds);
downloadService.postDelayed(searchDLNA, SEARCH_UPDATE_INTERVAL_SECONDS);
@@ -160,6 +171,11 @@ public class DLNAController extends RemoteController {
switch (lastChange.getEventedValue(0, AVTransportVariable.TransportState.class).getValue()) {
case PLAYING:
downloadService.setPlayerState(PlayerState.STARTED);
+
+ // Try to setup next playing after playback start has been registered
+ if(supportsSetupNext && downloadService.getNextPlayerState() == PlayerState.IDLE) {
+ downloadService.setNextPlaying();
+ }
break;
case PAUSED_PLAYBACK:
downloadService.setPlayerState(PlayerState.PAUSED);
@@ -308,6 +324,11 @@ public class DLNAController extends RemoteController {
}
@Override
+ public void changeNextTrack(DownloadFile song) {
+ setupNextSong(song);
+ }
+
+ @Override
public void setVolume(int volume) {
if(volume < 0) {
volume = 0;
@@ -382,152 +403,195 @@ public class DLNAController extends RemoteController {
error = false;
downloadService.setPlayerState(PlayerState.PREPARING);
- MusicDirectory.Entry song = currentPlaying.getSong();
try {
- // Get url for entry
- MusicService musicService = MusicServiceFactory.getMusicService(downloadService);
- String url;
- // In offline mode or playing offline song
- if(Util.isOffline(downloadService) || song.getId().indexOf(rootLocation) != -1) {
- if(proxy == null) {
- proxy = new FileProxy(downloadService);
- proxy.start();
- }
+ Pair<String, String> songInfo = getSongInfo(currentPlaying);
- // Offline song
- if(song.getId().indexOf(rootLocation) != -1) {
- url = proxy.getPublicAddress(song.getId());
- } else {
- // Playing online song in offline mode
- url = proxy.getPublicAddress(currentPlaying.getCompleteFile().getPath());
- }
- } else {
- // Check if we want a proxy going still
- if(Util.isCastProxy(downloadService)) {
- if(proxy instanceof FileProxy) {
- proxy.stop();
- proxy = null;
+ currentPlayingURI = songInfo.getFirst();
+ downloadService.setNextPlayerState(PlayerState.IDLE);
+ controlPoint.execute(new SetAVTransportURI(getTransportService(), songInfo.getFirst(), songInfo.getSecond()) {
+ @Override
+ public void success(ActionInvocation invocation) {
+ if(position != 0) {
+ changePosition(position);
}
- if(proxy == null) {
- proxy = createWebProxy();
- proxy.start();
+ if (autoStart) {
+ start();
+ } else {
+ downloadService.setPlayerState(PlayerState.PAUSED);
}
- } else if(proxy != null) {
- proxy.stop();
- proxy = null;
- }
- if(song.isVideo()) {
- url = musicService.getHlsUrl(song.getId(), currentPlaying.getBitRate(), downloadService);
- } else {
- url = musicService.getMusicUrl(downloadService, song, currentPlaying.getBitRate());
+ currentPosition = position;
+ lastUpdate.set(System.currentTimeMillis());
+ getUpdatedStatus();
}
- // If proxy is going, it is a WebProxy
- if(proxy != null) {
- url = proxy.getPublicAddress(url);
+ @Override
+ public void failure(ActionInvocation actionInvocation, UpnpResponse upnpResponse, String msg) {
+ Log.w(TAG, "Set URI failed: " + msg);
+ failedLoad();
}
- }
+ });
+ } catch (Exception e) {
+ Log.w(TAG, "Failed startSong", e);
+ failedLoad();
+ }
+ }
+ private void setupNextSong(final DownloadFile nextPlaying) {
+ this.nextPlaying = nextPlaying;
+ nextPlayingURI = null;
+ if(nextPlaying == null) {
+ downloadService.setNextPlayerState(PlayerState.IDLE);
+ Log.i(TAG, "Nothing to play next");
+ return;
+ }
- // Create metadata for entry
- Item track;
- if(song.isVideo()) {
- track = new VideoItem(song.getId(), song.getParent(), song.getTitle(), song.getArtist());
- } else {
- String contentType = null;
- if(song.getTranscodedContentType() != null) {
- contentType = song.getTranscodedContentType();
- } else if(song.getContentType() != null) {
- contentType = song.getContentType();
+ downloadService.setNextPlayerState(PlayerState.PREPARING);
+ try {
+ Pair<String, String> songInfo = getSongInfo(nextPlaying);
+
+ nextPlayingURI = songInfo.getFirst();
+ controlPoint.execute(new SetNextAVTransportURI(getTransportService(), songInfo.getFirst(), songInfo.getSecond()) {
+ @Override
+ public void success(ActionInvocation invocation) {
+ downloadService.setNextPlayerState(PlayerState.PREPARED);
}
- MimeType mimeType;
- // If we can parse the content type, use it instead of hard coding
- if(contentType != null && contentType.indexOf("/") != -1 && contentType.indexOf("/") != (contentType.length() - 1)) {
- String[] typeParts = contentType.split("/");
- mimeType = new MimeType(typeParts[0], typeParts[1]);
- } else {
- mimeType = new MimeType("audio", "mpeg");
+ @Override
+ public void failure(ActionInvocation actionInvocation, UpnpResponse upnpResponse, String msg) {
+ Log.w(TAG, "Set next URI failed: " + msg);
+ nextPlayingURI = null;
+ DLNAController.this.nextPlaying = null;
+ downloadService.setNextPlayerState(PlayerState.IDLE);
}
+ });
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to setup next song", e);
+ nextPlayingURI = null;
+ this.nextPlaying = null;
+ downloadService.setNextPlayerState(PlayerState.IDLE);
+ }
+ }
- Res res = new Res(mimeType, song.getSize(), url);
+ Pair<String, String> getSongInfo(final DownloadFile downloadFile) throws Exception {
+ MusicDirectory.Entry song = downloadFile.getSong();
+
+ // Get url for entry
+ MusicService musicService = MusicServiceFactory.getMusicService(downloadService);
+ String url;
+ // In offline mode or playing offline song
+ if(Util.isOffline(downloadService) || song.getId().indexOf(rootLocation) != -1) {
+ if(proxy == null) {
+ proxy = new FileProxy(downloadService);
+ proxy.start();
+ }
- if(song.getDuration() != null) {
- SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");
- df.setTimeZone(TimeZone.getTimeZone("UTC"));
- res.setDuration(df.format(new Date(song.getDuration() * 1000)));
+ // Offline song
+ if(song.getId().indexOf(rootLocation) != -1) {
+ url = proxy.getPublicAddress(song.getId());
+ } else {
+ // Playing online song in offline mode
+ url = proxy.getPublicAddress(downloadFile.getCompleteFile().getPath());
+ }
+ } else {
+ // Check if we want a proxy going still
+ if(Util.isCastProxy(downloadService)) {
+ if(proxy instanceof FileProxy) {
+ proxy.stop();
+ proxy = null;
}
- MusicTrack musicTrack = new MusicTrack(song.getId(), song.getParent(), song.getTitle(), song.getArtist(), song.getAlbum(), song.getArtist(), res);
- musicTrack.setOriginalTrackNumber(song.getTrack());
+ if(proxy == null) {
+ proxy = createWebProxy();
+ proxy.start();
+ }
+ } else if(proxy != null) {
+ proxy.stop();
+ proxy = null;
+ }
- if(song.getCoverArt() != null) {
- String coverArt = null;
- if(proxy == null || proxy instanceof WebProxy) {
- coverArt = musicService.getCoverArtUrl(downloadService, song);
+ if(song.isVideo()) {
+ url = musicService.getHlsUrl(song.getId(), downloadFile.getBitRate(), downloadService);
+ } else {
+ url = musicService.getMusicUrl(downloadService, song, downloadFile.getBitRate());
+ }
- // If proxy is going, it is a web proxy
- if(proxy != null) {
- coverArt = proxy.getPublicAddress(coverArt);
- }
- } else {
- File coverArtFile = FileUtil.getAlbumArtFile(downloadService, song);
- if(coverArtFile != null && coverArtFile.exists()) {
- coverArt = proxy.getPublicAddress(coverArtFile.getPath());
- }
- }
+ // If proxy is going, it is a WebProxy
+ if(proxy != null) {
+ url = proxy.getPublicAddress(url);
+ }
+ }
- if(coverArt != null) {
- DIDLObject.Property.UPNP.ALBUM_ART_URI albumArtUri = new DIDLObject.Property.UPNP.ALBUM_ART_URI(URI.create(coverArt));
- musicTrack.addProperty(albumArtUri);
- }
- }
+ // Create metadata for entry
+ Item track;
+ if(song.isVideo()) {
+ track = new VideoItem(song.getId(), song.getParent(), song.getTitle(), song.getArtist());
+ } else {
+ String contentType = null;
+ if(song.getTranscodedContentType() != null) {
+ contentType = song.getTranscodedContentType();
+ } else if(song.getContentType() != null) {
+ contentType = song.getContentType();
+ }
- track = musicTrack;
+ MimeType mimeType;
+ // If we can parse the content type, use it instead of hard coding
+ if(contentType != null && contentType.indexOf("/") != -1 && contentType.indexOf("/") != (contentType.length() - 1)) {
+ String[] typeParts = contentType.split("/");
+ mimeType = new MimeType(typeParts[0], typeParts[1]);
+ } else {
+ mimeType = new MimeType("audio", "mpeg");
}
- DIDLParser parser = new DIDLParser();
- DIDLContent didl = new DIDLContent();
- didl.addItem(track);
+ Res res = new Res(mimeType, song.getSize(), url);
- String metadata = "";
- try {
- metadata = parser.generate(didl);
- } catch(Exception e) {
- Log.w(TAG, "Metadata generation failed", e);
+ if(song.getDuration() != null) {
+ SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");
+ df.setTimeZone(TimeZone.getTimeZone("UTC"));
+ res.setDuration(df.format(new Date(song.getDuration() * 1000)));
}
- currentPlayingURI = url;
- controlPoint.execute(new SetAVTransportURI(getTransportService(), url, metadata) {
- @Override
- public void success(ActionInvocation invocation) {
- if(position != 0) {
- changePosition(position);
- }
+ MusicTrack musicTrack = new MusicTrack(song.getId(), song.getParent(), song.getTitle(), song.getArtist(), song.getAlbum(), song.getArtist(), res);
+ musicTrack.setOriginalTrackNumber(song.getTrack());
- if (autoStart) {
- start();
- } else {
- downloadService.setPlayerState(PlayerState.PAUSED);
- }
+ if(song.getCoverArt() != null) {
+ String coverArt = null;
+ if(proxy == null || proxy instanceof WebProxy) {
+ coverArt = musicService.getCoverArtUrl(downloadService, song);
- currentPosition = position;
- lastUpdate.set(System.currentTimeMillis());
- getUpdatedStatus();
+ // If proxy is going, it is a web proxy
+ if(proxy != null) {
+ coverArt = proxy.getPublicAddress(coverArt);
+ }
+ } else {
+ File coverArtFile = FileUtil.getAlbumArtFile(downloadService, song);
+ if(coverArtFile != null && coverArtFile.exists()) {
+ coverArt = proxy.getPublicAddress(coverArtFile.getPath());
+ }
}
- @Override
- public void failure(ActionInvocation actionInvocation, UpnpResponse upnpResponse, String msg) {
- Log.w(TAG, "Set URI failed: " + msg);
- failedLoad();
+ if(coverArt != null) {
+ DIDLObject.Property.UPNP.ALBUM_ART_URI albumArtUri = new DIDLObject.Property.UPNP.ALBUM_ART_URI(URI.create(coverArt));
+ musicTrack.addProperty(albumArtUri);
}
- });
- } catch (Exception e) {
- Log.w(TAG, "Failed startSong", e);
- failedLoad();
+ }
+
+ track = musicTrack;
+ }
+
+ DIDLParser parser = new DIDLParser();
+ DIDLContent didl = new DIDLContent();
+ didl.addItem(track);
+
+ String metadata = "";
+ try {
+ metadata = parser.generate(didl);
+ } catch(Exception e) {
+ Log.w(TAG, "Metadata generation failed", e);
}
+
+ return new Pair<String, String>(url, metadata);
}
private void failedLoad() {
@@ -572,6 +636,12 @@ public class DLNAController extends RemoteController {
// Let's get the updated position
currentPosition = (int) positionInfo.getTrackElapsedSeconds();
+ if(positionInfo.getTrackURI() != null && positionInfo.getTrackURI().equals(nextPlayingURI) && downloadService.getNextPlayerState() == PlayerState.PREPARED) {
+ downloadService.setCurrentPlaying(nextPlaying, true);
+ downloadService.setPlayerState(PlayerState.STARTED);
+ downloadService.setNextPlaying();
+ }
+
downloadService.postDelayed(new Runnable() {
@Override
public void run() {
@@ -593,4 +663,25 @@ public class DLNAController extends RemoteController {
}
});
}
+
+ private abstract class SetNextAVTransportURI extends ActionCallback {
+ public SetNextAVTransportURI(Service service, String uri) {
+ this(new UnsignedIntegerFourBytes(0), service, uri, null);
+ }
+
+ public SetNextAVTransportURI(Service service, String uri, String metadata) {
+ this(new UnsignedIntegerFourBytes(0), service, uri, metadata);
+ }
+
+ public SetNextAVTransportURI(UnsignedIntegerFourBytes instanceId, Service service, String uri) {
+ this(instanceId, service, uri, null);
+ }
+
+ public SetNextAVTransportURI(UnsignedIntegerFourBytes instanceId, Service service, String uri, String metadata) {
+ super(new ActionInvocation(service.getAction("SetNextAVTransportURI")));
+ getActionInvocation().setInput("InstanceID", instanceId);
+ getActionInvocation().setInput("NextURI", uri);
+ getActionInvocation().setInput("NextURIMetaData", metadata);
+ }
+ }
}
diff --git a/src/github/daneren2005/dsub/service/DownloadService.java b/src/github/daneren2005/dsub/service/DownloadService.java
index 562d621c..a6b66d5a 100644
--- a/src/github/daneren2005/dsub/service/DownloadService.java
+++ b/src/github/daneren2005/dsub/service/DownloadService.java
@@ -757,11 +757,15 @@ public class DownloadService extends Service {
synchronized void setNextPlaying() {
SharedPreferences prefs = Util.getPreferences(DownloadService.this);
- boolean gaplessPlayback = prefs.getBoolean(Constants.PREFERENCES_KEY_GAPLESS_PLAYBACK, true);
- if(!gaplessPlayback) {
- nextPlaying = null;
- nextPlayerState = IDLE;
- return;
+
+ // Only obey gapless playback for local
+ if(remoteState == LOCAL) {
+ boolean gaplessPlayback = prefs.getBoolean(Constants.PREFERENCES_KEY_GAPLESS_PLAYBACK, true);
+ if (!gaplessPlayback) {
+ nextPlaying = null;
+ nextPlayerState = IDLE;
+ return;
+ }
}
setNextPlayerState(IDLE);
@@ -774,10 +778,19 @@ public class DownloadService extends Service {
}
if(index < size() && index != -1 && index != currentPlayingIndex) {
nextPlaying = downloadList.get(index);
- nextPlayingTask = new CheckCompletionTask(nextPlaying);
- nextPlayingTask.execute();
+
+ if(remoteState == LOCAL) {
+ nextPlayingTask = new CheckCompletionTask(nextPlaying);
+ nextPlayingTask.execute();
+ } else if(remoteController != null) {
+ remoteController.changeNextTrack(nextPlaying);
+ }
} else {
- resetNext();
+ if(remoteState == LOCAL) {
+ resetNext();
+ } else if(remoteController != null) {
+ remoteController.changeNextTrack(nextPlaying);
+ }
nextPlaying = null;
}
}
@@ -812,6 +825,10 @@ public class DownloadService extends Service {
return currentDownloading;
}
+ public DownloadFile getNextPlaying() {
+ return nextPlaying;
+ }
+
public List<DownloadFile> getSongs() {
return downloadList;
}
@@ -1174,6 +1191,10 @@ public class DownloadService extends Service {
return playerState;
}
+ public PlayerState getNextPlayerState() {
+ return nextPlayerState;
+ }
+
public synchronized void setPlayerState(final PlayerState playerState) {
Log.i(TAG, this.playerState.name() + " -> " + playerState.name() + " (" + currentPlaying + ")");
@@ -1277,7 +1298,7 @@ public class DownloadService extends Service {
scrobbler.scrobble(this, currentPlaying, true);
}
- private synchronized void setNextPlayerState(PlayerState playerState) {
+ public synchronized void setNextPlayerState(PlayerState playerState) {
Log.i(TAG, "Next: " + this.nextPlayerState.name() + " -> " + playerState.name() + " (" + nextPlaying + ")");
this.nextPlayerState = playerState;
}
@@ -1777,11 +1798,15 @@ public class DownloadService extends Service {
DownloadFile movedSong = list.remove(from);
list.add(to, movedSong);
currentPlayingIndex = downloadList.indexOf(currentPlaying);
- if(remoteState != LOCAL && mainList) {
- 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();
+ if(mainList) {
+ if(remoteState == LOCAL || (remoteController != null && remoteController.isNextSupported())) {
+ // Moving next playing, current playing, or moving a song to be next playing
+ if(movedSong == nextPlaying || movedSong == currentPlaying || (currentPlayingIndex + 1) == to) {
+ setNextPlaying();
+ }
+ } else {
+ updateRemotePlaylist();
+ }
}
}
diff --git a/src/github/daneren2005/dsub/service/RemoteController.java b/src/github/daneren2005/dsub/service/RemoteController.java
index 4483598c..cac28c09 100644
--- a/src/github/daneren2005/dsub/service/RemoteController.java
+++ b/src/github/daneren2005/dsub/service/RemoteController.java
@@ -37,6 +37,7 @@ import github.daneren2005.serverproxy.WebProxy;
public abstract class RemoteController {
private static final String TAG = RemoteController.class.getSimpleName();
protected DownloadService downloadService;
+ protected boolean nextSupported = false;
public abstract void create(boolean playing, int seconds);
public abstract void start();
@@ -46,6 +47,13 @@ public abstract class RemoteController {
public abstract void updatePlaylist();
public abstract void changePosition(int seconds);
public abstract void changeTrack(int index, DownloadFile song);
+ // Really is abstract, just don't want to require RemoteController's support it
+ public void changeNextTrack(DownloadFile song) {
+
+ };
+ public boolean isNextSupported() {
+ return this.nextSupported;
+ }
public abstract void setVolume(int volume);
public abstract void updateVolume(boolean up);
public abstract double getVolume();