diff --git a/app/src/main/java/com/cappielloantonio/play/broadcast/receiver/MediaButtonIntentReceiver.java b/app/src/main/java/com/cappielloantonio/play/broadcast/receiver/MediaButtonIntentReceiver.java deleted file mode 100644 index 28b6b7e7..00000000 --- a/app/src/main/java/com/cappielloantonio/play/broadcast/receiver/MediaButtonIntentReceiver.java +++ /dev/null @@ -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(); - } - } -} diff --git a/app/src/main/java/com/cappielloantonio/play/helper/MusicProgressViewUpdateHelper.java b/app/src/main/java/com/cappielloantonio/play/helper/MusicProgressViewUpdateHelper.java deleted file mode 100644 index 3585601e..00000000 --- a/app/src/main/java/com/cappielloantonio/play/helper/MusicProgressViewUpdateHelper.java +++ /dev/null @@ -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); - } -} diff --git a/app/src/main/java/com/cappielloantonio/play/service/MultiPlayer.java b/app/src/main/java/com/cappielloantonio/play/service/MultiPlayer.java deleted file mode 100644 index 2b6043e2..00000000 --- a/app/src/main/java/com/cappielloantonio/play/service/MultiPlayer.java +++ /dev/null @@ -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); - } -} diff --git a/app/src/main/java/com/cappielloantonio/play/service/MusicPlayerRemote.java b/app/src/main/java/com/cappielloantonio/play/service/MusicPlayerRemote.java deleted file mode 100644 index fcab22f5..00000000 --- a/app/src/main/java/com/cappielloantonio/play/service/MusicPlayerRemote.java +++ /dev/null @@ -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 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 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 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 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 songToEnqueue = new ArrayList<>(); - songToEnqueue.add(song); - queueRepository.insertAllAndStartNew(songToEnqueue); - openQueue(songToEnqueue, 0, true); - } - return true; - } - - return false; - } - - public static boolean playNext(@NonNull List songs) { - if (musicService != null) { - QueueRepository queueRepository = new QueueRepository(App.getInstance()); - - if (getPlayingQueue().size() > 0) { - musicService.addSongs(getPosition() + 1, songs); - queueRepository.insertAllAndStartNew(getPlayingQueue()); - } else { - List 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 songToEnqueue = new ArrayList<>(); - songToEnqueue.add(song); - queueRepository.insertAllAndStartNew(songToEnqueue); - openQueue(songToEnqueue, 0, true); - } - return true; - } - - return false; - } - - public static boolean enqueue(@NonNull List songs) { - if (musicService != null) { - QueueRepository queueRepository = new QueueRepository(App.getInstance()); - - if (getPlayingQueue().size() > 0) { - musicService.addSongs(songs); - queueRepository.insertAllAndStartNew(getPlayingQueue()); - } else { - List 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; - } - } -} diff --git a/app/src/main/java/com/cappielloantonio/play/service/MusicService.java b/app/src/main/java/com/cappielloantonio/play/service/MusicService.java deleted file mode 100644 index 5e4eecdc..00000000 --- a/app/src/main/java/com/cappielloantonio/play/service/MusicService.java +++ /dev/null @@ -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 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 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 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 getPlayingQueue() { - return playingQueue; - } - - public void openQueue(@Nullable final List 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 songs) { - playingQueue.addAll(position, songs); - notifyChange(QUEUE_CHANGED); - } - - public void addSongs(List 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 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); - } - } -} diff --git a/app/src/main/java/com/cappielloantonio/play/viewmodel/PlayerBottomSheetViewModel.java b/app/src/main/java/com/cappielloantonio/play/viewmodel/PlayerBottomSheetViewModel.java index f43ebac0..c60ea946 100644 --- a/app/src/main/java/com/cappielloantonio/play/viewmodel/PlayerBottomSheetViewModel.java +++ b/app/src/main/java/com/cappielloantonio/play/viewmodel/PlayerBottomSheetViewModel.java @@ -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); } } }