1,需流媒体服务器才能支持快进到任意位置断点开始缓冲播放,不然只能在缓冲好的进度内播放。如需要实现快进效果,可在拖动进度到未缓冲区域的时候暂停播放,在缓冲回调中判断缓冲进度和进度条进度再行播放。
2,mediaplayer内部缓冲大小写死了,不可能更改。播放到一定位置才会继续缓冲下面部分。断网后无法继续缓冲
3,后台播放可以加唤醒锁防止休眠停止播放,wifi锁防止wifi断开停止缓冲
4,焦点,监听音频焦点控制暂停和恢复播放
5,最后播放完毕的时间偶尔会达不到总时长,需要在完成监听里纠正
下面是播放服务:
public class MusicService extends Service {
private MyBinder mBinder = new MyBinder();
public MediaPlayer mMediaPlayer;
private final VideoProgressUpdater updater = new VideoProgressUpdater();
private AudioManager.OnAudioFocusChangeListener mAudioFocusChangeListener;
private AudioManager mAudioMgr;
private boolean mIsCached;
private AudioAsyncTask mAudioAsyncTask;
public interface OnProgressListener {
void onProgress(int progress);
}
public interface OnCompletionCallBack {
void OnCompletionCallBack(MediaPlayer mp);
}
public MusicService() {
}
@Override
public void onCreate() {
super.onCreate();
if (null == mMediaPlayer) {
mMediaPlayer = new MediaPlayer();
}
}
private void initServiceAudioListener() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ECLAIR_MR1) {
mAudioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
//失去焦点之后的操作
mBinder.pauseMusic();
break;
case AudioManager.AUDIOFOCUS_GAIN:
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
//获得焦点之后的操作
mBinder.playMusic();
break;
}
}
};
}
}
private void requestAudioFocus() {
if (mAudioMgr == null)
mAudioMgr = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
int ret = mAudioMgr.requestAudioFocus(mAudioFocusChangeListener,
AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
if (ret != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
}
}
private void abandonAudioFocus() {
if (mAudioMgr != null) {
mAudioMgr.abandonAudioFocus(mAudioFocusChangeListener);
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public class MyBinder extends Binder {
public OnProgressListener onProgressListener;
private OnCompletionCallBack mOnCompletionCallBack;
public void setOnPreparedListener(MediaPlayer.OnPreparedListener onPreparedListener) {
mMediaPlayer.setOnPreparedListener(onPreparedListener);
}
// public void setOnCompletionListener(MediaPlayer.OnCompletionListener onCompletionListener) {
// mMediaPlayer.setOnCompletionListener(onCompletionListener);
// }
public void setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener) {
mMediaPlayer.setOnBufferingUpdateListener(onBufferingUpdateListener);
}
public void setOnProgressListener(OnProgressListener onProgressListener) {
this.onProgressListener = onProgressListener;
}
public void setCompletionCallBack(OnCompletionCallBack onCompletionCallBack) {
this.mOnCompletionCallBack = onCompletionCallBack;
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
updater.stop();
if (null != mOnCompletionCallBack) {
mOnCompletionCallBack.OnCompletionCallBack(mp);
}
}
});
}
public void initMusic(String url) {
initMediaPlay(url);
}
public void playMusic() {
if (null != mMediaPlayer && !mMediaPlayer.isPlaying()) {
mMediaPlayer.start();
updater.start();
requestAudioFocus();
}
}
public void pauseMusic() {
if (null != mMediaPlayer && mMediaPlayer.isPlaying()) {
mMediaPlayer.pause();
updater.stop();
abandonAudioFocus();
}
}
public boolean isPlaying() {
return mMediaPlayer.isPlaying();
}
public void resetMusic() {
if (null != mMediaPlayer && !mMediaPlayer.isPlaying()) {
mMediaPlayer.reset();
updater.stop();
}
}
public void closeMedia() {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
updater.stop();
}
}
public boolean isCached() {
return mIsCached;
}
public int getDuration() {
return mMediaPlayer.getDuration();
}
public void initAudioListener() {
initServiceAudioListener();
}
public int getCurrentPosition() {
return mMediaPlayer.getCurrentPosition();
}
public void seekToPositon(int msec) {
mMediaPlayer.seekTo(msec);
}
}
private void initMediaPlay(String url) {
VoiceFileUtils fileUtils = new VoiceFileUtils(this);
try {
if (url.startsWith("https")) {
url = url.replace("https", "http");
}
String path = fileUtils.exists(url);
mMediaPlayer.reset();
mMediaPlayer.setScreenOnWhilePlaying(true);
if (path != null) { // 存在缓存文件
mMediaPlayer.setDataSource(path);
mIsCached = true;
} else {
mAudioAsyncTask = new AudioAsyncTask(fileUtils);
mAudioAsyncTask.execute(url);
mMediaPlayer.setDataSource(this, Uri.parse(url));
mIsCached = false;
}
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.prepareAsync();
mMediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
@Override
public void onSeekComplete(MediaPlayer mp) {
}
});
mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
return false;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
private final class VideoProgressUpdater extends Handler {
public void start() {
sendEmptyMessage(0);
}
public void stop() {
removeMessages(0);
}
@Override
public void handleMessage(Message msg) {
mBinder.onProgressListener.onProgress(mMediaPlayer.getCurrentPosition());
sendEmptyMessageDelayed(0, 1000);
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (mAudioAsyncTask != null && mAudioAsyncTask.getStatus() == AsyncTask.Status.RUNNING) {
mAudioAsyncTask.checkDownFile();
mAudioAsyncTask.cancel(true);
mAudioAsyncTask = null;
}
if (null != mMediaPlayer) {
mBinder.closeMedia();
}
}
}
下载线程工具类:
public class AudioAsyncTask extends AsyncTask<String, Void, Void> {
private VoiceFileUtils audioFile;
public AudioAsyncTask(VoiceFileUtils audioFile) {
this.audioFile = audioFile;
}
public void checkDownFile(){
audioFile.checkDownFile();
}
@Override
protected Void doInBackground(String... params) {
HttpURLConnection conn = null;
try {
URL url = new URL(params[0]); // 构建URL
// 构造网络连接
conn = (HttpURLConnection) url.openConnection();
// 保存音频文件
audioFile.exists(params[0]);
audioFile.saveFile(conn.getInputStream(),params[0]);
} catch (IOException e) {
e.printStackTrace();
} finally {
assert conn != null;
conn.disconnect(); // 断开网络连接
}
return null;
}
}
文件处理工具类:
/**
* 文件处理工具类
*/
public class VoiceFileUtils {
public interface DeleteListener {
public void success();
public void failed(String error);
}
private final Context mContext;
private File file; // 文件根目录
private File tempFile; // 临时文件,保存下载的文件,每次下载过后,清空
public VoiceFileUtils(Context context) {
mContext = context;
// 构造文件根目录
file = new File(getSdcardPath());
if (!file.exists()) { // 判断文件夹是否存在
// 不存在,则创建文件夹
file.mkdirs();
}
}
/**
* 判断本地是否有指定url音频文件的缓存文件
*
* @param url 音频文件url
* @return 缓存文件路径, 如果不存在则返回null
*/
public String exists(String url) throws IOException {
// 截取地址, 获取缓存音频文件的名称
String fileName;
if (url.startsWith("http://")) {
fileName = getMediaID(url) == null ? url.substring(url.lastIndexOf("/")) : getMediaID(url);
// 构建文件
tempFile = new File(file, fileName);
Log.i("AUDIO_TOOLS", "缓存路径 = " + tempFile.getAbsolutePath());
// 如果文件存在,则返回文件路径,如果文件不存在则返回null并且创建文件
if (tempFile.exists()) {
return tempFile.getAbsolutePath();
} else {
tempFile.createNewFile(); // 创建音频文件
return null;
}
} else {
fileName = url;
tempFile = new File(fileName);
// 如果文件存在,则返回文件路径,如果文件不存在则返回null并且创建文件
if (tempFile.exists()) {
return tempFile.getAbsolutePath();
} else {
tempFile.createNewFile(); // 创建音频文件
return null;
}
}
}
/**
* 得到下载内容的大小
*
* @param downloadUrl
* @return
*/
private long getContentLength(String downloadUrl) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(downloadUrl).build();
try {
Response response = client.newCall(request).execute();
if (response != null && response.isSuccessful()) {
long contentLength = response.body().contentLength();
response.body().close();
return contentLength;
}
} catch (IOException e) {
e.printStackTrace();
}
return 0;
}
private String getMediaID(String url) {
// String mediaID = url.split("/media/")[1];
// return mediaID.split("/")[0];
String mediaID = url.replaceAll("\\.", "").replaceAll("/", "").replaceAll(":", "") + ".aud";
Log.i("AUDIO_TOOLS", "缓存id = " + mediaID);
return mediaID;
}
boolean isDownSucess;
public void checkDownFile() {
if (null != tempFile && tempFile.exists() && !isDownSucess) {
tempFile.delete();
}
}
/**
* 保存音频文件
*
* @param is InputStream
*/
public void saveFile(InputStream is, String url) throws IOException {
// 构建文件输出流
FileOutputStream fos = new FileOutputStream(tempFile);
byte[] b = new byte[1024]; // 构建缓冲区
// 循环读取数据
int byteCount; // 每次读取的长度
while ((byteCount = is.read(b)) != -1) {
fos.write(b, 0, byteCount); // 将每次读取的数据保存到文件当中
}
fos.close(); // 关闭文件输出流
is.close(); // 关闭输入流
if (tempFile.exists()) {
//如果文件存在的话,得到文件的大小
long downloadLength = tempFile.length();
long contentLength = getContentLength(url);
if (contentLength != 0 && contentLength == downloadLength) {
isDownSucess = true;
}
}
}
/**
* 删除文件(多个文件)
*
* @param fileNames 文件名的集合
*/
public void deleteFiles(List<String> fileNames) {
if (fileNames != null && fileNames.size() > 0) {
// 循环依次删除每一个文件
for (String fileName : fileNames) {
// 构建文件
File f = new File(file, fileName);
if (f.exists()) { // 文件存在
f.delete(); // 删除文件
}
}
}
}
/**
* 合并所有的音频文件为一个音频文件(后缀名为.amr)
* 由于.amr的文件的头文件大小固定都是6个字节
* 所以合并的时候,只需要把除去第一个文件外的头文件(6个字节)然后拼接在一起
*
* @param fileNames 文件名称集合
* @return 合并后的文件File
*/
public File mergeAudioFiles(List<String> fileNames) throws IOException {
// 创建临时文件保存合并后的文件
tempFile = new File(file, System.currentTimeMillis() + "_temp.amr");
if (tempFile.exists()) { // 如果文件存在,则删除文件
tempFile.delete(); // 删除文件
}
tempFile.createNewFile(); // 重新创建新文件
// 构建文件输出流,用来写数据到文件中
FileOutputStream os = new FileOutputStream(tempFile);
RandomAccessFile raf;
// 循环依次读取每一个文件的音频信息
for (int i = 0; i < fileNames.size(); i++) {
// 以只读模式打开文件流
raf = new RandomAccessFile(new File(file, fileNames.get(i)), "r");
// 如果不是第一个文件,则跳过文件头,直接读取音频帧信息
if (i != 0) {
raf.seek(6); // 跳过文件头,.amr文件头为固定的6个字节
}
// 构建临时缓存数组
byte[] bytes = new byte[1024];
int len; // 保存每次读取的数据长度
// 循环依次读取数据
while ((len = raf.read(bytes)) != -1) {
// 将读取的数据写入文件
os.write(bytes, 0, len);
}
raf.close(); // 关闭流
}
// 数据写入完成后
os.close(); // 关闭流
return tempFile;
}
public String audioFileCache = "/audio_cache";
/**
* 获取SDCARD的绝对路径
*
* @return Environment.getExternalStorageDirectory().getAbsolutePath()
*/
private String getSdcardPath() {
return getExternDir(audioFileCache);
}
/**
* 获取保存文件的根目录
*
* @return String file.getAbsolutePath()
*/
public String getFileDirector() {
return file.getAbsolutePath();
}
public File getFile() {
return file;
}
public String getExternDir(String dir) {
String path = mContext.getCacheDir().getPath();
if (dir != null) {
path += dir;
}
return path;
}
public void recursionDeleteFile(DeleteListener mDeleteListener) {
File file = new File(getSdcardPath());
if (file.isFile()) {
mDeleteListener.failed("error : the path is a file");
return;
}
if (file.isDirectory()) {
File[] childFile = file.listFiles();
if (childFile == null || childFile.length == 0) {
mDeleteListener.failed("the directory is already empty");
return;
}
for (File f : childFile) {
if (f.exists()) { // 文件存在
f.delete(); // 删除文件
}
}
}
mDeleteListener.success();
}
}