/* This file is part of JFLICKS. JFLICKS 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 3 of the License, or (at your option) any later version. JFLICKS 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 JFLICKS. If not, see <http://www.gnu.org/licenses/>. */ package org.jflicks.player.mplayer; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import org.jflicks.job.AbstractJob; import org.jflicks.job.JobEvent; import org.jflicks.job.JobListener; import org.jflicks.job.JobManager; import org.jflicks.player.PlayState; import org.jflicks.util.LogUtil; import org.jflicks.util.Util; /** * This job communicates with mplayer to maintain the play state of the * video. * * @author Doug Barnum * @version 1.0 */ public class PlayStateJob extends AbstractJob implements JobListener, PropertyChangeListener { private static final int STREAM_POSITION = 2; private static final int STREAM_END = 3; private static final String STREAM_POSITION_COMMAND = "pausing_keep_force get_property stream_pos"; private static final String STREAM_POSITION_ANSWER = "ANS_stream_pos"; private static final String STREAM_END_COMMAND = "pausing_keep_force get_property stream_end"; private static final String STREAM_END_ANSWER = "ANS_stream_end"; private MPlayer mplayer; private MPlayerJob mplayerJob; private double time; private double minimumTime; private int percent; private int state; private int startSeconds; private long position; private long length; private boolean usedSeconds; private PlayState currentPlayState; private boolean checkLength; private boolean preferTime; /** * Contructor with two required arguments. * * @param p The MPlayer implementation of a Player. * @param job The job that execs mplayer in slave mode. * @param startSeconds The video may have been started somewhere * after the start so we need to know so the current time is computed * correctly. * @param usedSeconds To work around a bug, we try to seek a second in * the video as the "-ss seconds" parameter sometimes seem to result * in a freeze frame when beginning playback. This is a hack for sure. * @param preferTime The bookmark should use the time value, so we will * skip setting the position totally. */ public PlayStateJob(MPlayer p, MPlayerJob job, int startSeconds, boolean usedSeconds, boolean preferTime) { setMPlayer(p); setUsedSeconds(usedSeconds); if (p != null) { p.addPropertyChangeListener("Playing", this); } setMPlayerJob(job); if (job != null) { job.addJobListener(this); } setPreferTime(preferTime); setMinimumTime(Double.MAX_VALUE); setStartSeconds(startSeconds); setPosition(0L); setLength(0L); setSleepTime(3000); setCheckLength(true); setCurrentPlayState(new PlayState()); } private MPlayer getMPlayer() { return (mplayer); } private void setMPlayer(MPlayer p) { mplayer = p; } private MPlayerJob getMPlayerJob() { return (mplayerJob); } private void setMPlayerJob(MPlayerJob j) { mplayerJob = j; } private int getStartSeconds() { return (startSeconds); } private void setStartSeconds(int i) { startSeconds = i; } private int getState() { return (state); } private void setState(int i) { state = i; } private boolean isUsedSeconds() { return (usedSeconds); } private void setUsedSeconds(boolean b) { usedSeconds = b; } private PlayState getCurrentPlayState() { return (currentPlayState); } private void setCurrentPlayState(PlayState ps) { currentPlayState = ps; } private boolean isCheckLength() { return (checkLength); } private void setCheckLength(boolean b) { checkLength = b; } private boolean isPreferTime() { return (preferTime); } private void setPreferTime(boolean b) { preferTime = b; } private double getTime() { return (time); } private void setTime(double d) { time = d; if (time < getMinimumTime()) { LogUtil.log(LogUtil.DEBUG, "setTime argument: " + time); LogUtil.log(LogUtil.DEBUG, "setTime start seconds: " + getStartSeconds()); double dtmp = (double) getStartSeconds(); if (Math.abs(time - dtmp) < 12) { dtmp = 0; } else { dtmp = time - dtmp; LogUtil.log(LogUtil.DEBUG, "setTime dtmp: " + dtmp); if (dtmp < 0.0) { dtmp = time; } } LogUtil.log(LogUtil.DEBUG, "setTime setMinimumTime: " + dtmp); setMinimumTime(dtmp); } } /** * Since the video we play may not be time stamped correctly, we need to * find the "lowest" value of a time stamp. We then can normalize any * time we want by using this value to adjust to zero. * * @return The lowset time stamp (closest to the beginning) of the video. */ public double getMinimumTime() { return (minimumTime); } private void setMinimumTime(double d) { minimumTime = d; } private int getPercent() { return (percent); } private void setPercent(int i) { percent = i; } private long getPosition() { return (position); } private void setPosition(long l) { position = l; } private long getLength() { return (length); } private void setLength(long l) { length = l; } /** * Retrieve the most recent PlayState contructed from a running mplayer * program. * * @return A PlayState instance. */ public PlayState getPlayState() { PlayState result = getCurrentPlayState(); result.setTime(getTime() - getMinimumTime()); result.setPosition(getPosition()); MPlayer p = getMPlayer(); if (p != null) { result.setPlaying(p.isPlaying()); result.setPaused(p.isPaused()); } return (result); } private void command(String s) { MPlayerJob job = getMPlayerJob(); if ((s != null) && (job != null)) { job.command(s); } } /** * {@inheritDoc} */ public void start() { setTerminate(false); } /** * {@inheritDoc} */ public void run() { MPlayer p = getMPlayer(); if (p != null) { while (!isTerminate()) { JobManager.sleep(getSleepTime()); if ((p.isPlaying()) && (!p.isPaused())) { int commandState = getState(); switch (commandState) { default: case STREAM_POSITION: if (!isPreferTime()) { command(STREAM_POSITION_COMMAND + "\n"); } break; case STREAM_END: if (isCheckLength()) { command(STREAM_END_COMMAND + "\n"); } break; } } } } fireJobEvent(JobEvent.COMPLETE); } /** * {@inheritDoc} */ public void stop() { setTerminate(true); } /** * We listen for property changes from the Player. * * @param event A PropertyChangeEvent instance. */ public void propertyChange(PropertyChangeEvent event) { setState(STREAM_POSITION); } /** * {@inheritDoc} */ public void jobUpdate(JobEvent event) { if (event.getType() == JobEvent.COMPLETE) { MPlayer p = getMPlayer(); if (p != null) { setPercent(0); p.setPlaying(false); p.setCompleted(!p.isUserStop()); p.dispose(); } stop(); } else { String message = event.getMessage(); MPlayer p = getMPlayer(); if ((p != null) && (p.isPlaying()) && (message != null)) { if (message.startsWith(STREAM_POSITION_ANSWER)) { setPosition(Util.str2long(message.substring( message.indexOf("=") + 1), 0L)); } else if (message.startsWith(STREAM_END_ANSWER)) { long oldlength = getLength(); long newlength = Util.str2long(message.substring( message.indexOf("=") + 1), 0L); if (oldlength != newlength) { setLength(newlength); } else { setCheckLength(false); } setState(STREAM_POSITION); } else if (message.indexOf("V:") != -1) { int sindex = message.indexOf("V:") + 2; int eindex = message.indexOf("A-V:") - 1; String tmp = message.substring(sindex, eindex); double dtmp = Util.str2double(tmp, Double.MAX_VALUE); if (dtmp > 0.0) { setTime(dtmp); if (isUsedSeconds()) { setUsedSeconds(false); command("seek 1\n"); } } } else { //LogUtil.log(LogUtil.DEBUG, "From mplayer: " + message); } } else { //LogUtil.log(LogUtil.DEBUG, "From mplayer: " + message); } } } }