GSoC 2025: Integrate music streaming from Funkwhale & Navidrome

Hey Everyone šŸ‘‹šŸ» !

I am Mohammad Amanullah (AKA m.amanullah7 on IRC and mAmanullah7 on GitHub) final year student at National Institute of Technology Agartala, India and along with that I am a diploma level student at Indian Institute of Technology Madras, India. I was thrilled to be selected as a contributor in the Google Summer of Code (GSoC) 2025 program. My project focused on integrating music streaming from Funkwhale & Navidrome. It was mentored by Lucifer and Monkey.

Let’s start šŸ™‚

Project Overview

ListenBrainz has a number of music discovery features that use BrainzPlayer to facilitate track playback. BrainzPlayer (BP) is a custom React component in ListenBrainz that uses multiple data sources to search and play a track. As of now, it supports Spotify, Youtube, Apple Music and Soundcloud as a music service. It would be useful for BrainzPlayer to support stream music web apps like Navidrome and Funkwhale so that users could stream their private collections as well on ListenBrainz. For those unfamiliar, Funkwhale and Navidrome are self-hosted music servers that implement the Subsonic API, a widely adopted standard for streaming and managing personal music libraries.

Before you proceed further, listen to a song and explore new services so you can feel more when you read the rest of the blog! Check out your Connect services page šŸŽ¶

Let’s Deep Dive into My Coding Journey!

Welcome back to blog after exploring new services and listening to a song. I started my coding journey during the community bonding period. In this period, I spent time exploring, learning, discussing and creating the mockups, finalizing the backend and frontend flow so on with aerozol, monkey and lucifer!

The UI that we finally decided upon after many iterations!

Integrate music streaming from Funkwhale

Funkwhale supports OAuth2 in addition to Subsonic API. As OAuth2 is more secure, I decided to integrate Funkwhale using OAuth2. The main challenge here, unlike centralized services (Spotify, Apple Music), is that any user can host their own Funkwhale instance. Each server acts as its own OAuth provider and an app needs to be created for it dynamically. The following flowchart explains the various steps taken to connect a Funkwhale server to ListenBrainz.

Flowchart of connection Funkwhale server to ListenBrainz

The initial database schema had a single table which included both the server details (host_url, client_id, client_secret) and the token details (access_token, refresh_token, token_expiry). This was problematic as either the server app details would need to be duplicated for multiple users or a new app would be created for each user. Hence in the next iteration, I split it into two tables: funkwhale_servers, funkwhale_tokens to avoid redundancies.

