How to replicate Spotify’s Equalizer in Android
One day, I was sitting in a bus, as always listening to music through Spotify. That time, I was thinking of any pet projects I can work with and it hit me, what if I recreate the Spotify app? So curious me started exploring the app and bump into the settings and its equalizer. And decided maybe I’ll just do the equalizer for now lol.
When I checked online Spotify’s equalizer UI on every platform, I got curious why does the UI for Spotify’s equalizer in android, different from it’s IOS counterpart. I was thinking they would do the same for the sake of consistency? Well personally, I like the UI in IOS counterpart.
So in this tutorial, we will discuss how to recreate Spotify’s IOS ui design with Android’s equalizer capabilities.
I’ll stop with the yada yadas and jump right into it!
Playing an audio
First off, we will setup the MediaPlayer class to play audio on start. We will not have any play, pause, stop control buttons here and just let the audio play on loop while the app is in foreground.
In setupMediaPlayer(), it assigns an instance of MediaPlayer class and includes R.raw.htmlthesong as the audio to play with (you can use any audio formats but I recommend .mp3 as it is small in size, put in onto your raw folder). It also tells the mediaPlayer to play the audio on repeat
In startMediaPlayer(), it starts and plays the audio. I added logic to handle if the mediaPlayer was previously stopped, release and set to null. In case that happens, setupMediaPlayer() will be called again. In stopMediaPlayer(), it stops the audio from playing and after that, releases the mediaPlayer from memory and sets it to null just in case. We need to do this to avoid any memory leaks from the app.
And that’s it! The audio player is playing the audio on repeat!
Modify audio settings
Before we can modify the audio, we need to setup a specific permission to the manifest.
This permission does not need any user approval in order to work so no need to ask every time the app starts. After that, we will now include and use the Equalizer class in our app.
Things we need to know:
- Number of bands that we can control and display
- Lowest band level range we can set to a band
- Highest band level range we can set to a band
- Center frequency of each band — name of band in Hz
All of this are provided in the Equalizer class.
As you can see, we iterated the numberOfBands starting from zero to get the center frequency for each band. The center frequency will represent as the name displayed for the band in UI. setBandLevel(), sets the level of gain to the bandId selected.
And that’s it. We can now work to incorporate this to an equalizer view!
Equalizer View
In order to achieve this, we need the lines, shadow, etc. in each separate view. The best way to approach this is using a ViewGroup class. This group is separated into four inner view classes such as:
- BandView — seekbar to control the band level
- BandConnectorLayout — line that connects each band
- BandNameLayout — name of each corresponding BandView (e.g. 60Hz, 230Hz, etc)
- BandConnectorShadowView —shade of the BandView and BandConnectorLayout
To achieve like an equalizer control knob view in BandView class, we used a 270 degree rotated seekbar. For the lines connecting the seekbar thumb, we calculated its center x coordinate from its bounds and drew them using the Path class. For the shadow, we used LinearGradient class to shade the paint assigned on the shadow path. Lastly, for the names, we also used the Paint class to draw them.
Then put it on the layout.
Control the Equalizer Class using the Equalizer View
To control and set the level of gain on a specific band, we need to set some values on the Equalizer view.
Things to set:
- Band names based on center frequency (e.g. 60Hz, 230Hz, etc)
- Maximum value of band from 0-n (highestBandLevel — lowestBandLevel)
- Listener when band level changed
To set the band names, call the kotlin android extension id of the equalizer view (in this case, view_eq), then call view_eq.setBands(bands). Because the band view is a seekbar, the minimum value it can give is 0. The lowest band level given is -n (e.g. -1500) and highest is n (e.g 1500). So in order to get the appropriate max value for the band view, we need to subtract the highest band level to the lowest band level and the difference would be the max value the band view can have. In order to listen for any value changes on a band, we need to implement in the activity EqualizerView.EventListener interface. Call draw() from view_eq to immediately see the latest changes.
When the user adjusts the level of a specific band from the view, the value should immediately reflect on Equalizer class and the audio. The value arg from onBandLevelChanged() ranges from 0 — max level set. This value does not necessarily directly translate to the Equalizer class. So we need to compute it to its appropriate level — value plus the lowestBandLevel. After that, set the band level through setBandLevel(bandId, bandLevel).
And that’s it, you can now control and modify the audio settings using the Equalizer view!
Equalizer Switch
We will add a functionality to enable the Equalizer class modify the audio. In order to do this, we will include a switch that sets a flag to equalizer?.enabled function.
First, we will make a custom layout for the switch.
Then we need to set a listener when the switch is toggled. Invoke equalizer?.enabled = true or false.
Equalizer Presets
The Equalizer class also provides a bunch of presets that we can use to modify audio. We will get all the available presets on the device and display it in a list.
Things we need to know:
- Number of available presets
- Preset names
- Current preset
- Band values of the current preset — display onto the Equalizer view
I made a PresetAdapter class that we can use to display the presets in a list.
Now, we need to get and set the available presets onto the adapter. When a preset item is selected, we need to update the current preset in the Equalizer class.
In setupPresetList(), we initially set the adapter onto the list (e.g list_preset). After that, we get the number of available presets using equalizer?.numberOfPresets(). Then we need to iterate from 0 to get its literal preset name using equalizer?.getPresetName(). Lastly, we add each onto an array list for reference and call notifyDataSetChanged() on the adapter to immediately update the list.
In setSelectedPreset(), it sets the selected preset in Equalizer class through equalizer?.usePreset(presetId). When you set the preset, it sets the band values real time. Basically, you can hear it in real time. To display the band levels of each band, we just need to call equalizer?.getBandLevel(). As previously discussed, we need to perform a computation in order to display it properly on the band view — bandLevel minus lowestBandLevel.
And that’s it, we can now choose a preset and it will immediately display on the Equalizer view!
The final code for the activity and its layout should look below. Also, I added logic to handle caching of configurations such as enabling equalizer, currently selected preset, etc.
Few notes
- The Equalizer class can substantially decrease or increase volume when enabled. I recommend to just use it minimally if you want to cut or enhance a specific frequency.
- I read that the number of bands can be different in every device. Majority, they have 5 bands available. Also, there are instances that a specific device can alter the volume.
- We did not include the bass boost and surround sound feature because it is entirely a separate audio effect and is not part of the Equalizer class. This project solely focuses on the Equalizer class.
The full project is available on Github! Give it a star if you liked it!
PS: When I explored all the available audio effects (e.g. Equalizer class) in android, I saw that in the upcoming android P, there will be a new available audio effect which is called Dynamics Processing! I can’t wait to play with it!
Thanks for reading! Give it a clap if you enjoyed!