LOG 042 · TECHNICAL · 2024-02-01
Built offline listening: downloads plus an offline-first data layer
2 min read
Situation
In early 2024 I was working on the Goodpods webapp, a podcast app that runs in the browser and inside a native shell built with Capacitor, a framework that wraps a web app in a native WebView. Offline listening is an expected feature of any podcasting app, and Goodpods did not have it.
Task
I was responsible for building it end to end: a download manager for episode audio, and an offline-first data layer so the rest of the app still worked without a connection.
Action
I built the download manager first. A download button on each episode saved the audio to local storage, a Downloads library view listed everything on the device, and the player could play episodes from local files instead of streaming. I then added the management pieces users expect: visible download progress, a working cancel button, and deleting downloaded episodes to free up space.
Users could queue several downloads at once, but I deliberately limited the app to downloading one episode at a time. The constraint was device memory. Capacitor did not have a good chunking download library, so pulling down several large audio files in parallel risked exhausting memory on the device. Queueing downloads and processing them one at a time kept the feature working within that constraint.
The second piece was the offline data layer. We used React Query, a library that fetches and caches server data, so I added persistent offline storage for its cache. That meant the app could cold start with no internet connection at all and still show the user’s data instead of empty screens. On the native app I also added an aggressive prefetch that grabbed all possible data ahead of time, so by the time a user went offline, the content they wanted was usually already cached.
Result
Offline listening shipped in February 2024. Users could download episodes, watch progress, cancel or delete downloads, and play everything without a connection, and the app itself opened and worked offline. It closed the gap on a feature users expect from any podcast app.