Skip to content

Commit

Permalink
New implementation of the MUS player (InfinityAmp)
Browse files Browse the repository at this point in the history
Features:
- More playback controls (jump to previous or next title, play, pause, stop)
- Playback options: Loop, Shuffle
- Supports playback of music files from all games at once
- Refreshing or opening another game does not reset music player content
- Improved display of playlist entries and current playback time
- Sound filter to allow auto-skipping silent sound segments
- Music entries and player configuration is saved in NI's preferences
- Options for importing or exporting playlists (*.m3u or *.m3u8)
- Supports extended keyboard media keys for playback controls (Windows only)

Internal changes:
- Added StopWatch class
- Added InputKeyHelper class for extended virtual keys support (e.g. media keys)
- Extended ChildFrame class to allow persistent frames that survive refreshing
  and changing games
  • Loading branch information
Argent77 committed Nov 6, 2024
1 parent 1139427 commit e2ec2d8
Show file tree
Hide file tree
Showing 11 changed files with 3,691 additions and 11 deletions.
5 changes: 4 additions & 1 deletion src/org/infinity/NearInfinity.java
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,7 @@ public void actionPerformed(ActionEvent event) {
statusBar.setMessage(msg);
BrowserMenuBar.getInstance().gameLoaded(oldGame, oldFile);
tree.setModel(treemodel);
ChildFrame.fireGameReset(false);
containerpanel.removeAll();
containerpanel.add(createJavaInfoPanel(), BorderLayout.CENTER);
containerpanel.revalidate();
Expand Down Expand Up @@ -879,6 +880,7 @@ public void openGame(Path keyFile) {
statusBar.setMessage(msg);
BrowserMenuBar.getInstance().gameLoaded(oldGame, Objects.requireNonNull(oldKeyFile).toString());
tree.setModel(treemodel);
ChildFrame.fireGameReset(false);
containerpanel.removeAll();
containerpanel.add(createJavaInfoPanel(), BorderLayout.CENTER);
containerpanel.revalidate();
Expand Down Expand Up @@ -915,7 +917,7 @@ public void showResourceEntry(ResourceEntry resourceEntry, Operation doneOperati

public void quit() {
if (removeViewable()) {
ChildFrame.closeWindows();
ChildFrame.closeWindows(true);
storePreferences();
clearCache(false);
System.exit(0);
Expand Down Expand Up @@ -944,6 +946,7 @@ public void refreshGame() {
containerpanel.repaint();
}
cacheResourceIcons(true);
ChildFrame.fireGameReset(true);
} finally {
blocker.setBlocked(false);
}
Expand Down
82 changes: 77 additions & 5 deletions src/org/infinity/gui/ChildFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,34 @@ public class ChildFrame extends JFrame {

private final boolean closeOnInvisible;

/** Closes all child windows. */
// Indicates whether the window should be closed when the game is reset or closed
private boolean closeOnReset;

/** Closes all child windows except for child windows with {@code closeOnReset} set to {@code false}. */
public static int closeWindows() {
return closeWindow((Class<ChildFrame>)null);
return closeWindow((Class<ChildFrame>)null, false);
}

/** Closes all windows of the specified window class. */
/** Closes all child windows. */
public static int closeWindows(boolean forced) {
return closeWindow((Class<ChildFrame>)null, forced);
}

/**
* Closes all windows of the specified window class except for child windows with {@code closeOnReset} set to
* {@code false}.
*/
public static int closeWindow(Class<ChildFrame> frameClass) {
return closeWindow(frameClass, false);
}

/** Closes all windows of the specified window class. */
public static int closeWindow(Class<ChildFrame> frameClass, boolean forced) {
int retVal = 0;
WindowEvent event = new WindowEvent(NearInfinity.getInstance(), WindowEvent.WINDOW_CLOSING);
for (Iterator<ChildFrame> i = WINDOWS.iterator(); i.hasNext();) {
ChildFrame frame = i.next();
if (frameClass == null || frame.getClass() == frameClass) {
if ((forced || frame.isCloseOnReset()) && (frameClass == null || frame.getClass() == frameClass)) {
i.remove();
retVal++;
closeWindow(frame, event);
Expand All @@ -77,6 +93,31 @@ public static boolean closeWindow(ChildFrame frame) {
return retVal;
}

/**
* Signals all persistent {@link ChildFrame} instances that the game has been refreshed or a new game has been opened.
*
* @param refreshOnly Specify {@code true} if the game has only been refreshed.
*/
public static void fireGameReset(boolean refreshOnly) {
fireGameReset(null, refreshOnly);
}

/**
* Signals all persistent {@link ChildFrame} instances of the given class that the game has been refreshed or a new
* game has been opened.
*
* @param frameClass Filter by the specific {@code ChildFrame} class. Specify {@code null} to process all instances.
* @param refreshOnly Specify {@code true} if the game has only been refreshed.
*/
public static void fireGameReset(Class<ChildFrame> frameClass, boolean refreshOnly) {
for (Iterator<ChildFrame> iter = WINDOWS.iterator(); iter.hasNext(); ) {
final ChildFrame frame = iter.next();
if (!frame.isCloseOnReset() && (frameClass == null || frame.getClass() == frameClass)) {
frame.gameReset(refreshOnly);
}
}
}

/**
* Returns first window with specified class or {@code null} if such window do not exist.
*
Expand Down Expand Up @@ -170,10 +211,14 @@ public static void updateWindowGUIs() {
}

protected ChildFrame(String title) {
this(title, false);
this(title, false, true);
}

public ChildFrame(String title, boolean closeOnInvisible) {
this(title, closeOnInvisible, true);
}

public ChildFrame(String title, boolean closeOnInvisible, boolean closeOnReset) {
super(title);
setIconImages(NearInfinity.getInstance().getIconImages());
this.closeOnInvisible = closeOnInvisible;
Expand Down Expand Up @@ -222,6 +267,33 @@ public void close() {
WINDOWS.remove(this);
}

/**
* Returns whether the {@code ChildFrame} instance is automatically closed when the game is refreshed or closed.
*
* @return {@code true} if the frame is closed automatically if the game state changes, {@code false} otherwise.
*/
public boolean isCloseOnReset() {
return closeOnReset;
}

/**
* Specifies whether the {@code ChildFrame} instance is automatically closed when the game is refreshed or closed.
*
* @param b Specify whether to close the frame automatically if the game state changes.
*/
public void setCloseOnReset(boolean b) {
closeOnReset = b;
}

/**
* This method is called whenever the game has been refreshed or a new game has been opened. It is only relevant for
* frames that persist after refreshing or reopening games, i.e. when {@link #isCloseOnReset()} returns {@code false}.
*
* @param refreshOnly {@code true} if the game content has only been refreshed.
*/
protected void gameReset(boolean refreshOnly) {
}

/**
* This method is called whenever the dialog is about to be closed and removed from memory.
*
Expand Down
1 change: 1 addition & 0 deletions src/org/infinity/gui/InfinityAmp.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.infinity.util.SimpleListModel;
import org.infinity.util.io.StreamUtils;

@Deprecated
public final class InfinityAmp extends ChildFrame
implements ActionListener, ListSelectionListener, Runnable, Closeable {
private final SimpleListModel<ResourceEntry> allMusModel = new SimpleListModel<>();
Expand Down
Loading

0 comments on commit e2ec2d8

Please sign in to comment.