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)。
  • 动画(旋转)和音频同步播放,点击暂停时两者同时暂停。
  • 生命周期管理(onPauseonDestroy)确保资源释放。

播放视频

视频播放需要将 MediaPlayer 绑定到 SurfaceViewTextureView 以渲染画面。以下是一个使用 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:现代媒体播放。

注意事项

  • 资源释放:在 onPauseonDestroy 调用 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)。

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注