表示調整
閉じる
挿絵表示切替ボタン
▼配色
▼行間
▼文字サイズ
▼メニューバー
×閉じる

ブックマークに追加しました

設定
0/400
設定を保存しました
エラーが発生しました
※文字以内
ブックマークを解除しました。

エラーが発生しました。

エラーの原因がわからない場合はヘルプセンターをご確認ください。

ブックマーク機能を使うにはログインしてください。
9/19

ミュージック プレイヤー アプリ 中編(サービス以外の.ktファイル)

 「小説家になろう」の「7万文字以内」の制限のため、前中後編の3つに分けました。

 ※下記のKotlinのプログラムなどのコードをコピペする場合は、2文字の全角空白を4文字の半角空白に置換してください。


 また、Android StudioにJavaやKotlinなどのプログラムのコードをコピペして、「import android.R」が自動で追加されてしまったら、削除してください。

 「android.R」は、「R.layout.activity_main」や「R.id.◯◯◯」の「R」とは違います。

 そのため、「import android.R」が有ると、コンパイル エラーが発生してしまいます。


 Android StudioにJavaやKotlinなどのプログラムのコードをコピペすると、変数の名前が半角バッククォート記号(`)で囲まれる事が有ります。

 Kotlinでは変数の名前を半角バッククォート記号(`)で囲むと予約語(inやnullなど)や半角空白記号( )などを変数の名前にできるそうです。

 可能であれば、半角バッククォート記号(`)で囲まれた変数の名前は、半角バッククォート記号(`)で囲まずに済む名前に変更したほうが良いのでは、と個人的に思っております。





 権限の許可を確認しています。



/home/◯◯◯/AndroidStudioProjects/FreeSearchableMusicPlayer/app/src/main/java/eliphas1810/freesearchablemusicplayer/MainActivity.kt

――――――――――――――――――――

package eliphas1810.freesearchablemusicplayer


import android.Manifest

import android.app.AlertDialog

import android.content.Intent

import android.content.pm.PackageManager

import android.os.Build

import android.os.Bundle

import android.widget.Button

import android.widget.EditText

import android.widget.Toast

import androidx.appcompat.app.AppCompatActivity


//Public Domain

//

//メイン画面と1対1対応のアクティビティ

class MainActivity : AppCompatActivity() {



  companion object {


    //メイン画面のアクティビティから、音楽ファイル一覧画面のアクティビティへ送信する情報の名前

    //

    //重複を避けるため、「パッケージ名 + 情報の名前」

    //

    const val INCLUSION_PATTERN_KEY = "eliphas1810.freesearchablemusicplayer.INCLUSION_PATTERN"

    const val EXCLUSION_PATTERN_KEY = "eliphas1810.freesearchablemusicplayer.EXCLUSION_PATTERN"


    //アンドロイド アプリ開発者が管理する場合の、権限の許可のリクエストコードは、アンドロイド アプリ開発者の責任で重複させない事

    private const val READ_MEDIA_AUDIO_REQUEST_CODE = 1

    private const val READ_EXTERNAL_STORAGE_REQUEST_CODE = 2

  }



  //権限の許可の要求の結果が出た時に呼ばれます。

  override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {

    try {

      super.onRequestPermissionsResult(requestCode, permissions, grantResults)


      //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限の許可の有無が選択された場合

      if (requestCode == READ_MEDIA_AUDIO_REQUEST_CODE) {


        //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限を許可された場合

        if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {


          return


          //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限を許可されなかった場合

        } else {


          //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限の許可が無いので、当アプリを実行できない事を説明して、処理を終了

          AlertDialog.Builder(this)

            .setMessage(getString(R.string.denied_read_media_audio))

            .setPositiveButton(getString(R.string.ok)) { _, _ ->

            }

            .create()

            .show()


          return

        }

      }


      //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限の許可の有無が選択された場合

      if (requestCode == READ_EXTERNAL_STORAGE_REQUEST_CODE) {


        //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限を許可された場合

        if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {


          return


          //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限を許可されなかった場合

        } else {


          //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限の許可が無いので、当アプリを実行できない事を説明して、処理を終了

          AlertDialog.Builder(this)

            .setMessage(getString(R.string.denied_read_external_storage))

            .setPositiveButton(getString(R.string.ok)) { _, _ ->

            }

            .create()

            .show()


          return

        }

      }


    } catch (exception: Exception) {

      Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    }

  }



  //メモリー上に作成される時にのみ呼ばれます。

