SoFunction
Updated on 2025-04-28

Various solutions to implement video playback on Android

1. Project introduction

1. Background and meaning

With the development of mobile Internet, video has become one of the media forms with the largest traffic. Whether it is social short videos, online video playback, or live streaming, Android applications have ubiquitous demand for video playback. To achieve a stable, smooth and functional richVideo playbackModules require mastering multiple underlying APIs and third-party frameworks to cope with different networks, formats, coding and business scenarios.

This tutorial will give a comprehensive introduction to the implementation of video playback on AndroidMultiple solutions,include:

  1. systemVideoView: The easiest API, fast integration

  2. NativeMediaPlayer + SurfaceView: More flexible underlying implementation

  3. NativeMediaPlayer + TextureView: Supports transformations such as rotation and scaling

  4. ExoPlayer: Google recommends, supports DASH/HLS, cache, DRM

  5. Media3(Jetpack)**: Inheriting ExoPlayer, future trends

  6. Third-party player: Such as IJKPlayer (FFmpeg), Vitamio, etc.

  7. Lower levelMediaCodec: Custom decoding pipelines, suitable for special needs

  8. Compose + AndroidView: Integrate videos in Jetpack Compose

By comparing the usage, advantages and disadvantages of each solution, applicable scenarios, and complete sample code, you will be able to quickly make decisions and integrate video playback functions according to project needs.

2. Related knowledge

Before diving into the code, please understand the following core concepts:

  1. Container Type

    • SurfaceView: Independent rendering buffer, high performance but does not support ordinary View hierarchical transformation.

    • TextureView: Rendering in a normal View layer, supports translation, rotation, and scaling, but has a slightly lower performance.

    • PlayerView / StyledPlayerView: Encapsulated view provided by ExoPlayer.

  2. Player API Layer

    • VideoView: EncapsulatedMediaPlayer + SurfaceView, fast integration but poor customization.

    • MediaPlayer: Android native media playback engine, supports local and network streaming media.

    • ExoPlayer: Open source of Google, supports DASH, HLS, SmoothStreaming, and custom data sources.

    • Media3: A higher-level Jetpack media library, future recommendations.

  3. Streaming protocol

    • HTTP Progressive: Download MP4, MKV and other files directly.

    • HLS (M3U8):pass#EXTM3UPlayer downloads and plays.

    • DASH (MPD): Dynamic adaptive bit rate.

  4. DRM and clarity switch

    • ExoPlayer and Media3 have built-in support for DRM such as Widevine, PlayReady, etc.

    • Dynamically switch resolution and code rate, it needs to be implementedTrackSelectororDefaultTrackSelector

  5. Lifecycle and recycling

    • Activity/Fragment'sonStart/onStoporonResume/onPauseThe player is controlledplay()/pause()and when destroyedrelease()

Ideas for realization

We will implement and compare each solution in the following order:

  1. Plan 1:VideoView

  2. Plan 2:MediaPlayer + SurfaceView

  3. Plan 3:MediaPlayer + TextureView

  4. Solution 4: ExoPlayer

  5. Plan 5: Media3

  6. Plan 6: IJKPlayer (FFmpeg)

  7. Plan 7:MediaCodecSelf-decoding

  8. Solution 8: Jetpack Compose Integration Solution

Each program will provide:

  • Layout example

  • Activity/Fragment code

  • Lifecycle Management

  • Error handling and callbacks

Finally, we will summarize the advantages and disadvantages of each plan and give best practice suggestions for different scenarios.

4. Environment and dependence

// app/
plugins {
  id ''
  id 'kotlin-android'
}
 
android {
  compileSdkVersion 34
  defaultConfig {
    applicationId ""
    minSdkVersion 21
    targetSdkVersion 34
  }
  buildFeatures { viewBinding true }
  kotlinOptions { jvmTarget = "1.8" }
}
 
