2021-07-12 10:14:26 +02:00
|
|
|
package audio.funkwhale.ffa.playback
|
2019-08-19 16:50:33 +02:00
|
|
|
|
|
|
|
import android.annotation.SuppressLint
|
|
|
|
import android.app.Service
|
|
|
|
import android.content.Context
|
|
|
|
import android.content.Intent
|
|
|
|
import android.content.IntentFilter
|
|
|
|
import android.media.AudioAttributes
|
|
|
|
import android.media.AudioFocusRequest
|
|
|
|
import android.media.AudioManager
|
2020-07-09 23:01:35 +02:00
|
|
|
import android.media.MediaMetadata
|
2019-08-19 16:50:33 +02:00
|
|
|
import android.os.Build
|
2019-10-21 11:51:32 +02:00
|
|
|
import android.os.IBinder
|
2020-07-09 23:01:35 +02:00
|
|
|
import android.support.v4.media.MediaMetadataCompat
|
2020-07-12 20:46:33 +02:00
|
|
|
import android.view.KeyEvent
|
2020-07-11 14:16:22 +02:00
|
|
|
import androidx.core.app.NotificationManagerCompat
|
2020-07-12 18:28:50 +02:00
|
|
|
import androidx.media.session.MediaButtonReceiver
|
2021-07-21 09:11:07 +02:00
|
|
|
import audio.funkwhale.ffa.R
|
2021-08-22 09:48:33 +02:00
|
|
|
import audio.funkwhale.ffa.model.Track
|
2021-09-09 09:56:15 +02:00
|
|
|
import audio.funkwhale.ffa.utils.Command
|
|
|
|
import audio.funkwhale.ffa.utils.CommandBus
|
|
|
|
import audio.funkwhale.ffa.utils.Event
|
|
|
|
import audio.funkwhale.ffa.utils.EventBus
|
|
|
|
import audio.funkwhale.ffa.utils.FFACache
|
|
|
|
import audio.funkwhale.ffa.utils.HeadphonesUnpluggedReceiver
|
|
|
|
import audio.funkwhale.ffa.utils.ProgressBus
|
|
|
|
import audio.funkwhale.ffa.utils.Request
|
|
|
|
import audio.funkwhale.ffa.utils.RequestBus
|
|
|
|
import audio.funkwhale.ffa.utils.Response
|
|
|
|
import audio.funkwhale.ffa.utils.log
|
|
|
|
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
|
|
|
|
import audio.funkwhale.ffa.utils.onApi
|
2020-07-10 17:18:29 +02:00
|
|
|
import com.google.android.exoplayer2.C
|
2022-08-27 09:21:03 +02:00
|
|
|
import com.google.android.exoplayer2.ExoPlayer
|
|
|
|
import com.google.android.exoplayer2.PlaybackException
|
2020-07-10 17:18:29 +02:00
|
|
|
import com.google.android.exoplayer2.Player
|
2022-08-27 09:21:03 +02:00
|
|
|
import com.google.android.exoplayer2.Tracks
|
2020-07-09 23:01:35 +02:00
|
|
|
import com.squareup.picasso.Picasso
|
2021-09-09 09:56:15 +02:00
|
|
|
import kotlinx.coroutines.CoroutineScope
|
2020-05-30 21:16:28 +02:00
|
|
|
import kotlinx.coroutines.Dispatchers.IO
|
2019-08-19 16:50:33 +02:00
|
|
|
import kotlinx.coroutines.Dispatchers.Main
|
2021-09-09 09:56:15 +02:00
|
|
|
import kotlinx.coroutines.Job
|
|
|
|
import kotlinx.coroutines.cancel
|
|
|
|
import kotlinx.coroutines.delay
|
2019-10-31 16:17:37 +01:00
|
|
|
import kotlinx.coroutines.flow.collect
|
2021-09-09 09:56:15 +02:00
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
import kotlinx.coroutines.runBlocking
|
2021-08-09 06:50:46 +02:00
|
|
|
import org.koin.java.KoinJavaComponent.inject
|
2019-08-19 16:50:33 +02:00
|
|
|
|
|
|
|
class PlayerService : Service() {
|
2020-07-09 23:01:35 +02:00
|
|
|
companion object {
|
|
|
|
const val INITIAL_COMMAND_KEY = "start_command"
|
|
|
|
}
|
|
|
|
|
2021-08-09 06:50:46 +02:00
|
|
|
private val mediaSession: MediaSession by inject(MediaSession::class.java)
|
|
|
|
|
2020-06-25 01:26:15 +02:00
|
|
|
private var started = false
|
|
|
|
private val scope: CoroutineScope = CoroutineScope(Job() + Main)
|
2019-08-19 16:50:33 +02:00
|
|
|
|
|
|
|
private lateinit var audioManager: AudioManager
|
|
|
|
private var audioFocusRequest: AudioFocusRequest? = null
|
|
|
|
private val audioFocusChangeListener = AudioFocusChange()
|
|
|
|
private var stateWhenLostFocus = false
|
|
|
|
|
2020-06-25 01:26:15 +02:00
|
|
|
private lateinit var queue: QueueManager
|
2019-08-19 16:50:33 +02:00
|
|
|
private lateinit var mediaControlsManager: MediaControlsManager
|
2022-08-27 09:21:03 +02:00
|
|
|
private lateinit var player: ExoPlayer
|
2019-08-19 16:50:33 +02:00
|
|
|
|
2020-07-09 23:01:35 +02:00
|
|
|
private val mediaMetadataBuilder = MediaMetadataCompat.Builder()
|
|
|
|
|
2019-08-19 16:50:33 +02:00
|
|
|
private lateinit var playerEventListener: PlayerEventListener
|
|
|
|
private val headphonesUnpluggedReceiver = HeadphonesUnpluggedReceiver()
|
|
|
|
|
|
|
|
private var progressCache = Triple(0, 0, 0)
|
|
|
|
|
2020-05-30 21:16:28 +02:00
|
|
|
private lateinit var radioPlayer: RadioPlayer
|
|
|
|
|
2019-08-19 16:50:33 +02:00
|
|
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
2020-07-12 18:28:50 +02:00
|
|
|
intent?.action?.let {
|
|
|
|
if (it == Intent.ACTION_MEDIA_BUTTON) {
|
2020-07-12 20:46:33 +02:00
|
|
|
intent.extras?.getParcelable<KeyEvent>(Intent.EXTRA_KEY_EVENT)?.let { key ->
|
|
|
|
when (key.keyCode) {
|
|
|
|
KeyEvent.KEYCODE_MEDIA_PLAY, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
|
2021-07-02 13:55:49 +02:00
|
|
|
if (hasAudioFocus(true)) MediaButtonReceiver.handleIntent(
|
2021-08-09 06:50:46 +02:00
|
|
|
mediaSession.session,
|
2021-07-02 13:55:49 +02:00
|
|
|
intent
|
|
|
|
)
|
2020-07-12 20:46:33 +02:00
|
|
|
Unit
|
|
|
|
}
|
2021-08-09 06:50:46 +02:00
|
|
|
else -> MediaButtonReceiver.handleIntent(mediaSession.session, intent)
|
2020-07-12 20:46:33 +02:00
|
|
|
}
|
|
|
|
}
|
2020-07-12 18:28:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-09 23:01:35 +02:00
|
|
|
if (!started) {
|
|
|
|
watchEventBus()
|
|
|
|
}
|
2020-06-25 01:26:15 +02:00
|
|
|
|
|
|
|
started = true
|
2019-08-19 16:50:33 +02:00
|
|
|
|
|
|
|
return START_STICKY
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:46:33 +02:00
|
|
|
@SuppressLint("NewApi")
|
2019-08-19 16:50:33 +02:00
|
|
|
override fun onCreate() {
|
|
|
|
super.onCreate()
|
|
|
|
|
|
|
|
queue = QueueManager(this)
|
2020-06-25 01:26:15 +02:00
|
|
|
radioPlayer = RadioPlayer(this, scope)
|
2019-08-19 16:50:33 +02:00
|
|
|
|
|
|
|
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
|
|
|
2020-07-12 20:46:33 +02:00
|
|
|
Build.VERSION_CODES.O.onApi {
|
2019-08-19 16:50:33 +02:00
|
|
|
audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run {
|
2021-09-09 09:56:15 +02:00
|
|
|
setAudioAttributes(
|
|
|
|
AudioAttributes.Builder().run {
|
|
|
|
setUsage(AudioAttributes.USAGE_MEDIA)
|
|
|
|
setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
2019-08-19 16:50:33 +02:00
|
|
|
|
2021-09-09 09:56:15 +02:00
|
|
|
setAcceptsDelayedFocusGain(true)
|
|
|
|
setOnAudioFocusChangeListener(audioFocusChangeListener)
|
2019-08-19 16:50:33 +02:00
|
|
|
|
2021-09-09 09:56:15 +02:00
|
|
|
build()
|
|
|
|
}
|
|
|
|
)
|
2019-08-19 16:50:33 +02:00
|
|
|
|
|
|
|
build()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-09 06:50:46 +02:00
|
|
|
mediaControlsManager = MediaControlsManager(this, scope, mediaSession.session)
|
2019-08-19 16:50:33 +02:00
|
|
|
|
2022-08-27 09:21:03 +02:00
|
|
|
player = ExoPlayer.Builder(this).build().apply {
|
2019-08-19 16:50:33 +02:00
|
|
|
playWhenReady = false
|
|
|
|
|
|
|
|
playerEventListener = PlayerEventListener().also {
|
|
|
|
addListener(it)
|
|
|
|
}
|
2022-08-27 09:21:03 +02:00
|
|
|
EventBus.send(Event.StateChanged(this.isPlaying()))
|
2020-07-10 17:18:29 +02:00
|
|
|
}
|
2019-08-19 16:50:33 +02:00
|
|
|
|
2021-08-09 06:50:46 +02:00
|
|
|
mediaSession.active = true
|
2020-07-12 18:28:50 +02:00
|
|
|
|
2021-08-09 06:50:46 +02:00
|
|
|
mediaSession.connector.apply {
|
2020-07-10 17:18:29 +02:00
|
|
|
setPlayer(player)
|
2019-08-19 16:50:33 +02:00
|
|
|
|
2020-07-10 17:18:29 +02:00
|
|
|
setMediaMetadataProvider {
|
|
|
|
buildTrackMetadata(queue.current())
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (queue.current > -1) {
|
2022-08-27 09:21:03 +02:00
|
|
|
player.setMediaSource(queue.dataSources)
|
|
|
|
player.prepare()
|
2020-05-29 10:32:09 +02:00
|
|
|
|
2022-06-12 14:48:32 +02:00
|
|
|
FFACache.getLine(this, "progress")?.let {
|
|
|
|
player.seekTo(queue.current, it.toLong())
|
2020-05-29 10:32:09 +02:00
|
|
|
|
2020-07-09 23:01:35 +02:00
|
|
|
val (current, duration, percent) = getProgress(true)
|
2020-05-29 10:32:09 +02:00
|
|
|
|
|
|
|
ProgressBus.send(current, duration, percent)
|
|
|
|
}
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
|
2021-07-02 13:55:49 +02:00
|
|
|
registerReceiver(
|
|
|
|
headphonesUnpluggedReceiver,
|
|
|
|
IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
|
|
|
|
)
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun watchEventBus() {
|
2020-06-25 01:26:15 +02:00
|
|
|
scope.launch(Main) {
|
2020-06-24 14:54:13 +02:00
|
|
|
CommandBus.get().collect { command ->
|
2022-08-26 14:06:41 +02:00
|
|
|
if (command is Command.RefreshService) {
|
|
|
|
if (queue.metadata.isNotEmpty()) {
|
2020-06-24 14:54:13 +02:00
|
|
|
CommandBus.send(Command.RefreshTrack(queue.current()))
|
2022-08-26 14:06:41 +02:00
|
|
|
EventBus.send(Event.StateChanged(player.playWhenReady))
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
2022-08-26 14:06:41 +02:00
|
|
|
} else if (command is Command.ReplaceQueue) {
|
|
|
|
if (!command.fromRadio) radioPlayer.stop()
|
|
|
|
|
|
|
|
queue.replace(command.queue)
|
2022-08-27 09:21:03 +02:00
|
|
|
player.setMediaSource(queue.dataSources)
|
|
|
|
player.prepare()
|
2022-08-26 14:06:41 +02:00
|
|
|
|
|
|
|
setPlaybackState(true)
|
|
|
|
|
|
|
|
CommandBus.send(Command.RefreshTrack(queue.current()))
|
|
|
|
} else if (command is Command.AddToQueue) {
|
|
|
|
queue.append(command.tracks)
|
|
|
|
} else if (command is Command.PlayNext) {
|
|
|
|
queue.insertNext(command.track)
|
|
|
|
} else if (command is Command.RemoveFromQueue) {
|
|
|
|
queue.remove(command.track)
|
|
|
|
} else if (command is Command.MoveFromQueue) {
|
|
|
|
queue.move(command.oldPosition, command.newPosition)
|
|
|
|
} else if (command is Command.PlayTrack) {
|
|
|
|
queue.current = command.index
|
|
|
|
player.seekTo(command.index, C.TIME_UNSET)
|
|
|
|
|
|
|
|
setPlaybackState(true)
|
|
|
|
|
|
|
|
CommandBus.send(Command.RefreshTrack(queue.current()))
|
|
|
|
} else if (command is Command.ToggleState) {
|
|
|
|
togglePlayback()
|
|
|
|
} else if (command is Command.SetState) {
|
|
|
|
setPlaybackState(command.state)
|
|
|
|
} else if (command is Command.NextTrack) {
|
|
|
|
skipToNextTrack()
|
|
|
|
} else if (command is Command.PreviousTrack) {
|
|
|
|
skipToPreviousTrack()
|
|
|
|
} else if (command is Command.Seek) {
|
|
|
|
seek(command.progress)
|
|
|
|
} else if (command is Command.ClearQueue) {
|
|
|
|
queue.clear()
|
|
|
|
player.stop()
|
|
|
|
} else if (command is Command.ShuffleQueue) {
|
|
|
|
queue.shuffle()
|
|
|
|
} else if (command is Command.PlayRadio) {
|
|
|
|
queue.clear()
|
|
|
|
radioPlayer.play(command.radio)
|
|
|
|
} else if (command is Command.SetRepeatMode) {
|
|
|
|
player.repeatMode = command.mode
|
|
|
|
} else if (command is Command.PinTrack) {
|
|
|
|
PinService.download(this@PlayerService, command.track)
|
|
|
|
} else if (command is Command.PinTracks) {
|
|
|
|
command.tracks.forEach {
|
2021-07-02 13:55:49 +02:00
|
|
|
PinService.download(
|
|
|
|
this@PlayerService,
|
|
|
|
it
|
|
|
|
)
|
|
|
|
}
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
}
|
2020-06-25 01:26:15 +02:00
|
|
|
}
|
2019-08-19 16:50:33 +02:00
|
|
|
|
2020-06-25 01:26:15 +02:00
|
|
|
scope.launch(Main) {
|
2019-10-31 16:17:37 +01:00
|
|
|
RequestBus.get().collect { request ->
|
2022-08-26 14:06:41 +02:00
|
|
|
if (request is Request.GetCurrentTrack) {
|
|
|
|
request.channel?.trySend(Response.CurrentTrack(queue.current()))?.isSuccess
|
|
|
|
} else if (request is Request.GetState) {
|
|
|
|
request.channel?.trySend(Response.State(player.playWhenReady))?.isSuccess
|
|
|
|
} else if (request is Request.GetQueue) {
|
|
|
|
request.channel?.trySend(Response.Queue(queue.get()))?.isSuccess
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
}
|
2020-06-25 01:26:15 +02:00
|
|
|
}
|
2019-08-19 16:50:33 +02:00
|
|
|
|
2020-06-25 01:26:15 +02:00
|
|
|
scope.launch(Main) {
|
2019-08-19 16:50:33 +02:00
|
|
|
while (true) {
|
|
|
|
delay(1000)
|
|
|
|
|
2020-07-09 23:01:35 +02:00
|
|
|
val (current, duration, percent) = getProgress()
|
2019-08-19 16:50:33 +02:00
|
|
|
|
|
|
|
if (player.playWhenReady) {
|
|
|
|
ProgressBus.send(current, duration, percent)
|
|
|
|
}
|
|
|
|
}
|
2020-06-25 01:26:15 +02:00
|
|
|
}
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
|
2019-10-21 11:51:32 +02:00
|
|
|
override fun onBind(intent: Intent?): IBinder? = null
|
2019-08-19 16:50:33 +02:00
|
|
|
|
2020-07-11 12:58:25 +02:00
|
|
|
override fun onTaskRemoved(rootIntent: Intent?) {
|
|
|
|
super.onTaskRemoved(rootIntent)
|
|
|
|
|
|
|
|
if (!player.playWhenReady) {
|
2020-07-11 14:16:22 +02:00
|
|
|
NotificationManagerCompat.from(this).cancelAll()
|
2020-07-11 12:58:25 +02:00
|
|
|
stopSelf()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-19 16:50:33 +02:00
|
|
|
@SuppressLint("NewApi")
|
|
|
|
override fun onDestroy() {
|
2020-07-09 23:01:35 +02:00
|
|
|
scope.cancel()
|
|
|
|
|
2019-08-19 16:50:33 +02:00
|
|
|
try {
|
|
|
|
unregisterReceiver(headphonesUnpluggedReceiver)
|
|
|
|
} catch (_: Exception) {
|
|
|
|
}
|
|
|
|
|
|
|
|
Build.VERSION_CODES.O.onApi(
|
|
|
|
{
|
|
|
|
audioFocusRequest?.let {
|
|
|
|
audioManager.abandonAudioFocusRequest(it)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
@Suppress("DEPRECATION")
|
|
|
|
audioManager.abandonAudioFocus(audioFocusChangeListener)
|
2021-09-09 09:56:15 +02:00
|
|
|
}
|
|
|
|
)
|
2019-08-19 16:50:33 +02:00
|
|
|
|
|
|
|
player.removeListener(playerEventListener)
|
2020-07-09 23:01:35 +02:00
|
|
|
setPlaybackState(false)
|
2019-08-19 16:50:33 +02:00
|
|
|
player.release()
|
|
|
|
|
2021-08-09 06:50:46 +02:00
|
|
|
mediaSession.active = false
|
2020-07-12 18:28:50 +02:00
|
|
|
|
2019-08-19 16:50:33 +02:00
|
|
|
super.onDestroy()
|
|
|
|
}
|
|
|
|
|
2020-07-09 23:01:35 +02:00
|
|
|
private fun setPlaybackState(state: Boolean) {
|
2020-05-29 10:32:09 +02:00
|
|
|
if (!state) {
|
2020-07-09 23:01:35 +02:00
|
|
|
val (progress, _, _) = getProgress()
|
2020-05-29 10:32:09 +02:00
|
|
|
|
2022-06-12 14:48:32 +02:00
|
|
|
FFACache.set(this@PlayerService, "progress", progress.toString())
|
2020-05-29 10:32:09 +02:00
|
|
|
}
|
|
|
|
|
2019-08-19 16:50:33 +02:00
|
|
|
if (state && player.playbackState == Player.STATE_IDLE) {
|
2022-08-27 09:21:03 +02:00
|
|
|
player.setMediaSource(queue.dataSources)
|
|
|
|
player.prepare()
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
|
2020-07-12 20:46:33 +02:00
|
|
|
if (hasAudioFocus(state)) {
|
2019-08-19 16:50:33 +02:00
|
|
|
player.playWhenReady = state
|
|
|
|
|
|
|
|
EventBus.send(Event.StateChanged(state))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-09 23:01:35 +02:00
|
|
|
private fun togglePlayback() {
|
2022-08-27 09:21:03 +02:00
|
|
|
setPlaybackState(!player.isPlaying)
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
|
2020-07-09 23:01:35 +02:00
|
|
|
private fun skipToPreviousTrack() {
|
2019-08-19 16:50:33 +02:00
|
|
|
if (player.currentPosition > 5000) {
|
|
|
|
return player.seekTo(0)
|
|
|
|
}
|
|
|
|
|
2022-08-27 09:21:03 +02:00
|
|
|
player.seekToPrevious()
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
|
2020-07-09 23:01:35 +02:00
|
|
|
private fun skipToNextTrack() {
|
2022-08-27 09:21:03 +02:00
|
|
|
player.seekToNext()
|
2020-07-09 23:01:35 +02:00
|
|
|
|
2022-06-12 14:48:32 +02:00
|
|
|
FFACache.set(this@PlayerService, "progress", "0")
|
2020-07-09 23:01:35 +02:00
|
|
|
ProgressBus.send(0, 0, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun getProgress(force: Boolean = false): Triple<Int, Int, Int> {
|
2020-05-29 10:32:09 +02:00
|
|
|
if (!player.playWhenReady && !force) return progressCache
|
2019-08-19 16:50:33 +02:00
|
|
|
|
|
|
|
return queue.current()?.bestUpload()?.let { upload ->
|
|
|
|
val current = player.currentPosition
|
|
|
|
val duration = upload.duration.toFloat()
|
|
|
|
val percent = ((current / (duration * 1000)) * 100).toInt()
|
|
|
|
|
|
|
|
progressCache = Triple(current.toInt(), duration.toInt(), percent)
|
|
|
|
progressCache
|
|
|
|
} ?: Triple(0, 0, 0)
|
|
|
|
}
|
|
|
|
|
2020-07-09 23:01:35 +02:00
|
|
|
private fun seek(value: Int) {
|
2019-08-19 16:50:33 +02:00
|
|
|
val duration = ((queue.current()?.bestUpload()?.duration ?: 0) * (value.toFloat() / 100)) * 1000
|
|
|
|
|
|
|
|
progressCache = Triple(duration.toInt(), queue.current()?.bestUpload()?.duration ?: 0, value)
|
|
|
|
|
|
|
|
player.seekTo(duration.toLong())
|
|
|
|
}
|
|
|
|
|
2020-07-09 23:01:35 +02:00
|
|
|
private fun buildTrackMetadata(track: Track?): MediaMetadataCompat {
|
|
|
|
track?.let {
|
2020-08-08 14:51:39 +02:00
|
|
|
val coverUrl = maybeNormalizeUrl(track.album?.cover())
|
2020-07-09 23:01:35 +02:00
|
|
|
|
|
|
|
return mediaMetadataBuilder.apply {
|
|
|
|
putString(MediaMetadataCompat.METADATA_KEY_TITLE, track.title)
|
|
|
|
putString(MediaMetadataCompat.METADATA_KEY_ARTIST, track.artist.name)
|
2021-07-02 13:55:49 +02:00
|
|
|
putLong(
|
|
|
|
MediaMetadata.METADATA_KEY_DURATION,
|
|
|
|
(track.bestUpload()?.duration?.toLong() ?: 0L) * 1000
|
|
|
|
)
|
2020-07-09 23:01:35 +02:00
|
|
|
|
|
|
|
try {
|
|
|
|
runBlocking(IO) {
|
2021-07-02 13:55:49 +02:00
|
|
|
this@apply.putBitmap(
|
|
|
|
MediaMetadataCompat.METADATA_KEY_ALBUM_ART,
|
|
|
|
Picasso.get().load(coverUrl).get()
|
|
|
|
)
|
2020-07-09 23:01:35 +02:00
|
|
|
}
|
|
|
|
} catch (e: Exception) {
|
|
|
|
}
|
|
|
|
}.build()
|
|
|
|
}
|
|
|
|
|
|
|
|
return mediaMetadataBuilder.build()
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:46:33 +02:00
|
|
|
@SuppressLint("NewApi")
|
|
|
|
private fun hasAudioFocus(state: Boolean): Boolean {
|
|
|
|
var allowed = !state
|
|
|
|
|
|
|
|
if (!allowed) {
|
|
|
|
Build.VERSION_CODES.O.onApi(
|
|
|
|
{
|
|
|
|
audioFocusRequest?.let {
|
|
|
|
allowed = when (audioManager.requestAudioFocus(it)) {
|
|
|
|
AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> true
|
|
|
|
else -> false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
|
|
|
|
@Suppress("DEPRECATION")
|
2021-07-02 13:55:49 +02:00
|
|
|
audioManager.requestAudioFocus(
|
|
|
|
audioFocusChangeListener,
|
|
|
|
AudioAttributes.CONTENT_TYPE_MUSIC,
|
|
|
|
AudioManager.AUDIOFOCUS_GAIN
|
|
|
|
).let {
|
2020-07-12 20:46:33 +02:00
|
|
|
allowed = when (it) {
|
|
|
|
AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> true
|
|
|
|
else -> false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return allowed
|
|
|
|
}
|
|
|
|
|
2020-07-09 23:01:35 +02:00
|
|
|
@SuppressLint("NewApi")
|
2022-08-27 09:21:03 +02:00
|
|
|
inner class PlayerEventListener : Player.Listener {
|
|
|
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
|
|
|
super.onIsPlayingChanged(isPlaying)
|
|
|
|
mediaControlsManager.updateNotification(queue.current(), isPlaying)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
|
|
|
|
super.onPlayWhenReadyChanged(playWhenReady, reason)
|
2019-08-19 16:50:33 +02:00
|
|
|
|
2020-06-01 16:31:58 +02:00
|
|
|
EventBus.send(Event.StateChanged(playWhenReady))
|
2019-08-19 16:50:33 +02:00
|
|
|
|
|
|
|
if (queue.current == -1) {
|
2020-06-24 14:54:13 +02:00
|
|
|
CommandBus.send(Command.RefreshTrack(queue.current()))
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
|
2022-08-27 09:21:03 +02:00
|
|
|
if (!playWhenReady) {
|
|
|
|
Build.VERSION_CODES.N.onApi(
|
|
|
|
{ stopForeground(STOP_FOREGROUND_DETACH) },
|
|
|
|
{ stopForeground(false) }
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2020-09-02 12:04:42 +02:00
|
|
|
|
2022-08-27 09:21:03 +02:00
|
|
|
override fun onPlaybackStateChanged(playbackState: Int) {
|
|
|
|
super.onPlaybackStateChanged(playbackState)
|
|
|
|
EventBus.send(Event.Buffering(playbackState == Player.STATE_BUFFERING))
|
|
|
|
when (playbackState) {
|
|
|
|
Player.STATE_ENDED -> {
|
|
|
|
setPlaybackState(false)
|
2020-09-02 12:04:42 +02:00
|
|
|
|
2022-08-27 09:21:03 +02:00
|
|
|
queue.current = 0
|
|
|
|
player.seekTo(0, C.TIME_UNSET)
|
2019-08-19 16:50:33 +02:00
|
|
|
|
2022-08-27 09:21:03 +02:00
|
|
|
ProgressBus.send(0, 0, 0)
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
|
2022-08-27 09:21:03 +02:00
|
|
|
Player.STATE_IDLE -> {
|
|
|
|
setPlaybackState(false)
|
2019-08-19 16:50:33 +02:00
|
|
|
|
2022-08-27 09:21:03 +02:00
|
|
|
EventBus.send(Event.PlaybackStopped)
|
2020-07-09 23:01:35 +02:00
|
|
|
|
2022-08-27 09:21:03 +02:00
|
|
|
if (!player.playWhenReady) {
|
|
|
|
mediaControlsManager.remove()
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-27 09:21:03 +02:00
|
|
|
override fun onTracksChanged(tracks: Tracks) {
|
|
|
|
super.onTracksChanged(tracks)
|
2019-08-19 16:50:33 +02:00
|
|
|
|
2022-08-27 09:21:03 +02:00
|
|
|
if (queue.current != player.currentMediaItemIndex) {
|
|
|
|
queue.current = player.currentMediaItemIndex
|
|
|
|
mediaControlsManager.updateNotification(queue.current(), player.isPlaying)
|
2020-07-12 18:28:50 +02:00
|
|
|
}
|
2019-08-19 16:50:33 +02:00
|
|
|
|
2022-08-25 14:58:19 +02:00
|
|
|
if (queue.get().isNotEmpty() &&
|
|
|
|
queue.current() == queue.get().last() && radioPlayer.isActive()
|
2021-07-02 13:55:49 +02:00
|
|
|
) {
|
2020-06-25 01:26:15 +02:00
|
|
|
scope.launch(IO) {
|
2020-05-30 21:16:28 +02:00
|
|
|
if (radioPlayer.lock.tryAcquire()) {
|
|
|
|
radioPlayer.prepareNextTrack()
|
|
|
|
radioPlayer.lock.release()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-12 14:48:32 +02:00
|
|
|
FFACache.set(this@PlayerService, "current", queue.current.toString())
|
2019-08-19 16:50:33 +02:00
|
|
|
|
2020-06-24 14:54:13 +02:00
|
|
|
CommandBus.send(Command.RefreshTrack(queue.current()))
|
2020-06-01 16:31:58 +02:00
|
|
|
}
|
|
|
|
|
2021-08-29 15:41:50 +02:00
|
|
|
override fun onPositionDiscontinuity(
|
|
|
|
oldPosition: Player.PositionInfo,
|
|
|
|
newPosition: Player.PositionInfo,
|
|
|
|
reason: Int
|
|
|
|
) {
|
|
|
|
super.onPositionDiscontinuity(oldPosition, newPosition, reason)
|
|
|
|
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
|
2021-08-13 10:55:26 +02:00
|
|
|
val currentTrack = queue.current().also {
|
|
|
|
it.log("Track finished")
|
|
|
|
}
|
|
|
|
EventBus.send(Event.TrackFinished(currentTrack))
|
2020-06-01 16:31:58 +02:00
|
|
|
}
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
|
2022-08-27 09:21:03 +02:00
|
|
|
override fun onPlayerError(error: PlaybackException) {
|
2020-06-01 16:31:58 +02:00
|
|
|
EventBus.send(Event.PlaybackError(getString(R.string.error_playback)))
|
2019-08-19 16:50:33 +02:00
|
|
|
|
2020-06-26 19:05:11 +02:00
|
|
|
if (player.playWhenReady) {
|
|
|
|
queue.current++
|
2022-08-27 09:21:03 +02:00
|
|
|
player.setMediaSource(queue.dataSources, true)
|
2020-06-26 19:05:11 +02:00
|
|
|
player.seekTo(queue.current, 0)
|
2022-08-27 09:21:03 +02:00
|
|
|
player.prepare()
|
2020-06-20 22:10:13 +02:00
|
|
|
|
2020-06-26 19:05:11 +02:00
|
|
|
CommandBus.send(Command.RefreshTrack(queue.current()))
|
|
|
|
}
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inner class AudioFocusChange : AudioManager.OnAudioFocusChangeListener {
|
|
|
|
override fun onAudioFocusChange(focus: Int) {
|
|
|
|
when (focus) {
|
|
|
|
AudioManager.AUDIOFOCUS_GAIN -> {
|
|
|
|
player.volume = 1f
|
|
|
|
|
2020-07-09 23:01:35 +02:00
|
|
|
setPlaybackState(stateWhenLostFocus)
|
2019-08-19 16:50:33 +02:00
|
|
|
stateWhenLostFocus = false
|
|
|
|
}
|
|
|
|
|
|
|
|
AudioManager.AUDIOFOCUS_LOSS -> {
|
|
|
|
stateWhenLostFocus = false
|
2020-07-09 23:01:35 +02:00
|
|
|
setPlaybackState(false)
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
|
|
|
|
stateWhenLostFocus = player.playWhenReady
|
2020-07-09 23:01:35 +02:00
|
|
|
setPlaybackState(false)
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
|
|
|
|
stateWhenLostFocus = player.playWhenReady
|
|
|
|
player.volume = 0.3f
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-02 13:55:49 +02:00
|
|
|
}
|