From e7f3c4461faac14fb6aab8fba876e2000fcfbdd6 Mon Sep 17 00:00:00 2001 From: "Diogo S. Martins" Date: Mon, 22 Jul 2024 14:24:27 -0300 Subject: [PATCH] =?UTF-8?q?solu=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- src/music/App.java | 142 +++++++++++++++++++--------- src/music/Duration.java | 52 +++++++++++ src/music/Player.java | 201 ++++++++++++++++++++++++++++++++++++++++ src/music/Playlist.java | 147 +++++++++++++++++++++++++++++ src/music/Track.java | 70 ++++++++++++++ 6 files changed, 570 insertions(+), 46 deletions(-) create mode 100644 src/music/Duration.java create mode 100644 src/music/Player.java create mode 100644 src/music/Playlist.java create mode 100644 src/music/Track.java diff --git a/README.md b/README.md index ae064ca..20def92 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -## Aula 09 - Classes (exercícios II) +## Aula 09 - Exercício Music Player -Código para um reprodutor de músicas (simulador). \ No newline at end of file +Solução do exercício proposto durante a aula 09. Veja o enunciado no Moodle. \ No newline at end of file diff --git a/src/music/App.java b/src/music/App.java index de95d4e..58daadd 100644 --- a/src/music/App.java +++ b/src/music/App.java @@ -1,68 +1,122 @@ package music; +/** + * Main class of the application + * + * Uncomment the code after you implemented the classes. + */ public class App { + /** + * Generate a playlist with some tracks + * + * @return the playlist + */ + // private static Playlist generatePlaylist() { + // Playlist playlist = new Playlist("Top 20 Hits Ever"); - // public static void simulatePlayer(Player player) { + // playlist.addTrack(new Track("Bohemian Rhapsody", "Queen", "A Night at the Opera", 1975, new Duration(555))); + // playlist.addTrack(new Track("Imagine", "John Lennon", "Imagine", 1971, new Duration(217))); // Duration in seconds + // playlist.addTrack(new Track("What a Wonderful World", "Louis Armstrong", "Satchmo Sings Darin", 1968, new Duration(128))); + // playlist.addTrack(new Track("Hallelujah", "Leonard Cohen", "Various Positions", 1984, new Duration(240))); + // playlist.addTrack(new Track("Singin' in the Rain", "Gene Kelly", "Singin' in the Rain", 1952, new Duration(208))); + // playlist.addTrack(new Track("Back in Black", "AC/DC", "Back in Black", 1980, new Duration(255))); + // playlist.addTrack(new Track("Billie Jean", "Michael Jackson", "Thriller", 1982, new Duration(334))); + // playlist.addTrack(new Track("Clair de Lune", "Claude Debussy", "Unknown", 1890, new Duration(280))); + // playlist.addTrack(new Track("Hey Jude", "The Beatles", "Hey Jude", 1968, new Duration(431))); + // playlist.addTrack(new Track("Hotel California", "Eagles", "Hotel California", 1976, new Duration(390))); + // playlist.addTrack(new Track("Lose Yourself", "Eminem", "The Eminem Show", 2002, new Duration(300))); + // playlist.addTrack(new Track("Respect", "Aretha Franklin", "I Never Loved a Man the Way I Love You", 1967, new Duration(157))); + // playlist.addTrack(new Track("Light My Fire", "The Doors", "The Doors", 1967, new Duration(428))); + // playlist.addTrack(new Track("A Whiter Shade of Pale", "Procol Harum", "Procol Harum", 1967, new Duration(249))); + // playlist.addTrack(new Track("Liège Concerto", "Saint-Saëns", "Unknown", 1913, new Duration(917))); + // playlist.addTrack(new Track("Somebody That I Used to Know", "Gotye ft. Kimbra", "Making Mirrors", 2011, new Duration(248))); + // playlist.addTrack(new Track("Can't Stop the Feeling!", "Justin Timberlake", "Trolls", 2016, new Duration(230))); + // playlist.addTrack(new Track("The Sound of Silence", "Simon & Garfunkel", "Wednesday Morning, 3 A.M.", 1964, new Duration(189))); + // playlist.addTrack(new Track("The Boxer", "Simon & Garfunkel", "Bridge over Troubled Water", 1970, new Duration(316))); + // playlist.addTrack(new Track("The Sound of Silence", "Disturbed", "Immortalized", 2015, new Duration(248))); + + // return playlist; + // } + + /** + * Test the playlist + * + * @param playlist the playlist to test + */ + // private static void playlistTest(Playlist playlist) { + // System.out.println("Playlist: " + playlist.getTitle()); + // System.out.println("Total duration: " + playlist.getDuration() + "s"); + // System.out.println("Number of tracks: " + playlist.getNumberOfTracks()); + // System.out.println("String representation: "); + // System.out.println(playlist); + // } + + /** + * Test the player, by calling some methods + * + * @param player the player to test + */ + // private static void playerTest(Player player) { + // System.out.println("Current track: " + player.getCurrentTrack().toString()); + + // player.next(); + // System.out.println("Next track: " + player.getCurrentTrack().toString()); + // player.next(); + // player.next(); + // player.next(); + // System.out.println("Next track: " + player.getCurrentTrack().toString()); + // player.previous(); + // System.out.println("Previous track: " + player.getCurrentTrack().toString()); + + // player.shuffle(); + + // System.out.println("Shuffled queue: "); + // System.out.println(player.getQueuePlaylist()); + // } + + /** + * Simulate the playback of the player, using a thread to simulate the time + * Due to the limitations of this model, it is not possible to skip the current track + * while it is playing + * + * @param player the player to simulate the playback + */ + // private static void simulatePlayback(Player player) { + // player.play(); // while (player.isPlaying()) { // System.out.println("Tocando: " + player.getCurrentTrack().toString()); // try { - // Thread.sleep(player.getCurrentTrack().getDuration() * 10); + // /** + // * Simulate the time of the track by using a thread sleep + // * The sleep method only accepts milliseconds, but the track duration is in seconds. + // * We multiply by 10 to simulate a faster playback and see the results faster + // * (the correct would be to muliply by 1000 to get the correct time) + // */ + // Thread.sleep(player.getCurrentTrack().getDuration().getSeconds() * 10); // } catch (InterruptedException e) { // e.printStackTrace(); // } // if (player.getQueuePlaylist().getNumberOfTracks() == 0) { // player.stop(); // } else { - // player.nextTrack(); + // player.next(); // } // } // } + + /** + * Start one of the main methods of the application + * @param args + */ public static void main(String[] args) { // Player player = new Player(); - // Playlist playlist = new Playlist("Top 20 Hits Ever"); - // playlist.addTrack(new Track("Bohemian Rhapsody", "Queen", "A Night at the Opera", 1975, 555)); - // playlist.addTrack(new Track("Imagine", "John Lennon", "Imagine", 1971, 217)); // Duration in seconds - // playlist.addTrack(new Track("What a Wonderful World", "Louis Armstrong", "Satchmo Sings Darin", 1968, 128)); - // playlist.addTrack(new Track("Hallelujah", "Leonard Cohen", "Various Positions", 1984, 240)); - // playlist.addTrack(new Track("Singin' in the Rain", "Gene Kelly", "Singin' in the Rain", 1952, 208)); - // playlist.addTrack(new Track("Back in Black", "AC/DC", "Back in Black", 1980, 255)); - // playlist.addTrack(new Track("Billie Jean", "Michael Jackson", "Thriller", 1982, 334)); - // playlist.addTrack(new Track("Clair de Lune", "Claude Debussy", "Unknown", 1890, 280)); - // playlist.addTrack(new Track("Hey Jude", "The Beatles", "Hey Jude", 1968, 431)); - // playlist.addTrack(new Track("Hotel California", "Eagles", "Hotel California", 1976, 390)); - // playlist.addTrack(new Track("Lose Yourself", "Eminem", "The Eminem Show", 2002, 300)); - // playlist.addTrack(new Track("Respect", "Aretha Franklin", "I Never Loved a Man the Way I Love You", 1967, 157)); - // playlist.addTrack(new Track("Light My Fire", "The Doors", "The Doors", 1967, 428)); - // playlist.addTrack(new Track("A Whiter Shade of Pale", "Procol Harum", "Procol Harum", 1967, 249)); - // playlist.addTrack(new Track("Liège Concerto", "Saint-Saëns", "Unknown", 1913, 917)); - // playlist.addTrack(new Track("Somebody That I Used to Know", "Gotye ft. Kimbra", "Making Mirrors", 2011, 248)); - // playlist.addTrack(new Track("Can't Stop the Feeling!", "Justin Timberlake", "Trolls", 2016, 230)); - // playlist.addTrack(new Track("The Sound of Silence", "Simon & Garfunkel", "Wednesday Morning, 3 A.M.", 1964, 189)); - // playlist.addTrack(new Track("The Boxer", "Simon & Garfunkel", "Bridge over Troubled Water", 1970, 316)); - // playlist.addTrack(new Track("The Sound of Silence", "Disturbed", "Immortalized", 2015, 248)); + // Playlist playlist = generatePlaylist(); + // playlistTest(playlist); // player.addPlaylist(playlist); - // System.out.println("Fila de reprodução: "); - // System.out.println(player.getQueuePlaylist()); - - // player.shuffle(); - - // System.out.println("Fila de reprodução: "); - // System.out.println(player.getQueuePlaylist()); - - // player.play(); - - // System.out.println("Música atual: " + player.getCurrentTrack().toString()); - - // player.nextTrack(); - // System.out.println("Próxima música: " + player.getCurrentTrack().toString()); - // player.nextTrack(); - // player.nextTrack(); - // player.nextTrack(); - // System.out.println("Próxima música: " + player.getCurrentTrack().toString()); - // player.previousTrack(); - // System.out.println("Música anterior: " + player.getCurrentTrack().toString()); + // // playerTest(player); + // simulatePlayback(player); } } diff --git a/src/music/Duration.java b/src/music/Duration.java new file mode 100644 index 0000000..36ba36c --- /dev/null +++ b/src/music/Duration.java @@ -0,0 +1,52 @@ +package music; + +/** + * Represents a duration in seconds. + * This is an immutable object, because the duration should not change. + */ +public class Duration { + private final int duration; + + /** + * Creates a new duration + * + * @param duration the duration in seconds + * @throws IllegalArgumentException if the duration is negative + */ + public Duration(int duration) { + if (duration < 0) { + throw new IllegalArgumentException("Invalid duration"); + } + this.duration = duration; + } + + /** + * Gets the duration in seconds + * + * @return the duration in seconds + */ + public int getSeconds() { + return duration; + } + + /** + * Add a duration to the current duration + * + * @param duration the duration to be added + * @return a new duration with the sum of the durations + */ + public Duration add(Duration duration) { + return new Duration(this.duration + duration.getSeconds()); + } + + /** + * Show the duration in the format (hh:mm:ss) + */ + public String toString() { + int hours = duration / 3600; + int minutes = (duration % 3600) / 60; + int seconds = duration % 60; + + return String.format("%02d:%02d:%02d", hours, minutes, seconds); + } +} diff --git a/src/music/Player.java b/src/music/Player.java new file mode 100644 index 0000000..54d3486 --- /dev/null +++ b/src/music/Player.java @@ -0,0 +1,201 @@ +package music; + +import java.util.ArrayList; +import java.util.Collections; + +/** + * Represents a music player. + * This is a mutable object, as there are methods to add and remove tracks from + * the queue, play, pause, stop, skip tracks, shuffle the queue, and change the + * volume. + */ +public class Player { + private ArrayList queue; + private boolean playing; + private int volume; + private Track currentTrack; + + /** + * Creates a new player + */ + public Player() { + this.queue = new ArrayList<>(); + this.playing = false; + this.volume = 50; + this.currentTrack = null; + } + + /** + * Add a track to the queue + * + * @param track the track to be added + */ + public void addTrack(Track track) { + queue.add(track); + } + + /** + * Remove a track from the queue + * + * @param track + */ + public void removeTrack(Track track) { + queue.remove(track); + } + + /** + * Add a playlist to the end of the queue + * + * @param playlist the playlist to be added + */ + public void addPlaylist(Playlist playlist) { + for (Track track : playlist.getTracks()) { + addTrack(track); + } + } + + /** + * Play the current track. + * If not track is currently playing, the first track in the queue is played. + * + * @throws IllegalStateException if there are no tracks in the queue + */ + public void play() { + if (queue.size() == 0) { + throw new IllegalStateException("Nenhuma faixa na playlist"); + } + if (currentTrack == null) { + currentTrack = queue.get(0); + } + playing = true; + } + + /** + * Pause the current track. + * For simplicity purposes, this method is equivalent to stop. + */ + public void pause() { + stop(); + } + + /** + * Stop the current track. + * If play is called again, the track will start from the beginning. + */ + public void stop() { + playing = false; + } + + /** + * Check if the player is currently playing a track + * + * @return true if the player is playing a track, false otherwise + */ + public boolean isPlaying() { + return playing; + } + + /** + * Get a playlist with the tracks in the queue. + * + * @return a playlist with the tracks in the queue + */ + public Playlist getQueuePlaylist() { + Playlist playlist = new Playlist("Queue"); + + for (Track track : queue) { + playlist.addTrack(track); + } + + return playlist; + } + + /** + * Get the current volume. + * + * @return the current volume + */ + public int getVolume() { + return volume; + } + + /** + * Set the volume. + * + * @param volume the new volume, between 0 and 100 + * @throws IllegalArgumentException if the volume is not between 0 and 100 + */ + public void setVolume(int volume) { + if (volume < 0 || volume > 100) { + throw new IllegalArgumentException("Volume inválido"); + } + this.volume = volume; + } + + /** + * Get the current track. + * + * @return the current track + */ + public Track getCurrentTrack() { + return currentTrack; + } + + /** + * Skip to next track in the queue. + * If the current track is the last in the queue, the player stops. + * + * @throws IllegalStateException if there are no tracks in the queue + */ + public void next() { + if (queue.size() == 0) { + throw new IllegalStateException("Nenhuma faixa na playlist"); + } + int index = queue.indexOf(currentTrack); + index++; + + if (index >= queue.size()) { + index = 0; + stop(); + } + currentTrack = queue.get(index); + } + + + /** + * Skip to previous track in the queue. + * If the current track is the first in the queue, the player stops. + * + * @throws IllegalStateException if there are no tracks in the queue + */ + public void previous() { + if (queue.size() == 0) { + throw new IllegalStateException("Nenhuma faixa na playlist"); + } + int index = queue.indexOf(currentTrack); + index--; + + if (index < 0) { + index = 0; + } + currentTrack = queue.get(index); + } + + /** + * Shuffle the remaining queue. + * If the player is currently playing a track, only shuffle the remaining tracks. + * If the player is not playing, shuffle the entire queue. + * If there are no tracks in the queue, do nothing. + */ + public void shuffle() { + if (queue.size() == 0) { + return; + } + int index = queue.indexOf(currentTrack); + ArrayList remainingQueue = new ArrayList<>(queue.subList(index, queue.size())); + Collections.shuffle(remainingQueue); + + queue.subList(index, queue.size()).clear(); + queue.addAll(remainingQueue); + } +} diff --git a/src/music/Playlist.java b/src/music/Playlist.java new file mode 100644 index 0000000..38fb536 --- /dev/null +++ b/src/music/Playlist.java @@ -0,0 +1,147 @@ +package music; + +import java.util.ArrayList; + +/** + * Represents a playlist of tracks. + * + * This is a mutable object, as there are methods to add and remove tracks from + * the playlist as well as change its title. + */ +public class Playlist { + private String title; + private final ArrayList tracks; + + /** + * Creates a new playlist + * + * @param title the title of the playlist + * @throws IllegalArgumentException if the title is invalid + */ + public Playlist(String title) { + if (title == null || title.isBlank()) { + throw new IllegalArgumentException("Invalid title"); + } + this.title = title; + this.tracks = new ArrayList<>(); + } + + /** + * Add a track to end of the playlist. + * + * @param track the track to be added + * @throws IllegalArgumentException if the track is invalid + */ + public void addTrack(Track track) { + if (track == null) { + throw new IllegalArgumentException("Invalid track"); + } + tracks.add(track); + } + + /** + * Remove a track from the playlist + * + * @param track the track to be removed + * @throws IllegalArgumentException if the track is invalid + */ + public void removeTrack(Track track) { + if (track == null) { + throw new IllegalArgumentException("Invalid track"); + } + tracks.remove(track); + } + + /** + * Gets the tracks in the playlist + * + * @return an array of tracks + */ + public Track[] getTracks() { + /* + * We use the toArray method to convert the ArrayList to an array of tracks. + * In this way, we do not expose the internal representation of the playlist as an ArrayList. + */ + return tracks.toArray(new Track[0]); + } + + /** + * Query the total duration of the playlist. + * + * @return the total duration of the playlist in seconds + */ + public Duration getDuration() { + Duration total = new Duration(0); + + for (Track track : tracks) { + total = total.add(track.getDuration()); + } + + return total; + } + + /** + * Get the title of the playlist. + * + * @return the title of the playlist + */ + public String getTitle() { + return title; + } + + /** + * Set the title of the playlist. + * + * @param title the new title of the playlist + * @throws IllegalArgumentException if the title is invalid + */ + public void setTitle(String title) { + if (title == null || title.isBlank()) { + throw new IllegalArgumentException("Invalid title"); + } + this.title = title; + } + + /** + * Get the number of tracks in the playlist. + * + * @return the number of tracks in the playlist + */ + public int getNumberOfTracks() { + return tracks.size(); + } + + /** + * Clear the playlist. + */ + public void clear() { + tracks.clear(); + } + + /** + * String representation of the playlist. + * The strings starts with the title of the playlist, followed by the duration in seconds. + * Then, each track is listed with its index, title, artist, album, year and duration. + */ + @Override + public String toString() { + /** + * We use a StringBuilder to concatenate the strings, as it is more efficient than using the + operator + * We should never use the + operator to concatenate strings in a loop, as it creates a new string each time + * (Strings are immutable in Java, so each concatenation creates a new string in memory) + */ + StringBuilder sb = new StringBuilder(); + int i = 1; + + sb.append(title).append(" (").append(getDuration()).append("s)\n"); + /** + * We delegate the string representation of the track to the Track class + * This is an example of separation of concerns, where each class is responsible for its own representation + */ + for (Track track : tracks) { + sb.append(i++ + ". ").append(track).append("\n"); + } + + return sb.toString(); + } +} diff --git a/src/music/Track.java b/src/music/Track.java new file mode 100644 index 0000000..53f26b5 --- /dev/null +++ b/src/music/Track.java @@ -0,0 +1,70 @@ +package music; + +/** + * Represents a track in a music library. + * This is an immutable object, because track information should not change. + */ +public class Track { + private final String title; + private final String artist; + private final String album; + private final int year; + private final Duration duration; + + /** + * Creates a new track + * + * @param title the title of the track + * @param artist the artist of the track + * @param album the album of the track + * @param year the year of the track + * @param duration the duration of the track in seconds + * @throws IllegalArgumentException if any of the parameters are invalid + */ + public Track(String title, String artist, String album, int year, Duration duration) { + if (title == null || title.isBlank()) { + throw new IllegalArgumentException("Invalid title"); + } + if (artist == null || artist.isBlank()) { + throw new IllegalArgumentException("Invalid artist"); + } + if (album == null || album.isBlank()) { + throw new IllegalArgumentException("Invalid album"); + } + if (year < 0) { + throw new IllegalArgumentException("Invalid year"); + } + if (duration == null) { + throw new IllegalArgumentException("Invalid duration"); + } + this.title = title; + this.artist = artist; + this.album = album; + this.year = year; + this.duration = duration; + } + + public String getTitle() { + return title; + } + + public String getArtist() { + return artist; + } + + public Duration getDuration() { + return duration; + } + + public String getAlbum() { + return album; + } + + public int getYear() { + return year; + } + + public String toString() { + return title + " - " + artist + " (" + album + ", " + year + ") " + duration; + } +}