Fresh Releases – My (G)SoC journey with MetaBrainz

For an open source software enthusiast like me who has contributed little pieces of code and documentation to various projects for almost a decade, the idea of applying for Google Summer of Code has always been exciting and intimidating because of its grand nature. After getting some experience in web development over the past year, I decided to not give in to my self-doubts and applied for the GSoC’22 with confidence and zeal. I am Chinmay Kunkikar from India, and I would like to talk about my project with the MetaBrainz Foundation – Fresh Releases, and take you on my journey through the GSoC 2022 program.

MusicBrainz is the largest structured online database of music metadata. Today, a myriad of developers leverage this data to build their client applications and projects. According to MusicBrainz Database statistics, 2022 alone saw a whopping 366,680*, releases, from 275,749 release groups, and 91.5% of these releases have cover art.  Given that it has a plethora of useful data about music releases available, but has no useful means to visually present it to general users, the idea of building the Fresh Releases page was born.

* as of 2022-11-30

Our objective with this project was, therefore,
  1. To make music discovery easier for the users by presenting the data available from the MusicBrainz database.
  2. To use a user’s listening habits to show them personalized music release suggestions.

Now let me take you one by one through the process of execution of the aforementioned idea. First comes the design process and choices for this page, then a discussion over the implementation of the card grid, the filters component, the timeline component, the user-specific releases page, and how they were made responsive. Later on, we can talk about some pains of testing React Hooks with the Enzyme library, and an accident I had with git. We will also see how we plan to improve Fresh Releases in the future. So let’s begin!

The design process

It was a natural design choice to represent a music release with a card as it is an accepted practice on modern music apps and websites. A release card shows the metadata of a release like the date, name, release group type, artist(s), and cover art in the middle.

A Release Card

Iteration 1 – The initial approach was to show a grid of release cards with an infinite scroll. The filters were of two kinds – One where a user can switch between Upcoming Releases, New Releases, and This Week’s Releases from a button group. And the user can filter releases based on the Release Group by selecting one from a dropdown menu. To limit loading hundreds of results at a time, a Show more button was placed at the bottom of the page. This design approach was similar to the Charts tab of the ListenBrainz user page.

It was pointed out during reviews that using dropdowns for the Release Group filters and having button groups to separate New releases from Upcoming releases were unintuitive choices. We also thought of having a filter that will remove all non-cover art releases from the page for a more visual exploration of music (good one, mayhem).

Iteration 2 – To keep the UI simple but interesting, we thought of showing today’s releases in the middle of the screen, allowing users to scroll up or down to see past or future releases respectively. The practicality and the user experience of this idea were a concern initially but the implementation of similar ideas in apps like Apple’s Time Machine app was convincing enough to make us go forward with it. This time, we used the classic Holy grail layout to design the page. The card grid will be shown in the main content column, the left sidebar will be used for filters, and the right sidebar will have a timeline slider component to scroll the grid up or down. This rectified the concerns we had earlier –

  1. The dropdown menu for filters is gone.
  2. Adopting the past/future scrolling idea resulted in an intuitive UI, getting rid of the filters that separated New releases from Upcoming releases.
  3. The toggle to hide releases without a cover art can be accommodated in the filters column with this layout.

Responsive layout – Thanks to the Holy grail layout, making the page responsive for mobile and tablet would be effortless. We transpose the layout such that the left sidebar is stacked horizontally on the top of the main content section and the right sidebar stacks horizontally below it, leaving the header and footer unchanged.

A set of two buttons were later added at the top of the page to switch between sitewide (or global) releases and user-specific releases (Discussed in detail in a later section).

API Design

We built two API endpoints – one for the sitewide releases and the second one for user-specific releases.

  1. Sitewide fresh releases – This endpoint was built around the idea of the timeline feature of the UI. It optionally accepts a pivot date as an argument to show releases around that date. It also optionally accepts the number of days as an argument to show releases from days before and after the pivot date.
GET /1/explore/fresh-releases

Parameters
1. release_date – Fresh releases will be shown around this pivot date. The default is today’s date.
2. days – The number of days of fresh releases to show. Max 30 days.

Sample response

