The Wayback Machine - https://web.archive.org/web/20230829043847/https://blog.metabrainz.org/

MusicBrainz Server update, 2023-08-28

On the menu of the day there are some improvements to the editing interface thanks to derat! It’s accompanied with a mesclun of changes (additions, fixes, improvements and updates) related to external links, especially some other databases and new relationship types for art gallery and ticketing. It’s spiced with a pinch of new icons (for collections, genres, and tags) and condiments.

A new release of MusicBrainz Docker is also available that matches this update of MusicBrainz Server. See the release notes for update instructions.

Thanks also to HibiscusKazeneko, Lotheric, Trevor, wonder_al, Xythium, yindesu, and yyoung for having reported bugs and suggested improvements. Thanks to chaban, outsidecontext, RandomMushroom128, and salo.rock for updating the translations. And thanks to all others who tested the beta version!

The git tag is v-2023-08-28.

Continue reading “MusicBrainz Server update, 2023-08-28”

GSoC’23: Integrating Apple Music with ListenBrainz

Hello everyone!

My name is Vardan and I am a student at University of Alberta. I’ve always had a thirst for knowledge and a deep desire to make contributions to projects that have a global impact. I like listening to music and the idea of contributing to an adjacent music metadata project was captivating for me. I eagerly embarked on this journey, contributed my best, and am happy to have made a meaningful impact. This experience not only aligns perfectly with my academic pursuits but also presents a chance to collaborate with some of the brightest minds in the field.

Objective

The primary objective of this project is to extend the functionality of ListenBrainz by integrating Apple Music, a music streaming service, into its ecosystem. The goal is to allow users to link their Apple Music accounts with ListenBrainz and utilize the integrated BrainzPlayer to play their music selections.

This project aims to meet the growing demand for broader music service integration within ListenBrainz, enhancing user experience and expanding the range of personalized music recommendations. Through successful implementation, this project will contribute to the platform, by catering to a wider audience and fostering a richer musical discovery experience.

Project Overview

We can divide the project mainly in two parts i.e.,

  1. Music Streaming Integration: Apple provides MusicKit.js library to add Apple Music playback facilities to a website. MusicKit JS handles all authentication on its own. Neither Apple Music nor MusicKit JS provide a way to refresh expired tokens. Due to lack of proper OAuth support in Apple Music, I had to jump quite a few hoops for an implmentation that ensures smooth user experience.
  2. Building an Apple Music Content Resolver: The metadata content resolver is used to store metadata of the entire catalog present in an external service’s database. Currently, ListenBrainz has such a resolver for Spotify. As a part of this project, I intend to add a similar content resolver for Apple Music as well.

Music Streaming Integration

  1. Connect account with Apple Music Frontend: To initiate the integration of Apple Music into ListenBrainz, the first step is configuring MusicKit JS on the frontend. I added an option for users to choose to link their Apple Music account. When the user clicks the button to link their account, the MusicKit library is loaded on the webpage.

  2. Developer Token and MusicKit Configuration: To configure MusicKit on the frontend, a developer token needs to be generated on the backend and sent to the frontend. After including the MusicKit library, you can configure the MusicKit instance using the generated developer token:
function music_kit_loaded_listener(token) {
  const configuration = {
    developerToken: token.access_token,
    debug: true,
    app: {
      name: "ListenBrainz",
      build: "latest",
    },
  };
  return function listener() {
    window.MusicKit.configure(configuration).then((_) => {
      const instance = window.MusicKit.getInstance();
      instance
        .authorize()
        .then((music_user_token) =>
          submit_music_user_token(
            token.access_token,
            music_user_token,
            token.expires_at
          )
        );
    });
  };
}
  1. User Authentication and Token Submission: The authorize method provided by MusicKit can be utilized to authenticate the user and gain access to their Apple Music account. Upon successful authorization, the obtained Music-User-Token is sent to the server’s callback URL. However, it’s important to note that Apple Music does not require a client secret for OAuth; instead, the process is carried out client-side using MusicKit.
  2. Backend Processing: The callback URL endpoint for music services receives the authorization code for other services. In the case of Apple Music, the OAuth process is slightly different due to the absence of a client secret. MusicKit is used to handle authorization client-side, and once the user grants access, the music.authorize().then(musicUserToken => {…}) block is executed. The retrieved Music-User-Token is then submitted to the server at the callback URL.
  3. Storing Token and Storefront: To ensure a seamless experience, the obtained token and the user’s storefront (region) should be stored in the database for future use. This data will be crucial for accessing and customizing the user’s Apple Music content.
  4. Integrate Apple Music in BrainzPlayer: The BrainzPlayer, which is a web-based music player used in ListenBrainz, can be extended to support Apple Music playback. MusicKit is integrated into the player in a manner similar to its inclusion in the OAuth frontend setup.
  5. Player Controls and Event Handling: Once the BrainzPlayer is loaded with MusicKit, various playback controls like play, pause, etc., can be accessed using the MusicKitInstance. Event callbacks can also be added to respond to changes in playback state through the playbackStateWillChange and playbackStateDidChange events.
  6. User Authorization Check: Before implementing event callbacks, it’s essential to verify whether the user has authorized Apple Music. This step ensures that only authorized users can interact with the Apple Music integration in the player.
  7. Track Enqueue and Playback: To play tracks from Apple Music, the MusicKit player allows tracks to be enqueued using the setQueue or playNext method. The Apple Music Identifier of the desired track is used to facilitate playback.
  8. Search and Playback: In cases where a specific track lacks an Apple Music Identifier in the ListenBrainz database, the search API can be employed to retrieve relevant tracks. Subsequently, tracks can be enqueued from the search results into the player for seamless playback within the BrainzPlayer interface.

