Android Mp3播放器,支持Service后台播放

来源:互联网 发布:手机好莱坞特效软件 编辑:程序博客网 时间:2024/05/16 07:31

大家好,作为小白,这是我第一篇文章,也是我的一个学习记录。废话就不多说了,开始进入正题吧。
这篇文章是我做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,这样他们就可以共享这些数据了。布局文件我就不贴出来了,这个可以自己实现,大体的思路就是这样,可能有些太长了,因为我把所有的代码基本上都放出来了。

好了,基本就这么多,这篇文章主要为了记录学习过程,还有很多不完善和不规范的地方,请大神勿喷。作为新手仅仅是想做一点学习记录而已。


2 0
原创粉丝点击