{
  "artist_credit_name": "Tar Blossom",
  "artist_mbids": [
    "313ab6d1-44e2-49eb-92e6-9e9ad2554bcd"
  ],
  "caa_id": 31955354514,
  "release_date": "2022-02-20",
  "release_group_mbid": "eadb43dd-9c2d-48b4-bf0c-4bb6baa61eb5",
  "release_group_primary_type": "Album",
  "release_mbid": "41c8921a-5fe9-4c15-ac62-0a7525271b5c",
  "release_name": "Of Mountains and Suns"
}
  1. User-specific Fresh Releases – This endpoint fetches releases for the current user within a month. This endpoint accepts no arguments. We will discuss more about User-specific releases in a separate section.
GET /1/user/{username}/fresh_releases

Some data sanitization

MusicBrainz, sometimes while collecting data from multiple sources, can create separate MBIDs of a release if there are minor changes in the metadata from two sources, resulting in duplicates of a release.

For example, {..." release_name": "Waterslide, Diving Board, Ladder to the Sky"} and {..." release_name": "Waterslide, Diving Board, Ladder To The Sky"} (notice the to the). Such results were deduplicated using lodash.uniqBy().

Filters section

Filters help users shortlist releases from specific categories, for example, Albums, Singles, or Remix, among others.

The filters component is divided into two sections – A Hide releases without coverart toggle button and a list of release group types. This list is a dynamically generated array of unique release group types from the API response object.
Multiple filters can be selected to show releases with a combination of filters.
The initial logic for the coverarts-only toggle required a lot of prop drilling through multiple components. To overcome this anti-pattern, we added the caa_id property to the API response. The caa_id is the Cover Arts Archive ID, which is available only for releases that have a valid cover art. The coverart-only toggle can thus use this property to hide the releases that don’t have a caa_id.

The Timeline

We were not sure what basic component or library should be used to implement the timeline. After struggling to find an implementation that matches our needs, a suggestion came from monkey to use the basic HTML range slider element. After contemplating this suggestion, we decided to use the rc-slider library which is based on the HTML range slider but is more React-friendly with additional useful features and styles. We got help from monkey again for the scrolling logic for the slider.

The slider shows marks with dates on them. Clicking on a date will trigger the changeHandler() function that accepts a percentage value from the slider’s current position and returns the position to scroll to on the page. This scrolls the page to the respective date. This was made possible by the createMarks() function that calculates a percent value for the number of releases per date in the releases list. This function creates an object that the slider uses to create the marks on the slider. The handleScroll() is a debounced function triggered every time the user manually changes the scroll position.

User-specific Fresh Releases page

User-specific releases or User Fresh Releases will show releases from artists that the user has listened to before. It uses a confidence score, which is the number of times the user has listened to an artist, to rank the releases in decreasing order. A user can switch between the sitewide releases and user-specific releases using the Pill buttons at the top of the page. If the user is not logged in, they will only see the sitewide releases.

Making the page responsive

Since ListenBrainz uses Bootstrap as its base styling framework, we used the Bootstrap 3 breakpoints with an additional breakpoint from Bootstrap 4 (576px) to make the page responsive for multiple screen sizes. The number of columns in the grid was pretty straightforward; we started with two columns and kept adding an extra column per breakpoint. The filters and timeline components that are vertical on desktop screens, change their orientation to horizontal on the screens up to the md breakpoint. As a future enhancement, we have plans for the timeline to look and behave like Android’s fastscroll widget (thanks to aerozol for the suggestion).

Tests

ListenBrainz uses snapshot testing for frontend to make sure there are no unexpected changes to the UI. While writing unit tests for mocking the API calls was a cakewalk, we struggled with writing tests for rendering and mocking the UI components for snapshot testing. A combination of Jest and Enzyme is used to test React components, which works quite well for React class components, but quickly becomes a nightmare when testing functional components that Fresh Releases use. Enzyme provides no APIs to test and mock hooks like the useState hook. Despite having no support for hooks, surprisingly, it can run the code inside of the hooks themselves. But there is no easy way to mock the useState setter function. As a result, there are still half-written tests of Fresh Releases. This limited support for newer features of React will only worsen over time because Enzyme is no longer actively maintained. There are discussions in the team to move away from Enzyme in favor of other testing libraries, and we believe there are good reasons to do so.

