Deleted all references to the old version of exoplayer from services and helpers

This commit is contained in:
CappielloAntonio 2021-12-29 10:14:22 +01:00
parent f9ac2f2646
commit 7a3cdb8806
6 changed files with 3 additions and 1544 deletions

View file

@ -1,218 +0,0 @@
/*
* Copyright (C) 2007 The Android Open Source Project Licensed under the Apache
* License, Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
* or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
// Modified for Phonograph by Karim Abou Zeid (kabouzeid).
package com.cappielloantonio.play.broadcast.receiver;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import android.view.KeyEvent;
import androidx.core.content.ContextCompat;
import com.cappielloantonio.play.BuildConfig;
import com.cappielloantonio.play.service.MusicService;
/**
* Used to control headset playback.
* Single press: pause/resume
* Double press: next track
* Triple press: previous track
*/
public class MediaButtonIntentReceiver extends BroadcastReceiver {
public static final String TAG = MediaButtonIntentReceiver.class.getSimpleName();
private static final boolean DEBUG = BuildConfig.DEBUG;
private static final int MSG_HEADSET_DOUBLE_CLICK_TIMEOUT = 2;
private static final int DOUBLE_CLICK = 400;
private static WakeLock mWakeLock = null;
private static int mClickCounter = 0;
private static long mLastClickTime = 0;
@SuppressLint("HandlerLeak")
private static final Handler mHandler = new Handler() {
@Override
public void handleMessage(final Message msg) {
switch (msg.what) {
case MSG_HEADSET_DOUBLE_CLICK_TIMEOUT:
final int clickCount = msg.arg1;
final String command;
if (DEBUG) Log.v(TAG, "Handling headset click, count = " + clickCount);
switch (clickCount) {
case 1:
command = MusicService.ACTION_TOGGLE;
break;
case 2:
command = MusicService.ACTION_SKIP;
break;
case 3:
command = MusicService.ACTION_REWIND;
break;
default:
command = null;
break;
}
if (command != null) {
final Context context = (Context) msg.obj;
startService(context, command);
}
break;
}
releaseWakeLockIfHandlerIdle();
}
};
public static boolean handleIntent(final Context context, final Intent intent) {
final String intentAction = intent.getAction();
if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
final KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (event == null) {
return false;
}
final int keycode = event.getKeyCode();
final int action = event.getAction();
// fallback to system time if event time is not available
final long eventTime = event.getEventTime() != 0
? event.getEventTime()
: System.currentTimeMillis();
String command = null;
switch (keycode) {
case KeyEvent.KEYCODE_MEDIA_STOP:
command = MusicService.ACTION_STOP;
break;
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
command = MusicService.ACTION_TOGGLE;
break;
case KeyEvent.KEYCODE_MEDIA_NEXT:
command = MusicService.ACTION_SKIP;
break;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
command = MusicService.ACTION_REWIND;
break;
case KeyEvent.KEYCODE_MEDIA_PAUSE:
command = MusicService.ACTION_PAUSE;
break;
case KeyEvent.KEYCODE_MEDIA_PLAY:
command = MusicService.ACTION_PLAY;
break;
}
if (command != null) {
if (action == KeyEvent.ACTION_DOWN) {
if (event.getRepeatCount() == 0) {
// Only consider the first event in a sequence, not the repeat events,
// so that we don't trigger in cases where the first event went to
// a different app (e.g. when the user ends a phone call by
// long pressing the headset button)
// The service may or may not be running, but we need to send it
// a command.
if (keycode == KeyEvent.KEYCODE_HEADSETHOOK || keycode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
if (eventTime - mLastClickTime >= DOUBLE_CLICK) {
mClickCounter = 0;
}
mClickCounter++;
if (DEBUG) Log.v(TAG, "Got headset click, count = " + mClickCounter);
mHandler.removeMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT);
Message msg = mHandler.obtainMessage(
MSG_HEADSET_DOUBLE_CLICK_TIMEOUT, mClickCounter, 0, context);
long delay = mClickCounter < 3 ? DOUBLE_CLICK : 0;
if (mClickCounter >= 3) {
mClickCounter = 0;
}
mLastClickTime = eventTime;
acquireWakeLockAndSendMessage(context, msg, delay);
} else {
startService(context, command);
}
return true;
}
}
}
}
return false;
}
private static void startService(Context context, String command) {
final Intent intent = new Intent(context, MusicService.class);
intent.setAction(command);
try {
// IMPORTANT NOTE: (kind of a hack)
// on Android O and above the following crashes when the app is not running
// there is no good way to check whether the app is running so we catch the exception
// we do not always want to use startForegroundService() because then one gets an ANR
// if no notification is displayed via startForeground()
// according to Play analytics this happens a lot, I suppose for example if command = PAUSE
context.startService(intent);
} catch (IllegalStateException ignored) {
ContextCompat.startForegroundService(context, intent);
}
}
private static void acquireWakeLockAndSendMessage(Context context, Message msg, long delay) {
if (mWakeLock == null) {
Context appContext = context.getApplicationContext();
PowerManager pm = (PowerManager) appContext.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, context.getClass().getName());
mWakeLock.setReferenceCounted(false);
}
if (DEBUG) Log.v(TAG, "Acquiring wake lock and sending " + msg.what);
mWakeLock.acquire(10000);
mHandler.sendMessageDelayed(msg, delay);
}
private static void releaseWakeLockIfHandlerIdle() {
if (mHandler.hasMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT)) {
if (DEBUG) Log.v(TAG, "Handler still has messages pending, not releasing wake lock");
return;
}
if (mWakeLock != null) {
if (DEBUG) Log.v(TAG, "Releasing wake lock");
mWakeLock.release();
mWakeLock = null;
}
}
@Override
public void onReceive(final Context context, final Intent intent) {
if (DEBUG) Log.v(TAG, "Received intent: " + intent);
if (handleIntent(context, intent) && isOrderedBroadcast()) {
abortBroadcast();
}
}
}

