com.reelfx.model.ScreenRecorder.java Source code

Java tutorial

Introduction

Here is the source code for com.reelfx.model.ScreenRecorder.java

Source

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.reelfx.model;

import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

import com.reelfx.Applet;
import com.reelfx.model.util.ProcessWrapper;
import com.reelfx.model.util.StreamGobbler;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;

import javax.sound.sampled.Mixer;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;

/**
 * 
 * @author Daniel Dixon (http://www.danieldixon.com)
 * 
 *    Copyright (C) 2010  ReelFX Creative Studios (http://www.reelfx.com)
 *
 *   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 3 of the License, or
 *    (at your option) 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, see <http://www.gnu.org/licenses/>
 *
 */
public class ScreenRecorder extends ProcessWrapper implements ActionListener {

    private static String EXT = Applet.IS_WINDOWS ? ".avi" : ".mov";
    private static Logger logger = Logger.getLogger(ScreenRecorder.class);

    // FILE LOCATIONS
    public static File OUTPUT_FILE = new File(
            Applet.BASE_FOLDER.getAbsolutePath() + File.separator + "screen_capture" + EXT);
    protected static File MAC_EXEC = new File(
            Applet.BIN_FOLDER.getAbsoluteFile() + File.separator + "mac-screen-recorder");
    protected static File FFMPEG_EXEC = new File(
            Applet.BIN_FOLDER.getAbsoluteFile() + File.separator + "ffmpeg" + (Applet.IS_WINDOWS ? ".exe" : ""));

    // STATES
    public final static int RECORDING_STARTED = 100;
    public final static int RECORDING_COMPLETE = 101;

    private Process recordingProcess;
    private StreamGobbler errorGobbler, inputGobbler;
    private Mixer audioSource = null;
    private int audioIndex = 0;
    private ScreenRecorder self = this;

    /**
     * If this guy is to handle audio as well, give it the Java Mixer object to read from.
     * 
     * @param mixer
     */
    public synchronized void start(Mixer mixer, int index) {
        audioSource = mixer;
        audioIndex = index;
        super.start();
    }

