Android 系统提供了几种播放音频和视频的方式,其中最常用的就是 MediaPlayer,和其他功能组件一样都有很多第三方框架提供更加丰富完备的功能,但是基本用法和时序基本都是参照 MediaPlayer 来设计的,本节就来看看 MediaPlayer 的使用方法。
1. MediaPlayer 的状态
MediaPlayer 有一套完善的状态机,通常出现一些奇怪的报错或者 Crash 大概率就是状态流转出了问题,而市面上大多数的播放器也会遵循 Android 官方设计的这套状态机来实现。首先看看所有的状态:
Idle:
空闲态,刚创建或者调用了reset()之后的状态,此时不能进行播放
Initailized:
初始化态,仅仅设置了媒体源,但还未进行任何网络资源的拉取或者媒体流的解析,此时仍然不能播放
Preparing:
准备中,触发了媒体流的下载以及媒体流的解析,但均未完成,处于准备中,尚不能进行播放
Prepared:
准备好,已经将媒体资源拉取并解析完成,随时可以开始播放
Started:
播放态,在媒体资源准备好之后,调用了start()触发了媒体的播放,则进入视频 / 音频播放
Paused:
暂停态,这个很好理解,视频 / 音频播放暂停,此时可以随时调用start()继续播放回到Started状态
PlaybackCompleted:
播放结束态,视频 / 音频播放到结尾,自然结束
Stoped:
停止态,在播放或者暂停过程中主动调用stop()停止播放,注意它和暂停态不同,“Stoped”态不能直接回到播放态;它和
播放结束态也不同,“Stoped”一定是由开发者主动触发的
End:
释放态,播放器调用release()触发播放器资源的释放,此时播放器资源被回收将不能使用
Error:
错误态,如果由于某种原因 MediaPlayer 出现了错误,会触发 OnErrorListener.onError()事件,此时 MediaPlayer 即进入 Error 状态,及时捕捉并妥善处理这些错误是很重要的,可以帮助我们及时释放相关的软硬件资源,也可以改善用户体验。通过setOnErrorListener可以设置该监听器。如果MediaPlayer进入了Error状态,可以通过调用reset()来恢复,使得MediaPlayer重新返回到 Idle 状态。
下面可以对照着状态看看官方给的状态机流转图:
这个图非常经典,建议大家收藏此文章,今后使用 MediaPlayer 过程中出现任何问题都可以看看状态机是否出现异常。
2. MediaPlayer 常用 API
使用 MediaPlayer 的 API 之前一定要先熟悉熟悉再熟悉上一小节的状态机时序图,否则盲目使用 API 会出现很多状态错误的异常发生。
setDataSource(FileDescriptor fd):
设置音频 / 视频资源地址
- isPlaying():
- 判断当前视频 / 音频是否正在播放
- seekTo(position):
- 直接跳转到视频 / 音频的某个时间点
- getCurrentPosition():
- 获取当前的播放进度
- getDuration():
- 获取媒体文件的总时长
- reset():
- 重置 MediaPlayer,此后会进入 Idle 态
- release():
- 释放播放器,在不使用的时候调用,节省系统资源
- setVolume(float leftVolume, float rightVolume):
- 设置媒体音量
- selectTrack(int index):
- 设置媒体轨道
- getTrackInfo():
- 返回一个数组,包含所有的轨道信息
3. MediaPlayer 使用步骤
Android 系统为 MediaPlayer 适配了多种场景,也为不同的场景提供了不同的使用方式,但大体上有几个步骤:
- 创建播放器
创建通常有两种方法 - 第一种方式直接在创建的时候传入媒体流地址,而第二种仅仅是创建一个“Idle”态的闲置播放器
- 设置媒体源
Android 提供下面的方法设置媒体数据源
mediaPlayer.setDataSource(www.mc.com/mc.mp3);
如果在创建 MediaPlayer 的时候就设置了媒体文件,那么可以跳过这一步
- 开始播放
在设置好媒体源地址之后,就可以开始播放了:
mediaPlayer.start();
- 播放控制
在播放过程中可以调用一些控制 API 进行播放状态的控制 - 结束播放
调用stop()
可以结束播放,并且记得在不用的时候还要调用release()
4. 播放器使用示例
本节来用 MediaPlayer 实现一个简单的播放器,并通过几个 API 来实现基本的播放控制。
4.1 MediaPlayer 的使用
首先看看 MainActivity:
package com.emercy.myapplication; import android.app.Activity; import android.media.MediaPlayer; import android.os.Bundle; import android.os.Handler; import android.view.View; import android.widget.Button; import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; import java.util.concurrent.TimeUnit; public class MainActivity extends Activity { private Button b1, b2, b3, b4; private MediaPlayer mediaPlayer; private double startTime = 0; private double finalTime = 0; private Handler myHandler = new Handler(); private int forwardTime = 5000; private int backwardTime = 5000; private SeekBar seekbar; private TextView tx1, tx2, tx3; public static int oneTimeOnly = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); b1 = (Button) findViewById(R.id.button); b2 = (Button) findViewById(R.id.button2); b3 = (Button) findViewById(R.id.button3); b4 = (Button) findViewById(R.id.button4); tx1 = (TextView) findViewById(R.id.textView2); tx2 = (TextView) findViewById(R.id.textView3); tx3 = (TextView) findViewById(R.id.textView4); mediaPlayer = MediaPlayer.create(this, R.raw.video); seekbar = (SeekBar) findViewById(R.id.seekBar); seekbar.setClickable(false); b2.setEnabled(false); b1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int temp = (int) startTime; if ((temp + forwardTime) <= finalTime) { startTime = startTime + forwardTime; mediaPlayer.seekTo((int) startTime); Toast.makeText(getApplicationContext(), "前进5秒", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(getApplicationContext(), "前方还剩不到5秒", Toast.LENGTH_SHORT).show(); } } }); b2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getApplicationContext(), "Pausing sound", Toast.LENGTH_SHORT).show(); mediaPlayer.pause(); b2.setEnabled(false); b3.setEnabled(true); } }); b3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getApplicationContext(), "音频播放", Toast.LENGTH_SHORT).show(); mediaPlayer.start(); finalTime = mediaPlayer.getDuration(); startTime = mediaPlayer.getCurrentPosition(); if (oneTimeOnly == 0) { seekbar.setMax((int) finalTime); oneTimeOnly = 1; } tx2.setText(String.format("%d min, %d sec", TimeUnit.MILLISECONDS.toMinutes((long) finalTime), TimeUnit.MILLISECONDS.toSeconds((long) finalTime) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes((long) finalTime))) ); tx1.setText(String.format("%d min, %d sec", TimeUnit.MILLISECONDS.toMinutes((long) startTime), TimeUnit.MILLISECONDS.toSeconds((long) startTime) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes((long) startTime))) ); seekbar.setProgress((int) startTime); myHandler.postDelayed(UpdateSongTime, 100); b2.setEnabled(true); b3.setEnabled(false); } }); b4.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int temp = (int) startTime; if ((temp - backwardTime) > 0) { startTime = startTime - backwardTime; mediaPlayer.seekTo((int) startTime); Toast.makeText(getApplicationContext(), "后退5秒", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(getApplicationContext(), "后方还剩不到5秒", Toast.LENGTH_SHORT).show(); } } }); } private Runnable UpdateSongTime = new Runnable() { public void run() { startTime = mediaPlayer.getCurrentPosition(); tx1.setText(String.format("%d min, %d sec", TimeUnit.MILLISECONDS.toMinutes((long) startTime), TimeUnit.MILLISECONDS.toSeconds((long) startTime) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS. toMinutes((long) startTime))) ); seekbar.setProgress((int) startTime); myHandler.postDelayed(this, 100); } }; }
通过MediaPlayer.create(this, R.raw.video)创建一个 MediaPlayer 对象并初始化媒体流地址,然后分别设置几个控制 Button 的监听事件,实现播放器的前进、后退、播放、暂停操作,当中还有一个UpdateSongTime的 Runnable 变量,用来每隔 100 毫秒更新一次播放进度,实现播放进度的同步刷新。
4.2 布局文件
布局文件就按照片一般播放器的摆放方式就可以,上面通常是页面的主题和描述,下方就是进度条、歌曲名称时长等等。
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="30dp" tools:context=".MainActivity"> <TextView android:id="@+id/textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:text="音乐播放器" android:textSize="35dp" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/textview" android:layout_centerHorizontal="true" android:text="Android教程" android:textColor="#ff7aff24" android:textSize="35dp" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentStart="true" android:layout_alignParentBottom="true" android:text="前进" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_toRightOf="@id/button" android:text="暂停" /> <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignTop="@+id/button2" android:layout_toEndOf="@+id/button2" android:text="播放" /> <Button android:id="@+id/button4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignTop="@+id/button3" android:layout_toEndOf="@+id/button3" android:layout_toRightOf="@+id/button3" android:text="后退" /> <SeekBar android:id="@+id/seekBar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@+id/button" android:layout_alignStart="@+id/textview" android:layout_alignEnd="@+id/textview" /> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@+id/seekBar" android:text="超哥音乐选集" android:textAppearance="?android:attr/textAppearanceSmall" /> <TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@+id/seekBar" android:layout_alignEnd="@+id/button4" android:text="02:23" android:textAppearance="?android:attr/textAppearanceSmall" /> <TextView android:id="@+id/textView4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/textView2" android:layout_alignBottom="@+id/textView2" android:layout_centerHorizontal="true" android:text="超哥吉他" android:textAppearance="?android:attr/textAppearanceMedium" /> </RelativeLayout>
当然也可以按照你自己的设计去摆放播放器的布局样式.
5. 小结
本节学习了 Android 内置的最常用的播放器,最关键的是要熟悉它的状态机时序图,因为可能在实际开发中你会使用更强大的第三方播放器,但是基本时序仍然是参照 Android 官方设计的,在了解时序之后就可以按照本节的步骤使用各种 API 来播放音视频了。在掌握了本节内容之后,如果感兴趣也可以研究研究市面上常用的开源播放器,可以让你对播放器有更深的理解。