开发者

在Android上实现视频播放的多种方案

目录
  • 一、项目介绍
    • 1. 背景与意义
  • 二、相关知识
    • 三、实现思路
      • 四、环境与依赖
        • 五、整合代码
          • 六、代码解读
            • 七、性能与优化
              • 八、项目总结与拓展
                • 九、FAQ

                  一、项目介绍

                  1. 背景与意义

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

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

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

                  2. 原生 MediaPla编程客栈yer + 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。

                    • 动态切换分辨率、码率,需实现 TrackShttp://www.devze.comelector 或 DefaultTrackSelector

                  5. Lifecycle 与回收

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

                  三、实现思路

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

                  1. 方案一:VideoView

                  2. 方案二:MediaPlayer + SurfaceView

                  3. 方案三:MediaPlayer + TextureView

                  4. 方js案四: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"
                       
                        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()
                    }
                  }

                  六、代码解读

                  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 嵌入。

                  以上就是在Android上实现视频播放的多种方案的详细内容,更多关于Android视频播放的资料请关注编程客栈(www.devze.com)其它相关文章!

                  0

                  上一篇:

                  下一篇:

                  精彩评论

                  暂无评论...
                  验证码 换一张
                  取 消

                  最新开发

                  开发排行榜