ミュージック プレイヤー アプリ 中編(サービス以外の.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のプログラムのパッケージの名前です。