From 54bc70931764db16bec39b1e94dbaa03611001e7 Mon Sep 17 00:00:00 2001 From: CappielloAntonio Date: Mon, 12 Apr 2021 10:35:41 +0200 Subject: [PATCH] Fix notification bitmap and progressbar --- app/build.gradle | 2 +- .../play/glide/CustomGlideRequest.java | 16 ++ .../glide/palette/BitmapPaletteWrapper.java | 23 --- .../play/helper/MusicPlayerRemote.java | 4 + .../play/interfaces/MediaCallback.java | 2 +- .../play/service/MultiPlayer.java | 190 +++++------------- .../play/service/MusicService.java | 55 +++-- .../play/service/UnknownMediaSourceFactory.kt | 73 +++++++ .../notification/PlayingNotification.java | 102 ++++++---- .../play/service/playback/Playback.java | 8 +- 10 files changed, 247 insertions(+), 228 deletions(-) delete mode 100644 app/src/main/java/com/cappielloantonio/play/glide/palette/BitmapPaletteWrapper.java create mode 100644 app/src/main/java/com/cappielloantonio/play/service/UnknownMediaSourceFactory.kt diff --git a/app/build.gradle b/app/build.gradle index e28a7f7f..e6dca9a5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -57,7 +57,6 @@ dependencies { implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.paging:paging-runtime-ktx:2.1.2' implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1" - implementation 'androidx.palette:palette-ktx:1.0.0' // Android Material implementation 'com.google.android.material:material:1.3.0' @@ -74,6 +73,7 @@ dependencies { // Glide implementation 'com.github.bumptech.glide:glide:4.11.0' + implementation 'com.github.bumptech.glide:annotations:4.11.0' implementation 'com.github.bumptech.glide:okhttp3-integration:4.11.0' implementation "com.github.woltapp:blurhash:f41a23cc50" diff --git a/app/src/main/java/com/cappielloantonio/play/glide/CustomGlideRequest.java b/app/src/main/java/com/cappielloantonio/play/glide/CustomGlideRequest.java index 7a631df0..76d2591e 100644 --- a/app/src/main/java/com/cappielloantonio/play/glide/CustomGlideRequest.java +++ b/app/src/main/java/com/cappielloantonio/play/glide/CustomGlideRequest.java @@ -55,11 +55,27 @@ public class CustomGlideRequest { return new Builder(context, item, placeholder, itemType, quality); } + public BitmapBuilder bitmap() { + return new BitmapBuilder(this); + } + public RequestBuilder build() { return requestManager.load(item); } } + public static class BitmapBuilder { + private final Builder builder; + + public BitmapBuilder(Builder builder) { + this.builder = builder; + } + + public RequestBuilder build() { + return builder.requestManager.asBitmap().load(builder.item); + } + } + public static RequestOptions createRequestOptions(String item, Drawable placeholder) { RequestOptions options = new RequestOptions() .placeholder(placeholder) diff --git a/app/src/main/java/com/cappielloantonio/play/glide/palette/BitmapPaletteWrapper.java b/app/src/main/java/com/cappielloantonio/play/glide/palette/BitmapPaletteWrapper.java deleted file mode 100644 index e6817671..00000000 --- a/app/src/main/java/com/cappielloantonio/play/glide/palette/BitmapPaletteWrapper.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.cappielloantonio.play.glide.palette; - -import android.graphics.Bitmap; - -import androidx.palette.graphics.Palette; - -public class BitmapPaletteWrapper { - private final Bitmap bitmap; - private final Palette palette; - - public BitmapPaletteWrapper(Bitmap bitmap, Palette palette) { - this.bitmap = bitmap; - this.palette = palette; - } - - public Bitmap getBitmap() { - return bitmap; - } - - public Palette getPalette() { - return palette; - } -} diff --git a/app/src/main/java/com/cappielloantonio/play/helper/MusicPlayerRemote.java b/app/src/main/java/com/cappielloantonio/play/helper/MusicPlayerRemote.java index ff26ec81..f2ab382c 100644 --- a/app/src/main/java/com/cappielloantonio/play/helper/MusicPlayerRemote.java +++ b/app/src/main/java/com/cappielloantonio/play/helper/MusicPlayerRemote.java @@ -138,6 +138,10 @@ public class MusicPlayerRemote { return musicService != null && musicService.isPlaying(); } + public static boolean isLoading() { + return musicService != null && musicService.isLoading(); + } + public static void resumePlaying() { if (musicService != null) { musicService.play(); diff --git a/app/src/main/java/com/cappielloantonio/play/interfaces/MediaCallback.java b/app/src/main/java/com/cappielloantonio/play/interfaces/MediaCallback.java index 0204d2d2..01a4d32c 100644 --- a/app/src/main/java/com/cappielloantonio/play/interfaces/MediaCallback.java +++ b/app/src/main/java/com/cappielloantonio/play/interfaces/MediaCallback.java @@ -6,4 +6,4 @@ public interface MediaCallback { void onError(Exception exception); void onLoadMedia(List media); -} \ No newline at end of file +} diff --git a/app/src/main/java/com/cappielloantonio/play/service/MultiPlayer.java b/app/src/main/java/com/cappielloantonio/play/service/MultiPlayer.java index 68445f86..da26bd06 100644 --- a/app/src/main/java/com/cappielloantonio/play/service/MultiPlayer.java +++ b/app/src/main/java/com/cappielloantonio/play/service/MultiPlayer.java @@ -5,8 +5,6 @@ import android.net.Uri; import android.util.Log; import android.widget.Toast; -import androidx.annotation.NonNull; - import com.cappielloantonio.play.R; import com.cappielloantonio.play.model.Song; import com.cappielloantonio.play.service.playback.Playback; @@ -15,14 +13,10 @@ import com.cappielloantonio.play.util.PreferenceUtil; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.database.ExoDatabaseProvider; -import com.google.android.exoplayer2.source.ConcatenatingMediaSource; -import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.source.ProgressiveMediaSource; -import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.source.hls.HlsMediaSource; -import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.source.MediaSourceFactory; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.FileDataSource; @@ -32,117 +26,62 @@ import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvicto import com.google.android.exoplayer2.upstream.cache.SimpleCache; import java.io.File; -import java.io.IOException; - -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Dispatcher; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; public class MultiPlayer implements Playback { public static final String TAG = MultiPlayer.class.getSimpleName(); private final Context context; - private final OkHttpClient httpClient; - - private SimpleExoPlayer exoPlayer; - private ConcatenatingMediaSource mediaSource; - + private final SimpleExoPlayer exoPlayer; private final SimpleCache simpleCache; - private final DataSource.Factory dataSource; private PlaybackCallbacks callbacks; - private boolean isReady = false; - private boolean isPlaying = false; - - private boolean requestPlay = false; - private int requestProgress = 0; - private final ExoPlayer.EventListener eventListener = new ExoPlayer.EventListener() { @Override - public void onTracksChanged(@NonNull TrackGroupArray trackGroups, @NonNull TrackSelectionArray trackSelections) { - Log.i(TAG, "onTracksChanged"); - } - - @Override - public void onIsLoadingChanged(boolean isLoadingChanged) { - Log.i(TAG, "onIsLoadingChanged: " + isLoadingChanged); - } - - @Override - public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { - Log.i(TAG, "onPlayerStateChanged playWhenReady: " + playWhenReady); - Log.i(TAG, "onPlayerStateChanged playbackState: " + playbackState); - - if (callbacks == null) return; - if (requestProgress != 0 && playbackState == Player.STATE_READY) { - exoPlayer.seekTo(requestProgress); - - requestProgress = 0; - } - - if (exoPlayer.isPlaying() || requestPlay && playbackState == ExoPlayer.STATE_READY) { - requestPlay = false; - isPlaying = true; - - exoPlayer.setPlayWhenReady(true); - callbacks.onTrackStarted(); - } + public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) { + Log.i(TAG, String.format("onPlayWhenReadyChanged: %b %d", playWhenReady, reason)); + if (callbacks != null) callbacks.onReadyChanged(playWhenReady, reason); } @Override public void onPlaybackStateChanged(int state) { - + Log.i(TAG, String.format("onPlaybackStateChanged: %d", state)); + if (callbacks != null) callbacks.onStateChanged(state); + } + + @Override + public void onMediaItemTransition(MediaItem mediaItem, int reason) { + Log.i(TAG, String.format("onMediaItemTransition: %s %d", mediaItem, reason)); + + if (exoPlayer.getMediaItemCount() > 1) { + exoPlayer.removeMediaItem(0); + } + + if (callbacks != null) { + callbacks.onTrackChanged(reason); + } } @Override public void onPositionDiscontinuity(int reason) { - Log.i(TAG, "onPositionDiscontinuity: " + reason); - int windowIndex = exoPlayer.getCurrentWindowIndex(); - - if (windowIndex == 1) { - mediaSource.removeMediaSource(0); - if (exoPlayer.isPlaying()) { - // there are still songs left in the queue - callbacks.onTrackWentToNext(); - } else { - callbacks.onTrackEnded(); - } - } + Log.i(TAG, String.format("onPositionDiscontinuity: %d", reason)); } @Override public void onPlayerError(ExoPlaybackException error) { - Log.i(TAG, "onPlayerError: " + error.getMessage()); - if (context == null) { - return; - } - + Log.i(TAG, String.format("onPlayerError: %s", error.getMessage())); Toast.makeText(context, context.getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show(); - exoPlayer.release(); - - exoPlayer = new SimpleExoPlayer.Builder(context).build(); - isReady = false; } }; public MultiPlayer(Context context) { this.context = context; - Dispatcher dispatcher = new Dispatcher(); - dispatcher.setMaxRequests(1); - - httpClient = new OkHttpClient.Builder().dispatcher(dispatcher).build(); - - exoPlayer = new SimpleExoPlayer.Builder(context).build(); - mediaSource = new ConcatenatingMediaSource(); + MediaSourceFactory mediaSourceFactory = new UnknownMediaSourceFactory(buildDataSourceFactory()); + exoPlayer = new SimpleExoPlayer.Builder(context).setMediaSourceFactory(mediaSourceFactory).build(); exoPlayer.addListener(eventListener); - exoPlayer.prepare(mediaSource); - exoPlayer.setRepeatMode(Player.REPEAT_MODE_OFF); + exoPlayer.prepare(); long cacheSize = PreferenceUtil.getInstance(context).getMediaCacheSize(); LeastRecentlyUsedCacheEvictor recentlyUsedCache = new LeastRecentlyUsedCacheEvictor(cacheSize); @@ -150,63 +89,36 @@ public class MultiPlayer implements Playback { File cacheDirectory = new File(context.getCacheDir(), "exoplayer"); simpleCache = new SimpleCache(cacheDirectory, recentlyUsedCache, databaseProvider); - dataSource = buildDataSourceFactory(); } @Override public void setDataSource(Song song) { - isReady = false; - mediaSource = new ConcatenatingMediaSource(); + String uri = MusicUtil.getSongFileUri(song); + MediaItem mediaItem = exoPlayer.getCurrentMediaItem(); - exoPlayer.addListener(eventListener); - exoPlayer.prepare(mediaSource); - - // queue and other information is currently handled outside exoplayer - exoPlayer.setRepeatMode(Player.REPEAT_MODE_OFF); + if (mediaItem != null && mediaItem.playbackProperties.uri.toString().equals(uri)) { + return; + } + exoPlayer.clearMediaItems(); appendDataSource(MusicUtil.getSongFileUri(song)); + exoPlayer.seekTo(0, 0); } @Override public void queueDataSource(Song song) { - String path = MusicUtil.getSongFileUri(song); - if (mediaSource.getSize() == 2 && mediaSource.getMediaSource(1).getTag() != path) { - mediaSource.removeMediaSource(1); + while (exoPlayer.getMediaItemCount() > 1) { + exoPlayer.removeMediaItem(1); } - if (mediaSource.getSize() != 2) { - appendDataSource(path); - } + appendDataSource(MusicUtil.getSongFileUri(song)); } private void appendDataSource(String path) { Uri uri = Uri.parse(path); + MediaItem mediaItem = MediaItem.fromUri(uri); - httpClient.newCall(new Request.Builder().url(path).head().build()).enqueue(new Callback() { - @Override - public void onFailure(Call call, IOException e) { - Toast.makeText(context, context.getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show(); - e.printStackTrace(); - } - - @Override - public void onResponse(Call call, Response response) throws IOException { - MediaSource source; - if (response.header("Content-Type").equals("application/x-mpegURL")) { - source = new HlsMediaSource.Factory(dataSource) - .setTag(path) - .setAllowChunklessPreparation(true) - .createMediaSource(uri); - } else { - source = new ProgressiveMediaSource.Factory(dataSource) - .setTag(path) - .createMediaSource(uri); - } - - mediaSource.addMediaSource(source); - isReady = true; - } - }); + exoPlayer.addMediaItem(mediaItem); } private DataSource.Factory buildDataSourceFactory() { @@ -227,28 +139,26 @@ public class MultiPlayer implements Playback { @Override public boolean isReady() { - return isReady; + return exoPlayer.getPlayWhenReady(); } @Override public boolean isPlaying() { - return isReady && isPlaying; + return exoPlayer.isPlaying() || exoPlayer.getPlayWhenReady(); + } + + @Override + public boolean isLoading() { + return exoPlayer.getPlaybackState() == Player.STATE_BUFFERING; } @Override public void start() { - if (!isReady) { - requestPlay = true; - return; - } - - isPlaying = true; exoPlayer.setPlayWhenReady(true); } @Override public void pause() { - isPlaying = false; exoPlayer.setPlayWhenReady(false); } @@ -256,30 +166,20 @@ public class MultiPlayer implements Playback { public void stop() { simpleCache.release(); exoPlayer.release(); - - exoPlayer = null; - isReady = false; } @Override public int getProgress() { - if (!isReady) return -1; return (int) exoPlayer.getCurrentPosition(); } @Override public int getDuration() { - if (!isReady) return -1; return (int) exoPlayer.getDuration(); } @Override public void setProgress(int progress) { - if (!isReady) { - requestProgress = progress; - return; - } - exoPlayer.seekTo(progress); } 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 72d21167..692a3a9c 100644 --- a/app/src/main/java/com/cappielloantonio/play/service/MusicService.java +++ b/app/src/main/java/com/cappielloantonio/play/service/MusicService.java @@ -50,8 +50,12 @@ import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_AUTO; +import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED; +import static com.google.android.exoplayer2.Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM; + public class MusicService extends Service implements Playback.PlaybackCallbacks { - public static final String PACKAGE_NAME = "com.dkanada.gramophone"; + public static final String PACKAGE_NAME = "com.antoniocappiello.play"; public static final String ACTION_TOGGLE = PACKAGE_NAME + ".toggle"; public static final String ACTION_PLAY = PACKAGE_NAME + ".play"; @@ -104,6 +108,7 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks private PowerManager.WakeLock wakeLock; private PlaybackHandler playerHandler; + private Handler uiThreadHandler; private ThrottledSeekHandler throttledSeekHandler; private QueueHandler queueHandler; private ProgressHandler progressHandler; @@ -160,6 +165,7 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks queueHandler = new QueueHandler(this, queueHandlerThread.getLooper()); throttledSeekHandler = new ThrottledSeekHandler(playerHandler); + uiThreadHandler = new Handler(); registerReceiver(becomingNoisyReceiver, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); @@ -352,8 +358,8 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks if (restoredPositionInTrack > 0) seek(restoredPositionInTrack); notHandledMetaChangedForCurrentTrack = true; - sendChangeInternal(META_CHANGED); - sendChangeInternal(QUEUE_CHANGED); + handleChangeInternal(META_CHANGED); + handleChangeInternal(QUEUE_CHANGED); } } @@ -386,6 +392,10 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks return playback != null && playback.isPlaying(); } + public boolean isLoading() { + return playback != null && playback.isLoading(); + } + public int getPosition() { return position; } @@ -433,7 +443,6 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks public void initNotification() { playingNotification = new PlayingNotification(); - playingNotification.init(this); } @@ -471,6 +480,12 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, null); metaData.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getPlayingQueue().size()); + + mediaSession.setMetadata(metaData.build()); + } + + public void runOnUiThread(Runnable runnable) { + uiThreadHandler.post(runnable); } public Song getCurrentSong() { @@ -714,23 +729,31 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks } @Override - public void onTrackStarted() { - progressHandler.sendEmptyMessage(TRACK_STARTED); - + public void onStateChanged(int state) { notifyChange(STATE_CHANGED); - prepareNext(); } @Override - public void onTrackWentToNext() { - playerHandler.sendEmptyMessage(TRACK_CHANGED); - progressHandler.sendEmptyMessage(TRACK_CHANGED); + public void onReadyChanged(boolean ready, int reason) { + notifyChange(STATE_CHANGED); + + if (ready) { + progressHandler.sendEmptyMessage(TRACK_STARTED); + prepareNext(); + } else if (reason == PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM) { + progressHandler.sendEmptyMessage(TRACK_ENDED); + } } @Override - public void onTrackEnded() { - playerHandler.sendEmptyMessage(TRACK_ENDED); - progressHandler.sendEmptyMessage(TRACK_ENDED); + public void onTrackChanged(int reason) { + if (reason == MEDIA_ITEM_TRANSITION_REASON_AUTO) { + playerHandler.sendEmptyMessage(TRACK_CHANGED); + progressHandler.sendEmptyMessage(TRACK_CHANGED); + } else if (reason == MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED) { + progressHandler.sendEmptyMessage(TRACK_CHANGED); + prepareNext(); + } } private static final class PlaybackHandler extends Handler { @@ -959,8 +982,8 @@ public class MusicService extends Service implements Playback.PlaybackCallbacks info.setItemId(mService.get().getCurrentSong().getId()); info.setPositionTicks(progress * 10000); - task.cancel(true); - executorService.shutdownNow(); + if (task != null) task.cancel(true); + if (executorService != null) executorService.shutdownNow(); } } } diff --git a/app/src/main/java/com/cappielloantonio/play/service/UnknownMediaSourceFactory.kt b/app/src/main/java/com/cappielloantonio/play/service/UnknownMediaSourceFactory.kt new file mode 100644 index 00000000..976d91da --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/play/service/UnknownMediaSourceFactory.kt @@ -0,0 +1,73 @@ +package com.cappielloantonio.play.service + +import com.google.android.exoplayer2.MediaItem +import com.google.android.exoplayer2.drm.DrmSessionManager +import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory +import com.google.android.exoplayer2.source.MediaSource +import com.google.android.exoplayer2.source.MediaSourceFactory +import com.google.android.exoplayer2.source.ProgressiveMediaSource +import com.google.android.exoplayer2.source.hls.HlsMediaSource +import com.google.android.exoplayer2.upstream.* + +import kotlinx.coroutines.* + +import java.net.HttpURLConnection +import java.net.URL + +class UnknownMediaSourceFactory(dataSourceFactory: DataSource.Factory) : MediaSourceFactory { + private val hlsMediaSource : HlsMediaSource.Factory + private val progressiveMediaSource : ProgressiveMediaSource.Factory + + private var loadErrorHandlingPolicy: LoadErrorHandlingPolicy + + override fun setDrmSessionManager(drmSessionManager: DrmSessionManager?): MediaSourceFactory { + return this + } + + override fun setDrmHttpDataSourceFactory(drmHttpDataSourceFactory: HttpDataSource.Factory?): MediaSourceFactory { + return this + } + + override fun setDrmUserAgent(drmUserAgent: String?): MediaSourceFactory { + return this + } + + override fun setLoadErrorHandlingPolicy(loadErrorHandlingPolicy: LoadErrorHandlingPolicy?): MediaSourceFactory { + this.loadErrorHandlingPolicy = loadErrorHandlingPolicy!! + return this + } + + override fun getSupportedTypes(): IntArray { + return intArrayOf() + } + + override fun createMediaSource(mediaItem: MediaItem): MediaSource { + val type: String? = runBlocking { + httpGet(mediaItem.playbackProperties!!.uri.toString()) + } + + val sourceFactory: MediaSourceFactory = if (type == "application/x-mpegURL") { + hlsMediaSource + } else { + progressiveMediaSource + } + + return sourceFactory.createMediaSource(mediaItem) + } + + private suspend fun httpGet(url: String?): String? { + return withContext(Dispatchers.IO) { + val request = URL(url) + val conn = request.openConnection() as HttpURLConnection + + return@withContext conn.getHeaderField("Content-Type") + } + } + + init { + hlsMediaSource = HlsMediaSource.Factory(dataSourceFactory) + progressiveMediaSource = ProgressiveMediaSource.Factory(dataSourceFactory, DefaultExtractorsFactory()) + + loadErrorHandlingPolicy = DefaultLoadErrorHandlingPolicy() + } +} \ No newline at end of file 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 bd889d30..cca3c94f 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 @@ -3,21 +3,28 @@ package com.cappielloantonio.play.service.notification; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; + +import androidx.annotation.RequiresApi; + import android.app.PendingIntent; import android.content.ComponentName; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Color; -import android.os.Build; +import android.graphics.drawable.Drawable; -import androidx.annotation.RequiresApi; +import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; +import androidx.media.app.NotificationCompat.MediaStyle; import com.cappielloantonio.play.R; +import com.cappielloantonio.play.glide.CustomGlideRequest; import com.cappielloantonio.play.model.Song; -import com.cappielloantonio.play.service.MusicService; import com.cappielloantonio.play.ui.activities.MainActivity; +import com.cappielloantonio.play.service.MusicService; + +import com.bumptech.glide.request.target.CustomTarget; +import com.bumptech.glide.request.transition.Transition; import static android.content.Context.NOTIFICATION_SERVICE; import static com.cappielloantonio.play.service.MusicService.ACTION_REWIND; @@ -41,18 +48,14 @@ public class PlayingNotification { public synchronized void init(MusicService service) { this.service = service; notificationManager = (NotificationManager) service.getSystemService(NOTIFICATION_SERVICE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createNotificationChannel(); - } + createNotificationChannel(); } public synchronized void update() { stopped = false; final Song song = service.getCurrentSong(); - final boolean isPlaying = service.isPlaying(); - final int playButtonResId = isPlaying ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp; Intent action = new Intent(service, MainActivity.class); @@ -64,39 +67,59 @@ public class PlayingNotification { intent.setComponent(serviceName); final PendingIntent deleteIntent = PendingIntent.getService(service, 0, intent, 0); - // 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); + 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) + .bitmap() + .build() + .into(new CustomTarget(bigNotificationImageSize, bigNotificationImageSize) { + @Override + public void onResourceReady(@NonNull Bitmap resource, Transition glideAnimation) { + update(resource); + } - 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 onLoadFailed(Drawable drawable) { + update(null); + } - // notification has been stopped before loading was finished - if (stopped) return; + @Override + public void onLoadCleared(Drawable drawable) { + update(null); + } - updateNotifyModeAndPostNotification(builder.build()); + void update(Bitmap bitmap) { + 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 MediaStyle().setMediaSession(service.getMediaSession().getSessionToken()).setShowActionsInCompactView(0, 1, 2)) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); + + // notification has been stopped before loading was finished + if (stopped) return; + + updateNotifyModeAndPostNotification(builder.build()); + } + })); } public synchronized void stop() { @@ -115,6 +138,7 @@ public class PlayingNotification { void updateNotifyModeAndPostNotification(Notification notification) { int newNotifyMode; + if (service.isPlaying()) { newNotifyMode = NOTIFY_MODE_FOREGROUND; } else { diff --git a/app/src/main/java/com/cappielloantonio/play/service/playback/Playback.java b/app/src/main/java/com/cappielloantonio/play/service/playback/Playback.java index 7cb48bcb..f2c9e403 100644 --- a/app/src/main/java/com/cappielloantonio/play/service/playback/Playback.java +++ b/app/src/main/java/com/cappielloantonio/play/service/playback/Playback.java @@ -13,6 +13,8 @@ public interface Playback { boolean isPlaying(); + boolean isLoading(); + void start(); void pause(); @@ -30,10 +32,10 @@ public interface Playback { int getVolume(); interface PlaybackCallbacks { - void onTrackStarted(); + void onStateChanged(int state); - void onTrackWentToNext(); + void onReadyChanged(boolean ready, int reason); - void onTrackEnded(); + void onTrackChanged(int reason); } }