From b10e89f304b75339c451ab7a4624cabe60b7ac98 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Thu, 13 Feb 2014 22:39:46 -0800 Subject: Added basic Cast support for a single song --- .../dsub/fragments/DownloadFragment.java | 2 +- .../dsub/service/CachedMusicService.java | 7 +- .../dsub/service/ChromeCastController.java | 211 ++++++++++++++++++++- .../dsub/service/DownloadServiceImpl.java | 7 +- .../daneren2005/dsub/service/MusicService.java | 2 + .../daneren2005/dsub/service/RESTMusicService.java | 13 +- .../daneren2005/dsub/util/compat/CastCompat.java | 7 +- 7 files changed, 241 insertions(+), 8 deletions(-) (limited to 'src/github/daneren2005') diff --git a/src/github/daneren2005/dsub/fragments/DownloadFragment.java b/src/github/daneren2005/dsub/fragments/DownloadFragment.java index d3c1938e..d87d8432 100644 --- a/src/github/daneren2005/dsub/fragments/DownloadFragment.java +++ b/src/github/daneren2005/dsub/fragments/DownloadFragment.java @@ -762,7 +762,7 @@ public class DownloadFragment extends SubsonicFragment implements OnGestureListe visualizerView.setActive(false); } if(getDownloadService() != null) { - getDownloadService().startRemoteScan(); + getDownloadService().stopRemoteScan(); } } diff --git a/src/github/daneren2005/dsub/service/CachedMusicService.java b/src/github/daneren2005/dsub/service/CachedMusicService.java index b8b440d7..4417ac78 100644 --- a/src/github/daneren2005/dsub/service/CachedMusicService.java +++ b/src/github/daneren2005/dsub/service/CachedMusicService.java @@ -293,7 +293,12 @@ public class CachedMusicService implements MusicService { return musicService.getDownloadInputStream(context, song, offset, maxBitrate, task); } - @Override + @Override + public String getMusicUrl(Context context, MusicDirectory.Entry song, int maxBitrate) throws Exception { + return musicService.getMusicUrl(context, song, maxBitrate); + } + + @Override public Version getLocalVersion(Context context) throws Exception { return musicService.getLocalVersion(context); } diff --git a/src/github/daneren2005/dsub/service/ChromeCastController.java b/src/github/daneren2005/dsub/service/ChromeCastController.java index 467e4d7b..22563089 100644 --- a/src/github/daneren2005/dsub/service/ChromeCastController.java +++ b/src/github/daneren2005/dsub/service/ChromeCastController.java @@ -15,32 +15,123 @@ package github.daneren2005.dsub.service; -import android.os.Handler; +import android.os.Bundle; +import android.util.Log; + +import com.google.android.gms.cast.ApplicationMetadata; +import com.google.android.gms.cast.Cast; +import com.google.android.gms.cast.CastDevice; +import com.google.android.gms.cast.MediaInfo; +import com.google.android.gms.cast.MediaMetadata; +import com.google.android.gms.cast.MediaStatus; +import com.google.android.gms.cast.RemoteMediaPlayer; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.common.api.Status; + +import java.io.IOException; + +import github.daneren2005.dsub.domain.MusicDirectory; +import github.daneren2005.dsub.util.compat.CastCompat; /** * Created by owner on 2/9/14. */ public class ChromeCastController extends RemoteController { + private static final String TAG = ChromeCastController.class.getSimpleName(); + private CastDevice castDevice; + private GoogleApiClient apiClient; + private ConnectionCallbacks connectionCallbacks; + private ConnectionFailedListener connectionFailedListener; + private Cast.Listener castClientListener; + + private boolean applicationStarted = false; + private boolean waitingForReconnect = false; + + private RemoteMediaPlayer mediaPlayer; public ChromeCastController(DownloadServiceImpl downloadService, CastDevice castDevice) { this.downloadService = downloadService; this.castDevice = castDevice; + + connectionCallbacks = new ConnectionCallbacks(); + connectionFailedListener = new ConnectionFailedListener(); + castClientListener = new Cast.Listener() { + @Override + public void onApplicationStatusChanged() { + if (apiClient != null) { + Log.d(TAG, "onApplicationStatusChanged: " + Cast.CastApi.getApplicationStatus(apiClient)); + + } + } + + @Override + public void onVolumeChanged() { + if (apiClient != null) { + Log.d(TAG, "onVolumeChanged: " + Cast.CastApi.getVolume(apiClient)); + } + } + + @Override + public void onApplicationDisconnected(int errorCode) { + Log.d(TAG, "onApplicationDisconnected: " + errorCode); + // teardown(); + } + + }; + + Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions.builder(castDevice, castClientListener); + apiClient = new GoogleApiClient.Builder(downloadService) + .addApi(Cast.API, apiOptionsBuilder.build()) + .addConnectionCallbacks(connectionCallbacks) + .addOnConnectionFailedListener(connectionFailedListener) + .build(); + + apiClient.connect(); } @Override public void start() { - + try { + mediaPlayer.play(apiClient); + } catch(Exception e) { + Log.e(TAG, "Failed to pause"); + } } @Override public void stop() { - + try { + mediaPlayer.pause(apiClient); + } catch(Exception e) { + Log.e(TAG, "Failed to pause"); + } } @Override public void shutdown() { + try { + if(mediaPlayer != null) { + mediaPlayer.stop(apiClient); + } + } catch(Exception e) { + Log.e(TAG, "Failed to stop mediaPlayer", e); + } + try { + Cast.CastApi.stopApplication(apiClient); + Cast.CastApi.removeMessageReceivedCallbacks(apiClient, mediaPlayer.getNamespace()); + mediaPlayer = null; + applicationStarted = false; + } catch(IOException e) { + Log.e(TAG, "Failed to shutdown application", e); + } + + if(apiClient.isConnected()) { + apiClient.disconnect(); + } } @Override @@ -67,4 +158,118 @@ public class ChromeCastController extends RemoteController { public int getRemotePosition() { return 0; } + + private class ConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks { + @Override + public void onConnected(Bundle connectionHint) { + if (waitingForReconnect) { + waitingForReconnect = false; + // reconnectChannels(); + } else { + launchApplication(); + } + } + + @Override + public void onConnectionSuspended(int cause) { + waitingForReconnect = true; + } + + void launchApplication() { + try { + Cast.CastApi.launchApplication(apiClient, CastCompat.APPLICATION_ID, false).setResultCallback(new ResultCallback() { + @Override + public void onResult(Cast.ApplicationConnectionResult result) { + Status status = result.getStatus(); + if (status.isSuccess()) { + ApplicationMetadata applicationMetadata = result.getApplicationMetadata(); + String sessionId = result.getSessionId(); + String applicationStatus = result.getApplicationStatus(); + boolean wasLaunched = result.getWasLaunched(); + + applicationStarted = true; + setupChannel(); + } else { + // teardown(); + } + } + }); + } catch (Exception e) { + Log.e(TAG, "Failed to launch application", e); + } + } + void setupChannel() { + mediaPlayer = new RemoteMediaPlayer(); + mediaPlayer.setOnStatusUpdatedListener(new RemoteMediaPlayer.OnStatusUpdatedListener() { + @Override + public void onStatusUpdated() { + MediaStatus mediaStatus = mediaPlayer.getMediaStatus(); + Log.d(TAG, "mediaPlayer status: " + mediaStatus); + boolean isPlaying = mediaStatus.getPlayerState() == MediaStatus.PLAYER_STATE_PLAYING; + + } + }); + mediaPlayer.setOnMetadataUpdatedListener(new RemoteMediaPlayer.OnMetadataUpdatedListener() { + @Override + public void onMetadataUpdated() { + MediaInfo mediaInfo = mediaPlayer.getMediaInfo(); + MediaMetadata metadata = mediaInfo.getMetadata(); + Log.d(TAG, "mediaInfo: " + mediaInfo); + Log.d(TAG, "metadata: " + metadata); + } + }); + + try { + Cast.CastApi.setMessageReceivedCallbacks(apiClient, mediaPlayer.getNamespace(), mediaPlayer); + } catch (IOException e) { + Log.e(TAG, "Exception while creating channel", e); + } + + startSong(); + } + void startSong() { + DownloadFile currentPlaying = downloadService.getCurrentPlaying(); + if(currentPlaying == null) { + // Don't start anything + return; + } + MusicDirectory.Entry song = currentPlaying.getSong(); + + MusicService musicService = MusicServiceFactory.getMusicService(downloadService); + try { + String url = musicService.getMusicUrl(downloadService, song, 0); + Log.d(TAG, "load: " + url); + + MediaMetadata mediaMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MUSIC_TRACK); + mediaMetadata.putString(MediaMetadata.KEY_TITLE, song.getTitle()); + MediaInfo mediaInfo = new MediaInfo.Builder(url) + .setContentType(song.getTranscodedContentType()) + .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) + .setMetadata(mediaMetadata) + .build(); + + mediaPlayer.load(apiClient, mediaInfo, true).setResultCallback(new ResultCallback() { + @Override + public void onResult(RemoteMediaPlayer.MediaChannelResult result) { + if (result.getStatus().isSuccess()) { + Log.d(TAG, "Media loaded successfully"); + } else { + Log.d(TAG, "Result: " + result.getStatus()); + } + } + }); + } catch (IllegalStateException e) { + Log.e(TAG, "Problem occurred with media during loading", e); + } catch (Exception e) { + Log.e(TAG, "Problem opening media during loading", e); + } + } + } + + private class ConnectionFailedListener implements GoogleApiClient.OnConnectionFailedListener { + @Override + public void onConnectionFailed(ConnectionResult result) { + // teardown(); + } + } } diff --git a/src/github/daneren2005/dsub/service/DownloadServiceImpl.java b/src/github/daneren2005/dsub/service/DownloadServiceImpl.java index 9a85bcdd..e83be019 100644 --- a/src/github/daneren2005/dsub/service/DownloadServiceImpl.java +++ b/src/github/daneren2005/dsub/service/DownloadServiceImpl.java @@ -372,7 +372,7 @@ public class DownloadServiceImpl extends Service implements DownloadService { SharedPreferences prefs = Util.getPreferences(this); remoteState = RemoteControlState.values()[prefs.getInt(Constants.PREFERENCES_KEY_CONTROL_MODE, 0)]; if(remoteState != RemoteControlState.LOCAL) { - setRemoteState(remoteState); + setRemoteState(remoteState, null); } boolean startShufflePlay = prefs.getBoolean(Constants.PREFERENCES_KEY_SHUFFLE_MODE, false); download(songs, false, false, false, false); @@ -1158,6 +1158,11 @@ public class DownloadServiceImpl extends Service implements DownloadService { remoteController = new JukeboxController(this, handler); break; case CHROMECAST: + // TODO: Fix case where starting up with chromecast set + if(ref == null) { + remoteState = RemoteControlState.LOCAL; + break; + } remoteController = (RemoteController) ref; break; case LOCAL: default: diff --git a/src/github/daneren2005/dsub/service/MusicService.java b/src/github/daneren2005/dsub/service/MusicService.java index 3674fd01..a1e54a81 100644 --- a/src/github/daneren2005/dsub/service/MusicService.java +++ b/src/github/daneren2005/dsub/service/MusicService.java @@ -95,6 +95,8 @@ public interface MusicService { HttpResponse getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, CancellableTask task) throws Exception; + String getMusicUrl(Context context, MusicDirectory.Entry song, int maxBitrate) throws Exception; + Version getLocalVersion(Context context) throws Exception; Version getLatestVersion(Context context, ProgressListener progressListener) throws Exception; diff --git a/src/github/daneren2005/dsub/service/RESTMusicService.java b/src/github/daneren2005/dsub/service/RESTMusicService.java index 43b5f887..8555f9e2 100644 --- a/src/github/daneren2005/dsub/service/RESTMusicService.java +++ b/src/github/daneren2005/dsub/service/RESTMusicService.java @@ -723,7 +723,18 @@ public class RESTMusicService implements MusicService { return response; } - @Override + @Override + public String getMusicUrl(Context context, MusicDirectory.Entry song, int maxBitrate) throws Exception { + StringBuilder builder = new StringBuilder(getRestUrl(context, "stream", false)); + builder.append("&id=").append(song.getId()); + builder.append("&maxBitRate=").append(maxBitrate); + + String url = rewriteUrlWithRedirect(context, builder.toString()); + Log.i(TAG, "Using music URL: " + url); + return url; + } + + @Override public String getVideoUrl(int maxBitrate, Context context, String id) { StringBuilder builder = new StringBuilder(getRestUrl(context, "videoPlayer")); builder.append("&id=").append(id); diff --git a/src/github/daneren2005/dsub/util/compat/CastCompat.java b/src/github/daneren2005/dsub/util/compat/CastCompat.java index 0892040e..31581816 100644 --- a/src/github/daneren2005/dsub/util/compat/CastCompat.java +++ b/src/github/daneren2005/dsub/util/compat/CastCompat.java @@ -17,6 +17,9 @@ package github.daneren2005.dsub.util.compat; import android.support.v7.media.MediaRouter; +import com.google.android.gms.cast.CastDevice; +import com.google.android.gms.cast.CastMediaControlIntent; + import github.daneren2005.dsub.service.ChromeCastController; import github.daneren2005.dsub.service.DownloadServiceImpl; import github.daneren2005.dsub.service.RemoteController; @@ -25,6 +28,8 @@ import github.daneren2005.dsub.service.RemoteController; * Created by owner on 2/9/14. */ public final class CastCompat { + public static final String APPLICATION_ID = "5F85EBEB"; + static { try { Class.forName("com.google.android.gms.cast.CastDevice"); @@ -47,6 +52,6 @@ public final class CastCompat { } public static String getCastControlCategory() { - return CastMediaControlIntent.categoryForCast("5F85EBEB"); + return CastMediaControlIntent.categoryForCast(APPLICATION_ID); } } -- cgit v1.2.3