Skip to content
This repository was archived by the owner on Aug 7, 2024. It is now read-only.

Commit c3f0b61

Browse files
authored
Merge pull request #102 from you-apps/equalizer
feat: implement in-app equalizer
2 parents 6a6b48a + c742090 commit c3f0b61

File tree

9 files changed

+408
-3
lines changed

9 files changed

+408
-3
lines changed

app/src/main/java/app/suhasdissa/vibeyou/MellowMusicApplication.kt

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.content.ComponentName
55
import android.graphics.Color
66
import androidx.media3.session.MediaController
77
import androidx.media3.session.SessionToken
8+
import app.suhasdissa.vibeyou.backend.data.EqualizerData
89
import app.suhasdissa.vibeyou.backend.database.SongDatabase
910
import app.suhasdissa.vibeyou.backend.services.PlayerService
1011
import app.suhasdissa.vibeyou.utils.Pref
@@ -20,6 +21,11 @@ class MellowMusicApplication : Application(), ImageLoaderFactory {
2021
lateinit var container: AppContainer
2122
var accentColor: Int = Color.TRANSPARENT
2223

24+
/**
25+
* Data stored here with the details of the equalizer
26+
*/
27+
var supportedEqualizerData: EqualizerData? = null
28+
2329
override fun onCreate() {
2430
super.onCreate()
2531
val sessionToken =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package app.suhasdissa.vibeyou.backend.data
2+
3+
data class EqualizerBand(
4+
val frequency: Int,
5+
val minLevel: Short,
6+
val maxLevel: Short,
7+
)
8+
9+
data class EqualizerData(
10+
val presets: List<String>,
11+
val bands: List<EqualizerBand>
12+
)

app/src/main/java/app/suhasdissa/vibeyou/backend/services/PlayerService.kt

+90
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import android.graphics.BitmapFactory
77
import android.graphics.Canvas
88
import android.graphics.Paint
99
import android.graphics.Rect
10+
import android.media.audiofx.Equalizer
1011
import android.media.audiofx.LoudnessEnhancer
1112
import android.net.Uri
13+
import android.os.Bundle
1214
import android.os.Handler
1315
import android.util.Log
1416
import androidx.annotation.ColorInt
@@ -17,6 +19,7 @@ import androidx.media3.common.AudioAttributes
1719
import androidx.media3.common.C
1820
import androidx.media3.common.MediaItem
1921
import androidx.media3.common.Player
22+
import androidx.media3.common.util.UnstableApi
2023
import androidx.media3.database.StandaloneDatabaseProvider
2124
import androidx.media3.datasource.DataSource
2225
import androidx.media3.datasource.DefaultDataSource
@@ -39,7 +42,11 @@ import androidx.media3.exoplayer.source.MediaSource
3942
import androidx.media3.session.BitmapLoader
4043
import androidx.media3.session.MediaSession
4144
import androidx.media3.session.MediaSessionService
45+
import androidx.media3.session.SessionCommand
46+
import androidx.media3.session.SessionResult
4247
import app.suhasdissa.vibeyou.MellowMusicApplication
48+
import app.suhasdissa.vibeyou.backend.data.EqualizerBand
49+
import app.suhasdissa.vibeyou.backend.data.EqualizerData
4350
import app.suhasdissa.vibeyou.backend.data.Song
4451
import app.suhasdissa.vibeyou.utils.DynamicDataSource
4552
import app.suhasdissa.vibeyou.utils.IS_LOCAL_KEY
@@ -58,16 +65,46 @@ import kotlinx.coroutines.launch
5865
import kotlinx.coroutines.runBlocking
5966
import kotlinx.coroutines.withContext
6067

68+
@UnstableApi
6169
class PlayerService : MediaSessionService(), MediaSession.Callback, Player.Listener {
6270
private var mediaSession: MediaSession? = null
6371
private lateinit var cache: SimpleCache
6472
private lateinit var player: ExoPlayer
73+
private lateinit var equalizer: Equalizer
6574

6675
private var loudnessEnhancer: LoudnessEnhancer? = null
6776

6877
val appInstance get() = application as MellowMusicApplication
6978
val container get() = appInstance.container
7079

80+
private val mediaSessionCallback = object : MediaSession.Callback {
81+
override fun onConnect(
82+
session: MediaSession,
83+
controller: MediaSession.ControllerInfo
84+
): MediaSession.ConnectionResult {
85+
val sessionCommands = MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
86+
.add(SessionCommand(COMMAND_UPDATE_EQUALIZER, Bundle.EMPTY))
87+
.build()
88+
89+
return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
90+
.setAvailableSessionCommands(sessionCommands)
91+
.build()
92+
}
93+
94+
override fun onCustomCommand(
95+
session: MediaSession,
96+
controller: MediaSession.ControllerInfo,
97+
customCommand: SessionCommand,
98+
args: Bundle
99+
): ListenableFuture<SessionResult> {
100+
if (customCommand.customAction == COMMAND_UPDATE_EQUALIZER) {
101+
updateEqualizerSettings()
102+
}
103+
104+
return super.onCustomCommand(session, controller, customCommand, args)
105+
}
106+
}
107+
71108
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
72109
override fun onCreate() {
73110
super.onCreate()
@@ -86,13 +123,15 @@ class PlayerService : MediaSessionService(), MediaSession.Callback, Player.Liste
86123
cache = SimpleCache(directory, cacheEvictor, StandaloneDatabaseProvider(this))
87124

88125
player = createPlayer()
126+
setupEqualizer()
89127

90128
player.repeatMode = Player.REPEAT_MODE_OFF
91129
player.playWhenReady = true
92130
player.addListener(this)
93131

94132
mediaSession = MediaSession.Builder(this, player).setCallback(this)
95133
.setBitmapLoader(CustomBitmapLoader(this))
134+
.setCallback(mediaSessionCallback)
96135
.build()
97136
}
98137

@@ -304,4 +343,55 @@ class PlayerService : MediaSessionService(), MediaSession.Callback, Player.Liste
304343
)
305344
}
306345
}
346+
347+
private fun setupEqualizer() {
348+
equalizer = Equalizer(Integer.MAX_VALUE, player.audioSessionId)
349+
appInstance.supportedEqualizerData = EqualizerData(
350+
presets = (0 until equalizer.numberOfPresets).map {
351+
equalizer.getPresetName(it.toShort())
352+
},
353+
bands = (0 until equalizer.numberOfBands).map { id ->
354+
val freqRange = equalizer.bandLevelRange
355+
EqualizerBand(
356+
equalizer.getCenterFreq(id.toShort()),
357+
freqRange.first(),
358+
freqRange.last()
359+
)
360+
}
361+
)
362+
363+
updateEqualizerSettings()
364+
}
365+
366+
/* private fun useSystemEqualizer() {
367+
val intent = Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
368+
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, player.audioSessionId);
369+
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, packageName);
370+
sendBroadcast(intent);
371+
}*/
372+
373+
private fun updateEqualizerSettings() {
374+
if (!::equalizer.isInitialized) return
375+
376+
equalizer.enabled = Pref.sharedPreferences.getBoolean(Pref.equalizerKey, false)
377+
if (!equalizer.enabled) return
378+
379+
val preset = Pref.sharedPreferences.getInt(Pref.equalizerPresetKey, -1)
380+
if (preset != -1) {
381+
equalizer.usePreset(preset.toShort())
382+
return
383+
}
384+
385+
val bandPrefs = Pref.sharedPreferences.getString(Pref.equalizerBandsKey, "")
386+
if (bandPrefs.isNullOrEmpty()) return
387+
388+
val bandLevels = bandPrefs.split(",").map(String::toShort)
389+
for (i in bandLevels.indices) {
390+
equalizer.setBandLevel(i.toShort(), bandLevels[i])
391+
}
392+
}
393+
394+
companion object {
395+
const val COMMAND_UPDATE_EQUALIZER = "update_equalizer"
396+
}
307397
}

