大家好,作为小白,这是我第一篇文章,也是我的一个学习记录。废话就不多说了,开始进入正题吧。
这篇文章是我做Mp3播放器的一个过程,首先整理一下思路,我们做播放器会需要什么东西呢?
一:获取本地的音频文件。并且保存到一个List里面方便我们来读取。
二:需要一个界面来展示出我们所获取到的音频文件。
三:播放界面,需要有播放,下一首,等一系列操作。
四:Service,我们需要运用Service来达到后台播放目的,当然使用它的前提就是我们能够先了解到Service的基本使用方法,这里我就不多做说明了.等以后我会写一点点关于这个的东西。
好了,大概的流程就是这样,下面我们来用代码实现。
一:获取本地音频文件
public class Audio {
private String mTitle,
mTitleKey,//标题
mArtist,//艺术家名
mArtistKey,
mComposer,
mAlbum,
mAlbumKey,
mDisplayName,//歌曲名
mMimeType,
mPath;//路径
private int mId,
mArtistId,
mAlbumId,
mYear,
mTrack;
private int mDuration = 0,//时长
mSize = 0;//大小
private boolean isRingtone = false,
isPodcast = false,
isAlarm = false,
isMusic = false,
isNotification = false;
public Audio(Bundle bundle) {
mId = bundle.getInt(MediaStore.Audio.Media._ID);
mTitle = bundle.getString(MediaStore.Audio.Media.TITLE);
mTitleKey = bundle.getString(MediaStore.Audio.Media.TITLE_KEY);
mArtist = bundle.getString(MediaStore.Audio.Media.ARTIST);
mArtistKey = bundle.getString(MediaStore.Audio.Media.ARTIST_KEY);
mComposer = bundle.getString(MediaStore.Audio.Media.COMPOSER);
mAlbum = bundle.getString(MediaStore.Audio.Media.ALBUM);
mAlbumKey = bundle.getString(MediaStore.Audio.Media.ALBUM_KEY);
mDisplayName = bundle.getString(MediaStore.Audio.Media.DISPLAY_NAME);
mYear = bundle.getInt(MediaStore.Audio.Media.YEAR);
mMimeType = bundle.getString(MediaStore.Audio.Media.MIME_TYPE);
mPath = bundle.getString(MediaStore.Audio.Media.DATA);
mArtistId = bundle.getInt(MediaStore.Audio.Media.ARTIST_ID);
mAlbumId = bundle.getInt(MediaStore.Audio.Media.ALBUM_ID);
mTrack = bundle.getInt(MediaStore.Audio.Media.TRACK);
mDuration = bundle.getInt(MediaStore.Audio.Media.DURATION);
mSize = bundle.getInt(MediaStore.Audio.Media.SIZE);
isRingtone = bundle.getInt(MediaStore.Audio.Media.IS_RINGTONE) == 1;
isPodcast = bundle.getInt(MediaStore.Audio.Media.IS_PODCAST) == 1;
isAlarm = bundle.getInt(MediaStore.Audio.Media.IS_ALARM) == 1;
isMusic = bundle.getInt(MediaStore.Audio.Media.IS_MUSIC) == 1;
isNotification = bundle.getInt(MediaStore.Audio.Media.IS_NOTIFICATION) == 1;
}
public int getId() {
return mId;
}
public String getMimeType () {
return mMimeType;
}
public int getDuration () {
return mDuration;
}
public int getSize () {
return mSize;
}
public boolean isRingtone () {
return isRingtone;
}
public boolean isPodcast () {
return isPodcast;
}
public boolean isAlarm () {
return isAlarm;
}
public boolean isMusic () {
return isMusic;
}
public boolean isNotification () {
return isNotification;
}
public String getTitle () {
return mTitle;
}
public String getTitleKey () {
return mTitleKey;
}
public String getArtist () {
return mArtist;
}
public int getArtistId () {
return mArtistId;
}
public String getArtistKey () {
return mArtistKey;
}
public String getComposer () {
return mComposer;
}
public String getAlbum () {
return mAlbum;
}
public int getAlbumId () {
return mAlbumId;
}
public String getAlbumKey () {
return mAlbumKey;
}
public String getDisplayName () {
return mDisplayName;
}
public int getYear () {
return mYear;
}
public int getTrack () {
return mTrack;
}
public String getPath () {
return mPath;
}
}
这里我们写了一个Audio的音频文件类,它所有的属性有mTitle,
mTitleKey,
mArtist,
mArtistKey,
mComposer,
mAlbum,
mAlbumKey,
mDisplayName,
mMimeType,
mPath;
我们从名字中就可以看到其作用,歌曲标题,艺术家名等.
public Audio(Bundle bundle) 这个构造的函数的作用呢,也就相当于我们取到本地音频的一系列值,然后存储到这个类中来。
然后就是实现这个类的属性的get方法,从类中获取到这些属性。
接下来,我们需要去浏览本地文件,选出音频文件进行保存:
public class MediaUtils {
public static final String[] AUDIO_KEYS = new String[]{
MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.TITLE_KEY,
MediaStore.Audio.Media.ARTIST,
MediaStore.Audio.Media.ARTIST_ID,
MediaStore.Audio.Media.ARTIST_KEY,
MediaStore.Audio.Media.COMPOSER,
MediaStore.Audio.Media.ALBUM,
MediaStore.Audio.Media.ALBUM_ID,
MediaStore.Audio.Media.ALBUM_KEY,
MediaStore.Audio.Media.DISPLAY_NAME,
MediaStore.Audio.Media.DURATION,
MediaStore.Audio.Media.SIZE,
MediaStore.Audio.Media.YEAR,
MediaStore.Audio.Media.TRACK,
MediaStore.Audio.Media.IS_RINGTONE,
MediaStore.Audio.Media.IS_PODCAST,
MediaStore.Audio.Media.IS_ALARM,
MediaStore.Audio.Media.IS_MUSIC,
MediaStore.Audio.Media.IS_NOTIFICATION,
MediaStore.Audio.Media.MIME_TYPE,
MediaStore.Audio.Media.DATA
};
//读取本地音频文件,保存到list里并返回
public static List<Audio> getAudioList(Context context){
List<Audio> audioList = new ArrayList<Audio>();
ContentResolver resolver = context.getContentResolver();
Cursor cursor = resolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
AUDIO_KEYS,
null,
null,
null);
for(cursor.moveToFirst();!cursor.isAfterLast();cursor.moveToNext()){
Bundle bundle = new Bundle();
for(int i=0;i<AUDIO_KEYS.length;i++){
final String key = AUDIO_KEYS[i];
final int columnIndex = cursor.getColumnIndex(key);
final int type = cursor.getType(columnIndex);
switch (type) {
case Cursor.FIELD_TYPE_BLOB:
break;
case Cursor.FIELD_TYPE_FLOAT:
float floatValue = cursor.getFloat(columnIndex);
bundle.putFloat(key, floatValue);
break;
case Cursor.FIELD_TYPE_INTEGER:
int intValue = cursor.getInt(columnIndex);
bundle.putInt(key, intValue);
break;
case Cursor.FIELD_TYPE_NULL:
break;
case Cursor.FIELD_TYPE_STRING:
String strValue = cursor.getString(columnIndex);
bundle.putString(key, strValue);
break;
}
}
Audio audio = new Audio (bundle);
audioList.add(audio);
}
cursor.close();
return audioList;
}
}
这一个类的主要目的就是去通过Cusor索引,然后去获取到本地的音频文件,然后保存到List里面,在返回一个List
这里我们已经拿到了步骤一种所说的,保存有本地所有音频文件的List
然后如何将这个List显示在手机界面上,并且可以点击的呢。
MainActivity
@ContentView(R.layout.activity_main)
public class MainActivity extends Activity {
@ViewInject(R.id.mp3_ListView)
ListView mp3ListView;
private Context mContext;
static List<Audio> mp3Infos = new ArrayList<Audio>();
private Mp3Adapter adapter;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
x.view().inject(this);
init();
//给MusicPlayActivity这个类的mp3Infos赋值
MusicPlayActivity.mp3Infos=mp3Infos;
}
public void init(){
this.mContext=this;
mp3Infos = (ArrayList<Audio>) MediaUtils.getAudioList(this.mContext);
adapter = new Mp3Adapter(this.mContext,mp3Infos);
mp3ListView.setAdapter(adapter);
//设置ListView Item点击监听器
mp3ListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent1 = new Intent();
//启动音乐播放界面
intent1 .setClass(MainActivity.this,MusicPlayActivity.class);
//带值跳转,传入点击的歌曲位置
intent1.putExtra("position", position);
startActivity(intent1);
}
});
}
}
Mp3Adapter
public class Mp3Adapter extends BaseAdapter{
private Context context=null;
private List<Audio> mp3Infos ;
public Mp3Adapter(Context context){
this.context=context;
}
public Mp3Adapter(Context context, List<Audio> mp3Infos){
this.context=context;
this.mp3Infos=mp3Infos;
}
public int getCount() {
return mp3Infos.size();
}
@Override
public Object getItem(int position) {
return mp3Infos.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(mp3Infos==null){
return null;
}
if(convertView==null){
AppView view = new AppView(this.context);
view.update(mp3Infos.get(position));
convertView=view;
convertView.setTag(view);
}else{
convertView= (View) convertView.getTag();
}
return convertView;
}
}
AppView
public class AppView extends RelativeLayout{
ImageView mp3ImageView;
TextView mp3ArtistName;
TextView mp3Name;
TextView mp3Duration;
private Context context;
public AppView(Context context){
super(context);
this.context=context;
init(context);
}
public void init(Context context){
this.context=context;
View view = LayoutInflater.from(this.context).inflate(R.layout.listview_item_activity,null);
mp3ImageView = (ImageView) view.findViewById(R.id.mpe3_imageView);
mp3ArtistName = (TextView) view.findViewById(R.id.mp3_ArttistName);
mp3Duration = (TextView) view.findViewById(R.id.mp3_dration);
mp3Name = (TextView) view.findViewById(R.id.Mp3_name);
addView(view);
}
public void update(Audio info){
mp3ArtistName.setText(info.getArtist());
mp3ImageView.setImageResource(R.mipmap.xxx);
mp3Duration.setText(TimeTransform.secToTime(info.getDuration()/1000));
String x = info.getDisplayName();
if(x.length()>=15){
mp3Name.setText(x.subSequence(0,15)+"...");
}else
mp3Name.setText(x);
}
}
上述代码是三个类,一个是Activity,一个是Adapter,一个是View
这三个类就是比较标准的实现显示一个ListView的操作吧,相信大家不会陌生,就是拿着我们返回的所有音频的List并且给他写好适配器,然后进行显示操作。当然代码中也有建立ListView的Item监听器。
现在我们实现了获取所有音频文件,然后将其显示在界面上,并且可以点击,现在点击后会需要跳转到播放界面。
上面代码中我已经写了跳转部分,现在我们实现以下需要跳转的类:
MusicPlayActivity
public class MusicPlayActivity extends Activity {
@ViewInject(R.id.play_music)
ImageView playMusicIv;
@ViewInject(R.id.next_music)
ImageView nextMusicIv;
@ViewInject(R.id.pre_music)
ImageView preMusicIv;
@ViewInject(R.id.play_mode)
ImageView playMusicModeIv;
@ViewInject(R.id.music_name)
TextView musicName;
@ViewInject(R.id.artist_name)
TextView artistName;
@ViewInject(R.id.seekBar)
SeekBar seekBar;
@ViewInject(R.id.MusicCurrentTime)
TextView MusicCurrentTime;
@ViewInject(R.id.MusicTime)
TextView MusicTime;
int array[];
public static List<Audio> mp3Infos;
boolean mBound=false;//控制绑定和开启Service
boolean flag = false;//判断播放状态
Thread myThread;
int position;
private int playFlag = 0;//0 顺序,1 单曲, 2,随机
boolean playStatus=true;//控制线程的开启和关闭
Mp3Service mService;
static String TAG="MusicPlayerActivity";
//Handler用于时时更新进度条
Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 0:
double progress = msg.getData().getDouble("progress");
int currentTime = msg.getData().getInt("currentTime");
int max = seekBar.getMax();
int position = (int) (max * progress);
//设置seekbar的实际位置
seekBar.setProgress(position);
MusicCurrentTime.setText(TimeTransform.secToTime(currentTime/1000));//设置总时间显示
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
x.view().inject(this);
//获取到从MainActivity传过来的歌曲的位置
Intent intent = getIntent();
position = intent.getIntExtra("position",-1);
playMusicModeIv.setImageDrawable(getResources().getDrawable(R.drawable.xunhuan_play_selector));
init();
}
public void init(){
//通过Mp3Service类中的isRunning来判断服务是否处于开启状态,如果是的话,则需要关闭辅助在进行别的操作
if(Mp3Service.isRunning==true){
// unbindService(mConnection);
stopService(new Intent(MusicPlayActivity.this, Mp3Service.class));
}
//获取到Mp3的信息
mp3Infos = MainActivity.mp3Infos;
//设置界面 musicName.setText(mp3Infos.get(position).getDisplayName());
MusicTime.setText(TimeTransform.secToTime(mp3Infos.get(position).getDuration()/1000));
artistName.setText(mp3Infos.get(position).getArtist());
Intent serviceIntent = new Intent(MusicPlayActivity.this,Mp3Service.class);
serviceIntent.putExtra("position",position);
if(mBound==false){
//开启和绑定服务
startService(serviceIntent);
bindService(serviceIntent,mConnection,BIND_AUTO_CREATE);
}
myThread = new Thread(new UpdateProgress());
//拖动seekBar调整播放进度
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
int dest = seekBar.getProgress();
//seekbar的最大值
int max = seekBar.getMax();
//调用service调节播放进度
mService.setProgress(max, dest);
}
});
}
//播放界面按钮的监听器
@Event(value={R.id.play_music,R.id.pre_music,R.id.next_music,R.id.play_mode})
private void setOnClickListener(View view){
switch (view.getId()) {
case R.id.play_music:
if (mBound && flag) {
playMusicIv.setImageDrawable(getResources().getDrawable(R.drawable.playmusic_selector));
mService.pause();
flag = false;
} else {
playMusicIv.setImageDrawable(getResources().getDrawable(R.drawable.pause_music_selector));
mService.play();
flag = true;
}
break;
case R.id.pre_music:
position=position-1;
if(position<0)
position = mp3Infos.size() - 1;
playStatus = false;
mBound=false;
unbindService(mConnection);
init();
break;
case R.id.next_music:
switch (playFlag){
case 0:
position = position+1;
if(position>=mp3Infos.size()){
position=0;
}
break;
case 1:
position = position;
Toast.makeText(MusicPlayActivity.this, "单曲播放中", Toast.LENGTH_SHORT).show();
break;
case 2:
int x=(int)(Math.random()*mp3Infos.size()-1);
position=x;
break;
}
playStatus = false;
mBound=false;
unbindService(mConnection);
init();
break;
case R.id.play_mode:
switch (playFlag){
case 0:
playFlag++;
playMusicModeIv.setImageDrawable(getResources().getDrawable(R.drawable.danqu_play_selector));
break;
case 1:
playFlag++;
playMusicModeIv.setImageDrawable(getResources().getDrawable(R.drawable.suiji_play_selector));
break;
case 2:
playFlag=0;
playMusicModeIv.setImageDrawable(getResources().getDrawable(R.drawable.xunhuan_play_selector));
break;
}
break;
}
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Mp3Service.MyBinder myBinder = (Mp3Service.MyBinder) service;
mService = (Mp3Service) myBinder.getService();
mBound=true;
playStatus=true;
playMusicIv.setImageDrawable(getResources().getDrawable(R.drawable.pause_music_selector));
// mService.play();
myThread.start();
flag=true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBound=false;
}
};
public void onDestroy() {
//销毁activity时,要记得销毁线程
playStatus = false;
super.onDestroy();
}
//更新进度线程,单位时间获取Service返回的
private class UpdateProgress implements Runnable {
Bundle data = new Bundle();
int millisecond=100;
double progress;
int currentTime;
@Override
public void run() {
while(playStatus){
try {
if(mBound){
Message msg = new Message();
data.clear();
progress = mService.getProgress();
currentTime=mService.getDurationTime();
msg.what=0;
data.putDouble("progress",progress);
data.putInt("currentTime",currentTime);
msg.setData(data);
mHandler.sendMessage(msg);
}
Thread.sleep(millisecond);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static boolean isServiceRunning(Context mContext, String className) {
boolean isRunning = false;
ActivityManager activityManager = (ActivityManager)
mContext.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> serviceList= activityManager.getRunningServices(50);
if (!(serviceList.size()>0)) {
return false;
}
for (int i=0; i<serviceList.size(); i++) {
String a =serviceList.get(i).service.getClassName();
if (serviceList.get(i).service.getClassName().equals(className) == true) {
isRunning = true;
break;
}
}
return isRunning;
}
}
这个类就是音乐的播放界面,在这里我们首先获取到了由ListView点击后传过来的位置参数,然后在开启服务之前首要去判断这个服务是否处于正在运行状态,如果是的话,就首先关闭服务,然后在进行别的操作,因为我们听歌的时候,切歌,必须先关闭前一首歌,再去播放另一首,然后isRunning就是Service里的属性,从这里判断是否服务正在进行。
当然更新进度也的放在这个类里,具体思想就是我们通过Thread和Handler去实现,线程不断从Service中获取歌曲播放进度,然后send给Handler,在由Handler去进行seekBar的更新。
下面就说一下我们比较核心的Service类,这个类就是后台播放歌曲的实现。
public class Mp3Service extends Service {
int position;
MediaPlayer mediaPlayer;//MediaPlayer 对象
//判断是否服务开启了
public static boolean isRunning=false;
//构建Binder,通过binder和Activity进行交互
MyBinder myBinder= new MyBinder();
public static List<Audio> mp3Infos;
public IBinder onBind(Intent intent) {
return myBinder;
}
public class MyBinder extends Binder{
//获取到Service
public Service getService(){
return Mp3Service.this;
}
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
//开启服务
public int onStartCommand(Intent intent, int flags, int startId) {
isRunning=true;
//取到带值跳转传过来的歌曲位置参数
position=intent.getIntExtra("position",-1);
mp3Infos= MusicPlayActivity.mp3Infos;
init();
return Service.START_NOT_STICKY;
}
//初始化音乐MediaPlayer
public void init(){
mediaPlayer = new MediaPlayer();
//拿到歌曲位置进行初始化
try {
mediaPlayer.reset();
mediaPlayer.setDataSource(mp3Infos.get(position).getPath());
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.prepare();
mediaPlayer.start();
} catch (IOException e) {
e.printStackTrace();
}
}
//获取进度
public double getProgress(){
int position = mediaPlayer.getCurrentPosition();
int time = mediaPlayer.getDuration();
double progress = (double)position / (double)time;
return progress;
}
//获取播放时间
public int getDurationTime(){
return mediaPlayer.getCurrentPosition();
}
//设置进度
public void setProgress(int max , int dest){
int time = mediaPlayer.getDuration();
mediaPlayer.seekTo(time*dest/max);
}
public void play(){
if(mediaPlayer!=null){
mediaPlayer.start();
}
}
public void pause(){
if(mediaPlayer!=null&&mediaPlayer.isPlaying()){
mediaPlayer.pause();
}
}
@Override
//服务销毁
public void onDestroy() {
if(mediaPlayer!=null&&mediaPlayer.isPlaying()){
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer=null;
isRunning=false;
}
super.onDestroy();
}
}
这个Mp3Service类就是实现歌曲后台播放的类,在这里我们首先接收到了由MusicPlayActivity传入的歌曲位置,当MusicPlayActivity类开启和绑定服务之后,我服务类进行了初始化,首先实例化了mediaPlayer并且设置好了他的播放设置。然后这个类里写了歌曲的暂停和播放功能。
在这里要知道什么时候服务开启,什么时候服务关闭,然后在合适的地方设置好isRunning的值。
这个类中我们也写了获取进度和设置进度的方法,获取进度就是为了方便MusicPlayActivity去时时获取进度进行界面进度条更新,设置进度就是为了拖动bar的时候能够调节进度。
当然要完善销毁方法,释放Mediaplayer的一系列操作。
好了差不多就是这样,然后还有用到的工具类,我也复制在底下:
public class TimeTransform {
public static String secToTime(int time) {
String timeStr = null;
int hour = 0;
int minute = 0;
int second = 0;
if (time <= 0)
return "00:00";
else {
minute = time / 60;
if (minute < 60) {
second = time % 60;
timeStr = unitFormat(minute) + ":" + unitFormat(second);
} else {
hour = minute / 60;
if (hour > 99)
return "99:59:59";
minute = minute % 60;
second = time - hour * 3600 - minute * 60;
timeStr = unitFormat(hour) + ":" + unitFormat(minute) + ":" + unitFormat(second);
}
}
return timeStr;
}
public static String unitFormat(int i) {
String retStr = null;
if (i >= 0 && i < 10)
retStr = "0" + Integer.toString(i);
else
retStr = "" + i;
return retStr;
}
}
这个类的作用就是传入一个秒数,然后他会返回一个标准化时间输出的字符串,例如传入xxxxxx,他会 返回xx:xx。因为音乐播放界面的时间应该是标准化的。
要记住Mediaplayer获取到的时间都是以毫秒为单位的,所以传入的时候必须转化成秒在传入。xxxx/1000
主要进行传值我们都用的是带值跳转和static 声明存放歌曲的List,这样他们就可以共享这些数据了。布局文件我就不贴出来了,这个可以自己实现,大体的思路就是这样,可能有些太长了,因为我把所有的代码基本上都放出来了。
好了,基本就这么多,这篇文章主要为了记录学习过程,还有很多不完善和不规范的地方,请大神勿喷。作为新手仅仅是想做一点学习记录而已。