Building an Apple Music Content Resolver

The Apple Metadata Content Resolver queries Apple Music Catalog APIs with artist/album/track identifiers to gather information about them and store it in ListenBrainz database. Its modeled on the existing Spotify Metadata Content Resolver. It took multiple passes of refactoring to get to the current implementation which shares a large amount of with Spotify Content Resolver. Hence, helping reduce redundant code.

Working with lucifer, I identified a common set of minimal functions that differed in Spotify and Apple resolvers. The rest of the code was separated into a runner or base class. In its latest implementation, Apple Music Resolver only needs to implement 6 custom methods for its entire functioning.

  1. get_seed_albums – queries the Apple Music Charts API to find newly released albums ids to seed the content resolver and assist it in finding new releases.
  2. The method get_items_from_listen extracts album IDs from a given “listen” data and creates a list of JobIteminstances.
  3. The method get_items_from_seeder takes a message containing Apple album IDs and creates a list of JobIteminstances.
  4. transform_album – converts a raw response from the Apple Music API to an Album object for storage in the ListenBrainz database. It removes a lot of the redundant relationship data and also organises tracks and artists appropriately with the Album.
  5. The method fetch_albums queries Apple Music Albums API using the given Apple Music album identifiers. If needed, it uses the pagination for tracks to fetch the remaining tracks in the album. Then it tries to find new seeds for the resolver by calling discover_albums on the album artists’ identifier.
  6. The method discover_albums given an Apple Music Artist identifier it queries the Apple Music Artists API to find all other albums of the artist and enqueues the discovered albums to the job queue.

Overall, the AppleCrawlerHandler class is designed to interact with the Apple Music API to fetch and process album and artist data, and generate job items for further processing. The class is part of a larger system or application responsible for crawling and caching Apple Music metadata.

Check out the code here Apple Music Content Resolver.

My experience & learnings

I’m truly grateful for the mentorship I received from riksucks, lucifer, and the entire MetaBrainz team. These individuals are not only excelling in their respective fields but also excelling as mentors. Contributing to ListenBrainz has been an immensely rewarding experience for me. The collaborative nature of Metabrainz team brings fun and dynamic perspectives of everyone into every discussion and suggestions flow freely.

Being part of a project that holds genuine significance is a programmer’s aspiration, I feel fortunate to have fulfilled that dream. The opportunity to contribute to a widely used project, alongside a community of seasoned contributors, has been invaluable. Working on an expansive codebase has stretched my capabilities and boosted my confidence in open-source skills.

GSoC ’23: Artist similarity graph

Hello everyone,

I am Arshdeep Singh, a 3rd CS student from the University of Manitoba. I worked as a GSoC contributor for MetaBrainz on the Artist similarity graph project. I would like to dive into the details of the project and share my experiences of the same.

Project Overview

What’s better than listening to your favourite music? Discovering new pieces to add to your personal collection and play on repeat. This very idea is at the heart of the artist similarity graph project. The project helps the users to uncover the connections between artists with similar genres and styles. It does so by providing a search interface to the users, where they can find their favourite artist and then generate a graph of similar artists. An artist panel featuring information about the artist is also presented, it showcases artist’s name, type, birth, area, wiki, top track and album. Users can also play the tracks right on the page itself using BrainzPlayer.