View file

@ -1,67 +0,0 @@
package com.cappielloantonio.play.helper;
import android.os.Handler;
import android.os.Message;
import androidx.annotation.NonNull;
import com.cappielloantonio.play.service.MusicPlayerRemote;
public class MusicProgressViewUpdateHelper extends Handler {
private static final int CMD_REFRESH_PROGRESS_VIEWS = 1;
private static final int MIN_INTERVAL = 20;
private static final int UPDATE_INTERVAL_PLAYING = 1000;
private static final int UPDATE_INTERVAL_PAUSED = 500;
private final Callback callback;
private final int intervalPlaying;
private final int intervalPaused;
public MusicProgressViewUpdateHelper(Callback callback) {
this.callback = callback;
this.intervalPlaying = UPDATE_INTERVAL_PLAYING;
this.intervalPaused = UPDATE_INTERVAL_PAUSED;
}
public void start() {
queueNextRefresh(1);
}
public void stop() {
removeMessages(CMD_REFRESH_PROGRESS_VIEWS);
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if (msg.what == CMD_REFRESH_PROGRESS_VIEWS) {
queueNextRefresh(refreshProgressViews());
}
}
private int refreshProgressViews() {
final int progressMillis = MusicPlayerRemote.getSongProgressMillis();
final int totalMillis = MusicPlayerRemote.getSongDurationMillis();
callback.onUpdateProgressViews(progressMillis, totalMillis);
if (!MusicPlayerRemote.isPlaying()) {
return intervalPaused;
}
final int remainingMillis = intervalPlaying - progressMillis % intervalPlaying;
return Math.max(MIN_INTERVAL, remainingMillis);
}
private void queueNextRefresh(final long delay) {
final Message message = obtainMessage(CMD_REFRESH_PROGRESS_VIEWS);
removeMessages(CMD_REFRESH_PROGRESS_VIEWS);
sendMessageDelayed(message, delay);
}
public interface Callback {
void onUpdateProgressViews(int progress, int total);
}
}

View file