  override fun onCreate(savedInstanceState: Bundle?) {

    try {

      super.onCreate(savedInstanceState)

      setContentView(R.layout.activity_main)



      //検索ボタンが押された時の処理

      findViewById<Button>(R.id.mainSearch).setOnClickListener { view ->

        try {


          var inclusionPattern: String = findViewById<EditText>(R.id.mainInclusionPattern)?.text?.toString() ?: "";

          if (inclusionPattern != "") {

            //検索条件の「正規表現」が正しいか検査

            try {

              Regex(inclusionPattern)

            } catch (exception: Exception) {

              Toast.makeText(view.context.applicationContext, getString(R.string.main_inclusion_pattern_wrong) + exception.message, Toast.LENGTH_LONG).show()

              return@setOnClickListener

            }

          }


          var exclusionPattern: String = findViewById<EditText>(R.id.mainExclusionPattern)?.text?.toString() ?: "";

          if (exclusionPattern != "") {

            //除外条件の「正規表現」が正しいか検査

            try {

              Regex(exclusionPattern)

            } catch (exception: Exception) {

              Toast.makeText(view.context.applicationContext, getString(R.string.main_exclusion_pattern_wrong) + exception.message, Toast.LENGTH_LONG).show()

              return@setOnClickListener

            }

          }


          //音楽ファイルの一覧画面へ遷移

          val intent = Intent(this, MusicList::class.java)

          intent.putExtra(INCLUSION_PATTERN_KEY, inclusionPattern)

          intent.putExtra(EXCLUSION_PATTERN_KEY, exclusionPattern)

          startActivity(intent)


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      //権限の許可の確認


      //当アプリ以外によるファイルを読み取る権限の許可が無い場合

      if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {


        //アンドロイド ティラミス以上の場合

        //アンドロイド13以上の場合

        if (Build.VERSION_CODES.TIRAMISU <= Build.VERSION.SDK_INT) {


          //アンドロイド13以降、Manifest.permission.READ_MEDIA_AUDIOが存在


          //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限の許可が無い場合

          if (checkSelfPermission(Manifest.permission.READ_MEDIA_AUDIO) == PackageManager.PERMISSION_DENIED) {


            //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限の許可ダイアログで「今後、表示しない」を未選択の場合

            if (shouldShowRequestPermissionRationale(Manifest.permission.READ_MEDIA_AUDIO)) {


              //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限の許可ダイアログを表示して、onRequestPermissionsResultで選択結果を受け取る

              requestPermissions(arrayOf(Manifest.permission.READ_MEDIA_AUDIO), READ_MEDIA_AUDIO_REQUEST_CODE)


              //一旦、当処理は終了

              return


              //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限の許可ダイアログで「今後、表示しない」を選択中の場合

            } else {


              //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限の許可が無いので、当アプリを実行できない事を説明して、処理を終了


              AlertDialog.Builder(this)

                .setMessage(getString(R.string.denied_read_media_audio))

                .setPositiveButton(getString(R.string.ok)) { _, _ ->

                }

                .create()

                .show()


              return

            }

          }


          //アンドロイド12以下の場合

        } else {


          //当アプリ以外によるファイルを読み取る権限の許可ダイアログで「今後、表示しない」を未選択の場合

          if (shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) {


            //当アプリ以外によるファイルを読み取る権限の許可ダイアログを表示して、onRequestPermissionsResultで選択結果を受け取る

            requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), READ_EXTERNAL_STORAGE_REQUEST_CODE)


            //一旦、当処理は終了

            return


            //当アプリ以外によるファイルを読み取る権限の許可ダイアログで「今後、表示しない」を選択中の場合

          } else {


            //当アプリ以外によるファイルを読み取る権限の許可が無いので、当アプリを実行できない事を説明して、処理を終了

            AlertDialog.Builder(this)

              .setMessage(getString(R.string.denied_read_external_storage))

              .setPositiveButton(getString(R.string.ok)) { _, _ ->

              }

              .create()

              .show()


            return

          }

        }

      }



    } catch (exception: Exception) {

      Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    }

  }

}

――――――――――――――――――――

 ◯◯◯はLinux Mintのユーザー名です。

 FreeSearchableMusicPlayerは著者が付けたAndroid Studioのプロジェクトの名前です。

 eliphas1810/freesearchablemusicplayerは著者が付けたJavaやKotlinのプログラムのパッケージのディレクトリの相対パスです。

 eliphas1810.freesearchablemusicplayerは著者が付けたJavaやKotlinのプログラムのパッケージの名前です。





 「メディア ストア」と呼ばれる物から音楽ファイルの一覧の情報を取得します。

 アンドロイド システムは自動でストレージのファイルをスキャンして「メディア ストア」という名前でアンドロイド スマホのアプリがアクセスできるように情報を提供してくれています。

 MediaStore.Imagesは、DCIM/、Pictures/ディレクトリの画像ファイルの情報を提供してくれます。

 MediaStore.Videoは、DCIM/、Pictures/、Movies/ディレクトリの動画ファイルの情報を提供してくれます。

 MediaStore.Audioは、Alarms/、Audiobooks/、Music/、Notifications/、Podcasts/、Ringtones/、Movies/、Recordings/ディレクトリの(音楽を含む)音声ファイルの情報を提供してくれます。

 MediaStore.Downloadsは、Download/ディレクトリのファイルの情報を提供してくれます。

 MediaStore.Filesは、Androidアプリによって作成されたファイルの情報を提供してくれたりするそうです。

 「メディア ストア」からはSQL風の命令文で情報を取得します。


 アンドロイド スマホのアプリの画面遷移で次の画面に情報を渡す場合、文字(String)や数値(Intなど)の基本データ型か、複数の基本データ型をひとまとめにしているandroid.os.Parcelableを実装した型か、android.os.Parcelable型の配列の型か、android.os.Parcelable型のjava.io.Serializableとjava.util.Listを実装しているような型の情報だけが渡せます。

 Android 7でアンドロイド スマホのアプリの画面遷移で次の画面にjava.io.Serializable型の情報を渡せなく成り、代わりにandroid.os.Parcelableを利用しなければいけません。

 ちなみに、java.util.Listはjava.io.Serializableを継承していないようです。java.util.ArrayListはjava.io.Serializableを実装しているようです。


 android.os.Parcelableを実装しているデータ クラス(data class)によって曲名やアーティスト名といった情報をひとまとめにしています。


 アダプターと呼ばれるクラスのサブクラス(ArrayAdapter)の、サブクラスを作って、getViewという処理の中で、music_list_row.xmlにデータ クラスの中の曲名やアーティスト名などの情報を設定する処理を書いています。

 ListView.onItemClickListenerにOnItemClickListenerの実装を設定し、OnItemClickListenerの処理の中で、ゼロから始まる選択された行の番号の情報を受け取り、アンドロイド スマホのアプリの画面遷移で情報を渡すのに利用するIntentを作成し、音楽ファイルの一覧の情報と、一覧でのゼロから始まる選択された音楽ファイルの番号をIntentに設定し、startActivity(intent)によって次の画面のアクティビティを起動しています。



/home/◯◯◯/AndroidStudioProjects/FreeSearchableMusicPlayer/app/src/main/java/eliphas1810/freesearchablemusicplayer/MusicList.kt

――――――――――――――――――――

package eliphas1810.freesearchablemusicplayer


import android.content.Context

import android.content.Intent

import android.os.*

import android.os.Parcelable.Creator

import android.provider.MediaStore

import android.view.LayoutInflater

import android.view.View

import android.view.ViewGroup

import android.widget.*

import android.widget.AdapterView.OnItemClickListener

import androidx.appcompat.app.AppCompatActivity


//Public Domain


//音楽ファイルの情報をひとまとめにした物

//

//Parcelableはアクティビティとサービス間で送信し合う事ができる情報

//

