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

Commit

Permalink
Merge pull request #102 from you-apps/equalizer
Browse files Browse the repository at this point in the history
feat: implement in-app equalizer
  • Loading branch information
Bnyro authored Apr 18, 2024
2 parents 6a6b48a + c742090 commit c3f0b61
Show file tree
Hide file tree
Showing 9 changed files with 408 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.content.ComponentName
import android.graphics.Color
import androidx.media3.session.MediaController
import androidx.media3.session.SessionToken
import app.suhasdissa.vibeyou.backend.data.EqualizerData
import app.suhasdissa.vibeyou.backend.database.SongDatabase
import app.suhasdissa.vibeyou.backend.services.PlayerService
import app.suhasdissa.vibeyou.utils.Pref
Expand All @@ -20,6 +21,11 @@ class MellowMusicApplication : Application(), ImageLoaderFactory {
lateinit var container: AppContainer
var accentColor: Int = Color.TRANSPARENT

/**
* Data stored here with the details of the equalizer
*/
var supportedEqualizerData: EqualizerData? = null

override fun onCreate() {
super.onCreate()
val sessionToken =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package app.suhasdissa.vibeyou.backend.data

data class EqualizerBand(
val frequency: Int,
val minLevel: Short,
val maxLevel: Short,
)

data class EqualizerData(
val presets: List<String>,
val bands: List<EqualizerBand>
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.media.audiofx.Equalizer
import android.media.audiofx.LoudnessEnhancer
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.util.Log
import androidx.annotation.ColorInt
Expand All @@ -17,6 +19,7 @@ import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.database.StandaloneDatabaseProvider
import androidx.media3.datasource.DataSource
import androidx.media3.datasource.DefaultDataSource
Expand All @@ -39,7 +42,11 @@ import androidx.media3.exoplayer.source.MediaSource
import androidx.media3.session.BitmapLoader
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService
import androidx.media3.session.SessionCommand
import androidx.media3.session.SessionResult
import app.suhasdissa.vibeyou.MellowMusicApplication
import app.suhasdissa.vibeyou.backend.data.EqualizerBand
import app.suhasdissa.vibeyou.backend.data.EqualizerData
import app.suhasdissa.vibeyou.backend.data.Song
import app.suhasdissa.vibeyou.utils.DynamicDataSource
import app.suhasdissa.vibeyou.utils.IS_LOCAL_KEY
Expand All @@ -58,16 +65,46 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext

@UnstableApi
class PlayerService : MediaSessionService(), MediaSession.Callback, Player.Listener {
private var mediaSession: MediaSession? = null
private lateinit var cache: SimpleCache
private lateinit var player: ExoPlayer
private lateinit var equalizer: Equalizer

private var loudnessEnhancer: LoudnessEnhancer? = null

val appInstance get() = application as MellowMusicApplication
val container get() = appInstance.container

private val mediaSessionCallback = object : MediaSession.Callback {
override fun onConnect(
session: MediaSession,
controller: MediaSession.ControllerInfo
): MediaSession.ConnectionResult {
val sessionCommands = MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
.add(SessionCommand(COMMAND_UPDATE_EQUALIZER, Bundle.EMPTY))
.build()

return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
.setAvailableSessionCommands(sessionCommands)
.build()
}

override fun onCustomCommand(
session: MediaSession,
controller: MediaSession.ControllerInfo,
customCommand: SessionCommand,
args: Bundle
): ListenableFuture<SessionResult> {
if (customCommand.customAction == COMMAND_UPDATE_EQUALIZER) {
updateEqualizerSettings()
}

return super.onCustomCommand(session, controller, customCommand, args)
}
}

@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
override fun onCreate() {
super.onCreate()
Expand All @@ -86,13 +123,15 @@ class PlayerService : MediaSessionService(), MediaSession.Callback, Player.Liste
cache = SimpleCache(directory, cacheEvictor, StandaloneDatabaseProvider(this))

player = createPlayer()
setupEqualizer()

player.repeatMode = Player.REPEAT_MODE_OFF
player.playWhenReady = true
player.addListener(this)

mediaSession = MediaSession.Builder(this, player).setCallback(this)
.setBitmapLoader(CustomBitmapLoader(this))
.setCallback(mediaSessionCallback)
.build()
}

Expand Down Expand Up @@ -304,4 +343,55 @@ class PlayerService : MediaSessionService(), MediaSession.Callback, Player.Liste
)
}
}

private fun setupEqualizer() {
equalizer = Equalizer(Integer.MAX_VALUE, player.audioSessionId)
appInstance.supportedEqualizerData = EqualizerData(
presets = (0 until equalizer.numberOfPresets).map {
equalizer.getPresetName(it.toShort())
},
bands = (0 until equalizer.numberOfBands).map { id ->
val freqRange = equalizer.bandLevelRange
EqualizerBand(
equalizer.getCenterFreq(id.toShort()),
freqRange.first(),
freqRange.last()
)
}
)

updateEqualizerSettings()
}

/* private fun useSystemEqualizer() {
val intent = Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, player.audioSessionId);
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, packageName);
sendBroadcast(intent);
}*/

private fun updateEqualizerSettings() {
if (!::equalizer.isInitialized) return

equalizer.enabled = Pref.sharedPreferences.getBoolean(Pref.equalizerKey, false)
if (!equalizer.enabled) return

val preset = Pref.sharedPreferences.getInt(Pref.equalizerPresetKey, -1)
if (preset != -1) {
equalizer.usePreset(preset.toShort())
return
}

val bandPrefs = Pref.sharedPreferences.getString(Pref.equalizerBandsKey, "")
if (bandPrefs.isNullOrEmpty()) return

val bandLevels = bandPrefs.split(",").map(String::toShort)
for (i in bandLevels.indices) {
equalizer.setBandLevel(i.toShort(), bandLevels[i])
}
}

companion object {
const val COMMAND_UPDATE_EQUALIZER = "update_equalizer"
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package app.suhasdissa.vibeyou.backend.viewmodel

import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.core.os.bundleOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
Expand All @@ -16,11 +18,13 @@ import androidx.lifecycle.viewmodel.viewModelFactory
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackParameters
import androidx.media3.session.MediaController
import androidx.media3.session.SessionCommand
import app.suhasdissa.vibeyou.MellowMusicApplication
import app.suhasdissa.vibeyou.backend.data.Song
import app.suhasdissa.vibeyou.backend.repository.LocalMusicRepository
import app.suhasdissa.vibeyou.backend.repository.PipedMusicRepository
import app.suhasdissa.vibeyou.backend.repository.SongDatabaseRepository
import app.suhasdissa.vibeyou.backend.services.PlayerService
import app.suhasdissa.vibeyou.utils.addNext
import app.suhasdissa.vibeyou.utils.asMediaItem
import app.suhasdissa.vibeyou.utils.enqueue
Expand Down Expand Up @@ -132,6 +136,12 @@ class PlayerViewModel(
}
}

@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
fun updateEqualizerSettings() {
val command = SessionCommand(PlayerService.COMMAND_UPDATE_EQUALIZER, Bundle.EMPTY)
controller!!.sendCustomCommand(command, Bundle.EMPTY)
}

suspend fun isFavourite(id: String): Boolean {
val song = songDatabaseRepository.getSongById(id)
song ?: return false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package app.suhasdissa.vibeyou.ui.components

import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderColors
import androidx.compose.material3.SliderDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.layout
import androidx.compose.ui.unit.Constraints

@Composable
fun VerticalSlider(
value: Float,
onValueChange: (Float) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
steps: Int = 0,
onValueChangeFinished: (() -> Unit)? = null,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: SliderColors = SliderDefaults.colors()
){
Slider(
colors = colors,
interactionSource = interactionSource,
onValueChangeFinished = onValueChangeFinished,
steps = steps,
valueRange = valueRange,
enabled = enabled,
value = value,
onValueChange = onValueChange,
modifier = Modifier
.graphicsLayer {
rotationZ = 270f
transformOrigin = TransformOrigin(0f, 0f)
}
.layout { measurable, constraints ->
val placeable = measurable.measure(
Constraints(
minWidth = constraints.minHeight,
maxWidth = constraints.maxHeight,
minHeight = constraints.minWidth,
maxHeight = constraints.maxHeight,
)
)
layout(placeable.height, placeable.width) {
placeable.place(-placeable.width, 0)
}
}
.then(modifier)
)
}
Loading

0 comments on commit c3f0b61

Please sign in to comment.