/* * Jajuk * Copyright (C) The Jajuk Team * http://jajuk.info * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ package org.jajuk.services.players; import java.util.Properties; import javax.sound.sampled.LineUnavailableException; import javax.swing.JOptionPane; import javazoom.jlgui.basicplayer.BasicPlayer; import org.jajuk.base.File; import org.jajuk.base.TypeManager; import org.jajuk.events.JajukEvent; import org.jajuk.events.JajukEvents; import org.jajuk.events.ObservationManager; import org.jajuk.services.webradio.WebRadio; import org.jajuk.ui.widgets.InformationJPanel; import org.jajuk.util.Conf; import org.jajuk.util.Const; import org.jajuk.util.Messages; import org.jajuk.util.error.JajukRuntimeException; import org.jajuk.util.log.Log; /** * abstract class for music player, independent from real implementation. */ public final class Player { /** The Constant PLAYER_0. */ private static final String PLAYER_0 = "Player.0"; /** Current file read. */ private static File fCurrent; /** Current player used. */ private static IPlayerImpl playerImpl; /** Current player used nb 1. */ private static IPlayerImpl playerImpl1; /** Current player used nb 2. */ private static IPlayerImpl playerImpl2; /** Mute flag. */ private static boolean bMute = false; /** Paused flag. */ private static boolean bPaused = false; /** Playing ?. */ private static boolean bPlaying = false; /** * private constructor to avoid instantiating utility class. */ private Player() { } /** * Asynchronous play for specified file with specified time interval. * * @param file to play * @param fPosition * @param length in ms * @return true if play is OK */ public static boolean play(final File file, final float fPosition, final long length) { if (file == null) { throw new IllegalArgumentException("Cannot play empty file."); } // Check if the file is on a mounted device, should be tested before but not always, see #1915 if (!file.getDevice().isMounted()) { // not mounted, ok let them a chance to mount it: final String sMessage = Messages.getString("Error.025") + " (" + file.getDirectory().getDevice().getName() + Messages.getString("FIFO.4"); int i = Messages.getChoice(sMessage, JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE); if (i == JOptionPane.YES_OPTION) { try { file.getDevice().mount(true); } catch (Exception e) { Log.error(e); Messages.showErrorMessage(11, file.getDevice().getName()); throw new JajukRuntimeException("Device not mounted"); } } else { // "cancel" or "no" throw new JajukRuntimeException("Device not mounted"); } } fCurrent = file; try { // Choose the player Class<IPlayerImpl> cPlayer = file.getTrack().getType().getPlayerClass(); // player 1 null ? if (playerImpl1 == null) { playerImpl1 = cPlayer.newInstance(); playerImpl = playerImpl1; } // player 1 not null, test if it is fading else if (playerImpl1.getState() != Const.FADING_STATUS) { // stop it playerImpl1.stop(); playerImpl1 = cPlayer.newInstance(); playerImpl = playerImpl1; } // player 1 fading, test player 2 else if (playerImpl2 == null) { playerImpl2 = cPlayer.newInstance(); playerImpl = playerImpl2; } // if here, the only normal case is player 1 is fading and // player 2 not null and not fading else { // stop it playerImpl2.stop(); playerImpl2 = cPlayer.newInstance(); playerImpl = playerImpl2; } bPlaying = true; bPaused = false; boolean bWaitingLine = true; while (bWaitingLine) { try { if (bMute) { playerImpl.play(fCurrent, fPosition, length, 0.0f); } else { playerImpl.play(fCurrent, fPosition, length, Conf.getFloat(Const.CONF_VOLUME)); } bWaitingLine = false; } catch (Exception bpe) { if (!(bpe.getCause() instanceof LineUnavailableException)) { throw bpe; } bWaitingLine = true; Log.debug("Line occupied, waiting"); InformationJPanel.getInstance().setMessage(Messages.getString(PLAYER_0), InformationJPanel.MessageType.WARNING); // wait for the line QueueModel.class.wait(Const.WAIT_AFTER_ERROR); } } // Save playing state Conf.setProperty(Const.CONF_STARTUP_STOPPED, "false"); return true; } catch (final Throwable t) { Properties pDetails = new Properties(); pDetails.put(Const.DETAIL_CONTENT, file); ObservationManager.notifySync(new JajukEvent(JajukEvents.PLAY_ERROR, pDetails)); Log.error(7, Messages.getString(PLAYER_0) + "{{" + fCurrent.getName() + "}}", t); return false; } } /** * Play a web radio stream. * * @param radio * * @return true, if play */ public static boolean play(WebRadio radio) { try { // check mplayer availability if (TypeManager.getInstance().getTypeByExtension(Const.EXT_RADIO) == null) { Messages.showWarningMessage(Messages.getString("Warning.4")); return false; } // Choose the player Class<IPlayerImpl> cPlayer = TypeManager.getInstance().getTypeByExtension(Const.EXT_RADIO) .getPlayerClass(); // Stop all streams stop(true); playerImpl1 = cPlayer.newInstance(); playerImpl = playerImpl1; bPlaying = true; bPaused = false; boolean bWaitingLine = true; while (bWaitingLine) { try { if (bMute) { playerImpl.play(radio, 0.0f); } else { playerImpl.play(radio, Conf.getFloat(Const.CONF_VOLUME)); } bWaitingLine = false; } catch (Exception bpe) { if (!(bpe.getCause() instanceof LineUnavailableException)) { throw bpe; } bWaitingLine = true; Log.debug("Line occupied, waiting"); InformationJPanel.getInstance().setMessage(Messages.getString(PLAYER_0), InformationJPanel.MessageType.WARNING); try { // wait for the line QueueModel.class.wait(Const.WAIT_AFTER_ERROR); } catch (InterruptedException e1) { Log.error(e1); } } } // Save playing state Conf.setProperty(Const.CONF_STARTUP_STOPPED, "false"); return true; } catch (final Throwable t) { Properties pDetails = new Properties(); pDetails.put(Const.DETAIL_CONTENT, radio); ObservationManager.notifySync(new JajukEvent(JajukEvents.PLAY_ERROR, pDetails)); Log.error(7, Messages.getString(PLAYER_0) + radio.getUrl() + "}}", t); return false; } } /** * Stop the played track. * * @param bAll stop fading tracks as well ? */ public static void stop(boolean bAll) { try { if (playerImpl1 != null && (playerImpl1.getState() != Const.FADING_STATUS || bAll)) { playerImpl1.stop(); playerImpl1 = null; } if (playerImpl2 != null && (playerImpl2.getState() != Const.FADING_STATUS || bAll)) { playerImpl2.stop(); playerImpl2 = null; } bPaused = false; // cancel any current pause bPlaying = false; } catch (Exception e) { Log.debug(Messages.getString("Error.008"), e); } } /** * Alternative Mute/unmute the player. * */ public static void mute() { // Ignore mute changes if Bit-perfect mode is enabled // See code should not normally be called because we // disable associated GUI if (Conf.getBoolean(Const.CONF_BIT_PERFECT)) { Log.warn("Bit-perfect option enabled, software mutes ignored"); return; } Player.bMute = !Player.bMute; mute(Player.bMute); } /** * Mute/unmute the player. * * @param pMute */ public static void mute(boolean pMute) { // Ignore mute changes if Bit-perfect mode is enabled // See code should not normally be called because we // disable associated GUI if (Conf.getBoolean(Const.CONF_BIT_PERFECT)) { Log.warn("Bit-perfect option enabled, software mutes ignored"); return; } try { if (playerImpl == null) { // none current player, leave return; } if (pMute) { if (playerImpl1 != null) { playerImpl1.setVolume(0.0f); } if (playerImpl2 != null) { playerImpl2.setVolume(0.0f); } } else { playerImpl.setVolume(Conf.getFloat(Const.CONF_VOLUME)); } Player.bMute = pMute; } catch (Exception e) { Log.error(e); } } /** * Checks if is muted. * * @return whether the player is muted or not */ public static boolean isMuted() { return bMute; } /** * Set the gain. * * @param pVolume */ public static void setVolume(float pVolume) { // Ignore volume changes if Bit-perfect mode is enabled // See code should not normally be called because we // disable associated GUI if (Conf.getBoolean(Const.CONF_BIT_PERFECT)) { Log.warn("Bit-perfect option enabled, software volume changes ignored"); return; } float fVolume = pVolume; try { // if user move the volume slider, unmute if (isMuted()) { mute(false); } // check, it can be over 1 when moving sliders if (pVolume < 0.0f) { fVolume = 0.0f; } else if (pVolume > 1.0f) { fVolume = 1.0f; } if (playerImpl != null) { playerImpl.setVolume(fVolume); } // Store the volume Conf.setProperty(Const.CONF_VOLUME, Float.toString(fVolume)); // Require all GUI (like volume sliders) to update ObservationManager.notify(new JajukEvent(JajukEvents.VOLUME_CHANGED)); } catch (Exception e) { Log.error(e); } } /** * Gets the elapsed time. * * @return Returns the lTime in ms */ public static long getElapsedTimeMillis() { if (playerImpl != null) { return playerImpl.getElapsedTimeMillis(); } else { return 0; } } /** * Gets the elapsed time. * * @return Returns the lTime in ms */ public static long getActuallyPlayedTimeMillis() { if (playerImpl != null) { return playerImpl.getActuallyPlayedTimeMillis(); } else { return 0; } } /** * Pause the player. */ public static void pause() { try { if (!bPlaying) { // ignore pause when not playing to avoid // confusion between two tracks return; } if (playerImpl != null) { playerImpl.pause(); } bPaused = true; ObservationManager.notify(new JajukEvent(JajukEvents.PLAYER_PAUSE)); } catch (Exception e) { Log.error(e); } } /** * resume the player. */ public static void resume() { try { if (playerImpl == null) { // none current player, leave return; } if (!isMuted()) { playerImpl.setVolume(Conf.getFloat(Const.CONF_VOLUME)); } // If we are playing a webradio, we stop and restart because players // like mplayer can't deal with resuming a stream (it cant restart under // mplayer when the resume is done after the end of the buffer) if (QueueModel.isPlayingRadio()) { WebRadio radio = QueueModel.getCurrentRadio(); stop(true); play(radio); } else { playerImpl.resume(); } bPaused = false; ObservationManager.notify(new JajukEvent(JajukEvents.PLAYER_RESUME)); } catch (Exception e) { Log.error(e); } } /** * Checks if is paused. * * @return whether player is paused */ public static boolean isPaused() { return bPaused; } /** * Seek to a given position in %. ex : 0.2 for 20% * * @param pfPosition */ public static void seek(float pfPosition) { float fPosition = pfPosition; if (playerImpl == null) { // none current player, leave return; } // bound seek if (fPosition < 0.0f) { fPosition = 0.0f; } else if (fPosition >= 1.0f) { fPosition = 0.99f; } try { Log.debug("Seeking to: " + fPosition); playerImpl.seek(fPosition); } catch (Exception e) { // we can get some errors in unexpected cases Log.debug(e.toString()); } } /** * Gets the current position. * * @return position in track in % [0;1] */ public static float getCurrentPosition() { if (playerImpl != null) { return playerImpl.getCurrentPosition(); } else { return 0.0f; } } /** * Gets the current track total duration (secs). * * @return current track total duration in secs */ public static long getDurationSec() { if (playerImpl != null) { return playerImpl.getDurationSec(); } else { return 0l; } } /** * Gets the current volume. * * @return volume in track in %, ex : 0.2 for 20% */ public static float getCurrentVolume() { if (playerImpl != null) { return playerImpl.getCurrentVolume(); } else { return Conf.getFloat(Const.CONF_VOLUME); } } /** * Checks if is playing. * * @return Returns the bPlaying. */ public static boolean isPlaying() { return bPlaying; } /** * Checks if is seeking. * * @return whether current player is seeking */ public static boolean isSeeking() { return (playerImpl != null && playerImpl.getState() == BasicPlayer.SEEKING); } }