Код: Выделить всё
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()
isBounded = true
urlFromAPI = activity?.intent?.getStringExtra("audio").toString()
startAudioService()
}
override fun onServiceDisconnected(name: ComponentName?) {
isBounded = false
audioService = null
stopAudioService()
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentAudioBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.tvAudioPosition.text = "00:00"
bindToAudioService()
binding.btnAudioOff.setOnClickListener {
if (wasButtonClicked) {
binding.btnAudioOff.setImageResource(R.drawable.baseline_pause_24)
wasButtonClicked = false
playAgainAudio()
} else {
binding.btnAudioOff.setImageResource(R.drawable.baseline_play_arrow_24)
wasButtonClicked = true
pauseAudio()
}
}
}
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()
serviceIntent.putExtra("data", dataFromAPI)
serviceIntent.putExtra("title", titleFromAPI)
serviceIntent.putExtra("audio", urlFromAPI)
serviceIntent.action = Constants.ACTION.STARTFOREGROUND_ACTION
activity?.startService(serviceIntent)
}
private fun pauseAudio() {
val serviceIntent = Intent(activity, AudioService::class.java)
serviceIntent.action = Constants.ACTION.PAUSEFOREGROUND_ACTION
activity?.startService(serviceIntent)
}
private fun playAgainAudio() {
val serviceIntent = Intent(activity, AudioService::class.java)
serviceIntent.action = Constants.ACTION.PLAYAGAINFOREGROUND_ACTION
activity?.startService(serviceIntent)
}
private fun stopAudioService() {
val serviceIntent = Intent(activity, AudioService::class.java)
activity?.stopService(serviceIntent)
}
override fun onDestroy() {
stopAudioService()
unbindToAudioService()
super.onDestroy()
}
companion object {
@SuppressLint("StaticFieldLeak")
lateinit var binding: FragmentAudioBinding
@JvmStatic
fun newInstance() = AudioFragment()
}
}
Код: Выделить всё
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
}
}
Я правда не могу понять, как и почему система сохраняет позицию и продолжительность ExoPlayer из уже уничтоженный сервис (даже после перезапуска приложения).
Подробнее здесь: https://stackoverflow.com/questions/787 ... mehow-save