Design Music Player UI With LVGL
This instructables show how to use LVGL and SquareLine Studio design a Music Player UI.
Supplies
Any ESP32 dev device with screen and audio output should be ok.
Below are some dev devices appear in this instructables:
https://item.taobao.com/item.htm?id=694922892355
https://www.aliexpress.com/item/1005004267336768.html
https://www.makerfabs.com/esp32-s3-parallel-tft-with-touch-4-inch.html
What Is LVGL?
LVGL is a good graphics library for making fancy GUI easily. And SquareLine Studio help you reduces much coding required.
You can find more details at my previous instructables:
https://www.instructables.com/Design-a-Fancy-GUI-for-Your-Project/
Sample Source Files
All the image sources, SquareLine Studio projects and source code can be found at Github:
https://github.com/moononournation/LVGL_Music_Player.git
UI Design
Current dev device have various shape of display, round, square, rectangle. It is better determine using which shape first.
Then the next step is determine what the UI look like.
Classic Music Player
If you do not have any UI design idea yet, imitate a classic music player may be a good start.
Winamp is a classic Windows desktop music player appeared from last century, then a similar UI variant with Chinese characters support called TTPlayer. This 2 applications are my (and also many people's) childhood memory, so I will use those UI as a design template.
Winamp is designed can change "skin" easily, and the skin image resources are in BMP format packed in a zip file. You can find a huge Winamp skin collection at skins.webamp.org, so it is easy to access your favor Winamp skin as a design template.
You can find how to start the UI design from the Winamp skin at bilibili:
Function Wishlist
Music player have various functions, but I'm more interested in the features found in Winamp or TTPlayer. Here are my function wishlist:
- Play MP3 from SD card
- List MP3 files from SD card
- Display Unicode characters
- Basic playing operation (play, pause, stop, previous and next)
- Volume Control
- Show MP3 ID3 information
- Show MP3 cover image
- Show MP3 Lyrics
- Synchronize display Lyrics while play
- Show audio spectrum analyzer
Play MP3 From SD Card
At the beginning, we need an audio library that can read MP3 file from SD card and play it out. This time I am using ESP32-audioI2S. It support the ESP32 family, read audio files from various source and play the output to I2S module. You can find more details at Github:
Basic Playing Operation
ESP32-audioI2S provided all basic playing API, we just need create all the corresponding button widgets for each operation. However, the original Winamp button design is too tiny for operating on the touchscreen with finger. So I enlarge the buttons a little bit and also extend the touch area a little bit with transparent background.
For each button, assign the button widget to a corresponding function.
Use play button as an example:
lv_obj_add_event_cb(ui_ButtonPlay, playSong, LV_EVENT_CLICKED, NULL);
And then in the playSong function, call the ESP32-audioI2S API:
void playSong(lv_event_t *e)
{
if (isPlaying)
{
audio.pauseResume();
}
else
{
play_selected_song();
}
}
List MP3 Files From SD Card
Before telling ESP32-audioI2S play the MP3 file, we need find and list out the MP3 files from the SD Card first. Here are the code extract of read_song_list() illustrate how to concat the song list string seperated by line feed character(\n):
File root = SD_MMC.open("/");
File file = root.openNextFile();
while (file)
{
if (file.isDirectory())
{
Serial.printf("DIR: %s\n", file.name());
}
else
{
const char *filename = file.name();
int8_t len = strlen(filename);
const char *MP3_EXT = ".mp3";
if ((filename[0] != '.') && (strcmp(MP3_EXT, &filename[len - 4]) == 0))
{
// Serial.printf("Song file: %s, size: %d\n", filename, file.size());
if (song_count > 0)
{
stringSongList += '\n';
}
stringSongList += filename;
song_count++;
}
}
file = root.openNextFile();
}
Then assign the concatenated song list string to the LVGL roller component:
lv_roller_set_options(ui_RollerPlayList, stringSongList.c_str(), LV_ROLLER_MODE_INFINITE);
Display Unicode Characters
LVGL support display Unicode characters but it requires a Unicode font file. My song list are mainly in Chinese characters, I selected 3 fonts for displaying it:
https://github.com/ACh-K/Cubic-11.git
https://fonts.google.com/noto/specimen/Noto+Sans+HK/glyphs
https://fonts.google.com/noto/specimen/Noto+Serif+HK/glyphs
Then use SquareLine Studio font tools to create the C source files.
Volume Control
Assign the volume slider widget to a value changed event function:
lv_obj_add_event_cb(ui_ScaleVolume, volumeChanged, LV_EVENT_VALUE_CHANGED, NULL);
And then in the event function call the ESP32-audioI2S API:
void volumeChanged(lv_event_t *e)
{
int16_t volume = lv_slider_get_value(ui_ScaleVolume);
audio.setVolume(volume);
}
The time progress UI also a slider widget. But it is too near the buttons and volume control UI so I disabled the touch input to avoid the unexpected operation.
Show MP3 ID3 Information
ESP32-audioI2S exposed an audio_id3data() callback function. Function called for each ID3 tag found in MP3 file.
In callback function, simply concatenate all data to a string:
if (playingStr.length() > 0)
{
playingStr += " ";
}
playingStr += info;
Then assign to a label for display:
lv_label_set_text(ui_LabelPlaying, playingStr.c_str());
Show MP3 Cover Image
ESP32-audioI2S exposed an audio_id3image() callback function. Function called if found cover image in MP3 ID3 tag. The image can be any image format, currently on support decode and display non-progressive JPEG image file.
In callback function, copy the binary data:
file.seek(pos);
file.read(coverImgFile, len);
Seek the JPEG header:
size_t idx = 11;
while ((idx < len) && ((coverImgFile[idx++] != 0xFF) || (coverImgFile[idx] != 0xD8)))
;
--idx;
Then decode with JPEGDEC:
jpegdec.openRAM(coverImgFile + idx, len - idx, jpegDrawCallback);
Show MP3 Lyrics
ESP32-audioI2S exposed an audio_id3lyrics() callback function. Function called if found synced lyrics, un-synced lyrics or text data tag in MP3 file.
In callback function, copy the binary data:
file.seek(pos);
file.read((uint8_t *)lyricsText, len);
Decode binary to UTF8 text:
audio.unicode2utf8(lyricsText, len);
If the text have sync time tag, store the time index to syncTimeLyricsSec[] and syncTimeLyricsLineIdx[] array.
Then set the lyrics text to the roller widget:
lv_roller_set_options(ui_RollerLyrics, lyricsText, LV_ROLLER_MODE_NORMAL);
Synchronize Display Lyrics While Play
If synced lyrics tag found, roll the lyrics widget while playing:
for (int i = 0; i < syncTimeLyricsCount; ++i)
{
if (syncTimeLyricsSec[i] == currentTime)
{
lv_roller_set_selected(ui_RollerLyrics, syncTimeLyricsLineIdx[i], LV_ANIM_ON);
break;
}
}
Show Audio Spectrum Analyzer
ESP32-audioI2S also exposed an audio_process_i2s() callback function for processing audio output. We can utilize this function collect the audio data to visualize the audio spectrum.
In callback function, collect the audio data:
raw_data[raw_data_idx++] = *sample;
If data full, process with FFT class:
if (raw_data_idx >= WAVE_SIZE)
{
fft.exec((int16_t *)raw_data);
draw_fft_level_meter(canvasFFT_gfx);
lv_obj_invalidate(ui_CanvasFFT);
raw_data_idx = 0;
}
Note:
The visualization is drawing to a canvas, canvasFFT_gfx, separately. And the canvas is associated with the LVGL widget ui_CanvasFFT.
Optional: Design Case
A beautiful case for the dev device can make it look more like a music player.
You can find the WT32-SC01 PLUS desktop case at Thingiverse:
Enjoy!
It's time to enjoy the music!
You can find more making of video at bilibili:
What's Next?
Here are further Function Wishlist we can implement:
- Shuffle play list order
- LVGL roller cannot handle a large list, it is better able list song by folder
- MP3 timeline seek
- Support more cover image format
- Connect to the Internet? No any idea yet