Android实现视频播放的几种实现方式(附带源码)

一、项目介绍

1. 背景与意义

随着移动互联网的发展,视频已成为流量最大的媒体形式之一。无论是社交短视频、在线视频播放、还是直播推流功能,Android 应用对视频播放的需求无处不在。要实现一个稳定、流畅、功能丰富的视频播放模块,需要掌握多种底层 API 与第三方框架,才能应对不同网络、格式、编码与业务场景。

本教程将全面介绍在 Android 上实现视频播放的多种方案,包括:

  1. 系统 VideoView:最简单的 API,快速集成

  2. 原生 MediaPlayer + SurfaceView:更灵活的底层实现

  3. 原生 MediaPlayer + TextureView:支持旋转、缩放等变换

  4. ExoPlayer:Google 推荐,支持 DASH/HLS、缓存、DRM

  5. Media3(Jetpack)**:继承 ExoPlayer,未来趋势

  6. 第三方播放器:如 IJKPlayer(FFmpeg)、Vitamio 等

  7. 低层 MediaCodec:自定义解码管线,适合特殊需求

  8. Compose + AndroidView:在 Jetpack Compose 中集成视频

通过对比各方案的用法、优缺点、适用场景,以及完整的示例代码,你将能够根据项目需求,快速抉择并集成视频播放功能。


二、相关知识

在深入代码之前,请先了解以下核心概念:

  1. 容器类型

    • SurfaceView:独立的渲染缓冲区,性能高但不支持普通 View 层级变换。

    • TextureView:在普通 View 层中渲染,支持平移、旋转、缩放,但性能略低。

    • PlayerView / StyledPlayerView:ExoPlayer 提供的封装视图。

  2. 播放器 API 层

    • VideoView:封装了 MediaPlayer + SurfaceView,快速集成但可定制性差。

    • MediaPlayer:Android 原生媒体播放引擎,支持本地与网络流媒体。

    • ExoPlayer:Google 开源,支持 DASH、HLS、SmoothStreaming、自定义数据源。

    • Media3:更高层的 Jetpack 媒体库,未来推荐。

  3. 流媒体协议

    • HTTP Progressive:直接下载 MP4、MKV 等文件。

    • HLS (M3U8):通过 #EXTM3U 播放器边下载边播放。

    • DASH (MPD):动态自适应比特率。

  4. DRM 与清晰度切换

    • ExoPlayer 和 Media3 内置支持 Widevine、PlayReady 等 DRM。

    • 动态切换分辨率、码率,需实现 TrackSelectorDefaultTrackSelector

  5. Lifecycle 与回收

    • Activity/Fragment 的 onStart/onStoponResume/onPause 中控制播放器的 play()/pause(),并在销毁时 release()


三、实现思路

我们将按以下顺序实现并对比各方案:

  1. 方案一:VideoView

  2. 方案二:MediaPlayer + SurfaceView

  3. 方案三:MediaPlayer + TextureView

  4. 方案四:ExoPlayer

  5. 方案五:Media3

  6. 方案六:IJKPlayer(FFmpeg)

  7. 方案七:MediaCodec 自解码

  8. 方案八:Jetpack Compose 集成方案

每个方案都将提供:

  • 布局示例

  • Activity/Fragment 代码

  • 生命周期管理

  • 错误处理与回调

最后,我们将总结各方案优缺点,并给出不同场景的最佳实践建议。


四、环境与依赖

// app/build.gradle
plugins {
  id 'com.android.application'
  id 'kotlin-android'
}

android {
  compileSdkVersion 34
  defaultConfig {
    applicationId "com.example.videoplaydemo"
    minSdkVersion 21
    targetSdkVersion 34
  }
  buildFeatures { viewBinding true }
  kotlinOptions { jvmTarget = "1.8" }
}

dependencies {
  // ExoPlayer
  implementation 'com.google.android.exoplayer:exoplayer:2.18.2'
  // Media3
  implementation "androidx.media3:media3-exoplayer:1.0.0"
  implementation "androidx.media3:media3-ui:1.0.0"

  // IJKPlayer
  implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8'
  implementation 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8'

  // Compose (for Compose 方案)
  implementation "androidx.compose.ui:ui:1.4.0"
  implementation "androidx.compose.material:material:1.4.0"
  implementation "androidx.activity:activity-compose:1.7.0"
}

五、整合代码