dependencies {
  // ExoPlayer
  implementation ':exoplayer:2.18.2'
  // Media3
  implementation "androidx.media3:media3-exoplayer:1.0.0"
  implementation "androidx.media3:media3-ui:1.0.0"
 
  // IJKPlayer
  implementation ':ijkplayer-java:0.8.8'
  implementation ':ijkplayer-arm64:0.8.8'
 
  // Compose (for Compose plan)  implementation ":ui:1.4.0"
  implementation ":material:1.4.0"
  implementation ":activity-compose:1.7.0"
}

5. Integrate code

// =======================================================
// File: res/layout/activity_main.xml// Description: Simple navigation, select different playback schemes// =======================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:andro
    android:orientation="vertical" android:padding="16dp"
    android:layout_width="match_parent" android:layout_height="match_parent">
  <Button android: android:text="VideoView Solution"/>
  <Button android:   android:text="MediaPlayer+SurfaceView"/>
  <Button android:   android:text="MediaPlayer+TextureView"/>
  <Button android:       android:text="ExoPlayer Solution"/>
  <Button android:    android:text="Media3 Solution"/>
  <Button android:       android:text="IJKPlayer Solution"/>
  <Button android:     android:text="MediaCodec Self-decoding"/>
  <Button android:   android:text="Compose Integration Solution"/>
</LinearLayout>
 
// =======================================================
// document:// Description: Jump to each example Activity// =======================================================
package 
 
import 
import 
import 
import 
 
class MainActivity : AppCompatActivity() {
  private lateinit var binding: ActivityMainBinding
  override fun onCreate(s: Bundle?) {
    (s)
    binding = (layoutInflater)
    setContentView()
 
      .setOnClickListener { startActivity(Intent(this, VideoViewActivity::)) }
        .setOnClickListener { startActivity(Intent(this, SurfaceActivity::)) }
        .setOnClickListener { startActivity(Intent(this, TextureActivity::)) }
            .setOnClickListener { startActivity(Intent(this, ExoActivity::)) }
    binding.btnMedia3     .setOnClickListener { startActivity(Intent(this, Media3Activity::)) }
            .setOnClickListener { startActivity(Intent(this, IjkActivity::)) }
          .setOnClickListener { startActivity(Intent(this, CodecActivity::)) }
        .setOnClickListener { startActivity(Intent(this, ComposeActivity::)) }
  }
}
 
// =======================================================
// Plan 1:// Layout: res/layout/activity_video_view.xml
// =======================================================
// activity_video_view.xml
/*
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:andro
    android:layout_width="match_parent" android:layout_height="match_parent">
  <VideoView
      android:
      android:layout_width="match_parent" android:layout_height="match_parent"/>
  <ProgressBar android:
      style="?android:attr/progressBarStyleLarge"
      android:layout_gravity="center"/>
</FrameLayout>
*/
// 
package 
import 
import 
import 
import 
import 
class VideoViewActivity : AppCompatActivity() {
  private lateinit var binding: ActivityVideoViewBinding
  override fun onCreate(s: Bundle?) {
    (s)
    binding = (layoutInflater)
    setContentView()
    val uri = ("/video.mp4")
    ()
    (uri)
    (MediaController(this))
     {
      ()
       = true
      ()
    }
  }
  override fun onPause(){ (); () }
  override fun onResume(){ (); () }
  override fun onDestroy(){ (); () }
}
 
// =======================================================
// Scheme 2: SurfaceView + MediaPlayer// File: res/layout/activity_surface.xml
// =======================================================
/*
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout ...>
  <SurfaceView android: .../>
  <ProgressBar android: .../>
</FrameLayout>
*/
// 
package 
import 
import 
import 
import 
import 
class SurfaceActivity: AppCompatActivity(),  {
  private lateinit var binding: ActivitySurfaceBinding
  private var player: MediaPlayer? = null
  override fun onCreate(s: Bundle?){ (s)
    binding = (layoutInflater)
    setContentView()
    (this)
  }
  override fun surfaceCreated(holder: SurfaceHolder) {
    player = MediaPlayer().apply {
      setDataSource("https://.../video.mp4")
      setDisplay(holder)
      setOnPreparedListener {
        ()
        isLooping = true; start()
      }
      prepareAsync()
    }
  }
  override fun surfaceDestroyed(holder: SurfaceHolder) {
    player?.release(); player = null
  }
  override fun surfaceChanged(h: SurfaceHolder, f:Int, w:Int, h2:Int){}
}
 
