Android 手机影音 开发过程记录(五)

来源:互联网 发布:淘宝店招可以修改吗 编辑:程序博客网 时间:2024/04/28 20:57

前一篇已经将视频模块弄好了,今天主要理一下音乐这一模块,包括:

  1. 通过ContentProvider获取音乐列表数据
  2. 音乐播放界面的动画和布局
  3. 音乐播放的界面AudioPlayerActivity和AudioPlayService交互。(实现播放、暂停、上下一首、播放模式的切换)

效果图

这里写图片描述 这里写图片描述

结构图

这里写图片描述

获取音乐列表数据

  1. 分析:这里还是通过内容提供者ContentProvider去获取。前面获取视频时没有将为什么这样获取,这里补充一下,面试概率还挺高的:使用ContentProvider可以实现不同应用程序下数据的共享,主要是通过把自己的数据通过uri的形式共享出去来实现共享的。这样的好处就是:屏蔽数据存储的细节,对开发者透明,我们只需要关心操作数据的uri就可以了。
  2. 观察音乐列表,我们需要这些数据:音乐名称(DISPLAY_NAME),音乐时长(DURATION),艺术家(ARTIST),音乐路径路径(DATA)。
  3. 下面是具体获取的方法(跟获取视频一模一样)。

    private AudioListAdapter adapter;private SimpleQueryHandler queryHandler;@Overrideprotected void initData() {    adapter = new AudioListAdapter(getActivity(), null, false);    lv.setAdapter(adapter);    queryHandler = new SimpleQueryHandler(getActivity().getContentResolver());    String[] projection = {Media._ID, Media.DISPLAY_NAME, Media.DURATION,            Media.DATA, Media.ARTIST};    queryHandler.startQuery(0, adapter, Media.EXTERNAL_CONTENT_URI,            projection, null, null, null);}class SimpleQueryHandler extends AsyncQueryHandler {    public SimpleQueryHandler(ContentResolver cr) {        super(cr);    }    /**     * token: 查询的标识     */    @Override    protected void onQueryComplete(int token, Object cookie, Cursor cursor) {        super.onQueryComplete(token, cookie, cursor);        if (cookie != null && cookie instanceof CursorAdapter) {            CursorAdapter adapter = (CursorAdapter) cookie;            adapter.changeCursor(cursor);//相当于notifyDatasetChange            CursorUtil.printCursor(cursor);        }    }}

至此,可以实现上面音乐的列表。


音乐播放界面的布局和动画

  1. 音乐之所以能播放并且能切换上/下一首,肯定在点击listview的条目的时候把所有数据传递过来了。

        lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {        @Override        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {            Cursor cursor = adapter.getCursor();            Bundle bundle = new Bundle();            bundle.putInt("currentPosition", position);            bundle.putSerializable("audioList", cursorToList(cursor));            enterActivity(AudioPlayerActivity.class, bundle);        }    });private ArrayList<AudioItem> cursorToList(Cursor cursor){    cursor.moveToPosition(-1);    ArrayList<AudioItem> list = new ArrayList<AudioItem>();    while (cursor.moveToNext()){        AudioItem audioItem = AudioItem.fromCursor(cursor);        list.add(audioItem);    }    return list;}
  2. 播放页面布局

    <?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@mipmap/base_bg"android:orientation="vertical"><!--titleBar--><RelativeLayout    android:layout_width="match_parent"    android:layout_height="50dp"    android:background="@mipmap/base_titlebar_bg"    android:orientation="horizontal">    <ImageView        android:id="@+id/iv_back"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:background="@drawable/selector_btn_back" />    <TextView        android:id="@+id/tv_title"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerInParent="true"        android:textColor="@color/white"        android:textSize="20sp" /></RelativeLayout><!--音频跳动的动画和歌曲艺术家--><RelativeLayout    android:layout_width="match_parent"    android:layout_height="wrap_content">    <ImageView        android:id="@+id/iv_anim"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerHorizontal="true"        android:background="@drawable/audio_player_anim" />    <TextView        android:id="@+id/tv_artist"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_alignBottom="@id/iv_anim"        android:layout_centerHorizontal="true"        android:textColor="@color/white"        android:textSize="16sp" /></RelativeLayout><!--自定义歌词--><com.justforme.mobileplayer.ui.view.LyricView    android:id="@+id/lyric_view"    android:layout_width="match_parent"    android:layout_height="0dp"    android:layout_weight="1" /><LinearLayout    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:orientation="vertical"    android:padding="10dp">    <!--歌曲进度条和歌曲时间-->    <TextView        android:id="@+id/tv_time"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="right"        android:textColor="@color/white"        android:textSize="16sp" />    <SeekBar        android:id="@+id/sb_audio"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginTop="3dp"        android:progressDrawable="@drawable/audio_progress_drawable"        android:thumb="@mipmap/audio_seek_thumb"        android:thumbOffset="0dp" />    <!--各种播放逻辑的控件布局-->    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginBottom="5dp"        android:layout_marginTop="5dp"        android:gravity="center"        android:orientation="horizontal">        <RelativeLayout            android:layout_width="0dp"            android:layout_height="wrap_content"            android:layout_weight="1">            <ImageView                android:id="@+id/iv_play_mode"                android:layout_width="40dp"                android:layout_height="40dp"                android:layout_centerInParent="true"                android:background="@drawable/selector_btn_mode_order" />        </RelativeLayout>        <RelativeLayout            android:layout_width="0dp"            android:layout_height="wrap_content"            android:layout_weight="1">            <ImageView                android:id="@+id/iv_pre"                android:layout_width="45dp"                android:layout_height="45dp"                android:layout_centerInParent="true"                android:background="@drawable/selector_btn_audio_pre" />        </RelativeLayout>        <RelativeLayout            android:layout_width="0dp"            android:layout_height="wrap_content"            android:layout_weight="1">            <ImageView                android:id="@+id/iv_play"                android:layout_width="40dp"                android:layout_height="40dp"                android:layout_centerInParent="true"                android:background="@drawable/selector_btn_audio_pause" />        </RelativeLayout>        <RelativeLayout            android:layout_width="0dp"            android:layout_height="wrap_content"            android:layout_weight="1">            <ImageView                android:id="@+id/iv_next"                android:layout_width="45dp"                android:layout_height="45dp"                android:layout_centerInParent="true"                android:background="@drawable/selector_btn_audio_next" />        </RelativeLayout>        <RelativeLayout            android:layout_width="0dp"            android:layout_height="wrap_content"            android:layout_weight="1">            <ImageView                android:layout_width="40dp"                android:layout_height="40dp"                android:layout_centerInParent="true"                android:background="@drawable/selector_btn_lyric" />        </RelativeLayout>    </LinearLayout></LinearLayout></LinearLayout>
  3. 音频跳动的动画效果 (准备9张图片)

    <?xml version="1.0" encoding="utf-8"?><animation-list xmlns:android="http://schemas.android.com/apk/res/android"    android:oneshot="false">    <item        android:drawable="@mipmap/now_playing_matrix_01"        android:duration="200" />    <item        android:drawable="@mipmap/now_playing_matrix_02"        android:duration="200" />    <item        android:drawable="@mipmap/now_playing_matrix_03"        android:duration="200" />    <item        android:drawable="@mipmap/now_playing_matrix_04"        android:duration="200" />    <item        android:drawable="@mipmap/now_playing_matrix_05"        android:duration="200" />    <item        android:drawable="@mipmap/now_playing_matrix_06"        android:duration="200" />    <item        android:drawable="@mipmap/now_playing_matrix_07"        android:duration="200" />    <item        android:drawable="@mipmap/now_playing_matrix_08"        android:duration="200" />    <item        android:drawable="@mipmap/now_playing_matrix_09"        android:duration="200" /></animation-list>
       //播放页面控件加载完成后,加载音频跳动动画   AnimationDrawable animationDrawable = (AnimationDrawable) ivAnim.getBackground();   animationDrawable.start();

Activity与Service交互

可以再看下上面的结构图,播放音乐等相关功能是在服务中实现的,通过服务的中间人对象,实现交互。
下面这些方法是写在服务中间人对象(AudioServiceBinder)中的。

  1. 播放音乐

    /** * 播放音乐 */public void playAudio() {    if (mediaPlayer != null) {        mediaPlayer.release();        mediaPlayer = null;    }    mediaPlayer = new MediaPlayer();    AudioItem audioItem = audioList.get(currentPosition);    try {        mediaPlayer.setDataSource(AudioPlayerService.this, Uri.parse(audioItem.getPath()));        mediaPlayer.setOnPreparedListener(onPreparedListener);        mediaPlayer.setOnCompletionListener(onCompletionListener);        mediaPlayer.prepareAsync();    } catch (IOException e) {        e.printStackTrace();    }}
  2. 判断音乐是否正在播放

    public boolean isPlaying() {     return mediaPlayer != null && mediaPlayer.isPlaying();}
  3. 暂停

    public void pause() {    if (mediaPlayer != null) {        mediaPlayer.pause();    }}
  4. 播放

    public void start() {    if (mediaPlayer != null) {        mediaPlayer.start();    }}
  5. 获取歌曲总时间

    public long getDuration() {    return mediaPlayer == null ? 0 : mediaPlayer.getDuration();}
  6. 获取歌曲当前的时间

    public long getCurrentPosition() {    return mediaPlayer == null ? 0 : mediaPlayer.getCurrentPosition();}
  7. 拖动歌曲到指定的时间

    public void seekTo(int progress) {    if (mediaPlayer != null) {        mediaPlayer.seekTo(progress);    }}
  8. 播放上一首

    public void playPre() {    if (currentPosition > 0) {        currentPosition--;        playAudio();    }}
  9. 播放下一首

    public void playNext() {    if (currentPosition < audioList.size() - 1) {        currentPosition++;        playAudio();    }}
  10. 获取当前正在播放的音乐的position

    public int getCurrentPlayingIndex() {    return currentPosition;}
  11. 获取音乐集合的大小

    public int getAudioListSize() {    return audioList.size();}
  12. 切换播放模式

    public void switchPlayMode() {switch (playMode) {    case MODE_ORDER://顺序播放 --> 单曲循环        playMode = MODE_SINGLE_REPEAT;        break;    case MODE_SINGLE_REPEAT://单曲循环 --> 循环播放        playMode = MODE_ALL_REPEAT;        break;    case MODE_ALL_REPEAT://循环播放 --> 顺序播放        playMode = MODE_ORDER;        break;}    savePlayMode();}
  13. 保存播放模式

    private void savePlayMode() {    SharedPreferenceUtils.putInt(AudioPlayerService.this, "playMode", playMode);}
  14. 获取播放模式(让外界获取)

    public int getPlayMode() {    return playMode;}
  15. 从SharedPreferences中获取播放模式

    private int getPlayModeFromSp() {    return SharedPreferenceUtils.getInt(AudioPlayerService.this, "playMode", MODE_ORDER);}
  16. 歌曲准备完成的监听器

    private OnPreparedListener onPreparedListener = new OnPreparedListener() {    @Override    public void onPrepared(MediaPlayer mp) {        mediaPlayer.start();        notifyPrepared();    }};/*** 音乐准备完成时,发送广播将数据传给外界*/private void notifyPrepared() {    Intent intent = new Intent(ACTION_PREPARED);    intent.putExtra("audioItem", audioList.get(currentPosition));    sendBroadcast(intent);}
  17. 歌曲播放完成的监听器

    private OnCompletionListener onCompletionListener = new MediaPlayer.OnCompletionListener() {    @Override    public void onCompletion(MediaPlayer mp) {        notifyCompletion();        autoPlayByPlayMode();    }};private void notifyCompletion() {        Intent intent = new Intent(ACTION_COMPLETION);        intent.putExtra("audioItem", audioList.get(currentPosition));        sendBroadcast(intent);}
  18. 根据播放模式自动播放

    private void autoPlayByPlayMode() {    switch (playMode) {        case MODE_ORDER:            serviceBinder.playNext();            break;        case MODE_SINGLE_REPEAT:            serviceBinder.playAudio();            break;        case MODE_ALL_REPEAT:            if (currentPosition == audioList.size() - 1) {                currentPosition = 0;                serviceBinder.playAudio();            } else {                serviceBinder.playNext();            }            break;    }}

至此,service中的方法基本上写好了。下面音乐播放的activity直接调用这些方法就可以了。

养成好习惯,服务销毁的时候,释放资源。

    @Override    public void onDestroy() {        super.onDestroy();        mediaPlayer.release();        mediaPlayer = null;    }

AudioPlayerActivity的调用代码

  1. 初始化数据

    @Overrideprotected void initData() {    registerAudioStateReceiver();    Bundle bundle = new Bundle();    //将音乐列表传递过来的数据(所有音乐的集合和当期音乐的position)传递给service    bundle = getIntent().getExtras();    Intent serviceIntent = new Intent(this, AudioPlayerService.class);    serviceIntent.putExtras(bundle);    bindService(serviceIntent, new AudioPlayerServiceConn(), Service.BIND_AUTO_CREATE);//为了拿到serviceBinder对象    startService(serviceIntent);//为了传递数据}
    /** * 注册一个音乐状态的广播接受者 */private void registerAudioStateReceiver() {    IntentFilter filter = new IntentFilter();    filter.addAction(AudioPlayerService.ACTION_PREPARED);    filter.addAction(AudioPlayerService.ACTION_COMPLETION);    audioServiceReceiver = new AudioServiceReceiver();    registerReceiver(audioServiceReceiver, filter);}private class AudioServiceReceiver extends BroadcastReceiver {    @Override    public void onReceive(Context context, Intent intent) {        String action = intent.getAction();        if (action.equals(AudioPlayerService.ACTION_PREPARED)) {    //音乐准备完成            AudioItem audioItem = (AudioItem) intent.getSerializableExtra("audioItem");            tvTitle.setText(StringUtil.formatAudioName(audioItem.getTitle()));            tvArtist.setText(audioItem.getArtist());            tvTime.setText("00:00/" + StringUtil.formatVideoDuration(audioItem.getDuration()));            sbAudio.setMax((int) audioItem.getDuration());            updatePlayProgress();            updatePlayModeBtnBg(false);        } else if (action.equals(AudioPlayerService.ACTION_COMPLETION)) {    //音乐播放完成            handler.removeMessages(UPDATE_PROGRESS);            AudioItem audioItem = (AudioItem) intent.getSerializableExtra("audioItem");            tvTime.setText(StringUtil.formatVideoDuration(audioItem.getDuration()) +                    "/" + StringUtil.formatVideoDuration(audioItem.getDuration()));            sbAudio.setProgress((int) audioItem.getDuration());            ivPlay.setBackgroundResource(R.drawable.selector_btn_audio_play);        }    }}
    /** * 更新播放进度 */private void updatePlayProgress() {    long currentProgress = serviceBinder.getCurrentPosition();    tvTime.setText(StringUtil.formatVideoDuration(currentProgress) + "/" + StringUtil.formatVideoDuration(serviceBinder.getDuration()));    sbAudio.setProgress((int) currentProgress);    handler.sendEmptyMessageDelayed(UPDATE_PROGRESS, 1000);}
  2. 控件的点击事件

    @Overrideprotected void processClick(View view) {    switch (view.getId()) {        case R.id.iv_back:            finish();            break;        case R.id.iv_play_mode:            serviceBinder.switchPlayMode();            updatePlayModeBtnBg(true);            break;        case R.id.iv_pre:            if (serviceBinder.getCurrentPlayingIndex() != 0) {                serviceBinder.playPre();            } else {                ToastUtil.showShort(this, "已经是第一首了");            }            break;        case R.id.iv_play:            if (serviceBinder.isPlaying()) {                serviceBinder.pause();                handler.removeMessages(UPDATE_PROGRESS);                handler.removeMessages(UPDATE_LYRIC);            } else {                serviceBinder.start();                handler.sendEmptyMessage(UPDATE_PROGRESS);                handler.sendEmptyMessage(UPDATE_LYRIC);            }            updatePlayBtnBg();            break;        case R.id.iv_next:            if (serviceBinder.getCurrentPlayingIndex() != serviceBinder.getAudioListSize() - 1) {                serviceBinder.playNext();            } else {                ToastUtil.showShort(this, "已经是最后一首了");            }            break;    }}
    /**更新播放模式按钮的背景 * @param isShowTip 是否显示建议(切换时的toast提醒) */private void updatePlayModeBtnBg(boolean isShowTip) {    int playMode = serviceBinder.getPlayMode();    switch (playMode) {        case AudioPlayerService.MODE_ORDER:            if (isShowTip) ToastUtil.showShort(this, "顺序播放");            ivPlayMode.setBackgroundResource(R.drawable.selector_btn_mode_order);            break;        case AudioPlayerService.MODE_SINGLE_REPEAT:            if (isShowTip) ToastUtil.showShort(this, "单曲循环");            ivPlayMode.setBackgroundResource(R.drawable.selector_btn_mode_single_repeat);            break;        case AudioPlayerService.MODE_ALL_REPEAT:            if (isShowTip) ToastUtil.showShort(this, "循环播放");            ivPlayMode.setBackgroundResource(R.drawable.selector_btn_mode_all_repeat);            break;    }}
    /** * 更新播放按钮的背景 */private void updatePlayBtnBg() {    ivPlay.setBackgroundResource(serviceBinder.isPlaying() ?            R.drawable.selector_btn_audio_pause :            R.drawable.selector_btn_audio_play);}

基本功能完成了,记住在activity销毁的时候,移除一些消息和广播。

    @Override    protected void onDestroy() {        super.onDestroy();        unregisterReceiver(audioServiceReceiver);        handler.removeCallbacksAndMessages(null);    }

好了,先整理到这里。下一篇将整理音乐发送系统通知栏的相关知识,以及滚动歌词的绘制

1 0