4. MediaPlayer播放本地音频文件

任务说明

本案例使用 Android 的 MediaPlayer 播放应用内部存储的本地音频文件(MP3/AAC),实现播放、暂停、停止三个基本控制,并展示播放状态与时长信息。音频文件放在 app/src/main/assets/ 目录下,通过 AssetFileDescriptor 打开,避免路径权限问题。

环境与依赖

  • Android Studio,minSdkVersion 21 及以上。
  • 无需第三方库,仅使用 android.media.MediaPlayer。
  • 准备一个音频文件,命名为 sample.mp3(或 sample.aac),放入 app/src/main/assets/。

核心实现

用一个 MediaPlayer 实例管理生命周期:prepare() 后进入 Prepared 状态,start() 播放,pause() 暂停,stop() 停止并回到 Idle 状态。停止后需重新 prepare() 才能再次播放,因此把“准备”封装成独立方法。播放结束通过 OnCompletionListener 复位按钮状态。

Idle Prepared Started Paused prepare() start() pause() start() stop() → Idle

完整代码

布局文件 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:orientation="vertical"
    android:gravity="center"
    android:padding="24dp">

    <TextView
        android:id="@+id/tvStatus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:text="未播放" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginTop="24dp">

        <Button
            android:id="@+id/btnPlay"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="播放" />

        <Button
            android:id="@+id/btnPause"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="暂停" />

        <Button
            android:id="@+id/btnStop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="停止" />
    </LinearLayout>
</LinearLayout>

主逻辑 MainActivity.java:

package com.example.audioplayer;

import android.content.res.AssetFileDescriptor;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    private MediaPlayer player;
    private TextView tvStatus;
    private boolean prepared = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tvStatus = findViewById(R.id.tvStatus);
        Button btnPlay = findViewById(R.id.btnPlay);
        Button btnPause = findViewById(R.id.btnPause);
        Button btnStop = findViewById(R.id.btnStop);

        player = new MediaPlayer();
        player.setOnCompletionListener(mp -> {
            prepared = false;
            tvStatus.setText("播放结束");
        });

        btnPlay.setOnClickListener(v -> {
            if (!prepared) {
                if (!preparePlayer()) return;
            }
            player.start();
            tvStatus.setText("播放中,时长 " + player.getDuration() + " ms");
        });

        btnPause.setOnClickListener(v -> {
            if (prepared && player.isPlaying()) {
                player.pause();
                tvStatus.setText("已暂停");
            }
        });

        btnStop.setOnClickListener(v -> {
            if (prepared) {
                player.stop();
                prepared = false;
                tvStatus.setText("已停止");
            }
        });
    }

    private boolean preparePlayer() {
        try {
            AssetFileDescriptor afd = getAssets().openFd("sample.mp3");
            player.reset();
            player.setDataSource(afd.getFileDescriptor(),
                    afd.getStartOffset(), afd.getLength());
            afd.close();
            player.prepare();
            prepared = true;
            return true;
        } catch (IOException e) {
            tvStatus.setText("加载失败:" + e.getMessage());
            return false;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (player != null) {
            player.release();
            player = null;
        }
    }
}

运行与验证

  1. 把音频文件放入 app/src/main/assets/sample.mp3,若用 AAC 则改名为 sample.aac 并同步修改代码中的文件名。
  2. 连接设备或启动模拟器,点击 Run 安装运行。
  3. 点击“播放”:应听到音频,状态栏显示“播放中,时长 xxx ms”。
  4. 点击“暂停”:声音停止,状态显示“已暂停”;再次点击“播放”应从暂停处继续。
  5. 点击“停止”:声音停止,状态显示“已停止”;再次点击“播放”应从头重新播放。
  6. 播放到结尾时状态自动变为“播放结束”。

通过条件:三个按钮行为与上述一致,无崩溃,退出页面后音频停止(onDestroy 中已 release)。

常见问题

  • 若报 IOException,检查 assets 文件名与代码是否一致,且文件确实被打包进 APK。
  • 停止后直接调用 start() 会抛 IllegalStateException,因此代码用 prepared 标志强制重新 prepare()。
  • 播放长音频建议放到后台线程 prepareAsync(),本案例音频较短,用同步 prepare() 即可。