Database schema for Funkwhale auth tables
CREATE TABLE funkwhale_servers (
    id                  INTEGER GENERATED ALWAYS AS IDENTITY,
    host_url            TEXT NOT NULL UNIQUE,
    client_id           TEXT NOT NULL,
    client_secret       TEXT NOT NULL,
    scopes              TEXT NOT NULL,
    created             TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE TABLE funkwhale_tokens (
    id                  INTEGER GENERATED ALWAYS AS IDENTITY,
    user_id             INTEGER NOT NULL,
    funkwhale_server_id INTEGER NOT NULL,
    access_token        TEXT NOT NULL,
    refresh_token       TEXT NOT NULL,
    token_expiry        TIMESTAMP WITH TIME ZONE NOT NULL
);
Flowchart explaining how a song is played through Funkwhale in BrainzPlayer

I added the option to connect a Funkwhale server in Connect services, the user needs to only input the url of their server.

Note: Funkwhale currently has a bug in the authentication workflow. If you are not logged into your Funkwhale server, before pressing “Connect to Funkwhale”Ā rather than being redirected to the login page, you will be presented with an authentication error! You can look at this issue for more details!

UI Before connecting Funkwhale
UI once Funkwale is conencted and ready for playback

I added the FunkwhalePlayer component to BrainzPlayer by taking reference from the existing BrainzPlayer architecture and DataSourceType interface to understand how to properly integrate with the existing services. It detects when a listen originates from Funkwhale, handles both direct track URLs and search based matching, and manages authenticated audio streaming.

I also created a custom icon component for both Funkwhale and Navidrome that emulates the FontAwesome icons exported from react-fontawesome. It helps us to avoid messy and hardocded styles for the icon.

export const faFunkwhale: IconDefinition = {
  prefix: "fab" as IconPrefix,
  iconName: "funkwhale" as IconName,
  icon: [512, 512, [], "", [
    "M138.7 135.3c12.6 6.5 26.1 7.7 38.3 14.8...", // Whale body path
    "M223.2 284.9c35.9 0 65.1-29.2 65.1-65.1..."  // Sound wave arcs
  ]],
};

Once connected, ensure that Funkwhale service is activate and set the desired in theĀ BrainzPlayer settingsĀ page.

All set! You are ready to play your Funkwhale collection on ListenBrainz!Ā 
Enjoy šŸŽ¶


One of the complex parts was handling Funkwhale’s multi-artist format variations, Funkwhale’s track search API does not work well if we try to filter tracks by artist name. Hence, we only filter by track name in the API request and manually filter the results on the artist name. The track matching algorithm was particularly complex because Funkwhale supports multiple artist credit formats. Some tracks use the legacy single artist format and some newer instances use multi-artist credits with joinphrases. I implemented a normalization system that handles both the formats and also removes the accents for better matching, and uses a fallback strategy, exact artist as well as title match first, then artist filtered results, then any playable track.

For audio streaming, Funkwhale requires an access token. I implemented a system that fetches the audio as an authenticated blob and creates an object URL, and feeds that to the HTML5 audio element. I also implemented automatic token refresh so that the user does not experience interruptions in playback.

Present Status and Future Improvements

The entire implementation for the Funkwhale integration is contained in the following PR: Integrate music streaming from Funkwhale. The PR has already been reviewed, merged and deployed to proudction.

As future improvements, it would be useful to allow users to connect multiple funkwhale servers to their ListenBrainz account. More thorough unit and integration tests would also be useful in preventing regressions.

Integrate music streaming from Navidrome

Navidrome does not support OAuth2 currently. Effort is underway to add API Key auth support but as of now the the insecure Subsonic Auth is the only viable option. It involves storing the user’s password safely in the database and then using the md5(password+salt) as a token for authentication.

Flowchart detailing how to connect Navidrome to ListenBrainz.

Storing passwords in cleartext in the database is not safe, hence I used Fernet (symmetric encryption). The user’s password is encrypted before storage and only decrypted using the key when needed to generate API authentication tokens. This ensures that even if the database is compromised, the passwords remain secure. Following a similar pattern as Funkwhale created two tables, we can reuse navidrome_servers so we don’t need to save server url for each time and can also be useful when we upgrade this to store OAuth ids, tokens, scopes in future.

Database schema for Navidrome auth
CREATE TABLE navidrome_servers (
    id       INTEGER PRIMARY KEY,
    host_url TEXT UNIQUE
);
CREATE TABLE navidrome_tokens (
    user_id             INTEGER,
    navidrome_server_id INTEGER,
    username            TEXT,
    encrypted_password  TEXT -- Fernet-encrypted password
);

The frontend implementation followed the same DataSourceType pattern as Funkwhale and other music services, but there was a major difference in how we are handling authentication. Instead of maintaining an access token like an OAuth based implementation, the player generates fresh MD5 authentication parameters every time for each API request using the user’s stored credentials in the database.

Flowchart of playing a song using Navidrome in ListenBrainz

Connecting to a navidrome server requires three user inputs, host_url, username and password unlike funkwhale which only has host_url. Users can also edit the credentials later.

UI Before connecting
UI once Navidrome is connected

As with Funkwhale, you can activate Navidrome playback and set its priority in theĀ BrainzPlayer settingsĀ page

All set! You are ready to play your Navidrome collection on ListenBrainz!Ā 
Enjoy šŸŽ¶

The track matching was more straightforward for Navidrome as the Subsonic API provides a better search endpoint. The search3 endpoint allows us to query by both track and artist name simultaneously, and also return simple well structured results that are easier to parse than Funkwhale multi-format artist credits.

Navidrome track search
const searchForNavidromeTrack = async (
  instanceURL: string, authParams: string, trackName?: string, artistName?: string
): Promise<NavidromeTrack | null> => {
  
  const query = `${trackName || ""} ${artistName || ""}`.trim();
  const searchUrl = `${instanceURL}/rest/search3?query=${encodeURIComponent(query)}&songCount=1&${authParams}`;

  const response = await fetch(searchUrl);
  const data = await response.json();
  
  const searchResult = data["subsonic-response"]?.searchResult3;
  
  // Return first matching track
  return searchResult?.song?.[0] || null;
};

Audio streaming was significantly simpler than Funkwhale because Navidrome’s stream URLs include authentication parameters directly in the query string. This means I could set the HTML5 audio element’s src directly to the authenticated stream URL without needing to fetch the audio as a blob.

Current Status and Future Improvements

The Navidrome integration code is contained within the following PR: Integrate music streaming from Navidrome. It is currently pending review, following which it will be merged and deployed soon.

As future improvements, OAuth2 or API Key auth support can be added for Navidrome once available. The ability to connect multiple servers and more tests would be useful as well.

Testing

It was my first timing writing tests but I was successfully able to write basic tests for frontend as well as backend. The existing tests helped were easy to read and served as a great reference! In the future, I will add more functional tests.

Overall GSoC Experience

This summer has been an incredible journey for me to work with the MetaBrainz, and I’m deeply grateful to GSoC for this amazing opportunity. Contributing to ListenBrainz and implementing both Funkwhale and Navidrome music service integrations has been both challenging and rewarding, seeing my work now live in production for users worldwide. Being a part of MetaBrainz is an incredible feeling. I’m gonna miss Monday meetings for sure. I will be continuing to fix bugs, issues or contribute other improvements.

Throughout this journey, I have learned so many things. I am now more comfortable with Git ang Github. Initially, I did’t have much TypeScript knowledge. But, in this period, I worked on my skills, tried – failed – asked for help when stuck and finally finished the implementation. I have become more comfortable with Docker now and stuff like OAuth2 integration and music streaming implementation etc.

I would like to thanks monkey, lucifer, aerozol, a lot for helping me throughout this period, guiding me and for constantly support me. Whether it was the MetaBrainz chat or the PR reviews, I always received detailed feedback, help and suggestions. NGL monkey, I was sure I wouldn’t gonna help to create the Navidrome Icon but God had other plans.

I built some cool stuff this summer and it’s going to be used by people all over the world. Thank you to all others who helped me throughout this journey and helped and guided me! I hope you guys will enjoy listening to songs more with more services.

My proposal can be found here , PDF here
All my pull requests, commits for ListenBrainz during GSoC 2025!

Leave a Reply

Your email address will not be published. Required fields are marked *