SUMMARY a
Screenshots: one, two, three, four, five.
- Like Spotify, but with songs from jw.org
- First prototype in 2021, rewrite in 2022, and this obituary in 2023 so I can put it to rest
- It was part learning project, part itch-scratching ("wouldn't it be nice to have a better media player for these songs? with playlists and other nice-to-haves?")
STACK a
It was a single-page webapp; some javascript, a database for songs, and a python server to connect the two. In more detail:
-
React + Material UI (frontend).
I needed a highly interactive interface, mimicking a native app, and figured I might as well test out React.
Looking back, I remember some big fuss about
useEffect()
vsuseState()
and feeling totally lost. JSX templating was quite ergonmic though, and it was a nice to have a design/widget/icon library too. - SQLite + Bottle (backend). Static files and one API endpoint:
app = Bottle() @app.route('/ids/<songIDs:re:.*>') def database_request(songIDs): result = db.execute(''' SELECT ... FROM ... WHERE ... ''') # ... app.run(host='localhost', port=8888, debug=True)
I probably chose Bottle because it was small and because I was comfortable with finding a random Python library. I was not comfortable with anything else in this area, though. I'd never created or populated a database before, and never routed a web request to a database. But I was happy to invest time into SQL because it seemed like an important tool to learn as a programmer. - No builds, just a bunch of javascript files:
<script src="static/lib/react-17.0.2.development.js"></script> <script src="static/lib/react-dom-17.0.2.development.js"></script> <script src="static/lib/react-router-dom-5.2.8.min.js"></script> <script src="static/lib/material-ui-5.2.8.development.js"></script> <script src="static/lib/babel-6.26.0.min.js"></script> <script src="static/utils.js"></script> <script src="static/playlist.jsx" type="text/babel"></script> <script src="static/all_playlists.jsx" type="text/babel"></script> <script src="static/player.jsx" type="text/babel"></script> <script src="static/main.jsx" type="text/babel"></script>
Babel and React all happened in-browser. This kind of setup has some obvious flaws, but avoiding npm was a huge plus. CDN javascript libraries are great for prototyping and for beginners. -
No deploys either.
I would
call Scripts/activate.bat
for the Python virtual environment (I was developing on plain Windows at the time), runpython server.py
, and access the page at localhost:8888. To use it on my phone, I'd runngrok http 8888
and copy the URL to my device. A bit tedious, but those were the tools I knew.
I'm proud of past me for trying to keep things simple. There's a lot of perks to a stack that stays out of the way. My web projects continue to riff off the same spirit of a minimal tech stack.
FEATURES a
Some features I cared about:
- Queue management: audio player actions like play next, add to queue, shuffling, and skipping. The first prototype was actually just a page that shuffled either vocal or instrumental songs. It became more Spotify-like in the second version, featuring custom playlists and more control over what's playing.
-
Local state: not keeping user data in a server.
Playlists were stored in
localStorage
, and the listening session was stored insessionStorage
(useful to bring back the most recent song after page refresh). Playlists were also represented in URLs likehttp://localhost:8888/playlist/name=my%20playlist&songIDs=1,2,3
This was an important feature because it meant me and my browser could store playlists. If the webapp ever became public, links would also be ideal for sharing. - UI, sort of: I'm a programmer who cares about design but who has no formal training in design. This was a nice opportunity to practice. I even got feedback from a friend who studied for a degree in UX.
- Native controls for playback, including: keyboard media keys for play/pause, "hey google skip song" on mobile, and album cover art on the lock screen. This happens for free if you use the browser globals
navigator.mediaSession
andMediaMetadata
:navigator.mediaSession.metadata = new MediaMetadata({ title: song.name, album: song.category, artwork: [{src: song.imagePath, type: 'image/jpg'}] })
navigator.mediaSession.setActionHandler('play', player.play) navigator.mediaSession.setActionHandler('pause', player.pause) navigator.mediaSession.setActionHandler('nexttrack', player.nextTrack) navigator.mediaSession.setActionHandler('previoustrack', player.prevTrack)
NON-FEATURES a
Some aspects I didn't focus on:
- Builds and deploys. Several reasons: because terms of service say don't distribute software that scrapes content (eg. audio and metadata) from jw.org, and because I was already learning a lot of new front end + back end stuff, and because I wasn't quite sure what a build step or deploy even looked like at the time.
- Although related to builds and deploys: I wanted to make a cronjob or some script to check for newly released songs every month and update the database.
- More queue management. Lots of specific features like 'weighted shuffle', or 'add playlist contents to queue', or 'stop playing after x minutes' are tempting when you have full programmatic access, compared to learned helplessness on Spotify and other closed-source clients.
- Responsive design for desktop. My design process is regrettably "start mobile-first, and desktop can deal with it". I think it takes real design chops to create good layouts at multiple sizes, and to implement them with flexbox or grid or whatever, and I am not that skilled. Maybe one day I'll learn to design layouts as described in The Web's Grain.
- Missing UI components. I made two screens, one for home (grid of cards) and one for playlists (header of widgets above a list), and each took lots of fiddling. Learning the framework was tedious. I'm pleased with how it turned out, though. Just would've been nice to add more components like a fullscreen player or an 'add to playlist' screen.
- Compressing URLs.
The
songIDs
parameter was a comma-separated list of ints, ranging from 0-500+. Some default playlists had 150+ songs, which meant the URLs were over 500 characters long (too impractical). I would've liked to shorten the URLs without losing information, and it seems like a fun data compression challenge. - Testing and error handling. Just thinking about it makes my hobby guts shrivel up. And I'm not sure where to start even if I wanted to; I had a bunch of declarative widgets and global browser objects, not many functions.
These are all sensible features worth addressing, but I didn't get to them earlier, and I won't get to them now. I'm reminded of the three innovation tokens that companies get to spend on new technology, but in hobby project terms - you only get a few learning tokens before the hobby project becomes too slow and un-fun.
This project took place in the middle of covid lockdown times like several of my other projects. It's hard to pick them up again, especially when they're "good enough". And now in 2023, JW Library introduced a comprehensive playlist feature that I'm happy to start using. So farewell my little webapp.