LoginSignup
3
1

More than 3 years have passed since last update.

MediaSessionを使って秒で音楽アプリを作成する(超簡易版)

Last updated at Posted at 2020-04-05

音楽アプリを作りたい

音楽アプリを作りたいんだけど、MediaSessionとかMediaBrowserServiceとかメンドクサイ。秒で音楽を再生できるようにしたい。という人向けの記事です。自分のメモ的な面もあるので、不備があればコメントお願いします。質問されればいつでも答える所存。

秒で基本的な機能を実装したい

その1

MusicClassとMusicServiceという名でktファイルを作成。(マニフェストに登録とかはすっ飛ばします)

その2

以下MusicClassのソースコード。これをコピーして貼り付け。

(自分のアプリからそのまま引っ張ってきているのでエラー多いかも。使わないなと思ったところは消して構わない。気にすることなかれ。)

class MusicClass(_context: Context) {

    private val context = _context
    private var service: MediaBrowserServiceCompat? = null

    private var musicMap = mutableMapOf<String, MusicData>()
    private val artworkMap = mutableMapOf<String, Bitmap?>()

    private var currentMusicData: MusicData?
        set(value) = settings.setString(SETTING_MUSIC_VALUE, "CurrentMusic", value?.musicId)
        get() = musicMap[settings.getString(SETTING_MUSIC_VALUE, "CurrentMusic", null)]

    private var currentMusicState: Int = 1
        set(value) {
            field = value
            callbacks.forEach { it.onUpdatePlayerState(value) }
        }

    private var queueIndex = -1
    private var queueItems = mutableListOf<MediaSessionCompat.QueueItem>()
    private var queueItemsOriginal = mutableListOf<MediaSessionCompat.QueueItem>()

    private var initMusicClassFlag = false
    private var initFirstConnection = false
    private var initMusicBrowserFlag = false
    private var tryConnectFlag = true

    private val audioManager: AudioManager by lazy { context.getSystemService(Context.AUDIO_SERVICE) as AudioManager }
    private val defaultArtwork: Bitmap by lazy { BitmapFactory.decodeResource(context.resources, R.drawable.album_art) }

    private lateinit var musicPlayer: MediaPlayer
    private lateinit var mediaSession: MediaSessionCompat
    private lateinit var mediaBrowser: MediaBrowserCompat
    private lateinit var mediaController: MediaControllerCompat

    private var listener: MusicListener? = null
    private var onCurrentMusicChangedListener: OnCurrentMusicChanged? = null
    private var callbacks = mutableListOf<MusicCallback>()

    private var reserveAction: (() -> Unit?)? = null

    private val mediaControllerCallback = object : MediaControllerCompat.Callback() {
        override fun onPlaybackStateChanged(state: PlaybackStateCompat?) {
            super.onPlaybackStateChanged(state)
            global.log(MUSIC_TAG, "MusicClass onPlaybackStateChanged")

            if (state != null) {
                callbacks.forEach { it.onUpdatePlayerState(state.state) }
            }
        }

        override fun onMetadataChanged(metadata: MediaMetadataCompat?) {
            super.onMetadataChanged(metadata)
            global.log(MUSIC_TAG, "MusicClass onMetadataChanged")

            if (metadata != null) {
                callbacks.forEach { it.onUpdateMusicMetadata(metadata) }
            }
        }
    }

    private val mediaSessionCallback = object : MediaSessionCompat.Callback() {
        override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) {
            currentMusicState = PlaybackStateCompat.STATE_PLAYING
            musicMap[mediaId]?.let {
                mediaSession.setMetadata(getMusicMetadata(mediaId))
                listener?.onPlayFromMusicData(it)
            }
        }

        override fun onPlay() {
            listener?.onPlay()
            currentMusicState = PlaybackStateCompat.STATE_PLAYING
        }

        override fun onPause() {
            listener?.onPause()
            currentMusicState = PlaybackStateCompat.STATE_PAUSED
        }

        override fun onStop() {
            listener?.onStop()
            currentMusicState = PlaybackStateCompat.STATE_STOPPED
        }

        override fun onSkipToNext() {
            if (queueItems.isNotEmpty()) {
                queueIndex = if (queueIndex + 1 >= queueItems.size) 0 else queueIndex + 1
                val musicData = musicMap[queueItems[queueIndex].description.mediaId] ?: return
                listener?.onSkipToNext(musicData, currentMusicState == PlaybackStateCompat.STATE_PLAYING)
            }
        }

        override fun onSkipToPrevious() {
            if (queueItems.isNotEmpty()) {
                if ((settings.currentMusicProgress.toInt() / 1000) > 4 && settings.pGetBoolean("pOneStepBack", true)) {
                    listener?.onSeekTo(0)
                } else {
                    queueIndex = if (queueIndex <= 0) queueItems.size - 1 else queueIndex - 1
                    val musicData = musicMap[queueItems[queueIndex].description.mediaId] ?: return
                    listener?.onSkipToPrevious(musicData, currentMusicState == PlaybackStateCompat.STATE_PLAYING)
                }
            }
        }

        override fun onSetShuffleMode(shuffleMode: Int) {
            if (shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_NONE) {
                settings.shuffleMode = false
                queueItems = queueItemsOriginal.toMutableList()
            } else {
                settings.shuffleMode = true
                val currentQueueItem = queueItems[queueIndex]
                val tempQueueList = mutableListOf<MediaSessionCompat.QueueItem>()
                queueItems.forEach {
                    if (it.description.mediaId != currentMusicData?.musicId) {
                        tempQueueList.add(it)
                    }
                }
                queueItems.clear()
                queueItems.add(currentQueueItem)
                queueItems.addAll(tempQueueList.shuffled())
            }

            currentMusicData?.let { updateQueueIndex(it) }
        }

        override fun onSetRepeatMode(repeatMode: Int) {
            settings.repeatMode = repeatMode
        }

        override fun onSeekTo(pos: Long) {
            listener?.onSeekTo(pos)
        }

