2020-06-21 09:31:38 +02:00
|
|
|
package org.moire.ultrasonic.service;
|
|
|
|
|
|
|
|
import android.app.Notification;
|
|
|
|
import android.app.NotificationChannel;
|
|
|
|
import android.app.NotificationManager;
|
|
|
|
import android.app.PendingIntent;
|
|
|
|
import android.app.Service;
|
|
|
|
import android.content.Context;
|
|
|
|
import android.content.Intent;
|
|
|
|
import android.graphics.Bitmap;
|
|
|
|
import android.os.Build;
|
|
|
|
import android.os.IBinder;
|
2021-04-12 07:27:55 +02:00
|
|
|
import android.support.v4.media.MediaMetadataCompat;
|
|
|
|
import android.support.v4.media.session.MediaSessionCompat;
|
|
|
|
import android.support.v4.media.session.PlaybackStateCompat;
|
|
|
|
import android.view.KeyEvent;
|
2020-06-21 09:31:38 +02:00
|
|
|
|
|
|
|
import androidx.annotation.Nullable;
|
|
|
|
import androidx.core.app.NotificationCompat;
|
|
|
|
import androidx.core.app.NotificationManagerCompat;
|
|
|
|
|
|
|
|
import org.moire.ultrasonic.R;
|
2021-02-05 21:45:50 +01:00
|
|
|
import org.moire.ultrasonic.activity.NavigationActivity;
|
2020-06-21 09:31:38 +02:00
|
|
|
import org.moire.ultrasonic.domain.MusicDirectory;
|
|
|
|
import org.moire.ultrasonic.domain.PlayerState;
|
|
|
|
import org.moire.ultrasonic.domain.RepeatMode;
|
2020-09-24 13:03:05 +02:00
|
|
|
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X1;
|
|
|
|
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X2;
|
|
|
|
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X3;
|
|
|
|
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X4;
|
2021-02-11 21:51:27 +01:00
|
|
|
import org.moire.ultrasonic.util.Constants;
|
2020-06-21 09:31:38 +02:00
|
|
|
import org.moire.ultrasonic.util.FileUtil;
|
2021-02-08 20:24:20 +01:00
|
|
|
import org.moire.ultrasonic.util.NowPlayingEventDistributor;
|
2020-06-21 09:31:38 +02:00
|
|
|
import org.moire.ultrasonic.util.ShufflePlayBuffer;
|
|
|
|
import org.moire.ultrasonic.util.SimpleServiceBinder;
|
|
|
|
import org.moire.ultrasonic.util.Util;
|
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
import java.util.ArrayList;
|
|
|
|
|
2020-06-22 18:35:58 +02:00
|
|
|
import kotlin.Lazy;
|
2021-04-12 07:27:55 +02:00
|
|
|
import timber.log.Timber;
|
2020-06-22 18:35:58 +02:00
|
|
|
|
2020-09-18 09:37:19 +02:00
|
|
|
import static org.koin.java.KoinJavaComponent.inject;
|
2020-06-21 09:31:38 +02:00
|
|
|
import static org.moire.ultrasonic.domain.PlayerState.COMPLETED;
|
|
|
|
import static org.moire.ultrasonic.domain.PlayerState.DOWNLOADING;
|
|
|
|
import static org.moire.ultrasonic.domain.PlayerState.IDLE;
|
|
|
|
import static org.moire.ultrasonic.domain.PlayerState.PAUSED;
|
|
|
|
import static org.moire.ultrasonic.domain.PlayerState.PREPARING;
|
|
|
|
import static org.moire.ultrasonic.domain.PlayerState.STARTED;
|
|
|
|
import static org.moire.ultrasonic.domain.PlayerState.STOPPED;
|
|
|
|
|
2020-06-26 15:18:14 +02:00
|
|
|
/**
|
|
|
|
* Android Foreground Service for playing music
|
|
|
|
* while the rest of the Ultrasonic App is in the background.
|
|
|
|
*/
|
2020-06-21 09:31:38 +02:00
|
|
|
public class MediaPlayerService extends Service
|
|
|
|
{
|
|
|
|
private static final String NOTIFICATION_CHANNEL_ID = "org.moire.ultrasonic";
|
|
|
|
private static final String NOTIFICATION_CHANNEL_NAME = "Ultrasonic background service";
|
|
|
|
private static final int NOTIFICATION_ID = 3033;
|
|
|
|
|
|
|
|
private static MediaPlayerService instance = null;
|
2020-06-26 13:31:31 +02:00
|
|
|
private static final Object instanceLock = new Object();
|
2020-06-21 09:31:38 +02:00
|
|
|
|
|
|
|
private final IBinder binder = new SimpleServiceBinder<>(this);
|
|
|
|
private final Scrobbler scrobbler = new Scrobbler();
|
|
|
|
|
2020-06-25 14:33:44 +02:00
|
|
|
public Lazy<JukeboxMediaPlayer> jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class);
|
2021-02-08 20:24:20 +01:00
|
|
|
private final Lazy<DownloadQueueSerializer> downloadQueueSerializerLazy = inject(DownloadQueueSerializer.class);
|
|
|
|
private final Lazy<ShufflePlayBuffer> shufflePlayBufferLazy = inject(ShufflePlayBuffer.class);
|
|
|
|
private final Lazy<Downloader> downloaderLazy = inject(Downloader.class);
|
|
|
|
private final Lazy<LocalMediaPlayer> localMediaPlayerLazy = inject(LocalMediaPlayer.class);
|
|
|
|
private final Lazy<NowPlayingEventDistributor> nowPlayingEventDistributor = inject(NowPlayingEventDistributor.class);
|
2021-04-12 07:27:55 +02:00
|
|
|
|
2020-06-25 14:33:44 +02:00
|
|
|
private LocalMediaPlayer localMediaPlayer;
|
2020-06-25 11:58:09 +02:00
|
|
|
private Downloader downloader;
|
|
|
|
private ShufflePlayBuffer shufflePlayBuffer;
|
2020-06-26 15:18:14 +02:00
|
|
|
private DownloadQueueSerializer downloadQueueSerializer;
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
private MediaSessionCompat mediaSession;
|
|
|
|
private MediaSessionCompat.Token mediaSessionToken;
|
|
|
|
|
2020-06-21 09:31:38 +02:00
|
|
|
private boolean isInForeground = false;
|
|
|
|
private NotificationCompat.Builder notificationBuilder;
|
|
|
|
|
|
|
|
public RepeatMode getRepeatMode() { return Util.getRepeatMode(this); }
|
|
|
|
|
|
|
|
public static MediaPlayerService getInstance(Context context)
|
|
|
|
{
|
2020-06-26 13:31:31 +02:00
|
|
|
synchronized (instanceLock) {
|
2020-07-02 17:20:06 +02:00
|
|
|
for (int i = 0; i < 20; i++) {
|
2020-06-26 13:31:31 +02:00
|
|
|
if (instance != null) return instance;
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
|
|
context.startForegroundService(new Intent(context, MediaPlayerService.class));
|
|
|
|
} else {
|
|
|
|
context.startService(new Intent(context, MediaPlayerService.class));
|
|
|
|
}
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2020-06-26 13:31:31 +02:00
|
|
|
Util.sleepQuietly(50L);
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2020-06-26 13:31:31 +02:00
|
|
|
return instance;
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static MediaPlayerService getRunningInstance()
|
|
|
|
{
|
2020-06-26 13:31:31 +02:00
|
|
|
synchronized (instanceLock)
|
|
|
|
{
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void executeOnStartedMediaPlayerService(final Context context, final Consumer<MediaPlayerService> taskToExecute)
|
|
|
|
{
|
|
|
|
Thread t = new Thread()
|
|
|
|
{
|
|
|
|
public void run()
|
|
|
|
{
|
|
|
|
MediaPlayerService instance = getInstance(context);
|
2020-07-02 17:20:06 +02:00
|
|
|
if (instance == null)
|
|
|
|
{
|
2020-09-30 14:47:59 +02:00
|
|
|
Timber.e("ExecuteOnStartedMediaPlayerService failed to get a MediaPlayerService instance!");
|
2020-07-02 17:20:06 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-06-26 13:31:31 +02:00
|
|
|
taskToExecute.accept(instance);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
t.start();
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Nullable
|
|
|
|
@Override
|
|
|
|
public IBinder onBind(Intent intent)
|
|
|
|
{
|
|
|
|
return binder;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onCreate()
|
|
|
|
{
|
|
|
|
super.onCreate();
|
|
|
|
|
2020-06-25 11:58:09 +02:00
|
|
|
downloader = downloaderLazy.getValue();
|
2020-06-25 14:33:44 +02:00
|
|
|
localMediaPlayer = localMediaPlayerLazy.getValue();
|
2020-06-25 11:58:09 +02:00
|
|
|
shufflePlayBuffer = shufflePlayBufferLazy.getValue();
|
2020-06-26 15:18:14 +02:00
|
|
|
downloadQueueSerializer = downloadQueueSerializerLazy.getValue();
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
initMediaSessions();
|
|
|
|
|
|
|
|
|
2020-06-25 11:58:09 +02:00
|
|
|
downloader.onCreate();
|
|
|
|
shufflePlayBuffer.onCreate();
|
|
|
|
|
2021-03-30 14:38:59 +02:00
|
|
|
localMediaPlayer.init();
|
2020-06-23 18:40:44 +02:00
|
|
|
setupOnCurrentPlayingChangedHandler();
|
|
|
|
setupOnPlayerStateChangedHandler();
|
|
|
|
setupOnSongCompletedHandler();
|
2021-04-12 07:27:55 +02:00
|
|
|
|
2020-06-25 14:33:44 +02:00
|
|
|
localMediaPlayer.onPrepared = new Runnable() {
|
2020-06-22 18:35:58 +02:00
|
|
|
@Override
|
2020-06-23 18:40:44 +02:00
|
|
|
public void run() {
|
2020-06-26 15:18:14 +02:00
|
|
|
downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList,
|
2020-06-25 11:58:09 +02:00
|
|
|
downloader.getCurrentPlayingIndex(), getPlayerPosition());
|
2020-06-22 18:35:58 +02:00
|
|
|
}
|
|
|
|
};
|
2020-06-25 14:33:44 +02:00
|
|
|
localMediaPlayer.onNextSongRequested = new Runnable() {
|
2020-06-23 18:40:44 +02:00
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
setNextPlaying();
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
};
|
2020-06-21 09:31:38 +02:00
|
|
|
|
|
|
|
// Create Notification Channel
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
|
|
//The suggested importance of a startForeground service notification is IMPORTANCE_LOW
|
|
|
|
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
|
|
|
|
channel.setLightColor(android.R.color.holo_blue_dark);
|
|
|
|
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
|
|
|
|
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
|
|
|
manager.createNotificationChannel(channel);
|
|
|
|
}
|
|
|
|
|
2020-06-22 18:35:58 +02:00
|
|
|
// Update notification early. It is better to show an empty one temporarily than waiting too long and letting Android kill the app
|
2020-06-23 18:40:44 +02:00
|
|
|
updateNotification(IDLE, null);
|
2021-04-12 07:27:55 +02:00
|
|
|
|
2020-06-21 09:31:38 +02:00
|
|
|
instance = this;
|
|
|
|
|
2020-09-30 14:47:59 +02:00
|
|
|
Timber.i("MediaPlayerService created");
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int onStartCommand(Intent intent, int flags, int startId)
|
|
|
|
{
|
|
|
|
super.onStartCommand(intent, flags, startId);
|
|
|
|
return START_NOT_STICKY;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onDestroy()
|
|
|
|
{
|
|
|
|
super.onDestroy();
|
|
|
|
|
|
|
|
instance = null;
|
|
|
|
|
2020-06-26 13:31:31 +02:00
|
|
|
try {
|
2021-03-30 14:38:59 +02:00
|
|
|
localMediaPlayer.release();
|
2020-06-29 16:17:22 +02:00
|
|
|
downloader.stop();
|
|
|
|
shufflePlayBuffer.onDestroy();
|
2021-04-12 07:27:55 +02:00
|
|
|
mediaSession.release();
|
2020-06-26 13:31:31 +02:00
|
|
|
} catch (Throwable ignored) {
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2020-09-30 14:47:59 +02:00
|
|
|
Timber.i("MediaPlayerService stopped");
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2020-06-26 13:31:31 +02:00
|
|
|
private void stopIfIdle()
|
|
|
|
{
|
|
|
|
synchronized (instanceLock)
|
|
|
|
{
|
|
|
|
// currentPlaying could be changed from another thread in the meantime, so check again before stopping for good
|
2020-06-29 14:44:19 +02:00
|
|
|
if (localMediaPlayer.currentPlaying == null || localMediaPlayer.playerState == STOPPED) stopSelf();
|
2020-06-26 13:31:31 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-21 09:31:38 +02:00
|
|
|
public synchronized void seekTo(int position)
|
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
if (jukeboxMediaPlayer.getValue().isEnabled())
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
jukeboxMediaPlayer.getValue().skip(downloader.getCurrentPlayingIndex(), position / 1000);
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
else
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
localMediaPlayer.seekTo(position);
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public synchronized int getPlayerPosition()
|
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
if (localMediaPlayer.playerState == IDLE || localMediaPlayer.playerState == DOWNLOADING || localMediaPlayer.playerState == PREPARING)
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-06-25 14:33:44 +02:00
|
|
|
return jukeboxMediaPlayer.getValue().isEnabled() ? jukeboxMediaPlayer.getValue().getPositionSeconds() * 1000 :
|
|
|
|
localMediaPlayer.getPlayerPosition();
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
public synchronized int getPlayerDuration()
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
return localMediaPlayer.getPlayerDuration();
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public synchronized void setCurrentPlaying(int currentPlayingIndex)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
localMediaPlayer.setCurrentPlaying(downloader.downloadList.get(currentPlayingIndex));
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
catch (IndexOutOfBoundsException x)
|
|
|
|
{
|
|
|
|
// Ignored
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
public void setupOnCurrentPlayingChangedHandler()
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
localMediaPlayer.onCurrentPlayingChanged = new Consumer<DownloadFile>() {
|
2020-06-23 18:40:44 +02:00
|
|
|
@Override
|
|
|
|
public void accept(DownloadFile currentPlaying) {
|
|
|
|
if (currentPlaying != null)
|
|
|
|
{
|
|
|
|
Util.broadcastNewTrackInfo(MediaPlayerService.this, currentPlaying.getSong());
|
|
|
|
Util.broadcastA2dpMetaDataChange(MediaPlayerService.this, getPlayerPosition(), currentPlaying,
|
2020-06-25 11:58:09 +02:00
|
|
|
downloader.getDownloads().size(), downloader.getCurrentPlayingIndex() + 1);
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Util.broadcastNewTrackInfo(MediaPlayerService.this, null);
|
|
|
|
Util.broadcastA2dpMetaDataChange(MediaPlayerService.this, getPlayerPosition(), null,
|
2020-06-25 11:58:09 +02:00
|
|
|
downloader.getDownloads().size(), downloader.getCurrentPlayingIndex() + 1);
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
// Update widget
|
2020-06-25 14:33:44 +02:00
|
|
|
PlayerState playerState = localMediaPlayer.playerState;
|
2020-06-23 18:40:44 +02:00
|
|
|
MusicDirectory.Entry song = currentPlaying == null? null : currentPlaying.getSong();
|
2020-09-24 13:03:05 +02:00
|
|
|
UltrasonicAppWidgetProvider4X1.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false);
|
|
|
|
UltrasonicAppWidgetProvider4X2.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, true);
|
|
|
|
UltrasonicAppWidgetProvider4X3.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false);
|
|
|
|
UltrasonicAppWidgetProvider4X4.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false);
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
if (currentPlaying != null)
|
|
|
|
{
|
2020-06-26 13:31:31 +02:00
|
|
|
updateNotification(localMediaPlayer.playerState, currentPlaying);
|
2021-03-01 17:24:25 +01:00
|
|
|
nowPlayingEventDistributor.getValue().raiseShowNowPlayingEvent();
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-03-01 17:24:25 +01:00
|
|
|
nowPlayingEventDistributor.getValue().raiseHideNowPlayingEvent();
|
2020-06-26 13:31:31 +02:00
|
|
|
stopForeground(true);
|
|
|
|
isInForeground = false;
|
|
|
|
stopIfIdle();
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
};
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public synchronized void setNextPlaying()
|
|
|
|
{
|
|
|
|
boolean gaplessPlayback = Util.getGaplessPlaybackPreference(this);
|
|
|
|
|
|
|
|
if (!gaplessPlayback)
|
|
|
|
{
|
2021-03-24 13:28:54 +01:00
|
|
|
localMediaPlayer.clearNextPlaying(true);
|
2020-06-21 09:31:38 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-06-25 11:58:09 +02:00
|
|
|
int index = downloader.getCurrentPlayingIndex();
|
2020-06-21 09:31:38 +02:00
|
|
|
|
|
|
|
if (index != -1)
|
|
|
|
{
|
|
|
|
switch (getRepeatMode())
|
|
|
|
{
|
|
|
|
case OFF:
|
|
|
|
index += 1;
|
|
|
|
break;
|
|
|
|
case ALL:
|
2020-06-25 11:58:09 +02:00
|
|
|
index = (index + 1) % downloader.downloadList.size();
|
2020-06-21 09:31:38 +02:00
|
|
|
break;
|
|
|
|
case SINGLE:
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-24 13:28:54 +01:00
|
|
|
localMediaPlayer.clearNextPlaying(false);
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2020-06-25 11:58:09 +02:00
|
|
|
if (index < downloader.downloadList.size() && index != -1)
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
localMediaPlayer.setNextPlaying(downloader.downloadList.get(index));
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-03-24 13:28:54 +01:00
|
|
|
localMediaPlayer.clearNextPlaying(true);
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public synchronized void togglePlayPause()
|
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
if (localMediaPlayer.playerState == PAUSED || localMediaPlayer.playerState == COMPLETED || localMediaPlayer.playerState == STOPPED)
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
|
|
|
start();
|
|
|
|
}
|
2020-06-25 14:33:44 +02:00
|
|
|
else if (localMediaPlayer.playerState == IDLE)
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
|
|
|
play();
|
|
|
|
}
|
2020-06-25 14:33:44 +02:00
|
|
|
else if (localMediaPlayer.playerState == STARTED)
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
|
|
|
pause();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-29 11:09:24 +02:00
|
|
|
public synchronized void resumeOrPlay()
|
|
|
|
{
|
|
|
|
if (localMediaPlayer.playerState == PAUSED || localMediaPlayer.playerState == COMPLETED || localMediaPlayer.playerState == STOPPED)
|
|
|
|
{
|
|
|
|
start();
|
|
|
|
}
|
|
|
|
else if (localMediaPlayer.playerState == IDLE)
|
|
|
|
{
|
|
|
|
play();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-21 09:31:38 +02:00
|
|
|
/**
|
|
|
|
* Plays either the current song (resume) or the first/next one in queue.
|
|
|
|
*/
|
|
|
|
public synchronized void play()
|
|
|
|
{
|
2020-06-25 11:58:09 +02:00
|
|
|
int current = downloader.getCurrentPlayingIndex();
|
2020-06-21 09:31:38 +02:00
|
|
|
if (current == -1)
|
|
|
|
{
|
|
|
|
play(0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
play(current);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public synchronized void play(int index)
|
|
|
|
{
|
|
|
|
play(index, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
public synchronized void play(int index, boolean start)
|
|
|
|
{
|
2020-09-30 14:47:59 +02:00
|
|
|
Timber.v("play requested for %d", index);
|
2020-06-25 11:58:09 +02:00
|
|
|
if (index < 0 || index >= downloader.downloadList.size())
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
|
|
|
resetPlayback();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-06-29 14:44:19 +02:00
|
|
|
setCurrentPlaying(index);
|
|
|
|
|
2020-06-21 09:31:38 +02:00
|
|
|
if (start)
|
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
if (jukeboxMediaPlayer.getValue().isEnabled())
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
jukeboxMediaPlayer.getValue().skip(index, 0);
|
|
|
|
localMediaPlayer.setPlayerState(STARTED);
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
localMediaPlayer.play(downloader.downloadList.get(index));
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-25 11:58:09 +02:00
|
|
|
downloader.checkDownloads();
|
2020-06-21 09:31:38 +02:00
|
|
|
setNextPlaying();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private synchronized void resetPlayback()
|
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
localMediaPlayer.reset();
|
|
|
|
localMediaPlayer.setCurrentPlaying(null);
|
2020-06-26 15:18:14 +02:00
|
|
|
downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList,
|
2020-06-25 11:58:09 +02:00
|
|
|
downloader.getCurrentPlayingIndex(), getPlayerPosition());
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
public synchronized void pause()
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
if (localMediaPlayer.playerState == STARTED)
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
if (jukeboxMediaPlayer.getValue().isEnabled())
|
2020-06-23 18:40:44 +02:00
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
jukeboxMediaPlayer.getValue().stop();
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
localMediaPlayer.pause();
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2020-06-25 14:33:44 +02:00
|
|
|
localMediaPlayer.setPlayerState(PAUSED);
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public synchronized void stop()
|
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
if (localMediaPlayer.playerState == STARTED)
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
if (jukeboxMediaPlayer.getValue().isEnabled())
|
2020-06-23 18:40:44 +02:00
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
jukeboxMediaPlayer.getValue().stop();
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
localMediaPlayer.pause();
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2020-06-25 14:33:44 +02:00
|
|
|
localMediaPlayer.setPlayerState(STOPPED);
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
public synchronized void start()
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
if (jukeboxMediaPlayer.getValue().isEnabled())
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
jukeboxMediaPlayer.getValue().start();
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
else
|
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
localMediaPlayer.start();
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2020-06-25 14:33:44 +02:00
|
|
|
localMediaPlayer.setPlayerState(STARTED);
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
public void setupOnPlayerStateChangedHandler()
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
localMediaPlayer.onPlayerStateChanged = new BiConsumer<PlayerState, DownloadFile>() {
|
2020-06-23 18:40:44 +02:00
|
|
|
@Override
|
|
|
|
public void accept(PlayerState playerState, DownloadFile currentPlaying) {
|
2021-04-12 07:27:55 +02:00
|
|
|
// Notify MediaSession
|
|
|
|
updateMediaSession(currentPlaying, playerState);
|
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
if (playerState == PAUSED)
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-26 15:18:14 +02:00
|
|
|
downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition());
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
boolean showWhenPaused = (playerState != PlayerState.STOPPED && Util.isNotificationAlwaysEnabled(MediaPlayerService.this));
|
|
|
|
boolean show = playerState == PlayerState.STARTED || showWhenPaused;
|
2020-06-25 14:33:44 +02:00
|
|
|
MusicDirectory.Entry song = currentPlaying == null? null : currentPlaying.getSong();
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
Util.broadcastPlaybackStatusChange(MediaPlayerService.this, playerState);
|
2020-06-25 14:33:44 +02:00
|
|
|
Util.broadcastA2dpPlayStatusChange(MediaPlayerService.this, playerState, song,
|
2020-06-25 11:58:09 +02:00
|
|
|
downloader.downloadList.size() + downloader.backgroundDownloadList.size(),
|
|
|
|
downloader.downloadList.indexOf(currentPlaying) + 1, getPlayerPosition());
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
// Update widget
|
2020-09-24 13:03:05 +02:00
|
|
|
UltrasonicAppWidgetProvider4X1.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false);
|
|
|
|
UltrasonicAppWidgetProvider4X2.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, true);
|
|
|
|
UltrasonicAppWidgetProvider4X3.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false);
|
|
|
|
UltrasonicAppWidgetProvider4X4.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false);
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
if (show)
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-07-30 11:59:22 +02:00
|
|
|
// Only update notification if player state is one that will change the icon
|
2020-06-26 13:31:31 +02:00
|
|
|
if (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED)
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-26 13:31:31 +02:00
|
|
|
updateNotification(playerState, currentPlaying);
|
2021-03-01 17:24:25 +01:00
|
|
|
nowPlayingEventDistributor.getValue().raiseShowNowPlayingEvent();
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
else
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2021-03-01 17:24:25 +01:00
|
|
|
nowPlayingEventDistributor.getValue().raiseHideNowPlayingEvent();
|
2020-06-26 13:31:31 +02:00
|
|
|
stopForeground(true);
|
|
|
|
isInForeground = false;
|
|
|
|
stopIfIdle();
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
if (playerState == STARTED)
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-23 18:40:44 +02:00
|
|
|
scrobbler.scrobble(MediaPlayerService.this, currentPlaying, false);
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
else if (playerState == COMPLETED)
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-23 18:40:44 +02:00
|
|
|
scrobbler.scrobble(MediaPlayerService.this, currentPlaying, true);
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
};
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
private void setupOnSongCompletedHandler()
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
localMediaPlayer.onSongCompleted = new Consumer<DownloadFile>() {
|
2020-06-21 09:31:38 +02:00
|
|
|
@Override
|
2020-06-23 18:40:44 +02:00
|
|
|
public void accept(DownloadFile currentPlaying) {
|
2020-06-25 11:58:09 +02:00
|
|
|
int index = downloader.getCurrentPlayingIndex();
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
if (currentPlaying != null)
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-23 18:40:44 +02:00
|
|
|
final MusicDirectory.Entry song = currentPlaying.getSong();
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2021-04-17 18:23:32 +02:00
|
|
|
if (song.getBookmarkPosition() > 0 && Util.getShouldClearBookmark(MediaPlayerService.this))
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-23 18:40:44 +02:00
|
|
|
MusicService musicService = MusicServiceFactory.getMusicService(MediaPlayerService.this);
|
|
|
|
try
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2021-02-14 15:55:16 +01:00
|
|
|
musicService.deleteBookmark(song.getId(), MediaPlayerService.this);
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
catch (Exception ignored)
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
if (index != -1)
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-23 18:40:44 +02:00
|
|
|
switch (getRepeatMode())
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-23 18:40:44 +02:00
|
|
|
case OFF:
|
2020-06-25 11:58:09 +02:00
|
|
|
if (index + 1 < 0 || index + 1 >= downloader.downloadList.size())
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-23 18:40:44 +02:00
|
|
|
if (Util.getShouldClearPlaylist(MediaPlayerService.this))
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-23 18:40:44 +02:00
|
|
|
clear(true);
|
2020-06-25 14:33:44 +02:00
|
|
|
jukeboxMediaPlayer.getValue().updatePlaylist();
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
resetPlayback();
|
|
|
|
break;
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
play(index + 1);
|
|
|
|
break;
|
|
|
|
case ALL:
|
2020-06-25 11:58:09 +02:00
|
|
|
play((index + 1) % downloader.downloadList.size());
|
2020-06-23 18:40:44 +02:00
|
|
|
break;
|
|
|
|
case SINGLE:
|
|
|
|
play(index);
|
|
|
|
break;
|
|
|
|
default:
|
2020-06-21 09:31:38 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
};
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
public synchronized void clear(boolean serialize)
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
2020-06-25 14:33:44 +02:00
|
|
|
localMediaPlayer.reset();
|
2020-06-25 11:58:09 +02:00
|
|
|
downloader.clear();
|
2020-06-25 14:33:44 +02:00
|
|
|
localMediaPlayer.setCurrentPlaying(null);
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
setNextPlaying();
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
if (serialize) {
|
2020-06-26 15:18:14 +02:00
|
|
|
downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList,
|
2020-06-25 11:58:09 +02:00
|
|
|
downloader.getCurrentPlayingIndex(), getPlayerPosition());
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
private void updateMediaSession(DownloadFile currentPlaying, PlayerState playerState) {
|
|
|
|
// Set Metadata
|
|
|
|
MediaMetadataCompat.Builder metadata = new MediaMetadataCompat.Builder();
|
|
|
|
Context context = getApplicationContext();
|
|
|
|
|
|
|
|
if (currentPlaying != null) {
|
|
|
|
try {
|
|
|
|
MusicDirectory.Entry song = currentPlaying.getSong();
|
|
|
|
|
|
|
|
Bitmap cover = FileUtil.getAlbumArtBitmap(context, song,
|
|
|
|
Util.getMinDisplayMetric(context), true
|
|
|
|
);
|
|
|
|
|
|
|
|
metadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.getArtist());
|
|
|
|
metadata.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, song.getArtist());
|
|
|
|
metadata.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.getAlbum());
|
|
|
|
metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.getTitle());
|
|
|
|
metadata.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, cover);
|
|
|
|
} catch (Exception e) {
|
|
|
|
Timber.e(e, "Error setting the metadata");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save the metadata
|
|
|
|
mediaSession.setMetadata(metadata.build());
|
|
|
|
|
|
|
|
// Create playback State
|
|
|
|
PlaybackStateCompat.Builder playbackState = new PlaybackStateCompat.Builder();
|
|
|
|
int state = (playerState == STARTED) ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED;
|
|
|
|
|
|
|
|
// If we set the playback position correctly, we can get a nice seek bar :)
|
|
|
|
playbackState.setState(state, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0F);
|
|
|
|
|
|
|
|
// Save the playback state
|
|
|
|
mediaSession.setPlaybackState(playbackState.build());
|
|
|
|
}
|
|
|
|
|
2020-06-23 18:40:44 +02:00
|
|
|
public void updateNotification(PlayerState playerState, DownloadFile currentPlaying)
|
2020-06-21 09:31:38 +02:00
|
|
|
{
|
|
|
|
if (Util.isNotificationEnabled(this)) {
|
2020-06-25 14:33:44 +02:00
|
|
|
if (isInForeground) {
|
2020-06-21 09:31:38 +02:00
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
|
|
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
2020-06-23 18:40:44 +02:00
|
|
|
notificationManager.notify(NOTIFICATION_ID, buildForegroundNotification(playerState, currentPlaying));
|
2021-04-12 07:27:55 +02:00
|
|
|
} else {
|
2020-06-21 09:31:38 +02:00
|
|
|
final NotificationManagerCompat notificationManager =
|
|
|
|
NotificationManagerCompat.from(this);
|
2020-06-23 18:40:44 +02:00
|
|
|
notificationManager.notify(NOTIFICATION_ID, buildForegroundNotification(playerState, currentPlaying));
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2020-09-30 14:47:59 +02:00
|
|
|
Timber.w("--- Updated notification");
|
2021-04-12 07:27:55 +02:00
|
|
|
} else {
|
2020-06-23 18:40:44 +02:00
|
|
|
startForeground(NOTIFICATION_ID, buildForegroundNotification(playerState, currentPlaying));
|
2020-06-21 09:31:38 +02:00
|
|
|
isInForeground = true;
|
2020-09-30 14:47:59 +02:00
|
|
|
Timber.w("--- Created Foreground notification");
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
/**
|
|
|
|
* This method builds a notification, reusing the Notification Builder if possible
|
|
|
|
*/
|
|
|
|
private Notification buildForegroundNotification(PlayerState playerState, DownloadFile currentPlaying) {
|
|
|
|
// Init
|
|
|
|
Context context = getApplicationContext();
|
2021-04-21 16:41:08 +02:00
|
|
|
MusicDirectory.Entry song = (currentPlaying != null) ? currentPlaying.getSong() : null;
|
2021-04-21 17:14:05 +02:00
|
|
|
PendingIntent stopIntent = getPendingIntentForMediaAction(context, KeyEvent.KEYCODE_MEDIA_STOP, 100);
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
// We should use a single notification builder, otherwise the notification may not be updated
|
|
|
|
if (notificationBuilder == null) {
|
|
|
|
notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
// Set some values that never change
|
|
|
|
notificationBuilder.setSmallIcon(R.drawable.ic_stat_ultrasonic);
|
|
|
|
notificationBuilder.setAutoCancel(false);
|
|
|
|
notificationBuilder.setOngoing(true);
|
|
|
|
notificationBuilder.setOnlyAlertOnce(true);
|
2021-04-21 16:41:08 +02:00
|
|
|
notificationBuilder.setWhen(System.currentTimeMillis());
|
|
|
|
notificationBuilder.setShowWhen(false);
|
2021-04-12 07:27:55 +02:00
|
|
|
notificationBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
|
|
|
|
notificationBuilder.setPriority(NotificationCompat.PRIORITY_LOW);
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
// Add content intent (when user taps on notification)
|
|
|
|
notificationBuilder.setContentIntent(getPendingIntentForContent());
|
2021-04-21 17:14:05 +02:00
|
|
|
|
|
|
|
// This intent is executed when the user closes the notification
|
|
|
|
notificationBuilder.setDeleteIntent(stopIntent);
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
// Use the Media Style, to enable native Android support for playback notification
|
|
|
|
androidx.media.app.NotificationCompat.MediaStyle style = new androidx.media.app.NotificationCompat.MediaStyle();
|
|
|
|
style.setMediaSession(mediaSessionToken);
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
// Clear old actions
|
|
|
|
notificationBuilder.clearActions();
|
|
|
|
|
|
|
|
// Add actions
|
2021-04-21 16:41:08 +02:00
|
|
|
int[] compactActions = addActions(context, notificationBuilder, playerState, song);
|
|
|
|
|
|
|
|
// Configure shortcut actions
|
2021-04-12 07:27:55 +02:00
|
|
|
style.setShowActionsInCompactView(compactActions);
|
|
|
|
notificationBuilder.setStyle(style);
|
|
|
|
|
2021-04-21 16:41:08 +02:00
|
|
|
// Set song title, artist and cover if possible
|
|
|
|
if (song != null) {
|
|
|
|
int iconSize = (int) (256 * context.getResources().getDisplayMetrics().density);
|
|
|
|
Bitmap bitmap = FileUtil.getAlbumArtBitmap(context, song, iconSize, true);
|
|
|
|
notificationBuilder.setContentTitle(song.getTitle());
|
|
|
|
notificationBuilder.setContentText(song.getArtist());
|
|
|
|
notificationBuilder.setLargeIcon(bitmap);
|
|
|
|
notificationBuilder.setSubText(song.getAlbum());
|
|
|
|
}
|
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
return notificationBuilder.build();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-04-21 16:41:08 +02:00
|
|
|
private int[] addActions(Context context, NotificationCompat.Builder notificationBuilder, PlayerState playerState, MusicDirectory.Entry song) {
|
2021-04-12 07:27:55 +02:00
|
|
|
ArrayList<Integer> compactActionList = new ArrayList<>();
|
|
|
|
int numActions = 0; // we start and 0 and then increment by 1 for each call to generateAction
|
|
|
|
|
|
|
|
|
2021-04-21 16:41:08 +02:00
|
|
|
// Star
|
|
|
|
if (song != null) {
|
|
|
|
notificationBuilder.addAction(generateStarUnstarAction(context, numActions, song.getStarred()));
|
|
|
|
}
|
|
|
|
numActions++;
|
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
// Next
|
|
|
|
notificationBuilder.addAction(generateAction(context, numActions));
|
|
|
|
compactActionList.add(numActions);
|
|
|
|
numActions++;
|
|
|
|
|
|
|
|
// Play/Pause button
|
|
|
|
notificationBuilder.addAction(generatePlayPauseAction(context, numActions, playerState));
|
|
|
|
compactActionList.add(numActions);
|
|
|
|
numActions++;
|
|
|
|
|
|
|
|
// Previous
|
|
|
|
notificationBuilder.addAction(generateAction(context, numActions));
|
|
|
|
compactActionList.add(numActions);
|
2021-04-21 16:41:08 +02:00
|
|
|
numActions++;
|
|
|
|
|
|
|
|
// Close
|
|
|
|
notificationBuilder.addAction(generateAction(context, numActions));
|
2021-04-12 07:27:55 +02:00
|
|
|
|
|
|
|
int[] actionArray = new int[compactActionList.size()];
|
|
|
|
|
|
|
|
for (int i = 0; i < actionArray.length; i++) {
|
|
|
|
actionArray[i] = compactActionList.get(i);
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
return actionArray;
|
|
|
|
//notificationBuilder.setShowActionsInCompactView())
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private NotificationCompat.Action generateAction(Context context, int requestCode) {
|
|
|
|
int keycode;
|
|
|
|
int icon;
|
|
|
|
String label;
|
|
|
|
|
|
|
|
// If you change the order here, also update the requestCode in updatePlayPauseAction()!
|
|
|
|
switch (requestCode) {
|
2021-04-21 16:41:08 +02:00
|
|
|
case 1:
|
2021-04-12 07:27:55 +02:00
|
|
|
keycode = KeyEvent.KEYCODE_MEDIA_PREVIOUS;
|
|
|
|
label = getString(R.string.common_play_previous);
|
2021-04-17 19:13:19 +02:00
|
|
|
icon = R.drawable.media_backward_medium_dark;
|
2021-04-12 07:27:55 +02:00
|
|
|
break;
|
2021-04-21 16:41:08 +02:00
|
|
|
case 2:
|
2021-04-12 07:27:55 +02:00
|
|
|
// Is handled in generatePlayPauseAction()
|
|
|
|
return null;
|
2021-04-21 16:41:08 +02:00
|
|
|
case 3:
|
2021-04-12 07:27:55 +02:00
|
|
|
keycode = KeyEvent.KEYCODE_MEDIA_NEXT;
|
|
|
|
label = getString(R.string.common_play_next);
|
2021-04-17 19:13:19 +02:00
|
|
|
icon = R.drawable.media_forward_medium_dark;
|
2021-04-12 07:27:55 +02:00
|
|
|
break;
|
2021-04-21 16:41:08 +02:00
|
|
|
case 4:
|
|
|
|
keycode = KeyEvent.KEYCODE_MEDIA_STOP;
|
|
|
|
label = getString(R.string.buttons_stop);
|
|
|
|
icon = R.drawable.ic_baseline_close_24;
|
|
|
|
break;
|
2021-04-12 07:27:55 +02:00
|
|
|
default:
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
PendingIntent pendingIntent = getPendingIntentForMediaAction(context, keycode, requestCode);
|
|
|
|
|
|
|
|
return new NotificationCompat.Action.Builder(icon, label, pendingIntent).build();
|
|
|
|
}
|
|
|
|
|
|
|
|
private NotificationCompat.Action generatePlayPauseAction(Context context, int requestCode, PlayerState playerState) {
|
|
|
|
|
|
|
|
boolean isPlaying = (playerState == STARTED);
|
|
|
|
PendingIntent pendingIntent = getPendingIntentForMediaAction(context, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, requestCode);
|
|
|
|
|
|
|
|
String label;
|
|
|
|
int icon;
|
|
|
|
|
|
|
|
if (isPlaying) {
|
|
|
|
label = getString(R.string.common_pause);
|
2021-04-17 19:13:19 +02:00
|
|
|
icon = R.drawable.media_pause_large_dark;
|
2021-04-12 07:27:55 +02:00
|
|
|
} else {
|
|
|
|
label = getString(R.string.common_play);
|
2021-04-17 19:13:19 +02:00
|
|
|
icon = R.drawable.media_start_large_dark;
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
return new NotificationCompat.Action.Builder(icon, label, pendingIntent).build();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-04-21 16:41:08 +02:00
|
|
|
private NotificationCompat.Action generateStarUnstarAction(Context context, int requestCode, Boolean isStarred) {
|
|
|
|
|
|
|
|
int keyCode;
|
|
|
|
String label;
|
|
|
|
int icon;
|
|
|
|
keyCode = KeyEvent.KEYCODE_STAR;
|
|
|
|
|
|
|
|
if (isStarred) {
|
|
|
|
label = getString(R.string.download_menu_star);
|
|
|
|
icon = R.drawable.ic_star_full_dark;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
label = getString(R.string.download_menu_star);
|
|
|
|
icon = R.drawable.ic_star_hollow_dark;
|
|
|
|
}
|
|
|
|
|
|
|
|
PendingIntent pendingIntent = getPendingIntentForMediaAction(context, keyCode, requestCode);
|
|
|
|
|
|
|
|
return new NotificationCompat.Action.Builder(icon, label, pendingIntent).build();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
private PendingIntent getPendingIntentForContent() {
|
|
|
|
Intent notificationIntent = new Intent(this, NavigationActivity.class)
|
|
|
|
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
|
|
|
notificationIntent.putExtra(Constants.INTENT_EXTRA_NAME_SHOW_PLAYER, true);
|
|
|
|
return PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
|
|
|
}
|
|
|
|
|
|
|
|
private PendingIntent getPendingIntentForMediaAction(Context context, int keycode, int requestCode) {
|
|
|
|
Intent intent = new Intent(Constants.CMD_PROCESS_KEYCODE);
|
|
|
|
intent.setPackage(context.getPackageName());
|
|
|
|
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, keycode));
|
|
|
|
|
|
|
|
return PendingIntent.getBroadcast(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void initMediaSessions() {
|
|
|
|
|
|
|
|
mediaSession = new MediaSessionCompat(getApplicationContext(), "UltrasonicService");
|
|
|
|
mediaSessionToken = mediaSession.getSessionToken();
|
|
|
|
//mediaController = new MediaControllerCompat(getApplicationContext(), mediaSessionToken);
|
|
|
|
|
|
|
|
mediaSession.setCallback(new MediaSessionCompat.Callback() {
|
|
|
|
@Override
|
|
|
|
public void onPlay() {
|
|
|
|
super.onPlay();
|
|
|
|
play();
|
|
|
|
Timber.w("Media Session Callback: onPlay");
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onPause() {
|
|
|
|
super.onPause();
|
|
|
|
pause();
|
|
|
|
Timber.w("Media Session Callback: onPause");
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onStop() {
|
|
|
|
super.onStop();
|
|
|
|
stop();
|
|
|
|
Timber.w("Media Session Callback: onStop");
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onSeekTo(long pos) {
|
|
|
|
super.onSeekTo(pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
);
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
}
|