DefaultAudioRendererPresentationControl.java
- /*
- * *********************************************************************************************************************
- *
- * blueMarine II: Semantic Media Centre
- * http://tidalwave.it/projects/bluemarine2
- *
- * Copyright (C) 2015 - 2021 by Tidalwave s.a.s. (http://tidalwave.it)
- *
- * *********************************************************************************************************************
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
- * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- *
- * *********************************************************************************************************************
- *
- * git clone https://bitbucket.org/tidalwave/bluemarine2-src
- * git clone https://github.com/tidalwave-it/bluemarine2-src
- *
- * *********************************************************************************************************************
- */
- package it.tidalwave.bluemarine2.ui.audio.renderer.impl;
- import javax.annotation.Nonnull;
- import javax.annotation.PostConstruct;
- import javax.inject.Inject;
- import java.time.Duration;
- import java.util.stream.Collectors;
- import it.tidalwave.util.annotation.VisibleForTesting;
- import javafx.beans.property.ObjectProperty;
- import javafx.beans.value.ChangeListener;
- import javafx.beans.value.ObservableValue;
- import javafx.application.Platform;
- import it.tidalwave.role.ui.UserAction;
- import it.tidalwave.messagebus.annotation.ListensTo;
- import it.tidalwave.messagebus.annotation.SimpleMessageSubscriber;
- import it.tidalwave.bluemarine2.model.audio.AudioFile;
- import it.tidalwave.bluemarine2.model.MediaItem.Metadata;
- import it.tidalwave.bluemarine2.model.PlayList;
- import it.tidalwave.bluemarine2.ui.commons.RenderAudioFileRequest;
- import it.tidalwave.bluemarine2.ui.commons.OnDeactivate;
- import it.tidalwave.bluemarine2.ui.audio.renderer.MediaPlayer;
- import it.tidalwave.bluemarine2.ui.audio.renderer.AudioRendererPresentation;
- import it.tidalwave.bluemarine2.ui.audio.renderer.MediaPlayer.Status;
- import lombok.extern.slf4j.Slf4j;
- import static it.tidalwave.role.ui.Displayable._Displayable_;
- import static it.tidalwave.bluemarine2.util.Formatters.format;
- import static it.tidalwave.bluemarine2.ui.audio.renderer.MediaPlayer.Status.*;
- import static it.tidalwave.bluemarine2.model.MediaItem.Metadata.*;
- import static it.tidalwave.util.PropertyWrapper.wrap;
- /***********************************************************************************************************************
- *
- * The Control of the {@link AudioRendererPresentation}.
- *
- * @stereotype Control
- *
- * @author Fabrizio Giudici
- *
- **********************************************************************************************************************/
- @SimpleMessageSubscriber @Slf4j
- public class DefaultAudioRendererPresentationControl
- {
- @Inject
- private AudioRendererPresentation presentation;
- @Inject
- private MediaPlayer mediaPlayer;
- private final AudioRendererPresentation.Properties properties = new AudioRendererPresentation.Properties();
- private Duration duration = Duration.ZERO;
- private PlayList<AudioFile> playList = PlayList.empty();
- // Discriminates a forced stop from media player just terminating
- private boolean stopped;
- private final UserAction prevAction = UserAction.of(() -> changeTrack(playList.previous().get()));
- private final UserAction nextAction = UserAction.of(() -> changeTrack(playList.next().get()));
- private final UserAction rewindAction = UserAction.of(() -> mediaPlayer.rewind());
- private final UserAction fastForwardAction = UserAction.of(() -> mediaPlayer.fastForward());
- private final UserAction pauseAction = UserAction.of(() -> mediaPlayer.pause());
- private final UserAction playAction = UserAction.of(this::play);
- private final UserAction stopAction = UserAction.of(this::stop);
- // FIXME: use expression binding
- // e.g. properties.progressProperty().bind(mediaPlayer.playTimeProperty().asDuration().dividedBy/duration));
- // FIXME: weak, remove previous listeners
- private final ChangeListener<Duration> l =
- (ObservableValue<? extends Duration> observable,
- Duration oldValue,
- Duration newValue) ->
- {
- // FIXME: the control shouldn't mess with JavaFX stuff
- Platform.runLater(() ->
- {
- properties.playTimeProperty().setValue(format(newValue));
- properties.progressProperty().setValue((double)newValue.toMillis() / duration.toMillis());
- });
- };
- /*******************************************************************************************************************
- *
- *
- ******************************************************************************************************************/
- @PostConstruct
- @VisibleForTesting void initialize()
- {
- presentation.bind(properties,
- prevAction, rewindAction, stopAction, pauseAction, playAction, fastForwardAction, nextAction);
- }
- /*******************************************************************************************************************
- *
- *
- ******************************************************************************************************************/
- @VisibleForTesting void onRenderAudioFileRequest (@ListensTo @Nonnull final RenderAudioFileRequest request)
- throws MediaPlayer.Exception
- {
- log.info("onRenderAudioFileRequest({})", request);
- playList = request.getPlayList();
- setAudioFile(playList.getCurrentItem().get());
- bindMediaPlayer();
- presentation.showUp(this);
- presentation.focusOnPlayButton();
- }
- /*******************************************************************************************************************
- *
- *
- ******************************************************************************************************************/
- @OnDeactivate
- @VisibleForTesting OnDeactivate.Result onDeactivate()
- throws MediaPlayer.Exception
- {
- stop();
- unbindMediaPlayer();
- playList = PlayList.empty();
- return OnDeactivate.Result.PROCEED;
- }
- /*******************************************************************************************************************
- *
- *
- ******************************************************************************************************************/
- private void setAudioFile (@Nonnull final AudioFile audioFile)
- throws MediaPlayer.Exception
- {
- log.info("setAudioFile({})", audioFile);
- final Metadata metadata = audioFile.getMetadata();
- log.info(">>>> metadata: {}", metadata);
- // FIXME: the control shouldn't mess with JavaFX stuff
- // FIXME: this performs some (short) queries that are executed in the JavaFX thread
- Platform.runLater(() ->
- {
- properties.titleProperty().setValue(metadata.get(TITLE).orElse(""));
- properties.artistProperty().setValue(audioFile.findMakers().stream()
- .map(maker -> maker.as(_Displayable_).getDisplayName())
- .collect(Collectors.joining(", ")));
- properties.composerProperty().setValue(audioFile.findComposers().stream()
- .map(composer -> composer.as(_Displayable_).getDisplayName())
- .collect(Collectors.joining(", ")));
- duration = metadata.get(DURATION).orElse(Duration.ZERO);
- properties.durationProperty().setValue(format(duration));
- properties.folderNameProperty().setValue(
- audioFile.getRecord().map(record -> record.as(_Displayable_).getDisplayName()).orElse(""));
- properties.nextTrackProperty().setValue(
- ((playList.getSize() == 1) ? "" : String.format("%d / %d", playList.getIndex() + 1, playList.getSize()) +
- playList.peekNext().map(t -> " - Next track: " + t.getMetadata().get(TITLE).orElse("")).orElse("")));
- });
- mediaPlayer.setMediaItem(audioFile);
- }
- /*******************************************************************************************************************
- *
- *
- *
- ******************************************************************************************************************/
- private void onMediaPlayerStarted()
- {
- log.info("onMediaPlayerStarted()");
- // presentation.focusOnStopButton();
- }
- /*******************************************************************************************************************
- *
- *
- *
- ******************************************************************************************************************/
- private void onMediaPlayerStopped()
- {
- log.info("onMediaPlayerStopped()");
- if (!stopped)
- {
- presentation.focusOnPlayButton();
- }
- if (!stopped && playList.hasNext())
- {
- // FIXME: check whether the disk is not gapless, and eventually pause
- try
- {
- setAudioFile(playList.next().get());
- play();
- }
- catch (MediaPlayer.Exception e)
- {
- log.error("", e);
- }
- }
- }
- /*******************************************************************************************************************
- *
- *
- *
- ******************************************************************************************************************/
- private void play()
- throws MediaPlayer.Exception
- {
- stopped = false;
- mediaPlayer.play();
- }
- /*******************************************************************************************************************
- *
- *
- *
- ******************************************************************************************************************/
- private void stop()
- throws MediaPlayer.Exception
- {
- stopped = true;
- mediaPlayer.stop();
- }
- /*******************************************************************************************************************
- *
- *
- *
- ******************************************************************************************************************/
- private void changeTrack (@Nonnull final AudioFile audioFile)
- throws MediaPlayer.Exception
- {
- final boolean wasPlaying = mediaPlayer.statusProperty().get().equals(PLAYING);
- if (wasPlaying)
- {
- stop();
- }
- setAudioFile(audioFile);
- if (wasPlaying)
- {
- play();
- }
- }
- /*******************************************************************************************************************
- *
- * Binds to the {@link MediaPlayer}.
- *
- ******************************************************************************************************************/
- @VisibleForTesting void bindMediaPlayer()
- {
- log.debug("bindMediaPlayer()");
- final ObjectProperty<Status> status = mediaPlayer.statusProperty();
- wrap(stopAction.enabled()).bind(status.isEqualTo(PLAYING));
- wrap(pauseAction.enabled()).bind(status.isEqualTo(PLAYING));
- wrap(playAction.enabled()).bind(status.isNotEqualTo(PLAYING));
- wrap(prevAction.enabled()).bind(playList.hasPreviousProperty());
- wrap(nextAction.enabled()).bind(playList.hasNextProperty());
- mediaPlayer.playTimeProperty().addListener(l);
- status.addListener((observable, oldValue, newValue) ->
- {
- switch (newValue)
- {
- case STOPPED:
- onMediaPlayerStopped();
- break;
- case PLAYING:
- onMediaPlayerStarted();
- break;
- }
- });
- }
- /*******************************************************************************************************************
- *
- * Unbinds from the {@link MediaPlayer}.
- *
- ******************************************************************************************************************/
- @VisibleForTesting void unbindMediaPlayer()
- {
- log.debug("unbindMediaPlayer()");
- wrap(stopAction.enabled()).unbind();
- wrap(pauseAction.enabled()).unbind();
- wrap(playAction.enabled()).unbind();
- wrap(prevAction.enabled()).unbind();
- wrap(nextAction.enabled()).unbind();
- mediaPlayer.playTimeProperty().removeListener(l);
- }
- }