Autopsy  4.9.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
GstVideoPanel.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2013-2018 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.contentviewers;
20 
21 import com.google.common.io.Files;
22 import java.awt.Dimension;
23 import java.awt.Image;
24 import java.awt.image.BufferedImage;
25 import java.io.File;
26 import java.io.IOException;
27 import java.nio.IntBuffer;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Set;
34 import java.util.concurrent.CancellationException;
35 import java.util.concurrent.ExecutionException;
36 import java.util.concurrent.TimeUnit;
37 import java.util.logging.Level;
38 import javax.swing.BoxLayout;
39 import javax.swing.JButton;
40 import javax.swing.JLabel;
41 import javax.swing.JPanel;
42 import javax.swing.JSlider;
43 import javax.swing.SwingUtilities;
44 import javax.swing.SwingWorker;
45 import javax.swing.event.ChangeEvent;
46 import org.gstreamer.ClockTime;
47 import org.gstreamer.Gst;
48 import org.gstreamer.GstException;
49 import org.gstreamer.State;
50 import org.gstreamer.StateChangeReturn;
51 import org.gstreamer.elements.PlayBin2;
52 import org.gstreamer.elements.RGBDataSink;
53 import org.gstreamer.swing.VideoComponent;
54 import org.netbeans.api.progress.ProgressHandle;
55 import org.openide.util.NbBundle;
56 import org.openide.util.lookup.ServiceProvider;
57 import org.openide.util.lookup.ServiceProviders;
65 import org.sleuthkit.datamodel.AbstractFile;
66 import org.sleuthkit.datamodel.TskCoreException;
67 import org.sleuthkit.datamodel.TskData;
68 
69 @ServiceProviders(value = {
70  @ServiceProvider(service = FrameCapture.class)
71 })
72 @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
73 public class GstVideoPanel extends MediaViewVideoPanel {
74 
75  private static final String[] EXTENSIONS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; //NON-NLS
76  private static final List<String> MIMETYPES = Arrays.asList("video/quicktime", "audio/mpeg", "audio/x-mpeg", "video/mpeg", "video/x-mpeg", "audio/mpeg3", "audio/x-mpeg-3", "video/x-flv", "video/mp4", "audio/x-m4a", "video/x-m4v", "audio/x-wav"); //NON-NLS
77 
78  private static final Logger logger = Logger.getLogger(GstVideoPanel.class.getName());
79  private boolean gstInited;
80  private static final long MIN_FRAME_INTERVAL_MILLIS = 500;
81  private static final long FRAME_CAPTURE_TIMEOUT_MILLIS = 1000;
82  private static final String MEDIA_PLAYER_ERROR_STRING = NbBundle.getMessage(GstVideoPanel.class, "GstVideoPanel.cannotProcFile.err");
83  //playback
84  private long durationMillis = 0;
86  private int totalHours, totalMinutes, totalSeconds;
87  private volatile PlayBin2 gstPlaybin2;
88  private VideoComponent gstVideoComponent;
89  private boolean autoTracking = false; // true if the slider is moving automatically
90  private final Object playbinLock = new Object(); // lock for synchronization of gstPlaybin2 player
91  private AbstractFile currentFile;
92  private final Set<String> badVideoFiles = Collections.synchronizedSet(new HashSet<>());
93 
97  public GstVideoPanel() {
98  initComponents();
99  customizeComponents();
100  }
101 
102  public JButton getPauseButton() {
103  return pauseButton;
104  }
105 
106  public JLabel getProgressLabel() {
107  return progressLabel;
108  }
109 
110  public JSlider getProgressSlider() {
111  return progressSlider;
112  }
113 
114  public JPanel getVideoPanel() {
115  return videoPanel;
116  }
117 
118  public VideoComponent getVideoComponent() {
119  return gstVideoComponent;
120  }
121 
122  @Override
123  public boolean isInited() {
124  return gstInited;
125  }
126 
127  private void customizeComponents() {
128  if (!initGst()) {
129  return;
130  }
131 
132  progressSlider.setEnabled(false); // disable slider; enable after user plays vid
133  progressSlider.setValue(0);
134 
135  progressSlider.addChangeListener((ChangeEvent e) -> {
141  int time = progressSlider.getValue();
142  synchronized (playbinLock) {
143  if (gstPlaybin2 != null && !autoTracking) {
144  State orig = gstPlaybin2.getState();
145  if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
146  logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed."); //NON-NLS
147  infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
148  return;
149  }
150  if (gstPlaybin2.seek(ClockTime.fromMillis(time)) == false) {
151  logger.log(Level.WARNING, "Attempt to call PlayBin2.seek() failed."); //NON-NLS
152  infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
153  return;
154  }
155  gstPlaybin2.setState(orig);
156  }
157  }
158  });
159  }
160 
161  private boolean initGst() {
162  try {
163  logger.log(Level.INFO, "Initializing gstreamer for video/audio viewing"); //NON-NLS
164  Gst.init();
165  gstInited = true;
166  } catch (GstException e) {
167  gstInited = false;
168  logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and frame extraction capabilities", e); //NON-NLS
170  NbBundle.getMessage(this.getClass(), "GstVideoPanel.initGst.gstException.msg"),
171  e.getMessage());
172  return false;
173  } catch (UnsatisfiedLinkError | NoClassDefFoundError | Exception e) {
174  gstInited = false;
175  logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and extraction capabilities", e); //NON-NLS
177  NbBundle.getMessage(this.getClass(), "GstVideoPanel.initGst.otherException.msg"),
178  e.getMessage());
179  return false;
180  }
181 
182  return true;
183  }
184 
185  @Override
186  @NbBundle.Messages ({"GstVideoPanel.noOpenCase.errMsg=No open case available."})
187  void setupVideo(final AbstractFile file, final Dimension dims) {
188  reset();
189  infoLabel.setText("");
190  currentFile = file;
191  final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC);
192  if (deleted) {
193  infoLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.setupVideo.infoLabel.text"));
194  videoPanel.removeAll();
195  pauseButton.setEnabled(false);
196  progressSlider.setEnabled(false);
197  return;
198  }
199 
200  java.io.File ioFile;
201  try {
202  ioFile = VideoUtils.getVideoFileInTempDir(file);
203  } catch (NoCurrentCaseException ex) {
204  logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
205  infoLabel.setText(Bundle.GstVideoPanel_noOpenCase_errMsg());
206  pauseButton.setEnabled(false);
207  progressSlider.setEnabled(false);
208 
209  return;
210  }
211 
212  String path = "";
213  try {
214  path = file.getUniquePath();
215  } catch (TskCoreException ex) {
216  logger.log(Level.SEVERE, "Cannot get unique path of video file"); //NON-NLS
217  }
218  infoLabel.setText(path);
219  infoLabel.setToolTipText(path);
220  pauseButton.setEnabled(true);
221  progressSlider.setEnabled(true);
222 
223 
224  gstVideoComponent = new VideoComponent();
225  synchronized (playbinLock) {
226  if (gstPlaybin2 != null) {
227  gstPlaybin2.dispose();
228  }
229  gstPlaybin2 = new PlayBin2("VideoPlayer"); //NON-NLS
230  gstPlaybin2.setVideoSink(gstVideoComponent.getElement());
231 
232  videoPanel.removeAll();
233 
234  videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
235  videoPanel.add(gstVideoComponent);
236 
237  videoPanel.setVisible(true);
238 
239  gstPlaybin2.setInputFile(ioFile);
240 
241  if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) {
242  logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.READY) failed."); //NON-NLS
243  infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
244  }
245  }
246 
247  }
248 
249  @Override
250  void reset() {
251 
252  // reset the progress label text on the event dispatch thread
253  SwingUtilities.invokeLater(() -> {
254  progressLabel.setText("");
255  });
256 
257  if (!isInited()) {
258  return;
259  }
260 
261  synchronized (playbinLock) {
262  if (gstPlaybin2 != null) {
263  if (gstPlaybin2.isPlaying()) {
264  if (gstPlaybin2.stop() == StateChangeReturn.FAILURE) {
265  logger.log(Level.WARNING, "Attempt to call PlayBin2.stop() failed."); //NON-NLS
266  infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
267  return;
268  }
269  }
270  if (gstPlaybin2.setState(State.NULL) == StateChangeReturn.FAILURE) {
271  logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.NULL) failed."); //NON-NLS
272  infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
273  return;
274  }
275  if (gstPlaybin2.getState().equals(State.NULL)) {
276  gstPlaybin2.dispose();
277  }
278  gstPlaybin2 = null;
279  }
280  gstVideoComponent = null;
281  }
282 
283  // get rid of any existing videoProgressWorker thread
284  if (videoProgressWorker != null) {
285  videoProgressWorker.cancel(true);
286  videoProgressWorker = null;
287  }
288 
289  currentFile = null;
290  }
291 
302  @Override
303  public List<VideoFrame> captureFrames(java.io.File file, int numFrames) throws Exception {
304 
305  List<VideoFrame> frames = new ArrayList<>();
306 
307  Object lock = new Object();
308  FrameCaptureRGBListener rgbListener = new FrameCaptureRGBListener(lock);
309 
310  if (!isInited()) {
311  return frames;
312  }
313 
314  // throw exception if this file is known to be problematic
315  if (badVideoFiles.contains(file.getName())) {
316  throw new Exception(
317  NbBundle.getMessage(this.getClass(), "GstVideoPanel.exception.problemFile.msg", file.getName()));
318  }
319 
320  // set up a PlayBin2 object
321  RGBDataSink videoSink = new RGBDataSink("rgb", rgbListener); //NON-NLS
322  PlayBin2 playbin = new PlayBin2("VideoFrameCapture"); //NON-NLS
323  playbin.setInputFile(file);
324  playbin.setVideoSink(videoSink);
325 
326  // this is necessary to get a valid duration value
327  StateChangeReturn ret = playbin.play();
328  if (ret == StateChangeReturn.FAILURE) {
329  // add this file to the set of known bad ones
330  badVideoFiles.add(file.getName());
331  throw new Exception(NbBundle.getMessage(this.getClass(), "GstVideoPanel.exception.problemPlay.msg"));
332  }
333  ret = playbin.pause();
334  if (ret == StateChangeReturn.FAILURE) {
335  // add this file to the set of known bad ones
336  badVideoFiles.add(file.getName());
337  throw new Exception(NbBundle.getMessage(this.getClass(), "GstVideoPanel.exception.problemPause.msg"));
338  }
339  playbin.getState();
340 
341  // get the duration of the video
342  TimeUnit unit = TimeUnit.MILLISECONDS;
343  long myDurationMillis = playbin.queryDuration(unit);
344  if (myDurationMillis <= 0) {
345  return frames;
346  }
347 
348  // calculate the number of frames to capture
349  int numFramesToGet = numFrames;
350  long frameInterval = myDurationMillis / numFrames;
351  if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) {
352  numFramesToGet = 1;
353  }
354 
355  // for each timeStamp, grap a frame
356  for (int i = 0; i < numFramesToGet; ++i) {
357  long timeStamp = i * frameInterval;
358 
359  ret = playbin.pause();
360  if (ret == StateChangeReturn.FAILURE) {
361  // add this file to the set of known bad ones
362  badVideoFiles.add(file.getName());
363  throw new Exception(
364  NbBundle.getMessage(this.getClass(), "GstVideoPanel.exception.problemPauseCaptFrame.msg"));
365  }
366  playbin.getState();
367 
368  if (!playbin.seek(timeStamp, unit)) {
369  logger.log(Level.INFO, "There was a problem seeking to {0} {1}", new Object[]{timeStamp, unit.name().toLowerCase()}); //NON-NLS
370  }
371 
372  ret = playbin.play();
373  if (ret == StateChangeReturn.FAILURE) {
374  // add this file to the set of known bad ones
375  badVideoFiles.add(file.getName());
376  throw new Exception(
377  NbBundle.getMessage(this.getClass(), "GstVideoPanel.exception.problemPlayCaptFrame.msg"));
378  }
379 
380  // wait for FrameCaptureRGBListener to finish
381  synchronized (lock) {
382  try {
383  lock.wait(FRAME_CAPTURE_TIMEOUT_MILLIS);
384  } catch (InterruptedException e) {
385  logger.log(Level.INFO, "InterruptedException occurred while waiting for frame capture.", e); //NON-NLS
386  }
387  }
388  Image image = rgbListener.getImage();
389 
390  ret = playbin.stop();
391  if (ret == StateChangeReturn.FAILURE) {
392  // add this file to the set of known bad ones
393  badVideoFiles.add(file.getName());
394  throw new Exception(
395  NbBundle.getMessage(this.getClass(), "GstVideoPanel.exception.problemStopCaptFrame.msg"));
396  }
397 
398  if (image == null) {
399  logger.log(Level.WARNING, "There was a problem while trying to capture a frame from file {0}", file.getName()); //NON-NLS
400  badVideoFiles.add(file.getName());
401  break;
402  }
403 
404  frames.add(new VideoFrame(image, timeStamp));
405  }
406 
407  return frames;
408  }
409 
410  private class FrameCaptureRGBListener implements RGBDataSink.Listener {
411 
412  public FrameCaptureRGBListener(Object waiter) {
413  this.waiter = waiter;
414  }
415 
416  private BufferedImage bi;
417  private final Object waiter;
418 
419  @Override
420  public void rgbFrame(boolean bln, int w, int h, IntBuffer rgbPixels) {
421  synchronized (waiter) {
422  bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
423  bi.setRGB(0, 0, w, h, rgbPixels.array(), 0, w);
424  waiter.notify();
425  }
426  }
427 
428  public Image getImage() {
429  synchronized (waiter) {
430  Image image = bi;
431  bi = null;
432  return image;
433  }
434  }
435 
436  }
437 
443  @SuppressWarnings("unchecked")
444  // <editor-fold defaultstate="collapsed" desc="Generated Code">
445  private void initComponents() {
446 
447  videoPanel = new javax.swing.JPanel();
448  controlPanel = new javax.swing.JPanel();
449  pauseButton = new javax.swing.JButton();
450  progressSlider = new javax.swing.JSlider();
451  progressLabel = new javax.swing.JLabel();
452  infoLabel = new javax.swing.JLabel();
453 
454  javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel);
455  videoPanel.setLayout(videoPanelLayout);
456  videoPanelLayout.setHorizontalGroup(
457  videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
458  .addGap(0, 0, Short.MAX_VALUE)
459  );
460  videoPanelLayout.setVerticalGroup(
461  videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
462  .addGap(0, 231, Short.MAX_VALUE)
463  );
464 
465  org.openide.awt.Mnemonics.setLocalizedText(pauseButton, org.openide.util.NbBundle.getMessage(GstVideoPanel.class, "MediaViewVideoPanel.pauseButton.text")); // NOI18N
466  pauseButton.addActionListener(new java.awt.event.ActionListener() {
467  public void actionPerformed(java.awt.event.ActionEvent evt) {
468  pauseButtonActionPerformed(evt);
469  }
470  });
471 
472  org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(GstVideoPanel.class, "MediaViewVideoPanel.progressLabel.text")); // NOI18N
473 
474  org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(GstVideoPanel.class, "MediaViewVideoPanel.infoLabel.text")); // NOI18N
475 
476  javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel);
477  controlPanel.setLayout(controlPanelLayout);
478  controlPanelLayout.setHorizontalGroup(
479  controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
480  .addGroup(controlPanelLayout.createSequentialGroup()
481  .addContainerGap()
482  .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
483  .addGroup(controlPanelLayout.createSequentialGroup()
484  .addGap(6, 6, 6)
485  .addComponent(infoLabel)
486  .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
487  .addGroup(controlPanelLayout.createSequentialGroup()
488  .addComponent(pauseButton)
489  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
490  .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 265, Short.MAX_VALUE)
491  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
492  .addComponent(progressLabel)
493  .addContainerGap())))
494  );
495  controlPanelLayout.setVerticalGroup(
496  controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
497  .addGroup(controlPanelLayout.createSequentialGroup()
498  .addContainerGap()
499  .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
500  .addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
501  .addComponent(pauseButton)
502  .addComponent(progressLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 29, javax.swing.GroupLayout.PREFERRED_SIZE))
503  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
504  .addComponent(infoLabel)
505  .addContainerGap())
506  );
507 
508  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
509  this.setLayout(layout);
510  layout.setHorizontalGroup(
511  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
512  .addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
513  .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
514  );
515  layout.setVerticalGroup(
516  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
517  .addGroup(layout.createSequentialGroup()
518  .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
519  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
520  .addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
521  );
522  }// </editor-fold>
523 
524  private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed
525  synchronized (playbinLock) {
526  State state = gstPlaybin2.getState();
527  if (state.equals(State.PLAYING)) {
528  if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
529  logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed."); //NON-NLS
530  infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
531  return;
532  }
533  pauseButton.setText("►");
534  // Is this call necessary considering we just called gstPlaybin2.pause()?
535  if (gstPlaybin2.setState(State.PAUSED) == StateChangeReturn.FAILURE) {
536  logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.PAUSED) failed."); //NON-NLS
537  infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
538  return;
539  }
540  } else if (state.equals(State.PAUSED)) {
541  if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
542  logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed."); //NON-NLS
543  infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
544  return;
545  }
546  pauseButton.setText("||");
547  // Is this call necessary considering we just called gstPlaybin2.play()?
548  if (gstPlaybin2.setState(State.PLAYING) == StateChangeReturn.FAILURE) {
549  logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.PLAYING) failed."); //NON-NLS
550  infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
551  return;
552  }
553  } else if (state.equals(State.READY)) {
554  final File tempVideoFile;
555  try {
556  tempVideoFile = VideoUtils.getVideoFileInTempDir(currentFile);
557  } catch (NoCurrentCaseException ex) {
558  logger.log(Level.WARNING, "Exception while getting open case."); //NON-NLS
559  infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
560  return;
561  }
562 
563  new ExtractMedia(currentFile, tempVideoFile).execute();
564 
565  }
566  }
567  }//GEN-LAST:event_pauseButtonActionPerformed
568 
569  // Variables declaration - do not modify//GEN-BEGIN:variables
570  private javax.swing.JPanel controlPanel;
571  private javax.swing.JLabel infoLabel;
572  private javax.swing.JButton pauseButton;
573  private javax.swing.JLabel progressLabel;
574  private javax.swing.JSlider progressSlider;
575  private javax.swing.JPanel videoPanel;
576  // End of variables declaration//GEN-END:variables
577 
578  private class VideoProgressWorker extends SwingWorker<Object, Object> {
579 
580  private final String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; //NON-NLS
581  private long millisElapsed = 0;
582  private final long INTER_FRAME_PERIOD_MS = 20;
583  private final long END_TIME_MARGIN_MS = 50;
584 
585  private boolean isPlayBinReady() {
586  synchronized (playbinLock) {
587  return gstPlaybin2 != null && !gstPlaybin2.getState().equals(State.NULL);
588  }
589  }
590 
591  private void resetVideo() throws Exception {
592  synchronized (playbinLock) {
593  if (gstPlaybin2 != null) {
594  if (gstPlaybin2.stop() == StateChangeReturn.FAILURE) {
595  logger.log(Level.WARNING, "Attempt to call PlayBin2.stop() failed."); //NON-NLS
596  infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
597  }
598  // ready to be played again
599  if (gstPlaybin2.setState(State.READY) == StateChangeReturn.FAILURE) {
600  logger.log(Level.WARNING, "Attempt to call PlayBin2.setState(State.READY) failed."); //NON-NLS
601  infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
602  }
603  gstPlaybin2.getState(); //NEW
604  }
605  }
606  pauseButton.setText("►");
607  progressSlider.setValue(0);
608 
609  String durationStr = String.format(durationFormat, 0, 0, 0,
610  totalHours, totalMinutes, totalSeconds);
611  progressLabel.setText(durationStr);
612  }
613 
620  private boolean hasNotEnded() {
621  return (durationMillis - millisElapsed) > END_TIME_MARGIN_MS;
622  }
623 
624  @Override
625  protected Object doInBackground() throws Exception {
626 
627  // enable the slider
628  progressSlider.setEnabled(true);
629 
630  ClockTime pos;
631  while (hasNotEnded() && isPlayBinReady() && !isCancelled()) {
632 
633  synchronized (playbinLock) {
634  pos = gstPlaybin2.queryPosition();
635  }
636  millisElapsed = pos.toMillis();
637 
638  // pick out the elapsed hours, minutes, seconds
639  long secondsElapsed = millisElapsed / 1000;
640  int elapsedHours = (int) secondsElapsed / 3600;
641  secondsElapsed -= elapsedHours * 3600;
642  int elapsedMinutes = (int) secondsElapsed / 60;
643  secondsElapsed -= elapsedMinutes * 60;
644  int elapsedSeconds = (int) secondsElapsed;
645 
646  String durationStr = String.format(durationFormat,
647  elapsedHours, elapsedMinutes, elapsedSeconds,
648  totalHours, totalMinutes, totalSeconds);
649 
650  progressLabel.setText(durationStr);
651  autoTracking = true;
652  progressSlider.setValue((int) millisElapsed);
653  autoTracking = false;
654 
655  try {
656  Thread.sleep(INTER_FRAME_PERIOD_MS);
657  } catch (InterruptedException ex) {
658  break;
659  }
660  }
661 
662  // disable the slider
663  progressSlider.setEnabled(false);
664 
665  resetVideo();
666 
667  return null;
668  }
669 
670  @Override
671  protected void done() {
672  // see if any exceptions were thrown
673  try {
674  get();
675  } catch (InterruptedException | ExecutionException ex) {
676  logger.log(Level.WARNING, "Error updating video progress: {0}", ex.getMessage()); //NON-NLS
677  infoLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.infoLabel.updateErr",
678  ex.getMessage()));
679  } // catch and ignore if we were cancelled
680  catch (java.util.concurrent.CancellationException ex) {
681  }
682  }
683  } //end class progress worker
684 
685  /*
686  * Thread that extracts and plays a file
687  */
688  private class ExtractMedia extends SwingWorker<Long, Void> {
689 
690  private ProgressHandle progress;
691  private final AbstractFile sourceFile;
692  private final java.io.File tempFile;
693 
694  ExtractMedia(AbstractFile sFile, java.io.File jFile) {
695  this.sourceFile = sFile;
696  this.tempFile = jFile;
697  }
698 
699  @Override
700  protected Long doInBackground() throws Exception {
701  if (tempFile.exists() == false || tempFile.length() < sourceFile.getSize()) {
702  progress = ProgressHandle.createHandle(NbBundle.getMessage(GstVideoPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sourceFile.getName()), () -> ExtractMedia.this.cancel(true));
703  progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering"));
704  progress.start(100);
705  try {
706  Files.createParentDirs(tempFile);
707  return ContentUtils.writeToFile(sourceFile, tempFile, progress, this, true);
708  } catch (IOException ex) {
709  logger.log(Level.WARNING, "Error buffering file", ex); //NON-NLS
710  return 0L;
711  }
712  }
713  return 0L;
714  }
715 
716  /*
717  * clean up or start the worker threads
718  */
719  @Override
720  protected void done() {
721  try {
722  super.get(); //block and get all exceptions thrown while doInBackground()
723  } catch (CancellationException ex) {
724  logger.log(Level.INFO, "Media buffering was canceled."); //NON-NLS
725  } catch (InterruptedException ex) {
726  logger.log(Level.INFO, "Media buffering was interrupted."); //NON-NLS
727  } catch (Exception ex) {
728  logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS
729  } finally {
730  if (progress != null) {
731  progress.finish();
732  }
733  if (!this.isCancelled()) {
734  playMedia();
735  }
736  }
737  }
738 
739  void playMedia() {
740  if (tempFile == null || !tempFile.exists()) {
741  progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progressLabel.bufferingErr"));
742  return;
743  }
744  ClockTime dur;
745  synchronized (playbinLock) {
746  // must play, then pause and get state to get duration.
747  if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
748  logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed."); //NON-NLS
749  infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
750  return;
751  }
752  if (gstPlaybin2.pause() == StateChangeReturn.FAILURE) {
753  logger.log(Level.WARNING, "Attempt to call PlayBin2.pause() failed."); //NON-NLS
754  infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
755  return;
756  }
757  gstPlaybin2.getState();
758  dur = gstPlaybin2.queryDuration();
759  }
760  durationMillis = dur.toMillis();
761 
762  // pick out the total hours, minutes, seconds
763  long durationSeconds = (int) durationMillis / 1000;
764  totalHours = (int) durationSeconds / 3600;
765  durationSeconds -= totalHours * 3600;
766  totalMinutes = (int) durationSeconds / 60;
767  durationSeconds -= totalMinutes * 60;
768  totalSeconds = (int) durationSeconds;
769 
770  SwingUtilities.invokeLater(() -> {
771  progressSlider.setMaximum((int) durationMillis);
772  progressSlider.setMinimum(0);
773 
774  synchronized (playbinLock) {
775  if (gstPlaybin2.play() == StateChangeReturn.FAILURE) {
776  logger.log(Level.WARNING, "Attempt to call PlayBin2.play() failed."); //NON-NLS
777  infoLabel.setText(MEDIA_PLAYER_ERROR_STRING);
778  }
779  }
780  pauseButton.setText("||");
781  videoProgressWorker = new VideoProgressWorker();
782  videoProgressWorker.execute();
783  });
784  }
785  }
786 
787  @Override
788  public String[] getExtensions() {
789  return EXTENSIONS.clone();
790  }
791 
792  @Override
793  public List<String> getMimeTypes() {
794  return MIMETYPES;
795  }
796 
797 }
void rgbFrame(boolean bln, int w, int h, IntBuffer rgbPixels)
static File getVideoFileInTempDir(AbstractFile file)
static< T > long writeToFile(Content content, java.io.File outputFile, ProgressHandle progress, Future< T > worker, boolean source)
static void error(String title, String message)
void pauseButtonActionPerformed(java.awt.event.ActionEvent evt)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
List< VideoFrame > captureFrames(java.io.File file, int numFrames)

Copyright © 2012-2018 Basis Technology. Generated on: Tue Dec 18 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.