// =======================================================
// 文件: res/layout/activity_main.xml
// 描述: 简单导航,选择不同播放方案
// =======================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:padding="16dp"
    android:layout_width="match_parent" android:layout_height="match_parent">
  <Button android:id="@+id/btnVideoView" android:text="VideoView 方案"/>
  <Button android:id="@+id/btnSurface"   android:text="MediaPlayer+SurfaceView"/>
  <Button android:id="@+id/btnTexture"   android:text="MediaPlayer+TextureView"/>
  <Button android:id="@+id/btnExo"       android:text="ExoPlayer 方案"/>
  <Button android:id="@+id/btnMedia3"    android:text="Media3 方案"/>
  <Button android:id="@+id/btnIJK"       android:text="IJKPlayer 方案"/>
  <Button android:id="@+id/btnCodec"     android:text="MediaCodec 自解码"/>
  <Button android:id="@+id/btnCompose"   android:text="Compose 集成方案"/>
</LinearLayout>

// =======================================================
// 文件: MainActivity.kt
// 描述: 跳转到各个示例 Activity
// =======================================================
package com.example.videoplaydemo

import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.videoplaydemo.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
  private lateinit var binding: ActivityMainBinding
  override fun onCreate(s: Bundle?) {
    super.onCreate(s)
    binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)

    binding.btnVideoView  .setOnClickListener { startActivity(Intent(this, VideoViewActivity::class.java)) }
    binding.btnSurface    .setOnClickListener { startActivity(Intent(this, SurfaceActivity::class.java)) }
    binding.btnTexture    .setOnClickListener { startActivity(Intent(this, TextureActivity::class.java)) }
    binding.btnExo        .setOnClickListener { startActivity(Intent(this, ExoActivity::class.java)) }
    binding.btnMedia3     .setOnClickListener { startActivity(Intent(this, Media3Activity::class.java)) }
    binding.btnIJK        .setOnClickListener { startActivity(Intent(this, IjkActivity::class.java)) }
    binding.btnCodec      .setOnClickListener { startActivity(Intent(this, CodecActivity::class.java)) }
    binding.btnCompose    .setOnClickListener { startActivity(Intent(this, ComposeActivity::class.java)) }
  }
}

// =======================================================
// 方案一:VideoViewActivity.kt
// Layout: res/layout/activity_video_view.xml
// =======================================================
// activity_video_view.xml
/*
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">
  <VideoView
      android:id="@+id/videoView"
      android:layout_width="match_parent" android:layout_height="match_parent"/>
  <ProgressBar android:id="@+id/progress"
      style="?android:attr/progressBarStyleLarge"
      android:layout_gravity="center"/>
</FrameLayout>
*/
// VideoViewActivity.kt
package com.example.videoplaydemo
import android.net.Uri
import android.os.Bundle
import android.widget.MediaController
import androidx.appcompat.app.AppCompatActivity
import com.example.videoplaydemo.databinding.ActivityVideoViewBinding
class VideoViewActivity : AppCompatActivity() {
  private lateinit var binding: ActivityVideoViewBinding
  override fun onCreate(s: Bundle?) {
    super.onCreate(s)
    binding = ActivityVideoViewBinding.inflate(layoutInflater)
    setContentView(binding.root)
    val uri = Uri.parse("https://www.example.com/video.mp4")
    binding.progress.show()
    binding.videoView.setVideoURI(uri)
    binding.videoView.setMediaController(MediaController(this))
    binding.videoView.setOnPreparedListener {
      binding.progress.hide()
      it.isLooping = true
      binding.videoView.start()
    }
  }
  override fun onPause(){ super.onPause(); binding.videoView.pause() }
  override fun onResume(){ super.onResume(); binding.videoView.start() }
  override fun onDestroy(){ super.onDestroy(); binding.videoView.stopPlayback() }
}

// =======================================================
// 方案二:SurfaceView + MediaPlayer
// File: res/layout/activity_surface.xml
// =======================================================
/*
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout ...>
  <SurfaceView android:id="@+id/surfaceView" .../>
  <ProgressBar android:id="@+id/progress" .../>
</FrameLayout>
*/
// SurfaceActivity.kt
package com.example.videoplaydemo
import android.media.MediaPlayer
import android.os.Bundle
import android.view.SurfaceHolder
import androidx.appcompat.app.AppCompatActivity
import com.example.videoplaydemo.databinding.ActivitySurfaceBinding
class SurfaceActivity: AppCompatActivity(), SurfaceHolder.Callback {
  private lateinit var binding: ActivitySurfaceBinding
  private var player: MediaPlayer? = null
  override fun onCreate(s: Bundle?){ super.onCreate(s)
    binding = ActivitySurfaceBinding.inflate(layoutInflater)
    setContentView(binding.root)
    binding.surfaceView.holder.addCallback(this)
  }
  override fun surfaceCreated(holder: SurfaceHolder) {
    player = MediaPlayer().apply {
      setDataSource("https://.../video.mp4")
      setDisplay(holder)
      setOnPreparedListener {
        binding.progress.hide()
        isLooping = true; start()
      }
      prepareAsync()
    }
  }
  override fun surfaceDestroyed(holder: SurfaceHolder) {
    player?.release(); player = null
  }
  override fun surfaceChanged(h: SurfaceHolder, f:Int, w:Int, h2:Int){}
}

