TSK-463: Media viewer: add seek, set position, see length in UI

This commit is contained in:
Dick Fickling 2012-04-19 10:20:59 -04:00
parent 4117ac141b
commit 162dc647d1
2 changed files with 161 additions and 60 deletions

View File

@ -19,7 +19,11 @@
<Component id="videoPanel" alignment="0" max="32767" attributes="0"/> <Component id="videoPanel" alignment="0" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0"> <Group type="102" alignment="0" attributes="0">
<Component id="pauseButton" min="-2" max="-2" attributes="0"/> <Component id="pauseButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="240" max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
<Component id="progressSlider" pref="160" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="progressLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group> </Group>
</Group> </Group>
</DimensionLayout> </DimensionLayout>
@ -28,7 +32,11 @@
<Group type="102" alignment="1" attributes="0"> <Group type="102" alignment="1" attributes="0">
<Component id="videoPanel" max="32767" attributes="0"/> <Component id="videoPanel" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
<Component id="pauseButton" min="-2" max="-2" attributes="0"/> <Group type="103" groupAlignment="1" attributes="0">
<Component id="pauseButton" min="-2" max="-2" attributes="0"/>
<Component id="progressLabel" min="-2" max="-2" attributes="0"/>
<Component id="progressSlider" min="-2" max="-2" attributes="0"/>
</Group>
</Group> </Group>
</Group> </Group>
</DimensionLayout> </DimensionLayout>
@ -59,5 +67,14 @@
</DimensionLayout> </DimensionLayout>
</Layout> </Layout>
</Container> </Container>
<Component class="javax.swing.JSlider" name="progressSlider">
</Component>
<Component class="javax.swing.JLabel" name="progressLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataContentViewerMedia.progressLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents> </SubComponents>
</Form> </Form>

View File