It is a part of the ever expanding explore section of ListenBrainz. Giving users more options to find new pieces of music which they will treasure. The initial approach was to use artist images for building the graph but due to the unavailability of the images a text based graph was decided upon. A network graph with a central node of the selected artist and the links to the related artists is displayed. The artists are arranged based on their similarity score. Artists with higher scores being closer and lower being further. To convey the strength of relationships between the artists a divergent colour scheme is used. The user also has the ability to travel across the graph by clicking thorough the artists (nodes). It creates a intuitive and rewarding process for the user. An initial mock up of the project is shown below.

Initial mockup

Technologies used:

  1. nivo: For artist graph generation
  2. React with Typescript: For web pages
  3. Figma: Building mock ups and prototypes
  4. Docker: To Containerize applications

Project Milestones

Normalizing data

The first challenge was to normalize the data before using it could be used to generate a graph. Given the non linear nature of the data, a square root transformation was used to transform the data. The result is a linear set of data which can be appropriately used in a graph.

Generating graph using nivo

Now, that data has been processed a graph is generated using the nivo framework. The project utilizes the network chart component in the nivo collection. A few modifications have been made to the network chart to better suit the project. By default, the network chart does not have any labels highlighting info about the node. Therefore, a new custom component had to be made. It displays the name of the artist in the middle and also truncates the name in case it exceeds a certain length.

Picture of regular graph. Regular graph component

Picture of custom component. Custom graph component

API endpoints

The project makes use of multiple API endpoints to collect varied data. The complete list of these endpoints is shown below:

  1. artist-similarity endpoint. Provides the data for the similar artists for a given artist.
https://labs.api.listenbrainz.org/similar-artists/json?artist_mbid={artist_mbid}&algorithm=session_based_days_7500_session_300_contribution_5_threshold_10_limit_100_filter_True_skip_30
  1. artist lookup: To search the list of artists from the MusicBrainz database.
https://musicbrainz.org/ws/2/artist/?query=artist:{artist_name}&fmt=json`
  1. Multiple endpoints are used in the artist info panel.

    i. Artist info from MusicBrainz lookup

    https://musicbrainz.org/ws/2/artist/{artist_mbid}?inc=aliases
    

    ii. Wikipedia-extract which is locally stored at MusicBrainz

    https://musicbrainz.org/artist/{artist_mbid}/wikipedia-extract
    

    iii. Artist-profile link at MusicBrainz.

    https://musicbrainz.org/artist/{artist_mbid}
    

    iv. ListenBrainz API for top recordings for a given artist

    https://test-api.listenbrainz.org/1/popularity/top-recordings-for-artist?artist_mbid={artist_mbid}
    

Search and Artist panel

The project also incorporates an artist search and an information panel to add to the utility of the project. The following image shows the final project.

Final project

The search features shows up as a drop down menu which can be used to search and select the desired artist. An additional option to modify the graph size also is also presented to set the number of similar artists to the user’s preference.

The information panel highlights important features about the artist like name, type, Wikipedia article, MusicBrainz profile link, top recording and album. The user can also use the BrainzPlayer to play the top recording or the track for the selected artist.

Current state

The project was able to achieve almost all of the requirements provided except a few. The BrainzPlayer can be a little tricky at times as some the tracks may be not show up and may require two tries from the user for the first time. In addition to this, the design of the website cannot perfectly match the mock ups due to different layout structure being used across the site and to keep the website consistent. Integration into the code base is good enough for the project to run smoothly but a few changes may be made to optimize it further. Top album feature may not be always accurate because of the lack of a dedicated endpoint for it and as a workaround the album of the top track is shown on the page. The PR for the project can be accessed here.

My learnings

It was my first project with react and learning typescript had a bit of a learning curve but it was a worthwhile experience. Working with nivo library was a bit difficult as well due to a lack of tutorials for it. However, I was able to get a lot of help from their documentation. I was also introduced to many new technologies as well, docker, figma and flask. All of which added to the quality of my experience.

The project also gave me an opportunity to apply a lot of the concepts I had learned in my coursework. I want to thank the amazing MetaBrainz community for always lending a helping hand during my time as a GSoC contributor. I feel I will be taking a lot of things forward with me in life and it was truly an enriching experience for me.

GSoC’23: Dataset Hoster Improvements

Hi Everyone!

I am Vishal Singh (also known as Pixelpenguin on IRC). This year I participated in Google Summer of Code under MetaBrainz and worked on improving MetaBrainz Dataset Hoster repository. My mentor for this project was Kartik Ohri (lucifer on IRC). This post summarizes my contributions made for this project.