// =======================================================
// 方案三:TextureView + MediaPlayer
// File: res/layout/activity_texture.xml
// =======================================================
/*
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout ...>
  <TextureView android:id="@+id/textureView" .../>
  <ProgressBar android:id="@+id/progress" .../>
</FrameLayout>
*/
// TextureActivity.kt
package com.example.videoplaydemo
import android.graphics.SurfaceTexture
import android.media.MediaPlayer
import android.os.Bundle
import android.view.TextureView
import androidx.appcompat.app.AppCompatActivity
import com.example.videoplaydemo.databinding.ActivityTextureBinding
class TextureActivity: AppCompatActivity(), TextureView.SurfaceTextureListener {
  private lateinit var binding: ActivityTextureBinding
  private var player: MediaPlayer? = null
  override fun onCreate(s: Bundle?){ super.onCreate(s)
    binding = ActivityTextureBinding.inflate(layoutInflater)
    setContentView(binding.root)
    binding.textureView.surfaceTextureListener = this
  }
  override fun onSurfaceTextureAvailable(st: SurfaceTexture, w:Int, h:Int){
    player = MediaPlayer().apply {
      setSurface(android.view.Surface(st))
      setDataSource("https://.../video.mp4")
      setOnPreparedListener {
        binding.progress.hide()
        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){}
}

// =======================================================
// 方案四:ExoPlayer
// File: res/layout/activity_exo.xml
// =======================================================
/*
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.exoplayer2.ui.PlayerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/playerView" .../>
*/
// ExoActivity.kt
package com.example.videoplaydemo
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.videoplaydemo.databinding.ActivityExoBinding
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
class ExoActivity: AppCompatActivity() {
  private lateinit var binding: ActivityExoBinding
  private var player: ExoPlayer? = null
  override fun onCreate(s: Bundle?){ super.onCreate(s)
    binding = ActivityExoBinding.inflate(layoutInflater)
    setContentView(binding.root)
    player = ExoPlayer.Builder(this).build().also {
      binding.playerView.player = it
      val mediaItem = MediaItem.fromUri(Uri.parse("https://.../video.mp4"))
      it.setMediaItem(mediaItem); it.repeatMode = ExoPlayer.REPEAT_MODE_ALL
      it.prepare(); it.play()
    }
  }
  override fun onPause(){ super.onPause(); player?.pause() }
  override fun onResume(){ super.onResume(); player?.play() }
  override fun onDestroy(){ super.onDestroy(); player?.release(); player=null }
}

// =======================================================
// 方案五:Media3 (Jetpack)
// File: res/layout/activity_media3.xml
// =======================================================
/*
<?xml version="1.0" encoding="utf-8"?>
<androidx.media3.ui.PlayerView ... android:id="@+id/playerView"/>
*/
// Media3Activity.kt
package com.example.videoplaydemo
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
import com.example.videoplaydemo.databinding.ActivityMedia3Binding
class Media3Activity: AppCompatActivity() {
  private lateinit var binding: ActivityMedia3Binding
  private var player: ExoPlayer? = null
  override fun onCreate(s: Bundle?){ super.onCreate(s)
    binding = ActivityMedia3Binding.inflate(layoutInflater)
    setContentView(binding.root)
    player = ExoPlayer.Builder(this).build().apply {
      setMediaItem(MediaItem.fromUri(Uri.parse("https://.../video.mp4")))
      repeatMode = ExoPlayer.REPEAT_MODE_ALL; prepare(); play()
    }
    binding.playerView.player = player
  }
  override fun onPause(){ super.onPause(); player?.pause() }
  override fun onResume(){ super.onResume(); player?.play() }
  override fun onDestroy(){ super.onDestroy(); player?.release(); player=null }
}

// =======================================================
// 方案六:IJKPlayer
// File: res/layout/activity_ijk.xml
// =======================================================
/*
<?xml version="1.0" encoding="utf-8"?>
<tv.danmaku.ijk.media.player.IjkVideoView ... android:id="@+id/ijkView"/>
*/
// IjkActivity.kt
package com.example.videoplaydemo
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import tv.danmaku.ijk.media.player.IjkMediaPlayer
import com.example.videoplaydemo.databinding.ActivityIjkBinding
class IjkActivity: AppCompatActivity() {
  private lateinit var binding: ActivityIjkBinding
  override fun onCreate(s: Bundle?){ super.onCreate(s)
    binding = ActivityIjkBinding.inflate(layoutInflater)
    setContentView(binding.root)
    IjkMediaPlayer.loadLibrariesOnce(null); IjkMediaPlayer.native_profileBegin("libijkplayer.so")
    binding.ijkView.setVideoPath("https://.../video.mp4")
    binding.ijkView.start()
  }
  override fun onDestroy(){ super.onDestroy()
    binding.ijkView.stopPlayback()
    IjkMediaPlayer.native_profileEnd()
  }
}

// =======================================================
// 方案七:MediaCodec 自解码(略示意)
// File: CodecActivity.kt
// =======================================================
// 此处省略数百行自解码代码,仅做简要示意:
// - 使用 MediaExtractor 分离轨道  
// - 用 MediaCodec 解码到 Surface  
// - 用 SurfaceView / TextureView 渲染  
// 建议查阅官方文档与 Codelab 深入实现。

// =======================================================
// 方案八:Compose 集成
// File: ComposeActivity.kt
// =======================================================
package com.example.videoplaydemo
import android.net.Uri
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
class ComposeActivity: AppCompatActivity() {
  override fun onCreate(s: Bundle?){ super.onCreate(s)
    val player = ExoPlayer.Builder(this).build().apply {
      setMediaItem(MediaItem.fromUri(Uri.parse("https://.../video.mp4")))
      prepare(); play()
    }
    setContent {
      Box(Modifier.fillMaxSize()) {
        AndroidView(factory = { ctx ->
          PlayerView(ctx).apply {
            this.player = player; useController=true
          }
        }, modifier=Modifier.fillMaxSize())
      }
    }
  }
  override fun onDestroy(){ super.onDestroy()
    player.release()
  }
}

六、代码解读

  1. VideoView

    • 简单易用,封装度高;

    • 无法控制底层缓冲或自定义渲染;

  2. MediaPlayer + SurfaceView

    • 适合大批量视频或直播;

    • 性能高,但不支持 View 变换;

  3. MediaPlayer + TextureView

    • 支持任意 2D 变换(旋转、缩放);

    • 性能次于 SurfaceView;

  4. ExoPlayer

    • 支持 DASH、HLS、自定义加载;

    • 拥有丰富扩展(缓存、DRM、字幕);

  5. Media3

    • Jetpack 新推荐,兼容未来更新;

    • API 与 ExoPlayer 基本一致;

  6. IJKPlayer

    • 基于 FFmpeg,支持更多格式;

    • 需部署 native 库,包体大;

  7. MediaCodec

    • 最低层控制,适合自定义渲染或特殊解码需求;

    • 开发成本高;

  8. Compose 集成

    • 在 Compose 中可使用 AndroidView 嵌入任意 View;

    • 未来可期待原生 Compose 视频组件;


七、性能与优化

  1. 硬件加速

    • SurfaceView 与 ExoPlayer 默认硬件加速;

  2. 网络缓冲

    • ExoPlayer 可自定义 LoadControl

  3. 并发与切换

    • 避免频繁 prepare()/release()

  4. 内存管理

    • 及时 release() 资源,避免泄漏;

  5. UI 与渲染

    • 避免在主线程做 heavy UI 操作;


八、项目总结与拓展

本文多角度、全方案地介绍了 Android 上几乎所有主流的视频播放实现方式,配以示例代码与优缺点对比,便于在不同业务场景中做出选择。未来可扩展:

  • 自适应码率:HLS/DASH 动态切换

  • DRM:Protected clearplay

  • 节省流量:集成缓存、预下载

  • UI 特效:滤镜、弹幕、画中画


九、FAQ

Q1:哪种方案最简单?
A:VideoView,但可定制性最低。

Q2:推荐使用哪个?
A:ExoPlayer/Media3,功能最全,社区活跃。

Q3:如何播放直播 HLS?
A:ExoPlayer 直接 MediaItem.fromUri("https://.../live.m3u8") 即可。

Q4:IJKPlayer 包体大怎么办?
A:可定制 native 库,只打包需要的 ABI。

Q5:Compose 未来会有原生视频组件吗?
A:已在开发中,但目前仍需 AndroidView 嵌入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值