android端音乐播放(无缝)

-by 李泽君 2019-10-25

  • 项目背景: 眼罩蓝牙项目

  • 实现功能: 音乐无缝播放及混合播放


前言

MediaPlayer 是多媒体包的一个 基本工具,但它一次只能处理一个音频或 视频文件.如果只有少量的音频要播放,并且想要快速的性能,SoundPool 类可提供帮助, 它在底层使用了 MediaPlayer APISoundPool 与 MediaPlayer 之间的一个区别是: SoundPool 是仅为处理本地媒体文件而设计的


11-20更新 setNextMediaPlayer实现方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private int mPlayResId = R.raw.wav1;
public void testLoopPlayer() {
mediaPlayer = MediaPlayer.create(this, mPlayResId);
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
mediaPlayer.start();
}
});
createNextMediaPlayer();
}

private void createNextMediaPlayer() {
nMediaPlayer = MediaPlayer.create(this, mPlayResId);
mediaPlayer.setNextMediaPlayer(nMediaPlayer);
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
mp.release();

mediaPlayer = nMediaPlayer;

createNextMediaPlayer();
}
});
}

一, MediaPlayer实现

######MediaPlayer简介:
Android MediaPlayer 设置Loop=true之后呢音乐是会循环,但循环中间会出现停顿,其实原来就是再整首播放结束后再调用MediaPlayer的start函数,但start函数调用是需要时间,这时间就是停顿的原因.

基本思路:
设置两个播放器, 在第一个播放时, 设置监听器, 对进度进行判断, 若到结尾, 则启动第二个播放器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164

mediaPlayerOne = new MediaPlayer();
mediaPlayerTwo = new MediaPlayer();

AssetManager assetManager = getAssets();
AssetFileDescriptor descriptor = null;

try {
descriptor = assetManager.openFd("3dmbridge.wav");

mediaPlayerOne.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength());
mediaPlayerTwo.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength());
mediaPlayerOne.prepareAsync();
mediaPlayerTwo.prepareAsync();
} catch (IOException e) {
e.printStackTrace();
}

viewById.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

mediaPlayerOne.start();
setDownTimerByTime(24 * 60 * 60 * 1000, 10);
}
});
}


public void setDownTimerByTime(int totalTime,int time){
if(timerSleep !=null){
timerSleep.cancel();
}
timerSleep = new CountDownTimer(totalTime, time) {

@Override
public void onTick(long millisUntilFinished) {

if(mediaplayIndex ==0){
//当前播放的是第一个播放器
if(mediaPlayerOne.isPlaying()) {
int av = mediaPlayerOne.getDuration() - mediaPlayerOne.getCurrentPosition();
if (av <= 5000) {
//如果播放快结束了,距离结束还剩下5秒,这个时候就要通过毫秒来获取进度了
setTimeMil(24 * 60 * 60 * 1000, 10);
if (timerSleep != null) {
timerSleep.cancel();
}
}
}
}else{
//当前播放的是第二个播放器
if(mediaPlayerTwo.isPlaying()) {
int av = mediaPlayerTwo.getDuration() - mediaPlayerTwo.getCurrentPosition();
if (av <= 5000) {
setTimeMil(24 * 60 * 60 * 1000, 10);
if (timerSleep != null) {
timerSleep.cancel();
}
}
}

}


}
@Override
public void onFinish() {

}
}.start();

}


