Java tutorial
/* * Copyright (c) 2011, Pro JavaFX Authors * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the name of JFXtras nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ import java.io.File; import java.net.URL; import javafx.application.Application; import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.MapChangeListener; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.geometry.VPos; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.LabelBuilder; import javafx.scene.control.Slider; import javafx.scene.effect.Reflection; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.DragEvent; import javafx.scene.input.Dragboard; import javafx.scene.input.TransferMode; import javafx.scene.layout.BorderPane; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.RowConstraints; import javafx.scene.media.Media; import javafx.scene.media.MediaPlayer; import javafx.scene.media.MediaPlayer.Status; import javafx.stage.FileChooser; import javafx.stage.Stage; import javafx.util.Duration; import com.sun.javafx.runtime.VersionInfo; /** * @author dean */ class AudioPlayer3 extends Application { private final SongModel songModel; private MetadataView metaDataView; private PlayerControlsView playerControlsView; public static void main(String[] args) { launch(args); } public AudioPlayer3() { songModel = new SongModel(); } @Override public void start(Stage primaryStage) { System.out.println("JavaFX version: " + VersionInfo.getRuntimeVersion()); songModel.setURL("http://traffic.libsyn.com/dickwall/JavaPosse373.mp3"); metaDataView = new MetadataView(songModel); playerControlsView = new PlayerControlsView(songModel); final BorderPane root = new BorderPane(); root.setCenter(metaDataView.getViewNode()); root.setBottom(playerControlsView.getViewNode()); final Scene scene = new Scene(root, 800, 400); initSceneDragAndDrop(scene); final URL stylesheet = getClass().getResource("media.css"); scene.getStylesheets().add(stylesheet.toString()); primaryStage.setScene(scene); primaryStage.setTitle("Audio Player 3"); primaryStage.show(); } private void initSceneDragAndDrop(Scene scene) { scene.setOnDragOver(new EventHandler<DragEvent>() { @Override public void handle(DragEvent event) { Dragboard db = event.getDragboard(); if (db.hasFiles() || db.hasUrl()) { event.acceptTransferModes(TransferMode.ANY); } event.consume(); } }); scene.setOnDragDropped(new EventHandler<DragEvent>() { @Override public void handle(DragEvent event) { Dragboard db = event.getDragboard(); String url = null; if (db.hasFiles()) { url = db.getFiles().get(0).toURI().toString(); } else if (db.hasUrl()) { url = db.getUrl(); } if (url != null) { songModel.setURL(url); songModel.getMediaPlayer().play(); } event.setDropCompleted(url != null); event.consume(); } }); } } /** * @author dean */ class MetadataView extends AbstractView { public MetadataView(SongModel songModel) { super(songModel); } @Override protected Node initView() { final Label title = createLabel("title"); final Label artist = createLabel("artist"); final Label album = createLabel("album"); final Label year = createLabel("year"); final ImageView albumCover = createAlbumCover(); title.textProperty().bind(songModel.titleProperty()); artist.textProperty().bind(songModel.artistProperty()); album.textProperty().bind(songModel.albumProperty()); year.textProperty().bind(songModel.yearProperty()); albumCover.imageProperty().bind(songModel.albumCoverProperty()); final GridPane gp = new GridPane(); gp.setPadding(new Insets(10)); gp.setHgap(20); gp.add(albumCover, 0, 0, 1, GridPane.REMAINING); gp.add(title, 1, 0); gp.add(artist, 1, 1); gp.add(album, 1, 2); gp.add(year, 1, 3); final ColumnConstraints c0 = new ColumnConstraints(); final ColumnConstraints c1 = new ColumnConstraints(); c1.setHgrow(Priority.ALWAYS); gp.getColumnConstraints().addAll(c0, c1); final RowConstraints r0 = new RowConstraints(); r0.setValignment(VPos.TOP); gp.getRowConstraints().addAll(r0, r0, r0, r0); return gp; } private Label createLabel(String id) { return LabelBuilder.create().id(id).build(); } private ImageView createAlbumCover() { final Reflection reflection = new Reflection(); reflection.setFraction(0.2); final ImageView albumCover = new ImageView(); albumCover.setFitWidth(240); albumCover.setPreserveRatio(true); albumCover.setSmooth(true); albumCover.setEffect(reflection); return albumCover; } } /** * @author dean */ class PlayerControlsView extends AbstractView { private Image pauseImg; private Image playImg; private ImageView playPauseIcon; private StatusListener statusListener; private CurrentTimeListener currentTimeListener; private Node controlPanel; private Label statusLabel; private Label currentTimeLabel; private Label totalDurationLabel; private Slider volumeSlider; private Slider positionSlider; public PlayerControlsView(SongModel songModel) { super(songModel); songModel.mediaPlayerProperty().addListener(new MediaPlayerListener()); statusListener = new StatusListener(); currentTimeListener = new CurrentTimeListener(); addListenersAndBindings(songModel.getMediaPlayer()); } @Override protected Node initView() { final Button openButton = createOpenButton(); controlPanel = createControlPanel(); volumeSlider = createSlider("volumeSlider"); statusLabel = createLabel("Buffering", "statusDisplay"); positionSlider = createSlider("positionSlider"); totalDurationLabel = createLabel("00:00", "mediaText"); currentTimeLabel = createLabel("00:00", "mediaText"); positionSlider.valueChangingProperty().addListener(new PositionListener()); final ImageView volLow = new ImageView(); volLow.setId("volumeLow"); final ImageView volHigh = new ImageView(); volHigh.setId("volumeHigh"); final GridPane gp = new GridPane(); gp.setHgap(1); gp.setVgap(1); gp.setPadding(new Insets(10)); final ColumnConstraints buttonCol = new ColumnConstraints(100); final ColumnConstraints spacerCol = new ColumnConstraints(40, 80, 80); final ColumnConstraints middleCol = new ColumnConstraints(); middleCol.setHgrow(Priority.ALWAYS); gp.getColumnConstraints().addAll(buttonCol, spacerCol, middleCol, spacerCol, buttonCol); GridPane.setValignment(openButton, VPos.BOTTOM); GridPane.setHalignment(volHigh, HPos.RIGHT); GridPane.setValignment(volumeSlider, VPos.TOP); GridPane.setHalignment(statusLabel, HPos.RIGHT); GridPane.setValignment(statusLabel, VPos.TOP); GridPane.setHalignment(currentTimeLabel, HPos.RIGHT); gp.add(openButton, 0, 0, 1, 3); gp.add(volLow, 1, 0); gp.add(volHigh, 1, 0); gp.add(volumeSlider, 1, 1); gp.add(controlPanel, 2, 0, 1, 2); gp.add(statusLabel, 3, 1); gp.add(currentTimeLabel, 1, 2); gp.add(positionSlider, 2, 2); gp.add(totalDurationLabel, 3, 2); return gp; } private Button createOpenButton() { final Button openButton = new Button(); openButton.setId("openButton"); openButton.setOnAction(new OpenHandler()); openButton.setPrefWidth(32); openButton.setPrefHeight(32); return openButton; } private Node createControlPanel() { final HBox hbox = new HBox(); hbox.setAlignment(Pos.CENTER); hbox.setFillHeight(false); final Button playPauseButton = createPlayPauseButton(); final Button seekStartButton = new Button(); seekStartButton.setId("seekStartButton"); seekStartButton.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { seekAndUpdatePosition(Duration.ZERO); } }); final Button seekEndButton = new Button(); seekEndButton.setId("seekEndButton"); seekEndButton.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { final MediaPlayer mediaPlayer = songModel.getMediaPlayer(); final Duration totalDuration = mediaPlayer.getTotalDuration(); final Duration oneSecond = Duration.seconds(1); seekAndUpdatePosition(totalDuration.subtract(oneSecond)); } }); hbox.getChildren().addAll(seekStartButton, playPauseButton, seekEndButton); return hbox; } private Button createPlayPauseButton() { URL url = getClass().getResource("resources/pause.png"); pauseImg = new Image(url.toString()); url = getClass().getResource("resources/play.png"); playImg = new Image(url.toString()); playPauseIcon = new ImageView(playImg); final Button playPauseButton = new Button(null, playPauseIcon); playPauseButton.setId("playPauseButton"); playPauseButton.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent arg0) { final MediaPlayer mediaPlayer = songModel.getMediaPlayer(); if (mediaPlayer.getStatus() == MediaPlayer.Status.PLAYING) { mediaPlayer.pause(); } else { mediaPlayer.play(); } } }); return playPauseButton; } private Slider createSlider(String id) { final Slider slider = new Slider(0.0, 1.0, 0.1); slider.setId(id); slider.setValue(0); return slider; } private Label createLabel(String text, String styleClass) { final Label label = new Label(text); label.getStyleClass().add(styleClass); return label; } private void addListenersAndBindings(final MediaPlayer mp) { mp.statusProperty().addListener(statusListener); mp.currentTimeProperty().addListener(currentTimeListener); mp.totalDurationProperty().addListener(new TotalDurationListener()); mp.setOnEndOfMedia(new Runnable() { @Override public void run() { songModel.getMediaPlayer().stop(); } }); volumeSlider.valueProperty().bindBidirectional(mp.volumeProperty()); } private void removeListenersAndBindings(MediaPlayer mp) { volumeSlider.valueProperty().unbind(); mp.statusProperty().removeListener(statusListener); mp.currentTimeProperty().removeListener(currentTimeListener); } private void seekAndUpdatePosition(Duration duration) { final MediaPlayer mediaPlayer = songModel.getMediaPlayer(); if (mediaPlayer.getStatus() == Status.STOPPED) { mediaPlayer.pause(); } mediaPlayer.seek(duration); if (mediaPlayer.getStatus() != Status.PLAYING) { updatePositionSlider(duration); } } private String formatDuration(Duration duration) { double millis = duration.toMillis(); int seconds = (int) (millis / 1000) % 60; int minutes = (int) (millis / (1000 * 60)); return String.format("%02d:%02d", minutes, seconds); } private void updateStatus(Status newStatus) { if (newStatus == Status.UNKNOWN || newStatus == null) { controlPanel.setDisable(true); positionSlider.setDisable(true); statusLabel.setText("Buffering"); } else { controlPanel.setDisable(false); positionSlider.setDisable(false); statusLabel.setText(newStatus.toString()); if (newStatus == Status.PLAYING) { playPauseIcon.setImage(pauseImg); } else { playPauseIcon.setImage(playImg); } } } private void updatePositionSlider(Duration currentTime) { if (positionSlider.isValueChanging()) return; final MediaPlayer mediaPlayer = songModel.getMediaPlayer(); final Duration total = mediaPlayer.getTotalDuration(); if (total == null || currentTime == null) { positionSlider.setValue(0); } else { positionSlider.setValue(currentTime.toMillis() / total.toMillis()); } } private class MediaPlayerListener implements ChangeListener<MediaPlayer> { @Override public void changed(ObservableValue<? extends MediaPlayer> observable, MediaPlayer oldValue, MediaPlayer newValue) { if (oldValue != null) { removeListenersAndBindings(oldValue); } addListenersAndBindings(newValue); } } private class OpenHandler implements EventHandler<ActionEvent> { @Override public void handle(ActionEvent event) { FileChooser fc = new FileChooser(); fc.setTitle("Pick a Sound File"); File song = fc.showOpenDialog(viewNode.getScene().getWindow()); if (song != null) { songModel.setURL(song.toURI().toString()); songModel.getMediaPlayer().play(); } } } private class StatusListener implements InvalidationListener { @Override public void invalidated(Observable observable) { Platform.runLater(new Runnable() { @Override public void run() { updateStatus(songModel.getMediaPlayer().getStatus()); } }); } } private class CurrentTimeListener implements InvalidationListener { @Override public void invalidated(Observable observable) { Platform.runLater(new Runnable() { @Override public void run() { final MediaPlayer mediaPlayer = songModel.getMediaPlayer(); final Duration currentTime = mediaPlayer.getCurrentTime(); currentTimeLabel.setText(formatDuration(currentTime)); updatePositionSlider(currentTime); } }); } } private class TotalDurationListener implements InvalidationListener { @Override public void invalidated(Observable observable) { final MediaPlayer mediaPlayer = songModel.getMediaPlayer(); final Duration totalDuration = mediaPlayer.getTotalDuration(); totalDurationLabel.setText(formatDuration(totalDuration)); } } private class PositionListener implements ChangeListener<Boolean> { @Override public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { if (oldValue && !newValue) { double pos = positionSlider.getValue(); final MediaPlayer mediaPlayer = songModel.getMediaPlayer(); final Duration seekTo = mediaPlayer.getTotalDuration().multiply(pos); seekAndUpdatePosition(seekTo); } } } } /** * @author dean */ final class SongModel { private static final String DEFAULT_IMG_URL = SongModel.class.getResource("resources/defaultAlbum.png") .toString(); private static final Image DEFAULT_ALBUM_COVER = new Image(DEFAULT_IMG_URL.toString()); private final StringProperty album = new SimpleStringProperty(this, "album"); private final StringProperty artist = new SimpleStringProperty(this, "artist"); private final StringProperty title = new SimpleStringProperty(this, "title"); private final StringProperty year = new SimpleStringProperty(this, "year"); private final ObjectProperty<Image> albumCover = new SimpleObjectProperty<Image>(this, "albumCover"); private final ReadOnlyObjectWrapper<MediaPlayer> mediaPlayer = new ReadOnlyObjectWrapper<MediaPlayer>(this, "mediaPlayer"); public SongModel() { resetProperties(); } public void setURL(String url) { if (mediaPlayer.get() != null) { mediaPlayer.get().stop(); } initializeMedia(url); } public String getAlbum() { return album.get(); } public void setAlbum(String value) { album.set(value); } public StringProperty albumProperty() { return album; } public String getArtist() { return artist.get(); } public void setArtist(String value) { artist.set(value); } public StringProperty artistProperty() { return artist; } public String getTitle() { return title.get(); } public void setTitle(String value) { title.set(value); } public StringProperty titleProperty() { return title; } public String getYear() { return year.get(); } public void setYear(String value) { year.set(value); } public StringProperty yearProperty() { return year; } public Image getAlbumCover() { return albumCover.get(); } public void setAlbumCover(Image value) { albumCover.set(value); } public ObjectProperty<Image> albumCoverProperty() { return albumCover; } public MediaPlayer getMediaPlayer() { return mediaPlayer.get(); } public ReadOnlyObjectProperty<MediaPlayer> mediaPlayerProperty() { return mediaPlayer.getReadOnlyProperty(); } private void resetProperties() { setArtist(""); setAlbum(""); setTitle(""); setYear(""); setAlbumCover(DEFAULT_ALBUM_COVER); } private void initializeMedia(String url) { resetProperties(); try { final Media media = new Media(url); media.getMetadata().addListener(new MapChangeListener<String, Object>() { @Override public void onChanged(Change<? extends String, ? extends Object> ch) { if (ch.wasAdded()) { handleMetadata(ch.getKey(), ch.getValueAdded()); } } }); mediaPlayer.setValue(new MediaPlayer(media)); mediaPlayer.get().setOnError(new Runnable() { @Override public void run() { String errorMessage = mediaPlayer.get().getError().getMessage(); // Handle errors during playback System.out.println("MediaPlayer Error: " + errorMessage); } }); } catch (RuntimeException re) { // Handle construction errors System.out.println("Caught Exception: " + re.getMessage()); } } private void handleMetadata(String key, Object value) { if (key.equals("album")) { setAlbum(value.toString()); } else if (key.equals("artist")) { setArtist(value.toString()); } if (key.equals("title")) { setTitle(value.toString()); } if (key.equals("year")) { setYear(value.toString()); } if (key.equals("image")) { setAlbumCover((Image) value); } } } /** * @author dean */ abstract class AbstractView { protected final SongModel songModel; protected final Node viewNode; public AbstractView(SongModel songModel) { this.songModel = songModel; this.viewNode = initView(); } public Node getViewNode() { return viewNode; } protected abstract Node initView(); }