Experiments Never Fail

Xamarin.Android で音声ファイルを順次再生する方法

の回答で書いたやつなんですが。

メディアファイル「a.mp3」「b.mp3」「c.mp3」があり、 a の再生が終わったら b を再生…とする方法です(MediaPlayer 使用)。

//using Android.App;
//using Android.Widget;
//using Android.OS;
//using Android.Media;
//using System.Threading.Tasks;

public class MainActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.Main);
var button = FindViewById<Button>(Resource.Id.myButton);

var sounds = new int[]
{
Resource.Raw.trumpet1,
Resource.Raw.trumpet2
};

button.Click += async (sender, e) =>
{
foreach (var id in sounds)
{
await PlayAsync(id);
}
};
}

// 再生が終了したら true を、エラーだったら false を返す
private Task<bool> PlayAsync(int rscId)
{
var compSource = new TaskCompletionSource<bool>();
var mp = MediaPlayer.Create(this, rscId);

mp.Completion += (_, __) =>
{
compSource.SetResult(true);
};

mp.Error += (_, __) =>
{
compSource.SetResult(false);
};

mp.Start();
return compSource.Task;
}
}

MediaPlayer は、再生が完了すると onCompletion を通知するので、それを受信して次の曲を再生開始すればよいのですが、普通に書くとコールバック地獄に陥るので、Task<T> 化して、フラットに書けるようにします。

このような、「非同期処理で完了がイベントやコールバックで通知されるやつ」を Task<T> な非同期メソッドに変換するために TaskCompletionSource<T> を使う方法、は非常によく使うので覚えておくとよいと思います。過去にはダイアログボックスの表示について同様のテクニックで async/await 化する方法を書きました。

この Task<T> を使ったテクニックは 「C# ならでは」 でしたが、Androidアプリ開発の公式言語である Kotlin でも同じようなことができます。

package nepula.net.soundsample

import android.media.MediaPlayer
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.experimental.launch
import kotlin.coroutines.experimental.suspendCoroutine

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val sounds = arrayOf(
R.raw.trumpet1,
R.raw.trumpet2
)

button.setOnClickListener {
launch {
sounds.forEach {id ->
playAsync(id)
}
}
}
}

suspend fun playAsync(id:Int) : Boolean {
return suspendCoroutine { cont : Continuation<Boolean> ->
val mp = MediaPlayer.create(this, id)

mp.setOnCompletionListener {
cont.resume(true)
}

mp.setOnErrorListener ( object : MediaPlayer.OnErrorListener {
override fun onError(p0: MediaPlayer?, p1: Int, p2: Int): Boolean {
cont.resume(false)
return true
}
})

mp.start()
}

}
}

TaskCompletionSource<T> の代わりに Continuation<T> を使う感じで。
非同期処理でも、レスポンス(or エラー)が一発で終わるものは RxJava を使う必要はないので、上記のようなパターンもよく使いますね。

published at tags: Android C# Xamarin