MetaBrainz Dataset Hoster

The MetaBrainz Dataset Hoster project involves processing data to populate a Python object and subsequently hosting the results as an API. This initiative stems from MetaBrainz’s music recommendation work, aiming to efficiently transform and evaluate datasets for integration into their recommendation tools.

API’s can be created using a simple template class such as

Coding Tasks

Allow users to invoke new dataset hoster queries from results

The idea for a dataset hostler is to walk through the datasets that are in the data set hoster. This feature allows users to repeat the similar artist query from one of the results of that page.

Dropdown is shown next to a row of results in the dataset hoster, such as a recording MBID. This dropdown allows users to utilize the displayed MBID as input for a new query within the dataset hoster. Which then is opened in a new tab, allowing recursively to go through the dataset.

Types support in Dataset Hoster

Feature allows queries to define the allowed types for each input so that the dataset holster repository can perform validation on the inputs. For instance, the Similar Recordings query has defined an input model as:

If an invalid mbid is input by the user, Dataset Hoster throws an error.

Added select input to dataset hoster

Currently, datasethoster only handles text inputs. However, sometimes a query wants to specify a list of acceptable values. To do so, the query can declare an enum field and its possible values will be rendered as a HTML select field to the user with all the possible values of the Enum as the option.

The implementation also allows for select fields with dynamic options. For example, the ‘algorithm’ field in a similar-recordings query needs to be populated dynamically depending on the datasets currently present in the database tables. Further, these options should populate dynamically, allowing queries to fetch values from databases or Redis. Options could be cached on app startup or fetched per query execution for efficiency.

Result:

Datetime field support in dataset hoster

For queries that accept a datetime field in their input, we display a HTML datetime input to let the user choose the desired time easily. When using the JSON API version, the user can submit datetime as a UTC timestamp or ISO format date string.

Result:

List of issues

  • LB-1246: Datetime field support in dataset hoster
  • LB-1244: Add select input to datasethoster
  • LB-1249: Allow users to invoke new dataset hoster queries from results
  • LB-1243: Types support in Dataset Hoster
  • LB-1247: Improving response format for dataset hoster

List of Pull Requests

  • #15: Array type support in Web query handler
  • #14: Queries to be used in dropdown for similar result
  • #12: Pydantic support in query inputs outputs
  • #9: Strip space from args
  • #2533: LB Labs recording similarity: Dynamic selection of options
  • #2522: Migrate all labs api to have Pydantic support

What’s Left?

Pull requests in Dataset Hostler are merged, ensuring the implementation of the features discussed above. However, the migration of queries related to these changes are not yet deployed to production. because updates to these queries also require changes in their respective clients as well, only after simultaneous improvements can the changes be deployed.

More details can be found here – Migrate all labs api to have Pydantic support

Experience and Learning

I feel this  was a once in a lifetime experience for me. Metabrainz gave me this opportunity and opened me to the world of open source. I am really glad that I got mentorship from lucifer (Kartik Ohri), who helped me through the entire period. There is still a lot I can learn from him, he is really an expert when it comes to the tech stack of Metabrainz.

Project helped me learn about python, API’s, docker, music (and its presence in open source) and multiple details about the production environment. It was really interesting to learn about scalability involved in api queries and how the servers of Metabrainz handle them.

Lastly, I want to give a special thanks to Google for organizing such amazing..

GSoC’23: Administration System for BookBrainz

Namaste!

I am Shivam Awasthi, a recent graduate from IIT(BHU), Varanasi. As part of Google Summer of Code’23, I participated as a contributor for the MetaBrainz Foundation, where I worked on creating an Administration System for BookBrainz.

During this period, I was mentored by monkey, and ansh. With this post, I’ll be giving an overview of my project.

What is my project all about?

BookBrainz lacked an Administration System, which meant that direct database access is required in order to do admin work like adding a relationship type or an identifier type. Trusted community members should have an interface which allows them to add/edit new types to the database. 

Thus, a system was required which allows us to give special privileges to certain users and add types to the database, specifically relationship and identifier types.

Therefore, this project involved:

  • Creating an Administration system, which allows admins to give special privileges to users.
  • Creating Type Editors for both Relationship-Types and Identifier-Types.
  • Creating an Admin Logs of these administrative actions
  • Privileged users should be able to trigger a reindex of the search engine

The Administration System

After making the required schema changes for an admin-logs table, and modifying the editor table to support privileges, I started my work on creating an Admin Panel.

