Reverted Video Panel back to vlc.

This commit is contained in:
Jeff Wallace 2013-08-05 08:53:01 -04:00
parent ed75e9258b
commit fe8257e1db

View File

@ -18,13 +18,18 @@
*/ */
package org.sleuthkit.autopsy.corecomponents; package org.sleuthkit.autopsy.corecomponents;
import com.sun.jna.Native;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Image;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException; import java.io.IOException;
import java.nio.IntBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.BoxLayout; import javax.swing.BoxLayout;
import javax.swing.JButton; import javax.swing.JButton;
@ -35,6 +40,14 @@ import javax.swing.SwingUtilities;
import javax.swing.SwingWorker; import javax.swing.SwingWorker;
import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener; import javax.swing.event.ChangeListener;
import org.gstreamer.ClockTime;
import org.gstreamer.Gst;
import org.gstreamer.GstException;
import org.gstreamer.State;
import org.gstreamer.StateChangeReturn;
import org.gstreamer.elements.PlayBin2;
import org.gstreamer.elements.RGBDataSink;
import org.gstreamer.swing.VideoComponent;
import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory; import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Cancellable; import org.openide.util.Cancellable;
@ -47,15 +60,9 @@ import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData;
import uk.co.caprica.vlcj.binding.LibVlc;
import uk.co.caprica.vlcj.component.EmbeddedMediaPlayerComponent;
import uk.co.caprica.vlcj.player.MediaPlayer;
import uk.co.caprica.vlcj.player.MediaPlayerEventAdapter;
import uk.co.caprica.vlcj.player.MediaPlayerFactory;
import uk.co.caprica.vlcj.runtime.RuntimeUtil;
/** /**
* Video viewer part of the Media View layered pane. * Video viewer part of the Media View layered pane
*/ */
@ServiceProviders(value = { @ServiceProviders(value = {
@ServiceProvider(service = FrameCapture.class) @ServiceProvider(service = FrameCapture.class)
@ -63,37 +70,21 @@ import uk.co.caprica.vlcj.runtime.RuntimeUtil;
public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapture { public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapture {
private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName()); private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName());
private boolean vlcInited; // Has the vlc library been initalized properly? private boolean gstInited;
private static final long MIN_FRAME_INTERVAL_MILLIS = 500; private static final long MIN_FRAME_INTERVAL_MILLIS = 500;
private static final long INTER_FRAME_PERIOD_MS = 20; // Time between frames. private static final long FRAME_CAPTURE_TIMEOUT_MILLIS = 1000;
private static final String MEDIA_PLAYER_ERROR_STRING = "The media player cannot process this file."; private static final String MEDIA_PLAYER_ERROR_STRING = "The media player cannot process this file.";
private static final int POS_FACTOR = 10000; // The number of ticks on the video progress bar //playback
// Frame Capture Media Player Args
private static final String[] VLC_FRAME_CAPTURE_ARGS = {
"--intf", "dummy", // no interface
"--vout", "dummy", // we don't want video (output)
"--no-audio", // we don't want audio (decoding)
"--no-video-title-show", // nor the filename displayed
"--no-stats", // no stats
"--no-sub-autodetect-file", // we don't want subtitles
"--no-disable-screensaver", // we don't want interfaces
"--no-snapshot-preview" // no blending in dummy vout
};
// Playback state information
private long durationMillis = 0; private long durationMillis = 0;
private int totalHours, totalMinutes, totalSeconds;
private boolean autoTracking = false; // true if the slider is moving automatically
private boolean replay;
// VLC Components
private MediaPlayer vlcMediaPlayer;
private EmbeddedMediaPlayerComponent vlcVideoComponent;
// Worker threads
private VideoProgressWorker videoProgressWorker; private VideoProgressWorker videoProgressWorker;
private MediaPlayerThread mediaPlayerThread; private int totalHours, totalMinutes, totalSeconds;
// Current media content representations private volatile PlayBin2 gstPlaybin2;
private VideoComponent gstVideoComponent;
private boolean autoTracking = false; // true if the slider is moving automatically
private final Object playbinLock = new Object(); // lock for synchronization of gstPlaybin2 player
private AbstractFile currentFile; private AbstractFile currentFile;
private java.io.File currentVideoFile; private Set<String> badVideoFiles = Collections.synchronizedSet(new HashSet<String>());
/** /**
* Creates new form MediaViewVideoPanel * Creates new form MediaViewVideoPanel
*/ */
@ -118,82 +109,73 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
return videoPanel; return videoPanel;
} }
public EmbeddedMediaPlayerComponent getVideoComponent() { public VideoComponent getVideoComponent() {
return vlcVideoComponent; return gstVideoComponent;
} }
public boolean isInited() { public boolean isInited() {
return vlcInited; return gstInited;
} }
private void customizeComponents() { private void customizeComponents() {
if (!initVlc()) { if (!initGst()) {
return; return;
} }
progressSlider.setEnabled(false); // disable slider; enable after user plays vid progressSlider.setEnabled(false); // disable slider; enable after user plays vid
progressSlider.setValue(0); progressSlider.setValue(0);
progressSlider.addChangeListener(new ChangeListener() { progressSlider.addChangeListener(new ChangeListener() {
@Override /**
public void stateChanged(ChangeEvent e) { * Should always try to synchronize any call to
if (vlcMediaPlayer != null && !autoTracking) { * progressSlider.setValue() to avoid a different thread
float positionValue = progressSlider.getValue() / (float) POS_FACTOR; * changing playbin while stateChanged() is processing
// Avoid end of file freeze-up */
if (positionValue > 0.99f) { @Override
positionValue = 0.99f; public void stateChanged(ChangeEvent e) {
int time = progressSlider.getValue();
synchronized (playbinLock) {
if (gstPlaybin2 != null && !autoTracking) {
State orig = gstPlaybin2.getState();
if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
if (gstPlaybin2.seek(ClockTime.fromMillis(time)) == false) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.seek() failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
gstPlaybin2.setState(orig);
}
} }
vlcMediaPlayer.setPosition(positionValue);
} }
} });
});
}
private void pause() {
if (vlcMediaPlayer != null) {
vlcMediaPlayer.setPause(true);
pauseButton.setText("");
}
}
private void unPause() {
if (vlcMediaPlayer != null) {
vlcMediaPlayer.setPause(false);
pauseButton.setText("||");
}
}
/**
* Reset the video for replaying the current file.
*
* Called when finished Event is fired from MediaPlayer.
*/
private void finished() {
if (videoProgressWorker != null) {
videoProgressWorker.cancel(true);
videoProgressWorker = null;
}
mediaPlayerThread = new MediaPlayerThread();
replay = true;
} }
/** private boolean initGst() {
* Load the VLC library dll using JNA.
*
* @return <code>true</code>, if the library was loaded correctly.
* <code>false</code>, otherwise.
*/
private boolean initVlc() {
try { try {
Native.loadLibrary(RuntimeUtil.getLibVlcLibraryName(), LibVlc.class); logger.log(Level.INFO, "Initializing gstreamer for video/audio viewing");
vlcInited = true; Gst.init();
} catch (UnsatisfiedLinkError e) { gstInited = true;
logger.log(Level.SEVERE, "Error initalizing vlc for audio/video viewing and extraction capabilities", e); } catch (GstException e) {
MessageNotifyUtil.Notify.error("Error initializing vlc for audio/video viewing and frame extraction capabilities. " gstInited = false;
+ " Video and audio viewing will be disabled. ", e.getMessage()); logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and frame extraction capabilities", e);
vlcInited = false; MessageNotifyUtil.Notify.error("Error initializing gstreamer for audio/video viewing and frame extraction capabilities. "
+ " Video and audio viewing will be disabled. ",
e.getMessage());
return false;
} catch (UnsatisfiedLinkError | NoClassDefFoundError | Exception e) {
gstInited = false;
logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and extraction capabilities", e);
MessageNotifyUtil.Notify.error("Error initializing gstreamer for audio/video viewing frame extraction capabilities. "
+ " Video and audio viewing will be disabled. ",
e.getMessage());
return false;
} }
return vlcInited;
return true;
} }
/** /**
@ -205,7 +187,6 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
void setupVideo(final AbstractFile file, final Dimension dims) { void setupVideo(final AbstractFile file, final Dimension dims) {
infoLabel.setText(""); infoLabel.setText("");
currentFile = file; currentFile = file;
replay = false;
final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC); final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC);
if (deleted) { if (deleted) {
infoLabel.setText("Playback of deleted videos is not supported, use an external player."); infoLabel.setText("Playback of deleted videos is not supported, use an external player.");
@ -214,7 +195,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
progressSlider.setEnabled(false); progressSlider.setEnabled(false);
return; return;
} }
String path = ""; String path = "";
try { try {
path = file.getUniquePath(); path = file.getUniquePath();
@ -225,29 +206,37 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
infoLabel.setToolTipText(path); infoLabel.setToolTipText(path);
pauseButton.setEnabled(true); pauseButton.setEnabled(true);
progressSlider.setEnabled(true); progressSlider.setEnabled(true);
// Create and Configure MediaPlayer java.io.File ioFile = getJFile(file);
vlcVideoComponent = new EmbeddedMediaPlayerComponent();
vlcMediaPlayer = vlcVideoComponent.getMediaPlayer(); gstVideoComponent = new VideoComponent();
vlcMediaPlayer.setPlaySubItems(true); synchronized (playbinLock) {
vlcMediaPlayer.addMediaPlayerEventListener(new VlcMediaPlayerEventListener()); if (gstPlaybin2 != null) {
gstPlaybin2.dispose();
// Configure VideoPanel }
videoPanel.removeAll(); gstPlaybin2 = new PlayBin2("VideoPlayer");
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); gstPlaybin2.setVideoSink(gstVideoComponent.getElement());
videoPanel.add(vlcVideoComponent);
videoPanel.setVisible(true); videoPanel.removeAll();
// Create Extraction and Playback Threads
mediaPlayerThread = new MediaPlayerThread(); videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile)); videoPanel.add(gstVideoComponent);
em.execute();
videoPanel.setVisible(true);
gstPlaybin2.setInputFile(ioFile);
if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.READY) failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
}
}
} }
/**
* Cleanup all threads and VLC components and reset UI.
*/
void reset() { void reset() {
// reset the progress label text on the event dispatch thread // reset the progress label text on the event dispatch thread
SwingUtilities.invokeLater(new Runnable() { SwingUtilities.invokeLater(new Runnable() {
@Override @Override
@ -259,10 +248,27 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
if (!isInited()) { if (!isInited()) {
return; return;
} }
if (mediaPlayerThread != null) { synchronized (playbinLock) {
mediaPlayerThread.cancel(); if (gstPlaybin2 != null) {
mediaPlayerThread = null; if (gstPlaybin2.isPlaying()) {
if (gstPlaybin2.stop() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.stop() failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
}
if (gstPlaybin2.setState(State.NULL) == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.NULL) failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
if (gstPlaybin2.getState().equals(State.NULL)) {
gstPlaybin2.dispose();
}
gstPlaybin2 = null;
}
gstVideoComponent = null;
} }
// get rid of any existing videoProgressWorker thread // get rid of any existing videoProgressWorker thread
@ -271,27 +277,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
videoProgressWorker = null; videoProgressWorker = null;
} }
if (vlcMediaPlayer != null) {
if (vlcMediaPlayer.isPlaying()) {
vlcMediaPlayer.stop();
}
vlcMediaPlayer.release();
vlcMediaPlayer = null;
}
if (vlcVideoComponent != null) {
vlcVideoComponent.release(true);
vlcVideoComponent = null;
}
currentFile = null; currentFile = null;
currentVideoFile = null;
}
/**
* Start the media player.
*/
void playMedia() {
SwingUtilities.invokeLater(mediaPlayerThread);
} }
private java.io.File getJFile(AbstractFile file) { private java.io.File getJFile(AbstractFile file) {
@ -321,59 +307,134 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
public List<VideoFrame> captureFrames(java.io.File file, int numFrames) throws Exception { public List<VideoFrame> captureFrames(java.io.File file, int numFrames) throws Exception {
List<VideoFrame> frames = new ArrayList<>(); List<VideoFrame> frames = new ArrayList<>();
Object lock = new Object();
FrameCaptureRGBListener rgbListener = new FrameCaptureRGBListener(lock);
if (!isInited()) { if (!isInited()) {
return frames; return frames;
} }
// Create Media Player with no video output
MediaPlayerFactory mediaPlayerFactory = new MediaPlayerFactory(VLC_FRAME_CAPTURE_ARGS);
MediaPlayer mediaPlayer = mediaPlayerFactory.newHeadlessMediaPlayer();
boolean mediaPrepared = mediaPlayer.prepareMedia(file.getAbsolutePath());
if (!mediaPrepared) {
logger.log(Level.WARNING, "Video file was not loaded properly.");
return frames;
}
// throw exception if this file is known to be problematic
if (badVideoFiles.contains(file.getName())) {
throw new Exception("Cannot capture frames from this file (" + file.getName() + ").");
}
// set up a PlayBin2 object
RGBDataSink videoSink = new RGBDataSink("rgb", rgbListener);
PlayBin2 playbin = new PlayBin2("VideoFrameCapture");
playbin.setInputFile(file);
playbin.setVideoSink(videoSink);
// this is necessary to get a valid duration value
StateChangeReturn ret = playbin.play();
if (ret == StateChangeReturn.FAILURE) {
// add this file to the set of known bad ones
badVideoFiles.add(file.getName());
throw new Exception("Problem with video file; problem when attempting to play while obtaining duration.");
}
ret = playbin.pause();
if (ret == StateChangeReturn.FAILURE) {
// add this file to the set of known bad ones
badVideoFiles.add(file.getName());
throw new Exception("Problem with video file; problem when attempting to pause while obtaining duration.");
}
playbin.getState();
// get the duration of the video // get the duration of the video
mediaPlayer.parseMedia(); TimeUnit unit = TimeUnit.MILLISECONDS;
long myDurationMillis = mediaPlayer.getMediaMeta().getLength(); long myDurationMillis = playbin.queryDuration(unit);
if (myDurationMillis <= 0) { if (myDurationMillis <= 0) {
return frames; return frames;
} }
// calculate the number of frames to capture // calculate the number of frames to capture
int numFramesToGet = numFrames; int numFramesToGet = numFrames;
long frameInterval = (myDurationMillis - INTER_FRAME_PERIOD_MS) / numFrames; long frameInterval = myDurationMillis / numFrames;
if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) { if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) {
numFramesToGet = 1; numFramesToGet = 1;
} }
mediaPlayer.start();
mediaPlayer.setPause(true);
BufferedImage snapShot;
// for each timeStamp, grap a frame // for each timeStamp, grap a frame
for (int i = 0; i < numFramesToGet; ++i) { for (int i = 0; i < numFramesToGet; ++i) {
logger.log(Level.INFO, "Grabbing a frame..."); long timeStamp = i * frameInterval;
long timeStamp = i * frameInterval + INTER_FRAME_PERIOD_MS;
mediaPlayer.setTime(timeStamp); ret = playbin.pause();
mediaPlayer.setPause(true); if (ret == StateChangeReturn.FAILURE) {
// add this file to the set of known bad ones
snapShot = mediaPlayer.getSnapshot(); badVideoFiles.add(file.getName());
throw new Exception("Problem with video file; problem when attempting to pause while capturing a frame.");
if (snapShot == null) {
continue;
} }
frames.add(new VideoFrame(snapShot, timeStamp)); playbin.getState();
//System.out.println("Seeking to " + timeStamp + "milliseconds.");
if (!playbin.seek(timeStamp, unit)) {
logger.log(Level.INFO, "There was a problem seeking to " + timeStamp + " " + unit.name().toLowerCase());
}
ret = playbin.play();
if (ret == StateChangeReturn.FAILURE) {
// add this file to the set of known bad ones
badVideoFiles.add(file.getName());
throw new Exception("Problem with video file; problem when attempting to play while capturing a frame.");
}
// wait for FrameCaptureRGBListener to finish
synchronized(lock) {
try {
lock.wait(FRAME_CAPTURE_TIMEOUT_MILLIS);
} catch (InterruptedException e) {
logger.log(Level.INFO, "InterruptedException occurred while waiting for frame capture.", e);
}
}
Image image = rgbListener.getImage();
ret = playbin.stop();
if (ret == StateChangeReturn.FAILURE) {
// add this file to the set of known bad ones
badVideoFiles.add(file.getName());
throw new Exception("Problem with video file; problem when attempting to stop while capturing a frame.");
}
if (image == null) {
logger.log(Level.WARNING, "There was a problem while trying to capture a frame from file " + file.getName());
badVideoFiles.add(file.getName());
break;
}
frames.add(new VideoFrame(image, timeStamp));
} }
// cleanup media player
mediaPlayer.release();
mediaPlayerFactory.release();
return frames; return frames;
} }
private class FrameCaptureRGBListener implements RGBDataSink.Listener {
public FrameCaptureRGBListener(Object waiter) {
this.waiter = waiter;
}
private BufferedImage bi;
private final Object waiter;
@Override
public void rgbFrame(boolean bln, int w, int h, IntBuffer rgbPixels) {
synchronized (waiter) {
bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
bi.setRGB(0, 0, w, h, rgbPixels.array(), 0, w);
waiter.notify();
}
}
public Image getImage() {
synchronized (waiter) {
Image image = bi;
bi = null;
return image;
}
}
}
/** /**
* This method is called from within the constructor to initialize the form. * This method is called from within the constructor to initialize the form.
@ -409,8 +470,6 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
} }
}); });
progressSlider.setSnapToTicks(true);
org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.progressLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.progressLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.infoLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.infoLabel.text")); // NOI18N
@ -459,14 +518,39 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
}// </editor-fold>//GEN-END:initComponents }// </editor-fold>//GEN-END:initComponents
private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed
if (replay) { synchronized (playbinLock) {
// File has completed playing. Play button now replays media. State state = gstPlaybin2.getState();
replay = false; if (state.equals(State.PLAYING)) {
playMedia(); if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
} else if (vlcMediaPlayer.isPlaying()) { logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed.");
this.pause(); infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
} else { return;
this.unPause(); }
pauseButton.setText("");
// Is this call necessary considering we just called gstPlaybin2.pause()?
if (gstPlaybin2.setState(State.PAUSED) == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.PAUSED) failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
} else if (state.equals(State.PAUSED)) {
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
pauseButton.setText("||");
// Is this call necessary considering we just called gstPlaybin2.play()?
if (gstPlaybin2.setState(State.PLAYING) == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.PLAYING) failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
} else if (state.equals(State.READY)) {
ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile));
em.execute();
em.getExtractedBytes();
}
} }
}//GEN-LAST:event_pauseButtonActionPerformed }//GEN-LAST:event_pauseButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables
@ -478,29 +562,53 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
private javax.swing.JPanel videoPanel; private javax.swing.JPanel videoPanel;
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
/**
* Thread that updates the video progress bar and current time with information
* queried from the MediaPlayer.
*/
private class VideoProgressWorker extends SwingWorker<Object, Object> { private class VideoProgressWorker extends SwingWorker<Object, Object> {
private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d ";
private long millisElapsed = 0; private long millisElapsed = 0;
private float currentPosition = 0.0f; private final long INTER_FRAME_PERIOD_MS = 20;
private final long END_TIME_MARGIN_MS = 50;
private boolean hadError = false;
private boolean isMediaPlayerReady() { private boolean isPlayBinReady() {
return vlcMediaPlayer != null; synchronized (playbinLock) {
return gstPlaybin2 != null && !gstPlaybin2.getState().equals(State.NULL);
}
} }
// TODO: Could be moved to finished()
private void resetVideo() throws Exception { private void resetVideo() throws Exception {
synchronized (playbinLock) {
if (gstPlaybin2 != null) {
if (gstPlaybin2.stop() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.stop() failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
}
// ready to be played again
if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.READY) failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
}
gstPlaybin2.getState(); //NEW
}
}
pauseButton.setText(""); pauseButton.setText("");
progressSlider.setValue(0);
String durationStr = String.format(durationFormat, 0, 0, 0, String durationStr = String.format(durationFormat, 0, 0, 0,
totalHours, totalMinutes, totalSeconds); totalHours, totalMinutes, totalSeconds);
progressLabel.setText(durationStr); progressLabel.setText(durationStr);
} }
/**
* @return true while millisElapsed is greater than END_TIME_MARGIN_MS
* from durationMillis. This is used to indicate when the video has
* ended because for some videos the time elapsed never becomes equal to
* the reported duration of the video.
*/
private boolean hasNotEnded() {
return (durationMillis - millisElapsed) > END_TIME_MARGIN_MS;
}
@Override @Override
protected Object doInBackground() throws Exception { protected Object doInBackground() throws Exception {
@ -508,10 +616,14 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
progressSlider.setEnabled(true); progressSlider.setEnabled(true);
int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1; int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1;
while (isMediaPlayerReady() && !isCancelled()) { ClockTime pos = null;
millisElapsed = vlcMediaPlayer.getTime(); while (hasNotEnded() && isPlayBinReady() && !isCancelled()) {
currentPosition = vlcMediaPlayer.getPosition();
synchronized (playbinLock) {
pos = gstPlaybin2.queryPosition();
}
millisElapsed = pos.toMillis();
// pick out the elapsed hours, minutes, seconds // pick out the elapsed hours, minutes, seconds
long secondsElapsed = millisElapsed / 1000; long secondsElapsed = millisElapsed / 1000;
elapsedHours = (int) secondsElapsed / 3600; elapsedHours = (int) secondsElapsed / 3600;
@ -526,7 +638,7 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
progressLabel.setText(durationStr); progressLabel.setText(durationStr);
autoTracking = true; autoTracking = true;
progressSlider.setValue((int) (currentPosition * POS_FACTOR)); progressSlider.setValue((int) millisElapsed);
autoTracking = false; autoTracking = false;
try { try {
@ -545,16 +657,15 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
} }
} //end class progress worker } //end class progress worker
/** /* Thread that extracts and plays a file */
* Thread that extracts Media from a Sleuthkit file representation to a
* Java file representation that the Media Player can take as input.
*/
private class ExtractMedia extends SwingWorker<Object, Void> { private class ExtractMedia extends SwingWorker<Object, Void> {
private ProgressHandle progress; private ProgressHandle progress;
boolean success = false; boolean success = false;
private AbstractFile sFile; private AbstractFile sFile;
private java.io.File jFile; private java.io.File jFile;
private String duration;
private String position;
private long extractedBytes; private long extractedBytes;
ExtractMedia(org.sleuthkit.datamodel.AbstractFile sFile, java.io.File jFile) { ExtractMedia(org.sleuthkit.datamodel.AbstractFile sFile, java.io.File jFile) {
@ -583,7 +694,6 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
} catch (IOException ex) { } catch (IOException ex) {
logger.log(Level.WARNING, "Error buffering file", ex); logger.log(Level.WARNING, "Error buffering file", ex);
} }
logger.log(Level.INFO, "Done buffering: " + jFile.getName());
success = true; success = true;
return null; return null;
} }
@ -602,35 +712,34 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
} finally { } finally {
progress.finish(); progress.finish();
if (!this.isCancelled()) { if (!this.isCancelled()) {
logger.log(Level.INFO, "ExtractMedia in done: " + jFile.getName());
currentVideoFile = jFile;
playMedia(); playMedia();
} }
} }
} }
}
/**
* Thread that is responsible for running the Media Player.
*/
private class MediaPlayerThread implements Runnable, Cancellable {
private volatile boolean cancelled = false; void playMedia() {
if (jFile == null || !jFile.exists()) {
/* Prepare and run the current media. */
@Override
public void run() {
if (currentVideoFile == null || !currentVideoFile.exists()) {
progressLabel.setText("Error buffering file"); progressLabel.setText("Error buffering file");
return; return;
}
boolean mediaPrepared = vlcMediaPlayer.prepareMedia(currentVideoFile.getAbsolutePath());
if (mediaPrepared) {
vlcMediaPlayer.parseMedia();
durationMillis = vlcMediaPlayer.getMediaMeta().getLength();
} else {
progressLabel.setText(MEDIA_PLAYER_ERROR_STRING);
} }
ClockTime dur = null;
synchronized (playbinLock) {
// must play, then pause and get state to get duration.
if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed.");
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
gstPlaybin2.getState();
dur = gstPlaybin2.queryDuration();
}
duration = dur.toString();
durationMillis = dur.toMillis();
// pick out the total hours, minutes, seconds // pick out the total hours, minutes, seconds
long durationSeconds = (int) durationMillis / 1000; long durationSeconds = (int) durationMillis / 1000;
@ -640,45 +749,23 @@ public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapt
durationSeconds -= totalMinutes * 60; durationSeconds -= totalMinutes * 60;
totalSeconds = (int) durationSeconds; totalSeconds = (int) durationSeconds;
progressSlider.setMaximum(POS_FACTOR); SwingUtilities.invokeLater(new Runnable() {
progressSlider.setMinimum(0); @Override
if (!isCancelled()) { public void run() {
vlcMediaPlayer.start(); progressSlider.setMaximum((int) durationMillis);
pauseButton.setText("||"); progressSlider.setMinimum(0);
videoProgressWorker = new VideoProgressWorker();
videoProgressWorker.execute();
} else {
logger.log(Level.INFO, "Media Playing cancelled.");
}
}
@Override synchronized (playbinLock) {
public boolean cancel() { if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
cancelled = true; logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed.");
return cancelled; infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
} }
}
private boolean isCancelled() { pauseButton.setText("||");
return cancelled; videoProgressWorker = new VideoProgressWorker();
} videoProgressWorker.execute();
}
} });
/**
* An Object that listens and handles events thrown by the VLC Media Player.
*/
private class VlcMediaPlayerEventListener extends MediaPlayerEventAdapter {
@Override
public void finished(MediaPlayer mediaPlayer) {
logger.log(Level.INFO, "Media Player finished playing the media");
finished();
}
@Override
public void error(MediaPlayer mediaPlayer) {
logger.log(Level.WARNING, "A VLC error occured. Resetting the video panel.");
reset();
} }
} }
} }