data class MusicInfo(

  var id: Long?,

  var musicTitle: String?,

  var artistName: String?,

  var albumTitle: String?,

  var filePath: String?,

  var duration: Int? //音楽の所要時間

) : Parcelable {


  constructor(parcel: Parcel) : this(0, null, null, null, null, 0) {

    id = parcel.readLong()

    musicTitle = parcel.readString()

    artistName = parcel.readString()

    albumTitle = parcel.readString()

    filePath = parcel.readString()

    duration = parcel.readInt()

  }


  companion object {


    @field: JvmField

    val CREATOR: Creator<MusicInfo?> = object : Creator<MusicInfo?> {

      override fun createFromParcel(parcel: Parcel): MusicInfo? {

        return MusicInfo(parcel)

      }


      override fun newArray(size: Int): Array<MusicInfo?> {

        return arrayOfNulls<MusicInfo>(size)

      }

    }

  }


  override fun writeToParcel(parcel: Parcel, flags: Int) { //コンストラクタでParcelから取得する順番と同じ順番でParcelに書き込む必要が有ります。

    parcel.writeLong(id ?: 0) //idがnullの場合はゼロ

    parcel.writeString(musicTitle ?: "null") //曲名がnullの場合は「null」という文字

    parcel.writeString(artistName ?: "null")

    parcel.writeString(albumTitle ?: "null")

    parcel.writeString(filePath ?: "null")

    parcel.writeInt(duration ?: 0)

  }


  //Parcel.describeContents()は普通はゼロを返すように実装

  override fun describeContents(): Int {

    return 0

  }

}



//音楽ファイルの一覧画面で、Listの各件の内容をListViewの各行に設定する物

private class Adapter(context: Context, list: List<MusicInfo>) : ArrayAdapter<MusicInfo>(context, R.layout.music_list_row, list) {


  //例えば、「1分02.003秒」という形式に音楽の総再生時間を編集

  fun convertMusicDurationToText(musicDuration: Int) : String {


    val minutes = musicDuration / (1000 * 60)

    val seconds = (musicDuration % (1000 * 60)) / 1000

    val milliSeconds = musicDuration % 1000


    var durationText = ""

    durationText = durationText + minutes

    durationText = durationText + context.getString(R.string.minutes_unit_label)

    durationText = durationText + seconds

    durationText = durationText + context.getString(R.string.seconds_and_milli_seconds_separator)

    durationText = durationText + "%03d".format(milliSeconds)

    durationText = durationText + context.getString(R.string.seconds_unit_label)

    return durationText

  }


  //音楽ファイルの一覧画面で、Listの各件の内容をListViewの各行に設定

  override fun getView(position: Int, view: View?, viewGroup: ViewGroup): View {


    var view: View? = view


    try {


      if (view == null) {

        view = LayoutInflater.from(context).inflate(R.layout.music_list_row, viewGroup, false)

      }


      val musicInfo = getItem(position)


      view?.findViewById<TextView>(R.id.musicListMusicTitle)?.text = musicInfo?.musicTitle

      view?.findViewById<TextView>(R.id.musicListArtistName)?.text = musicInfo?.artistName

      view?.findViewById<TextView>(R.id.musicListAlbumTitle)?.text = musicInfo?.albumTitle

      view?.findViewById<TextView>(R.id.musicListMusicFilePath)?.text = musicInfo?.filePath

      view?.findViewById<TextView>(R.id.musicListMusicDuration)?.text = convertMusicDurationToText(musicInfo?.duration ?: 0)


    } catch (exception: Exception) {

      Toast.makeText(view?.context?.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    }


    return view!!

  }

}



//音楽ファイル一覧画面と1対1対応のアクティビティ

class MusicList : AppCompatActivity() {


  companion object {


    //メイン画面のアクティビティから、音楽ファイル一覧画面のアクティビティへ送信する情報の名前

    //

    //重複を避けるため、「パッケージ名 + 情報の名前」

    //

    const val INCLUSION_PATTERN_KEY = "eliphas1810.freesearchablemusicplayer.INCLUSION_PATTERN"

    const val EXCLUSION_PATTERN_KEY = "eliphas1810.freesearchablemusicplayer.EXCLUSION_PATTERN"



    //音楽ファイル一覧画面のアクティビティから、音楽ファイル詳細画面のアクティビティへ送信する情報の名前

    //

    //重複を避けるため、「パッケージ名 + 情報の名前」

    //

    const val MUSIC_INFO_LIST_KEY = "eliphas1810.freesearchablemusicplayer.MUSIC_INFO_LIST"

    const val MUSIC_INFO_INDEX_KEY = "eliphas1810.freesearchablemusicplayer.MUSIC_INFO_INDEX"

  }



  private var musicInfoList: MutableList<MusicInfo>? = null



  //全角の英大文字、半角の英大文字、全角の英小文字を半角の英小文字に置換

  //

  //全角数字を半角数字に置換

  //

  private fun halfWidthLowerCase(string : String) : String {


    if (string == null) {

      return ""

    }


    return string

      .replace("a", "a")

      .replace("b", "b")

      .replace("c", "c")

      .replace("d", "d")

      .replace("e", "e")

      .replace("f", "f")

      .replace("g", "g")

      .replace("h", "h")

      .replace("i", "i")

      .replace("j", "j")

      .replace("k", "k")

      .replace("l", "l")

      .replace("m", "m")

      .replace("n", "n")

      .replace("o", "o")

      .replace("p", "p")

      .replace("q", "q")

      .replace("r", "r")

      .replace("s", "s")

      .replace("t", "t")

      .replace("u", "u")

      .replace("v", "v")

      .replace("w", "w")

      .replace("x", "x")

      .replace("y", "y")

      .replace("z", "z")

      .replace("A", "A")

      .replace("B", "B")

      .replace("C", "C")

      .replace("D", "D")

      .replace("E", "E")

      .replace("F", "F")

      .replace("G", "G")

      .replace("H", "H")

      .replace("I", "I")

      .replace("J", "J")

      .replace("K", "K")

      .replace("L", "L")

      .replace("M", "M")

      .replace("N", "N")

      .replace("O", "O")

      .replace("P", "P")

      .replace("Q", "Q")

      .replace("R", "R")

      .replace("S", "S")

      .replace("T", "T")

      .replace("U", "U")

      .replace("V", "V")

      .replace("W", "W")

      .replace("X", "X")

      .replace("Y", "Y")

      .replace("Z", "Z")

      .replace("0", "0")

      .replace("1", "1")

      .replace("2", "2")

      .replace("3", "3")

      .replace("4", "4")

      .replace("5", "5")

      .replace("6", "6")

      .replace("7", "7")

      .replace("8", "8")

      .replace("9", "9")

      .lowercase()

  }



