Complete assignment #1
Generated
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -4,7 +4,6 @@ import javafx.application.Application;
|
||||
|
||||
public class Launcher{
|
||||
public static void main(String[] args) {
|
||||
// TODO: Launch the JavaFX application by calling Application.launch(Main.class)
|
||||
Application.launch(MusicApp.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,19 +5,20 @@ import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class MusicApp extends Application {
|
||||
|
||||
@Override
|
||||
public void start(Stage stage) throws IOException
|
||||
{
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(MusicApp.class.getResource("App-view.fxml"));
|
||||
Scene scene = new Scene(fxmlLoader.load());
|
||||
public void start(Stage stage) throws Exception {
|
||||
FXMLLoader loader = new FXMLLoader(
|
||||
getClass().getResource("App-view.fxml"));
|
||||
Scene scene = new Scene(loader.load());
|
||||
stage.setTitle("Music Player");
|
||||
stage.setScene(scene);
|
||||
stage.show();
|
||||
stage.setResizable(false);
|
||||
stage.show();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,387 @@
|
||||
package sbu.javafx;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.media.Media;
|
||||
import javafx.scene.media.MediaPlayer;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
public class MusicController {
|
||||
|
||||
// TODO: Define your UI components using the @FXML annotation.
|
||||
// ========================================= fxml Fields ============================================
|
||||
// player panel
|
||||
@FXML private ImageView albumCover;
|
||||
@FXML private Label songTitle, artistName;
|
||||
@FXML private Label currentTime, totalTime;
|
||||
@FXML private Button playPauseBtn, shuffleBtn, repeatBtn;
|
||||
@FXML private Button libraryTabBtn, playlistTabBtn;
|
||||
@FXML private Slider progressSlider, volumeSlider;
|
||||
|
||||
//TODO: Implement the logic to play a song. This method should update the UI to show which song is currently playing.
|
||||
// library and playlists panel
|
||||
@FXML private VBox libraryPane, playlistsPane, playlistsListPane, playlistDetailPane;
|
||||
@FXML private ListView<Song> libraryView;
|
||||
@FXML private ListView<String> playlistsView;
|
||||
@FXML private ListView<Song> playlistDetailView;
|
||||
@FXML private Label libraryCount, playlistDetailName;
|
||||
|
||||
// my fields
|
||||
private MediaPlayer mediaPlayer;
|
||||
private ObservableList<Song> library = FXCollections.observableArrayList();
|
||||
private HashMap<String, ObservableList<Song>> playlists = new HashMap<>();
|
||||
|
||||
private List<Song> playQueue = new ArrayList<>();
|
||||
private int currentIndex = -1;
|
||||
private boolean isPlaying = false;
|
||||
private boolean isShuffle = false;
|
||||
private boolean isRepeat = false;
|
||||
|
||||
// ========================================= Methods ============================================
|
||||
public void initialize(){ // runs without calling
|
||||
libraryView.setItems(library);
|
||||
|
||||
// volume
|
||||
volumeSlider.valueProperty().addListener((obs, oldVal, newVal) -> {
|
||||
if(mediaPlayer != null){
|
||||
mediaPlayer.setVolume(newVal.doubleValue());
|
||||
}
|
||||
});
|
||||
|
||||
// progress slider - seek functionality
|
||||
progressSlider.setOnMouseReleased(e -> {
|
||||
if(mediaPlayer != null){
|
||||
mediaPlayer.seek(Duration.seconds(progressSlider.getValue()));
|
||||
}
|
||||
});
|
||||
progressSlider.setMax(100);
|
||||
|
||||
// library - double click to play
|
||||
libraryView.setOnMouseClicked(e -> {
|
||||
if(e.getClickCount() == 2){ // double click
|
||||
Song selectedSong = libraryView.getSelectionModel().getSelectedItem();
|
||||
if(selectedSong != null){
|
||||
playQueue = new ArrayList<>(library);
|
||||
currentIndex = playQueue.indexOf(selectedSong);
|
||||
playSong(selectedSong);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// playlist detail - double click to play
|
||||
playlistDetailView.setOnMouseClicked(e -> {
|
||||
if(e.getClickCount() == 2){ // double click
|
||||
Song selectedSong = playlistDetailView.getSelectionModel().getSelectedItem();
|
||||
if(selectedSong != null){
|
||||
playQueue = new ArrayList<>(playlistDetailView.getItems());
|
||||
currentIndex = playQueue.indexOf(selectedSong);
|
||||
playSong(selectedSong);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// playlists - double click to open
|
||||
playlistsView.setOnMouseClicked(e -> {
|
||||
if(e.getClickCount() == 2){ // double click
|
||||
String selectedPlaylist = playlistsView.getSelectionModel().getSelectedItem();
|
||||
if(selectedPlaylist != null){
|
||||
openPlayList(selectedPlaylist);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setupLibraryItems();
|
||||
|
||||
}
|
||||
|
||||
// ========================================= Playback ============================================
|
||||
private void playSong(Song song) {
|
||||
if(mediaPlayer != null){
|
||||
mediaPlayer.stop();
|
||||
mediaPlayer.dispose(); // file not in use
|
||||
}
|
||||
|
||||
File file = new File(song.getFilePath());
|
||||
if (!file.exists()) {
|
||||
System.out.println("FILE NOT FOUND!");
|
||||
return;
|
||||
}
|
||||
|
||||
Media media = new Media(file.toURI().toString());
|
||||
mediaPlayer = new MediaPlayer(media);
|
||||
mediaPlayer.setVolume(volumeSlider.getValue());
|
||||
|
||||
// UI changes
|
||||
songTitle.setText(song.getTitle());
|
||||
artistName.setText(song.getArtist());
|
||||
loadCover(song);
|
||||
|
||||
// progress bar
|
||||
mediaPlayer.currentTimeProperty().addListener((obs, oldVal, newVal) -> {
|
||||
if(!progressSlider.isValueChanging()){
|
||||
progressSlider.setValue(newVal.toSeconds());
|
||||
currentTime.setText(formatTime(newVal.toSeconds()));
|
||||
}
|
||||
});
|
||||
|
||||
mediaPlayer.setOnReady(() -> {
|
||||
double total= mediaPlayer.getTotalDuration().toSeconds();
|
||||
progressSlider.setMax(total);
|
||||
totalTime.setText(formatTime(total));
|
||||
});
|
||||
|
||||
mediaPlayer.setOnEndOfMedia(() -> {
|
||||
if(isRepeat){
|
||||
mediaPlayer.seek(Duration.ZERO);
|
||||
mediaPlayer.play();
|
||||
}
|
||||
else{
|
||||
handleNext();
|
||||
}
|
||||
});
|
||||
mediaPlayer.play();
|
||||
isPlaying = true;
|
||||
playPauseBtn.setText("⏸");
|
||||
libraryView.refresh();
|
||||
}
|
||||
|
||||
private void loadCover(Song song){
|
||||
try{
|
||||
if(song.getCoverPath() != null && !song.getCoverPath().isEmpty()){
|
||||
albumCover.setImage(new Image(
|
||||
new File(song.getCoverPath()).toURI().toString()
|
||||
));
|
||||
}
|
||||
else{
|
||||
albumCover.setImage(new Image(
|
||||
Objects.requireNonNull(getClass().getResourceAsStream("/sbu/images/default-cover.png"))
|
||||
));
|
||||
}
|
||||
} catch(Exception e){
|
||||
albumCover.setImage(null);
|
||||
}
|
||||
}
|
||||
|
||||
private String formatTime(double seconds) {
|
||||
int minute = (int) seconds / 60;
|
||||
int second = (int) seconds % 60;
|
||||
return String.format("%d:%02d", minute, second);
|
||||
}
|
||||
|
||||
// ========================================= Control buttons ============================================
|
||||
@FXML
|
||||
public void handlePlayPause() {
|
||||
if(mediaPlayer == null) return;
|
||||
|
||||
if(isPlaying){
|
||||
mediaPlayer.pause();
|
||||
playPauseBtn.setText("▶");
|
||||
}
|
||||
else{
|
||||
mediaPlayer.play();
|
||||
playPauseBtn.setText("⏸");
|
||||
}
|
||||
isPlaying = !isPlaying;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void handlePlayAction() {
|
||||
//Update labels, change button icons, etc.
|
||||
public void handleNext() {
|
||||
if(playQueue.isEmpty()) return;
|
||||
if(isShuffle){
|
||||
currentIndex = (int) (Math.random() * playQueue.size());
|
||||
}
|
||||
else{
|
||||
currentIndex = (currentIndex + 1) % playQueue.size(); // when we reach end of the queue it goes to the first one
|
||||
}
|
||||
playSong(playQueue.get(currentIndex));
|
||||
}
|
||||
|
||||
//TODO: Logic to add a specific song to the user's playlist.
|
||||
|
||||
public void addToPlaylist(String songName) {
|
||||
//Add the song to a list or update a ListView.
|
||||
@FXML
|
||||
public void handlePrev() {
|
||||
if(playQueue.isEmpty()) return;
|
||||
currentIndex = (currentIndex - 1 + playQueue.size()) % playQueue.size();
|
||||
playSong(playQueue.get(currentIndex));
|
||||
}
|
||||
|
||||
// TODO:This method should open a new window or change the current scene to display the "Playlist" page.
|
||||
|
||||
public void openPlaylistWindow() {
|
||||
//Create a new stage and show the playlist UI.
|
||||
@FXML
|
||||
public void handleShuffle() {
|
||||
isShuffle = !isShuffle;
|
||||
if(isShuffle) shuffleBtn.getStyleClass().add("active-btn");
|
||||
else shuffleBtn.getStyleClass().remove("active-btn");
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void handleRepeat() {
|
||||
isRepeat = !isRepeat;
|
||||
if (isRepeat) repeatBtn.getStyleClass().add("active-btn");
|
||||
else repeatBtn.getStyleClass().remove("active-btn");
|
||||
}
|
||||
|
||||
// ========================================= Library ============================================
|
||||
@FXML
|
||||
public void handleAddSong() {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle("Add Songs");
|
||||
fileChooser.getExtensionFilters().add(
|
||||
new FileChooser.ExtensionFilter("Audio Files", "*.mp3", "*.wav", "*.aac", "*.flac")
|
||||
);
|
||||
|
||||
List<File> files = fileChooser.showOpenMultipleDialog(null);
|
||||
if(files == null) return;
|
||||
for(File file : files){
|
||||
String name = file.getName();
|
||||
Song song = new Song(name, "Unknown", "Unknown", file.getAbsolutePath(), "", 0);
|
||||
library.add(song);
|
||||
}
|
||||
libraryCount.setText(library.size() + " Songs");
|
||||
}
|
||||
|
||||
private void setupLibraryItems() {
|
||||
libraryView.setCellFactory(lv -> new ListCell<>() {
|
||||
private final ImageView cover = new ImageView();
|
||||
private final Label index = new Label();
|
||||
private final Label title = new Label();
|
||||
private final Label artist = new Label();
|
||||
private final Label duration = new Label();
|
||||
private final VBox info = new VBox(2, title, artist);
|
||||
private final javafx.scene.layout.HBox root = new javafx.scene.layout.HBox(10, index, cover, info, duration);
|
||||
|
||||
// Context Menu
|
||||
private final ContextMenu contextMenu = new ContextMenu();
|
||||
private final Menu addToPlaylistMenu = new Menu("Add to Playlist");
|
||||
|
||||
{
|
||||
cover.setFitWidth(36);
|
||||
cover.setFitHeight(36);
|
||||
cover.setPreserveRatio(true);
|
||||
index.setStyle("-fx-text-fill: #9E9E9E; -fx-font-size: 11px; -fx-min-width: 20px;");
|
||||
title.setStyle("-fx-font-size: 13px; -fx-text-fill: #212121;");
|
||||
artist.setStyle("-fx-font-size: 11px; -fx-text-fill: #757575;");
|
||||
duration.setStyle("-fx-font-size: 11px; -fx-text-fill: #9E9E9E;");
|
||||
javafx.scene.layout.HBox.setHgrow(info, javafx.scene.layout.Priority.ALWAYS);
|
||||
root.setAlignment(javafx.geometry.Pos.CENTER_LEFT);
|
||||
root.setPadding(new javafx.geometry.Insets(4, 8, 4, 8));
|
||||
|
||||
// Context Menu
|
||||
contextMenu.getItems().add(addToPlaylistMenu);
|
||||
contextMenu.setOnShowing(e -> {
|
||||
addToPlaylistMenu.getItems().clear();
|
||||
if (playlists.isEmpty()) {
|
||||
MenuItem none = new MenuItem("No playlists yet");
|
||||
none.setDisable(true);
|
||||
addToPlaylistMenu.getItems().add(none);
|
||||
} else {
|
||||
for (String name : playlists.keySet()) {
|
||||
MenuItem item = new MenuItem(name);
|
||||
item.setOnAction(ev -> {
|
||||
Song song = getItem();
|
||||
if (song != null) playlists.get(name).add(song);
|
||||
});
|
||||
addToPlaylistMenu.getItems().add(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(Song song, boolean empty) {
|
||||
super.updateItem(song, empty);
|
||||
if (empty || song == null) {
|
||||
setGraphic(null);
|
||||
setContextMenu(null);
|
||||
} else {
|
||||
index.setText(String.valueOf(getIndex() + 1));
|
||||
title.setText(song.getTitle());
|
||||
artist.setText(song.getArtist());
|
||||
duration.setText(song.getFormattedDuration());
|
||||
|
||||
try {
|
||||
if (song.getCoverPath() != null && !song.getCoverPath().isEmpty()) {
|
||||
cover.setImage(new Image(new File(song.getCoverPath()).toURI().toString(), true));
|
||||
} else {
|
||||
cover.setImage(new Image(
|
||||
Objects.requireNonNull(getClass().getResourceAsStream("/sbu/images/default-cover.png"))));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
cover.setImage(null);
|
||||
}
|
||||
|
||||
if (getIndex() == currentIndex && playQueue.equals(new ArrayList<>(library))) {
|
||||
title.setStyle("-fx-font-size: 13px; -fx-text-fill: #1DB954; -fx-font-weight: bold;");
|
||||
} else {
|
||||
title.setStyle("-fx-font-size: 13px; -fx-text-fill: #212121;");
|
||||
}
|
||||
|
||||
setGraphic(root);
|
||||
setContextMenu(contextMenu);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ========================================= Playlists ============================================
|
||||
@FXML
|
||||
public void handleNewPlaylist() {
|
||||
TextInputDialog dialog = new TextInputDialog();
|
||||
dialog.setTitle("New Playlist");
|
||||
dialog.setHeaderText(null);
|
||||
dialog.setContentText("Playlist name: ");
|
||||
dialog.showAndWait().ifPresent(name -> {
|
||||
if(!name.isBlank() && !playlists.containsKey(name)){
|
||||
playlists.put(name, FXCollections.observableArrayList());
|
||||
playlistsView.setItems( FXCollections.observableArrayList(playlists.keySet()) );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void openPlayList(String name) {
|
||||
playlistDetailName.setText(name);
|
||||
playlistDetailView.setItems(playlists.get(name));
|
||||
playlistsListPane.setVisible(false);
|
||||
playlistsListPane.setManaged(false);
|
||||
playlistDetailPane.setVisible(true);
|
||||
playlistDetailPane.setManaged(true);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void handleBackToPlaylists() {
|
||||
playlistDetailPane.setVisible(false);
|
||||
playlistDetailPane.setManaged(false);
|
||||
playlistsListPane.setVisible(true);
|
||||
playlistsListPane.setManaged(true);
|
||||
}
|
||||
|
||||
// switching between tabs (library and playlists)
|
||||
@FXML
|
||||
public void showLibrary() {
|
||||
libraryPane.setVisible(true);
|
||||
libraryPane.setManaged(true);
|
||||
playlistsPane.setVisible(false);
|
||||
playlistsPane.setManaged(false);
|
||||
|
||||
libraryTabBtn.getStyleClass().add("tab-active");
|
||||
playlistTabBtn.getStyleClass().remove("tab-active");
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void showPlaylists() {
|
||||
playlistsPane.setVisible(true);
|
||||
playlistsPane.setManaged(true);
|
||||
libraryPane.setVisible(false);
|
||||
libraryPane.setManaged(false);
|
||||
|
||||
playlistTabBtn.getStyleClass().add("tab-active");
|
||||
libraryTabBtn.getStyleClass().remove("tab-active");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package sbu.javafx;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Playlist {
|
||||
|
||||
private String name;
|
||||
private List<Song> songs = new ArrayList<>();
|
||||
|
||||
// Constructor
|
||||
public Playlist(String name){
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
// ========================================= getters & setters ============================================
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public List<Song> getSongs() {
|
||||
return songs;
|
||||
}
|
||||
|
||||
// ========================================= Methods ============================================
|
||||
public void addSong(Song song){
|
||||
if(song != null && !songs.contains(song))
|
||||
songs.add(song);
|
||||
}
|
||||
|
||||
public void removeSong(Song song){
|
||||
songs.remove(song);
|
||||
}
|
||||
|
||||
public int size(){
|
||||
return songs.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name + " (" + songs.size() + " songs";
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ public class Song {
|
||||
}
|
||||
|
||||
// ========================================= Methods ============================================
|
||||
public String formattedDuration(){
|
||||
public String getFormattedDuration(){
|
||||
int minutes = duration / 60;
|
||||
int seconds = duration % 60;
|
||||
return String.format("%d:%02d)", minutes, seconds);
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
Reference in New Issue
Block a user