// =======================================================
// Scheme 3: TextureView + MediaPlayer// File: res/layout/activity_texture.xml
// =======================================================
/*
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout ...>
  <TextureView android: .../>
  <ProgressBar android: .../>
</FrameLayout>
*/
// 
package 
import 
import 
import 
import 
import 
import 
class TextureActivity: AppCompatActivity(),  {
  private lateinit var binding: ActivityTextureBinding
  private var player: MediaPlayer? = null
  override fun onCreate(s: Bundle?){ (s)
    binding = (layoutInflater)
    setContentView()
     = this
  }
  override fun onSurfaceTextureAvailable(st: SurfaceTexture, w:Int, h:Int){
    player = MediaPlayer().apply {
      setSurface((st))
      setDataSource("https://.../video.mp4")
      setOnPreparedListener {
        ()
        isLooping=true; start()
      }
      prepareAsync()
    }
  }
  override fun onSurfaceTextureSizeChanged(st:SurfaceTexture,w:Int,h:Int){}
  override fun onSurfaceTextureDestroyed(st:SurfaceTexture):Boolean{ player?.release(); player=null; return true }
  override fun onSurfaceTextureUpdated(st:SurfaceTexture){}
}
 
// =======================================================
// Solution 4: ExoPlayer// File: res/layout/activity_exo.xml
// =======================================================
/*
<?xml version="1.0" encoding="utf-8"?>
<.
    xmlns:andro
    android: .../>
*/
// 
package 
import 
import 
import 
import 
import .
import .
class ExoActivity: AppCompatActivity() {
  private lateinit var binding: ActivityExoBinding
  private var player: ExoPlayer? = null
  override fun onCreate(s: Bundle?){ (s)
    binding = (layoutInflater)
    setContentView()
    player = (this).build().also {
       = it
      val mediaItem = (("https://.../video.mp4"))
      (mediaItem);  = ExoPlayer.REPEAT_MODE_ALL
      (); ()
    }
  }
  override fun onPause(){ (); player?.pause() }
  override fun onResume(){ (); player?.play() }
  override fun onDestroy(){ (); player?.release(); player=null }
}
 
// =======================================================
// Plan 5: Media3 (Jetpack)// File: res/layout/activity_media3.xml
// =======================================================
/*
<?xml version="1.0" encoding="utf-8"?>
<androidx. ... android:/>
*/
// 
package 
import 
import 
import 
import androidx.
import androidx.
import .ActivityMedia3Binding
class Media3Activity: AppCompatActivity() {
  private lateinit var binding: ActivityMedia3Binding
  private var player: ExoPlayer? = null
  override fun onCreate(s: Bundle?){ (s)
    binding = (layoutInflater)
    setContentView()
    player = (this).build().apply {
      setMediaItem((("https://.../video.mp4")))
      repeatMode = ExoPlayer.REPEAT_MODE_ALL; prepare(); play()
    }
     = player
  }
  override fun onPause(){ (); player?.pause() }
  override fun onResume(){ (); player?.play() }
  override fun onDestroy(){ (); player?.release(); player=null }
}
 
