安卓MediaPlayer如何正确使用?

99ANYc3cd6
预计阅读时长 40 分钟
位置: 首页 安卓 正文

MediaPlayer 简介

MediaPlayer 是一个功能强大的媒体播放器,它可以播放来自不同来源的音频和视频文件,

安卓mediaplayer
(图片来源网络,侵删)
  • 应用资源文件 (res/raw/)
  • 应用内部存储 (Internal Storage)
  • 外部存储 (SD Card)
  • 网络流 (HTTP/HTTPS URL)

它支持多种常见的媒体格式,如 MP3, MP4, 3GP, AAC, WAV, AMR 等。


MediaPlayer 的核心工作流程

使用 MediaPlayer 的标准流程通常遵循以下生命周期,这非常重要,理解了它就能避免大多数错误。

状态机图: 这是一个简化的状态转换图,可以帮助你理解 MediaPlayer 的不同状态和它们之间的转换。

       (prepareAsync)
+----------------+      +----------------+      +----------------+
|  初始状态      |----->|  初始化状态     |----->|  准备就绪状态   |
| (Idle)         |      | (Initialized)  |      | (Prepared)     |
+----------------+      +----------------+      +----------------+
        ^  |                 ^  |                      |  |
        |  | (setDataSource) |  | (prepare)           |  | (start)
        |  |                 |  |                      |  |
        |  v                 |  v                      |  v
+----------------+      +----------------+      +----------------+
|  错误状态      |<-----|  准备中状态     |<-----|  播放中状态     |
| (Error)        |      | (Preparing)    |      | (Started)      |
+----------------+      +----------------+      +----------------+
        ^                      |                      |  |
        | (reset)              | (seekTo)             |  | (pause)
        |                      |                      |  |
        |                      v                      |  v
        |                +----------------+      +----------------+
        +---------------|  暂停状态       |------>|  暂停就绪状态   |
                        | (Paused)       |      | (PlaybackCompleted) |
                        +----------------+      +----------------+

关键状态解释:

安卓mediaplayer
(图片来源网络,侵删)
  1. Idle (初始状态): 创建 MediaPlayer 对象后的默认状态,可以调用 reset(), setDataSource(), setDisplay()
  2. Initialized (初始化状态): 成功调用 setDataSource() 后进入此状态,可以调用 prepare(), prepareAsync(), reset()
  3. Prepared (准备就绪状态): 成功调用 prepare()prepareAsync() 后进入此状态,此时可以调用 start(), pause(), seekTo(), stop()
  4. Started (播放中状态): 调用 start() 后进入,可以调用 pause(), stop(), seekTo()
  5. Paused (暂停状态): 调用 pause() 后进入,可以调用 start(), stop()
  6. PlaybackCompleted (播放完成状态): 播放自然结束时进入,可以调用 start() (重新播放), stop(), seekTo()
  7. Error (错误状态): 发生错误时进入,需要调用 reset()release() 来恢复或释放。
  8. End (结束状态): 调用 release() 后进入。MediaPlayer 对象不能再被使用。

基本使用步骤(以播放本地资源为例)

下面是一个完整的、可运行的示例,展示了如何播放 res/raw 目录下的音频文件。

添加资源文件

将你的音频文件(my_music.mp3)放入 app/src/main/res/raw/ 目录下,Android 会自动将其作为资源处理。