        override fun onMediaButtonEvent(mediaButtonEvent: Intent?): Boolean {
            val key: KeyEvent? = mediaButtonEvent!!.getParcelableExtra(Intent.EXTRA_KEY_EVENT)
            global.log(MUSIC_TAG, "MusicClass onMediaButtonEvent: ${key?.keyCode}")
            return when (key?.keyCode) {
                KeyEvent.KEYCODE_MEDIA_PLAY     -> {
                    currentMusicData?.let { this@MusicClass.onPlay(it, null) }
                    true
                }
                KeyEvent.KEYCODE_MEDIA_PAUSE    -> {
                    this@MusicClass.onPause()
                    true
                }
                KeyEvent.KEYCODE_MEDIA_NEXT     -> {
                    this@MusicClass.onSkipToNext()
                    true
                }
                KeyEvent.KEYCODE_MEDIA_PREVIOUS -> {
                    this@MusicClass.onSkipToPrevious()
                    true
                }
                else                            -> super.onMediaButtonEvent(mediaButtonEvent)
            }
        }
    }

    private val connectionCallback = object : MediaBrowserCompat.ConnectionCallback() {
        override fun onConnected() {
            global.log(MUSIC_TAG, "MusicClass onConnected: server connected.")

            mediaController = MediaControllerCompat(context, mediaBrowser.sessionToken)
            mediaController.registerCallback(mediaControllerCallback)

            initQueueItems()

            initMusicBrowserFlag = true
            initFirstConnection = true
            tryConnectFlag = true
            callbacks.forEach { it.onMusicPlayerConnected() }

            reserveAction?.let { it() }
            reserveAction = null
        }

        override fun onConnectionFailed() {
            tryConnectFlag = true
            global.log(MUSIC_TAG, "MusicClass onConnectionFailed: server connection failed.")
        }

        override fun onConnectionSuspended() {
            tryConnectFlag = true
            global.log(MUSIC_TAG, "MusicClass onConnectionSuspended: server connection suspended.")
        }
    }

    init {
        initialize()
    }

    fun connect() {
        if (!initMusicClassFlag) {
            initialize()
        }

        if (!initMusicBrowserFlag && tryConnectFlag) {
            global.log(MUSIC_TAG, "connecting...")
            tryConnectFlag = false
            mediaBrowser.connect()
        } else if (initMusicBrowserFlag) {
            global.log(MUSIC_TAG, "server already connected.")
            callbacks.forEach { it.onMusicPlayerConnected() }
        } else global.log(MUSIC_TAG, "Reject connection.")
    }

    fun disconnect() {
        mediaController.unregisterCallback(mediaControllerCallback)
        mediaBrowser.disconnect()

        callbacks.forEach { it.onMusicPlayerDisconnected() }

        initMusicBrowserFlag = false
        tryConnectFlag = true

        global.log(MUSIC_TAG, "disconnected.")
    }

    fun attach(service: MediaBrowserServiceCompat, musicPlayer: MediaPlayer) {
        global.log(MUSIC_TAG, "MusicClass attach: service attach.")
        this.service = service
        this.musicPlayer = musicPlayer

        music.mediaSession = MediaSessionCompat(service.baseContext, MusicService::class.java.simpleName).apply {
            setPlaybackState(
                PlaybackStateCompat.Builder()
                    .setActions(PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PAUSE or PlaybackStateCompat.ACTION_SKIP_TO_NEXT or PlaybackStateCompat.ACTION_STOP)
                    .build()
            )
            setCallback(music.mediaSessionCallback)
            service.sessionToken = sessionToken
        }
    }

    fun detach() {
        global.log(MUSIC_TAG, "MusicClass detach: service detach.")
        this.service = null
    }

    private fun initialize() {
        global.log(MUSIC_TAG, "MusicClass initialize: Server is initializing...")
        getMusic()
        mediaBrowser = MediaBrowserCompat(context, ComponentName(context, MusicService::class.java), connectionCallback, null)
        initMusicClassFlag = true
    }

    fun clearData() {
        musicMap.clear()
        artworkMap.clear()
    }

    fun reserveMusicAction(action: () -> (Unit)) {
        if (!initMusicBrowserFlag) reserveAction = action
        else action()
    }

    fun setCurrentMusic(newData: MusicData) {
        global.log(MUSIC_TAG, "MusicClass setCurrentMusic: change current music data.")
        currentMusicData = newData
        onCurrentMusicChangedListener?.onCurrentMusicChanged(newData)
        callbacks.forEach { it.onUpdateCurrentMusicData(newData) }
    }

    fun isConnect() = initMusicBrowserFlag

    fun getCurrentMusic() = currentMusicData

    fun getCurrentMusicState() = currentMusicState

    fun getMusicMap() = musicMap

    fun getQueueList() = queueItems

    fun getQueueIndex() = queueIndex

    fun getArtworkFromMap(albumName: String?) = artworkMap[albumName]

    fun onPlay(musicData: MusicData, queueList: List<MusicData>?, onShuffle: Boolean = true, repeat: Boolean = false) {
        if (!isConnect()) {
            reserveMusicAction { onPlay(musicData, queueList, onShuffle, repeat) }
            connect()
        } else if (currentMusicData?.musicId != musicData.musicId || currentMusicState == 1 || currentMusicState == PlaybackStateCompat.STATE_STOPPED || repeat) {
            if (queueList != null) setQueueItemList(musicData, queueList, onShuffle)
            saveQueueItem()
            mediaController.transportControls.playFromMediaId(musicData.musicId, null)
        } else {
            saveQueueItem()
            mediaController.transportControls.play()
        }
    }

    fun onPause() {
        if (!isConnect()) {
            reserveMusicAction { onPause() }
            connect()
        } else mediaController.transportControls.pause()
    }

    fun onStop() {
        mediaController.transportControls.stop()
    }

    fun onSkipToNext() {
        if (!isConnect()) {
            reserveMusicAction { onSkipToNext() }
            connect()
        } else mediaController.transportControls.skipToNext()
    }

    fun onSkipToPrevious() {
        if (!isConnect()) {
            reserveMusicAction { onSkipToPrevious() }
            connect()
        } else mediaController.transportControls.skipToPrevious()
    }

    fun onShuffle(mode: Int) {
        if (!isConnect()) {
            reserveMusicAction { onShuffle(mode) }
            connect()
        } else mediaController.transportControls.setShuffleMode(mode)
    }

    fun onRepeat(mode: Int) {
        if (!isConnect()) {
            reserveMusicAction { onRepeat(mode) }
            connect()
        } else mediaController.transportControls.setRepeatMode(mode)
    }

    fun onSeek(progress: Long) {
        if (!isConnect()) {
            reserveMusicAction { onSeek(progress) }
            connect()
        } else mediaController.transportControls.seekTo(progress)
    }

    fun addCallback(callback: MusicCallback) {
        this.callbacks.add(callback)
    }

    fun removeCallback(callback: MusicCallback) {
        this.callbacks.remove(callback)
    }

    fun setListener(listener: MusicListener) {
        this.listener = listener
    }

    fun removeListener() {
        this.listener = null
    }

    fun setCurrentMusicChangeListener(listener: OnCurrentMusicChanged) {
        this.onCurrentMusicChangedListener = listener
    }

    fun removeCurrentMusicChangeListener() {
        this.onCurrentMusicChangedListener = null
    }

    fun setVolume(volumeLeft: Float, volumeRight: Float) {
        callbacks.forEach { it.onUpdatePlayerVolume(volumeLeft, volumeRight) }
    }

    fun resetVolume() {
        val baseAudioVolume = settings.pGetInt("qBaseVolume", 5).toFloat() / 10f
        callbacks.forEach { it.onUpdatePlayerVolume(baseAudioVolume, baseAudioVolume) }
    }

    fun setBass(strength: Int) {
        callbacks.forEach { it.onUpdatePlayerBass((strength * 50).toShort()) }
    }

    fun resetBass() {
        callbacks.forEach { it.onUpdatePlayerBass(0) }
    }

    fun setEqualizer(hzMap: Pair<Int, Int>) {
        callbacks.forEach { it.onUpdatePlayerEqualizer(hzMap) }
    }

    fun resetEqualizer() {
        global.equalizerBandMap.forEach { band ->
            callbacks.forEach { it.onUpdatePlayerEqualizer(Pair(band.key, 0)) }
        }
    }

    fun setReverbEffect(effect: Short) {
        callbacks.forEach { it.onUpdatePlayerReverbEffect(effect) }
    }

    fun progressUpdate(progress: Long) {
        settings.currentMusicProgress = progress
        callbacks.forEach { it.onUpdateMusicProgress(progress) }
    }

    fun setMasterMute(isMute: Boolean): Boolean {
        val audioMethods = audioManager.javaClass.declaredMethods
        for(method in audioMethods){
            if(method.name == "setMasterMute"){
                try {
                    method.invoke(audioManager, isMute, 0)
                    Log.d(MUSIC_TAG, "set master mute success. STATUS: $isMute")
                    return true
                }
                catch (e: Exception){
                    global.stackTrace(e.toString())
                }
            }
        }
        Log.d(MUSIC_TAG, "set master mute failed. STATUS: $isMute")
        return false
    }

    fun isMasterMute(): Boolean {
        val audioMethods = audioManager.javaClass.declaredMethods
        for(method in audioMethods){
            if(method.name == "isMasterMute"){
                try {
                    val status = method.invoke(audioManager) as Boolean
                    Log.d(MUSIC_TAG, "get master mute success. STATUS: $status")
                    return status
                }
                catch (e: Exception){
                    global.stackTrace(e.toString())
                }
            }
        }
        Log.d(MUSIC_TAG, "set master mute failed.")
        return false
    }

    fun getMusic() {
        var cursor: Cursor? = null
        val tempMap = mutableMapOf<String, MusicData>()

        try {
            cursor = context.contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, "title")

            if (cursor != null && cursor.moveToFirst()) {
                val artistColumn: Int = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST)
                val titleColumn: Int = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE)
                val albumColumn: Int = cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM)
                val albumIdColumn: Int = cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID)
                val durationColumn: Int = cursor.getColumnIndex(MediaStore.Audio.Media.DURATION)
                val idColumn: Int = cursor.getColumnIndex(MediaStore.Audio.Media._ID)
                val idTruck: Int = cursor.getColumnIndex(MediaStore.Audio.Media.TRACK)
                val pathColumn: Int = cursor.getColumnIndex(MediaStore.Audio.Media.DATA)
                val yearColumn = cursor.getColumnIndex(MediaStore.Audio.Media.YEAR)

                do {
                    if (cursor.getInt(durationColumn) > 3000) {
                        val contentUri = Uri.withAppendedPath(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, cursor.getInt(idColumn).toString())
                        tempMap[cursor.getInt(idColumn).toString()] = MusicData(
                            cursor.getInt(idColumn).toString(),
                            cursor.getString(pathColumn),
                            null,
                            cursor.getString(titleColumn),
                            cursor.getString(artistColumn),
                            cursor.getString(albumColumn),
                            cursor.getLong(albumIdColumn),
                            cursor.getInt(durationColumn),
                            cursor.getString(idTruck),
                            cursor.getInt(yearColumn).toString(),
                            null,
                            contentUri
                        )
                    }
                } while (cursor.moveToNext())
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }

        cursor?.close()
        musicMap = tempMap
    }

    fun getMusicMediaItem(_musicData: MusicData?, musicId: String? = null): MediaBrowserCompat.MediaItem {
        val musicData = _musicData ?: musicMap[musicId] ?: throw IllegalStateException("Cannot found music data.")

        val metadata = MediaMetadataCompat.Builder().putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, musicData.musicId)
            .putString(MediaMetadataCompat.METADATA_KEY_TITLE, musicData.title).putString(MediaMetadataCompat.METADATA_KEY_ARTIST, musicData.artist)
            .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, musicData.album)
            .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, musicData.duration.toLong())
            .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, musicData.albumArt).build()
        return MediaBrowserCompat.MediaItem(metadata.description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
    }

    fun getMusicMetadata(id: String?): MediaMetadataCompat? {
        id ?: return null
        val musicData = musicMap[id] ?: return null
        return MediaMetadataCompat.Builder().putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, musicData.musicId)
            .putString(MediaMetadataCompat.METADATA_KEY_TITLE, musicData.title).putString(MediaMetadataCompat.METADATA_KEY_ARTIST, musicData.artist)
            .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, musicData.album)
            .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, musicData.duration.toLong())
            .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, musicData.albumArt).build()
    }

    fun getMusicMetadataList(): MutableList<MediaBrowserCompat.MediaItem> {
        var index = 0
        val metadataList = mutableListOf<MediaBrowserCompat.MediaItem>()
        for ((_, musicData) in musicMap) {
            val metadata = MediaMetadataCompat.Builder().putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, musicData.musicId)
                .putString(MediaMetadataCompat.METADATA_KEY_TITLE, musicData.title)
                .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, musicData.artist)
                .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, musicData.album)
                .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, musicData.duration.toLong())
                .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, musicData.albumArt).build()

            metadataList.add(MediaBrowserCompat.MediaItem(metadata.description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE))
            index++
        }
        return metadataList
    }

    fun getArtwork(musicData: MusicData?, _albumName: String? = null, _albumId: Long? = null): Bitmap {
        val musicId = musicData?.musicId
        val albumName = musicData?.album ?: _albumName
        val albumId = musicData?.albumId ?: _albumId

        if (albumName == null || albumId == null) return defaultArtwork

        try {
            val albumArtFile = File(context.filesDir, "${musicData?.albumId}.bmp")
            val albumArtUri = Uri.parse("content://media/external/audio/albumart")
            val albumUri = ContentUris.withAppendedId(albumArtUri, albumId)

            if (albumArtFile.exists()) {
                BitmapFactory.decodeFile(albumArtFile.absolutePath)?.let {
                    artworkMap[albumName] = it
                    musicMap[musicId]?.albumArt = it
                    return it
                }
            }

            val inputStream = context.contentResolver.openInputStream(albumUri)
            BitmapFactory.decodeStream(inputStream)?.let {
                artworkMap[albumName] = it
                musicMap[musicId]?.albumArt = it
                return it
            }
        } catch (e: FileNotFoundException) {
        } catch (e: IOException) {
        } catch (e: Exception) {
            e.printStackTrace()
        }

        if (musicMap[musicId]?.albumArt != null) return musicMap[musicId]?.albumArt!!
        if (artworkMap[albumName] != null) return artworkMap[albumName]!!

        val char1 = if (albumName.isNotEmpty()) albumName[0].toString().toUpperCase(Locale.ROOT) else null
        val char2 = if (albumName.length > 1) albumName[1].toString().toUpperCase(Locale.ROOT) else null
        val resolution = settings.pGetString("pResolution", "400")?.toInt() ?: 400

        val defaultArtworkView = when (resolution) {
            800  -> View.inflate(context, R.layout.view_default_artwork_0, null)
            600  -> View.inflate(context, R.layout.view_default_artwork_1, null)
            400  -> View.inflate(context, R.layout.view_default_artwork_2, null)
            300  -> View.inflate(context, R.layout.view_default_artwork_3, null)
            200  -> View.inflate(context, R.layout.view_default_artwork_4, null)
            else -> View.inflate(context, R.layout.view_default_artwork_2, null)
        }

        if (char1 != null && char2 != null) {
            defaultArtworkView.findViewById<TextView>(R.id.VDA_Char1).text = char1
            defaultArtworkView.findViewById<TextView>(R.id.VDA_Char2).text = char2
        } else if (char1 != null && char2 == null) {
            defaultArtworkView.findViewById<TextView>(R.id.VDA_Char1).text = char1
            defaultArtworkView.findViewById<TextView>(R.id.VDA_Char2).text = char1
        } else if (char1 == null && char2 == null) {
            global.log(TAG, "char1 & char2 name is null so return default artwork")
            return defaultArtwork
        }

        defaultArtworkView.findViewById<ConstraintLayout>(R.id.VDA_Background).background = when (global.getStringColorCode(albumName)) {
            0    -> ColorDrawable(ContextCompat.getColor(context, R.color.colorDefaultArtwork0))
            1    -> ColorDrawable(ContextCompat.getColor(context, R.color.colorDefaultArtwork1))
            2    -> ColorDrawable(ContextCompat.getColor(context, R.color.colorDefaultArtwork2))
            3    -> ColorDrawable(ContextCompat.getColor(context, R.color.colorDefaultArtwork3))
            4    -> ColorDrawable(ContextCompat.getColor(context, R.color.colorDefaultArtwork4))
            else -> ColorDrawable(ContextCompat.getColor(context, R.color.colorDefaultArtwork0))
        }

        if (defaultArtworkView.measuredHeight <= 0) {
            defaultArtworkView.measure(
                View.MeasureSpec.makeMeasureSpec(resolution, View.MeasureSpec.EXACTLY),
                View.MeasureSpec.makeMeasureSpec(resolution, View.MeasureSpec.EXACTLY)
            )

            val defBitmap = Bitmap.createBitmap(defaultArtworkView.measuredWidth, defaultArtworkView.measuredHeight, Bitmap.Config.ARGB_8888)
            val canvas = Canvas(defBitmap)

            defaultArtworkView.layout(0, 0, defaultArtworkView.measuredWidth, defaultArtworkView.measuredHeight)
            defaultArtworkView.draw(canvas)

            val scaleX = (0.7f * defBitmap.width).toInt()
            val scaleY = (0.7f * defBitmap.height).toInt()
            val startX = (defBitmap.width - scaleX) / 2
            val startY = (defBitmap.height - scaleY) / 2

            val resultBitmap = Bitmap.createBitmap(defBitmap, startX, startY, scaleX, scaleY, null, true)

            artworkMap[albumName] = resultBitmap
            musicMap[musicId]?.albumArt = resultBitmap

            return resultBitmap
        }

        return defaultArtwork
    }

    fun updateQueueIndex(musicData: MusicData?) {
        for ((i, data) in queueItems.withIndex()) {
            if (data.description.mediaId == musicData?.musicId) {
                queueIndex = i
                break
            }
        }
    }

    fun initQueueItems() {
        val arrays = getQueueItem()

        queueItems = arrays.first
        queueItemsOriginal = arrays.second

        if (queueItems.isEmpty() || queueItemsOriginal.isEmpty()) {
            queueItems.clear()
            for ((i, item) in getMusicMetadataList().withIndex()) {
                queueItems.add(MediaSessionCompat.QueueItem(item.description, i.toLong()))
            }
            queueItemsOriginal.addAll(queueItems)
        }
    }

    private fun saveQueueItem() {
        thread {
            val dataList = mutableListOf<String>()
            val dataList2 = mutableListOf<String>()
            val queueItems = queueItems.toList()
            val queueItemsOriginal = queueItemsOriginal.toList()

            queueItems.forEach { dataList.add(it.description.mediaId ?: "") }
            queueItemsOriginal.forEach { dataList2.add(it.description.mediaId ?: "") }

            val json = Gson().toJson(dataList)
            val json2 = Gson().toJson(dataList2)

            settings.setString(SETTING_MUSIC_VALUE, "queueItems", json)
            settings.setString(SETTING_MUSIC_VALUE, "queueItemsOriginal", json2)
        }
    }

    fun addQueueItemsList(musicList: List<MusicData>) {
        val addList = mutableListOf<MediaSessionCompat.QueueItem>()

        for ((addCount, data) in musicList.withIndex()) {
            addList.add(MediaSessionCompat.QueueItem(getMusicMediaItem(data).description, (queueItems.size + addCount).toLong()))
        }

        queueItems.addAll(if (settings.shuffleMode) addList.shuffled() else addList)
        queueItemsOriginal.addAll(addList)
    }

    fun setQueueItemList(newMusicData: MusicData?, musicList: List<MusicData>, onShuffle: Boolean = true) {
        val tempList = mutableListOf<MediaSessionCompat.QueueItem>()

        for ((i, musicData) in musicList.withIndex()) {
            tempList.add(MediaSessionCompat.QueueItem(getMusicMediaItem(musicData).description, i.toLong()))
        }

        queueItems.clear()
        queueItems.addAll(tempList)
        queueItemsOriginal.clear()
        queueItemsOriginal.addAll(queueItems)

        updateQueueIndex(newMusicData)

        if (settings.shuffleMode && onShuffle) {
            onShuffle(PlaybackStateCompat.SHUFFLE_MODE_ALL)
        }
    }

    private fun getQueueItem(): Pair<MutableList<MediaSessionCompat.QueueItem>, MutableList<MediaSessionCompat.QueueItem>> {
        val json = settings.getString(SETTING_MUSIC_VALUE, "queueItems", null) ?: return Pair(mutableListOf(), mutableListOf())
        val json2 = settings.getString(SETTING_MUSIC_VALUE, "queueItemsOriginal", null) ?: return Pair(mutableListOf(), mutableListOf())
        val arrayList: List<String> = Gson().fromJson(json, ArrayList<String>().javaClass) ?: listOf()
        val arrayList2: List<String> = Gson().fromJson(json2, ArrayList<String>().javaClass) ?: listOf()
        val items = mutableListOf<MediaSessionCompat.QueueItem>()
        val items2 = mutableListOf<MediaSessionCompat.QueueItem>()
        val item3 = mutableListOf<MusicData?>()

        for ((i, id) in arrayList.withIndex()) {
            val description = getMusicMediaItem(null, id).description
            items.add(MediaSessionCompat.QueueItem(description, i.toLong()))
            item3.add(musicMap[id])
        }

        for ((i, id) in arrayList2.withIndex()) {
            val description = getMusicMediaItem(null, id).description
            items2.add(MediaSessionCompat.QueueItem(description, i.toLong()))
        }

        return Pair(items, items2)
    }

    fun setMusicMetadata(
        musicData: MusicData,
        newTitle: String,
        newArtist: String,
        newAlbum: String,
        newTrack: String,
        newYear: String,
        newAlbumId: String,
        newAlbumArtUri: Uri?
    ): Boolean {

        musicData.uri ?: return false

        val fileName = global.getFileName(musicData.path) ?: return false
        val artworkFileName = "${musicData.albumId}.bmp"
        val inputStream = context.contentResolver.openInputStream(musicData.uri!!) ?: return false
        val inputStream2 = newAlbumArtUri?.let { context.contentResolver.openInputStream(it) }

        context.openFileOutput(fileName, Context.MODE_PRIVATE).use { outputStream ->
            inputStream.use { it.copyTo(outputStream) }
        }

        context.openFileOutput(artworkFileName, Context.MODE_PRIVATE).use { outputStream ->
            inputStream2?.use { it.copyTo(outputStream) }
        }

        val albumArtUri = Uri.parse("content://media/external/audio/albumart")
        val albumUri = ContentUris.withAppendedId(albumArtUri, musicData.albumId)

        context.contentResolver.delete(albumUri, null, null)

        val artwork = ArtworkFactory.createArtworkFromFile(File(context.filesDir, artworkFileName))
        val audioFile = AudioFileIO.read(File(context.filesDir, fileName))

        audioFile.tag.apply {
            setField(FieldKey.TITLE, newTitle)
            setField(FieldKey.ARTIST, newArtist)
            setField(FieldKey.ALBUM, newAlbum)
            setField(FieldKey.TRACK, newTrack)
            setField(FieldKey.YEAR, newYear)

            newAlbumArtUri?.let { setField(artwork) }
        }

        audioFile.commit()

        val localFile = File(context.filesDir, fileName)

        if (localFile.exists()) {
            context.contentResolver.openOutputStream(musicData.uri!!)?.use { outputStream ->
                FileInputStream(localFile).use { it.copyTo(outputStream) }
            }
        }

        localFile.delete()

        val contentValues = ContentValues().apply {
            put(MediaStore.Audio.Media.ALBUM_ID, newAlbumId.toInt())
        }

        context.contentResolver.update(musicData.uri!!, contentValues, null, null)

        clearData()
        getMusic()

        return true
    }

    fun createMusicNotify(notificationManager: NotificationManager, notifyId: Int): Notification? {
        val musicData = currentMusicData ?: return null

        val notifyTitle = musicData.title
        val notifyText = musicData.artist
        val channelName = context.getString(R.string.mediaNotifyChannel)
        val channelDescription = context.getString(R.string.mediaNotifyDescription)

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && notificationManager.getNotificationChannel(notifyId.toString()) == null) {
            val channel = NotificationChannel(notifyId.toString(), channelName, NotificationManager.IMPORTANCE_LOW).apply {
                description = channelDescription
            }

            notificationManager.createNotificationChannel(channel)
        }

        val stackBuilder = TaskStackBuilder.create(context).addNextIntent(Intent(context, MusicActivity::class.java))
        val pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)

        val notificationBuilder = NotificationCompat.Builder(context, notifyId.toString()).apply {
            setStyle(
                androidx.media.app.NotificationCompat.MediaStyle().setMediaSession(mediaSession.sessionToken).setShowActionsInCompactView(0, 1, 2)
                    .setShowCancelButton(true)
                    .setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP))
            )
            setSmallIcon(R.drawable.ic_music)
            setLargeIcon(getArtwork(musicData))
            setContentTitle(notifyTitle)
            setContentText(notifyText)
            setAutoCancel(false)
            setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP))
            setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
            setContentIntent(pendingIntent)
        }

        var action = NotificationCompat.Action(
            R.drawable.notification_backward_48,
            context.getString(R.string.SkipToPrevious),
            MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)
        )

        notificationBuilder.addAction(action)

        action = if (currentMusicState == PlaybackStateCompat.STATE_PLAYING) {
            NotificationCompat.Action(
                R.drawable.notification_pause_48,
                context.getString(R.string.pause),
                MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_PAUSE)
            )
        } else {
            NotificationCompat.Action(
                R.drawable.notification_play_48,
                context.getString(R.string.play),
                MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_PLAY)
            )
        }

        notificationBuilder.addAction(action)

        action = NotificationCompat.Action(
            R.drawable.notification_forward_48,
            context.getString(R.string.SkipToNext),
            MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_SKIP_TO_NEXT)
        )

        notificationBuilder.addAction(action)

        return notificationBuilder.build()
    }

    abstract class MusicCallback {
        open fun onMusicPlayerConnected() {}
        open fun onMusicPlayerDisconnected() {}
        open fun onUpdatePlayerState(state: Int) {}
        open fun onUpdatePlayerVolume(volumeLeft: Float, volumeRight: Float) {}
        open fun onUpdatePlayerBass(strength: Short) {}
        open fun onUpdatePlayerEqualizer(hzPair: Pair<Int, Int>) {}
        open fun onUpdatePlayerReverbEffect(effect: Short) {}
        open fun onUpdateCurrentMusicData(newData: MusicData) {}
        open fun onUpdateMusicMetadata(metadata: MediaMetadataCompat) {}
        open fun onUpdateMusicProgress(progress: Long) {}
    }

    interface MusicListener {
        fun onSetMusicData(musicData: MusicData)
        fun onPlayFromMusicData(musicData: MusicData, focedFirst: Boolean = false)
        fun onPlay()
        fun onPause()
        fun onStop()
        fun onSkipToNext(musicData: MusicData, isPlay: Boolean)
        fun onSkipToPrevious(musicData: MusicData, isPlay: Boolean)
        fun onSeekTo(progress: Long)
    }

    interface OnCurrentMusicChanged {
        fun onCurrentMusicChanged(newData: MusicData)
    }

    companion object {
        const val MUSIC_TAG = "<MUSIC>"
    }
}

