Before music streaming services took over, people were quite literally left to their own devices. Ignoring the occasional nuisances that come with building out your own music library, all was well and you'd have full control over whatever you'd want to do with your acquired music.
As MP3 players and smartphones popped up out of nowhere however, making sure music on different devices remained in sync, turned out to be a world of pain. Right when you felt like going out for that walk or run, the device's music library would be outdated and something would stand in your way of synchronizing it in time.
Surely this drove streaming's early adoption, among other factors. What bliss, not having to assemble your own system but simply get it out of the box. Hold up though, have we handed in our control for mere convenience at the same time?
Taking back control
Fortunately the answer is no, or - maybe more accurately - at least not all of it. We're still left to the willingness of these streaming giants to give back some control to their users but luckily, Spotify does just that. They maintain what is called an Application Programming Interface (API), which is simply a way of programmatically interacting with applications out there. Tom Scott does a great job at explaining this without focusing on code itself, which might be helpful for those who are less familiar with it.
Ever since I've made the switch to streaming, I've always missed a peculiarity that came with my manual workflow. Considering the overhead of syncing a new track to multiple devices, I tended to batch them together over a couple of weeks until I had enough to justify going through the bothersome process. Oftentimes I found myself listening to unfinished batches on shuffle. Over time, a certain connection would develop between the individual tracks, but also to the life phase I found myself in, bringing back memories when listening to these again later.
While using Spotify, what I usually ended up doing was playing all tracks in my library instead, ordered by the date I saved them. As you can expect, this gets boringly predictive real fast. What I wanted instead was being able to shuffle only the latest tracks I'd saved, like before. I could of course create a playlist for these and keep it up to date manually but then wouldn't I have traded one manual effort for the other, even though arguably a less inconvenient one? Could I somehow, in the long term, get around Lieven Scheire's unofficial Law of Conservation of Hassle?
"Any new product alleviating the hassle of an existing one, causes as much hassle in implementation and troubleshooting as it has avoided elsewhere."
This is exactly where the API and automation come in. My hope is to show you how easy it can be bringing an idea like this to life when you leave out irrelevant details about how specific technological systems work and need to be set up. Let's deal with the task at hand by splitting it up into more manageable subtasks, and tackling these in the following sections.
Gain access to my library
First of all, we initialize the Spotify API and provide credentials that we've gathered from our account. We also specify what we need to be able to do exactly by providing scopes; which in this case would be reading from my library and reading and modifying private playlists.
val api = initializeApi(
clientId = clientId,
clientSecret = clientSecret,
redirectUri = redirectUri,
accessToken = accessToken,
refreshToken = refreshToken,
scopes = listOf(
SpotifyScope.USER_LIBRARY_READ,
SpotifyScope.PLAYLIST_READ_PRIVATE,
SpotifyScope.PLAYLIST_MODIFY_PRIVATE,
),
)
Select my latest saved tracks
Rather than using a time window or a fixed amount of tracks, I want to find a meaningful cut-off point in time between them. My Spotify usage normally alternates between listening to my library for a prolonged period of time and being on the lookout for new music; which usually means quite some time can pass without me saving any new tracks at all and then suddenly saving multiple on the same day. Therefore, I chose the selection to be anywhere between the latest 20 to 40 tracks I've saved, with the cut-off point being the maximum amount of days between saving tracks.
val minNumberOfTracks = 20
val maxNumberOfTracks = 40
val latestSavedTracks = api.getLatestSavedTracks(maxAmount = maxNumberOfTracks)
val maxDaysBetweenSaving = latestSavedTracks
.subList(minNumberOfTracks, maxNumberOfTracks)
.zipWithNext { savedTrack1, savedTrack2 ->
calculateDaysBetweenSaving(savedTrack1, savedTrack2)
}
.maxOrNull()
val selectedLatestTrackIds = latestSavedTracks
.zipWithNext()
.dropLastWhile { (savedTrack1, savedTrack2) ->
calculateDaysBetweenSaving(savedTrack1, savedTrack2) != maxDaysBetweenSaving
}
.map { (savedTrack1, _) -> savedTrack1.track.id }
Save these in a newly created playlist
We'll give our desired playlist a nice name and description. First we'll check if maybe it exists already, since we wouldn't want to create a duplicate; and if it doesn't yet, we'll create it. Afterwards, we'll simply save our selected tracks from before.
val recentSelectionPlaylistName = "Recent selection"
val recentSelectionPlaylistDescription = "Selection of latest saved library tracks (min. ${minNumberOfTracks}, max. ${maxNumberOfTracks})"
val recentSelectionPlaylistId = api.getExistingPlaylistId(name = recentSelectionPlaylistName)
?: api.createPlaylist(name = recentSelectionPlaylistName, description = recentSelectionPlaylistDescription).id
api.replacePlaylistTracks(
playlistId = recentSelectionPlaylistId,
trackIds = selectedLatestTrackIds,
)
Keep these up to date
Since the API doesn't include any functionality to notify us whenever a track has been saved or removed, we'll just have to run the code frequently enough instead. For now, I've set it up to run every 5 minutes on a Raspberry Pi (essentially a mini-computer); so whenever there's an update, we can expect our playlist to be updated at the latest 5 minutes after.
Outcome
While I've only been using this setup for a bit now, I can already tell it's improved my usual listening experience tremendously. Moreover, it actually solves another problem as well, which is having limited device storage. Moving forwards, this is the only playlist I keep downloaded at all times, whereas before all tracks in my library needed to be.
Of course this setup solves problems that might be fairly specific to me, but I hope at least it gave you a glimpse into what is possible in terms of bringing your own ideas to life, even without having a lot of coding knowledge. Feel free to use (and adapt) the full source code to your specific use case. Happy automating!