在Android上实现视频播放的多种方案
目录
- 一、项目介绍
- 1. 背景与意义
- 二、相关知识
- 三、实现思路
- 四、环境与依赖
- 五、整合代码
- 六、代码解读
- 七、性能与优化
- 八、项目总结与拓展
- 九、FAQ
一、项目介绍
1. 背景与意义
随着移动互联网的发展,视频已成为流量最大的媒体形式之一。无论是社交短视频、在线视频播放、还是直播推流功能,android 应用对视频播放的需求无处不在。要实现一个稳定、流畅、功能丰富的视频播放模块,需要掌握多种底层 API 与第三方框架,才能应对不同网络、格式、编码与业务场景。
本教程将全面介绍在 Android 上实现视频播放的多种方案,包括:
系统
VideoView
:最简单的 API,快速集成原生
MediaPla编程客栈yer
+SurfaceView
:更灵活的底层实现原生
MediaPlayer
+TextureView
:支持旋转、缩放等变换ExoPlayer:Google 推荐,支持 DASH/HLS、缓存、DRM
Media3(Jetpack)**:继承 ExoPlayer,未来趋势
第三方播放器:如 IJKPlayer(FFmpeg)、Vitamio 等
低层
MediaCodec
:自定义解码管线,适合特殊需求Compose +
AndroidView
:在 Jetpack Compose 中集成视频
通过对比各方案的用法、优缺点、适用场景,以及完整的示例代码,你将能够根据项目需求,快速抉择并集成视频播放功能。
二、相关知识
在深入代码之前,请先了解以下核心概念:
容器类型
SurfaceView
:独立的渲染缓冲区,性能高但不支持普通 View 层级变换。TextureView
:在普通 View 层中渲染,支持平移、旋转、缩放,但性能略低。PlayerView
/StyledPlayerView
:ExoPlayer 提供的封装视图。
播放器 API 层
VideoView
:封装了MediaPlayer
+SurfaceView
,快速集成但可定制性差。MediaPlayer
:Android 原生媒体播放引擎,支持本地与网络流媒体。ExoPlayer
:Google 开源,支持 DASH、HLS、SmoothStreaming、自定义数据源。Media3
:更高层的 Jetpack 媒体库,未来推荐。
流媒体协议
HTTP Progressive:直接下载 MP4、MKV 等文件。
HLS (M3U8):通过
#EXTM3U
播放器边下载边播放。DASH (MPD):动态自适应比特率。
DRM 与清晰度切换
ExoPlayer 和 Media3 内置支持 Widevine、PlayReady 等 DRM。
动态切换分辨率、码率,需实现
TrackShttp://www.devze.comelector
或DefaultTrackSelector
。
Lifecycle 与回收
Activity/Fragment 的
onStart
/onStop
或onResume
/onPause
中控制播放器的play()
/pause()
,并在销毁时release()
。
三、实现思路
我们将按以下顺序实现并对比各方案:
方案一:
VideoView
方案二:
MediaPlayer
+SurfaceView
方案三:
MediaPlayer
+TextureView
方js案四:ExoPlayer
方案五:Media3
方案六:IJKPlayer(FFmpeg)
方案七:
MediaCodec
自解码方案八: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" 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)) setDataSandroidource("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){} } // ==python===================================================== // 方案四: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() } }
六、代码解读
VideoView
简单易用,封装度高;
无法控制底层缓冲或自定义渲染;
MediaPlayer
+SurfaceView
适合大批量视频或直播;
性能高,但不支持 View 变换;
MediaPlayer
+TextureView
支持任意 2D 变换(旋转、缩放);
性能次于 SurfaceView;
ExoPlayer
支持 DASH、HLS、自定义加载;
拥有丰富扩展(缓存、DRM、字幕);
Media3
Jetpack 新推荐,兼容未来更新;
API 与 ExoPlayer 基本一致;
IJKPlayer
基于 FFmpeg,支持更多格式;
需部署 native 库,包体大;
MediaCodec
最低层控制,适合自定义渲染或特殊解码需求;
开发成本高;
Compose 集成
在 Compose 中可使用
AndroidView
嵌入任意 View;未来可期待原生 Compose 视频组件;
七、性能与优化
硬件加速
SurfaceView
与 ExoPlayer 默认硬件加速;
网络缓冲
ExoPlayer 可自定义
LoadControl
;
并发与切换
避免频繁
prepare()
/release()
;
内存管理
及时
release()
资源,避免泄漏;
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
嵌入。
以上就是在Android上实现视频播放的多种方案的详细内容,更多关于Android视频播放的资料请关注编程客栈(www.devze.com)其它相关文章!
精彩评论