The incident with git

I messed with git reset –-hard and git push –-force on my working branch, wiping away the entire commit history from the local branch and the remote branch. We used git reflog to recover the lost commits. Git reflog is similar to git log, but instead of a list of commits of the current HEAD, it shows the list (or log) of times when HEAD (or reference) itself was changed. We checked out the previous HEAD to a new branch and all of the commit history was seen again (thanks again, lucifer). An important lesson learned that night was to never play around with git reset unless you’re 100% sure what you are doing. Use separate branches to test the commands that can mutate your commit history. But also do not panic if you do run into such situations. There is a high chance git has cached the history somewhere locally.

Git will never fail to surprise you, no matter how experienced you are in using it.

Future improvements and enhancements

To add to the list in the LB-1172 ticket,

  1. Release card grid – Remove all text content from the cards and keep just the coverarts when the “Hide releases without coverarts” filter is set. The text can be shown on the cover art when the Release Card is hovered over. And the name of the filter will be changed to “Show coverarts only.” (suggestion by aerozol)
  2. Integration of BrainzPlayer on the page will add the ability to play new releases directly on the page. To quote monkey, “the solution would be to redirect users to a playable album page on LB. For example, listenbrainz.org/player/release/f5d6d909-06dc-4811-8e13-811e6af31b82 “.
  3. Performance optimizations – One issue Fresh Releases faces is the rendering of hundreds (if not thousands) of DOM nodes on the page. This can significantly slow down the page and the browser. The temporary solution we have used is to limit the number of release days shown. But we want the page to be able to show releases of up to a month. This issue can be solved either by adding o “load more” buttons or by virtualizing or “windowing” the cards grid component using  libraries like react-window.
  4. Implement aerozol’s idea to modify the timeline on mobile screens to look and behave like Android’s fastscroll widget (discussed above).
  5. Add more tests to test combinations of filters.

My journey with MetaBrainz Foundation

While browsing the organizations, I recognized that the MetaBrainz Foundation is the brains (pun intended) behind MusicBrainz Picard, the handy tool that has kept my music library clean for years. After reading about their projects, my interest in the ListenBrainz project was piqued because I learned that it is essentially an open source alternative to Last.FM, a service where I enjoy perusing my music listening habits.

During the community bonding period of GSoC, I set up and played around with the ListenBrainz codebase, as well as closed a few tickets. During this time, I worked on adding a tooltip to the BrainzPlayer progress bar and updating node dependencies to the most recent versions (more complicated than it sounds).

Here is a list of all of my pull requests – metabrainz/listenbrainz-server/pulls.

Even though my original project proposal was not selected as a GSoC project, the MetaBrainz team was so impressed with my work that they chose to create an internship position exclusively for me. Fresh Releases was not an official GSoC project, but the team nonetheless considered it as such. You can read the story in a previous blog post.

My experience & learnings

I’ve always admired open source and the opportunities it provides. Working on projects like ListenBrainz has pushed me to contribute more to open source. In these past few months, apart from working on Fresh Releases, I also had a chance to work on other parts of the codebase. That boosted my confidence further in working on large codebases. I am fortunate to work with mayhem, lucifer and monkey, the lead developers who work in open source environments and require the extra skill of connecting with the community and being patient with new enthusiasts. monkey’s thorough code reviews motivated me to work on the project more. He would explain concepts and then provide short code snippets to show me how to implement them in the code. The entire MetaBrainz community is motivated and a joy to work with. Every design and technical decision is backed up by well-thought-out recommendations and open discussions on IRC. Developers will take note of your suggestions. Your efforts will be recognised. Working here has been a lot of fun!

P.S. Did I mention that they printed the proposals of all the selected students and put them up on the MetaBrainz office wall?!

My proposal along with others’ on the office wall!

One thought on “Fresh Releases – My (G)SoC journey with MetaBrainz”

  1. Amazing work chinmay! Loving my Fresh ‘Releases for you’ page!!

    And thanks to the global page I now know that zippy kid is releasing some more albums (those who know, know :P)

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.