LOG 044 · TECHNICAL · 2024-04-01
Built a native audio engine from scratch in Swift and Android Media3
2 min read
Situation
Goodpods is a podcast app built as a web app wrapped in Capacitor, a framework that runs web code inside native iOS and Android shells. The existing web-based audio player crashed, and it could not do the things podcast listeners expect from a native app: reliable background playback, lock screen controls, and playlists. Off-the-shelf plugins were not an answer either: the Capacitor community audio plugin was poorly maintained at the time, did not support all the audio formats we needed, and lacked playback speed, queues, and lock screen handling.
Task
I decided the only way to get the player we needed was to build our own native audio engine on both platforms, and I took that on end to end despite being a web and TypeScript engineer by background.
Action
On iOS I wrote roughly 1,950 lines of new Swift: an AVFoundation-based player with an audio source model, exposed to the web layer through a Capacitor plugin and a shared TypeScript interface. On Android I built the native player and then migrated it to Jetpack Media3, Google’s ExoPlayer-based media framework, which gave us the media session and lock screen integration for free. I still tested the simpler path: in early May I trialled the Capacitor audio plugin as an alternative, found it did not work the same way as our engine, and abandoned it rather than force it to fit. From single-track playback I grew the engine into a queue player, then added background queue playback, lock screen controls on both platforms, offline file playback, a sleep timer, and progress sync. iOS fought the hardest, mostly on lock screen integration where the documentation is obscure and the simulator behaves differently from a physical device, so I did a lot of testing on real hardware. Once the design had settled I did a deliberate simplification pass over the player code, then spent the last two weeks fixing the remaining playback issues, including one affecting specific episodes.
Result
The new player fixed the crashes the old web player had. It kept playing with the app backgrounded and supported playlists, neither of which worked before. The clients were very happy with the result.