Fixed Audio Fade, Crossfade & Android Auto search

This commit is contained in:
Prathamesh More 2021-09-18 14:10:22 +05:30
parent 7f683eb0ee
commit ee7545f64e
15 changed files with 269 additions and 79 deletions

View file

@ -1,50 +1,66 @@
package code.name.monkey.retromusic.service
import android.animation.Animator
import android.animation.ValueAnimator
import android.media.MediaPlayer
import androidx.core.animation.doOnEnd
import code.name.monkey.retromusic.service.playback.Playback
import java.util.*
import code.name.monkey.retromusic.util.PreferenceUtil
class AudioFader(
private val player: Playback,
durationMillis: Long,
private val fadeIn: Boolean,
private val doOnEnd: Runnable
) {
val timer = Timer()
var volume = if (fadeIn) 0F else 1F
val maxVolume = if (fadeIn) 1F else 0F
private val volumeStep: Float = PERIOD / durationMillis.toFloat()
class AudioFader {
companion object {
fun start() {
timer.scheduleAtFixedRate(
object : TimerTask() {
override fun run() {
setVolume()
if (volume < 0 || volume > 1) {
player.setVolume(maxVolume)
stop()
doOnEnd.run()
} else {
player.setVolume(volume)
}
@JvmStatic
inline fun createFadeAnimator(
fadeIn: Boolean /* fadeIn -> true fadeOut -> false*/,
mediaPlayer: MediaPlayer,
crossinline endAction: (animator: Animator) -> Unit /* Code to run when Animator Ends*/
): Animator? {
val duration = PreferenceUtil.crossFadeDuration * 1000
if (duration == 0) {
return null
}
val startValue = if (fadeIn) 0f else 1.0f
val endValue = if (fadeIn) 1.0f else 0f
return ValueAnimator.ofFloat(startValue, endValue).apply {
this.duration = duration.toLong()
addUpdateListener { animation: ValueAnimator ->
mediaPlayer.setVolume(
animation.animatedValue as Float, animation.animatedValue as Float
)
}
}, 0, PERIOD
)
}
doOnEnd {
endAction(it)
// Set end values
mediaPlayer.setVolume(endValue, endValue)
}
}
}
fun stop() {
timer.purge()
timer.cancel()
}
private fun setVolume() {
if (fadeIn) {
volume += volumeStep
} else {
volume -= volumeStep
@JvmStatic
fun startFadeAnimator(
playback: Playback,
fadeIn: Boolean /* fadeIn -> true fadeOut -> false*/,
callback: Runnable /* Code to run when Animator Ends*/
) {
val duration = PreferenceUtil.audioFadeDuration.toLong()
if (duration == 0L) {
callback.run()
return
}
val startValue = if (fadeIn) 0f else 1.0f
val endValue = if (fadeIn) 1.0f else 0f
val animator = ValueAnimator.ofFloat(startValue, endValue)
animator.duration = duration
animator.addUpdateListener { animation: ValueAnimator ->
playback.setVolume(
animation.animatedValue as Float
)
}
animator.doOnEnd {
callback.run()
}
animator.start()
}
}
companion object {
const val PERIOD = 100L
}
}

View file

@ -17,6 +17,7 @@ package code.name.monkey.retromusic.service
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.provider.MediaStore
import android.support.v4.media.session.MediaSessionCompat
import code.name.monkey.retromusic.auto.AutoMediaIDHelper
import code.name.monkey.retromusic.helper.MusicPlayerRemote
@ -31,7 +32,6 @@ import code.name.monkey.retromusic.service.MusicService.*
import code.name.monkey.retromusic.util.MusicUtil
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.util.*
/**
@ -56,8 +56,7 @@ class MediaSessionCallback(
println(musicId)
val itemId = musicId?.toLong() ?: -1
val songs: ArrayList<Song> = ArrayList()
val category = AutoMediaIDHelper.extractCategory(mediaId)
when (category) {
when (val category = AutoMediaIDHelper.extractCategory(mediaId)) {
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM -> {
val album: Album = albumRepository.album(itemId)
songs.addAll(album.songs)
@ -111,6 +110,43 @@ class MediaSessionCallback(
musicService.play()
}
override fun onPlayFromSearch(query: String?, extras: Bundle?) {
val songs = ArrayList<Song>()
if (query.isNullOrEmpty()) {
// The user provided generic string e.g. 'Play music'
// Build appropriate playlist queue
songs.addAll(songRepository.songs())
} else {
// Build a queue based on songs that match "query" or "extras" param
val mediaFocus: String? = extras?.getString(MediaStore.EXTRA_MEDIA_FOCUS)
if (mediaFocus == MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE) {
val artistQuery = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST)
if (artistQuery != null) {
artistRepository.artists(artistQuery).forEach {
songs.addAll(it.songs)
}
}
} else if (mediaFocus == MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE) {
val albumQuery = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM)
if (albumQuery != null) {
albumRepository.albums(albumQuery).forEach {
songs.addAll(it.songs)
}
}
}
}
if (songs.isEmpty()) {
// No focus found, search by query for song title
query?.also {
songs.addAll(songRepository.songs(it))
}
}
musicService.openQueue(songs, 0, true)
musicService.play()
}
override fun onPlay() {
super.onPlay()

View file

@ -21,6 +21,7 @@ import static code.name.monkey.retromusic.ConstantsKt.CLASSIC_NOTIFICATION;
import static code.name.monkey.retromusic.ConstantsKt.COLORED_NOTIFICATION;
import static code.name.monkey.retromusic.ConstantsKt.CROSS_FADE_DURATION;
import static code.name.monkey.retromusic.ConstantsKt.TOGGLE_HEADSET;
import static code.name.monkey.retromusic.service.AudioFader.startFadeAnimator;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
@ -97,12 +98,14 @@ import code.name.monkey.retromusic.util.MusicUtil;
import code.name.monkey.retromusic.util.PackageValidator;
import code.name.monkey.retromusic.util.PreferenceUtil;
import code.name.monkey.retromusic.util.RetroUtil;
import code.name.monkey.retromusic.volume.AudioVolumeObserver;
import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener;
/**
* @author Karim Abou Zeid (kabouzeid), Andrew Neal
*/
public class MusicService extends MediaBrowserServiceCompat
implements SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks {
implements SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks, OnAudioVolumeChangedListener {
public static final String TAG = MusicService.class.getSimpleName();
public static final String RETRO_MUSIC_PACKAGE_NAME = "code.name.monkey.retromusic";
@ -238,6 +241,7 @@ public class MusicService extends MediaBrowserServiceCompat
private List<Song> originalPlayingQueue = new ArrayList<>();
private List<Song> playingQueue = new ArrayList<>();
private boolean pausedByTransientLossOfFocus;
private AudioVolumeObserver audioVolumeObserver = null;
private final BroadcastReceiver becomingNoisyReceiver =
new BroadcastReceiver() {
@ -348,7 +352,6 @@ public class MusicService extends MediaBrowserServiceCompat
private ThrottledSeekHandler throttledSeekHandler;
private Handler uiThreadHandler;
private PowerManager.WakeLock wakeLock;
private AudioFader fader;
private static Bitmap copy(Bitmap bitmap) {
Bitmap.Config config = bitmap.getConfig();
@ -446,6 +449,9 @@ public class MusicService extends MediaBrowserServiceCompat
.registerContentObserver(
MediaStore.Audio.Playlists.INTERNAL_CONTENT_URI, true, mediaStoreObserver);
audioVolumeObserver = new AudioVolumeObserver(this);
audioVolumeObserver.register(AudioManager.STREAM_MUSIC, this);
PreferenceUtil.INSTANCE.registerOnSharedPreferenceChangedListener(this);
restoreState();
@ -491,6 +497,23 @@ public class MusicService extends MediaBrowserServiceCompat
wakeLock.acquire(milli);
}
boolean pausedByZeroVolume;
@Override
public void onAudioVolumeChanged(int currentVolume, int maxVolume) {
if (PreferenceUtil.INSTANCE.isPauseOnZeroVolume()) {
if (isPlaying() && currentVolume < 1) {
pause();
System.out.println("Paused");
pausedByZeroVolume = true;
} else if (pausedByZeroVolume && currentVolume >= 1) {
System.out.println("Played");
play();
pausedByZeroVolume = false;
}
}
}
public void addSong(int position, Song song) {
playingQueue.add(position, song);
originalPlayingQueue.add(position, song);
@ -558,10 +581,10 @@ public class MusicService extends MediaBrowserServiceCompat
}
public Song getNextSong() {
if (!isLastTrack() || getRepeatMode() == REPEAT_MODE_THIS) {
return getSongAt(getNextPosition(false));
} else {
if (isLastTrack() && getRepeatMode() == REPEAT_MODE_NONE) {
return null;
} else {
return getSongAt(getNextPosition(false));
}
}
@ -926,6 +949,13 @@ public class MusicService extends MediaBrowserServiceCompat
playerHandler.sendEmptyMessage(TRACK_ENDED);
}
@Override
public void onTrackEndedWithCrossfade() {
trackEndedByCrossfade = true;
acquireWakeLock(30000);
playerHandler.sendEmptyMessage(TRACK_ENDED);
}
@Override
public void onTrackWentToNext() {
playerHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
@ -981,16 +1011,11 @@ public class MusicService extends MediaBrowserServiceCompat
public void pause() {
pausedByTransientLossOfFocus = false;
if (playback != null && playback.isPlaying()) {
if (fader != null) {
fader.stop();
}
fader = new AudioFader(playback, PreferenceUtil.INSTANCE.getAudioFadeDuration(), false, () -> {
if (playback != null && playback.isPlaying()) {
playback.pause();
notifyChange(PLAY_STATE_CHANGED);
}
startFadeAnimator(playback, false, () -> {
//Code to run when Animator Ends
playback.pause();
notifyChange(PLAY_STATE_CHANGED);
});
fader.start();
}
}
@ -1013,10 +1038,8 @@ public class MusicService extends MediaBrowserServiceCompat
if (MusicPlayerRemote.INSTANCE.isCasting()) {
return;
}
if (fader != null) {
fader.stop();
}
fader = new AudioFader(playback, PreferenceUtil.INSTANCE.getAudioFadeDuration(), false, () -> {
startFadeAnimator(playback, true, () -> {
// Code when Animator Ends
if (!becomingNoisyReceiverRegistered) {
registerReceiver(becomingNoisyReceiver, becomingNoisyReceiverIntentFilter);
becomingNoisyReceiverRegistered = true;
@ -1031,9 +1054,9 @@ public class MusicService extends MediaBrowserServiceCompat
playerHandler.removeMessages(DUCK);
playerHandler.sendEmptyMessage(UNDUCK);
});
//Start Playback with Animator
playback.start();
notifyChange(PLAY_STATE_CHANGED);
fader.start();
}
}
} else {

View file

@ -51,5 +51,7 @@ interface Playback {
fun onTrackWentToNext()
fun onTrackEnded()
fun onTrackEndedWithCrossfade()
}
}