app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/PlayerViewModel.kt

+10
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package app.suhasdissa.vibeyou.backend.viewmodel
22

33
import android.net.Uri
4+
import android.os.Bundle
45
import android.os.Handler
56
import android.os.Looper
67
import android.util.Log
78
import androidx.compose.runtime.getValue
89
import androidx.compose.runtime.mutableStateOf
910
import androidx.compose.runtime.setValue
11+
import androidx.core.os.bundleOf
1012
import androidx.lifecycle.ViewModel
1113
import androidx.lifecycle.ViewModelProvider
1214
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
@@ -16,11 +18,13 @@ import androidx.lifecycle.viewmodel.viewModelFactory
1618
import androidx.media3.common.MediaItem
1719
import androidx.media3.common.PlaybackParameters
1820
import androidx.media3.session.MediaController
21+
import androidx.media3.session.SessionCommand
1922
import app.suhasdissa.vibeyou.MellowMusicApplication
2023
import app.suhasdissa.vibeyou.backend.data.Song
2124
import app.suhasdissa.vibeyou.backend.repository.LocalMusicRepository
2225
import app.suhasdissa.vibeyou.backend.repository.PipedMusicRepository
2326
import app.suhasdissa.vibeyou.backend.repository.SongDatabaseRepository
27+
import app.suhasdissa.vibeyou.backend.services.PlayerService
2428
import app.suhasdissa.vibeyou.utils.addNext
2529
import app.suhasdissa.vibeyou.utils.asMediaItem
2630
import app.suhasdissa.vibeyou.utils.enqueue
@@ -132,6 +136,12 @@ class PlayerViewModel(
132136
}
133137
}
134138

