【Androidアプリ開発】音を鳴らす。メディア再生

2021年4月10日

前回はサービスと通知を行いました。

【Androidアプリ開発】サービスと通知
【Androidアプリ開発】サービスと通知
前回は、「リストビューのカスタマイズ」を行いました。 今回はバッググラウンドで動作するサービス、それと通知をや…
http://nomux2.net/post-2347/

今回自己学習で作成しているのは、Todoアプリで期限に近づいたらアラームを鳴らしたいのでメディア再生をやろうと思います。

メディア再生

まずはソースを記載します。

メディア再生するActivityのレイアウトファイルです。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".AlarmActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/alarm_btn_stop"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.7"
        android:onClick="OnAlarmStop"
        />
</androidx.constraintlayout.widget.ConstraintLayout>

メディア再生するActivityです。

package com.example.todomanager

import android.media.MediaPlayer
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import java.io.IOException
import java.lang.IllegalArgumentException

class AlarmActivity : AppCompatActivity() {

    private var _player: MediaPlayer? = null

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

        //フィールドのメディアプレーヤーオブジェクトを生成
        _player = MediaPlayer()
        //音声ファイルのURI文字列を作成
        val mediaFileUriStr = "android.resource://${packageName}/${R.raw.alarm1}"
        //音声ファイルのURI文字列を元にURIオブジェクトを生成
        val mediaFileUri = Uri.parse(mediaFileUriStr)
        try{
            //メディアプレーヤーに音声ファイルを指定
            _player?.setDataSource(applicationContext, mediaFileUri)
            //非同期でのメディア再生準備が完了した際のリスナを設定
            _player?.setOnPreparedListener(PlayerPreparedLister())
            //メディア再生が終了した際のリスナを設定
            _player?.setOnCompletionListener(PlayerCompletionLister())
            //非同期でメディア再生準備
            _player?.prepareAsync()
        }
        catch(ex: IllegalArgumentException) {
            Log.e("AlarmActivity", "メディアプレーヤー準備時の例外発生", ex)
        }
        catch (ex: IOException) {
            Log.e("AlarmActivity", "メディアプレーヤー準備時の例外発生", ex)
        }
    }

    //再生準備ができた時のリスナクラス
    private inner class PlayerPreparedLister : MediaPlayer.OnPreparedListener {
        override fun onPrepared(p0: MediaPlayer?) {
            //再生準備ができたら再生開始
            AlarmStart()
        }
    }

    //再生が終了したときのリスナクラス
    private inner class PlayerCompletionLister : MediaPlayer.OnCompletionListener {
        override fun onCompletion(p0: MediaPlayer?) {
            //再生が終了した時の処理を書く
        }
    }

    //再生開始
    fun AlarmStart(){
        //フィールドのプレーヤーがnullじゃなかったら
        _player?.let {
            //プレーヤーが再生中でないなら再生開始
            if(!it.isPlaying) {
                //ループ設定する
                it.isLooping = true
                //再生開始
                it.start()
            }
        }
    }

    //停止
    fun OnAlarmStop(view: View) {
        //フィールドのプレーヤーがnullじゃなかったら
        _player?.let {
            //プレーヤーが再生中の場合
            if(it.isPlaying) {
                //再生停止
                it.stop()
            }
            //プレーヤーを開放
            it.release()
            //プレーヤー用フィールドをnull
            _player = null
        }
    }
}

素材を用意

今回、使用させていたのは以下のサイトです。

効果音ラボ(https://soundeffect-lab.info/agreement/

こちらのサイトから好きな効果音を取得し、ファイル名をAlarm1.mp3としました。
これをプロジェクトのrawフォルダに追加します。

まずは「raw」フォルダを作成します。
「res」フォルダを右クリックして「New」-「Android Resource Directory」を選択します。

「Resource type」を「raw」にします。そうすると自動的に「Directory name」もrawになります。
「OK」をクリックしてフォルダを作成します。

作成した「raw」フォルダにドラッグ&ドロップでメディアファイルをいれます。

OKをクリックします。

メディアプレーヤーを作成

メディアプレーヤーを入れるActivityを作成します。

作成されたActivityにメディアプレーヤを作成する処理を記述します。

class AlarmActivity : AppCompatActivity() {

    private var _player: MediaPlayer? = null

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

        //フィールドのメディアプレーヤーオブジェクトを生成
        _player = MediaPlayer()
        //音声ファイルのURI文字列を作成
        val mediaFileUriStr = "android.resource://${packageName}/${R.raw.alarm1}"
        //音声ファイルのURI文字列を元にURIオブジェクトを生成
        val mediaFileUri = Uri.parse(mediaFileUriStr)
        try{
            //メディアプレーヤーに音声ファイルを指定
            _player?.setDataSource(applicationContext, mediaFileUri)
            //非同期でのメディア再生準備が完了した際のリスナを設定
            _player?.setOnPreparedListener(PlayerPreparedLister())
            //メディア再生が終了した際のリスナを設定
            _player?.setOnCompletionListener(PlayerCompletionLister())
            //非同期でメディア再生準備
            _player?.prepareAsync()
        }
        catch(ex: IllegalArgumentException) {
            Log.e("AlarmActivity", "メディアプレーヤー準備時の例外発生", ex)
        }
        catch (ex: IOException) {
            Log.e("AlarmActivity", "メディアプレーヤー準備時の例外発生", ex)
        }
    }
}

基本的にDataSourceに再生するメディアを指定し、リスナクラスを設定して再生準備(prepareAsync)をすれば完了です。

リスナを作成

メディアの再生準備ができたときに発生するイベントと再生が終了した時に発生するイベントを作成します。

class AlarmActivity : AppCompatActivity() {

    //再生準備ができた時のリスナクラス
    private inner class PlayerPreparedLister : MediaPlayer.OnPreparedListener {
        override fun onPrepared(p0: MediaPlayer?) {
            //再生準備ができたら再生開始
            AlarmStart()
        }
    }

    //再生が終了したときのリスナクラス
    private inner class PlayerCompletionLister : MediaPlayer.OnCompletionListener {
        override fun onCompletion(p0: MediaPlayer?) {
            //再生が終了した時の処理を書く
        }
    }
}

メディアプレーヤーを作成する。

再生する

メディアの再生準備ができたら再生することができます。

//再生開始
fun AlarmStart(){
    //フィールドのプレーヤーがnullじゃなかったら
    _player?.let {
        //プレーヤーが再生中でないなら再生開始
        if(!it.isPlaying) {
            //ループ設定する
            it.isLooping = true
            //再生開始
            it.start()
        }
    }
}

再生をループし続ける場合は、isLoopingをtrueにしてあげれば、再生が終わった後勝手にまた再生してくれます。

停止する

停止するにはstopメソッドを実行してあげます。

//停止
fun OnAlarmStop(view: View) {
    //フィールドのプレーヤーがnullじゃなかったら
    _player?.let {
        //プレーヤーが再生中の場合
        if(it.isPlaying) {
            //再生停止
            it.stop()
        }
        //プレーヤーを開放
        it.release()
        //プレーヤー用フィールドをnull
        _player = null
    }
}

まとめ

メディアプレーヤーの状態遷移図を載せておきます。

android developers Documentation参照

この図をみればメディア再生がどういう流れで行われているかわかりやすいと思います。

ここまで読んで頂いてありがとうございます。