Now there is support for 5 different types of privileges:

  • Entity Editor: This is a basic privilege, which allows normal users to edit and create new entities.
  • Reindex Search Server: This privilege allows a user to directly trigger a reindex of the search server.
  • Relationship Type Editor: This privilege allows a user to edit and add relationship-types.
  • Identifier Type Editor: This privilege allows a user to edit and add identifier-types.
  • Administrator: This privilege allows a user to edit privileges of other users.

BookBrainz now has a Privilege Dropdown which allows Administrators to access an Admin Panel:

On the Admin Panel, you can search for a user by name. You can see the privileges that a user currently has by their privilege badges. There is also a distinct shield icon for Administrators.

On clicking the Edit button, a Modal opens up which allows us to change privileges and add a reason for the change.

These changes are recorded on an Admin Logs page:

Relationship Types Matrix, Relationship Types Page, and the Type Editor:

After having some discussion on how the different relationship types should be displayed on the website, we, like always, decided to do it like MB does.

We now have a matrix which allows us to view the relationship-types between any two types of entities:

The Relationship Types are listed in a Tree type of format:

The PR also added a Relationship Type Editor which allows us to add/edit relationship types.

The Identifier Types page, and the Identifier Type Editor

The final PR for this project added a page which lists all the different Identifier Types and an Identifier Type Editor.

We can access the relationship types page and the identifier types page through a newly-created ‘Docs’ Menu:

TypeScript Challenges:

BookBrainz is slowly moving to TypeScript, which means there is only partial support for types from the bb-data end. Since there is already another project which will add support for more types, and in the process create a more definite naming convention for the different types on BookBrainz, I was sometimes left wondering whether I should create the new type on the bb-site or the bb-data.

We finally decided that we should, for the time being, create the required types on the bb-site repo and move them to the bb-data after the project.

What’s left?

Below are some things which are extended goals, and can be considered as a good continuation of the project:

  • Attribute Type Editor
  • Moving the types to the bb-data repo
  • Better error handling for the relationship type editor

I plan to work on these after GSoC.

Most of these features are already deployed on the test website.

My Proposal can be seen here.

My PRs: merged | unmerged

My experience with MetaBrainz

Working on this project has been a very fulfilling experience.

From careful reviews by ansh, to the various gotchas pointed out by monkey, to the constant feedback around the features by the community, it has been an enriching journey, both as a developer, and as a member of the BookBrainz community.

I am really glad that I got to be a part of this project, and received guidance from wonderful mentors like monkey and ansh

I would be remiss if I dont mention the constant feedback I received from fellow GSoC participant kellnerd, who provided insightful comments and suggestions from time to time.

The MetaBrainz Foundation was my first foray into open source, and I am glad that I got the opportunity to contribute as a GSoC student for the project!

GSoC 2023: Feed Section in ListenBrainz Android

Hello everyone!

I am Jasjeet Singh (also known as jasje on IRC and 07jasjeet on GitHub). I am an undergraduate student pursuing information technology at UIET, Panjab University, Chandigarh, India. This year I participated in Google Summer of Code under MetaBrainz and worked on implementing a feed section in the new and upcoming ListenBrainz Android app derived from the ListenBrainz website. Along with the implementation of the original feed section, I have also added new features to the section that are currently available only on the app. My mentor for this project was Akshat Tiwari. (akshaaatt on IRC)

Proposal

For readers who are not acquainted with ListenBrainz, it is a website where you can integrate multiple listening services and view and manage all your listens on a unified platform. One can not only peruse their own listening habits but also find other interesting listeners and interact with them. Moreover, one can even interact with their followers by sending recommendations or pin recordings. Feed is an integral part of developing this social interaction between users and so, my GSoC proposal was aiming to bring it to the ListenBrainz Android using compose as the primary UI toolkit and Kotlin as the primary language.

Pre-Community Bonding Period

The pre-community bonding period refers to the time frame between when I first hopped into the MetaBrainz community and when my GSoC proposal was selected. I had done a lot for ListenBrainz Android by the time the GSoC results were announced. For example, I implemented Year in Music, Explicit Theme Switch, fixed and redesigned the Listen Scrobble Service, fixed multiple bugs related to BrainzPlayer, ANRs, and a lot more. I became well aware of the codebase and how things work in MetaBrainz even before the proposals were submitted.

Community Bonding Period

During the community bonding period, I tried to maintain the pace, but due to exams, I could only fix two minor bugs. While the bug fixing didn’t go as expected, the designing part went great with the help of MetaBrainz’s in-house designer, aerozol, we initialised the design system for ListenBrainz Android and also created mockups of the feed section in Figma.

