CodiotFree estimate
Mobile development

Building a production Android media player with Media3

Raj Parmar··8 min read

Building a production Android media player means adopting Media3, the current media stack that now contains ExoPlayer, and then solving the problems the getting-started guide skips: audio focus, MediaSession and notification wiring, device-specific codec behaviour, picture-in-picture, and background playback. Media3 gives you a working player in an afternoon. Making it behave like a real app, one that survives a phone call mid-song and a headphone unplug, is where the weeks go. This is written from having just shipped one.

What Media3 actually replaced, and why ExoPlayer knowledge still matters

Media3 is the library that consolidated ExoPlayer and Android's older framework media APIs into one place, so ExoPlayer did not disappear, it moved. If you learned media on Android before Media3, that knowledge still applies; the concepts are the same and the ExoPlayer implementation is now a module you use through the Media3 Player interface.

The practical effect is mostly about imports and mental model. Older articles, sample code, and Stack Overflow answers written for standalone ExoPlayer are still largely correct, but the package names have changed, so you spend the first day translating rather than relearning. The upside of the consolidation is real: one library, one set of components for the player, the session, and the UI, instead of stitching together ExoPlayer and the legacy media framework yourself. The thing to internalise early is that Media3 is not a new playback engine to learn from scratch; it is the same engine with a tidier surface, which means the hard-won ExoPlayer wisdom about buffering, track selection, and adaptive streaming is still exactly what you want.

The architecture decisions that matter before the first line of code

Decide three things before you write any playback code: where the player object lives, how playback state is kept separate from the UI, and whether background playback is a real requirement, because retrofitting any of these later is closer to a rewrite than a refactor. These are architecture choices, not implementation details, and they are the ones teams get wrong by starting with a player embedded in an activity and discovering its limits too late.

The first decision is player lifecycle versus UI lifecycle. A player tied to an activity dies when the activity does, which is fine for a video that should stop when you leave the screen and wrong for audio that should keep going. The second is service versus in-activity: a player that must run in the background, survive configuration changes, or be controlled from a notification belongs in a media session service, not in a screen. The third follows from the first two: if background playback is in scope at all, design for it from the start, because the session, the notification, and the service that hosts them are the backbone everything else hangs off. Getting these three right up front is the difference between adding features smoothly and repeatedly fighting the framework. We treat this decision as the actual start of the project, ahead of any UI.

The problems that eat your weeks

The time sinks in a real media player are almost never playback itself; they are the surrounding behaviours that users notice only when they break. Five recur: audio focus, MediaSession and notification wiring, fragmented device codec behaviour, picture-in-picture, and the gap between local and streaming playback.

Audio focus is the etiquette layer: your player has to duck or pause when a call comes in, a navigation prompt speaks, or another app takes over, and resume correctly afterward. Getting this wrong is the most common way a media app feels broken, and it is invisible until you test with real interruptions. MediaSession and notification wiring is what connects your player to the rest of the system, the lock screen, the notification controls, Bluetooth buttons, and Android Auto; it is fiddly, stateful, and easy to get subtly wrong so that the notification and the player disagree. Device codec fragmentation is the tax of the Android ecosystem: the same file plays perfectly on one manufacturer's phone and stutters or refuses on another, and you only find out by testing on real hardware. Picture-in-picture looks like a small feature and is a whole lifecycle of its own, with its own transitions to handle. And local versus streaming playback are different problems wearing the same interface: buffering, adaptive quality, and error recovery matter enormously for streams and barely at all for local files. Budgeting for these five, rather than for "make it play," is how you avoid the schedule surprise.

How do you test a media player?

You test a media player against interruptions and device variety, not just against "does it play," because the failures that reach users live in the events around playback rather than in playback itself. A player that plays a file cleanly on your desk can still fail the moment a call arrives, a headset is unplugged, the app goes to the background, or it lands on a device whose codecs behave differently.

Concretely, that means a test pass built around real-world disruption: incoming calls and notifications mid-playback, wired and Bluetooth headset connects and disconnects, background and foreground transitions, screen locking and waking, low-memory conditions, and a spread of real devices rather than one emulator. It also means checking that the notification, the session state, and the actual playback never disagree, because that desync is where "it worked on my phone" bugs come from. This is the same discipline our QA team applies to anything with hidden states, and it is worth reading how they think about it in how we test software before you ever see a bug. For a media player, testing is not a final gate; it is the only way to find the class of problems that are structurally invisible to the person who wrote the happy path.

