ForegroundService MediaPlayer запускает 2 зацикленных звука на эмуляторе Marshmallow (Android 6.0, API 23) - PullRequest
0 голосов
/ 12 февраля 2020

Android версия: эмулятор Marshmallow (Android 6.0, API 23).

Проблема: ForegroundService с MediaPlayer запускает два сигнала цикла и должен запускать только один сигнал цикла. Когда я нажимаю pause, он приостанавливает только один из двух запущенных звуков.

Он корректно работает на Android версиях эмулятора (5, 5.1, 7, 7.1.1, 8, 8.1, 9, 10)

Я попытался вставить много Log.d и могу подтвердить, что медиаплеер никогда не создается дважды, также медиаплеер.start () также вызывается только один раз, если он еще не воспроизводится. Я также попробовал и .mp3, и .wav безуспешно.

Приложение представляет собой таймеры обратного отсчета с несколькими таймерами обратного отсчета, когда они достигают 0, должен начаться звук и нажать паузу или открыть приложение с завершенными таймерами. следует остановить аудио.

class AlarmAudioService : Service() {
    private var mediaPlayer: MediaPlayer? = null
    private val completedTimers = mutableListOf<String>()
    private val runningTimers = mutableListOf<String>()
    private var notificationController: NotificationController? = null

    enum class SERVICE_ACTION {
        START,
        ADD_RUNNING_TIMER,
        REMOVE_RUNNING_TIMER,
        TIMER_COMPLETE,
        TIMERS_IN_FOCUS,
        STOP
    }

    companion object {
        private var isServiceRunning: Boolean = false

        fun startService(context: Context) {
            if (isServiceRunning) return

            val intent = createAlarmAudioServiceIntent(context, SERVICE_ACTION.START)
            ContextCompat.startForegroundService(context, intent)
        }

        fun addRunningTimer(context: Context, timerTitle: String) {
            if (!isServiceRunning) return

            val intent = createAlarmAudioServiceIntent(context, SERVICE_ACTION.ADD_RUNNING_TIMER)
            val paramTitleKey = context.getString(R.string.notifications_parameter_title_key)
            intent.putExtra(paramTitleKey, timerTitle)
            ContextCompat.startForegroundService(context, intent)
        }

        fun removeRunningTimer(context: Context, timerTitle: String) {
            if (!isServiceRunning) return

            val intent = createAlarmAudioServiceIntent(context, SERVICE_ACTION.REMOVE_RUNNING_TIMER)
            val paramTitleKey = context.getString(R.string.notifications_parameter_title_key)
            intent.putExtra(paramTitleKey, timerTitle)
            ContextCompat.startForegroundService(context, intent)
        }

        fun timerComplete(context: Context, timerTitle: String) {
            if (!isServiceRunning) return

            val intent = createAlarmAudioServiceIntent(context, SERVICE_ACTION.TIMER_COMPLETE)
            val paramTitleKey = context.getString(R.string.notifications_parameter_title_key)
            intent.putExtra(paramTitleKey, timerTitle)
            ContextCompat.startForegroundService(context, intent)
        }

        fun timersInFocus(context: Context) {
            if (!isServiceRunning) return

            // This is called when we got the user attention, we can assume they know of completed timers and we can stop the alarm sound
            val intent = createAlarmAudioServiceIntent(context, SERVICE_ACTION.TIMERS_IN_FOCUS)
            ContextCompat.startForegroundService(context, intent)
        }

        fun stopService(context: Context) {
            if (!isServiceRunning) return

            val intent = createAlarmAudioServiceIntent(context, SERVICE_ACTION.STOP)
            ContextCompat.startForegroundService(context, intent)
        }

        private fun createAlarmAudioServiceIntent(context: Context, action: SERVICE_ACTION): Intent {
            val intent = Intent(context, AlarmAudioService::class.java)
            intent.putExtra("action", action)
            return intent
        }
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val action = intent?.getSerializableExtra("action") as SERVICE_ACTION

        when (action) {
            SERVICE_ACTION.START -> start()
            SERVICE_ACTION.ADD_RUNNING_TIMER -> addRunningTimer(intent)
            SERVICE_ACTION.REMOVE_RUNNING_TIMER -> removeRunningTimer(intent)
            SERVICE_ACTION.TIMER_COMPLETE -> timerComplete(intent)
            SERVICE_ACTION.TIMERS_IN_FOCUS -> timersInFocus()
            SERVICE_ACTION.STOP -> stop()
        }

        return START_STICKY
    }

