From 70e323eb0ab509576a5ea644cd339048ba6755f4 Mon Sep 17 00:00:00 2001 From: Prathamesh More Date: Fri, 13 May 2022 00:14:34 +0530 Subject: [PATCH] Handle playback with PlaybackManager --- .../retromusic/service/CrossFadePlayer.kt | 44 +- .../retromusic/service/MultiPlayer.java | 368 ----------------- .../monkey/retromusic/service/MultiPlayer.kt | 379 ++++++++++++++++++ .../monkey/retromusic/service/MusicService.kt | 255 +++++------- .../retromusic/service/PlaybackHandler.kt | 261 +++++------- .../retromusic/service/playback/Playback.kt | 8 +- 6 files changed, 617 insertions(+), 698 deletions(-) delete mode 100644 app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.java create mode 100644 app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.kt diff --git a/app/src/main/java/code/name/monkey/retromusic/service/CrossFadePlayer.kt b/app/src/main/java/code/name/monkey/retromusic/service/CrossFadePlayer.kt index 13c2310b0..4a54231f7 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/CrossFadePlayer.kt +++ b/app/src/main/java/code/name/monkey/retromusic/service/CrossFadePlayer.kt @@ -14,7 +14,9 @@ import androidx.core.net.toUri import code.name.monkey.appthemehelper.util.VersionUtils.hasMarshmallow import code.name.monkey.retromusic.R import code.name.monkey.retromusic.extensions.showToast +import code.name.monkey.retromusic.extensions.uri import code.name.monkey.retromusic.helper.MusicPlayerRemote +import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.service.AudioFader.Companion.createFadeAnimator import code.name.monkey.retromusic.service.playback.Playback import code.name.monkey.retromusic.service.playback.Playback.PlaybackCallbacks @@ -124,18 +126,26 @@ class CrossFadePlayer(val context: Context) : Playback, MediaPlayer.OnCompletion override val isPlaying: Boolean get() = mIsInitialized && getCurrentPlayer()?.isPlaying == true - override fun setDataSource(path: String, force: Boolean): Boolean { + override fun setDataSource( + song: Song, + force: Boolean, + completion: (success: Boolean) -> Unit, + ) { cancelFade() if (force) hasDataSource = false mIsInitialized = false /* We've already set DataSource if initialized is true in setNextDataSource */ if (!hasDataSource) { - getCurrentPlayer()?.let { mIsInitialized = setDataSourceImpl(it, path) } + getCurrentPlayer()?.let { + setDataSourceImpl(it, song.uri.toString()) { success -> + mIsInitialized = success + completion(success) + } + } hasDataSource = true } else { mIsInitialized = true } - return mIsInitialized } override fun setNextDataSource(path: String?) {} @@ -148,9 +158,9 @@ class CrossFadePlayer(val context: Context) : Playback, MediaPlayer.OnCompletion private fun setDataSourceImpl( player: MediaPlayer, path: String, - ): Boolean { + completion: (success: Boolean) -> Unit, + ) { player.reset() - player.setOnPreparedListener(null) try { if (path.startsWith("content://")) { player.setDataSource(context, path.toUri()) @@ -160,20 +170,18 @@ class CrossFadePlayer(val context: Context) : Playback, MediaPlayer.OnCompletion player.setAudioAttributes( AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build() ) - player.prepare() - player.setPlaybackSpeedPitch(playbackSpeed, playbackPitch) + player.setPlaybackSpeedPitch(PreferenceUtil.playbackSpeed, PreferenceUtil.playbackPitch) + player.setOnPreparedListener { + player.setOnPreparedListener(null) + completion(true) + } + player.prepareAsync() } catch (e: Exception) { + completion(false) e.printStackTrace() - return false } player.setOnCompletionListener(this) player.setOnErrorListener(this) - val intent = Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION) - intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId) - intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.packageName) - intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC) - context.sendBroadcast(intent) - return true } override fun setAudioSessionId(sessionId: Int): Boolean { @@ -321,9 +329,11 @@ class CrossFadePlayer(val context: Context) : Playback, MediaPlayer.OnCompletion getNextPlayer()?.let { player -> val nextSong = MusicPlayerRemote.nextSong if (nextSong != null) { - setDataSourceImpl(player, MusicUtil.getSongFileUri(nextSong.id).toString()) - // Switch to other player / Crossfade only if next song exists - switchPlayer() + setDataSourceImpl(player, + MusicUtil.getSongFileUri(nextSong.id).toString()) { success -> + // Switch to other player (Crossfade) only if next song exists + if (success) switchPlayer() + } } } } diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.java b/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.java deleted file mode 100644 index 66556bbe0..000000000 --- a/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.java +++ /dev/null @@ -1,368 +0,0 @@ -/* - * 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.content.Context; -import android.content.Intent; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.media.PlaybackParams; -import android.media.audiofx.AudioEffect; -import android.net.Uri; -import android.os.PowerManager; -import android.util.Log; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.jetbrains.annotations.NotNull; - -import code.name.monkey.appthemehelper.util.VersionUtils; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.service.playback.Playback; -import code.name.monkey.retromusic.util.PreferenceUtil; - -/** - * @author Andrew Neal, Karim Abou Zeid (kabouzeid) - */ -public class MultiPlayer - implements Playback, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener { - public static final String TAG = MultiPlayer.class.getSimpleName(); - - private MediaPlayer mCurrentMediaPlayer = new MediaPlayer(); - private MediaPlayer mNextMediaPlayer; - - private final Context context; - @Nullable - private Playback.PlaybackCallbacks callbacks; - - private boolean mIsInitialized = false; - - /** - * Constructor of MultiPlayer - */ - MultiPlayer(final Context context) { - this.context = context; - mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); - } - - /** - * @param path The path of the file, or the http/rtsp URL of the stream you want to play - * @return True if the player has been prepared and is ready to play, false otherwise - */ - @Override - public boolean setDataSource(@NotNull final String path, boolean force) { - mIsInitialized = false; - mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path); - if (mIsInitialized) { - setNextDataSource(null); - } - return mIsInitialized; - } - - /** - * @param player The {@link MediaPlayer} to use - * @param path The path of the file, or the http/rtsp URL of the stream you want to play - * @return True if the player has been prepared and is ready to play, false otherwise - */ - private boolean setDataSourceImpl(@NonNull final MediaPlayer player, @NonNull final String path) { - if (context == null) { - return false; - } - try { - player.reset(); - player.setOnPreparedListener(null); - if (path.startsWith("content://")) { - player.setDataSource(context, Uri.parse(path)); - } else { - player.setDataSource(path); - } - setPlaybackSpeedPitch(PreferenceUtil.INSTANCE.getPlaybackSpeed(), PreferenceUtil.INSTANCE.getPlaybackPitch()); - player.setAudioStreamType(AudioManager.STREAM_MUSIC); - player.prepare(); - } catch (Exception e) { - return false; - } - player.setOnCompletionListener(this); - player.setOnErrorListener(this); - final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION); - intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId()); - intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName()); - intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC); - context.sendBroadcast(intent); - return true; - } - - /** - * Set the MediaPlayer to start when this MediaPlayer finishes playback. - * - * @param path The path of the file, or the http/rtsp URL of the stream you want to play - */ - @Override - public void setNextDataSource(@Nullable final String path) { - if (context == null) { - return; - } - try { - mCurrentMediaPlayer.setNextMediaPlayer(null); - } catch (IllegalArgumentException e) { - Log.i(TAG, "Next media player is current one, continuing"); - } catch (IllegalStateException e) { - Log.e(TAG, "Media player not initialized!"); - return; - } - if (mNextMediaPlayer != null) { - mNextMediaPlayer.release(); - mNextMediaPlayer = null; - } - if (path == null) { - return; - } - if (PreferenceUtil.INSTANCE.isGapLessPlayback()) { - mNextMediaPlayer = new MediaPlayer(); - mNextMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); - mNextMediaPlayer.setAudioSessionId(getAudioSessionId()); - if (setDataSourceImpl(mNextMediaPlayer, path)) { - try { - mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer); - } catch (@NonNull IllegalArgumentException | IllegalStateException e) { - Log.e(TAG, "setNextDataSource: setNextMediaPlayer()", e); - if (mNextMediaPlayer != null) { - mNextMediaPlayer.release(); - mNextMediaPlayer = null; - } - } - } else { - if (mNextMediaPlayer != null) { - mNextMediaPlayer.release(); - mNextMediaPlayer = null; - } - } - } - } - - /** - * Sets the callbacks - * - * @param callbacks The callbacks to use - */ - @Override - public void setCallbacks(@Nullable final Playback.PlaybackCallbacks callbacks) { - this.callbacks = callbacks; - } - - /** - * @return True if the player is ready to go, false otherwise - */ - @Override - public boolean isInitialized() { - return mIsInitialized; - } - - /** - * Starts or resumes playback. - */ - @Override - public boolean start() { - try { - mCurrentMediaPlayer.start(); - return true; - } catch (IllegalStateException e) { - return false; - } - } - - /** - * Resets the MediaPlayer to its uninitialized state. - */ - @Override - public void stop() { - mCurrentMediaPlayer.reset(); - mIsInitialized = false; - } - - /** - * Releases resources associated with this MediaPlayer object. - */ - @Override - public void release() { - stop(); - mCurrentMediaPlayer.release(); - if (mNextMediaPlayer != null) { - mNextMediaPlayer.release(); - } - } - - /** - * Pauses playback. Call start() to resume. - */ - @Override - public boolean pause() { - try { - mCurrentMediaPlayer.pause(); - return true; - } catch (IllegalStateException e) { - return false; - } - } - - /** - * Checks whether the MultiPlayer is playing. - */ - @Override - public boolean isPlaying() { - return mIsInitialized && mCurrentMediaPlayer.isPlaying(); - } - - /** - * Gets the duration of the file. - * - * @return The duration in milliseconds - */ - @Override - public int duration() { - if (!mIsInitialized) { - return -1; - } - try { - return mCurrentMediaPlayer.getDuration(); - } catch (IllegalStateException e) { - return -1; - } - } - - /** - * Gets the current playback position. - * - * @return The current position in milliseconds - */ - @Override - public int position() { - if (!mIsInitialized) { - return -1; - } - try { - return mCurrentMediaPlayer.getCurrentPosition(); - } catch (IllegalStateException e) { - return -1; - } - } - - /** - * Gets the current playback position. - * - * @param whereto The offset in milliseconds from the start to seek to - * @return The offset in milliseconds from the start to seek to - */ - @Override - public int seek(final int whereto) { - try { - mCurrentMediaPlayer.seekTo(whereto); - return whereto; - } catch (IllegalStateException e) { - return -1; - } - } - - @Override - public boolean setVolume(final float vol) { - try { - mCurrentMediaPlayer.setVolume(vol, vol); - return true; - } catch (IllegalStateException e) { - return false; - } - } - - /** - * Sets the audio session ID. - * - * @param sessionId The audio session ID - */ - @Override - public boolean setAudioSessionId(final int sessionId) { - try { - mCurrentMediaPlayer.setAudioSessionId(sessionId); - return true; - } catch (@NonNull IllegalArgumentException | IllegalStateException e) { - return false; - } - } - - /** - * Returns the audio session ID. - * - * @return The current audio session ID. - */ - @Override - public int getAudioSessionId() { - return mCurrentMediaPlayer.getAudioSessionId(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean onError(final MediaPlayer mp, final int what, final int extra) { - mIsInitialized = false; - mCurrentMediaPlayer.release(); - mCurrentMediaPlayer = new MediaPlayer(); - mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); - if (context != null) { - Toast.makeText( - context, - context.getResources().getString(R.string.unplayable_file), - Toast.LENGTH_SHORT) - .show(); - Log.e(TAG, String.valueOf(what) + extra); - } - return false; - } - - /** - * {@inheritDoc} - */ - @Override - public void onCompletion(final MediaPlayer mp) { - if (mp.equals(mCurrentMediaPlayer) && mNextMediaPlayer != null) { - mIsInitialized = false; - mCurrentMediaPlayer.release(); - mCurrentMediaPlayer = mNextMediaPlayer; - mIsInitialized = true; - mNextMediaPlayer = null; - if (callbacks != null) callbacks.onTrackWentToNext(); - } else { - if (callbacks != null) callbacks.onTrackEnded(); - } - } - - @Override - public void setCrossFadeDuration(int duration) { - } - - @Override - public void setPlaybackSpeedPitch(float speed, float pitch) { - if (VersionUtils.INSTANCE.hasMarshmallow()) { - boolean wasPlaying = mCurrentMediaPlayer.isPlaying(); - mCurrentMediaPlayer.setPlaybackParams(new PlaybackParams() - .setSpeed(PreferenceUtil.INSTANCE.getPlaybackSpeed()) - .setPitch(PreferenceUtil.INSTANCE.getPlaybackPitch())); - if (!wasPlaying) { - if (mCurrentMediaPlayer.isPlaying()) mCurrentMediaPlayer.pause(); - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.kt b/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.kt new file mode 100644 index 000000000..89dda0868 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.kt @@ -0,0 +1,379 @@ +/* + * 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.content.Context +import android.content.Intent +import android.content.SharedPreferences +import android.content.SharedPreferences.OnSharedPreferenceChangeListener +import android.media.AudioAttributes +import android.media.MediaPlayer +import android.media.MediaPlayer.OnCompletionListener +import android.media.PlaybackParams +import android.media.audiofx.AudioEffect +import android.net.Uri +import android.os.PowerManager +import android.util.Log +import androidx.preference.PreferenceManager +import code.name.monkey.appthemehelper.util.VersionUtils.hasMarshmallow +import code.name.monkey.retromusic.PLAYBACK_PITCH +import code.name.monkey.retromusic.PLAYBACK_SPEED +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.extensions.showToast +import code.name.monkey.retromusic.extensions.uri +import code.name.monkey.retromusic.model.Song +import code.name.monkey.retromusic.service.playback.Playback +import code.name.monkey.retromusic.service.playback.Playback.PlaybackCallbacks +import code.name.monkey.retromusic.util.PreferenceUtil.isGapLessPlayback +import code.name.monkey.retromusic.util.PreferenceUtil.playbackPitch +import code.name.monkey.retromusic.util.PreferenceUtil.playbackSpeed + +/** + * @author Andrew Neal, Karim Abou Zeid (kabouzeid) + */ +class MultiPlayer internal constructor(private val context: Context) : Playback, + MediaPlayer.OnErrorListener, OnCompletionListener, OnSharedPreferenceChangeListener { + private var mCurrentMediaPlayer = MediaPlayer() + private var mNextMediaPlayer: MediaPlayer? = null + private var callbacks: PlaybackCallbacks? = null + + /** + * @return True if the player is ready to go, false otherwise + */ + override var isInitialized = false + private set + + /** + * @param path The path of the file, or the http/rtsp URL of the stream you want to play + * @return True if the `player` has been prepared and is ready to play, false otherwise + */ + override fun setDataSource( + song: Song, + force: Boolean, + completion: (success: Boolean) -> Unit, + ) { + isInitialized = false + setDataSourceImpl(mCurrentMediaPlayer, song.uri.toString()) { success -> + isInitialized = success + if (isInitialized) { + setNextDataSource(null) + } + completion(isInitialized) + } + } + + /** + * @param player The [MediaPlayer] to use + * @param path The path of the file, or the http/rtsp URL of the stream you want to play + * @return True if the `player` has been prepared and is ready to play, false otherwise + */ + private fun setDataSourceImpl( + player: MediaPlayer, + path: String, + completion: (success: Boolean) -> Unit, + ) { + try { + player.reset() + player.setOnPreparedListener(null) + if (path.startsWith("content://")) { + player.setDataSource(context, Uri.parse(path)) + } else { + player.setDataSource(path) + } + setPlaybackSpeedPitch(player) + player.setAudioAttributes(AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .build() + ) + player.setOnPreparedListener { + player.setOnPreparedListener(null) + completion(true) + } + player.prepareAsync() + } catch (e: Exception) { + completion(false) + e.printStackTrace() + } + player.setOnCompletionListener(this) + player.setOnErrorListener(this) + } + + fun openEqualizerSession() { + val intent = Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION) + intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId) + intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.packageName) + intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC) + context.sendBroadcast(intent) + } + + fun closeEqualizerSession() { + val intent = Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION) + intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId) + intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.packageName) + intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC) + context.sendBroadcast(intent) + } + + /** + * Set the MediaPlayer to start when this MediaPlayer finishes playback. + * + * @param path The path of the file, or the http/rtsp URL of the stream you want to play + */ + override fun setNextDataSource(path: String?) { + try { + mCurrentMediaPlayer.setNextMediaPlayer(null) + } catch (e: IllegalArgumentException) { + Log.i(TAG, "Next media player is current one, continuing") + } catch (e: IllegalStateException) { + Log.e(TAG, "Media player not initialized!") + return + } + if (mNextMediaPlayer != null) { + mNextMediaPlayer?.release() + mNextMediaPlayer = null + } + if (path == null) { + return + } + if (isGapLessPlayback) { + mNextMediaPlayer = MediaPlayer() + mNextMediaPlayer?.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK) + mNextMediaPlayer?.audioSessionId = audioSessionId + setDataSourceImpl(mNextMediaPlayer!!, path) { success -> + if (success) { + try { + mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer) + } catch (e: IllegalArgumentException) { + Log.e(TAG, "setNextDataSource: setNextMediaPlayer()", e) + if (mNextMediaPlayer != null) { + mNextMediaPlayer?.release() + mNextMediaPlayer = null + } + } catch (e: IllegalStateException) { + Log.e(TAG, "setNextDataSource: setNextMediaPlayer()", e) + if (mNextMediaPlayer != null) { + mNextMediaPlayer?.release() + mNextMediaPlayer = null + } + } + } else { + if (mNextMediaPlayer != null) { + mNextMediaPlayer?.release() + mNextMediaPlayer = null + } + } + } + } + } + + /** + * Sets the callbacks + * + * @param callbacks The callbacks to use + */ + override fun setCallbacks(callbacks: PlaybackCallbacks) { + this.callbacks = callbacks + } + + /** + * Starts or resumes playback. + */ + override fun start(): Boolean { + return try { + mCurrentMediaPlayer.start() + true + } catch (e: IllegalStateException) { + false + } + } + + /** + * Resets the MediaPlayer to its uninitialized state. + */ + override fun stop() { + mCurrentMediaPlayer.reset() + isInitialized = false + } + + /** + * Releases resources associated with this MediaPlayer object. + */ + override fun release() { + stop() + mCurrentMediaPlayer.release() + if (mNextMediaPlayer != null) { + mNextMediaPlayer?.release() + } + PreferenceManager.getDefaultSharedPreferences(context) + .unregisterOnSharedPreferenceChangeListener(this) + } + + /** + * Pauses playback. Call start() to resume. + */ + override fun pause(): Boolean { + return try { + mCurrentMediaPlayer.pause() + true + } catch (e: IllegalStateException) { + false + } + } + + /** + * Checks whether the MultiPlayer is playing. + */ + override val isPlaying: Boolean + get() = isInitialized && mCurrentMediaPlayer.isPlaying + + /** + * Gets the duration of the file. + * + * @return The duration in milliseconds + */ + override fun duration(): Int { + return if (!this.isInitialized) { + -1 + } else try { + mCurrentMediaPlayer.duration + } catch (e: IllegalStateException) { + -1 + } + } + + /** + * Gets the current playback position. + * + * @return The current position in milliseconds + */ + override fun position(): Int { + return if (!this.isInitialized) { + -1 + } else try { + mCurrentMediaPlayer.currentPosition + } catch (e: IllegalStateException) { + -1 + } + } + + /** + * Gets the current playback position. + * + * @param whereto The offset in milliseconds from the start to seek to + * @return The offset in milliseconds from the start to seek to + */ + override fun seek(whereto: Int): Int { + return try { + mCurrentMediaPlayer.seekTo(whereto) + whereto + } catch (e: IllegalStateException) { + -1 + } + } + + override fun setVolume(vol: Float): Boolean { + return try { + mCurrentMediaPlayer.setVolume(vol, vol) + true + } catch (e: IllegalStateException) { + false + } + } + + /** + * Sets the audio session ID. + * + * @param sessionId The audio session ID + */ + override fun setAudioSessionId(sessionId: Int): Boolean { + return try { + mCurrentMediaPlayer.audioSessionId = sessionId + true + } catch (e: IllegalArgumentException) { + false + } catch (e: IllegalStateException) { + false + } + } + + /** + * Returns the audio session ID. + * + * @return The current audio session ID. + */ + override val audioSessionId: Int + get() = mCurrentMediaPlayer.audioSessionId + + /** + * {@inheritDoc} + */ + override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean { + isInitialized = false + mCurrentMediaPlayer.release() + mCurrentMediaPlayer = MediaPlayer() + mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK) + context.showToast(R.string.unplayable_file) + Log.e(TAG, what.toString() + extra) + return false + } + + /** + * {@inheritDoc} + */ + override fun onCompletion(mp: MediaPlayer) { + if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) { + isInitialized = false + mCurrentMediaPlayer.release() + mCurrentMediaPlayer = mNextMediaPlayer!! + isInitialized = true + mNextMediaPlayer = null + if (callbacks != null) callbacks?.onTrackWentToNext() + } else { + if (callbacks != null) callbacks?.onTrackEnded() + } + } + + override fun setCrossFadeDuration(duration: Int) {} + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { + if (key == PLAYBACK_SPEED || key == PLAYBACK_PITCH) { + setPlaybackSpeedPitch(mCurrentMediaPlayer) + } + } + + private fun setPlaybackSpeedPitch(mp: MediaPlayer) { + if (hasMarshmallow()) { + val wasPlaying = mp.isPlaying + mp.playbackParams = PlaybackParams() + .setSpeed(playbackSpeed) + .setPitch(playbackPitch) + if (!wasPlaying) { + if (mp.isPlaying) mp.pause() + } + } + } + + companion object { + val TAG: String = MultiPlayer::class.java.simpleName + } + + /** + * Constructor of `MultiPlayer` + */ + init { + mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK) + PreferenceManager.getDefaultSharedPreferences(context) + .registerOnSharedPreferenceChangeListener(this) + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.kt b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.kt index 4274978e0..7dd58ef5a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.kt +++ b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.kt @@ -25,7 +25,6 @@ import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.media.AudioManager import android.media.AudioManager.OnAudioFocusChangeListener -import android.media.audiofx.AudioEffect import android.os.* import android.os.Build.VERSION import android.os.Build.VERSION_CODES @@ -57,11 +56,11 @@ import code.name.monkey.retromusic.auto.AutoMediaIDHelper import code.name.monkey.retromusic.auto.AutoMusicProvider import code.name.monkey.retromusic.extensions.showToast import code.name.monkey.retromusic.extensions.toMediaSessionQueue +import code.name.monkey.retromusic.extensions.uri import code.name.monkey.retromusic.glide.BlurTransformation import code.name.monkey.retromusic.glide.GlideApp import code.name.monkey.retromusic.glide.RetroGlideExtension.getDefaultTransition import code.name.monkey.retromusic.glide.RetroGlideExtension.getSongModel -import code.name.monkey.retromusic.helper.MusicPlayerRemote.isCasting import code.name.monkey.retromusic.helper.ShuffleHelper.makeShuffleList import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song.Companion.emptySong @@ -69,14 +68,12 @@ 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.AudioFader.Companion.startFadeAnimator import code.name.monkey.retromusic.service.notification.PlayingNotification import code.name.monkey.retromusic.service.notification.PlayingNotificationClassic import code.name.monkey.retromusic.service.notification.PlayingNotificationImpl24 import code.name.monkey.retromusic.service.playback.Playback import code.name.monkey.retromusic.service.playback.Playback.PlaybackCallbacks import code.name.monkey.retromusic.util.MusicUtil.getMediaStoreAlbumCoverUri -import code.name.monkey.retromusic.util.MusicUtil.getSongFileUri import code.name.monkey.retromusic.util.MusicUtil.toggleFavorite import code.name.monkey.retromusic.util.PackageValidator import code.name.monkey.retromusic.util.PreferenceUtil.crossFadeDuration @@ -96,6 +93,7 @@ import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.transition.Transition +import com.google.android.gms.cast.framework.CastSession import org.koin.java.KoinJavaComponent.get import java.util.* @@ -112,8 +110,10 @@ class MusicService : MediaBrowserServiceCompat(), @JvmField var pendingQuit = false - @JvmField - var playback: Playback? = null + private lateinit var playbackManager: PlaybackManager + + val playback: Playback? get() = playbackManager.playback + private var mPackageValidator: PackageValidator? = null private val mMusicProvider = get(AutoMusicProvider::class.java) private var trackEndedByCrossfade = false @@ -288,15 +288,10 @@ class MusicService : MediaBrowserServiceCompat(), wakeLock?.setReferenceCounted(false) musicPlayerHandlerThread = HandlerThread("PlaybackHandler") musicPlayerHandlerThread?.start() - playerHandler = PlaybackHandler(this, musicPlayerHandlerThread!!.looper) + playerHandler = PlaybackHandler(this, mainLooper) - // Set MultiPlayer when crossfade duration is 0 i.e. off - playback = if (crossFadeDuration == 0) { - MultiPlayer(this) - } else { - CrossFadePlayer(this) - } - playback?.setCallbacks(this) + playbackManager = PlaybackManager(this) + playbackManager.setCallbacks(this) setupMediaSession() // queue saving needs to run on a separate thread so that it doesn't block the playback handler @@ -457,11 +452,11 @@ class MusicService : MediaBrowserServiceCompat(), } val audioSessionId: Int - get() = if (playback != null) { - playback!!.audioSessionId - } else -1 + get() = playbackManager.audioSessionId + val currentSong: Song get() = getSongAt(getPosition()) + val nextSong: Song? get() = if (isLastTrack && repeatMode == REPEAT_MODE_NONE) { null @@ -501,9 +496,11 @@ class MusicService : MediaBrowserServiceCompat(), } private fun setPosition(position: Int) { - // handle this on the handlers thread to avoid blocking the ui thread - playerHandler?.removeMessages(SET_POSITION) - playerHandler?.obtainMessage(SET_POSITION, position, 0)?.sendToTarget() + openTrackAndPrepareNextAt(position) { success -> + if (success) { + notifyChange(PLAY_STATE_CHANGED) + } + } } private fun getPreviousPosition(force: Boolean): Int { @@ -577,13 +574,10 @@ class MusicService : MediaBrowserServiceCompat(), } val songDurationMillis: Int - get() = if (playback != null) { - playback!!.duration() - } else -1 + get() = playbackManager.songDurationMillis + val songProgressMillis: Int - get() = if (playback != null) { - playback!!.position() - } else -1 + get() = playbackManager.songProgressMillis fun handleAndSendChangeInternal(what: String) { handleChangeInternal(what) @@ -602,8 +596,9 @@ class MusicService : MediaBrowserServiceCompat(), val isLastTrack: Boolean get() = getPosition() == playingQueue.size - 1 + val isPlaying: Boolean - get() = playback != null && playback!!.isPlaying + get() = playbackManager.isPlaying fun moveSong(from: Int, to: Int) { if (from == to) { @@ -702,38 +697,10 @@ class MusicService : MediaBrowserServiceCompat(), CROSS_FADE_DURATION -> { val progress = songProgressMillis val wasPlaying = isPlaying - /* Switch to MultiPlayer if Crossfade duration is 0 and - Playback is not an instance of MultiPlayer */ - if (playback !is MultiPlayer && crossFadeDuration == 0) { - if (playback != null) { - playback?.release() - } - playback = null - playback = MultiPlayer(this) - playback?.setCallbacks(this) - if (openTrackAndPrepareNextAt(position)) { - seek(progress) - if (wasPlaying) { - play() - } - } - } else if (playback !is CrossFadePlayer && crossFadeDuration > 0) { - if (playback != null) { - playback?.release() - } - playback = null - playback = CrossFadePlayer(this) - playback?.setCallbacks(this) - if (openTrackAndPrepareNextAt(position)) { - seek(progress) - if (wasPlaying) { - play() - } - } - } - if (playback != null) playback?.setCrossFadeDuration( - crossFadeDuration - ) + + playbackManager.maybeSwitchToCrossFade(crossFadeDuration) + restorePlaybackState(wasPlaying, progress) + } ALBUM_ART_ON_LOCK_SCREEN, BLURRED_ALBUM_ART -> updateMediaSessionMetaData() COLORED_NOTIFICATION -> { @@ -795,6 +762,10 @@ class MusicService : MediaBrowserServiceCompat(), playerHandler?.sendEmptyMessage(TRACK_WENT_TO_NEXT) } + override fun onPlayStateChanged() { + notifyChange(PLAY_STATE_CHANGED) + } + override fun onUnbind(intent: Intent): Boolean { if (!isPlaying) { stopSelf() @@ -828,34 +799,21 @@ class MusicService : MediaBrowserServiceCompat(), } @Synchronized - fun openTrackAndPrepareNextAt(position: Int): Boolean { + fun openTrackAndPrepareNextAt(position: Int, completion: (success: Boolean) -> Unit) { this.position = position - val prepared = openCurrent() - if (prepared) { - prepareNextImpl() - } - notifyChange(META_CHANGED) - notHandledMetaChangedForCurrentTrack = false - return prepared - } - - fun pause() { - Log.i(TAG, "Paused") - isPausedByTransientLossOfFocus = false - if (playback != null && playback!!.isPlaying) { - startFadeAnimator(playback!!, false) { - - //Code to run when Animator Ends - playback?.pause() - notifyChange(PLAY_STATE_CHANGED) + openCurrent { success -> + if (success) { + prepareNextImpl() } + notifyChange(META_CHANGED) + notHandledMetaChangedForCurrentTrack = false + completion(success) } } - fun forcePause() { + fun pause(force: Boolean = false) { isPausedByTransientLossOfFocus = false - if (playback != null && playback!!.isPlaying) { - playback?.pause() + playbackManager.pause(force) { notifyChange(PLAY_STATE_CHANGED) } } @@ -863,39 +821,26 @@ class MusicService : MediaBrowserServiceCompat(), @Synchronized fun play() { if (requestFocus()) { - if (playback != null && !playback!!.isPlaying) { - if (!playback!!.isInitialized) { - playSongAt(getPosition()) - } else { - //Don't Start playing when it's casting - if (isCasting) { - return - } - startFadeAnimator(playback!!, true) { + playbackManager.play(onNotInitialized = { playSongAt(getPosition()) }) { - // 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) + 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) } + notifyChange(PLAY_STATE_CHANGED) } else { showToast(R.string.audio_focus_denied) } @@ -910,16 +855,12 @@ class MusicService : MediaBrowserServiceCompat(), } fun playSongAt(position: Int) { - // handle this on the handlers thread to avoid blocking the ui thread - playerHandler?.removeMessages(PLAY_SONG) - playerHandler?.obtainMessage(PLAY_SONG, position, 0)?.sendToTarget() - } - - fun playSongAtImpl(position: Int) { - if (openTrackAndPrepareNextAt(position)) { - play() - } else { - showToast(resources.getString(R.string.unplayable_file)) + openTrackAndPrepareNextAt(position) { success -> + if (success) { + play() + } else { + showToast(resources.getString(R.string.unplayable_file)) + } } } @@ -942,7 +883,7 @@ class MusicService : MediaBrowserServiceCompat(), fun prepareNextImpl() { try { val nextPosition = getNextPosition(false) - playback?.setNextDataSource(getTrackUri(getSongAt(nextPosition))) + playbackManager.setNextDataSource(getSongAt(nextPosition).uri.toString()) this.nextPosition = nextPosition } catch (ignored: Exception) { } @@ -952,7 +893,7 @@ class MusicService : MediaBrowserServiceCompat(), pause() stopForeground(true) notificationManager?.cancel(PlayingNotification.NOTIFICATION_ID) - closeAudioEffectSession() + playbackManager.closeAudioEffectSession() audioManager?.abandonAudioFocus(audioFocusListener) stopSelf() } @@ -1030,13 +971,15 @@ class MusicService : MediaBrowserServiceCompat(), originalPlayingQueue = ArrayList(restoredOriginalQueue) playingQueue = ArrayList(restoredQueue) position = restoredPosition - openCurrent() - prepareNext() - if (restoredPositionInTrack > 0) { - seek(restoredPositionInTrack) + openCurrent { + prepareNext() + if (restoredPositionInTrack > 0) { + seek(restoredPositionInTrack) + } + notHandledMetaChangedForCurrentTrack = true + sendChangeInternal(META_CHANGED) } - notHandledMetaChangedForCurrentTrack = true - sendChangeInternal(META_CHANGED) + sendChangeInternal(QUEUE_CHANGED) } } @@ -1067,9 +1010,7 @@ class MusicService : MediaBrowserServiceCompat(), fun seek(millis: Int): Int { return try { var newPosition = 0 - if (playback != null) { - newPosition = playback!!.seek(millis) - } + newPosition = playbackManager.seek(millis) throttledSeekHandler?.notifySeek() newPosition } catch (e: Exception) { @@ -1178,15 +1119,6 @@ class MusicService : MediaBrowserServiceCompat(), } } - private fun closeAudioEffectSession() { - val audioEffectsIntent = Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION) - if (playback != null) { - audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, playback!!.audioSessionId) - } - audioEffectsIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, packageName) - sendBroadcast(audioEffectsIntent) - } - private fun handleChangeInternal(what: String) { when (what) { PLAY_STATE_CHANGED -> { @@ -1284,23 +1216,39 @@ class MusicService : MediaBrowserServiceCompat(), } @Synchronized - private fun openCurrent(): Boolean { + private fun openCurrent(completion: (success: Boolean) -> Unit) { val force = if (!trackEndedByCrossfade) { true } else { trackEndedByCrossfade = false false } - return try { - if (playback != null) { - return playback!!.setDataSource(getTrackUri(currentSong), force) - } else false - } catch (e: Exception) { - e.printStackTrace() - false + playbackManager.setDataSource(currentSong, force) { success -> + completion(success) } } + fun switchToLocalPlayback() { + playbackManager.switchToLocalPlayback(this::restorePlaybackState) + } + + fun switchToRemotePlayback(castSession: CastSession) { + playbackManager.switchToRemotePlayback(castSession, this::restorePlaybackState) + } + + private fun restorePlaybackState(wasPlaying: Boolean, progress: Int) { + playbackManager.setCallbacks(this) + openTrackAndPrepareNextAt(position) { success -> + if (success) { + seek(progress) + if (wasPlaying) { + play() + } + } + } + playbackManager.setCrossFadeDuration(crossFadeDuration) + } + private fun playFromPlaylist(intent: Intent) { val playlist: AbsSmartPlaylist? = intent.getParcelableExtra(INTENT_EXTRA_PLAYLIST) val shuffleMode = intent.getIntExtra(INTENT_EXTRA_SHUFFLE_MODE, getShuffleMode()) @@ -1347,10 +1295,7 @@ class MusicService : MediaBrowserServiceCompat(), musicPlayerHandlerThread?.quitSafely() queueSaveHandler?.removeCallbacksAndMessages(null) queueSaveHandlerThread?.quitSafely() - if (playback != null) { - playback?.release() - } - playback = null + playbackManager.release() mediaSession?.release() } @@ -1508,9 +1453,5 @@ class MusicService : MediaBrowserServiceCompat(), null } } - - private fun getTrackUri(song: Song): String { - return getSongFileUri(song.id).toString() - } } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/service/PlaybackHandler.kt b/app/src/main/java/code/name/monkey/retromusic/service/PlaybackHandler.kt index 8c92a8f5e..86fbb04ac 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/PlaybackHandler.kt +++ b/app/src/main/java/code/name/monkey/retromusic/service/PlaybackHandler.kt @@ -11,165 +11,116 @@ * 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 -package code.name.monkey.retromusic.service; +import android.media.AudioManager +import android.os.Handler +import android.os.Looper +import android.os.Message +import code.name.monkey.retromusic.util.PreferenceUtil.isAudioDucking +import code.name.monkey.retromusic.util.PreferenceUtil.isAudioFocusEnabled +import java.lang.ref.WeakReference -import static code.name.monkey.retromusic.service.MusicService.DUCK; -import static code.name.monkey.retromusic.service.MusicService.META_CHANGED; -import static code.name.monkey.retromusic.service.MusicService.PLAY_STATE_CHANGED; -import static code.name.monkey.retromusic.service.MusicService.REPEAT_MODE_NONE; -import static code.name.monkey.retromusic.service.MusicService.TRACK_ENDED; -import static code.name.monkey.retromusic.service.MusicService.TRACK_WENT_TO_NEXT; +internal class PlaybackHandler(service: MusicService, looper: Looper) : Handler(looper) { + private val mService: WeakReference + private var currentDuckVolume = 1.0f -import android.media.AudioManager; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; - -import androidx.annotation.NonNull; - -import java.lang.ref.WeakReference; - -import code.name.monkey.retromusic.util.PreferenceUtil; - -class PlaybackHandler extends Handler { - - @NonNull private final WeakReference mService; - private float currentDuckVolume = 1.0f; - - - 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; + override fun handleMessage(msg: Message) { + val service = mService.get() ?: return + when (msg.what) { + MusicService.DUCK -> { + if (isAudioDucking) { + currentDuckVolume -= .05f + if (currentDuckVolume > .2f) { + sendEmptyMessageDelayed(MusicService.DUCK, 10) + } else { + currentDuckVolume = .2f + } + } else { + currentDuckVolume = 1f + } + service.playback?.setVolume(currentDuckVolume) + } + MusicService.UNDUCK -> { + if (isAudioDucking) { + currentDuckVolume += .03f + if (currentDuckVolume < 1f) { + sendEmptyMessageDelayed(MusicService.UNDUCK, 10) + } else { + currentDuckVolume = 1f + } + } else { + currentDuckVolume = 1f + } + service.playback?.setVolume(currentDuckVolume) + } + MusicService.TRACK_WENT_TO_NEXT -> + if (service.pendingQuit || service.repeatMode == MusicService.REPEAT_MODE_NONE && service.isLastTrack) { + service.pause(false) + service.seek(0) + if (service.pendingQuit) { + service.pendingQuit = false + service.quit() + } + } else { + service.position = service.nextPosition + service.prepareNextImpl() + service.notifyChange(MusicService.META_CHANGED) + } + MusicService.TRACK_ENDED -> { + // if there is a timer finished, don't continue + if (service.pendingQuit + || service.repeatMode == MusicService.REPEAT_MODE_NONE && service.isLastTrack + ) { + service.notifyChange(MusicService.PLAY_STATE_CHANGED) + service.seek(0) + if (service.pendingQuit) { + service.pendingQuit = false + service.quit() + } + } else { + service.playNextSong(false) + } + sendEmptyMessage(MusicService.RELEASE_WAKELOCK) + } + MusicService.RELEASE_WAKELOCK -> service.releaseWakeLock() + MusicService.PREPARE_NEXT -> service.prepareNextImpl() + MusicService.RESTORE_QUEUES -> service.restoreQueuesAndPositionIfNecessary() + MusicService.FOCUS_CHANGE -> when (msg.arg1) { + AudioManager.AUDIOFOCUS_GAIN -> { + if (!service.isPlaying && service.isPausedByTransientLossOfFocus) { + service.play() + service.isPausedByTransientLossOfFocus = false + } + removeMessages(MusicService.DUCK) + sendEmptyMessage(MusicService.UNDUCK) + } + AudioManager.AUDIOFOCUS_LOSS -> { + // Lost focus for an unbounded amount of time: stop playback and release media playback + val isAudioFocusEnabled = isAudioFocusEnabled + if (!isAudioFocusEnabled) { + service.pause(true) + } + } + AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> { + // Lost focus for a short time, but we have to stop + // playback. We don't release the media playback because playback + // is likely to resume + val wasPlaying = service.isPlaying + service.pause(true) + service.isPausedByTransientLossOfFocus = wasPlaying + } + AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> { + // Lost focus for a short time, but it's ok to keep playing + // at an attenuated level + removeMessages(MusicService.UNDUCK) + sendEmptyMessage(MusicService.DUCK) + } + } + } } - switch (msg.what) { - case MusicService.DUCK: - if (PreferenceUtil.INSTANCE.isAudioDucking()) { - currentDuckVolume -= .05f; - if (currentDuckVolume > .2f) { - sendEmptyMessageDelayed(DUCK, 10); - } else { - currentDuckVolume = .2f; - } - } else { - currentDuckVolume = 1f; - } - service.playback.setVolume(currentDuckVolume); - break; - - case MusicService.UNDUCK: - if (PreferenceUtil.INSTANCE.isAudioDucking()) { - currentDuckVolume += .03f; - if (currentDuckVolume < 1f) { - sendEmptyMessageDelayed(MusicService.UNDUCK, 10); - } else { - currentDuckVolume = 1f; - } - } else { - currentDuckVolume = 1f; - } - service.playback.setVolume(currentDuckVolume); - break; - - case TRACK_WENT_TO_NEXT: - if (service.pendingQuit - || service.getRepeatMode() == REPEAT_MODE_NONE && service.isLastTrack()) { - service.pause(); - service.seek(0); - if (service.pendingQuit) { - service.pendingQuit = false; - service.quit(); - break; - } - } else { - service.position = service.nextPosition; - service.prepareNextImpl(); - service.notifyChange(META_CHANGED); - } - break; - - case TRACK_ENDED: - // if there is a timer finished, don't continue - if (service.pendingQuit - || service.getRepeatMode() == REPEAT_MODE_NONE && service.isLastTrack()) { - service.notifyChange(PLAY_STATE_CHANGED); - service.seek(0); - if (service.pendingQuit) { - service.pendingQuit = false; - service.quit(); - break; - } - } else { - service.playNextSong(false); - } - sendEmptyMessage(MusicService.RELEASE_WAKELOCK); - break; - - case MusicService.RELEASE_WAKELOCK: - service.releaseWakeLock(); - break; - - case MusicService.PLAY_SONG: - service.playSongAtImpl(msg.arg1); - break; - - case MusicService.SET_POSITION: - service.openTrackAndPrepareNextAt(msg.arg1); - service.notifyChange(PLAY_STATE_CHANGED); - break; - - case MusicService.PREPARE_NEXT: - service.prepareNextImpl(); - break; - - case MusicService.RESTORE_QUEUES: - service.restoreQueuesAndPositionIfNecessary(); - break; - - case MusicService.FOCUS_CHANGE: - switch (msg.arg1) { - case AudioManager.AUDIOFOCUS_GAIN: - if (!service.isPlaying() && service.isPausedByTransientLossOfFocus()) { - service.play(); - service.setPausedByTransientLossOfFocus(false); - } - removeMessages(DUCK); - sendEmptyMessage(MusicService.UNDUCK); - break; - - case AudioManager.AUDIOFOCUS_LOSS: - // Lost focus for an unbounded amount of time: stop playback and release media playback - boolean isAudioFocusEnabled = PreferenceUtil.INSTANCE.isAudioFocusEnabled(); - if (!isAudioFocusEnabled) { - service.forcePause(); - } - break; - - case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: - // Lost focus for a short time, but we have to stop - // playback. We don't release the media playback because playback - // is likely to resume - boolean wasPlaying = service.isPlaying(); - service.forcePause(); - service.setPausedByTransientLossOfFocus(wasPlaying); - break; - - case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: - // Lost focus for a short time, but it's ok to keep playing - // at an attenuated level - removeMessages(MusicService.UNDUCK); - sendEmptyMessage(DUCK); - break; - } - break; + init { + mService = WeakReference(service) } - } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/service/playback/Playback.kt b/app/src/main/java/code/name/monkey/retromusic/service/playback/Playback.kt index 4d98f3336..2330b179e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/playback/Playback.kt +++ b/app/src/main/java/code/name/monkey/retromusic/service/playback/Playback.kt @@ -14,6 +14,8 @@ package code.name.monkey.retromusic.service.playback +import code.name.monkey.retromusic.model.Song + interface Playback { @@ -23,7 +25,9 @@ interface Playback { val audioSessionId: Int - fun setDataSource(path: String, force: Boolean): Boolean + fun setDataSource( + song: Song, force: Boolean, completion: (success: Boolean) -> Unit, + ) fun setNextDataSource(path: String?) @@ -57,5 +61,7 @@ interface Playback { fun onTrackEnded() fun onTrackEndedWithCrossfade() + + fun onPlayStateChanged() } }