From 3da246de2ca75afaa03985a8a5a73c12bf1995c4 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Fri, 9 Oct 2015 10:01:32 -0700 Subject: Start of Auto support --- .../dsub/service/AutoMediaBrowserService.java | 94 ++++++++++ .../daneren2005/dsub/service/DownloadService.java | 10 +- .../dsub/util/compat/RemoteControlClientBase.java | 59 +++--- .../util/compat/RemoteControlClientHelper.java | 32 ---- .../dsub/util/compat/RemoteControlClientICS.java | 2 +- .../dsub/util/compat/RemoteControlClientLP.java | 205 +++++++++++++++++++++ 6 files changed, 331 insertions(+), 71 deletions(-) create mode 100644 app/src/main/java/github/daneren2005/dsub/service/AutoMediaBrowserService.java delete mode 100644 app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientHelper.java create mode 100644 app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientLP.java (limited to 'app/src/main/java/github/daneren2005') diff --git a/app/src/main/java/github/daneren2005/dsub/service/AutoMediaBrowserService.java b/app/src/main/java/github/daneren2005/dsub/service/AutoMediaBrowserService.java new file mode 100644 index 00000000..4d21d793 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/service/AutoMediaBrowserService.java @@ -0,0 +1,94 @@ +/* + 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 2015 (C) Scott Jackson +*/ +package github.daneren2005.dsub.service; + +import android.annotation.TargetApi; +import android.content.Intent; +import android.media.MediaDescription; +import android.media.browse.MediaBrowser; +import android.media.session.MediaSession; +import android.os.Build; +import android.os.Bundle; +import android.service.media.MediaBrowserService; +import android.support.annotation.Nullable; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +import github.daneren2005.dsub.util.Util; +import github.daneren2005.dsub.util.compat.RemoteControlClientLP; + +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +public class AutoMediaBrowserService extends MediaBrowserService { + private static final String TAG = AutoMediaBrowserService.class.getSimpleName(); + private static final String BROWSER_ROOT = "root"; + MediaSession mediaSession; + + @Override + public void onCreate() { + super.onCreate(); + + DownloadService downloadService = getDownloadService(); + RemoteControlClientLP remoteControlClient = (RemoteControlClientLP) downloadService.getRemoteControlClient(); + setSessionToken(remoteControlClient.getMediaSession().getSessionToken()); + } + + @Nullable + @Override + public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { + BrowserRoot root = new BrowserRoot(BROWSER_ROOT, null); + return root; + } + + @Override + public void onLoadChildren(String parentId, Result> result) { + if(BROWSER_ROOT.equals(parentId)) { + getRootFolders(result); + } else { + + } + } + + private void getRootFolders(Result> result) { + List mediaItems = new ArrayList<>(); + + MediaDescription.Builder library = new MediaDescription.Builder(); + library.setDescription("Library") + .setMediaId("library"); + mediaItems.add(new MediaBrowser.MediaItem(library.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE)); + + result.sendResult(mediaItems); + } + + public DownloadService getDownloadService() { + // If service is not available, request it to start and wait for it. + for (int i = 0; i < 5; i++) { + DownloadService downloadService = DownloadService.getInstance(); + if (downloadService != null) { + break; + } + Log.w(TAG, "DownloadService not running. Attempting to start it."); + startService(new Intent(this, DownloadService.class)); + Util.sleepQuietly(50L); + } + + return DownloadService.getInstance(); + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java b/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java index 7af21831..6dedca29 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java @@ -49,7 +49,7 @@ import github.daneren2005.dsub.util.MediaRouteManager; import github.daneren2005.dsub.util.ShufflePlayBuffer; import github.daneren2005.dsub.util.SimpleServiceBinder; import github.daneren2005.dsub.util.Util; -import github.daneren2005.dsub.util.compat.RemoteControlClientHelper; +import github.daneren2005.dsub.util.compat.RemoteControlClientBase; import github.daneren2005.dsub.util.tags.BastpUtil; import github.daneren2005.dsub.view.UpdateView; import github.daneren2005.serverproxy.BufferProxy; @@ -106,7 +106,7 @@ public class DownloadService extends Service { private static final int SHUFFLE_MODE_ALL = 1; private static final int SHUFFLE_MODE_ARTIST = 2; - private RemoteControlClientHelper mRemoteControl; + private RemoteControlClientBase mRemoteControl; private final IBinder binder = new SimpleServiceBinder(this); private Looper mediaPlayerLooper; @@ -238,7 +238,7 @@ public class DownloadService extends Service { if (mRemoteControl == null) { // Use the remote control APIs (if available) to set the playback state - mRemoteControl = RemoteControlClientHelper.createInstance(); + mRemoteControl = RemoteControlClientBase.createInstance(); ComponentName mediaButtonReceiverComponent = new ComponentName(getPackageName(), MediaButtonIntentReceiver.class.getName()); mRemoteControl.register(this, mediaButtonReceiverComponent); } @@ -2232,6 +2232,10 @@ public class DownloadService extends Service { } clearCurrentBookmark(downloadFile.getSong(), true); } + + public RemoteControlClientBase getRemoteControlClient() { + return mRemoteControl; + } private boolean isPastCutoff() { return isPastCutoff(getPlayerPosition(), getPlayerDuration()); diff --git a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientBase.java b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientBase.java index 320092e9..357e2585 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientBase.java +++ b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientBase.java @@ -1,43 +1,32 @@ package github.daneren2005.dsub.util.compat; -import github.daneren2005.dsub.domain.MusicDirectory.Entry; +import github.daneren2005.dsub.domain.MusicDirectory; import android.content.ComponentName; import android.content.Context; import android.support.v7.media.MediaRouter; -import android.util.Log; - -public class RemoteControlClientBase extends RemoteControlClientHelper { - - private static final String TAG = RemoteControlClientBase.class.getSimpleName(); - - @Override - public void register(Context context, ComponentName mediaButtonReceiverComponent) { - - } - - @Override - public void unregister(Context context) { - +import android.os.Build; + +public abstract class RemoteControlClientBase { + + public static RemoteControlClientBase createInstance() { + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return new RemoteControlClientLP(); + } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + return new RemoteControlClientJB(); + } else { + return new RemoteControlClientICS(); + } } - - @Override - public void setPlaybackState(int state) { - + + protected RemoteControlClientBase() { + // Avoid instantiation } - - @Override - public void updateMetadata(Context context, Entry currentSong) { - - } - - @Override - public void registerRoute(MediaRouter router) { - - } - - @Override - public void unregisterRoute(MediaRouter router) { - - } - + + public abstract void register(final Context context, final ComponentName mediaButtonReceiverComponent); + public abstract void unregister(final Context context); + public abstract void setPlaybackState(final int state); + public abstract void updateMetadata(final Context context, final MusicDirectory.Entry currentSong); + public abstract void registerRoute(MediaRouter router); + public abstract void unregisterRoute(MediaRouter router); + } diff --git a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientHelper.java b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientHelper.java deleted file mode 100644 index 93075a28..00000000 --- a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientHelper.java +++ /dev/null @@ -1,32 +0,0 @@ -package github.daneren2005.dsub.util.compat; - -import github.daneren2005.dsub.domain.MusicDirectory; -import android.content.ComponentName; -import android.content.Context; -import android.support.v7.media.MediaRouter; -import android.os.Build; - -public abstract class RemoteControlClientHelper { - - public static RemoteControlClientHelper createInstance() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - return new RemoteControlClientBase(); - } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - return new RemoteControlClientJB(); - } else { - return new RemoteControlClientICS(); - } - } - - protected RemoteControlClientHelper() { - // Avoid instantiation - } - - public abstract void register(final Context context, final ComponentName mediaButtonReceiverComponent); - public abstract void unregister(final Context context); - public abstract void setPlaybackState(final int state); - public abstract void updateMetadata(final Context context, final MusicDirectory.Entry currentSong); - public abstract void registerRoute(MediaRouter router); - public abstract void unregisterRoute(MediaRouter router); - -} diff --git a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java index 46b6b47d..9b4725bd 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java +++ b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java @@ -16,7 +16,7 @@ import android.support.v7.media.MediaRouter; import github.daneren2005.dsub.activity.SubsonicActivity; @TargetApi(14) -public class RemoteControlClientICS extends RemoteControlClientHelper { +public class RemoteControlClientICS extends RemoteControlClientBase { private static String TAG = RemoteControlClientICS.class.getSimpleName(); protected RemoteControlClient mRemoteControl; diff --git a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientLP.java b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientLP.java new file mode 100644 index 00000000..2cf2dd20 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientLP.java @@ -0,0 +1,205 @@ +/* + 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 2015 (C) Scott Jackson +*/ +package github.daneren2005.dsub.util.compat; + +import android.annotation.TargetApi; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.media.AudioAttributes; +import android.media.MediaMetadata; +import android.media.Rating; +import android.media.RemoteControlClient; +import android.media.session.MediaSession; +import android.media.session.PlaybackState; +import android.os.Build; +import android.support.v7.media.MediaRouter; + +import github.daneren2005.dsub.activity.SubsonicFragmentActivity; +import github.daneren2005.dsub.domain.MusicDirectory; +import github.daneren2005.dsub.service.DownloadService; +import github.daneren2005.dsub.util.Constants; + +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +public class RemoteControlClientLP extends RemoteControlClientBase { + private MediaSession mediaSession; + private DownloadService downloadService; + + private PlaybackState previousState; + + @Override + public void register(Context context, ComponentName mediaButtonReceiverComponent) { + downloadService = (DownloadService) context; + mediaSession = new MediaSession(downloadService, "DSub MediaSession"); + + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + mediaButtonIntent.setComponent(mediaButtonReceiverComponent); + PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(), 0, mediaButtonIntent, 0); + mediaSession.setMediaButtonReceiver(mediaPendingIntent); + + Intent activityIntent = new Intent(context, SubsonicFragmentActivity.class); + activityIntent.putExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD, true); + activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + PendingIntent activityPendingIntent = PendingIntent.getActivity(context, 0, activityIntent, 0); + mediaSession.setSessionActivity(activityPendingIntent); + + mediaSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS); + mediaSession.setCallback(new EventCallback()); + + if(Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) { + mediaSession.setRatingType(Rating.RATING_THUMB_UP_DOWN); + } + + AudioAttributes.Builder audioAttributesBuilder = new AudioAttributes.Builder(); + audioAttributesBuilder.setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC); + mediaSession.setPlaybackToLocal(audioAttributesBuilder.build()); + mediaSession.setActive(true); + } + + @Override + public void unregister(Context context) { + mediaSession.release(); + } + + @Override + public void setPlaybackState(int state) { + PlaybackState.Builder builder; + if(previousState == null) { + builder = new PlaybackState.Builder(); + } else { + builder = new PlaybackState.Builder(previousState); + } + + int newState = PlaybackState.STATE_NONE; + switch(state) { + case RemoteControlClient.PLAYSTATE_PLAYING: + newState = PlaybackState.STATE_PLAYING; + break; + case RemoteControlClient.PLAYSTATE_STOPPED: + newState = PlaybackState.STATE_STOPPED; + break; + case RemoteControlClient.PLAYSTATE_PAUSED: + newState = PlaybackState.STATE_PAUSED; + break; + case RemoteControlClient.PLAYSTATE_BUFFERING: + newState = PlaybackState.STATE_BUFFERING; + break; + } + + long position = -1; + if(state == RemoteControlClient.PLAYSTATE_PLAYING || state == RemoteControlClient.PLAYSTATE_PAUSED) { + position = downloadService.getPlayerPosition(); + } + builder.setState(newState, position, 1.0f); + builder.setActions(getPlaybackActions()); + + PlaybackState playbackState = builder.build(); + mediaSession.setPlaybackState(playbackState); + previousState = playbackState; + } + + @Override + public void updateMetadata(Context context, MusicDirectory.Entry currentSong) { + MediaMetadata.Builder builder = new MediaMetadata.Builder(); + builder.putString(MediaMetadata.METADATA_KEY_ARTIST, (currentSong == null) ? null : currentSong.getArtist()) + .putString(MediaMetadata.METADATA_KEY_ALBUM, (currentSong == null) ? null : currentSong.getAlbum()) + .putString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST, (currentSong == null) ? null : currentSong.getArtist()) + .putString(MediaMetadata.METADATA_KEY_TITLE, (currentSong) == null ? null : currentSong.getTitle()) + .putString(MediaMetadata.METADATA_KEY_GENRE, (currentSong) == null ? null : currentSong.getGenre()) + .putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, (currentSong == null) ? + 0 : ((currentSong.getTrack() == null) ? 0 : currentSong.getTrack())) + .putLong(MediaMetadata.METADATA_KEY_DURATION, (currentSong == null) ? + 0 : ((currentSong.getDuration() == null) ? 0 : (currentSong.getDuration() * 1000))); + + int userRating = currentSong.getRating(); + Rating rating; + if(userRating == 1) { + rating = Rating.newThumbRating(false); + } else if(userRating == 5) { + rating = Rating.newThumbRating(true); + } else { + rating = Rating.newUnratedRating(Rating.RATING_THUMB_UP_DOWN); + } + builder.putRating(MediaMetadata.METADATA_KEY_USER_RATING, rating); + + mediaSession.setMetadata(builder.build()); + } + + @Override + public void registerRoute(MediaRouter router) { + router.setMediaSession(mediaSession); + } + + @Override + public void unregisterRoute(MediaRouter router) { + router.setMediaSession(null); + } + + public MediaSession getMediaSession() { + return mediaSession; + } + + protected long getPlaybackActions() { + return PlaybackState.ACTION_PLAY | + PlaybackState.ACTION_PAUSE | + PlaybackState.ACTION_SEEK_TO | + PlaybackState.ACTION_SET_RATING; + } + + private class EventCallback extends MediaSession.Callback { + @Override + public void onPlay() { + downloadService.start(); + } + + @Override + public void onStop() { + downloadService.pause(); + } + + @Override + public void onPause() { + downloadService.pause(); + } + + @Override + public void onSeekTo(long position) { + downloadService.seekTo((int) position); + } + + @Override + public void onSetRating(Rating rating) { + if(rating.getRatingStyle() != Rating.RATING_THUMB_UP_DOWN) { + return; + } + + if(rating.isRated()) { + if(rating.isThumbUp()) { + + } else { + + } + } else { + + } + } + } +} -- cgit v1.2.3