Android学习整理- 8 -MediaPlayer 放歌
为了实现后台放歌,可以转移到Service中,具体的逻辑和在Activity中是差不多的
功能需求:
- 播放,暂停,停止
- 歌曲进度条(SeekBar)
- 后台
实现过程
下载4首歌曲改名a1,a2,a3,a4放进sd卡目录下Sounds文件夹
新建服务MediaService
private static final String TAG = "MediaService";
private MyBinder mBinder = new MyBinder();
//标记当前歌曲的序号
private int i = 0;
//歌曲路径
private String[] musicPath = new String[]{
Environment.getExternalStorageDirectory() + "/Sounds/a1.mp3",
Environment.getExternalStorageDirectory() + "/Sounds/a2.mp3",
Environment.getExternalStorageDirectory() + "/Sounds/a3.mp3",
Environment.getExternalStorageDirectory() + "/Sounds/a4.mp3"
};
//这里要是路径有问题,就加上getAbsolutePath(),像下面这样
//Environment.getExternalStorageDirectory().getAbsolutePath() + "/Sounds/a1.mp3",
//初始化MediaPlayer
public MediaPlayer mMediaPlayer = new MediaPlayer();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public class MyBinder extends Binder {
}
在MyBinder类里面添加逻辑功能暂停,播放,下一首,上一首,等
/**
* 播放音乐
*/
public void playMusic() {
if (!mMediaPlayer.isPlaying()) {
//如果还没开始播放,就开始
mMediaPlayer.start();
}
}
/**
* 暂停播放
*/
public void pauseMusic() {
if (mMediaPlayer.isPlaying()) {
//如果还没开始播放,就开始
mMediaPlayer.pause();
}
}
/**
* 下一首
*/
public void nextMusic() {
if (mMediaPlayer != null && i < 4 && i >= 0) {
//切换歌曲reset()很重要很重要很重要,没有会报IllegalStateException
mMediaPlayer.reset();
iniMediaPlayerFile(i + 1);
//这里的if只要是为了不让歌曲的序号越界,因为只有4首歌
if (i == 2) {
} else {
i = i + 1;
}
playMusic();
}
}
/**
* 上一首
*/
public void preciousMusic() {
if (mMediaPlayer != null && i < 4 && i > 0) {
mMediaPlayer.reset();
iniMediaPlayerFile(i - 1);
if (i == 1) {
} else {
i = i - 1;
}
playMusic();
}
}
/**
* 关闭播放器
*/
public void closeMedia() {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.release();
}
}
注意
在下一首和上一首的功能里重新调用 setDataSource时,要先reset再重新加载资源,不然会爆java.lang.IllegalStateException错误,
下面这段有点多余,也是MyBinder里面的,因为可以在Activity中直接获取到MediaPlayer的对象而不用像下面这样拐弯,嗯,暂时先这样写了
/**
* 获取歌曲长度
**/
public int getProgress() {
return mMediaPlayer.getDuration();
}
/**
* 获取播放位置
*/
public int getPlayPosition() {
return mMediaPlayer.getCurrentPosition();
}
/**
* 播放指定位置
*/
public void seekToPositon(int msec) {
mMediaPlayer.seekTo(msec);
}
接着载服务中MediaService中
/**
* 添加file文件到MediaPlayer对象并且准备播放音频
*/
private void iniMediaPlayerFile(int dex) {
//获取文件路径
try {
//此处的两个方法需要捕获IO异常
//设置音频文件到MediaPlayer对象中
mMediaPlayer.setDataSource(musicPath[dex]);
//让MediaPlayer对象准备
mMediaPlayer.prepare();
} catch (IOException e) {
Log.d(TAG, "设置资源,准备阶段出错");
e.printStackTrace();
}
}
加入权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
注册服务
<service android:name=".service.MediaService"/>
布局文件如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.minminaya.mediaservice.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/play"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:text="paly"/>
<Button
android:id="@+id/pause"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:text="pause"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/precious"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:text="precious"/>
<Button
android:id="@+id/next"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:text="next"/>
</LinearLayout>
<SeekBar
android:layout_marginTop="20dp"
android:id="@+id/seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:text="当前进度:"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
Activity中,首先先实现播放的4大功能,声明
private MediaService.MyBinder mMyBinder;
private Button playButton;
private Button pauseButton;
private Button nextButton;
private Button preciousButton;
//“绑定”服务的intent
Intent MediaServiceIntent;
服务与活动的纽带ServiceConnection
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mMyBinder = (MediaService.MyBinder) service;
Log.d(TAG, "Service与Activity已连接");
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
绑定id,这里所有view都绑了,button事件
private void iniView() {
playButton = (Button) findViewById(R.id.play);
pauseButton = (Button) findViewById(R.id.pause);
nextButton = (Button) findViewById(R.id.next);
preciousButton = (Button) findViewById(R.id.precious);
mSeekBar = (SeekBar) findViewById(R.id.seekbar);
mTextView = (TextView) findViewById(R.id.text1);
playButton.setOnClickListener(this);
pauseButton.setOnClickListener(this);
nextButton.setOnClickListener(this);
preciousButton.setOnClickListener(this);
}
button事件详细
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.play:
mMyBinder.playMusic();
break;
case R.id.pause:
mMyBinder.pauseMusic();
break;
case R.id.next:
mMyBinder.nextMusic();
break;
case R.id.precious:
mMyBinder.preciousMusic();
break;
}
}
接下来是onCreate方法,针对Android6.0以上的运行时权限,动态申请权限
iniView();
MediaServiceIntent = new Intent(this, MediaService.class);
//判断权限够不够,不够就给
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE
}, 1);
} else {
//够了绑定播放音乐的服务
bindService(MediaServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
}
运行时权限的回调,在Activity中的
//获取到权限回调方法
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[]permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
bindService(MediaServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
} else {
Toast.makeText(this, "权限不够获取不到音乐,程序将退出", Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
break;
}
}
onDestroy方法种添加
mMyBinder.closeMedia();
unbindService(mServiceConnection);
这个时候可以播放了,进度条当然没有动
接下来实现进度条的功能
在Activity中实例化handler
private Handler mHandler = new Handler();
private SeekBar mSeekBar;
private TextView mTextView;
//进度条下面的当前进度文字,将毫秒化为m:ss格式
private SimpleDateFormat time = new SimpleDateFormat("m:ss");
在ServiceConnection中添加
mSeekBar.setMax(mMyBinder.getProgress());
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
//这里很重要,如果不判断是否来自用户操作进度条,会不断执行下面语句块里面的逻辑,然后就会卡顿卡顿
if(fromUser){
mMyBinder.seekToPositon(seekBar.getProgress());
// mMediaService.mMediaPlayer.seekTo(seekBar.getProgress());
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
mHandler.post(mRunnable);
注意
- 这里的seekbar回调里,如果不判断fromUser,播放会一直卡顿
还有一个runnable
/**
* 更新ui的runnable
*/
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
mSeekBar.setProgress(mMyBinder.getPlayPosition());
mTextView.setText(time.format(mMyBinder.getPlayPosition()) + "s");
mHandler.postDelayed(mRunnable, 1000);
}
};
onDestroy中 mMyBinder.closeMedia();前添加
//我们的handler发送是定时1000s发送的,如果不关闭,MediaPlayer release掉了还在获取getCurrentPosition就会爆IllegalStateException错误
mHandler.removeCallbacks(mRunnable);
注意关闭handle的队列,不然,转入后台播放时程序崩溃
贴全部代码环节
全部Activity代码如下
package com.minminaya.mediaservice;
import android.Manifest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.format.Time;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import com.minminaya.mediaservice.service.MediaService;
import java.text.SimpleDateFormat;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Handler mHandler = new Handler();
private static final String TAG = "MainActivity";
private MediaService.MyBinder mMyBinder;
// private MediaService mMediaService;
private Button playButton;
private Button pauseButton;
private Button nextButton;
private Button preciousButton;
private SeekBar mSeekBar;
private TextView mTextView;
//进度条下面的当前进度文字,将毫秒化为m:ss格式
private SimpleDateFormat time = new SimpleDateFormat("m:ss");
//“绑定”服务的intent
Intent MediaServiceIntent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iniView();
MediaServiceIntent = new Intent(this, MediaService.class);
//判断权限够不够,不够就给
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE
}, 1);
} else {
//够了就设置路径等,准备播放
bindService(MediaServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
}
}
//获取到权限回调方法
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[]permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
bindService(MediaServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
} else {
Toast.makeText(this, "权限不够获取不到音乐,程序将退出", Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
break;
}
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mMyBinder = (MediaService.MyBinder) service;
// mMediaService = ((MediaService.MyBinder) service).getInstance();
mSeekBar.setMax(mMyBinder.getProgress());
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
//这里很重要,如果不判断是否来自用户操作进度条,会不断执行下面语句块里面的逻辑,然后就会卡顿卡顿
if(fromUser){
mMyBinder.seekToPositon(seekBar.getProgress());
// mMediaService.mMediaPlayer.seekTo(seekBar.getProgress());
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
mHandler.post(mRunnable);
Log.d(TAG, "Service与Activity已连接");
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
private void iniView() {
playButton = (Button) findViewById(R.id.play);
pauseButton = (Button) findViewById(R.id.pause);
nextButton = (Button) findViewById(R.id.next);
preciousButton = (Button) findViewById(R.id.precious);
mSeekBar = (SeekBar) findViewById(R.id.seekbar);
mTextView = (TextView) findViewById(R.id.text1);
playButton.setOnClickListener(this);
pauseButton.setOnClickListener(this);
nextButton.setOnClickListener(this);
preciousButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.play:
mMyBinder.playMusic();
break;
case R.id.pause:
mMyBinder.pauseMusic();
break;
case R.id.next:
mMyBinder.nextMusic();
break;
case R.id.precious:
mMyBinder.preciousMusic();
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//我们的handler发送是定时1000s发送的,如果不关闭,MediaPlayer release掉了还在获取getCurrentPosition就会爆IllegalStateException错误
mHandler.removeCallbacks(mRunnable);
mMyBinder.closeMedia();
unbindService(mServiceConnection);
}
/**
* 更新ui的runnable
*/
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
mSeekBar.setProgress(mMyBinder.getPlayPosition());
mTextView.setText(time.format(mMyBinder.getPlayPosition()) + "s");
mHandler.postDelayed(mRunnable, 1000);
}
};
}
服务的
package com.minminaya.mediaservice.service;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.Environment;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;
import java.io.IOException;
/**
* Created by NIWA on 2017/3/17.
*/
public class MediaService extends Service {
private static final String TAG = "MediaService";
private MyBinder mBinder = new MyBinder();
//标记当前歌曲的序号
private int i = 0;
//歌曲路径
private String[] musicPath = new String[]{
Environment.getExternalStorageDirectory() + "/Sounds/a1.mp3",
Environment.getExternalStorageDirectory() + "/Sounds/a2.mp3",
Environment.getExternalStorageDirectory() + "/Sounds/a3.mp3",
Environment.getExternalStorageDirectory() + "/Sounds/a4.mp3"
};
//初始化MediaPlayer
public MediaPlayer mMediaPlayer = new MediaPlayer();
public MediaService() {
iniMediaPlayerFile(i);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public class MyBinder extends Binder {
// /**
// * 获取MediaService.this(方便在ServiceConnection中)
// *
// * *//*
// public MediaService getInstance() {
// return MediaService.this;
// }*/
/**
* 播放音乐
*/
public void playMusic() {
if (!mMediaPlayer.isPlaying()) {
//如果还没开始播放,就开始
mMediaPlayer.start();
}
}
/**
* 暂停播放
*/
public void pauseMusic() {
if (mMediaPlayer.isPlaying()) {
//如果还没开始播放,就开始
mMediaPlayer.pause();
}
}
/**
* reset
*/
public void resetMusic() {
if (!mMediaPlayer.isPlaying()) {
//如果还没开始播放,就开始
mMediaPlayer.reset();
iniMediaPlayerFile(i);
}
}
/**
* 关闭播放器
*/
public void closeMedia() {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.release();
}
}
/**
* 下一首
*/
public void nextMusic() {
if (mMediaPlayer != null && i < 4 && i >= 0) {
//切换歌曲reset()很重要很重要很重要,没有会报IllegalStateException
mMediaPlayer.reset();
iniMediaPlayerFile(i + 1);
//这里的if只要是为了不让歌曲的序号越界,因为只有4首歌
if (i == 2) {
} else {
i = i + 1;
}
playMusic();
}
}
/**
* 上一首
*/
public void preciousMusic() {
if (mMediaPlayer != null && i < 4 && i > 0) {
mMediaPlayer.reset();
iniMediaPlayerFile(i - 1);
if (i == 1) {
} else {
i = i - 1;
}
playMusic();
}
}
/**
* 获取歌曲长度
**/
public int getProgress() {
return mMediaPlayer.getDuration();
}
/**
* 获取播放位置
*/
public int getPlayPosition() {
return mMediaPlayer.getCurrentPosition();
}
/**
* 播放指定位置
*/
public void seekToPositon(int msec) {
mMediaPlayer.seekTo(msec);
}
}
/**
* 添加file文件到MediaPlayer对象并且准备播放音频
*/
private void iniMediaPlayerFile(int dex) {
//获取文件路径
try {
//此处的两个方法需要捕获IO异常
//设置音频文件到MediaPlayer对象中
mMediaPlayer.setDataSource(musicPath[dex]);
//让MediaPlayer对象准备
mMediaPlayer.prepare();
} catch (IOException e) {
Log.d(TAG, "设置资源,准备阶段出错");
e.printStackTrace();
}
}
}
Github源代码:MediaService
高级播放器。。。人类用的
上面的播放是固有的几个文件(连音频文件名都订好了,真坑)
有个博主,做的毕业项目,是一系列文章,感觉不错,
Android开源音乐播放器之播放器基本功能
注意事项,
几个注意点在上面了