2021-08-27 23:53:31 +02:00
|
|
|
package org.moire.ultrasonic.service
|
|
|
|
|
|
|
|
import org.koin.core.component.KoinComponent
|
|
|
|
import org.koin.core.component.inject
|
|
|
|
import org.moire.ultrasonic.domain.MusicDirectory
|
|
|
|
import org.moire.ultrasonic.util.Util.isExternalStoragePresent
|
|
|
|
import org.moire.ultrasonic.util.Util.isNetworkConnected
|
|
|
|
import org.moire.ultrasonic.util.Util.getPreloadCount
|
|
|
|
import org.moire.ultrasonic.util.Util.getMaxSongs
|
|
|
|
import org.moire.ultrasonic.util.ShufflePlayBuffer
|
|
|
|
import timber.log.Timber
|
|
|
|
import org.moire.ultrasonic.domain.PlayerState
|
|
|
|
import org.moire.ultrasonic.util.LRUCache
|
|
|
|
import java.util.ArrayList
|
|
|
|
import java.util.PriorityQueue
|
|
|
|
import java.util.concurrent.Executors
|
|
|
|
import java.util.concurrent.ScheduledExecutorService
|
|
|
|
import java.util.concurrent.TimeUnit
|
2020-06-23 18:40:44 +02:00
|
|
|
|
2020-06-26 15:18:14 +02:00
|
|
|
/**
|
|
|
|
* This class is responsible for maintaining the playlist and downloading
|
|
|
|
* its items from the network to the filesystem.
|
|
|
|
*/
|
2021-08-27 23:53:31 +02:00
|
|
|
class Downloader(
|
|
|
|
private val shufflePlayBuffer: ShufflePlayBuffer,
|
|
|
|
private val externalStorageMonitor: ExternalStorageMonitor,
|
|
|
|
private val localMediaPlayer: LocalMediaPlayer
|
|
|
|
): KoinComponent {
|
|
|
|
val playList: MutableList<DownloadFile> = ArrayList()
|
|
|
|
private val downloadQueue: PriorityQueue<DownloadFile> = PriorityQueue<DownloadFile>()
|
|
|
|
private val activelyDownloading: MutableList<DownloadFile> = ArrayList()
|
|
|
|
|
|
|
|
private val jukeboxMediaPlayer: JukeboxMediaPlayer by inject()
|
|
|
|
|
|
|
|
private val downloadFileCache = LRUCache<MusicDirectory.Entry, DownloadFile>(100)
|
|
|
|
|
|
|
|
private var executorService: ScheduledExecutorService? = null
|
|
|
|
var downloadListUpdateRevision: Long = 0
|
|
|
|
private set
|
|
|
|
|
|
|
|
val downloadChecker = Runnable {
|
|
|
|
try {
|
|
|
|
Timber.w("checking Downloads")
|
|
|
|
checkDownloadsInternal()
|
|
|
|
} catch (all: Exception) {
|
|
|
|
Timber.e(all, "checkDownloads() failed.")
|
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
fun onCreate() {
|
|
|
|
executorService = Executors.newSingleThreadScheduledExecutor()
|
|
|
|
executorService!!.scheduleWithFixedDelay(downloadChecker, 5, 5, TimeUnit.SECONDS)
|
|
|
|
Timber.i("Downloader created")
|
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
fun onDestroy() {
|
|
|
|
stop()
|
|
|
|
clearPlaylist()
|
|
|
|
clearBackground()
|
|
|
|
Timber.i("Downloader destroyed")
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
fun stop() {
|
|
|
|
if (executorService != null) executorService!!.shutdown()
|
|
|
|
Timber.i("Downloader stopped")
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
fun checkDownloads() {
|
|
|
|
executorService?.execute(downloadChecker)
|
2020-06-29 16:17:22 +02:00
|
|
|
}
|
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
@Synchronized
|
|
|
|
fun checkDownloadsInternal() {
|
|
|
|
if (!isExternalStoragePresent() || !externalStorageMonitor.isExternalStorageAvailable) {
|
|
|
|
return
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2021-08-27 23:53:31 +02:00
|
|
|
if (shufflePlayBuffer.isEnabled) {
|
|
|
|
checkShufflePlay()
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2021-08-27 23:53:31 +02:00
|
|
|
if (jukeboxMediaPlayer.isEnabled || !isNetworkConnected()) {
|
|
|
|
return
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
// Check the active downloads for failures or completions
|
|
|
|
activelyDownloading.retainAll {
|
|
|
|
when {
|
|
|
|
it.isDownloading -> true
|
|
|
|
it.isFailed && it.shouldRetry() -> {
|
|
|
|
// Add it back to queue
|
|
|
|
downloadQueue.add(it)
|
|
|
|
false
|
|
|
|
}
|
|
|
|
else -> {
|
|
|
|
it.cleanup()
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
// Check if need to preload more from playlist
|
|
|
|
val preloadCount = getPreloadCount()
|
2020-06-23 18:40:44 +02:00
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
// Start preloading at the current playing song
|
|
|
|
var start = if (localMediaPlayer.currentPlaying == null) 0 else currentPlayingIndex
|
|
|
|
if (start == -1) start = 0
|
2020-06-23 18:40:44 +02:00
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
var end = (start + preloadCount).coerceAtMost(playList.size)
|
2020-06-23 18:40:44 +02:00
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
// Playlist also contains played songs!!!!
|
|
|
|
for (i in start until end) {
|
|
|
|
val download = playList[i]
|
2020-06-23 18:40:44 +02:00
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
// Set correct priority (the lower the number, the higher the priority)
|
|
|
|
download.priority = i
|
2020-06-23 18:40:44 +02:00
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
// Add file to queue if not in one of the queues already.
|
|
|
|
if (!download.isWorkDone && !activelyDownloading.contains(download) && !downloadQueue.contains(download)) {
|
|
|
|
downloadQueue.add(download)
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
// Fill up active List with waiting tasks
|
|
|
|
while (activelyDownloading.size < PARALLEL_DOWNLOADS && downloadQueue.size > 0 ) {
|
|
|
|
val task = downloadQueue.remove()
|
|
|
|
activelyDownloading.add(task)
|
|
|
|
task.download()
|
2020-06-23 18:40:44 +02:00
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
// The next file on the playlist is currently downloading
|
|
|
|
// TODO: really necessary?
|
|
|
|
if (playList.indexOf(task) == 1) {
|
|
|
|
localMediaPlayer.setNextPlayerState(PlayerState.DOWNLOADING)
|
|
|
|
}
|
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
|
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
// fun oldStuff() {
|
|
|
|
// // Need to download current playing?
|
|
|
|
// if (localMediaPlayer.currentPlaying != null && localMediaPlayer.currentPlaying != currentDownloading && !localMediaPlayer.currentPlaying!!.isWorkDone) {
|
|
|
|
// // Cancel current download, if necessary.
|
|
|
|
// if (currentDownloading != null) {
|
|
|
|
// currentDownloading!!.cancelDownload()
|
|
|
|
// }
|
|
|
|
// currentDownloading = localMediaPlayer.currentPlaying
|
|
|
|
// currentDownloading!!.download()
|
|
|
|
// cleanupCandidates.add(currentDownloading)
|
|
|
|
//
|
|
|
|
// // Delete obsolete .partial and .complete files.
|
|
|
|
// cleanup()
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // Find a suitable target for download.
|
|
|
|
// if (currentDownloading != null &&
|
|
|
|
// !currentDownloading!!.isWorkDone &&
|
|
|
|
// (!currentDownloading!!.isFailed || playList.isEmpty() && backgroundDownloadList.isEmpty())
|
|
|
|
// ) {
|
|
|
|
// cleanup()
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // There is a target to download
|
|
|
|
// currentDownloading = null
|
|
|
|
// val n = playList.size
|
|
|
|
// var preloaded = 0
|
|
|
|
// if (n != 0) {
|
|
|
|
// var start = if (localMediaPlayer.currentPlaying == null) 0 else currentPlayingIndex
|
|
|
|
// if (start == -1) start = 0
|
|
|
|
// var i = start
|
|
|
|
// // Check all DownloadFiles on the playlist
|
|
|
|
// do {
|
|
|
|
// val downloadFile = playList[i]
|
|
|
|
// if (!downloadFile.isWorkDone) {
|
|
|
|
// if (downloadFile.shouldSave() || preloaded < getPreloadCount()) {
|
|
|
|
// currentDownloading = downloadFile
|
|
|
|
// currentDownloading!!.download()
|
|
|
|
// cleanupCandidates.add(currentDownloading)
|
|
|
|
// if (i == start + 1) {
|
|
|
|
// // The next file on the playlist is currently downloading
|
|
|
|
// localMediaPlayer.setNextPlayerState(PlayerState.DOWNLOADING)
|
|
|
|
// }
|
|
|
|
// break
|
|
|
|
// }
|
|
|
|
// } else if (localMediaPlayer.currentPlaying != downloadFile) {
|
|
|
|
// preloaded++
|
|
|
|
// }
|
|
|
|
// i = (i + 1) % n
|
|
|
|
// } while (i != start)
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // If the downloadList contains no work, check the backgroundDownloadList
|
|
|
|
// if ((preloaded + 1 == n || preloaded >= getPreloadCount() || playList.isEmpty()) && backgroundDownloadList.isNotEmpty()) {
|
|
|
|
// var i = 0
|
|
|
|
// while (i < backgroundDownloadList.size) {
|
|
|
|
// val downloadFile = backgroundDownloadList[i]
|
|
|
|
// if (downloadFile.isWorkDone && (!downloadFile.shouldSave() || downloadFile.isSaved)) {
|
|
|
|
// scanMedia(downloadFile.completeFile)
|
|
|
|
//
|
|
|
|
// // Don't need to keep list like active song list
|
|
|
|
// backgroundDownloadList.removeAt(i)
|
|
|
|
// downloadListUpdateRevision++
|
|
|
|
// i--
|
|
|
|
// } else if (downloadFile.isFailed && !downloadFile.shouldRetry()) {
|
|
|
|
// // Don't continue to attempt to download forever
|
|
|
|
// backgroundDownloadList.removeAt(i)
|
|
|
|
// downloadListUpdateRevision++
|
|
|
|
// i--
|
|
|
|
// } else {
|
|
|
|
// currentDownloading = downloadFile
|
|
|
|
// currentDownloading!!.download()
|
|
|
|
// cleanupCandidates.add(currentDownloading)
|
|
|
|
// break
|
|
|
|
// }
|
|
|
|
// i++
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// }
|
|
|
|
|
|
|
|
@get:Synchronized
|
|
|
|
val currentPlayingIndex: Int
|
|
|
|
get() = playList.indexOf(localMediaPlayer.currentPlaying)
|
|
|
|
|
|
|
|
@get:Synchronized
|
|
|
|
val downloadListDuration: Long
|
|
|
|
get() {
|
|
|
|
var totalDuration: Long = 0
|
|
|
|
for (downloadFile in playList) {
|
|
|
|
val song = downloadFile.song
|
|
|
|
if (!song.isDirectory) {
|
|
|
|
if (song.artist != null) {
|
|
|
|
if (song.duration != null) {
|
|
|
|
totalDuration += song.duration!!.toLong()
|
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-08-27 23:53:31 +02:00
|
|
|
return totalDuration
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
@get:Synchronized
|
|
|
|
val downloads: List<DownloadFile?>
|
|
|
|
get() {
|
|
|
|
val temp: MutableList<DownloadFile?> = ArrayList()
|
|
|
|
temp.addAll(playList)
|
|
|
|
temp.addAll(activelyDownloading)
|
|
|
|
temp.addAll(downloadQueue)
|
|
|
|
return temp.distinct()
|
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
@Synchronized
|
|
|
|
fun clearPlaylist() {
|
|
|
|
playList.clear()
|
2020-06-23 18:40:44 +02:00
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
// Cancel all active downloads with a high priority
|
|
|
|
for (download in activelyDownloading) {
|
|
|
|
if (download.priority < 100)
|
|
|
|
download.cancelDownload()
|
|
|
|
}
|
|
|
|
|
|
|
|
downloadListUpdateRevision++
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
@Synchronized
|
|
|
|
private fun clearBackground() {
|
|
|
|
// Clear the pending queue
|
|
|
|
downloadQueue.clear()
|
|
|
|
|
|
|
|
// Cancel all active downloads with a low priority
|
|
|
|
for (download in activelyDownloading) {
|
|
|
|
if (download.priority >= 100)
|
|
|
|
download.cancelDownload()
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2021-08-27 23:53:31 +02:00
|
|
|
|
|
|
|
downloadListUpdateRevision++
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
@Synchronized
|
|
|
|
fun clearActiveDownloads() {
|
|
|
|
// Cancel all active downloads with a low priority
|
|
|
|
for (download in activelyDownloading) {
|
|
|
|
download.cancelDownload()
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
@Synchronized
|
|
|
|
fun removeFromPlaylist(downloadFile: DownloadFile) {
|
|
|
|
if (activelyDownloading.contains(downloadFile)) {
|
|
|
|
downloadFile.cancelDownload()
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2021-08-27 23:53:31 +02:00
|
|
|
playList.remove(downloadFile)
|
|
|
|
downloadListUpdateRevision++
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
@Synchronized
|
|
|
|
fun addToPlaylist(
|
|
|
|
songs: List<MusicDirectory.Entry?>,
|
|
|
|
save: Boolean,
|
|
|
|
autoPlay: Boolean,
|
|
|
|
playNext: Boolean,
|
|
|
|
newPlaylist: Boolean
|
|
|
|
) {
|
|
|
|
shufflePlayBuffer.isEnabled = false
|
|
|
|
var offset = 1
|
|
|
|
if (songs.isEmpty()) {
|
|
|
|
return
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2021-08-27 23:53:31 +02:00
|
|
|
if (newPlaylist) {
|
|
|
|
playList.clear()
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2021-08-27 23:53:31 +02:00
|
|
|
if (playNext) {
|
|
|
|
if (autoPlay && currentPlayingIndex >= 0) {
|
|
|
|
offset = 0
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2021-08-27 23:53:31 +02:00
|
|
|
for (song in songs) {
|
|
|
|
val downloadFile = DownloadFile(song!!, save)
|
|
|
|
playList.add(currentPlayingIndex + offset, downloadFile)
|
|
|
|
offset++
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2021-08-27 23:53:31 +02:00
|
|
|
} else {
|
|
|
|
for (song in songs) {
|
|
|
|
val downloadFile = DownloadFile(song!!, save)
|
|
|
|
playList.add(downloadFile)
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
}
|
2021-08-27 23:53:31 +02:00
|
|
|
downloadListUpdateRevision++
|
|
|
|
//checkDownloads()
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
@Synchronized
|
|
|
|
fun downloadBackground(songs: List<MusicDirectory.Entry>, save: Boolean) {
|
2020-06-23 18:40:44 +02:00
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
// Because of the priority handling we add the songs in the reverse order they
|
|
|
|
// were requested, then it is correct in the end.
|
|
|
|
for (song in songs.asReversed()) {
|
|
|
|
downloadQueue.add(DownloadFile(song, save))
|
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
downloadListUpdateRevision++
|
|
|
|
//checkDownloads()
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
@Synchronized
|
|
|
|
fun shuffle() {
|
|
|
|
playList.shuffle()
|
|
|
|
|
|
|
|
// Move the current song to the top..
|
|
|
|
if (localMediaPlayer.currentPlaying != null) {
|
|
|
|
playList.remove(localMediaPlayer.currentPlaying)
|
|
|
|
playList.add(0, localMediaPlayer.currentPlaying!!)
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2021-08-27 23:53:31 +02:00
|
|
|
|
|
|
|
downloadListUpdateRevision++
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
@Synchronized
|
|
|
|
fun getDownloadFileForSong(song: MusicDirectory.Entry): DownloadFile {
|
|
|
|
for (downloadFile in playList) {
|
|
|
|
if (downloadFile.song == song) {
|
|
|
|
return downloadFile
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
}
|
2021-08-27 23:53:31 +02:00
|
|
|
for (downloadFile in activelyDownloading) {
|
|
|
|
if (downloadFile.song == song) {
|
|
|
|
return downloadFile
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
}
|
2021-08-27 23:53:31 +02:00
|
|
|
for (downloadFile in downloadQueue) {
|
|
|
|
if (downloadFile.song == song) {
|
|
|
|
return downloadFile
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
}
|
2021-08-27 23:53:31 +02:00
|
|
|
var downloadFile = downloadFileCache[song]
|
|
|
|
if (downloadFile == null) {
|
|
|
|
downloadFile = DownloadFile(song, false)
|
|
|
|
downloadFileCache.put(song, downloadFile)
|
|
|
|
}
|
|
|
|
return downloadFile
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-08-27 23:53:31 +02:00
|
|
|
@Synchronized
|
|
|
|
private fun checkShufflePlay() {
|
|
|
|
// Get users desired random playlist size
|
|
|
|
val listSize = getMaxSongs()
|
|
|
|
val wasEmpty = playList.isEmpty()
|
|
|
|
val revisionBefore = downloadListUpdateRevision
|
2020-06-23 18:40:44 +02:00
|
|
|
|
|
|
|
// First, ensure that list is at least 20 songs long.
|
2021-08-27 23:53:31 +02:00
|
|
|
val size = playList.size
|
|
|
|
if (size < listSize) {
|
|
|
|
for (song in shufflePlayBuffer[listSize - size]) {
|
|
|
|
val downloadFile = DownloadFile(song, false)
|
|
|
|
playList.add(downloadFile)
|
|
|
|
downloadListUpdateRevision++
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
}
|
2021-08-27 23:53:31 +02:00
|
|
|
|
|
|
|
val currIndex = if (localMediaPlayer.currentPlaying == null) 0 else currentPlayingIndex
|
2020-06-23 18:40:44 +02:00
|
|
|
|
|
|
|
// Only shift playlist if playing song #5 or later.
|
2021-08-27 23:53:31 +02:00
|
|
|
if (currIndex > 4) {
|
|
|
|
val songsToShift = currIndex - 2
|
|
|
|
for (song in shufflePlayBuffer[songsToShift]) {
|
|
|
|
playList.add(DownloadFile(song, false))
|
|
|
|
playList[0].cancelDownload()
|
|
|
|
playList.removeAt(0)
|
|
|
|
downloadListUpdateRevision++
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
}
|
2021-08-27 23:53:31 +02:00
|
|
|
if (revisionBefore != downloadListUpdateRevision) {
|
|
|
|
jukeboxMediaPlayer.updatePlaylist()
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
2021-08-27 23:53:31 +02:00
|
|
|
if (wasEmpty && playList.isNotEmpty()) {
|
|
|
|
if (jukeboxMediaPlayer.isEnabled) {
|
|
|
|
jukeboxMediaPlayer.skip(0, 0)
|
|
|
|
localMediaPlayer.setPlayerState(PlayerState.STARTED)
|
|
|
|
} else {
|
|
|
|
localMediaPlayer.play(playList[0])
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-08-27 23:53:31 +02:00
|
|
|
companion object {
|
|
|
|
const val PARALLEL_DOWNLOADS = 3
|
|
|
|
}
|
2020-06-23 18:40:44 +02:00
|
|
|
}
|