/* * Copyright (c) 2021 William Hubbard. All Rights Reserved. */ package edu.regis.universeplayer.player; 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 net.harawata.appdirs.AppDirsFactory; 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; import java.awt.event.WindowListener; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; import java.util.concurrent.Future; /** * 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, UpdateListener { private static final Logger logger = LoggerFactory.getLogger(Interface.class); private static File dataDir; private static File configDir; /** * A reference to the panel containing links to different collection views. */ private final Collections collectionTypes; /** * A reference to the central view showing a list of songs. */ private final SongList songList; /** * A reference to the central view showing a list of collections. */ private final CollectionList collectionList; /** * A reference to the central view scroll pane. */ private final JScrollPane centerView; /** * A reference to the player controls pane. */ private final PlayerControls controls; /** * A link to the browser. */ private final ArrayList> players = new ArrayList<>(); private int currentPlayer = -1; public static void main(String[] args) { Thread browserThread; Interface inter = null; Browser browser; try { /* * Add this just in case of a crash or something. It won't work if the * program is forcibly terminated by the OS, but it could be helpful * otherwise. */ logger.info("Starting application"); inter = new Interface(); inter.pack(); inter.setVisible(true); try { inter.players.add(browser = Browser.createBrowser()); browserThread = new Thread(browser); browserThread.start(); Runtime.getRuntime().addShutdownHook(new Thread(browser::close)); LinkedList> pingRequests = new LinkedList<>(); for (int i = 0; i < 20; i++) { logger.info("Sending ping"); pingRequests.add(browser.sendObject("ping")); } LinkedList> toRemove = new LinkedList<>(); while (pingRequests.size() > 0) { for (Future future : pingRequests) { if (future.isDone()) { logger.info("Receiving {}", future.get()); toRemove.add(future); } } pingRequests.removeAll(toRemove); toRemove.clear(); } } catch (IOException e) { 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) { logger.error("Could not open browser background", e); JOptionPane.showMessageDialog(inter != null && inter.isVisible() ? inter : null, e, "Error!", JOptionPane.ERROR_MESSAGE); } } /** * Obtains the data storage directory for the application, creating it if * needed. * @return The data storage directory. */ public static File getDataDir() { if (dataDir == null) { dataDir = new File(AppDirsFactory.getInstance().getUserDataDir("universalmusic", null, null, true)); if (!dataDir.exists()) { if (!dataDir.mkdir()) { logger.error("Could not create data directory {}", dataDir); } } } return dataDir; } /** * Obtains the configuration directory for the application, creating it if * needed. * @return The configuration directory. */ public static File getConfigDir() { if (configDir == null) { configDir = new File(AppDirsFactory.getInstance().getUserConfigDir("universalmusic", null, null, true)); if (!configDir.exists()) { if (!configDir.mkdir()) { logger.error("Could not create configuration directory {}", configDir); } } } return configDir; } /** * Creates an interface */ public Interface() { super(); this.setTitle("Universal Music Player"); this.getContentPane().setLayout(new BorderLayout()); this.getContentPane() .add(this.collectionTypes = new Collections(), BorderLayout.LINE_START); this.collectionTypes.addSongDisplayListener(this); this.controls = new PlayerControls(); controls.addCommandListener(this); this.getContentPane().add(controls, BorderLayout.PAGE_END); this.songList = new SongList(); this.collectionList = new CollectionList(); this.collectionList.addSongDisplayListener(this); this.centerView = new JScrollPane(this.songList); this.getContentPane().add(this.centerView, BorderLayout.CENTER); this.componentResized(null); this.addComponentListener(this); this.addWindowListener(this); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.updateSongs(SongProvider.INSTANCE.getSongs()); } @Override public void updateSongs(Collection songs) { this.songList.listAlbums(songs); this.centerView.setViewportView(this.songList); this.songList.setPreferredSize(new Dimension(this.centerView.getViewport() .getExtentSize().width, Integer.MAX_VALUE)); this.songList.setPreferredSize(new Dimension(this.centerView.getViewport() .getExtentSize().width, this.songList .getMinimumSize().height)); } @Override public void updateCollections(CollectionType type, Collection collections) { this.collectionList.listCollection(type, collections); this.centerView.setViewportView(this.collectionList); this.collectionList.setPreferredSize(new Dimension(this.centerView.getViewport() .getExtentSize().width, Integer.MAX_VALUE)); this.collectionList.setPreferredSize(new Dimension(this.centerView.getViewport() .getExtentSize().width, this.collectionList .getMinimumSize().height)); } @Override public void componentResized(ComponentEvent event) { this.songList.setPreferredSize(new Dimension(this.centerView.getViewport() .getExtentSize().width, Integer.MAX_VALUE)); this.songList.setPreferredSize(new Dimension(this.centerView.getViewport() .getExtentSize().width, this.songList .getMinimumSize().height)); this.collectionList.setPreferredSize(new Dimension(this.centerView.getViewport() .getExtentSize().width, Integer.MAX_VALUE)); this.collectionList.setPreferredSize(new Dimension(this.centerView.getViewport() .getExtentSize().width, this.collectionList .getMinimumSize().height)); } @Override public void componentMoved(ComponentEvent event) { } @Override public void componentShown(ComponentEvent event) { } @Override public void componentHidden(ComponentEvent event) { } @Override public void windowOpened(WindowEvent windowEvent) { logger.info("Interface opened"); } @Override public void windowClosing(WindowEvent windowEvent) { this.players.forEach(Player::close); } @Override public void windowClosed(WindowEvent windowEvent) { } @Override public void windowIconified(WindowEvent windowEvent) { } @Override public void windowDeiconified(WindowEvent windowEvent) { } @Override public void windowActivated(WindowEvent windowEvent) { } @Override public void windowDeactivated(WindowEvent windowEvent) { } /** * Called when a playback command is issued. * * @param command - The command issued. * @param data - Additional data relevent to the command. */ @Override public void onCommand(PlaybackCommand command, Object data) { Player player; if (this.currentPlayer >= 0 && this.currentPlayer < this.players.size()) { player = this.players.get(this.currentPlayer); } else { throw new NullPointerException("No player available"); } switch (command) { case PLAY -> { if (data instanceof Song) { player.loadSong((Song) data); } } case PAUSE -> { } case NEXT -> { } case PREVIOUS -> { } case SEEK -> { } } } @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()); } } }