data class MusicData(
    val musicId: String,
    val path: String,
    var albumArt: Bitmap?,
    val title: String,
    val artist: String,
    val album: String,
    val albumId: Long,
    val duration: Int,
    val truck: String?,
    val year: String,
    var favorite: Boolean?,
    var uri: Uri? = null,
    var queueId: Int = -1
)

その4

MusicClassを超省略して、主要な関数のみ説明。

MusicClassの基本

インスタンスはアプリ内で一つだけ作成。トップレベルで保持することを推奨。(今回はトップレベルで"music"という名でインスタンスを作成している)

ApplicationクラスのonCreateなどで作成することを推奨。

initialize

MusicClassを初期化&音楽ライブラリの準備&MediaBrowserService接続準備

connect, disconnect

MediaBrowserと接続or切断するための関数。

attach. detach

MediaBrowserからクラスに接続or切断するための関数。

上記にも書いたが、基本は全ての音楽関連処理をこのクラスで行う。実際にMediaPlayerなどを使って音楽を再生するのはMusieServiceの役目。

onPlay, onPause, onStop, etc...

音楽を再生、中断、中止など。

getMusic

contentProviderから音楽情報を引っ張ってくる。MediaBrowserに頼らない私流のやり方。外部から安易に呼び出さないこと推奨。

