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

Copyright © 2012-2016 Basis Technology. Generated on: Mon May 7 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.