playback: add audioeffect integration [#211]

Add support for external AudioEffect implementers, like Wavelet.

Doing this involves doing a weird broadcast dance with AudioEffect
Intents at special points to indicate a valid audio session that
can be manipulated. Still has some issues, such as a null name
showing up in wavelet. As far as I am aware, this is the best
possible system I can do, and allows me to delegate an equalizer
implementation to other apps instead of making my own.

Resolves #211.
This commit is contained in:
OxygenCobalt 2022-08-09 16:21:42 -06:00
parent deffe065d5
commit ce2e950a9b
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
2 changed files with 32 additions and 0 deletions

View file

@ -3,6 +3,7 @@
## dev
#### What's New
- Added basic equalizer support in external apps like Wavelet
- Detail UI now displays the type of item shown (ex. the release type)
#### What's Fixed

View file

@ -23,6 +23,7 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.media.AudioManager
import android.media.audiofx.AudioEffect
import android.os.IBinder
import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.ExoPlayer
@ -93,6 +94,7 @@ class PlaybackService :
// State
private lateinit var foregroundManager: ForegroundManager
private var hasPlayed = false
private var openAudioEffectSession = false
// Coroutines
private val serviceJob = Job()
@ -175,7 +177,13 @@ class PlaybackService :
widgetComponent.release()
mediaSessionComponent.release()
player.release()
if (openAudioEffectSession) {
// Make sure to close the audio session when we release the player.
broadcastAudioEffectAction(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)
openAudioEffectSession = false
}
logD("Service destroyed")
}
@ -197,6 +205,7 @@ class PlaybackService :
override fun onPlaybackStateChanged(state: Int) {
when (state) {
Player.STATE_ENDED -> {
logD("State ended")
if (playbackManager.repeatMode == RepeatMode.TRACK) {
playbackManager.rewind()
if (settings.pauseOnRepeat) {
@ -250,6 +259,12 @@ class PlaybackService :
// Stop the foreground state if there's nothing to play.
logD("Nothing playing, stopping playback")
player.stop()
if (openAudioEffectSession) {
// Make sure to close the audio session when we stop playback.
broadcastAudioEffectAction(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)
openAudioEffectSession = false
}
stopAndSave()
return
}
@ -257,6 +272,14 @@ class PlaybackService :
logD("Loading ${song.rawName}")
player.setMediaItem(MediaItem.fromUri(song.uri))
player.prepare()
if (!openAudioEffectSession) {
// Wavelet does not like it if you start an audio effect session without having
// something within your player buffer. Make sure we only start one when we load
// a song.
broadcastAudioEffectAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION)
openAudioEffectSession = true
}
}
override fun seekTo(positionMs: Long) {
@ -330,6 +353,14 @@ class PlaybackService :
.build()
}
private fun broadcastAudioEffectAction(event: String) {
sendBroadcast(
Intent(event)
.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, packageName)
.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, player.audioSessionId)
.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC))
}
/** Stop the foreground state and hide the notification */
private fun stopAndSave() {
if (foregroundManager.tryStopForeground()) {