Androidアプリで前奏より後だけループするBGMを流す

趣味でAndroid向けアクションゲームを作っている。
年末年始を使ってBGMとSEを組み込んでいた。

AndroidでのBGM再生にはMediaPlayerクラスを使う。
res/raw/以下にoggファイルを突っ込む
(mp3でもいいけど端末によってはあまり嬉しくないことがあるとか?)
とかその辺はいいんだけど、ゲームでよくある
「前奏が一回だけ流れた後、ループ部だけが延々繰り返される」
BGMを流したかった。

流すBGMの音楽ファイルは、あらかじめ前奏部とループ部に分割しておく。
で、両方の曲データを使ってMediaPlayerのインスタンスを作る。
MediaPlayerのインスタンスは4つまでしか作れないとかあるいは10個までとか
いろんな情報があるけど、何にしろアプリ内で使う曲全部を
起動時に全部読み込んでおくのはできないらしい。
曲を変更するときはインスタンスも作り直し。

overture=MediaPlayer.create(context, nowPlaying.overture);
overture.setAudioStreamType(AudioManager.STREAM_MUSIC);
overture.setLooping(false);


refrain=MediaPlayer.create(context, nowPlaying.refrain);
refrain.setAudioStreamType(AudioManager.STREAM_MUSIC);
refrain.setLooping(true);



overtureとrefrainは両方MediaPlayerクラス。
nowPlayingにはBGMの情報がいろいろ収まっている。(名前もそのまま、BGMInfoってクラスを作った)
MediaPlayer.createで渡しているのは音楽データのリソースID。
もちろんR.raw.bgm1とかを直接渡してもいい。


んで、ここで2つできたMediaPlayerインスタンスのうち、
overtureを一回だけ流してから途切れずにrefrainに移行したい。
AndroidAPI Levelが16(Jelly Bean)以上ならとても簡単だったりする。

overture.setNextMediaPlayer(refrain);



これだけ。overtureの再生が終わったらすぐにrefrainの再生を開始してくれる。
曰くas seamlessly as possible.ギャップレス再生も大抵可能。
だけどもうちょい古い端末でも動作させたい。


MediaPlayerにはOnCompletionListenerなんてのがある。
曲の再生が終わったら呼ばれるので、ここでrefrainの再生を開始させようと思ったけど、
どうも曲の切り替わりタイミングで一瞬音が途切れるのでボツ。

結局力技でやることにした。
とりあえず再生開始時にはAPI Levelを調べて、setNextMediaPlayerが使えるならセット。
古い端末ならovertureの総再生時間を調べる。

if(Build.VERSION.SDK_INT>=16){
    overture.setNextMediaPlayer(refrain);
}else{
	refrain.start();
	refrain.pause();
	nowPlaying.overtureLength=overture.getDuration();
}

overture.start();



refrainの再生開始時に少しでも時間をかけないように、
一旦startしてからすぐにpauseしてる。…効果があるのかは微妙なところ。


そして、アプリ内で定期的にovertureが終わりそうか否かチェック。

public static void conduct(int interval){
    if(Build.VERSION.SDK_INT<16 && overture!=null && refrain!=null){
    	if(overture.isPlaying()
			&& overture.getCurrentPosition()
    			> nowPlaying.overtureLength-interval/2)
		{
			Thread t=new Thread(new Runnable(){
				public void run(){
					overture.stop();
					refrain.start();
				}});
			t.start();
		}
	}
}



intervalはこのconductメソッドを呼び出す周期。ゲームのフレームレートとか。
overtureの現在時間が、終了時間間際とか直後とか、
その辺だったらrefrainに移行する。この辺のタイミング判定は要調整かも。
アクションゲームでの使用を想定しているので、
曲の切り替えで処理落ちとかさせたくない。
という訳で非同期で曲変更。…これも果たして意味があるのかどうか。


やってみた感じだと違和感なく繋がっている模様。