Below are the designs we tentatively put forth during the community bonding period. But, as of now, there have been crucial design changes to all screens, especially the Similar Listens screen that will be completed after GSoC.

Apart from designing the mockups, two new ideas for feed were established and finalised. Follow Listens and Similar Listens were the new additions to feed for which endpoints in the server didn’t exist. I took up the task and started its implementation during this period.

Coding Period (Before midterm)

This project was divided into two phases. The first phase primarily included the creation of a search user feature in the app and follow listens and similar listens endpoints for the server.

The search feature was an integral part because of the upcoming implementations on top of it. In this project, only users can be searched and followed, but its scope is a lot bigger than just that, which I cannot reveal in this post. Coming back, searching is a heavy operation for the server. Searching whenever a user types a letter leads to the user hitting rate limits and the server taking excess load. To solve this, debouncing was implemented with a delay of 700 milliseconds.

This screen also consisted of the follow button, which is a critical reusable component that has app-wide use. We had a new approach for the implementation of this button. This button worked optimistically. The button toggles state as soon as it is clicked, which is optimistic because it expects the request to the server to be successful. But, in case of failure, this button reverts its state back and shows a message to the user that the operation has failed. This gives the user a perception of blazing-fast functionality, which is what we wanted to achieve.

A lot of core components were also established, like a streamlined error handling class called ResponseError, which is easier to package and show in the UI, and a base unit test class with the Mockito-Kotlin framework.

This is a demonstration of how this feature works.

This feature was completed in the span of three PRs:

  1. Phase 1.1 (Social service, repository and tests setup with error handling class)
  2. Phase 1.2 (View model, UI and unit tests for view model)
  3. Phase 1.3 (View models tests with updated BaseUnitTest class)

Apart from the Android side of things, on the server, the development of the Follow Listens endpoint (GET /user/<user_name>/feed/events/listens/following) was initiated, which provides all the listens from users that are followed by the requesting authority. While developing this endpoint, we realised that the implementation was flawed as the search space wasn’t limited to any specific time period, nor was the count of listens per user. This meant the endpoint would eventually time out if available data dated far back and listens of only one follower would be returned if the data was available. For this, lucifer (mentor as well) suggested that we limit the search space to 7 days and have pagination based on timestamps alone.

Tests were also created for the endpoint after a painful debugging session that was resolved after puny sleep() call thanks to lucifer. Setting up follow listens endpoint took a lot longer than expected, so the development of similar listens endpoint was pushed to phase 2.

Relevant links to my work on Follow Listens endpoint:

  1. Pull Request
  2. Ticket (For relevant details)

Coding Period (Phase 2: After midterm)

The main big bang of the season, i.e., feed, was started in full force after the midterm evaluation, although planning was done beforehand with a few changes.

From the beginning, our aim was to make all components reusable, and that trend followed through this period. Another aim was to preserve compose preview for composables by hoisting states up in the compose hierarchy. With these two in mind, we set about creating a reactive and scalable UI for the project.

Base Feed Layout

To make a feed event reusable, BaseFeedLayout composable was established, which is widely used for creating all kinds of feed events.

@Composable
fun BaseFeedLayout(
    modifier: Modifier = Modifier,
    eventType: FeedEventType,
    event: FeedEvent,
    parentUser: String,
    onDeleteOrHide: () -> Unit,
    isHidden: Boolean = event.hidden == true,
    content: @Composable () -> Unit
)
Here, the selected FeedEventType for preview is PIN.

One of the complex parts was how to adjust the horizontal line that runs parallel to the Content part as the height of the Content changes. This could be done by first compose calculating its bounds and laying out the composable, and then we would measure the composable and relay all the content. But this approach was inefficient and caused guaranteed recomposition on layout, which makes scaling impossible. To bypass this, we made use of SubcomposeLayout, which pauses composition, calculates the bounds of Content and feeds the bounds to our horizontal line, and lays them together. This approach saved the recomposition that we would incur if we used the previously defined approach and actually improved performance by up to 2X.

Feed Event Type class

Ultimately, BaseFeedLayout composable is responsible for displaying a feed event based on the parameters. FeedEvent is the data class for our raw JSON response from the server, while FeedEventType is an enum class that defines the ambiguous properties of a FeedEvent.

