package de.calette.mephisto3.ui.radio; import callete.api.Callete; import callete.api.services.music.model.Stream; import callete.api.services.music.player.PlaylistMetaData; import callete.api.services.resources.ArtistResources; import callete.api.services.resources.ImageResource; import de.calette.mephisto3.Mephisto3; import de.calette.mephisto3.control.ServiceController; import de.calette.mephisto3.ui.ControllablePanel; import de.calette.mephisto3.ui.ServiceScroller; import de.calette.mephisto3.util.ComponentUtil; import de.calette.mephisto3.util.Executor; import de.calette.mephisto3.util.TransitionUtil; import javafx.animation.FadeTransition; import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.control.ProgressIndicator; import javafx.scene.effect.ColorAdjust; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * All UI components of the stream panel */ public class StreamsUI extends VBox { private final static Logger LOG = LoggerFactory.getLogger(StreamsUI.class); public final static String NO_DATA_TITLE = " - keine Informationen verfügbar -"; public final static String LOADING_DATA_TITLE = "Warte auf Metadaten..."; private final static int MINIMUM_IMAGE_SIZE = 400; private final static int IMAGE_HEIGHT = 345; private final static int PLAYER_MINIMUM_IMAGE_SIZE = 45; private final static int PLAYER_IMAGE_SIZE = 42; private ServiceScroller serviceScroller = new ServiceScroller(); private ImageView artistBackgroundImageView; private Label nameLabel; private Label artistLabel; private Label titleLabel; private HBox imageLoader; private ProgressIndicator metaDataBusyIndicator; private ProgressIndicator imageDataBusyIndicator; private Image artistBackgroundImage; private Image artistStatusImage; private ArtistResources artistResources; private StreamStatusBox playerStatusBox; private boolean imageLoaderActive = false; public StreamsUI(ControllablePanel parent, Stream stream) { createUI(parent, stream); } // ------------------------- UI State Updates ---------------------------------------- public void reset() { Stream stream = (Stream) ServiceController.getInstance().getServiceState().getSelection(); activateStream(stream); playerStatusBox.updateStatus(stream.getName(), "", null); } /** * Called when another stream has been selected but not confirmed for playback yet. */ public void selectStream() { Platform.runLater(() -> { imageLoader.setOpacity(0); metaDataBusyIndicator.setOpacity(0); Stream stream = (Stream) ServiceController.getInstance().getServiceState().getSelection(); nameLabel.setText(stream.getName()); artistLabel.setText(NO_DATA_TITLE); titleLabel.setText(""); removeImageClasses(); setBackgroundImage(null); }); } /** * Called when the playback of a newly selected station should start. */ public void activateStream(Stream stream) { artistBackgroundImage = null; Platform.runLater(() -> { removeImageClasses(); nameLabel.setText(applyName(stream, null)); artistLabel.setText(applyArtist(null)); titleLabel.setText(applyTitle(null)); playerStatusBox.updateStatus(stream.getName(), "", null); setBackgroundImage(null); }); } /** * Called when the active stream has been re-selected, the stored metadata must be re-applied. */ public void selectActiveStream(Stream stream, final PlaylistMetaData currentMetaData) { Platform.runLater(new Runnable() { @Override public void run() { nameLabel.setText(applyName(stream, currentMetaData)); artistLabel.setText(applyArtist(currentMetaData)); titleLabel.setText(applyTitle(currentMetaData)); if(currentMetaData != null && !StringUtils.isEmpty(currentMetaData.getArtist()) && !StringUtils.isEmpty(currentMetaData.getTitle())) { playerStatusBox.updateStatus(currentMetaData.getArtist() + " - " + currentMetaData.getTitle()); } } }); Platform.runLater(new Runnable() { @Override public void run() { //only apply additional data if metadata is available. if(currentMetaData == null || StringUtils.isEmpty(currentMetaData.getArtist())) { LOG.debug("No meta data available or no artist found, resetting UI."); removeImageClasses(); setBackgroundImage(null); return; } //the artist loader is already loading if(imageLoaderActive) { LOG.debug("Image loader already active, skipping resource request."); return; } //first check if an image has already been requested if(artistResources == null || !artistResources.getArtist().equals(currentMetaData.getArtist())) { imageLoader.setOpacity(1); setBackgroundImage(null); loadArtistResource(currentMetaData); } //apply image if already available if(artistResources != null && artistResources.getArtist().equals(currentMetaData.getArtist()) && artistBackgroundImage != null) { playerStatusBox.updateStatus(null, null, artistStatusImage); addImageClasses(); setBackgroundImage(artistBackgroundImage); if(imageLoader.getOpacity() != 0) { imageLoader.setOpacity(0); } } //hide the image loader if no images are available if(artistResources != null && artistResources.isEmpty()) { if(imageLoader.getOpacity() != 0) { LOG.debug("No resources found, hiding image loader."); imageLoader.setOpacity(0); } } } }); } // -------------------- Helper ------------------------------------------- /** * Asynchronously loading of image resources. * * @param currentMetaData the meta data used to retrieve the image data */ private void loadArtistResource(final PlaylistMetaData currentMetaData) { imageLoaderActive = true; Callete.getMusicPlayer().enableMonitoring(false); Executor.run(new Runnable() { @Override public void run() { LOG.debug("Requesting resources for " + currentMetaData.getArtist()); artistResources = Callete.getResourcesService().getImageResourcesFor(currentMetaData.getArtist()); if(!artistResources.isEmpty()) { LOG.debug("Creating images for " + currentMetaData.getArtist()); ImageResource randomPlayerImage = artistResources.getRandomImage(PLAYER_IMAGE_SIZE, PLAYER_IMAGE_SIZE, PLAYER_MINIMUM_IMAGE_SIZE); ImageResource img = artistResources.getRandomImage(Mephisto3.WIDTH, IMAGE_HEIGHT, MINIMUM_IMAGE_SIZE); if(img != null) { artistBackgroundImage = ComponentUtil.toFXImage(img); } else { artistBackgroundImage = null; } if(randomPlayerImage != null) { artistStatusImage = ComponentUtil.toFXImage(randomPlayerImage); } else { artistStatusImage = null; } } imageLoaderActive = false; Callete.getMusicPlayer().enableMonitoring(true); } }); } private void addImageClasses() { nameLabel.getStyleClass().remove("stream-name"); nameLabel.getStyleClass().add("stream-name-active"); artistLabel.getStyleClass().remove("stream-artist"); artistLabel.getStyleClass().add("stream-artist-active"); titleLabel.getStyleClass().remove("stream-title"); titleLabel.getStyleClass().add("stream-title-active"); } private void removeImageClasses() { nameLabel.getStyleClass().clear(); nameLabel.getStyleClass().addAll("label", "label-defaults", "stream-name"); artistLabel.getStyleClass().clear(); artistLabel.getStyleClass().addAll("label", "label-defaults", "stream-artist"); titleLabel.getStyleClass().clear(); titleLabel.getStyleClass().addAll("label", "label-defaults", "stream-title"); } private String applyArtist(PlaylistMetaData metaData) { if(metaData == null) { metaDataBusyIndicator.setOpacity(1); return LOADING_DATA_TITLE; } metaDataBusyIndicator.setOpacity(0); if(StringUtils.isEmpty(metaData.getArtist()) || String.valueOf(metaData.getArtist()).equals(metaData.getTitle())) { return NO_DATA_TITLE; } return metaData.getArtist(); } private String applyTitle(PlaylistMetaData metaData) { if(metaData == null) { return ""; } if(StringUtils.isEmpty(metaData.getTitle()) || String.valueOf(metaData.getArtist()).equals(String.valueOf(metaData.getTitle()))) { return ""; } return metaData.getTitle(); } private String applyName(Stream stream, PlaylistMetaData metaData) { if(metaData != null && StringUtils.isEmpty(metaData.getName()) && metaData.getName().equals(stream.getName())) { return metaData.getName(); } return stream.getName(); } private void setBackgroundImage(Image image) { //method is called for an image that is already applied if(image != null && artistBackgroundImageView.getImage() != null && artistBackgroundImageView.getImage().equals(image)) { //only check if the image is visible if(artistBackgroundImageView.getOpacity() == 0) { TransitionUtil.createInFader(artistBackgroundImageView, 100).play(); } return; } //hide image and already hidden? ignore call if(image == null && artistBackgroundImageView.getOpacity() == 0) { return; } if(artistBackgroundImageView.getImage() != null && artistBackgroundImageView.getOpacity() == 1) { FadeTransition outFader = TransitionUtil.createOutFader(artistBackgroundImageView, 100); outFader.setOnFinished(actionEvent -> { if(image != null) { artistBackgroundImageView.setImage(image); if(image != null) { addImageClasses(); } TransitionUtil.createInFader(artistBackgroundImageView, 100).play(); } }); outFader.play(); } else { artistBackgroundImageView.setImage(image); FadeTransition inFader = TransitionUtil.createInFader(artistBackgroundImageView, 100); inFader.setOnFinished(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { if(image != null) { addImageClasses(); } } }); inFader.play(); } } /** * Only create the UI, using the initial stream as default which is also * played at the start of the radio UI. * * @param parent * @param stream the initially activated stream. */ private void createUI(ControllablePanel parent, Stream stream) { setMinWidth(Mephisto3.WIDTH); VBox labelBox = new VBox(20); labelBox.getStyleClass().add("streams-panel"); labelBox.setMinHeight(Mephisto3.HEIGHT - 60); labelBox.setAlignment(Pos.TOP_LEFT); getChildren().add(labelBox); getChildren().add(serviceScroller); labelBox.setPadding(new Insets(20, 30, 30, 30)); artistBackgroundImageView = new ImageView(); ColorAdjust brightness = new ColorAdjust(); brightness.setBrightness(-0.3); artistBackgroundImageView.setEffect(brightness); parent.getChildren().add(artistBackgroundImageView); nameLabel = ComponentUtil.createLabel(stream.getName(), "stream-name", labelBox); HBox artistLabelBox = new HBox(); artistLabelBox.setMaxHeight(28); artistLabel = ComponentUtil.createLabel(LOADING_DATA_TITLE, "stream-artist", artistLabelBox); labelBox.getChildren().add(artistLabelBox); metaDataBusyIndicator = new ProgressIndicator(); artistLabelBox.getChildren().add(metaDataBusyIndicator); titleLabel = ComponentUtil.createLabel("", "stream-title", labelBox); HBox spacer = new HBox(); spacer.setMinHeight(70); labelBox.getChildren().add(spacer); imageLoader = new HBox(); imageLoader.setOpacity(0); imageLoader.setMinHeight(20); imageLoader.setMaxWidth(Mephisto3.WIDTH); imageLoader.setAlignment(Pos.CENTER_RIGHT); labelBox.getChildren().add(imageLoader); ComponentUtil.createLabel("Suche nach Bildern...", "", imageLoader); imageDataBusyIndicator = new ProgressIndicator(); imageLoader.getChildren().add(imageDataBusyIndicator); playerStatusBox = new StreamStatusBox(); getChildren().add(playerStatusBox); //set the initial UI state playerStatusBox.updateStatus(stream.getName(), null, null); } public void hideControl() { serviceScroller.hideScroller(); } public void showControl() { serviceScroller.showScroller(); } public void updateSelection() { serviceScroller.updateSelection(); } }