@ -1,213 +0,0 @@
package com.cappielloantonio.play.service;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.interfaces.Playback;
import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.util.CacheUtil;
import com.cappielloantonio.play.util.DownloadUtil;
import com.cappielloantonio.play.util.MusicUtil;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
public class MultiPlayer implements Playback {
public static final String TAG = MultiPlayer.class.getSimpleName();
private final Context context;
private final SimpleExoPlayer exoPlayer;
private final SimpleCache simpleCache;
private PlaybackCallbacks callbacks;
private final ExoPlayer.EventListener eventListener = new ExoPlayer.EventListener() {
@Override
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 onPlaybackSuppressionReasonChanged(@Player.PlaybackSuppressionReason int playbackSuppressionReason) {
Log.i(TAG, String.format("onPlaybackSuppressionReasonChanged: %d", playbackSuppressionReason));
if (callbacks != null) {
callbacks.onStateChanged(Player.STATE_READY);
}
}
@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, String.format("onPositionDiscontinuity: %d", reason));
}
@Override
public void onPlayerError(ExoPlaybackException error) {
Log.i(TAG, String.format("onPlayerError: %s", error.getMessage()));
Toast.makeText(context, context.getResources().getString(R.string.exo_info_unplayable_file), Toast.LENGTH_SHORT).show();
exoPlayer.clearMediaItems();
exoPlayer.prepare();
}
};
public MultiPlayer(Context context) {
this.context = context;
this.simpleCache = CacheUtil.getCache(context);
DataSource.Factory downloadDataSourceFactory = new CacheDataSource.Factory()
.setCache(DownloadUtil.getDownloadCache(context))
.setUpstreamDataSourceFactory(DownloadUtil.getHttpDataSourceFactory())
.setCacheWriteDataSinkFactory(null); // Disable writing.
DataSource.Factory cacheDataSourceFactory = new CacheDataSource.Factory()
.setCache(simpleCache)
.setUpstreamDataSourceFactory(CacheUtil.getHttpDataSourceFactory());
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setUsage(C.USAGE_MEDIA)
.setContentType(C.CONTENT_TYPE_MUSIC)
.build();
/* TODO: Capire come affiancare due media source factory */
exoPlayer = new SimpleExoPlayer.Builder(context)
.setMediaSourceFactory(new DefaultMediaSourceFactory(cacheDataSourceFactory))
.setMediaSourceFactory(new DefaultMediaSourceFactory(downloadDataSourceFactory))
.setAudioAttributes(audioAttributes, true)
.setHandleAudioBecomingNoisy(true)
.setWakeMode(C.WAKE_MODE_NETWORK)
.build();
exoPlayer.addListener(eventListener);
exoPlayer.prepare();
}
@Override
public void setDataSource(Song song) {
String uri = MusicUtil.getSongStreamUri(context, song);
MediaItem mediaItem = exoPlayer.getCurrentMediaItem();
if (mediaItem != null && mediaItem.playbackProperties != null && mediaItem.playbackProperties.uri.toString().equals(uri)) {
return;
}
exoPlayer.clearMediaItems();
appendDataSource(MusicUtil.getSongStreamUri(context, song));
exoPlayer.seekTo(0, 0);
}
@Override
public void queueDataSource(Song song) {
while (exoPlayer.getMediaItemCount() > 1) {
exoPlayer.removeMediaItem(1);
}
appendDataSource(MusicUtil.getSongStreamUri(context, song));
}
private void appendDataSource(String path) {
Uri uri = Uri.parse(path);
MediaItem mediaItem = MediaItem.fromUri(uri);
exoPlayer.addMediaItem(mediaItem);
}
@Override
public void setCallbacks(Playback.PlaybackCallbacks callbacks) {
this.callbacks = callbacks;
}
@Override
public boolean isReady() {
return exoPlayer.getPlayWhenReady();
}
@Override
public boolean isPlaying() {
return exoPlayer.getPlayWhenReady() && exoPlayer.getPlaybackSuppressionReason() == Player.PLAYBACK_SUPPRESSION_REASON_NONE;
}
@Override
public boolean isLoading() {
return exoPlayer.getPlaybackState() == Player.STATE_BUFFERING;
}
@Override
public void start() {
exoPlayer.setPlayWhenReady(true);
}
@Override
public void pause() {
exoPlayer.setPlayWhenReady(false);
}
@Override
public void stop() {
simpleCache.release();
exoPlayer.release();
}
@Override
public int getProgress() {
return (int) exoPlayer.getCurrentPosition();
}
@Override
public void setProgress(int progress) {
exoPlayer.seekTo(progress);
}
@Override
public int getDuration() {
return (int) exoPlayer.getDuration();
}
@Override
public int getVolume() {
return (int) (exoPlayer.getVolume() * 100);
}
@Override
public void setVolume(int volume) {
exoPlayer.setVolume(volume / 100f);
}
}

View file