Should you build a custom player or embed a ready one?

Embed a ready-made player when playback is incidental to your product, and build a custom Media3 player when playback is the product or when you need control the packaged options will not give you. This is a genuine fork, not a default, and the honest answer for many apps is to embed.

If your app shows the occasional video or plays a sound and the experience does not need to be special, a ready player or a light wrapper over Media3's default UI is the right call, because you get a maintained, correct player without owning its complexity. You build custom when playback is central, when you need bespoke controls, unusual formats, tight integration with your own background and notification behaviour, or a look that the default UI cannot produce. The table below frames the trade honestly.

FactorEmbed a ready playerBuild custom on Media3
Control over UI and behaviourLimited to what it exposesFull
Time to first working versionFastestSlower, front-loaded architecture
Ongoing maintenanceMostly the library'sYours, including device quirks
Best fitPlayback is a featurePlayback is the product

Neither answer is more sophisticated than the other; forcing a custom build when an embed would do is how teams spend weeks on something a user never notices.

What we would do differently next time

Next time we start from the service-and-MediaSession architecture, budget device testing from week one, and treat audio focus and notifications as first-class features rather than end-of-project polish. Every week we lost, we lost by treating a structural concern as a detail.

The specific lessons are simple to state and easy to skip under deadline pressure. Design the session and background service first, so the UI hangs off a stable core instead of the reverse. Get onto a range of real devices early, because codec and manufacturer differences are not edge cases on Android, they are the terrain. And write the interruption handling, audio focus, and notification sync while the architecture is fresh, not bolted on at the end when every change risks the parts that already work. None of this is exotic; it is the difference between a player that demos well and one that survives contact with real phones and real users. If you are weighing a build like this, our Android development work covers the stack, hiring dedicated Android developers is the fastest way to add the capability, and a scoping conversation is the honest way to size the interruption handling and device coverage before you commit.

FAQ

How long does it take to build a custom Android media player?
The honest answer is that a basic player is quick and a production player is not, so the range is wide and scope-driven. Getting audio or video playing with Media3 is an afternoon. Turning that into something that survives phone calls, headphone unplugs, background playback, notifications, and a dozen manufacturers' codec quirks is where the real time goes, and it depends on how many of those you need. The mistake is quoting the afternoon and discovering the weeks later. Scope the interruption handling, background behaviour, and device coverage up front, because those, not playback itself, set the timeline.
Media3 vs ExoPlayer, what is the difference?
Media3 is the current library and ExoPlayer is now a module inside it, so it is less a versus than a rename-and-consolidation. Google folded ExoPlayer and the older framework media APIs into one Media3 library with consistent packages, which means years of accumulated ExoPlayer knowledge still applies, just under new import paths. Practically, you depend on Media3, use its ExoPlayer implementation of the Player interface, and treat older ExoPlayer articles as still-relevant with translated package names. New work should start on Media3; there is no reason to begin on the standalone ExoPlayer today.
Does Media3 support DRM?
Yes, Media3 supports protected content through the same DRM mechanisms Android provides, so premium or licensed media is feasible. The library integrates with the platform's content-protection stack for the common schemes, and the ExoPlayer module has long handled DRM sessions and key exchange. The real work is not whether it is supported but the surrounding setup: license servers, secure surfaces, and the device and OS constraints on protected playback, which vary and need testing. Treat DRM as an integration and compliance effort around a supported capability, not as a checkbox.
Can a Media3 player handle offline playback?
Yes, Media3 supports downloading and playing media offline, which is a common requirement for apps used in poor-connectivity conditions. It provides the pieces for downloading, storing, and playing back local media, so a player can serve content without a live connection once it is cached. Designing for offline is the same discipline we apply to field software generally, where the network is assumed to fail; our write-up on [offline-first apps for places with no signal](/blog/offline-first-apps-for-farms-with-no-signal) covers that mindset. For a media player it means deciding early what is cached, when, and how storage and licences are managed.
Start

Got an idea? Let's build it.

Tell us what you're making. We'll reply within two business days with an honest take on scope, timeline, and cost.

Get a free estimate