  //メモリー上に作成される時にのみ呼ばれます。

  override fun onCreate(savedInstanceState: Bundle?) {

    try {

      super.onCreate(savedInstanceState)

      setContentView(R.layout.music_list)



      //音楽ファイル一覧画面の閉じるボタンが押された時の処理

      findViewById<Button>(R.id.musicListClose).setOnClickListener { view ->

        try {


          //前の画面へ戻る

          finish()


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      if (musicInfoList != null && 1 <= (musicInfoList?.size ?: 0)) {

        musicInfoList?.clear()

      }


      musicInfoList = mutableListOf<MusicInfo>()



      //前の画面から、検索条件の文字パターン「正規表現」を取得

      var inclusionPattern = getIntent()?.getStringExtra(INCLUSION_PATTERN_KEY) ?: ""


      //全角の英大文字、半角の英大文字、全角の英小文字を半角の英小文字に置換

      //

      //全角数字を半角数字に置換

      //

      inclusionPattern = halfWidthLowerCase(inclusionPattern)


      var inclusionRegex : Regex? = null

      if (inclusionPattern != "") {

        inclusionRegex = Regex(inclusionPattern)

      }



      //前の画面から、除外条件の文字パターン「正規表現」を取得

      var exclusionPattern = getIntent()?.getStringExtra(EXCLUSION_PATTERN_KEY) ?: ""



      //全角の英大文字、半角の英大文字、全角の英小文字を半角の英小文字に置換

      //

      //全角数字を半角数字に置換

      //

      exclusionPattern = halfWidthLowerCase(exclusionPattern)


      var exclusionRegex : Regex? = null

      if (exclusionPattern != "") {

        exclusionRegex = Regex(exclusionPattern)

      }



      //当アプリ以外によるファイルの取得先

      val externalContentUri =

        if (Build.VERSION_CODES.Q <= Build.VERSION.SDK_INT) { //アンドロイド10(Q)以上の場合

          MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)

        } else {

          MediaStore.Audio.Media.EXTERNAL_CONTENT_URI

        }


      getContentResolver().query(

        externalContentUri, //当アプリ以外によるファイルの取得先

        arrayOf( //SQLのSELECTに相当

          MediaStore.Audio.Media._ID, //android.media.MediaPlayerへの曲の指定に必要なID

          MediaStore.Audio.Media.TITLE, //音楽ファイルの曲名を取得

          MediaStore.Audio.Media.ARTIST, //音楽ファイルのアーティスト名を取得

          MediaStore.Audio.Media.ALBUM, //音楽ファイルのアルバム名を取得

          MediaStore.Audio.Media.DATA, //音楽ファイルのパスを取得

          MediaStore.Audio.Media.DURATION //音楽ファイルの総再生時間を取得

        ),

        "${MediaStore.Audio.Media.IS_MUSIC} != 0", //SQLのWHEREに相当。音楽ファイルに限定して一覧検索。

        null, //SQLのWHEREの?への指定パラメーターに相当

        "${MediaStore.Audio.Media.TITLE} ASC" //SQLのORDER BYに相当

      )?.use { cursor ->


        val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)

        val titleIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE)

        val artistIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)

        val albumIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM)

        val filePathIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA)

        val durationIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)


        while (cursor.moveToNext()) {


          var id = cursor.getLong(idIndex)

          var title = cursor.getString(titleIndex)

          var artist = cursor.getString(artistIndex)

          var album = cursor.getString(albumIndex)

          var filePath = cursor.getString(filePathIndex)

          var duration = cursor.getInt(durationIndex)



          var musicInfoTsv = title + "\t" + artist + "\t" + album + "\t" + filePath


          //全角の英大文字、半角の英大文字、全角の英小文字を半角の英小文字に置換

          //

          //全角数字を半角数字に置換

          //

          musicInfoTsv = halfWidthLowerCase(musicInfoTsv)


          //検索条件が未指定の場合か、検索条件が含まれている場合

          if (inclusionRegex == null || inclusionRegex.containsMatchIn(musicInfoTsv)) {


            //除外条件が未指定の場合か、除外条件が含まれていない場合

            if (exclusionRegex == null || exclusionRegex.containsMatchIn(musicInfoTsv) == false) {


              musicInfoList?.add(MusicInfo(id, title, artist, album, filePath, duration))

            }

          }

        }

      }


      //音楽ファイルが見つからない場合

      if ((musicInfoList?.size ?: 0) <= 0) {

        Toast.makeText(this, getString(R.string.no_music_file_list), Toast.LENGTH_LONG).show()

      }


      //音楽ファイルの一覧画面で、ListViewの各行の内容を設定する物を指定

      val listView = findViewById<ListView>(R.id.musicList)

      listView.adapter = Adapter(this, musicInfoList ?: mutableListOf<MusicInfo>())


      //音楽ファイルの一覧画面で、ListViewの各行が押された時の処理

      listView.onItemClickListener = OnItemClickListener { adapterView, view, position, id ->

        try {


          //音楽ファイルの詳細画面へ遷移

          val intent = Intent(this, MusicDetail::class.java)

          intent.putExtra(MUSIC_INFO_LIST_KEY, ArrayList(musicInfoList))

          intent.putExtra(MUSIC_INFO_INDEX_KEY, position)

          startActivity(intent)


        } catch (exception: Exception) {

          Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }


    } catch (exception: Exception) {

      Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    }

  }



  //メモリーから破棄される時にのみ呼ばれます。

  override fun onDestroy() {

    try {


      musicInfoList?.clear()

      musicInfoList = null


    } catch (exception: Exception) {

      Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    } finally {

      super.onDestroy()

    }

  }

}

