IjkPlayer 实现音乐播放(进度条、缓存、是否正在缓冲等)
枚举:
public enum AudioPlayEnum { /**播放空闲*/ PLAYER_FREE, /**预缓冲,准备播放中*/ PLAYER_PREPARING, /**播放中*/ PLAYER_PLAYING, /**播放完毕*/ PLAYER_COMPLETE, /**播放暂停*/ PLAYER_PAUSE, /**播放错误*/ PLAYER_ERROR }
回调接口
public interface AudioPlayerEvent { /** * 缓存更新 * */ void onBufferingUpdate(int percent); /** * 是否正在缓冲*/ void onBuffering(boolean isBuffering); /** * 播放进度*/ void onPlayProgress(long duration,long currPosition); /** * 播放状态改变 * @param mStatus 状态 */ void onStatusChange(AudioPlayEnum mStatus,int currPlayPotion); }
抽象类
public abstract class MyAbstractAudioPlayer { AudioPlayerEvent mAudioPlayerEvent; /** * 暂停之后调用,接着上一次播放 */ public abstract void reStart(); /** * 带资源播放,播放当前资源 * @param path path */ public abstract void start(String path); /** * 将整个列表添加到播放列表,如果是未播放状态,则播放第一个 * @param pathList 资源列表 */ public abstract void start(List<String> pathList); /** * 播放事件监听器 * @param event 时间 */ public abstract void setAudioPlayerListener(AudioPlayerEvent event); /** * 暂停 */ public abstract void pause(); /** * 暂停缓存 * @param path 路径 */ public abstract void stopCacheAndShutdown(String path); /** * 下一首 */ public abstract void nextPlay(); /** * 上一首 */ public abstract void prevPlay(); /** * 是否正在播放 * @return boolean */ public abstract boolean isPlaying(); /** * 当前播放状态 * @return AudioPlayEnum */ public abstract AudioPlayEnum getPlayerStatus(); /** * 调整进度 * @param time 时间 */ public abstract void seekTo(long time); /** * 拖动进度条,通知(防止拖动时Timmer跑进度条) */ public abstract void seekStart(); /** * 释放播放器 */ public abstract void release(); /** * 获取当前播放的位置 * @return 获取当前播放的位置 */ public abstract long getCurrentPosition(); /** * 获取视频总时长 * @return long */ public abstract long getDuration(); /** * 获取缓冲百分比 * @return int */ public abstract int getBufferedPercentage(); /** * 设置列表是否循环播放 * @param isLooping 循环 */ public abstract void setListLooping(boolean isLooping); /** * 设置是否单曲循环 * @param isLooping 循环 */ public abstract void setSingleLooping(boolean isLooping); }
该类实现对电话监听
/** * 自定义内部类对来电的电话状态进行监听 * @author chenpin */ public class MyPhoneStateListener extends PhoneStateListener { private MyAudioManager mMyAudioManager; /**记录来电时,是否在播放状态,在电话空闲时恢复*/ private boolean mAudioPlayingWhenCallRinging = false; MyPhoneStateListener(MyAudioManager mMyAudioManager) { this.mMyAudioManager = mMyAudioManager; } @Override public void onCallStateChanged(int state, String incomingNumber) { super.onCallStateChanged(state, incomingNumber); if (mMyAudioManager == null) { return; } switch (state) { case TelephonyManager.CALL_STATE_RINGING: case TelephonyManager.CALL_STATE_OFFHOOK: if(mMyAudioManager.isPlaying()){ mAudioPlayingWhenCallRinging = true; mMyAudioManager.pause(); }else{ mAudioPlayingWhenCallRinging = false; } break; case TelephonyManager.CALL_STATE_IDLE: //打完电话 mMyAudioManager.reStart(); break; default: break; } } }
封装AndroidVideoCache缓存类
/** * @author chenpin */ public class AudioCacheManager { /** * 最大缓存容量 */ private static final long DEFAULT_MAX_SIZE = 200 * 1024 * 1024; /** * 最大缓存数量 */ private static final int DEFAULT_MAX_FILE_COUNT = 20; /** * SD卡APP保存文件名 */ private static final String SD_SAVE_PATH = "appName"; @SuppressLint("SdCardPath") private static final String APP_SAVE_PATH = "/data/data/"; private static final String SAVE_AUDIO_PATH = "audio_cache"; private static AudioCacheManager mInstance; private static HttpProxyCacheServer mCacheServer; private CacheListener mCacheListener; public static AudioCacheManager getInstance(Context context) { Context applicationContext = context.getApplicationContext(); if (null == mInstance) { synchronized (AudioCacheManager.class) { if (null == mInstance) { mInstance = new AudioCacheManager(applicationContext); } } } return mInstance; } private AudioCacheManager(Context context) { File cacheDir = getCacheDirectory(context); Md5FileNameGenerator md5FileNameGenerator = new Md5FileNameGenerator(); HttpProxyCacheServer.Builder builder = new HttpProxyCacheServer.Builder(context) .maxCacheFilesCount(DEFAULT_MAX_FILE_COUNT).cacheDirectory(cacheDir).fileNameGenerator(md5FileNameGenerator); mCacheServer = builder.build(); } static HttpProxyCacheServer getProxy(Context context) { return mCacheServer == null ? (mCacheServer = newProxy(context)) : mCacheServer; } private static HttpProxyCacheServer newProxy(Context context) { return new HttpProxyCacheServer.Builder(context) .maxCacheSize(DEFAULT_MAX_SIZE) .build(); } private File getCacheDirectory(Context context) { File cacheParentDir = getCacheParentDirectory(context); File cacheDir = new File(cacheParentDir, SAVE_AUDIO_PATH); if (!cacheDir.exists()) { cacheDir.mkdirs(); } return cacheDir; } private File getCacheParentDirectory(Context context) { File appCacheDir = null; String externalStorageState; try { externalStorageState = Environment.getExternalStorageState(); } catch (NullPointerException e) { externalStorageState = ""; } if (MEDIA_MOUNTED.equals(externalStorageState)) { appCacheDir = getExternalCacheDir(context); } if (appCacheDir == null) { appCacheDir = context.getCacheDir(); } if (appCacheDir == null) { String cacheDirPath = APP_SAVE_PATH + context.getPackageName() + File.separator+SAVE_AUDIO_PATH+File.separator; appCacheDir = new File(cacheDirPath); } return appCacheDir; } private static File getExternalCacheDir(Context context) { String pathPrix = Environment.getExternalStorageDirectory() + "/"; File file = new File(pathPrix + SD_SAVE_PATH); if (!file.exists()) { file.mkdirs(); } return file; } String getProxyUrl(String url) { return mCacheServer.getProxyUrl(url); } void registerCacheListener(String url, CacheListener listener) { mCacheListener = listener; mCacheServer.registerCacheListener(listener, url); } void unregisterCacheListener() { mCacheServer.unregisterCacheListener(mCacheListener); } }
主要代码,音频管理类:
//音频管理类
/**
* 集成IjkPlayer音頻播放
*
* @author chenpin
*/
public class MyAudioManager extends MyAbstractAudioPlayer {
public static final String TEST_MP3_URL = "http:XXX.mp3";
/**
* 定时器检测播放进度时间间隔
*/
private final int TIMER_PROGRESS_INTERVAL = 200;
/**
* 最大连续播放错误数
*/
private static final int MAX_CONTINUE_ERROR_NUM = 3;
private IjkMediaPlayer mMediaPlayer;
private Context mContext;
private Context appContext;
/**
* 播放错误次数,连续三次错误,则不进行下一次播放了
*/
private int mErrorPlayNum = 0;
/**
* 播放路径资源存储
*/
private List<String> mDataSourceList = new ArrayList<>();
/**
* 播放位置存储
*/
private int currPlayPotion = -1;
/**
* 是否列表循环播放
*/
private boolean mListLooping = false;
/**
* 是否单曲循环
*/
private boolean mSingleLooping = false;
/**
* 缓存百分比
*/
private int mBufferedPercent;
/**
* 是否正在拖动进度条
*/
private boolean mSeekTouch = false;
/**
* 监听播放器进度Timer以及Task
*/
private Timer mProgressTimer;
private TimerTask mProgressTask;
private final Object mProgressLock = new Object();
/**
* 进度线程活动
*/
private boolean mAudioStateAlive = true;
/**
* 音频缓存
*/
private HttpProxyCacheServer mCacheServer;
/**
* 记录上一次播放进度,用于判断是否正在缓冲
*/
private long prevPlayPosition;
/**
* 电话管理者对象
*/
private TelephonyManager mTelephonyManager;
/**
* 电话状态监听者
*/
private MyPhoneStateListener myPhoneStateListener;
/**
* 用于发送是否在缓冲的消息Handler
*/
private Handler mBufferHandler = new Handler(Looper.getMainLooper());
/**
* 当前播放器状态
* */
private AudioPlayEnum mPlayerStatus = AudioPlayEnum.PLAYER_FREE;
public MyAudioManager(Context context) {
mContext = context;
appContext = context.getApplicationContext();
initPlayer();
}
private void initPlayer() {
//初始化
mMediaPlayer = new IjkMediaPlayer(mContext);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
//缓存文件的路径
// mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "cache_file_path", path);
//添加监听
mMediaPlayer.setOnErrorListener(onErrorListener);
mMediaPlayer.setOnCompletionListener(onCompletionListener);
mMediaPlayer.setOnInfoListener(onInfoListener);
mMediaPlayer.setOnBufferingUpdateListener(onBufferingUpdateListener);
mMediaPlayer.setOnPreparedListener(onPreparedListener);
//来电监听
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
myPhoneStateListener = new MyPhoneStateListener(this);
mTelephonyManager.listen(myPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
}
/**
* 播放
*/
private void prepareAsync(int position, String path) {
try {
mMediaPlayer.reset();
sendPlayerStatus(AudioPlayEnum.PLAYER_PREPARING);
mCacheServer = AudioCacheManager.getProxy(appContext);
AudioCacheManager.getInstance(appContext).registerCacheListener(path, new CacheListener() {
@Override
public void onCacheAvailable(File file, String s, int i) {
mBufferedPercent = i;
if (mAudioPlayerEvent != null) {
mAudioPlayerEvent.onBufferingUpdate(mBufferedPercent);
}
}
});
String proxyPath = AudioCacheManager.getInstance(appContext).getProxyUrl(path);
if (mCacheServer.isCached(path)
|| proxyPath.startsWith("file://")
|| proxyPath.startsWith("/storage/emulated/0/")) {
mBufferedPercent = 100;
if (mAudioPlayerEvent != null) {
sendBufferingHandler(true, false);
}
//不要在这里发进度,上层调用不到duration,设置不了max
} else {
mBufferedPercent = 0;
if (mAudioPlayerEvent != null) {
sendBufferingHandler(false, true);
}
}
mMediaPlayer.setDataSource(proxyPath);
mMediaPlayer.prepareAsync();
currPlayPotion = position;
} catch (Exception e) {
e.printStackTrace();
onErrorPlay();
}
}
@Override
public void reStart() {
if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) {
sendPlayerStatus(AudioPlayEnum.PLAYER_PLAYING);
mMediaPlayer.start();
synchronized (mProgressLock) {
mProgressLock.notifyAll();
}
}
}
@Override
public void start(String path) {
if(!StringUtils.isEmpty(path)){
List<String> pathList = new ArrayList<>();
pathList.add(path);
start(pathList);
}
}
@Override
public void start(List<String> pathList) {
if (pathList == null || pathList.isEmpty()) {
return;
}
mDataSourceList.clear();
mDataSourceList.addAll(pathList);
prepareAsync(0, mDataSourceList.get(0));
}
@Override
public void setAudioPlayerListener(AudioPlayerEvent event) {
this.mAudioPlayerEvent = event;
}
@Override
public void pause() {
try {
if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
sendBufferingHandler(true, false);
sendPlayerStatus(AudioPlayEnum.PLAYER_PAUSE);
mMediaPlayer.pause();
}
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
@Override
public void stopCacheAndShutdown(String path) {
if (mCacheServer != null && !StringUtils.isEmpty(path)) {
mCacheServer.stopCacheAndShutdown(path);
}
}
@Override
public void nextPlay() {
if (mDataSourceList.isEmpty()) {
return;
}
if (currPlayPotion < 0) {
prepareAsync(0, mDataSourceList.get(0));
} else {
if (mDataSourceList.size() > currPlayPotion + 1) {
prepareAsync(currPlayPotion + 1, mDataSourceList.get(currPlayPotion + 1));
} else {
if (mListLooping) {
prepareAsync(0, mDataSourceList.get(0));
}
}
}
}
@Override
public void prevPlay() {
if (mDataSourceList.isEmpty() || currPlayPotion < 0) {
return;
}
if (currPlayPotion == 0) {
if (mListLooping) {
prepareAsync(mDataSourceList.size() - 1, mDataSourceList.get(mDataSourceList.size() - 1));
}
} else {
prepareAsync(currPlayPotion - 1, mDataSourceList.get(currPlayPotion - 1));
}
}
@Override
public boolean isPlaying() {
if (mMediaPlayer != null) {
return mMediaPlayer.isPlaying();
}
return false;
}
@Override
public AudioPlayEnum getPlayerStatus() {
return mPlayerStatus;
}
@Override
public void seekTo(long time) {
try {
if (mMediaPlayer != null) {
mMediaPlayer.seekTo(time);
mSeekTouch = false;
if (mMediaPlayer.isPlaying()) {
synchronized (mProgressLock) {
mProgressLock.notifyAll();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void seekStart() {
mSeekTouch = true;
}
@Override
public void release() {
mTelephonyManager.listen(myPhoneStateListener, PhoneStateListener.LISTEN_NONE);
mBufferHandler.removeCallbacksAndMessages(null);
if (mCacheServer != null) {
AudioCacheManager.getInstance(appContext).unregisterCacheListener();
mCacheServer.shutdown();
}
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mAudioStateAlive = false;
currPlayPotion = -1;
mMediaPlayer.release();
}
}
@Override
public long getCurrentPosition() {
return mMediaPlayer.getCurrentPosition();
}
@Override
public long getDuration() {
return mMediaPlayer.getDuration();
}
@Override
public int getBufferedPercentage() {
return mBufferedPercent;
}
@Override
public void setListLooping(boolean isLooping) {
mListLooping = isLooping;
}
@Override
public void setSingleLooping(boolean isLooping) {
mSingleLooping = isLooping;
}
/**
* 销毁Tmmer
*/
private void destroyTimer() {
if (mProgressTimer != null) {
mProgressTimer.cancel();
mProgressTimer = null;
}
}
/**
* 错误时,自动播放下一首
*/
private void onErrorPlay() {
sendPlayerStatus(AudioPlayEnum.PLAYER_ERROR);
destroyTimer();
if (mErrorPlayNum < MAX_CONTINUE_ERROR_NUM) {
mErrorPlayNum++;
if (mSingleLooping) {
//单曲循环
prepareAsync(currPlayPotion, mDataSourceList.get(currPlayPotion));
} else if (mListLooping || mDataSourceList.size() < currPlayPotion + 1) {
//列表循环
nextPlay();
}
}
}
/**
* 延时发送是否在缓冲的状态,防止假缓冲
*/
private void sendBufferingHandler(boolean sendNow, final boolean isBuffering) {
if (mAudioPlayerEvent != null) {
mAudioPlayerEvent.onBuffering(isBuffering);
}
/*mBufferHandler.removeCallbacksAndMessages(null);
mBufferHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (mAudioPlayerEvent != null) {
mAudioPlayerEvent.onBuffering(isBuffering);
}
}
}, sendNow ? 0 : (long) (2.5 * TIMER_PROGRESS_INTERVAL));*/
}
/**
* 设置当前播放状态
*/
private void sendPlayerStatus(AudioPlayEnum mStatus){
mPlayerStatus = mStatus;
if(mAudioPlayerEvent != null){
mAudioPlayerEvent.onStatusChange(mPlayerStatus,currPlayPotion);
}
}
/**
* 定时器检测播放进度
*/
private void playProgressListener() {
if (mProgressTimer == null) {
mProgressTimer = new Timer();
}
if (mProgressTask != null) {
mProgressTask.cancel();
mProgressTask = null;
}
mProgressTask = new TimerTask() {
@Override
public void run() {
while (mAudioStateAlive) {
if (mMediaPlayer == null
|| !mMediaPlayer.isPlaying()
|| mSeekTouch) {
synchronized (mProgressLock) {
try {
mProgressLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} else {
try {
Thread.sleep(TIMER_PROGRESS_INTERVAL);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (mAudioPlayerEvent != null
&& !mSeekTouch
&& mMediaPlayer.isPlaying()
&& mMediaPlayer != null) {
long currPosition = mMediaPlayer.getCurrentPosition();
//播放之前的缓冲在onPrepare时已经缓冲完了,所以这里要排除进度为0
if (currPosition != 0 && prevPlayPosition >= currPosition) {
sendBufferingHandler(false, true);
} else {
sendBufferingHandler(true, false);
}
prevPlayPosition = currPosition;
mAudioPlayerEvent.onPlayProgress(mMediaPlayer.getDuration(), currPosition);
}
}
}
};
mProgressTimer.schedule(mProgressTask, 0);
}
private IMediaPlayer.OnErrorListener onErrorListener = new IMediaPlayer.OnErrorListener() {
@Override
public boolean onError(IMediaPlayer iMediaPlayer, int frameworkErr, int implErr) {
sendBufferingHandler(true, false);
onErrorPlay();
LogUtils.e(frameworkErr + "==>chenpin error" + implErr);
return true;
}
};
private IMediaPlayer.OnInfoListener onInfoListener = new IMediaPlayer.OnInfoListener() {
@Override
public boolean onInfo(IMediaPlayer iMediaPlayer, int what, int extra) {
return true;
}
};
private IMediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new IMediaPlayer.OnBufferingUpdateListener() {
@Override
public void onBufferingUpdate(IMediaPlayer iMediaPlayer, int percent) {
}
};
private IMediaPlayer.OnPreparedListener onPreparedListener = new IMediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(IMediaPlayer iMediaPlayer) {
if (mAudioPlayerEvent != null) {
synchronized (mProgressLock) {
mProgressLock.notifyAll();
}
mErrorPlayNum = 0;
//准备完毕后发送一次缓存(有可能已经缓存完毕)
mAudioPlayerEvent.onBufferingUpdate(mBufferedPercent);
//当前正播放,播放器已经预缓存完毕,开始播放了
sendBufferingHandler(true, false);
//记录并发送当前播放状态
sendPlayerStatus(AudioPlayEnum.PLAYER_PLAYING);
//开始监听播放进度
playProgressListener();
}
}
};
private IMediaPlayer.OnCompletionListener onCompletionListener = new IMediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(IMediaPlayer iMediaPlayer) {
//播放完毕
if (mAudioPlayerEvent != null) {
prevPlayPosition = 0;
sendPlayerStatus(AudioPlayEnum.PLAYER_COMPLETE);
destroyTimer();
if (mSingleLooping) {
//单曲循环
prepareAsync(currPlayPotion, mDataSourceList.get(currPlayPotion));
} else if (mListLooping || mDataSourceList.size() < currPlayPotion + 1) {
//列表循环
nextPlay();
}
}
}
};
}
实现代码:
//该处按钮监听 if (v == nextIcon) { //下一首 myAudioManager.nextPlay(); } else if (v == playStatusIcon) { //播放状态图标 if (myAudioManager.isPlaying()) { myAudioManager.pause(); } else if (myAudioManager.getPlayerStatus() == AudioPlayEnum.PLAYER_PAUSE) { myAudioManager.reStart(); } else { List<String> list = new ArrayList<>(); list.add(TEST_MP3_URL); list.add(TEST_MP3_URL); list.add(TEST_MP3_URL); myAudioManager.start(list); } } else if (v == listLoopTxt || v == listLoopTxtWhenScroll) { //循环 myAudioManager.setListLooping(true); }
//如何使用回调(这里用seekbar来接收进度)
myAudioManager.setAudioPlayerListener(new AudioPlayerEvent() { @Override public void onBufferingUpdate(final int percent) { runOnUiThread(new Runnable() { @Override public void run() { int max = (int) (myAudioManager.getDuration()); if (max > 0) { seekBar.setMax(max); seekBar.setSecondaryProgress(max * percent / 100); } } }); } @Override public void onBuffering(final boolean isBuffering) { runOnUiThread(new Runnable() { @Override public void run() { // title.setText(isBuffering ? "正在缓冲..." : "缓冲完毕"); } }); } @Override public void onPlayProgress(final long duration, final long currPosition) { runOnUiThread(new Runnable() { @Override public void run() { seekBar.setMax((int) duration); seekBar.setProgress((int) currPosition); } }); } @Override public void onStatusChange(AudioPlayEnum mStatus, int currPlayPotion) { //状态改变,会在这回调,可以更新上层播放UI等 } });
}
//seekbar监听,(注意一定要在开始触摸的时候调用seekStart方法,否则会导致进度条回退)
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { } @Override public void onStartTrackingTouch(SeekBar seekBar) { myAudioManager.seekStart(); } @Override public void onStopTrackingTouch(SeekBar seekBar) { int progress = seekBar.getProgress(); myAudioManager.seekTo(progress); } });
在ondestory中销毁
@Override protected void onDestroy() { super.onDestroy(); if (myAudioManager != null) { myAudioManager.release(); } }
这个功能集成在项目里,没有单独弄出来上传资源了