DefaultAudioRendererPresentationControl.java

  1. /*
  2.  * *********************************************************************************************************************
  3.  *
  4.  * blueMarine II: Semantic Media Centre
  5.  * http://tidalwave.it/projects/bluemarine2
  6.  *
  7.  * Copyright (C) 2015 - 2021 by Tidalwave s.a.s. (http://tidalwave.it)
  8.  *
  9.  * *********************************************************************************************************************
  10.  *
  11.  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  12.  * the License. You may obtain a copy of the License at
  13.  *
  14.  *     http://www.apache.org/licenses/LICENSE-2.0
  15.  *
  16.  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  17.  * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
  18.  * specific language governing permissions and limitations under the License.
  19.  *
  20.  * *********************************************************************************************************************
  21.  *
  22.  * git clone https://bitbucket.org/tidalwave/bluemarine2-src
  23.  * git clone https://github.com/tidalwave-it/bluemarine2-src
  24.  *
  25.  * *********************************************************************************************************************
  26.  */
  27. package it.tidalwave.bluemarine2.ui.audio.renderer.impl;

  28. import javax.annotation.Nonnull;
  29. import javax.annotation.PostConstruct;
  30. import javax.inject.Inject;
  31. import java.time.Duration;
  32. import java.util.stream.Collectors;
  33. import it.tidalwave.util.annotation.VisibleForTesting;
  34. import javafx.beans.property.ObjectProperty;
  35. import javafx.beans.value.ChangeListener;
  36. import javafx.beans.value.ObservableValue;
  37. import javafx.application.Platform;
  38. import it.tidalwave.role.ui.UserAction;
  39. import it.tidalwave.messagebus.annotation.ListensTo;
  40. import it.tidalwave.messagebus.annotation.SimpleMessageSubscriber;
  41. import it.tidalwave.bluemarine2.model.audio.AudioFile;
  42. import it.tidalwave.bluemarine2.model.MediaItem.Metadata;
  43. import it.tidalwave.bluemarine2.model.PlayList;
  44. import it.tidalwave.bluemarine2.ui.commons.RenderAudioFileRequest;
  45. import it.tidalwave.bluemarine2.ui.commons.OnDeactivate;
  46. import it.tidalwave.bluemarine2.ui.audio.renderer.MediaPlayer;
  47. import it.tidalwave.bluemarine2.ui.audio.renderer.AudioRendererPresentation;
  48. import it.tidalwave.bluemarine2.ui.audio.renderer.MediaPlayer.Status;
  49. import lombok.extern.slf4j.Slf4j;
  50. import static it.tidalwave.role.ui.Displayable._Displayable_;
  51. import static it.tidalwave.bluemarine2.util.Formatters.format;
  52. import static it.tidalwave.bluemarine2.ui.audio.renderer.MediaPlayer.Status.*;
  53. import static it.tidalwave.bluemarine2.model.MediaItem.Metadata.*;
  54. import static it.tidalwave.util.PropertyWrapper.wrap;

  55. /***********************************************************************************************************************
  56.  *
  57.  * The Control of the {@link AudioRendererPresentation}.
  58.  *
  59.  * @stereotype  Control
  60.  *
  61.  * @author  Fabrizio Giudici
  62.  *
  63.  **********************************************************************************************************************/
  64. @SimpleMessageSubscriber @Slf4j
  65. public class DefaultAudioRendererPresentationControl
  66.   {
  67.     @Inject
  68.     private AudioRendererPresentation presentation;

  69.     @Inject
  70.     private MediaPlayer mediaPlayer;

  71.     private final AudioRendererPresentation.Properties properties = new AudioRendererPresentation.Properties();

  72.     private Duration duration = Duration.ZERO;

  73.     private PlayList<AudioFile> playList = PlayList.empty();

  74.     // Discriminates a forced stop from media player just terminating
  75.     private boolean stopped;

  76.     private final UserAction prevAction = UserAction.of(() -> changeTrack(playList.previous().get()));

  77.     private final UserAction nextAction = UserAction.of(() -> changeTrack(playList.next().get()));

  78.     private final UserAction rewindAction = UserAction.of(() -> mediaPlayer.rewind());

  79.     private final UserAction fastForwardAction = UserAction.of(() -> mediaPlayer.fastForward());

  80.     private final UserAction pauseAction = UserAction.of(() -> mediaPlayer.pause());

  81.     private final UserAction playAction = UserAction.of(this::play);

  82.     private final UserAction stopAction = UserAction.of(this::stop);

  83.     // FIXME: use expression binding
  84.     // e.g.  properties.progressProperty().bind(mediaPlayer.playTimeProperty().asDuration().dividedBy/duration));
  85.     // FIXME: weak, remove previous listeners
  86.     private final ChangeListener<Duration> l =
  87.                 (ObservableValue<? extends Duration> observable,
  88.                  Duration oldValue,
  89.                  Duration newValue) ->
  90.         {
  91.           // FIXME: the control shouldn't mess with JavaFX stuff
  92.           Platform.runLater(() ->
  93.             {
  94.               properties.playTimeProperty().setValue(format(newValue));
  95.               properties.progressProperty().setValue((double)newValue.toMillis() / duration.toMillis());
  96.             });
  97.         };

  98.     /*******************************************************************************************************************
  99.      *
  100.      *
  101.      ******************************************************************************************************************/
  102.     @PostConstruct
  103.     @VisibleForTesting void initialize()
  104.       {
  105.         presentation.bind(properties,
  106.                           prevAction, rewindAction, stopAction, pauseAction, playAction, fastForwardAction, nextAction);
  107.       }

  108.     /*******************************************************************************************************************
  109.      *
  110.      *
  111.      ******************************************************************************************************************/
  112.     @VisibleForTesting void onRenderAudioFileRequest (@ListensTo @Nonnull final RenderAudioFileRequest request)
  113.       throws MediaPlayer.Exception
  114.       {
  115.         log.info("onRenderAudioFileRequest({})", request);

  116.         playList = request.getPlayList();
  117.         setAudioFile(playList.getCurrentItem().get());
  118.         bindMediaPlayer();
  119.         presentation.showUp(this);
  120.         presentation.focusOnPlayButton();
  121.       }

  122.     /*******************************************************************************************************************
  123.      *
  124.      *
  125.      ******************************************************************************************************************/
  126.     @OnDeactivate
  127.     @VisibleForTesting OnDeactivate.Result onDeactivate()
  128.       throws MediaPlayer.Exception
  129.       {
  130.         stop();
  131.         unbindMediaPlayer();
  132.         playList = PlayList.empty();
  133.         return OnDeactivate.Result.PROCEED;
  134.       }

  135.     /*******************************************************************************************************************
  136.      *
  137.      *
  138.      ******************************************************************************************************************/
  139.     private void setAudioFile (@Nonnull final AudioFile audioFile)
  140.       throws MediaPlayer.Exception
  141.       {
  142.         log.info("setAudioFile({})", audioFile);
  143.         final Metadata metadata = audioFile.getMetadata();
  144.         log.info(">>>> metadata:  {}", metadata);

  145.         // FIXME: the control shouldn't mess with JavaFX stuff
  146.         // FIXME: this performs some (short) queries that are executed in the JavaFX thread
  147.         Platform.runLater(() ->
  148.           {
  149.             properties.titleProperty().setValue(metadata.get(TITLE).orElse(""));
  150.             properties.artistProperty().setValue(audioFile.findMakers().stream()
  151.                     .map(maker -> maker.as(_Displayable_).getDisplayName())
  152.                     .collect(Collectors.joining(", ")));
  153.             properties.composerProperty().setValue(audioFile.findComposers().stream()
  154.                     .map(composer -> composer.as(_Displayable_).getDisplayName())
  155.                     .collect(Collectors.joining(", ")));
  156.             duration = metadata.get(DURATION).orElse(Duration.ZERO);
  157.             properties.durationProperty().setValue(format(duration));
  158.             properties.folderNameProperty().setValue(
  159.                     audioFile.getRecord().map(record -> record.as(_Displayable_).getDisplayName()).orElse(""));
  160.             properties.nextTrackProperty().setValue(
  161.                     ((playList.getSize() == 1) ? "" : String.format("%d / %d", playList.getIndex() + 1, playList.getSize()) +
  162.                     playList.peekNext().map(t -> " - Next track: " + t.getMetadata().get(TITLE).orElse("")).orElse("")));
  163.           });

  164.         mediaPlayer.setMediaItem(audioFile);
  165.       }

  166.     /*******************************************************************************************************************
  167.      *
  168.      *
  169.      *
  170.      ******************************************************************************************************************/
  171.     private void onMediaPlayerStarted()
  172.       {
  173.         log.info("onMediaPlayerStarted()");
  174. //        presentation.focusOnStopButton();
  175.       }

  176.     /*******************************************************************************************************************
  177.      *
  178.      *
  179.      *
  180.      ******************************************************************************************************************/
  181.     private void onMediaPlayerStopped()
  182.       {
  183.         log.info("onMediaPlayerStopped()");

  184.         if (!stopped)
  185.           {
  186.             presentation.focusOnPlayButton();
  187.           }

  188.         if (!stopped && playList.hasNext())
  189.           {
  190.             // FIXME: check whether the disk is not gapless, and eventually pause
  191.             try
  192.               {
  193.                 setAudioFile(playList.next().get());
  194.                 play();
  195.               }
  196.             catch (MediaPlayer.Exception e)
  197.               {
  198.                 log.error("", e);
  199.               }
  200.           }
  201.       }

  202.     /*******************************************************************************************************************
  203.      *
  204.      *
  205.      *
  206.      ******************************************************************************************************************/
  207.     private void play()
  208.       throws MediaPlayer.Exception
  209.       {
  210.         stopped = false;
  211.         mediaPlayer.play();
  212.       }

  213.     /*******************************************************************************************************************
  214.      *
  215.      *
  216.      *
  217.      ******************************************************************************************************************/
  218.     private void stop()
  219.       throws MediaPlayer.Exception
  220.       {
  221.         stopped = true;
  222.         mediaPlayer.stop();
  223.       }

  224.     /*******************************************************************************************************************
  225.      *
  226.      *
  227.      *
  228.      ******************************************************************************************************************/
  229.     private void changeTrack (@Nonnull final AudioFile audioFile)
  230.       throws MediaPlayer.Exception
  231.       {
  232.         final boolean wasPlaying = mediaPlayer.statusProperty().get().equals(PLAYING);

  233.         if (wasPlaying)
  234.           {
  235.             stop();
  236.           }

  237.         setAudioFile(audioFile);

  238.         if (wasPlaying)
  239.           {
  240.             play();
  241.           }
  242.       }

  243.     /*******************************************************************************************************************
  244.      *
  245.      * Binds to the {@link MediaPlayer}.
  246.      *
  247.      ******************************************************************************************************************/
  248.     @VisibleForTesting void bindMediaPlayer()
  249.       {
  250.         log.debug("bindMediaPlayer()");
  251.         final ObjectProperty<Status> status = mediaPlayer.statusProperty();
  252.         wrap(stopAction.enabled()).bind(status.isEqualTo(PLAYING));
  253.         wrap(pauseAction.enabled()).bind(status.isEqualTo(PLAYING));
  254.         wrap(playAction.enabled()).bind(status.isNotEqualTo(PLAYING));
  255.         wrap(prevAction.enabled()).bind(playList.hasPreviousProperty());
  256.         wrap(nextAction.enabled()).bind(playList.hasNextProperty());
  257.         mediaPlayer.playTimeProperty().addListener(l);

  258.         status.addListener((observable, oldValue, newValue) ->
  259.           {
  260.             switch (newValue)
  261.               {
  262.                 case STOPPED:
  263.                     onMediaPlayerStopped();
  264.                     break;

  265.                 case PLAYING:
  266.                     onMediaPlayerStarted();
  267.                     break;
  268.               }
  269.           });
  270.       }

  271.     /*******************************************************************************************************************
  272.      *
  273.      * Unbinds from the {@link MediaPlayer}.
  274.      *
  275.      ******************************************************************************************************************/
  276.     @VisibleForTesting void unbindMediaPlayer()
  277.       {
  278.         log.debug("unbindMediaPlayer()");
  279.         wrap(stopAction.enabled()).unbind();
  280.         wrap(pauseAction.enabled()).unbind();
  281.         wrap(playAction.enabled()).unbind();
  282.         wrap(prevAction.enabled()).unbind();
  283.         wrap(nextAction.enabled()).unbind();
  284.         mediaPlayer.playTimeProperty().removeListener(l);
  285.       }
  286.   }