Android 音频播放速率调整实现
最近接触到的一个项目, 有音频播放、切换播放速率和拖动进度到某处播放的需求 ,由于之前只是见过并没有尝试过切换播放速率 , 于是开始调研并最终实现,下面简单记录一下这次的调研过程。
- MediaPlayer
播放音频最先想到的就是MediaPlayer这个Android提供的原生API了,在Android 6.0+(23+)MediaPlayer可以通过setSpeed来改变播放速率
在代码中,我们需要:
// 设置音乐播放速度 public static void changeplayerSpeed(float speed) { if (mPlayer == null) { return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // API 23 (6.0)以上 ,通过设置Speed改变音乐的播放速率 if (mPlayer.isPlaying()) { // 判断是否正在播放,未播放时,要在设置Speed后,暂停音乐播放 mPlayer.setPlaybackParams(mPlayer.getPlaybackParams().setSpeed(speed)); } else { mPlayer.setPlaybackParams(mPlayer.getPlaybackParams().setSpeed(speed)); mPlayer.pause(); } } else { // 在Android6.0以前,需要另想办法处理,后续查到好的方法再补充 } }
实际实现过程中 ,我发现手上的测试机Honor V9执行该操作后 ,播放静默了 ,不仅没有实现播放速率的切换,播放也不能恢复。无奈,只好另寻他法。
- PLMediaPlayer
PLDroidPlayer是七牛SDK提供的一套API, PLMediaPlayer
实现了一个媒体播放器的各种基础功能和接口,与 Android 官方的 MediaPlayer
的设计基本保持一致。
关键代码就一行
private PLOnPreparedListener mOnPreparedListener = new PLOnPreparedListener() { @Override public void onPrepared(int preparedTime) { Log.i(TAG, "On Prepared !"); mMediaPlayer.start();
//设置播放速率为2x mMediaPlayer.setPlaySpeed(2.0f); mIsStopped = false; } };
实际实现过程中 ,播放速率切换正常,但seekTo操作大概率失效,于是去github上查探究竟,发现仍存在该问题的ISSUE,遂放弃。
- ijkPlayer
ijkplayer是b站基于ffplay的轻量级Android/iOS视频播放器,实现了跨平台的功能,API易于集成;编译配置可裁剪,方便控制安装包大小。
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "soundtouch", 1);
ijkMediaPlayer.setSpeed(2.0f);
api和用法类似mediaplayer,关键代码也是只有一行。
实际实现过程中 ,seekTo正常,播放速率切换也正常(只是在切换到慢速0.5x的时候存在重音的情况),但是播放不了https开头url的音频文件,搜索了一下需要自己编译ijkplayer源码以支持https,遂放弃。
- ExoPlayer
最终选择的是google的exoPlayer来实现,api类似MediaPlayer,但也有些差异,下面贴出关键播放控制部分的代码。
package com.weex.app.media; import android.content.Context; import android.net.Uri; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.util.Util; import java.io.File; public class AudioPlayerManager { private static final String TAG = "AudioPlayerManager"; public static Float[] speedArray = new Float[]{1.0f, 1.25f, 1.75f, 0.5f, 0.75f}; private static AudioPlayerManager instance; private Context context; private SimpleExoPlayer mediaPlayer; private DataSource.Factory dataSourceFactory; private AudioPlayerManager(Context context) { this.context = context; createPlayer(); } public static AudioPlayerManager getInstance(Context context) { synchronized (AudioPlayerManager.class) { if (instance == null) { instance = new AudioPlayerManager(context); } } return instance; } public ExoPlayer getMediaPlayer() { return mediaPlayer; } //设置播放url public void setAudioUrl(String audioUrl) { try { //这是一个代表将要被播放的媒体的MediaSource MediaSource mediaSource = new ExtractorMediaSource.Factory(dataSourceFactory) .createMediaSource(Uri.parse(audioUrl)); mediaPlayer.prepare(mediaSource); mediaPlayer.setPlayWhenReady(false); } catch (Exception e) { e.printStackTrace(); } } //设置播放file public void setAudioFile(File file) { try { //这是一个代表将要被播放的媒体的MediaSource MediaSource mediaSource = new ExtractorMediaSource.Factory(dataSourceFactory) .createMediaSource(Uri.fromFile(file)); mediaPlayer.prepare(mediaSource); mediaPlayer.setPlayWhenReady(false); } catch (Exception e) { e.printStackTrace(); } } //开始播放 public void start() { mediaPlayer.setPlayWhenReady(true); } //判断是否是播放状态 public boolean isPlaying() { int playbackState = mediaPlayer.getPlaybackState(); if (playbackState == SimpleExoPlayer.STATE_READY && mediaPlayer.getPlayWhenReady()) { return true; } return false; } //播放,带回调事件 public void playWithCompletionListener(String url, Player.EventListener listener) { if (listener != null) { mediaPlayer.addListener(listener); } if (url.startsWith("http")) { setAudioUrl(url); } else { setAudioFile(new File(url)); } mediaPlayer.setPlayWhenReady(true); } //播放or暂停 public void playOrPause() { if (isPlaying()) { mediaPlayer.setPlayWhenReady(false); } else { mediaPlayer.setPlayWhenReady(true); } } //切换播放速率 public void switchSpeed(int speedIndex) { // API 23 (6.0)以上 ,通过设置Speed改变音乐的播放速率 if (isPlaying()) { // 判断是否正在播放,未播放时,要在设置Speed后,暂停音乐播放 getMediaPlayer().setPlaybackParameters(new PlaybackParameters(speedArray[speedIndex])); } else { getMediaPlayer().setPlaybackParameters(new PlaybackParameters(speedArray[speedIndex])); getMediaPlayer().setPlayWhenReady(false); } } //停止播放 public void stop(boolean reset) { if (mediaPlayer != null) { mediaPlayer.stop(reset); } } //释放资源 public void release() { if (mediaPlayer != null) { mediaPlayer.release(); } } //创建一个新的player private void createPlayer() { // 创建带宽 BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); // 创建轨道选择工厂 TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter); // 创建轨道选择器实例 TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); //step2.创建播放器 mediaPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector); //创建一个DataSource对象,通过它来下载多媒体数据 dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(context, "loader")); } }
实际自测过程中,表现正常,而且切换播放速率时,没有重(chong)音的情况,但没有在6.0以下的设备上测试过。