Article: Beat-Aligned Mobile Audio Streaming with Virtual Chunks and Native Playback Colossal, before pivoting to agentic commerce, built a mobile beat-discovery app that required beat-aligned, artifact-free audio switching under mobile constraints. The system stores one encoded MP3 per beat, fetches prioritized byte ranges, and schedules changes inside a native C++ playback engine, avoiding full-file preloading and standard player limitations. Key Takeaways - Interactive audio apps often require a different streaming model than linear media playback, especially when users frequently jump between sections or tracks. - Beat-aligned switching is a scheduling problem as much as a buffering problem, so timing-critical decisions need to be made within the native playback execution path. - Deterministic prefetching based on a user's likely next actions is a practical first step before introducing more complex behavior-driven predictive models. - Virtual chunks let a system store one encoded file and fetch only prioritized byte ranges. - Selective MP3 decoding needs codec-boundary handling; otherwise, starting playback at arbitrary chunks can produce audible artifacts even when byte ranges are correct. At Colossal, before the company pivoted to agentic commerce, we were building a mobile beat-discovery app. It allowed producers to upload beats, and artists to swipe through a personalized feed ranked by a machine learning ML recommender using real-time in-session signals such as skip rate, listening time, repeated section locks, and replays. The core interaction model of the mobile app was to: - Play the current track from the start. - Jump to the previous or next section within the current track. - Lock a section, then keep that same section active while swiping through different tracks. That interaction model combined personalized feed navigation with hard audio constraints. Each swipe or section jump had to trigger an immediate, beat-aligned, artifact-free track or section switch despite mobile latency, network jitter, and bandwidth limits. The final design avoided full-file preloading by storing one encoded MP3 per beat, generating compact chunk descriptors, fetching only prioritized byte ranges, decoding chunks with MP3 warm-up context, and scheduling track or section changes inside a native audio control loop. For this project, I was responsible for the system end to end: backend audio analysis and chunk-metadata generation, binary descriptor format, mobile range-streaming strategy, and a native C++ playback engine integrated with React Native on iOS and Android. Figure 1: Beat discovery with section locks. Image Source: created by the author. Design Constraints For the system to work, all of these constraints had to be simultaneously held: - Section switches had to be immediate, seamless, and artifact-free. - Section loops had to avoid clicks or gaps at loop boundaries. - Track transitions had to be bar-aligned to preserve rhythmic continuity. - Playback had to stay seamless despite users spending only two to three seconds per beat and feed order changing in real time. - The implementation had to remain usable on weak 3G-class mobile connections. The difficult part was not any single requirement. It was making codec handling, transport, buffering, scheduling, and native playback behave as one system under mobile constraints. Why Existing Approaches Fell Short Before building a custom system, I evaluated three baseline approaches against the product's playback model. Standard Audio Players At first, I investigated whether standard audio player libraries could support the required playback model. While they were well-suited for basic linear playback, they were not designed for immediate, beat-aligned section and track switching. They also did not expose the low-level playback control or targeted prebuffering capabilities this use case required. The React Native RN bridge architecture introduced an additional limitation. A track switch could not reliably occur at the required moment because pausing the current player and starting the next one still had to cross the RN bridge. In my tests, this track switch added roughly five to ten milliseconds of delay plus jitter. By the time progress callbacks reached the JavaScript layer, playback had already advanced, making precise timing difficult to achieve. Figure 2: Delays in standard players flow. Image Source: created by the author. HLS/DASH I also evaluated HTTP Live Streaming HLS and Dynamic Adaptive Streaming over HTTP DASH , two widely used streaming protocols that deliver media as a sequence of small segments. These protocols are optimized for sequential playback, adaptive bitrate switching, and efficient streaming over varying network conditions. However, they were not designed for the kind of rapid, non-linear section hopping required by this app. Another challenge was that MP3 decoding depends on context from preceding frames. If playback starts at a physical segment boundary without that context, the first decoded samples can be incorrect, producing audible artifacts. While crossfades can mask some of these artifacts, they do not restore the missing decode context and were not acceptable for the interaction model we wanted. Another limitation was buffering behavior. Users could jump to a different section at any time, and if the target section was not already buffered, playback had to pause while the segment was fetched and decoded. That pausing introduced latency at the moment when an immediate response was required. Figure 3: HLS/DASH vs. MP3 frame decoding. Image Source: created by the author. Full File Downloads I also considered fully downloading each audio file before playback. However, the bandwidth requirements were far beyond the target operating conditions. The average beat size was about three megabytes, while users typically spent only two to three seconds on a beat before swiping away. Fully downloading the next beat within that window would require roughly eight megabits per second of sustained throughput for audio data alone. Once decoding overhead, network variability, and bandwidth shared with other app assets were included, the practical requirement was closer to fourteen megabits per second. Because the app needed to work reliably at around five hundred kilobits per second , even the optimistic estimate exceeded the available bandwidth by roughly sixteen times, making full-file downloads unviable. This evaluation made clear that meeting the product constraints required a custom transport model and a native playback engine. High-Level Architecture At a high level, the system had six stages: upload, server-side processing, descriptor generation, storage, mobile range fetching, and native playback. Figure 4: Architecture overview. Image Source: created by the author. Virtual Chunking When a producer uploaded a beat, a backend worker processed and analyzed it. One key design decision was how to support selective loading on mobile. There were two ways to chunk the file: - When using physical segments, HLS-style, each track becomes many small objects, which increases request count, metadata overhead, cache fragmentation, and boundary-handling complexity at segment edges. - When using virtual chunks, keep one MP3 file in storage, store byte ranges for each chunk, and fetch only the needed ranges with HTTP Range requests. I chose virtual chunks because they gave precise control over which bytes were fetched next, without the storage and request overhead of managing many physical segment files. Figure 5: Virtual chunks visualization. Image Source: created by the author. MP3 and CBR for v1 I evaluated MP3 and Advanced Audio Coding AAC for this pipeline and chose MP3 for v1 because frame-level inspection and chunk planning were simpler in the processing pipeline, decoder behavior was predictable across the target devices and libraries, and it let me move faster within the v1 delivery timeline. I also forced constant bitrate CBR during audio processing. Keeping most chunks close to the same size enabled a simple pool of preallocated buffers in the player, which reduced allocation complexity and made buffer reuse simpler at runtime. Chunk-Size Trade-Off The playback engine imposed a practical lower bound of roughly two seconds of audio per chunk. Smaller chunks were possible, but they did not make sense in this implementation because the player could not use less than that size, and splitting further only added requests. Smaller chunks improved responsiveness but increased request overhead. Larger chunks reduced request pressure, but on low-bandwidth connections they consumed too much throughput on audio the user might skip, which delayed chunks that actually needed to load next. Around 3.5 seconds was the best balance for this interaction model. MP3 Boundary Handling MP3 frames are not always independently decodable. Because of bit-reservoir behavior https://en.wikipedia.org/wiki/MP3 , the first valid samples of a chunk can depend on data from earlier frames. That idiosyncrasy made it harder to decode a chunk in isolation to stitch it into the playback buffer because it had to reproduce the same PCM output we would get at that point from continuous full-file decoding. To preserve that continuity in my decoder path, I prepended nine overlap frames to every non-initial chunk during chunk planning. Then I decoded with that warm-up context and discarded the warm-up samples before writing PCM into the playback buffer. If I skipped that overlap warm-up, chunk starts produced audible artifacts on real devices. The nine-frame value was based on MP3 decoder warm-up behavior and validated against the target decoder path. If this system had supported multiple codec implementations, I would have treated this situation as a per-decoder compatibility rule rather than a universal constant. Descriptor Format The mobile client had to fetch and parse chunk metadata quickly on weak connections, so I treated the descriptor as part of the transport design. I first used JSON for inspection, then added a compact binary format optimized for fast mobile parsing and low overhead. Each track had two descriptors: a JSON version for debugging and inspection, and a compact binary descriptor containing the chunk metadata required by the mobile client. Each chunk was described by this structure: @dataclass class ChunkData: start frame: int frames: int start byte: int bytes: int Track position was derived from frame index: position ms = frame index samples per frame 1000 / sample rate That mapping lets the client resolve a section timestamp to the correct chunk index. With a ten-minute max track length as a v1 product constraint, each chunk record fit into twelve bytes: - start frame: uint16 - frames: uint16 - start byte: uint32 - bytes: uint32 f.write struct.pack "