Add parallel downloading, better priority handling

This commit is contained in:
tzugen 2021-08-27 23:53:31 +02:00
parent b8eddb2d24
commit f9aac1ca43
No known key found for this signature in database
GPG Key ID: 61E9C34BC10EC930
12 changed files with 470 additions and 533 deletions

View File

@ -4,7 +4,6 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
@ -25,8 +24,8 @@ import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker;
import org.moire.ultrasonic.subsonic.VideoPlayer;
import org.moire.ultrasonic.util.CancellationToken;
import org.moire.ultrasonic.util.Constants;
import org.moire.ultrasonic.util.Pair;
import org.moire.ultrasonic.util.FragmentBackgroundTask;
import org.moire.ultrasonic.util.Pair;
import org.moire.ultrasonic.util.Util;
import org.moire.ultrasonic.view.EntryAdapter;
@ -78,37 +77,27 @@ public class BookmarksFragment extends Fragment {
refreshAlbumListView = view.findViewById(R.id.select_album_entries_refresh);
albumListView = view.findViewById(R.id.select_album_entries_list);
refreshAlbumListView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener()
{
@Override
public void onRefresh()
{
enableButtons();
getBookmarks();
}
refreshAlbumListView.setOnRefreshListener(() -> {
enableButtons();
getBookmarks();
});
albumListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
albumListView.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
albumListView.setOnItemClickListener((parent, view17, position, id) -> {
if (position >= 0)
{
if (position >= 0)
{
MusicDirectory.Entry entry = (MusicDirectory.Entry) parent.getItemAtPosition(position);
MusicDirectory.Entry entry = (MusicDirectory.Entry) parent.getItemAtPosition(position);
if (entry != null)
if (entry != null)
{
if (entry.isVideo())
{
if (entry.isVideo())
{
VideoPlayer.Companion.playVideo(getContext(), entry);
}
else
{
enableButtons();
}
VideoPlayer.Companion.playVideo(getContext(), entry);
}
else
{
enableButtons();
}
}
}
@ -130,58 +119,24 @@ public class BookmarksFragment extends Fragment {
playLastButton.setVisibility(View.GONE);
oreButton.setVisibility(View.GONE);
playNowButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View view)
{
playNow(getSelectedSongs(albumListView));
}
});
playNowButton.setOnClickListener(view16 -> playNow(getSelectedSongs(albumListView)));
selectButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View view)
{
selectAllOrNone();
}
selectButton.setOnClickListener(view15 -> selectAllOrNone());
pinButton.setOnClickListener(view14 -> {
downloadBackground(true);
selectAll(false, false);
});
pinButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View view)
{
downloadBackground(true);
selectAll(false, false);
}
unpinButton.setOnClickListener(view13 -> {
unpin();
selectAll(false, false);
});
unpinButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View view)
{
unpin();
selectAll(false, false);
}
downloadButton.setOnClickListener(view12 -> {
downloadBackground(false);
selectAll(false, false);
});
downloadButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View view)
{
downloadBackground(false);
selectAll(false, false);
}
});
deleteButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View view)
{
delete();
selectAll(false, false);
}
deleteButton.setOnClickListener(view1 -> {
delete();
selectAll(false, false);
});
registerForContextMenu(albumListView);
@ -230,7 +185,8 @@ public class BookmarksFragment extends Fragment {
{
if (albumListView.isItemChecked(i))
{
songs.add((MusicDirectory.Entry) albumListView.getItemAtPosition(i));
MusicDirectory.Entry song = (MusicDirectory.Entry) albumListView.getItemAtPosition(i);
if (song != null) songs.add(song);
}
}
}
@ -291,6 +247,7 @@ public class BookmarksFragment extends Fragment {
for (MusicDirectory.Entry song : selection)
{
if (song == null) continue;
DownloadFile downloadFile = mediaPlayerController.getValue().getDownloadFileForSong(song);
if (downloadFile.isWorkDone())
{
@ -326,22 +283,17 @@ public class BookmarksFragment extends Fragment {
private void downloadBackground(final boolean save, final List<MusicDirectory.Entry> songs)
{
Runnable onValid = new Runnable()
{
@Override
public void run()
{
networkAndStorageChecker.getValue().warnIfNetworkOrStorageUnavailable();
mediaPlayerController.getValue().downloadBackground(songs, save);
Runnable onValid = () -> {
networkAndStorageChecker.getValue().warnIfNetworkOrStorageUnavailable();
mediaPlayerController.getValue().downloadBackground(songs, save);
if (save)
{
Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_pinned, songs.size(), songs.size()));
}
else
{
Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_downloaded, songs.size(), songs.size()));
}
if (save)
{
Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_pinned, songs.size(), songs.size()));
}
else
{
Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_downloaded, songs.size(), songs.size()));
}
};

