Skip to content

Commit 5e4f64d

Browse files
committed
beetroot: full project page writeup with history, screenshots, and video
1 parent a9ed99a commit 5e4f64d

10 files changed

Lines changed: 191 additions & 19 deletions

File tree

_projects/beetroot.md

Lines changed: 124 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,142 @@ layout: project
55
platforms:
66
- Desktop
77
- Web
8-
links:
9-
- label: Website
10-
url: https://beetroot.live/
11-
- label: GitHub
12-
url: https://github.com/beetroot-music
138
archived: true
149
---
1510

16-
<div class="callout">
17-
<p>Development on Beetroot has been stopped.</p>
18-
<p>Integrating with multiple music services means working around APIs that are largely locked down. Keeping those integrations working solo over time just isn't feasible.</p>
11+
<div class="callout callout-archived">
12+
<p><i class="bi bi-x-octagon"></i> Development on Beetroot has been stopped.</p>
1913
</div>
2014

2115
Beetroot answers the question of "who's on aux?" — a shared queue that lets anyone at a gathering add songs, similar to [SharePlay] and [Spotify Jams] but across multiple music sources.
2216

23-
<div style="text-align:center">
24-
<img src="{{ '/assets/images/beetroot/client-feb-2025.png' | relative_url }}" alt="Screenshot of Beetroot Player with a few tracks in the queue." style="max-width:100%">
25-
</div>
26-
27-
[SharePlay]: https://support.apple.com/en-nz/guide/iphone/iphb657eb791/ios
28-
[Spotify Jams]: https://support.spotify.com/us/article/jam
17+
![Screenshot of Beetroot Player with a few tracks in the queue.]({{ '/assets/images/beetroot/client-feb-2025.png' | relative_url }}){:.full}
2918

3019
## History
3120

32-
Beetroot started in high school and got revisited a few times through university.
21+
[librespot-java]: https://github.com/librespot-org/librespot-java
22+
23+
The very first project with the name of "Beetroot" was a program I wrote in
24+
2016 to sort my music by its metadata.
25+
26+
The tagline was "It roots through your beats, so you don't have to."
27+
28+
![Screenshot of the 2016 Beetroot music sorter.]({{ '/assets/images/beetroot/2016-beetroot.png' | relative_url }}){:.full}
29+
30+
It had some development, I had toyed around with hosting an HTTP server out of
31+
the app to play my library remotely, but hadn't gone past it much.
32+
33+
A couple of years later, I reused the name for an even better project idea.
34+
3335

34-
The earliest version was built on [Electron]. It later moved to [UWP], and eventually to Java after discovering [librespot-java] — a reverse-engineered Spotify client library. That shift unlocked real progress: Spotify playback and MP3 support both landed, along with a rudimentary web relay that let people queue songs remotely.
36+
## 2018 - Beetroot Player
3537

36-
It stalled out while trying to add YouTube support. Both YouTube and Spotify changed their APIs around the same time, and maintaining integrations across multiple services that actively resist third-party clients wasn't something I could keep up with solo.
38+
I then created Beetroot Player in 2018, when I was in my last year of
39+
college[^college] with a simple goal in mind: "Allow party guests to queue
40+
tracks to the playlist."
41+
42+
[^college]: that's "high school" for the Americans
43+
44+
It was originally created with JavaScript on [Electron]; I quickly built out
45+
the user interface, library management, localisation, and playback, and was
46+
fairly happy with what was there.
47+
48+
![Screenshot of Beetroot Player (2018).]({{ '/assets/images/beetroot/2018-beetroot.png' | relative_url }}){:.full}
3749

