You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
477 lines
13 KiB
477 lines
13 KiB
/*
|
|
This file is part of Airsonic.
|
|
|
|
Airsonic is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Airsonic is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Airsonic. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
Copyright 2016 (C) Airsonic Authors
|
|
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
|
|
*/
|
|
package org.airsonic.player.domain;
|
|
|
|
import org.apache.commons.lang.StringUtils;
|
|
|
|
import java.util.*;
|
|
|
|
/**
|
|
* A play queue is a list of music files that are associated to a remote player.
|
|
*
|
|
* @author Sindre Mehus
|
|
*/
|
|
public class PlayQueue {
|
|
|
|
private List<MediaFile> files = new ArrayList<MediaFile>();
|
|
private boolean repeatEnabled;
|
|
private String name = "(unnamed)";
|
|
private Status status = Status.PLAYING;
|
|
|
|
private RandomSearchCriteria randomSearchCriteria;
|
|
private InternetRadio internetRadio;
|
|
|
|
/**
|
|
* The index of the current song, or -1 is the end of the playlist is reached.
|
|
* Note that both the index and the playlist size can be zero.
|
|
*/
|
|
private int index = 0;
|
|
|
|
/**
|
|
* Used for undo functionality.
|
|
*/
|
|
private List<MediaFile> filesBackup = new ArrayList<MediaFile>();
|
|
private int indexBackup = 0;
|
|
|
|
/**
|
|
* Returns the user-defined name of the playlist.
|
|
*
|
|
* @return The name of the playlist, or <code>null</code> if no name has been assigned.
|
|
*/
|
|
public synchronized String getName() {
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* Sets the user-defined name of the playlist.
|
|
*
|
|
* @param name The name of the playlist.
|
|
*/
|
|
public synchronized void setName(String name) {
|
|
this.name = name;
|
|
}
|
|
|
|
/**
|
|
* Returns the current song in the playlist.
|
|
*
|
|
* @return The current song in the playlist, or <code>null</code> if no current song exists.
|
|
*/
|
|
public synchronized MediaFile getCurrentFile() {
|
|
if (index == -1 || index == 0 && size() == 0) {
|
|
setStatus(Status.STOPPED);
|
|
return null;
|
|
} else {
|
|
MediaFile file = files.get(index);
|
|
|
|
// Remove file from playlist if it doesn't exist.
|
|
if (!file.exists()) {
|
|
files.remove(index);
|
|
index = Math.max(0, Math.min(index, size() - 1));
|
|
return getCurrentFile();
|
|
}
|
|
|
|
return file;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns all music files in the playlist.
|
|
*
|
|
* @return All music files in the playlist.
|
|
*/
|
|
public synchronized List<MediaFile> getFiles() {
|
|
return files;
|
|
}
|
|
|
|
/**
|
|
* Returns the music file at the given index.
|
|
*
|
|
* @param index The index.
|
|
* @return The music file at the given index.
|
|
* @throws IndexOutOfBoundsException If the index is out of range.
|
|
*/
|
|
public synchronized MediaFile getFile(int index) {
|
|
return files.get(index);
|
|
}
|
|
|
|
/**
|
|
* Skip to the next song in the playlist.
|
|
*/
|
|
public synchronized void next() {
|
|
index++;
|
|
|
|
// Reached the end?
|
|
if (index >= size()) {
|
|
index = isRepeatEnabled() ? 0 : -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the number of songs in the playlists.
|
|
*
|
|
* @return The number of songs in the playlists.
|
|
*/
|
|
public synchronized int size() {
|
|
return files.size();
|
|
}
|
|
|
|
/**
|
|
* Returns whether the playlist is empty.
|
|
*
|
|
* @return Whether the playlist is empty.
|
|
*/
|
|
public synchronized boolean isEmpty() {
|
|
return files.isEmpty();
|
|
}
|
|
|
|
/**
|
|
* Returns the index of the current song.
|
|
*
|
|
* @return The index of the current song, or -1 if the end of the playlist is reached.
|
|
*/
|
|
public synchronized int getIndex() {
|
|
return index;
|
|
}
|
|
|
|
/**
|
|
* Sets the index of the current song.
|
|
*
|
|
* @param index The index of the current song.
|
|
*/
|
|
public synchronized void setIndex(int index) {
|
|
makeBackup();
|
|
this.index = Math.max(0, Math.min(index, size() - 1));
|
|
setStatus(Status.PLAYING);
|
|
}
|
|
|
|
/**
|
|
* Adds one or more music file to the playlist.
|
|
*
|
|
* @param mediaFiles The music files to add.
|
|
* @param index Where to add them.
|
|
*/
|
|
public synchronized void addFilesAt(Iterable<MediaFile> mediaFiles, int index) {
|
|
makeBackup();
|
|
for (MediaFile mediaFile : mediaFiles) {
|
|
files.add(index, mediaFile);
|
|
index++;
|
|
}
|
|
setStatus(Status.PLAYING);
|
|
}
|
|
|
|
/**
|
|
* Adds one or more music file to the playlist.
|
|
*
|
|
* @param append Whether existing songs in the playlist should be kept.
|
|
* @param mediaFiles The music files to add.
|
|
*/
|
|
public synchronized void addFiles(boolean append, Iterable<MediaFile> mediaFiles) {
|
|
makeBackup();
|
|
if (!append) {
|
|
index = 0;
|
|
files.clear();
|
|
}
|
|
for (MediaFile mediaFile : mediaFiles) {
|
|
files.add(mediaFile);
|
|
}
|
|
setStatus(Status.PLAYING);
|
|
}
|
|
|
|
/**
|
|
* Convenience method, equivalent to {@link #addFiles(boolean, Iterable)}.
|
|
*/
|
|
public synchronized void addFiles(boolean append, MediaFile... mediaFiles) {
|
|
addFiles(append, Arrays.asList(mediaFiles));
|
|
}
|
|
|
|
/**
|
|
* Removes the music file at the given index.
|
|
*
|
|
* @param index The playlist index.
|
|
*/
|
|
public synchronized void removeFileAt(int index) {
|
|
makeBackup();
|
|
index = Math.max(0, Math.min(index, size() - 1));
|
|
if (this.index > index) {
|
|
this.index--;
|
|
}
|
|
files.remove(index);
|
|
|
|
this.index = Math.max(0, Math.min(this.index, size() - 1));
|
|
}
|
|
|
|
/**
|
|
* Clears the playlist.
|
|
*/
|
|
public synchronized void clear() {
|
|
makeBackup();
|
|
files.clear();
|
|
setRandomSearchCriteria(null);
|
|
setInternetRadio(null);
|
|
index = 0;
|
|
}
|
|
|
|
/**
|
|
* Shuffles the playlist.
|
|
*/
|
|
public synchronized void shuffle() {
|
|
makeBackup();
|
|
MediaFile currentFile = getCurrentFile();
|
|
Collections.shuffle(files);
|
|
if (currentFile != null) {
|
|
Collections.swap(files, files.indexOf(currentFile), 0);
|
|
index = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sorts the playlist according to the given sort order.
|
|
*/
|
|
public synchronized void sort(final SortOrder sortOrder) {
|
|
makeBackup();
|
|
MediaFile currentFile = getCurrentFile();
|
|
|
|
Comparator<MediaFile> comparator = (a, b) -> {
|
|
switch (sortOrder) {
|
|
case TRACK:
|
|
Integer trackA = a.getTrackNumber();
|
|
Integer trackB = b.getTrackNumber();
|
|
if (trackA == null) {
|
|
trackA = 0;
|
|
}
|
|
if (trackB == null) {
|
|
trackB = 0;
|
|
}
|
|
return trackA.compareTo(trackB);
|
|
|
|
case ARTIST:
|
|
String artistA = StringUtils.trimToEmpty(a.getArtist());
|
|
String artistB = StringUtils.trimToEmpty(b.getArtist());
|
|
return artistA.compareTo(artistB);
|
|
|
|
case ALBUM:
|
|
String albumA = StringUtils.trimToEmpty(a.getAlbumName());
|
|
String albumB = StringUtils.trimToEmpty(b.getAlbumName());
|
|
return albumA.compareTo(albumB);
|
|
default:
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
files.sort(comparator);
|
|
if (currentFile != null) {
|
|
index = files.indexOf(currentFile);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Rearranges the playlist using the provided indexes.
|
|
*/
|
|
public synchronized void rearrange(int[] indexes) {
|
|
makeBackup();
|
|
if (indexes == null || indexes.length != size()) {
|
|
return;
|
|
}
|
|
|
|
MediaFile[] newFiles = new MediaFile[files.size()];
|
|
for (int i = 0; i < indexes.length; i++) {
|
|
newFiles[i] = files.get(indexes[i]);
|
|
}
|
|
for (int i = 0; i < indexes.length; i++) {
|
|
if (index == indexes[i]) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
files.clear();
|
|
files.addAll(Arrays.asList(newFiles));
|
|
}
|
|
|
|
/**
|
|
* Moves the song at the given index one step up.
|
|
*
|
|
* @param index The playlist index.
|
|
*/
|
|
public synchronized void moveUp(int index) {
|
|
moveDown(index - 1);
|
|
}
|
|
|
|
/**
|
|
* Moves the song at the given index one step down.
|
|
*
|
|
* @param index The playlist index.
|
|
*/
|
|
public synchronized void moveDown(int index) {
|
|
makeBackup();
|
|
if (index < 0 || index >= size() - 1) {
|
|
return;
|
|
}
|
|
Collections.swap(files, index, index + 1);
|
|
|
|
if (this.index == index) {
|
|
this.index++;
|
|
} else if (this.index == index + 1) {
|
|
this.index--;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns whether the playlist is repeating.
|
|
*
|
|
* @return Whether the playlist is repeating.
|
|
*/
|
|
public synchronized boolean isRepeatEnabled() {
|
|
return repeatEnabled;
|
|
}
|
|
|
|
/**
|
|
* Sets whether the playlist is repeating.
|
|
*
|
|
* @param repeatEnabled Whether the playlist is repeating.
|
|
*/
|
|
public synchronized void setRepeatEnabled(boolean repeatEnabled) {
|
|
this.repeatEnabled = repeatEnabled;
|
|
}
|
|
|
|
/**
|
|
* Returns whether the play queue is in shuffle radio mode.
|
|
*
|
|
* @return Whether the play queue is a shuffle radio mode.
|
|
*/
|
|
public synchronized boolean isShuffleRadioEnabled() {
|
|
return this.randomSearchCriteria != null;
|
|
}
|
|
|
|
/**
|
|
* Returns whether the play queue is a internet radio mode.
|
|
*
|
|
* @return Whether the play queue is a internet radio mode.
|
|
*/
|
|
public synchronized boolean isInternetRadioEnabled() {
|
|
return this.internetRadio != null;
|
|
}
|
|
|
|
/**
|
|
* Revert the last operation.
|
|
*/
|
|
public synchronized void undo() {
|
|
List<MediaFile> filesTmp = new ArrayList<MediaFile>(files);
|
|
int indexTmp = index;
|
|
|
|
index = indexBackup;
|
|
files = filesBackup;
|
|
|
|
indexBackup = indexTmp;
|
|
filesBackup = filesTmp;
|
|
}
|
|
|
|
/**
|
|
* Returns the playlist status.
|
|
*
|
|
* @return The playlist status.
|
|
*/
|
|
public synchronized Status getStatus() {
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Sets the playlist status.
|
|
*
|
|
* @param status The playlist status.
|
|
*/
|
|
public synchronized void setStatus(Status status) {
|
|
this.status = status;
|
|
if (index == -1) {
|
|
index = Math.max(0, Math.min(index, size() - 1));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the current internet radio
|
|
*
|
|
* @param internetRadio An internet radio, or <code>null</code> if this is not an internet radio playlist
|
|
*/
|
|
public void setInternetRadio(InternetRadio internetRadio) {
|
|
this.internetRadio = internetRadio;
|
|
}
|
|
|
|
/**
|
|
* Gets the current internet radio
|
|
*
|
|
* @return The current internet radio, or <code>null</code> if this is not an internet radio playlist
|
|
*/
|
|
public InternetRadio getInternetRadio() {
|
|
return internetRadio;
|
|
}
|
|
|
|
/**
|
|
* Returns the criteria used to generate this random playlist
|
|
*
|
|
* @return The search criteria, or <code>null</code> if this is not a random playlist.
|
|
*/
|
|
public synchronized RandomSearchCriteria getRandomSearchCriteria() {
|
|
return randomSearchCriteria;
|
|
}
|
|
|
|
/**
|
|
* Sets the criteria used to generate this random playlist
|
|
*
|
|
* @param randomSearchCriteria The search criteria, or <code>null</code> if this is not a random playlist.
|
|
*/
|
|
public synchronized void setRandomSearchCriteria(RandomSearchCriteria randomSearchCriteria) {
|
|
this.randomSearchCriteria = randomSearchCriteria;
|
|
}
|
|
|
|
/**
|
|
* Returns the total length in bytes.
|
|
*
|
|
* @return The total length in bytes.
|
|
*/
|
|
public synchronized long length() {
|
|
long length = 0;
|
|
for (MediaFile mediaFile : files) {
|
|
length += mediaFile.getFileSize();
|
|
}
|
|
return length;
|
|
}
|
|
|
|
private void makeBackup() {
|
|
filesBackup = new ArrayList<MediaFile>(files);
|
|
indexBackup = index;
|
|
}
|
|
|
|
/**
|
|
* Playlist status.
|
|
*/
|
|
public enum Status {
|
|
PLAYING,
|
|
STOPPED
|
|
}
|
|
|
|
/**
|
|
* Playlist sort order.
|
|
*/
|
|
public enum SortOrder {
|
|
TRACK,
|
|
ARTIST,
|
|
ALBUM
|
|
}
|
|
}
|
|
|