MediaPlayer播放音频与视频
使用 MediaPlayer 播放音频与视频
MediaPlayer
是 Android 提供的多功能媒体播放类,用于播放音频和视频文件,支持本地资源、网络流和文件路径。它适合较长的音频或视频播放(如音乐、电影),相比 SoundPool
(适合短促音效),MediaPlayer
提供更丰富的控制功能,如播放进度、循环、暂停等。本文将详细介绍 MediaPlayer
的使用方法,包括初始化、播放音频、播放视频、状态管理及错误处理,结合 Android 动画合集的背景,可用于为动画添加背景音乐或视频效果。
MediaPlayer 的作用与原理
- 作用:
MediaPlayer
是一个强大的媒体播放工具,支持多种格式的音频(如 MP3、AAC、WAV)和视频(如 MP4、3GP),可用于本地文件、assets、raw 资源或网络流。它支持播放控制(如暂停、快进)、状态监听和表面绑定(视频)。 - 原理:
MediaPlayer
基于 Android 的媒体框架,通过解码器处理媒体数据,输出到扬声器(音频)或SurfaceView
(视频)。它通过状态机管理播放流程(如 Idle、Prepared、Started),需要正确处理状态转换以避免错误。 - 应用场景:
- 播放背景音乐(如动画或游戏配乐)。
- 播放视频(如开屏视频或教程)。
- 动画伴随音效(如渐入动画的背景音)。
- 在线流媒体(如网络音乐或视频)。
MediaPlayer 的核心方法与状态
MediaPlayer
使用状态机管理生命周期,常见状态包括:
- Idle:创建后或重置后的初始状态。
- Initialized:设置数据源后。
- Prepared:准备完成,可播放。
- Started:播放中。
- Paused:暂停。
- Stopped:停止。
- Completed:播放结束。
关键方法:
方法 | 描述 | 用法示例 |
---|---|---|
setDataSource(String) | 设置媒体数据源(文件路径、URL 等)。 | mediaPlayer.setDataSource("file:///sdcard/music.mp3"); |
prepare() | 同步准备媒体(阻塞主线程)。 | mediaPlayer.prepare(); |
prepareAsync() | 异步准备媒体(推荐,避免阻塞)。 | mediaPlayer.prepareAsync(); |
start() | 开始或恢复播放。 | mediaPlayer.start(); |
pause() | 暂停播放。 | mediaPlayer.pause(); |
stop() | 停止播放,需重新 prepare。 | mediaPlayer.stop(); |
release() | 释放资源,销毁 MediaPlayer。 | mediaPlayer.release(); |
seekTo(int) | 跳转到指定时间(毫秒)。 | mediaPlayer.seekTo(5000); |
setLooping(boolean) | 设置是否循环播放。 | mediaPlayer.setLooping(true); |
setOnPreparedListener | 监听准备完成事件。 | mediaPlayer.setOnPreparedListener(mp -> mp.start()); |
setOnCompletionListener | 监听播放完成事件。 | mediaPlayer.setOnCompletionListener(mp -> Log.d("Media", "Done")); |
setSurface(Surface) | 设置视频输出的 Surface(视频播放)。 | mediaPlayer.setSurface(surfaceView.getHolder().getSurface()); |
播放音频
以下是一个播放音频的完整示例,结合属性动画触发背景音乐。
import android.animation.ObjectAnimator;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;
public class AudioPlayerActivity extends AppCompatActivity {
private MediaPlayer mediaPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_player);
ImageView imageView = findViewById(R.id.image_view);
// 初始化 MediaPlayer
mediaPlayer = MediaPlayer.create(this, R.raw.background_music); // 从 res/raw 加载
mediaPlayer.setLooping(true); // 设置循环播放
mediaPlayer.setOnPreparedListener(mp -> mp.start()); // 准备完成后自动播放
// 属性动画
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "rotation", 0f, 360f);
animator.setDuration(2000);
animator.setRepeatCount(ObjectAnimator.INFINITE);
// 按钮控制
findViewById(R.id.start_button).setOnClickListener(v -> {
if (!mediaPlayer.isPlaying()) {
mediaPlayer.start();
animator.start();
}
});
findViewById(R.id.pause_button).setOnClickListener(v -> {
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause();
animator.pause();
}
});
}
@Override
protected void onPause() {
super.onPause();
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
}
}
}
- 布局 XML(res/layout/activity_audio_player.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">
<ImageView
android:id="@+id/image_view"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_launcher_foreground" />
<Button
android:id="@+id/start_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Play" />
<Button
android:id="@+id/pause_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pause" />
</LinearLayout>
- 说明:
- 使用
MediaPlayer.create(context, R.raw.background_music)
快速创建并加载音频(自动调用 prepare)。 - 动画(旋转)和音频同步播放,点击暂停时两者同时暂停。
- 生命周期管理(
onPause
、onDestroy
)确保资源释放。
播放视频
视频播放需要将 MediaPlayer
绑定到 SurfaceView
或 TextureView
以渲染画面。以下是一个使用 SurfaceView
播放视频的示例。
import android.media.MediaPlayer;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import androidx.appcompat.app.AppCompatActivity;
public class VideoPlayerActivity extends AppCompatActivity {
private MediaPlayer mediaPlayer;
private SurfaceView surfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_player);
surfaceView = findViewById(R.id.surface_view);
SurfaceHolder holder = surfaceView.getHolder();
// 初始化 MediaPlayer
mediaPlayer = new MediaPlayer();
holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
mediaPlayer.setDisplay(holder); // 绑定 Surface
try {
mediaPlayer.setDataSource("file:///sdcard/video.mp4"); // 或网络 URL
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(mp -> mp.start());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.stop();
}
}
});
// 按钮控制
findViewById(R.id.start_button).setOnClickListener(v -> {
if (!mediaPlayer.isPlaying()) {
mediaPlayer.start();
}
});
findViewById(R.id.pause_button).setOnClickListener(v -> {
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
});
}
@Override
protected void onPause() {
super.onPause();
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
}
}
}
- 布局 XML(res/layout/activity_video_player.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">
<SurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="200dp" />
<Button
android:id="@+id/start_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Play" />
<Button
android:id="@+id/pause_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pause" />
</LinearLayout>
- 说明:
SurfaceHolder
管理SurfaceView
的生命周期,setDisplay
绑定视频输出。- 使用
prepareAsync()
异步准备,避免阻塞主线程。 - 确保视频文件(如
/sdcard/video.mp4
)存在,或替换为网络 URL。
错误处理与监听器
MediaPlayer
可能因格式不支持、网络中断等原因抛出异常,需妥善处理。
- 错误监听器:
mediaPlayer.setOnErrorListener((mp, what, extra) -> {
Log.e("MediaPlayer", "Error: " + what + ", Extra: " + extra);
mp.reset(); // 重置到 Idle 状态
return true; // 已处理
});
- 常见错误:
what=MediaPlayer.MEDIA_ERROR_UNKNOWN
:未知错误。what=MediaPlayer.MEDIA_ERROR_SERVER_DIED
:服务端错误。- 检查
extra
代码获取更多信息。 - 其他监听器:
setOnCompletionListener
:播放完成。setOnBufferingUpdateListener
:网络流缓冲进度。setOnVideoSizeChangedListener
:视频尺寸变化。
优缺点
- 优点:
- 支持多种音频/视频格式(MP3、AAC、MP4、3GP 等)。
- 提供丰富控制(如进度、音量、循环)。
- 支持本地和网络流媒体。
- 缺点:
- 内存和 CPU 占用较高(相比 SoundPool)。
- 延迟较高,不适合短促音效。
- 状态管理复杂,需小心避免 IllegalStateException。
- 替代方案:
- SoundPool:短促音效。
- ExoPlayer:更现代的媒体播放库,支持高级功能(如自适应流)。
- VideoView:简化的视频播放组件,封装 MediaPlayer 和 SurfaceView。
- Jetpack Compose Media3:现代媒体播放。
注意事项
- 资源释放:在
onPause
或onDestroy
调用release()
,避免内存泄漏。 - 状态管理:严格遵循状态机(如
prepare
后才能start
),否则抛出IllegalStateException
。 - 文件格式:确保媒体格式受设备支持(常见格式:MP3、WAV、MP4)。
- 权限:
- 网络播放需
<uses-permission android:name="android.permission.INTERNET" />
。 - 本地文件需存储权限(API 23+)。
- 性能:异步准备(
prepareAsync
)避免阻塞主线程;视频播放注意 Surface 生命周期。 - 调试:使用
OnErrorListener
和 Log 监控错误,检查状态转换。
扩展:Jetpack Compose 中的实现
在 Jetpack Compose 中,MediaPlayer
可直接使用,或通过 Media3
(ExoPlayer)实现更现代的播放。以下是结合动画的音频播放示例:
import androidx.compose.foundation.Image
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import android.media.MediaPlayer
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.clickable
@Composable
fun MediaAnimation() {
val context = LocalContext.current
val mediaPlayer = remember {
MediaPlayer.create(context, R.raw.background_music).apply {
isLooping = true
}
}
var isPlaying by remember { mutableStateOf(false) }
val rotation by animateFloatAsState(
targetValue = if (isPlaying) 360f else 0f,
animationSpec = tween(durationMillis = 2000)
)
DisposableEffect(Unit) {
onDispose {
mediaPlayer.release()
}
}
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "Media Animation",
modifier = Modifier
.size(100.dp)
.graphicsLayer(rotationZ = rotation)
.clickable {
isPlaying = !isPlaying
if (isPlaying) mediaPlayer.start() else mediaPlayer.pause()
}
)
}
- 说明:
MediaPlayer.create
简化初始化。DisposableEffect
确保资源释放。- 点击触发旋转动画和音频播放/暂停。
示例:动画触发背景音乐
为按钮点击添加背景音乐:
MediaPlayer mediaPlayer = MediaPlayer.create(this, R.raw.click_sound);
Button button = findViewById(R.id.start_button);
button.setOnClickListener(v -> {
if (!mediaPlayer.isPlaying()) {
mediaPlayer.start();
v.animate().scaleX(1.2f).scaleY(1.2f).setDuration(200).start();
}
});
如果需要更复杂功能(如进度条、视频全屏播放、ExoPlayer 迁移)、与动画的更深入结合、或 Compose 中的高级媒体播放,请告诉我!后续可探讨 Android 动画合集的其他部分(如过渡动画、Lottie)。