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()
}
}