Provides the interface the ability to load up songs from the file system.
This commit is contained in:
@@ -15,7 +15,8 @@ public class Album implements Comparable<Album>
|
||||
public int year;
|
||||
public String[] genres;
|
||||
public int totalTracks;
|
||||
|
||||
public int totalDiscs;
|
||||
|
||||
@Override
|
||||
public int compareTo(Album o)
|
||||
{
|
||||
|
||||
@@ -4,94 +4,112 @@
|
||||
|
||||
package edu.regis.universeplayer.data;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A song provider that serves as a central point for any and all song providers, caching the results in memory for quick and easy access.
|
||||
* A song provider that serves as a central point for any and all song
|
||||
* providers, caching the results in memory for quick and easy access.
|
||||
*
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class CompiledSongProvider implements SongProvider
|
||||
public class CompiledSongProvider implements SongProvider<Song>
|
||||
{
|
||||
private final LinkedList<UpdateListener> listeners = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* A set of all providers we pull from
|
||||
*/
|
||||
private HashMap<SongProvider, Set<Song>> providers = new HashMap<>();
|
||||
|
||||
private final HashMap<SongProvider<?>, Set<Song>> providers = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The collection of update listeners for each provider.
|
||||
*/
|
||||
private final HashMap<SongProvider<?>, UpdateListener> updateListener = new HashMap<>();
|
||||
|
||||
/**
|
||||
* A cache of all albums used.
|
||||
*/
|
||||
private HashMap<Album, Set<Song>> cachedAlbums = new HashMap<>();
|
||||
|
||||
private final HashMap<Album, Set<Song>> cachedAlbums = new HashMap<>();
|
||||
|
||||
/**
|
||||
* A cache of all album names.
|
||||
*/
|
||||
private HashMap<String, Album> cachedAlbumNames = new HashMap<>();
|
||||
|
||||
private final HashMap<String, Album> cachedAlbumNames = new HashMap<>();
|
||||
|
||||
/**
|
||||
* A cache of all songs used.
|
||||
*/
|
||||
private HashSet<Song> cachedSongs = new HashSet<>();
|
||||
|
||||
private final HashSet<Song> cachedSongs = new HashSet<>();
|
||||
|
||||
/**
|
||||
* A cache of all song artists used.
|
||||
*/
|
||||
private HashMap<String, Set<Song>> cachedArtists = new HashMap<>();
|
||||
|
||||
private final HashMap<String, Set<Song>> cachedArtists = new HashMap<>();
|
||||
|
||||
/**
|
||||
* A cache of all album artists used.
|
||||
*/
|
||||
private HashMap<String, Set<Album>> cachedAlbumArtists = new HashMap<>();
|
||||
|
||||
private final HashMap<String, Set<Album>> cachedAlbumArtists = new HashMap<>();
|
||||
|
||||
/**
|
||||
* A cache of all genres used.
|
||||
*/
|
||||
private HashMap<String, Set<Album>> cachedGenres = new HashMap<>();
|
||||
|
||||
private final HashMap<String, Set<Album>> cachedGenres = new HashMap<>();
|
||||
|
||||
/**
|
||||
* A cache of all years used.
|
||||
*/
|
||||
private HashMap<Integer, Set<Album>> cachedYears = new HashMap<>();
|
||||
|
||||
private final HashMap<Integer, Set<Album>> cachedYears = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new CompiledSongProvider containing a set of existing providers.
|
||||
*
|
||||
* @param providers - Providers to add.
|
||||
*/
|
||||
public CompiledSongProvider(SongProvider... providers)
|
||||
public CompiledSongProvider(SongProvider<?>... providers)
|
||||
{
|
||||
for (SongProvider provider : providers)
|
||||
for (SongProvider<?> provider : providers)
|
||||
{
|
||||
this.addProvider(provider);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a provider to the list
|
||||
*
|
||||
* @param provider - The provider to add.
|
||||
*/
|
||||
public void addProvider(SongProvider provider)
|
||||
public void addProvider(SongProvider<?> provider)
|
||||
{
|
||||
UpdateListener listener;
|
||||
if (!this.providers.containsKey(provider))
|
||||
{
|
||||
this.providers.put(provider, new HashSet<>(provider.getSongs()));
|
||||
listener = (song, totalSongs, updateText) -> {
|
||||
/*
|
||||
* Resets the cache.
|
||||
*/
|
||||
if (song == totalSongs || totalSongs == 0)
|
||||
{
|
||||
this.removeFromCache(this.providers.get(provider));
|
||||
this.addToCache(provider);
|
||||
}
|
||||
this.triggerUpdateListeners();
|
||||
};
|
||||
provider.addUpdateListener(listener);
|
||||
this.addToCache(provider);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Caches all the songs contained in a provider.
|
||||
*
|
||||
* @param provider - The provider to cache.
|
||||
*/
|
||||
private void addToCache(SongProvider provider)
|
||||
private <T extends Song> void addToCache(SongProvider<T> provider)
|
||||
{
|
||||
for (Song song : provider.getSongs())
|
||||
for (T song : provider.getSongs())
|
||||
{
|
||||
if (this.cachedSongs.add(song))
|
||||
{
|
||||
@@ -141,25 +159,25 @@ public class CompiledSongProvider implements SongProvider
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes a provider from the compilation.
|
||||
*
|
||||
* @param provider - The provider to remove.
|
||||
*/
|
||||
public void removeProvider(SongProvider provider)
|
||||
public void removeProvider(SongProvider<?> provider)
|
||||
{
|
||||
this.removeFromCache(provider);
|
||||
this.removeFromCache(this.providers.remove(provider));
|
||||
provider.removeUpdateListener(this.updateListener.remove(provider));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all songs from a provider from a cache.
|
||||
*
|
||||
* @param provider - The provider to move out.
|
||||
* @param songs - The songs to move out.
|
||||
*/
|
||||
private void removeFromCache(SongProvider provider)
|
||||
private void removeFromCache(Set<Song> songs)
|
||||
{
|
||||
Set<Song> songs = this.providers.remove(provider);
|
||||
if (songs != null)
|
||||
{
|
||||
for (Song song : songs)
|
||||
@@ -206,7 +224,7 @@ public class CompiledSongProvider implements SongProvider
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains all albums within the collection.
|
||||
*
|
||||
@@ -217,7 +235,7 @@ public class CompiledSongProvider implements SongProvider
|
||||
{
|
||||
return this.cachedAlbums.keySet();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains all songs within the collection.
|
||||
*
|
||||
@@ -228,7 +246,7 @@ public class CompiledSongProvider implements SongProvider
|
||||
{
|
||||
return this.cachedSongs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains a list of all artists.
|
||||
*
|
||||
@@ -239,7 +257,7 @@ public class CompiledSongProvider implements SongProvider
|
||||
{
|
||||
return this.cachedArtists.keySet();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains a list of all album artists.
|
||||
*
|
||||
@@ -250,7 +268,7 @@ public class CompiledSongProvider implements SongProvider
|
||||
{
|
||||
return this.cachedAlbumArtists.keySet();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains a list of all genres.
|
||||
*
|
||||
@@ -261,7 +279,7 @@ public class CompiledSongProvider implements SongProvider
|
||||
{
|
||||
return this.cachedGenres.keySet();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains a list of all years that have albums.
|
||||
*
|
||||
@@ -272,7 +290,7 @@ public class CompiledSongProvider implements SongProvider
|
||||
{
|
||||
return this.cachedYears.keySet();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains all songs from an album.
|
||||
*
|
||||
@@ -284,7 +302,7 @@ public class CompiledSongProvider implements SongProvider
|
||||
{
|
||||
return this.cachedAlbums.get(album);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains all songs written by a given artist.
|
||||
*
|
||||
@@ -297,7 +315,7 @@ public class CompiledSongProvider implements SongProvider
|
||||
{
|
||||
return this.cachedArtists.get(artist);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains an album by a specific name.
|
||||
*
|
||||
@@ -310,7 +328,7 @@ public class CompiledSongProvider implements SongProvider
|
||||
{
|
||||
return this.cachedAlbumNames.get(name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains all albums that were written by a certain artist.
|
||||
*
|
||||
@@ -322,7 +340,7 @@ public class CompiledSongProvider implements SongProvider
|
||||
{
|
||||
return this.cachedAlbumArtists.get(artist);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains all albums that match a certain genre
|
||||
*
|
||||
@@ -334,7 +352,7 @@ public class CompiledSongProvider implements SongProvider
|
||||
{
|
||||
return this.cachedGenres.get(genre);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains all albums that were released a certain year.
|
||||
*
|
||||
@@ -346,4 +364,69 @@ public class CompiledSongProvider implements SongProvider
|
||||
{
|
||||
return this.cachedYears.get(year);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUpdateProgress()
|
||||
{
|
||||
int totalUpdate = 0;
|
||||
for (SongProvider<?> provider : this.providers.keySet())
|
||||
{
|
||||
totalUpdate += provider.getUpdateProgress();
|
||||
}
|
||||
return totalUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalUpdateSongs()
|
||||
{
|
||||
int totalUpdate = 0;
|
||||
for (SongProvider<?> provider : this.providers.keySet())
|
||||
{
|
||||
if (provider.getTotalUpdateSongs() == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
totalUpdate += provider.getTotalUpdateSongs();
|
||||
}
|
||||
}
|
||||
return totalUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUpdateText()
|
||||
{
|
||||
for (SongProvider<?> provider: this.providers.keySet())
|
||||
{
|
||||
if (provider.getUpdateText() != null)
|
||||
{
|
||||
return provider.getUpdateText();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUpdateListener(UpdateListener listener)
|
||||
{
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdateListener(UpdateListener listener)
|
||||
{
|
||||
this.listeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers all update listeners.
|
||||
*/
|
||||
protected void triggerUpdateListeners()
|
||||
{
|
||||
for (UpdateListener listener : this.listeners)
|
||||
{
|
||||
listener.onUpdate(this.getUpdateProgress(), this.getTotalUpdateSongs(), this.getUpdateText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import java.io.File;
|
||||
/**
|
||||
* This song represents a song found on the local file system.
|
||||
*/
|
||||
public abstract class LocalSong extends Song
|
||||
public class LocalSong extends Song
|
||||
{
|
||||
public File file;
|
||||
public String type;
|
||||
public String codec;
|
||||
}
|
||||
|
||||
@@ -4,48 +4,473 @@
|
||||
|
||||
package edu.regis.universeplayer.data;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class LocalSongProvider implements SongProvider
|
||||
public class LocalSongProvider implements SongProvider<LocalSong>
|
||||
{
|
||||
private File source;
|
||||
|
||||
private HashMap<File, Song> songs = new HashMap<>();
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(LocalSongProvider.class);
|
||||
private static final HashSet<String> formats = new HashSet<>();
|
||||
private static final HashSet<String> codecs = new HashSet<>();
|
||||
|
||||
private final File source;
|
||||
|
||||
private final HashSet<LocalSong> songs = new HashSet<>();
|
||||
private final HashMap<String, Album> albums = new HashMap<>();
|
||||
/**
|
||||
* A cache of all song artists.
|
||||
*/
|
||||
private final HashSet<String> artists = new HashSet<>();
|
||||
/**
|
||||
* A cache of all album genres.
|
||||
*/
|
||||
private final HashSet<String> genres = new HashSet<>();
|
||||
/**
|
||||
* A cache of all album artists.
|
||||
*/
|
||||
private final HashSet<String> albumArtists = new HashSet<>();
|
||||
/**
|
||||
* A cache of all album release years.
|
||||
*/
|
||||
private final HashSet<Integer> years = new HashSet<>();
|
||||
|
||||
private int updatedSongs;
|
||||
private int totalUpdate;
|
||||
private final LinkedList<UpdateListener> listeners = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* Obtains all formats supported by FFMPEG. Note that this list includes
|
||||
* video and image formats as well.
|
||||
*
|
||||
* @return A string array of all supported file formats.
|
||||
*/
|
||||
public static Set<String> getFormats()
|
||||
{
|
||||
final Pattern FILEPAT = Pattern.compile("^\\s*[D ][E ]\\s*([a-z1-9_]{2,}(,[a-z_]{2,})*)\\s*[A-Za-z1-9 \\(\\)-/'\\.\":]+$");
|
||||
final Pattern FILEPAT2 = Pattern.compile("[a-z1-9_]{2,}(,[a-z1-9_]{2,})*");
|
||||
Matcher matcher;
|
||||
String ffmpegData;
|
||||
String name;
|
||||
|
||||
synchronized (formats)
|
||||
{
|
||||
if (formats.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
Process process = Runtime.getRuntime().exec(new String[] {"ffmpeg", "-formats"});
|
||||
logger.debug("Getting formats");
|
||||
logger.debug("Process complete");
|
||||
try (Scanner scanner = new Scanner(process.getInputStream()))
|
||||
{
|
||||
while (scanner.hasNextLine())
|
||||
{
|
||||
ffmpegData = scanner.nextLine();
|
||||
matcher = FILEPAT.matcher(ffmpegData);
|
||||
if (matcher.matches())
|
||||
{
|
||||
matcher = FILEPAT2.matcher(ffmpegData);
|
||||
if (!matcher.find())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
name = matcher.group();
|
||||
formats.addAll(Arrays.asList(name.split(",")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Could not launch ffmpeg", e);
|
||||
}
|
||||
logger.debug("Supported formats: {}", formats);
|
||||
}
|
||||
}
|
||||
return formats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains all audio formats supported by FFMPEG.
|
||||
*
|
||||
* @return A string array of all supported file codecs.
|
||||
*/
|
||||
public static Set<String> getCodecs()
|
||||
{
|
||||
final Pattern FILEPAT = Pattern.compile("^\\s*D[E.]A[I.][L.][S.]\\s*([a-z1-9_]{2,})\\s*[A-Za-z1-9 \\(\\)-/'\\.\":]+$");
|
||||
final Pattern FILEPAT2 = Pattern.compile("[a-z1-9_]{2,}");
|
||||
Matcher matcher;
|
||||
String ffmpegData;
|
||||
String name;
|
||||
|
||||
synchronized (codecs)
|
||||
{
|
||||
if (codecs.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.debug("Getting codecs");
|
||||
Process process = Runtime.getRuntime().exec(new String[] {"ffmpeg", "-codecs"});
|
||||
logger.debug("Process complete");
|
||||
try (Scanner scanner = new Scanner(process.getInputStream()))
|
||||
{
|
||||
while (scanner.hasNextLine())
|
||||
{
|
||||
ffmpegData = scanner.nextLine();
|
||||
matcher = FILEPAT.matcher(ffmpegData);
|
||||
if (matcher.matches())
|
||||
{
|
||||
matcher = FILEPAT2.matcher(ffmpegData);
|
||||
if (!matcher.find())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
name = matcher.group();
|
||||
codecs.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Could not launch ffmpeg", e);
|
||||
}
|
||||
logger.debug("Supported codecs: {}", codecs);
|
||||
}
|
||||
}
|
||||
return codecs;
|
||||
}
|
||||
|
||||
private class SongScanner implements Runnable
|
||||
{
|
||||
private static final int serviceThreads = Math.max(Runtime.getRuntime().availableProcessors() - 2, 1);
|
||||
private static final ExecutorService service = Executors.newFixedThreadPool(serviceThreads);
|
||||
private static String currentFolder;
|
||||
|
||||
private final File file;
|
||||
|
||||
SongScanner(File folder)
|
||||
{
|
||||
this.file = folder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void scanFolder(File folder)
|
||||
{
|
||||
Process process;
|
||||
String line;
|
||||
String[] streamData;
|
||||
|
||||
String type;
|
||||
LinkedList<File> subFolders = new LinkedList<>();
|
||||
for (File file: folder.listFiles())
|
||||
String codec;
|
||||
|
||||
String genre = null;
|
||||
String title = null;
|
||||
String artist = null;
|
||||
String albumTitle = null;
|
||||
String albumArtist = null;
|
||||
long duration = 0;
|
||||
Integer[] track = null;
|
||||
Integer[] disc = null;
|
||||
|
||||
LocalSong song;
|
||||
Album album;
|
||||
|
||||
try
|
||||
{
|
||||
if (file.isDirectory())
|
||||
{
|
||||
this.scanFolder(file);
|
||||
}
|
||||
if (file.getName().lastIndexOf(".") < file.getName().length() - 1)
|
||||
{
|
||||
type = file.getName().substring(file.getName().lastIndexOf('.') + 1).toLowerCase();
|
||||
switch (type)
|
||||
totalUpdate--;
|
||||
for (File subFile : Objects.requireNonNull(file.listFiles()))
|
||||
{
|
||||
case "mp3":
|
||||
|
||||
totalUpdate++;
|
||||
triggerUpdateListeners();
|
||||
service.submit(new SongScanner(subFile));
|
||||
}
|
||||
}
|
||||
else if (file.getName().lastIndexOf(".") < file.getName().length() - 1)
|
||||
{
|
||||
type = file.getName().substring(file.getName().lastIndexOf('.') + 1).toLowerCase();
|
||||
if (getFormats().contains(type))
|
||||
{
|
||||
try
|
||||
{
|
||||
currentFolder = file.getPath();
|
||||
codec = null;
|
||||
process = Runtime.getRuntime().exec(new String[] {"ffprobe", "-hide_banner", file.getAbsolutePath()});
|
||||
process.waitFor();
|
||||
try (Scanner scanner = new Scanner(process.getErrorStream()))
|
||||
{
|
||||
int i = 0;
|
||||
while (scanner.hasNextLine())
|
||||
{
|
||||
line = scanner.nextLine().trim();
|
||||
switch (line.toLowerCase().substring(0, line.indexOf(' ') > 0 ? line.indexOf(' ') : line.length()))
|
||||
{
|
||||
case "genre" -> {
|
||||
/*
|
||||
* We only take the first one, as to
|
||||
* avoid mishaps with labels after the
|
||||
* metadata.
|
||||
*/
|
||||
if (genre == null)
|
||||
{
|
||||
genre = line.substring(line.indexOf(':') + 2);
|
||||
}
|
||||
}
|
||||
case "title" -> {
|
||||
if (title == null)
|
||||
{
|
||||
title = line.substring(line.indexOf(':') + 2);
|
||||
}
|
||||
}
|
||||
case "artist" -> {
|
||||
if (artist == null)
|
||||
{
|
||||
artist = line.substring(line.indexOf(':') + 2);
|
||||
}
|
||||
}
|
||||
case "album" -> {
|
||||
if (albumTitle == null)
|
||||
{
|
||||
albumTitle = line.substring(line.indexOf(':') + 2);
|
||||
}
|
||||
}
|
||||
case "album_artist" -> {
|
||||
if (albumArtist == null)
|
||||
{
|
||||
albumArtist = line.substring(line.indexOf(':') + 2);
|
||||
}
|
||||
}
|
||||
case "track" -> {
|
||||
line = line.substring(line.indexOf(':') + 2);
|
||||
if (line.indexOf('/') >= 0)
|
||||
{
|
||||
track = Arrays.stream(line.split("/")).map(Integer::parseInt).toArray(Integer[]::new);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (track != null)
|
||||
{
|
||||
track[0] = Integer.parseInt(line);
|
||||
}
|
||||
else
|
||||
{
|
||||
track = new Integer[] {Integer.parseInt(line), -1};
|
||||
}
|
||||
}
|
||||
}
|
||||
case "tracktotal" -> {
|
||||
if (track != null)
|
||||
{
|
||||
track[1] = Integer.parseInt(line.substring(line.indexOf(':') + 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
track = new Integer[] {-1, Integer.parseInt(line.substring(line.indexOf(':') + 2))};
|
||||
}
|
||||
}
|
||||
case "disc" -> {
|
||||
line = line.substring(line.indexOf(':') + 2);
|
||||
if (line.indexOf('/') >= 0)
|
||||
{
|
||||
disc = Arrays.stream(line.split("/")).map(Integer::parseInt).toArray(Integer[]::new);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (disc != null)
|
||||
{
|
||||
disc[0] = Integer.parseInt(line);
|
||||
}
|
||||
else
|
||||
{
|
||||
disc = new Integer[] {Integer.parseInt(line), -1};
|
||||
}
|
||||
}
|
||||
}
|
||||
case "disctotal" -> {
|
||||
if (disc != null)
|
||||
{
|
||||
disc[1] = Integer.parseInt(line.substring(line.indexOf(':') + 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
disc = new Integer[] {-1, Integer.parseInt(line.substring(line.indexOf(':') + 2))};
|
||||
}
|
||||
}
|
||||
case "duration:" -> {
|
||||
if (duration == 0)
|
||||
{
|
||||
line = line.substring(line.indexOf(':') + 2, line.indexOf(','));
|
||||
duration = Long.parseLong(line.substring(0, 2)) * 3600 * 1000 + Long.parseLong(line.substring(3, 5)) * 60 * 1000 + Long.parseLong(line.substring(6, 8)) * 1000 + (long) (Float.parseFloat(line.substring(8, line.length() - 1)) * 1000);
|
||||
}
|
||||
}
|
||||
case "stream" -> {
|
||||
streamData = line.split(" ");
|
||||
if (streamData[2].equals("Audio:"))
|
||||
{
|
||||
codec = streamData[3];
|
||||
if (codec.endsWith(","))
|
||||
{
|
||||
codec = codec.substring(0, codec.length() - 1);
|
||||
}
|
||||
/*
|
||||
* If this isn't a supported codec,
|
||||
* discard.
|
||||
*/
|
||||
if (!getCodecs().contains(codec))
|
||||
{
|
||||
logger.trace("Invalid codec {} for song {}", codec, file);
|
||||
codec = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.trace("Found codec {} for song {}", codec, file);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.trace("Found non-audio stream {} for {}", line, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// logger.trace("Finished scanning {}", file);
|
||||
}
|
||||
if (codec != null)
|
||||
{
|
||||
/*
|
||||
* Update album information.
|
||||
*/
|
||||
synchronized (albums)
|
||||
{
|
||||
album = albums.get(albumTitle);
|
||||
if (album == null)
|
||||
{
|
||||
album = new Album();
|
||||
album.name = albumTitle;
|
||||
albums.put(albumTitle, album);
|
||||
}
|
||||
}
|
||||
if (album.artists == null && albumArtist != null)
|
||||
{
|
||||
album.artists = Arrays.stream(albumArtist.split(";")).map(String::trim).toArray(String[]::new);
|
||||
synchronized (albumArtists)
|
||||
{
|
||||
albumArtists.addAll(Arrays.asList(album.artists));
|
||||
}
|
||||
}
|
||||
if (album.genres == null && genre != null)
|
||||
{
|
||||
album.genres = Arrays.stream(genre.split(";")).map(String::trim).toArray(String[]::new);
|
||||
synchronized (genres)
|
||||
{
|
||||
genres.addAll(Arrays.asList(album.genres));
|
||||
}
|
||||
}
|
||||
if (album.totalTracks == 0 && track != null && track[1] > 0)
|
||||
{
|
||||
album.totalTracks = track[1];
|
||||
}
|
||||
if (album.totalDiscs == 0 && disc != null && disc[1] > 0)
|
||||
{
|
||||
album.totalDiscs = disc[1];
|
||||
}
|
||||
|
||||
/*
|
||||
* Create the song
|
||||
*/
|
||||
song = new LocalSong();
|
||||
song.file = file.getAbsoluteFile();
|
||||
song.type = type;
|
||||
song.codec = codec;
|
||||
song.album = album;
|
||||
if (title != null)
|
||||
{
|
||||
song.title = title;
|
||||
}
|
||||
if (artist != null)
|
||||
{
|
||||
song.artists = Arrays.stream(artist.split(";")).map(String::trim).toArray(String[]::new);
|
||||
synchronized (artists)
|
||||
{
|
||||
artists.addAll(Arrays.asList(song.artists));
|
||||
}
|
||||
}
|
||||
if (disc != null && disc[0] > 0)
|
||||
{
|
||||
song.disc = disc[0];
|
||||
}
|
||||
if (track != null && track[0] > 0)
|
||||
{
|
||||
song.trackNum = track[0];
|
||||
}
|
||||
if (duration > 0)
|
||||
{
|
||||
song.duration = duration;
|
||||
}
|
||||
|
||||
logger.debug("Caching song {} ({})", song, song.file);
|
||||
|
||||
updatedSongs++;
|
||||
triggerUpdateListeners();
|
||||
synchronized (songs)
|
||||
{
|
||||
songs.add(song);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Never mind, this isn't an updatable song.
|
||||
*/
|
||||
totalUpdate--;
|
||||
triggerUpdateListeners();
|
||||
logger.trace("Could not find codec for {}", file);
|
||||
}
|
||||
}
|
||||
catch (IOException | InterruptedException e)
|
||||
{
|
||||
logger.error("Could not get ffprobe information on " + file, e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Never mind, this isn't an updatable song.
|
||||
*/
|
||||
totalUpdate--;
|
||||
triggerUpdateListeners();
|
||||
// logger.trace("Scanned {}, not applicable", file);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Never mind, this isn't an updatable song.
|
||||
*/
|
||||
totalUpdate--;
|
||||
triggerUpdateListeners();
|
||||
// logger.trace("Scanned {}, not applicable", file);
|
||||
}
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
logger.error("Error in obtaining song " + this.file, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public LocalSongProvider(File source)
|
||||
{
|
||||
this.source = source;
|
||||
@@ -53,10 +478,33 @@ public class LocalSongProvider implements SongProvider
|
||||
{
|
||||
throw new IllegalArgumentException("File source must be existing directory");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// TODO - Add some sort of caching system
|
||||
totalUpdate = 1;
|
||||
SongScanner.service.submit(new SongScanner(source));
|
||||
|
||||
/*
|
||||
* Resets the song scanner when ready.
|
||||
*/
|
||||
SongScanner.service.submit(() -> {
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!SongScanner.service.awaitTermination(60, TimeUnit.SECONDS)) break;
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
logger.error("Error in waiting for song scan.", e);
|
||||
}
|
||||
}
|
||||
SongScanner.currentFolder = "";
|
||||
updatedSongs = 0;
|
||||
totalUpdate = 0;
|
||||
triggerUpdateListeners();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains all albums within the collection.
|
||||
*
|
||||
@@ -65,20 +513,23 @@ public class LocalSongProvider implements SongProvider
|
||||
@Override
|
||||
public Collection<Album> getAlbums()
|
||||
{
|
||||
return null;
|
||||
return this.albums.values();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains all songs within the collection.
|
||||
*
|
||||
* @return A list of songs.
|
||||
*/
|
||||
@Override
|
||||
public Collection<Song> getSongs()
|
||||
public Collection<LocalSong> getSongs()
|
||||
{
|
||||
return null;
|
||||
synchronized (this.songs)
|
||||
{
|
||||
return Collections.unmodifiableCollection(this.songs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains a list of all artists.
|
||||
*
|
||||
@@ -87,9 +538,9 @@ public class LocalSongProvider implements SongProvider
|
||||
@Override
|
||||
public Collection<String> getArtists()
|
||||
{
|
||||
return null;
|
||||
return this.artists;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains a list of all album artists.
|
||||
*
|
||||
@@ -98,9 +549,9 @@ public class LocalSongProvider implements SongProvider
|
||||
@Override
|
||||
public Collection<String> getAlbumArtists()
|
||||
{
|
||||
return null;
|
||||
return this.albumArtists;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains a list of all genres.
|
||||
*
|
||||
@@ -109,9 +560,9 @@ public class LocalSongProvider implements SongProvider
|
||||
@Override
|
||||
public Collection<String> getGenres()
|
||||
{
|
||||
return null;
|
||||
return this.genres;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains a list of all years that have albums.
|
||||
*
|
||||
@@ -120,9 +571,9 @@ public class LocalSongProvider implements SongProvider
|
||||
@Override
|
||||
public Collection<Integer> getYears()
|
||||
{
|
||||
return null;
|
||||
return this.years;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains all songs from an album.
|
||||
*
|
||||
@@ -130,11 +581,14 @@ public class LocalSongProvider implements SongProvider
|
||||
* @return All songs from the requested album, or null if that album is not in the database.
|
||||
*/
|
||||
@Override
|
||||
public Collection<Song> getSongsFromAlbum(Album album)
|
||||
public Collection<LocalSong> getSongsFromAlbum(Album album)
|
||||
{
|
||||
return null;
|
||||
synchronized (this.songs)
|
||||
{
|
||||
return this.songs.stream().filter(song -> song.album.equals(album)).collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains all songs written by a given artist.
|
||||
*
|
||||
@@ -143,11 +597,14 @@ public class LocalSongProvider implements SongProvider
|
||||
* database.
|
||||
*/
|
||||
@Override
|
||||
public Collection<Song> getSongsFromArtist(String artist)
|
||||
public Collection<LocalSong> getSongsFromArtist(String artist)
|
||||
{
|
||||
return null;
|
||||
synchronized (this.songs)
|
||||
{
|
||||
return this.songs.stream().filter(song -> Arrays.asList(song.artists).contains(artist)).collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains an album by a specific name.
|
||||
*
|
||||
@@ -158,9 +615,12 @@ public class LocalSongProvider implements SongProvider
|
||||
@Override
|
||||
public Album getAlbumByName(String name)
|
||||
{
|
||||
return null;
|
||||
synchronized (this.albums)
|
||||
{
|
||||
return this.albums.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains all albums that were written by a certain artist.
|
||||
*
|
||||
@@ -170,9 +630,12 @@ public class LocalSongProvider implements SongProvider
|
||||
@Override
|
||||
public Collection<Album> getAlbumsFromArtist(String artist)
|
||||
{
|
||||
return null;
|
||||
synchronized (this.albums)
|
||||
{
|
||||
return this.albums.values().stream().filter(album -> Arrays.asList(album.artists).contains(artist)).collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains all albums that match a certain genre
|
||||
*
|
||||
@@ -182,9 +645,12 @@ public class LocalSongProvider implements SongProvider
|
||||
@Override
|
||||
public Collection<Album> getAlbumsFromGenre(String genre)
|
||||
{
|
||||
return null;
|
||||
synchronized (this.albums)
|
||||
{
|
||||
return this.albums.values().stream().filter(album -> Arrays.asList(album.genres).contains(genre)).collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains all albums that were released a certain year.
|
||||
*
|
||||
@@ -194,6 +660,44 @@ public class LocalSongProvider implements SongProvider
|
||||
@Override
|
||||
public Collection<Album> getAlbumsFromYear(int year)
|
||||
{
|
||||
return null;
|
||||
synchronized (this.albums)
|
||||
{
|
||||
return this.albums.values().stream().filter(album -> album.year == year).collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUpdateProgress()
|
||||
{
|
||||
return this.updatedSongs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalUpdateSongs()
|
||||
{
|
||||
return this.totalUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUpdateText()
|
||||
{
|
||||
return SongScanner.currentFolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUpdateListener(UpdateListener listener)
|
||||
{
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdateListener(UpdateListener listener)
|
||||
{
|
||||
this.listeners.remove(listener);
|
||||
}
|
||||
|
||||
protected void triggerUpdateListeners()
|
||||
{
|
||||
this.listeners.forEach(listener -> listener.onUpdate(this.getUpdateProgress(), this.getTotalUpdateSongs(), this.getUpdateText()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.util.stream.Collectors;
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class SimpleSongProvider implements SongProvider
|
||||
public class SimpleSongProvider implements SongProvider<Song>
|
||||
{
|
||||
private ArrayList<Album> albums;
|
||||
private ArrayList<Song> songs;
|
||||
@@ -175,4 +175,34 @@ public class SimpleSongProvider implements SongProvider
|
||||
return this.albums.stream().filter(album -> album.year == year)
|
||||
.sorted().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUpdateProgress()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalUpdateSongs()
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUpdateText()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUpdateListener(UpdateListener listener)
|
||||
{
|
||||
// We don't need update listeners here
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdateListener(UpdateListener listener)
|
||||
{
|
||||
// We don't need update listeners here
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,19 @@
|
||||
|
||||
package edu.regis.universeplayer.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Contains data for a song.
|
||||
*/
|
||||
public abstract class Song implements Comparable<Song>
|
||||
public abstract class Song implements Comparable<Song>, Serializable
|
||||
{
|
||||
public String title;
|
||||
public String[] artists;
|
||||
public int trackNum;
|
||||
public int disc;
|
||||
public long duration;
|
||||
|
||||
/**
|
||||
* A reference to the album this song is part of.
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
package edu.regis.universeplayer.data;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
@@ -12,63 +13,63 @@ import java.util.Collection;
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public interface SongProvider
|
||||
public interface SongProvider<T extends Song>
|
||||
{
|
||||
/**
|
||||
* A SongProvider instance designed to
|
||||
*/
|
||||
CompiledSongProvider INSTANCE = new CompiledSongProvider(new SimpleSongProvider());
|
||||
|
||||
SongProvider<Song> INSTANCE = new CompiledSongProvider(new LocalSongProvider(new File(System.getProperty("user.home"), "Music")));
|
||||
|
||||
/**
|
||||
* Obtains all albums within the collection.
|
||||
*
|
||||
* @return A list of albums.
|
||||
*/
|
||||
Collection<Album> getAlbums();
|
||||
|
||||
|
||||
/**
|
||||
* Obtains all songs within the collection.
|
||||
*
|
||||
* @return A list of songs.
|
||||
*/
|
||||
Collection<Song> getSongs();
|
||||
|
||||
Collection<T> getSongs();
|
||||
|
||||
/**
|
||||
* Obtains a list of all artists.
|
||||
*
|
||||
* @return All artists.
|
||||
*/
|
||||
Collection<String> getArtists();
|
||||
|
||||
|
||||
/**
|
||||
* Obtains a list of all album artists.
|
||||
*
|
||||
* @return All album artists.
|
||||
*/
|
||||
Collection<String> getAlbumArtists();
|
||||
|
||||
|
||||
/**
|
||||
* Obtains a list of all genres.
|
||||
*
|
||||
* @return All genres.
|
||||
*/
|
||||
Collection<String> getGenres();
|
||||
|
||||
|
||||
/**
|
||||
* Obtains a list of all years that have albums.
|
||||
*
|
||||
* @return All years.
|
||||
*/
|
||||
Collection<Integer> getYears();
|
||||
|
||||
|
||||
/**
|
||||
* Obtains all songs from an album.
|
||||
*
|
||||
* @param album - The album to obtain
|
||||
* @return All songs from the requested album, or null if that album is not in the database.
|
||||
*/
|
||||
Collection<Song> getSongsFromAlbum(Album album);
|
||||
|
||||
Collection<T> getSongsFromAlbum(Album album);
|
||||
|
||||
/**
|
||||
* Obtains all songs written by a given artist.
|
||||
*
|
||||
@@ -76,8 +77,8 @@ public interface SongProvider
|
||||
* @return A list of all songs from the specified artist, or null if that artist is not in the
|
||||
* database.
|
||||
*/
|
||||
Collection<Song> getSongsFromArtist(String artist);
|
||||
|
||||
Collection<T> getSongsFromArtist(String artist);
|
||||
|
||||
/**
|
||||
* Obtains an album by a specific name.
|
||||
*
|
||||
@@ -86,7 +87,7 @@ public interface SongProvider
|
||||
* the database.
|
||||
*/
|
||||
Album getAlbumByName(String name);
|
||||
|
||||
|
||||
/**
|
||||
* Obtains all albums that were written by a certain artist.
|
||||
*
|
||||
@@ -94,7 +95,7 @@ public interface SongProvider
|
||||
* @return - The collection on matching albums.
|
||||
*/
|
||||
Collection<Album> getAlbumsFromArtist(String artist);
|
||||
|
||||
|
||||
/**
|
||||
* Obtains all albums that match a certain genre
|
||||
*
|
||||
@@ -102,7 +103,7 @@ public interface SongProvider
|
||||
* @return - The collection on matching albums.
|
||||
*/
|
||||
Collection<Album> getAlbumsFromGenre(String genre);
|
||||
|
||||
|
||||
/**
|
||||
* Obtains all albums that were released a certain year.
|
||||
*
|
||||
@@ -110,4 +111,53 @@ public interface SongProvider
|
||||
* @return - The collection on matching albums.
|
||||
*/
|
||||
Collection<Album> getAlbumsFromYear(int year);
|
||||
|
||||
/**
|
||||
* Checks to see if we are updating any songs.
|
||||
*
|
||||
* @return True if we are updating the song cache.
|
||||
*/
|
||||
default boolean isUpdating()
|
||||
{
|
||||
return this.getTotalUpdateSongs() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* When we are updating the song cache, this method obtains the number of
|
||||
* songs already updated.
|
||||
*
|
||||
* @return The number of songs successfully updated.
|
||||
*/
|
||||
int getUpdateProgress();
|
||||
|
||||
/**
|
||||
* Determines whether or not we are updating the cache and, if so, gets the
|
||||
* total number of songs we need to update
|
||||
*
|
||||
* @return The total number of songs to update. A 0 means that we do not
|
||||
* have any songs to update, and a negative number means that we are
|
||||
* currently calculating how many songs we need to update.
|
||||
*/
|
||||
int getTotalUpdateSongs();
|
||||
|
||||
/**
|
||||
* Determines the text displayed for the progress of updates
|
||||
*
|
||||
* @return The update status text.
|
||||
*/
|
||||
String getUpdateText();
|
||||
|
||||
/**
|
||||
* Adds a listener for song updates.
|
||||
*
|
||||
* @param listener - The listener to add.
|
||||
*/
|
||||
void addUpdateListener(UpdateListener listener);
|
||||
|
||||
/**
|
||||
* Removes a listener for song updates.
|
||||
*
|
||||
* @param listener - The listener to remove.
|
||||
*/
|
||||
void removeUpdateListener(UpdateListener listener);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.data;
|
||||
|
||||
/**
|
||||
* A song update listener is used to tell others when a song provider starts
|
||||
* updating songs.
|
||||
*
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public interface UpdateListener
|
||||
{
|
||||
/**
|
||||
* Called when the update status of the player has changed.
|
||||
* @param updated - The number of songs updated.
|
||||
* @param totalUpdate - The total number of songs to update, or -1 if we are
|
||||
* still determining that.
|
||||
* @param updating - The text to display on update bars.
|
||||
*/
|
||||
void onUpdate(int updated, int totalUpdate, String updating);
|
||||
}
|
||||
@@ -5,12 +5,7 @@
|
||||
package edu.regis.universeplayer.player;
|
||||
|
||||
import java.awt.FlowLayout;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
@@ -39,23 +34,23 @@ public class CollectionList extends JPanel
|
||||
* A link between the JLabel and the object they point towards.
|
||||
*/
|
||||
private Map<JLabel, Object> labelMap = new HashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* A list of all things interested in knowing when we click a collection.
|
||||
*/
|
||||
private LinkedList<SongDisplayListener> listeners = new LinkedList<>();
|
||||
|
||||
|
||||
/**
|
||||
* Creates a collections list view.
|
||||
*/
|
||||
public CollectionList()
|
||||
{
|
||||
super();
|
||||
|
||||
|
||||
FlowLayout layout = new FlowLayout();
|
||||
this.setLayout(layout);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the collections currently listed, sorted by album.
|
||||
*
|
||||
@@ -65,7 +60,7 @@ public class CollectionList extends JPanel
|
||||
public void listCollection(CollectionType type, Collection<?> objects)
|
||||
{
|
||||
Class<?> fType = objects.stream().filter(Objects::nonNull).map(Object::getClass).findFirst()
|
||||
.orElse(null);
|
||||
.orElse(null);
|
||||
if (objects.isEmpty() || fType == null)
|
||||
{
|
||||
/*
|
||||
@@ -78,38 +73,28 @@ public class CollectionList extends JPanel
|
||||
if (!type.objectType.isAssignableFrom(fType))
|
||||
{
|
||||
throw new ClassCastException("Can't assign " + type.objectType
|
||||
.getName() + " from " + fType
|
||||
.getName() + " from " + fType
|
||||
.getName());
|
||||
}
|
||||
|
||||
|
||||
this.labelMap.clear();
|
||||
this.removeAll();
|
||||
this.type = type;
|
||||
switch (type)
|
||||
{
|
||||
case album:
|
||||
this.addAlbums(objects.stream().sorted().map(ob -> (Album) ob)
|
||||
.collect(Collectors.toList()));
|
||||
break;
|
||||
case artist:
|
||||
this.addArtists(objects.stream().sorted().map(ob -> (String) ob)
|
||||
.collect(Collectors.toList()), false);
|
||||
break;
|
||||
case albumArtist:
|
||||
this.addArtists(objects.stream().sorted().map(ob -> (String) ob)
|
||||
.collect(Collectors.toList()), true);
|
||||
break;
|
||||
case genre:
|
||||
this.addGenres(objects.stream().sorted().map(ob -> (String) ob)
|
||||
.collect(Collectors.toList()));
|
||||
break;
|
||||
case year:
|
||||
this.addYears(objects.stream().sorted().map(ob -> (Integer) ob)
|
||||
.collect(Collectors.toList()));
|
||||
break;
|
||||
case album -> this.addAlbums(objects.stream().sorted().map(ob -> (Album) ob)
|
||||
.collect(Collectors.toList()));
|
||||
case artist -> this.addArtists(objects.stream().sorted().map(ob -> (String) ob)
|
||||
.collect(Collectors.toList()), false);
|
||||
case albumArtist -> this.addArtists(objects.stream().sorted().map(ob -> (String) ob)
|
||||
.collect(Collectors.toList()), true);
|
||||
case genre -> this.addGenres(objects.stream().sorted().map(ob -> (String) ob)
|
||||
.collect(Collectors.toList()));
|
||||
case year -> this.addYears(objects.stream().sorted().map(ob -> (Integer) ob)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the display to show a list of artists
|
||||
*
|
||||
@@ -119,10 +104,10 @@ public class CollectionList extends JPanel
|
||||
private void addArtists(List<String> artists, boolean album)
|
||||
{
|
||||
final int ART_SIZE = 128;
|
||||
|
||||
|
||||
JLabel artistLabel;
|
||||
ImageIcon icon;
|
||||
|
||||
|
||||
for (String artist : artists)
|
||||
{
|
||||
artistLabel = new JLabel();
|
||||
@@ -134,7 +119,7 @@ public class CollectionList extends JPanel
|
||||
// else
|
||||
// {
|
||||
icon = new ImageIcon(this.getClass()
|
||||
.getResource("/gui/icons/artist.png"), "Default");
|
||||
.getResource("/gui/icons/artist.png"), "Default");
|
||||
// }
|
||||
icon.setImage(icon.getImage().getScaledInstance(ART_SIZE, ART_SIZE, 0));
|
||||
artistLabel.setIcon(icon);
|
||||
@@ -147,20 +132,20 @@ public class CollectionList extends JPanel
|
||||
this.triggerSongDisplayListeners(SongProvider.INSTANCE
|
||||
.getAlbumsFromArtist(artist).stream()
|
||||
.flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2)
|
||||
.stream())
|
||||
.stream())
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.triggerSongDisplayListeners(SongProvider.INSTANCE
|
||||
.getSongsFromArtist(artist));
|
||||
this.triggerSongDisplayListeners(new ArrayList<>(SongProvider.INSTANCE
|
||||
.getSongsFromArtist(artist)));
|
||||
}
|
||||
});
|
||||
this.add(artistLabel);
|
||||
this.labelMap.put(artistLabel, artist);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the display to show a list of albums.
|
||||
*
|
||||
@@ -169,34 +154,27 @@ public class CollectionList extends JPanel
|
||||
private void addAlbums(List<Album> albums)
|
||||
{
|
||||
final int ART_SIZE = 128;
|
||||
|
||||
|
||||
JLabel albumLabel;
|
||||
ImageIcon icon;
|
||||
|
||||
|
||||
for (Album album : albums)
|
||||
{
|
||||
albumLabel = new JLabel();
|
||||
if (album.art != null)
|
||||
{
|
||||
icon = album.art;
|
||||
}
|
||||
else
|
||||
{
|
||||
icon = new ImageIcon(this.getClass()
|
||||
.getResource("/gui/icons/defaultart.png"), "Default");
|
||||
}
|
||||
icon = Objects.requireNonNullElseGet(album.art, () -> new ImageIcon(this.getClass()
|
||||
.getResource("/gui/icons/defaultart.png"), "Default"));
|
||||
icon.setImage(icon.getImage().getScaledInstance(ART_SIZE, ART_SIZE, 0));
|
||||
albumLabel.setIcon(icon);
|
||||
albumLabel.setText(album.name);
|
||||
albumLabel.setHorizontalTextPosition(JLabel.CENTER);
|
||||
albumLabel.setVerticalTextPosition(JLabel.BOTTOM);
|
||||
albumLabel.addMouseListener((ClickListener) mouseEvent -> this.triggerSongDisplayListeners(SongProvider.INSTANCE
|
||||
.getSongsFromAlbum(album)));
|
||||
albumLabel.addMouseListener((ClickListener) mouseEvent -> this.triggerSongDisplayListeners(new ArrayList<>(SongProvider.INSTANCE
|
||||
.getSongsFromAlbum(album))));
|
||||
this.add(albumLabel);
|
||||
this.labelMap.put(albumLabel, album);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the display to show a list of genres.
|
||||
*
|
||||
@@ -205,10 +183,10 @@ public class CollectionList extends JPanel
|
||||
private void addGenres(List<String> genres)
|
||||
{
|
||||
// final int ART_SIZE = 128;
|
||||
|
||||
|
||||
JLabel genreLabel;
|
||||
// ImageIcon icon;
|
||||
|
||||
|
||||
for (String genre : genres)
|
||||
{
|
||||
genreLabel = new JLabel();
|
||||
@@ -230,13 +208,13 @@ public class CollectionList extends JPanel
|
||||
genreLabel.addMouseListener((ClickListener) mouseEvent -> this.triggerSongDisplayListeners(SongProvider.INSTANCE
|
||||
.getAlbumsFromGenre(genre).stream()
|
||||
.flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2)
|
||||
.stream())
|
||||
.stream())
|
||||
.collect(Collectors.toList())));
|
||||
this.add(genreLabel);
|
||||
this.labelMap.put(genreLabel, genre);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the display to show a list of release years.
|
||||
*
|
||||
@@ -245,10 +223,10 @@ public class CollectionList extends JPanel
|
||||
private void addYears(List<Integer> years)
|
||||
{
|
||||
// final int ART_SIZE = 128;
|
||||
|
||||
|
||||
JLabel yearLabel;
|
||||
// ImageIcon icon;
|
||||
|
||||
|
||||
for (Integer year : years)
|
||||
{
|
||||
yearLabel = new JLabel();
|
||||
@@ -270,13 +248,13 @@ public class CollectionList extends JPanel
|
||||
yearLabel.addMouseListener((ClickListener) mouseEvent -> this.triggerSongDisplayListeners(SongProvider.INSTANCE
|
||||
.getAlbumsFromYear(year).stream()
|
||||
.flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2)
|
||||
.stream())
|
||||
.stream())
|
||||
.collect(Collectors.toList())));
|
||||
this.add(yearLabel);
|
||||
this.labelMap.put(yearLabel, year);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a listener for when the displayed songs should change.
|
||||
*
|
||||
@@ -286,7 +264,7 @@ public class CollectionList extends JPanel
|
||||
{
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a listener for when the displayed songs should change.
|
||||
*
|
||||
@@ -296,7 +274,7 @@ public class CollectionList extends JPanel
|
||||
{
|
||||
this.listeners.remove(listener);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Triggers all the song display listeners.
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package edu.regis.universeplayer.player;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
|
||||
@@ -29,7 +30,7 @@ public class Collections extends JPanel
|
||||
* A list of all things interested in knowing when we click a collection.
|
||||
*/
|
||||
private LinkedList<SongDisplayListener> listeners = new LinkedList<>();
|
||||
|
||||
|
||||
/**
|
||||
* Creates a collections list view.
|
||||
*/
|
||||
@@ -38,11 +39,11 @@ public class Collections extends JPanel
|
||||
JLabel label;
|
||||
BoxLayout layout = new BoxLayout(this, BoxLayout.Y_AXIS);
|
||||
this.setLayout(layout);
|
||||
|
||||
|
||||
this.add(label = new JLabel("All Songs"));
|
||||
label.setForeground(Color.BLUE);
|
||||
label.addMouseListener((ClickListener) mouseEvent -> this
|
||||
.triggerSongDisplayListeners(SongProvider.INSTANCE.getSongs()));
|
||||
.triggerSongDisplayListeners(new ArrayList<>(SongProvider.INSTANCE.getSongs())));
|
||||
this.add(label = new JLabel("Artists"));
|
||||
label.setForeground(Color.BLUE);
|
||||
label.addMouseListener((ClickListener) mouseEvent -> this
|
||||
@@ -67,7 +68,7 @@ public class Collections extends JPanel
|
||||
this.add(label = new JLabel("Playlists"));
|
||||
label.setForeground(Color.BLUE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a listener for when the displayed songs should change.
|
||||
*
|
||||
@@ -77,7 +78,7 @@ public class Collections extends JPanel
|
||||
{
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a listener for when the displayed songs should change.
|
||||
*
|
||||
@@ -87,7 +88,7 @@ public class Collections extends JPanel
|
||||
{
|
||||
this.listeners.remove(listener);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Triggers all the song display listeners.
|
||||
*/
|
||||
@@ -98,7 +99,7 @@ public class Collections extends JPanel
|
||||
listener.updateSongs(songs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Triggers all the song display listeners.
|
||||
*/
|
||||
|
||||
@@ -4,8 +4,17 @@
|
||||
|
||||
package edu.regis.universeplayer.player;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import edu.regis.universeplayer.Player;
|
||||
import edu.regis.universeplayer.browser.Browser;
|
||||
import edu.regis.universeplayer.data.CollectionType;
|
||||
import edu.regis.universeplayer.data.Song;
|
||||
import edu.regis.universeplayer.data.SongProvider;
|
||||
import edu.regis.universeplayer.data.UpdateListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.ComponentListener;
|
||||
import java.awt.event.WindowEvent;
|
||||
@@ -16,49 +25,41 @@ import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JScrollPane;
|
||||
|
||||
import edu.regis.universeplayer.Player;
|
||||
import edu.regis.universeplayer.browser.Browser;
|
||||
import edu.regis.universeplayer.browser.MessageManager;
|
||||
import edu.regis.universeplayer.data.CollectionType;
|
||||
import edu.regis.universeplayer.data.Song;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The Interface class serves as the primary GUI that the player interacts with.
|
||||
*
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class Interface extends JFrame implements SongDisplayListener, ComponentListener, WindowListener, PlaybackCommandListener
|
||||
public class Interface extends JFrame implements SongDisplayListener, ComponentListener, WindowListener, PlaybackCommandListener, UpdateListener
|
||||
{
|
||||
private static Logger logger = LoggerFactory.getLogger(Interface.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(Interface.class);
|
||||
|
||||
/**
|
||||
* A reference to the panel containing links to different collection views.
|
||||
*/
|
||||
private Collections collectionTypes;
|
||||
private final Collections collectionTypes;
|
||||
/**
|
||||
* A reference to the central view showing a list of songs.
|
||||
*/
|
||||
private SongList songList;
|
||||
private final SongList songList;
|
||||
/**
|
||||
* A reference to the central view showing a list of collections.
|
||||
*/
|
||||
private CollectionList collectionList;
|
||||
private final CollectionList collectionList;
|
||||
/**
|
||||
* A reference to the central view scroll pane.
|
||||
*/
|
||||
private JScrollPane centerView;
|
||||
private final JScrollPane centerView;
|
||||
/**
|
||||
* A reference to the player controls pane.
|
||||
*/
|
||||
private final PlayerControls controls;
|
||||
|
||||
/**
|
||||
* A link to the browser.
|
||||
*/
|
||||
private ArrayList<Player> players = new ArrayList<>();
|
||||
private final ArrayList<Player<?>> players = new ArrayList<>();
|
||||
private int currentPlayer = -1;
|
||||
|
||||
public static void main(String[] args)
|
||||
@@ -70,7 +71,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
{
|
||||
/*
|
||||
* Add this just in case of a crash or something. It won't work if the
|
||||
* program is forcible terminated by the OS, but it could be helpful
|
||||
* program is forcibly terminated by the OS, but it could be helpful
|
||||
* otherwise.
|
||||
*/
|
||||
logger.info("Starting application");
|
||||
@@ -84,7 +85,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
browserThread = new Thread(browser);
|
||||
browserThread.start();
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(browser::close));
|
||||
|
||||
|
||||
LinkedList<Future<Object>> pingRequests = new LinkedList<>();
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
@@ -95,7 +96,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
LinkedList<Future<Object>> toRemove = new LinkedList<>();
|
||||
while (pingRequests.size() > 0)
|
||||
{
|
||||
for (Future<Object> future: pingRequests)
|
||||
for (Future<Object> future : pingRequests)
|
||||
{
|
||||
if (future.isDone())
|
||||
{
|
||||
@@ -112,6 +113,8 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
logger.error("Could not open browser background", e);
|
||||
JOptionPane.showMessageDialog(inter, e, "Could not open browser background", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
SongProvider.INSTANCE.addUpdateListener(inter);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
@@ -126,14 +129,13 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
public Interface()
|
||||
{
|
||||
super();
|
||||
PlayerControls controls;
|
||||
|
||||
this.setTitle("Universal Music Player");
|
||||
this.getContentPane().setLayout(new BorderLayout());
|
||||
this.getContentPane()
|
||||
.add(this.collectionTypes = new Collections(), BorderLayout.LINE_START);
|
||||
this.collectionTypes.addSongDisplayListener(this);
|
||||
controls = new PlayerControls();
|
||||
this.controls = new PlayerControls();
|
||||
controls.addCommandListener(this);
|
||||
this.getContentPane().add(controls, BorderLayout.PAGE_END);
|
||||
|
||||
@@ -152,7 +154,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSongs(Collection<Song> songs)
|
||||
public void updateSongs(Collection<? extends Song> songs)
|
||||
{
|
||||
this.songList.listAlbums(songs);
|
||||
this.centerView.setViewportView(this.songList);
|
||||
@@ -285,4 +287,18 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(int updated, int totalUpdate, String updating)
|
||||
{
|
||||
this.controls.setUpdateProgress(updated, totalUpdate, updating);
|
||||
if (updated == totalUpdate || totalUpdate == 0)
|
||||
{
|
||||
logger.debug("Resetting the song provider.");
|
||||
/*
|
||||
* TODO - Add some way to get back to the current view, just updated
|
||||
*/
|
||||
this.updateSongs(SongProvider.INSTANCE.getSongs());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,7 @@ import java.awt.Dimension;
|
||||
import java.awt.FlowLayout;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSlider;
|
||||
import javax.swing.SpringLayout;
|
||||
import javax.swing.*;
|
||||
|
||||
/**
|
||||
* This panel contains the buttons necessary for controlling the playback of audio.
|
||||
@@ -22,15 +18,16 @@ import javax.swing.SpringLayout;
|
||||
*/
|
||||
public class PlayerControls extends JPanel
|
||||
{
|
||||
private JButton playButton;
|
||||
private JButton nextButton;
|
||||
private JButton prevButton;
|
||||
private JSlider progress;
|
||||
private final JButton playButton;
|
||||
private final JButton nextButton;
|
||||
private final JButton prevButton;
|
||||
private final JSlider progress;
|
||||
private final JProgressBar updateProgress;
|
||||
|
||||
/**
|
||||
* A list of all things interested in knowing when we trigger a command.
|
||||
*/
|
||||
private LinkedList<PlaybackCommandListener> listeners = new LinkedList<>();
|
||||
private final LinkedList<PlaybackCommandListener> listeners = new LinkedList<>();
|
||||
|
||||
public PlayerControls()
|
||||
{
|
||||
@@ -54,9 +51,7 @@ public class PlayerControls extends JPanel
|
||||
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
|
||||
this.prevButton.setIcon(icon);
|
||||
this.prevButton.setPreferredSize(BUTTON_SIZE);
|
||||
this.prevButton.addActionListener(actionEvent -> {
|
||||
this.triggerCommandListeners(PlaybackCommand.PREVIOUS, null);
|
||||
});
|
||||
this.prevButton.addActionListener(actionEvent -> this.triggerCommandListeners(PlaybackCommand.PREVIOUS, null));
|
||||
buttonCont.add(this.prevButton);
|
||||
|
||||
this.playButton = new JButton();
|
||||
@@ -64,9 +59,7 @@ public class PlayerControls extends JPanel
|
||||
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
|
||||
this.playButton.setIcon(icon);
|
||||
this.playButton.setPreferredSize(BUTTON_SIZE);
|
||||
this.playButton.addActionListener(actionEvent -> {
|
||||
this.triggerCommandListeners(PlaybackCommand.PLAY, null);
|
||||
});
|
||||
this.playButton.addActionListener(actionEvent -> this.triggerCommandListeners(PlaybackCommand.PLAY, null));
|
||||
buttonCont.add(this.playButton);
|
||||
|
||||
this.nextButton = new JButton();
|
||||
@@ -74,9 +67,7 @@ public class PlayerControls extends JPanel
|
||||
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
|
||||
this.nextButton.setIcon(icon);
|
||||
this.nextButton.setPreferredSize(BUTTON_SIZE);
|
||||
this.nextButton.addActionListener(actionEvent -> {
|
||||
this.triggerCommandListeners(PlaybackCommand.NEXT, null);
|
||||
});
|
||||
this.nextButton.addActionListener(actionEvent -> this.triggerCommandListeners(PlaybackCommand.NEXT, null));
|
||||
buttonCont.add(this.nextButton);
|
||||
|
||||
progressLayout = new SpringLayout();
|
||||
@@ -84,18 +75,50 @@ public class PlayerControls extends JPanel
|
||||
this.add(progressCont);
|
||||
|
||||
this.progress = new JSlider();
|
||||
this.progress.addChangeListener(changeEvent -> {
|
||||
this.triggerCommandListeners(PlaybackCommand.SEEK, this.progress.getValue());
|
||||
});
|
||||
this.progress.addChangeListener(changeEvent -> this.triggerCommandListeners(PlaybackCommand.SEEK, this.progress.getValue()));
|
||||
this.add(this.progress);
|
||||
|
||||
this.updateProgress = new JProgressBar();
|
||||
this.updateProgress.setStringPainted(true);
|
||||
this.setUpdateProgress(0, 0, null);
|
||||
this.add(this.updateProgress);
|
||||
|
||||
layout.putConstraint(SpringLayout.NORTH, buttonCont, 0, SpringLayout.NORTH, this);
|
||||
layout.putConstraint(SpringLayout.WEST, buttonCont, 0, SpringLayout.WEST, this);
|
||||
layout.putConstraint(SpringLayout.EAST, buttonCont, 0, SpringLayout.EAST, this);
|
||||
layout.putConstraint(SpringLayout.NORTH, this.progress, 5, SpringLayout.SOUTH, buttonCont);
|
||||
layout.putConstraint(SpringLayout.WEST, this.progress, 5, SpringLayout.WEST, this);
|
||||
layout.putConstraint(SpringLayout.EAST, this, 5, SpringLayout.EAST, this.progress);
|
||||
layout.putConstraint(SpringLayout.SOUTH, this, 5, SpringLayout.SOUTH, this.progress);
|
||||
layout.putConstraint(SpringLayout.EAST, buttonCont, 0, SpringLayout.EAST, this);
|
||||
layout.putConstraint(SpringLayout.EAST, this.progress, 5, SpringLayout.EAST, this);
|
||||
layout.putConstraint(SpringLayout.NORTH, this.updateProgress, 5, SpringLayout.SOUTH, this.progress);
|
||||
layout.putConstraint(SpringLayout.WEST, this.updateProgress, 5, SpringLayout.WEST, this);
|
||||
|
||||
layout.putConstraint(SpringLayout.EAST, this, 5, SpringLayout.EAST, this.updateProgress);
|
||||
layout.putConstraint(SpringLayout.SOUTH, this, 5, SpringLayout.SOUTH, this.updateProgress);
|
||||
}
|
||||
|
||||
void setUpdateProgress(int updated, int toUpdate, String updating)
|
||||
{
|
||||
this.updateProgress.setString(updating);
|
||||
if (toUpdate == 0)
|
||||
{
|
||||
this.updateProgress.setVisible(false);
|
||||
// this.updateProgress.setPreferredSize(new Dimension(this.updateProgress.getPreferredSize().width, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.updateProgress.setVisible(true);
|
||||
// this.updateProgress.setSize(new Dimension(this.updateProgress.getPreferredSize().width, this.updateSize));
|
||||
if (toUpdate < 0)
|
||||
{
|
||||
this.updateProgress.setIndeterminate(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.updateProgress.setIndeterminate(false);
|
||||
this.updateProgress.setMaximum(toUpdate);
|
||||
this.updateProgress.setValue(updated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,7 +23,7 @@ public interface SongDisplayListener extends EventListener
|
||||
*
|
||||
* @param songs - The songs to display.
|
||||
*/
|
||||
void updateSongs(Collection<Song> songs);
|
||||
void updateSongs(Collection<? extends Song> songs);
|
||||
|
||||
/**
|
||||
* Called to display a list of collections
|
||||
|
||||
@@ -36,7 +36,7 @@ public class SongList extends JPanel
|
||||
GridBagLayout layout = new GridBagLayout();
|
||||
this.setLayout(layout);
|
||||
|
||||
SongProvider provider = SongProvider.INSTANCE;
|
||||
SongProvider<?> provider = SongProvider.INSTANCE;
|
||||
this.listAlbums(provider.getSongs());
|
||||
}
|
||||
|
||||
@@ -45,11 +45,11 @@ public class SongList extends JPanel
|
||||
*
|
||||
* @param songs - The songs to display.
|
||||
*/
|
||||
public void listAlbums(Collection<Song> songs)
|
||||
public void listAlbums(Collection<? extends Song> songs)
|
||||
{
|
||||
Map<Album, List<Song>> albums = songs.stream().sorted().collect(Collectors
|
||||
.groupingBy(song -> song.album, Collectors
|
||||
.mapping(song -> song, Collectors.toList())));
|
||||
.mapping(song -> (Song) song, Collectors.toList())));
|
||||
GridBagConstraints c = new GridBagConstraints(), c2 = new GridBagConstraints();
|
||||
c.fill = GridBagConstraints.NONE;
|
||||
c.insets = new Insets(0, 0, 20, 0);
|
||||
|
||||
Reference in New Issue
Block a user