PlayerAndroid/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java

1644 lines
64 KiB
Java

/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.service;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.bluetooth.BluetoothDevice;
import android.content.*;
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.audiofx.AudioEffect;
import android.os.*;
import android.os.Build.VERSION;
import android.os.Process;
import android.os.Build.VERSION_CODES;
import android.provider.MediaStore;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.media.MediaBrowserServiceCompat;
import androidx.preference.PreferenceManager;
import code.name.monkey.appthemehelper.util.VersionUtils;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.activities.LockScreenActivity;
import code.name.monkey.retromusic.appwidgets.*;
import code.name.monkey.retromusic.auto.AutoMediaIDHelper;
import code.name.monkey.retromusic.auto.AutoMusicProvider;
import code.name.monkey.retromusic.glide.BlurTransformation;
import code.name.monkey.retromusic.glide.GlideApp;
import code.name.monkey.retromusic.glide.RetroGlideExtension;
import code.name.monkey.retromusic.helper.MusicPlayerRemote;
import code.name.monkey.retromusic.helper.ShuffleHelper;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist;
import code.name.monkey.retromusic.providers.HistoryStore;
import code.name.monkey.retromusic.providers.MusicPlaybackQueueStore;
import code.name.monkey.retromusic.providers.SongPlayCountStore;
import code.name.monkey.retromusic.service.notification.PlayingNotification;
import code.name.monkey.retromusic.service.notification.PlayingNotificationImpl;
import code.name.monkey.retromusic.service.notification.PlayingNotificationOreo;
import code.name.monkey.retromusic.service.playback.Playback;
import code.name.monkey.retromusic.util.MusicUtil;
import code.name.monkey.retromusic.util.PackageValidator;
import code.name.monkey.retromusic.util.PreferenceUtil;
import code.name.monkey.retromusic.util.RetroUtil;
import code.name.monkey.retromusic.volume.AudioVolumeObserver;
import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.request.target.SimpleTarget;
import kotlin.Unit;
import software.lavender.music.player.ExoPlayerPlayback;
import java.util.*;
import static android.support.v4.media.MediaBrowserCompat.MediaItem.FLAG_PLAYABLE;
import static androidx.media.MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT;
import static code.name.monkey.retromusic.ConstantsKt.*;
import static code.name.monkey.retromusic.service.AudioFader.startFadeAnimator;
import static org.koin.java.KoinJavaComponent.get;
/**
* @author Karim Abou Zeid (kabouzeid), Andrew Neal
*/
public class MusicService extends MediaBrowserServiceCompat
implements SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks, OnAudioVolumeChangedListener {
public static final String TAG = MusicService.class.getSimpleName();
public static final String RETRO_MUSIC_PACKAGE_NAME = "code.name.monkey.retromusic";
public static final String MUSIC_PACKAGE_NAME = "com.android.music";
public static final String ACTION_TOGGLE_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".togglepause";
public static final String ACTION_PLAY = RETRO_MUSIC_PACKAGE_NAME + ".play";
public static final String ACTION_PLAY_PLAYLIST = RETRO_MUSIC_PACKAGE_NAME + ".play.playlist";
public static final String ACTION_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".pause";
public static final String ACTION_STOP = RETRO_MUSIC_PACKAGE_NAME + ".stop";
public static final String ACTION_SKIP = RETRO_MUSIC_PACKAGE_NAME + ".skip";
public static final String ACTION_REWIND = RETRO_MUSIC_PACKAGE_NAME + ".rewind";
public static final String ACTION_QUIT = RETRO_MUSIC_PACKAGE_NAME + ".quitservice";
public static final String ACTION_PENDING_QUIT = RETRO_MUSIC_PACKAGE_NAME + ".pendingquitservice";
public static final String INTENT_EXTRA_PLAYLIST =
RETRO_MUSIC_PACKAGE_NAME + "intentextra.playlist";
public static final String INTENT_EXTRA_SHUFFLE_MODE =
RETRO_MUSIC_PACKAGE_NAME + ".intentextra.shufflemode";
public static final String APP_WIDGET_UPDATE = RETRO_MUSIC_PACKAGE_NAME + ".appwidgetupdate";
public static final String EXTRA_APP_WIDGET_NAME = RETRO_MUSIC_PACKAGE_NAME + "app_widget_name";
// Do not change these three strings as it will break support with other apps (e.g. last.fm
// scrobbling)
public static final String META_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".metachanged";
public static final String QUEUE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".queuechanged";
public static final String PLAY_STATE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".playstatechanged";
public static final String FAVORITE_STATE_CHANGED =
RETRO_MUSIC_PACKAGE_NAME + "favoritestatechanged";
public static final String REPEAT_MODE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".repeatmodechanged";
public static final String SHUFFLE_MODE_CHANGED =
RETRO_MUSIC_PACKAGE_NAME + ".shufflemodechanged";
public static final String MEDIA_STORE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".mediastorechanged";
public static final String CYCLE_REPEAT = RETRO_MUSIC_PACKAGE_NAME + ".cyclerepeat";
public static final String TOGGLE_SHUFFLE = RETRO_MUSIC_PACKAGE_NAME + ".toggleshuffle";
public static final String TOGGLE_FAVORITE = RETRO_MUSIC_PACKAGE_NAME + ".togglefavorite";
public static final String SAVED_POSITION = "POSITION";
public static final String SAVED_POSITION_IN_TRACK = "POSITION_IN_TRACK";
public static final String SAVED_SHUFFLE_MODE = "SHUFFLE_MODE";
public static final String SAVED_REPEAT_MODE = "REPEAT_MODE";
public static final int RELEASE_WAKELOCK = 0;
public static final int TRACK_ENDED = 1;
public static final int TRACK_WENT_TO_NEXT = 2;
public static final int PLAY_SONG = 3;
public static final int PREPARE_NEXT = 4;
public static final int SET_POSITION = 5;
public static final int FOCUS_CHANGE = 6;
public static final int DUCK = 7;
public static final int UNDUCK = 8;
public static final int RESTORE_QUEUES = 9;
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;
public static final int SAVE_QUEUES = 0;
private static final long MEDIA_SESSION_ACTIONS =
PlaybackStateCompat.ACTION_PLAY
| PlaybackStateCompat.ACTION_PAUSE
| PlaybackStateCompat.ACTION_PLAY_PAUSE
| PlaybackStateCompat.ACTION_SKIP_TO_NEXT
| PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
| PlaybackStateCompat.ACTION_STOP
| PlaybackStateCompat.ACTION_SEEK_TO;
private final IBinder musicBind = new MusicBinder();
public int nextPosition = -1;
public boolean pendingQuit = false;
@Nullable
public Playback playback;
private PackageValidator mPackageValidator;
private final AutoMusicProvider mMusicProvider = get(AutoMusicProvider.class);
public boolean trackEndedByCrossfade = false;
public int position = -1;
private final AppWidgetBig appWidgetBig = AppWidgetBig.Companion.getInstance();
private final AppWidgetCard appWidgetCard = AppWidgetCard.Companion.getInstance();
private final AppWidgetClassic appWidgetClassic = AppWidgetClassic.Companion.getInstance();
private final AppWidgetSmall appWidgetSmall = AppWidgetSmall.Companion.getInstance();
private final AppWidgetText appWidgetText = AppWidgetText.Companion.getInstance();
private final AppWidgetMD3 appWidgetMd3 = AppWidgetMD3.Companion.getInstance();
private final BroadcastReceiver widgetIntentReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final String command = intent.getStringExtra(EXTRA_APP_WIDGET_NAME);
final int[] ids = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (command != null) {
switch (command) {
case AppWidgetClassic.NAME: {
appWidgetClassic.performUpdate(MusicService.this, ids);
break;
}
case AppWidgetSmall.NAME: {
appWidgetSmall.performUpdate(MusicService.this, ids);
break;
}
case AppWidgetBig.NAME: {
appWidgetBig.performUpdate(MusicService.this, ids);
break;
}
case AppWidgetCard.NAME: {
appWidgetCard.performUpdate(MusicService.this, ids);
break;
}
case AppWidgetText.NAME: {
appWidgetText.performUpdate(MusicService.this, ids);
break;
}
case AppWidgetMD3.NAME: {
appWidgetMd3.performUpdate(MusicService.this, ids);
break;
}
}
}
}
};
private AudioManager audioManager;
private final IntentFilter becomingNoisyReceiverIntentFilter =
new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
private boolean becomingNoisyReceiverRegistered;
private final IntentFilter bluetoothConnectedIntentFilter =
new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED);
private boolean bluetoothConnectedRegistered = false;
private final IntentFilter headsetReceiverIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
private boolean headsetReceiverRegistered = false;
private MediaSessionCompat mediaSession;
private ContentObserver mediaStoreObserver;
private HandlerThread musicPlayerHandlerThread;
private boolean notHandledMetaChangedForCurrentTrack;
private List<Song> originalPlayingQueue = new ArrayList<>();
private List<Song> playingQueue = new ArrayList<>();
private boolean pausedByTransientLossOfFocus;
private final BroadcastReceiver becomingNoisyReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, @NonNull Intent intent) {
if (intent.getAction() != null
&& intent.getAction().equals(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
pause();
}
}
};
private PlaybackHandler playerHandler;
private final AudioManager.OnAudioFocusChangeListener audioFocusListener =
new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(final int focusChange) {
playerHandler.obtainMessage(FOCUS_CHANGE, focusChange, 0).sendToTarget();
}
};
private PlayingNotification playingNotification;
private final BroadcastReceiver updateFavoriteReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
playingNotification.updateFavorite(getCurrentSong(), MusicService.this::startForegroundOrNotify);
startForegroundOrNotify();
}
};
private final BroadcastReceiver lockScreenReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (PreferenceUtil.INSTANCE.isLockScreen() && isPlaying()) {
Intent lockIntent = new Intent(context, LockScreenActivity.class);
lockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(lockIntent);
}
}
};
private QueueSaveHandler queueSaveHandler;
private HandlerThread queueSaveHandlerThread;
private boolean queuesRestored;
private int repeatMode;
private int shuffleMode;
private final SongPlayCountHelper songPlayCountHelper = new SongPlayCountHelper();
private final BroadcastReceiver bluetoothReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
String action = intent.getAction();
if (action != null) {
if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)
&& PreferenceUtil.INSTANCE.isBluetoothSpeaker()) {
if (VERSION.SDK_INT >= VERSION_CODES.M) {
if (getAudioManager().getDevices(AudioManager.GET_DEVICES_OUTPUTS).length > 0) {
play();
}
} else {
if (getAudioManager().isBluetoothA2dpOn()) {
play();
}
}
}
}
}
};
private final PhoneStateListener phoneStateListener =
new PhoneStateListener() {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
// Not in call: Play music
play();
break;
case TelephonyManager.CALL_STATE_RINGING:
case TelephonyManager.CALL_STATE_OFFHOOK:
// A call is dialing, active or on hold
pause();
break;
default:
}
super.onCallStateChanged(state, incomingNumber);
}
};
private final BroadcastReceiver headsetReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action != null) {
if (Intent.ACTION_HEADSET_PLUG.equals(action)) {
int state = intent.getIntExtra("state", -1);
switch (state) {
case 0:
pause();
break;
case 1:
play();
break;
}
}
}
}
};
private ThrottledSeekHandler throttledSeekHandler;
private Handler uiThreadHandler;
private PowerManager.WakeLock wakeLock;
private NotificationManager notificationManager;
private boolean isForeground = false;
private static Bitmap copy(Bitmap bitmap) {
Bitmap.Config config = bitmap.getConfig();
if (config == null) {
config = Bitmap.Config.RGB_565;
}
try {
return bitmap.copy(config, false);
} catch (OutOfMemoryError e) {
e.printStackTrace();
return null;
}
}
private static String getTrackUri(@NonNull Song song) {
return song.getData();//MusicUtil.INSTANCE.getSongFileUri(song.getId()).toString();
}
@Override
public void onCreate() {
super.onCreate();
final TelephonyManager telephonyManager =
(TelephonyManager) getSystemService(TELEPHONY_SERVICE);
if (telephonyManager != null) {
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
}
final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (powerManager != null) {
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
}
wakeLock.setReferenceCounted(false);
musicPlayerHandlerThread = new HandlerThread("PlaybackHandler");
musicPlayerHandlerThread.start();
playerHandler = new PlaybackHandler(this, musicPlayerHandlerThread.getLooper());
// Set MultiPlayer when crossfade duration is 0 i.e. off
// TODO: do crossfading in exoplayer or remove the feature entirely for now, crossfadeplayer will NOT work right now
if (PreferenceUtil.INSTANCE.getCrossFadeDuration() == 0) {
playback = new ExoPlayerPlayback(this);
} else {
playback = new CrossFadePlayer(this);
}
playback.setCallbacks(this);
setupMediaSession();
// queue saving needs to run on a separate thread so that it doesn't block the playback handler
// events
queueSaveHandlerThread =
new HandlerThread("QueueSaveHandler", Process.THREAD_PRIORITY_BACKGROUND);
queueSaveHandlerThread.start();
queueSaveHandler = new QueueSaveHandler(this, queueSaveHandlerThread.getLooper());
uiThreadHandler = new Handler();
registerReceiver(widgetIntentReceiver, new IntentFilter(APP_WIDGET_UPDATE));
registerReceiver(updateFavoriteReceiver, new IntentFilter(FAVORITE_STATE_CHANGED));
registerReceiver(lockScreenReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
setSessionToken(mediaSession.getSessionToken());
if (VersionUtils.INSTANCE.hasMarshmallow()) {
notificationManager = getSystemService(NotificationManager.class);
}
initNotification();
mediaStoreObserver = new MediaStoreObserver(this, playerHandler);
throttledSeekHandler = new ThrottledSeekHandler(this, playerHandler);
getContentResolver()
.registerContentObserver(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, mediaStoreObserver);
getContentResolver()
.registerContentObserver(
MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, true, mediaStoreObserver);
getContentResolver()
.registerContentObserver(
MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, true, mediaStoreObserver);
getContentResolver()
.registerContentObserver(
MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, true, mediaStoreObserver);
getContentResolver()
.registerContentObserver(
MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, true, mediaStoreObserver);
getContentResolver()
.registerContentObserver(
MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mediaStoreObserver);
getContentResolver()
.registerContentObserver(
MediaStore.Audio.Albums.INTERNAL_CONTENT_URI, true, mediaStoreObserver);
getContentResolver()
.registerContentObserver(
MediaStore.Audio.Artists.INTERNAL_CONTENT_URI, true, mediaStoreObserver);
getContentResolver()
.registerContentObserver(
MediaStore.Audio.Genres.INTERNAL_CONTENT_URI, true, mediaStoreObserver);
getContentResolver()
.registerContentObserver(
MediaStore.Audio.Playlists.INTERNAL_CONTENT_URI, true, mediaStoreObserver);
AudioVolumeObserver audioVolumeObserver = new AudioVolumeObserver(this);
audioVolumeObserver.register(AudioManager.STREAM_MUSIC, this);
PreferenceUtil.INSTANCE.registerOnSharedPreferenceChangedListener(this);
restoreState();
sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_CREATED"));
registerHeadsetEvents();
registerBluetoothConnected();
mPackageValidator = new PackageValidator(this, R.xml.allowed_media_browser_callers);
mMusicProvider.setMusicService(this);
}
@Override
public void onDestroy() {
unregisterReceiver(widgetIntentReceiver);
unregisterReceiver(updateFavoriteReceiver);
unregisterReceiver(lockScreenReceiver);
if (becomingNoisyReceiverRegistered) {
unregisterReceiver(becomingNoisyReceiver);
becomingNoisyReceiverRegistered = false;
}
if (headsetReceiverRegistered) {
unregisterReceiver(headsetReceiver);
headsetReceiverRegistered = false;
}
if (bluetoothConnectedRegistered) {
unregisterReceiver(bluetoothReceiver);
bluetoothConnectedRegistered = false;
}
mediaSession.setActive(false);
quit();
releaseResources();
getContentResolver().unregisterContentObserver(mediaStoreObserver);
PreferenceUtil.INSTANCE.unregisterOnSharedPreferenceChangedListener(this);
wakeLock.release();
sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_DESTROYED"));
}
public void acquireWakeLock(long milli) {
wakeLock.acquire(milli);
}
boolean pausedByZeroVolume;
@Override
public void onAudioVolumeChanged(int currentVolume, int maxVolume) {
if (PreferenceUtil.INSTANCE.isPauseOnZeroVolume()) {
if (isPlaying() && currentVolume < 1) {
pause();
System.out.println("Paused");
pausedByZeroVolume = true;
} else if (pausedByZeroVolume && currentVolume >= 1) {
System.out.println("Played");
play();
pausedByZeroVolume = false;
}
}
}
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<Song> songs) {
playingQueue.addAll(position, songs);
originalPlayingQueue.addAll(position, songs);
notifyChange(QUEUE_CHANGED);
}
public void addSongs(List<Song> songs) {
playingQueue.addAll(songs);
originalPlayingQueue.addAll(songs);
notifyChange(QUEUE_CHANGED);
}
public void back(boolean force) {
if (getSongProgressMillis() > 2000) {
seek(0);
} else {
playPreviousSong(force);
}
}
public void clearQueue() {
playingQueue.clear();
originalPlayingQueue.clear();
setPosition(-1);
notifyChange(QUEUE_CHANGED);
}
public void cycleRepeatMode() {
switch (getRepeatMode()) {
case REPEAT_MODE_NONE:
setRepeatMode(REPEAT_MODE_ALL);
break;
case REPEAT_MODE_ALL:
setRepeatMode(REPEAT_MODE_THIS);
break;
default:
setRepeatMode(REPEAT_MODE_NONE);
break;
}
}
public int getAudioSessionId() {
if (playback != null) {
return playback.getAudioSessionId();
}
return -1;
}
@NonNull
public Song getCurrentSong() {
return getSongAt(getPosition());
}
public Song getNextSong() {
if (isLastTrack() && getRepeatMode() == REPEAT_MODE_NONE) {
return null;
} else {
return getSongAt(getNextPosition(false));
}
}
@NonNull
public MediaSessionCompat getMediaSession() {
return mediaSession;
}
public int getNextPosition(boolean force) {
int position = getPosition() + 1;
switch (getRepeatMode()) {
case REPEAT_MODE_ALL:
if (isLastTrack()) {
position = 0;
}
break;
case REPEAT_MODE_THIS:
if (force) {
if (isLastTrack()) {
position = 0;
}
} else {
position -= 1;
}
break;
default:
case REPEAT_MODE_NONE:
if (isLastTrack()) {
position -= 1;
}
break;
}
return position;
}
@Nullable
public List<Song> getPlayingQueue() {
return playingQueue;
}
public int getPosition() {
return position;
}
public void setPosition(final int position) {
// handle this on the handlers thread to avoid blocking the ui thread
playerHandler.removeMessages(SET_POSITION);
playerHandler.obtainMessage(SET_POSITION, position, 0).sendToTarget();
}
public int getPreviousPosition(boolean force) {
int newPosition = getPosition() - 1;
switch (repeatMode) {
case REPEAT_MODE_ALL:
if (newPosition < 0) {
if (getPlayingQueue() != null) {
newPosition = getPlayingQueue().size() - 1;
}
}
break;
case REPEAT_MODE_THIS:
if (force) {
if (newPosition < 0) {
if (getPlayingQueue() != null) {
newPosition = getPlayingQueue().size() - 1;
}
}
} else {
newPosition = getPosition();
}
break;
default:
case REPEAT_MODE_NONE:
if (newPosition < 0) {
newPosition = 0;
}
break;
}
return newPosition;
}
public long getQueueDurationMillis(int position) {
long duration = 0;
for (int i = position + 1; i < playingQueue.size(); i++) {
duration += playingQueue.get(i).getDuration();
}
return duration;
}
public int getRepeatMode() {
return repeatMode;
}
public void setRepeatMode(final int repeatMode) {
switch (repeatMode) {
case REPEAT_MODE_NONE:
case REPEAT_MODE_ALL:
case REPEAT_MODE_THIS:
this.repeatMode = repeatMode;
PreferenceManager.getDefaultSharedPreferences(this)
.edit()
.putInt(SAVED_REPEAT_MODE, repeatMode)
.apply();
prepareNext();
handleAndSendChangeInternal(REPEAT_MODE_CHANGED);
break;
}
}
public int getShuffleMode() {
return shuffleMode;
}
public void setShuffleMode(final int shuffleMode) {
PreferenceManager.getDefaultSharedPreferences(this)
.edit()
.putInt(SAVED_SHUFFLE_MODE, shuffleMode)
.apply();
switch (shuffleMode) {
case SHUFFLE_MODE_SHUFFLE:
this.shuffleMode = shuffleMode;
if (this.getPlayingQueue() != null) {
ShuffleHelper.INSTANCE.makeShuffleList(this.getPlayingQueue(), getPosition());
}
position = 0;
break;
case SHUFFLE_MODE_NONE:
this.shuffleMode = shuffleMode;
long currentSongId = Objects.requireNonNull(getCurrentSong()).getId();
playingQueue = new ArrayList<>(originalPlayingQueue);
int newPosition = 0;
if (getPlayingQueue() != null) {
for (Song song : getPlayingQueue()) {
if (song.getId() == currentSongId) {
newPosition = getPlayingQueue().indexOf(song);
}
}
}
position = newPosition;
break;
}
handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED);
notifyChange(QUEUE_CHANGED);
}
@NonNull
public Song getSongAt(int position) {
if (position >= 0 && getPlayingQueue() != null && position < getPlayingQueue().size()) {
return getPlayingQueue().get(position);
} else {
return Song.Companion.getEmptySong();
}
}
public int getSongDurationMillis() {
if (playback != null) {
return playback.duration();
}
return -1;
}
public int getSongProgressMillis() {
if (playback != null) {
return playback.position();
}
return -1;
}
public void handleAndSendChangeInternal(@NonNull final String what) {
handleChangeInternal(what);
sendChangeInternal(what);
}
public void initNotification() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
&& !PreferenceUtil.INSTANCE.isClassicNotification()) {
playingNotification = PlayingNotificationImpl.Companion.from(this, notificationManager, mediaSession);
} else {
playingNotification = PlayingNotificationOreo.Companion.from(this, notificationManager);
}
}
public boolean isLastTrack() {
if (getPlayingQueue() != null) {
return getPosition() == getPlayingQueue().size() - 1;
}
return false;
}
public boolean isPausedByTransientLossOfFocus() {
return pausedByTransientLossOfFocus;
}
public void setPausedByTransientLossOfFocus(boolean pausedByTransientLossOfFocus) {
this.pausedByTransientLossOfFocus = pausedByTransientLossOfFocus;
}
public boolean isPlaying() {
return playback != null && playback.isPlaying();
}
public void moveSong(int from, int to) {
if (from == to) {
return;
}
final int currentPosition = getPosition();
Song songToMove = playingQueue.remove(from);
playingQueue.add(to, songToMove);
if (getShuffleMode() == SHUFFLE_MODE_NONE) {
Song tmpSong = originalPlayingQueue.remove(from);
originalPlayingQueue.add(to, tmpSong);
}
if (from > currentPosition && to <= currentPosition) {
position = currentPosition + 1;
} else if (from < currentPosition && to >= currentPosition) {
position = currentPosition - 1;
} else if (from == currentPosition) {
position = to;
}
notifyChange(QUEUE_CHANGED);
}
public void notifyChange(@NonNull final String what) {
handleAndSendChangeInternal(what);
sendPublicIntent(what);
}
@NonNull
@Override
public IBinder onBind(Intent intent) {
// For Android auto, need to call super, or onGetRoot won't be called.
if (intent != null && "android.media.browse.MediaBrowserService".equals(intent.getAction())) {
return super.onBind(intent);
}
return musicBind;
}
@Nullable
@Override
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) {
// Check origin to ensure we're not allowing any arbitrary app to browse app contents
if (!mPackageValidator.isKnownCaller(clientPackageName, clientUid)) {
// Request from an untrusted package: return an empty browser root
return new BrowserRoot(AutoMediaIDHelper.MEDIA_ID_EMPTY_ROOT, null);
} else {
/**
* By default return the browsable root. Treat the EXTRA_RECENT flag as a special case
* and return the recent root instead.
*/
boolean isRecentRequest = false;
if (rootHints != null) {
isRecentRequest = rootHints.getBoolean(EXTRA_RECENT);
}
String browserRootPath;
if (isRecentRequest) {
browserRootPath = AutoMediaIDHelper.RECENT_ROOT;
} else {
browserRootPath = AutoMediaIDHelper.MEDIA_ID_ROOT;
}
return new BrowserRoot(browserRootPath, null);
}
}
@Override
public void onLoadChildren(@NonNull String parentId, @NonNull MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>> result) {
if (parentId.equals(AutoMediaIDHelper.RECENT_ROOT)) {
Song song = getCurrentSong();
MediaBrowserCompat.MediaItem mediaItem = new MediaBrowserCompat.MediaItem(
new MediaDescriptionCompat.Builder()
.setMediaId(String.valueOf(song.getId()))
.setTitle(song.getTitle())
.setSubtitle(song.getArtistName())
.setIconUri(song.getCoverArtUri())
.build(), FLAG_PLAYABLE
);
result.sendResult(Collections.singletonList(mediaItem));
} else {
result.sendResult(mMusicProvider.getChildren(parentId, getResources()));
}
}
@Override
public void onSharedPreferenceChanged(
@NonNull SharedPreferences sharedPreferences, @NonNull String key) {
switch (key) {
case CROSS_FADE_DURATION:
int progress = getSongProgressMillis();
boolean wasPlaying = isPlaying();
/* Switch to MultiPlayer if Crossfade duration is 0 and
Playback is not an instance of MultiPlayer */
if (playback != null)
playback.setCrossFadeDuration(PreferenceUtil.INSTANCE.getCrossFadeDuration());
if (!(playback instanceof MultiPlayer) && PreferenceUtil.INSTANCE.getCrossFadeDuration() == 0) {
if (playback != null) {
playback.release();
}
playback = null;
playback = new ExoPlayerPlayback(this);
playback.setCallbacks(this);
if (openTrackAndPrepareNextAt(position)) {
seek(progress);
if (wasPlaying) {
play();
}
}
}
/* Switch to CrossFadePlayer if Crossfade duration is greater than 0 and
Playback is not an instance of CrossFadePlayer */
else if (!(playback instanceof CrossFadePlayer) && PreferenceUtil.INSTANCE.getCrossFadeDuration() > 0) {
if (playback != null) {
playback.release();
}
playback = null;
playback = new CrossFadePlayer(this);
playback.setCallbacks(this);
if (openTrackAndPrepareNextAt(position)) {
seek(progress);
if (wasPlaying) {
play();
}
}
}
break;
case ALBUM_ART_ON_LOCK_SCREEN:
case BLURRED_ALBUM_ART:
updateMediaSessionMetaData();
break;
case COLORED_NOTIFICATION:
updateNotification();
break;
case CLASSIC_NOTIFICATION:
initNotification();
updateNotification();
break;
case TOGGLE_HEADSET:
registerHeadsetEvents();
break;
}
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
if (intent != null && intent.getAction() != null) {
restoreQueuesAndPositionIfNecessary();
String action = intent.getAction();
switch (action) {
case ACTION_TOGGLE_PAUSE:
if (isPlaying()) {
pause();
} else {
play();
}
break;
case ACTION_PAUSE:
pause();
break;
case ACTION_PLAY:
play();
break;
case ACTION_PLAY_PLAYLIST:
playFromPlaylist(intent);
break;
case ACTION_REWIND:
back(true);
break;
case ACTION_SKIP:
playNextSong(true);
break;
case ACTION_STOP:
case ACTION_QUIT:
pendingQuit = false;
quit();
break;
case ACTION_PENDING_QUIT:
pendingQuit = true;
break;
case TOGGLE_FAVORITE:
MusicUtil.INSTANCE.toggleFavorite(getApplicationContext(), getCurrentSong());
break;
}
}
return START_NOT_STICKY;
}
@Override
public void onTrackEnded() {
acquireWakeLock(30000);
playerHandler.sendEmptyMessage(TRACK_ENDED);
}
@Override
public void onTrackEndedWithCrossfade() {
trackEndedByCrossfade = true;
acquireWakeLock(30000);
playerHandler.sendEmptyMessage(TRACK_ENDED);
}
@Override
public void onTrackWentToNext() {
playerHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
}
@Override
public boolean onUnbind(Intent intent) {
if (!isPlaying()) {
stopSelf();
}
return true;
}
public void openQueue(
@Nullable final List<Song> 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/remove songs later
originalPlayingQueue = new ArrayList<>(playingQueue);
this.playingQueue = new ArrayList<>(originalPlayingQueue);
int position = startPosition;
if (shuffleMode == SHUFFLE_MODE_SHUFFLE) {
ShuffleHelper.INSTANCE.makeShuffleList(this.playingQueue, startPosition);
position = 0;
}
if (startPlaying) {
playSongAt(position);
} else {
setPosition(position);
}
notifyChange(QUEUE_CHANGED);
}
}
public boolean openTrackAndPrepareNextAt(int position) {
synchronized (this) {
this.position = position;
boolean prepared = openCurrent();
if (prepared) {
prepareNextImpl();
}
notifyChange(META_CHANGED);
notHandledMetaChangedForCurrentTrack = false;
return prepared;
}
}
public void pause() {
Log.i(TAG, "Paused");
pausedByTransientLossOfFocus = false;
if (playback != null && playback.isPlaying()) {
startFadeAnimator(playback, false, () -> {
//Code to run when Animator Ends
if (playback != null) {
playback.pause();
}
notifyChange(PLAY_STATE_CHANGED);
});
}
}
public void forcePause() {
pausedByTransientLossOfFocus = false;
if (playback != null && playback.isPlaying()) {
playback.pause();
notifyChange(PLAY_STATE_CHANGED);
}
}
public void play() {
synchronized (this) {
if (requestFocus()) {
if (playback != null && !playback.isPlaying()) {
if (!playback.isInitialized()) {
playSongAt(getPosition());
} else {
//Don't Start playing when it's casting
if (MusicPlayerRemote.INSTANCE.isCasting()) {
return;
}
startFadeAnimator(playback, true, () -> {
// Code when Animator Ends
if (!becomingNoisyReceiverRegistered) {
registerReceiver(becomingNoisyReceiver, becomingNoisyReceiverIntentFilter);
becomingNoisyReceiverRegistered = true;
}
if (notHandledMetaChangedForCurrentTrack) {
handleChangeInternal(META_CHANGED);
notHandledMetaChangedForCurrentTrack = false;
}
// fixes a bug where the volume would stay ducked because the
// AudioManager.AUDIOFOCUS_GAIN event is not sent
playerHandler.removeMessages(DUCK);
playerHandler.sendEmptyMessage(UNDUCK);
});
//Start Playback with Animator
playback.start();
notifyChange(PLAY_STATE_CHANGED);
}
}
} else {
Toast.makeText(
this, getResources().getString(R.string.audio_focus_denied), Toast.LENGTH_SHORT)
.show();
}
}
}
public void playNextSong(boolean force) {
playSongAt(getNextPosition(force));
}
public void playPreviousSong(boolean force) {
playSongAt(getPreviousPosition(force));
}
public void playSongAt(final int position) {
// handle this on the handlers thread to avoid blocking the ui thread
playerHandler.removeMessages(PLAY_SONG);
playerHandler.obtainMessage(PLAY_SONG, position, 0).sendToTarget();
}
public void playSongAtImpl(int position) {
if (!trackEndedByCrossfade) {
// This is only imp if we are using crossfade
if (playback instanceof CrossFadePlayer) {
((CrossFadePlayer) playback).sourceChangedByUser();
}
} else {
trackEndedByCrossfade = false;
}
if (openTrackAndPrepareNextAt(position)) {
play();
} else {
Toast.makeText(this, getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT)
.show();
}
}
public void playSongs(ArrayList<Song> songs, int shuffleMode) {
if (songs != null && !songs.isEmpty()) {
if (shuffleMode == SHUFFLE_MODE_SHUFFLE) {
int startPosition = new Random().nextInt(songs.size());
openQueue(songs, startPosition, false);
setShuffleMode(shuffleMode);
} else {
openQueue(songs, 0, false);
}
play();
} else {
Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show();
}
}
public void prepareNextImpl() {
synchronized (this) {
try {
int nextPosition = getNextPosition(false);
if (playback != null) {
playback.setNextDataSource(getTrackUri(Objects.requireNonNull(getSongAt(nextPosition))));
}
this.nextPosition = nextPosition;
} catch (Exception ignored) {
}
}
}
public void quit() {
pause();
stopForeground(true);
notificationManager.cancel(PlayingNotification.NOTIFICATION_ID);
closeAudioEffectSession();
getAudioManager().abandonAudioFocus(audioFocusListener);
stopSelf();
}
public void releaseWakeLock() {
if (wakeLock.isHeld()) {
wakeLock.release();
}
}
public void removeSong(int position) {
if (getShuffleMode() == SHUFFLE_MODE_NONE) {
playingQueue.remove(position);
originalPlayingQueue.remove(position);
} else {
originalPlayingQueue.remove(playingQueue.remove(position));
}
rePosition(position);
notifyChange(QUEUE_CHANGED);
}
public void removeSongImpl(@NonNull Song song) {
for (int i = 0; i < playingQueue.size(); i++) {
if (playingQueue.get(i).getId() == song.getId()) {
playingQueue.remove(i);
rePosition(i);
}
}
for (int i = 0; i < originalPlayingQueue.size(); i++) {
if (originalPlayingQueue.get(i).getId() == song.getId()) {
originalPlayingQueue.remove(i);
}
}
}
public void removeSong(@NonNull Song song) {
removeSongImpl(song);
notifyChange(QUEUE_CHANGED);
}
public void removeSongs(@NonNull List<Song> songs) {
for (Song song : songs) {
removeSongImpl(song);
}
notifyChange(QUEUE_CHANGED);
}
public synchronized void restoreQueuesAndPositionIfNecessary() {
if (!queuesRestored && playingQueue.isEmpty()) {
List<Song> restoredQueue = MusicPlaybackQueueStore.getInstance(this).getSavedPlayingQueue();
List<Song> restoredOriginalQueue =
MusicPlaybackQueueStore.getInstance(this).getSavedOriginalPlayingQueue();
int restoredPosition =
PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION, -1);
int restoredPositionInTrack =
PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION_IN_TRACK, -1);
if (restoredQueue.size() > 0
&& restoredQueue.size() == restoredOriginalQueue.size()
&& restoredPosition != -1) {
this.originalPlayingQueue = restoredOriginalQueue;
this.playingQueue = restoredQueue;
position = restoredPosition;
openCurrent();
prepareNext();
if (restoredPositionInTrack > 0) {
seek(restoredPositionInTrack);
}
notHandledMetaChangedForCurrentTrack = true;
sendChangeInternal(META_CHANGED);
sendChangeInternal(QUEUE_CHANGED);
}
}
queuesRestored = true;
}
public void runOnUiThread(Runnable runnable) {
uiThreadHandler.post(runnable);
}
public void savePositionInTrack() {
PreferenceManager.getDefaultSharedPreferences(this)
.edit()
.putInt(SAVED_POSITION_IN_TRACK, getSongProgressMillis())
.apply();
}
public void saveQueuesImpl() {
MusicPlaybackQueueStore.getInstance(this).saveQueues(playingQueue, originalPlayingQueue);
}
public void saveState() {
saveQueues();
savePosition();
savePositionInTrack();
}
public int seek(int millis) {
synchronized (this) {
try {
int newPosition = 0;
if (playback != null) {
newPosition = playback.seek(millis);
}
throttledSeekHandler.notifySeek();
return newPosition;
} catch (Exception e) {
return -1;
}
}
}
// to let other apps know whats playing. i.E. last.fm (scrobbling) or musixmatch
public void sendPublicIntent(@NonNull final String what) {
final Intent intent = new Intent(what.replace(RETRO_MUSIC_PACKAGE_NAME, MUSIC_PACKAGE_NAME));
final Song song = getCurrentSong();
if (song != null) {
intent.putExtra("id", song.getId());
intent.putExtra("artist", song.getArtistName());
intent.putExtra("album", song.getAlbumName());
intent.putExtra("track", song.getTitle());
intent.putExtra("duration", song.getDuration());
intent.putExtra("position", (long) getSongProgressMillis());
intent.putExtra("playing", isPlaying());
intent.putExtra("scrobbling_source", RETRO_MUSIC_PACKAGE_NAME);
sendStickyBroadcast(intent);
}
}
public void toggleShuffle() {
if (getShuffleMode() == SHUFFLE_MODE_NONE) {
setShuffleMode(SHUFFLE_MODE_SHUFFLE);
} else {
setShuffleMode(SHUFFLE_MODE_NONE);
}
}
public void updateMediaSessionPlaybackState() {
PlaybackStateCompat.Builder stateBuilder =
new PlaybackStateCompat.Builder()
.setActions(MEDIA_SESSION_ACTIONS)
.setState(
isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED,
getSongProgressMillis(),
1);
setCustomAction(stateBuilder);
mediaSession.setPlaybackState(stateBuilder.build());
}
public void updateNotification() {
if (playingNotification != null && getCurrentSong().getId() != -1) {
stopForegroundAndNotification();
initNotification();
}
}
public void updateMediaSessionMetaData() {
Log.i(TAG, "onResourceReady: ");
final Song song = getCurrentSong();
if (song.getId() == -1) {
mediaSession.setMetadata(null);
return;
}
final MediaMetadataCompat.Builder metaData = new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.getArtistName())
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, song.getArtistName())
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.getAlbumName())
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.getTitle())
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.getDuration())
.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, getPosition() + 1)
.putLong(MediaMetadataCompat.METADATA_KEY_YEAR, song.getYear())
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, null);
metaData.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getPlayingQueue().size());
if (PreferenceUtil.INSTANCE.isAlbumArtOnLockScreen()) {
final Point screenSize = RetroUtil.getScreenSize(MusicService.this);
final RequestBuilder<Bitmap> request = GlideApp.with(MusicService.this).asBitmap().songCoverOptions(song).load(RetroGlideExtension.INSTANCE.getSongModel(song));
if (PreferenceUtil.INSTANCE.isBlurredAlbumArt()) {
request.transform(new BlurTransformation.Builder(MusicService.this).build());
}
runOnUiThread(new Runnable() {
@Override
public void run() {
request.into(new SimpleTarget<Bitmap>(screenSize.x, screenSize.y) {
@Override
public void onLoadFailed(Drawable errorDrawable) {
super.onLoadFailed(errorDrawable);
mediaSession.setMetadata(metaData.build());
}
@Override
public void onResourceReady(@NonNull Bitmap resource, @Nullable com.bumptech.glide.request.transition.Transition<? super Bitmap> transition) {
metaData.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, copy(resource));
mediaSession.setMetadata(metaData.build());
}
});
}
});
} else {
mediaSession.setMetadata(metaData.build());
}
}
private void closeAudioEffectSession() {
final Intent audioEffectsIntent =
new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
if (playback != null) {
audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, playback.getAudioSessionId());
}
audioEffectsIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
sendBroadcast(audioEffectsIntent);
}
private AudioManager getAudioManager() {
if (audioManager == null) {
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
}
return audioManager;
}
private void handleChangeInternal(@NonNull final String what) {
switch (what) {
case PLAY_STATE_CHANGED:
updateMediaSessionPlaybackState();
final boolean isPlaying = isPlaying();
if (!isPlaying && getSongProgressMillis() > 0) {
savePositionInTrack();
}
songPlayCountHelper.notifyPlayStateChanged(isPlaying);
playingNotification.setPlaying(isPlaying);
startForegroundOrNotify();
break;
case FAVORITE_STATE_CHANGED:
playingNotification.updateFavorite(getCurrentSong(), this::startForegroundOrNotify);
case META_CHANGED:
playingNotification.updateMetadata(getCurrentSong(), this::startForegroundOrNotify);
updateMediaSessionMetaData();
updateMediaSessionPlaybackState();
savePosition();
savePositionInTrack();
final Song currentSong = getCurrentSong();
HistoryStore.getInstance(this).addSongId(currentSong.getId());
if (songPlayCountHelper.shouldBumpPlayCount()) {
SongPlayCountStore.getInstance(this).bumpPlayCount(songPlayCountHelper.getSong().getId());
}
songPlayCountHelper.notifySongChanged(currentSong);
break;
case QUEUE_CHANGED:
updateMediaSessionMetaData(); // because playing queue size might have changed
saveState();
if (playingQueue.size() > 0) {
prepareNext();
} else {
stopForegroundAndNotification();
}
break;
}
}
private Unit startForegroundOrNotify() {
if (!isForeground) {
// Specify that this is a media service, if supported.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(
PlayingNotification.NOTIFICATION_ID, playingNotification.build(),
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
);
} else {
startForeground(PlayingNotification.NOTIFICATION_ID, playingNotification.build());
}
isForeground = true;
} else {
// If we are already in foreground just update the notification
notificationManager.notify(
PlayingNotification.NOTIFICATION_ID, playingNotification.build()
);
}
return Unit.INSTANCE;
}
private void stopForegroundAndNotification() {
stopForeground(true);
notificationManager.cancel(PlayingNotification.NOTIFICATION_ID);
isForeground = false;
}
private boolean openCurrent() {
synchronized (this) {
try {
if (playback != null) {
return playback.setDataSource(getTrackUri(Objects.requireNonNull(getCurrentSong())));
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
return false;
}
private void playFromPlaylist(Intent intent) {
AbsSmartPlaylist playlist = intent.getParcelableExtra(INTENT_EXTRA_PLAYLIST);
int shuffleMode = intent.getIntExtra(INTENT_EXTRA_SHUFFLE_MODE, getShuffleMode());
if (playlist != null) {
List<Song> playlistSongs = playlist.songs();
if (!playlistSongs.isEmpty()) {
if (shuffleMode == SHUFFLE_MODE_SHUFFLE) {
int startPosition = new Random().nextInt(playlistSongs.size());
openQueue(playlistSongs, startPosition, true);
setShuffleMode(shuffleMode);
} else {
openQueue(playlistSongs, 0, true);
}
} else {
Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG)
.show();
}
} else {
Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show();
}
}
private void prepareNext() {
playerHandler.removeMessages(PREPARE_NEXT);
playerHandler.obtainMessage(PREPARE_NEXT).sendToTarget();
}
private void rePosition(int deletedPosition) {
int currentPosition = getPosition();
if (deletedPosition < currentPosition) {
position = currentPosition - 1;
} else if (deletedPosition == currentPosition) {
if (playingQueue.size() > deletedPosition) {
setPosition(position);
} else {
setPosition(position - 1);
}
}
}
private void registerBluetoothConnected() {
Log.i(TAG, "registerBluetoothConnected: ");
if (!bluetoothConnectedRegistered) {
registerReceiver(bluetoothReceiver, bluetoothConnectedIntentFilter);
bluetoothConnectedRegistered = true;
}
}
private void registerHeadsetEvents() {
if (!headsetReceiverRegistered && PreferenceUtil.INSTANCE.isHeadsetPlugged()) {
registerReceiver(headsetReceiver, headsetReceiverIntentFilter);
headsetReceiverRegistered = true;
}
}
private void releaseResources() {
playerHandler.removeCallbacksAndMessages(null);
musicPlayerHandlerThread.quitSafely();
queueSaveHandler.removeCallbacksAndMessages(null);
queueSaveHandlerThread.quitSafely();
if (playback != null) {
playback.release();
}
playback = null;
mediaSession.release();
}
private boolean requestFocus() {
return (getAudioManager()
.requestAudioFocus(
audioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN)
== AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
}
private void restoreState() {
shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_SHUFFLE_MODE, 0);
repeatMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_REPEAT_MODE, 0);
handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED);
handleAndSendChangeInternal(REPEAT_MODE_CHANGED);
playerHandler.removeMessages(RESTORE_QUEUES);
playerHandler.sendEmptyMessage(RESTORE_QUEUES);
}
private void savePosition() {
PreferenceManager.getDefaultSharedPreferences(this)
.edit()
.putInt(SAVED_POSITION, getPosition())
.apply();
}
private void saveQueues() {
queueSaveHandler.removeMessages(SAVE_QUEUES);
queueSaveHandler.sendEmptyMessage(SAVE_QUEUES);
}
private void sendChangeInternal(final String what) {
sendBroadcast(new Intent(what));
appWidgetBig.notifyChange(this, what);
appWidgetClassic.notifyChange(this, what);
appWidgetSmall.notifyChange(this, what);
appWidgetCard.notifyChange(this, what);
appWidgetText.notifyChange(this, what);
appWidgetMd3.notifyChange(this, what);
}
private void setCustomAction(PlaybackStateCompat.Builder stateBuilder) {
int repeatIcon = R.drawable.ic_repeat; // REPEAT_MODE_NONE
if (getRepeatMode() == REPEAT_MODE_THIS) {
repeatIcon = R.drawable.ic_repeat_one;
} else if (getRepeatMode() == REPEAT_MODE_ALL) {
repeatIcon = R.drawable.ic_repeat_white_circle;
}
stateBuilder.addCustomAction(
new PlaybackStateCompat.CustomAction.Builder(
CYCLE_REPEAT, getString(R.string.action_cycle_repeat), repeatIcon)
.build());
final int shuffleIcon =
getShuffleMode() == SHUFFLE_MODE_NONE
? R.drawable.ic_shuffle_off_circled
: R.drawable.ic_shuffle_on_circled;
stateBuilder.addCustomAction(
new PlaybackStateCompat.CustomAction.Builder(
TOGGLE_SHUFFLE, getString(R.string.action_toggle_shuffle), shuffleIcon)
.build());
final int favoriteIcon =
MusicUtil.INSTANCE.isFavorite(getApplicationContext(), getCurrentSong())
? R.drawable.ic_favorite
: R.drawable.ic_favorite_border;
stateBuilder.addCustomAction(
new PlaybackStateCompat.CustomAction.Builder(
TOGGLE_FAVORITE, getString(R.string.action_toggle_favorite), favoriteIcon)
.build());
}
private void setupMediaSession() {
ComponentName mediaButtonReceiverComponentName =
new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class);
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setComponent(mediaButtonReceiverComponentName);
PendingIntent mediaButtonReceiverPendingIntent;
mediaButtonReceiverPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent,
VersionUtils.INSTANCE.hasMarshmallow() ? PendingIntent.FLAG_IMMUTABLE : 0);
// TODO: change tag
mediaSession = new MediaSessionCompat(
this,
"RetroMusicPlayer",
mediaButtonReceiverComponentName,
mediaButtonReceiverPendingIntent);
MediaSessionCallback mediasessionCallback =
new MediaSessionCallback(getApplicationContext(), this);
mediaSession.setCallback(mediasessionCallback);
mediaSession.setActive(true);
mediaSession.setMediaButtonReceiver(mediaButtonReceiverPendingIntent);
}
public class MusicBinder extends Binder {
@NonNull
public MusicService getService() {
return MusicService.this;
}
}
}