From fb69cf8533c74483e873804d9f980e242d737c62 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Sat, 8 Feb 2014 21:42:25 -0800 Subject: Converted RC from button to MediaRoute + added Chromecast requires --- .../dsub/fragments/DownloadFragment.java | 25 ++--- .../dsub/provider/JukeboxRouteProvider.java | 106 +++++++++++++++++++++ .../daneren2005/dsub/service/DownloadService.java | 4 + .../dsub/service/DownloadServiceImpl.java | 19 ++++ 4 files changed, 135 insertions(+), 19 deletions(-) create mode 100644 src/github/daneren2005/dsub/provider/JukeboxRouteProvider.java (limited to 'src/github/daneren2005') diff --git a/src/github/daneren2005/dsub/fragments/DownloadFragment.java b/src/github/daneren2005/dsub/fragments/DownloadFragment.java index d7f58e88..c5be820c 100644 --- a/src/github/daneren2005/dsub/fragments/DownloadFragment.java +++ b/src/github/daneren2005/dsub/fragments/DownloadFragment.java @@ -13,6 +13,7 @@ import android.content.SharedPreferences; import android.graphics.Color; import android.os.Bundle; import android.os.Handler; +import android.support.v7.app.MediaRouteButton; import android.view.ContextMenu; import android.view.Display; import android.view.GestureDetector; @@ -41,7 +42,6 @@ import github.daneren2005.dsub.R; import github.daneren2005.dsub.activity.SubsonicFragmentActivity; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.PlayerState; -import github.daneren2005.dsub.domain.RemoteControlState; import github.daneren2005.dsub.domain.RepeatMode; import github.daneren2005.dsub.service.DownloadFile; import github.daneren2005.dsub.service.DownloadService; @@ -88,7 +88,6 @@ public class DownloadFragment extends SubsonicFragment implements OnGestureListe private ImageButton repeatButton; private Button equalizerButton; private Button visualizerButton; - private Button jukeboxButton; private View toggleListButton; private ImageButton starButton; private ImageButton bookmarkButton; @@ -170,7 +169,6 @@ public class DownloadFragment extends SubsonicFragment implements OnGestureListe repeatButton = (ImageButton)rootView.findViewById(R.id.download_repeat); equalizerButton = (Button)rootView.findViewById(R.id.download_equalizer); visualizerButton = (Button)rootView.findViewById(R.id.download_visualizer); - jukeboxButton = (Button)rootView.findViewById(R.id.download_jukebox); bookmarkButton = (ImageButton) rootView.findViewById(R.id.download_bookmark); LinearLayout visualizerViewLayout = (LinearLayout)rootView.findViewById(R.id.download_visualizer_view_layout); toggleListButton =rootView.findViewById(R.id.download_toggle_list); @@ -203,7 +201,6 @@ public class DownloadFragment extends SubsonicFragment implements OnGestureListe startButton.setOnTouchListener(touchListener); equalizerButton.setOnTouchListener(touchListener); visualizerButton.setOnTouchListener(touchListener); - jukeboxButton.setOnTouchListener(touchListener); bookmarkButton.setOnTouchListener(touchListener); emptyTextView.setOnTouchListener(touchListener); albumArtImageView.setOnTouchListener(touchListener); @@ -375,17 +372,6 @@ public class DownloadFragment extends SubsonicFragment implements OnGestureListe } }); - jukeboxButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - boolean jukeboxEnabled = !getDownloadService().isRemoteEnabled(); - getDownloadService().setRemoteEnabled(jukeboxEnabled ? RemoteControlState.JUKEBOX_SERVER : RemoteControlState.LOCAL); - updateButtons(); - Util.toast(context, jukeboxEnabled ? R.string.download_jukebox_on : R.string.download_jukebox_off, false); - setControlsVisible(true); - } - }); - bookmarkButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { @@ -516,6 +502,11 @@ public class DownloadFragment extends SubsonicFragment implements OnGestureListe if(downloadService != null && downloadService.getKeepScreenOn() && nowPlaying) { menu.findItem(R.id.menu_screen_on_off).setTitle(R.string.download_menu_screen_off); } + if(downloadService != null) { + MenuItem mediaRouteItem = menu.findItem(R.id.menu_mediaroute); + MediaRouteButton mediaRouteButton = (MediaRouteButton) mediaRouteItem.getActionView(); + mediaRouteButton.setRouteSelector(downloadService.getRemotesAvailable()); + } } @Override @@ -832,9 +823,6 @@ public class DownloadFragment extends SubsonicFragment implements OnGestureListe if (visualizerView != null) { visualizerButton.setTextColor(visualizerView.isActive() ? COLOR_BUTTON_ENABLED : COLOR_BUTTON_DISABLED); } - - boolean jukeboxEnabled = getDownloadService() != null && getDownloadService().isRemoteEnabled(); - jukeboxButton.setTextColor(jukeboxEnabled ? COLOR_BUTTON_ENABLED : COLOR_BUTTON_DISABLED); if(Util.isOffline(context)) { bookmarkButton.setVisibility(View.GONE); @@ -1195,7 +1183,6 @@ public class DownloadFragment extends SubsonicFragment implements OnGestureListe break; } - jukeboxButton.setTextColor(isJukeboxEnabled ? COLOR_BUTTON_ENABLED : COLOR_BUTTON_DISABLED); onProgressChangedTask = null; } }; diff --git a/src/github/daneren2005/dsub/provider/JukeboxRouteProvider.java b/src/github/daneren2005/dsub/provider/JukeboxRouteProvider.java new file mode 100644 index 00000000..eaefbf55 --- /dev/null +++ b/src/github/daneren2005/dsub/provider/JukeboxRouteProvider.java @@ -0,0 +1,106 @@ +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see . + + Copyright 2014 (C) Scott Jackson +*/ +package github.daneren2005.dsub.provider; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.media.MediaRouter; +import android.support.v7.media.MediaControlIntent; +import android.support.v7.media.MediaRouteDescriptor; +import android.support.v7.media.MediaRouteProvider; +import android.support.v7.media.MediaRouteProviderDescriptor; + +import github.daneren2005.dsub.domain.RemoteControlState; +import github.daneren2005.dsub.service.DownloadService; + +/** + * Created by Scott on 11/28/13. + */ +public class JukeboxRouteProvider extends MediaRouteProvider { + public static final String CATEGORY_SAMPLE_ROUTE = "github.daneren2005.dsub.SERVER_JUKEBOX"; + private static int MAX_VOLUME = 10; + + private DownloadService downloadService; + + public JukeboxRouteProvider(Context context) { + super(context); + this.downloadService = (DownloadService) context; + + // Create intents + IntentFilter routeIntentFilter = new IntentFilter(); + routeIntentFilter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); + routeIntentFilter.addAction(MediaControlIntent.ACTION_START_SESSION); + routeIntentFilter.addAction(MediaControlIntent.ACTION_GET_SESSION_STATUS); + routeIntentFilter.addAction(MediaControlIntent.ACTION_END_SESSION); + + // Create route descriptor + MediaRouteDescriptor.Builder routeBuilder = new MediaRouteDescriptor.Builder("Jukebox Route", "Subsonic Jukebox"); + routeBuilder.addControlFilter(routeIntentFilter) + .setPlaybackStream(AudioManager.STREAM_MUSIC) + .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE) + .setDescription("Subsonic Jukebox") + .setVolume(5) + .setVolumeMax(MAX_VOLUME) + .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE); + + // Create descriptor + MediaRouteProviderDescriptor.Builder providerBuilder = new MediaRouteProviderDescriptor.Builder(); + providerBuilder.addRoute(routeBuilder.build()); + setDescriptor(providerBuilder.build()); + } + + @Override + public MediaRouteProvider.RouteController onCreateRouteController(String routeId) { + return new JukeboxRouteController(downloadService); + } + + private static class JukeboxRouteController extends RouteController { + private DownloadService downloadService; + + public JukeboxRouteController(DownloadService downloadService) { + this.downloadService = downloadService; + } + + @Override + public boolean onControlRequest(Intent intent, android.support.v7.media.MediaRouter.ControlRequestCallback callback) { + if (intent.hasCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) { + return true; + } else { + return false; + } + } + + @Override + public void onRelease() { + downloadService.setRemoteEnabled(RemoteControlState.LOCAL); + } + + @Override + public void onSelect() { + downloadService.setRemoteEnabled(RemoteControlState.JUKEBOX_SERVER); + } + + @Override + public void onUnselect() { + downloadService.setRemoteEnabled(RemoteControlState.LOCAL); + } + } +} diff --git a/src/github/daneren2005/dsub/service/DownloadService.java b/src/github/daneren2005/dsub/service/DownloadService.java index 1a254c73..1a96b2be 100644 --- a/src/github/daneren2005/dsub/service/DownloadService.java +++ b/src/github/daneren2005/dsub/service/DownloadService.java @@ -18,6 +18,8 @@ */ package github.daneren2005.dsub.service; +import android.support.v7.media.MediaRouteSelector; + import java.util.List; import github.daneren2005.dsub.audiofx.EqualizerController; @@ -124,6 +126,8 @@ public interface DownloadService { VisualizerController getVisualizerController(); + MediaRouteSelector getRemotesAvailable(); + boolean isRemoteEnabled(); void setRemoteEnabled(RemoteControlState newState); diff --git a/src/github/daneren2005/dsub/service/DownloadServiceImpl.java b/src/github/daneren2005/dsub/service/DownloadServiceImpl.java index 9f1e5ee9..ad2f0490 100644 --- a/src/github/daneren2005/dsub/service/DownloadServiceImpl.java +++ b/src/github/daneren2005/dsub/service/DownloadServiceImpl.java @@ -34,6 +34,7 @@ import github.daneren2005.dsub.domain.PlayerState; import github.daneren2005.dsub.domain.PodcastEpisode; import github.daneren2005.dsub.domain.RemoteControlState; import github.daneren2005.dsub.domain.RepeatMode; +import github.daneren2005.dsub.provider.JukeboxRouteProvider; import github.daneren2005.dsub.receiver.MediaButtonIntentReceiver; import github.daneren2005.dsub.util.CancellableTask; import github.daneren2005.dsub.util.Constants; @@ -63,6 +64,9 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.PowerManager; +import android.support.v7.media.MediaControlIntent; +import android.support.v7.media.MediaRouteSelector; +import android.support.v7.media.MediaRouter; import android.util.Log; import android.support.v4.util.LruCache; import java.net.URLEncoder; @@ -136,6 +140,8 @@ public class DownloadServiceImpl extends Service implements DownloadService { private int timerDuration; private boolean autoPlayStart = false; + private MediaRouteSelector remoteSelector; + static { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { equalizerAvailable = true; @@ -210,6 +216,14 @@ public class DownloadServiceImpl extends Service implements DownloadService { getVisualizerController(); showVisualization = true; } + + MediaRouter mediaRouter = MediaRouter.getInstance(this); + JukeboxRouteProvider routeProvider = new JukeboxRouteProvider(this); + mediaRouter.addProvider(routeProvider); + + MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder(); + builder.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); + remoteSelector = builder.build(); } @Override @@ -1117,6 +1131,11 @@ public class DownloadServiceImpl extends Service implements DownloadService { return visualizerController; } + @Override + public MediaRouteSelector getRemotesAvailable() { + return remoteSelector; + } + @Override public boolean isRemoteEnabled() { return remoteState != RemoteControlState.LOCAL; -- cgit v1.2.3 From 0a7bae6b058cfb8098a91f1896932d591079c5ba Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Sun, 9 Feb 2014 10:16:51 -0800 Subject: Added route manager abstraction, ChromeCast compat class for devices without Play Services --- AndroidManifest.xml | 3 + .../dsub/fragments/DownloadFragment.java | 8 +- .../dsub/service/ChromeCastController.java | 70 +++++++++++++++++ .../daneren2005/dsub/service/DownloadService.java | 7 +- .../dsub/service/DownloadServiceImpl.java | 38 +++++---- .../daneren2005/dsub/util/MediaRouteManager.java | 91 ++++++++++++++++++++++ .../daneren2005/dsub/util/compat/CastCompat.java | 52 +++++++++++++ 7 files changed, 252 insertions(+), 17 deletions(-) create mode 100644 src/github/daneren2005/dsub/service/ChromeCastController.java create mode 100644 src/github/daneren2005/dsub/util/MediaRouteManager.java create mode 100644 src/github/daneren2005/dsub/util/compat/CastCompat.java (limited to 'src/github/daneren2005') diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f1eab09d..51de852a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -201,6 +201,9 @@ + diff --git a/src/github/daneren2005/dsub/fragments/DownloadFragment.java b/src/github/daneren2005/dsub/fragments/DownloadFragment.java index c5be820c..d3c1938e 100644 --- a/src/github/daneren2005/dsub/fragments/DownloadFragment.java +++ b/src/github/daneren2005/dsub/fragments/DownloadFragment.java @@ -505,7 +505,7 @@ public class DownloadFragment extends SubsonicFragment implements OnGestureListe if(downloadService != null) { MenuItem mediaRouteItem = menu.findItem(R.id.menu_mediaroute); MediaRouteButton mediaRouteButton = (MediaRouteButton) mediaRouteItem.getActionView(); - mediaRouteButton.setRouteSelector(downloadService.getRemotesAvailable()); + mediaRouteButton.setRouteSelector(downloadService.getRemoteSelector()); } } @@ -749,6 +749,9 @@ public class DownloadFragment extends SubsonicFragment implements OnGestureListe if(currentPlaying == null && downloadService != null && currentPlaying == downloadService.getCurrentPlaying()) { getImageLoader().loadImage(albumArtImageView, null, true, false); } + if(downloadService != null) { + downloadService.startRemoteScan(); + } } @Override @@ -758,6 +761,9 @@ public class DownloadFragment extends SubsonicFragment implements OnGestureListe if (visualizerView != null && visualizerView.isActive()) { visualizerView.setActive(false); } + if(getDownloadService() != null) { + getDownloadService().startRemoteScan(); + } } @Override diff --git a/src/github/daneren2005/dsub/service/ChromeCastController.java b/src/github/daneren2005/dsub/service/ChromeCastController.java new file mode 100644 index 00000000..467e4d7b --- /dev/null +++ b/src/github/daneren2005/dsub/service/ChromeCastController.java @@ -0,0 +1,70 @@ +/* + This file is part of Subsonic. + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see . + Copyright 2014 (C) Scott Jackson +*/ + +package github.daneren2005.dsub.service; + +import android.os.Handler; + +/** + * Created by owner on 2/9/14. + */ +public class ChromeCastController extends RemoteController { + private CastDevice castDevice; + + public ChromeCastController(DownloadServiceImpl downloadService, CastDevice castDevice) { + this.downloadService = downloadService; + this.castDevice = castDevice; + } + + @Override + public void start() { + + } + + @Override + public void stop() { + + } + + @Override + public void shutdown() { + + } + + @Override + public void updatePlaylist() { + + } + + @Override + public void changePosition(int seconds) { + + } + + @Override + public void changeTrack(int index, DownloadFile song) { + + } + + @Override + public void setVolume(boolean up) { + + } + + @Override + public int getRemotePosition() { + return 0; + } +} diff --git a/src/github/daneren2005/dsub/service/DownloadService.java b/src/github/daneren2005/dsub/service/DownloadService.java index 1a96b2be..471b6d5d 100644 --- a/src/github/daneren2005/dsub/service/DownloadService.java +++ b/src/github/daneren2005/dsub/service/DownloadService.java @@ -29,6 +29,7 @@ import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.PlayerState; import github.daneren2005.dsub.domain.RemoteControlState; import github.daneren2005.dsub.domain.RepeatMode; +import github.daneren2005.dsub.util.MediaRouteManager; /** * @author Sindre Mehus @@ -126,13 +127,17 @@ public interface DownloadService { VisualizerController getVisualizerController(); - MediaRouteSelector getRemotesAvailable(); + MediaRouteSelector getRemoteSelector(); boolean isRemoteEnabled(); void setRemoteEnabled(RemoteControlState newState); void setRemoteVolume(boolean up); + + void startRemoteScan(); + + void stopRemoteScan(); void setSleepTimerDuration(int duration); diff --git a/src/github/daneren2005/dsub/service/DownloadServiceImpl.java b/src/github/daneren2005/dsub/service/DownloadServiceImpl.java index ad2f0490..9a85bcdd 100644 --- a/src/github/daneren2005/dsub/service/DownloadServiceImpl.java +++ b/src/github/daneren2005/dsub/service/DownloadServiceImpl.java @@ -34,10 +34,10 @@ import github.daneren2005.dsub.domain.PlayerState; import github.daneren2005.dsub.domain.PodcastEpisode; import github.daneren2005.dsub.domain.RemoteControlState; import github.daneren2005.dsub.domain.RepeatMode; -import github.daneren2005.dsub.provider.JukeboxRouteProvider; import github.daneren2005.dsub.receiver.MediaButtonIntentReceiver; import github.daneren2005.dsub.util.CancellableTask; import github.daneren2005.dsub.util.Constants; +import github.daneren2005.dsub.util.MediaRouteManager; import github.daneren2005.dsub.util.ShufflePlayBuffer; import github.daneren2005.dsub.util.SimpleServiceBinder; import github.daneren2005.dsub.util.Util; @@ -64,9 +64,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.PowerManager; -import android.support.v7.media.MediaControlIntent; import android.support.v7.media.MediaRouteSelector; -import android.support.v7.media.MediaRouter; import android.util.Log; import android.support.v4.util.LruCache; import java.net.URLEncoder; @@ -140,7 +138,7 @@ public class DownloadServiceImpl extends Service implements DownloadService { private int timerDuration; private boolean autoPlayStart = false; - private MediaRouteSelector remoteSelector; + private MediaRouteManager mediaRouter; static { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { @@ -217,13 +215,7 @@ public class DownloadServiceImpl extends Service implements DownloadService { showVisualization = true; } - MediaRouter mediaRouter = MediaRouter.getInstance(this); - JukeboxRouteProvider routeProvider = new JukeboxRouteProvider(this); - mediaRouter.addProvider(routeProvider); - - MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder(); - builder.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); - remoteSelector = builder.build(); + mediaRouter = new MediaRouteManager(this); } @Override @@ -1132,8 +1124,8 @@ public class DownloadServiceImpl extends Service implements DownloadService { } @Override - public MediaRouteSelector getRemotesAvailable() { - return remoteSelector; + public MediaRouteSelector getRemoteSelector() { + return mediaRouter.getSelector(); } @Override @@ -1143,13 +1135,16 @@ public class DownloadServiceImpl extends Service implements DownloadService { @Override public void setRemoteEnabled(RemoteControlState newState) { - setRemoteState(newState); + setRemoteEnabled(newState, null); + } + public void setRemoteEnabled(RemoteControlState newState, Object ref) { + setRemoteState(newState, ref); SharedPreferences.Editor editor = Util.getPreferences(this).edit(); editor.putInt(Constants.PREFERENCES_KEY_CONTROL_MODE, newState.getValue()); editor.commit(); } - private void setRemoteState(RemoteControlState newState) { + private void setRemoteState(RemoteControlState newState, Object ref) { if(remoteController != null) { remoteController.stop(); setPlayerState(PlayerState.IDLE); @@ -1162,6 +1157,9 @@ public class DownloadServiceImpl extends Service implements DownloadService { case JUKEBOX_SERVER: remoteController = new JukeboxController(this, handler); break; + case CHROMECAST: + remoteController = (RemoteController) ref; + break; case LOCAL: default: break; } @@ -1188,6 +1186,16 @@ public class DownloadServiceImpl extends Service implements DownloadService { remoteController.setVolume(up); } + @Override + public void startRemoteScan() { + mediaRouter.startScan(); + } + + @Override + public void stopRemoteScan() { + mediaRouter.stopScan(); + } + private synchronized void bufferAndPlay() { bufferAndPlay(0); } diff --git a/src/github/daneren2005/dsub/util/MediaRouteManager.java b/src/github/daneren2005/dsub/util/MediaRouteManager.java new file mode 100644 index 00000000..ab6d6a10 --- /dev/null +++ b/src/github/daneren2005/dsub/util/MediaRouteManager.java @@ -0,0 +1,91 @@ +/* + This file is part of Subsonic. + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see . + Copyright 2014 (C) Scott Jackson +*/ + +package github.daneren2005.dsub.util; + +import android.support.v7.media.MediaControlIntent; +import android.support.v7.media.MediaRouteSelector; +import android.support.v7.media.MediaRouter; + +import github.daneren2005.dsub.domain.RemoteControlState; +import github.daneren2005.dsub.provider.JukeboxRouteProvider; +import github.daneren2005.dsub.service.DownloadServiceImpl; +import github.daneren2005.dsub.service.RemoteController; +import github.daneren2005.dsub.util.compat.CastCompat; + +/** + * Created by owner on 2/8/14. + */ +public class MediaRouteManager extends MediaRouter.Callback { + private static boolean castAvailable = false; + + private DownloadServiceImpl downloadService; + private MediaRouter router; + private MediaRouteSelector selector; + + static { + try { + CastCompat.checkAvailable(); + castAvailable = true; + } catch(Throwable t) { + castAvailable = false; + } + } + + public MediaRouteManager(DownloadServiceImpl downloadService) { + this.downloadService = downloadService; + router = MediaRouter.getInstance(downloadService); + addProviders(); + buildSelector(); + } + + @Override + public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo info) { + if(castAvailable) { + RemoteController controller = CastCompat.getController(downloadService, info); + if(controller != null) { + downloadService.setRemoteEnabled(RemoteControlState.CHROMECAST, controller); + } + } + } + @Override + public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo info) { + downloadService.setRemoteEnabled(RemoteControlState.LOCAL); + } + + public void startScan() { + router.addCallback(selector, this, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); + } + public void stopScan() { + router.removeCallback(this); + } + + public MediaRouteSelector getSelector() { + return selector; + } + + private void addProviders() { + JukeboxRouteProvider routeProvider = new JukeboxRouteProvider(downloadService); + router.addProvider(routeProvider); + } + private void buildSelector() { + MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder(); + builder.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); + if(castAvailable) { + builder.addControlCategory(CastCompat.getCastControlCategory()); + } + selector = builder.build(); + } +} diff --git a/src/github/daneren2005/dsub/util/compat/CastCompat.java b/src/github/daneren2005/dsub/util/compat/CastCompat.java new file mode 100644 index 00000000..0892040e --- /dev/null +++ b/src/github/daneren2005/dsub/util/compat/CastCompat.java @@ -0,0 +1,52 @@ +/* + This file is part of Subsonic. + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see . + Copyright 2014 (C) Scott Jackson +*/ + +package github.daneren2005.dsub.util.compat; + +import android.support.v7.media.MediaRouter; + +import github.daneren2005.dsub.service.ChromeCastController; +import github.daneren2005.dsub.service.DownloadServiceImpl; +import github.daneren2005.dsub.service.RemoteController; + +/** + * Created by owner on 2/9/14. + */ +public final class CastCompat { + static { + try { + Class.forName("com.google.android.gms.cast.CastDevice"); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + public static void checkAvailable() throws Throwable { + // Calling here forces class initialization. + } + + public static RemoteController getController(DownloadServiceImpl downloadService, MediaRouter.RouteInfo info) { + CastDevice device = CastDevice.getFromBundle(info.getExtras()); + if(device != null) { + return new ChromeCastController(downloadService, device); + } else { + return null; + } + } + + public static String getCastControlCategory() { + return CastMediaControlIntent.categoryForCast("5F85EBEB"); + } +} -- cgit v1.2.3 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 --- Subsonic.iml | 2 +- .../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 +- 8 files changed, 242 insertions(+), 9 deletions(-) (limited to 'src/github/daneren2005') diff --git a/Subsonic.iml b/Subsonic.iml index f9f7ef9c..31ebc6b4 100644 --- a/Subsonic.iml +++ b/Subsonic.iml @@ -14,12 +14,12 @@ - + 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 From 9a4100108f921af9cf6cfc4def87bc7c7dfec1fb Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Fri, 14 Feb 2014 14:59:18 -0800 Subject: CC should use separate profile, add volume, position modification code --- .../dsub/service/ChromeCastController.java | 149 ++++++++++++++------- src/github/daneren2005/dsub/util/Constants.java | 1 + 2 files changed, 99 insertions(+), 51 deletions(-) (limited to 'src/github/daneren2005') diff --git a/src/github/daneren2005/dsub/service/ChromeCastController.java b/src/github/daneren2005/dsub/service/ChromeCastController.java index 22563089..550414c4 100644 --- a/src/github/daneren2005/dsub/service/ChromeCastController.java +++ b/src/github/daneren2005/dsub/service/ChromeCastController.java @@ -51,6 +51,7 @@ public class ChromeCastController extends RemoteController { private boolean waitingForReconnect = false; private RemoteMediaPlayer mediaPlayer; + private double gain = 0.5; public ChromeCastController(DownloadServiceImpl downloadService, CastDevice castDevice) { this.downloadService = downloadService; @@ -63,21 +64,19 @@ public class ChromeCastController extends RemoteController { 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)); + gain = Cast.CastApi.getVolume(apiClient); } } @Override public void onApplicationDisconnected(int errorCode) { - Log.d(TAG, "onApplicationDisconnected: " + errorCode); - // teardown(); + shutdown(); } }; @@ -141,24 +140,97 @@ public class ChromeCastController extends RemoteController { @Override public void changePosition(int seconds) { - + try { + mediaPlayer.seek(apiClient, seconds * 1000L); + } catch(Exception e) { + Log.e(TAG, "FAiled to seek to " + seconds); + } } @Override public void changeTrack(int index, DownloadFile song) { - + startSong(song, true); } @Override public void setVolume(boolean up) { + double delta = up ? 0.1 : -0.1; + gain += delta; + gain = Math.max(gain, 0.0); + gain = Math.min(gain, 1.0); + getVolumeToast().setVolume(gain); + try { + Cast.CastApi.setVolume(apiClient, gain); + } catch(Exception e) { + Log.e(TAG, "Failed to the volume"); + } } @Override public int getRemotePosition() { - return 0; + if(remotePlayer != null) { + return remotePlayer.getApproximateStreamPosition(); + } else { + return 0; + } + } + + void startSong(DownloadFile currentPlaying, boolean autoStart) { + if(currentPlaying == null) { + // Don't start anything + return; + } + MusicDirectory.Entry song = currentPlaying.getSong(); + + try { + MusicService musicService = MusicServiceFactory.getMusicService(downloadService); + String url = song.isVideo() ? musicVideo.getHlsUrl(song.getId(), downloadFile.getBitRate(), downloadService) : musicService.getMusicUrl(downloadService, song, currentPlaying.getBitRate()); + // Use separate profile for Chromecast so users can do ogg on phone, mp3 for CC + url = url.replace(Constants.REST_CLIENT_ID, Constants.CHROMECAST_CLIENT_ID); + + // Setup song/video information + MediaMetadata meta = new MediaMetadata(song.isVideo() ? MediaMetadata.MEDIA_TYPE_MOVIE : MediaMetadata.MEDIA_TYPE_MUSIC_TRACK); + meta.putString(MediaMetadata.KEY_TITLE, song.getTitle()); + if(song.getTrack() != null) { + meta.putInt(MediaMetadata.KEY_TRACK_NUMBER, song.getTrack()); + } + if(!song.isVideo()) { + meta.putString(MediaMetadata.KEY_ARTIST, song.getArtist()); + meta.putString(MediaMetadata.KEY_ALBUM_ARTIST, song.getArtist()); + meta.putString(MediaMetadata.KEY_ALBUM_TITLE, song.getAlbum()); + } + + // Load it into a MediaInfo wrapper + MediaInfo mediaInfo = new MediaInfo.Builder(url) + .setContentType(song.isVideo() ? "application/x-mpegURL" : song.getTranscodedContentType()) + .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) + .setMetadata(meta) + .build(); + + mediaPlayer.load(apiClient, mediaInfo, autoPlay).setResultCallback(new ResultCallback() { + @Override + public void onResult(RemoteMediaPlayer.MediaChannelResult result) { + if (result.getStatus().isSuccess()) { + if(mediaPlayer.getMediaStatus().getPlayerState() == MediaStatus.PLAYER_STATE_PLAYING) { + downloadService.setPlayerState(PlayerState.STARTED); + } else { + downloadService.setPlayerState(PlayerState.PREPARED); + } + } else { + Log.e(TAG, "Failed to load"); + downloadService.setPlayerState(PlayerState.STOPPED); + } + } + }); + } 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 ConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks { @Override public void onConnected(Bundle connectionHint) { @@ -190,7 +262,7 @@ public class ChromeCastController extends RemoteController { applicationStarted = true; setupChannel(); } else { - // teardown(); + shutdown(); } } }); @@ -204,9 +276,21 @@ public class ChromeCastController extends RemoteController { @Override public void onStatusUpdated() { MediaStatus mediaStatus = mediaPlayer.getMediaStatus(); - Log.d(TAG, "mediaPlayer status: " + mediaStatus); - boolean isPlaying = mediaStatus.getPlayerState() == MediaStatus.PLAYER_STATE_PLAYING; - + switch(mediaStatus.getPlayerState()) { + case PLAYER_STATE_PLAYING: + downloadService.setPlayerState(PlayerState.STARTED); + break; + case PLAYER_STATE_PAUSED: + downloadService.setPlayerState(PlayerState.PAUSED); + break; + case PLAYER_STATE_BUFFERING: + downloadService.setPlayerState(PlayerState.PREPARING); + break; + case PLAYER_STATE_IDLE: + downloadService.setPlayerState(PlayerState.COMPLETED); + downloadService.next(); + break; + } } }); mediaPlayer.setOnMetadataUpdatedListener(new RemoteMediaPlayer.OnMetadataUpdatedListener() { @@ -214,8 +298,7 @@ public class ChromeCastController extends RemoteController { public void onMetadataUpdated() { MediaInfo mediaInfo = mediaPlayer.getMediaInfo(); MediaMetadata metadata = mediaInfo.getMetadata(); - Log.d(TAG, "mediaInfo: " + mediaInfo); - Log.d(TAG, "metadata: " + metadata); + // TODO: Do I care about this? } }); @@ -225,51 +308,15 @@ public class ChromeCastController extends RemoteController { 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); - } + startSong(currentPlaying, false); } } private class ConnectionFailedListener implements GoogleApiClient.OnConnectionFailedListener { @Override public void onConnectionFailed(ConnectionResult result) { - // teardown(); + shutdown(); } } } diff --git a/src/github/daneren2005/dsub/util/Constants.java b/src/github/daneren2005/dsub/util/Constants.java index 5d80a961..2b96ac4c 100644 --- a/src/github/daneren2005/dsub/util/Constants.java +++ b/src/github/daneren2005/dsub/util/Constants.java @@ -31,6 +31,7 @@ public final class Constants { // Note: Keep it as low as possible to maintain compatibility with older servers. public static final String REST_PROTOCOL_VERSION = "1.2.0"; public static final String REST_CLIENT_ID = "DSub"; + public static final String CHROMECAST_CLIENT_ID = "DSubCC"; public static final String LAST_VERSION = "subsonic.version"; // Names for intent extras. -- cgit v1.2.3 From 3c1de917b661cb105aaa80542d87a1e4bf6c2c77 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Sat, 15 Feb 2014 11:45:30 -0800 Subject: Fix a bunch of casting bugs, start of album art --- res/values/strings.xml | 1 + .../dsub/service/CachedMusicService.java | 7 ++- .../dsub/service/ChromeCastController.java | 58 ++++++++++++++-------- .../daneren2005/dsub/service/MusicService.java | 2 + .../daneren2005/dsub/service/RESTMusicService.java | 8 +++ .../daneren2005/dsub/util/MediaRouteManager.java | 2 + 6 files changed, 57 insertions(+), 21 deletions(-) (limited to 'src/github/daneren2005') diff --git a/res/values/strings.xml b/res/values/strings.xml index de4070bb..ea8a1022 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -197,6 +197,7 @@ Current: %1$s \nEstimated Size: %2$s \nAverage Speed: %3$s kbps + Failed to load DSub: New media is available New podcasts: %s diff --git a/src/github/daneren2005/dsub/service/CachedMusicService.java b/src/github/daneren2005/dsub/service/CachedMusicService.java index 4417ac78..0826e967 100644 --- a/src/github/daneren2005/dsub/service/CachedMusicService.java +++ b/src/github/daneren2005/dsub/service/CachedMusicService.java @@ -283,7 +283,12 @@ public class CachedMusicService implements MusicService { return musicService.getRandomSongs(size, folder, genre, startYear, endYear, context, progressListener); } - @Override + @Override + public String getCoverArtUrl(Context context, MusicDirectory.Entry entry) throws Exception { + return musicService.getCoverArtUrl(context, entry); + } + + @Override public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, ProgressListener progressListener) throws Exception { return musicService.getCoverArt(context, entry, size, progressListener); } diff --git a/src/github/daneren2005/dsub/service/ChromeCastController.java b/src/github/daneren2005/dsub/service/ChromeCastController.java index 550414c4..c70ccdcd 100644 --- a/src/github/daneren2005/dsub/service/ChromeCastController.java +++ b/src/github/daneren2005/dsub/service/ChromeCastController.java @@ -32,7 +32,11 @@ import com.google.android.gms.common.api.Status; import java.io.IOException; +import github.daneren2005.dsub.R; import github.daneren2005.dsub.domain.MusicDirectory; +import github.daneren2005.dsub.domain.PlayerState; +import github.daneren2005.dsub.util.Constants; +import github.daneren2005.dsub.util.Util; import github.daneren2005.dsub.util.compat.CastCompat; /** @@ -49,6 +53,7 @@ public class ChromeCastController extends RemoteController { private boolean applicationStarted = false; private boolean waitingForReconnect = false; + private boolean error = false; private RemoteMediaPlayer mediaPlayer; private double gain = 0.5; @@ -93,10 +98,17 @@ public class ChromeCastController extends RemoteController { @Override public void start() { + if(error) { + error = false; + Log.w(TAG, "Attempting to restart song"); + startSong(downloadService.getCurrentPlaying(), true); + return; + } + try { mediaPlayer.play(apiClient); } catch(Exception e) { - Log.e(TAG, "Failed to pause"); + Log.e(TAG, "Failed to start"); } } @@ -112,7 +124,7 @@ public class ChromeCastController extends RemoteController { @Override public void shutdown() { try { - if(mediaPlayer != null) { + if(mediaPlayer != null && !error) { mediaPlayer.stop(apiClient); } } catch(Exception e) { @@ -120,17 +132,20 @@ public class ChromeCastController extends RemoteController { } try { - Cast.CastApi.stopApplication(apiClient); - Cast.CastApi.removeMessageReceivedCallbacks(apiClient, mediaPlayer.getNamespace()); - mediaPlayer = null; - applicationStarted = false; - } catch(IOException e) { + if(apiClient != null) { + Cast.CastApi.stopApplication(apiClient); + Cast.CastApi.removeMessageReceivedCallbacks(apiClient, mediaPlayer.getNamespace()); + mediaPlayer = null; + applicationStarted = false; + } + } catch(Exception e) { Log.e(TAG, "Failed to shutdown application", e); } - if(apiClient.isConnected()) { + if(apiClient != null && apiClient.isConnected()) { apiClient.disconnect(); } + apiClient = null; } @Override @@ -159,7 +174,7 @@ public class ChromeCastController extends RemoteController { gain = Math.max(gain, 0.0); gain = Math.min(gain, 1.0); - getVolumeToast().setVolume(gain); + getVolumeToast().setVolume((float) gain); try { Cast.CastApi.setVolume(apiClient, gain); } catch(Exception e) { @@ -169,8 +184,8 @@ public class ChromeCastController extends RemoteController { @Override public int getRemotePosition() { - if(remotePlayer != null) { - return remotePlayer.getApproximateStreamPosition(); + if(mediaPlayer != null) { + return (int) (mediaPlayer.getApproximateStreamPosition() / 1000L); } else { return 0; } @@ -181,11 +196,12 @@ public class ChromeCastController extends RemoteController { // Don't start anything return; } + downloadService.setPlayerState(PlayerState.PREPARING); MusicDirectory.Entry song = currentPlaying.getSong(); try { MusicService musicService = MusicServiceFactory.getMusicService(downloadService); - String url = song.isVideo() ? musicVideo.getHlsUrl(song.getId(), downloadFile.getBitRate(), downloadService) : musicService.getMusicUrl(downloadService, song, currentPlaying.getBitRate()); + String url = song.isVideo() ? musicService.getHlsUrl(song.getId(), currentPlaying.getBitRate(), downloadService) : musicService.getMusicUrl(downloadService, song, currentPlaying.getBitRate()); // Use separate profile for Chromecast so users can do ogg on phone, mp3 for CC url = url.replace(Constants.REST_CLIENT_ID, Constants.CHROMECAST_CLIENT_ID); @@ -208,7 +224,7 @@ public class ChromeCastController extends RemoteController { .setMetadata(meta) .build(); - mediaPlayer.load(apiClient, mediaInfo, autoPlay).setResultCallback(new ResultCallback() { + mediaPlayer.load(apiClient, mediaInfo, autoStart).setResultCallback(new ResultCallback() { @Override public void onResult(RemoteMediaPlayer.MediaChannelResult result) { if (result.getStatus().isSuccess()) { @@ -217,9 +233,11 @@ public class ChromeCastController extends RemoteController { } else { downloadService.setPlayerState(PlayerState.PREPARED); } - } else { + } else if(result.getStatus().getStatusCode() != ConnectionResult.SIGN_IN_REQUIRED) { Log.e(TAG, "Failed to load"); downloadService.setPlayerState(PlayerState.STOPPED); + error = true; + Util.toast(downloadService, downloadService.getResources().getString(R.string.download_failed_to_load)); } } }); @@ -276,17 +294,18 @@ public class ChromeCastController extends RemoteController { @Override public void onStatusUpdated() { MediaStatus mediaStatus = mediaPlayer.getMediaStatus(); + Log.d(TAG, "state: " + mediaStatus.getPlayerState()); switch(mediaStatus.getPlayerState()) { - case PLAYER_STATE_PLAYING: + case MediaStatus.PLAYER_STATE_PLAYING: downloadService.setPlayerState(PlayerState.STARTED); break; - case PLAYER_STATE_PAUSED: + case MediaStatus.PLAYER_STATE_PAUSED: downloadService.setPlayerState(PlayerState.PAUSED); break; - case PLAYER_STATE_BUFFERING: + case MediaStatus.PLAYER_STATE_BUFFERING: downloadService.setPlayerState(PlayerState.PREPARING); break; - case PLAYER_STATE_IDLE: + case MediaStatus.PLAYER_STATE_IDLE: downloadService.setPlayerState(PlayerState.COMPLETED); downloadService.next(); break; @@ -297,7 +316,6 @@ public class ChromeCastController extends RemoteController { @Override public void onMetadataUpdated() { MediaInfo mediaInfo = mediaPlayer.getMediaInfo(); - MediaMetadata metadata = mediaInfo.getMetadata(); // TODO: Do I care about this? } }); @@ -309,7 +327,7 @@ public class ChromeCastController extends RemoteController { } DownloadFile currentPlaying = downloadService.getCurrentPlaying(); - startSong(currentPlaying, false); + startSong(currentPlaying, true); } } diff --git a/src/github/daneren2005/dsub/service/MusicService.java b/src/github/daneren2005/dsub/service/MusicService.java index a1e54a81..d5fdc251 100644 --- a/src/github/daneren2005/dsub/service/MusicService.java +++ b/src/github/daneren2005/dsub/service/MusicService.java @@ -91,6 +91,8 @@ public interface MusicService { MusicDirectory getRandomSongs(int size, String folder, String genre, String startYear, String endYear, Context context, ProgressListener progressListener) throws Exception; + String getCoverArtUrl(Context context, MusicDirectory.Entry entry) throws Exception; + Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, ProgressListener progressListener) throws Exception; HttpResponse getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, CancellableTask task) throws Exception; diff --git a/src/github/daneren2005/dsub/service/RESTMusicService.java b/src/github/daneren2005/dsub/service/RESTMusicService.java index 8555f9e2..5c909d1f 100644 --- a/src/github/daneren2005/dsub/service/RESTMusicService.java +++ b/src/github/daneren2005/dsub/service/RESTMusicService.java @@ -644,6 +644,14 @@ public class RESTMusicService implements MusicService { } } + @Override + public String getCoverArtUrl(Context context, MusicDirectory.Entry entry) throws Exception { + StringBuilder builder = new StringBuilder(getRestUrl(context, "getCoverArt")); + builder.append("&id=").append(entry.getId()); + String url = rewriteUrlWithRedirect(context, builder.toString()); + return url; + } + @Override public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, ProgressListener progressListener) throws Exception { diff --git a/src/github/daneren2005/dsub/util/MediaRouteManager.java b/src/github/daneren2005/dsub/util/MediaRouteManager.java index ab6d6a10..7c0d33e6 100644 --- a/src/github/daneren2005/dsub/util/MediaRouteManager.java +++ b/src/github/daneren2005/dsub/util/MediaRouteManager.java @@ -18,6 +18,7 @@ package github.daneren2005.dsub.util; import android.support.v7.media.MediaControlIntent; import android.support.v7.media.MediaRouteSelector; import android.support.v7.media.MediaRouter; +import android.util.Log; import github.daneren2005.dsub.domain.RemoteControlState; import github.daneren2005.dsub.provider.JukeboxRouteProvider; @@ -29,6 +30,7 @@ import github.daneren2005.dsub.util.compat.CastCompat; * Created by owner on 2/8/14. */ public class MediaRouteManager extends MediaRouter.Callback { + private static final String TAG = MediaRouteManager.class.getSimpleName(); private static boolean castAvailable = false; private DownloadServiceImpl downloadService; -- cgit v1.2.3 From abe7f243dee1b85a0f82fab3fe552c8ae4e6ee87 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Sat, 15 Feb 2014 14:20:48 -0800 Subject: Add album art to cast --- src/github/daneren2005/dsub/service/ChromeCastController.java | 7 ++++++- src/github/daneren2005/dsub/service/RESTMusicService.java | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'src/github/daneren2005') diff --git a/src/github/daneren2005/dsub/service/ChromeCastController.java b/src/github/daneren2005/dsub/service/ChromeCastController.java index c70ccdcd..49f36997 100644 --- a/src/github/daneren2005/dsub/service/ChromeCastController.java +++ b/src/github/daneren2005/dsub/service/ChromeCastController.java @@ -15,6 +15,7 @@ package github.daneren2005.dsub.service; +import android.net.Uri; import android.os.Bundle; import android.util.Log; @@ -26,9 +27,11 @@ 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.GooglePlayServicesUtil; 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 com.google.android.gms.common.images.WebImage; import java.io.IOException; @@ -215,6 +218,8 @@ public class ChromeCastController extends RemoteController { meta.putString(MediaMetadata.KEY_ARTIST, song.getArtist()); meta.putString(MediaMetadata.KEY_ALBUM_ARTIST, song.getArtist()); meta.putString(MediaMetadata.KEY_ALBUM_TITLE, song.getAlbum()); + String coverArt = musicService.getCoverArtUrl(downloadService, song); + meta.addImage(new WebImage(Uri.parse(coverArt))); } // Load it into a MediaInfo wrapper @@ -234,7 +239,7 @@ public class ChromeCastController extends RemoteController { downloadService.setPlayerState(PlayerState.PREPARED); } } else if(result.getStatus().getStatusCode() != ConnectionResult.SIGN_IN_REQUIRED) { - Log.e(TAG, "Failed to load"); + Log.e(TAG, "Failed to load: " + result.getStatus().toString()); downloadService.setPlayerState(PlayerState.STOPPED); error = true; Util.toast(downloadService, downloadService.getResources().getString(R.string.download_failed_to_load)); diff --git a/src/github/daneren2005/dsub/service/RESTMusicService.java b/src/github/daneren2005/dsub/service/RESTMusicService.java index 5c909d1f..37e2bf44 100644 --- a/src/github/daneren2005/dsub/service/RESTMusicService.java +++ b/src/github/daneren2005/dsub/service/RESTMusicService.java @@ -646,7 +646,7 @@ public class RESTMusicService implements MusicService { @Override public String getCoverArtUrl(Context context, MusicDirectory.Entry entry) throws Exception { - StringBuilder builder = new StringBuilder(getRestUrl(context, "getCoverArt")); + StringBuilder builder = new StringBuilder(getRestUrl(context, "getCoverArt", false)); builder.append("&id=").append(entry.getId()); String url = rewriteUrlWithRedirect(context, builder.toString()); return url; -- cgit v1.2.3