    //#region Service Actions
    private fun start() {
        if (isServiceRunning) {
            return
        }

        isServiceRunning = true
        InitNotificationController()
        updateNotificationDescription()
        InitMediaPlayer()
    }

    private fun addRunningTimer(intent: Intent) {
        val paramTitleKey = getString(R.string.notifications_parameter_title_key)
        val timerTitle = intent.getStringExtra(paramTitleKey) ?: ""
        if (timerTitle.isNotEmpty()) {
            runningTimers.add(timerTitle)
        }
    }

    private fun removeRunningTimer(intent: Intent) {
        val paramTitleKey = getString(R.string.notifications_parameter_title_key)
        val timerTitle = intent.getStringExtra(paramTitleKey) ?: ""
        if (timerTitle.isNotEmpty()) {
            runningTimers.remove(timerTitle)
        }
    }

    private fun timerComplete(intent: Intent) {
        val paramTitleKey = getString(R.string.notifications_parameter_title_key)
        val timerTitle = intent.getStringExtra(paramTitleKey) ?: ""
        if (timerTitle.isNotEmpty()) {
            completedTimers.add(timerTitle)
            runningTimers.removeAll{ it == timerTitle }
        }
        StartSound()
        updateNotificationDescription()
    }

    private fun timersInFocus() {
        StopSound()
        completedTimers.clear()
        updateNotificationDescription()
    }

    private fun stop() {
        if (runningTimers.count() > 0) {
            return
        }

        isServiceRunning = false
        runningTimers.clear()
        completedTimers.clear()
        mediaPlayer?.stop()
        mediaPlayer?.release()
        mediaPlayer = null
        notificationController?.CancelNotification(1)
        notificationController = null
        stopSelf()
    }
    //#endregion


    private fun InitMediaPlayer() {
        if (mediaPlayer == null) {
            val attr = AudioAttributes.Builder()
                    .setUsage(AudioAttributes.USAGE_ALARM)
                    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                    .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
                    .build()
            mediaPlayer = MediaPlayer.create(this, R.raw.alarm_sound, attr, 0)
            mediaPlayer?.isLooping = true
        }
    }

    private fun InitNotificationController() {
        if (notificationController == null) {
            notificationController = NotificationController(this)
        }
    }

    override fun onBind(intent: Intent): IBinder? {
        return null
    }

    private fun StartSound() {
        // Only start media player if not playing
        if (mediaPlayer?.isPlaying == false) {
            mediaPlayer?.start()
        }
    }

    private fun StopSound() {
        if (mediaPlayer?.isPlaying == true) {
            mediaPlayer?.pause()
        }
    }

    private fun createNotificationDescription(): String {
        return when {
            completedTimers.count() > 1 -> {
                "Multiple timers are completed."
            }
            completedTimers.count() == 1 -> {
                "${completedTimers.first()} is completed."
            }
            else -> {
                "You will be notified when a timer is complete."
            }
        }
    }

    private fun updateNotificationDescription() {
        if (!isServiceRunning) {
            return
        }

        val description = createNotificationDescription()
        // Oreo API 26+ requires a foreground notification for a service
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val notification = notificationController?.CreateForegroundNotification(description)
            startForeground(1, notification)
        } else {
            val notification = notificationController?.CreateForegroundNotification(description)
            startForeground(1, notification)
        }
    }
}
...