――――――――――――――――――――

 ◯◯◯はLinux Mintのユーザー名です。

 FreeSearchableMusicPlayerは著者が付けたAndroid Studioのプロジェクトの名前です。

 eliphas1810/freesearchablemusicplayerは著者が付けたJavaやKotlinのプログラムのパッケージのディレクトリの相対パスです。

 eliphas1810.freesearchablemusicplayerは著者が付けたJavaやKotlinのプログラムのパッケージの名前です。





 「サービス」からの、音楽ファイルの詳細画面に表示する音楽ファイルの情報の更新を促す通知を「ブロードキャスト レシーバー」のサブクラス経由で受け取ります。

 「サービス」からの、音楽ファイルの詳細画面に表示する現在の再生時間の更新を促す通知を「ブロードキャスト レシーバー」のサブクラス経由で受け取ります。


 2025年2月6日に、アンドロイド14対応で、自作のブロードキャスト レシーバーをアンドロイド システムに登録する時に、RECEIVER_EXPORTEDの指定を追加しました。


 startForegroundService(intent: Intent)で、「(フォア グラウンド )サービス」を起動します。

 「サービス」がまだ無い場合は新規作成されます。

 「フォア グラウンド サービス」ではない「サービス」は、バック グラウンドのまま30分くらい経つと、アンドロイド システムに強制終了されてしまいます。


 「フォア グラウンド サービス」はstartForegroundService(intent: Intent)から5秒以内にstartForeground(flag: Int, notification: Notification)を呼ぶ必要が有ります。

 startForeground(flag: Int, notification: Notification)を呼ばないと、startForegroundService(intent: Intent)から5秒が経ったら、アンドロイド システムに強制終了されてしまいます。

 通知のNotificationクラスは、通知チャンネルのNotificationChannelを作成する必要が有ります。


 bindService()で「サービス」へ接続します。


 ※ちなみに、Kotlin言語では、クロージャーなどで、ブロック({})の外の変数を参照したり変更したりできます。



/home/◯◯◯/AndroidStudioProjects/FreeSearchableMusicPlayer/app/src/main/java/eliphas1810/freesearchablemusicplayer/MusicDetail.kt

――――――――――――――――――――

package eliphas1810.freesearchablemusicplayer



import android.content.*

import android.os.*

import android.widget.Button

import android.widget.SeekBar

import android.widget.TextView

import android.widget.Toast

import androidx.appcompat.app.AppCompatActivity


//Public Domain

//

//音楽ファイル詳細画面と1対1対応のアクティビティ

class MusicDetail : AppCompatActivity() {



  companion object {


    //音楽ファイル一覧画面のアクティビティから、音楽ファイル詳細画面のアクティビティへ送信する情報の名前

    //当アクティビティから、音楽を再生したりするサービスへ送信する情報の名前

    //サービスからの通知情報の名前

    //

    //重複を避けるため、「パッケージ名 + 情報の名前」

    //

    const val MUSIC_INFO_LIST_KEY = "eliphas1810.freesearchablemusicplayer.MUSIC_INFO_LIST"

    const val MUSIC_INFO_INDEX_KEY = "eliphas1810.freesearchablemusicplayer.MUSIC_INFO_INDEX"

    const val LOOP_MUSIC_KEY = "eliphas1810.freesearchablemusicplayer.LOOP_MUSIC"

    const val RANDOM_MUSIC_KEY = "eliphas1810.freesearchablemusicplayer.RANDOM_MUSIC"

    const val CURRENT_MUSIC_DURATION_KEY = "eliphas1810.freesearchablemusicplayer.CURRENT_MUSIC_DURATION"



    //音楽ファイルの詳細画面に表示する音楽ファイルの情報の更新を促される通知の名前

    const val UPDATE_MUSIC_INFO_KEY = "eliphas1810.freesearchablemusicplayer.UPDATE_MUSIC_INFO"



    //音楽ファイルの詳細画面に表示する現在の再生時間の更新を促される通知の名前

    const val UPDATE_MUSIC_CURRENT_DURATION_KEY = "eliphas1810.freesearchablemusicplayer.UPDATE_MUSIC_CURRENT_DURATION"



    //当アクティビティから、音楽を再生したりするサービスへの送信情報のコードは、アンドロイド アプリ開発者の責任で重複させない事

    const val START_MUSIC_MESSAGE = 1

    const val PAUSE_MUSIC_MESSAGE = 2

    const val STOP_MUSIC_MESSAGE = 3

    const val PREVIOUS_MUSIC_MESSAGE = 4

    const val NEXT_MUSIC_MESSAGE = 5

    const val SEEK_MUSIC_MESSAGE = 6

    const val LOOP_MUSIC_MESSAGE = 7

    const val RANDOM_MUSIC_MESSAGE = 8

    const val REQUEST_MUSIC_INFO_MESSAGE = 9

  }



  var musicInfoList: ArrayList<MusicInfo>? = null

  var musicInfoIndex = 0

  var loop = false

  var random = false

  var currentMusicDuration = 0


  var musicCurrentDurationChanging = false


  var connectingWithService: Boolean = false


  private var serviceConnection: ServiceConnection? = null


  var messenger: Messenger? = null


  private var musicInfoUpdateBroadcastReceiver: BroadcastReceiver? = null


  private var musicCurrentDurationUpdateBroadcastReceiver: BroadcastReceiver? = null



  //例えば、「1分2.003秒」といった形式に音楽の再生時間を編集

  fun convertMusicDurationToText(musicDuration: Int) : String {


    val minutes = musicDuration / (1000 * 60)

    val seconds = (musicDuration % (1000 * 60)) / 1000

    val milliSeconds = musicDuration % 1000


    var musicDurationText = ""

    musicDurationText = musicDurationText + minutes

    musicDurationText = musicDurationText + getString(R.string.minutes_unit_label)

    musicDurationText = musicDurationText + seconds

    musicDurationText = musicDurationText + getString(R.string.seconds_and_milli_seconds_separator)

    musicDurationText = musicDurationText + "%03d".format(milliSeconds)

    musicDurationText = musicDurationText + getString(R.string.seconds_unit_label)

    return musicDurationText

  }