3850
[Electron]: https://www.electronjs.org/
51+
52+
When I started work on the server connectivity, which was needed for the
53+
"having your guests queue tracks" bit, my inexperience left a lot of tech debt
54+
I didn't know how to properly address at the time.
55+
56+
Looking back, if I had continued building it, the relay service would have been
57+
written in PHP and use [SSE (Server-Sent Events)][SSE], which would've caused
58+
its own issues.
59+
60+
[SSE]: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
61+
62+
Additionally, codec support was not uniform across platforms. A lot of music
63+
playback technology[^mp3] is locked behind software patents, which are enforced
64+
differently across the world, leading to having to install them manually on
65+
most Linux distributions, which I wanted to support.
66+
67+
[^mp3]: such as MP3, AAC and friends
68+
69+
With all that in mind, I worked on other projects, with more interesting
70+
problems in the meantime and stopped working on it as I was focusing more on my
71+
studies.
72+
73+
74+
## 2020 - Phoenix
75+
76+
Having learned some software development during university, and with COVID
77+
having me at home most of the time, it was time to dust off this project.
78+
79+
This time around, I opted to go for native applications on each platform,
80+
starting with [UWP] on Windows. I was fairly excited too, because I had been a
81+
former Windows Phone user, and learned the basics of making apps[^wpapps] on
82+
the platform — those skills transferred to UWP.
83+
3984
[UWP]: https://learn.microsoft.com/en-us/windows/uwp/get-started/universal-application-platform-guide
40-
[librespot-java]: https://github.com/librespot-org/librespot-java
85+
[^wpapps]: sadly I didn't publish or archive them. I wish I did.
86+
87+
From the get-go, there were improvements. A clear data model, proper bindings,
88+
a nice media playback API, and the UX actually felt nice.
89+
90+
![Screenshot of Phoenix (2020).]({{ '/assets/images/beetroot/2020-beetroot.png' | relative_url }}){:.full}
91+
92+
While working on the player app, I had also started work on the web service,
93+
going for TypeScript and Express. When I returned to the project later that
94+
year, I picked up the web service first — the plan being to get the relay sorted
95+
before returning to the app. By then though, UWP was fading in relevance, and
96+
the app side stayed shelved.
97+
98+
99+
## 2023 - Beetroot Music
100+
101+
By this point, I was on my way to finishing university and had been working in
102+
software development for a bit. I had a much better sense of how to actually
103+
build something like this.
104+
105+
The key unlock was discovering [librespot-java], an open-source
106+
reverse-engineered Spotify client. That's what led me to choose Kotlin for the
107+
desktop app — the library was JVM-based, and it meant I could offer real Spotify
108+
playback without going through their official SDK. Mobile was Flutter, as both
109+
Spotify and Apple Music had libraries available there.
110+
111+
<div style="background:var(--colour-background-elevated);margin-inline:-1.5rem;padding:1rem 1.5rem;display:flex;justify-content:center;align-items:center;border-radius:0.5rem;gap:1rem;">
112+
<img src="{{ '/assets/images/beetroot/logo-master.svg' | relative_url }}" alt="Beetroot logo" width="64" height="64">
113+
<img src="{{ '/assets/images/beetroot/logo-client.svg' | relative_url }}" alt="Beetroot Player logo" width="64" height="64">
114+
<img src="{{ '/assets/images/beetroot/logo-relay.svg' | relative_url }}" alt="Beetroot Relay logo" width="64" height="64">
115+
<img src="{{ '/assets/images/beetroot/logo-neurofunk.svg' | relative_url }}" alt="Beetroot Neurofunk logo" width="64" height="64">
116+
</div>
117+
118+
Given the licensing situation with those integrations, I went with an open-core
119+
model. The desktop app, Beetroot "Amen Break", would be open-source with local
120+
library support and an open plugin ecosystem. A separate closed-source build
121+
would include the Spotify, Apple Music, and SoundCloud integrations. The mobile
122+
app and relay service followed a similar split — Deepcore and the command-line
123+
client NeuroFunk stayed open, the mobile app didn't.
124+
125+
This was the most complete version of the project by some distance. The relay
126+
actually worked end-to-end. The plan was to develop in private and run open
127+
betas around parties I was hosting or helping set up — a fitting way to test it.
128+
129+
It stalled for a reason that, in hindsight, was always going to catch up with
130+
the project. When you're building on top of services that haven't agreed to
131+
support you, you're at their mercy. YouTube changed their API mid-development.
132+
Spotify changed their login system — librespot eventually caught up, but the
133+
underlying Mercury protocol isn't documented anywhere, and reverse-engineering
134+
it alone wasn't something I was going to take on.
135+
136+
By the time I stepped back, [SharePlay] and [Spotify Jams] had both shipped as
137+
native features. The problem Beetroot set out to solve was solved, just not by
138+
me.
139+
140+
[SharePlay]: https://support.apple.com/en-nz/guide/iphone/iphb657eb791/ios
141+
[Spotify Jams]: https://support.spotify.com/us/article/jam
142+
143+
<video class="full" src="{{ '/assets/images/beetroot/beetroot-final.mp4' | relative_url }}" controls></video>
144+
145+
At some point I'll come back, clean it up, and release the source — not to pick
146+
it up again, but to give the work somewhere to live.

_sass/matte/components/image.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
max-width: 100%;
33
}
44

5-
.chrome-inset article img.full {
5+
.chrome-inset article img.full,
6+
.chrome-inset article video.full {
67
display: block;
78
width: calc(100% + 3rem);
89
margin-inline: -1.5rem;
69.9 KB
Loading
451 KB
Loading
492 KB
Loading
5.44 MB
Binary file not shown.
Lines changed: 12 additions & 0 deletions
Loading
Lines changed: 11 additions & 0 deletions
Loading
Lines changed: 24 additions & 0 deletions
Loading
Lines changed: 18 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)