public void setTimeMil(int totalTime,int time){

if(timerSleepMil !=null){
timerSleepMil.cancel();
}
timerSleepMil = new CountDownTimer(totalTime, time) {

@Override
public void onTick(long millisUntilFinished) {

if(mediaPlayerOne !=null){
if(mediaplayIndex==0){
if(mediaPlayerOne.isPlaying()) {
int av = mediaPlayerOne.getDuration() - mediaPlayerOne.getCurrentPosition();
if (av <= 250) {
//距离快结束250毫秒的时候,启动另外一个音乐播放器,但是音量不适合太大,不然会有重音,暂定0.25吧。
if (!mediaPlayerTwo.isPlaying()) {
mediaPlayerTwo.setVolume(0.25f,0.25f);
mediaPlayerTwo.start();
}
}

if(av<=250&&av>=100){
//在进度剩余100-250的时候呢,对第一个播放器进行降音,对第二播放器进行升音。
float ind=0.75f-((av*3)/1000f);
mediaPlayerOne.setVolume(1-ind,1-ind);
mediaPlayerTwo.setVolume(0.25f+ind,0.25f+ind);
}

if (av <= 100) {
//距离快结束100毫秒的时候,关掉当前播放的播放器,我也想不通我为什么要这么做。
if(mediaPlayerOne.isPlaying()) {
mediaPlayerOne.seekTo(0);
}
if(mediaPlayerOne.isPlaying()) {
mediaPlayerOne.pause();
}
mediaplayIndex = 1;

setDownTimerByTime(24 * 60 * 60 * 1000, 1000);
if (timerSleepMil != null) {
timerSleepMil.cancel();
}
}
}

}else{
//同上面一致
if(mediaPlayerTwo.isPlaying()) {
int av = mediaPlayerTwo.getDuration() - mediaPlayerTwo.getCurrentPosition();
if (av <= 250) {
if (!mediaPlayerOne.isPlaying()) {
mediaPlayerOne.setVolume(0.25f,0.25f);
mediaPlayerOne.start();
}
}

if(av<=250&&av>=100){

float ind=0.75f-((av*3)/1000f);
mediaPlayerTwo.setVolume(1-ind,1-ind);
mediaPlayerOne.setVolume(0.25f+ind,0.25f+ind);
}

if (av <= 100) {
if(mediaPlayerTwo.isPlaying()) {
mediaPlayerTwo.seekTo(0);
}
if(mediaPlayerTwo.isPlaying()) {
mediaPlayerTwo.pause();
}
mediaplayIndex = 0;
setDownTimerByTime(24 * 60 * 60 * 1000, 1000);
if (timerSleepMil != null) {
timerSleepMil.cancel();
}
}
}
}
}

}
@Override
public void onFinish() {

}
}.start();

}

在使用MediaPlayer播放一段流媒体的时候,需要使用prepare()或prepareAsync()方法把流媒体装载进MediaPlayer,才可以调用start()方法播放流媒体。setAudioStreamType()方法用于指定播放流媒体的类型,它传递的是一个int类型的数据,均以常量定义在AudioManager类中, 一般我们播放音频文件,设置为AudioManager.STREAM_MUSIC即可


注意释放操作:

1
2
3
4
5
6
7
8
9
@Override
protected void onDestroy() {
super.onDestroy();
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
}

二 SoundPool实现:

######SoundPool简介:
SoundPool支持多个音频文件同时播放(组合音频也是有上限的),延时短,比较适合短促、密集的场景

实现思路:
直接设置为循环播放即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

AssetManager assetManager = getAssets();
AssetFileDescriptor descriptor = null;


final SoundPool soundPool = new SoundPool(4, 0, 0);
final int haha = soundPool.load(descriptor, 1);
soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
@Override
public void onLoadComplete(SoundPool soundPool, int i, int i1) {
Log.d(TAG, "onLoadComplete: " + i + "...." + i1);
Log.d(TAG, "soundPool play time: " + System.currentTimeMillis());
}
});
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(view.getContext(), "bofang ", Toast.LENGTH_LONG).show();
Log.d(TAG, "soundPool play time: " + System.currentTimeMillis());
soundPool.play(haha, 0.5f, 0.5f, 1, -1, 1.0f);
}
});

注意点:
loop mode (0 = no loop, -1 = loop forever)

音频播放的两种方式, 以后在项目中可以作为模板代码使用.