diff --git a/CoreComponents/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form b/CoreComponents/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form index 4759f9b5d9..a8f4017e03 100644 --- a/CoreComponents/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form +++ b/CoreComponents/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form @@ -1,4 +1,4 @@ - +
@@ -20,7 +20,7 @@ - + @@ -32,10 +32,10 @@ - - - - + + + + @@ -47,6 +47,15 @@ + + + + + + + + + @@ -68,6 +77,9 @@ + + + diff --git a/CoreComponents/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java b/CoreComponents/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java index c92deeca35..86b5e330c1 100644 --- a/CoreComponents/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java +++ b/CoreComponents/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.corecomponents; import java.awt.Component; import java.io.IOException; import java.util.Arrays; +import java.util.concurrent.CancellationException; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.BoxLayout; @@ -45,18 +46,20 @@ import org.sleuthkit.datamodel.TskData; * * @author dfickling */ -@ServiceProvider(service = DataContentViewer.class, position=5) +@ServiceProvider(service = DataContentViewer.class, position = 5) public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer { - private static final String[] IMAGES = new String[]{ ".jpg", ".jpeg", ".png", ".gif", ".jpe", ".bmp"}; - private static final String[] VIDEOS = new String[]{ ".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg"}; - private static final String[] AUDIOS = new String[]{ ".mp3", ".wav", ".wma"}; + private static final String[] IMAGES = new String[]{".jpg", ".jpeg", ".png", ".gif", ".jpe", ".bmp"}; + private static final String[] VIDEOS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg"}; + private static final String[] AUDIOS = new String[]{".mp3", ".wav", ".wma"}; private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName()); - private VideoComponent videoComponent; + private VideoComponent videoComponent; private PlayBin2 playbin2; private File currentFile; private long durationMillis = 0; private boolean autoTracking = false; // true if the slider is moving automatically + private final Object playbinLock = new Object(); // lock for synchronization of playbin2 player + /** * Creates new form DataContentViewerVideo */ @@ -64,24 +67,28 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo initComponents(); customizeComponents(); } - + private void customizeComponents() { Gst.init(); progressSlider.addChangeListener(new ChangeListener() { - - @Override - public void stateChanged(ChangeEvent e) { - int time = progressSlider.getValue(); - if(playbin2 != null && !autoTracking) { + /** + * Should always try to synchronize any call to + * progressSlider.setValue() to avoid a different thread changing + * playbin while stateChanged() is processing + */ + @Override + public void stateChanged(ChangeEvent e) { + int time = progressSlider.getValue(); + synchronized (playbinLock) { + if (playbin2 != null && !autoTracking) { State orig = playbin2.getState(); playbin2.pause(); - playbin2.getState(); playbin2.seek(ClockTime.fromMillis(time)); playbin2.setState(orig); } } - - }); + } + }); } /** @@ -99,6 +106,9 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo progressLabel = new javax.swing.JLabel(); pauseButton.setText(org.openide.util.NbBundle.getMessage(DataContentViewerMedia.class, "DataContentViewerMedia.pauseButton.text")); // NOI18N + pauseButton.setMaximumSize(new java.awt.Dimension(45, 23)); + pauseButton.setMinimumSize(new java.awt.Dimension(45, 23)); + pauseButton.setPreferredSize(new java.awt.Dimension(45, 23)); pauseButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { pauseButtonActionPerformed(evt); @@ -116,6 +126,8 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo .addGap(0, 242, Short.MAX_VALUE) ); + progressSlider.setValue(0); + progressLabel.setText(org.openide.util.NbBundle.getMessage(DataContentViewerMedia.class, "DataContentViewerMedia.progressLabel.text")); // NOI18N javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); @@ -124,9 +136,9 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() - .addComponent(pauseButton) + .addComponent(pauseButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 160, Short.MAX_VALUE) + .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 158, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(progressLabel) .addContainerGap()) @@ -136,26 +148,30 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(pauseButton) - .addComponent(progressLabel) - .addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(pauseButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) ); }// //GEN-END:initComponents private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed - if(playbin2.getState().equals(State.PLAYING)){ - playbin2.pause(); - pauseButton.setText("►"); - } else if(playbin2.getState().equals(State.PAUSED)) { - playbin2.play(); - pauseButton.setText("||"); - } else { - ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile)); - em.execute(); + synchronized (playbinLock) { + State state = playbin2.getState(); + if (state.equals(State.PLAYING)) { + playbin2.pause(); + pauseButton.setText("►"); + playbin2.setState(State.PAUSED); + } else if (state.equals(State.PAUSED)) { + playbin2.play(); + pauseButton.setText("||"); + playbin2.setState(State.PLAYING); + } else if (state.equals(State.READY)) { + ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile)); + em.execute(); + } } }//GEN-LAST:event_pauseButtonActionPerformed - // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton pauseButton; private javax.swing.JLabel progressLabel; @@ -165,48 +181,93 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo @Override public void setNode(Node selectedNode) { - pauseButton.setText("►"); - if(selectedNode == null) { - setDataView(null); + reset(); + setComponentsVisibility(false); + if (selectedNode == null) { return; } + File file = selectedNode.getLookup().lookup(File.class); - setDataView(file); - if(file == null) { + if (file == null) { return; } - boolean isVidOrAud = containsExt(file.getName(), VIDEOS) || containsExt(file.getName(), AUDIOS); - pauseButton.setVisible(isVidOrAud); - progressLabel.setVisible(isVidOrAud); - progressSlider.setVisible(isVidOrAud); + + currentFile = file; + if (containsExt(file.getName(), IMAGES)) { + showImage(file); + } else if (containsExt(file.getName(), VIDEOS) || containsExt(file.getName(), AUDIOS)) { + setupVideo(file); + } } - private void setDataView(File file) { - if(file == null) { - setComponentsVisibility(false); - return; - } else { - setComponentsVisibility(true); - } - this.currentFile = file; - - if (containsExt(file.getName(), IMAGES)) { - java.io.File ioFile = getJFile(file); - if (!ioFile.exists()) { - try { - ContentUtils.writeToFile(file, ioFile); - } catch (IOException ex) { - logger.log(Level.WARNING, "Error buffering file", ex); - } + /** + * Initialize vars and display the image on the panel. + * + * @param file + */ + private void showImage(File file) { + java.io.File ioFile = getJFile(file); + if (!ioFile.exists()) { + try { + ContentUtils.writeToFile(file, ioFile); + } catch (IOException ex) { + logger.log(Level.WARNING, "Error buffering file", ex); } + } + + videoComponent = new VideoComponent(); + synchronized (playbinLock) { + playbin2 = new PlayBin2("ImageViewer"); + playbin2.setVideoSink(videoComponent.getElement()); + } + + videoPanel.removeAll(); + videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); + videoPanel.add(videoComponent); + videoPanel.revalidate(); + videoPanel.repaint(); + + synchronized (playbinLock) { playbin2.setInputFile(ioFile); playbin2.play(); } + videoPanel.setVisible(true); } + + /** + * Initialize all the necessary vars to play a video/audio file. + * + * @param file the File to play + */ + private void setupVideo(File file) { + java.io.File ioFile = getJFile(file); + + pauseButton.setText("►"); + progressSlider.setValue(0); + + videoComponent = new VideoComponent(); + synchronized (playbinLock) { + playbin2 = new PlayBin2("VideoPlayer"); + playbin2.setVideoSink(videoComponent.getElement()); + } + + videoPanel.removeAll(); + videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); + videoPanel.add(videoComponent); + videoPanel.revalidate(); + videoPanel.repaint(); + + synchronized (playbinLock) { + playbin2.setInputFile(ioFile); + playbin2.setState(State.READY); + } + setComponentsVisibility(true); + } + /** * To set the visibility of specific components in this class. * - * @param isVisible whether to show or hide the specific components + * @param isVisible whether to show or hide the specific components */ private void setComponentsVisibility(boolean isVisible) { pauseButton.setVisible(isVisible); @@ -237,42 +298,31 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo @Override public void resetComponent() { + // we don't want this to do anything + // because we already reset on each selected node } - - private void resetVideo() { - if(playbin2 != null) { - if(playbin2.isPlaying()) { - playbin2.stop(); + + private void reset() { + synchronized (playbinLock) { + if (playbin2 != null) { + if (playbin2.isPlaying()) { + playbin2.stop(); + } + playbin2.setState(State.NULL); +// try { +// Thread.sleep(20); // gstreamer needs to catch up +// } catch (InterruptedException ex) { } + if (playbin2.getState().equals(State.NULL)) { + playbin2.dispose(); + } + playbin2 = null; } - playbin2.setState(State.NULL); - if(playbin2.getState() == State.NULL) { - playbin2.dispose(); - } - playbin2 = null; - } - - videoComponent = null; - - playbin2 = new PlayBin2("VideoPlayer"); - videoComponent = new VideoComponent(); - playbin2.setVideoSink(videoComponent.getElement()); - - videoPanel.removeAll(); - videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); - videoPanel.add(videoComponent); - videoPanel.revalidate(); - videoPanel.repaint(); - } - - private void stopVideo() { - if(playbin2 != null && playbin2.isPlaying()) { - playbin2.stop(); + videoComponent = null; } } @Override public boolean isSupported(Node node) { - stopVideo(); if (node == null) { return false; } @@ -285,30 +335,29 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo if (File.dirFlagToValue(file.getDir_flags()).equals(TskData.TSK_FS_NAME_FLAG_ENUM.TSK_FS_NAME_FLAG_UNALLOC.toString())) { return false; } - - if(file.getSize() == 0) { + + if (file.getSize() == 0) { return false; } String name = file.getName().toLowerCase(); - - if(containsExt(name, IMAGES) || containsExt(name, AUDIOS) || containsExt(name, VIDEOS)) { - resetVideo(); + + if (containsExt(name, IMAGES) || containsExt(name, AUDIOS) || containsExt(name, VIDEOS)) { return true; } - + return false; } @Override public int isPreferred(Node node, boolean isSupported) { - if(isSupported) { + if (isSupported) { return 7; } else { return 0; } } - + private static boolean containsExt(String name, String[] exts) { int extStart = name.lastIndexOf("."); String ext = ""; @@ -317,7 +366,7 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo } return Arrays.asList(exts).contains(ext); } - + private java.io.File getJFile(File file) { // Get the temp folder path of the case String tempPath = Case.getCurrentCase().getTempDirectory(); @@ -332,19 +381,23 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo java.io.File tempFile = new java.io.File(tempPath); return tempFile; } - + /* Thread that extracts and plays a file */ - private class ExtractMedia extends SwingWorker { + private class ExtractMedia extends SwingWorker { private ProgressHandle progress; boolean success = false; private File sFile; private java.io.File jFile; - - ExtractMedia(org.sleuthkit.datamodel.File sFile, java.io.File jFile){ + String duration; + String position; + + ExtractMedia(org.sleuthkit.datamodel.File sFile, java.io.File jFile) { this.sFile = sFile; this.jFile = jFile; - }; + } + + ; @Override protected Object doInBackground() throws Exception { @@ -357,9 +410,9 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo }); progressLabel.setText("Buffering..."); progress.start(); - progress.switchToIndeterminate(); + progress.switchToDeterminate(100); try { - ContentUtils.writeToFile(sFile, jFile); + ContentUtils.writeToFile(sFile, jFile, progress, this, true); } catch (IOException ex) { logger.log(Level.WARNING, "Error buffering file", ex); } @@ -370,9 +423,19 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo /* clean up or start the worker threads */ @Override protected void done() { - progress.finish(); - if (success) { - play(); + try { + super.get(); //block and get all exceptions thrown while doInBackground() + } catch (CancellationException ex) { + logger.log(Level.INFO, "Media buffering was canceled."); + } catch (InterruptedException ex) { + logger.log(Level.INFO, "Media buffering was interrupted."); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); + } finally { + progress.finish(); + if (!this.isCancelled()) { + play(); + } } } @@ -381,34 +444,49 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo progressLabel.setText("Error buffering file"); return; } - playbin2.setInputFile(jFile); - playbin2.play(); // must play, then pause and get state to get duration. - playbin2.pause(); - playbin2.getState(); - String duration = playbin2.queryDuration().toString(); - durationMillis = playbin2.queryDuration().toMillis(); - progressSlider.setMaximum((int)durationMillis); + ClockTime dur = null; + synchronized (playbinLock) { + playbin2.play(); // must play, then pause and get state to get duration. + playbin2.pause(); + playbin2.getState(); + dur = playbin2.queryDuration(); + } + duration = dur.toString(); + durationMillis = dur.toMillis(); + + progressSlider.setMaximum((int) durationMillis); progressSlider.setMinimum(0); final String finalDuration; - if(duration.length() == 8 && duration.substring(0,3).equals("00:")) { + if (duration.length() == 8 && duration.substring(0, 3).equals("00:")) { finalDuration = duration.substring(3); progressLabel.setText("00:00/" + duration); } else { finalDuration = duration; progressLabel.setText("00:00:00/" + duration); } - playbin2.play(); + synchronized (playbinLock) { + playbin2.play(); + } pauseButton.setText("||"); new Thread(new Runnable() { + private boolean isPlayBinReady() { + synchronized (playbinLock) { + return playbin2 != null && !playbin2.getState().equals(State.NULL); + } + } @Override public void run() { long positionMillis = 0; while (positionMillis < durationMillis - && playbin2 != null - && !playbin2.getState().equals(State.NULL)) { - String position = playbin2.queryPosition().toString(); - positionMillis = playbin2.queryPosition().toMillis(); + && isPlayBinReady() ) { + ClockTime pos = null; + synchronized (playbinLock) { + pos = playbin2.queryPosition(); + } + position = pos.toString(); + positionMillis = pos.toMillis(); + if (position.length() == 8) { position = position.substring(3); } @@ -426,13 +504,22 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo } else { progressLabel.setText("00:00:00/" + finalDuration); } - if (playbin2 != null) { - playbin2.stop(); - playbin2.getState(); - pauseButton.setText("►"); - progressSlider.setValue(0); + // If it reached the end + if (progressSlider.getValue() == progressSlider.getMaximum()) { + restartVideo(); } } + + public void restartVideo() { + synchronized (playbinLock) { + if (playbin2 != null) { + playbin2.stop(); + playbin2.setState(State.READY); // ready to be played again + } + } + pauseButton.setText("►"); + progressSlider.setValue(0); + } }).start(); } } diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java index b8869af026..7aed58ea4b 100644 --- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java +++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java @@ -77,12 +77,7 @@ public class HashDbIngestModule implements IngestModuleAbstractFile { return instance; } - /** - * notification from manager that brand new processing should be initiated. - * Module loads its configuration and performs initialization - * - * @param services handle to the manager to postMessage() to - */ + @Override public void init(IngestModuleInit initContext) { services = IngestServices.getDefault(); @@ -127,10 +122,7 @@ public class HashDbIngestModule implements IngestModuleAbstractFile { } } - /** - * notification from manager that there is no more content to process and all work is done. - * Module performs any clean-up, notifies viewers and may also write results to the black-board - */ + @Override public void complete() { StringBuilder detailsSb = new StringBuilder(); diff --git a/Ingest/src/org/sleuthkit/autopsy/ingest/IngestModuleAbstract.java b/Ingest/src/org/sleuthkit/autopsy/ingest/IngestModuleAbstract.java index c98c445d91..3d028f02a5 100644 --- a/Ingest/src/org/sleuthkit/autopsy/ingest/IngestModuleAbstract.java +++ b/Ingest/src/org/sleuthkit/autopsy/ingest/IngestModuleAbstract.java @@ -41,9 +41,13 @@ public interface IngestModuleAbstract { }; /** - * Notification from manager that brand new ingest should be initiated. - * Module loads its configuration and performs initialization - * Invoked once per new worker thread, per ingest + * Notification from manager that brand new ingest should be initiated.. + * Module loads its configuration and performs initialization. + * Invoked once per new worker thread, per ingest. + * In this method initialize always IngestServices handle + * using IngestServices.getDefault() lazy-loading approach. + * NEVER initialize IngestServices handle in the member declaration, because it might result + * in multiple instances of the singleton -- different class loaders are used in different modules. * @param initContext context used to initialize some modules */ public void init(IngestModuleInit initContext); diff --git a/NEWS.txt b/NEWS.txt index e45dbd7aca..d181b6a84b 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -1,4 +1,4 @@ -3.0.0b5 (August 30, 2012) +3.0.0b5 (September 12, 2012) Funded by US Army Intelligence Center of Excellence (USAICoE): New features: @@ -21,6 +21,7 @@ Bugfixes: - Directory tree now shows which directories have no children before user clicks. - Fixed bug when recent cases would not get updated. - Fixed a bug when sometimes a case would get deleted. +- Fixed occasional Media View crashes. 3.0.0b4 (June 29, 2012) Funded by US Army Intelligence Center of Excellence (USAICoE):