139+
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
140+
fun updateEqualizerSettings() {
141+
val command = SessionCommand(PlayerService.COMMAND_UPDATE_EQUALIZER, Bundle.EMPTY)
142+
controller!!.sendCustomCommand(command, Bundle.EMPTY)
143+
}
144+
135145
suspend fun isFavourite(id: String): Boolean {
136146
val song = songDatabaseRepository.getSongById(id)
137147
song ?: return false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package app.suhasdissa.vibeyou.ui.components
2+
3+
import androidx.compose.foundation.interaction.MutableInteractionSource
4+
import androidx.compose.material3.Slider
5+
import androidx.compose.material3.SliderColors
6+
import androidx.compose.material3.SliderDefaults
7+
import androidx.compose.runtime.Composable
8+
import androidx.compose.runtime.remember
9+
import androidx.compose.ui.Modifier
10+
import androidx.compose.ui.graphics.TransformOrigin
11+
import androidx.compose.ui.graphics.graphicsLayer
12+
import androidx.compose.ui.layout.layout
13+
import androidx.compose.ui.unit.Constraints
14+
15+
@Composable
16+
fun VerticalSlider(
17+
value: Float,
18+
onValueChange: (Float) -> Unit,
19+
modifier: Modifier = Modifier,
20+
enabled: Boolean = true,
21+
valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
22+
steps: Int = 0,
23+
onValueChangeFinished: (() -> Unit)? = null,
24+
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
25+
colors: SliderColors = SliderDefaults.colors()
26+
){
27+
Slider(
28+
colors = colors,
29+
interactionSource = interactionSource,
30+
onValueChangeFinished = onValueChangeFinished,
31+
steps = steps,
32+
valueRange = valueRange,
33+
enabled = enabled,
34+
value = value,
35+
onValueChange = onValueChange,
36+
modifier = Modifier
37+
.graphicsLayer {
38+
rotationZ = 270f
39+
transformOrigin = TransformOrigin(0f, 0f)
40+
}
41+
.layout { measurable, constraints ->
42+
val placeable = measurable.measure(
43+
Constraints(
44+
minWidth = constraints.minHeight,
45+
maxWidth = constraints.maxHeight,
46+
minHeight = constraints.minWidth,
47+
maxHeight = constraints.maxHeight,
48+
)
49+
)
50+
layout(placeable.height, placeable.width) {
51+
placeable.place(-placeable.width, 0)
52+
}
53+
}
54+
.then(modifier)
55+
)
56+
}

0 commit comments

Comments
 (0)