General code clean-up and fix for videos not correctly reporting their total length until the play button is pressed

This commit is contained in:
U-BASIS\dsmyda 2019-03-26 12:19:51 -04:00
parent 00a0adb175
commit 1e3358f456

View File

@ -20,7 +20,8 @@ package org.sleuthkit.autopsy.contentviewers;
import com.google.common.io.Files; import com.google.common.io.Files;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.EventQueue; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
@ -36,7 +37,6 @@ import javax.swing.JButton;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JSlider; import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker; import javax.swing.SwingWorker;
import javax.swing.Timer; import javax.swing.Timer;
import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeEvent;
@ -44,7 +44,6 @@ import javax.swing.event.ChangeListener;
import org.freedesktop.gstreamer.ClockTime; import org.freedesktop.gstreamer.ClockTime;
import org.freedesktop.gstreamer.Gst; import org.freedesktop.gstreamer.Gst;
import org.freedesktop.gstreamer.GstException; import org.freedesktop.gstreamer.GstException;
import org.freedesktop.gstreamer.State;
import org.freedesktop.gstreamer.StateChangeReturn; import org.freedesktop.gstreamer.StateChangeReturn;
import org.freedesktop.gstreamer.elements.PlayBin; import org.freedesktop.gstreamer.elements.PlayBin;
import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandle;
@ -66,7 +65,7 @@ import org.sleuthkit.datamodel.TskData;
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaViewPanel { public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaViewPanel {
private static final String[] FILE_EXTENSIONS = new String[] { private static final String[] FILE_EXTENSIONS = new String[]{
".3g2", ".3g2",
".3gp", ".3gp",
".3gpp", ".3gpp",
@ -94,94 +93,89 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
".wav", ".wav",
".webm", ".webm",
".wma", ".wma",
".wmv", ".wmv",}; //NON-NLS
}; //NON-NLS
private static final List<String> MIME_TYPES = Arrays.asList( private static final List<String> MIME_TYPES = Arrays.asList(
"video/3gpp", "video/3gpp",
"video/3gpp2", "video/3gpp2",
"audio/aiff", "audio/aiff",
"audio/amr-wb", "audio/amr-wb",
"audio/basic", "audio/basic",
"audio/mp4", "audio/mp4",
"video/mp4", "video/mp4",
"audio/mpeg", "audio/mpeg",
"video/mpeg", "video/mpeg",
"audio/mpeg3", "audio/mpeg3",
"application/mxf", "application/mxf",
"application/ogg", "application/ogg",
"video/quicktime", "video/quicktime",
"audio/vorbis", "audio/vorbis",
"audio/vnd.wave", "audio/vnd.wave",
"video/webm", "video/webm",
"video/x-3ivx", "video/x-3ivx",
"audio/x-aac", "audio/x-aac",
"audio/x-adpcm", "audio/x-adpcm",
"audio/x-alaw", "audio/x-alaw",
"audio/x-cinepak", "audio/x-cinepak",
"video/x-divx", "video/x-divx",
"audio/x-dv", "audio/x-dv",
"video/x-dv", "video/x-dv",
"video/x-ffv", "video/x-ffv",
"audio/x-flac", "audio/x-flac",
"video/x-flv", "video/x-flv",
"audio/x-gsm", "audio/x-gsm",
"video/x-h263", "video/x-h263",
"video/x-h264", "video/x-h264",
"video/x-huffyuv", "video/x-huffyuv",
"video/x-indeo", "video/x-indeo",
"video/x-intel-h263", "video/x-intel-h263",
"audio/x-ircam", "audio/x-ircam",
"video/x-jpeg", "video/x-jpeg",
"audio/x-m4a", "audio/x-m4a",
"video/x-m4v", "video/x-m4v",
"audio/x-mace", "audio/x-mace",
"audio/x-matroska", "audio/x-matroska",
"video/x-matroska", "video/x-matroska",
"audio/x-mpeg", "audio/x-mpeg",
"video/x-mpeg", "video/x-mpeg",
"audio/x-mpeg-3", "audio/x-mpeg-3",
"video/x-ms-asf", "video/x-ms-asf",
"audio/x-ms-wma", "audio/x-ms-wma",
"video/x-ms-wmv", "video/x-ms-wmv",
"video/x-msmpeg", "video/x-msmpeg",
"video/x-msvideo", "video/x-msvideo",
"video/x-msvideocodec", "video/x-msvideocodec",
"audio/x-mulaw", "audio/x-mulaw",
"audio/x-nist", "audio/x-nist",
"audio/x-oggflac", "audio/x-oggflac",
"audio/x-paris", "audio/x-paris",
"audio/x-qdm2", "audio/x-qdm2",
"audio/x-raw", "audio/x-raw",
"video/x-raw", "video/x-raw",
"video/x-rle", "video/x-rle",
"audio/x-speex", "audio/x-speex",
"video/x-svq", "video/x-svq",
"audio/x-svx", "audio/x-svx",
"video/x-tarkin", "video/x-tarkin",
"video/x-theora", "video/x-theora",
"audio/x-voc", "audio/x-voc",
"audio/x-vorbis", "audio/x-vorbis",
"video/x-vp3", "video/x-vp3",
"audio/x-w64", "audio/x-w64",
"audio/x-wav", "audio/x-wav",
"audio/x-wma", "audio/x-wma",
"video/x-wmv", "video/x-wmv",
"video/x-xvid" "video/x-xvid"
); //NON-NLS ); //NON-NLS
private static final Logger logger = Logger.getLogger(MediaPlayerPanel.class.getName()); private static final Logger logger = Logger.getLogger(MediaPlayerPanel.class.getName());
private boolean gstInited; private boolean gstInited;
private static final String MEDIA_PLAYER_ERROR_STRING = NbBundle.getMessage(MediaPlayerPanel.class, "GstVideoPanel.cannotProcFile.err"); private static final String MEDIA_PLAYER_ERROR_STRING = NbBundle.getMessage(MediaPlayerPanel.class, "GstVideoPanel.cannotProcFile.err");
//playback
private long durationMillis = 0;
private int totalHours, totalMinutes, totalSeconds;
private volatile PlayBin gstPlayBin; private volatile PlayBin gstPlayBin;
private GstVideoRendererPanel gstVideoRenderer;
private final Object playbinLock = new Object(); // lock for synchronization of gstPlayBin player private final Object playbinLock = new Object(); // lock for synchronization of gstPlayBin player
private AbstractFile currentFile;
private Timer timer; private Timer timer;
private ExtractMedia extractMediaWorker; private volatile ExtractMedia extractMediaWorker;
private static final long END_TIME_MARGIN_NS = 50000000; private static final long END_TIME_MARGIN_NS = 50000000;
private static final int PLAYER_STATUS_UPDATE_INTERVAL_MS = 50; private static final int PLAYER_STATUS_UPDATE_INTERVAL_MS = 50;
@ -228,6 +222,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
progressSlider.setMinimum(0); progressSlider.setMinimum(0);
progressSlider.setMaximum(2000); progressSlider.setMaximum(2000);
progressSlider.setValue(0); progressSlider.setValue(0);
//Manage the gstreamer video position when a user is dragging the slider in the panel.
progressSlider.addChangeListener(new ChangeListener() { progressSlider.addChangeListener(new ChangeListener() {
@Override @Override
public void stateChanged(ChangeEvent event) { public void stateChanged(ChangeEvent event) {
@ -251,6 +247,10 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
}); });
} }
/**
*
* @return
*/
private boolean initGst() { private boolean initGst() {
try { try {
logger.log(Level.INFO, "Initializing gstreamer for video/audio viewing"); //NON-NLS logger.log(Level.INFO, "Initializing gstreamer for video/audio viewing"); //NON-NLS
@ -274,132 +274,53 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
* @param file Media file to play. * @param file Media file to play.
* @param dims Dimension of the parent window. * @param dims Dimension of the parent window.
*/ */
@NbBundle.Messages ({"GstVideoPanel.noOpenCase.errMsg=No open case available."}) @NbBundle.Messages({"GstVideoPanel.noOpenCase.errMsg=No open case available."})
void loadFile(final AbstractFile file, final Dimension dims) { void loadFile(final AbstractFile file, final Dimension dims) {
EventQueue.invokeLater(() -> { //Ensure everything is back in the inital state
reset(); reset();
infoLabel.setText("");
currentFile = file;
final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC);
if (deleted) {
infoLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.setupVideo.infoLabel.text"));
videoPanel.removeAll();
pauseButton.setEnabled(false);
progressSlider.setEnabled(false);
return;
}
java.io.File ioFile; if (file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC)) {
infoLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.setupVideo.infoLabel.text"));
return;
}
try {
String path = file.getUniquePath();
infoLabel.setText(path);
infoLabel.setToolTipText(path);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Cannot get unique path of video file.", ex); //NON-NLS
}
synchronized (playbinLock) {
try { try {
ioFile = VideoUtils.getVideoFileInTempDir(file); extractMediaWorker = new ExtractMedia(file, VideoUtils.getVideoFileInTempDir(file));
extractMediaWorker.execute();
} catch (NoCurrentCaseException ex) { } catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
infoLabel.setText(Bundle.GstVideoPanel_noOpenCase_errMsg()); infoLabel.setText(Bundle.GstVideoPanel_noOpenCase_errMsg());
pauseButton.setEnabled(false); pauseButton.setEnabled(false);
progressSlider.setEnabled(false); progressSlider.setEnabled(false);
return;
} }
}
String path = "";
try {
path = file.getUniquePath();
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Cannot get unique path of video file.", ex); //NON-NLS
}
infoLabel.setText(path);
infoLabel.setToolTipText(path);
pauseButton.setEnabled(true);
progressSlider.setEnabled(true);
timer = new Timer(PLAYER_STATUS_UPDATE_INTERVAL_MS, event -> {
if (!progressSlider.getValueIsAdjusting()) {
long duration;
long position;
synchronized (playbinLock) {
duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
if (duration > 0) {
long positionDelta = duration - position;
if (positionDelta <= END_TIME_MARGIN_NS && gstPlayBin.isPlaying()) {
gstPlayBin.pause();
if (gstPlayBin.seek(ClockTime.ZERO) == false) {
logger.log(Level.WARNING, "Attempt to call PlayBin.seek() failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
progressSlider.setValue(0);
pauseButton.setText("");
} else {
double relativePosition = (double) position / duration;
progressSlider.setValue((int) (relativePosition * 2000));
}
}
}
durationMillis = duration / 1000000;
// pick out the total hours, minutes, seconds
long durationSeconds = (int) durationMillis / 1000;
totalHours = (int) durationSeconds / 3600;
durationSeconds -= totalHours * 3600;
totalMinutes = (int) durationSeconds / 60;
durationSeconds -= totalMinutes * 60;
totalSeconds = (int) durationSeconds;
long millisElapsed = position / 1000000;
// pick out the elapsed hours, minutes, seconds
long secondsElapsed = millisElapsed / 1000;
int elapsedHours = (int) secondsElapsed / 3600;
secondsElapsed -= elapsedHours * 3600;
int elapsedMinutes = (int) secondsElapsed / 60;
secondsElapsed -= elapsedMinutes * 60;
int elapsedSeconds = (int) secondsElapsed;
String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; //NON-NLS
String durationStr = String.format(durationFormat,
elapsedHours, elapsedMinutes, elapsedSeconds,
totalHours, totalMinutes, totalSeconds);
progressLabel.setText(durationStr);
}
});
timer.start();
gstVideoRenderer = new GstVideoRendererPanel();
synchronized (playbinLock) {
if (gstPlayBin != null) {
gstPlayBin.dispose();
}
gstPlayBin = new PlayBin("VideoPlayer"); //NON-NLS
gstPlayBin.setVideoSink(gstVideoRenderer.getVideoSink());
videoPanel.removeAll();
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
videoPanel.add(gstVideoRenderer);//add jfx ui to JPanel
videoPanel.setVisible(true);
gstPlayBin.setInputFile(ioFile);
}
});
} }
/** /**
* Prepare this MediaViewVideoPanel to accept a different media file. * Prepare this MediaViewVideoPanel to accept a different media file.
*/ */
void reset() { void reset() {
if (!isInited()) {
return;
}
if (timer != null) { if (timer != null) {
timer.stop(); timer.stop();
} }
// reset the progress label text on the event dispatch thread pauseButton.setEnabled(false);
SwingUtilities.invokeLater(() -> { progressSlider.setEnabled(false);
progressLabel.setText(""); progressLabel.setText("00:00:00/00:00:00");
}); infoLabel.setText("");
if (!isInited()) {
return;
}
synchronized (playbinLock) { synchronized (playbinLock) {
if (gstPlayBin != null) { if (gstPlayBin != null) {
@ -411,13 +332,16 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
gstPlayBin.dispose(); gstPlayBin.dispose();
gstPlayBin = null; gstPlayBin = null;
} }
gstVideoRenderer = null;
} }
if (extractMediaWorker != null) {
extractMediaWorker.cancel(true);
extractMediaWorker = null;
}
videoPanel.removeAll();
progressSlider.setValue(0); progressSlider.setValue(0);
pauseButton.setText(""); pauseButton.setText("");
currentFile = null;
} }
/** /**
@ -440,11 +364,11 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
videoPanel.setLayout(videoPanelLayout); videoPanel.setLayout(videoPanelLayout);
videoPanelLayout.setHorizontalGroup( videoPanelLayout.setHorizontalGroup(
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 0, Short.MAX_VALUE) .addGap(0, 0, Short.MAX_VALUE)
); );
videoPanelLayout.setVerticalGroup( videoPanelLayout.setVerticalGroup(
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 231, Short.MAX_VALUE) .addGap(0, 231, Short.MAX_VALUE)
); );
org.openide.awt.Mnemonics.setLocalizedText(pauseButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaViewVideoPanel.pauseButton.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(pauseButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaViewVideoPanel.pauseButton.text")); // NOI18N
@ -462,47 +386,47 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
controlPanel.setLayout(controlPanelLayout); controlPanel.setLayout(controlPanelLayout);
controlPanelLayout.setHorizontalGroup( controlPanelLayout.setHorizontalGroup(
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup() .addGroup(controlPanelLayout.createSequentialGroup()
.addContainerGap() .addContainerGap()
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup() .addGroup(controlPanelLayout.createSequentialGroup()
.addGap(6, 6, 6) .addGap(6, 6, 6)
.addComponent(infoLabel) .addComponent(infoLabel)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(controlPanelLayout.createSequentialGroup() .addGroup(controlPanelLayout.createSequentialGroup()
.addComponent(pauseButton) .addComponent(pauseButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 265, Short.MAX_VALUE) .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 265, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressLabel) .addComponent(progressLabel)
.addContainerGap()))) .addContainerGap())))
); );
controlPanelLayout.setVerticalGroup( controlPanelLayout.setVerticalGroup(
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup() .addGroup(controlPanelLayout.createSequentialGroup()
.addContainerGap() .addContainerGap()
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(pauseButton) .addComponent(pauseButton)
.addComponent(progressLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 29, javax.swing.GroupLayout.PREFERRED_SIZE)) .addComponent(progressLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 29, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(infoLabel) .addComponent(infoLabel)
.addContainerGap()) .addContainerGap())
); );
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(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(controlPanel, 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) .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
); );
layout.setVerticalGroup( layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup() .addGroup(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(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
); );
}// </editor-fold> }// </editor-fold>
@ -511,38 +435,23 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
if (gstPlayBin == null) { if (gstPlayBin == null) {
return; return;
} }
State state = gstPlayBin.getState(); switch (gstPlayBin.getState()) {
if (state.equals(State.PLAYING)) { case PLAYING:
if (gstPlayBin.pause() == StateChangeReturn.FAILURE) { pauseButton.setText("");
logger.log(Level.WARNING, "Attempt to call PlayBin.pause() failed."); //NON-NLS if (gstPlayBin.pause() == StateChangeReturn.FAILURE) {
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); logger.log(Level.WARNING, "Attempt to call PlayBin.pause() failed."); //NON-NLS
return; infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
} }
pauseButton.setText(""); break;
} else if (state.equals(State.PAUSED)) { case PAUSED:
if (gstPlayBin.play() == StateChangeReturn.FAILURE) { case READY:
logger.log(Level.WARNING, "Attempt to call PlayBin.play() failed."); //NON-NLS case NULL:
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING); pauseButton.setText("||");
return; if (gstPlayBin.play() == StateChangeReturn.FAILURE) {
} logger.log(Level.WARNING, "Attempt to call PlayBin.play() failed."); //NON-NLS
pauseButton.setText("||"); infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
} else if (state.equals(State.READY) || state.equals(State.NULL)) { }
final File tempVideoFile; break;
try {
tempVideoFile = VideoUtils.getVideoFileInTempDir(currentFile);
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Exception while getting open case."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
if (extractMediaWorker != null) {
extractMediaWorker.cancel(true);
extractMediaWorker = null;
}
extractMediaWorker = new ExtractMedia(currentFile, tempVideoFile);
extractMediaWorker.execute();
} }
} }
}//GEN-LAST:event_pauseButtonActionPerformed }//GEN-LAST:event_pauseButtonActionPerformed
@ -556,78 +465,6 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
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 extracts and plays a file
*/
private class ExtractMedia extends SwingWorker<Long, Void> {
private ProgressHandle progress;
private final AbstractFile sourceFile;
private final java.io.File tempFile;
ExtractMedia(AbstractFile sFile, java.io.File jFile) {
this.sourceFile = sFile;
this.tempFile = jFile;
}
@Override
protected Long doInBackground() throws Exception {
if (tempFile.exists() == false || tempFile.length() < sourceFile.getSize()) {
progress = ProgressHandle.createHandle(NbBundle.getMessage(MediaPlayerPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sourceFile.getName()), () -> this.cancel(true));
progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering"));
progress.start(100);
try {
Files.createParentDirs(tempFile);
return ContentUtils.writeToFile(sourceFile, tempFile, progress, this, true);
} catch (IOException ex) {
logger.log(Level.WARNING, "Error buffering file", ex); //NON-NLS
return 0L;
}
}
return 0L;
}
/*
* clean up or start the worker threads
*/
@Override
protected void done() {
try {
super.get(); //block and get all exceptions thrown while doInBackground()
} catch (CancellationException ex) {
logger.log(Level.INFO, "Media buffering was canceled."); //NON-NLS
} catch (InterruptedException ex) {
logger.log(Level.INFO, "Media buffering was interrupted."); //NON-NLS
} catch (ExecutionException ex) {
logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS
} finally {
if (progress != null) {
progress.finish();
}
if (!this.isCancelled()) {
playMedia();
}
}
}
void playMedia() {
if (tempFile == null || !tempFile.exists()) {
progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progressLabel.bufferingErr"));
return;
}
synchronized (playbinLock) {
gstPlayBin.seek(ClockTime.ZERO);
// must play, then pause and get state to get duration.
if (gstPlayBin.play() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin.play() failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
pauseButton.setText("||");
}
}
}
@Override @Override
public List<String> getSupportedExtensions() { public List<String> getSupportedExtensions() {
return Arrays.asList(FILE_EXTENSIONS.clone()); return Arrays.asList(FILE_EXTENSIONS.clone());
@ -674,4 +511,134 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
return false; return false;
} }
/**
* Thread that extracts and plays a file
*/
private class ExtractMedia extends SwingWorker<Long, Void> {
private ProgressHandle progress;
private final AbstractFile sourceFile;
private final java.io.File tempFile;
ExtractMedia(AbstractFile sFile, File jFile) {
this.sourceFile = sFile;
this.tempFile = jFile;
}
@Override
protected Long doInBackground() throws Exception {
if (tempFile.exists() == false || tempFile.length() < sourceFile.getSize()) {
progress = ProgressHandle.createHandle(NbBundle.getMessage(MediaPlayerPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sourceFile.getName()), () -> this.cancel(true));
progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering"));
progress.start(100);
try {
Files.createParentDirs(tempFile);
return ContentUtils.writeToFile(sourceFile, tempFile, progress, this, true);
} catch (IOException ex) {
logger.log(Level.WARNING, "Error buffering file", ex); //NON-NLS
return 0L;
}
}
return 0L;
}
/*
* clean up or start the worker threads
*/
@Override
protected void done() {
try {
super.get(); //block and get all exceptions thrown while doInBackground()
} catch (CancellationException ex) {
logger.log(Level.INFO, "Media buffering was canceled."); //NON-NLS
} catch (InterruptedException ex) {
logger.log(Level.INFO, "Media buffering was interrupted."); //NON-NLS
} catch (ExecutionException ex) {
logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS
} finally {
if (progress != null) {
progress.finish();
}
if (!this.isCancelled()) {
//PlayBin file is ready for playback, initialize all components.
synchronized (playbinLock) {
gstPlayBin = new PlayBin("VideoPlayer"); //NON-NLS
gstPlayBin.setInputFile(tempFile);
GstVideoRendererPanel gstVideoRenderer = new GstVideoRendererPanel();
gstPlayBin.setVideoSink(gstVideoRenderer.getVideoSink());
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
videoPanel.add(gstVideoRenderer);//add jfx ui to JPanel
/*
* It seems like PlayBin cannot be queried for duration
* until the video is actually being played. This call
* to pause below is used to 'initialize' the PlayBin to
* display the duration in the content viewer before the
* play button is pressed. This is a suggested solution
* in the gstreamer google groups page.
*/
gstPlayBin.pause();
timer = new Timer(PLAYER_STATUS_UPDATE_INTERVAL_MS, new VideoPanelUpdater());
timer.start();
videoPanel.setVisible(true);
pauseButton.setEnabled(true);
progressSlider.setEnabled(true);
}
}
}
}
}
/**
*
*/
private class VideoPanelUpdater implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if (!progressSlider.getValueIsAdjusting()) {
synchronized (playbinLock) {
long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
//Duration is -1 when the PlayBin is in the inital READY or
//NULL states, do nothing in these cases.
//if (duration <= 0) {
// return;
//}
long positionDelta = duration - position;
//NOTE: This conditional is problematic and is responsible for JIRA-4863
if (positionDelta <= END_TIME_MARGIN_NS && gstPlayBin.isPlaying()) {
gstPlayBin.pause();
if (gstPlayBin.seek(ClockTime.ZERO) == false) {
logger.log(Level.WARNING, "Attempt to call PlayBin.seek() failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
progressSlider.setValue(0);
pauseButton.setText("");
} else {
double relativePosition = (double) position / duration;
progressSlider.setValue((int) (relativePosition * 2000));
}
String durationStr = String.format("%s/%s", formatTime(position), formatTime(duration));
progressLabel.setText(durationStr);
}
}
}
/**
* Convert nanoseconds into an HH:MM:SS format.
*/
private String formatTime(long ns) {
long millis = ns / 1000000;
long seconds = (int) millis / 1000;
long hours = (int) seconds / 3600;
seconds -= hours * 3600;
long minutes = (int) seconds / 60;
seconds -= minutes * 60;
seconds = (int) seconds;
return String.format("%02d:%02d:%02d", hours, minutes, seconds);
}
}
} }