  //メモリー上に作成される時にのみ呼ばれます。

  override fun onCreate(savedInstanceState: Bundle?) {

    try {

      super.onCreate(savedInstanceState)

      setContentView(R.layout.music_detail)



      if (musicInfoList != null && 1 <= (musicInfoList?.size ?: 0)) {

        musicInfoList?.clear()

      }


      musicInfoList = ArrayList(listOf())



      serviceConnection = object: ServiceConnection {

        override fun onServiceConnected(componentName: ComponentName, iBinder: IBinder) {

          messenger = Messenger(iBinder)

          connectingWithService = true

        }

        override fun onServiceDisconnected(componentName: ComponentName) {

          messenger = null

          connectingWithService = false

        }

      }



      //アンドロイド8(オレオ)以上の場合

      if (Build.VERSION_CODES.O <= Build.VERSION.SDK_INT) {


        //アンドロイド アプリのバックグラウンド処理の部分である「サービス」と呼ばれる物を起動

        //

        //音楽を再生する「サービス」を起動

        //

        //音楽を再生する「サービス」がまだ無い場合は新規作成されます。

        //

        startForegroundService(Intent(applicationContext, MusicPlayerService::class.java))


      } else {


        //アンドロイド アプリのバックグラウンド処理の部分である「サービス」と呼ばれる物を起動

        //

        //音楽を再生する「サービス」を起動

        //

        //音楽を再生する「サービス」がまだ無い場合は新規作成されます。

        //

        startService(Intent(applicationContext, MusicPlayerService::class.java))

      }



      //アンドロイド アプリのバックグラウンド処理の部分である「サービス」と呼ばれる物へ接続

      //

      //音楽を再生する「サービス」へ接続

      //

      //音楽を再生する「サービス」がまだ無い場合は新規作成されます。

      //

      bindService(Intent(applicationContext, MusicPlayerService::class.java), serviceConnection!!, Context.BIND_AUTO_CREATE)



      //サービスからの、音楽ファイルの詳細画面に表示する音楽ファイルの情報の更新を促す通知を受け取ります。

      musicInfoUpdateBroadcastReceiver = object : BroadcastReceiver() {

        override fun onReceive(context: Context?, intent: Intent?) {

          try {


            val bundle = intent?.extras


            //サービスからの通知情報から、音楽ファイルの一覧の情報を受け取ります。


            musicInfoList = if (Build.VERSION_CODES.TIRAMISU <= Build.VERSION.SDK_INT) { //アンドロイド13(ティラミス)以上の場合


              bundle?.getParcelableArrayList(MUSIC_INFO_LIST_KEY, MusicInfo::class.java)


            } else {


              bundle?.getParcelableArrayList(MUSIC_INFO_LIST_KEY)

            }



            if ((musicInfoList?.size ?: 0) <= 0) {

              return

            }



            //サービスからの通知情報から、選択中のゼロから始まる音楽ファイルの番号を受け取ります。

            musicInfoIndex = bundle?.getInt(MUSIC_INFO_INDEX_KEY) ?: 0



            //サービスからの通知情報から、ループ再生するか否かを受け取ります。

            loop = bundle?.getBoolean(LOOP_MUSIC_KEY) ?: false



            //サービスからの通知情報から、ループ再生するか否かを受け取ります。

            random = bundle?.getBoolean(RANDOM_MUSIC_KEY) ?: false



            //音楽ファイルの詳細画面の内容を更新


            val musicInfo = musicInfoList?.get(musicInfoIndex)


            findViewById<TextView>(R.id.musicDetailMusicTitle)?.text = musicInfo?.musicTitle

            findViewById<TextView>(R.id.musicDetailArtistName)?.text = musicInfo?.artistName

            findViewById<TextView>(R.id.musicDetailAlbumTitle)?.text = musicInfo?.albumTitle

            findViewById<TextView>(R.id.musicDetailMusicFilePath)?.text = musicInfo?.filePath

            findViewById<TextView>(R.id.musicDetailMusicDuration)?.text = convertMusicDurationToText(musicInfo?.duration ?: 0)


            if (loop) {

              findViewById<Button>(R.id.musicDetailLoop)?.setTextColor(resources.getColor(R.color.white, theme))

              findViewById<Button>(R.id.musicDetailLoop)?.setBackgroundColor(resources.getColor(R.color.lime, theme))

            } else {

              findViewById<Button>(R.id.musicDetailLoop)?.setTextColor(resources.getColor(R.color.silver, theme))

              findViewById<Button>(R.id.musicDetailLoop)?.setBackgroundColor(resources.getColor(R.color.gray, theme))

            }



            if (random) {

              findViewById<Button>(R.id.musicDetailRandom)?.setTextColor(resources.getColor(R.color.white, theme))

              findViewById<Button>(R.id.musicDetailRandom)?.setBackgroundColor(resources.getColor(R.color.lime, theme))

            } else {

              findViewById<Button>(R.id.musicDetailRandom)?.setTextColor(resources.getColor(R.color.silver, theme))

              findViewById<Button>(R.id.musicDetailRandom)?.setBackgroundColor(resources.getColor(R.color.gray, theme))

            }



            //音楽ファイルの詳細画面の、現在の再生時間のシーク バーが動かされていない場合

            if (musicCurrentDurationChanging == false) {


              //サービスからの通知情報から、現在の再生時間を受け取ります。

              currentMusicDuration = bundle?.getInt(CURRENT_MUSIC_DURATION_KEY) ?: 0


              //音楽ファイルの詳細画面の、現在の再生時間のシーク バーを更新

              findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).max = musicInfo?.duration ?: 0

              findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).progress = currentMusicDuration


              //音楽ファイルの詳細画面の、現在の再生時間を更新

              findViewById<TextView>(R.id.musicDetailMusicCurrentDuration)?.text = convertMusicDurationToText(currentMusicDuration)

            }


          } catch (exception: Exception) {

            Toast.makeText(context?.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

            throw exception

          }

        }

      }



      if (Build.VERSION_CODES.TIRAMISU <= Build.VERSION.SDK_INT) {

        registerReceiver(musicInfoUpdateBroadcastReceiver, IntentFilter(UPDATE_MUSIC_INFO_KEY), RECEIVER_EXPORTED)

      } else {

        registerReceiver(musicInfoUpdateBroadcastReceiver, IntentFilter(UPDATE_MUSIC_INFO_KEY))

      }



      //サービスからの、音楽ファイルの詳細画面に表示する現在の再生時間の更新を促す通知を受け取ります。

      musicCurrentDurationUpdateBroadcastReceiver = object : BroadcastReceiver() {

        override fun onReceive(context: Context?, intent: Intent?) {

          try {


            //音楽ファイルの詳細画面の、現在の再生時間のシーク バーが動かされていない場合

            if (musicCurrentDurationChanging == false) {


              val bundle = intent?.extras


              //サービスからの通知情報から、音楽ファイルの一覧の情報を受け取ります。


              musicInfoList = if (Build.VERSION_CODES.TIRAMISU <= Build.VERSION.SDK_INT) { //アンドロイド13(ティラミス)以上の場合

                bundle?.getParcelableArrayList(MUSIC_INFO_LIST_KEY, MusicInfo::class.java)

              } else {

                bundle?.getParcelableArrayList(MUSIC_INFO_LIST_KEY)

              }


              //サービスからの通知情報から、選択中のゼロから始まる音楽ファイルの番号を受け取ります。

              musicInfoIndex = bundle?.getInt(MUSIC_INFO_INDEX_KEY) ?: 0


              val musicInfo = musicInfoList?.get(musicInfoIndex)


              //サービスからの通知情報から、現在の再生時間を受け取ります。

              currentMusicDuration = bundle?.getInt(CURRENT_MUSIC_DURATION_KEY) ?: 0


              //音楽ファイルの詳細画面の、現在の再生時間のシーク バーを更新

              findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).max = musicInfo?.duration ?: 0

              findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).progress = currentMusicDuration


              //音楽ファイルの詳細画面の、現在の再生時間を更新

              findViewById<TextView>(R.id.musicDetailMusicCurrentDuration)?.text = convertMusicDurationToText(currentMusicDuration)

            }


          } catch (exception: Exception) {

            Toast.makeText(context?.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

            throw exception

          }

        }

      }