View File

@ -562,7 +562,7 @@ public class SearchFragment extends Fragment {
mediaPlayerController.clear();
}
mediaPlayerController.download(Collections.singletonList(song), false, false, false, false, false);
mediaPlayerController.addToPlaylist(Collections.singletonList(song), false, false, false, false, false);
if (true)
{

View File

@ -1,449 +1,428 @@
package org.moire.ultrasonic.service;
package org.moire.ultrasonic.service
import androidx.annotation.Nullable;
import org.moire.ultrasonic.domain.MusicDirectory;
import org.moire.ultrasonic.util.LRUCache;
import org.moire.ultrasonic.util.ShufflePlayBuffer;
import org.moire.ultrasonic.util.Util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import kotlin.Lazy;
import timber.log.Timber;
import static org.koin.java.KoinJavaComponent.inject;
import static org.moire.ultrasonic.domain.PlayerState.DOWNLOADING;
import static org.moire.ultrasonic.domain.PlayerState.STARTED;
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
/**
* This class is responsible for maintaining the playlist and downloading
* its items from the network to the filesystem.
*/
public class Downloader
{
public final List<DownloadFile> downloadList = new ArrayList<>();
public final List<DownloadFile> backgroundDownloadList = new ArrayList<>();
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()
@Nullable
public DownloadFile currentDownloading;
private val jukeboxMediaPlayer: JukeboxMediaPlayer by inject()
private val downloadFileCache = LRUCache<MusicDirectory.Entry, DownloadFile>(100)
private final ShufflePlayBuffer shufflePlayBuffer;
private final ExternalStorageMonitor externalStorageMonitor;
private final LocalMediaPlayer localMediaPlayer;
private var executorService: ScheduledExecutorService? = null
var downloadListUpdateRevision: Long = 0
private set
// TODO: This is a circular reference, try to remove
private final Lazy<JukeboxMediaPlayer> jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class);
private final List<DownloadFile> cleanupCandidates = new ArrayList<>();
private final LRUCache<MusicDirectory.Entry, DownloadFile> downloadFileCache = new LRUCache<>(100);
private ScheduledExecutorService executorService;
private long revision;
public Downloader(ShufflePlayBuffer shufflePlayBuffer, ExternalStorageMonitor externalStorageMonitor,
LocalMediaPlayer localMediaPlayer)
{
this.shufflePlayBuffer = shufflePlayBuffer;
this.externalStorageMonitor = externalStorageMonitor;
this.localMediaPlayer = localMediaPlayer;
val downloadChecker = Runnable {
try {
Timber.w("checking Downloads")
checkDownloadsInternal()
} catch (all: Exception) {
Timber.e(all, "checkDownloads() failed.")
}
}
public void onCreate()
{
Runnable downloadChecker = () -> {
try
{
checkDownloads();
fun onCreate() {
executorService = Executors.newSingleThreadScheduledExecutor()
executorService!!.scheduleWithFixedDelay(downloadChecker, 5, 5, TimeUnit.SECONDS)
Timber.i("Downloader created")
}
fun onDestroy() {
stop()
clearPlaylist()
clearBackground()
Timber.i("Downloader destroyed")
}
fun stop() {
if (executorService != null) executorService!!.shutdown()
Timber.i("Downloader stopped")
}
fun checkDownloads() {
executorService?.execute(downloadChecker)
}
@Synchronized
fun checkDownloadsInternal() {
if (!isExternalStoragePresent() || !externalStorageMonitor.isExternalStorageAvailable) {
return
}
if (shufflePlayBuffer.isEnabled) {
checkShufflePlay()
}
if (jukeboxMediaPlayer.isEnabled || !isNetworkConnected()) {
return
}
// 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
}
}
catch (Throwable x)
{
Timber.e(x,"checkDownloads() failed.");
}
// Check if need to preload more from playlist
val preloadCount = getPreloadCount()
// Start preloading at the current playing song
var start = if (localMediaPlayer.currentPlaying == null) 0 else currentPlayingIndex
if (start == -1) start = 0
var end = (start + preloadCount).coerceAtMost(playList.size)
// Playlist also contains played songs!!!!
for (i in start until end) {
val download = playList[i]
// Set correct priority (the lower the number, the higher the priority)
download.priority = i
// Add file to queue if not in one of the queues already.
if (!download.isWorkDone && !activelyDownloading.contains(download) && !downloadQueue.contains(download)) {
downloadQueue.add(download)
}
};
executorService = Executors.newSingleThreadScheduledExecutor();
executorService.scheduleWithFixedDelay(downloadChecker, 5, 5, TimeUnit.SECONDS);
Timber.i("Downloader created");
}
public void onDestroy()
{
stop();
clear();
clearBackground();
Timber.i("Downloader destroyed");
}
public void stop()
{
if (executorService != null) executorService.shutdown();
Timber.i("Downloader stopped");
}
public synchronized void checkDownloads()
{
if (!Util.isExternalStoragePresent() || !externalStorageMonitor.isExternalStorageAvailable())
{
return;
}
if (shufflePlayBuffer.isEnabled)
{
checkShufflePlay();
}
// Fill up active List with waiting tasks
while (activelyDownloading.size < PARALLEL_DOWNLOADS && downloadQueue.size > 0 ) {
val task = downloadQueue.remove()
activelyDownloading.add(task)
task.download()
if (jukeboxMediaPlayer.getValue().isEnabled() || !Util.isNetworkConnected())
{
return;
}
if (downloadList.isEmpty() && backgroundDownloadList.isEmpty())
{
return;
}
// 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();
// The next file on the playlist is currently downloading
// TODO: really necessary?
if (playList.indexOf(task) == 1) {
localMediaPlayer.setNextPlayerState(PlayerState.DOWNLOADING)
}
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() || (downloadList.isEmpty() && backgroundDownloadList.isEmpty())))
{
cleanup();
return;
}
}
// There is a target to download
currentDownloading = null;
int n = downloadList.size();
int preloaded = 0;
// 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++
// }
// }
//
// }
if (n != 0)
{
int start = localMediaPlayer.currentPlaying == null ? 0 : getCurrentPlayingIndex();
if (start == -1) start = 0;
@get:Synchronized
val currentPlayingIndex: Int
get() = playList.indexOf(localMediaPlayer.currentPlaying)
int i = start;
// Check all DownloadFiles on the playlist
do
{
DownloadFile downloadFile = downloadList.get(i);
if (!downloadFile.isWorkDone())
{
if (downloadFile.shouldSave() || preloaded < Util.getPreloadCount())
{
currentDownloading = downloadFile;
currentDownloading.download();
cleanupCandidates.add(currentDownloading);
if (i == (start + 1))
{
// The next file on the playlist is currently downloading
localMediaPlayer.setNextPlayerState(DOWNLOADING);
@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()
}
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 >= Util.getPreloadCount() || downloadList.isEmpty()) && !backgroundDownloadList.isEmpty())
{
for (int i = 0; i < backgroundDownloadList.size(); i++)
{
DownloadFile downloadFile = backgroundDownloadList.get(i);
if (downloadFile.isWorkDone() && (!downloadFile.shouldSave() || downloadFile.isSaved()))
{
Util.scanMedia(downloadFile.getCompleteFile());
// Don't need to keep list like active song list
backgroundDownloadList.remove(i);
revision++;
i--;
}
else if (downloadFile.isFailed() && !downloadFile.shouldRetry()) {
// Don't continue to attempt to download forever
backgroundDownloadList.remove(i);
revision++;
i--;
}
else
{
currentDownloading = downloadFile;
currentDownloading.download();
cleanupCandidates.add(currentDownloading);
break;
}
}
}
// Delete obsolete .partial and .complete files.
cleanup();
}
public synchronized int getCurrentPlayingIndex()
{
return downloadList.indexOf(localMediaPlayer.currentPlaying);
}
public long getDownloadListDuration()
{
long totalDuration = 0;
for (DownloadFile downloadFile : downloadList)
{
MusicDirectory.Entry entry = downloadFile.getSong();
if (!entry.isDirectory())
{
if (entry.getArtist() != null)
{
Integer duration = entry.getDuration();
if (duration != null)
{
totalDuration += duration;
}
}
}
return totalDuration
}
return totalDuration;
@get:Synchronized
val downloads: List<DownloadFile?>
get() {
val temp: MutableList<DownloadFile?> = ArrayList()
temp.addAll(playList)
temp.addAll(activelyDownloading)
temp.addAll(downloadQueue)
return temp.distinct()
}
@Synchronized
fun clearPlaylist() {
playList.clear()
// Cancel all active downloads with a high priority
for (download in activelyDownloading) {
if (download.priority < 100)
download.cancelDownload()
}
downloadListUpdateRevision++
}
public synchronized List<DownloadFile> getDownloads()
{
List<DownloadFile> temp = new ArrayList<>();
temp.addAll(downloadList);
temp.addAll(backgroundDownloadList);
return temp;
@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()
}
downloadListUpdateRevision++
}
public long getDownloadListUpdateRevision()
{
return revision;
}
public synchronized void clear()
{
downloadList.clear();
revision++;
if (currentDownloading != null)
{
currentDownloading.cancelDownload();
currentDownloading = null;
@Synchronized
fun clearActiveDownloads() {
// Cancel all active downloads with a low priority
for (download in activelyDownloading) {
download.cancelDownload()
}
}
private void clearBackground()
{
if (currentDownloading != null && backgroundDownloadList.contains(currentDownloading))
{
currentDownloading.cancelDownload();
currentDownloading = null;
@Synchronized
fun removeFromPlaylist(downloadFile: DownloadFile) {
if (activelyDownloading.contains(downloadFile)) {
downloadFile.cancelDownload()
}
backgroundDownloadList.clear();
playList.remove(downloadFile)
downloadListUpdateRevision++
}
public synchronized void removeDownloadFile(DownloadFile downloadFile)
{
if (downloadFile == currentDownloading)
{
currentDownloading.cancelDownload();
currentDownloading = null;
@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
}
downloadList.remove(downloadFile);
backgroundDownloadList.remove(downloadFile);
revision++;
}
public synchronized void download(List<MusicDirectory.Entry> songs, boolean save, boolean autoPlay, boolean playNext, boolean newPlaylist)
{
shufflePlayBuffer.isEnabled = false;
int offset = 1;
if (songs.isEmpty())
{
return;
if (newPlaylist) {
playList.clear()
}
if (newPlaylist)
{
downloadList.clear();
}
if (playNext)
{
if (autoPlay && getCurrentPlayingIndex() >= 0)
{
offset = 0;
if (playNext) {
if (autoPlay && currentPlayingIndex >= 0) {
offset = 0
}
for (MusicDirectory.Entry song : songs)
{
DownloadFile downloadFile = new DownloadFile(song, save);
downloadList.add(getCurrentPlayingIndex() + offset, downloadFile);
offset++;
for (song in songs) {
val downloadFile = DownloadFile(song!!, save)
playList.add(currentPlayingIndex + offset, downloadFile)
offset++
}
} else {
for (song in songs) {
val downloadFile = DownloadFile(song!!, save)
playList.add(downloadFile)
}
}
else
{
for (MusicDirectory.Entry song : songs)
{
DownloadFile downloadFile = new DownloadFile(song, save);
downloadList.add(downloadFile);
downloadListUpdateRevision++
//checkDownloads()
}
@Synchronized
fun downloadBackground(songs: List<MusicDirectory.Entry>, save: Boolean) {
// 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))
}
downloadListUpdateRevision++
//checkDownloads()
}
@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!!)
}
downloadListUpdateRevision++
}
@Synchronized
fun getDownloadFileForSong(song: MusicDirectory.Entry): DownloadFile {
for (downloadFile in playList) {
if (downloadFile.song == song) {
return downloadFile
}
}
revision++;
}
public synchronized void downloadBackground(List<MusicDirectory.Entry> songs, boolean save)
{
for (MusicDirectory.Entry song : songs)
{
DownloadFile downloadFile = new DownloadFile(song, save);
backgroundDownloadList.add(downloadFile);
}
revision++;
checkDownloads();
}
public synchronized void shuffle()
{
Collections.shuffle(downloadList);
if (localMediaPlayer.currentPlaying != null)
{
downloadList.remove(localMediaPlayer.currentPlaying);
downloadList.add(0, localMediaPlayer.currentPlaying);
}
revision++;
}
public synchronized DownloadFile getDownloadFileForSong(MusicDirectory.Entry song)
{
for (DownloadFile downloadFile : downloadList)
{
if (downloadFile.getSong().equals(song) && ((downloadFile.isDownloading() && !downloadFile.isDownloadCancelled() && downloadFile.getPartialFile().exists()) || downloadFile.isWorkDone()))
{
return downloadFile;
for (downloadFile in activelyDownloading) {
if (downloadFile.song == song) {
return downloadFile
}
}
for (DownloadFile downloadFile : backgroundDownloadList)
{
if (downloadFile.getSong().equals(song))
{
return downloadFile;
for (downloadFile in downloadQueue) {
if (downloadFile.song == song) {
return downloadFile
}
}
DownloadFile downloadFile = downloadFileCache.get(song);
if (downloadFile == null)
{
downloadFile = new DownloadFile(song, false);
downloadFileCache.put(song, downloadFile);
var downloadFile = downloadFileCache[song]
if (downloadFile == null) {
downloadFile = DownloadFile(song, false)
downloadFileCache.put(song, downloadFile)
}
return downloadFile;
return downloadFile
}
private synchronized void cleanup()
{
Iterator<DownloadFile> iterator = cleanupCandidates.iterator();
while (iterator.hasNext())
{
DownloadFile downloadFile = iterator.next();
if (downloadFile != localMediaPlayer.currentPlaying && downloadFile != currentDownloading)
{
if (downloadFile.cleanup())
{
iterator.remove();
}
}
}
}
private synchronized void checkShufflePlay()
{
@Synchronized
private fun checkShufflePlay() {
// Get users desired random playlist size
int listSize = Util.getMaxSongs();
boolean wasEmpty = downloadList.isEmpty();
long revisionBefore = revision;
val listSize = getMaxSongs()
val wasEmpty = playList.isEmpty()
val revisionBefore = downloadListUpdateRevision
// First, ensure that list is at least 20 songs long.
int size = downloadList.size();
if (size < listSize)
{
for (MusicDirectory.Entry song : shufflePlayBuffer.get(listSize - size))
{
DownloadFile downloadFile = new DownloadFile(song, false);
downloadList.add(downloadFile);
revision++;
val size = playList.size
if (size < listSize) {
for (song in shufflePlayBuffer[listSize - size]) {
val downloadFile = DownloadFile(song, false)
playList.add(downloadFile)
downloadListUpdateRevision++
}
}
int currIndex = localMediaPlayer.currentPlaying == null ? 0 : getCurrentPlayingIndex();
val currIndex = if (localMediaPlayer.currentPlaying == null) 0 else currentPlayingIndex
// Only shift playlist if playing song #5 or later.
if (currIndex > 4)
{
int songsToShift = currIndex - 2;
for (MusicDirectory.Entry song : shufflePlayBuffer.get(songsToShift))
{
downloadList.add(new DownloadFile(song, false));
downloadList.get(0).cancelDownload();
downloadList.remove(0);
revision++;
if (currIndex > 4) {
val songsToShift = currIndex - 2
for (song in shufflePlayBuffer[songsToShift]) {
playList.add(DownloadFile(song, false))
playList[0].cancelDownload()
playList.removeAt(0)
downloadListUpdateRevision++
}
}
if (revisionBefore != revision)
{
jukeboxMediaPlayer.getValue().updatePlaylist();
if (revisionBefore != downloadListUpdateRevision) {
jukeboxMediaPlayer.updatePlaylist()
}
if (wasEmpty && !downloadList.isEmpty())
{
if (jukeboxMediaPlayer.getValue().isEnabled())
{
jukeboxMediaPlayer.getValue().skip(0, 0);
localMediaPlayer.setPlayerState(STARTED);
}
else
{
localMediaPlayer.play(downloadList.get(0));
if (wasEmpty && playList.isNotEmpty()) {
if (jukeboxMediaPlayer.isEnabled) {
jukeboxMediaPlayer.skip(0, 0)
localMediaPlayer.setPlayerState(PlayerState.STARTED)
} else {
localMediaPlayer.play(playList[0])
}
}
}
companion object {
const val PARALLEL_DOWNLOADS = 3
}
}

View File

@ -456,7 +456,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
requireActivity().invalidateOptionsMenu()
}
// Scroll to current playing/downloading.
// Scroll to current playing.
private fun scrollToCurrent() {
val adapter = playlistView.adapter
if (adapter != null) {
@ -467,13 +467,6 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
return
}
}
val currentDownloading = mediaPlayerController.currentDownloading
for (i in 0 until count) {
if (currentDownloading == playlistView.getItemAtPosition(i)) {
playlistView.smoothScrollToPositionFromTop(i, 40)
return
}
}
}
}
@ -643,7 +636,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
return true
}
R.id.menu_remove -> {
mediaPlayerController.remove(song!!)
mediaPlayerController.removeFromPlaylist(song!!)
onDownloadListChanged()
return true
}

View File

@ -517,6 +517,7 @@ class TrackCollectionFragment : Fragment() {
var pinnedCount = 0
for (song in selection) {
if (song == null) continue
val downloadFile = mediaPlayerController.getDownloadFileForSong(song)
if (downloadFile.isWorkDone) {
deleteEnabled = true

View File

@ -1066,7 +1066,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
}
private fun playSongs(songs: List<MusicDirectory.Entry?>?) {
mediaPlayerController.download(
mediaPlayerController.addToPlaylist(
songs,
save = false,
autoPlay = true,
@ -1077,7 +1077,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
}
private fun playSong(song: MusicDirectory.Entry) {
mediaPlayerController.download(
mediaPlayerController.addToPlaylist(
listOf(song),
save = false,
autoPlay = false,

View File

@ -40,7 +40,7 @@ import timber.log.Timber
class DownloadFile(
val song: MusicDirectory.Entry,
private val save: Boolean
) : KoinComponent {
) : KoinComponent, Comparable<DownloadFile> {
val partialFile: File
val completeFile: File
private val saveFile: File = FileUtil.getSongFile(song)
@ -50,6 +50,8 @@ class DownloadFile(
private val desiredBitRate: Int = Util.getMaxBitRate()
var priority = 100
@Volatile
private var isPlaying = false
@ -387,6 +389,10 @@ class DownloadFile(
}
}
override fun compareTo(other: DownloadFile): Int {
return priority.compareTo(other.priority)
}
companion object {
const val MAX_RETRIES = 5
}

View File

@ -74,7 +74,7 @@ class MediaPlayerController(
autoPlay: Boolean,
newPlaylist: Boolean
) {
download(
addToPlaylist(
songs,
save = false,
autoPlay = false,
@ -167,7 +167,7 @@ class MediaPlayerController(
@Synchronized
@Suppress("LongParameterList")
fun download(
fun addToPlaylist(
songs: List<MusicDirectory.Entry?>?,
save: Boolean,
autoPlay: Boolean,
@ -175,10 +175,12 @@ class MediaPlayerController(
shuffle: Boolean,
newPlaylist: Boolean
) {
downloader.download(songs, save, autoPlay, playNext, newPlaylist)
if (songs == null) return
val filteredSongs = songs.filterNotNull()
downloader.addToPlaylist(filteredSongs, save, autoPlay, playNext, newPlaylist)
jukeboxMediaPlayer.updatePlaylist()
if (shuffle) shuffle()
val isLastTrack = (downloader.downloadList.size - 1 == downloader.currentPlayingIndex)
val isLastTrack = (downloader.playList.size - 1 == downloader.currentPlayingIndex)
if (!playNext && !autoPlay && isLastTrack) {
val mediaPlayerService = runningInstance
@ -188,15 +190,15 @@ class MediaPlayerController(
if (autoPlay) {
play(0)
} else {
if (localMediaPlayer.currentPlaying == null && downloader.downloadList.size > 0) {
localMediaPlayer.currentPlaying = downloader.downloadList[0]
downloader.downloadList[0].setPlaying(true)
if (localMediaPlayer.currentPlaying == null && downloader.playList.size > 0) {
localMediaPlayer.currentPlaying = downloader.playList[0]
downloader.playList[0].setPlaying(true)
}
downloader.checkDownloads()
}
downloadQueueSerializer.serializeDownloadQueue(
downloader.downloadList,
downloader.playList,
downloader.currentPlayingIndex,
playerPosition
)
@ -204,9 +206,11 @@ class MediaPlayerController(
@Synchronized
fun downloadBackground(songs: List<MusicDirectory.Entry?>?, save: Boolean) {
downloader.downloadBackground(songs, save)
if (songs == null) return
val filteredSongs = songs.filterNotNull()
downloader.downloadBackground(filteredSongs, save)
downloadQueueSerializer.serializeDownloadQueue(
downloader.downloadList,
downloader.playList,
downloader.currentPlayingIndex,
playerPosition
)
@ -237,7 +241,7 @@ class MediaPlayerController(
fun shuffle() {
downloader.shuffle()
downloadQueueSerializer.serializeDownloadQueue(
downloader.downloadList,
downloader.playList,
downloader.currentPlayingIndex,
playerPosition
)
@ -267,10 +271,10 @@ class MediaPlayerController(
mediaPlayerService.clear(serialize)
} else {
// If no MediaPlayerService is available, just empty the playlist
downloader.clear()
downloader.clearPlaylist()
if (serialize) {
downloadQueueSerializer.serializeDownloadQueue(
downloader.downloadList,
downloader.playList,
downloader.currentPlayingIndex, playerPosition
)
}
@ -281,7 +285,7 @@ class MediaPlayerController(
@Synchronized
fun clearIncomplete() {
reset()
val iterator = downloader.downloadList.iterator()
val iterator = downloader.playList.iterator()
while (iterator.hasNext()) {
val downloadFile = iterator.next()
if (!downloadFile.isCompleteFileAvailable) {
@ -290,7 +294,7 @@ class MediaPlayerController(
}
downloadQueueSerializer.serializeDownloadQueue(
downloader.downloadList,
downloader.playList,
downloader.currentPlayingIndex,
playerPosition
)
@ -299,15 +303,15 @@ class MediaPlayerController(
}
@Synchronized
fun remove(downloadFile: DownloadFile) {
fun removeFromPlaylist(downloadFile: DownloadFile) {
if (downloadFile == localMediaPlayer.currentPlaying) {
reset()
currentPlaying = null
}
downloader.removeDownloadFile(downloadFile)
downloader.removeFromPlaylist(downloadFile)
downloadQueueSerializer.serializeDownloadQueue(
downloader.downloadList,
downloader.playList,
downloader.currentPlayingIndex,
playerPosition
)
@ -321,15 +325,17 @@ class MediaPlayerController(
}
@Synchronized
// TODO: Make it require not null
fun delete(songs: List<MusicDirectory.Entry?>) {
for (song in songs) {
for (song in songs.filterNotNull()) {
downloader.getDownloadFileForSong(song).delete()
}
}
@Synchronized
// TODO: Make it require not null
fun unpin(songs: List<MusicDirectory.Entry?>) {
for (song in songs) {
for (song in songs.filterNotNull()) {
downloader.getDownloadFileForSong(song).unpin()
}
}
@ -357,12 +363,12 @@ class MediaPlayerController(
when (repeatMode) {
RepeatMode.SINGLE, RepeatMode.OFF -> {
// Play next if exists
if (index + 1 >= 0 && index + 1 < downloader.downloadList.size) {
if (index + 1 >= 0 && index + 1 < downloader.playList.size) {
play(index + 1)
}
}
RepeatMode.ALL -> {
play((index + 1) % downloader.downloadList.size)
play((index + 1) % downloader.playList.size)
}
else -> {
}
@ -409,7 +415,7 @@ class MediaPlayerController(
reset()
// Cancel current download, if necessary.
downloader.currentDownloading?.cancelDownload()
downloader.clearActiveDownloads()
} else {
jukeboxMediaPlayer.stopJukeboxService()
}
@ -489,16 +495,13 @@ class MediaPlayerController(
}
val playlistSize: Int
get() = downloader.downloadList.size
get() = downloader.playList.size
val currentPlayingNumberOnPlaylist: Int
get() = downloader.currentPlayingIndex
val currentDownloading: DownloadFile?
get() = downloader.currentDownloading
val playList: List<DownloadFile>
get() = downloader.downloadList
get() = downloader.playList
val playListUpdateRevision: Long
get() = downloader.downloadListUpdateRevision
@ -506,7 +509,7 @@ class MediaPlayerController(
val playListDuration: Long
get() = downloader.downloadListDuration
fun getDownloadFileForSong(song: MusicDirectory.Entry?): DownloadFile {
fun getDownloadFileForSong(song: MusicDirectory.Entry): DownloadFile {
return downloader.getDownloadFileForSong(song)
}

View File

@ -76,7 +76,7 @@ class MediaPlayerLifecycleSupport : KoinComponent {
// Work-around: Serialize again, as the restore() method creates a
// serialization without current playing info.
downloadQueueSerializer.serializeDownloadQueue(
downloader.downloadList,
downloader.playList,
downloader.currentPlayingIndex,
mediaPlayerController.playerPosition
)
@ -94,7 +94,7 @@ class MediaPlayerLifecycleSupport : KoinComponent {
if (!created) return
downloadQueueSerializer.serializeDownloadQueueNow(
downloader.downloadList,
downloader.playList,
downloader.currentPlayingIndex,
mediaPlayerController.playerPosition
)

View File

@ -88,7 +88,7 @@ class MediaPlayerService : Service() {
localMediaPlayer.onPrepared = {
downloadQueueSerializer.serializeDownloadQueue(
downloader.downloadList,
downloader.playList,
downloader.currentPlayingIndex,
playerPosition
)
@ -189,7 +189,7 @@ class MediaPlayerService : Service() {
@Synchronized
fun setCurrentPlaying(currentPlayingIndex: Int) {
try {
localMediaPlayer.setCurrentPlaying(downloader.downloadList[currentPlayingIndex])
localMediaPlayer.setCurrentPlaying(downloader.playList[currentPlayingIndex])
} catch (ignored: IndexOutOfBoundsException) {
}
}
@ -208,7 +208,7 @@ class MediaPlayerService : Service() {
if (index != -1) {
when (repeatMode) {
RepeatMode.OFF -> index += 1
RepeatMode.ALL -> index = (index + 1) % downloader.downloadList.size
RepeatMode.ALL -> index = (index + 1) % downloader.playList.size
RepeatMode.SINGLE -> {
}
else -> {
@ -217,8 +217,8 @@ class MediaPlayerService : Service() {
}
localMediaPlayer.clearNextPlaying(false)
if (index < downloader.downloadList.size && index != -1) {
localMediaPlayer.setNextPlaying(downloader.downloadList[index])
if (index < downloader.playList.size && index != -1) {
localMediaPlayer.setNextPlaying(downloader.playList[index])
} else {
localMediaPlayer.clearNextPlaying(true)
}
@ -271,7 +271,7 @@ class MediaPlayerService : Service() {
@Synchronized
fun play(index: Int, start: Boolean) {
Timber.v("play requested for %d", index)
if (index < 0 || index >= downloader.downloadList.size) {
if (index < 0 || index >= downloader.playList.size) {
resetPlayback()
} else {
setCurrentPlaying(index)
@ -280,7 +280,7 @@ class MediaPlayerService : Service() {
jukeboxMediaPlayer.skip(index, 0)
localMediaPlayer.setPlayerState(PlayerState.STARTED)
} else {
localMediaPlayer.play(downloader.downloadList[index])
localMediaPlayer.play(downloader.playList[index])
}
}
downloader.checkDownloads()
@ -293,7 +293,7 @@ class MediaPlayerService : Service() {
localMediaPlayer.reset()
localMediaPlayer.setCurrentPlaying(null)
downloadQueueSerializer.serializeDownloadQueue(
downloader.downloadList,
downloader.playList,
downloader.currentPlayingIndex, playerPosition
)
}
@ -395,7 +395,7 @@ class MediaPlayerService : Service() {
if (playerState === PlayerState.PAUSED) {
downloadQueueSerializer.serializeDownloadQueue(
downloader.downloadList, downloader.currentPlayingIndex, playerPosition
downloader.playList, downloader.currentPlayingIndex, playerPosition
)
}
@ -408,8 +408,8 @@ class MediaPlayerService : Service() {
Util.broadcastPlaybackStatusChange(context, playerState)
Util.broadcastA2dpPlayStatusChange(
context, playerState, song,
downloader.downloadList.size + downloader.backgroundDownloadList.size,
downloader.downloadList.indexOf(currentPlaying) + 1, playerPosition
downloader.playList.size,
downloader.playList.indexOf(currentPlaying) + 1, playerPosition
)
// Update widget
@ -455,7 +455,7 @@ class MediaPlayerService : Service() {
if (index != -1) {
when (repeatMode) {
RepeatMode.OFF -> {
if (index + 1 < 0 || index + 1 >= downloader.downloadList.size) {
if (index + 1 < 0 || index + 1 >= downloader.playList.size) {
if (Util.getShouldClearPlaylist()) {
clear(true)
jukeboxMediaPlayer.updatePlaylist()
@ -466,7 +466,7 @@ class MediaPlayerService : Service() {
}
}
RepeatMode.ALL -> {
play((index + 1) % downloader.downloadList.size)
play((index + 1) % downloader.playList.size)
}
RepeatMode.SINGLE -> play(index)
else -> {
@ -480,12 +480,12 @@ class MediaPlayerService : Service() {
@Synchronized
fun clear(serialize: Boolean) {
localMediaPlayer.reset()
downloader.clear()
downloader.clearPlaylist()
localMediaPlayer.setCurrentPlaying(null)
setNextPlaying()
if (serialize) {
downloadQueueSerializer.serializeDownloadQueue(
downloader.downloadList,
downloader.playList,
downloader.currentPlayingIndex, playerPosition
)
}

View File

@ -39,7 +39,7 @@ class DownloadHandler(
mediaPlayerController.clear()
}
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
mediaPlayerController.download(
mediaPlayerController.addToPlaylist(
songs,
save,
autoPlay,
@ -297,7 +297,7 @@ class DownloadHandler(
if (unpin) {
mediaPlayerController.unpin(songs)
} else {
mediaPlayerController.download(
mediaPlayerController.addToPlaylist(
songs,
save,
autoPlay,

View File

@ -218,10 +218,13 @@ class SongView(context: Context) : UpdateView(context), Checkable, KoinComponent
override fun updateBackground() {}
@Synchronized
public override fun update() {
updateBackground()
downloadFile = mediaPlayerController.getDownloadFileForSong(entry)
val song = entry ?: return
downloadFile = mediaPlayerController.getDownloadFileForSong(song)
updateDownloadStatus(downloadFile!!)