enum class FeedEventType (
    val type: String,  // String identifier for the event.
    @DrawableRes val icon: Int,  // Feed icon for the event.
    val isPlayable: Boolean = true,  // Can the event be played?
    val isDeletable: Boolean = false,  // Can the event be deleted?
    val isHideable: Boolean = false,  // Can the event be hidden?
) {
    /* Our Events */;
 
    // Our helper functions

    @Composable
    fun Tagline(/* Params */)  // Tagline for the respective feed event.

    @Composable
    fun Content(/* Params */)  // Content for the respective feed event
}

The parameters and functions of the FeedEventType class contain all the ambiguous properties that remain static and are not provided by the server response.

Now, getting a feed event is as simple as writing this line of code, which demonstrates the power of polymorphism:

eventType.Content(/* params */)

With this function set, it was time to stitch it up and have a beta look at it. Automatic paging is an important function that every app in today’s era has. For that, we employed the View Pager jetpack library, and with a few hiccups here and there, paging was a success. Soon after, the whole feed section came together, and the beta UI was ready.

Performance optimisations for feed UI

While building this feature, performance was a big concern. To limit the number of states on a screen, an array of steps were taken, such as state hoisting and creating a MutableStateList instead of assigning individual states to a repeating composable. Replacing MutableStateList with MutableStateMap wherever feasible.

Compose complier reports were generated, and classes were marked as stable or immutable to make most of the components skippable and stable, which led to better overall performance.

Remote Playback

The next big task to accomplish was the refactoring of old code and the integration of remote players, mainly Spotify App Remote and Youtube Music API, into Feed. Previous implementations of these players were limited to only one screen, weren’t reusable, and had some flaws. We were onto fixing the same.

First, we began with the Youtube Music API, which was simple enough to mould into a dependency that could be injected, and that is what we did by creating a RemotePlaybackHandler class.

Contrary to Youtube Music, Spotify App Remote is an SDK, which means a lot of work went into making it reusable. One complex challenge we faced was converting the SDK’s AsyncTask functions to suspend functions so that Kotlin Coroutines could be used to call these functions synchronously. Another challenge was to convert Subscriptions used by the SDK to Flows and manage them as well to avoid memory leaks. With this, we added the Spotify app remote’s important interactive functions to the RemotePlaybackHandler class and used this new class to play tracks app-wide.

Dialogs

With this done, all that was left were the dropdowns and dialogs. There were four dialogs within the scope of this project. But the foundation for what’s to come had to be established early. With this, I started breaking down all the dialogs on the website to see a common pattern. And thus BaseDialog composable was created which was the core foundation for other dialogs.

@Composable
fun BaseDialog(
    onDismiss: () -> Unit,
    title: @Composable BoxScope.() -> Unit,
    content: @Composable ColumnScope.() -> Unit,
    footer: @Composable BoxScope.() -> Unit
)
Base dialog with simple texts as content

And thus came the rest of the dialogs. The following are the renders from compose previews:

Review enabled dialog
Review disabled dialog
Personal recommendation dialog
Pin dialog

All these dialogs share the same color scheme, text styles, paddings, sizes and of course, reusables composables such as buttons, text fields and search fields.

Performance optimisations for dialogs

While these dialogs came out pretty and aligned with the website, maintaining their state was challenging. After some thought, we came up with a solution where only one state was required to control the dialogs and only one dialog per dialog type was required for all three screens while maintaining previews. This meant a significant performance improvement over other alternatives and no loss of code quality.

This feature was covered in the span of 6 PRs:

  1. Phase 2.1 (Feed Service, repository and Base Feed UI setup)
  2. Phase 2.2 (Feed UI Setup with all three screens)
  3. Compose Stability and minor updates
  4. Phase 2.3 Part 1 (Remote Players Refactor Part 1)
  5. Phase 2.3 Part 2 (Remote Players Refactor Part 2)
  6. Phase 2.4 (All Dialogs)

On server side of things, we had completed the Similar Listens endpoint (GET /user/<user_name>/feed/events/listens/similar) fairly quickly due to it being similar to the Follow Listens endpoint. Similar listens are listens from users that are similar to you in terms of listening habits. There is a high possibility that you may discover new tracks while meddling around on this page.

Relevant links to my work on Similar Listens endpoint:

  1. Pull Request
  2. Ticket (For relevant details)

Surplus Jobs

Apart from only working on the GSoC project, I have been actively taking this opportunity to work as a team member and not just a contributor. These are some of the jobs I’ve worked on as well:

  1. Made listen submission core-code reusable and employed Work Manager to persist tasks with dependency injection. (Work)
  2. Implemented bulk submission which resulted in efficiency when offline listens were captured. (Work)
  3. Better theme switching. (Work)
  4. Fixing metadata of core data classes used for Listen Submission. (Work)

