Autopsy  4.17.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
MediaPlayerPanel.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2013-2020 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.Color;
23 import java.awt.Dimension;
24 import java.awt.Graphics;
25 import java.awt.Graphics2D;
26 import java.awt.Point;
27 import java.awt.Rectangle;
28 import java.awt.RenderingHints;
29 import java.awt.event.ActionEvent;
30 import java.awt.event.ActionListener;
31 import java.awt.event.MouseEvent;
32 import java.awt.event.MouseListener;
33 import java.io.File;
34 import java.io.IOException;
35 import java.util.Arrays;
36 import java.util.EnumSet;
37 import java.util.List;
38 import java.util.SortedSet;
39 import java.util.TreeSet;
40 import java.util.concurrent.CancellationException;
41 import java.util.concurrent.ExecutionException;
42 import java.util.concurrent.Semaphore;
43 import java.util.concurrent.TimeUnit;
44 import java.util.logging.Level;
45 import javax.swing.BoxLayout;
46 import javax.swing.JPanel;
47 import javax.swing.SwingWorker;
48 import javax.swing.Timer;
49 import javax.swing.event.ChangeEvent;
50 import org.freedesktop.gstreamer.Bus;
51 import org.freedesktop.gstreamer.Gst;
52 import org.freedesktop.gstreamer.GstObject;
53 import org.freedesktop.gstreamer.State;
54 import org.freedesktop.gstreamer.elements.PlayBin;
55 import org.netbeans.api.progress.ProgressHandle;
56 import org.openide.util.NbBundle;
62 import org.sleuthkit.datamodel.AbstractFile;
63 import org.sleuthkit.datamodel.TskData;
64 import javafx.embed.swing.JFXPanel;
65 import javax.swing.ImageIcon;
66 import javax.swing.JComponent;
67 import javax.swing.JSlider;
68 import javax.swing.SwingUtilities;
69 import javax.swing.event.ChangeListener;
70 import javax.swing.plaf.basic.BasicSliderUI;
71 import javax.swing.plaf.basic.BasicSliderUI.TrackListener;
72 import org.freedesktop.gstreamer.ClockTime;
73 import org.freedesktop.gstreamer.Format;
74 import org.freedesktop.gstreamer.GstException;
75 import org.freedesktop.gstreamer.event.SeekFlags;
76 import org.freedesktop.gstreamer.event.SeekType;
80 
85 @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
86 public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaViewPanel {
87 
88  //Enumerate the accepted file extensions and mimetypes
89  private static final String[] FILE_EXTENSIONS = new String[]{
90  ".3g2",
91  ".3gp",
92  ".3gpp",
93  ".aac",
94  ".aif",
95  ".aiff",
96  ".amr",
97  ".asf",
98  ".au",
99  ".avi",
100  ".flac",
101  ".flv",
102  ".m4a",
103  ".m4v",
104  ".mka",
105  ".mkv",
106  ".mov",
107  ".mp2",
108  ".mp3",
109  ".mp4",
110  ".mpeg",
111  ".mpg",
112  ".mxf",
113  ".ogg",
114  ".wav",
115  ".webm",
116  ".wma",
117  ".wmv"}; //NON-NLS
118  private static final List<String> MIME_TYPES = Arrays.asList(
119  "video/3gpp",
120  "video/3gpp2",
121  "audio/aiff",
122  "audio/amr-wb",
123  "audio/basic",
124  "audio/mp4",
125  "video/mp4",
126  "audio/mpeg",
127  "video/mpeg",
128  "audio/mpeg3",
129  "application/mxf",
130  "application/ogg",
131  "video/quicktime",
132  "audio/vorbis",
133  "audio/vnd.wave",
134  "video/webm",
135  "video/x-3ivx",
136  "audio/x-aac",
137  "audio/x-adpcm",
138  "audio/x-alaw",
139  "audio/x-cinepak",
140  "video/x-divx",
141  "audio/x-dv",
142  "video/x-dv",
143  "video/x-ffv",
144  "audio/x-flac",
145  "video/x-flv",
146  "audio/x-gsm",
147  "video/x-h263",
148  "video/x-h264",
149  "video/x-huffyuv",
150  "video/x-indeo",
151  "video/x-intel-h263",
152  "audio/x-ircam",
153  "video/x-jpeg",
154  "audio/x-m4a",
155  "video/x-m4v",
156  "audio/x-mace",
157  "audio/x-matroska",
158  "video/x-matroska",
159  "audio/x-mpeg",
160  "video/x-mpeg",
161  "audio/x-mpeg-3",
162  "video/x-ms-asf",
163  "audio/x-ms-wma",
164  "video/x-ms-wmv",
165  "video/x-msmpeg",
166  "video/x-msvideo",
167  "video/x-msvideocodec",
168  "audio/x-mulaw",
169  "audio/x-nist",
170  "audio/x-oggflac",
171  "audio/x-paris",
172  "audio/x-qdm2",
173  "audio/x-raw",
174  "video/x-raw",
175  "video/x-rle",
176  "audio/x-speex",
177  "video/x-svq",
178  "audio/x-svx",
179  "video/x-tarkin",
180  "video/x-theora",
181  "audio/x-voc",
182  "audio/x-vorbis",
183  "video/x-vp3",
184  "audio/x-w64",
185  "audio/x-wav",
186  "audio/x-wma",
187  "video/x-wmv",
188  "video/x-xvid"
189  ); //NON-NLS
190 
191  private static final Logger logger = Logger.getLogger(MediaPlayerPanel.class.getName());
192  private static final String MEDIA_PLAYER_ERROR_STRING = NbBundle.getMessage(MediaPlayerPanel.class,
193  "GstVideoPanel.cannotProcFile.err");
194 
195  //Video playback components
196  private volatile PlayBin gstPlayBin;
197  private JavaFxAppSink fxAppSink;
198  private Bus.ERROR errorListener;
199  private Bus.STATE_CHANGED stateChangeListener;
200  private Bus.EOS endOfStreamListener;
201 
202  //Update progress bar and time label during video playback
203  //Updating every 16 MS = 62.5 FPS.
204  private final Timer timer = new Timer(16, new VideoPanelUpdater());
205  private static final int PROGRESS_SLIDER_SIZE = 2000;
206  private static final int SKIP_IN_SECONDS = 30;
207 
208  private final ImageIcon playIcon = new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/contentviewers/images/Play-arrow-01.png"));
209  private final ImageIcon pauseIcon = new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/contentviewers/images/Pause-01.png"));
210 
212 
213  //Serialize setting the value of the Video progress slider.
214  //The slider is a shared resource between the VideoPanelUpdater
215  //and the TrackListener on the slider itself.
216  private final Semaphore sliderLock;
217 
218  private static volatile boolean IS_GST_ENABLED = true;
219 
223  public MediaPlayerPanel() throws GstException, UnsatisfiedLinkError {
224  initComponents();
225  customizeComponents();
226  //True for fairness. In other words,
227  //acquire() calls are processed in order of invocation.
228  sliderLock = new Semaphore(1, true);
229  }
230 
231  private void customizeComponents() {
232  enableComponents(false);
233  progressSlider.setMinimum(0);
234  progressSlider.setMaximum(PROGRESS_SLIDER_SIZE);
235  progressSlider.setValue(0);
236  //Manage the gstreamer video position when a user is dragging the slider in the panel.
237  progressSlider.addChangeListener(new ChangeListener() {
238  @Override
239  public void stateChanged(ChangeEvent e) {
240  if (progressSlider.getValueIsAdjusting() && gstPlayBin != null) {
241  long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
242  double relativePosition = progressSlider.getValue() * 1.0 / PROGRESS_SLIDER_SIZE;
243  long newStartTime = (long) (relativePosition * duration);
244  double playBackRate = getPlayBackRate();
245  gstPlayBin.seek(playBackRate,
246  Format.TIME,
247  //FLUSH - flushes the pipeline
248  //ACCURATE - video will seek exactly to the position requested
249  EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE),
250  //Set the start position to newTime
251  SeekType.SET, newStartTime,
252  //Do nothing for the end position
253  SeekType.NONE, -1);
254  //Keep constantly updating the time label so users have a sense of
255  //where the slider they are dragging is in relation to the video time
256  updateTimeLabel(newStartTime, duration);
257  }
258  }
259  });
260  //Manage the video while the user is performing actions on the track.
261  progressSlider.addMouseListener(new MouseListener() {
262  private State previousState = State.NULL;
263 
264  @Override
265  public void mousePressed(MouseEvent e) {
266  if (gstPlayBin != null) {
267  previousState = gstPlayBin.getState();
268  gstPlayBin.pause();
269  }
270  }
271 
272  @Override
273  public void mouseReleased(MouseEvent e) {
274  if (previousState.equals(State.PLAYING) && gstPlayBin != null) {
275  gstPlayBin.play();
276  }
277  previousState = State.NULL;
278  }
279 
280  @Override
281  public void mouseClicked(MouseEvent e) {
282  }
283 
284  @Override
285  public void mouseEntered(MouseEvent e) {
286  }
287 
288  @Override
289  public void mouseExited(MouseEvent e) {
290  }
291 
292  });
293  //Manage the audio level when the user is adjusting the volume slider
294  audioSlider.addChangeListener((ChangeEvent event) -> {
295  if (audioSlider.getValueIsAdjusting() && gstPlayBin != null) {
296  double audioPercent = (audioSlider.getValue() * 2.0) / 100.0;
297  gstPlayBin.setVolume(audioPercent);
298  }
299  });
300  errorListener = new Bus.ERROR() {
301  @Override
302  public void errorMessage(GstObject go, int i, String string) {
303  SwingUtilities.invokeLater(() -> {
304  enableComponents(false);
305  infoLabel.setText(String.format(
306  "<html><font color='red'>%s</font></html>",
307  MEDIA_PLAYER_ERROR_STRING));
308  });
309  timer.stop();
310  }
311  };
312  stateChangeListener = new Bus.STATE_CHANGED() {
313  @Override
314  public void stateChanged(GstObject go, State oldState, State currentState, State pendingState) {
315  if (State.PLAYING.equals(currentState)) {
316  SwingUtilities.invokeLater(() -> {
317  playButton.setIcon(pauseIcon);
318  });
319  } else {
320  SwingUtilities.invokeLater(() -> {
321  playButton.setIcon(playIcon);
322  });
323  }
324  }
325  };
326  endOfStreamListener = new Bus.EOS() {
327  @Override
328  public void endOfStream(GstObject go) {
329  if (gstPlayBin != null) {
330  gstPlayBin.seek(ClockTime.ZERO);
334  Gst.getExecutor().submit(() -> gstPlayBin.pause());
335  }
336  }
337  };
338  }
339 
346  @NbBundle.Messages({"GstVideoPanel.noOpenCase.errMsg=No open case available."})
347  void loadFile(final AbstractFile file) {
348  //Ensure everything is back in the initial state
349  infoLabel.setText("");
350  if (file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC)) {
351  infoLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.setupVideo.infoLabel.text"));
352  return;
353  }
354 
355  try {
356  //Pushing off initialization to the background
357  extractMediaWorker = new ExtractMedia(file, VideoUtils.getVideoFileInTempDir(file));
358  extractMediaWorker.execute();
359  } catch (NoCurrentCaseException ex) {
360  logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
361  infoLabel.setText(String.format("<html><font color='red'>%s</font></html>", Bundle.GstVideoPanel_noOpenCase_errMsg()));
362  enableComponents(false);
363  }
364  }
365 
370  @NbBundle.Messages({
371  "MediaPlayerPanel.noSupport=File not supported."
372  })
373  void resetComponents() {
374  progressLabel.setText(String.format("%s/%s", Bundle.MediaPlayerPanel_unknownTime(),
375  Bundle.MediaPlayerPanel_unknownTime()));
376  infoLabel.setText(Bundle.MediaPlayerPanel_noSupport());
377  progressSlider.setValue(0);
378  }
379 
383  void reset() {
384  if (extractMediaWorker != null) {
385  extractMediaWorker.cancel(true);
386  }
387  timer.stop();
388  if (gstPlayBin != null) {
389  Gst.getExecutor().submit(() -> {
390  gstPlayBin.stop();
391  gstPlayBin.getBus().disconnect(endOfStreamListener);
392  gstPlayBin.getBus().disconnect(stateChangeListener);
393  gstPlayBin.getBus().disconnect(errorListener);
394  gstPlayBin.getBus().dispose();
395  gstPlayBin.dispose();
396  fxAppSink.clear();
397  gstPlayBin = null;
398  });
399  }
400  videoPanel.removeAll();
401  resetComponents();
402  enableComponents(false);
403  }
404 
405  private void enableComponents(boolean isEnabled) {
406  playButton.setEnabled(isEnabled);
407  progressSlider.setEnabled(isEnabled);
408  videoPanel.setEnabled(isEnabled);
409  audioSlider.setEnabled(isEnabled);
410  rewindButton.setEnabled(isEnabled);
411  fastForwardButton.setEnabled(isEnabled);
412  playBackSpeedComboBox.setEnabled(isEnabled);
413  }
414 
415  @Override
416  public List<String> getSupportedExtensions() {
417  return Arrays.asList(FILE_EXTENSIONS.clone());
418  }
419 
420  @Override
421  public List<String> getSupportedMimeTypes() {
422  return MIME_TYPES;
423  }
424 
425  @Override
426  public boolean isSupported(AbstractFile file) {
427  if (!IS_GST_ENABLED) {
428  return false;
429  }
430 
431  String extension = file.getNameExtension();
448  if (getSupportedExtensions().contains("." + extension)) {
449  SortedSet<String> mimeTypes = new TreeSet<>(getSupportedMimeTypes());
450  try {
451  String mimeType = new FileTypeDetector().getMIMEType(file);
452  return mimeTypes.contains(mimeType);
454  logger.log(Level.WARNING, "Failed to look up mimetype for " + file.getName() + " using FileTypeDetector. Fallingback on AbstractFile.isMimeType", ex);
455  if (!mimeTypes.isEmpty() && file.isMimeType(mimeTypes) == AbstractFile.MimeMatchEnum.TRUE) {
456  return true;
457  }
458  }
459 
460  return getSupportedExtensions().contains("." + extension);
461  }
462  return false;
463  }
464 
472  private void updateTimeLabel(long start, long total) {
473  progressLabel.setText(formatTime(start) + "/" + formatTime(total));
474  }
475 
481  private double getPlayBackRate() {
482  int selectIndex = playBackSpeedComboBox.getSelectedIndex();
483  String selectText = playBackSpeedComboBox.getItemAt(selectIndex);
484  return Double.valueOf(selectText.substring(0, selectText.length() - 1));
485  }
486 
490  @NbBundle.Messages({
491  "MediaPlayerPanel.unknownTime=Unknown",
492  "MediaPlayerPanel.timeFormat=%02d:%02d:%02d"
493  })
494  private String formatTime(long ns) {
495  if (ns == -1) {
496  return Bundle.MediaPlayerPanel_unknownTime();
497  }
498 
499  long seconds = TimeUnit.SECONDS.convert(ns, TimeUnit.NANOSECONDS);
500  long hours = TimeUnit.HOURS.convert(seconds, TimeUnit.SECONDS);
501  seconds -= TimeUnit.SECONDS.convert(hours, TimeUnit.HOURS);
502  long minutes = TimeUnit.MINUTES.convert(seconds, TimeUnit.SECONDS);
503  seconds -= TimeUnit.SECONDS.convert(minutes, TimeUnit.MINUTES);
504 
505  return String.format(Bundle.MediaPlayerPanel_timeFormat(), hours, minutes, seconds);
506  }
507 
512  private class ExtractMedia extends SwingWorker<Void, Void> {
513 
514  private ProgressHandle progress;
515  private final AbstractFile sourceFile;
516  private final java.io.File tempFile;
517 
518  ExtractMedia(AbstractFile sFile, File jFile) {
519  this.sourceFile = sFile;
520  this.tempFile = jFile;
521  }
522 
523  @Override
524  protected Void doInBackground() throws Exception {
525  if (!tempFile.exists() || tempFile.length() < sourceFile.getSize()) {
526  progress = ProgressHandle.createHandle(NbBundle.getMessage(MediaPlayerPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sourceFile.getName()), () -> this.cancel(true));
527 
528  SwingUtilities.invokeLater(() -> {
529  progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering"));
530  });
531 
532  progress.start(100);
533  try {
534  Files.createParentDirs(tempFile);
535  ContentUtils.writeToFile(sourceFile, tempFile, progress, this, true);
536  } catch (IOException ex) {
537  logger.log(Level.WARNING, "Error creating parent directory for copying video/audio in temp directory", ex); //NON-NLS
538  } finally {
539  progress.finish();
540  }
541  }
542  return null;
543  }
544 
545  /*
546  * Initialize the playback components if the extraction was successful.
547  */
548  @NbBundle.Messages({
549  "MediaPlayerPanel.playbackDisabled=A problem was encountered with"
550  + " the video and audio playback service. Video and audio "
551  + "playback will be disabled for the remainder of the session."
552  })
553  @Override
554  protected void done() {
555  try {
556  super.get();
557 
558  if (this.isCancelled()) {
559  return;
560  }
561 
562  GstStatus loadStatus = GstLoader.tryLoad();
563  if (loadStatus == GstStatus.FAILURE) {
564  MessageNotifyUtil.Message.error(Bundle.MediaPlayerPanel_playbackDisabled());
565 
566  // This will disable the panel for future use.
567  IS_GST_ENABLED = false;
568  return;
569  }
570 
571  Gst.getExecutor().submit(() -> {
572  //Video is ready for playback. Create new components
573  gstPlayBin = new PlayBin("VideoPlayer", tempFile.toURI());
574  //Configure event handling
575  Bus playBinBus = gstPlayBin.getBus();
576  playBinBus.connect(endOfStreamListener);
577  playBinBus.connect(stateChangeListener);
578  playBinBus.connect(errorListener);
579 
580  if (this.isCancelled()) {
581  return;
582  }
583 
584  JFXPanel fxPanel = new JFXPanel();
585  videoPanel.removeAll();
586  videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
587  videoPanel.add(fxPanel);
588  fxAppSink = new JavaFxAppSink("JavaFxAppSink", fxPanel);
589  if (gstPlayBin != null) {
590  gstPlayBin.setVideoSink(fxAppSink);
591  }
592  if (this.isCancelled()) {
593  return;
594  }
595  if (gstPlayBin != null) {
596  gstPlayBin.setVolume((audioSlider.getValue() * 2.0) / 100.0);
597  gstPlayBin.pause();
598  }
599 
600  timer.start();
601  SwingUtilities.invokeLater(() -> {
602  enableComponents(true);
603  });
604  });
605  } catch (CancellationException ex) {
606  logger.log(Level.INFO, "Media buffering was canceled."); //NON-NLS
607  } catch (InterruptedException ex) {
608  logger.log(Level.INFO, "Media buffering was interrupted."); //NON-NLS
609  } catch (ExecutionException ex) {
610  logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS
611  }
612  }
613  }
614 
618  private class VideoPanelUpdater implements ActionListener {
619 
620  @Override
621  public void actionPerformed(ActionEvent e) {
622  if (!progressSlider.getValueIsAdjusting() && gstPlayBin != null) {
623  Gst.getExecutor().submit(() -> {
624  try {
625  sliderLock.acquireUninterruptibly();
626  long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
627  long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
634  if (duration >= 0 && position >= 0) {
635  double relativePosition = (double) position / duration;
636  progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE));
637  }
638 
639  SwingUtilities.invokeLater(() -> {
640  updateTimeLabel(position, duration);
641  });
642  } finally {
643  sliderLock.release();
644  }
645  });
646  }
647  }
648  }
649 
653  private class CircularJSliderUI extends BasicSliderUI {
654 
655  private final Dimension thumbDimension;
656  private final Color thumbColor;
657  private final Color trackUnseen;
658  private final Color trackSeen;
659 
668  public CircularJSliderUI(JSlider slider, Dimension thumbDimension) {
669  super(slider);
670  this.thumbDimension = thumbDimension;
671 
672  //Configure track and thumb colors.
673  Color lightBlue = new Color(0, 130, 255);
674  thumbColor = lightBlue;
675  trackSeen = lightBlue;
676  trackUnseen = Color.LIGHT_GRAY;
677  }
678 
679  @Override
680  protected Dimension getThumbSize() {
681  return new Dimension(thumbDimension);
682  }
683 
688  @Override
689  public void paintThumb(Graphics graphic) {
690  Rectangle thumb = this.thumbRect;
691 
692  Color original = graphic.getColor();
693 
694  //Change the thumb view from the rectangle
695  //controller to an oval.
696  graphic.setColor(thumbColor);
697  graphic.fillOval(thumb.x, thumb.y, thumbDimension.width, thumbDimension.height);
698 
699  //Preserve the graphics original color
700  graphic.setColor(original);
701  }
702 
703  @Override
704  public void paintTrack(Graphics graphic) {
705  //This rectangle is the bounding box for the progress bar
706  //portion of the slider. The track is painted in the middle
707  //of this rectangle and the thumb laid overtop.
708  Rectangle track = this.trackRect;
709 
710  //Get the location of the thumb, this point splits the
711  //progress bar into 2 line segments, seen and unseen.
712  Rectangle thumb = this.thumbRect;
713  int thumbX = thumb.x;
714  int thumbY = thumb.y;
715 
716  Color original = graphic.getColor();
717 
718  //Paint the seen side
719  graphic.setColor(trackSeen);
720  graphic.drawLine(track.x, track.y + track.height / 2,
721  thumbX, thumbY + track.height / 2);
722 
723  //Paint the unseen side
724  graphic.setColor(trackUnseen);
725  graphic.drawLine(thumbX, thumbY + track.height / 2,
726  track.x + track.width, track.y + track.height / 2);
727 
728  //Preserve the graphics color.
729  graphic.setColor(original);
730  }
731 
732  @Override
733  protected TrackListener createTrackListener(JSlider slider) {
742  return new TrackListener() {
743  @Override
744  public void mousePressed(MouseEvent e) {
745  if (!slider.isEnabled() || !SwingUtilities.isLeftMouseButton(e)) {
746  return;
747  }
748  //Snap the thumb to position of the mouse
749  scrollDueToClickInTrack(0);
750 
751  //Handle the event as normal.
752  super.mousePressed(e);
753  }
754  };
755  }
756 
757  @Override
758  protected void scrollDueToClickInTrack(int direction) {
759  //Set the thumb position to the mouse press location, as opposed
760  //to the closest "block" which is the default behavior.
761  Point mousePosition = slider.getMousePosition();
762  if (mousePosition == null) {
763  return;
764  }
765  int value = this.valueForXPosition(mousePosition.x);
766 
767  //Lock the slider down, which is a shared resource.
768  //The VideoPanelUpdater keeps the
769  //slider in sync with the video position, so without
770  //proper locking our change could be overwritten.
771  sliderLock.acquireUninterruptibly();
772  slider.setValueIsAdjusting(true);
773  slider.setValue(value);
774  slider.setValueIsAdjusting(false);
775  sliderLock.release();
776  }
777 
781  @Override
782  public void update(Graphics graphic, JComponent component) {
783  if (graphic instanceof Graphics2D) {
784  Graphics2D graphic2 = (Graphics2D) graphic;
785  graphic2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
786  RenderingHints.VALUE_ANTIALIAS_ON);
787  }
788 
789  super.update(graphic, component);
790  }
791  }
792 
798  @SuppressWarnings("unchecked")
799  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
800  private void initComponents() {
801  java.awt.GridBagConstraints gridBagConstraints;
802 
803  videoPanel = new javax.swing.JPanel();
804  controlPanel = new javax.swing.JPanel();
805  progressSlider = new javax.swing.JSlider();
806  progressLabel = new javax.swing.JLabel();
807  buttonPanel = new javax.swing.JPanel();
808  playButton = new javax.swing.JButton();
809  fastForwardButton = new javax.swing.JButton();
810  rewindButton = new javax.swing.JButton();
811  VolumeIcon = new javax.swing.JLabel();
812  audioSlider = new javax.swing.JSlider();
813  infoLabel = new javax.swing.JLabel();
814  playBackPanel = new javax.swing.JPanel();
815  playBackSpeedComboBox = new javax.swing.JComboBox<>();
816  playBackSpeedLabel = new javax.swing.JLabel();
817 
818  javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel);
819  videoPanel.setLayout(videoPanelLayout);
820  videoPanelLayout.setHorizontalGroup(
821  videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
822  .addGap(0, 0, Short.MAX_VALUE)
823  );
824  videoPanelLayout.setVerticalGroup(
825  videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
826  .addGap(0, 117, Short.MAX_VALUE)
827  );
828 
829  progressSlider.setValue(0);
830  progressSlider.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
831  progressSlider.setDoubleBuffered(true);
832  progressSlider.setMinimumSize(new java.awt.Dimension(36, 21));
833  progressSlider.setPreferredSize(new java.awt.Dimension(200, 21));
834  progressSlider.setUI(new CircularJSliderUI(progressSlider, new Dimension(18,18)));
835 
836  org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.progressLabel.text")); // NOI18N
837 
838  buttonPanel.setLayout(new java.awt.GridBagLayout());
839 
840  playButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/contentviewers/images/Play-arrow-01.png"))); // NOI18N
841  org.openide.awt.Mnemonics.setLocalizedText(playButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.playButton.text")); // NOI18N
842  playButton.setMaximumSize(new java.awt.Dimension(53, 29));
843  playButton.setMinimumSize(new java.awt.Dimension(53, 29));
844  playButton.setPreferredSize(new java.awt.Dimension(49, 29));
845  playButton.addActionListener(new java.awt.event.ActionListener() {
846  public void actionPerformed(java.awt.event.ActionEvent evt) {
847  playButtonActionPerformed(evt);
848  }
849  });
850  gridBagConstraints = new java.awt.GridBagConstraints();
851  gridBagConstraints.gridx = 1;
852  gridBagConstraints.gridy = 0;
853  gridBagConstraints.ipadx = 21;
854  gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
855  gridBagConstraints.insets = new java.awt.Insets(5, 6, 0, 0);
856  buttonPanel.add(playButton, gridBagConstraints);
857 
858  fastForwardButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/contentviewers/images/Fast-forward-01.png"))); // NOI18N
859  org.openide.awt.Mnemonics.setLocalizedText(fastForwardButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.fastForwardButton.text")); // NOI18N
860  fastForwardButton.addActionListener(new java.awt.event.ActionListener() {
861  public void actionPerformed(java.awt.event.ActionEvent evt) {
862  fastForwardButtonActionPerformed(evt);
863  }
864  });
865  gridBagConstraints = new java.awt.GridBagConstraints();
866  gridBagConstraints.gridx = 2;
867  gridBagConstraints.gridy = 0;
868  gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
869  gridBagConstraints.insets = new java.awt.Insets(5, 6, 0, 0);
870  buttonPanel.add(fastForwardButton, gridBagConstraints);
871 
872  rewindButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/contentviewers/images/Fast-rewind-01.png"))); // NOI18N
873  org.openide.awt.Mnemonics.setLocalizedText(rewindButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.rewindButton.text")); // NOI18N
874  rewindButton.addActionListener(new java.awt.event.ActionListener() {
875  public void actionPerformed(java.awt.event.ActionEvent evt) {
876  rewindButtonActionPerformed(evt);
877  }
878  });
879  gridBagConstraints = new java.awt.GridBagConstraints();
880  gridBagConstraints.gridx = 0;
881  gridBagConstraints.gridy = 0;
882  gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
883  gridBagConstraints.insets = new java.awt.Insets(5, 0, 1, 0);
884  buttonPanel.add(rewindButton, gridBagConstraints);
885 
886  org.openide.awt.Mnemonics.setLocalizedText(VolumeIcon, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.VolumeIcon.text")); // NOI18N
887  VolumeIcon.setHorizontalTextPosition(javax.swing.SwingConstants.LEFT);
888  VolumeIcon.setMaximumSize(new java.awt.Dimension(34, 29));
889  VolumeIcon.setMinimumSize(new java.awt.Dimension(34, 29));
890  VolumeIcon.setPreferredSize(new java.awt.Dimension(34, 19));
891  gridBagConstraints = new java.awt.GridBagConstraints();
892  gridBagConstraints.gridx = 3;
893  gridBagConstraints.gridy = 0;
894  gridBagConstraints.ipadx = 8;
895  gridBagConstraints.ipady = 7;
896  gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
897  gridBagConstraints.insets = new java.awt.Insets(6, 14, 0, 0);
898  buttonPanel.add(VolumeIcon, gridBagConstraints);
899 
900  audioSlider.setMajorTickSpacing(10);
901  audioSlider.setMaximum(50);
902  audioSlider.setMinorTickSpacing(5);
903  audioSlider.setToolTipText(org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.audioSlider.toolTipText")); // NOI18N
904  audioSlider.setValue(25);
905  audioSlider.setMaximumSize(new java.awt.Dimension(32767, 19));
906  audioSlider.setMinimumSize(new java.awt.Dimension(200, 19));
907  audioSlider.setPreferredSize(new java.awt.Dimension(200, 30));
908  audioSlider.setRequestFocusEnabled(false);
909  audioSlider.setUI(new CircularJSliderUI(audioSlider, new Dimension(15,15)));
910  gridBagConstraints = new java.awt.GridBagConstraints();
911  gridBagConstraints.gridx = 4;
912  gridBagConstraints.gridy = 0;
913  gridBagConstraints.ipadx = -116;
914  gridBagConstraints.ipady = 7;
915  gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
916  gridBagConstraints.insets = new java.awt.Insets(3, 1, 0, 10);
917  buttonPanel.add(audioSlider, gridBagConstraints);
918 
919  infoLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
920  org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.infoLabel.text")); // NOI18N
921  infoLabel.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
922 
923  playBackSpeedComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "0.25x", "0.50x", "0.75x", "1x", "1.25x", "1.50x", "1.75x", "2x" }));
924  playBackSpeedComboBox.setSelectedIndex(3);
925  playBackSpeedComboBox.setMaximumSize(new java.awt.Dimension(53, 29));
926  playBackSpeedComboBox.setMinimumSize(new java.awt.Dimension(53, 29));
927  playBackSpeedComboBox.setPreferredSize(new java.awt.Dimension(53, 29));
928  playBackSpeedComboBox.setRequestFocusEnabled(false);
929  playBackSpeedComboBox.addActionListener(new java.awt.event.ActionListener() {
930  public void actionPerformed(java.awt.event.ActionEvent evt) {
931  playBackSpeedComboBoxActionPerformed(evt);
932  }
933  });
934 
935  org.openide.awt.Mnemonics.setLocalizedText(playBackSpeedLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.playBackSpeedLabel.text")); // NOI18N
936  playBackSpeedLabel.setMaximumSize(new java.awt.Dimension(34, 19));
937  playBackSpeedLabel.setMinimumSize(new java.awt.Dimension(34, 19));
938  playBackSpeedLabel.setPreferredSize(new java.awt.Dimension(34, 19));
939 
940  javax.swing.GroupLayout playBackPanelLayout = new javax.swing.GroupLayout(playBackPanel);
941  playBackPanel.setLayout(playBackPanelLayout);
942  playBackPanelLayout.setHorizontalGroup(
943  playBackPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
944  .addGroup(playBackPanelLayout.createSequentialGroup()
945  .addComponent(playBackSpeedLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 34, javax.swing.GroupLayout.PREFERRED_SIZE)
946  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
947  .addComponent(playBackSpeedComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
948  .addGap(13, 13, 13))
949  );
950  playBackPanelLayout.setVerticalGroup(
951  playBackPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
952  .addGroup(playBackPanelLayout.createSequentialGroup()
953  .addGap(7, 7, 7)
954  .addGroup(playBackPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
955  .addGroup(playBackPanelLayout.createSequentialGroup()
956  .addGap(2, 2, 2)
957  .addComponent(playBackSpeedLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
958  .addComponent(playBackSpeedComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
959  .addGap(10, 10, 10))
960  );
961 
962  javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel);
963  controlPanel.setLayout(controlPanelLayout);
964  controlPanelLayout.setHorizontalGroup(
965  controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
966  .addGroup(controlPanelLayout.createSequentialGroup()
967  .addContainerGap()
968  .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
969  .addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
970  .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, controlPanelLayout.createSequentialGroup()
971  .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
972  .addComponent(buttonPanel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
973  .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 623, Short.MAX_VALUE))
974  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
975  .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
976  .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
977  .addComponent(playBackPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))
978  .addGap(10, 10, 10)))
979  .addGap(0, 0, 0))
980  );
981  controlPanelLayout.setVerticalGroup(
982  controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
983  .addGroup(controlPanelLayout.createSequentialGroup()
984  .addGap(0, 0, 0)
985  .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
986  .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
987  .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
988  .addGap(5, 5, 5)
989  .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
990  .addComponent(buttonPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
991  .addComponent(playBackPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
992  .addGap(14, 14, 14)
993  .addComponent(infoLabel))
994  );
995 
996  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
997  this.setLayout(layout);
998  layout.setHorizontalGroup(
999  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
1000  .addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
1001  .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
1002  );
1003  layout.setVerticalGroup(
1004  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
1005  .addGroup(layout.createSequentialGroup()
1006  .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
1007  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
1008  .addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
1009  );
1010  }// </editor-fold>//GEN-END:initComponents
1011 
1012  private void rewindButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_rewindButtonActionPerformed
1013  Gst.getExecutor().submit(() -> {
1014  if (gstPlayBin != null) {
1015  long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
1016  //Skip 30 seconds.
1017  long rewindDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS);
1018  //Ensure new video position is within bounds
1019  long newTime = Math.max(currentTime - rewindDelta, 0);
1020  double playBackRate = getPlayBackRate();
1021  gstPlayBin.seek(playBackRate,
1022  Format.TIME,
1023  //FLUSH - flushes the pipeline
1024  //ACCURATE - video will seek exactly to the position requested
1025  EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE),
1026  //Set the start position to newTime
1027  SeekType.SET, newTime,
1028  //Do nothing for the end position
1029  SeekType.NONE, -1);
1030  }
1031  });
1032  }//GEN-LAST:event_rewindButtonActionPerformed
1033 
1034  private void fastForwardButtonActionPerformed(java.awt.event.ActionEvent evt) {
1035  Gst.getExecutor().submit(() -> {
1036  if (gstPlayBin != null) {
1037  long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
1038  long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
1039  //Skip 30 seconds.
1040  long fastForwardDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS);
1041  //Don't allow skipping within 2 seconds of video ending. Skipping right to
1042  //the end causes undefined behavior for some gstreamer plugins.
1043  long twoSecondsInNano = TimeUnit.NANOSECONDS.convert(2, TimeUnit.SECONDS);
1044  if ((duration - currentTime) <= twoSecondsInNano) {
1045  return;
1046  }
1047 
1048  long newTime;
1049  if (currentTime + fastForwardDelta >= duration) {
1050  //If there are less than 30 seconds left, only fast forward to the midpoint.
1051  newTime = currentTime + (duration - currentTime) / 2;
1052  } else {
1053  newTime = currentTime + fastForwardDelta;
1054  }
1055 
1056  double playBackRate = getPlayBackRate();
1057  gstPlayBin.seek(playBackRate,
1058  Format.TIME,
1059  //FLUSH - flushes the pipeline
1060  //ACCURATE - video will seek exactly to the position requested
1061  EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE),
1062  //Set the start position to newTime
1063  SeekType.SET, newTime,
1064  //Do nothing for the end position
1065  SeekType.NONE, -1);
1066  }
1067  });
1068  }
1069 
1070  private void playButtonActionPerformed(java.awt.event.ActionEvent evt) {
1071  Gst.getExecutor().submit(() -> {
1072  if (gstPlayBin != null) {
1073  if (gstPlayBin.isPlaying()) {
1074  gstPlayBin.pause();
1075  } else {
1076  double playBackRate = getPlayBackRate();
1077  long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
1078  //Set playback rate before play.
1079  gstPlayBin.seek(playBackRate,
1080  Format.TIME,
1081  //FLUSH - flushes the pipeline
1082  //ACCURATE - video will seek exactly to the position requested
1083  EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE),
1084  //Set the start position to newTime
1085  SeekType.SET, currentTime,
1086  //Do nothing for the end position
1087  SeekType.NONE, -1);
1088  gstPlayBin.play();
1089  }
1090  }
1091  });
1092  }
1093 
1094  private void playBackSpeedComboBoxActionPerformed(java.awt.event.ActionEvent evt) {
1095  Gst.getExecutor().submit(() -> {
1096  if (gstPlayBin != null) {
1097  double playBackRate = getPlayBackRate();
1098  long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
1099  gstPlayBin.seek(playBackRate,
1100  Format.TIME,
1101  //FLUSH - flushes the pipeline
1102  //ACCURATE - video will seek exactly to the position requested
1103  EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE),
1104  //Set the position to the currentTime, we are only adjusting the
1105  //playback rate.
1106  SeekType.SET, currentTime,
1107  SeekType.NONE, 0);
1108  }
1109  });
1110  }
1111 
1112  // Variables declaration - do not modify//GEN-BEGIN:variables
1113  private javax.swing.JLabel VolumeIcon;
1114  private javax.swing.JSlider audioSlider;
1115  private javax.swing.JPanel buttonPanel;
1116  private javax.swing.JPanel controlPanel;
1117  private javax.swing.JButton fastForwardButton;
1118  private javax.swing.JLabel infoLabel;
1119  private javax.swing.JPanel playBackPanel;
1120  private javax.swing.JComboBox<String> playBackSpeedComboBox;
1121  private javax.swing.JLabel playBackSpeedLabel;
1122  private javax.swing.JButton playButton;
1123  private javax.swing.JLabel progressLabel;
1124  private javax.swing.JSlider progressSlider;
1125  private javax.swing.JButton rewindButton;
1126  private javax.swing.JPanel videoPanel;
1127  // End of variables declaration//GEN-END:variables
1128 }
void playButtonActionPerformed(java.awt.event.ActionEvent evt)
static< T > long writeToFile(Content content, java.io.File outputFile, ProgressHandle progress, Future< T > worker, boolean source)
void playBackSpeedComboBoxActionPerformed(java.awt.event.ActionEvent evt)
void rewindButtonActionPerformed(java.awt.event.ActionEvent evt)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
void fastForwardButtonActionPerformed(java.awt.event.ActionEvent evt)

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