Android 多媒体 —— 播放音视频
播放音频
在 Android 中播放音频一般时通过 MediaPlayer 去实现的,它提供了非常全面的方法去控制音频的播放。
setDataSource()
:设置音频数据源,设置方式较多prepare()
:执行准备工作。可能涉及获取和解码媒体数据,需要很长时间来执行,主线程中应使用prepareAsync()
方法prepareAsync()
:异步执行准备工作,需实现 MediaPlayer.OnPreparedListener,以便在准备工作完成后获得通知并开始播放。start()
:开始播放pause()
:暂停播放reset()
:重置 MediaPlayer 对象为 new 状态seekto(int msec)
:指定播放的位置(以毫秒为单位的时间)stop()
:停止,调用该方法后该 MediaPlayer 对象无法再播放音频release()
:释放 MediaPlayer 对象isPlaying()
:是否正在播放getDuration()
:获取音频总时长getCurrentPosition()
:获取当前的播放位置setLooping(boolean looping)
:设置是否循环播放setVolume(float leftVolume, float rightVolume)
:设置音量setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener)
: 网络流媒体的缓冲监听setOnCompletionListener(MediaPlayer.OnCompletionListener listener)
: 网络流媒体播放结束监听setOnErrorListener(MediaPlayer.OnErrorListener listener)
: 设置错误信息监听
这里不作详细介绍,请参考代码和注释。
// 简单实现带滑动进度条的音频播放器,布局自行设计
public class MediaPlayerActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener, View.OnClickListener {
private TextView musicName;
private TextView total;
private TextView current;
private SeekBar seekBar;
private Button startMusic;
private Button pauseMusic;
private Button stopMusic;
private Button resetMusic;
private MediaPlayer mediaPlayer = new MediaPlayer();
private Timer timer;
private boolean isSeekBarChanging; // 互斥变量,防止进度条与定时器冲突。
private int currentPosition; // 当前音乐播放的进度
private SimpleDateFormat sd = new SimpleDateFormat("mm:ss");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_media_player);
initView();
initMediaPlayer();
}
private void initMediaPlayer() {
Uri uri = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.tell_me_a_story);
try {
mediaPlayer.setDataSource(this, uri);
// res/raw 下的资源 tell_me_a_story.mp3:可以通过 MediaPlayer.create() 方法便捷创建对象,且无需手动 setDataSource()
// mediaPlayer = MediaPlayer.create(this, R.raw.tell_me_a_story);
// 本地文件路径:
// mediaPlayer.setDataSource("/sdcard/tell_me_a_story.mp3");
// 网络 URL 文件:
// mediaPlayer.setDataSource("http://www.xxx.com/music/tell_me_a_story.mp3");
mediaPlayer.prepare(); // 一定是先执行 prepare() 才能执行其他操作,这里仅作演示不使用 prepareAsync(),
int duration = mediaPlayer.getDuration(); // 获取音频总时长
seekBar.setMax(duration); // 设置拖动条最大长度
musicName.setText("tell_me_a_story");
total.setText(sd.format(duration));
} catch (IOException e) {
e.printStackTrace();
}
}
private void initView() { /**...*/ }
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// 让当前时间进度跟随滑动变化
current.setText(sd.format(progress));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// 滑动时暂停后台定时器
isSeekBarChanging = true;
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// 滑动结束后,重新设置值
isSeekBarChanging = false;
currentPosition = seekBar.getProgress();
mediaPlayer.seekTo(currentPosition);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_start_music:
if (! mediaPlayer.isPlaying()) {
mediaPlayer.start();
mediaPlayer.seekTo(currentPosition); // 从指定位置播放
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
if (! isSeekBarChanging) {
currentPosition = mediaPlayer.getCurrentPosition();
seekBar.setProgress(currentPosition);
runOnUiThread(new Runnable() {
@Override
public void run() {
current.setText(sd.format(currentPosition));
}
});
}
}
}, 0, 50);
}
break;
case R.id.btn_pause_music:
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
break;
case R.id.btn_stop_music:
if (mediaPlayer.isPlaying()) {
// 调用 stop() 方法后该 MediaPlayer 对象无法再播放音频
// 一般在 onDestroy() 方法中调用 stop() 和 release()
// mediaPlayer.stop();
}
break;
case R.id.btn_reset_music:
if (mediaPlayer.isPlaying()) {
mediaPlayer.reset(); // 重置为刚刚创建的状态
initMediaPlayer(); // 需要重新对 MediaPlayer 信息进行初始化
}
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
isSeekBarChanging = true;
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
if (timer != null){
timer.cancel();
timer = null;
}
}
}
播放视频
MediaPlayer 主要用于播放音频,事实上也可以借助 SurfaceView 来播放视频。另外 Android 还提供了一个可以播放视频的 VideoView 控件。
使用 MediaPlayer 播放视频
使用起来与播放音频区别不大,只是还需要对 SurfaceView 进行初始化。当然,也需要在布局文件中添加 <SurfaceView>
控件标签。
<SurfaceView
android:id="@+id/sfv_video"
android:layout_width="match_parent"
android:layout_height="220dp" />
public class MediaPlayerActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener, View.OnClickListener, SurfaceHolder.Callback {
// ...
private MediaPlayer mediaPlayer = new MediaPlayer();
private SurfaceView surfaceView;
private SurfaceHolder surfaceHolder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_media_player);
initView();
initMediaPlayer();
}
private void initView() {
// ...
// 初始化 SurfaceHolder 类,SurfaceView 的控制器
surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(this); // 需要实现回调方法
surfaceHolder.setFixedSize(320, 220); // 设置分辨率,不设置则为视频默认分辨率
}
private void initMediaPlayer() { /**...*/ }
@Override
public void onClick(View v) { /**...*/ }
@Override
protected void onDestroy() { /**...*/ }
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
// 设置音频流类型
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
// 设置视频显示在 SurfaceView 上
mediaPlayer.setDisplay(holder); // 这里的 holder 与 surfaceHolder 是同一对象
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
}
}
使用 VideoView 播放视频
VideoView 继承自 SurfaceView,里面封装了一个 MediaPlayer 用于具体的播放业务,并自带了一个简单的控制界面 MediaController。
与 MediaPlayer 配合 SurfaceView 播放视频不同,VideoView 播放之前无需编码装载视频,它会在 start() 开始播放的时候自动装载视频,并且 VideoView 在使用完之后,无需编码回收资源。
VideoView 的功能比较简单,非常适用于那些只单纯地播放视频的场景,如不断循环播放的广告视频。不适用于交互性多的视频播放场景,像调节亮度、调节音量、双击暂停等交互逻辑,VideoView 是无法实现的。
setVideoPath(String path)
:以文件路径的方式设置 VideoView 播放的视频源setVideoURI(Uri uri)
:以 Uri 的方式设置视频源,可以是网络 Uri 或本地 Uristart()
:开始或继续播放pause()
:暂停播放resume()
:将视频从头开始播放seekto(int msec)
:从指定的位置开始播放视频(以毫秒为单位的时间)isPlaying()
:是否正在播放getDuration()
:获取视频总时长getCurrentPosition()
:获取当前的播放位置setMediaController(MediaController controller)
:设置 MediaController 控制器setOnCompletionListener(MediaPlayer.onCompletionListener l)
:监听播放完成的事件setOnErrorListener(MediaPlayer.OnErrorListener l)
:监听播放发生错误时候的事件setOnPreparedListener(MediaPlayer.OnPreparedListener l)
:监听视频装载完成的事件
下面是 VideoView 最简单使用,MediaController 暂不介绍:
-
在布局文件中添加
<VideoView>
控件标签。<VideoView android:id="@+id/video_view" android:layout_width="match_parent" android:layout_height="220dp" />
-
设置视频源,设置按钮点击事件控制视频播放暂停。
public class VideoViewActivity extends AppCompatActivity implements View.OnClickListener { private VideoView videoView; private Button startVideoView; private Button pauseVideoView; private Button resumeVideoView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_video_view); initView(); initVideoPath(); } private void initView() { /**...*/ } private void initVideoPath() { // 根据文件路径播放 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { videoView.setVideoPath(Environment.getExternalStorageDirectory() + "/test.mp4"); } // 读取放在 raw 目录下的文件 videoView.setVideoURI(Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.test)); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_start_video_view: if (! videoView.isPlaying()) { videoView.start(); } break; case R.id.btn_pause_video_view: if (videoView.isPlaying()) { videoView.pause(); } break; case R.id.btn_resume_video_view: if (videoView.isPlaying()) { videoView.resume(); } break; } } @Override protected void onDestroy() { super.onDestroy(); if (videoView != null) { videoView.suspend(); // 释放 VideoView } } }