And many more.

What’s left?

There are a few tasks that could not be completed due to my mismanagement and the vast scope of the project that will be completed after GSoC. These tasks have been given a green flag from my mentor for postponement as of writing this blog. These tasks are:

  1. As mentioned earlier, Similar Listens screen incurred significant changes in its UI design and hence, new intuitive UI will be designed as per the requirements.
  2. Unit tests for feed and UI tests for both, search and feed.

Experience

I am really glad that I got mentorship from akshaaatt, lucifer, and the rest of the MetaBrainz team. These are people whom I look up to, as they are people who are not only good at their crafts but are also very good mentors. I really enjoy contributing to ListenBrainz because almost every discussion is a roundtable discussion. Anyone can chime in, suggest, and have very interesting discussions on the IRC. I am very glad that my open-source journey started with MetaBrainz and its wholesome community. It is almost every programmer’s dream to work on projects that actually matter. I am so glad that I had the good luck to work on a project that is actually being used by a lot of people and also had the opportunity to work on a large codebase where a lot of people have already contributed. It really made me push my boundaries and made me more confident about being good at open source!

Picard 2.9.1 released

Picard 2.9.1 is a maintenance release for the recently released Picard 2.9 with fixes for reported issues and updated translations.
This release contains important fixes for a potential data loss on Windows and several crashes. Windows users are highly recommended to upgrade.
Please see below for details.

The latest release is available for download on the Picard download page.

The detailed changes for this maintenance release are below. For an overview of the new features since Picard 2.8 please see our detailed release announcement for Picard 2.9.

Thanks a lot to everyone who gave feedback and reported issues.

Continue reading “Picard 2.9.1 released”

MusicBrainz Server update, 2023-08-07

This is another summer release with no new features, but it contains a fair amount of small bug fixes and improvements, many related to URL handling. The last user account related pages are also converted to React with this update.

A new release of MusicBrainz Docker is also available that matches this update of MusicBrainz Server. See the release notes for update instructions.

Thanks to atj, chaban, Cyberskull, darkshyne, derat, Freso, scoobert, Van de Bugger and yindesu for having reported bugs and suggested improvements. Thanks to atj and derat for submitting code patches. And thanks to all others who tested the beta version!

The git tag is v-2023-08-07.

Continue reading “MusicBrainz Server update, 2023-08-07”

Picard 2.9 released

The Picard team is happy to announce that the final version 2.9 of MusicBrainz Picard is now available for download. MusicBrainz Picard is the official tag editor for the MusicBrainz database and helps you get your music collection sorted and cleaned up with the latest data from MusicBrainz.

This release brings many changes, including single instance mode, new scripting variables, ID3v2.4 by default, new macOS icon and more. More details below.

Continue reading “Picard 2.9 released”

Happy 23rd Birthday, MusicBrainz!

23 years is a quite a long time and I had never thought that this little project I was starting in 2000, would grow into what it is today — it has been an amazing journey with many ups and downs. Despite many early hardships, I’m extremely happy that I stuck with it and nurtured the project into an important resource for the internet today.

With that in mind, I feel compelled to tell a story of where the name MusicBrainz came from and how July 17, 2000 was the first day that the name has ever been uttered and the domain was registered seconds later.

During this time, I was living in a party household and we were having yet another party — as you do on a Monday evening, right? I was sitting next to my friend Billy when I mentioned that I wanted to create a new project, but that I was lacking a good name for it. He asked me: “What will this project do?”

And I started explaining my vision for the project and after finishing my summary for the idea I said: “… a project about music that brings together many people. … many brains around around the world. Something like MusicBrains!”

I sat bolt upright and then literally jumped up and left Billy sitting on the couch wondering what has just happened. I ran to my computer and checked to see if MusicBrains.org was taken, but then realized that name was great, but it needed a little… something. I didn’t search for musicbrains, my fingers simply typed musicbrainz, without a conscious thought about the z.

The domain was available, so I registered it! I returned to the party, happy to have solved a tricky problem.

Bonus fact: I had just been laid off from my dot com gig, so money was short. I decided to not get musicbrainz.com, figuring that I would register it later if that was going to be needed. But no, in the era of domain squatters, musicbrainz.com was registered shortly after the .org version. With the advent of Google, this was never a problem, except for the number of domain squatters who have tried to sell me this domain over the years. I’m happy knowing that all of their money was 100% wasted. 🙂