文章目录
- 一丶MediaPlayer 简介
- 二丶实现音乐播放器
- 三丶在 Service 中使用 MediaPlayer
- 四丶总结
一丶MediaPlayer 简介
Android 多媒体框架支持播放各种常见媒体类型,以便您轻松地将音频、视频和图片集成到应用中。您可以使用 MediaPlayer API,播放存储在应用资源(原始资源)内的媒体文件、文件系统中的独立文件或者通过网络连接获得的数据流中的音频或视频。
基本用法请参考:官网:MediaPlayer 概览
它支持多种不同的媒体源 ,例如:
- 本地资源
- 内部 URI,例如您可能从内容解析器那获取的 URI
- 外部网址(流式传输)
在编写与 MediaPlayer 对象互动的代码时,请始终牢记该状态图。
二丶实现音乐播放器
效果图:
在写代码之前,我先告诉你们一个坑点,我在实现这个功能的时候,在导入raw文件时遇到了一个坑,找不到这个文件夹,如果你们也遇到了这种情况,参考我之前写的一篇博客,即可解决问题:
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button btnStart,btnStop,btnPause,btnReplay; // 播放器按钮
private SeekBar seekBar; // 进度条
private MediaPlayer mediaPlayer = null; // 音乐播放控制对象,可以操控暂停、停止、播放、重置等等
private Object obj = new Object(); // 对象锁,播放线程暂停时,让进度条线程进入等待状态
private Thread seekThread; // 线程
private boolean isRun = false; // 进度条线程控制
private boolean suspended = false; // 进度条线程等待状态控制
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView(); // 初始化控件
initJian(); // 添加监听事件
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { // 进度条的监听事件
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
pause(); // 进度条开始前,调用 pause()
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) { // 进度条开始后,调用 pause()
if (mediaPlayer != null && !mediaPlayer.isPlaying()){
int progress2 = seekBar.getProgress();
int currentPosition2 = mediaPlayer.getDuration()*progress2/100;
continuePlay(progress2,currentPosition2);
}
}
});
}
private void initJian() {
btnStart.setOnClickListener(this);
btnStop.setOnClickListener(this);
btnReplay.setOnClickListener(this);
btnPause.setOnClickListener(this);
}
private void initView() {
btnStart = findViewById(R.id.btn_Start);
btnStop = findViewById(R.id.btn_Stop);
btnPause = findViewById(R.id.btn_Pause);
btnReplay = findViewById(R.id.btn_Replay);
seekBar = findViewById(R.id.seekBar);
}
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_Start: // 播放
if (mediaPlayer == null){ // 如果没有暂停
play(); // 调用 play 方法进行播放
}else{
if (!mediaPlayer.isPlaying()){ // 如果处于暂停状态
int progress = seekBar.getProgress(); // 得到 SeekBar 的进度
int currentPosition = mediaPlayer.getCurrentPosition(); // 获取当前位置
continuePlay(progress,currentPosition); // 在从暂停状态恢复播放时使用,除了继续播放音乐,还需要唤醒等待中的进度条绘制线程
}
}
break;
case R.id.btn_Pause: // 暂停
pause();
break;
case R.id.btn_Replay: // 停止
if (mediaPlayer == null){ // 播放器对象还未创建或者已经销毁
play();
}else{
if (!mediaPlayer.isPlaying()){ // 暂停状态
continuePlay(0,0); // 进度条为0,第二个参数是:当前播放的位置,因为停止了,所以回到0
}else{ // 正在播放状态不需要唤醒线程的操作,并且这种情况是点了一次停止以后再点停止就会变成播放的作用
mediaPlayer.seekTo(0); // 从0开始播放
mediaPlayer.start(); // 开始播放
}
}
break;
case R.id.btn_Stop: // 重播
stop(); // 这里重播相当于停止
break;
}
}
/* 线程用来根据音乐播放进度绘制进度条 */
class SeekThread extends Thread{
int duration = mediaPlayer.getDuration(); //当前音乐总长度
int currentPosition = 0;
public void run(){
while (isRun){
currentPosition = mediaPlayer.getCurrentPosition(); // 获取当前音乐播放到了哪里
seekBar.setProgress(currentPosition*100 / duration);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
while (suspended){
try {
obj.wait(); // 音乐暂停时让进度条线程也暂停
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
/* 初始化播放,一个是音乐播放,一个线程控制的进度条绘制 */
@RequiresApi(api = Build.VERSION_CODES.M)
private void play() {
mediaPlayer = MediaPlayer.create(MainActivity.this,R.raw.music_bgw); // 直接理解为拿到音频资源文件就行
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { // 监听播放结束事件(停止、暂停、播放完了一首音乐)
@Override
public void onCompletion(MediaPlayer mp) {
if (mediaPlayer != null){ // 如果是暂停的情况
stop(); // 调用暂停方法
}
}
});
isRun = true; // 进度条线程控制,ture为暂停进度条线程的绘制
seekThread = new SeekThread(); // 实例化一个线程对象,开始发挥作用
mediaPlayer.start(); // 开始播放音乐
seekThread.start(); // 启动线程
}
private void stop() {
if (mediaPlayer != null){ // 只要有资源
seekBar.setProgress(0); // 进度条回到0的位置
isRun = false; // 线程
mediaPlayer.stop(); // 停止播放音乐
mediaPlayer.release(); // 释放资源
mediaPlayer = null; // 销毁音乐对象
seekThread = null; // 销毁线程
}
}
private void pause() {
if (mediaPlayer != null && mediaPlayer.isPlaying()){ // 如果音乐对象是有资源 并且 音乐正在播放的状态下
mediaPlayer.pause(); // 暂停
suspended = true; // 进度条线程等待状态控制
}
}
// 在从暂停状态恢复播放时使用,除了继续播放音乐,还需要唤醒等待中的进度条绘制线程
private void continuePlay(int progress, int currentPosition) {
mediaPlayer.seekTo(currentPosition); // 跳转到对应时间进行播放
mediaPlayer.start(); // 开始(暂停也是一种开始,跟播放一样,只是暂停是要回到之前的位置继续播放)
seekBar.setProgress(progress); // 设置回到之前的位置,这个progress是之前播放到的进度!
suspended = false; // 进度条线程等待状态控制
synchronized (obj){ // 对象锁,播放线程暂停时,让进度条线程进入等待状态
obj.notify(); // 唤醒线程,开始绘制进度条
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<SeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"/>
<LinearLayout
android:id="@+id/ly_main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="200dp">
<Button
android:id="@+id/btn_Start"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="18dp"
android:text="播放"/>
<Button
android:id="@+id/btn_Stop"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="18dp"
android:text="重播"/>
<Button
android:id="@+id/btn_Pause"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="18dp"
android:text="暂停"/>
<Button
android:id="@+id/btn_Replay"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="18dp"
android:text="停止"/>
</LinearLayout>
</LinearLayout>
三丶在 Service 中使用 MediaPlayer
涉及四大知识点
- 异步运行
- 处理异步错误
- 使用唤醒锁定
- 进行清理
MainActivity.java
Intent intent = new Intent(MainActivity.this,MyService.class);
intent.setAction("com.example.action.PLAY");
startService(intent);
MyService.java
public class MyService extends Service implements MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener {
private static final String ACTION_PLAY = "com.example.action.PLAY";
MediaPlayer mediaPlayer = null;
public MyService() {
}
@Override
public void onCreate() {
super.onCreate();
Log.d("wangrui","onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("wangrui","onStartCommand");
if (intent.getAction().equals(ACTION_PLAY)){
//写法一:
mediaPlayer = MediaPlayer.create(getApplicationContext(),R.raw.xiaye);
mediaPlayer.setLooping(true);
//在主线程中使用 MediaPlayer 时,您应该调用 prepareAsync() 而非 prepare(),并实现 MediaPlayer.OnPreparedListener,以便在准备工作完成后获得通知并开始播放
mediaPlayer.setOnPreparedListener(this);
//使用异步资源时,应确保以适当的方式向应用发出错误通知
mediaPlayer.setOnErrorListener(this);
// //防止CPU休眠,MediaPlayer框架会在播放时保持指定的锁定状态,并在暂停或停止播放时释放锁定
// mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
// //防止WLAN休眠
// WifiManager.WifiLock wifiLock = ((WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE)).createWifiLock(WifiManager.WIFI_MODE_FULL,"mylock");
// wifiLock.acquire(); //手动持锁。注意:我们在暂停或停止媒体内容,或者当您不再需要网络时,手动释放该锁定 wifiLock.release();
try {
mediaPlayer.prepareAsync(); //准备async以不阻塞主线程
}catch (Exception e){
e.printStackTrace();
}
}
return super.onStartCommand(intent, flags, startId);
}
//在MediaPlayer准备就绪时调用
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
mediaPlayer.start();
}
@Override
public boolean onError(MediaPlayer mediaPlayer, int i, int i1) {
Log.d("wangrui","MediaPlayer 发生错误,请重置");
return false;
}
@Override
public void onDestroy() {
super.onDestroy();
if (mediaPlayer != null) mediaPlayer.release(); //释放
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
四丶总结
在本例启动了一个用来绘制进度条的线程 SeekThread,不断获取当前播放的进度,根据进度比例绘制进度条,当音乐播放暂停时,由对象锁 obj 让进度条线程进入等待状态,自爱此播放时唤醒线程,让进度条继续绘制。