Autopsy  4.21.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  progressLabel.setText("");
310  });
311  timer.stop();
312  }
313  };
314  stateChangeListener = new Bus.STATE_CHANGED() {
315  @Override
316  public void stateChanged(GstObject go, State oldState, State currentState, State pendingState) {
317  if (State.PLAYING.equals(currentState)) {
318  SwingUtilities.invokeLater(() -> {
319  playButton.setIcon(pauseIcon);
320  });
321  } else {
322  SwingUtilities.invokeLater(() -> {
323  playButton.setIcon(playIcon);
324  });
325  }
326  }
327  };
328  endOfStreamListener = new Bus.EOS() {
329  @Override
330  public void endOfStream(GstObject go) {
331  if (gstPlayBin != null) {
332  gstPlayBin.seek(ClockTime.ZERO);
336  Gst.getExecutor().submit(() -> gstPlayBin.pause());
337  }
338  }
339  };
340  }
341 
348  @NbBundle.Messages({"GstVideoPanel.noOpenCase.errMsg=No open case available."})
349  void loadFile(final AbstractFile file) {
350  //Ensure everything is back in the initial state
351  infoLabel.setText("");
352  if (file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC)) {
353  infoLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.setupVideo.infoLabel.text"));
354  return;
355  }
356 
357  try {
358  //Pushing off initialization to the background
359  extractMediaWorker = new ExtractMedia(file, VideoUtils.getVideoFileInTempDir(file));
360  extractMediaWorker.execute();
361  } catch (NoCurrentCaseException ex) {
362  logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
363  infoLabel.setText(String.format("<html><font color='red'>%s</font></html>", Bundle.GstVideoPanel_noOpenCase_errMsg()));
364  enableComponents(false);
365  }
366  }
367 
372  @NbBundle.Messages({
373  "MediaPlayerPanel.noSupport=File not supported."
374  })
375  void resetComponents() {
376  progressLabel.setText(String.format("%s/%s", Bundle.MediaPlayerPanel_unknownTime(),
377  Bundle.MediaPlayerPanel_unknownTime()));
378  infoLabel.setText(Bundle.MediaPlayerPanel_noSupport());
379  progressSlider.setValue(0);
380  }
381 
385  void reset() {
386  if (extractMediaWorker != null) {
387  extractMediaWorker.cancel(true);
388  }
389  timer.stop();
390  if (gstPlayBin != null) {
391  Gst.getExecutor().submit(() -> {
392  gstPlayBin.stop();
393  gstPlayBin.getBus().disconnect(endOfStreamListener);
394  gstPlayBin.getBus().disconnect(stateChangeListener);
395  gstPlayBin.getBus().disconnect(errorListener);
396  gstPlayBin.getBus().dispose();
397  gstPlayBin.dispose();
398  fxAppSink.clear();
399  gstPlayBin = null;
400  });
401  }
402  videoPanel.removeAll();
403  resetComponents();
404  enableComponents(false);
405  }
406 
407  private void enableComponents(boolean isEnabled) {
408  playButton.setEnabled(isEnabled);
409  progressSlider.setEnabled(isEnabled);
410  videoPanel.setEnabled(isEnabled);
411  audioSlider.setEnabled(isEnabled);
412  rewindButton.setEnabled(isEnabled);
413  fastForwardButton.setEnabled(isEnabled);
414  playBackSpeedComboBox.setEnabled(isEnabled);
415  }
416 
417  @Override
418  public List<String> getSupportedExtensions() {
419  return Arrays.asList(FILE_EXTENSIONS.clone());
420  }
421 
422  @Override
423  public List<String> getSupportedMimeTypes() {
424  return MIME_TYPES;
425  }
426 
427  @Override
428  public boolean isSupported(AbstractFile file) {
429  if (!IS_GST_ENABLED) {
430  return false;
431  }
432 
433  String extension = file.getNameExtension();
450  if (getSupportedExtensions().contains("." + extension)) {
451  SortedSet<String> mimeTypes = new TreeSet<>(getSupportedMimeTypes());
452  try {
453  String mimeType = new FileTypeDetector().getMIMEType(file);
454  return mimeTypes.contains(mimeType);
456  logger.log(Level.WARNING, "Failed to look up mimetype for " + file.getName() + " using FileTypeDetector. Fallingback on AbstractFile.isMimeType", ex);
457  if (!mimeTypes.isEmpty() && file.isMimeType(mimeTypes) == AbstractFile.MimeMatchEnum.TRUE) {
458  return true;
459  }
460  }
461 
462  return getSupportedExtensions().contains("." + extension);
463  }
464  return false;
465  }
466 
474  private void updateTimeLabel(long start, long total) {
475  progressLabel.setText(formatTime(start) + "/" + formatTime(total));
476  }
477 
483  private double getPlayBackRate() {
484  int selectIndex = playBackSpeedComboBox.getSelectedIndex();
485  String selectText = playBackSpeedComboBox.getItemAt(selectIndex);
486  return Double.valueOf(selectText.substring(0, selectText.length() - 1));
487  }
488 
492  @NbBundle.Messages({
493  "MediaPlayerPanel.unknownTime=Unknown",
494  "MediaPlayerPanel.timeFormat=%02d:%02d:%02d"
495  })
496  private String formatTime(long ns) {
497  if (ns == -1) {
498  return Bundle.MediaPlayerPanel_unknownTime();
499  }
500 
501  long seconds = TimeUnit.SECONDS.convert(ns, TimeUnit.NANOSECONDS);
502  long hours = TimeUnit.HOURS.convert(seconds, TimeUnit.SECONDS);
503  seconds -= TimeUnit.SECONDS.convert(hours, TimeUnit.HOURS);
504  long minutes = TimeUnit.MINUTES.convert(seconds, TimeUnit.SECONDS);
505  seconds -= TimeUnit.SECONDS.convert(minutes, TimeUnit.MINUTES);
506 
507  return String.format(Bundle.MediaPlayerPanel_timeFormat(), hours, minutes, seconds);
508  }
509 
514  private class ExtractMedia extends SwingWorker<GstStatus, Void> {
515 
516  private ProgressHandle progress;
517  private final AbstractFile sourceFile;
518  private final java.io.File tempFile;
519 
520  ExtractMedia(AbstractFile sFile, File jFile) {
521  this.sourceFile = sFile;
522  this.tempFile = jFile;
523  }
524 
525  @Override
526  protected GstStatus doInBackground() throws Exception {
527  if (this.isCancelled()) {
528  throw new InterruptedException("Thread has been interrupted");
529  }
530 
531  GstStatus loadStatus = GstLoader.tryLoad();
532  if (loadStatus == GstStatus.FAILURE) {
533  return loadStatus;
534  }
535 
536  if (this.isCancelled()) {
537  throw new InterruptedException("Thread has been interrupted");
538  }
539 
540  if (!tempFile.exists() || tempFile.length() < sourceFile.getSize()) {
541  progress = ProgressHandle.createHandle(NbBundle.getMessage(MediaPlayerPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sourceFile.getName()), () -> this.cancel(true));
542 
543  SwingUtilities.invokeLater(() -> {
544  progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering"));
545  });
546 
547  progress.start(100);
548  try {
549  Files.createParentDirs(tempFile);
550  ContentUtils.writeToFile(sourceFile, tempFile, progress, this, true);
551  } catch (IOException ex) {
552  logger.log(Level.WARNING, "Error creating parent directory for copying video/audio in temp directory", ex); //NON-NLS
553  } finally {
554  progress.finish();
555  }
556  }
557  return loadStatus;
558  }
559 
560  /*
561  * Initialize the playback components if the extraction was successful.
562  */
563  @NbBundle.Messages({
564  "MediaPlayerPanel.playbackDisabled=A problem was encountered with"
565  + " the video and audio playback service. Video and audio "
566  + "playback will be disabled for the remainder of the session."
567  })
568  @Override
569  protected void done() {
570  try {
571  if (this.isCancelled()) {
572  return;
573  }
574 
575  GstStatus loadStatus = super.get();
576  if (loadStatus == null || loadStatus == GstStatus.FAILURE) {
577  return;
578  }
579 
580  if (this.isCancelled()) {
581  return;
582  }
583 
584  Gst.getExecutor().submit(() -> {
585  //Video is ready for playback. Create new components
586  gstPlayBin = new PlayBin("VideoPlayer", tempFile.toURI());
587  //Configure event handling
588  Bus playBinBus = gstPlayBin.getBus();
589  playBinBus.connect(endOfStreamListener);
590  playBinBus.connect(stateChangeListener);
591  playBinBus.connect(errorListener);
592 
593  if (this.isCancelled()) {
594  return;
595  }
596 
597  JFXPanel fxPanel = new JFXPanel();
598  videoPanel.removeAll();
599  videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
600  videoPanel.add(fxPanel);
601  fxAppSink = new JavaFxAppSink("JavaFxAppSink", fxPanel);
602  if (gstPlayBin != null) {
603  gstPlayBin.setVideoSink(fxAppSink);
604  }
605  if (this.isCancelled()) {
606  return;
607  }
608  if (gstPlayBin != null) {
609  gstPlayBin.setVolume((audioSlider.getValue() * 2.0) / 100.0);
610  gstPlayBin.pause();
611  }
612 
613  timer.start();
614  SwingUtilities.invokeLater(() -> {
615  enableComponents(true);
616  });
617  });
618  } catch (CancellationException ex) {
619  logger.log(Level.INFO, "Media buffering was canceled."); //NON-NLS
620  } catch (InterruptedException ex) {
621  logger.log(Level.INFO, "Media buffering was interrupted."); //NON-NLS
622  } catch (ExecutionException ex) {
623  logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS
624  }
625  }
626  }
627 
631  private class VideoPanelUpdater implements ActionListener {
632 
633  @Override
634  public void actionPerformed(ActionEvent e) {
635  if (!progressSlider.getValueIsAdjusting() && gstPlayBin != null) {
636  Gst.getExecutor().submit(() -> {
637  try {
638  sliderLock.acquireUninterruptibly();
639  long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
640  long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
647  if (duration >= 0 && position >= 0) {
648  double relativePosition = (double) position / duration;
649  progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE));
650  }
651 
652  SwingUtilities.invokeLater(() -> {
653  updateTimeLabel(position, duration);
654  });
655  } finally {
656  sliderLock.release();
657  }
658  });
659  }
660  }
661  }
662 
666  private class CircularJSliderUI extends BasicSliderUI {
667 
668  private final Dimension thumbDimension;
669  private final Color thumbColor;
670  private final Color trackUnseen;
671  private final Color trackSeen;
672 
681  public CircularJSliderUI(JSlider slider, Dimension thumbDimension) {
682  super(slider);
683  this.thumbDimension = thumbDimension;
684 
685  //Configure track and thumb colors.
686  Color lightBlue = new Color(0, 130, 255);
687  thumbColor = lightBlue;
688  trackSeen = lightBlue;
689  trackUnseen = Color.LIGHT_GRAY;
690  }
691 
692  @Override
693  protected Dimension getThumbSize() {
694  return new Dimension(thumbDimension);
695  }
696 
701  @Override
702  public void paintThumb(Graphics graphic) {
703  Rectangle thumb = this.thumbRect;
704 
705  Color original = graphic.getColor();
706 
707  //Change the thumb view from the rectangle
708  //controller to an oval.
709  graphic.setColor(thumbColor);
710  graphic.fillOval(thumb.x, thumb.y, thumbDimension.width, thumbDimension.height);
711 
712  //Preserve the graphics original color
713  graphic.setColor(original);
714  }
715 
716  @Override
717  public void paintTrack(Graphics graphic) {
718  //This rectangle is the bounding box for the progress bar
719  //portion of the slider. The track is painted in the middle
720  //of this rectangle and the thumb laid overtop.
721  Rectangle track = this.trackRect;
722 
723  //Get the location of the thumb, this point splits the
724  //progress bar into 2 line segments, seen and unseen.
725  Rectangle thumb = this.thumbRect;
726  int thumbX = thumb.x;
727  int thumbY = thumb.y;
728 
729  Color original = graphic.getColor();
730 
731  //Paint the seen side
732  graphic.setColor(trackSeen);
733  graphic.drawLine(track.x, track.y + track.height / 2,
734  thumbX, thumbY + track.height / 2);
735 
736  //Paint the unseen side
737  graphic.setColor(trackUnseen);
738  graphic.drawLine(thumbX, thumbY + track.height / 2,
739  track.x + track.width, track.y + track.height / 2);
740 
741  //Preserve the graphics color.
742  graphic.setColor(original);
743  }
744 
745  @Override
746  protected TrackListener createTrackListener(JSlider slider) {
755  return new TrackListener() {
756  @Override
757  public void mousePressed(MouseEvent e) {
758  if (!slider.isEnabled() || !SwingUtilities.isLeftMouseButton(e)) {
759  return;
760  }
761  //Snap the thumb to position of the mouse
762  scrollDueToClickInTrack(0);
763 
764  //Handle the event as normal.
765  super.mousePressed(e);
766  }
767  };
768  }
769 
770  @Override
771  protected void scrollDueToClickInTrack(int direction) {
772  //Set the thumb position to the mouse press location, as opposed
773  //to the closest "block" which is the default behavior.
774  Point mousePosition = slider.getMousePosition();
775  if (mousePosition == null) {
776  return;
777  }
778  int value = this.valueForXPosition(mousePosition.x);
779 
780  //Lock the slider down, which is a shared resource.
781  //The VideoPanelUpdater keeps the
782  //slider in sync with the video position, so without
783  //proper locking our change could be overwritten.
784  sliderLock.acquireUninterruptibly();
785  slider.setValueIsAdjusting(true);
786  slider.setValue(value);
787  slider.setValueIsAdjusting(false);
788  sliderLock.release();
789  }
790 
794  @Override
795  public void update(Graphics graphic, JComponent component) {
796  if (graphic instanceof Graphics2D) {
797  Graphics2D graphic2 = (Graphics2D) graphic;
798  graphic2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
799  RenderingHints.VALUE_ANTIALIAS_ON);
800  }
801 
802  super.update(graphic, component);
803  }
804  }
805 
811  @SuppressWarnings("unchecked")
812  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
813  private void initComponents() {
814  java.awt.GridBagConstraints gridBagConstraints;
815 
816  videoPanel = new javax.swing.JPanel();
817  controlPanel = new javax.swing.JPanel();
818  progressSlider = new javax.swing.JSlider();
819  progressLabel = new javax.swing.JLabel();
820  buttonPanel = new javax.swing.JPanel();
821  playButton = new javax.swing.JButton();
822  fastForwardButton = new javax.swing.JButton();
823  rewindButton = new javax.swing.JButton();
824  VolumeIcon = new javax.swing.JLabel();
825  audioSlider = new javax.swing.JSlider();
826  infoLabel = new javax.swing.JLabel();
827  playBackPanel = new javax.swing.JPanel();
828  playBackSpeedComboBox = new javax.swing.JComboBox<>();
829  playBackSpeedLabel = new javax.swing.JLabel();
830 
831  javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel);
832  videoPanel.setLayout(videoPanelLayout);
833  videoPanelLayout.setHorizontalGroup(
834  videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
835  .addGap(0, 0, Short.MAX_VALUE)
836  );
837  videoPanelLayout.setVerticalGroup(
838  videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
839  .addGap(0, 117, Short.MAX_VALUE)
840  );
841 
842  progressSlider.setValue(0);
843  progressSlider.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
844  progressSlider.setDoubleBuffered(true);
845  progressSlider.setMinimumSize(new java.awt.Dimension(36, 21));
846  progressSlider.setPreferredSize(new java.awt.Dimension(200, 21));
847  progressSlider.setUI(new CircularJSliderUI(progressSlider, new Dimension(18,18)));
848 
849  org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.progressLabel.text")); // NOI18N
850 
851  buttonPanel.setLayout(new java.awt.GridBagLayout());
852 
853  playButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/contentviewers/images/Play-arrow-01.png"))); // NOI18N
854  org.openide.awt.Mnemonics.setLocalizedText(playButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.playButton.text")); // NOI18N
855  playButton.setMaximumSize(new java.awt.Dimension(53, 29));
856  playButton.setMinimumSize(new java.awt.Dimension(53, 29));
857  playButton.setPreferredSize(new java.awt.Dimension(49, 29));
858  playButton.addActionListener(new java.awt.event.ActionListener() {
859  public void actionPerformed(java.awt.event.ActionEvent evt) {
860  playButtonActionPerformed(evt);
861  }
862  });
863  gridBagConstraints = new java.awt.GridBagConstraints();
864  gridBagConstraints.gridx = 1;
865  gridBagConstraints.gridy = 0;
866  gridBagConstraints.ipadx = 21;
867  gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
868  gridBagConstraints.insets = new java.awt.Insets(5, 6, 0, 0);
869  buttonPanel.add(playButton, gridBagConstraints);
870 
871  fastForwardButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/contentviewers/images/Fast-forward-01.png"))); // NOI18N
872  org.openide.awt.Mnemonics.setLocalizedText(fastForwardButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.fastForwardButton.text")); // NOI18N
873  fastForwardButton.addActionListener(new java.awt.event.ActionListener() {
874  public void actionPerformed(java.awt.event.ActionEvent evt) {
875  fastForwardButtonActionPerformed(evt);
876  }
877  });
878  gridBagConstraints = new java.awt.GridBagConstraints();
879  gridBagConstraints.gridx = 2;
880  gridBagConstraints.gridy = 0;
881  gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
882  gridBagConstraints.insets = new java.awt.Insets(5, 6, 0, 0);
883  buttonPanel.add(fastForwardButton, gridBagConstraints);
884 
885  rewindButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/contentviewers/images/Fast-rewind-01.png"))); // NOI18N
886  org.openide.awt.Mnemonics.setLocalizedText(rewindButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.rewindButton.text")); // NOI18N
887  rewindButton.addActionListener(new java.awt.event.ActionListener() {
888  public void actionPerformed(java.awt.event.ActionEvent evt) {
889  rewindButtonActionPerformed(evt);
890  }
891  });
892  gridBagConstraints = new java.awt.GridBagConstraints();
893  gridBagConstraints.gridx = 0;
894  gridBagConstraints.gridy = 0;
895  gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
896  gridBagConstraints.insets = new java.awt.Insets(5, 0, 1, 0);
897  buttonPanel.add(rewindButton, gridBagConstraints);
898 
899  org.openide.awt.Mnemonics.setLocalizedText(VolumeIcon, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.VolumeIcon.text")); // NOI18N
900  VolumeIcon.setHorizontalTextPosition(javax.swing.SwingConstants.LEFT);
901  VolumeIcon.setMaximumSize(new java.awt.Dimension(34, 29));
902  VolumeIcon.setMinimumSize(new java.awt.Dimension(34, 29));
903  VolumeIcon.setPreferredSize(new java.awt.Dimension(34, 19));
904  gridBagConstraints = new java.awt.GridBagConstraints();
905  gridBagConstraints.gridx = 3;
906  gridBagConstraints.gridy = 0;
907  gridBagConstraints.ipadx = 8;
908  gridBagConstraints.ipady = 7;
909  gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
910  gridBagConstraints.insets = new java.awt.Insets(6, 14, 0, 0);
911  buttonPanel.add(VolumeIcon, gridBagConstraints);
912 
913  audioSlider.setMajorTickSpacing(10);
914  audioSlider.setMaximum(50);
915  audioSlider.setMinorTickSpacing(5);
916  audioSlider.setToolTipText(org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.audioSlider.toolTipText")); // NOI18N
917  audioSlider.setValue(25);
918  audioSlider.setMaximumSize(new java.awt.Dimension(32767, 19));
919  audioSlider.setMinimumSize(new java.awt.Dimension(200, 19));
920  audioSlider.setPreferredSize(new java.awt.Dimension(200, 30));
921  audioSlider.setRequestFocusEnabled(false);
922  audioSlider.setUI(new CircularJSliderUI(audioSlider, new Dimension(15,15)));
923  gridBagConstraints = new java.awt.GridBagConstraints();
924  gridBagConstraints.gridx = 4;
925  gridBagConstraints.gridy = 0;
926  gridBagConstraints.ipadx = -116;
927  gridBagConstraints.ipady = 7;
928  gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
929  gridBagConstraints.insets = new java.awt.Insets(3, 1, 0, 10);
930  buttonPanel.add(audioSlider, gridBagConstraints);
931 
932  infoLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
933  org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.infoLabel.text")); // NOI18N
934  infoLabel.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
935 
936  playBackSpeedComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "0.25x", "0.50x", "0.75x", "1x", "1.25x", "1.50x", "1.75x", "2x" }));
937  playBackSpeedComboBox.setSelectedIndex(3);
938  playBackSpeedComboBox.setMaximumSize(new java.awt.Dimension(53, 29));
939  playBackSpeedComboBox.setMinimumSize(new java.awt.Dimension(53, 29));
940  playBackSpeedComboBox.setPreferredSize(new java.awt.Dimension(53, 29));
941  playBackSpeedComboBox.setRequestFocusEnabled(false);
942  playBackSpeedComboBox.addActionListener(new java.awt.event.ActionListener() {
943  public void actionPerformed(java.awt.event.ActionEvent evt) {
944  playBackSpeedComboBoxActionPerformed(evt);
945  }
946  });
947 
948  org.openide.awt.Mnemonics.setLocalizedText(playBackSpeedLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.playBackSpeedLabel.text")); // NOI18N
949  playBackSpeedLabel.setMaximumSize(new java.awt.Dimension(34, 19));
950  playBackSpeedLabel.setMinimumSize(new java.awt.Dimension(34, 19));
951  playBackSpeedLabel.setPreferredSize(new java.awt.Dimension(34, 19));
952 
953  javax.swing.GroupLayout playBackPanelLayout = new javax.swing.GroupLayout(playBackPanel);
954  playBackPanel.setLayout(playBackPanelLayout);
955  playBackPanelLayout.setHorizontalGroup(
956  playBackPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
957  .addGroup(playBackPanelLayout.createSequentialGroup()
958  .addComponent(playBackSpeedLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 34, javax.swing.GroupLayout.PREFERRED_SIZE)
959  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
960  .addComponent(playBackSpeedComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
961  .addGap(13, 13, 13))
962  );
963  playBackPanelLayout.setVerticalGroup(
964  playBackPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
965  .addGroup(playBackPanelLayout.createSequentialGroup()
966  .addGap(7, 7, 7)
967  .addGroup(playBackPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
968  .addGroup(playBackPanelLayout.createSequentialGroup()
969  .addGap(2, 2, 2)
970  .addComponent(playBackSpeedLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
971  .addComponent(playBackSpeedComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
972  .addGap(10, 10, 10))
973  );
974 
975  javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel);
976  controlPanel.setLayout(controlPanelLayout);
977  controlPanelLayout.setHorizontalGroup(
978  controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
979  .addGroup(controlPanelLayout.createSequentialGroup()
980  .addContainerGap()
981  .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
982  .addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
983  .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, controlPanelLayout.createSequentialGroup()
984  .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
985  .addComponent(buttonPanel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
986  .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 623, Short.MAX_VALUE))
987  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
988  .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
989  .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
990  .addComponent(playBackPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))
991  .addGap(10, 10, 10)))
992  .addGap(0, 0, 0))
993  );
994  controlPanelLayout.setVerticalGroup(
995  controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
996  .addGroup(controlPanelLayout.createSequentialGroup()
997  .addGap(0, 0, 0)
998  .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
999  .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
1000  .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
1001  .addGap(5, 5, 5)
1002  .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
1003  .addComponent(buttonPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
1004  .addComponent(playBackPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
1005  .addGap(14, 14, 14)
1006  .addComponent(infoLabel))
1007  );
1008 
1009  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
1010  this.setLayout(layout);
1011  layout.setHorizontalGroup(
1012  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
1013  .addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
1014  .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
1015  );
1016  layout.setVerticalGroup(
1017  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
1018  .addGroup(layout.createSequentialGroup()
1019  .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
1020  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
1021  .addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
1022  );
1023  }// </editor-fold>//GEN-END:initComponents
1024 
1025  private void rewindButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_rewindButtonActionPerformed
1026  Gst.getExecutor().submit(() -> {
1027  if (gstPlayBin != null) {
1028  long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
1029  //Skip 30 seconds.
1030  long rewindDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS);
1031  //Ensure new video position is within bounds
1032  long newTime = Math.max(currentTime - rewindDelta, 0);
1033  double playBackRate = getPlayBackRate();
1034  gstPlayBin.seek(playBackRate,
1035  Format.TIME,
1036  //FLUSH - flushes the pipeline
1037  //ACCURATE - video will seek exactly to the position requested
1038  EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE),
1039  //Set the start position to newTime
1040  SeekType.SET, newTime,
1041  //Do nothing for the end position
1042  SeekType.NONE, -1);
1043  }
1044  });
1045  }//GEN-LAST:event_rewindButtonActionPerformed
1046 
1047  private void fastForwardButtonActionPerformed(java.awt.event.ActionEvent evt) {
1048  Gst.getExecutor().submit(() -> {
1049  if (gstPlayBin != null) {
1050  long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
1051  long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
1052  //Skip 30 seconds.
1053  long fastForwardDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS);
1054  //Don't allow skipping within 2 seconds of video ending. Skipping right to
1055  //the end causes undefined behavior for some gstreamer plugins.
1056  long twoSecondsInNano = TimeUnit.NANOSECONDS.convert(2, TimeUnit.SECONDS);
1057  if ((duration - currentTime) <= twoSecondsInNano) {
1058  return;
1059  }
1060 
1061  long newTime;
1062  if (currentTime + fastForwardDelta >= duration) {
1063  //If there are less than 30 seconds left, only fast forward to the midpoint.
1064  newTime = currentTime + (duration - currentTime) / 2;
1065  } else {
1066  newTime = currentTime + fastForwardDelta;
1067  }
1068 
1069  double playBackRate = getPlayBackRate();
1070  gstPlayBin.seek(playBackRate,
1071  Format.TIME,
1072  //FLUSH - flushes the pipeline
1073  //ACCURATE - video will seek exactly to the position requested
1074  EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE),
1075  //Set the start position to newTime
1076  SeekType.SET, newTime,
1077  //Do nothing for the end position
1078  SeekType.NONE, -1);
1079  }
1080  });
1081  }
1082 
1083  private void playButtonActionPerformed(java.awt.event.ActionEvent evt) {
1084  Gst.getExecutor().submit(() -> {
1085  if (gstPlayBin != null) {
1086  if (gstPlayBin.isPlaying()) {
1087  gstPlayBin.pause();
1088  } else {
1089  double playBackRate = getPlayBackRate();
1090  long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
1091  //Set playback rate before play.
1092  gstPlayBin.seek(playBackRate,
1093  Format.TIME,
1094  //FLUSH - flushes the pipeline
1095  //ACCURATE - video will seek exactly to the position requested
1096  EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE),
1097  //Set the start position to newTime
1098  SeekType.SET, currentTime,
1099  //Do nothing for the end position
1100  SeekType.NONE, -1);
1101  gstPlayBin.play();
1102  }
1103  }
1104  });
1105  }
1106 
1107  private void playBackSpeedComboBoxActionPerformed(java.awt.event.ActionEvent evt) {
1108  Gst.getExecutor().submit(() -> {
1109  if (gstPlayBin != null) {
1110  double playBackRate = getPlayBackRate();
1111  long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
1112  gstPlayBin.seek(playBackRate,
1113  Format.TIME,
1114  //FLUSH - flushes the pipeline
1115  //ACCURATE - video will seek exactly to the position requested
1116  EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE),
1117  //Set the position to the currentTime, we are only adjusting the
1118  //playback rate.
1119  SeekType.SET, currentTime,
1120  SeekType.NONE, 0);
1121  }
1122  });
1123  }
1124 
1125  // Variables declaration - do not modify//GEN-BEGIN:variables
1126  private javax.swing.JLabel VolumeIcon;
1127  private javax.swing.JSlider audioSlider;
1128  private javax.swing.JPanel buttonPanel;
1129  private javax.swing.JPanel controlPanel;
1130  private javax.swing.JButton fastForwardButton;
1131  private javax.swing.JLabel infoLabel;
1132  private javax.swing.JPanel playBackPanel;
1133  private javax.swing.JComboBox<String> playBackSpeedComboBox;
1134  private javax.swing.JLabel playBackSpeedLabel;
1135  private javax.swing.JButton playButton;
1136  private javax.swing.JLabel progressLabel;
1137  private javax.swing.JSlider progressSlider;
1138  private javax.swing.JButton rewindButton;
1139  private javax.swing.JPanel videoPanel;
1140  // End of variables declaration//GEN-END:variables
1141 }
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-2022 Basis Technology. Generated on: Tue Feb 6 2024
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.