From ee7545f64eb56ddf6b4d7e8d9e0fb4207f4e17ee Mon Sep 17 00:00:00 2001 From: Prathamesh More Date: Sat, 18 Sep 2021 14:10:22 +0530 Subject: [PATCH] Fixed Audio Fade, Crossfade & Android Auto search --- app/build.gradle | 6 +- app/src/main/AndroidManifest.xml | 4 +- .../player/blur/BlurPlayerFragment.kt | 3 +- .../player/cardblur/CardBlurFragment.kt | 3 +- .../retromusic/helper/MusicPlayerRemote.kt | 15 +-- .../monkey/retromusic/service/AudioFader.kt | 96 +++++++++++-------- .../service/MediaSessionCallback.kt | 42 +++++++- .../retromusic/service/MusicService.java | 61 ++++++++---- .../retromusic/service/playback/Playback.kt | 2 + .../name/monkey/retromusic/util/FileUtil.java | 81 ++++++++++++++++ .../monkey/retromusic/util/PreferenceUtil.kt | 5 +- app/src/main/res/xml/pref_audio.xml | 1 + .../main/res/xml/pref_now_playing_screen.xml | 1 + .../prefs/supportv7/ATESeekBarPreference.kt | 24 +++++ appthemehelper/src/main/res/values/attrs.xml | 4 + 15 files changed, 269 insertions(+), 79 deletions(-) mode change 100755 => 100644 appthemehelper/src/main/res/values/attrs.xml diff --git a/app/build.gradle b/app/build.gradle index 2cf04291..f12388f0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,7 +6,6 @@ apply plugin: 'kotlin-parcelize' android { compileSdkVersion 31 - buildToolsVersion = '29.0.3' defaultConfig { minSdkVersion 21 @@ -33,6 +32,8 @@ android { buildTypes { release { //debuggable true + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release } debug { @@ -104,7 +105,7 @@ dependencies { //WebServer by NanoHttpd implementation "org.nanohttpd:nanohttpd:2.3.1" - def nav_version = '2.4.0-alpha09' + def nav_version = "2.4.0-alpha09" implementation "androidx.navigation:navigation-runtime-ktx:$nav_version" implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-ui-ktx:$nav_version" @@ -158,6 +159,7 @@ dependencies { implementation 'com.heinrichreimersoftware:material-intro:2.0.0' implementation 'com.github.dhaval2404:imagepicker:1.7.1' implementation 'me.zhanghai.android.fastscroll:library:1.1.7' + implementation 'cat.ereza:customactivityoncrash:2.3.0' debugImplementation 'com.amitshekhar.android:debug-db:1.0.6' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0ef67ef1..917cad85 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -265,7 +265,7 @@ android:value="true" /> - + 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 - } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MediaSessionCallback.kt b/app/src/main/java/code/name/monkey/retromusic/service/MediaSessionCallback.kt index c901a391..379ffa64 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/MediaSessionCallback.kt +++ b/app/src/main/java/code/name/monkey/retromusic/service/MediaSessionCallback.kt @@ -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 = 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() + 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() diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java index 6ea0e2f4..44559021 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java +++ b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java @@ -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 originalPlayingQueue = new ArrayList<>(); private List 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 { 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 7925bef9..7dbf931f 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 @@ -51,5 +51,7 @@ interface Playback { fun onTrackWentToNext() fun onTrackEnded() + + fun onTrackEndedWithCrossfade() } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java index 0023ec5f..8a125a14 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java @@ -28,13 +28,22 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; +import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.StringTokenizer; + +import code.name.monkey.retromusic.adapter.Storage; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.repository.RealSongRepository; +import code.name.monkey.retromusic.repository.SortedCursor; import code.name.monkey.retromusic.model.Song; import code.name.monkey.retromusic.repository.RealSongRepository; @@ -258,4 +267,76 @@ public final class FileUtil { return file.getAbsoluteFile(); } } + + // https://github.com/DrKLO/Telegram/blob/ab221dafadbc17459d78d9ea3e643ae18e934b16/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertDocumentLayout.java#L939 + public static ArrayList listRoots() { + ArrayList storageItems = new ArrayList<>(); + HashSet paths = new HashSet<>(); + String defaultPath = Environment.getExternalStorageDirectory().getPath(); + String defaultPathState = Environment.getExternalStorageState(); + if (defaultPathState.equals(Environment.MEDIA_MOUNTED) || defaultPathState.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) { + Storage ext = new Storage(); + if (Environment.isExternalStorageRemovable()) { + ext.title = "SD Card"; + } else { + ext.title = "Internal Storage"; + } + ext.file = Environment.getExternalStorageDirectory(); + storageItems.add(ext); + paths.add(defaultPath); + } + + BufferedReader bufferedReader = null; + try { + bufferedReader = new BufferedReader(new FileReader("/proc/mounts")); + String line; + while ((line = bufferedReader.readLine()) != null) { + if (line.contains("vfat") || line.contains("/mnt")) { + StringTokenizer tokens = new StringTokenizer(line, " "); + tokens.nextToken(); + String path = tokens.nextToken(); + if (paths.contains(path)) { + continue; + } + if (line.contains("/dev/block/vold")) { + if (!line.contains("/mnt/secure") && !line.contains("/mnt/asec") && !line.contains("/mnt/obb") && !line.contains("/dev/mapper") && !line.contains("tmpfs")) { + if (!new File(path).isDirectory()) { + int index = path.lastIndexOf('/'); + if (index != -1) { + String newPath = "/storage/" + path.substring(index + 1); + if (new File(newPath).isDirectory()) { + path = newPath; + } + } + } + paths.add(path); + try { + Storage item = new Storage(); + if (path.toLowerCase().contains("sd")) { + item.title = "SD Card"; + } else { + item.title = "External Storage"; + } + item.file = new File(path); + storageItems.add(item); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + return storageItems; + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.kt b/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.kt index 62a24375..60f09831 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.kt +++ b/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.kt @@ -269,6 +269,8 @@ object PreferenceUtil { BLURRED_ALBUM_ART, false ) + val blurAmount get() = sharedPreferences.getInt(NEW_BLUR_AMOUNT, 25) + val isCarouselEffect get() = sharedPreferences.getBoolean( CAROUSEL_EFFECT, false @@ -608,8 +610,7 @@ object PreferenceUtil { val isWhiteList: Boolean get() = sharedPreferences.getBoolean(WHITELIST_MUSIC, false) - var crossFadeDuration + val crossFadeDuration get() = sharedPreferences .getInt(CROSS_FADE_DURATION, 0) - set(value) = sharedPreferences.edit { putInt(CROSS_FADE_DURATION, value) } } diff --git a/app/src/main/res/xml/pref_audio.xml b/app/src/main/res/xml/pref_audio.xml index 1732634e..d9a02bb4 100755 --- a/app/src/main/res/xml/pref_audio.xml +++ b/app/src/main/res/xml/pref_audio.xml @@ -21,6 +21,7 @@ android:defaultValue="0" android:key="cross_fade_duration" android:layout="@layout/list_item_view_seekbar" + app:ateKey_pref_unit="s" android:max="12" android:summary="@string/pref_summary_cross_fade" android:title="@string/pref_title_cross_fade" diff --git a/app/src/main/res/xml/pref_now_playing_screen.xml b/app/src/main/res/xml/pref_now_playing_screen.xml index 43b980de..d01bd1cd 100644 --- a/app/src/main/res/xml/pref_now_playing_screen.xml +++ b/app/src/main/res/xml/pref_now_playing_screen.xml @@ -86,6 +86,7 @@ android:summary="@string/pref_blur_amount_summary" android:title="@string/pref_blur_amount_title" app:icon="@drawable/ic_blur_on" + app:ateKey_pref_unit="px" app:showSeekBarValue="true" /> diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATESeekBarPreference.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATESeekBarPreference.kt index 213971b2..098cb840 100644 --- a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATESeekBarPreference.kt +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATESeekBarPreference.kt @@ -1,10 +1,13 @@ package code.name.monkey.appthemehelper.common.prefs.supportv7 import android.content.Context +import android.text.Editable import android.util.AttributeSet import android.widget.SeekBar +import android.widget.TextView import androidx.core.graphics.BlendModeColorFilterCompat import androidx.core.graphics.BlendModeCompat +import androidx.core.widget.doAfterTextChanged import androidx.preference.PreferenceViewHolder import androidx.preference.SeekBarPreference import code.name.monkey.appthemehelper.R @@ -19,7 +22,16 @@ class ATESeekBarPreference @JvmOverloads constructor( defStyleRes: Int = -1 ) : SeekBarPreference(context, attrs, defStyleAttr, defStyleRes) { + var unit: String = "" + init { + val attributes = + context.obtainStyledAttributes(attrs, R.styleable.ATESeekBarPreference, 0, 0) + + attributes.getString(R.styleable.ATESeekBarPreference_ateKey_pref_unit)?.let { + unit = it + } + attributes.recycle() icon?.colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat( ATHUtil.resolveColor( context, @@ -32,5 +44,17 @@ class ATESeekBarPreference @JvmOverloads constructor( super.onBindViewHolder(view) val seekBar = view.findViewById(R.id.seekbar) as SeekBar TintHelper.setTintAuto(seekBar, ThemeStore.accentColor(context), false) + (view.findViewById(R.id.seekbar_value) as TextView).apply { + appendUnit(editableText) + doAfterTextChanged { + appendUnit(it) + } + } + } + + private fun TextView.appendUnit(editable: Editable?) { + if (!editable.toString().endsWith(unit)) { + append(unit) + } } } diff --git a/appthemehelper/src/main/res/values/attrs.xml b/appthemehelper/src/main/res/values/attrs.xml old mode 100755 new mode 100644 index bce101f2..2288b702 --- a/appthemehelper/src/main/res/values/attrs.xml +++ b/appthemehelper/src/main/res/values/attrs.xml @@ -16,6 +16,10 @@ + + + +