// =======================================================
// Plan 6: IJKPlayer// File: res/layout/activity_ijk.xml
// =======================================================
/*
<?xml version="1.0" encoding="utf-8"?>
< ... android:/>
*/
// 
package 
import 
import 
import 
import 
class IjkActivity: AppCompatActivity() {
  private lateinit var binding: ActivityIjkBinding
  override fun onCreate(s: Bundle?){ (s)
    binding = (layoutInflater)
    setContentView()
    (null); IjkMediaPlayer.native_profileBegin("")
    ("https://.../video.mp4")
    ()
  }
  override fun onDestroy(){ ()
    ()
    IjkMediaPlayer.native_profileEnd()
  }
}
 
// =======================================================
// Scheme 7: MediaCodec self-decoding (short description)// File: 
// =======================================================
// Hundreds of lines of self-decoded code are omitted here, and only brief descriptions are given:// - Use MediaExtractor to separate tracks// - Decode to Surface with MediaCodec// - Render with SurfaceView / TextureView// It is recommended to consult the official documents and Codelab to implement it in depth. 
// =======================================================
// Solution 8: Compose integration// File: 
// =======================================================
package 
import 
import 
import 
import 
import 
import 
import 
import 
import androidx.
import androidx.
class ComposeActivity: AppCompatActivity() {
  override fun onCreate(s: Bundle?){ (s)
    val player = (this).build().apply {
      setMediaItem((("https://.../video.mp4")))
      prepare(); play()
    }
    setContent {
      Box(()) {
        AndroidView(factory = { ctx ->
          PlayerView(ctx).apply {
             = player; useController=true
          }
        }, modifier=())
      }
    }
  }
  override fun onDestroy(){ ()
    ()
  }
}

6. Code interpretation

  1. VideoView

    • Simple and easy to use, high packaging;

    • Unable to control underlying buffering or custom rendering;

  2. MediaPlayer + SurfaceView

    • Suitable for large-scale video or live broadcast;

    • High performance, but does not support View transformation;

  3. MediaPlayer + TextureView

    • Supports any 2D transformation (rotate, zoom);

    • Performance is second to SurfaceView;

  4. ExoPlayer

    • Supports DASH, HLS, and custom loading;

    • Have rich extensions (cache, DRM, subtitles);

  5. Media3

    • Jetpack new recommendations, compatible with future updates;

    • The API is basically the same as ExoPlayer;

  6. IJKPlayer

    • Based on FFmpeg, support more formats;

    • Need to deploy native libraries, with a large package;

  7. MediaCodec

    • Lowest-level control, suitable for custom rendering or special decoding needs;

    • High development costs;

  8. Compose integration

    • Can be used in ComposeAndroidViewEmbed any View;

    • In the future, you can expect native Compose video components;

7. Performance and Optimization

  1. Hardware acceleration

    • SurfaceViewwith ExoPlayer default hardware acceleration;

  2. Network buffering

    • ExoPlayer can be customizedLoadControl

  3. Concurrency and Switching

    • Avoid frequentprepare()/release()

  4. Memory management

    • Timelyrelease()Resources to avoid leakage;

  5. UI and Rendering

    • Avoid heavy UI operations on the main thread;

8. Project Summary and Expansion

This article introduces almost all mainstream video playback implementation methods on Android from multiple angles and in full schemes, and compares sample code with advantages and disadvantages to facilitate making choices in different business scenarios. Future scalable:

  • Adaptive code rate:HLS/DASH dynamic switching

  • DRM:Protected clearplay

  • Save traffic: Integrated cache, pre-download

  • UI special effects: Filters, barrage, picture in picture

9. FAQ

Q1: Which solution is the easiest?

A:VideoView, but the customizability is minimal.

Q2: Which one is recommended?

A: ExoPlayer/Media3, the most complete functions and active community.

Q3: How to play live HLS?

A: ExoPlayer Direct("https://.../live.m3u8")Just do it.

Q4: What should I do if IJKPlayer has a large body?

A: The native library can be customized and only package the required ABI.

Q5: Will there be native video components in Compose in the future?

A: It is under development, but it is still necessaryAndroidViewEmbed.

The above is the detailed content of various solutions for realizing video playback on Android. For more information about Android video playback, please follow my other related articles!