diff --git a/.gitignore b/.gitignore index 72fc76f27..0616b363c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ local.properties build/ release/ -deps/ +srclibs/ # Studio .idea/ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index df783de16..3e8f565a2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -50,8 +50,11 @@ + @@ -62,16 +65,8 @@ android:name=".playback.system.PlaybackService" android:foregroundServiceType="mediaPlayback" android:icon="@mipmap/ic_launcher" - android:exported="true" - android:roundIcon="@mipmap/ic_launcher"> - - - - - + android:exported="false" + android:roundIcon="@mipmap/ic_launcher" /> tag.key == R128_TRACK }?.let { tag -> trackGain += tag.value / 256f found = true diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt new file mode 100644 index 000000000..0173fc7b5 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt @@ -0,0 +1,27 @@ +package org.oxycblt.auxio.playback.system + +import android.content.BroadcastReceiver +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import androidx.core.content.ContextCompat + +/** + * Some apps like to party like it's 2011 and just blindly query for the ACTION_MEDIA_BUTTON + * intent to determine the media apps on a system. *Auxio does not expose this.* Auxio exposes + * a MediaSession that an app should control instead through the much better MediaController API. + * But who cares about that, we need to make sure the 3% of barely functioning TouchWiz devices + * running KitKat don't break! To prevent Auxio from not showing up at all in these apps, we + * declare a BroadcastReceiver that deliberately handles this event. This also means that Auxio + * will start without warning if you use the media buttons while the app exists, because I guess + * we just have to deal with this. + * @author OxygenCobalt + */ +class MediaButtonReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == Intent.ACTION_MEDIA_BUTTON) { + intent.component = ComponentName(context, PlaybackService::class.java) + ContextCompat.startForegroundService(context, intent) + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt index fdd373db6..7ef8d0a3e 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt @@ -20,7 +20,6 @@ package org.oxycblt.auxio.playback.system import android.app.NotificationManager import android.app.Service -import android.bluetooth.BluetoothDevice import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -88,7 +87,7 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac // System backend components private lateinit var audioReactor: AudioReactor private lateinit var widgets: WidgetController - private val systemReceiver = SystemEventReceiver() + private val systemReceiver = PlaybackReceiver() // Managers private val playbackManager = PlaybackStateManager.getInstance() @@ -102,10 +101,13 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac // --- SERVICE OVERRIDES --- - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - // Since this service exposes a media button intent, pass it off to the - // MediaSession if the intent really is an instance of one. - MediaButtonReceiver.handleIntent(mediaSession, intent) + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + if (intent.action == Intent.ACTION_MEDIA_BUTTON) { + // Workaround to get GadgetBridge and other apps that blindly query for + // ACTION_MEDIA_BUTTON working. + MediaButtonReceiver.handleIntent(mediaSession, intent) + } + return START_NOT_STICKY } @@ -146,18 +148,16 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac // Then the notif/headset callbacks IntentFilter().apply { + addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED) + addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY) + addAction(AudioManager.ACTION_HEADSET_PLUG) + addAction(ACTION_LOOP) addAction(ACTION_SHUFFLE) addAction(ACTION_SKIP_PREV) addAction(ACTION_PLAY_PAUSE) addAction(ACTION_SKIP_NEXT) addAction(ACTION_EXIT) - - addAction(BluetoothDevice.ACTION_ACL_CONNECTED) - addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED) - addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY) - addAction(Intent.ACTION_HEADSET_PLUG) - addAction(WidgetProvider.ACTION_WIDGET_UPDATE) registerReceiver(systemReceiver, this) @@ -449,15 +449,29 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac } /** - * A [BroadcastReceiver] for receiving system events from notifications, widgets, or - * headset plug events. + * A [BroadcastReceiver] for receiving general playback events from the system. */ - private inner class SystemEventReceiver : BroadcastReceiver() { + private inner class PlaybackReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { when (intent.action) { + // --- SYSTEM EVENTS --- + AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED -> { + when (intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1)) { + AudioManager.SCO_AUDIO_STATE_CONNECTED -> resumeFromPlug() + AudioManager.SCO_AUDIO_STATE_DISCONNECTED -> pauseFromPlug() + } + } - // --- NOTIFICATION CASES --- + AudioManager.ACTION_AUDIO_BECOMING_NOISY -> pauseFromPlug() + AudioManager.ACTION_HEADSET_PLUG -> { + when (intent.getIntExtra("state", -1)) { + 0 -> resumeFromPlug() + 1 -> pauseFromPlug() + } + } + + // --- AUXIO EVENTS --- ACTION_PLAY_PAUSE -> playbackManager.setPlaying( !playbackManager.isPlaying ) @@ -478,57 +492,41 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac stopForegroundAndNotification() } - // --- HEADSET CASES --- - - BluetoothDevice.ACTION_ACL_CONNECTED -> resumeFromPlug() - BluetoothDevice.ACTION_ACL_DISCONNECTED -> pauseFromPlug() - - AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED -> { - when (intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1)) { - AudioManager.SCO_AUDIO_STATE_CONNECTED -> resumeFromPlug() - AudioManager.SCO_AUDIO_STATE_DISCONNECTED -> pauseFromPlug() - } - } - - AudioManager.ACTION_AUDIO_BECOMING_NOISY -> pauseFromPlug() - - Intent.ACTION_HEADSET_PLUG -> { - when (intent.getIntExtra("state", -1)) { - CONNECTED -> resumeFromPlug() - DISCONNECTED -> pauseFromPlug() - } - } - WidgetProvider.ACTION_WIDGET_UPDATE -> widgets.update() - } - } - /** - * Resume from a headset plug event, as long as its allowed. - */ - private fun resumeFromPlug() { - if (playbackManager.song != null && settingsManager.doPlugMgt) { - logD("Device connected, resuming...") - - playbackManager.setPlaying(true) - } - } - - /** - * Pause from a headset plug, as long as its allowed. - */ - private fun pauseFromPlug() { - if (playbackManager.song != null && settingsManager.doPlugMgt) { - logD("Device disconnected, pausing...") - - playbackManager.setPlaying(false) + else -> handleSystemIntent(intent) } } } + private fun handleSystemIntent(intent: Intent) { + when (intent.action) { + + Intent.ACTION_MEDIA_BUTTON -> MediaButtonReceiver.handleIntent(mediaSession, intent) + } + } + + /** + * Resume from a headset plug event, as long as its allowed. + */ + private fun resumeFromPlug() { + if (playbackManager.song != null && settingsManager.doPlugMgt) { + logD("Device connected, resuming...") + playbackManager.setPlaying(true) + } + } + + /** + * Pause from a headset plug, as long as its allowed. + */ + private fun pauseFromPlug() { + if (playbackManager.song != null && settingsManager.doPlugMgt) { + logD("Device disconnected, pausing...") + playbackManager.setPlaying(false) + } + } + companion object { - private const val DISCONNECTED = 0 - private const val CONNECTED = 1 private const val POS_POLL_INTERVAL = 500L const val ACTION_LOOP = BuildConfig.APPLICATION_ID + ".action.LOOP" diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt index d09bb027f..edc753671 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt @@ -71,7 +71,6 @@ class SearchViewModel : ViewModel() { if (query.isEmpty() || musicStore == null) { mSearchResults.value = listOf() - return } @@ -80,7 +79,7 @@ class SearchViewModel : ViewModel() { val sort = Sort.ByName(true) val results = mutableListOf() - // A filter mode of null means to not filter at all. + // Note: a filter mode of null means to not filter at all. if (mFilterMode == null || mFilterMode == DisplayMode.SHOW_ARTISTS) { musicStore.artists.filterByOrNull(query)?.let { artists -> diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 335e9556e..b3e8db030 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -73,7 +73,7 @@ Pausieren wenn andere Töne abspielt wird [Bsp. Anrufe] Kopfhörerfokus Abspielen/Pausieren wenn sich die Kopfhörerverbindung ändert - ReplayGain (Nur MP3/FLAC) + ReplayGain (Experimentell) Aus Titel bevorzugen Album bevorzugen diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4b1250834..f3390d1da 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -85,7 +85,7 @@ Pause when other audio plays (ex. Calls) Headset focus Play/Pause when the headset connection changes - ReplayGain + ReplayGain (Experimental) Off Prefer track Prefer album diff --git a/info/FAQ.md b/info/FAQ.md index 9b5c909d4..f03c944a6 100644 --- a/info/FAQ.md +++ b/info/FAQ.md @@ -37,10 +37,12 @@ This is for a couple reason: defined ReplayGain standard for those. - Auxio doesn't recognize your ReplayGain tags. This is usually because of a non-standard tag like ID3v2's `RVAD` or an unrecognized name. +- Your tags use a ReplayGain value higher than 0. Due to technical limitations, Auxio does not support this right now. +I do plan to add it eventually. #### What is dynamic ReplayGain? -Dynamic ReplayGain is a quirk based off the FooBar2000 plugin that dynamically switches from track gain to album +Dynamic ReplayGain is a quirk setting based off the FooBar2000 plugin that dynamically switches from track gain to album gain depending on if the current playback is from an album or not. #### Why are accents lighter/less saturated in dark mode? diff --git a/prebuild.py b/prebuild.py index 810f23316..c0667502b 100755 --- a/prebuild.py +++ b/prebuild.py @@ -30,7 +30,7 @@ def sh(cmd): print(FATAL + "fatal:" + NC + " command failed with exit code " + str(code)) sys.exit(1) -exoplayer_path = os.path.join(os.path.abspath(os.curdir), "deps", "exoplayer") +exoplayer_path = os.path.join(os.path.abspath(os.curdir), "srclibs", "exoplayer") if os.path.exists(exoplayer_path): reinstall = input(INFO + "info:" + NC + " exoplayer is already installed. would you like to reinstall it? [y/n] ") @@ -69,7 +69,7 @@ if ndk_path is None or not os.path.isfile(os.path.join(ndk_path, "ndk_build")): system.exit(1) # Now try to install ExoPlayer. -sh("rm -rf deps") +sh("rm -rf srclibs") print(INFO + "info:" + NC + " cloning exoplayer...") sh("git clone https://github.com/oxygencobalt/ExoPlayer.git " + exoplayer_path) diff --git a/settings.gradle b/settings.gradle index 37f1807a0..16811fffa 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,4 @@ include ':app' rootProject.name = "Auxio" gradle.ext.exoplayerModulePrefix = 'exoplayer-' -apply from: file("deps/exoplayer/core_settings.gradle") \ No newline at end of file +apply from: file("srclibs/exoplayer/core_settings.gradle") \ No newline at end of file