diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 531efac3..cf58d399 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -23,7 +23,6 @@
-
-
+
\ No newline at end of file
diff --git a/app/src/main/java/com/cappielloantonio/play/adapter/PlayerNowPlayingSongAdapter.java b/app/src/main/java/com/cappielloantonio/play/adapter/PlayerNowPlayingSongAdapter.java
index d3095448..6407cc8c 100644
--- a/app/src/main/java/com/cappielloantonio/play/adapter/PlayerNowPlayingSongAdapter.java
+++ b/app/src/main/java/com/cappielloantonio/play/adapter/PlayerNowPlayingSongAdapter.java
@@ -57,7 +57,7 @@ public class PlayerNowPlayingSongAdapter extends RecyclerView.Adapter mConnectionMap = new WeakHashMap<>();
+
+ public static ServiceToken bindToService(@NonNull final Context context, final ServiceConnection callback) {
+ Activity realActivity = ((Activity) context).getParent();
+ if (realActivity == null) {
+ realActivity = (Activity) context;
+ }
+
+ final ContextWrapper contextWrapper = new ContextWrapper(realActivity);
+ contextWrapper.startService(new Intent(contextWrapper, MusicService.class));
+
+ final ServiceBinder binder = new ServiceBinder(callback);
+
+ if (contextWrapper.bindService(new Intent().setClass(contextWrapper, MusicService.class), binder, Context.BIND_AUTO_CREATE)) {
+ mConnectionMap.put(contextWrapper, binder);
+ return new ServiceToken(contextWrapper);
+ }
+
+ return null;
+ }
+
+ public static void unbindFromService(@Nullable final ServiceToken token) {
+ if (token == null) {
+ return;
+ }
+
+ final ContextWrapper mContextWrapper = token.mWrappedContext;
+ final ServiceBinder mBinder = mConnectionMap.remove(mContextWrapper);
+ if (mBinder == null) {
+ return;
+ }
+
+ mContextWrapper.unbindService(mBinder);
+ if (mConnectionMap.isEmpty()) {
+ musicService = null;
+ }
+ }
+
+ public static final class ServiceBinder implements ServiceConnection {
+ private final ServiceConnection mCallback;
+
+ public ServiceBinder(final ServiceConnection callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void onServiceConnected(final ComponentName className, final IBinder service) {
+ MusicService.MusicBinder binder = (MusicService.MusicBinder) service;
+ musicService = binder.getService();
+ if (mCallback != null) {
+ mCallback.onServiceConnected(className, service);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(final ComponentName className) {
+ if (mCallback != null) {
+ mCallback.onServiceDisconnected(className);
+ }
+
+ musicService = null;
+ }
+ }
+
+ public static final class ServiceToken {
+ public ContextWrapper mWrappedContext;
+
+ public ServiceToken(final ContextWrapper context) {
+ mWrappedContext = context;
+ }
+ }
+
+ public static void playSongAt(final int position) {
+ if (musicService != null) {
+ musicService.playSongAt(position);
+ }
+ }
+
+ public static void setPosition(final int position) {
+ if (musicService != null) {
+ musicService.setPosition(position);
+ }
+ }
+
+ public static void pauseSong() {
+ if (musicService != null) {
+ musicService.pause();
+ }
+ }
+
+ public static void playNextSong() {
+ if (musicService != null) {
+ musicService.playNextSong(true);
+ }
+ }
+
+ public static void playPreviousSong() {
+ if (musicService != null) {
+ musicService.playPreviousSong(true);
+ }
+ }
+
+ public static void back() {
+ if (musicService != null) {
+ musicService.back(true);
+ }
+ }
+
+ public static boolean isPlaying() {
+ return musicService != null && musicService.isPlaying();
+ }
+
+ public static void resumePlaying() {
+ if (musicService != null) {
+ musicService.play();
+ }
+ }
+
+ public static void openQueue(final List queue, final int startPosition, final boolean startPlaying) {
+ if (!tryToHandleOpenPlayingQueue(queue, startPosition, startPlaying) && musicService != null) {
+ musicService.openQueue(queue, startPosition, startPlaying);
+ }
+ }
+
+ public static void openAndShuffleQueue(final List queue, boolean startPlaying) {
+ int startPosition = 0;
+ if (!queue.isEmpty()) {
+ startPosition = new Random().nextInt(queue.size());
+ }
+
+ if (!tryToHandleOpenPlayingQueue(queue, startPosition, startPlaying) && musicService != null) {
+ openQueue(queue, startPosition, startPlaying);
+ }
+ }
+
+ private static boolean tryToHandleOpenPlayingQueue(final List queue, final int startPosition, final boolean startPlaying) {
+ if (getPlayingQueue() == queue) {
+ if (startPlaying) {
+ playSongAt(startPosition);
+ } else {
+ setPosition(startPosition);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public static Song getCurrentSong() {
+ return musicService.getCurrentSong();
+ }
+
+ public static int getPosition() {
+ if (musicService != null) {
+ return musicService.getPosition();
+ }
+
+ return -1;
+ }
+
+ public static List getPlayingQueue() {
+ if (musicService != null) {
+ return musicService.getPlayingQueue();
+ }
+
+ return new ArrayList<>();
+ }
+
+ public static int getSongProgressMillis() {
+ if (musicService != null) {
+ return musicService.getSongProgressMillis();
+ }
+
+ return -1;
+ }
+
+ public static int getSongDurationMillis() {
+ if (musicService != null) {
+ return musicService.getSongDurationMillis();
+ }
+
+ return -1;
+ }
+
+ public static long getQueueDurationMillis(int position) {
+ if (musicService != null) {
+ return musicService.getQueueDurationMillis(position);
+ }
+
+ return -1;
+ }
+
+ public static int seekTo(int millis) {
+ if (musicService != null) {
+ return musicService.seek(millis);
+ }
+
+ return -1;
+ }
+
+ public static int getRepeatMode() {
+ if (musicService != null) {
+ return musicService.getRepeatMode();
+ }
+
+ return MusicService.REPEAT_MODE_NONE;
+ }
+
+ public static boolean cycleRepeatMode() {
+ if (musicService != null) {
+ musicService.cycleRepeatMode();
+ return true;
+ }
+
+ return false;
+ }
+
+ public static boolean playNext(Song song) {
+ if (musicService != null) {
+ if (getPlayingQueue().size() > 0) {
+ musicService.addSong(getPosition() + 1, song);
+ } else {
+ List queue = new ArrayList<>();
+ queue.add(song);
+ openQueue(queue, 0, false);
+ }
+
+ Toast.makeText(musicService, musicService.getResources().getString(R.string.added_title_to_queue), Toast.LENGTH_SHORT).show();
+ return true;
+ }
+
+ return false;
+ }
+
+ public static boolean playNext(@NonNull List songs) {
+ if (musicService != null) {
+ if (getPlayingQueue().size() > 0) {
+ musicService.addSongs(getPosition() + 1, songs);
+ } else {
+ openQueue(songs, 0, false);
+ }
+
+ final String toast = songs.size() == 1 ? musicService.getResources().getString(R.string.added_title_to_queue) : musicService.getResources().getString(R.string.added_x_titles_to_queue, songs.size());
+ Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show();
+ return true;
+ }
+
+ return false;
+ }
+
+ public static boolean enqueue(Song song) {
+ if (musicService != null) {
+ if (getPlayingQueue().size() > 0) {
+ musicService.addSong(song);
+ } else {
+ List queue = new ArrayList<>();
+ queue.add(song);
+ openQueue(queue, 0, false);
+ }
+
+ Toast.makeText(musicService, musicService.getResources().getString(R.string.added_title_to_queue), Toast.LENGTH_SHORT).show();
+ return true;
+ }
+
+ return false;
+ }
+
+ public static boolean enqueue(@NonNull List songs) {
+ if (musicService != null) {
+ if (getPlayingQueue().size() > 0) {
+ musicService.addSongs(songs);
+ } else {
+ openQueue(songs, 0, false);
+ }
+
+ final String toast = songs.size() == 1 ? musicService.getResources().getString(R.string.added_title_to_queue) : musicService.getResources().getString(R.string.added_x_titles_to_queue, songs.size());
+ Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show();
+ return true;
+ }
+
+ return false;
+ }
+
+ public static boolean removeFromQueue(int position) {
+ if (musicService != null && position >= 0 && position < getPlayingQueue().size()) {
+ musicService.removeSong(position);
+ return true;
+ }
+
+ return false;
+ }
+
+ public static boolean moveSong(int from, int to) {
+ if (musicService != null && from >= 0 && to >= 0 && from < getPlayingQueue().size() && to < getPlayingQueue().size()) {
+ musicService.moveSong(from, to);
+ return true;
+ }
+
+ return false;
+ }
+
+ public static boolean clearQueue() {
+ if (musicService != null) {
+ musicService.clearQueue();
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/app/src/main/java/com/cappielloantonio/play/helper/MusicProgressViewUpdateHelper.java b/app/src/main/java/com/cappielloantonio/play/helper/MusicProgressViewUpdateHelper.java
new file mode 100644
index 00000000..1c7dd222
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/play/helper/MusicProgressViewUpdateHelper.java
@@ -0,0 +1,65 @@
+package com.cappielloantonio.play.helper;
+
+import android.os.Handler;
+import android.os.Message;
+
+import androidx.annotation.NonNull;
+
+public class MusicProgressViewUpdateHelper extends Handler {
+ private static final int CMD_REFRESH_PROGRESS_VIEWS = 1;
+
+ private static final int MIN_INTERVAL = 20;
+ private static final int UPDATE_INTERVAL_PLAYING = 1000;
+ private static final int UPDATE_INTERVAL_PAUSED = 500;
+
+ private Callback callback;
+ private int intervalPlaying;
+ private int intervalPaused;
+
+ public void start() {
+ queueNextRefresh(1);
+ }
+
+ public void stop() {
+ removeMessages(CMD_REFRESH_PROGRESS_VIEWS);
+ }
+
+ public MusicProgressViewUpdateHelper(Callback callback) {
+ this.callback = callback;
+ this.intervalPlaying = UPDATE_INTERVAL_PLAYING;
+ this.intervalPaused = UPDATE_INTERVAL_PAUSED;
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ super.handleMessage(msg);
+ if (msg.what == CMD_REFRESH_PROGRESS_VIEWS) {
+ queueNextRefresh(refreshProgressViews());
+ }
+ }
+
+ private int refreshProgressViews() {
+ final int progressMillis = MusicPlayerRemote.getSongProgressMillis();
+ final int totalMillis = MusicPlayerRemote.getSongDurationMillis();
+
+ callback.onUpdateProgressViews(progressMillis, totalMillis);
+
+ if (!MusicPlayerRemote.isPlaying()) {
+ return intervalPaused;
+ }
+
+ final int remainingMillis = intervalPlaying - progressMillis % intervalPlaying;
+
+ return Math.max(MIN_INTERVAL, remainingMillis);
+ }
+
+ private void queueNextRefresh(final long delay) {
+ final Message message = obtainMessage(CMD_REFRESH_PROGRESS_VIEWS);
+ removeMessages(CMD_REFRESH_PROGRESS_VIEWS);
+ sendMessageDelayed(message, delay);
+ }
+
+ public interface Callback {
+ void onUpdateProgressViews(int progress, int total);
+ }
+}
diff --git a/app/src/main/java/com/cappielloantonio/play/interfaces/MusicServiceEventListener.java b/app/src/main/java/com/cappielloantonio/play/interfaces/MusicServiceEventListener.java
new file mode 100644
index 00000000..d750cd14
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/play/interfaces/MusicServiceEventListener.java
@@ -0,0 +1,15 @@
+package com.cappielloantonio.play.interfaces;
+
+public interface MusicServiceEventListener {
+ void onServiceConnected();
+
+ void onServiceDisconnected();
+
+ void onQueueChanged();
+
+ void onPlayMetadataChanged();
+
+ void onPlayStateChanged();
+
+ void onRepeatModeChanged();
+}
diff --git a/app/src/main/java/com/cappielloantonio/play/model/Song.java b/app/src/main/java/com/cappielloantonio/play/model/Song.java
index e28f609c..c190c4f5 100644
--- a/app/src/main/java/com/cappielloantonio/play/model/Song.java
+++ b/app/src/main/java/com/cappielloantonio/play/model/Song.java
@@ -144,6 +144,11 @@ public class Song implements Parcelable {
this.lastPlay = lastPlay;
}
+ @Ignore
+ public Song() {
+ this.id = UUID.randomUUID().toString();
+ }
+
@Ignore
public Song(BaseItemDto itemDto) {
this.id = itemDto.getId();
diff --git a/app/src/main/java/com/cappielloantonio/play/service/MusicService.java b/app/src/main/java/com/cappielloantonio/play/service/MusicService.java
index 34c978e7..fb151a99 100644
--- a/app/src/main/java/com/cappielloantonio/play/service/MusicService.java
+++ b/app/src/main/java/com/cappielloantonio/play/service/MusicService.java
@@ -30,6 +30,7 @@ import com.cappielloantonio.play.App;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.model.Playlist;
import com.cappielloantonio.play.model.Song;
+import com.cappielloantonio.play.repository.QueueRepository;
import com.cappielloantonio.play.service.notification.PlayingNotification;
import com.cappielloantonio.play.service.playback.Playback;
import com.cappielloantonio.play.util.PreferenceUtil;
@@ -63,14 +64,12 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks
public static final String ACTION_PENDING_QUIT = PACKAGE_NAME + ".quit.pending";
public static final String INTENT_EXTRA_PLAYLIST = PACKAGE_NAME + ".extra.playlist";
- public static final String INTENT_EXTRA_SHUFFLE = PACKAGE_NAME + ".extra.shuffle";
public static final String STATE_CHANGED = PACKAGE_NAME + ".state.changed";
public static final String META_CHANGED = PACKAGE_NAME + ".meta.changed";
public static final String QUEUE_CHANGED = PACKAGE_NAME + ".queue.changed";
public static final String REPEAT_MODE_CHANGED = PACKAGE_NAME + ".repeat.changed";
- public static final String SHUFFLE_MODE_CHANGED = PACKAGE_NAME + ".shuffle.changed";
public static final int TRACK_STARTED = 9;
public static final int TRACK_CHANGED = 1;
@@ -84,9 +83,6 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks
public static final int DUCK = 7;
public static final int UNDUCK = 8;
- public static final int SHUFFLE_MODE_NONE = 0;
- public static final int SHUFFLE_MODE_SHUFFLE = 1;
-
public static final int REPEAT_MODE_NONE = 0;
public static final int REPEAT_MODE_ALL = 1;
public static final int REPEAT_MODE_THIS = 2;
@@ -101,7 +97,6 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks
private Playback playback;
private List playingQueue = new ArrayList<>();
- private List originalPlayingQueue = new ArrayList<>();
private int position = -1;
private int nextPosition = -1;
@@ -336,12 +331,8 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks
}
private void saveQueue() {
- App.getDatabase().songDao().deleteSongs();
- App.getDatabase().songDao().insertSongs(playingQueue);
-
- App.getDatabase().queueSongDao().deleteQueueSongs();
- App.getDatabase().queueSongDao().setQueue(playingQueue, 0);
- App.getDatabase().queueSongDao().setQueue(originalPlayingQueue, 1);
+ QueueRepository queueRepository = new QueueRepository(App.getInstance());
+ queueRepository.insertAllAndStartNew(playingQueue);
}
private void savePosition() {
@@ -371,14 +362,14 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks
private synchronized void restoreQueuesAndPositionIfNecessary() {
if (!queuesRestored && playingQueue.isEmpty()) {
- List restoredQueue = App.getDatabase().queueSongDao().getQueue(0);
- List restoredOriginalQueue = App.getDatabase().queueSongDao().getQueue(1);
+ QueueRepository queueRepository = new QueueRepository(App.getInstance());
+
+ List restoredQueue = queueRepository.getSongs();
int restoredPosition = PreferenceManager.getDefaultSharedPreferences(this).getInt(PreferenceUtil.POSITION, -1);
int restoredPositionInTrack = PreferenceManager.getDefaultSharedPreferences(this).getInt(PreferenceUtil.PROGRESS, -1);
- if (restoredQueue.size() > 0 && restoredQueue.size() == restoredOriginalQueue.size() && restoredPosition != -1) {
- this.originalPlayingQueue = restoredOriginalQueue;
+ if (restoredQueue.size() > 0 && restoredPosition != -1) {
this.playingQueue = restoredQueue;
position = restoredPosition;
@@ -517,7 +508,11 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks
}
public Song getSongAt(int position) {
- return getPlayingQueue().get(position);
+ if (position >= 0 && position < getPlayingQueue().size()) {
+ return getPlayingQueue().get(position);
+ } else {
+ return new Song();
+ }
}
public int getNextPosition(boolean force) {
@@ -577,10 +572,6 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks
public void openQueue(@Nullable final List playingQueue, final int startPosition, final boolean startPlaying) {
if (playingQueue != null && !playingQueue.isEmpty() && startPosition >= 0 && startPosition < playingQueue.size()) {
- // it is important to copy the playing queue here first as we might add or remove songs later
- originalPlayingQueue = new ArrayList<>(playingQueue);
- this.playingQueue = new ArrayList<>(originalPlayingQueue);
-
if (startPlaying) {
playSongAt(startPosition);
} else {
@@ -593,30 +584,26 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks
public void addSong(int position, Song song) {
playingQueue.add(position, song);
- originalPlayingQueue.add(position, song);
notifyChange(QUEUE_CHANGED);
}
public void addSong(Song song) {
playingQueue.add(song);
- originalPlayingQueue.add(song);
notifyChange(QUEUE_CHANGED);
}
public void addSongs(int position, List songs) {
playingQueue.addAll(position, songs);
- originalPlayingQueue.addAll(position, songs);
notifyChange(QUEUE_CHANGED);
}
public void addSongs(List songs) {
playingQueue.addAll(songs);
- originalPlayingQueue.addAll(songs);
notifyChange(QUEUE_CHANGED);
}
public void removeSong(int position) {
- originalPlayingQueue.remove(playingQueue.remove(position));
+ playingQueue.remove(position);
reposition(position);
notifyChange(QUEUE_CHANGED);
}
@@ -653,7 +640,6 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks
public void clearQueue() {
playingQueue.clear();
- originalPlayingQueue.clear();
setPosition(-1);
notifyChange(QUEUE_CHANGED);
@@ -1059,8 +1045,8 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks
startInfo.setCanSeek(true);
startInfo.setIsPaused(false);
- App.getInstance().getApiClientInstance(App.getInstance()).ensureWebSocket();
- App.getInstance().getApiClientInstance(App.getInstance()).ReportPlaybackStartAsync(startInfo, new EmptyResponse());
+ App.getApiClientInstance(App.getInstance()).ensureWebSocket();
+ App.getApiClientInstance(App.getInstance()).ReportPlaybackStartAsync(startInfo, new EmptyResponse());
}
public void onProgress() {
@@ -1071,10 +1057,10 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks
double duration = mService.get().getSongDurationMillis();
if (progress / duration > 0.9) {
Song current = mService.get().getCurrentSong();
- String user = App.getInstance().getApiClientInstance(App.getInstance()).getCurrentUserId();
+ String user = App.getApiClientInstance(App.getInstance()).getCurrentUserId();
Date time = new Date(System.currentTimeMillis());
- App.getInstance().getApiClientInstance(App.getInstance()).MarkPlayedAsync(current.getId(), user, time, new Response<>());
+ App.getApiClientInstance(App.getInstance()).MarkPlayedAsync(current.getId(), user, time, new Response<>());
}
progressInfo.setItemId(mService.get().getCurrentSong().getId());
@@ -1083,7 +1069,7 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks
progressInfo.setIsPaused(!mService.get().playback.isPlaying());
progressInfo.setCanSeek(true);
- App.getInstance().getApiClientInstance(App.getInstance()).ReportPlaybackProgressAsync(progressInfo, new EmptyResponse());
+ App.getApiClientInstance(App.getInstance()).ReportPlaybackProgressAsync(progressInfo, new EmptyResponse());
}
public void onStop() {
diff --git a/app/src/main/java/com/cappielloantonio/play/service/notification/PlayingNotification.java b/app/src/main/java/com/cappielloantonio/play/service/notification/PlayingNotification.java
index d98fa520..a5342566 100644
--- a/app/src/main/java/com/cappielloantonio/play/service/notification/PlayingNotification.java
+++ b/app/src/main/java/com/cappielloantonio/play/service/notification/PlayingNotification.java
@@ -9,19 +9,12 @@ import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
-import android.graphics.drawable.Drawable;
import android.os.Build;
-import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
-import androidx.palette.graphics.Palette;
-import com.bumptech.glide.request.target.CustomTarget;
-import com.bumptech.glide.request.transition.Transition;
import com.cappielloantonio.play.R;
-import com.cappielloantonio.play.glide.CustomGlideRequest;
-import com.cappielloantonio.play.glide.palette.BitmapPaletteWrapper;
import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.service.MusicService;
import com.cappielloantonio.play.ui.activities.MainActivity;
@@ -71,64 +64,39 @@ public class PlayingNotification {
intent.setComponent(serviceName);
final PendingIntent deleteIntent = PendingIntent.getService(service, 0, intent, 0);
- final int bigNotificationImageSize = service.getResources().getDimensionPixelSize(R.dimen.notification_big_image_size);
- service.runOnUiThread(() -> CustomGlideRequest.Builder
- .from(service, song.getPrimary(), song.getBlurHash(), CustomGlideRequest.PRIMARY, CustomGlideRequest.TOP_QUALITY)
- .build()
- .into(new CustomTarget(bigNotificationImageSize, bigNotificationImageSize) {
- @Override
- public void onResourceReady(@NonNull BitmapPaletteWrapper resource, Transition super BitmapPaletteWrapper> glideAnimation) {
- Palette palette = resource.getPalette();
- update(resource.getBitmap(), palette.getVibrantColor(palette.getMutedColor(Color.TRANSPARENT)));
- }
+ Bitmap bitmap = BitmapFactory.decodeResource(service.getResources(), R.drawable.default_album_art);
+ NotificationCompat.Action playPauseAction = new NotificationCompat.Action(playButtonResId,
+ service.getString(R.string.action_play_pause),
+ retrievePlaybackAction(ACTION_TOGGLE));
+ NotificationCompat.Action previousAction = new NotificationCompat.Action(R.drawable.ic_skip_previous_white_24dp,
+ service.getString(R.string.action_previous),
+ retrievePlaybackAction(ACTION_REWIND));
+ NotificationCompat.Action nextAction = new NotificationCompat.Action(R.drawable.ic_skip_next_white_24dp,
+ service.getString(R.string.action_next),
+ retrievePlaybackAction(ACTION_SKIP));
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(service, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_notification)
+ .setSubText(song.getAlbumName())
+ .setLargeIcon(bitmap)
+ .setContentIntent(clickIntent)
+ .setDeleteIntent(deleteIntent)
+ .setContentTitle(song.getTitle())
+ .setContentText(song.getArtistName())
+ .setOngoing(isPlaying)
+ .setShowWhen(false)
+ .addAction(previousAction)
+ .addAction(playPauseAction)
+ .addAction(nextAction);
- @Override
- public void onLoadFailed(Drawable drawable) {
- update(null, Color.TRANSPARENT);
- }
+ builder.setStyle(new androidx.media.app.NotificationCompat.MediaStyle().setMediaSession(service.getMediaSession().getSessionToken())
+ .setShowActionsInCompactView(0, 1, 2))
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .setColor(Color.TRANSPARENT);
- @Override
- public void onLoadCleared(Drawable drawable) {
- update(null, Color.TRANSPARENT);
- }
+ // notification has been stopped before loading was finished
+ if (stopped) return;
- void update(Bitmap bitmap, int color) {
- if (bitmap == null)
- bitmap = BitmapFactory.decodeResource(service.getResources(), R.drawable.default_album_art);
- NotificationCompat.Action playPauseAction = new NotificationCompat.Action(playButtonResId,
- service.getString(R.string.action_play_pause),
- retrievePlaybackAction(ACTION_TOGGLE));
- NotificationCompat.Action previousAction = new NotificationCompat.Action(R.drawable.ic_skip_previous_white_24dp,
- service.getString(R.string.action_previous),
- retrievePlaybackAction(ACTION_REWIND));
- NotificationCompat.Action nextAction = new NotificationCompat.Action(R.drawable.ic_skip_next_white_24dp,
- service.getString(R.string.action_next),
- retrievePlaybackAction(ACTION_SKIP));
- NotificationCompat.Builder builder = new NotificationCompat.Builder(service, NOTIFICATION_CHANNEL_ID)
- .setSmallIcon(R.drawable.ic_notification)
- .setSubText(song.getAlbumName())
- .setLargeIcon(bitmap)
- .setContentIntent(clickIntent)
- .setDeleteIntent(deleteIntent)
- .setContentTitle(song.getTitle())
- .setContentText(song.getArtistName())
- .setOngoing(isPlaying)
- .setShowWhen(false)
- .addAction(previousAction)
- .addAction(playPauseAction)
- .addAction(nextAction);
-
- builder.setStyle(new androidx.media.app.NotificationCompat.MediaStyle().setMediaSession(service.getMediaSession().getSessionToken())
- .setShowActionsInCompactView(0, 1, 2))
- .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
- .setColor(color);
-
- // notification has been stopped before loading was finished
- if (stopped) return;
-
- updateNotifyModeAndPostNotification(builder.build());
- }
- }));
+ updateNotifyModeAndPostNotification(builder.build());
}
public synchronized void stop() {
diff --git a/app/src/main/java/com/cappielloantonio/play/ui/activities/base/BaseActivity.java b/app/src/main/java/com/cappielloantonio/play/ui/activities/base/BaseActivity.java
index b4868a95..8a860a89 100644
--- a/app/src/main/java/com/cappielloantonio/play/ui/activities/base/BaseActivity.java
+++ b/app/src/main/java/com/cappielloantonio/play/ui/activities/base/BaseActivity.java
@@ -1,29 +1,80 @@
package com.cappielloantonio.play.ui.activities.base;
import android.Manifest;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
import android.os.PowerManager;
import android.provider.Settings;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import com.cappielloantonio.play.R;
+import com.cappielloantonio.play.helper.MusicPlayerRemote;
+import com.cappielloantonio.play.interfaces.MusicServiceEventListener;
+import com.cappielloantonio.play.service.MusicService;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
import java.util.List;
import pub.devrel.easypermissions.AppSettingsDialog;
import pub.devrel.easypermissions.EasyPermissions;
-public class BaseActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks {
+public class BaseActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks, MusicServiceEventListener {
+ private static final String TAG = "BaseActivity";
public static final int REQUEST_PERM_ACCESS = 1;
+ private final List mMusicServiceEventListeners = new ArrayList<>();
+
+ private MusicPlayerRemote.ServiceToken serviceToken;
+ private MusicStateReceiver musicStateReceiver;
+
+ private boolean receiverRegistered;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.d(TAG, "onCreate");
+
+ serviceToken = MusicPlayerRemote.bindToService(this, new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.d(TAG, "onServiceConnected");
+ BaseActivity.this.onServiceConnected();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.d(TAG, "onServiceDisconnected");
+ BaseActivity.this.onServiceDisconnected();
+ }
+ });
+ }
+
@Override
protected void onResume() {
super.onResume();
checkPermissions();
-// checkBatteryOptimization();
+ checkBatteryOptimization();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ MusicPlayerRemote.unbindFromService(serviceToken);
+ if (receiverRegistered) {
+ unregisterReceiver(musicStateReceiver);
+ receiverRegistered = false;
+ }
}
private void checkBatteryOptimization() {
@@ -80,4 +131,120 @@ public class BaseActivity extends AppCompatActivity implements EasyPermissions.P
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
+
+ public void addMusicServiceEventListener(final MusicServiceEventListener listener) {
+ if (listener != null) {
+ mMusicServiceEventListeners.add(listener);
+ }
+ }
+
+ public void removeMusicServiceEventListener(final MusicServiceEventListener listener) {
+ if (listener != null) {
+ mMusicServiceEventListeners.remove(listener);
+ }
+ }
+
+ @Override
+ public void onServiceConnected() {
+ if (!receiverRegistered) {
+ musicStateReceiver = new MusicStateReceiver(this);
+
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(MusicService.STATE_CHANGED);
+ filter.addAction(MusicService.REPEAT_MODE_CHANGED);
+ filter.addAction(MusicService.META_CHANGED);
+ filter.addAction(MusicService.QUEUE_CHANGED);
+
+ registerReceiver(musicStateReceiver, filter);
+
+ receiverRegistered = true;
+ }
+
+ for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
+ if (listener != null) {
+ listener.onServiceConnected();
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected() {
+ if (receiverRegistered) {
+ unregisterReceiver(musicStateReceiver);
+ receiverRegistered = false;
+ }
+
+ for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
+ if (listener != null) {
+ listener.onServiceDisconnected();
+ }
+ }
+ }
+
+ @Override
+ public void onQueueChanged() {
+ for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
+ if (listener != null) {
+ listener.onQueueChanged();
+ }
+ }
+ }
+
+ @Override
+ public void onPlayMetadataChanged() {
+ for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
+ if (listener != null) {
+ listener.onPlayMetadataChanged();
+ }
+ }
+ }
+
+ @Override
+ public void onPlayStateChanged() {
+ for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
+ if (listener != null) {
+ listener.onPlayStateChanged();
+ }
+ }
+ }
+
+ @Override
+ public void onRepeatModeChanged() {
+ for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
+ if (listener != null) {
+ listener.onRepeatModeChanged();
+ }
+ }
+ }
+
+ private static final class MusicStateReceiver extends BroadcastReceiver {
+
+ private final WeakReference reference;
+
+ public MusicStateReceiver(final BaseActivity activity) {
+ reference = new WeakReference<>(activity);
+ }
+
+ @Override
+ public void onReceive(final Context context, @NonNull final Intent intent) {
+ final String action = intent.getAction();
+ BaseActivity activity = reference.get();
+ if (activity != null && action != null) {
+ switch (action) {
+ case MusicService.META_CHANGED:
+ activity.onPlayMetadataChanged();
+ break;
+ case MusicService.QUEUE_CHANGED:
+ activity.onQueueChanged();
+ break;
+ case MusicService.STATE_CHANGED:
+ activity.onPlayStateChanged();
+ break;
+ case MusicService.REPEAT_MODE_CHANGED:
+ activity.onRepeatModeChanged();
+ break;
+ }
+ }
+ }
+ }
}
diff --git a/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerBottomSheetFragment.java b/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerBottomSheetFragment.java
index 2e958ff7..f9aa9303 100644
--- a/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerBottomSheetFragment.java
+++ b/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerBottomSheetFragment.java
@@ -5,6 +5,8 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ScrollView;
+import android.widget.SeekBar;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -17,11 +19,14 @@ import com.cappielloantonio.play.R;
import com.cappielloantonio.play.adapter.PlayerNowPlayingSongAdapter;
import com.cappielloantonio.play.adapter.PlayerSongQueueAdapter;
import com.cappielloantonio.play.databinding.FragmentPlayerBottomSheetBinding;
+import com.cappielloantonio.play.helper.MusicPlayerRemote;
+import com.cappielloantonio.play.helper.MusicProgressViewUpdateHelper;
+import com.cappielloantonio.play.interfaces.MusicServiceEventListener;
import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.ui.activities.MainActivity;
import com.cappielloantonio.play.viewmodel.PlayerBottomSheetViewModel;
-public class PlayerBottomSheetFragment extends Fragment {
+public class PlayerBottomSheetFragment extends Fragment implements MusicServiceEventListener, MusicProgressViewUpdateHelper.Callback {
private static final String TAG = "PlayerBottomSheetFragment";
private FragmentPlayerBottomSheetBinding bind;
@@ -31,7 +36,14 @@ public class PlayerBottomSheetFragment extends Fragment {
private PlayerNowPlayingSongAdapter playerNowPlayingSongAdapter;
private PlayerSongQueueAdapter playerSongQueueAdapter;
- private boolean isNowPlaying = false;
+ private MusicProgressViewUpdateHelper progressViewUpdateHelper;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this);
+ }
@Nullable
@Override
@@ -49,6 +61,35 @@ public class PlayerBottomSheetFragment extends Fragment {
return view;
}
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ activity.addMusicServiceEventListener(this);
+ setUpMusicControllers();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ progressViewUpdateHelper.start();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ progressViewUpdateHelper.stop();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ activity.removeMusicServiceEventListener(this);
+ }
+
private void initQueueSlideView() {
bind.playerBodyLayout.playerSongCoverViewPager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
@@ -81,6 +122,28 @@ public class PlayerBottomSheetFragment extends Fragment {
bind.playerBodyLayout.buttonFavorite.setOnClickListener(v -> playerBottomSheetViewModel.setFavorite());
}
+ private void initSeekBar() {
+ bind.playerBodyLayout.playerBigSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (fromUser) {
+ MusicPlayerRemote.seekTo(progress);
+ onUpdateProgressViews(MusicPlayerRemote.getSongProgressMillis(), MusicPlayerRemote.getSongDurationMillis());
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+
+ }
+ });
+ }
+
private void setSongInfo(Song song) {
playerBottomSheetViewModel.setNowPlayingSong(song);
@@ -95,6 +158,26 @@ public class PlayerBottomSheetFragment extends Fragment {
playSong(song);
}
+ private void setUpMusicControllers() {
+ setUpPlayPauseButton();
+// setUpPrevNext();
+// setUpRepeatButton();
+// setUpShuffleButton();
+ initSeekBar();
+ }
+
+ private void setUpPlayPauseButton() {
+ bind.playerBodyLayout.playPauseButton.setOnClickListener(v -> {
+ if (MusicPlayerRemote.isPlaying()) {
+ MusicPlayerRemote.pauseSong();
+ Toast.makeText(requireContext(), "PAUSING", Toast.LENGTH_SHORT).show();
+ } else {
+ MusicPlayerRemote.resumePlaying();
+ Toast.makeText(requireContext(), "PLAYING", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
private void playSong(Song song) {
// Toast.makeText(activity, MusicUtil.getSongFileUri(song), Toast.LENGTH_SHORT).show();
}
@@ -111,4 +194,40 @@ public class PlayerBottomSheetFragment extends Fragment {
bind.playerBodyLayout.playerSongCoverViewPager.setCurrentItem(page, smoothScroll);
setSongInfo(song);
}
+
+ @Override
+ public void onServiceConnected() {
+
+ }
+
+ @Override
+ public void onServiceDisconnected() {
+
+ }
+
+ @Override
+ public void onQueueChanged() {
+
+ }
+
+ @Override
+ public void onPlayMetadataChanged() {
+
+ }
+
+ @Override
+ public void onPlayStateChanged() {
+
+ }
+
+ @Override
+ public void onRepeatModeChanged() {
+
+ }
+
+ @Override
+ public void onUpdateProgressViews(int progress, int total) {
+ bind.playerBodyLayout.playerBigSeekBar.setMax(total);
+ bind.playerBodyLayout.playerBigSeekBar.setProgress(progress);
+ }
}
diff --git a/app/src/main/res/layout/item_player_now_playing_song.xml b/app/src/main/res/layout/item_player_now_playing_song.xml
index 54d28e6d..4cb60b3c 100644
--- a/app/src/main/res/layout/item_player_now_playing_song.xml
+++ b/app/src/main/res/layout/item_player_now_playing_song.xml
@@ -10,9 +10,8 @@
app:cardCornerRadius="4dp">
+ android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/app/src/main/res/layout/player_body_bottom_sheet.xml b/app/src/main/res/layout/player_body_bottom_sheet.xml
index b66e2c7f..b5b6cf9e 100644
--- a/app/src/main/res/layout/player_body_bottom_sheet.xml
+++ b/app/src/main/res/layout/player_body_bottom_sheet.xml
@@ -22,6 +22,21 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
+
+
-
@@ -51,7 +64,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/player_big_progress_bar"
+ app:layout_constraintTop_toBottomOf="@+id/player_big_seek_bar"
app:layout_constraintEnd_toEndOf="parent"
android:orientation="horizontal">
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6631ddde..46181001 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -35,6 +35,8 @@
Couldn\'t play this song.
Playlist is empty
Audio focus denied.
+ "Added 1 title to the queue."
+ Added %1$d titles to the queue.
Play next
Play