MainActivity
package org.wp.activity;
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
/**
* 使用MediaPlayer来播放音频文件存在一些不足:
* 例如:资源占用量较高、延迟时间较长、不支持多个音频同时播放等。
* 这些缺点决定了MediaPlayer在某些场合的使用情况不会很理想,
* 例如在对时间精准度要求相对较高的游戏开发中。
* 游戏里面同时播放多个音效是常有的事,用过MediaPlayer的朋友都该知道,
* 它是不支持实时播放多个声音的,会出现或多或少的延迟,
* 而且这个延迟是无法让人忍受的,
* 尤其是在快速连续播放声音(比如连续猛点按钮)时,会非常明显,
* 长的时候会出现3~5秒的延迟,
* 【使用MediaPlayer.seekTo()这个方法来解决此问题】;
*
*
*
* 相对于使用SoundPool存在的一些问题:
* 1. SoundPool最大只能申请1M的内存空间,
* 这就意味着我们只能使用一些很短的声音片段,
* 而不是用它来播放歌曲或者游戏背景音乐
* (背景音乐可以考虑使用JetPlayer来播放)。
*
* 2. SoundPool提供了pause和stop方法,
* 但这些方法建议最好不要轻易使用,
* 因为有些时候它们可能会使你的程序莫名其妙的终止。
* 还有些朋友反映它们不会立即中止播放声音,
* 而是把缓冲区里的数据播放完才会停下来,也许会多播放一秒钟。
*
* 3.音频格式建议使用OGG格式。
* 使用WAV格式的音频文件存放游戏音效,经过反复测试,
* 在音效播放间隔较短的情况下会出现异常关闭的情况
* (有说法是SoundPool目前只对16bit的WAV文件有较好的支持)。
* 后来将文件转成OGG格式,问题得到了解决。
*
* 4.在使用SoundPool播放音频的时候,
* 如果在初始化中就调用播放函数进行播放音乐那么根本没有声音,
* 不是因为没有执行,而是SoundPool需要一准备时间!
* 囧。当然这个准备时间也很短,不会影响使用,
* 只是程序一运行就播放会没有声音罢了,
* 所以我把SoundPool播放写在了按键中处理了
*
*
* 怎么才知道一首歌曲播放完了,那么这里给说下:
* PlaybackCompleted状态:文件正常播放完毕,而又没有设置循环播放的话就进入该状态,
* 并会触发OnCompletionListener的onCompletion()方法。
* 此时可以调用start()方法重新从头播放文件,
* 也可以stop()停止MediaPlayer,或者也可以seekTo()来重新定位播放位置。
* 注意:1、 别忘记绑定操作! mp.setOnCompletionListener(this);
* 2、如果你设置了循环播放 mp.setLooping(true); 的话,
* 那么永远都不会监听到播放完成的状态!!!!这里一定要注意!
*
* @author wp
*
*/
public class MainActivity extends Activity {
public static Activity INSTANCE;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
INSTANCE = this;
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(new MySurfaceView(this));
}
}
MySurfaceView
package org.wp.activity;
import java.util.HashMap;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.SoundPool;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/**
* ==============================================================
* AudioManager
* ==============================================================
* AudioManager provides access to volume and ringer mode control.
* Use Context.getSystemService(Context.AUDIO_SERVICE)
* to get an instance of this class.
*
* AudioManager 类提供访问音量和振铃模式的控制。
* 用Context.getSystemService(Context.AUDIO_SERVICE)得到这个类的实例。
*
*
* ==============================================================
* getStreamMaxVolume
* ==============================================================
* public int getStreamMaxVolume (int streamType)
* Since: API Level 1 Returns the maximum volume index for a particular stream.
* Parameters
* streamType The stream type whose maximum volume index is returned.
* Returns The maximum valid volume index for the stream.
*
* public int getStreamMaxVolume (int streamType)
* 返回特定流的最大音量索引。
* 参数
* streamType 返回最大音量索引的流类型。
* 返回值 流的最大有效音量索引。
*
*
* ==============================================================
* STREAM_MUSIC
* ==============================================================
* public static final int STREAM_MUSIC
* Since: API Level 1 The audio stream for music playback
*
* public static final int STREAM_MUSIC 用于音乐回放的音频流。
*
* ==============================================================
* SoundPool
* ==============================================================
* The SoundPool class manages and plays audio resources for applications.
*
* public SoundPool (int maxStreams, int streamType, int srcQuality)
* Parameters
* maxStreams the maximum number of simultaneous streams for this SoundPool object
* streamType the audio stream type as described in AudioManager For example,
* game applications will normally use STREAM_MUSIC.
* srcQuality the sample-rate converter quality. Currently has no effect.
* Use 0 for the default.
* Returns
* a SoundPool object, or null if creation failed
*
* public SoundPool (int maxStream, int streamType, int srcQuality)
* maxStream —— 同时播放的流的最大数量
* streamType —— 流的类型,一般为STREAM_MUSIC(具体在AudioManager类中列出)
* srcQuality —— 采样率转化质量,当前无效果,使用0作为默认值
*
*
* ==============================================================
* load
* ==============================================================
* public int load (Context context, int resId, int priority)
* Since: API Level 1 Load the sound from the specified APK resource.
* Note that the extension is dropped. For example, if you want to load a sound
* from the raw resource file "explosion.mp3", you would specify "R.raw.explosion"
* as the resource ID. Note that this means you cannot have both an "explosion.wav"
* and an "explosion.mp3" in the res/raw directory.
*
* Parameters
* context the application context
* resId the resource ID
* priority the priority of the sound. Currently has no effect.
* Use a value of 1 for future compatibility.
* Returns
* a sound ID. This value can be used to play or unload the sound.
*
* priority 流的优先级,值越大优先级高,标识优先考虑的声音
* 影响当同时播放数量超出了最大支持数时SoundPool对该流的处理
* 这个参数目前没有效果,建议设置为1
* 使用了也只是对未来的兼容性价值。
*
* @author wp
*
*/
public class MySurfaceView extends SurfaceView implements
SurfaceHolder.Callback, Runnable {
private SurfaceHolder sfh;
private Paint paint;
private Canvas canvas;
private AudioManager am;
private int currentVol, maxVol;
private MediaPlayer player;
private SoundPool soundPool;
private HashMap<Integer, Integer> soundPoolMap;
private int loadId;
private boolean ON = true;
public MySurfaceView(Context context) {
super(context);
this.setKeepScreenOn(true);
this.setFocusable(true);
sfh = this.getHolder();
sfh.addCallback(this);
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
// 获取最大音量值(15最大,不是100!)
maxVol = am.getStreamVolume(AudioManager.STREAM_MUSIC);
/** static MediaPlayer create(Context context, int resid)
根据给定的资源id创建MediaPlayer对象. **/
player = MediaPlayer.create(context, R.raw.wp);
player.setLooping(true);
soundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 100);
/*
* 我这里使用hashmap主要是为了存入多个音频的ID,播放的时候可以同时播放多个音频。
* SoundPool可以支持多个音频同时播放,
* 而且SoundPool在播放的时候调用的这个方法
* soundPool.play(loadId, currentVol, currentVol, 1, 0, 1f);
* 第一个参数指的就是之前的loadId
* 是通过soundPool.load(context, R.raw.himi_ogg, 1);方法取出来的
*/
soundPoolMap = new HashMap<Integer, Integer>();
soundPoolMap.put(1, soundPool.load(context, R.raw.wp_ogg, 1));
loadId = soundPool.load(context, R.raw.wp_ogg, 1);
}
/**
* ==============================================================
* setVolumeControlStream
* ==============================================================
* public final void setVolumeControlStream (int streamType)
* Since: API Level 1 Suggests an audio stream whose volume
* should be changed by the hardware volume controls.
* The suggested audio stream will be tied to the window of this Activity.
* If the Activity is switched, the stream set here is no longer the suggested stream.
* The client does not need to save and restore the old suggested
* stream value in onPause and onResume.
* Parameters
* streamType The type of the audio stream whose volume
* should be changed by the hardware volume controls.
* It is not guaranteed that the hardware volume controls will always change
* this stream's volume
* (for example, if a call is in progress, its stream's volume may be changed instead).
* To reset back to the default, use USE_DEFAULT_STREAM_TYPE.
*
* Android OS中,如果你去按手机上的调节音量的按钮,会分两种情况,
* 一种是调整手机本身的铃声音量,一种是调整游戏,软件,音乐播放的音量
* 当我们在游戏中的时候 ,总是想调整游戏的音量而不是手机的铃声音量,
* 可是烦人的问题又来了,我在开发中发现,只有游戏中有声音在播放的时候,
* 你才能去调整游戏的音量,否则就是手机的音量
*/
@Override
public void surfaceCreated(SurfaceHolder holder) {
// 设定调整音量为媒体音量,当暂停播放的时候调整音量就不会再默认调整铃声音量了
MainActivity.INSTANCE.setVolumeControlStream(AudioManager.STREAM_MUSIC);
player.start(); // 开始播放音乐
new Thread(this).start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
@Override
public void run() {
while (true) {
draw();
logic();
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void draw() {
canvas = sfh.lockCanvas();
canvas.drawColor(Color.WHITE);
canvas.drawText("当前音量: " + currentVol, 100, 100, paint);
canvas.drawText("当前播放的时间: " + player.getCurrentPosition() + "毫秒", 100, 130, paint);
canvas.drawText("方向键中间按钮切换 暂停/开始", 100, 160, paint);
canvas.drawText("方向键←键快退5秒 ", 100, 190, paint);
canvas.drawText("方向键→键快进5秒 ", 100, 220, paint);
canvas.drawText("方向键↑键增加音量 ", 100, 250, paint);
canvas.drawText("方向键↓键减小音量", 100, 280, paint);
sfh.unlockCanvasAndPost(canvas);
}
/**
* ==============================================================
* getStreamVolume
* ==============================================================
* public int getStreamVolume (int streamType)
* Since: API Level 1 Returns the current volume index for a particular stream.
* Parameters
* streamType The stream type whose volume index is returned.
* Returns
* The current volume index for the stream.
*
* public int getStreamVolume (int streamType)
* 返回特定类的当前音量索引。
* 参数
* streamType 返回音量索引的流类型。
* 返回值
* 流的当前音量索引。
*/
private void logic() {
currentVol = am.getStreamVolume(AudioManager.STREAM_MUSIC); // 不停获得当前音量大小
}
/**
* ==============================================================
* setStreamVolume
* ==============================================================
* public void setStreamVolume (int streamType, int index, int flags)
* Since: API Level 1 Sets the volume index for a particular stream.
* Parameters
* streamType The stream whose volume index should be set.
* index The volume index to set.
* See getStreamMaxVolume(int) for the largest valid value.
* flags One or more flags.
*
* public void setStreamVolume (int streamType, int index, int flags)
* 设置特定流的音量索引。
* 参数
* streamType 欲设置音量索引的流。
* index 欲设置的音量索引。
* 参照getStreamMaxVolume(int) 获得最大有效值。
* flags 一个或多个标志。
*
*
* ==============================================================
* FLAG_PLAY_SOUND
* ==============================================================
* public static final int FLAG_PLAY_SOUND
* Since: API Level 1 Whether to play a sound when changing the volume.
* If this is given to adjustVolume(int, int) or adjustSuggestedStreamVolume(int, int, int),
* it may be ignored in some cases (for example, the decided stream type is not STREAM_RING,
* or the volume is being adjusted downward).
*
* public static final int FLAG_PLAY_SOUND
* 当改变音量时,是否播放声音。
* 若由adjustVolume(int, int)或adjustSuggestedStreamVolume(int, int, int)给出,
* 则在某些情况下会被忽略(例如,采用的流类型不是STREAM_RING,或正在向下调整音量)。
*
* ==============================================================
* seekTo
* ==============================================================
* public void seekTo (int msec)
* Since: API Level 1 Seeks to specified time position.
* Parameters
* msec the offset in milliseconds from the start to seek to
* Throws
* IllegalStateException if the internal player engine has not been initialized
*
*
* ==============================================================
* play
* ==============================================================
* public final int play (int soundID, float leftVolume, float rightVolume,
* int priority, int loop, float rate)
* Since: API Level 1 Play a sound from a sound ID.
* Play the sound specified by the soundID.
* This is the value returned by the load() function.
* Returns a non-zero streamID if successful, zero if it fails.
* The streamID can be used to further control playback.
* Note that calling play() may cause another sound to stop playing if
* the maximum number of active streams is exceeded.
*
* A loop value of -1 means loop forever,
* a value of 0 means don't loop, other values indicate the number of repeats,
* e.g. a value of 1 plays the audio twice.
* The playback rate allows the application
* to vary the playback rate (pitch) of the sound.
* A value of 1.0 means play back at the original frequency.
* A value of 2.0 means play back twice as fast,
* and a value of 0.5 means playback at half speed.
*
* Parameters
* soundID a soundID returned by the load() function
* leftVolume left volume value (range = 0.0 to 1.0)
* rightVolume right volume value (range = 0.0 to 1.0)
* priority stream priority (0 = lowest priority)
* loop loop mode (0 = no loop, -1 = loop forever)
* rate playback rate (1.0 = normal playback, range 0.5 to 2.0)
* Returns
* non-zero streamID if successful, zero if failed
*
* public final int play (int soundID, float leftVolume, float rightVolume,
* int priority, int loop, float rate)
* 播放指定音频的音效,并返回一个streamID 。
*
* play()函数传递的是一个load()返回的soundID——指向一个被记载的音频资源 ,
* 如果播放成功则返回一个非0的streamID——指向一个成功播放的流 ;
* 同一个soundID 可以通过多次调用play()而获得多个不同的streamID (只要不超出同时播放的最大数量);
*
* play()中的priority参数,
* 只在同时播放的流的数量超过了预先设定的最大数量是起作用,
* 管理器将自动终止优先级低的播放流。
*
* 如果存在多个同样优先级的流,再进一步根据其创建事件来处理,
* 新创建的流的年龄是最小的,将被终止;
* 无论如何,程序退出时,手动终止播放并释放资源是必要的。
* eg.
*
* //这里对soundID1的音效进行播放——优先级为0(最低),无限循环,正常速率。
* int streamID = soundPool.play(soundID1 , 1.0, 1.0, 0, -1, 1.0);
* if(streamID ==0){
* // 播放失败
* }else{
* // 播放成功
* }
* // 暂停soundID1的播放
* soundPool.pause(streamID );
* // 恢复soundID1的播放
* soundPool.resume(streamID );
* // 终止播放,记住循环为-1时必须手动停止
* soundPool.stop(streamID );
*
* API中指出,即使使用无效的soundID /streamID
* (操作失败或指向无效的资源)来调用相关函数也不会导致错误,这样能减轻逻辑的处理。
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
ON = !ON;
if (ON)
player.start();
else
player.pause();
} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
currentVol += 1;
if (currentVol > maxVol)
currentVol = 100;
am.setStreamVolume(AudioManager.STREAM_MUSIC, currentVol, AudioManager.FLAG_PLAY_SOUND);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
currentVol -= 1;
if (currentVol < 0)
currentVol = 0;
am.setStreamVolume(AudioManager.STREAM_MUSIC, currentVol, AudioManager.FLAG_PLAY_SOUND);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
if (player.getCurrentPosition() < 5000)
player.seekTo(0);
else
player.seekTo(player.getCurrentPosition() - 5000);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
player.seekTo(player.getCurrentPosition() + 5000);
}
/*
* 第一个参数是通过SoundPool.load()方法返回的音频对应id,
* 第二个第三个参数表示左右声道大小,
* 第四个参数是优先级,第五个参数是循环次数,
* 最后一个是播放速率(1.0 =正常播放,范围是0.5至2.0)
*/
soundPool.play(loadId, currentVol, currentVol, 1, 0, 1f);
return super.onKeyDown(keyCode, event);
}
}