/*
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.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
import javax.swing.InputMap;
import javax.swing.KeyStroke;
import javax.swing.JComponent;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JWindow;
import javax.swing.Timer;
import com.sun.jna.Native;
import org.jflicks.job.JobContainer;
import org.jflicks.job.JobEvent;
import org.jflicks.job.JobListener;
import org.jflicks.job.JobManager;
import org.jflicks.job.SystemJob;
import org.jflicks.player.BasePlayer;
import org.jflicks.player.Bookmark;
import org.jflicks.player.PlayState;
import org.jflicks.util.LogUtil;
import org.jflicks.util.Util;
/**
* This Player (with other classes in this package) is capable of
* executing the program mplayer to play media files.
*
* @author Doug Barnum
* @version 1.0
*/
public class MPlayer extends BasePlayer {
private JWindow window;
private JPanel keyPanel;
private MPlayerCanvas canvas;
private MPlayerJob mplayerJob;
private PlayStateJob statusJob;
private JobContainer jobContainer;
private JobContainer statusJobContainer;
private boolean userStop;
private boolean forceFullscreen;
private String programName;
private int disposeTimeout;
private String[] args;
/**
* Simple constructor.
*/
public MPlayer() {
setType(PLAYER_VIDEO);
setTitle("M");
setProgramName("mplayer");
setDisposeTimeout(0);
JPanel pan = new JPanel(new BorderLayout());
pan.setFocusable(true);
InputMap map = pan.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
QuitAction quitAction = new QuitAction();
map.put(KeyStroke.getKeyStroke("Q"), "q");
pan.getActionMap().put("q", quitAction);
InfoAction infoAction = new InfoAction();
map.put(KeyStroke.getKeyStroke("I"), "i");
pan.getActionMap().put("i", infoAction);
UpAction upAction = new UpAction();
map.put(KeyStroke.getKeyStroke("UP"), "up");
pan.getActionMap().put("up", upAction);
DownAction downAction = new DownAction();
map.put(KeyStroke.getKeyStroke("DOWN"), "down");
pan.getActionMap().put("down", downAction);
LeftAction leftAction = new LeftAction();
map.put(KeyStroke.getKeyStroke("LEFT"), "left");
pan.getActionMap().put("left", leftAction);
RightAction rightAction = new RightAction();
map.put(KeyStroke.getKeyStroke("RIGHT"), "right");
pan.getActionMap().put("right", rightAction);
EnterAction enterAction = new EnterAction();
map.put(KeyStroke.getKeyStroke("ENTER"), "enter");
pan.getActionMap().put("enter", enterAction);
GuideAction guideAction = new GuideAction();
map.put(KeyStroke.getKeyStroke("G"), "g");
pan.getActionMap().put("g", guideAction);
PauseAction pauseAction = new PauseAction();
map.put(KeyStroke.getKeyStroke("P"), "p");
pan.getActionMap().put("p", pauseAction);
PageUpAction pageupAction = new PageUpAction();
map.put(KeyStroke.getKeyStroke("PAGE_UP"), "pageup");
pan.getActionMap().put("pageup", pageupAction);
PageDownAction pagedownAction = new PageDownAction();
map.put(KeyStroke.getKeyStroke("PAGE_DOWN"), "pagedown");
pan.getActionMap().put("pagedown", pagedownAction);
RewindAction rewindAction = new RewindAction();
map.put(KeyStroke.getKeyStroke("R"), "r");
pan.getActionMap().put("r", rewindAction);
ForwardAction forwardAction = new ForwardAction();
map.put(KeyStroke.getKeyStroke("F"), "f");
pan.getActionMap().put("f", forwardAction);
SkipBackwardAction skipBackwardAction = new SkipBackwardAction();
map.put(KeyStroke.getKeyStroke("Z"), "z");
pan.getActionMap().put("z", skipBackwardAction);
SkipForwardAction skipForwardAction = new SkipForwardAction();
map.put(KeyStroke.getKeyStroke("X"), "x");
pan.getActionMap().put("x", skipForwardAction);
SapAction sapAction = new SapAction();
map.put(KeyStroke.getKeyStroke("S"), "s");
pan.getActionMap().put("s", sapAction);
AudioSyncPlusAction audioSyncPlusAction = new AudioSyncPlusAction();
map.put(KeyStroke.getKeyStroke("N"), "n");
pan.getActionMap().put("n", audioSyncPlusAction);
AudioSyncMinusAction audioSyncMinusAction = new AudioSyncMinusAction();
map.put(KeyStroke.getKeyStroke("M"), "m");
pan.getActionMap().put("m", audioSyncMinusAction);
MPlayerCanvas can = new MPlayerCanvas();
can.setBackground(Color.BLACK);
can.setFocusable(true);
pan.add(can, BorderLayout.CENTER);
setKeyPanel(pan);
setCanvas(can);
}
/**
* We allow custom arguments to the mplayer process.
*
* @return The arguments as a String array.
*/
public String[] getArgs() {
String[] result = null;
if (args != null) {
result = Arrays.copyOf(args, args.length);
}
return (result);
}
/**
* We allow custom arguments to the mplayer process.
*
* @param array The arguments as a String array.
*/
public void setArgs(String[] array) {
if (array != null) {
args = Arrays.copyOf(array, array.length);
} else {
args = null;
}
}
private JWindow getWindow() {
return (window);
}
private void setWindow(JWindow w) {
window = w;
}
private JPanel getKeyPanel() {
return (keyPanel);
}
private void setKeyPanel(JPanel p) {
keyPanel = p;
}
private MPlayerCanvas getCanvas() {
return (canvas);
}
private void setCanvas(MPlayerCanvas c) {
canvas = c;
}
private MPlayerJob getMPlayerJob() {
return (mplayerJob);
}
private void setMPlayerJob(MPlayerJob j) {
mplayerJob = j;
}
private PlayStateJob getPlayStateJob() {
return (statusJob);
}
private void setPlayStateJob(PlayStateJob j) {
statusJob = j;
}
private JobContainer getJobContainer() {
return (jobContainer);
}
private void setJobContainer(JobContainer jc) {
jobContainer = jc;
}
private JobContainer getPlayStateJobContainer() {
return (statusJobContainer);
}
private void setPlayStateJobContainer(JobContainer jc) {
statusJobContainer = jc;
}
/**
* Flag to signify that the user stopped the video, that it didn't come
* to it's natural end.
*
* @return True if the user quit.
*/
public boolean isUserStop() {
return (userStop);
}
private void setUserStop(boolean b) {
userStop = b;
}
/**
* Force mplayer to go fullscreen.
*
* @return True when you want mplayer to go fullscreen.
*/
public boolean isForceFullscreen() {
return (forceFullscreen);
}
/**
* Force mplayer to go fullscreen.
*
* @param b True when you want mplayer to go fullscreen.
*/
public void setForceFullscreen(boolean b) {
forceFullscreen = b;
}
/**
* The program name to run. This allows use to use mplayer or
* mplayer2.
*
* @return The program name property.
*/
public String getProgramName() {
return (programName);
}
/**
* The program name to run. This allows use to use mplayer or
* mplayer2.
*
* @param s The program name property.
*/
public void setProgramName(String s) {
programName = s;
}
/**
* A hack-ish value to timeout before doing a dispose of the window.
* The idea is that closing the window too soon stops a system job that
* runs mplayer to not exit properly. This seems to happen in a jflicks
* client-server instance often so we wait a bit before disposing and
* it seems to fix it. Again this is a hack.
*
* @return The timeout value.
*/
public int getDisposeTimeout() {
return (disposeTimeout);
}
/**
* A hack-ish value to timeout before doing a dispose of the window.
* The idea is that closing the window too soon stops a system job that
* runs mplayer to not exit properly. This seems to happen in a jflicks
* client-server instance often so we wait a bit before disposing and
* it seems to fix it. Again this is a hack.
*
* @param i The timeout value.
*/
public void setDisposeTimeout(int i) {
disposeTimeout = i;
}
/**
* {@inheritDoc}
*/
public boolean supportsPause() {
return (true);
}
/**
* {@inheritDoc}
*/
public boolean supportsAutoSkip() {
return (true);
}
/**
* {@inheritDoc}
*/
public boolean supportsMaximize() {
return (false);
}
/**
* {@inheritDoc}
*/
public boolean supportsSeek() {
return (true);
}
/**
* {@inheritDoc}
*/
public void play(String ... urls) {
if (urls != null) {
if (urls.length == 1) {
play(urls[0], null);
} else {
// We need to make a playlist file.
try {
FileWriter fw = new FileWriter("playlist.txt");
for (int i = 0; i < urls.length; i++) {
String tmp = urls[i] + "\n";
fw.write(tmp, 0, tmp.length());
}
fw.close();
play("-playlist playlist.txt", null);
} catch (IOException ex) {
LogUtil.log(LogUtil.DEBUG, ex.getMessage());
}
}
}
}
/**
* {@inheritDoc}
*/
public synchronized void play(String url, Bookmark b) {
if (!isPlaying()) {
setAudioOffset(0);
setPaused(false);
setPlaying(true);
setCompleted(false);
setUserStop(false);
String mytype = getType();
if (PLAYER_VIDEO_STREAM_UDP.equals(mytype)) {
int port = 1234;
MPlayerUdpJob job = null;
if (isForceFullscreen()) {
job = new MPlayerUdpJob(this, null, getArgs(), port);
setMPlayerJob(job);
} else {
MPlayerCanvas can = getCanvas();
JWindow win = createWindow();
setWindow(win);
win.setVisible(true);
long canid = -1;
try {
canid = Native.getComponentID(can);
} catch (Exception ex) {
LogUtil.log(LogUtil.DEBUG, ex.getMessage());
}
LogUtil.log(LogUtil.DEBUG, "canvas id: " + canid);
String wid = "" + canid;
job = new MPlayerUdpJob(this, wid, getArgs(), port);
setMPlayerJob(job);
}
JobContainer jc = JobManager.getJobContainer(job);
setJobContainer(jc);
jc.start();
} else {
long position = 0L;
int time = 0;
int playStateTime = 0;
boolean bookmarkSeconds = false;
if (b != null) {
playStateTime = b.getTime();
if (b.isPreferTime()) {
bookmarkSeconds = true;
time = playStateTime;
} else {
position = b.getPosition();
}
}
MPlayerJob job = null;
if (isForceFullscreen()) {
job = new MPlayerJob(this, null, getArgs(), position,
time, url, isAutoSkip());
setMPlayerJob(job);
} else {
MPlayerCanvas can = getCanvas();
JWindow win = createWindow();
setWindow(win);
win.setVisible(true);
long canid = -1;
try {
canid = Native.getComponentID(can);
} catch (Exception ex) {
LogUtil.log(LogUtil.DEBUG, ex.getMessage());
}
LogUtil.log(LogUtil.DEBUG, "canvas id: " + canid);
String wid = "" + canid;
job = new MPlayerJob(this, wid, getArgs(), position,
time, url, isAutoSkip());
setMPlayerJob(job);
}
boolean preferTime = true;
if (mytype == PLAYER_VIDEO_TRANSPORT_STREAM) {
preferTime = false;
}
PlayStateJob psj = new PlayStateJob(this, job, playStateTime,
bookmarkSeconds, preferTime);
setPlayStateJob(psj);
JobContainer jc = JobManager.getJobContainer(psj);
setPlayStateJobContainer(jc);
jc.start();
jc = JobManager.getJobContainer(job);
setJobContainer(jc);
jc.start();
}
}
}
private JWindow createWindow() {
JWindow result = null;
Rectangle r = null;
if (isFullscreen()) {
r = getFullscreenRectangle();
} else {
r = getRectangle();
}
int x = (int) r.getX();
int y = (int) r.getY();
int width = (int) r.getWidth();
int height = (int) r.getHeight();
LogUtil.log(LogUtil.DEBUG, "getFrame: " + getFrame());
result = new JWindow(getFrame());
result.setFocusableWindowState(true);
//result.setUndecorated(true);
result.setFocusable(true);
result.setBounds(x, y, width, height);
result.setBackground(Color.BLACK);
MPlayerCanvas can = getCanvas();
JPanel pan = getKeyPanel();
JLayeredPane lpane = new JLayeredPane();
setLayeredPane(lpane);
pan.setBounds(0, 0, width, height);
lpane.add(pan, Integer.valueOf(100));
result.add(lpane, BorderLayout.CENTER);
Cursor cursor = Util.getNoCursor();
if (cursor != null) {
pan.setCursor(cursor);
can.setCursor(cursor);
result.setCursor(cursor);
}
result.addWindowFocusListener(new WindowAdapter() {
public void windowGainedFocus(WindowEvent e) {
LogUtil.log(LogUtil.DEBUG, "windowGainedFocus: ");
getKeyPanel().requestFocusInWindow();
}
});
return (result);
}
/**
* {@inheritDoc}
*/
public void stop() {
LogUtil.log(LogUtil.DEBUG, "Stop being called.");
setPaused(false);
setPlaying(false);
setUserStop(true);
if (PLAYER_VIDEO_STREAM_UDP.equals(getType())) {
kill();
} else {
command("stop\n");
}
dispose();
}
/**
* {@inheritDoc}
*/
public void maximize(boolean b) {
setMaximized(b);
}
/**
* {@inheritDoc}
*/
public void pause(boolean b) {
setPaused(b);
command("pause\n");
}
/**
* {@inheritDoc}
*/
public void seek(int seconds) {
if (seconds > 0) {
command("seek +" + seconds + "\n");
} else {
command("seek " + seconds + "\n");
}
}
/**
* {@inheritDoc}
*/
public void seekPosition(int seconds) {
PlayStateJob job = getPlayStateJob();
if (job != null) {
double min = job.getMinimumTime();
if (min != Double.MAX_VALUE) {
int place = seconds + (int) min;
command("seek " + place + " 2\n");
command("set_property fullscreen 1\n");
}
}
}
/**
* {@inheritDoc}
*/
public void seekPosition(double percentage) {
int per = (int) (percentage * 100.0);
command("seek " + per + " 1\n");
command("set_property fullscreen 1\n");
}
/**
* {@inheritDoc}
*/
public void next() {
command("pt_step +1\n");
}
/**
* {@inheritDoc}
*/
public void previous() {
command("pt_step -1\n");
}
/**
* {@inheritDoc}
*/
public void sap() {
// mplayer2 chokes on this command so we just don't allow it.
if (!getProgramName().equals("mplayer2")) {
command("switch_audio\n");
}
}
/**
* {@inheritDoc}
*/
public void audiosync(double offset) {
double current = getAudioOffset();
current += offset;
if (current < -100.0) {
current = -100.0;
}
if (current > 100.0) {
current = 100.0;
}
setAudioOffset(current);
command("set_property audio_delay " + current + "\n");
}
@Override
public void setSize(Rectangle r) {
if (r != null) {
JWindow d = getWindow();
if (d != null) {
d.setBounds(r);
}
JPanel p = getKeyPanel();
if (p != null) {
p.setBounds(0, 0, r.width, r.height);
}
}
}
/**
* {@inheritDoc}
*/
public PlayState getPlayState() {
PlayState result = null;
PlayStateJob job = getPlayStateJob();
if (job != null) {
result = job.getPlayState();
}
return (result);
}
/**
* Clean up window resources.
*/
public synchronized void dispose() {
JWindow w = getWindow();
if (w != null) {
waitForFinish(getDisposeTimeout(), 10);
w.setVisible(false);
w.dispose();
setWindow(null);
if (Util.isLinux()) {
LogUtil.log(LogUtil.INFO, "On Linux: doing a killall just for fun.");
SystemJob job = SystemJob.getInstance("killall " + getProgramName());
JobContainer jc = JobManager.getJobContainer(job);
jc.start();
}
}
}
private void waitForFinish(int timeout, int step) {
JobContainer jc = getJobContainer();
if ((jc != null) && (timeout > 0)) {
int sofar = 0;
boolean done = false;
while (!done) {
if (jc.isAlive()) {
JobManager.sleep((long) step);
sofar += step;
if (sofar >= timeout) {
LogUtil.log(LogUtil.DEBUG, "Not Cool we timed out.");
done = true;
}
} else {
LogUtil.log(LogUtil.DEBUG, "Cool Job thread was done.");
done = true;
}
}
}
}
private void command(String s) {
MPlayerJob job = getMPlayerJob();
if ((s != null) && (job != null)) {
LogUtil.log(LogUtil.DEBUG, "send command to mplayer <" + s + ">");
job.command(s);
}
}
private void kill() {
MPlayerJob job = getMPlayerJob();
if (job != null) {
LogUtil.log(LogUtil.DEBUG, "send stop to mplayer");
job.stop();
}
}
}