getArtwork

artworkを取得。今気づいたけど、オリジナルの処理が入っているからエラー出るかも。ごめん。ここは重要ではないので消しても構わない。

MusicCallback

MusicClassの状態変化によって呼び出される。機能は関数名そのまま。

MusiscListener

音楽の再生状態の変化によって呼び出される。MediaPlayerなどの音楽を実際に再生するクラス以外overrideしてはいけない。

reserveMusicAction

MediaBrowserとまだ接続してないけど、接続したらやってね!という動作を登録しておきたいときに使用する。

setCurrentMusic

currentMusicを更新したいときに呼ぶ。MediaPlayerなどの音楽を実際に再生するクラス以外overrideしてはいけない。

その5

以下MusicServiceのソースコード。これをコピーして貼り付け。

(自分のアプリからそのまま引っ張ってきているのでエラー多いかも。使わないなと思ったところは消して構わない。気にすることなかれ。)


class MusicService : MediaBrowserServiceCompat() {

    private lateinit var audioFocusRequest: AudioFocusRequestCompat
    private lateinit var bassBoost: BassBoost
    private lateinit var equalizer: Equalizer
    private lateinit var presetReverb: PresetReverb

    private var musicPlayer = MediaPlayer()
    private val audioManager by lazy { getSystemService(Context.AUDIO_SERVICE) as AudioManager }
    private val audioNoisyFilter = IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)

    private var bootFlag = true

    private val audioNoisyReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            music.onPause()
        }
    }

    private val completionListener = MediaPlayer.OnCompletionListener {
        global.log(MUSIC_TAG, "MusicService onCompletionListener: onComplete")

        when (settings.repeatMode) {
            PlaybackStateCompat.REPEAT_MODE_ALL  -> {
                music.onSkipToNext()
            }
            PlaybackStateCompat.REPEAT_MODE_ONE  -> {
                music.onSeek(0)
                music.getCurrentMusic()?.let { it1 -> music.onPlay(it1, null, onShuffle = true) }
            }
            PlaybackStateCompat.REPEAT_MODE_NONE -> {
                if (music.getQueueIndex() + 1 >= music.getQueueList().size) music.onPause()
                music.onSkipToNext()
            }
            else                                 -> music.onSkipToNext()
        }
    }

    private val playerErrorListener = MediaPlayer.OnErrorListener { _, _, _ ->
        Log.d(MUSIC_TAG, "service: Invalid state of music player.")
        resetMusicPlayer()
        return@OnErrorListener true
    }

    private val audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { audioFocus ->
        when (audioFocus) {
            AudioManager.AUDIOFOCUS_GAIN           -> {
                global.log(MUSIC_TAG, "MusicService : AudioFocus gain.")
                music.getCurrentMusic()?.let { music.onPlay(it, null) }
            }
            AudioManager.AUDIOFOCUS_LOSS           -> {
                global.log(MUSIC_TAG, "MusicService : AudioFocus lost.")
                music.onPause()
            }
            AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
                global.log(MUSIC_TAG, "MusicService : AudioFocus loss transient.")
                music.onPause()
            }
        }
    }

    private val musicCallback = object : MusicClass.MusicCallback() {
        override fun onMusicPlayerConnected() {
            prepare()
        }

        override fun onUpdatePlayerState(state: Int) {
            musicNotify()

            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) music.getCurrentVolumeDb()
        }

        override fun onUpdatePlayerVolume(volumeLeft: Float, volumeRight: Float) {
            musicPlayer.setVolume(volumeLeft, volumeRight)
            Log.d(MUSIC_TAG, "set music volume ($volumeLeft, $volumeRight).")
        }

        override fun onUpdatePlayerBass(strength: Short) {
            try {
                bassBoost.setStrength(strength)
                Log.d(MUSIC_TAG, "bass boost. STG: $strength")
            } catch (e: Exception) {
                global.stackTrace(e.toString())
            }
        }

        override fun onUpdatePlayerEqualizer(hzPair: Pair<Int, Int>) {
            try {
                equalizer.setBandLevel(hzPair.first.toShort(), hzPair.second.toShort())
                Log.d(MUSIC_TAG, "set equalizer. $hzPair")
            } catch (e: Exception) {
                global.stackTrace(e.toString())
            }
        }

        override fun onUpdatePlayerReverbEffect(effect: Short) {
            try {
                presetReverb.preset = effect
                Log.d(MUSIC_TAG, "set reverb effect. $effect")
            } catch (e: Exception) {
                global.stackTrace(e.toString())
            }
        }
    }

    private val musicListener = object : MusicClass.MusicListener {
        override fun onSetMusicData(musicData: MusicData) {
            global.log(MUSIC_TAG, "MusicService onSetMusicData: Set new music data.")
            musicPlayer.reset()
            musicPlayer.setDataSource(musicData.path)
            musicPlayer.prepare()
            global.addMusicHistory(musicData.musicId)

            musicNotify()
        }

        override fun onPlayFromMusicData(musicData: MusicData, focedFirst: Boolean) {
            onSetMusicData(musicData)
            if (musicData.musicId == music.getCurrentMusic()?.musicId && settings.pGetBoolean("pMiddlePlay", true) && !focedFirst) {
                onSeekTo(settings.currentMusicProgress)
            }
            music.setCurrentMusic(musicData)
            music.onPlay(musicData, null)
        }

        override fun onPlay() {
            if (gainAudioFocus()) {
                val baseAudioVolume = settings.pGetInt("qBaseVolume", 5).toFloat() / 10f
                val volume = if (settings.pGetBoolean("qDynamicNormalizer", false)) {
                    music.dynamicNormalize(music.getCurrentMusic()) ?: baseAudioVolume
                } else baseAudioVolume

                Log.d(MUSIC_TAG, "set music volume $volume.")

                registerReceiver(audioNoisyReceiver, audioNoisyFilter)
                music.setVolume(volume, volume)

                musicPlayer.setOnCompletionListener(completionListener)
                musicPlayer.start()
                global.setTimer()
            }
        }

        override fun onPause() {
            musicPlayer.pause()
            releaseAudioFocus()
            removeReceiver()
            global.cancelTimer()
        }

        override fun onStop() {
            musicPlayer.stop()
            releaseAudioFocus()
            removeReceiver()
            global.cancelTimer()

            disorganize()
        }

        override fun onSkipToNext(musicData: MusicData, isPlay: Boolean) {
            onSetMusicData(musicData)
            if (isPlay) onPlayFromMusicData(musicData, true)
            else {
                music.setCurrentMusic(musicData)
                music.progressUpdate(0)
            }
        }

        override fun onSkipToPrevious(musicData: MusicData, isPlay: Boolean) {
            onSetMusicData(musicData)
            if (isPlay) onPlayFromMusicData(musicData, true)
            else {
                music.setCurrentMusic(musicData)
                music.progressUpdate(0)
            }
        }

        override fun onSeekTo(progress: Long) {
            musicPlayer.seekTo(progress.toInt())
            music.progressUpdate(progress)
        }

        private fun removeReceiver() {
            try {
                unregisterReceiver(audioNoisyReceiver)
            } catch (e: IllegalArgumentException) {
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    private val progressUpdate = object : Runnable {
        override fun run() {
            if (musicPlayer.isPlaying) music.progressUpdate(musicPlayer.currentPosition.toLong())
            global.handler.postDelayed(this, 500)
        }
    }

    override fun onGetRoot(clientPackageName: String, clientUid: Int, rootHints: Bundle?): BrowserRoot? {
        global.log(MUSIC_TAG, "MusicService onGetRoot")
        return BrowserRoot("All-OK", null)
    }

    override fun onLoadChildren(parentId: String, result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
        global.log(MUSIC_TAG, "MusicService onLoadChildren")
        result.sendResult(music.getMusicMetadataList())
    }

    override fun onCreate() {
        super.onCreate()

        music.addCallback(musicCallback)
        music.setListener(musicListener)
        music.attach(this, musicPlayer)

        global.handler.postDelayed(progressUpdate, 500)

        global.log(MUSIC_TAG, "MusicService onCreate: connected to Server.")
    }

    override fun onDestroy() {
        super.onDestroy()
        global.log(MUSIC_TAG, "MusicService onDestroy: disconnected from server.")
    }

    private fun prepare() {
        global.log(MUSIC_TAG, "MusicService prepare: preparing...")

        resetMusicPlayer()
        initAudioEffect()

        audioFocusRequest = AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN).setAudioAttributes(
                AudioAttributesCompat.Builder().setUsage(AudioAttributesCompat.USAGE_MEDIA).setContentType(AudioAttributesCompat.CONTENT_TYPE_MUSIC)
                    .build()
            ).setOnAudioFocusChangeListener(audioFocusChangeListener).build()

        music.getCurrentMusic()?.let { musicListener.onSetMusicData(it) }

        global.log(MUSIC_TAG, "MusicService prepare: prepared.")
    }

    private fun disorganize() {
        global.log(MUSIC_TAG, "MusicService disorganize: disorganizing...")

        bootFlag = false
        stopForeground(false)

        musicPlayer.release()
        bassBoost.release()
        equalizer.release()

        global.handler.removeCallbacks(progressUpdate)

        music.detach()
        music.removeListener()
        music.removeCallback(musicCallback)
        music.disconnect()

        stopSelf()

        global.log(MUSIC_TAG, "MusicService disorganize: disorganized.")
    }

    private fun initAudioEffect() {
        bassBoost = BassBoost(100, musicPlayer.audioSessionId)
        equalizer = Equalizer(100, musicPlayer.audioSessionId)
        presetReverb = PresetReverb(100, 0)

        musicPlayer.attachAuxEffect(presetReverb.id)
        musicPlayer.setAuxEffectSendLevel(1f)

        bassBoost.enabled = false
        equalizer.enabled = false
        presetReverb.enabled = false

        if (settings.pGetBoolean("qBassBoost", false)) {
            music.setBass(settings.pGetInt("qBassBoostValue", 0))
        }

        music.setReverbEffect(settings.pGetString("qReverbEffector", "0")?.toShort() ?: 0)

        val bands = equalizer.numberOfBands
        global.minEQLevel = equalizer.bandLevelRange[0]
        global.maxWQLevel = equalizer.bandLevelRange[1]
        global.equalizerBandMap.clear()

        for (i in 0 until bands) {
            global.equalizerBandMap[i] = (equalizer.getCenterFreq(i.toShort()) / 1000).toString() + "Hz"
        }

        AudioEffect.queryEffects().forEach {
            Log.d(MUSIC_TAG, "AUDIO EFFECT: ${it.name}, TYPE: ${it.type}")
        }

        bassBoost.enabled = true
        equalizer.enabled = true
        presetReverb.enabled = true

        Log.d(MUSIC_TAG, "audio effect initialized.")
    }

    private fun resetMusicPlayer() {
        musicPlayer.release()
        musicPlayer = MediaPlayer()
        musicPlayer.setOnErrorListener(playerErrorListener)
    }

    private fun gainAudioFocus(): Boolean {
        return when (AudioManagerCompat.requestAudioFocus(audioManager, audioFocusRequest)) {
            AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> true
            AudioManager.AUDIOFOCUS_REQUEST_FAILED  -> false
            AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> false
            else                                    -> false
        }
    }

    private fun releaseAudioFocus() {
        AudioManagerCompat.abandonAudioFocusRequest(audioManager, audioFocusRequest)
    }

    private fun musicNotify() {
        if (!bootFlag) return

        try {
            val notificationManager = baseContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            val notify = music.createMusicNotify(notificationManager, MUSIC_NOTIFY_ID) ?: return

            if (music.getCurrentMusicState() == PlaybackStateCompat.STATE_PLAYING) startForeground(MUSIC_NOTIFY_ID, notify)
            else stopForeground(false)

            notificationManager.notify(MUSIC_NOTIFY_ID, notify)
        } catch (e: Exception) {
            global.stackTrace(e.toString())
        }
    }

    companion object {
        const val MUSIC_NOTIFY_ID = 671
    }
}

その6

MusicServiceを超省略して、主要な関数のみ説明。

prepare

MusicServiceを初期化&MusicClassとの通信確立&再生準備

disorganize

MusicServiceを破棄&MusicClassとの通信を切断

musicNotify

音楽再生中はForegroundServiceとして振る舞い、中断中はいつでも破棄可能とする。

musicCallback, musicListener

MusicClassからの指示を拾い、実際に音楽を再生したりする。

progressUpdate

0.5sずつに呼び出して音楽の再生状況をMusicClassに報告する。(MusicClassはそれを受けて音楽の再生状況をActivityに伝えたりする)

その7

あとはApplicationクラスでMusicClassをインスタンス化して、実際にActivityなどでmusic.onPlayを呼び出せば音楽が再生される。

まとめ

質問を随時受付中。
この記事は自分のために高速で作ったものなので不備ありありだが、質問されれれば全力で答えていく。

3
1
0

Register as a new user and use Qiita more conveniently

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