Archive

Posts Tagged ‘mplayer’

JMPlayer – Embedding MPlayer in Java

January 30, 2008 96 comments

MPlayer is a very good (if not the best) open source multimedia player. But what has got to do with Java? A lot of people will argue with me and they will jump immediately to say JMF. Java Media Framework, in my opinion, is an obsolete, unfriendly, unmaintained library. So let me tell you, although I’m a Java fan, if you want multimedia playing capabilities in your Java apps, until a good revival initiative for JMF is there on the market, MPlayer is the answer.

If anyway, we are not using Java native library, why MPlayer? Here the answer is simpler, even tough still debatable. I’ve chosen MPlayer for several reasons:

  • it is open source
  • it is well maintained and updated with the latest video formats
  • it is easy embeddable in your application using the slave mode and easy to integrate in your installer
  • it is ported for almost all the major OSes (Windows, Unix, Mac) so the portability won’t suffer too much

This is gonna be a little bit bigger article as I’ll walk you through all the details, especially the trickier ones). Before reading this you should have basic Java knowledge (especially threads and I/O). You may also want to read before or refer to MPlayer documentation and its slave mode (try this link if the previous one does not work).

First of all, the big picture: we will start, from our Java application, MPlayer in a separate process, in slave mode. MPlayer, in slave mode, accepts commands (terminated by newline character) from standard input, executes them and print the response, if any, to the standard output. So we will inject commands into the MPlayer standard input (that being an output stream for us) and we will parse the MPlayer standard output and standard error (input streams for us) to interpret the answers.

To start MPlayer use the below:

Process mplayerProcess = Runtime.getRuntime().exec("/path/to/mplayer -slave -quiet -idle file/to/play.avi");

This will start MPlayer in a separate process. /path/to/mplayer is the path to the mplayer executable. Then we have a few command line options:

slave
mandatory; tells MPlayer to start in slave mode
quiet
optional; reduces the amount of messages that MPlayer will output
idle
optional; it doesn’t close MPlayer after a file finished playing; this is quite useful as you don’t want to start a new process everytime you want to play a file, but rather loading the file into the existing already started process (for performance reasons)

Now we will redirect the standard output and error of MPlayer into other two (or, my preference, only one) other streams. This should be continuously and we will do it in separate threads. For the sake of simplicity I’ll describe first a helper class:

class LineRedirecter extends Thread {
    /** The input stream to read from. */
    private InputStream in;
    /** The output stream to write to. */
    private OutputStream out;
    
    /**
     * @param in the input stream to read from.
     * @param out the output stream to write to.
     * @param prefix the prefix used to prefix the lines when outputting to the logger.
     */
    LineRedirecter(InputStream in, OutputStream out) {
        this.in = in;
        this.out = out;
    }
    
    public void run()
    {
        try {
            // creates the decorating reader and writer
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            PrintStream printStream = new PrintStream(out);
            String line;
    
            // read line by line
            while ( (line = reader.readLine()) != null) {
                printStream.println(line);
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }
    
}

Basically the LineRedirecter class is a thread that will read from one input stream, line by line, and it will write to an output stream. The both streams are specified in the constructor. If an error occurs or if the input stream is closed, the thread ends gracefully. If you have basic knowledge on streams and I/O this is actually very simple so I’ll insist no more here.

And now back to MPlayer

// create the piped streams where to redirect the standard output and error of MPlayer
// specify a bigger pipesize than the default of 1024
PipedInputStream  readFrom = new PipedInputStream(256*1024);
PipedOutputStream writeTo = new PipedOutputStream(readFrom);
BufferedReader mplayerOutErr = new BufferedReader(new InputStreamReader(readFrom));

// create the threads to redirect the standard output and error of MPlayer
new LineRedirecter(mplayerProcess.getInputStream(), writeTo).start();
new LineRedirecter(mplayerProcess.getErrorStream(), writeTo).start();

// the standard input of MPlayer
PrintStream mplayerIn = new PrintStream(mplayerProcess.getOutputStream());

readFrom and writeTo creates a piped input output stream used by the above threads to redirect the standard output and error of MPlayer into a single stream. It is recommended to use a bigger buffer for the piped stream than the default one (in my example I use 256kb).

From now on we will use mplayerIn to send commands to MPlayer and mplayerOutErr to read and parse the answers. Now you’re practically set, but to help you I’ll just give you some sample code to see how everything is working

Open a new file
mplayerIn.print("loadfile \"/path/to/new file.avi\" 0");
mplayerIn.print("\n");
mplayerIn.flush();

Notice here two things. First: the path to the new file is enclosed in " as the file name contains space characters. Second: I haven’t used println, but print and then printed a newline character. println in some OSes (e.g. Windows) also prints the \r character and MPlayer will interpret it as part of the command, leading to an error.

Pause/play the current file
mplayerIn.print("pause");
mplayerIn.print("\n");
mplayerIn.flush();
Get the total playing time of the current file
>mplayerIn.print("get_property length");
mplayerIn.print("\n");
mplayerIn.flush();
String answer;
int totalTime = -1;
try {
    while ((answer = mplayerOutErr.readLine()) != null) {
        if (answer.startsWith("ANS_length=")) {
            totalTime = Integer.parseInt(answer.substring("ANS_length=".length()));
            break;    
        }
    }
}
catch (IOException e) {
}
System.out.println(totalTime);

Of course, this can be refined. We can also look to see if there is no error output, we can try to match against a regular expression, rather than test the starting string.

Jump to a given time in file
mplayerIn.print("set_property time_pos 300");
mplayerIn.print("\n");
mplayerIn.flush();

Jumps at the beginning of minute 5 in the movie (300 represents the seconds from the start)

Close MPlayer
mplayerIn.print("pause");
mplayerIn.print("\n");
mplayerIn.flush();            
try {
    mplayerProcess.waitFor();
}
catch (InterruptedException e) {}

Beside the fact that it sends the quit command to MPlayer also waits until the MPlayer process previously created is finished.

Now that you got the hang of it, you can create pretty complex interfaces or automation tasks with MPlayer.

All the code samples were tested with MPlayer CCCP 1.0rc2-4.2.1.

Later edit: I compiled a small one for you and you can download it here.

Advertisements
Categories: Software Tags: ,