@ -5,20 +5,23 @@
package org.sleuthkit.autopsy.corecomponents; package org.sleuthkit.autopsy.corecomponents;
import java.awt.Component; import java.awt.Component;
import java.io.BufferedInputStream; import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays; import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.swing.BoxLayout; import javax.swing.BoxLayout;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.gstreamer.*; import org.gstreamer.*;
import org.gstreamer.elements.DecodeBin2; import org.gstreamer.elements.PlayBin2;
import org.gstreamer.io.InputStreamSrc;
import org.gstreamer.swing.VideoComponent; import org.gstreamer.swing.VideoComponent;
import org.openide.nodes.Node; import org.openide.nodes.Node;
import org.openide.util.lookup.ServiceProvider; import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.File; import org.sleuthkit.datamodel.File;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData;
/** /**
@ -32,10 +35,10 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
private static final String[] VIDEOS = new String[]{ ".mov", ".m4v", ".flv", ".mp4", ".3gp"}; private static final String[] VIDEOS = new String[]{ ".mov", ".m4v", ".flv", ".mp4", ".3gp"};
private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName()); private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName());
private VideoComponent videoComponent; private VideoComponent videoComponent;
private Pipeline pipe; private PlayBin2 playbin2;
private Element source = null; private File currentFile;
private DecodeBin2 decodeBin; private long durationMillis = 0;
private Bin audioBin; private boolean autoTracking = false; // true if the slider is moving automatically
/** /**
* Creates new form DataContentViewerVideo * Creates new form DataContentViewerVideo
*/ */
@ -47,6 +50,21 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
private void customizeComponents() { private void customizeComponents() {
Gst.init(); Gst.init();
resetVideo(); resetVideo();
progressSlider.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
int time = progressSlider.getValue();
if(playbin2 != null && !autoTracking) {
State orig = playbin2.getState();
playbin2.pause();
playbin2.getState();
playbin2.seek(ClockTime.fromMillis(time));
playbin2.setState(orig);
}
}
});
} }
/** /**
@ -60,6 +78,8 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
pauseButton = new javax.swing.JButton(); pauseButton = new javax.swing.JButton();
videoPanel = new javax.swing.JPanel(); videoPanel = new javax.swing.JPanel();
progressSlider = new javax.swing.JSlider();
progressLabel = new javax.swing.JLabel();
pauseButton.setText(org.openide.util.NbBundle.getMessage(DataContentViewerMedia.class, "DataContentViewerMedia.pauseButton.text")); // NOI18N pauseButton.setText(org.openide.util.NbBundle.getMessage(DataContentViewerMedia.class, "DataContentViewerMedia.pauseButton.text")); // NOI18N
pauseButton.addActionListener(new java.awt.event.ActionListener() { pauseButton.addActionListener(new java.awt.event.ActionListener() {
@ -79,6 +99,8 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
.addGap(0, 242, Short.MAX_VALUE) .addGap(0, 242, Short.MAX_VALUE)
); );
progressLabel.setText(org.openide.util.NbBundle.getMessage(DataContentViewerMedia.class, "DataContentViewerMedia.progressLabel.text")); // NOI18N
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout); this.setLayout(layout);
layout.setHorizontalGroup( layout.setHorizontalGroup(
@ -86,29 +108,95 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup() .addGroup(layout.createSequentialGroup()
.addComponent(pauseButton) .addComponent(pauseButton)
.addGap(240, 240, 240)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 160, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressLabel)
.addContainerGap())
); );
layout.setVerticalGroup( layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(pauseButton)) .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)))
); );
}// </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(pipe.isPlaying()){ if(playbin2.getState().equals(State.PLAYING)){
pipe.pause(); playbin2.pause();
pauseButton.setText(""); pauseButton.setText("");
} else { } else if(playbin2.getState().equals(State.PAUSED)) {
pipe.play(); playbin2.play();
pauseButton.setText("||"); pauseButton.setText("||");
} else {
progressLabel.setText("Buffering...");
java.io.File ioFile = extractFile(currentFile);
if(ioFile == null || !ioFile.exists()) {
progressLabel.setText("Error buffering file");
return;
}
playbin2.setInputFile(ioFile);
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);
progressSlider.setMinimum(0);
final String finalDuration;
if(duration.length() == 8) {
finalDuration = duration.substring(3);
progressLabel.setText("00:00/" + duration);
} else {
finalDuration = duration;
progressLabel.setText("00:00:00/" + duration);
}
playbin2.play();
pauseButton.setText("||");
new Thread(new Runnable() {
@Override
public void run() {
long positionMillis = 0;
while (positionMillis < durationMillis
&& !playbin2.getState().equals(State.NULL)) {
String position = playbin2.queryPosition().toString();
positionMillis = playbin2.queryPosition().toMillis();
if (position.length() == 8) {
position = position.substring(3);
}
progressLabel.setText(position + "/" + finalDuration);
autoTracking = true;
progressSlider.setValue((int) positionMillis);
autoTracking = false;
try {
Thread.sleep(20);
} catch (InterruptedException ex) {
}
}
if (finalDuration.length() == 5) {
progressLabel.setText("00:00/" + finalDuration);
} else {
progressLabel.setText("00:00:00/" + finalDuration);
}
playbin2.stop();
playbin2.getState();
pauseButton.setText("");
progressSlider.setValue(0);
}
}).start();
} }
}//GEN-LAST:event_pauseButtonActionPerformed }//GEN-LAST:event_pauseButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton pauseButton; private javax.swing.JButton pauseButton;
private javax.swing.JLabel progressLabel;
private javax.swing.JSlider progressSlider;
private javax.swing.JPanel videoPanel; private javax.swing.JPanel videoPanel;
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
@ -124,39 +212,20 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
setDataView(file); setDataView(file);
boolean isVid = isVid(file.getName()); boolean isVid = isVid(file.getName());
pauseButton.setVisible(isVid); pauseButton.setVisible(isVid);
progressLabel.setVisible(isVid);
progressSlider.setVisible(isVid);
} }
private void setDataView(File file) { private void setDataView(File file) {
if(file == null) if(file == null)
return; return;
InputStream is = new ReadContentInputStream(file); this.currentFile = file;
source = new InputStreamSrc(new BufferedInputStream(is), "input file");
pipe.add(source);
source.link(decodeBin); if(!isVid(file.getName())) {
java.io.File ioFile = extractFile(file);
decodeBin.connect(new DecodeBin2.NEW_DECODED_PAD() { playbin2.setInputFile(ioFile);
@Override playbin2.play();
public void newDecodedPad(DecodeBin2 elem, Pad pad, boolean last) { }
/*
* only link once
*/
if (pad.isLinked()) {
return;
}
Caps caps = pad.getCaps();
Structure struct = caps.getStructure(0);
if (struct.getName().startsWith("audio/")) {
pad.link(audioBin.getStaticPad("sink"));
} else if (struct.getName().startsWith("video/")) {
pad.link(videoComponent.getElement().getStaticPad("sink"));
}
}
});
if(!isVid(file.getName()))
pipe.play();
} }
@Override @Override
@ -184,28 +253,24 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
} }
private void resetVideo() { private void resetVideo() {
if(pipe != null) { if(playbin2 != null) {
pipe.stop(); playbin2.stop();
pipe.dispose(); playbin2.getState();
} else {
playbin2 = new PlayBin2("VideoPlayer");
}
if(videoComponent != null && videoComponent.getElement() != null) {
videoComponent.getElement().stop();
videoComponent.getElement().getState();
} else {
videoComponent = new VideoComponent();
playbin2.setVideoSink(videoComponent.getElement());
} }
pipe = new Pipeline("main pipeline");
videoComponent = new VideoComponent();
videoPanel.removeAll(); videoPanel.removeAll();
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
videoPanel.add(videoComponent); videoPanel.add(videoComponent);
videoPanel.revalidate(); videoPanel.revalidate();
videoPanel.repaint(); videoPanel.repaint();
decodeBin = (DecodeBin2) ElementFactory.make("decodebin2", "Decode Bin");
pipe.add(decodeBin);
audioBin = new Bin("Audio Bin");
Element conv = ElementFactory.make("audioconvert", "Audio Convert");
Element resample = ElementFactory.make("audioresample", "Audio Resample");
Element sink = ElementFactory.make("autoaudiosink", "sink");
audioBin.addMany(conv, resample, sink);
Element.linkMany(conv, resample, sink);
audioBin.addPad(new GhostPad("sink", conv.getStaticPad("sink")));
pipe.add(audioBin);
pipe.add(videoComponent.getElement());
} }
@Override @Override
@ -259,4 +324,23 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo
} }
return Arrays.asList(VIDEOS).contains(ext); return Arrays.asList(VIDEOS).contains(ext);
} }
private java.io.File extractFile(File file) {
// Get the temp folder path of the case
String tempPath = Case.getCurrentCase().getTempDirectory();
tempPath = tempPath + java.io.File.separator + file.getName();
// create the temporary file
java.io.File tempFile = new java.io.File(tempPath);
if (tempFile.exists()) {
tempFile.delete();
}
try {
ContentUtils.writeToFile(file, tempFile);
} catch (IOException ex) {
logger.log(Level.INFO, "Error buffering file", ex);
}
return tempFile;
}
} }