LoginSignup
5
5

More than 5 years have passed since last update.

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

Posted at

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

メディアファイル「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 を使う必要はないので、上記のようなパターンもよく使いますね。

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5