      if (Build.VERSION_CODES.TIRAMISU <= Build.VERSION.SDK_INT) {

        registerReceiver(musicCurrentDurationUpdateBroadcastReceiver, IntentFilter(UPDATE_MUSIC_CURRENT_DURATION_KEY), RECEIVER_EXPORTED)

      } else {

        registerReceiver(musicCurrentDurationUpdateBroadcastReceiver, IntentFilter(UPDATE_MUSIC_CURRENT_DURATION_KEY))

      }



      //前の画面から音楽ファイルの一覧を取得


      musicInfoList = if (Build.VERSION_CODES.TIRAMISU <= Build.VERSION.SDK_INT) { //アンドロイド13(ティラミス)以上の場合

        ArrayList(intent.getParcelableArrayListExtra(MUSIC_INFO_LIST_KEY, MusicInfo::class.java))

      } else {

        ArrayList(intent.getParcelableArrayListExtra(MUSIC_INFO_LIST_KEY))

      }



      //前の画面から選択されたゼロから始まる音楽ファイルの番号を取得

      musicInfoIndex = intent.getIntExtra(MUSIC_INFO_INDEX_KEY, 0)



      val musicInfo = musicInfoList?.get(musicInfoIndex)



      //画面の1回目の表示時の処理

      findViewById<TextView>(R.id.musicDetailMusicTitle)?.text = musicInfo?.musicTitle

      findViewById<TextView>(R.id.musicDetailArtistName)?.text = musicInfo?.artistName

      findViewById<TextView>(R.id.musicDetailAlbumTitle)?.text = musicInfo?.albumTitle

      findViewById<TextView>(R.id.musicDetailMusicFilePath)?.text = musicInfo?.filePath

      findViewById<TextView>(R.id.musicDetailMusicDuration)?.text = convertMusicDurationToText(musicInfo?.duration ?: 0)


      findViewById<TextView>(R.id.musicDetailMusicCurrentDuration)?.text = convertMusicDurationToText(currentMusicDuration)


      findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).max = musicInfo?.duration ?: 0

      findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).progress = currentMusicDuration


      findViewById<Button>(R.id.musicDetailLoop).setTextColor(resources.getColor(R.color.silver, theme))

      findViewById<Button>(R.id.musicDetailLoop).setBackgroundColor(resources.getColor(R.color.gray, theme))


      findViewById<Button>(R.id.musicDetailRandom).setTextColor(resources.getColor(R.color.silver, theme))

      findViewById<Button>(R.id.musicDetailRandom).setBackgroundColor(resources.getColor(R.color.gray, theme))



      //閉じるボタンが押された時の処理

      findViewById<Button>(R.id.musicDetailClose).setOnClickListener { view ->

        try {


          //音楽を再生する「サービス」に、音楽を停止するようにメッセージを送信


          val bundle = Bundle()

          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, STOP_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)


          //音楽を再生する「サービス」を終了させます。

          stopService(Intent(applicationContext, MusicPlayerService::class.java))


          //前の画面へ戻る

          finish()


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      //音楽ファイルの詳細画面の、現在の再生時間のシーク バーが操作された時の処理

      findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).setOnSeekBarChangeListener(object: SeekBar.OnSeekBarChangeListener {

        override fun onStartTrackingTouch(seekBar: SeekBar) {


          musicCurrentDurationChanging = true


          //音楽を再生する「サービス」に、音楽を一時停止するようにメッセージを送信


          val bundle = Bundle()


          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, PAUSE_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)

        }


        override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromTouch: Boolean) {

          currentMusicDuration = progress

          findViewById<TextView>(R.id.musicDetailMusicCurrentDuration)?.text = convertMusicDurationToText(currentMusicDuration)

        }


        override fun onStopTrackingTouch(seekBar: SeekBar) {


          //音楽を再生する「サービス」に、音楽の再生開始時間を指定して、再生するようにメッセージを送信


          val bundle = Bundle()


          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, SEEK_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)


          musicCurrentDurationChanging = false

        }

      })



      //再生ボタンが押された時の処理

      findViewById<Button>(R.id.musicDetailStart).setOnClickListener { view ->

        try {


          //音楽を再生する「サービス」に、音楽を再生するようにメッセージを送信


          val bundle = Bundle()


          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, START_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      //一時停止ボタンが押された時の処理

      findViewById<Button>(R.id.musicDetailPause).setOnClickListener { view ->

        try {


          //音楽を再生する「サービス」に、音楽を一時停止するようにメッセージを送信


          val bundle = Bundle()


          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, PAUSE_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      //停止ボタンが押された時の処理

      findViewById<Button>(R.id.musicDetailStop).setOnClickListener { view ->

        try {


          currentMusicDuration = 0


          findViewById<TextView>(R.id.musicDetailMusicCurrentDuration)?.text = convertMusicDurationToText(currentMusicDuration)


          findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).progress = currentMusicDuration


          //音楽を再生する「サービス」に、音楽を停止するようにメッセージを送信


          val bundle = Bundle()


          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, STOP_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      //「前の曲へ戻る」ボタンが押された時の処理

      findViewById<Button>(R.id.musicDetailPrevious).setOnClickListener { view ->

        try {


          currentMusicDuration = 0


          findViewById<TextView>(R.id.musicDetailMusicCurrentDuration)?.text = convertMusicDurationToText(currentMusicDuration)


          findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).progress = currentMusicDuration


          //音楽を再生する「サービス」に、前の曲へ戻って再生するようにメッセージを送信


          val bundle = Bundle()


          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, PREVIOUS_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      //「次の曲へ進む」ボタンが押された時の処理

      findViewById<Button>(R.id.musicDetailNext).setOnClickListener { view ->

        try {


          currentMusicDuration = 0


          findViewById<TextView>(R.id.musicDetailMusicCurrentDuration)?.text = convertMusicDurationToText(currentMusicDuration)


          findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).progress = currentMusicDuration


          //音楽を再生する「サービス」に、次の曲へ進んで再生するようにメッセージを送信


          val bundle = Bundle()


          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, NEXT_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      //「ループ モード」ボタンが押された時の処理

      findViewById<Button>(R.id.musicDetailLoop).setOnClickListener { view ->

        try {


          loop = !loop


          //音楽を再生する「サービス」に、ループ再生するようにメッセージを送信


          val bundle = Bundle()


          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, LOOP_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)


          if (loop) {

            findViewById<Button>(R.id.musicDetailLoop)?.setTextColor(resources.getColor(R.color.white, theme))

            findViewById<Button>(R.id.musicDetailLoop)?.setBackgroundColor(resources.getColor(R.color.lime, theme))

          } else {

            findViewById<Button>(R.id.musicDetailLoop)?.setTextColor(resources.getColor(R.color.silver, theme))

            findViewById<Button>(R.id.musicDetailLoop)?.setBackgroundColor(resources.getColor(R.color.gray, theme))

          }


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      //「ランダム モード」ボタンが押された時の処理

      findViewById<Button>(R.id.musicDetailRandom).setOnClickListener { view ->

        try {


          random = !random


          //音楽を再生する「サービス」に、ランダム再生するようにメッセージを送信


          val bundle = Bundle()


          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, RANDOM_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)


          if (random) {

            findViewById<Button>(R.id.musicDetailRandom)?.setTextColor(resources.getColor(R.color.white, theme))

            findViewById<Button>(R.id.musicDetailRandom)?.setBackgroundColor(resources.getColor(R.color.lime, theme))

          } else {

            findViewById<Button>(R.id.musicDetailRandom)?.setTextColor(resources.getColor(R.color.silver, theme))

            findViewById<Button>(R.id.musicDetailRandom)?.setBackgroundColor(resources.getColor(R.color.gray, theme))

          }


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      //音楽を再生する「サービス」に、音楽ファイルの詳細画面に表示する音楽ファイルの情報の更新を促す通知をするようにメッセージを送信


      val message: Message = Message.obtain(null, REQUEST_MUSIC_INFO_MESSAGE)


      messenger?.send(message)



    } catch (exception: Exception) {

      Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    }

  }



  override fun onResume() {

    try {

      super.onResume()



      //音楽を再生する「サービス」に、音楽ファイルの詳細画面に表示する音楽ファイルの情報の更新を促す通知をするようにメッセージを送信


      val message: Message = Message.obtain(null, REQUEST_MUSIC_INFO_MESSAGE)


      messenger?.send(message)


    } catch (exception: Exception) {

      Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    }

  }



  //メモリーから破棄される時にのみ呼ばれます。

  override fun onDestroy() {

    try {


      musicInfoList?.clear()

      musicInfoList = null


      unregisterReceiver(musicInfoUpdateBroadcastReceiver)

      musicInfoUpdateBroadcastReceiver = null


      unregisterReceiver(musicCurrentDurationUpdateBroadcastReceiver)

      musicCurrentDurationUpdateBroadcastReceiver = null


      messenger = null


    } catch (exception: Exception) {

      Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    } finally {

      super.onDestroy()

    }

  }

}

――――――――――――――――――――

 ◯◯◯はLinux Mintのユーザー名です。

 FreeSearchableMusicPlayerは著者が付けたAndroid Studioのプロジェクトの名前です。

 eliphas1810/freesearchablemusicplayerは著者が付けたJavaやKotlinのプログラムのパッケージのディレクトリの相対パスです。

 eliphas1810.freesearchablemusicplayerは著者が付けたJavaやKotlinのプログラムのパッケージの名前です。

評価をするにはログインしてください。
この作品をシェア
Twitter LINEで送る
ブックマークに追加
ブックマーク機能を使うにはログインしてください。
+注意+

特に記載なき場合、掲載されている作品はすべてフィクションであり実在の人物・団体等とは一切関係ありません。
特に記載なき場合、掲載されている作品の著作権は作者にあります(一部作品除く)。
作者以外の方による作品の引用を超える無断転載は禁止しており、行った場合、著作権法の違反となります。

この作品はリンクフリーです。ご自由にリンク(紹介)してください。
この作品はスマートフォン対応です。スマートフォンかパソコンかを自動で判別し、適切なページを表示します。

↑ページトップへ