Video Player

ExoPlayer: What It Is, How It Works, and How to Use It on Android

17 min read
Hand holding an Android smartphone playing video, illustrating ExoPlayer media playback
Reading Time: 12 minutes

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

  • ExoPlayer is the top-level object that holds playback state and coordinates everything below it. You build it with ExoPlayer.Builder(context).build().
  • MediaSource knows how to load a particular kind of media. ProgressiveMediaSource handles a plain MP4, HlsMediaSource handles an HLS playlist, DashMediaSource handles a DASH manifest.
  • DataSource does the actual byte fetching over HTTP, from disk, or from a cache.
  • Extractor demuxes a container into separate audio, video, and text sample streams.
  • TrackSelector picks which tracks play, including which quality rendition to use at any moment. This is where adaptive bitrate logic lives.
  • LoadControl decides when to buffer more data and when to start playing.
  • Renderer decodes samples and pushes them to output. MediaCodecVideoRenderer drives the hardware decoder to a Surface, MediaCodecAudioRenderer drives an AudioTrack.
  • PlayerView is 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

  1. You set a media item. player.setMediaItem(MediaItem.fromUri(url)) tells the player what to play, and a MediaSource.Factory inspects the URI to pick the right MediaSource.
  2. 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.
  3. LoadControl requests data. The player starts pulling segments through the DataSource chain, filling buffers ahead of the playhead.
  4. Extractor demuxes. Each downloaded chunk gets parsed into audio, video, and subtitle samples, which land in per-track sample queues.
  5. TrackSelector chooses. Based on measured bandwidth, buffer health, and device capability, it picks the rendition to keep pulling. This runs continuously, not once.
  6. Renderers decode. Samples feed into MediaCodec, which hands the work to the device’s hardware decoder wherever possible.
  7. Output syncs. Decoded video frames go to the Surface and audio goes to the AudioTrack, 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 Google 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.

Join 200,000+ satisfied streamers

Still on the fence? Take a sneak peek and see what you can do with Castr.

No Castr Branding

No Castr Branding

We do not include our branding on your videos.

No Commitment

No Commitment

No contracts. Cancel or change your plans anytime.

24/7 Support

24/7 Support

Highly skilled in-house engineers ready to help.

  • Check Free 7-day trial
  • CheckCancel anytime
  • CheckNo credit card required

Related Articles