@ -1,325 +0,0 @@
package com.cappielloantonio.play.service;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.cappielloantonio.play.App;
import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.repository.QueueRepository;
import java.util.ArrayList;
import java.util.List;
import java.util.WeakHashMap;
public class MusicPlayerRemote {
private static final String TAG = "MusicPlayerRemote";
private static final WeakHashMap<Context, ServiceBinder> mConnectionMap = new WeakHashMap<>();
public static MusicService musicService;
public static ServiceToken bindToService(@NonNull final Context context, final ServiceConnection callback) {
Activity realActivity = ((Activity) context).getParent();
if (realActivity == null) {
realActivity = (Activity) context;
}
final ContextWrapper contextWrapper = new ContextWrapper(realActivity);
contextWrapper.startService(new Intent(contextWrapper, MusicService.class));
final ServiceBinder binder = new ServiceBinder(callback);
if (contextWrapper.bindService(new Intent().setClass(contextWrapper, MusicService.class), binder, Context.BIND_AUTO_CREATE)) {
mConnectionMap.put(contextWrapper, binder);
return new ServiceToken(contextWrapper);
}
return null;
}
public static void unbindFromService(@Nullable final ServiceToken token) {
if (token == null) {
return;
}
final ContextWrapper mContextWrapper = token.mWrappedContext;
final ServiceBinder mBinder = mConnectionMap.remove(mContextWrapper);
if (mBinder == null) {
return;
}
mContextWrapper.unbindService(mBinder);
if (mConnectionMap.isEmpty()) {
musicService = null;
}
}
public static void playSongAt(final int position) {
if (musicService != null) {
musicService.playSongAt(position);
}
}
public static void pauseSong() {
if (musicService != null) {
musicService.pause();
}
}
public static void playNextSong() {
if (musicService != null) {
musicService.playNextSong();
}
}
public static void playPreviousSong() {
if (musicService != null) {
musicService.playPreviousSong(true);
}
}
public static void back() {
if (musicService != null) {
musicService.back(true);
}
}
public static boolean isPlaying() {
return musicService != null && musicService.isPlaying();
}
public static boolean isLoading() {
return musicService != null && musicService.isLoading();
}
public static void quitPlaying() {
if (musicService != null) {
musicService.quitPlaying();
}
}
public static void resumePlaying() {
if (musicService != null) {
musicService.play();
}
}
public static void openQueue(final List<Song> queue, final int startPosition, final boolean startPlaying) {
if (!tryToHandleOpenPlayingQueue(queue, startPosition, startPlaying) && musicService != null) {
musicService.openQueue(queue, startPosition, startPlaying);
}
}
private static boolean tryToHandleOpenPlayingQueue(final List<Song> queue, final int startPosition, final boolean startPlaying) {
if (getPlayingQueue() == queue) {
if (startPlaying) {
playSongAt(startPosition);
} else {
setPosition(startPosition);
}
return true;
}
return false;
}
public static Song getCurrentSong() {
if (musicService != null) {
return musicService.getCurrentSong();
}
return null;
}
public static int getPosition() {
if (musicService != null) {
return musicService.getPosition();
}
return -1;
}
public static void setPosition(final int position) {
if (musicService != null) {
musicService.setPosition(position);
}
}
public static List<Song> getPlayingQueue() {
if (musicService != null) {
return musicService.getPlayingQueue();
}
return new ArrayList<>();
}
public static int getSongProgressMillis() {
if (musicService != null) {
return musicService.getSongProgressMillis();
}
return -1;
}
public static int getSongDurationMillis() {
if (musicService != null) {
return musicService.getSongDurationMillis();
}
return -1;
}
public static int seekTo(int millis) {
if (musicService != null) {
return musicService.seek(millis);
}
return -1;
}
public static boolean playNext(Song song) {
if (musicService != null) {
QueueRepository queueRepository = new QueueRepository(App.getInstance());
if (getPlayingQueue().size() > 0) {
musicService.addSong(getPosition() + 1, song);
queueRepository.insertAllAndStartNew(getPlayingQueue());
} else {
List<Song> songToEnqueue = new ArrayList<>();
songToEnqueue.add(song);
queueRepository.insertAllAndStartNew(songToEnqueue);
openQueue(songToEnqueue, 0, true);
}
return true;
}
return false;
}
public static boolean playNext(@NonNull List<Song> songs) {
if (musicService != null) {
QueueRepository queueRepository = new QueueRepository(App.getInstance());
if (getPlayingQueue().size() > 0) {
musicService.addSongs(getPosition() + 1, songs);
queueRepository.insertAllAndStartNew(getPlayingQueue());
} else {
List<Song> songToEnqueue = new ArrayList<>();
songToEnqueue.addAll(songs);
queueRepository.insertAllAndStartNew(songToEnqueue);
openQueue(songToEnqueue, 0, true);
}
return true;
}
return false;
}
public static boolean enqueue(Song song) {
if (musicService != null) {
QueueRepository queueRepository = new QueueRepository(App.getInstance());
if (getPlayingQueue().size() > 0) {
musicService.addSong(song);
queueRepository.insertAllAndStartNew(getPlayingQueue());
} else {
List<Song> songToEnqueue = new ArrayList<>();
songToEnqueue.add(song);
queueRepository.insertAllAndStartNew(songToEnqueue);
openQueue(songToEnqueue, 0, true);
}
return true;
}
return false;
}
public static boolean enqueue(@NonNull List<Song> songs) {
if (musicService != null) {
QueueRepository queueRepository = new QueueRepository(App.getInstance());
if (getPlayingQueue().size() > 0) {
musicService.addSongs(songs);
queueRepository.insertAllAndStartNew(getPlayingQueue());
} else {
List<Song> songToEnqueue = new ArrayList<>();
songToEnqueue.addAll(songs);
queueRepository.insertAllAndStartNew(songToEnqueue);
openQueue(songToEnqueue, 0, true);
}
return true;
}
return false;
}
public static boolean removeFromQueue(int position) {
if (musicService != null && position >= 0 && position < getPlayingQueue().size()) {
musicService.removeSong(position);
return true;
}
return false;
}
public static boolean moveSong(int from, int to) {
if (musicService != null && from >= 0 && to >= 0 && from < getPlayingQueue().size() && to < getPlayingQueue().size()) {
musicService.moveSong(from, to);
return true;
}
return false;
}
public static boolean clearQueue() {
if (musicService != null) {
musicService.clearQueue();
return true;
}
return false;
}
public static final class ServiceBinder implements ServiceConnection {
private final ServiceConnection mCallback;
public ServiceBinder(final ServiceConnection callback) {
mCallback = callback;
}
@Override
public void onServiceConnected(final ComponentName className, final IBinder service) {
MusicService.MusicBinder binder = (MusicService.MusicBinder) service;
musicService = binder.getService();
if (mCallback != null) {
mCallback.onServiceConnected(className, service);
}
}
@Override
public void onServiceDisconnected(final ComponentName className) {
if (mCallback != null) {
mCallback.onServiceDisconnected(className);
}
musicService = null;
}
}
public static final class ServiceToken {
public ContextWrapper mWrappedContext;
public ServiceToken(final ContextWrapper context) {
mWrappedContext = context;
}
}
}

