android media player实现一个可随机/顺序播放的可加载专辑图片的音频播放器


文章来自: http://blog.csdn.net/intbird 转载请说明出处

1. 媒体交互框架

Android媒体框架: https://developer.android.com/guide/topics/media-apps/audio-app/building-an-audio-app
https://developer.android.com/guide/topics/media-apps/images/audio-activity-and-service.png

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation "com.android.support:support-media-compat:28.0.0"

1. 服务端

媒体播放资源提供端

  • MediaBrowserServiceCompat
  • onGetRoot
  • onLoadChildren
  • MediaSessionCompat
  • MediaSessionCompat.Token
  • MediaSessionCompat.Callback
  • MediaControllerCompat
  • MediaControllerCompat.Callback
  • PlaybackStateCompat.Builder
  • MediaPlayer

实际播放器,基于接口可被替换

2. 客户端

展示媒体进度和控制播放状态

  • MediaBrowserCompat.ConnectionCallback
  • MediaControllerCompat.Callback
  • MediaBrowserCompat.SubscriptionCallback

3. 两端消息交互

  1. 服务端通知客户端:

mSession.sendSessionEvent(“error_index”, bundle);

  1. 客户端通知服务端:

mediaControllerCompat.getTransportControls().sendCustomAction(“play_index”,bundle);

2. 播放模式

1. 单曲循环和整体循环

1.单曲循环 PlaybackStateCompat.REPEAT_MODE_ONE
2.整体循环 PlaybackStateCompat.REPEAT_MODE_ALL
3.不做循环 PlaybackStateCompat.REPEAT_MODE_NONE

2. 随机播放和顺序播放

  1. 随机播放是否允许再次播放 顺序播放已播放过的内容
  2. 顺序播放是否允许再次播放 随机播放已播放过的内容
  3. 随机播放顺序播放 都是播放队列不循环播放模式

3. 几个问题:

1. 合并播放模式

一般 顺序播放/随机播放/单曲循环/列表循环 是在一个按钮上做来回切换
但是getRepeatMode 和 getShuffleMode是两个模式, 简单做下同步

单曲循环 = controllerCompat.getRepeatMode() == PlaybackStateCompat.REPEAT_MODE_ONE 
          && controllerCompat.getShuffleMode() == PlaybackStateCompat.SHUFFLE_MODE_NONE

if (单曲循环) { 
      controllerCompat.getTransportControls().setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_NONE);
      controllerCompat.getTransportControls().setRepeatMode(PlaybackStateCompat.REPEAT_MODE_ONE);
}  

点击按钮循环模式切换和应用
shuffleIndex = (++shuffleIndex % shuffleConfigMapSize);

点击按钮切换的配置列表
 private void defaultShuffleConfig(MediaControllerCompat controllerCompat) {
        playerShuffleResource.add(new ShuffleConfig(1, R.drawable.icon_play_mode_shuffle));
        playerShuffleResource.add(new ShuffleConfig(2, R.drawable.icon_play_mode_order));
        playerShuffleResource.add(new ShuffleConfig(3, R.drawable.icon_play_cycle_one));
        playerShuffleResource.add(new ShuffleConfig(4, R.drawable.icon_play_cycle_all));
    }

2. 播放指定歌曲

  1. 如果指定的歌曲在队列的某个位置,从此位置后开始继续顺序或者随机播放
  2. 如果指定的歌曲不在列表中,先插入队列,在改变播放指针
  3. 客户端调用sendCustomAction通知到服务端 播放指定的 index
private void showPlayIndex(MediaControllerCompat mediaControllerCompat) {
        Bundle bundle = getIntent().getExtras();
        if (null != bundle) {
            mediaControllerCompat.getTransportControls().sendCustomAction(AudioPlayerHelper.ACTION_INDEX, bundle);
        }
    }

4. 整体无重复随机

  1. 如果播放时随机的话会导致重复播放问题,所以要整体随机
  2. 随机播放和顺序播放可以是
    同一个完整的播放队列(好做同步控制,添加,删除等)
    +两个播放指针 + 两个播放指针记录
    +两个已播队列
    3.待整理上传源码后分享

代码: 生成随机数代码

  if (mShuffleList.size() >= 3) {
        int[] shuffleListIndexes = randomList(0, mShuffleList.size() - 1, mShuffleList.size());
   }
   
  public static int[] randomList(int min, int max, int n) {
       int len = max - min + 1;

       if (max < min || n > len) {
           return null;
       }

       int[] source = new int[len];
       for (int i = min; i < min + len; i++) {
           source[i - min] = i;
       }

       int[] result = new int[n];
       Random rd = new Random();
       int index = 0;
       for (int i = 0; i < result.length; i++) {
           index = Math.abs(rd.nextInt() % len--);
           result[i] = source[index];
           source[index] = source[len];
       }
       return result;
    }

5. ViewPager切换播放歌曲

滑动时切换歌曲

1. ViewPager 和 ViewPager2

PageTransformer

2. RecyclerView 和 PagerSnapeHelper