布局文件 (activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">
    <Button
        android:id="@+id/btn_play"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="播放" />
    <Button
        android:id="@+id/btn_pause"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="暂停" />
    <Button
        android:id="@+id/btn_stop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="停止" />
</LinearLayout>

Java 代码 (MainActivity.java)

import androidx.appcompat.app.AppCompatActivity;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private MediaPlayer mediaPlayer;
    private Button btnPlay, btnPause, btnStop;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnPlay = findViewById(R.id.btn_play);
        btnPause = findViewById(R.id.btn_pause);
        btnStop = findViewById(R.id.btn_stop);
        btnPlay.setOnClickListener(this);
        btnPause.setOnClickListener(this);
        btnStop.setOnClickListener(this);
        // 1. 创建 MediaPlayer 对象 (Idle 状态)
        mediaPlayer = new MediaPlayer();
        try {
            // 2. 设置数据源 (Initialized 状态)
            // 注意:使用 R.raw.xxx 的方式
            mediaPlayer.setDataSource(this, R.raw.my_music);
            // 3. 准备播放器 (Prepared 状态)
            // prepare() 是同步的,会阻塞调用线程,不适合在主线程(UI线程)中使用
            // prepareAsync() 是异步的,推荐使用
            mediaPlayer.prepare();
            // 如果使用异步准备,需要监听 OnPreparedListener
            /*
            mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                    // 准备完成后,可以在这里开始播放
                    // mediaPlayer.start();
                }
            });
            mediaPlayer.prepareAsync();
            */
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(this, "初始化 MediaPlayer 失败", Toast.LENGTH_SHORT).show();
        }
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_play:
                if (mediaPlayer != null && !mediaPlayer.isPlaying()) {
                    // 4. 开始播放 (Started 状态)
                    mediaPlayer.start();
                    Toast.makeText(this, "开始播放", Toast.LENGTH_SHORT).show();
                }
                break;
            case R.id.btn_pause:
                if (mediaPlayer != null && mediaPlayer.isPlaying()) {
                    // 5. 暂停播放 (Paused 状态)
                    mediaPlayer.pause();
                    Toast.makeText(this, "暂停播放", Toast.LENGTH_SHORT).show();
                }
                break;
            case R.id.btn_stop:
                if (mediaPlayer != null) {
                    // 6. 停止播放 (Stopped -> Prepared 状态)
                    // 注意:stop() 后需要重新 prepare() 才能再次 start()
                    mediaPlayer.stop();
                    Toast.makeText(this, "停止播放", Toast.LENGTH_SHORT).show();
                    // 如果想再次播放,需要重新准备
                    // try {
                    //     mediaPlayer.prepare(); // 重新准备
                    //     mediaPlayer.start();
                    // } catch (IOException e) {
                    //     e.printStackTrace();
                    // }
                }
                break;
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 7. 释放资源 (End 状态)
        // 这是至关重要的一步,防止内存泄漏
        if (mediaPlayer != null) {
            mediaPlayer.release();
            mediaPlayer = null;
        }
    }
}

高级功能与最佳实践

异步准备 (prepareAsync)

在主线程中调用 prepare() 会导致界面卡顿(ANR - Application Not Responding)。强烈推荐使用 prepareAsync()

mediaPlayer.prepareAsync();
// 设置准备监听器
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
    @Override
    public void onPrepared(MediaPlayer mp) {
        // 当准备完成时,这个回调会在主线程被调用
        // 此时可以安全地调用 start()
        Toast.makeText(context, "准备完成,可以播放", Toast.LENGTH_SHORT).show();
        // mediaPlayer.start(); // 如果想自动播放
    }
});

错误处理

播放过程中可能会发生各种错误(如网络中断、文件损坏、格式不支持等),必须设置 OnErrorListener 来处理。

安卓mediaplayer
(图片来源网络,侵删)
mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        // 发生错误时,返回 true 表示我们已经处理了错误
        // 返回 false 则 MediaPlayer 会尝试自己处理(通常会进入错误状态)
        Toast.makeText(context, "播放错误: " + what + ", " + extra, Toast.LENGTH_LONG).show();
        // 通常在发生错误后,需要重置或释放 MediaPlayer
        // reset(); // 或者 release();
        return true;
    }
});

播放完成监听

当播放到文件末尾时,可以设置监听器来执行后续操作,如播放下一首。

mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
    @Override
    public void onCompletion(MediaPlayer mp) {
        Toast.makeText(context, "播放完成", Toast.LENGTH_SHORT).show();
        // 在这里执行播放下一首的逻辑
    }
});