View file

@ -1,718 +0,0 @@
package com.cappielloantonio.play.service;
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 android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.cappielloantonio.play.App;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.broadcast.receiver.MediaButtonIntentReceiver;
import com.cappielloantonio.play.interfaces.Playback;
import com.cappielloantonio.play.model.Playlist;
import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.repository.QueueRepository;
import com.cappielloantonio.play.repository.SongRepository;
import com.cappielloantonio.play.ui.notification.PlayingNotification;
import com.cappielloantonio.play.util.MusicUtil;
import com.cappielloantonio.play.util.PreferenceUtil;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
public class MusicService extends Service implements Playback.PlaybackCallbacks {
private static final String TAG = "MusicService";
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";
public static final String ACTION_PLAY_PLAYLIST = PACKAGE_NAME + ".play.playlist";
public static final String ACTION_PAUSE = PACKAGE_NAME + ".pause";
public static final String ACTION_STOP = PACKAGE_NAME + ".stop";
public static final String ACTION_SKIP = PACKAGE_NAME + ".skip";
public static final String ACTION_REWIND = PACKAGE_NAME + ".rewind";
public static final String ACTION_QUIT = PACKAGE_NAME + ".quit";
public static final String ACTION_PENDING_QUIT = PACKAGE_NAME + ".quit.pending";
public static final String INTENT_EXTRA_PLAYLIST = PACKAGE_NAME + ".extra.playlist";
public static final String STATE_CHANGED = PACKAGE_NAME + ".state.changed";
public static final String META_CHANGED = PACKAGE_NAME + ".meta.changed";
public static final String QUEUE_CHANGED = PACKAGE_NAME + ".queue.changed";
public static final int TRACK_CHANGED = 1;
public static final int TRACK_ENDED = 2;
public static final int PLAY_SONG = 3;
public static final int PREPARE_NEXT = 4;
public static final int SET_POSITION = 5;
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 musicBinder = new MusicBinder();
public boolean pendingQuit = false;
private Playback playback;
private List<Song> playingQueue = new ArrayList<>();
private int position = -1;
private int nextPosition = -1;
private boolean notHandledMetaChangedForCurrentTrack;
private PlayingNotification playingNotification;
private MediaSessionCompat mediaSession;
private PlaybackHandler playerHandler;
private Handler uiThreadHandler;
private ThrottledSeekHandler throttledSeekHandler;
private HandlerThread playerHandlerThread;
@Override
public void onCreate() {
super.onCreate();
playback = new MultiPlayer(this);
playback.setCallbacks(this);
playerHandlerThread = new HandlerThread(PlaybackHandler.class.getName());
playerHandlerThread.start();
playerHandler = new PlaybackHandler(this, playerHandlerThread.getLooper());
throttledSeekHandler = new ThrottledSeekHandler(playerHandler);
uiThreadHandler = new Handler();
initNotification();
initMediaSession();
restoreState();
mediaSession.setActive(true);
}
private void initMediaSession() {
ComponentName mediaButtonReceiverComponentName = new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class);
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setComponent(mediaButtonReceiverComponentName);
PendingIntent mediaButtonReceiverPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, PendingIntent.FLAG_IMMUTABLE);
mediaSession = new MediaSessionCompat(this, getResources().getString(R.string.app_name), mediaButtonReceiverComponentName, mediaButtonReceiverPendingIntent);
mediaSession.setCallback(new MediaSessionCompat.Callback() {
@Override
public void onPlay() {
play();
}
@Override
public void onPause() {
pause();
}
@Override
public void onSkipToNext() {
playNextSong();
}
@Override
public void onSkipToPrevious() {
back(true);
}
@Override
public void onStop() {
quit();
}
@Override
public void onSeekTo(long pos) {
seek((int) pos);
}
@Override
public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
return MediaButtonIntentReceiver.handleIntent(MusicService.this, mediaButtonEvent);
}
});
mediaSession.setMediaButtonReceiver(mediaButtonReceiverPendingIntent);
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
if (intent != null) {
if (intent.getAction() != null) {
String action = intent.getAction();
switch (action) {
case ACTION_TOGGLE:
if (isPlaying()) {
pause();
} else {
play();
}
break;
case ACTION_PAUSE:
pause();
break;
case ACTION_PLAY:
play();
break;
case ACTION_PLAY_PLAYLIST:
Playlist playlist = intent.getParcelableExtra(INTENT_EXTRA_PLAYLIST);
if (playlist != null) {
List<Song> playlistSongs = new ArrayList<>();
if (!playlistSongs.isEmpty()) {
openQueue(playlistSongs, 0, true);
} else {
Toast.makeText(getApplicationContext(), R.string.exo_info_empty_playlist, Toast.LENGTH_LONG).show();
}
} else {
Toast.makeText(getApplicationContext(), R.string.exo_info_empty_playlist, Toast.LENGTH_LONG).show();
}
break;
case ACTION_REWIND:
back(true);
break;
case ACTION_SKIP:
playNextSong();
break;
case ACTION_STOP:
case ACTION_QUIT:
pendingQuit = false;
quit();
break;
case ACTION_PENDING_QUIT:
pendingQuit = true;
break;
}
}
}
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
mediaSession.setActive(false);
quit();
releaseResources();
}
@Override
public IBinder onBind(Intent intent) {
return musicBinder;
}
public void saveState() {
savePosition();
saveProgress();
}
private void savePosition() {
PreferenceUtil.getInstance(getApplicationContext()).setPosition(getPosition());
}
private void saveProgress() {
Log.d(TAG, "saveProgress(): " + getSongProgressMillis());
PreferenceUtil.getInstance(getApplicationContext()).setProgress(getSongProgressMillis());
}
private void restoreState() {
try {
QueueRepository queueRepository = new QueueRepository(App.getInstance());
List<Song> restoredQueue = queueRepository.getSongs();
int restoredPosition = PreferenceUtil.getInstance(getApplicationContext()).getPosition();
int restoredPositionInTrack = PreferenceUtil.getInstance(getApplicationContext()).getProgress();
if (restoredQueue.size() > 0 && restoredPosition != -1) {
this.playingQueue = restoredQueue;
position = restoredPosition;
openCurrent();
if (restoredPositionInTrack > 0) seek(restoredPositionInTrack);
notHandledMetaChangedForCurrentTrack = true;
handleChangeInternal(META_CHANGED);
handleChangeInternal(QUEUE_CHANGED);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void quit() {
pause();
playingNotification.stop();
stopSelf();
}
private void releaseResources() {
playerHandler.removeCallbacksAndMessages(null);
playerHandlerThread.quitSafely();
playback.stop();
mediaSession.release();
}
public boolean isPlaying() {
return playback != null && playback.isPlaying();
}
public boolean isLoading() {
return playback != null && playback.isLoading();
}
public void quitPlaying() {
quit();
}
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 void playNextSong() {
playSongAt(getNextPosition());
}
private void openTrackAndPrepareNextAt(int position) {
synchronized (this) {
this.position = position;
openCurrent();
playback.start();
notifyChange(META_CHANGED);
notHandledMetaChangedForCurrentTrack = false;
}
}
private void openCurrent() {
synchronized (this) {
// current song will be null when queue is cleared
if (getCurrentSong() == null) return;
playback.setDataSource(getCurrentSong());
}
}
private void prepareNext() {
playerHandler.removeMessages(PREPARE_NEXT);
playerHandler.obtainMessage(PREPARE_NEXT).sendToTarget();
}
private void prepareNextImpl() {
synchronized (this) {
nextPosition = getNextPosition();
playback.queueDataSource(getSongAt(nextPosition));
}
increaseSongCount();
}
public void initNotification() {
playingNotification = new PlayingNotification();
playingNotification.init(this);
}
public void updateNotification() {
if (playingNotification != null && getCurrentSong() != null) {
playingNotification.update();
}
}
private void updateMediaSessionState() {
mediaSession.setPlaybackState(
new PlaybackStateCompat.Builder()
.setActions(MEDIA_SESSION_ACTIONS)
.setState(isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED, getSongProgressMillis(), 1)
.build());
}
@SuppressLint("CheckResult")
private void updateMediaSessionMetadata() {
final Song song = getCurrentSong();
if (song == null) {
mediaSession.setMetadata(null);
return;
}
final MediaMetadataCompat.Builder metaData = new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, MusicUtil.getReadableString(song.getArtistName()))
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, MusicUtil.getReadableString(song.getArtistName()))
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, MusicUtil.getReadableString(song.getAlbumName()))
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, MusicUtil.getReadableString(song.getTitle()))
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.getDuration() * 1000)
.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());
mediaSession.setMetadata(metaData.build());
}
public void runOnUiThread(Runnable runnable) {
uiThreadHandler.post(runnable);
}
public Song getCurrentSong() {
return getSongAt(getPosition());
}
public Song getSongAt(int position) {
if (position >= 0 && position < getPlayingQueue().size()) {
return getPlayingQueue().get(position);
} else {
return new Song();
}
}
public int getNextPosition() {
int position = getPosition() + 1;
if (isLastTrack()) {
position -= 1;
}
return position;
}
private boolean isLastTrack() {
return getPosition() == getPlayingQueue().size() - 1;
}
public List<Song> getPlayingQueue() {
return playingQueue;
}
public void openQueue(@Nullable final List<Song> playingQueue, final int startPosition, final boolean startPlaying) {
if (playingQueue != null && !playingQueue.isEmpty() && startPosition >= 0 && startPosition < playingQueue.size()) {
this.playingQueue = playingQueue;
this.position = startPosition;
if (startPlaying) {
playSongAt(position);
} else {
setPosition(position);
}
notifyChange(QUEUE_CHANGED);
}
}
public void addSong(int position, Song song) {
playingQueue.add(position, song);
notifyChange(QUEUE_CHANGED);
}
public void addSong(Song song) {
playingQueue.add(song);
notifyChange(QUEUE_CHANGED);
}
public void addSongs(int position, List<Song> songs) {
playingQueue.addAll(position, songs);
notifyChange(QUEUE_CHANGED);
}
public void addSongs(List<Song> songs) {
playingQueue.addAll(songs);
notifyChange(QUEUE_CHANGED);
}
public void removeSong(int position) {
playingQueue.remove(position);
reposition(position);
notifyChange(QUEUE_CHANGED);
}
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);
}
}
}
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 (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 clearQueue() {
playingQueue.clear();
setPosition(-1);
notifyChange(QUEUE_CHANGED);
}
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();
}
private void playSongAtImpl(int position) {
openTrackAndPrepareNextAt(position);
}
public void pause() {
if (playback.isPlaying()) {
playback.pause();
notifyChange(STATE_CHANGED);
}
}
public void play() {
synchronized (this) {
if (!playback.isPlaying()) {
if (!playback.isReady()) {
playSongAt(getPosition());
} else {
playback.start();
if (notHandledMetaChangedForCurrentTrack) {
handleChangeInternal(META_CHANGED);
notHandledMetaChangedForCurrentTrack = false;
}
notifyChange(STATE_CHANGED);
}
}
}
}
public void playPreviousSong(boolean force) {
playSongAt(getPreviousPosition(force));
}
public void back(boolean force) {
if (getSongProgressMillis() > 5000) {
seek(0);
} else {
playPreviousSong(force);
}
}
public int getPreviousPosition(boolean force) {
return getPosition() - 1;
}
public int getSongProgressMillis() {
return playback.getProgress();
}
public int getSongDurationMillis() {
return playback.getDuration();
}
public int seek(int millis) {
synchronized (this) {
playback.setProgress(millis);
throttledSeekHandler.notifySeek();
return millis;
}
}
private void notifyChange(@NonNull final String what) {
handleChangeInternal(what);
sendChangeInternal(what);
}
private void sendChangeInternal(final String what) {
sendBroadcast(new Intent(what));
}
private void handleChangeInternal(@NonNull final String what) {
switch (what) {
case STATE_CHANGED:
updateNotification();
updateMediaSessionState();
if (!isPlaying()) saveProgress();
break;
case META_CHANGED:
updateNotification();
updateMediaSessionMetadata();
updateMediaSessionState();
savePosition();
saveProgress();
break;
case QUEUE_CHANGED:
updateMediaSessionMetadata();
saveState();
if (playingQueue.size() > 0) {
prepareNext();
} else {
playingNotification.stop();
}
break;
}
}
public MediaSessionCompat getMediaSession() {
return mediaSession;
}
private void increaseSongCount() {
SongRepository songRepository = new SongRepository(App.getInstance());
QueueRepository queueRepository = new QueueRepository(App.getInstance());
songRepository.scrobble(getCurrentSong().getId());
queueRepository.setTimestamp(getCurrentSong());
}
@Override
public void onStateChanged(int state) {
notifyChange(STATE_CHANGED);
}
@Override
public void onReadyChanged(boolean ready, int reason) {
notifyChange(STATE_CHANGED);
if (ready) {
prepareNext();
}
}
@Override
public void onTrackChanged(int reason) {
if (reason == MEDIA_ITEM_TRANSITION_REASON_AUTO) {
playerHandler.sendEmptyMessage(TRACK_CHANGED);
} else if (reason == MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED) {
prepareNext();
}
}
private static final class PlaybackHandler extends Handler {
private final WeakReference<MusicService> mService;
public PlaybackHandler(final MusicService service, @NonNull final Looper looper) {
super(looper);
mService = new WeakReference<>(service);
}
@Override
public void handleMessage(@NonNull final Message msg) {
final MusicService service = mService.get();
if (service == null) {
return;
}
switch (msg.what) {
case TRACK_CHANGED:
if (service.isLastTrack()) {
service.pause();
service.seek(0);
service.notifyChange(STATE_CHANGED);
} else {
service.position = service.nextPosition;
service.prepareNextImpl();
service.notifyChange(META_CHANGED);
service.notifyChange(QUEUE_CHANGED);
}
break;
case TRACK_ENDED:
// if there is a timer finished, don't continue
if (service.pendingQuit && service.isLastTrack()) {
service.notifyChange(STATE_CHANGED);
service.seek(0);
if (service.pendingQuit) {
service.pendingQuit = false;
service.quit();
break;
}
} else {
service.playNextSong();
}
break;
case PLAY_SONG:
service.playSongAtImpl(msg.arg1);
service.notifyChange(STATE_CHANGED);
break;
case SET_POSITION:
service.openTrackAndPrepareNextAt(msg.arg1);
service.notifyChange(STATE_CHANGED);
break;
case PREPARE_NEXT:
service.prepareNextImpl();
break;
}
}
}
public class MusicBinder extends Binder {
@NonNull
public MusicService getService() {
return MusicService.this;
}
}
private class ThrottledSeekHandler implements Runnable {
// milliseconds to throttle before calling run to aggregate events
private static final long THROTTLE = 500;
private final Handler mHandler;
public ThrottledSeekHandler(Handler handler) {
mHandler = handler;
}
public void notifySeek() {
mHandler.removeCallbacks(this);
mHandler.postDelayed(this, THROTTLE);
}
@Override
public void run() {
notifyChange(STATE_CHANGED);
}
}
}

View file

@ -15,7 +15,6 @@ import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.repository.ArtistRepository;
import com.cappielloantonio.play.repository.QueueRepository;
import com.cappielloantonio.play.repository.SongRepository;
import com.cappielloantonio.play.service.MusicPlayerRemote;
import com.cappielloantonio.play.util.DownloadUtil;
import com.cappielloantonio.play.util.PreferenceUtil;
@ -49,7 +48,8 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
}
public Song getCurrentSong() {
return MusicPlayerRemote.getCurrentSong();
// return MusicPlayerRemote.getCurrentSong();
return null;
}
public void setFavorite(Context context) {
@ -64,7 +64,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
song.setFavorite(true);
if(PreferenceUtil.getInstance(context).isStarredSyncEnabled()) {
DownloadUtil.getDownloadTracker(context).download(Collections.singletonList(song), null, null);
// DownloadUtil.getDownloadTracker(context).download(Collections.singletonList(song), null, null);
}
}
}