2021-04-22 19:47:06 +02:00
|
|
|
/*
|
|
|
|
* MediaPlayerService.kt
|
|
|
|
* Copyright (C) 2009-2021 Ultrasonic developers
|
|
|
|
*
|
|
|
|
* Distributed under terms of the GNU GPLv3 license.
|
|
|
|
*/
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
package org.moire.ultrasonic.service
|
|
|
|
|
|
|
|
import android.app.Notification
|
|
|
|
import android.app.NotificationChannel
|
|
|
|
import android.app.NotificationManager
|
|
|
|
import android.app.PendingIntent
|
|
|
|
import android.app.Service
|
2021-05-01 10:42:25 +02:00
|
|
|
import android.content.ComponentName
|
2021-04-22 19:25:50 +02:00
|
|
|
import android.content.Context
|
|
|
|
import android.content.Intent
|
|
|
|
import android.os.Build
|
|
|
|
import android.os.IBinder
|
|
|
|
import android.support.v4.media.MediaMetadataCompat
|
|
|
|
import android.support.v4.media.session.MediaSessionCompat
|
|
|
|
import android.support.v4.media.session.PlaybackStateCompat
|
|
|
|
import android.view.KeyEvent
|
|
|
|
import androidx.core.app.NotificationCompat
|
|
|
|
import androidx.core.app.NotificationManagerCompat
|
2021-04-22 19:47:06 +02:00
|
|
|
import org.koin.android.ext.android.inject
|
2021-04-22 19:25:50 +02:00
|
|
|
import org.moire.ultrasonic.R
|
|
|
|
import org.moire.ultrasonic.activity.NavigationActivity
|
|
|
|
import org.moire.ultrasonic.domain.MusicDirectory
|
|
|
|
import org.moire.ultrasonic.domain.PlayerState
|
|
|
|
import org.moire.ultrasonic.domain.RepeatMode
|
|
|
|
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X1
|
|
|
|
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X2
|
|
|
|
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X3
|
|
|
|
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X4
|
2021-05-01 10:42:25 +02:00
|
|
|
import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver
|
2021-04-22 19:25:50 +02:00
|
|
|
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
|
|
|
|
import org.moire.ultrasonic.util.Constants
|
|
|
|
import org.moire.ultrasonic.util.FileUtil
|
|
|
|
import org.moire.ultrasonic.util.NowPlayingEventDistributor
|
|
|
|
import org.moire.ultrasonic.util.ShufflePlayBuffer
|
|
|
|
import org.moire.ultrasonic.util.SimpleServiceBinder
|
|
|
|
import org.moire.ultrasonic.util.Util
|
|
|
|
import timber.log.Timber
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2020-06-26 15:18:14 +02:00
|
|
|
/**
|
|
|
|
* Android Foreground Service for playing music
|
|
|
|
* while the rest of the Ultrasonic App is in the background.
|
|
|
|
*/
|
2021-04-22 19:25:50 +02:00
|
|
|
class MediaPlayerService : Service() {
|
|
|
|
private val binder: IBinder = SimpleServiceBinder(this)
|
|
|
|
private val scrobbler = Scrobbler()
|
2021-04-22 20:17:03 +02:00
|
|
|
|
2021-04-22 19:47:06 +02:00
|
|
|
private val jukeboxMediaPlayer by inject<JukeboxMediaPlayer>()
|
|
|
|
private val downloadQueueSerializer by inject<DownloadQueueSerializer>()
|
|
|
|
private val shufflePlayBuffer by inject<ShufflePlayBuffer>()
|
|
|
|
private val downloader by inject<Downloader>()
|
|
|
|
private val localMediaPlayer by inject<LocalMediaPlayer>()
|
|
|
|
private val nowPlayingEventDistributor by inject<NowPlayingEventDistributor>()
|
|
|
|
private val mediaPlayerLifecycleSupport by inject<MediaPlayerLifecycleSupport>()
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
private var mediaSession: MediaSessionCompat? = null
|
|
|
|
private var mediaSessionToken: MediaSessionCompat.Token? = null
|
|
|
|
private var isInForeground = false
|
|
|
|
private var notificationBuilder: NotificationCompat.Builder? = null
|
2021-04-22 19:47:06 +02:00
|
|
|
|
2021-04-22 20:17:03 +02:00
|
|
|
private val repeatMode: RepeatMode
|
2021-05-09 08:35:15 +02:00
|
|
|
get() = Util.getRepeatMode()
|
2021-04-22 19:25:50 +02:00
|
|
|
|
2021-04-22 19:47:06 +02:00
|
|
|
override fun onBind(intent: Intent): IBinder {
|
2021-04-22 19:25:50 +02:00
|
|
|
return binder
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onCreate() {
|
|
|
|
super.onCreate()
|
2021-04-22 19:47:06 +02:00
|
|
|
|
|
|
|
downloader.onCreate()
|
|
|
|
shufflePlayBuffer.onCreate()
|
|
|
|
localMediaPlayer.init()
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
setupOnCurrentPlayingChangedHandler()
|
|
|
|
setupOnPlayerStateChangedHandler()
|
|
|
|
setupOnSongCompletedHandler()
|
2021-04-22 19:47:06 +02:00
|
|
|
|
|
|
|
localMediaPlayer.onPrepared = {
|
|
|
|
downloadQueueSerializer.serializeDownloadQueue(
|
|
|
|
downloader.downloadList,
|
|
|
|
downloader.currentPlayingIndex,
|
|
|
|
playerPosition
|
2021-04-22 19:25:50 +02:00
|
|
|
)
|
|
|
|
null
|
|
|
|
}
|
2021-04-22 19:47:06 +02:00
|
|
|
|
|
|
|
localMediaPlayer.onNextSongRequested = Runnable { setNextPlaying() }
|
2021-04-22 11:47:15 +02:00
|
|
|
|
2020-06-21 09:31:38 +02:00
|
|
|
// Create Notification Channel
|
2021-04-22 19:25:50 +02:00
|
|
|
createNotificationChannel()
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2021-04-22 19:47:06 +02:00
|
|
|
// Update notification early. It is better to show an empty one temporarily
|
|
|
|
// than waiting too long and letting Android kill the app
|
2021-04-22 19:25:50 +02:00
|
|
|
updateNotification(PlayerState.IDLE, null)
|
|
|
|
instance = this
|
|
|
|
Timber.i("MediaPlayerService created")
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
|
|
|
super.onStartCommand(intent, flags, startId)
|
|
|
|
return START_NOT_STICKY
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
override fun onDestroy() {
|
|
|
|
super.onDestroy()
|
|
|
|
instance = null
|
2020-06-26 13:31:31 +02:00
|
|
|
try {
|
2021-04-22 19:47:06 +02:00
|
|
|
localMediaPlayer.release()
|
|
|
|
downloader.stop()
|
|
|
|
shufflePlayBuffer.onDestroy()
|
2021-04-23 11:52:08 +02:00
|
|
|
mediaSession?.release()
|
2021-05-06 12:53:25 +02:00
|
|
|
mediaSession = null
|
2021-04-22 19:25:50 +02:00
|
|
|
} catch (ignored: Throwable) {
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2021-04-22 19:25:50 +02:00
|
|
|
Timber.i("MediaPlayerService stopped")
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
private fun stopIfIdle() {
|
|
|
|
synchronized(instanceLock) {
|
2021-04-22 19:47:06 +02:00
|
|
|
// currentPlaying could be changed from another thread in the meantime,
|
|
|
|
// so check again before stopping for good
|
|
|
|
if (localMediaPlayer.currentPlaying == null ||
|
|
|
|
localMediaPlayer.playerState === PlayerState.STOPPED
|
|
|
|
) {
|
|
|
|
stopSelf()
|
|
|
|
}
|
2020-06-26 13:31:31 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
@Synchronized
|
|
|
|
fun seekTo(position: Int) {
|
2021-04-22 19:47:06 +02:00
|
|
|
if (jukeboxMediaPlayer.isEnabled) {
|
|
|
|
// TODO These APIs should be more aligned
|
|
|
|
val seconds = position / 1000
|
|
|
|
jukeboxMediaPlayer.skip(downloader.currentPlayingIndex, seconds)
|
2021-04-22 19:25:50 +02:00
|
|
|
} else {
|
2021-04-22 19:47:06 +02:00
|
|
|
localMediaPlayer.seekTo(position)
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
@get:Synchronized
|
|
|
|
val playerPosition: Int
|
|
|
|
get() {
|
2021-04-22 19:47:06 +02:00
|
|
|
if (localMediaPlayer.playerState === PlayerState.IDLE ||
|
|
|
|
localMediaPlayer.playerState === PlayerState.DOWNLOADING ||
|
|
|
|
localMediaPlayer.playerState === PlayerState.PREPARING
|
|
|
|
) {
|
2021-04-22 19:25:50 +02:00
|
|
|
return 0
|
|
|
|
}
|
2021-04-22 19:47:06 +02:00
|
|
|
return if (jukeboxMediaPlayer.isEnabled) {
|
|
|
|
jukeboxMediaPlayer.positionSeconds * 1000
|
|
|
|
} else {
|
|
|
|
localMediaPlayer.playerPosition
|
|
|
|
}
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
@get:Synchronized
|
|
|
|
val playerDuration: Int
|
2021-04-22 19:47:06 +02:00
|
|
|
get() = localMediaPlayer.playerDuration
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
@Synchronized
|
|
|
|
fun setCurrentPlaying(currentPlayingIndex: Int) {
|
|
|
|
try {
|
2021-04-22 19:47:06 +02:00
|
|
|
localMediaPlayer.setCurrentPlaying(downloader.downloadList[currentPlayingIndex])
|
2021-04-22 19:25:50 +02:00
|
|
|
} catch (x: IndexOutOfBoundsException) {
|
2020-06-21 09:31:38 +02:00
|
|
|
// Ignored
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
@Synchronized
|
|
|
|
fun setNextPlaying() {
|
2021-05-09 08:35:15 +02:00
|
|
|
val gaplessPlayback = Util.getGaplessPlaybackPreference()
|
2021-04-22 19:47:06 +02:00
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
if (!gaplessPlayback) {
|
2021-04-22 19:47:06 +02:00
|
|
|
localMediaPlayer.clearNextPlaying(true)
|
2021-04-22 19:25:50 +02:00
|
|
|
return
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2021-04-22 19:47:06 +02:00
|
|
|
|
|
|
|
var index = downloader.currentPlayingIndex
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
if (index != -1) {
|
|
|
|
when (repeatMode) {
|
|
|
|
RepeatMode.OFF -> index += 1
|
2021-04-22 19:47:06 +02:00
|
|
|
RepeatMode.ALL -> index = (index + 1) % downloader.downloadList.size
|
2021-04-22 19:25:50 +02:00
|
|
|
RepeatMode.SINGLE -> {
|
|
|
|
}
|
|
|
|
else -> {
|
|
|
|
}
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
}
|
2021-04-22 19:47:06 +02:00
|
|
|
|
|
|
|
localMediaPlayer.clearNextPlaying(false)
|
|
|
|
if (index < downloader.downloadList.size && index != -1) {
|
|
|
|
localMediaPlayer.setNextPlaying(downloader.downloadList[index])
|
2021-04-22 19:25:50 +02:00
|
|
|
} else {
|
2021-04-22 19:47:06 +02:00
|
|
|
localMediaPlayer.clearNextPlaying(true)
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
@Synchronized
|
|
|
|
fun togglePlayPause() {
|
2021-04-22 19:47:06 +02:00
|
|
|
if (localMediaPlayer.playerState === PlayerState.PAUSED ||
|
|
|
|
localMediaPlayer.playerState === PlayerState.COMPLETED ||
|
|
|
|
localMediaPlayer.playerState === PlayerState.STOPPED
|
|
|
|
) {
|
2021-04-22 19:25:50 +02:00
|
|
|
start()
|
2021-04-22 19:47:06 +02:00
|
|
|
} else if (localMediaPlayer.playerState === PlayerState.IDLE) {
|
2021-04-22 19:25:50 +02:00
|
|
|
play()
|
2021-04-22 19:47:06 +02:00
|
|
|
} else if (localMediaPlayer.playerState === PlayerState.STARTED) {
|
2021-04-22 19:25:50 +02:00
|
|
|
pause()
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
@Synchronized
|
|
|
|
fun resumeOrPlay() {
|
2021-04-22 19:47:06 +02:00
|
|
|
if (localMediaPlayer.playerState === PlayerState.PAUSED ||
|
|
|
|
localMediaPlayer.playerState === PlayerState.COMPLETED ||
|
|
|
|
localMediaPlayer.playerState === PlayerState.STOPPED
|
|
|
|
) {
|
2021-04-22 19:25:50 +02:00
|
|
|
start()
|
2021-04-22 19:47:06 +02:00
|
|
|
} else if (localMediaPlayer.playerState === PlayerState.IDLE) {
|
2021-04-22 19:25:50 +02:00
|
|
|
play()
|
2020-09-29 11:09:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-21 09:31:38 +02:00
|
|
|
/**
|
|
|
|
* Plays either the current song (resume) or the first/next one in queue.
|
|
|
|
*/
|
2021-04-22 19:25:50 +02:00
|
|
|
@Synchronized
|
|
|
|
fun play() {
|
2021-04-22 19:47:06 +02:00
|
|
|
val current = downloader.currentPlayingIndex
|
2021-04-22 19:25:50 +02:00
|
|
|
if (current == -1) {
|
|
|
|
play(0)
|
|
|
|
} else {
|
|
|
|
play(current)
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
@Synchronized
|
|
|
|
fun play(index: Int) {
|
|
|
|
play(index, true)
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
@Synchronized
|
|
|
|
fun play(index: Int, start: Boolean) {
|
|
|
|
Timber.v("play requested for %d", index)
|
2021-04-22 19:47:06 +02:00
|
|
|
if (index < 0 || index >= downloader.downloadList.size) {
|
2021-04-22 19:25:50 +02:00
|
|
|
resetPlayback()
|
|
|
|
} else {
|
|
|
|
setCurrentPlaying(index)
|
|
|
|
if (start) {
|
2021-04-22 19:47:06 +02:00
|
|
|
if (jukeboxMediaPlayer.isEnabled) {
|
|
|
|
jukeboxMediaPlayer.skip(index, 0)
|
|
|
|
localMediaPlayer.setPlayerState(PlayerState.STARTED)
|
2021-04-22 19:25:50 +02:00
|
|
|
} else {
|
2021-04-22 19:47:06 +02:00
|
|
|
localMediaPlayer.play(downloader.downloadList[index])
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
}
|
2021-04-22 19:47:06 +02:00
|
|
|
downloader.checkDownloads()
|
2021-04-22 19:25:50 +02:00
|
|
|
setNextPlaying()
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
@Synchronized
|
|
|
|
private fun resetPlayback() {
|
2021-04-22 19:47:06 +02:00
|
|
|
localMediaPlayer.reset()
|
|
|
|
localMediaPlayer.setCurrentPlaying(null)
|
|
|
|
downloadQueueSerializer.serializeDownloadQueue(
|
|
|
|
downloader.downloadList,
|
|
|
|
downloader.currentPlayingIndex, playerPosition
|
|
|
|
)
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
@Synchronized
|
|
|
|
fun pause() {
|
2021-04-22 19:47:06 +02:00
|
|
|
if (localMediaPlayer.playerState === PlayerState.STARTED) {
|
|
|
|
if (jukeboxMediaPlayer.isEnabled) {
|
|
|
|
jukeboxMediaPlayer.stop()
|
2021-04-22 19:25:50 +02:00
|
|
|
} else {
|
2021-04-22 19:47:06 +02:00
|
|
|
localMediaPlayer.pause()
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2021-04-22 19:47:06 +02:00
|
|
|
localMediaPlayer.setPlayerState(PlayerState.PAUSED)
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
@Synchronized
|
|
|
|
fun stop() {
|
2021-04-22 19:47:06 +02:00
|
|
|
if (localMediaPlayer.playerState === PlayerState.STARTED) {
|
|
|
|
if (jukeboxMediaPlayer.isEnabled) {
|
|
|
|
jukeboxMediaPlayer.stop()
|
2021-04-22 19:25:50 +02:00
|
|
|
} else {
|
2021-04-22 19:47:06 +02:00
|
|
|
localMediaPlayer.pause()
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2021-04-22 19:47:06 +02:00
|
|
|
localMediaPlayer.setPlayerState(PlayerState.STOPPED)
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
@Synchronized
|
|
|
|
fun start() {
|
2021-04-22 19:47:06 +02:00
|
|
|
if (jukeboxMediaPlayer.isEnabled) {
|
|
|
|
jukeboxMediaPlayer.start()
|
2021-04-22 19:25:50 +02:00
|
|
|
} else {
|
2021-04-22 19:47:06 +02:00
|
|
|
localMediaPlayer.start()
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2021-04-22 19:47:06 +02:00
|
|
|
localMediaPlayer.setPlayerState(PlayerState.STARTED)
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:47:06 +02:00
|
|
|
private fun updateWidget(playerState: PlayerState, song: MusicDirectory.Entry?) {
|
|
|
|
val started = playerState === PlayerState.STARTED
|
|
|
|
val context = this@MediaPlayerService
|
|
|
|
|
|
|
|
UltrasonicAppWidgetProvider4X1.getInstance().notifyChange(context, song, started, false)
|
|
|
|
UltrasonicAppWidgetProvider4X2.getInstance().notifyChange(context, song, started, true)
|
|
|
|
UltrasonicAppWidgetProvider4X3.getInstance().notifyChange(context, song, started, false)
|
|
|
|
UltrasonicAppWidgetProvider4X4.getInstance().notifyChange(context, song, started, false)
|
2021-04-22 11:47:15 +02:00
|
|
|
}
|
2021-04-12 07:27:55 +02:00
|
|
|
|
2021-04-22 19:47:06 +02:00
|
|
|
private fun setupOnCurrentPlayingChangedHandler() {
|
|
|
|
localMediaPlayer.onCurrentPlayingChanged = { currentPlaying: DownloadFile? ->
|
|
|
|
|
|
|
|
if (currentPlaying != null) {
|
|
|
|
Util.broadcastNewTrackInfo(this@MediaPlayerService, currentPlaying.song)
|
|
|
|
Util.broadcastA2dpMetaDataChange(
|
|
|
|
this@MediaPlayerService, playerPosition, currentPlaying,
|
|
|
|
downloader.downloads.size, downloader.currentPlayingIndex + 1
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
Util.broadcastNewTrackInfo(this@MediaPlayerService, null)
|
|
|
|
Util.broadcastA2dpMetaDataChange(
|
|
|
|
this@MediaPlayerService, playerPosition, null,
|
|
|
|
downloader.downloads.size, downloader.currentPlayingIndex + 1
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update widget
|
|
|
|
val playerState = localMediaPlayer.playerState
|
|
|
|
val song = currentPlaying?.song
|
|
|
|
|
|
|
|
updateWidget(playerState, song)
|
|
|
|
|
|
|
|
if (currentPlaying != null) {
|
|
|
|
updateNotification(localMediaPlayer.playerState, currentPlaying)
|
|
|
|
nowPlayingEventDistributor.raiseShowNowPlayingEvent()
|
|
|
|
} else {
|
|
|
|
nowPlayingEventDistributor.raiseHideNowPlayingEvent()
|
|
|
|
stopForeground(true)
|
|
|
|
isInForeground = false
|
|
|
|
stopIfIdle()
|
|
|
|
}
|
|
|
|
null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun setupOnPlayerStateChangedHandler() {
|
|
|
|
localMediaPlayer.onPlayerStateChanged = {
|
|
|
|
playerState: PlayerState,
|
|
|
|
currentPlaying: DownloadFile?
|
|
|
|
->
|
|
|
|
|
|
|
|
val context = this@MediaPlayerService
|
|
|
|
|
2021-04-22 11:47:15 +02:00
|
|
|
// Notify MediaSession
|
2021-04-22 19:25:50 +02:00
|
|
|
updateMediaSession(currentPlaying, playerState)
|
2021-04-22 20:58:15 +02:00
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
if (playerState === PlayerState.PAUSED) {
|
2021-04-22 19:47:06 +02:00
|
|
|
downloadQueueSerializer.serializeDownloadQueue(
|
|
|
|
downloader.downloadList, downloader.currentPlayingIndex, playerPosition
|
|
|
|
)
|
2021-04-22 11:47:15 +02:00
|
|
|
}
|
2021-04-22 19:47:06 +02:00
|
|
|
|
|
|
|
val showWhenPaused = playerState !== PlayerState.STOPPED &&
|
2021-05-09 08:35:15 +02:00
|
|
|
Util.isNotificationAlwaysEnabled()
|
2021-04-22 19:47:06 +02:00
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
val show = playerState === PlayerState.STARTED || showWhenPaused
|
|
|
|
val song = currentPlaying?.song
|
2021-04-22 19:47:06 +02:00
|
|
|
|
|
|
|
Util.broadcastPlaybackStatusChange(context, playerState)
|
|
|
|
Util.broadcastA2dpPlayStatusChange(
|
|
|
|
context, playerState, song,
|
|
|
|
downloader.downloadList.size + downloader.backgroundDownloadList.size,
|
|
|
|
downloader.downloadList.indexOf(currentPlaying) + 1, playerPosition
|
|
|
|
)
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2021-04-22 11:47:15 +02:00
|
|
|
// Update widget
|
2021-04-22 19:47:06 +02:00
|
|
|
updateWidget(playerState, song)
|
2021-04-22 20:58:15 +02:00
|
|
|
|
2021-04-22 11:47:15 +02:00
|
|
|
if (show) {
|
|
|
|
// Only update notification if player state is one that will change the icon
|
2021-04-22 19:25:50 +02:00
|
|
|
if (playerState === PlayerState.STARTED || playerState === PlayerState.PAUSED) {
|
|
|
|
updateNotification(playerState, currentPlaying)
|
2021-04-22 19:47:06 +02:00
|
|
|
nowPlayingEventDistributor.raiseShowNowPlayingEvent()
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2021-04-22 11:47:15 +02:00
|
|
|
} else {
|
2021-04-22 19:47:06 +02:00
|
|
|
nowPlayingEventDistributor.raiseHideNowPlayingEvent()
|
2021-04-22 19:25:50 +02:00
|
|
|
stopForeground(true)
|
|
|
|
isInForeground = false
|
|
|
|
stopIfIdle()
|
2021-04-22 11:47:15 +02:00
|
|
|
}
|
2021-04-22 20:58:15 +02:00
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
if (playerState === PlayerState.STARTED) {
|
2021-04-22 19:47:06 +02:00
|
|
|
scrobbler.scrobble(context, currentPlaying, false)
|
2021-04-22 19:25:50 +02:00
|
|
|
} else if (playerState === PlayerState.COMPLETED) {
|
2021-04-22 19:47:06 +02:00
|
|
|
scrobbler.scrobble(context, currentPlaying, true)
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2021-04-22 20:58:15 +02:00
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
null
|
|
|
|
}
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
private fun setupOnSongCompletedHandler() {
|
2021-04-22 19:47:06 +02:00
|
|
|
localMediaPlayer.onSongCompleted = { currentPlaying: DownloadFile? ->
|
|
|
|
val index = downloader.currentPlayingIndex
|
|
|
|
|
2021-04-22 11:47:15 +02:00
|
|
|
if (currentPlaying != null) {
|
2021-04-22 19:25:50 +02:00
|
|
|
val song = currentPlaying.song
|
2021-05-09 08:35:15 +02:00
|
|
|
if (song.bookmarkPosition > 0 && Util.getShouldClearBookmark()) {
|
2021-05-09 10:38:03 +02:00
|
|
|
val musicService = getMusicService()
|
2021-04-22 11:47:15 +02:00
|
|
|
try {
|
2021-05-11 12:57:29 +02:00
|
|
|
musicService.deleteBookmark(song.id)
|
2021-04-22 19:25:50 +02:00
|
|
|
} catch (ignored: Exception) {
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2021-04-22 11:47:15 +02:00
|
|
|
}
|
|
|
|
if (index != -1) {
|
2021-04-22 19:25:50 +02:00
|
|
|
when (repeatMode) {
|
|
|
|
RepeatMode.OFF -> {
|
2021-04-22 19:47:06 +02:00
|
|
|
if (index + 1 < 0 || index + 1 >= downloader.downloadList.size) {
|
2021-05-09 08:35:15 +02:00
|
|
|
if (Util.getShouldClearPlaylist()) {
|
2021-04-22 19:25:50 +02:00
|
|
|
clear(true)
|
2021-04-22 19:47:06 +02:00
|
|
|
jukeboxMediaPlayer.updatePlaylist()
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2021-04-22 19:25:50 +02:00
|
|
|
resetPlayback()
|
2021-04-22 19:47:06 +02:00
|
|
|
} else {
|
|
|
|
play(index + 1)
|
2021-04-22 11:47:15 +02:00
|
|
|
}
|
2021-04-22 19:25:50 +02:00
|
|
|
}
|
2021-04-22 19:47:06 +02:00
|
|
|
RepeatMode.ALL -> {
|
|
|
|
play((index + 1) % downloader.downloadList.size)
|
|
|
|
}
|
2021-04-22 19:25:50 +02:00
|
|
|
RepeatMode.SINGLE -> play(index)
|
|
|
|
else -> {
|
|
|
|
}
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
}
|
2021-04-22 19:25:50 +02:00
|
|
|
null
|
|
|
|
}
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
@Synchronized
|
|
|
|
fun clear(serialize: Boolean) {
|
2021-04-22 19:47:06 +02:00
|
|
|
localMediaPlayer.reset()
|
|
|
|
downloader.clear()
|
|
|
|
localMediaPlayer.setCurrentPlaying(null)
|
2021-04-22 19:25:50 +02:00
|
|
|
setNextPlaying()
|
2020-06-23 18:40:44 +02:00
|
|
|
if (serialize) {
|
2021-04-22 19:47:06 +02:00
|
|
|
downloadQueueSerializer.serializeDownloadQueue(
|
|
|
|
downloader.downloadList,
|
|
|
|
downloader.currentPlayingIndex, playerPosition
|
|
|
|
)
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
private fun updateMediaSession(currentPlaying: DownloadFile?, playerState: PlayerState) {
|
2021-04-25 12:24:35 +02:00
|
|
|
Timber.d("Updating the MediaSession")
|
2021-04-23 11:52:08 +02:00
|
|
|
|
|
|
|
if (mediaSession == null) initMediaSessions()
|
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
// Set Metadata
|
2021-04-22 19:25:50 +02:00
|
|
|
val metadata = MediaMetadataCompat.Builder()
|
|
|
|
val context = applicationContext
|
2021-04-12 07:27:55 +02:00
|
|
|
if (currentPlaying != null) {
|
|
|
|
try {
|
2021-04-22 19:25:50 +02:00
|
|
|
val song = currentPlaying.song
|
2021-04-22 19:47:06 +02:00
|
|
|
val cover = FileUtil.getAlbumArtBitmap(
|
2021-05-11 12:57:29 +02:00
|
|
|
song, Util.getMinDisplayMetric(context),
|
|
|
|
true
|
2021-04-22 19:25:50 +02:00
|
|
|
)
|
|
|
|
metadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, -1L)
|
|
|
|
metadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.artist)
|
|
|
|
metadata.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, song.artist)
|
|
|
|
metadata.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.album)
|
|
|
|
metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.title)
|
|
|
|
metadata.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, cover)
|
|
|
|
} catch (e: Exception) {
|
|
|
|
Timber.e(e, "Error setting the metadata")
|
2021-04-12 07:27:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save the metadata
|
2021-04-22 19:25:50 +02:00
|
|
|
mediaSession!!.setMetadata(metadata.build())
|
2021-04-12 07:27:55 +02:00
|
|
|
|
|
|
|
// Create playback State
|
2021-04-22 19:25:50 +02:00
|
|
|
val playbackState = PlaybackStateCompat.Builder()
|
2021-04-22 20:58:15 +02:00
|
|
|
val state: Int
|
2021-05-06 13:00:44 +02:00
|
|
|
val isActive: Boolean
|
2021-04-22 20:58:15 +02:00
|
|
|
|
2021-05-06 12:53:43 +02:00
|
|
|
var actions: Long = PlaybackStateCompat.ACTION_PLAY_PAUSE or
|
|
|
|
PlaybackStateCompat.ACTION_SKIP_TO_NEXT or
|
|
|
|
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
|
2021-04-22 20:58:15 +02:00
|
|
|
|
|
|
|
// Map our playerState to native PlaybackState
|
|
|
|
// TODO: Synchronize these APIs
|
|
|
|
when (playerState) {
|
|
|
|
PlayerState.STARTED -> {
|
|
|
|
state = PlaybackStateCompat.STATE_PLAYING
|
2021-05-06 13:00:44 +02:00
|
|
|
isActive = true
|
2021-04-23 11:52:08 +02:00
|
|
|
actions = actions or
|
|
|
|
PlaybackStateCompat.ACTION_PAUSE or
|
|
|
|
PlaybackStateCompat.ACTION_STOP
|
2021-04-22 20:58:15 +02:00
|
|
|
}
|
|
|
|
PlayerState.COMPLETED,
|
|
|
|
PlayerState.STOPPED -> {
|
2021-05-06 13:00:44 +02:00
|
|
|
isActive = false
|
2021-04-22 20:58:15 +02:00
|
|
|
state = PlaybackStateCompat.STATE_STOPPED
|
|
|
|
}
|
|
|
|
PlayerState.IDLE -> {
|
2021-05-06 13:00:44 +02:00
|
|
|
isActive = false
|
2021-04-22 20:58:15 +02:00
|
|
|
state = PlaybackStateCompat.STATE_NONE
|
|
|
|
actions = 0L
|
|
|
|
}
|
|
|
|
PlayerState.PAUSED -> {
|
2021-05-06 13:00:44 +02:00
|
|
|
isActive = true
|
2021-04-22 20:58:15 +02:00
|
|
|
state = PlaybackStateCompat.STATE_PAUSED
|
2021-04-23 11:52:08 +02:00
|
|
|
actions = actions or
|
|
|
|
PlaybackStateCompat.ACTION_PLAY or
|
|
|
|
PlaybackStateCompat.ACTION_STOP
|
2021-04-22 20:58:15 +02:00
|
|
|
}
|
2021-05-06 13:00:44 +02:00
|
|
|
else -> {
|
|
|
|
// These are the states PREPARING, PREPARED & DOWNLOADING
|
|
|
|
isActive = true
|
|
|
|
state = PlaybackStateCompat.STATE_PAUSED
|
|
|
|
}
|
2021-04-22 19:47:06 +02:00
|
|
|
}
|
2021-04-12 07:27:55 +02:00
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
playbackState.setState(state, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f)
|
2021-04-12 07:27:55 +02:00
|
|
|
|
2021-04-22 20:58:15 +02:00
|
|
|
// Set actions
|
|
|
|
playbackState.setActions(actions)
|
2021-04-22 11:47:35 +02:00
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
// Save the playback state
|
2021-04-22 19:25:50 +02:00
|
|
|
mediaSession!!.setPlaybackState(playbackState.build())
|
2021-04-22 20:58:15 +02:00
|
|
|
|
|
|
|
// Set Active state
|
2021-05-06 13:00:44 +02:00
|
|
|
mediaSession!!.isActive = isActive
|
2021-04-22 20:58:15 +02:00
|
|
|
|
2021-05-06 13:00:44 +02:00
|
|
|
Timber.d("Setting the MediaSession to active = %s", isActive)
|
2021-04-12 07:27:55 +02:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
private fun createNotificationChannel() {
|
2021-04-21 17:40:51 +02:00
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
2021-04-22 19:47:06 +02:00
|
|
|
|
|
|
|
// The suggested importance of a startForeground service notification is IMPORTANCE_LOW
|
|
|
|
val channel = NotificationChannel(
|
|
|
|
NOTIFICATION_CHANNEL_ID,
|
|
|
|
NOTIFICATION_CHANNEL_NAME,
|
|
|
|
NotificationManager.IMPORTANCE_LOW
|
|
|
|
)
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
channel.lightColor = android.R.color.holo_blue_dark
|
|
|
|
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
|
|
|
channel.setShowBadge(false)
|
2021-04-22 19:47:06 +02:00
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
|
|
|
manager.createNotificationChannel(channel)
|
2021-04-21 17:40:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
fun updateNotification(playerState: PlayerState, currentPlaying: DownloadFile?) {
|
2021-04-22 19:47:06 +02:00
|
|
|
val notification = buildForegroundNotification(playerState, currentPlaying)
|
|
|
|
|
2021-05-09 08:35:15 +02:00
|
|
|
if (Util.isNotificationEnabled()) {
|
2020-06-25 14:33:44 +02:00
|
|
|
if (isInForeground) {
|
2020-06-21 09:31:38 +02:00
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
2021-04-22 19:47:06 +02:00
|
|
|
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
|
|
|
manager.notify(NOTIFICATION_ID, notification)
|
2021-04-12 07:27:55 +02:00
|
|
|
} else {
|
2021-04-22 19:47:06 +02:00
|
|
|
val manager = NotificationManagerCompat.from(this)
|
|
|
|
manager.notify(NOTIFICATION_ID, notification)
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2021-04-22 19:47:06 +02:00
|
|
|
Timber.v("Updated notification")
|
2021-04-12 07:27:55 +02:00
|
|
|
} else {
|
2021-04-22 19:47:06 +02:00
|
|
|
startForeground(NOTIFICATION_ID, notification)
|
2021-04-22 19:25:50 +02:00
|
|
|
isInForeground = true
|
2021-04-25 12:24:35 +02:00
|
|
|
Timber.v("Created Foreground notification")
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
/**
|
|
|
|
* This method builds a notification, reusing the Notification Builder if possible
|
|
|
|
*/
|
2021-04-22 19:47:06 +02:00
|
|
|
private fun buildForegroundNotification(
|
|
|
|
playerState: PlayerState,
|
|
|
|
currentPlaying: DownloadFile?
|
|
|
|
): Notification {
|
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
// Init
|
2021-04-22 19:25:50 +02:00
|
|
|
val context = applicationContext
|
|
|
|
val song = currentPlaying?.song
|
|
|
|
val stopIntent = getPendingIntentForMediaAction(context, KeyEvent.KEYCODE_MEDIA_STOP, 100)
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
// We should use a single notification builder, otherwise the notification may not be updated
|
|
|
|
if (notificationBuilder == null) {
|
2021-04-22 19:25:50 +02:00
|
|
|
notificationBuilder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
// Set some values that never change
|
2021-04-22 19:25:50 +02:00
|
|
|
notificationBuilder!!.setSmallIcon(R.drawable.ic_stat_ultrasonic)
|
|
|
|
notificationBuilder!!.setAutoCancel(false)
|
|
|
|
notificationBuilder!!.setOngoing(true)
|
|
|
|
notificationBuilder!!.setOnlyAlertOnce(true)
|
|
|
|
notificationBuilder!!.setWhen(System.currentTimeMillis())
|
|
|
|
notificationBuilder!!.setShowWhen(false)
|
|
|
|
notificationBuilder!!.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
|
|
notificationBuilder!!.priority = NotificationCompat.PRIORITY_LOW
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
// Add content intent (when user taps on notification)
|
2021-04-22 19:47:06 +02:00
|
|
|
notificationBuilder!!.setContentIntent(getPendingIntentForContent())
|
2021-04-21 17:14:05 +02:00
|
|
|
|
|
|
|
// This intent is executed when the user closes the notification
|
2021-04-22 19:25:50 +02:00
|
|
|
notificationBuilder!!.setDeleteIntent(stopIntent)
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
// Use the Media Style, to enable native Android support for playback notification
|
2021-04-22 19:25:50 +02:00
|
|
|
val style = androidx.media.app.NotificationCompat.MediaStyle()
|
2021-04-23 11:52:08 +02:00
|
|
|
|
|
|
|
if (mediaSessionToken != null) {
|
|
|
|
style.setMediaSession(mediaSessionToken)
|
|
|
|
}
|
2020-06-21 09:31:38 +02:00
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
// Clear old actions
|
2021-04-22 19:25:50 +02:00
|
|
|
notificationBuilder!!.clearActions()
|
2021-04-12 07:27:55 +02:00
|
|
|
|
|
|
|
// Add actions
|
2021-04-22 19:25:50 +02:00
|
|
|
val compactActions = addActions(context, notificationBuilder!!, playerState, song)
|
2021-04-21 16:41:08 +02:00
|
|
|
|
|
|
|
// Configure shortcut actions
|
2021-04-22 19:25:50 +02:00
|
|
|
style.setShowActionsInCompactView(*compactActions)
|
|
|
|
notificationBuilder!!.setStyle(style)
|
2021-04-12 07:27:55 +02:00
|
|
|
|
2021-04-21 16:41:08 +02:00
|
|
|
// Set song title, artist and cover if possible
|
|
|
|
if (song != null) {
|
2021-04-22 19:25:50 +02:00
|
|
|
val iconSize = (256 * context.resources.displayMetrics.density).toInt()
|
2021-05-11 12:57:29 +02:00
|
|
|
val bitmap = FileUtil.getAlbumArtBitmap(song, iconSize, true)
|
2021-04-22 19:25:50 +02:00
|
|
|
notificationBuilder!!.setContentTitle(song.title)
|
|
|
|
notificationBuilder!!.setContentText(song.artist)
|
|
|
|
notificationBuilder!!.setLargeIcon(bitmap)
|
|
|
|
notificationBuilder!!.setSubText(song.album)
|
2021-04-21 16:41:08 +02:00
|
|
|
}
|
2021-04-22 19:25:50 +02:00
|
|
|
return notificationBuilder!!.build()
|
2021-04-12 07:27:55 +02:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:47:06 +02:00
|
|
|
private fun addActions(
|
|
|
|
context: Context,
|
|
|
|
notificationBuilder: NotificationCompat.Builder,
|
|
|
|
playerState: PlayerState,
|
|
|
|
song: MusicDirectory.Entry?
|
|
|
|
): IntArray {
|
|
|
|
// Init
|
2021-04-22 19:25:50 +02:00
|
|
|
val compactActionList = ArrayList<Int>()
|
|
|
|
var numActions = 0 // we start and 0 and then increment by 1 for each call to generateAction
|
2021-04-12 07:27:55 +02:00
|
|
|
|
2021-04-21 16:41:08 +02:00
|
|
|
// Star
|
|
|
|
if (song != null) {
|
2021-04-22 19:47:06 +02:00
|
|
|
notificationBuilder.addAction(generateStarAction(context, numActions, song.starred))
|
2021-04-21 16:41:08 +02:00
|
|
|
}
|
2021-04-22 19:25:50 +02:00
|
|
|
numActions++
|
2021-04-21 16:41:08 +02:00
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
// Next
|
2021-04-22 19:25:50 +02:00
|
|
|
notificationBuilder.addAction(generateAction(context, numActions))
|
|
|
|
compactActionList.add(numActions)
|
|
|
|
numActions++
|
2021-04-12 07:27:55 +02:00
|
|
|
|
|
|
|
// Play/Pause button
|
2021-04-22 19:25:50 +02:00
|
|
|
notificationBuilder.addAction(generatePlayPauseAction(context, numActions, playerState))
|
|
|
|
compactActionList.add(numActions)
|
|
|
|
numActions++
|
2021-04-12 07:27:55 +02:00
|
|
|
|
|
|
|
// Previous
|
2021-04-22 19:25:50 +02:00
|
|
|
notificationBuilder.addAction(generateAction(context, numActions))
|
|
|
|
compactActionList.add(numActions)
|
|
|
|
numActions++
|
2021-04-21 16:41:08 +02:00
|
|
|
|
|
|
|
// Close
|
2021-04-22 19:25:50 +02:00
|
|
|
notificationBuilder.addAction(generateAction(context, numActions))
|
|
|
|
val actionArray = IntArray(compactActionList.size)
|
|
|
|
for (i in actionArray.indices) {
|
|
|
|
actionArray[i] = compactActionList[i]
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2021-04-22 19:25:50 +02:00
|
|
|
return actionArray
|
2021-04-22 19:47:06 +02:00
|
|
|
// notificationBuilder.setShowActionsInCompactView())
|
2021-04-12 07:27:55 +02:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
private fun generateAction(context: Context, requestCode: Int): NotificationCompat.Action? {
|
|
|
|
val keycode: Int
|
|
|
|
val icon: Int
|
|
|
|
val label: String
|
2021-04-22 19:47:06 +02:00
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
when (requestCode) {
|
|
|
|
1 -> {
|
|
|
|
keycode = KeyEvent.KEYCODE_MEDIA_PREVIOUS
|
|
|
|
label = getString(R.string.common_play_previous)
|
|
|
|
icon = R.drawable.media_backward_medium_dark
|
|
|
|
}
|
2021-04-22 19:47:06 +02:00
|
|
|
2 -> // Is handled in generatePlayPauseAction()
|
2021-04-22 19:25:50 +02:00
|
|
|
return null
|
|
|
|
3 -> {
|
|
|
|
keycode = KeyEvent.KEYCODE_MEDIA_NEXT
|
|
|
|
label = getString(R.string.common_play_next)
|
|
|
|
icon = R.drawable.media_forward_medium_dark
|
|
|
|
}
|
|
|
|
4 -> {
|
|
|
|
keycode = KeyEvent.KEYCODE_MEDIA_STOP
|
|
|
|
label = getString(R.string.buttons_stop)
|
|
|
|
icon = R.drawable.ic_baseline_close_24
|
|
|
|
}
|
|
|
|
else -> return null
|
2021-04-12 07:27:55 +02:00
|
|
|
}
|
2021-04-22 19:47:06 +02:00
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
val pendingIntent = getPendingIntentForMediaAction(context, keycode, requestCode)
|
|
|
|
return NotificationCompat.Action.Builder(icon, label, pendingIntent).build()
|
2021-04-12 07:27:55 +02:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:47:06 +02:00
|
|
|
private fun generatePlayPauseAction(
|
|
|
|
context: Context,
|
|
|
|
requestCode: Int,
|
|
|
|
playerState: PlayerState
|
|
|
|
): NotificationCompat.Action {
|
2021-04-22 19:25:50 +02:00
|
|
|
val isPlaying = playerState === PlayerState.STARTED
|
2021-04-22 19:47:06 +02:00
|
|
|
val keycode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
|
|
|
|
val pendingIntent = getPendingIntentForMediaAction(context, keycode, requestCode)
|
2021-04-22 19:25:50 +02:00
|
|
|
val label: String
|
|
|
|
val icon: Int
|
2021-04-22 19:47:06 +02:00
|
|
|
|
2021-04-12 07:27:55 +02:00
|
|
|
if (isPlaying) {
|
2021-04-22 19:25:50 +02:00
|
|
|
label = getString(R.string.common_pause)
|
|
|
|
icon = R.drawable.media_pause_large_dark
|
2021-04-12 07:27:55 +02:00
|
|
|
} else {
|
2021-04-22 19:25:50 +02:00
|
|
|
label = getString(R.string.common_play)
|
|
|
|
icon = R.drawable.media_start_large_dark
|
2020-06-21 09:31:38 +02:00
|
|
|
}
|
2021-04-22 19:47:06 +02:00
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
return NotificationCompat.Action.Builder(icon, label, pendingIntent).build()
|
2021-04-12 07:27:55 +02:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:47:06 +02:00
|
|
|
private fun generateStarAction(
|
|
|
|
context: Context,
|
|
|
|
requestCode: Int,
|
|
|
|
isStarred: Boolean
|
|
|
|
): NotificationCompat.Action {
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
val label: String
|
|
|
|
val icon: Int
|
2021-04-22 19:47:06 +02:00
|
|
|
val keyCode: Int = KeyEvent.KEYCODE_STAR
|
|
|
|
|
2021-04-21 16:41:08 +02:00
|
|
|
if (isStarred) {
|
2021-04-22 19:25:50 +02:00
|
|
|
label = getString(R.string.download_menu_star)
|
|
|
|
icon = R.drawable.ic_star_full_dark
|
2021-04-21 16:41:08 +02:00
|
|
|
} else {
|
2021-04-22 19:25:50 +02:00
|
|
|
label = getString(R.string.download_menu_star)
|
|
|
|
icon = R.drawable.ic_star_hollow_dark
|
2021-04-21 16:41:08 +02:00
|
|
|
}
|
2021-04-22 19:47:06 +02:00
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
val pendingIntent = getPendingIntentForMediaAction(context, keyCode, requestCode)
|
|
|
|
return NotificationCompat.Action.Builder(icon, label, pendingIntent).build()
|
|
|
|
}
|
2021-04-21 16:41:08 +02:00
|
|
|
|
2021-04-22 19:47:06 +02:00
|
|
|
private fun getPendingIntentForContent(): PendingIntent {
|
|
|
|
val intent = Intent(this, NavigationActivity::class.java)
|
|
|
|
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
|
|
val flags = PendingIntent.FLAG_UPDATE_CURRENT
|
|
|
|
intent.putExtra(Constants.INTENT_EXTRA_NAME_SHOW_PLAYER, true)
|
|
|
|
return PendingIntent.getActivity(this, 0, intent, flags)
|
|
|
|
}
|
2021-04-21 16:41:08 +02:00
|
|
|
|
2021-04-22 19:47:06 +02:00
|
|
|
private fun getPendingIntentForMediaAction(
|
|
|
|
context: Context,
|
|
|
|
keycode: Int,
|
|
|
|
requestCode: Int
|
|
|
|
): PendingIntent {
|
2021-04-22 19:25:50 +02:00
|
|
|
val intent = Intent(Constants.CMD_PROCESS_KEYCODE)
|
2021-04-22 19:47:06 +02:00
|
|
|
val flags = PendingIntent.FLAG_UPDATE_CURRENT
|
2021-04-22 19:25:50 +02:00
|
|
|
intent.setPackage(context.packageName)
|
|
|
|
intent.putExtra(Intent.EXTRA_KEY_EVENT, KeyEvent(KeyEvent.ACTION_DOWN, keycode))
|
2021-04-22 19:47:06 +02:00
|
|
|
return PendingIntent.getBroadcast(context, requestCode, intent, flags)
|
2021-04-21 16:41:08 +02:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
private fun initMediaSessions() {
|
2021-04-23 11:52:08 +02:00
|
|
|
@Suppress("MagicNumber")
|
|
|
|
val keycode = 110
|
|
|
|
|
|
|
|
Timber.w("Creating media session")
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
mediaSession = MediaSessionCompat(applicationContext, "UltrasonicService")
|
|
|
|
mediaSessionToken = mediaSession!!.sessionToken
|
2021-05-04 16:29:27 +02:00
|
|
|
|
|
|
|
updateMediaButtonReceiver()
|
2021-04-22 19:47:06 +02:00
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
mediaSession!!.setCallback(object : MediaSessionCompat.Callback() {
|
|
|
|
override fun onPlay() {
|
|
|
|
super.onPlay()
|
2021-04-23 11:52:08 +02:00
|
|
|
|
|
|
|
getPendingIntentForMediaAction(
|
|
|
|
applicationContext,
|
|
|
|
KeyEvent.KEYCODE_MEDIA_PLAY,
|
|
|
|
keycode
|
|
|
|
).send()
|
|
|
|
|
2021-04-25 12:24:35 +02:00
|
|
|
Timber.v("Media Session Callback: onPlay")
|
2021-04-22 19:25:50 +02:00
|
|
|
}
|
2021-04-21 16:41:08 +02:00
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
override fun onPause() {
|
|
|
|
super.onPause()
|
2021-04-23 11:52:08 +02:00
|
|
|
getPendingIntentForMediaAction(
|
|
|
|
applicationContext,
|
|
|
|
KeyEvent.KEYCODE_MEDIA_PAUSE,
|
|
|
|
keycode
|
|
|
|
).send()
|
2021-04-25 12:24:35 +02:00
|
|
|
Timber.v("Media Session Callback: onPause")
|
2021-04-22 19:25:50 +02:00
|
|
|
}
|
2021-04-12 07:27:55 +02:00
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
override fun onStop() {
|
|
|
|
super.onStop()
|
2021-04-23 11:52:08 +02:00
|
|
|
getPendingIntentForMediaAction(
|
|
|
|
applicationContext,
|
|
|
|
KeyEvent.KEYCODE_MEDIA_STOP,
|
|
|
|
keycode
|
|
|
|
).send()
|
2021-04-25 12:24:35 +02:00
|
|
|
Timber.v("Media Session Callback: onStop")
|
2021-04-22 19:25:50 +02:00
|
|
|
}
|
2021-04-12 07:27:55 +02:00
|
|
|
|
2021-05-06 12:53:43 +02:00
|
|
|
override fun onSkipToNext() {
|
|
|
|
super.onSkipToNext()
|
|
|
|
getPendingIntentForMediaAction(
|
|
|
|
applicationContext,
|
|
|
|
KeyEvent.KEYCODE_MEDIA_NEXT,
|
|
|
|
keycode
|
|
|
|
).send()
|
|
|
|
Timber.v("Media Session Callback: onSkipToNext")
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onSkipToPrevious() {
|
|
|
|
super.onSkipToPrevious()
|
|
|
|
getPendingIntentForMediaAction(
|
|
|
|
applicationContext,
|
|
|
|
KeyEvent.KEYCODE_MEDIA_PREVIOUS,
|
|
|
|
keycode
|
|
|
|
).send()
|
|
|
|
Timber.v("Media Session Callback: onSkipToPrevious")
|
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {
|
|
|
|
// This probably won't be necessary once we implement more
|
|
|
|
// of the modern media APIs, like the MediaController etc.
|
|
|
|
val event = mediaButtonEvent.extras!!["android.intent.extra.KEY_EVENT"] as KeyEvent?
|
2021-04-22 19:47:06 +02:00
|
|
|
mediaPlayerLifecycleSupport.handleKeyEvent(event)
|
2021-04-22 19:25:50 +02:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-05-04 16:29:27 +02:00
|
|
|
fun updateMediaButtonReceiver() {
|
2021-05-09 08:35:15 +02:00
|
|
|
if (Util.getMediaButtonsEnabled()) {
|
2021-05-04 16:29:27 +02:00
|
|
|
registerMediaButtonEventReceiver()
|
|
|
|
} else {
|
|
|
|
unregisterMediaButtonEventReceiver()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-06 12:53:25 +02:00
|
|
|
private fun registerMediaButtonEventReceiver() {
|
2021-05-01 10:42:25 +02:00
|
|
|
val component = ComponentName(packageName, MediaButtonIntentReceiver::class.java.name)
|
|
|
|
val mediaButtonIntent = Intent(Intent.ACTION_MEDIA_BUTTON)
|
|
|
|
mediaButtonIntent.component = component
|
|
|
|
|
|
|
|
val pendingIntent = PendingIntent.getBroadcast(
|
|
|
|
this,
|
|
|
|
INTENT_CODE_MEDIA_BUTTON,
|
|
|
|
mediaButtonIntent,
|
|
|
|
PendingIntent.FLAG_CANCEL_CURRENT
|
|
|
|
)
|
|
|
|
|
|
|
|
mediaSession?.setMediaButtonReceiver(pendingIntent)
|
|
|
|
}
|
|
|
|
|
2021-05-06 12:53:25 +02:00
|
|
|
private fun unregisterMediaButtonEventReceiver() {
|
2021-05-01 10:42:25 +02:00
|
|
|
mediaSession?.setMediaButtonReceiver(null)
|
|
|
|
}
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
companion object {
|
|
|
|
private const val NOTIFICATION_CHANNEL_ID = "org.moire.ultrasonic"
|
|
|
|
private const val NOTIFICATION_CHANNEL_NAME = "Ultrasonic background service"
|
|
|
|
private const val NOTIFICATION_ID = 3033
|
2021-05-01 10:42:25 +02:00
|
|
|
private const val INTENT_CODE_MEDIA_BUTTON = 161
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
private var instance: MediaPlayerService? = null
|
|
|
|
private val instanceLock = Any()
|
2021-04-22 19:47:06 +02:00
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
@JvmStatic
|
|
|
|
fun getInstance(context: Context): MediaPlayerService? {
|
|
|
|
synchronized(instanceLock) {
|
|
|
|
for (i in 0..19) {
|
|
|
|
if (instance != null) return instance
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
2021-04-22 19:47:06 +02:00
|
|
|
context.startForegroundService(
|
|
|
|
Intent(context, MediaPlayerService::class.java)
|
|
|
|
)
|
2021-04-22 19:25:50 +02:00
|
|
|
} else {
|
|
|
|
context.startService(Intent(context, MediaPlayerService::class.java))
|
|
|
|
}
|
|
|
|
Util.sleepQuietly(50L)
|
|
|
|
}
|
|
|
|
return instance
|
|
|
|
}
|
|
|
|
}
|
2021-04-12 07:27:55 +02:00
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
@JvmStatic
|
|
|
|
val runningInstance: MediaPlayerService?
|
|
|
|
get() {
|
|
|
|
synchronized(instanceLock) { return instance }
|
|
|
|
}
|
2021-04-12 07:27:55 +02:00
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
@JvmStatic
|
2021-04-22 19:47:06 +02:00
|
|
|
fun executeOnStartedMediaPlayerService(
|
|
|
|
context: Context,
|
2021-05-01 09:57:11 +02:00
|
|
|
taskToExecute: (MediaPlayerService?) -> Unit
|
2021-04-22 19:47:06 +02:00
|
|
|
) {
|
|
|
|
|
2021-04-22 19:25:50 +02:00
|
|
|
val t: Thread = object : Thread() {
|
|
|
|
override fun run() {
|
|
|
|
val instance = getInstance(context)
|
|
|
|
if (instance == null) {
|
2021-04-22 19:47:06 +02:00
|
|
|
Timber.e("ExecuteOnStarted.. failed to get a MediaPlayerService instance!")
|
2021-04-22 19:25:50 +02:00
|
|
|
return
|
|
|
|
}
|
2021-05-01 09:57:11 +02:00
|
|
|
taskToExecute(instance)
|
2021-04-22 19:25:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
t.start()
|
|
|
|
}
|
|
|
|
}
|
2021-04-22 19:47:06 +02:00
|
|
|
}
|