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 java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
@ -36,7 +37,6 @@ import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.event.ChangeEvent;
@ -44,7 +44,6 @@ import javax.swing.event.ChangeListener;
import org.freedesktop.gstreamer.ClockTime;
import org.freedesktop.gstreamer.Gst;
import org.freedesktop.gstreamer.GstException;
import org.freedesktop.gstreamer.State;
import org.freedesktop.gstreamer.StateChangeReturn;
import org.freedesktop.gstreamer.elements.PlayBin;
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
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",
".3gp",
".3gpp",
@ -94,95 +93,90 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
".wav",
".webm",
".wma",
".wmv",
}; //NON-NLS
".wmv",}; //NON-NLS
private static final List<String> MIME_TYPES = Arrays.asList(
"video/3gpp",
"video/3gpp2",
"audio/aiff",
"audio/amr-wb",
"audio/basic",
"audio/mp4",
"video/mp4",
"audio/mpeg",
"video/mpeg",
"audio/mpeg3",
"application/mxf",
"application/ogg",
"video/quicktime",
"audio/vorbis",
"audio/vnd.wave",
"video/webm",
"video/x-3ivx",
"audio/x-aac",
"audio/x-adpcm",
"audio/x-alaw",
"audio/x-cinepak",
"video/x-divx",
"audio/x-dv",
"video/x-dv",
"video/x-ffv",
"audio/x-flac",
"video/x-flv",
"audio/x-gsm",
"video/x-h263",
"video/x-h264",
"video/x-huffyuv",
"video/x-indeo",
"video/x-intel-h263",
"audio/x-ircam",
"video/x-jpeg",
"audio/x-m4a",
"video/x-m4v",
"audio/x-mace",
"audio/x-matroska",
"video/x-matroska",
"audio/x-mpeg",
"video/x-mpeg",
"audio/x-mpeg-3",
"video/x-ms-asf",
"audio/x-ms-wma",
"video/x-ms-wmv",
"video/x-msmpeg",
"video/x-msvideo",
"video/x-msvideocodec",
"audio/x-mulaw",
"audio/x-nist",
"audio/x-oggflac",
"audio/x-paris",
"audio/x-qdm2",
"audio/x-raw",
"video/x-raw",
"video/x-rle",
"audio/x-speex",
"video/x-svq",
"audio/x-svx",
"video/x-tarkin",
"video/x-theora",
"audio/x-voc",
"audio/x-vorbis",
"video/x-vp3",
"audio/x-w64",
"audio/x-wav",
"audio/x-wma",
"video/x-wmv",
"video/x-xvid"
"video/3gpp",
"video/3gpp2",
"audio/aiff",
"audio/amr-wb",
"audio/basic",
"audio/mp4",
"video/mp4",
"audio/mpeg",
"video/mpeg",
"audio/mpeg3",
"application/mxf",
"application/ogg",
"video/quicktime",
"audio/vorbis",
"audio/vnd.wave",
"video/webm",
"video/x-3ivx",
"audio/x-aac",
"audio/x-adpcm",
"audio/x-alaw",
"audio/x-cinepak",
"video/x-divx",
"audio/x-dv",
"video/x-dv",
"video/x-ffv",
"audio/x-flac",
"video/x-flv",
"audio/x-gsm",
"video/x-h263",
"video/x-h264",
"video/x-huffyuv",
"video/x-indeo",
"video/x-intel-h263",
"audio/x-ircam",
"video/x-jpeg",
"audio/x-m4a",
"video/x-m4v",
"audio/x-mace",
"audio/x-matroska",
"video/x-matroska",
"audio/x-mpeg",
"video/x-mpeg",
"audio/x-mpeg-3",
"video/x-ms-asf",
"audio/x-ms-wma",
"video/x-ms-wmv",
"video/x-msmpeg",
"video/x-msvideo",
"video/x-msvideocodec",
"audio/x-mulaw",
"audio/x-nist",
"audio/x-oggflac",
"audio/x-paris",
"audio/x-qdm2",
"audio/x-raw",
"video/x-raw",
"video/x-rle",
"audio/x-speex",
"video/x-svq",
"audio/x-svx",
"video/x-tarkin",
"video/x-theora",
"audio/x-voc",
"audio/x-vorbis",
"video/x-vp3",
"audio/x-w64",
"audio/x-wav",
"audio/x-wma",
"video/x-wmv",
"video/x-xvid"
); //NON-NLS
private static final Logger logger = Logger.getLogger(MediaPlayerPanel.class.getName());
private boolean gstInited;
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 GstVideoRendererPanel gstVideoRenderer;
private final Object playbinLock = new Object(); // lock for synchronization of gstPlayBin player
private AbstractFile currentFile;
private Timer timer;
private ExtractMedia extractMediaWorker;
private volatile ExtractMedia extractMediaWorker;
private static final long END_TIME_MARGIN_NS = 50000000;
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.setMaximum(2000);
progressSlider.setValue(0);
//Manage the gstreamer video position when a user is dragging the slider in the panel.
progressSlider.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent event) {
@ -251,6 +247,10 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
});
}
/**
*
* @return
*/
private boolean initGst() {
try {
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 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) {
EventQueue.invokeLater(() -> {
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;
}
//Ensure everything is back in the inital state
reset();
if (file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC)) {
infoLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.setupVideo.infoLabel.text"));
return;
}
java.io.File ioFile;
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 {
ioFile = VideoUtils.getVideoFileInTempDir(file);
extractMediaWorker = new ExtractMedia(file, VideoUtils.getVideoFileInTempDir(file));
extractMediaWorker.execute();
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
infoLabel.setText(Bundle.GstVideoPanel_noOpenCase_errMsg());
pauseButton.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.
*/
void reset() {
if (timer != null) {
timer.stop();
}
// reset the progress label text on the event dispatch thread
SwingUtilities.invokeLater(() -> {
progressLabel.setText("");
});
if (!isInited()) {
return;
}
if (timer != null) {
timer.stop();
}
pauseButton.setEnabled(false);
progressSlider.setEnabled(false);
progressLabel.setText("00:00:00/00:00:00");
infoLabel.setText("");
synchronized (playbinLock) {
if (gstPlayBin != null) {
@ -411,13 +332,16 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
gstPlayBin.dispose();
gstPlayBin = null;
}
gstVideoRenderer = null;
}
if (extractMediaWorker != null) {
extractMediaWorker.cancel(true);
extractMediaWorker = null;
}
videoPanel.removeAll();
progressSlider.setValue(0);
pauseButton.setText("");
currentFile = null;
}
/**
@ -440,11 +364,11 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
videoPanel.setLayout(videoPanelLayout);
videoPanelLayout.setHorizontalGroup(
videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 0, Short.MAX_VALUE)
.addGap(0, 0, Short.MAX_VALUE)
);
videoPanelLayout.setVerticalGroup(
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
@ -462,47 +386,47 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
controlPanel.setLayout(controlPanelLayout);
controlPanelLayout.setHorizontalGroup(
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addContainerGap()
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addGap(6, 6, 6)
.addComponent(infoLabel)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(controlPanelLayout.createSequentialGroup()
.addComponent(pauseButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 265, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressLabel)
.addContainerGap())))
.addGroup(controlPanelLayout.createSequentialGroup()
.addContainerGap()
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addGap(6, 6, 6)
.addComponent(infoLabel)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(controlPanelLayout.createSequentialGroup()
.addComponent(pauseButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 265, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressLabel)
.addContainerGap())))
);
controlPanelLayout.setVerticalGroup(
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(controlPanelLayout.createSequentialGroup()
.addContainerGap()
.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(pauseButton)
.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)
.addComponent(infoLabel)
.addContainerGap())
.addGroup(controlPanelLayout.createSequentialGroup()
.addContainerGap()
.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(pauseButton)
.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)
.addComponent(infoLabel)
.addContainerGap())
);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.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(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)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createSequentialGroup()
.addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
);
}// </editor-fold>
@ -511,38 +435,23 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
if (gstPlayBin == null) {
return;
}
State state = gstPlayBin.getState();
if (state.equals(State.PLAYING)) {
if (gstPlayBin.pause() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin.pause() failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
return;
}
pauseButton.setText("");
} else if (state.equals(State.PAUSED)) {
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("||");
} else if (state.equals(State.READY) || state.equals(State.NULL)) {
final File tempVideoFile;
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();
switch (gstPlayBin.getState()) {
case PLAYING:
pauseButton.setText("");
if (gstPlayBin.pause() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin.pause() failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
}
break;
case PAUSED:
case READY:
case NULL:
pauseButton.setText("||");
if (gstPlayBin.play() == StateChangeReturn.FAILURE) {
logger.log(Level.WARNING, "Attempt to call PlayBin.play() failed."); //NON-NLS
infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
}
break;
}
}
}//GEN-LAST:event_pauseButtonActionPerformed
@ -556,78 +465,6 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
private javax.swing.JPanel videoPanel;
// 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
public List<String> getSupportedExtensions() {
return Arrays.asList(FILE_EXTENSIONS.clone());
@ -674,4 +511,134 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
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);
}
}
}