播放控制

  • seekTo(int msec): 跳转到指定位置的毫秒数。
  • getCurrentPosition(): 获取当前播放位置的毫秒数。
  • getDuration(): 获取媒体总时长(毫秒)。
  • setVolume(float leftVolume, float rightVolume): 设置左右声道音量(0.0f 到 1.0f)。
  • setLooping(boolean looping): 设置是否循环播放。

播放视频

播放视频和音频的步骤基本相同,但需要额外设置一个 SurfaceHolder 来显示视频画面。

// 在布局中添加一个 SurfaceView
<SurfaceView
    android:id="@+id/surfaceView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
// 在代码中
SurfaceView surfaceView = findViewById(R.id.surfaceView);
SurfaceHolder holder = surfaceView.getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // Surface 创建成功后,设置显示
        mediaPlayer.setDisplay(holder);
    }
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // Surface 销毁时,必须清除显示
        mediaPlayer.setDisplay(null);
    }
});

重要注意事项

  1. 不要阻塞UI线程: 绝对不要在 onCreate, onStart 等生命周期方法中直接调用 prepare(),使用 prepareAsync() 或将准备工作放在后台线程(如 AsyncTask, Thread, ExecutorService)中。
  2. 资源释放 (release()): MediaPlayer 占用大量系统资源(如解码器、文件句柄),当 Activity 被销毁时(onDestroy),必须调用 release() 来释放这些资源,否则会导致严重的内存泄漏,甚至可能使其他应用无法使用媒体功能。
  3. 生命周期管理: 为了避免复杂的生命周期管理问题,推荐使用 MediaPlayer 的静态方法 create(),它会自动完成初始化、设置数据源和准备操作,并返回一个处于 Prepared 状态的 MediaPlayer 对象。
    // 简化版创建方式
    MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.my_music);
    // 此时可以直接调用 start()

    但请注意,create() 内部也是调用了 prepare(),它是在一个后台线程中执行的,所以不会阻塞UI线程。

  4. 异常处理: 所有 MediaPlayer 的方法都可能抛出 IllegalStateException,如果调用时对象的状态不正确,务必参考状态机图,在正确的状态下调用正确的方法。

替代方案:ExoPlayer

虽然 MediaPlayer 非常强大,但它也存在一些缺点:

  • 功能有限: 不支持 DASH, HLS 等现代流媒体协议。
  • 性能问题: 在某些设备上可能性能不佳,尤其是在处理高清视频时。
  • 扩展性差: 自定义和扩展功能比较困难。

Google 推出了 ExoPlayer 作为 MediaPlayer 的现代替代品。

ExoPlayer 的优点:

  • 高度可定制: 模块化设计,可以轻松替换组件(如渲染器、来源、解码器)。
  • 功能强大: 原生支持 DASH, HLS, SmoothStreaming, MP4, M4A, MP3, AAC, OGG 等格式。
  • 性能优异: 经过优化,性能通常优于 MediaPlayer
  • 活跃维护: 由 Google 团队持续维护和更新。

对于新的项目,尤其是需要播放网络视频、支持直播或对播放体验有较高要求的应用,强烈推荐直接使用 ExoPlayer

特性 MediaPlayer ExoPlayer
易用性 简单,几行代码即可使用 较复杂,需要更多配置
功能 基础播放功能强大 极其强大,支持现代流媒体协议
性能 一般,依赖设备硬件 优秀,高度优化
定制性 极高,完全模块化
适用场景 简单的音频/视频播放,播放本地文件或简单网络流 复杂的视频应用,直播,DASH/HLS播放,需要高度定制

建议:

  • 如果只是播放一个简单的背景音乐或本地视频,MediaPlayer 足够用且更简单。
  • 如果你的应用核心功能是视频播放,或者需要播放网络视频、直播,请直接选择 ExoPlayer
-- 展开阅读全文 --
头像
苹果6无法开机,究竟是什么原因?
« 上一篇 12-22
苹果5s详细参数配置有哪些?
下一篇 » 12-22

相关文章

取消
微信二维码
支付宝二维码

最近发表

标签列表

目录[+]