Autopsy  4.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
FXVideoPanel.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2013-15 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.corecomponents;
20 
21 import com.google.common.io.Files;
22 import java.awt.Dimension;
23 import java.io.File;
24 import java.io.IOException;
25 import java.nio.file.Paths;
26 import java.util.Arrays;
27 import java.util.List;
28 import java.util.concurrent.CancellationException;
29 import java.util.logging.Level;
30 import javafx.application.Platform;
31 import javafx.beans.Observable;
32 import javafx.concurrent.Task;
33 import javafx.event.ActionEvent;
34 import javafx.event.EventHandler;
35 import javafx.geometry.Insets;
36 import javafx.geometry.Pos;
37 import javafx.scene.Scene;
38 import javafx.scene.control.Button;
39 import javafx.scene.control.Label;
40 import javafx.scene.control.Slider;
41 import javafx.scene.control.Tooltip;
42 import javafx.scene.layout.BorderPane;
43 import javafx.scene.layout.HBox;
44 import javafx.scene.layout.Priority;
45 import javafx.scene.layout.VBox;
46 import javafx.scene.media.Media;
47 import javafx.scene.media.MediaException;
48 import javafx.scene.media.MediaPlayer;
49 import javafx.scene.media.MediaPlayer.Status;
50 import static javafx.scene.media.MediaPlayer.Status.PAUSED;
51 import static javafx.scene.media.MediaPlayer.Status.PLAYING;
52 import static javafx.scene.media.MediaPlayer.Status.READY;
53 import static javafx.scene.media.MediaPlayer.Status.STOPPED;
54 import javafx.scene.media.MediaView;
55 import javafx.util.Duration;
56 import javax.swing.JPanel;
57 import org.netbeans.api.progress.ProgressHandle;
58 import org.openide.util.NbBundle;
59 import org.openide.util.lookup.ServiceProvider;
60 import org.openide.util.lookup.ServiceProviders;
69 
73 @ServiceProviders(value = {
74  @ServiceProvider(service = FrameCapture.class)
75 })
76 public class FXVideoPanel extends MediaViewVideoPanel {
77 
78  // Refer to https://docs.oracle.com/javafx/2/api/javafx/scene/media/package-summary.html
79  // for Javafx supported formats
80  private static final String[] EXTENSIONS = new String[]{".m4v", ".fxm", ".flv", ".m3u8", ".mp4", ".aif", ".aiff", ".mp3", "m4a", ".wav"}; //NON-NLS
81  private static final List<String> MIMETYPES = Arrays.asList("audio/x-aiff", "video/x-javafx", "video/x-flv", "application/vnd.apple.mpegurl", " audio/mpegurl", "audio/mpeg", "video/mp4", "audio/x-m4a", "video/x-m4v", "audio/x-wav"); //NON-NLS
82  private static final Logger logger = Logger.getLogger(FXVideoPanel.class.getName());
83 
84  private boolean fxInited = false;
85 
87 
89 
90  public FXVideoPanel() {
91  fxInited = Installer.isJavaFxInited();
92  initComponents();
93  if (fxInited) {
94  Platform.runLater(() -> {
95 
96  mediaPane = new MediaPane();
97  Scene fxScene = new Scene(mediaPane);
98  jFXPanel.setScene(fxScene);
99  });
100  }
101  }
102 
103  @Deprecated
104  public JPanel getVideoPanel() {
105  return this;
106  }
107 
108  @Override
109  void setupVideo(final AbstractFile file, final Dimension dims) {
110  if (file.equals(currentFile)) {
111  return;
112  }
113  if (!Case.isCaseOpen()) {
114  //handle in-between condition when case is being closed
115  //and an image was previously selected
116  return;
117  }
118  reset();
119  currentFile = file;
120  final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC);
121  if (deleted) {
122  mediaPane.setInfoLabelText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.mediaPane.infoLabel"));
123  removeAll();
124  return;
125  }
126  mediaPane.setFit(dims);
127 
128  String path = "";
129  try {
130  path = file.getUniquePath();
131  } catch (TskCoreException ex) {
132  logger.log(Level.SEVERE, "Cannot get unique path of video file", ex); //NON-NLS
133  }
134  mediaPane.setInfoLabelText(path);
135  mediaPane.setInfoLabelToolTipText(path);
136 
137  final File tempFile = VideoUtils.getTempVideoFile(currentFile);
138 
139  new Thread(mediaPane.new ExtractMedia(currentFile, tempFile)).start();
140 
141  }
142 
143  @Override
144  void reset() {
145  Platform.runLater(() -> {
146  if (mediaPane != null) {
147  mediaPane.reset();
148  }
149  });
150  currentFile = null;
151  }
152 
158  @SuppressWarnings("unchecked")
159  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
160  private void initComponents() {
161 
162  jFXPanel = new javafx.embed.swing.JFXPanel();
163 
164  setBackground(new java.awt.Color(0, 0, 0));
165 
166  javax.swing.GroupLayout jFXPanelLayout = new javax.swing.GroupLayout(jFXPanel);
167  jFXPanel.setLayout(jFXPanelLayout);
168  jFXPanelLayout.setHorizontalGroup(
169  jFXPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
170  .addGap(0, 400, Short.MAX_VALUE)
171  );
172  jFXPanelLayout.setVerticalGroup(
173  jFXPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
174  .addGap(0, 300, Short.MAX_VALUE)
175  );
176 
177  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
178  this.setLayout(layout);
179  layout.setHorizontalGroup(
180  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
181  .addComponent(jFXPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
182  );
183  layout.setVerticalGroup(
184  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
185  .addComponent(jFXPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
186  );
187  }// </editor-fold>//GEN-END:initComponents
188  // Variables declaration - do not modify//GEN-BEGIN:variables
189  private javafx.embed.swing.JFXPanel jFXPanel;
190  // End of variables declaration//GEN-END:variables
191 
192  @Override
193  public boolean isInited() {
194  return fxInited;
195  }
196 
197  private class MediaPane extends BorderPane {
198 
199  private MediaPlayer mediaPlayer;
200 
201  private final MediaView mediaView;
202 
206  private Duration duration;
207 
211  private final HBox mediaTools;
212 
216  private final HBox mediaViewPane;
217 
218  private final VBox controlPanel;
219 
220  private final Slider progressSlider;
221 
222  private final Button pauseButton;
223 
224  private final Button stopButton;
225 
226  private final Label progressLabel;
227 
228  private final Label infoLabel;
229 
230  private int totalHours;
231 
232  private int totalMinutes;
233 
234  private int totalSeconds;
235 
236  private final String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; //NON-NLS
237 
238  private static final String PLAY_TEXT = "►";
239 
240  private static final String PAUSE_TEXT = "||";
241 
242  private static final String STOP_TEXT = "X"; //NON-NLS
243 
244  public MediaPane() {
245  // Video Display
246  mediaViewPane = new HBox();
247  mediaViewPane.setStyle("-fx-background-color: black"); //NON-NLS
248  mediaViewPane.setAlignment(Pos.CENTER);
249  mediaView = new MediaView();
250  mediaViewPane.getChildren().add(mediaView);
251  setCenter(mediaViewPane);
252 
253  // Media Controls
254  controlPanel = new VBox();
255  mediaTools = new HBox();
256  mediaTools.setAlignment(Pos.CENTER);
257  mediaTools.setPadding(new Insets(5, 10, 5, 10));
258 
259  pauseButton = new Button(PLAY_TEXT);
260  stopButton = new Button(STOP_TEXT);
261  mediaTools.getChildren().add(pauseButton);
262  mediaTools.getChildren().add(new Label(" "));
263  mediaTools.getChildren().add(stopButton);
264  mediaTools.getChildren().add(new Label(" "));
265  progressSlider = new Slider();
266  HBox.setHgrow(progressSlider, Priority.ALWAYS);
267  progressSlider.setMinWidth(50);
268  progressSlider.setMaxWidth(Double.MAX_VALUE);
269  mediaTools.getChildren().add(progressSlider);
270  progressLabel = new Label();
271  progressLabel.setPrefWidth(135);
272  progressLabel.setMinWidth(135);
273  mediaTools.getChildren().add(progressLabel);
274 
275  controlPanel.getChildren().add(mediaTools);
276  controlPanel.setStyle("-fx-background-color: white"); //NON-NLS
277  infoLabel = new Label("");
278  controlPanel.getChildren().add(infoLabel);
279  setBottom(controlPanel);
280  setProgressActionListeners();
281  }
282 
287  public void reset() {
288  if (mediaPlayer != null) {
289  setInfoLabelText("");
290  if (mediaPlayer.getStatus() == Status.PLAYING) {
291  mediaPlayer.stop();
292  }
293  mediaPlayer = null;
294  mediaView.setMediaPlayer(null);
295  }
296  resetProgress();
297  }
298 
304  public void setInfoLabelText(final String text) {
305  logger.log(Level.INFO, "Setting Info Label Text: {0}", text); //NON-NLS
306  Platform.runLater(() -> {
307  infoLabel.setText(text);
308  });
309  }
310 
316  public void setFit(final Dimension dims) {
317  Platform.runLater(() -> {
318  setPrefSize(dims.getWidth(), dims.getHeight());
319  // Set the Video output to fit the size allocated for it. give an
320  // extra few px to ensure the info label will be shown
321  mediaView.setFitHeight(dims.getHeight() - controlPanel.getHeight());
322  });
323  }
324 
328  private void setProgressActionListeners() {
329  pauseButton.setOnAction(new EventHandler<ActionEvent>() {
330  @Override
331  public void handle(ActionEvent e) {
332  if (mediaPlayer == null) {
333  return;
334  }
335 
336  Status status = mediaPlayer.getStatus();
337 
338  switch (status) {
339  // If playing, pause
340  case PLAYING:
341  mediaPlayer.pause();
342  break;
343  // If ready, paused or stopped, continue playing
344  case READY:
345  case PAUSED:
346  case STOPPED:
347  mediaPlayer.play();
348  break;
349  default:
350  logger.log(Level.INFO, "MediaPlayer in unexpected state: {0}", status.toString()); //NON-NLS
351  // If the MediaPlayer is in an unexpected state, stop playback.
352  mediaPlayer.stop();
353  setInfoLabelText(NbBundle.getMessage(this.getClass(),
354  "FXVideoPanel.pauseButton.infoLabel.playbackErr"));
355  break;
356  }
357  }
358  });
359 
360  stopButton.setOnAction((ActionEvent e) -> {
361  if (mediaPlayer == null) {
362  return;
363  }
364 
365  mediaPlayer.stop();
366  });
367 
368  progressSlider.valueProperty().addListener((Observable o) -> {
369  if (mediaPlayer == null) {
370  return;
371  }
372 
373  if (progressSlider.isValueChanging()) {
374  mediaPlayer.seek(duration.multiply(progressSlider.getValue() / 100.0));
375  }
376  });
377  }
378 
382  private void resetProgress() {
383  totalHours = 0;
384  totalMinutes = 0;
385  totalSeconds = 0;
386  progressSlider.setValue(0.0);
387  updateTime(Duration.ZERO);
388  }
389 
399  private MediaPlayer createMediaPlayer(String mediaUri) {
400  Media media = new Media(mediaUri);
401 
402  MediaPlayer player = new MediaPlayer(media);
403  player.setOnReady(new ReadyListener());
404  final Runnable pauseListener = () -> {
405  pauseButton.setText(PLAY_TEXT);
406  };
407  player.setOnPaused(pauseListener);
408  player.setOnStopped(pauseListener);
409  player.setOnPlaying(() -> {
410  pauseButton.setText(PAUSE_TEXT);
411  });
412  player.setOnEndOfMedia(new EndOfMediaListener());
413 
414  player.currentTimeProperty().addListener((observable, oldTime, newTime) -> {
415  updateSlider(newTime);
416  updateTime(newTime);
417  });
418 
419  return player;
420  }
421 
426  private void updateProgress() {
427  if (mediaPlayer == null) {
428  return;
429  }
430  Duration currentTime = mediaPlayer.getCurrentTime();
431  updateSlider(currentTime);
432  updateTime(currentTime);
433  }
434 
440  private void updateSlider(Duration currentTime) {
441  if (progressSlider != null) {
442  progressSlider.setDisable(currentTime.isUnknown());
443  if (!progressSlider.isDisabled() && duration.greaterThan(Duration.ZERO)
444  && !progressSlider.isValueChanging()) {
445  progressSlider.setValue(currentTime.divide(duration.toMillis()).toMillis() * 100.0);
446  }
447  }
448  }
449 
455  private void updateTime(Duration currentTime) {
456  long millisElapsed = (long) currentTime.toMillis();
457 
458  long elapsedHours, elapsedMinutes, elapsedSeconds;
459  // pick out the elapsed hours, minutes, seconds
460  long secondsElapsed = millisElapsed / 1000;
461  elapsedHours = (int) secondsElapsed / 3600;
462  secondsElapsed -= elapsedHours * 3600;
463  elapsedMinutes = (int) secondsElapsed / 60;
464  secondsElapsed -= elapsedMinutes * 60;
465  elapsedSeconds = (int) secondsElapsed;
466 
467  String durationStr = String.format(durationFormat,
468  elapsedHours, elapsedMinutes, elapsedSeconds,
469  totalHours, totalMinutes, totalSeconds);
470  Platform.runLater(() -> {
471  progressLabel.setText(durationStr);
472  });
473  }
474 
475  private void setInfoLabelToolTipText(final String text) {
476  Platform.runLater(() -> {
477  infoLabel.setTooltip(new Tooltip(text));
478  });
479  }
480 
486  private class ReadyListener implements Runnable {
487 
488  @Override
489  public void run() {
490  if (mediaPlayer == null) {
491  return;
492  }
493 
494  duration = mediaPlayer.getMedia().getDuration();
495  long durationInMillis = (long) mediaPlayer.getMedia().getDuration().toMillis();
496 
497  // pick out the total hours, minutes, seconds
498  long durationSeconds = (int) durationInMillis / 1000;
499  totalHours = (int) durationSeconds / 3600;
500  durationSeconds -= totalHours * 3600;
501  totalMinutes = (int) durationSeconds / 60;
502  durationSeconds -= totalMinutes * 60;
503  totalSeconds = (int) durationSeconds;
504  updateProgress();
505  }
506  }
507 
513  private class EndOfMediaListener implements Runnable {
514 
515  @Override
516  public void run() {
517  if (mediaPlayer == null) {
518  return;
519  }
520 
521  Duration beginning = mediaPlayer.getStartTime();
522  mediaPlayer.stop();
523  mediaPlayer.pause();
524  pauseButton.setText(PLAY_TEXT);
525  updateSlider(beginning);
526  updateTime(beginning);
527  }
528  }
529 
534  private class ExtractMedia extends Task<Long> {
535 
536  private ProgressHandle progress;
537 
538  private final AbstractFile sourceFile;
539 
540  private final java.io.File tempFile;
541 
542  ExtractMedia(AbstractFile sFile, java.io.File jFile) {
543  this.sourceFile = sFile;
544  this.tempFile = jFile;
545  }
546 
552  public String getMediaUri() {
553  return Paths.get(tempFile.getAbsolutePath()).toUri().toString();
554  }
555 
556  @Override
557  protected Long call() throws Exception {
558  if (tempFile.exists() == false || tempFile.length() < sourceFile.getSize()) {
559  progress = ProgressHandle.createHandle(
560  NbBundle.getMessage(this.getClass(),
561  "FXVideoPanel.progress.bufferingFile",
562  sourceFile.getName()
563  ),
564  () -> ExtractMedia.this.cancel(true));
565 
566  Platform.runLater(() -> {
567  progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progressLabel.buffering"));
568  });
569 
570  progress.start(100);
571  try {
572  Files.createParentDirs(tempFile);
573  return ContentUtils.writeToFile(sourceFile, tempFile, progress, this, true);
574  } catch (IOException ex) {
575  logger.log(Level.WARNING, "Error buffering file", ex); //NON-NLS
576  return 0L;
577  } finally {
578  logger.log(Level.INFO, "Done buffering: {0}", tempFile.getName()); //NON-NLS
579  }
580  }
581  return 0L;
582  }
583 
584  @Override
585  protected void failed() {
586  super.failed();
587  onDone();
588  }
589 
590  @Override
591  protected void succeeded() {
592  super.succeeded();
593  onDone();
594  }
595 
596  @Override
597  protected void cancelled() {
598  super.cancelled();
599  onDone();
600  }
601 
602  private void onDone() {
603  progressLabel.setText("");
604  try {
605  super.get(); //block and get all exceptions thrown while doInBackground()
606  } catch (CancellationException ex) {
607  logger.log(Level.INFO, "Media buffering was canceled."); //NON-NLS
608  progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.bufferingCancelled"));
609  } catch (InterruptedException ex) {
610  logger.log(Level.INFO, "Media buffering was interrupted."); //NON-NLS
611  progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.bufferingInterrupted"));
612  } catch (Exception ex) {
613  logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS
614  progressLabel.setText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.progress.errorWritingVideoToDisk"));
615  } finally {
616  if (null != progress) {
617  progress.finish();
618  }
619  if (!this.isCancelled()) {
620  logger.log(Level.INFO, "ExtractMedia is done: {0}", tempFile.getName()); //NON-NLS
621  try {
622  mediaPane.mediaPlayer = mediaPane.createMediaPlayer(getMediaUri());
623  mediaView.setMediaPlayer(mediaPane.mediaPlayer);
624  } catch (MediaException ex) {
625  progressLabel.setText("");
626  mediaPane.setInfoLabelText(NbBundle.getMessage(this.getClass(), "FXVideoPanel.media.unsupportedFormat"));
627  }
628  }
629  }
630  }
631  }
632  }
633 
644  @Override
645  public List<VideoFrame> captureFrames(java.io.File file, int numFrames) throws Exception {
646  //What is/was the point of this method /interface.
647  return null;
648  }
649 
650  @Override
651  public String[] getExtensions() {
652  return EXTENSIONS.clone();
653  }
654 
655  @Override
656  public List<String> getMimeTypes() {
657  return MIMETYPES;
658  }
659 }
boolean isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM flag)
static< T > long writeToFile(Content content, java.io.File outputFile, ProgressHandle progress, Future< T > worker, boolean source)
List< VideoFrame > captureFrames(java.io.File file, int numFrames)
synchronized static Logger getLogger(String name)
Definition: Logger.java:161

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