Refactor after conversion

This commit is contained in:
tzugen 2021-04-22 19:47:06 +02:00
parent 35e89b47c6
commit 3aae91bf13
No known key found for this signature in database
GPG Key ID: 61E9C34BC10EC930
1 changed files with 293 additions and 186 deletions

View File

@ -1,3 +1,10 @@
/*
* MediaPlayerService.kt
* Copyright (C) 2009-2021 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.service package org.moire.ultrasonic.service
import android.app.Notification import android.app.Notification
@ -15,7 +22,8 @@ import android.support.v4.media.session.PlaybackStateCompat
import android.view.KeyEvent import android.view.KeyEvent
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import org.koin.java.KoinJavaComponent.inject import java.util.ArrayList
import org.koin.android.ext.android.inject
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.activity.NavigationActivity import org.moire.ultrasonic.activity.NavigationActivity
import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.MusicDirectory
@ -25,7 +33,6 @@ import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X1
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X2 import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X2
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X3 import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X3
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X4 import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X4
import org.moire.ultrasonic.service.MediaPlayerService
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.FileUtil import org.moire.ultrasonic.util.FileUtil
@ -34,7 +41,6 @@ import org.moire.ultrasonic.util.ShufflePlayBuffer
import org.moire.ultrasonic.util.SimpleServiceBinder import org.moire.ultrasonic.util.SimpleServiceBinder
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
import timber.log.Timber import timber.log.Timber
import java.util.ArrayList
/** /**
* Android Foreground Service for playing music * Android Foreground Service for playing music
@ -43,55 +49,54 @@ import java.util.ArrayList
class MediaPlayerService : Service() { class MediaPlayerService : Service() {
private val binder: IBinder = SimpleServiceBinder(this) private val binder: IBinder = SimpleServiceBinder(this)
private val scrobbler = Scrobbler() private val scrobbler = Scrobbler()
var jukeboxMediaPlayer = inject(JukeboxMediaPlayer::class.java) private val jukeboxMediaPlayer by inject<JukeboxMediaPlayer>()
private val downloadQueueSerializerLazy = inject(DownloadQueueSerializer::class.java) private val downloadQueueSerializer by inject<DownloadQueueSerializer>()
private val shufflePlayBufferLazy = inject(ShufflePlayBuffer::class.java) private val shufflePlayBuffer by inject<ShufflePlayBuffer>()
private val downloaderLazy = inject(Downloader::class.java) private val downloader by inject<Downloader>()
private val localMediaPlayerLazy = inject(LocalMediaPlayer::class.java) private val localMediaPlayer by inject<LocalMediaPlayer>()
private val nowPlayingEventDistributor = inject(NowPlayingEventDistributor::class.java) private val nowPlayingEventDistributor by inject<NowPlayingEventDistributor>()
private val mediaPlayerLifecycleSupport = inject(MediaPlayerLifecycleSupport::class.java) private val mediaPlayerLifecycleSupport by inject<MediaPlayerLifecycleSupport>()
private var localMediaPlayer: LocalMediaPlayer? = null
private var downloader: Downloader? = null
private var shufflePlayBuffer: ShufflePlayBuffer? = null
private var downloadQueueSerializer: DownloadQueueSerializer? = null
private var mediaSession: MediaSessionCompat? = null private var mediaSession: MediaSessionCompat? = null
private var mediaSessionToken: MediaSessionCompat.Token? = null private var mediaSessionToken: MediaSessionCompat.Token? = null
private var isInForeground = false private var isInForeground = false
private var notificationBuilder: NotificationCompat.Builder? = null private var notificationBuilder: NotificationCompat.Builder? = null
val repeatMode: RepeatMode val repeatMode: RepeatMode
get() = Util.getRepeatMode(this) get() = Util.getRepeatMode(this)
override fun onBind(intent: Intent): IBinder? { override fun onBind(intent: Intent): IBinder {
return binder return binder
} }
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
downloader = downloaderLazy.value
localMediaPlayer = localMediaPlayerLazy.value
shufflePlayBuffer = shufflePlayBufferLazy.value
downloadQueueSerializer = downloadQueueSerializerLazy.value
initMediaSessions() initMediaSessions()
downloader!!.onCreate() downloader.onCreate()
shufflePlayBuffer!!.onCreate() shufflePlayBuffer.onCreate()
localMediaPlayer!!.init() localMediaPlayer.init()
setupOnCurrentPlayingChangedHandler() setupOnCurrentPlayingChangedHandler()
setupOnPlayerStateChangedHandler() setupOnPlayerStateChangedHandler()
setupOnSongCompletedHandler() setupOnSongCompletedHandler()
localMediaPlayer!!.onPrepared = {
downloadQueueSerializer!!.serializeDownloadQueue( localMediaPlayer.onPrepared = {
downloader!!.downloadList, downloadQueueSerializer.serializeDownloadQueue(
downloader!!.currentPlayingIndex, downloader.downloadList,
playerPosition downloader.currentPlayingIndex,
playerPosition
) )
null null
} }
localMediaPlayer!!.onNextSongRequested = Runnable { setNextPlaying() }
localMediaPlayer.onNextSongRequested = Runnable { setNextPlaying() }
// Create Notification Channel // Create Notification Channel
createNotificationChannel() createNotificationChannel()
// Update notification early. It is better to show an empty one temporarily than waiting too long and letting Android kill the app // Update notification early. It is better to show an empty one temporarily
// than waiting too long and letting Android kill the app
updateNotification(PlayerState.IDLE, null) updateNotification(PlayerState.IDLE, null)
instance = this instance = this
Timber.i("MediaPlayerService created") Timber.i("MediaPlayerService created")
@ -106,9 +111,9 @@ class MediaPlayerService : Service() {
super.onDestroy() super.onDestroy()
instance = null instance = null
try { try {
localMediaPlayer!!.release() localMediaPlayer.release()
downloader!!.stop() downloader.stop()
shufflePlayBuffer!!.onDestroy() shufflePlayBuffer.onDestroy()
mediaSession!!.release() mediaSession!!.release()
} catch (ignored: Throwable) { } catch (ignored: Throwable) {
} }
@ -117,113 +122,108 @@ class MediaPlayerService : Service() {
private fun stopIfIdle() { private fun stopIfIdle() {
synchronized(instanceLock) { synchronized(instanceLock) {
// currentPlaying could be changed from another thread in the meantime, so check again before stopping for good // currentPlaying could be changed from another thread in the meantime,
if (localMediaPlayer!!.currentPlaying == null || localMediaPlayer!!.playerState === PlayerState.STOPPED) stopSelf() // so check again before stopping for good
if (localMediaPlayer.currentPlaying == null ||
localMediaPlayer.playerState === PlayerState.STOPPED
) {
stopSelf()
}
} }
} }
@Synchronized @Synchronized
fun seekTo(position: Int) { fun seekTo(position: Int) {
if (jukeboxMediaPlayer.value.isEnabled) { if (jukeboxMediaPlayer.isEnabled) {
jukeboxMediaPlayer.value.skip(downloader!!.currentPlayingIndex, position / 1000) // TODO These APIs should be more aligned
val seconds = position / 1000
jukeboxMediaPlayer.skip(downloader.currentPlayingIndex, seconds)
} else { } else {
localMediaPlayer!!.seekTo(position) localMediaPlayer.seekTo(position)
} }
} }
@get:Synchronized @get:Synchronized
val playerPosition: Int val playerPosition: Int
get() { get() {
if (localMediaPlayer!!.playerState === PlayerState.IDLE || localMediaPlayer!!.playerState === PlayerState.DOWNLOADING || localMediaPlayer!!.playerState === PlayerState.PREPARING) { if (localMediaPlayer.playerState === PlayerState.IDLE ||
localMediaPlayer.playerState === PlayerState.DOWNLOADING ||
localMediaPlayer.playerState === PlayerState.PREPARING
) {
return 0 return 0
} }
return if (jukeboxMediaPlayer.value.isEnabled) jukeboxMediaPlayer.value.positionSeconds * 1000 else localMediaPlayer!!.playerPosition return if (jukeboxMediaPlayer.isEnabled) {
jukeboxMediaPlayer.positionSeconds * 1000
} else {
localMediaPlayer.playerPosition
}
} }
@get:Synchronized @get:Synchronized
val playerDuration: Int val playerDuration: Int
get() = localMediaPlayer!!.playerDuration get() = localMediaPlayer.playerDuration
@Synchronized @Synchronized
fun setCurrentPlaying(currentPlayingIndex: Int) { fun setCurrentPlaying(currentPlayingIndex: Int) {
try { try {
localMediaPlayer!!.setCurrentPlaying(downloader!!.downloadList[currentPlayingIndex]) localMediaPlayer.setCurrentPlaying(downloader.downloadList[currentPlayingIndex])
} catch (x: IndexOutOfBoundsException) { } catch (x: IndexOutOfBoundsException) {
// Ignored // Ignored
} }
} }
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.value.raiseShowNowPlayingEvent()
} else {
nowPlayingEventDistributor.value.raiseHideNowPlayingEvent()
stopForeground(true)
isInForeground = false
stopIfIdle()
}
null
}
}
@Synchronized @Synchronized
fun setNextPlaying() { fun setNextPlaying() {
val gaplessPlayback = Util.getGaplessPlaybackPreference(this) val gaplessPlayback = Util.getGaplessPlaybackPreference(this)
if (!gaplessPlayback) { if (!gaplessPlayback) {
localMediaPlayer!!.clearNextPlaying(true) localMediaPlayer.clearNextPlaying(true)
return return
} }
var index = downloader!!.currentPlayingIndex
var index = downloader.currentPlayingIndex
if (index != -1) { if (index != -1) {
when (repeatMode) { when (repeatMode) {
RepeatMode.OFF -> index += 1 RepeatMode.OFF -> index += 1
RepeatMode.ALL -> index = (index + 1) % downloader!!.downloadList.size RepeatMode.ALL -> index = (index + 1) % downloader.downloadList.size
RepeatMode.SINGLE -> { RepeatMode.SINGLE -> {
} }
else -> { else -> {
} }
} }
} }
localMediaPlayer!!.clearNextPlaying(false)
if (index < downloader!!.downloadList.size && index != -1) { localMediaPlayer.clearNextPlaying(false)
localMediaPlayer!!.setNextPlaying(downloader!!.downloadList[index]) if (index < downloader.downloadList.size && index != -1) {
localMediaPlayer.setNextPlaying(downloader.downloadList[index])
} else { } else {
localMediaPlayer!!.clearNextPlaying(true) localMediaPlayer.clearNextPlaying(true)
} }
} }
@Synchronized @Synchronized
fun togglePlayPause() { fun togglePlayPause() {
if (localMediaPlayer!!.playerState === PlayerState.PAUSED || localMediaPlayer!!.playerState === PlayerState.COMPLETED || localMediaPlayer!!.playerState === PlayerState.STOPPED) { if (localMediaPlayer.playerState === PlayerState.PAUSED ||
localMediaPlayer.playerState === PlayerState.COMPLETED ||
localMediaPlayer.playerState === PlayerState.STOPPED
) {
start() start()
} else if (localMediaPlayer!!.playerState === PlayerState.IDLE) { } else if (localMediaPlayer.playerState === PlayerState.IDLE) {
play() play()
} else if (localMediaPlayer!!.playerState === PlayerState.STARTED) { } else if (localMediaPlayer.playerState === PlayerState.STARTED) {
pause() pause()
} }
} }
@Synchronized @Synchronized
fun resumeOrPlay() { fun resumeOrPlay() {
if (localMediaPlayer!!.playerState === PlayerState.PAUSED || localMediaPlayer!!.playerState === PlayerState.COMPLETED || localMediaPlayer!!.playerState === PlayerState.STOPPED) { if (localMediaPlayer.playerState === PlayerState.PAUSED ||
localMediaPlayer.playerState === PlayerState.COMPLETED ||
localMediaPlayer.playerState === PlayerState.STOPPED
) {
start() start()
} else if (localMediaPlayer!!.playerState === PlayerState.IDLE) { } else if (localMediaPlayer.playerState === PlayerState.IDLE) {
play() play()
} }
} }
@ -233,7 +233,7 @@ class MediaPlayerService : Service() {
*/ */
@Synchronized @Synchronized
fun play() { fun play() {
val current = downloader!!.currentPlayingIndex val current = downloader.currentPlayingIndex
if (current == -1) { if (current == -1) {
play(0) play(0)
} else { } else {
@ -249,119 +249,176 @@ class MediaPlayerService : Service() {
@Synchronized @Synchronized
fun play(index: Int, start: Boolean) { fun play(index: Int, start: Boolean) {
Timber.v("play requested for %d", index) Timber.v("play requested for %d", index)
if (index < 0 || index >= downloader!!.downloadList.size) { if (index < 0 || index >= downloader.downloadList.size) {
resetPlayback() resetPlayback()
} else { } else {
setCurrentPlaying(index) setCurrentPlaying(index)
if (start) { if (start) {
if (jukeboxMediaPlayer.value.isEnabled) { if (jukeboxMediaPlayer.isEnabled) {
jukeboxMediaPlayer.value.skip(index, 0) jukeboxMediaPlayer.skip(index, 0)
localMediaPlayer!!.setPlayerState(PlayerState.STARTED) localMediaPlayer.setPlayerState(PlayerState.STARTED)
} else { } else {
localMediaPlayer!!.play(downloader!!.downloadList[index]) localMediaPlayer.play(downloader.downloadList[index])
} }
} }
downloader!!.checkDownloads() downloader.checkDownloads()
setNextPlaying() setNextPlaying()
} }
} }
@Synchronized @Synchronized
private fun resetPlayback() { private fun resetPlayback() {
localMediaPlayer!!.reset() localMediaPlayer.reset()
localMediaPlayer!!.setCurrentPlaying(null) localMediaPlayer.setCurrentPlaying(null)
downloadQueueSerializer!!.serializeDownloadQueue(downloader!!.downloadList, downloadQueueSerializer.serializeDownloadQueue(
downloader!!.currentPlayingIndex, playerPosition) downloader.downloadList,
downloader.currentPlayingIndex, playerPosition
)
} }
@Synchronized @Synchronized
fun pause() { fun pause() {
if (localMediaPlayer!!.playerState === PlayerState.STARTED) { if (localMediaPlayer.playerState === PlayerState.STARTED) {
if (jukeboxMediaPlayer.value.isEnabled) { if (jukeboxMediaPlayer.isEnabled) {
jukeboxMediaPlayer.value.stop() jukeboxMediaPlayer.stop()
} else { } else {
localMediaPlayer!!.pause() localMediaPlayer.pause()
} }
localMediaPlayer!!.setPlayerState(PlayerState.PAUSED) localMediaPlayer.setPlayerState(PlayerState.PAUSED)
} }
} }
@Synchronized @Synchronized
fun stop() { fun stop() {
if (localMediaPlayer!!.playerState === PlayerState.STARTED) { if (localMediaPlayer.playerState === PlayerState.STARTED) {
if (jukeboxMediaPlayer.value.isEnabled) { if (jukeboxMediaPlayer.isEnabled) {
jukeboxMediaPlayer.value.stop() jukeboxMediaPlayer.stop()
} else { } else {
localMediaPlayer!!.pause() localMediaPlayer.pause()
} }
} }
localMediaPlayer!!.setPlayerState(PlayerState.STOPPED) localMediaPlayer.setPlayerState(PlayerState.STOPPED)
} }
@Synchronized @Synchronized
fun start() { fun start() {
if (jukeboxMediaPlayer.value.isEnabled) { if (jukeboxMediaPlayer.isEnabled) {
jukeboxMediaPlayer.value.start() jukeboxMediaPlayer.start()
} else { } else {
localMediaPlayer!!.start() localMediaPlayer.start()
} }
localMediaPlayer!!.setPlayerState(PlayerState.STARTED) localMediaPlayer.setPlayerState(PlayerState.STARTED)
} }
private fun UpdateWidget(playerState: PlayerState, song: MusicDirectory.Entry?) { private fun updateWidget(playerState: PlayerState, song: MusicDirectory.Entry?) {
UltrasonicAppWidgetProvider4X1.getInstance().notifyChange(this@MediaPlayerService, song, playerState === PlayerState.STARTED, false) val started = playerState === PlayerState.STARTED
UltrasonicAppWidgetProvider4X2.getInstance().notifyChange(this@MediaPlayerService, song, playerState === PlayerState.STARTED, true) val context = this@MediaPlayerService
UltrasonicAppWidgetProvider4X3.getInstance().notifyChange(this@MediaPlayerService, song, playerState === PlayerState.STARTED, false)
UltrasonicAppWidgetProvider4X4.getInstance().notifyChange(this@MediaPlayerService, song, playerState === PlayerState.STARTED, false) 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)
} }
fun setupOnPlayerStateChangedHandler() { private fun setupOnCurrentPlayingChangedHandler() {
localMediaPlayer!!.onPlayerStateChanged = { playerState: PlayerState, currentPlaying: DownloadFile? -> 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
// Notify MediaSession // Notify MediaSession
updateMediaSession(currentPlaying, playerState) updateMediaSession(currentPlaying, playerState)
if (playerState === PlayerState.PAUSED) { if (playerState === PlayerState.PAUSED) {
downloadQueueSerializer!!.serializeDownloadQueue(downloader!!.downloadList, downloader!!.currentPlayingIndex, playerPosition) downloadQueueSerializer.serializeDownloadQueue(
downloader.downloadList, downloader.currentPlayingIndex, playerPosition
)
} }
val showWhenPaused = playerState !== PlayerState.STOPPED && Util.isNotificationAlwaysEnabled(this@MediaPlayerService)
val showWhenPaused = playerState !== PlayerState.STOPPED &&
Util.isNotificationAlwaysEnabled(context)
val show = playerState === PlayerState.STARTED || showWhenPaused val show = playerState === PlayerState.STARTED || showWhenPaused
val song = currentPlaying?.song val song = currentPlaying?.song
Util.broadcastPlaybackStatusChange(this@MediaPlayerService, playerState)
Util.broadcastA2dpPlayStatusChange(this@MediaPlayerService, playerState, song, Util.broadcastPlaybackStatusChange(context, playerState)
downloader!!.downloadList.size + downloader!!.backgroundDownloadList.size, Util.broadcastA2dpPlayStatusChange(
downloader!!.downloadList.indexOf(currentPlaying) + 1, playerPosition) context, playerState, song,
downloader.downloadList.size + downloader.backgroundDownloadList.size,
downloader.downloadList.indexOf(currentPlaying) + 1, playerPosition
)
// Update widget // Update widget
UpdateWidget(playerState, song) updateWidget(playerState, song)
if (show) { if (show) {
// Only update notification if player state is one that will change the icon // Only update notification if player state is one that will change the icon
if (playerState === PlayerState.STARTED || playerState === PlayerState.PAUSED) { if (playerState === PlayerState.STARTED || playerState === PlayerState.PAUSED) {
updateNotification(playerState, currentPlaying) updateNotification(playerState, currentPlaying)
nowPlayingEventDistributor.value.raiseShowNowPlayingEvent() nowPlayingEventDistributor.raiseShowNowPlayingEvent()
} }
} else { } else {
nowPlayingEventDistributor.value.raiseHideNowPlayingEvent() nowPlayingEventDistributor.raiseHideNowPlayingEvent()
stopForeground(true) stopForeground(true)
isInForeground = false isInForeground = false
stopIfIdle() stopIfIdle()
} }
if (playerState === PlayerState.STARTED) { if (playerState === PlayerState.STARTED) {
scrobbler.scrobble(this@MediaPlayerService, currentPlaying, false) scrobbler.scrobble(context, currentPlaying, false)
} else if (playerState === PlayerState.COMPLETED) { } else if (playerState === PlayerState.COMPLETED) {
scrobbler.scrobble(this@MediaPlayerService, currentPlaying, true) scrobbler.scrobble(context, currentPlaying, true)
} }
null null
} }
} }
private fun setupOnSongCompletedHandler() { private fun setupOnSongCompletedHandler() {
localMediaPlayer!!.onSongCompleted = { currentPlaying: DownloadFile? -> localMediaPlayer.onSongCompleted = { currentPlaying: DownloadFile? ->
val index = downloader!!.currentPlayingIndex val index = downloader.currentPlayingIndex
val context = this@MediaPlayerService
if (currentPlaying != null) { if (currentPlaying != null) {
val song = currentPlaying.song val song = currentPlaying.song
if (song.bookmarkPosition > 0 && Util.getShouldClearBookmark(this@MediaPlayerService)) { if (song.bookmarkPosition > 0 && Util.getShouldClearBookmark(context)) {
val musicService = getMusicService(this@MediaPlayerService) val musicService = getMusicService(context)
try { try {
musicService.deleteBookmark(song.id, this@MediaPlayerService) musicService.deleteBookmark(song.id, context)
} catch (ignored: Exception) { } catch (ignored: Exception) {
} }
} }
@ -369,17 +426,19 @@ class MediaPlayerService : Service() {
if (index != -1) { if (index != -1) {
when (repeatMode) { when (repeatMode) {
RepeatMode.OFF -> { RepeatMode.OFF -> {
if (index + 1 < 0 || index + 1 >= downloader!!.downloadList.size) { if (index + 1 < 0 || index + 1 >= downloader.downloadList.size) {
if (Util.getShouldClearPlaylist(this@MediaPlayerService)) { if (Util.getShouldClearPlaylist(context)) {
clear(true) clear(true)
jukeboxMediaPlayer.value.updatePlaylist() jukeboxMediaPlayer.updatePlaylist()
} }
resetPlayback() resetPlayback()
break } else {
play(index + 1)
} }
play(index + 1)
} }
RepeatMode.ALL -> play((index + 1) % downloader!!.downloadList.size) RepeatMode.ALL -> {
play((index + 1) % downloader.downloadList.size)
}
RepeatMode.SINGLE -> play(index) RepeatMode.SINGLE -> play(index)
else -> { else -> {
} }
@ -391,13 +450,15 @@ class MediaPlayerService : Service() {
@Synchronized @Synchronized
fun clear(serialize: Boolean) { fun clear(serialize: Boolean) {
localMediaPlayer!!.reset() localMediaPlayer.reset()
downloader!!.clear() downloader.clear()
localMediaPlayer!!.setCurrentPlaying(null) localMediaPlayer.setCurrentPlaying(null)
setNextPlaying() setNextPlaying()
if (serialize) { if (serialize) {
downloadQueueSerializer!!.serializeDownloadQueue(downloader!!.downloadList, downloadQueueSerializer.serializeDownloadQueue(
downloader!!.currentPlayingIndex, playerPosition) downloader.downloadList,
downloader.currentPlayingIndex, playerPosition
)
} }
} }
@ -408,8 +469,9 @@ class MediaPlayerService : Service() {
if (currentPlaying != null) { if (currentPlaying != null) {
try { try {
val song = currentPlaying.song val song = currentPlaying.song
val cover = FileUtil.getAlbumArtBitmap(context, song, val cover = FileUtil.getAlbumArtBitmap(
Util.getMinDisplayMetric(context), true context, song,
Util.getMinDisplayMetric(context), true
) )
metadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, -1L) metadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, -1L)
metadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.artist) metadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.artist)
@ -427,9 +489,13 @@ class MediaPlayerService : Service() {
// Create playback State // Create playback State
val playbackState = PlaybackStateCompat.Builder() val playbackState = PlaybackStateCompat.Builder()
val state = if (playerState === PlayerState.STARTED) PlaybackStateCompat.STATE_PLAYING else PlaybackStateCompat.STATE_PAUSED val state = if (playerState === PlayerState.STARTED) {
// If we set the playback position correctly, we can get a nice seek bar :)
PlaybackStateCompat.STATE_PLAYING
} else {
PlaybackStateCompat.STATE_PAUSED
}
// If we set the playback position correctly, we can get a nice seek bar :)
playbackState.setState(state, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f) playbackState.setState(state, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f)
// Set Active state // Set Active state
@ -441,31 +507,40 @@ class MediaPlayerService : Service() {
private fun createNotificationChannel() { private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//The suggested importance of a startForeground service notification is IMPORTANCE_LOW
val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW) // The suggested importance of a startForeground service notification is IMPORTANCE_LOW
val channel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
NOTIFICATION_CHANNEL_NAME,
NotificationManager.IMPORTANCE_LOW
)
channel.lightColor = android.R.color.holo_blue_dark channel.lightColor = android.R.color.holo_blue_dark
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
channel.setShowBadge(false) channel.setShowBadge(false)
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel) manager.createNotificationChannel(channel)
} }
} }
fun updateNotification(playerState: PlayerState, currentPlaying: DownloadFile?) { fun updateNotification(playerState: PlayerState, currentPlaying: DownloadFile?) {
val notification = buildForegroundNotification(playerState, currentPlaying)
if (Util.isNotificationEnabled(this)) { if (Util.isNotificationEnabled(this)) {
if (isInForeground) { if (isInForeground) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(NOTIFICATION_ID, buildForegroundNotification(playerState, currentPlaying)) manager.notify(NOTIFICATION_ID, notification)
} else { } else {
val notificationManager = NotificationManagerCompat.from(this) val manager = NotificationManagerCompat.from(this)
notificationManager.notify(NOTIFICATION_ID, buildForegroundNotification(playerState, currentPlaying)) manager.notify(NOTIFICATION_ID, notification)
} }
Timber.w("--- Updated notification") Timber.v("Updated notification")
} else { } else {
startForeground(NOTIFICATION_ID, buildForegroundNotification(playerState, currentPlaying)) startForeground(NOTIFICATION_ID, notification)
isInForeground = true isInForeground = true
Timber.w("--- Created Foreground notification") Timber.w("Created Foreground notification")
} }
} }
} }
@ -473,7 +548,11 @@ class MediaPlayerService : Service() {
/** /**
* This method builds a notification, reusing the Notification Builder if possible * This method builds a notification, reusing the Notification Builder if possible
*/ */
private fun buildForegroundNotification(playerState: PlayerState, currentPlaying: DownloadFile?): Notification { private fun buildForegroundNotification(
playerState: PlayerState,
currentPlaying: DownloadFile?
): Notification {
// Init // Init
val context = applicationContext val context = applicationContext
val song = currentPlaying?.song val song = currentPlaying?.song
@ -494,7 +573,7 @@ class MediaPlayerService : Service() {
notificationBuilder!!.priority = NotificationCompat.PRIORITY_LOW notificationBuilder!!.priority = NotificationCompat.PRIORITY_LOW
// Add content intent (when user taps on notification) // Add content intent (when user taps on notification)
notificationBuilder!!.setContentIntent(pendingIntentForContent) notificationBuilder!!.setContentIntent(getPendingIntentForContent())
// This intent is executed when the user closes the notification // This intent is executed when the user closes the notification
notificationBuilder!!.setDeleteIntent(stopIntent) notificationBuilder!!.setDeleteIntent(stopIntent)
@ -526,14 +605,19 @@ class MediaPlayerService : Service() {
return notificationBuilder!!.build() return notificationBuilder!!.build()
} }
private fun addActions(context: Context, notificationBuilder: NotificationCompat.Builder, playerState: PlayerState, song: MusicDirectory.Entry?): IntArray { private fun addActions(
context: Context,
notificationBuilder: NotificationCompat.Builder,
playerState: PlayerState,
song: MusicDirectory.Entry?
): IntArray {
// Init
val compactActionList = ArrayList<Int>() val compactActionList = ArrayList<Int>()
var numActions = 0 // we start and 0 and then increment by 1 for each call to generateAction var numActions = 0 // we start and 0 and then increment by 1 for each call to generateAction
// Star // Star
if (song != null) { if (song != null) {
notificationBuilder.addAction(generateStarUnstarAction(context, numActions, song.starred)) notificationBuilder.addAction(generateStarAction(context, numActions, song.starred))
} }
numActions++ numActions++
@ -559,20 +643,21 @@ class MediaPlayerService : Service() {
actionArray[i] = compactActionList[i] actionArray[i] = compactActionList[i]
} }
return actionArray return actionArray
//notificationBuilder.setShowActionsInCompactView()) // notificationBuilder.setShowActionsInCompactView())
} }
private fun generateAction(context: Context, requestCode: Int): NotificationCompat.Action? { private fun generateAction(context: Context, requestCode: Int): NotificationCompat.Action? {
val keycode: Int val keycode: Int
val icon: Int val icon: Int
val label: String val label: String
when (requestCode) { when (requestCode) {
1 -> { 1 -> {
keycode = KeyEvent.KEYCODE_MEDIA_PREVIOUS keycode = KeyEvent.KEYCODE_MEDIA_PREVIOUS
label = getString(R.string.common_play_previous) label = getString(R.string.common_play_previous)
icon = R.drawable.media_backward_medium_dark icon = R.drawable.media_backward_medium_dark
} }
2 -> // Is handled in generatePlayPauseAction() 2 -> // Is handled in generatePlayPauseAction()
return null return null
3 -> { 3 -> {
keycode = KeyEvent.KEYCODE_MEDIA_NEXT keycode = KeyEvent.KEYCODE_MEDIA_NEXT
@ -586,15 +671,22 @@ class MediaPlayerService : Service() {
} }
else -> return null else -> return null
} }
val pendingIntent = getPendingIntentForMediaAction(context, keycode, requestCode) val pendingIntent = getPendingIntentForMediaAction(context, keycode, requestCode)
return NotificationCompat.Action.Builder(icon, label, pendingIntent).build() return NotificationCompat.Action.Builder(icon, label, pendingIntent).build()
} }
private fun generatePlayPauseAction(context: Context, requestCode: Int, playerState: PlayerState): NotificationCompat.Action { private fun generatePlayPauseAction(
context: Context,
requestCode: Int,
playerState: PlayerState
): NotificationCompat.Action {
val isPlaying = playerState === PlayerState.STARTED val isPlaying = playerState === PlayerState.STARTED
val pendingIntent = getPendingIntentForMediaAction(context, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, requestCode) val keycode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
val pendingIntent = getPendingIntentForMediaAction(context, keycode, requestCode)
val label: String val label: String
val icon: Int val icon: Int
if (isPlaying) { if (isPlaying) {
label = getString(R.string.common_pause) label = getString(R.string.common_pause)
icon = R.drawable.media_pause_large_dark icon = R.drawable.media_pause_large_dark
@ -602,14 +694,20 @@ class MediaPlayerService : Service() {
label = getString(R.string.common_play) label = getString(R.string.common_play)
icon = R.drawable.media_start_large_dark icon = R.drawable.media_start_large_dark
} }
return NotificationCompat.Action.Builder(icon, label, pendingIntent).build() return NotificationCompat.Action.Builder(icon, label, pendingIntent).build()
} }
private fun generateStarUnstarAction(context: Context, requestCode: Int, isStarred: Boolean): NotificationCompat.Action { private fun generateStarAction(
val keyCode: Int context: Context,
requestCode: Int,
isStarred: Boolean
): NotificationCompat.Action {
val label: String val label: String
val icon: Int val icon: Int
keyCode = KeyEvent.KEYCODE_STAR val keyCode: Int = KeyEvent.KEYCODE_STAR
if (isStarred) { if (isStarred) {
label = getString(R.string.download_menu_star) label = getString(R.string.download_menu_star)
icon = R.drawable.ic_star_full_dark icon = R.drawable.ic_star_full_dark
@ -617,29 +715,36 @@ class MediaPlayerService : Service() {
label = getString(R.string.download_menu_star) label = getString(R.string.download_menu_star)
icon = R.drawable.ic_star_hollow_dark icon = R.drawable.ic_star_hollow_dark
} }
val pendingIntent = getPendingIntentForMediaAction(context, keyCode, requestCode) val pendingIntent = getPendingIntentForMediaAction(context, keyCode, requestCode)
return NotificationCompat.Action.Builder(icon, label, pendingIntent).build() return NotificationCompat.Action.Builder(icon, label, pendingIntent).build()
} }
private val pendingIntentForContent: PendingIntent private fun getPendingIntentForContent(): PendingIntent {
private get() { val intent = Intent(this, NavigationActivity::class.java)
val notificationIntent = Intent(this, NavigationActivity::class.java) .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) val flags = PendingIntent.FLAG_UPDATE_CURRENT
notificationIntent.putExtra(Constants.INTENT_EXTRA_NAME_SHOW_PLAYER, true) intent.putExtra(Constants.INTENT_EXTRA_NAME_SHOW_PLAYER, true)
return PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT) return PendingIntent.getActivity(this, 0, intent, flags)
} }
private fun getPendingIntentForMediaAction(context: Context, keycode: Int, requestCode: Int): PendingIntent { private fun getPendingIntentForMediaAction(
context: Context,
keycode: Int,
requestCode: Int
): PendingIntent {
val intent = Intent(Constants.CMD_PROCESS_KEYCODE) val intent = Intent(Constants.CMD_PROCESS_KEYCODE)
val flags = PendingIntent.FLAG_UPDATE_CURRENT
intent.setPackage(context.packageName) intent.setPackage(context.packageName)
intent.putExtra(Intent.EXTRA_KEY_EVENT, KeyEvent(KeyEvent.ACTION_DOWN, keycode)) intent.putExtra(Intent.EXTRA_KEY_EVENT, KeyEvent(KeyEvent.ACTION_DOWN, keycode))
return PendingIntent.getBroadcast(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT) return PendingIntent.getBroadcast(context, requestCode, intent, flags)
} }
private fun initMediaSessions() { private fun initMediaSessions() {
mediaSession = MediaSessionCompat(applicationContext, "UltrasonicService") mediaSession = MediaSessionCompat(applicationContext, "UltrasonicService")
mediaSessionToken = mediaSession!!.sessionToken mediaSessionToken = mediaSession!!.sessionToken
//mediaController = new MediaControllerCompat(getApplicationContext(), mediaSessionToken); // mediaController = new MediaControllerCompat(getApplicationContext(), mediaSessionToken);
mediaSession!!.setCallback(object : MediaSessionCompat.Callback() { mediaSession!!.setCallback(object : MediaSessionCompat.Callback() {
override fun onPlay() { override fun onPlay() {
super.onPlay() super.onPlay()
@ -659,16 +764,11 @@ class MediaPlayerService : Service() {
Timber.w("Media Session Callback: onStop") Timber.w("Media Session Callback: onStop")
} }
override fun onSeekTo(pos: Long) {
super.onSeekTo(pos)
}
override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean { override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {
// This probably won't be necessary once we implement more // This probably won't be necessary once we implement more
// of the modern media APIs, like the MediaController etc. // of the modern media APIs, like the MediaController etc.
val event = mediaButtonEvent.extras!!["android.intent.extra.KEY_EVENT"] as KeyEvent? val event = mediaButtonEvent.extras!!["android.intent.extra.KEY_EVENT"] as KeyEvent?
val lifecycleSupport = mediaPlayerLifecycleSupport.value mediaPlayerLifecycleSupport.handleKeyEvent(event)
lifecycleSupport.handleKeyEvent(event)
return true return true
} }
} }
@ -681,13 +781,16 @@ class MediaPlayerService : Service() {
private const val NOTIFICATION_ID = 3033 private const val NOTIFICATION_ID = 3033
private var instance: MediaPlayerService? = null private var instance: MediaPlayerService? = null
private val instanceLock = Any() private val instanceLock = Any()
@JvmStatic @JvmStatic
fun getInstance(context: Context): MediaPlayerService? { fun getInstance(context: Context): MediaPlayerService? {
synchronized(instanceLock) { synchronized(instanceLock) {
for (i in 0..19) { for (i in 0..19) {
if (instance != null) return instance if (instance != null) return instance
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(Intent(context, MediaPlayerService::class.java)) context.startForegroundService(
Intent(context, MediaPlayerService::class.java)
)
} else { } else {
context.startService(Intent(context, MediaPlayerService::class.java)) context.startService(Intent(context, MediaPlayerService::class.java))
} }
@ -704,12 +807,16 @@ class MediaPlayerService : Service() {
} }
@JvmStatic @JvmStatic
fun executeOnStartedMediaPlayerService(context: Context, taskToExecute: Consumer<MediaPlayerService?>) { fun executeOnStartedMediaPlayerService(
context: Context,
taskToExecute: Consumer<MediaPlayerService?>
) {
val t: Thread = object : Thread() { val t: Thread = object : Thread() {
override fun run() { override fun run() {
val instance = getInstance(context) val instance = getInstance(context)
if (instance == null) { if (instance == null) {
Timber.e("ExecuteOnStartedMediaPlayerService failed to get a MediaPlayerService instance!") Timber.e("ExecuteOnStarted.. failed to get a MediaPlayerService instance!")
return return
} }
taskToExecute.accept(instance) taskToExecute.accept(instance)
@ -718,4 +825,4 @@ class MediaPlayerService : Service() {
t.start() t.start()
} }
} }
} }