Search for ExoPlayer and the top result is a GitHub repo with a deprecation notice on it. No wonder so many developers arrive unsure whether the library is dead.
It isn’t. ExoPlayer is still the media player inside YouTube, and it’s still what most serious Android video apps run on.
It just moved house. The code now lives in AndroidX Media3 under a new package name, and the old standalone project stopped taking commits in April 2024.
That single move explains almost every outdated tutorial you’ll find, and it’s where most migration pain comes from.
What Is ExoPlayer?
ExoPlayer is an open-source media player library for Android that plays audio and video from local files or network streams, with support for adaptive streaming protocols the platform’s built-in player can’t handle.
It’s the default implementation of the Player interface in Jetpack Media3. Google builds it, YouTube uses it, and it ships as an app dependency rather than as part of the operating system.
That last detail matters more than it sounds. Android’s built-in MediaPlayer is baked into the OS, so its behavior shifts across devices and Android versions, and you can’t patch a bug on a user’s phone.
ExoPlayer travels with your APK. Fix a playback bug, ship an app update, done.
Here’s how the two compare:
| ExoPlayer (Media3) | Android MediaPlayer | |
|---|---|---|
| Ships with | Your app | The operating system |
| Bug fixes | App update | OS update (or never) |
| Adaptive streaming | HLS, DASH, SmoothStreaming | Limited HLS, partial DASH |
| Customization | Every component is swappable | Nearly none |
| DRM | Widevine, PlayReady, ClearKey | Widevine only, device-dependent |
| Playlists | Built in, with gapless playback | Manual |
| APK size cost | ~1 to 3 MB | Zero |
| Behavior across devices | Consistent | Varies by vendor |
The tradeoff is size and complexity. You’re adding a library where the platform already hands you something free.
For a single MP4 on an onboarding screen, that’s a bad deal. For live streams, adaptive bitrates, DRM, or precise playback control, it’s the only sane choice.
ExoPlayer vs Media3: Why the Library Moved
ExoPlayer didn’t get replaced. It got relocated.
The library used to ship standalone as com.google.android.exoplayer2. In 2023 Google folded it into Jetpack Media3, an AndroidX library that unifies media playback, media sessions, transformation, and UI under one set of packages. Same player, new name: androidx.media3.exoplayer.
The old project is finished. Version 2.19.0 marked the deprecation, ExoPlayer 2.19.1 was the final artifact, and the team stopped pushing commits to the dev-v2 branch on April 3, 2024.
No security patches. No new codec support. No bug fixes.
| Old ExoPlayer | Media3 ExoPlayer | |
|---|---|---|
| Package | com.google.android.exoplayer2 |
androidx.media3.exoplayer |
| Gradle group | com.google.android.exoplayer |
androidx.media3 |
| Last version | 2.19.1 (2023) | 1.11.0 (August 2026) |
| Status | Deprecated, no commits since April 2024 | Actively developed |
| Minimum SDK | 16 | 23 (Android 6.0), since Media3 1.9.0 |
| Repository | google/ExoPlayer | androidx/media |
Two things catch teams off guard during migration.
The version number went backwards. You upgrade from ExoPlayer 2.19.1 to Media3 1.x and it looks like a downgrade. It isn’t. Media3 restarted versioning at 1.0 as a new library.
And the minimum SDK jumped. Media3 1.9.0 raised minSdk to 23, matching the rest of AndroidX. If you’re still supporting Android 5.0, you’re pinned to Media3 1.8.1 or older.
For the migration itself, Google publishes a shell script that rewrites package names across Gradle files, Java, Kotlin, and XML layouts. It handles the mechanical renaming. Anything where you subclassed an ExoPlayer internal still needs a human.
How Does ExoPlayer Work?
ExoPlayer isn’t one component. It’s a pipeline of swappable pieces that move bytes from a URL to pixels on a screen.
Know those pieces and you graduate from “I got a video to play” to “I can debug why this stream stalls.”
The core components
ExoPlayeris the top-level object that holds playback state and coordinates everything below it. You build it withExoPlayer.Builder(context).build().MediaSourceknows how to load a particular kind of media.ProgressiveMediaSourcehandles a plain MP4,HlsMediaSourcehandles an HLS playlist,DashMediaSourcehandles a DASH manifest.DataSourcedoes the actual byte fetching over HTTP, from disk, or from a cache.Extractordemuxes a container into separate audio, video, and text sample streams.TrackSelectorpicks which tracks play, including which quality rendition to use at any moment. This is where adaptive bitrate logic lives.LoadControldecides when to buffer more data and when to start playing.Rendererdecodes samples and pushes them to output.MediaCodecVideoRendererdrives the hardware decoder to aSurface,MediaCodecAudioRendererdrives anAudioTrack.PlayerViewis the UI layer: the surface, the playback controls, the subtitle overlay.
Every one of those is an interface with a default implementation you can replace.
The playback pipeline, step by step
- You set a media item.
player.setMediaItem(MediaItem.fromUri(url))tells the player what to play, and aMediaSource.Factoryinspects the URI to pick the rightMediaSource. - The manifest loads. For adaptive streams, ExoPlayer fetches the HLS playlist or DASH manifest first and reads what renditions exist, what codecs they use, and how long the segments are.
LoadControlrequests data. The player starts pulling segments through theDataSourcechain, filling buffers ahead of the playhead.Extractordemuxes. Each downloaded chunk gets parsed into audio, video, and subtitle samples, which land in per-track sample queues.TrackSelectorchooses. Based on measured bandwidth, buffer health, and device capability, it picks the rendition to keep pulling. This runs continuously, not once.- Renderers decode. Samples feed into
MediaCodec, which hands the work to the device’s hardware decoder wherever possible. - Output syncs. Decoded video frames go to the
Surfaceand audio goes to theAudioTrack, timed against the player’s internal clock so lips match voices.
This architecture matters because it localizes bugs. When a stream misbehaves, the fix almost always sits in one specific component.
Stalling on a slow network is a LoadControl and TrackSelector problem. Audio playing over a black screen is a Renderer and codec problem. A stream that won’t start at all is usually a DataSource or manifest problem.
The pipeline turns a vague bug report into a short list of suspects.
What Formats Does ExoPlayer Support?
ExoPlayer’s format coverage is the widest of any Android player, partly because it falls back on the device’s own decoders and partly because it ships optional software decoders for the gaps.
Adaptive streaming protocols
| Protocol | Containers | Content protection | Live |
|---|---|---|---|
| HLS | MPEG-TS, fMP4/CMAF, ADTS, MP3 | AES-128, Widevine, PlayReady SL2000 | Yes, including low-latency HLS |
| MPEG-DASH | fMP4, WebM, Matroska | Widevine, PlayReady SL2000, ClearKey | Yes, including ultra low-latency CMAF |
| SmoothStreaming | fMP4 | PlayReady SL2000 (Android TV) | Yes |
| RTSP | H.264, AAC, AC-3 payloads | None | Yes, RTP over UDP or TCP |
HLS and DASH both work best with fragmented MP4 segments, and if you package once as CMAF you can serve both protocols from a single set of files.
Video codecs
ExoPlayer uses the device’s hardware decoders by default and ships software extensions for the rest.
| Codec | Support |
|---|---|
| H.264 (AVC) | Hardware, universal |
| H.265 (HEVC) | Hardware on most devices since Android 5.0 |
| VP9 | Hardware on many devices, software extension available |
| AV1 | Hardware on newer chipsets, software extension available |
| Dolby Vision | Device-dependent |
| HDR10+ | In Matroska and WebM |
H.264 remains the safe default for reach. If you’re weighing H.265 against H.264 for bandwidth savings, ExoPlayer will play either, so the decision comes down to encoding cost and licensing rather than playback support. For HDR content, check the specific device’s capabilities at runtime instead of assuming.
Audio codecs
AAC, HE-AAC, AAC-ELD, MP3, FLAC, Opus, Vorbis, AMR-NB, AMR-WB, AC-3, and E-AC-3 all decode through platform decoders. Optional extensions add FFmpeg (which brings DTS, TrueHD, ALAC, and PCM variants), IAMF, and MPEG-H.
Containers and subtitles
Progressive containers: MP4, M4A, fMP4, WebM, Matroska, MP3, Ogg, WAV, MPEG-TS, MPEG-PS, FLV, ADTS, FLAC, and AMR. A few of those, including MP3, ADTS, and AMR, only support constant bitrate seeking.
Subtitle formats: WebVTT, TTML, SMPTE-TT, SubRip, SubStationAlpha, plus CEA-608 and CEA-708 for embedded broadcast captions. The full matrix lives in the Android format documentation.
Advantages of ExoPlayer
It updates with your app
Ship a fix in your next release instead of waiting for device manufacturers. This is the single biggest practical difference from MediaPlayer, and it’s why every large Android video app made the switch years ago.
Adaptive streaming actually works
Built-in HLS, DASH, and SmoothStreaming support with real adaptive bitrate streaming logic. The TrackSelector measures throughput, watches buffer health, and switches renditions mid-playback. You can tune the thresholds or write your own selection algorithm.
Behavior is consistent across devices
Because the player logic lives in your APK rather than in vendor firmware, a stream that works on a Pixel behaves the same way on a five-year-old budget phone with the same decoder capabilities. Device fragmentation still bites at the hardware decoder layer, but everything above it is yours.
Every component is replaceable
Custom DataSource to add request signing. Custom LoadControl to buffer aggressively on Wi-Fi and conservatively on cellular. Custom TrackSelector to cap resolution on metered connections. The extension points are interfaces, not hacks.
Playlists and gapless playback come free
player.setMediaItems(list) gives you queue management, transitions, and gapless audio playback without writing state machines. Clipping and merging media items is built in too.
The instrumentation is genuinely good
AnalyticsListener exposes buffering events, dropped frames, bandwidth estimates, decoder initialization, and playback errors with enough detail to build real quality-of-experience dashboards. Most teams wire it straight into their analytics pipeline.
Disadvantages of ExoPlayer
It adds meaningful APK size
Expect roughly 1 to 3 MB depending on which modules you include, and more if you add software decoder extensions. R8 shrinking helps, but you can’t get to zero. Include only the modules you use: pulling in media3-exoplayer-dash when you only serve HLS is wasted bytes.
The learning curve is real
MediaPlayer has maybe six methods worth knowing. ExoPlayer has a component graph, a threading model, and a lifecycle you have to respect.
Getting a video playing takes fifteen minutes. Getting it to behave across rotation, backgrounding, audio focus changes, and network transitions takes considerably longer.
Lifecycle management is on you
Forget to call player.release() and you leak the codec, the surface, and the audio session. Do it in the wrong lifecycle callback and playback dies when the user rotates the screen. This is the most common source of ExoPlayer bugs in production apps.
Android only
There’s no iOS build. Cross-platform teams end up pairing ExoPlayer with AVPlayer on iOS and something like hls.js or Shaka Player on web, which means three playback implementations and three sets of quirks. Wrappers exist for React Native and Flutter, but they’re thin layers over ExoPlayer that expose a fraction of the API.
Hardware decoder fragmentation still exists
ExoPlayer normalizes the software side, not the silicon. A device that reports AV1 support and then drops frames at 1080p is still a device you have to detect and work around. MediaCodecInfo capability checks and a fallback rendition ladder remain necessary.
The architecture reads heavier than the setup actually is. Here’s the code.
How to Add ExoPlayer to an Android App
Step 1: Add the Gradle dependencies
Every Media3 module has to use the same version number. Mixing versions produces confusing runtime crashes.
dependencies {
implementation("androidx.media3:media3-exoplayer:1.11.0")
implementation("androidx.media3:media3-ui:1.11.0")
// Add only the protocols you actually serve
implementation("androidx.media3:media3-exoplayer-hls:1.11.0")
implementation("androidx.media3:media3-exoplayer-dash:1.11.0")
}
Step 2: Request network permission
<uses-permission android:name="android.permission.INTERNET" />
Step 3: Add PlayerView to your layout
<androidx.media3.ui.PlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:keepScreenOn="true"
app:resize_mode="fit"
app:show_buffering="when_playing" />
keepScreenOn stops the display sleeping mid-video. show_buffering gives users a spinner instead of a frozen frame.
Step 4: Build the player and start playback
class PlayerActivity : AppCompatActivity() {
private var player: ExoPlayer? = null
private lateinit var playerView: PlayerView
private fun initializePlayer() {
player = ExoPlayer.Builder(this)
.build()
.also { exoPlayer ->
playerView.player = exoPlayer
val mediaItem = MediaItem.fromUri(
"https://your-stream-host.example.com/live/stream.m3u8"
)
exoPlayer.setMediaItem(mediaItem)
exoPlayer.playWhenReady = true
exoPlayer.prepare()
}
}
}
That’s the whole playback path. ExoPlayer reads the .m3u8 extension, picks HlsMediaSource, fetches the playlist, and starts adaptive playback on its own.
Step 5: Handle the lifecycle properly
This is the step people skip, and it’s the one that causes leaked codecs and dead playback after rotation.
override fun onStart() {
super.onStart()
if (Build.VERSION.SDK_INT > 23) initializePlayer()
}
override fun onResume() {
super.onResume()
if (Build.VERSION.SDK_INT <= 23 || player == null) initializePlayer()
}
override fun onPause() {
super.onPause()
if (Build.VERSION.SDK_INT <= 23) releasePlayer()
}
override fun onStop() {
super.onStop()
if (Build.VERSION.SDK_INT > 23) releasePlayer()
}
private fun releasePlayer() {
player?.let { exoPlayer ->
playbackPosition = exoPlayer.currentPosition
playWhenReady = exoPlayer.playWhenReady
exoPlayer.release()
}
player = null
}
Save the position before releasing and restore it on the next initializePlayer() call. Users notice when a rotation restarts the video.
The URL in Step 4 is the hard part.
ExoPlayer plays an HLS or DASH stream, but something has to produce that stream: ingest RTMP or SRT from a broadcaster, transcode it into a rendition ladder, package it into segments, and serve it from a CDN. A streaming API like LiveAPI returns a ready HLS URL you can drop straight into MediaItem.fromUri(), which is why most teams pair a client player with a hosted backend rather than building both.
ExoPlayer Features Worth Configuring in Production
The defaults get you playing. These get you shipping.
Tune the buffering policy
DefaultLoadControl buffers 50 seconds by default, which is generous for VOD and terrible for live. Cut it down for live streams:
val loadControl = DefaultLoadControl.Builder()
.setBufferDurationsMs(
/* minBufferMs = */ 2_000,
/* maxBufferMs = */ 8_000,
/* bufferForPlaybackMs = */ 1_000,
/* bufferForPlaybackAfterRebufferMs = */ 2_000
)
.build()
val player = ExoPlayer.Builder(context)
.setLoadControl(loadControl)
.build()
Shorter buffers cut startup time and latency but make rebuffering more likely on unstable connections. Test against real network conditions, not office Wi-Fi.
Constrain track selection
Cap resolution on cellular, or lock to a maximum bitrate to control data costs:
player.trackSelectionParameters = player.trackSelectionParameters
.buildUpon()
.setMaxVideoSize(1280, 720)
.setMaxVideoBitrate(2_500_000)
.build()
Add DRM for protected content
ExoPlayer handles Widevine natively, which covers Android. Apple devices need FairPlay instead, so multi-platform catalogs typically license both.
val mediaItem = MediaItem.Builder()
.setUri(streamUrl)
.setDrmConfiguration(
MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
.setLicenseUri(licenseServerUrl)
.build()
)
.build()
Widevine has three security levels. L1 does decryption in the device’s trusted execution environment and is what studios require for HD and 4K playback. L3 runs in software and most studios cap it at 480p or 720p. Check the level at runtime before promising a resolution.
Cache segments for offline and repeat views
SimpleCache backed by a CacheDataSource.Factory stores downloaded segments on disk. Pair it with DownloadManager for full offline downloads with progress tracking and background continuation.
Cut latency on live streams
For low-latency HLS, ExoPlayer reads the #EXT-X-SERVER-CONTROL and partial segment tags automatically. You control how aggressively it chases the live edge:
val mediaItem = MediaItem.Builder()
.setUri(liveStreamUrl)
.setLiveConfiguration(
MediaItem.LiveConfiguration.Builder()
.setTargetOffsetMs(3_000)
.setMinPlaybackSpeed(0.97f)
.setMaxPlaybackSpeed(1.03f)
.build()
)
.build()
The speed range lets the player subtly adjust playback rate to hold its target offset instead of pausing or skipping.
Wire up captions and ads
Sideloaded captions and subtitles attach through MediaItem.SubtitleConfiguration, and embedded CEA-608 and CEA-708 tracks show up in the track selector automatically. For monetization, the media3-exoplayer-ima module handles client-side ads through Google IMA, while server-side ad insertion stitches ads into the manifest before ExoPlayer ever sees them, which sidesteps ad blockers entirely.
ExoPlayer vs VLC and Other Android Players
| ExoPlayer (Media3) | libVLC for Android | Android MediaPlayer | |
|---|---|---|---|
| Best for | Streaming apps, OTT, VOD | Playing any file a user throws at it | Simple local playback |
| Adaptive streaming | Excellent | Basic | Poor |
| Format breadth | Wide, tied to device decoders | Widest, bundles its own decoders | Narrow |
| DRM | Widevine, PlayReady, ClearKey | Limited | Widevine only |
| APK size | 1 to 3 MB | 20 to 40 MB | 0 |
| License | Apache 2.0 | LGPL / GPL | Platform |
| Maintained by | VideoLAN | Google (platform) |
The split is straightforward. libVLC bundles its own decoders, so it plays obscure formats ExoPlayer refuses, which is why media center and IPTV apps favor it. That breadth costs you 20 to 40 MB and an LGPL obligation.
ExoPlayer wins for anything that streams commercial content: better adaptive logic, proper DRM, smaller footprint, and a permissive license.
What ExoPlayer Doesn’t Handle: The Server Side
ExoPlayer is a client. It plays streams. It doesn’t make them.
Ship a video feature and the player is maybe 20% of the work. The rest sits on the server:
- Ingest. Accepting RTMP or SRT from encoders, mobile broadcasters, or IP cameras.
- Transcoding. Turning one input into a rendition ladder so adaptive selection has something to choose between. One 1080p source needs 4 or 5 renditions to serve users on bad connections.
- Packaging. Segmenting into HLS or DASH with correct manifests, keyframe alignment, and byte-range indexes.
- Delivery. Global CDN distribution, because a stream served from one region buffers everywhere else.
- Recording. Turning live to VOD so viewers can watch after the broadcast ends.
- Storage, access control, and analytics. Signed URLs, geo restrictions, viewer metrics.
Building that stack is months of infrastructure work plus a permanent operations burden.
LiveAPI handles the whole chain behind an API: RTMP and SRT ingest, instant encoding into adaptive renditions up to 4K, HLS output, delivery across Akamai, Cloudflare, and Fastly, and automatic live-to-VOD recording. Your Android app calls the API, gets back an HLS URL, and hands it to ExoPlayer.
That’s the practical division of labor. ExoPlayer owns playback on the device. A streaming API owns everything upstream of the URL.
Is ExoPlayer Right for Your App?
Use ExoPlayer if you’re doing any of these:
- Playing live streams or adaptive HLS and DASH content
- Distributing DRM-protected content
- Building an OTT, video-on-demand, or Android TV app
- Needing consistent playback behavior across the device landscape
- Tracking playback quality metrics in production
- Supporting offline downloads or background audio
Stick with MediaPlayer or a lighter option if:
- You play short local MP4 or MP3 files and nothing else
- APK size is tightly constrained and video is a minor feature
- You need one line of code and zero configuration
For most apps where video is a core feature rather than a decoration, ExoPlayer is the right answer. The APK cost buys you control, and the control is what you need the first time a stream breaks on a specific device in a specific market.
ExoPlayer FAQ
Is ExoPlayer deprecated?
The standalone com.google.android.exoplayer2 library is deprecated. ExoPlayer itself is not. The code moved to AndroidX Media3 as androidx.media3.exoplayer and is actively developed, with 1.11.0 released in August 2026. Only the old GitHub project and package name are dead.
What is ExoPlayer used for?
Playing audio and video in Android apps, especially streaming content. It handles adaptive protocols like HLS and DASH, DRM-protected playback, playlists, offline downloads, live streams, and background audio. YouTube and most major Android streaming apps run on it.
What’s the difference between ExoPlayer and Media3?
Media3 is the AndroidX library that contains ExoPlayer. ExoPlayer is the player implementation inside it. Media3 also includes media sessions, UI components, and a transformation library. In practice “Media3 ExoPlayer” and “ExoPlayer” refer to the same player.
Is ExoPlayer better than VLC?
For streaming apps, yes. ExoPlayer has stronger adaptive bitrate logic, proper DRM support, a much smaller footprint, and a permissive Apache 2.0 license. libVLC plays a wider set of exotic file formats because it bundles its own decoders, which makes it a better fit for general-purpose media player apps.
What is the latest ExoPlayer version?
AndroidX Media3 1.11.0, released in August 2026. The last standalone ExoPlayer release was 2.19.1, and the version numbering restarted at 1.0 when the library moved to Media3, so a “lower” number is still newer.
What minimum SDK does ExoPlayer require?
Media3 1.9.0 and later require API 23 (Android 6.0). Media3 1.8.1 and earlier support API 21. The old ExoPlayer 2.19.1 supported API 16.
Does ExoPlayer support RTMP?
Yes, through the optional media3-datasource-rtmp module, though RTMP playback is uncommon in modern apps. RTMP is typically used for ingest from encoder to server, with HLS or DASH delivered to viewers. ExoPlayer also supports RTSP natively for IP camera feeds.
Can ExoPlayer play 4K and HDR video?
Yes, subject to the device’s hardware decoder. ExoPlayer supports Dolby Vision and HDR10+, and exposes device capabilities through MediaCodecInfo so you can check before selecting a rendition. On DRM content, 4K generally requires Widevine L1.
Getting Started with ExoPlayer
ExoPlayer is the default choice for video playback on Android, and the Media3 move didn’t change that. It changed the package name, the versioning, and the minimum SDK. If you’re starting a new project, add androidx.media3:media3-exoplayer and skip the old library entirely.
Once playback works, the harder problem is upstream: producing the adaptive streams ExoPlayer expects, encoding them fast enough to be useful, and delivering them worldwide without buffering.
Try LiveAPI free and get an HLS URL your Android app can play in minutes, with RTMP and SRT ingest, instant encoding, and multi-CDN delivery handled for you.