    public void run() {
        try {
            if (Applet.IS_MAC) {
                List<String> macArgs = new ArrayList<String>();
                macArgs.add(MAC_EXEC.getAbsolutePath());
                macArgs.add(OUTPUT_FILE.getAbsolutePath());

                ProcessBuilder pb = new ProcessBuilder(macArgs);
                recordingProcess = pb.start();
                fireProcessUpdate(RECORDING_STARTED);

                errorGobbler = new StreamGobbler(recordingProcess.getErrorStream(), false, "mac E");
                inputGobbler = new StreamGobbler(recordingProcess.getInputStream(), false, "mac O");

                logger.info("Starting listener threads...");
                errorGobbler.start();
                inputGobbler.start();

                recordingProcess.waitFor();
                fireProcessUpdate(RECORDING_COMPLETE);
            }

            else if (Applet.IS_LINUX) {
                // can have problem with file permissions when methods are invoked via Javascript even if applet is signed, 
                // thus some code needs to wrapped in a privledged block
                AccessController.doPrivileged(new PrivilegedAction<Object>() {

                    @Override
                    public Object run() {

                        try {
                            int width = Applet.CAPTURE_VIEWPORT.width;
                            if (width % 2 != 0)
                                width--;
                            int height = Applet.CAPTURE_VIEWPORT.height;
                            if (height % 2 != 0)
                                height--;

                            List<String> ffmpegArgs = new ArrayList<String>();
                            //ffmpegArgs.add("/usr/bin/ffmpeg");
                            ffmpegArgs.add(Applet.BIN_FOLDER.getAbsoluteFile() + File.separator + "ffmpeg");
                            // screen capture settings
                            ffmpegArgs.addAll(
                                    parseParameters("-y -f x11grab -s " + width + "x" + height + " -r 20 -i :0.0+"
                                            + Applet.CAPTURE_VIEWPORT.x + "," + Applet.CAPTURE_VIEWPORT.y));
                            // microphone settings (good resource: http://www.oreilly.de/catalog/multilinux/excerpt/ch14-05.htm)
                            /* 04/29/2010 - ffmpeg gets much better framerate when not recording microphone (let Java do this)
                             * if(audioSource != null) { 
                               String driver = audioIndex > 0 ? "/dev/dsp"+audioIndex : "/dev/dsp";
                               ffmpegArgs.addAll(parseParameters("-f oss -ac 1 -ar "+AudioRecorder.FREQ+" -i "+driver));
                            }*/
                            // output file settings
                            ffmpegArgs.addAll(parseParameters("-vcodec mpeg4 -r 20 -b 5000k")); // -s "+Math.round(width*SCALE)+"x"+Math.round(height*SCALE))
                            ffmpegArgs.add(OUTPUT_FILE.getAbsolutePath());

                            logger.info("Executing this command: " + prettyCommand(ffmpegArgs));

                            ProcessBuilder pb = new ProcessBuilder(ffmpegArgs);
                            recordingProcess = pb.start();
                            // fireProcessUpdate(RECORDING_STARTED); // moved to action listener method

                            errorGobbler = new StreamGobbler(recordingProcess.getErrorStream(), false, "ffmpeg E");
                            inputGobbler = new StreamGobbler(recordingProcess.getInputStream(), false, "ffmpeg O");

                            logger.info("Starting listener threads...");
                            errorGobbler.start();
                            errorGobbler.addActionListener("Stream mapping:", self);
                            inputGobbler.start();

                            recordingProcess.waitFor();

                            fireProcessUpdate(RECORDING_COMPLETE);

                        } catch (Exception e) {
                            logger.error("Error running Linux screen recorder!", e);
                        }
                        return null;
                    }
                });
            }

            else if (Applet.IS_WINDOWS) {
                // can have problem with file permissions when methods are invoked via Javascript even if applet is signed, 
                // thus some code needs to wrapped in a privileged block
                AccessController.doPrivileged(new PrivilegedAction<Object>() {

                    @Override
                    public Object run() {

                        try {
                            List<String> ffmpegArgs = new ArrayList<String>();
                            ffmpegArgs.add(FFMPEG_EXEC.getAbsolutePath());
                            // for full screen, use simply "cursor:desktop"
                            int width = Applet.CAPTURE_VIEWPORT.width % 2 == 0 ? Applet.CAPTURE_VIEWPORT.width
                                    : Applet.CAPTURE_VIEWPORT.width - 1;
                            int height = Applet.CAPTURE_VIEWPORT.height % 2 == 0 ? Applet.CAPTURE_VIEWPORT.height
                                    : Applet.CAPTURE_VIEWPORT.height - 1;
                            String viewport = ":offset=" + Applet.CAPTURE_VIEWPORT.x + ","
                                    + Applet.CAPTURE_VIEWPORT.y;
                            viewport += ":size=" + width + "," + height;
                            ffmpegArgs.addAll(parseParameters("-y -f gdigrab -r 20 -i cursor:desktop" + viewport
                                    + " -vcodec mpeg4 -b 5000k " + OUTPUT_FILE));

                            logger.info("Executing this command: " + prettyCommand(ffmpegArgs));
                            ProcessBuilder pb = new ProcessBuilder(ffmpegArgs);
                            recordingProcess = pb.start();
                            //fireProcessUpdate(RECORDING_STARTED); // moved to action listener method

                            // ffmpeg doesn't get the microphone on Windows, but this allows it to record a better frame rate anyway

                            errorGobbler = new StreamGobbler(recordingProcess.getErrorStream(), false, "ffmpeg E");
                            inputGobbler = new StreamGobbler(recordingProcess.getInputStream(), false, "ffmpeg O");

                            logger.info("Starting listener threads...");
                            errorGobbler.start();
                            errorGobbler.addActionListener("Stream mapping:", self);
                            inputGobbler.start();

                            recordingProcess.waitFor();

                            fireProcessUpdate(RECORDING_COMPLETE);

                        } catch (Exception e) {
                            logger.error("Error while running Windows screen recorder!", e);
                        }
                        return null;
                    }
                });

            }

        } catch (Exception ie) {
            logger.error("Exception while running ScreenRecorder!", ie);
        }
    }

    public void startRecording() {
        if (Applet.IS_MAC) {
            PrintWriter pw = new PrintWriter(recordingProcess.getOutputStream());
            pw.println("start");
            pw.flush();
        }
        // nothing for linux or windows
    }

    public void stopRecording() {
        if (Applet.IS_LINUX || Applet.IS_WINDOWS) {
            PrintWriter pw = new PrintWriter(recordingProcess.getOutputStream());
            pw.print("q");
            pw.flush();
        } else if (Applet.IS_MAC) {
            PrintWriter pw = new PrintWriter(recordingProcess.getOutputStream());
            pw.println("stop");
            pw.flush();
        }
        logger.info("Screen recording should be stopped.");
    }

    public void closeDown() {
        logger.info("Closing down ScreenRecorder...");
        if (Applet.IS_MAC && recordingProcess != null) {
            PrintWriter pw = new PrintWriter(recordingProcess.getOutputStream());
            pw.println("quit");
            pw.flush();
        } else if ((Applet.IS_LINUX || Applet.IS_WINDOWS) && recordingProcess != null) {
            PrintWriter pw = new PrintWriter(recordingProcess.getOutputStream());
            pw.print("q");
            pw.flush();
        }
        // nothing for linux or windows
    }

    @Override
    protected void finalize() throws Throwable {
        logger.info("Finalizing ScreenRecorder...");
        super.finalize();
        closeDown();
        if (recordingProcess != null)
            recordingProcess.destroy();
    }

    public static void deleteOutput() {
        AccessController.doPrivileged(new PrivilegedAction<Object>() {

            @Override
            public Object run() {
                try {
                    if (OUTPUT_FILE.exists() && !OUTPUT_FILE.delete())
                        throw new Exception("Can't delete the old video file!");
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
                return null;
            }
        });
    }

    @Override
    public void actionPerformed(ActionEvent arg0) {
        fireProcessUpdate(RECORDING_STARTED);
    }
}