3. 滑动时和播放列表对应

 // viewPager的item data 为按播放列表顺序放入的 mediaId
 // 每次切换时从媒体库查询最新信息进行加载
 // 也可以放入封装的一个MediaItemData,包含更多信息,不再查询
 int index = musicPagerAdapter.getItemPosition(mediaId);

在这里插入图片描述

6. 页面下滑关闭

1.监听onTouch

override fun onTouch(v: View, event: MotionEvent): Boolean {
              ...
                when (event.actionMasked) {
                    MotionEvent.ACTION_DOWN -> {
                        executeAction = false
                        lastTouchEventX = event.x
                        lastTouchEventY = event.y
                    }
                    MotionEvent.ACTION_MOVE -> {
                        if (executeAction) return true
                        val distanceX = event.x - lastTouchEventX
                        val distanceY = event.y - lastTouchEventY

                        if (abs(distanceX) < touchLimitDistance && distanceY > touchYDistance) {
                            executeAction = true
                            [email protected]()
                            return true
                        }
                    }
                }
                return false
            }
        })

2.页面退出效果

activity Theme.Translucent

 <activity
            android:name=".ui.AudioActivity"
            android:theme="@android:style/Theme.Translucent" />

overridePendingTransition

      public static void startAudioActivity(Context context) {
        Intent intent = new Intent(context, AudioActivity.class);
        context.startActivity(intent);
        if (context instanceof Activity) {
            ((Activity) context).overridePendingTransition(R.anim.lib_audio_player_music_in, 0);
        }
    }

在这里插入图片描述

7.加载音频专辑图片

1. 加载传入媒体库的icon_url

添加媒体库时放入的url: MediaMetadataCompat#METADATA_KEY_ALBUM_ART_URI

 private void loadImage1(final String mediaId, final ImageView albumArt) {
        String imageUrl = MusicLibrary.getAlbumImageUrl(mediaId);
        Glide.with(context)
                .load(imageUrl)
                .transform(getTransform())
                .placeholder(errorPlaceHolder)
                .error(errorPlaceHolder)
                .circleCrop()
                .into(albumArt);
    }

2. 加载手机albumart_url图片

  private void loadImage2(final String mediaId, final ImageView albumArt) {
        Uri albumUri = Uri.parse("content://media/external/audio/albumart");
        Uri uri = ContentUris.withAppendedId(albumUri, Long.parseLong(mediaId));
        Glide.with(context)
                .load(uri)
                .transform(getTransform())
                .placeholder(errorPlaceHolder)
                .error(errorPlaceHolder)
                .circleCrop()
                .into(albumArt);
    }

3. 加载mp3歌曲的附属图片信息

 private void loadImage3(final String mediaId, final ImageView albumArt) {
        //albumArt.setTag(mediaId);
        String pathUrl = MusicLibrary.getAudioPathUrl(mediaId);
        if (!TextUtils.isEmpty(pathUrl)) {
            MusicImageTask.ImageTaskDone imageTaskDone = new MusicImageTask.ImageTaskDone() {
                @Override
                public void done(String pathUrl, byte[] bytes) {
                    Glide.with(context)
                            .load(bytes)
                            .transform(getTransform())
                            .placeholder(errorPlaceHolder)
                            .error(errorPlaceHolder)
                            .circleCrop()
                            .into(albumArt);
                }
            };
            MusicImageTask musicImageTask = new MusicImageTask(pathUrl, imageTaskDone);
            musicImageTask.execute(pathUrl);
            taskDoneArrayList.add(musicImageTask);
        }
    }

MusicImageTask

...
private byte[] parserImage(String mediaUri) {
        MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
        mediaMetadataRetriever.setDataSource(mediaUri);
        return mediaMetadataRetriever.getEmbeddedPicture();
    }

4. Glide依次加载三个url

RequestBuilder

利用 gilde error时使用 RequestBuilder

 @NonNull
  public RequestBuilder<TranscodeType> error(@Nullable RequestBuilder<TranscodeType> errorBuilder) {
    this.errorBuilder = errorBuilder;
    return this;
  }

出错时依次加载


Glide.with(context)
                .load(imageUrl)
                .transform(getTransform())
                .placeholder(errorPlaceHolder)
                .error(errorPlaceHolder)
                .error(Glide.with(context)
                        .load(uri)
                        .transform(getTransform())
                        .placeholder(errorPlaceHolder)
                        .error(errorPlaceHolder)
                        .listener(new RequestListener<Drawable>() {
                            @Override
                            public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
                                loadImage3(mediaId, albumArt);
                                return false;
                            }

                            @Override
                            public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                                return false;
                            }
                        }))
                .circleCrop()
                .into(albumArt);

8.源码实现

待整理后上传到github
文章来自:http://blog.csdn.net/intbird 转载请说明出处

1. 播放效果

SVID

2. 播放截图

在这里插入图片描述

待整理后上传…
4. 媒体服务之 musicService
5. 播放展示之 musicActivity
6. 播放展示之 musicView
7. 播放展示之 musicProgressBar
8. 播放展示之 musicNotification (MediaStyle)

猜你喜欢

转载自blog.csdn.net/intbird/article/details/106849988
今日推荐