org.jajuk.services.players.AbstractMPlayerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.jajuk.services.players.AbstractMPlayerImpl.java

Source

/*
 *  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.io.PrintStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.jajuk.base.File;
import org.jajuk.services.webradio.WebRadio;
import org.jajuk.util.Conf;
import org.jajuk.util.Const;
import org.jajuk.util.UtilSystem;
import org.jajuk.util.log.Log;

/**
 * Mplayer player implementation.
 */
public abstract class AbstractMPlayerImpl implements IPlayerImpl, Const {
    /** Stored Volume. */
    float fVolume;
    /** Mplayer process. */
    volatile Process proc;
    /** End of file flag *. */
    volatile boolean bEOF = false;
    /** File is opened flag *. */
    volatile boolean bOpening = false;
    /** Stop position thread flag. */
    volatile boolean bStop = false;
    /** Fading state. */
    volatile boolean bFading = false;
    /** pause flag *. */
    protected volatile boolean bPaused = false;
    /** Whether the track has been started in bitperfect mode **/
    boolean bitPerfect = false;

    /*
     *
     * Kill abruptly the mplayer process (this way, killing is synchronous, and
     * easier than sending a quit command). Do not try to send a 'quit' command to
     * mplayer because then, it's not possible to differentiate end of file from
     * forced quit and the fifo will comes out of control
     */
    /* (non-Javadoc)
     * @see org.jajuk.services.players.IPlayerImpl#stop()
     */
    @Override
    public void stop() throws Exception {
        bFading = false;
        this.bStop = true;
        Log.debug("Stop");
        if (proc != null) {
            if (UtilSystem.isUnderLinux()) {
                /*
                 * Under linux (not sure if it may happen on others Unix and never
                 * reproduced under Windows), mplayer process can "zombified" after
                 * destroy() method call for unknown reason (linked with the mplayer
                 * slave mode ?). Even worse, these processes block the dsp audio line
                 * and then all new mplayer processes fail. To avoid this, we force a
                 * kill on every process call under Linux.
                 *
                 * Note also that mplayer slave mode opens two processes with different
                 * pids. When we try to kill them with -9 (abruptly) only the parent
                 * process dies and the second process is left hanging in the
                 * background. The solution is to just use kill (without -9) to let both
                 * mplayer processes die gracefully. I guess the destroy() method
                 * internally also tries to use -9 and so both pids are never killed.
                 */
                Field field = proc.getClass().getDeclaredField("pid");
                field.setAccessible(true);
                int pid = field.getInt(proc);
                try {
                    ProcessBuilder pb = new ProcessBuilder("kill", Integer.toString(pid));
                    pb.start();
                } catch (Error error) {
                    Log.error(error);
                }
            } else {
                proc.destroy();
            }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.jajuk.base.IPlayerImpl#setVolume(float)
     */
    @Override
    public void setVolume(float fVolume) {
        this.fVolume = fVolume;
        // Fix for a issue under Linux (at least with pulseaudio) : if a track is started in bitperfect mode (no volume specified), then 
        // the mode is unset when the same track is playing. When the fade out occurs, the volume commands sent to mplayer are propagated for some reasons
        // directly to the pulsaudio mixer and the next track sound volume is affected (muted most of times).
        if (bitPerfect) {
            Log.warn(
                    "This track was started in bit-perfect mode, even if the mode has been disabled, it can apply only to next track");
            return;
        }
        sendCommand("volume " + (int) (100 * fVolume) + " 2");
        // Not not log this when fading, generates too much logs
        if (!bFading) {
            Log.debug("Set Volume= " + (int) (100 * fVolume) + " %");
        }
    }

    /**
     * Send a command to mplayer slave.
     * 
     * @param command 
     */
    protected void sendCommand(String command) {
        if (proc != null) {
            PrintStream out = new PrintStream(proc.getOutputStream());
            // Do not use println() : it doesn't work under windows
            out.print(command + '\n');
            out.flush();
            // don't close out here otherwise the output stream of the Process
            // will be closed as well and subsequent sendCommand() calls will silently
            // fail!!
        }
    }

    /*
    * (non-Javadoc)
    * 
    * @see org.jajuk.players.IPlayerImpl#getCurrentVolume()
    */
    @Override
    public float getCurrentVolume() {
        return fVolume;
    }

    /**
     * Build the mplayer command line.
     * 
     * @param url the url to play
     * @param startPositionSec the position in the track when starting in secs (0 means we plat from the begining)
     * 
     * @return command line as a String array
     */
    List<String> buildCommand(String url, int startPositionSec) {
        String sCommand = "mplayer";
        // Use any forced mplayer path
        String forced = Conf.getString(Const.CONF_MPLAYER_PATH_FORCED);
        if (!StringUtils.isBlank(forced)) {
            sCommand = forced;
        } else {
            if (UtilSystem.isUnderWindows()) {
                sCommand = UtilSystem.getMPlayerWindowsPath().getAbsolutePath();
            } else if (UtilSystem.isUnderOSX()) {
                sCommand = UtilSystem.getMPlayerOSXPath().getAbsolutePath();
            }
        }
        String sAdditionalArgs = Conf.getString(Const.CONF_MPLAYER_ARGS);
        // Build command
        List<String> cmd = new ArrayList<String>(10);
        cmd.add(sCommand);
        // Start at given position
        cmd.add("-ss");
        cmd.add(Integer.toString(startPositionSec));
        // quiet: less traces
        cmd.add("-quiet");
        // slave: slave mode (control with stdin)
        cmd.add("-slave");
        // No af options if bit perfect is enabled
        if (!Conf.getBoolean(CONF_BIT_PERFECT)) {
            // -af volume: Use volnorm to limit gain to max
            // If mute, use -200db otherwise, use a linear scale
            cmd.add("-af");
            cmd.add(buildAudioFilters());
            // -softvol : use soft mixer, allows to set volume only to this mplayer
            // instance, not others programs
            cmd.add("-softvol");
        }
        // Define a cache. It is useful to avoid sound gliches but also to
        // overide a local mplayer large cache configuration in
        // ~/.mplayer/config file. User can set a large cache for video for ie.
        String cacheSize = "500";
        // 500Kb, mplayer starts before the cache is filled up
        if (!Conf.getString(Const.CONF_MPLAYER_ARGS).matches(".*-cache.*")) {
            // If user already forced a cache value, do not overwrite it
            cmd.add("-cache");
            cmd.add(cacheSize);
        }
        if (!StringUtils.isBlank(sAdditionalArgs)) {
            // Add any additional arguments provided by user
            String[] sArgs = sAdditionalArgs.split(" ");
            for (String element : sArgs) {
                cmd.add(element);
            }
        }
        // If it is a playlist, add the -playlist option, must be the last option
        // because options after -playlist are ignored (see mplayer man page).
        // Moreover, we only use this option if we are about to play line-based stream like m3u or the playback will fail.
        if (url.matches(".*://.*") && (url.toLowerCase().endsWith(".m3u") || url.toLowerCase().endsWith(".asx")
                || url.toLowerCase().endsWith(".pls"))) {
            cmd.add("-playlist");
        }
        cmd.add(url);
        return cmd;
    }

    /**
     * Build the -af audio filters command part.
     * 
     * @return the string
     */
    private String buildAudioFilters() {
        // Audio filters syntax : -af
        // <filter1[=parameter1:parameter2:...],filter2,...>
        // Add -volnorm (audio normalization) if option is set
        StringBuilder audiofilters = new StringBuilder();
        if (Conf.getBoolean(CONF_USE_VOLNORM)) {
            audiofilters.append("volnorm,");
        }
        // gain = -200 = mute
        int volume = -200;
        if (fVolume != 0) {
            // Gain = 10 * log(fVolume)
            volume = (int) (10 * Math.log(fVolume));
        }
        audiofilters.append("volume=" + volume);
        // Add karaoke state if required
        if (Conf.getBoolean(CONF_STATE_KARAOKE)) {
            audiofilters.append(",karaoke");
        }
        return audiofilters.toString();
    }

    /*
     * (non-Javadoc)
     *
     * @see org.jajuk.players.IPlayerImpl#getDurationSec()
     */
    @Override
    public long getDurationSec() {
        return 0;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.jajuk.players.IPlayerImpl#getCurrentPosition()
     */
    @Override
    public float getCurrentPosition() {
        return 0;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.jajuk.players.IPlayerImpl#getElapsedTimeMillis()
     */
    @Override
    public long getElapsedTimeMillis() {
        return 0;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.jajuk.players.IPlayerImpl#play(org.jajuk.base.File, float, long,
     *      float)
     */
    @Override
    public abstract void play(File file, float fPosition, long length, float fVolume) throws Exception;

    /*
     * (non-Javadoc)
     *
     * @see org.jajuk.players.IPlayerImpl#play(org.jajuk.base.WebRadio, float)
     */
    @Override
    public abstract void play(WebRadio radio, float fVolume) throws Exception;

    /*
     * (non-Javadoc)
     *
     * @see org.jajuk.players.IPlayerImpl#seek(float)
     */
    @Override
    public void seek(float fPosition) {
        // required by interface, but nothing to do here...
    }

    /**
     * Gets the state.
     * 
     * @return player state, -1 if player is null.
     */
    @Override
    public int getState() {
        return -1;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.jajuk.players.IPlayerImpl#pause()
     */
    @Override
    public void pause() throws Exception {
        bPaused = true;
        sendCommand("pause");
    }

    /*
     * (non-Javadoc)
     *
     * @see org.jajuk.players.IPlayerImpl#resume()
     */
    @Override
    public void resume() throws Exception {
        // This test is required because in case of volume change, mplayer is
        // already resumed and we don't want to send another pause command
        if (bPaused) {
            bPaused = false;
            sendCommand("pause");
        }
    }
}