class AudioService : Service() {
private lateinit var dataFromAPI: String
private lateinit var titleFromAPI: String
private lateinit var urlFromAPI: String
var mediaPlayer: ExoPlayer? = null
var duration: Int = 0
var currentPosition: Int = 0
private val audioBinder = AudioBinder()
@RequiresApi(Build.VERSION_CODES.O)
@SuppressLint("ForegroundServiceType")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
dataFromAPI = intent?.getStringExtra("data").toString()
titleFromAPI = intent?.getStringExtra("title").toString()
urlFromAPI = intent?.getStringExtra("audio").toString()
when (intent?.action) {
Constants.ACTION.STARTFOREGROUND_ACTION -> {
initialiseAudio(urlFromAPI)
startForeground(NOTIFICATION_ID, createNotification())
initialiseSeekBar()
}
Constants.ACTION.PAUSEFOREGROUND_ACTION -> {
pauseAudio()
startForeground(NOTIFICATION_ID, createNotification())
}
Constants.ACTION.PLAYAGAINFOREGROUND_ACTION -> {
playAudio()
startForeground(NOTIFICATION_ID, createNotification())
}
Constants.ACTION.STOPFOREGROUND_ACTION -> {
stopForeground(true)
pauseAudio()
stopSelf()
}
}
return START_NOT_STICKY
}
private fun initialiseAudio(urlFromAPI: String) {
mediaPlayer = ExoPlayer.Builder(this).build()
mediaPlayer!!.setMediaItem(MediaItem.fromUri(urlFromAPI))
try {
mediaPlayer!!.prepare()
mediaPlayer!!.play()
} catch (e: IOException) {
e.printStackTrace()
}
}
private fun playAudio() {
if (!isPlaying())
mediaPlayer!!.play()
}
private fun pauseAudio() {
if (isPlaying())
mediaPlayer!!.pause()
}
override fun onDestroy() {
mediaPlayer!!.playWhenReady = false
mediaPlayer!!.stop()
mediaPlayer!!.release()
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder {
return audioBinder
}
inner class AudioBinder : Binder() {
fun getService(): AudioService {
return this@AudioService
}
}
@OptIn(UnstableApi::class)
private fun initialiseSeekBar() {
mediaPlayer!!.addListener(object : Player.Listener {
@SuppressLint("UseCompatLoadingForDrawables")
@Deprecated("Deprecated in Java")
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
if (playbackState == Player.STATE_READY && mediaPlayer!!.playWhenReady) {
binding.btnAudioOff.setImageDrawable(resources.getDrawable(R.drawable.baseline_pause_24))
} else {
binding.btnAudioOff.setImageDrawable(resources.getDrawable(R.drawable.baseline_play_arrow_24))
}
}
override fun onIsPlayingChanged(isPlaying: Boolean) {
duration = mediaPlayer!!.duration.toInt() / 1000
binding.skAudioSeekBar.max = duration
binding.tvAudioDuration.text = getTimeString(duration)
}
override fun onPositionDiscontinuity(
oldPosition: Player.PositionInfo,
newPosition: Player.PositionInfo,
reason: Int
) {
currentPosition = mediaPlayer!!.currentPosition.toInt() / 1000
binding.skAudioSeekBar.progress = currentPosition
binding.tvAudioPosition.text = getTimeString(currentPosition)
binding.tvAudioDuration.text = getTimeString(mediaPlayer!!.duration.toInt() / 1000)
}
})
binding.skAudioSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser) {
mediaPlayer!!.seekTo(progress.toLong() * 1000)
binding.tvAudioPosition.text = getTimeString(progress)
binding.tvAudioDuration.text = getTimeString(duration)
}
}
override fun onStartTrackingTouch(p0: SeekBar?) {
}
override fun onStopTrackingTouch(p0: SeekBar?) {
}
})
val handler = Handler(Looper.getMainLooper())
handler.post(object : Runnable {
override fun run() {
currentPosition = mediaPlayer!!.currentPosition.toInt() / 1000
binding.skAudioSeekBar.progress = currentPosition
binding.tvAudioPosition.text = getTimeString(currentPosition)
binding.tvAudioDuration.text = getTimeString(duration)
handler.postDelayed(this, 1000)
}
})
}
@SuppressLint("DefaultLocale")
fun getTimeString(duration: Int): String {
val min = duration / 60
val sec = duration % 60
val time = String.format("%02d:%02d", min, sec)
return time
}
@OptIn(UnstableApi::class)
@Suppress("DEPRECATED_IDENTITY_EQUALS")
private fun isPlaying(): Boolean {
return mediaPlayer!!.playbackState === Player.STATE_READY && mediaPlayer!!.playWhenReady
}
}
Когда AudioService.kt вызывается в первый раз, все работает нормально, но когда вы вызываете его снова, TextView с позицией и длительность звуковой дорожки начинает «мигать»: сначала (на секунду) показывается положение и длительность, актуальная для предыдущего запуска сервиса, а затем (на секунду) — для текущего. Вместе с ним «подпрыгивает» SeekBar.
Я правда не могу понять, как и почему система сохраняет позицию и продолжительность ExoPlayer из уже уничтоженный сервис (даже после перезапуска приложения).
Есть фрагмент ([b]AudioFragment.kt[/b]), в котором запускается сервис ([b]AudioService.kt[/b]) с экземпляром ExoPlayer: [code] class AudioFragment : Fragment() {
private lateinit var urlFromAPI: String
private var wasButtonClicked: Boolean = false
private var isBounded = false
private var audioService: AudioService? = null
private val serviceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, iBinder: IBinder?) { val localBinder = iBinder as AudioService.AudioBinder audioService = localBinder.getService()
private fun unbindToAudioService() { activity?.unbindService(serviceConnection) }
private fun bindToAudioService() {
val intent = Intent(activity, AudioService::class.java) activity?.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
}
private fun startAudioService() { val serviceIntent = Intent(activity, AudioService::class.java)
val dataFromAPI = activity?.intent?.getStringExtra("data").toString() val titleFromAPI = activity?.intent?.getStringExtra("title").toString() val urlFromAPI = activity?.intent?.getStringExtra("audio").toString()
@SuppressLint("DefaultLocale") fun getTimeString(duration: Int): String { val min = duration / 60 val sec = duration % 60 val time = String.format("%02d:%02d", min, sec) return time }
@OptIn(UnstableApi::class) @Suppress("DEPRECATED_IDENTITY_EQUALS") private fun isPlaying(): Boolean { return mediaPlayer!!.playbackState === Player.STATE_READY && mediaPlayer!!.playWhenReady } } [/code] Когда [b]AudioService.kt[/b] вызывается в первый раз, все работает нормально, но когда вы вызываете его снова, TextView с позицией и длительность звуковой дорожки начинает «мигать»: сначала (на секунду) показывается положение и длительность, актуальная для предыдущего запуска сервиса, а затем (на секунду) — для текущего. Вместе с ним «подпрыгивает» SeekBar. Я правда не могу понять, как и почему система сохраняет позицию и продолжительность ExoPlayer из уже уничтоженный сервис (даже после перезапуска приложения).