/**
*
* @author pquiring
*
* Created : Sept 27, 2013
*/
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javaforce.*;
import javaforce.media.*;
public class MainPanel extends javax.swing.JPanel implements MediaIO, ActionListener {
/**
* Creates new form MainPanel
*/
public MainPanel(JFrame frame) {
initComponents();
listAudioDevices();
this.frame = frame;
mouse = new JFImage();
mouse.loadPNG(this.getClass().getClassLoader().getResourceAsStream("mouse.png"));
initTray();
}
/**
* This method is called from within the constructor to initialize the form. WARNING: Do NOT modify this code. The
* content of this method is always regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
buttonGroup1 = new javax.swing.ButtonGroup();
buttonGroup2 = new javax.swing.ButtonGroup();
audio = new javax.swing.JRadioButton();
noaudio = new javax.swing.JRadioButton();
start = new javax.swing.JButton();
freq = new javax.swing.JComboBox();
mono = new javax.swing.JRadioButton();
stereo = new javax.swing.JRadioButton();
jLabel3 = new javax.swing.JLabel();
fps = new javax.swing.JSpinner();
jLabel4 = new javax.swing.JLabel();
audioDevices = new javax.swing.JComboBox();
showMouseCursor = new javax.swing.JCheckBox();
jLabel5 = new javax.swing.JLabel();
vBitRate = new javax.swing.JComboBox();
jLabel6 = new javax.swing.JLabel();
aBitRate = new javax.swing.JComboBox();
jLabel7 = new javax.swing.JLabel();
delay3seconds = new javax.swing.JCheckBox();
jLabel8 = new javax.swing.JLabel();
jLabel1 = new javax.swing.JLabel();
jLabel2 = new javax.swing.JLabel();
buttonGroup1.add(audio);
audio.setSelected(true);
audio.setText("Audio");
buttonGroup1.add(noaudio);
noaudio.setText("No Audio");
start.setText("Start");
start.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
startActionPerformed(evt);
}
});
freq.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "44100", "22050", "11025", "8000" }));
freq.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
freqActionPerformed(evt);
}
});
buttonGroup2.add(mono);
mono.setSelected(true);
mono.setText("mono");
buttonGroup2.add(stereo);
stereo.setText("stereo");
jLabel3.setText("FPS");
fps.setModel(new javax.swing.SpinnerNumberModel(16, 1, 60, 1));
jLabel4.setText("Audio Device");
showMouseCursor.setSelected(true);
showMouseCursor.setText("Show mouse cursor");
jLabel5.setText("Video Quality");
vBitRate.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "8M", "6M", "4M", "2M", "1M", "800k", "400k", "200k" }));
vBitRate.setSelectedIndex(4);
vBitRate.setToolTipText("");
vBitRate.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
vBitRateActionPerformed(evt);
}
});
jLabel6.setText("Audio Quality");
aBitRate.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "256k", "192k", "128k", "64k", "32k" }));
aBitRate.setSelectedIndex(2);
aBitRate.setToolTipText("");
jLabel7.setText("Freq:");
delay3seconds.setText("3 second delay");
delay3seconds.setToolTipText("Delay so you can minimize this window (Applet)");
jLabel8.setText("When you start a tray icon you be added to pause/stop recording.");
jLabel1.setText("bits/sec");
jLabel2.setText("bits/sec");
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel4)
.addGap(23, 23, 23)
.addComponent(audioDevices, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(audio)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(jLabel7))
.addComponent(noaudio))
.addComponent(freq, javax.swing.GroupLayout.PREFERRED_SIZE, 93, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(mono)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(stereo))
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel3)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(fps, javax.swing.GroupLayout.PREFERRED_SIZE, 56, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addGap(0, 0, Short.MAX_VALUE)))
.addGap(22, 22, 22))
.addGroup(layout.createSequentialGroup()
.addComponent(delay3seconds)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(start))
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel5)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(vBitRate, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jLabel1))
.addComponent(showMouseCursor)
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel6)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(aBitRate, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jLabel2))
.addComponent(jLabel8))
.addGap(0, 0, Short.MAX_VALUE)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel4)
.addComponent(audioDevices, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(fps, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jLabel3))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(audio)
.addComponent(freq, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(mono)
.addComponent(stereo)
.addComponent(jLabel7))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(noaudio)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(showMouseCursor)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel5)
.addComponent(vBitRate, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jLabel1))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel6)
.addComponent(aBitRate, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jLabel2))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jLabel8)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(start)
.addComponent(delay3seconds))
.addContainerGap())
);
}// </editor-fold>//GEN-END:initComponents
private void startActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_startActionPerformed
start();
}//GEN-LAST:event_startActionPerformed
private void freqActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_freqActionPerformed
// TODO add your handling code here:
}//GEN-LAST:event_freqActionPerformed
private void vBitRateActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_vBitRateActionPerformed
// TODO add your handling code here:
}//GEN-LAST:event_vBitRateActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JComboBox aBitRate;
private javax.swing.JRadioButton audio;
private javax.swing.JComboBox audioDevices;
private javax.swing.ButtonGroup buttonGroup1;
private javax.swing.ButtonGroup buttonGroup2;
private javax.swing.JCheckBox delay3seconds;
private javax.swing.JSpinner fps;
private javax.swing.JComboBox freq;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
private javax.swing.JLabel jLabel3;
private javax.swing.JLabel jLabel4;
private javax.swing.JLabel jLabel5;
private javax.swing.JLabel jLabel6;
private javax.swing.JLabel jLabel7;
private javax.swing.JLabel jLabel8;
private javax.swing.JRadioButton mono;
private javax.swing.JRadioButton noaudio;
private javax.swing.JCheckBox showMouseCursor;
private javax.swing.JButton start;
private javax.swing.JRadioButton stereo;
private javax.swing.JComboBox vBitRate;
// End of variables declaration//GEN-END:variables
private AudioInput mic;
private RandomAccessFile raf;
private boolean active = false;
private boolean paused = false;
private boolean working = false;
private JFrame frame;
private JFImage mouse;
public void listAudioDevices() {
mic = new AudioInput();
String list[] = mic.listDevices();
audioDevices.removeAllItems();
for(int a=0;a<list.length;a++) {
audioDevices.addItem(list[a]);
}
}
public void setState(boolean state) {
start.setEnabled(state);
if (state) start.setText("Start");
audioDevices.setEnabled(state);
audio.setEnabled(state);
noaudio.setEnabled(state);
mono.setEnabled(state);
stereo.setEnabled(state);
freq.setEnabled(state);
fps.setEnabled(state);
delay3seconds.setEnabled(state);
}
public void start() {
if (working) {
active = false;
start.setText("Stopping");
return;
}
working = true;
setState(false);
new Worker().start();
}
public void failed(String msg) {
JF.showError("Error", msg);
setState(true);
working = false;
}
public void swapEndian(byte in[], short out[]) {
int p = 0;
short s;
int cnt = out.length;
for(int a=0;a<cnt;a++) {
s = in[p++];
s <<= 8;
s += in[p++] & 0xff;
out[a] = s;
}
}
public class Worker extends Thread {
public void run() {
setState(false);
JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
chooser.setMultiSelectionEnabled(false);
chooser.setCurrentDirectory(new File(JF.getUserPath() + "/Videos"));
javax.swing.filechooser.FileFilter ffAVI = new javax.swing.filechooser.FileFilter() {
public boolean accept(File file) {
if (file.isDirectory()) return true;
if (file.getName().endsWith(".avi")) return true;
return false;
}
public String getDescription() {
return "AVI (*.avi)";
}
public String toString() {
return ".avi";
}
};
chooser.addChoosableFileFilter(ffAVI);
chooser.setFileFilter(ffAVI);
if (chooser.showSaveDialog(MainPanel.this) != JFileChooser.APPROVE_OPTION) {
setState(true);
working = false;
return;
}
String fn = chooser.getSelectedFile().getAbsolutePath();
String fnlc = fn.toLowerCase();
if ((!fnlc.endsWith(".avi"))) {
// javax.swing.filechooser.FileFilter ff = chooser.getFileFilter();
// fn += ff.toString();
fn += ".avi";
}
boolean doAudio = audio.isSelected();
int audioRate = JF.atoi((String)freq.getSelectedItem());
if (audioRate < 8000 || audioRate > 44100) audioRate = 44100;
int chs = mono.isSelected() ? 1 : 2;
int frameRate = (Integer)fps.getValue();
int samples = audioRate * chs / frameRate;
try {
raf = new RandomAccessFile(fn, "rw");
raf.setLength(0);
} catch (Exception e) {
JFLog.log(e);
failed("Unable to create output file");
return;
}
JFImage img = JFImage.createScreenCapture();
MediaEncoder encoder = new MediaEncoder();
encoder.setAudioBitRate(getAudioBitRate());
encoder.setVideoBitRate(getVideoBitRate());
int width = img.getWidth();
int height = img.getHeight();
JFLog.log("size=" + width + "," + height);
JFLog.log("frameRate=" + frameRate);
JFLog.log("audioRate=" + audioRate + ",chs=" + chs);
if (delay3seconds.isSelected()) {
JF.sleep(3000);
}
if (!encoder.start(MainPanel.this, width, height, frameRate, chs
, audioRate, "avi", true, doAudio))
{
failed("Unable to create output file");
return;
}
if (doAudio) {
mic = new AudioInput();
if (!mic.start(chs, audioRate, 16, samples * 2, (String)audioDevices.getSelectedItem())) {
failed("Unable to start recording audio");
return;
}
}
active = true;
paused = false;
start.setText("Stop");
start.setEnabled(true);
byte sams8[] = new byte[samples*2];
short sams16[] = new short[samples];
double current = System.currentTimeMillis();
double delay = 1000.0 / frameRate;
boolean showMouse = showMouseCursor.isSelected();
if (frame != null) frame.setVisible(false);
boolean skip_frame = false;
pause.setLabel("Pause");
addTray();
while (active) {
if (paused) {
while (mic.read(sams8)) {} //discard audio
JF.sleep(100);
continue;
}
if (!skip_frame) {
img = JFImage.createScreenCapture();
if (showMouse) {
Point mpt = MouseInfo.getPointerInfo().getLocation();
img.putJFImageBlend(mouse, mpt.x, mpt.y, true);
}
} else {
skip_frame = false;
}
int px[] = img.getPixels();
encoder.addVideo(px);
if (doAudio) {
while (mic.read(sams8)) {
swapEndian(sams8, sams16);
encoder.addAudio(sams16);
}
}
double now = System.currentTimeMillis();
int sleep = (int)(delay - (now - current));
if (sleep > 0) {
JFLog.log("sleep=" + sleep);
JF.sleep(sleep);
} else {
JFLog.log("sleep <= 0");
skip_frame = true; //system too slow
}
current += delay;
}
delTray();
if (frame != null) frame.setVisible(true);
encoder.stop();
if (doAudio) mic.stop();
try {
raf.close();
} catch (Exception e) {
JFLog.log(e);
}
working = false;
setState(true);
}
}
public int read(MediaCoder coder, byte[] bytes) {
return 0;
}
public int write(MediaCoder coder, byte[] bytes) {
try {
raf.write(bytes);
return bytes.length;
} catch (Exception e) {
JFLog.log(e);
return 0;
}
}
public long seek(MediaCoder coder, long pos, int how) {
try {
switch (how) {
case MediaCoder.SEEK_SET: break;
case MediaCoder.SEEK_CUR: pos += raf.getFilePointer(); break;
case MediaCoder.SEEK_END: pos += raf.length(); break;
}
raf.seek(pos);
return pos;
} catch (Exception e) {
JFLog.log(e);
}
return 0;
}
public SystemTray tray;
public TrayIcon icon;
public MenuItem stop, pause;
private void initTray() {
if (SystemTray.isSupported()) {
tray = SystemTray.getSystemTray();
// create a popup menu
PopupMenu popup = new PopupMenu();
pause = new MenuItem("Pause");
pause.addActionListener(this);
popup.add(pause);
popup.addSeparator();
stop = new MenuItem("Stop");
stop.addActionListener(this);
popup.add(stop);
Dimension size = tray.getTrayIconSize();
JFImage appicon = new JFImage();
appicon.loadPNG(this.getClass().getResourceAsStream("jfrecorddesktop.png"));
JFImage scaled = new JFImage(size.width, size.height);
scaled.fill(0, 0, size.width, size.height, 0x00000000, true); //fill with alpha transparent
if (false) {
//scaled image (looks bad sometimes)
scaled.getGraphics().drawImage(appicon.getImage()
, 0, 0, size.width, size.height
, 0, 0, appicon.getIconWidth(), appicon.getIconHeight()
, null);
} else {
//center image
scaled.getGraphics().drawImage(appicon.getImage()
, (size.width - appicon.getIconWidth()) / 2
, (size.height - appicon.getIconHeight()) / 2
, null);
}
icon = new TrayIcon(scaled.getImage(), "jfRecordDesktop", popup);
icon.addActionListener(this);
}
}
private void addTray() {
try { tray.add(icon); } catch (Exception e) { JFLog.log(e); }
}
private void delTray() {
try { tray.remove(icon); } catch (Exception e) { JFLog.log(e); }
}
public void actionPerformed(ActionEvent e) {
Object o = e.getSource();
if (o == stop) {
active = false;
}
if (o == pause) {
paused = !paused;
if (paused) {
pause.setLabel("Unpause");
} else {
pause.setLabel("Pause");
}
}
}
private int getRate(String rate) {
int scale = 1;
if (rate.endsWith("M")) {
rate = rate.substring(0, rate.length()-1);
scale = 1024 * 1024;
}
else if (rate.endsWith("k")) {
rate = rate.substring(0, rate.length()-1);
scale = 1024;
}
int value = JF.atoi(rate) * scale;
System.out.println("rate=" + value);
return value;
}
private int getAudioBitRate() {
return getRate((String)aBitRate.getSelectedItem());
}
private int getVideoBitRate() {
return getRate((String)vBitRate.getSelectedItem());
}
}