diff options
author | Scott Jackson <daneren2005@gmail.com> | 2015-03-11 17:25:04 -0700 |
---|---|---|
committer | Scott Jackson <daneren2005@gmail.com> | 2015-03-11 17:25:04 -0700 |
commit | d29f9f7073c4a74c072cf7894b8314e793cea9ac (patch) | |
tree | 47480cadb1978f0a80d3f893e1c4c86dcc8c21fd /src/github | |
parent | ec16b487769752f7bc4c32567bebfc23537fe3c9 (diff) | |
download | dsub-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/github')
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(); |