19 package org.sleuthkit.autopsy.contentviewers;
21 import com.google.common.io.Files;
22 import java.awt.Dimension;
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;
76 @ServiceProviders(value = {
77 @ServiceProvider(service = FrameCapture.class)
79 @SuppressWarnings(
"PMD.SingularField")
84 private static final String[] EXTENSIONS =
new String[]{
".m4v",
".fxm",
".flv",
".m3u8",
".mp4",
".aif",
".aiff",
".mp3",
"m4a",
".wav"};
85 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");
88 private boolean fxInited =
false;
98 Platform.runLater(() -> {
101 Scene fxScene =
new Scene(mediaPane);
102 jFXPanel.setScene(fxScene);
113 void setupVideo(
final AbstractFile file,
final Dimension dims) {
114 if (file.equals(currentFile)) {
117 if (!Case.isCaseOpen()) {
124 final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC);
126 mediaPane.
setInfoLabelText(NbBundle.getMessage(
this.getClass(),
"FXVideoPanel.mediaPane.infoLabel"));
134 path = file.getUniquePath();
135 }
catch (TskCoreException ex) {
136 logger.log(Level.SEVERE,
"Cannot get unique path of video file", ex);
143 tempFile = VideoUtils.getVideoFileInTempDir(currentFile);
144 }
catch (NoCurrentCaseException ex) {
145 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
149 new Thread(mediaPane.new ExtractMedia(currentFile, tempFile)).start();
155 Platform.runLater(() -> {
156 if (mediaPane != null) {
168 @SuppressWarnings(
"unchecked")
170 private
void initComponents() {
172 jFXPanel =
new javafx.embed.swing.JFXPanel();
174 setBackground(
new java.awt.Color(0, 0, 0));
176 javax.swing.GroupLayout jFXPanelLayout =
new javax.swing.GroupLayout(jFXPanel);
177 jFXPanel.setLayout(jFXPanelLayout);
178 jFXPanelLayout.setHorizontalGroup(
179 jFXPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
180 .addGap(0, 400, Short.MAX_VALUE)
182 jFXPanelLayout.setVerticalGroup(
183 jFXPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
184 .addGap(0, 300, Short.MAX_VALUE)
187 javax.swing.GroupLayout layout =
new javax.swing.GroupLayout(
this);
188 this.setLayout(layout);
189 layout.setHorizontalGroup(
190 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
191 .addComponent(jFXPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
193 layout.setVerticalGroup(
194 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
195 .addComponent(jFXPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
246 private final String durationFormat =
"%02d:%02d:%02d/%02d:%02d:%02d ";
248 private static final String PLAY_TEXT =
"►";
250 private static final String PAUSE_TEXT =
"||";
252 private static final String STOP_TEXT =
"X";
256 mediaViewPane =
new HBox();
257 mediaViewPane.setStyle(
"-fx-background-color: black");
258 mediaViewPane.setAlignment(Pos.CENTER);
259 mediaView =
new MediaView();
260 mediaViewPane.getChildren().add(mediaView);
261 setCenter(mediaViewPane);
264 controlPanel =
new VBox();
265 mediaTools =
new HBox();
266 mediaTools.setAlignment(Pos.CENTER);
267 mediaTools.setPadding(
new Insets(5, 10, 5, 10));
269 pauseButton =
new Button(PLAY_TEXT);
270 stopButton =
new Button(STOP_TEXT);
271 mediaTools.getChildren().add(pauseButton);
272 mediaTools.getChildren().add(
new Label(
" "));
273 mediaTools.getChildren().add(stopButton);
274 mediaTools.getChildren().add(
new Label(
" "));
275 progressSlider =
new Slider();
276 HBox.setHgrow(progressSlider, Priority.ALWAYS);
277 progressSlider.setMinWidth(50);
278 progressSlider.setMaxWidth(Double.MAX_VALUE);
279 mediaTools.getChildren().add(progressSlider);
280 progressLabel =
new Label();
281 progressLabel.setPrefWidth(135);
282 progressLabel.setMinWidth(135);
283 mediaTools.getChildren().add(progressLabel);
285 controlPanel.getChildren().add(mediaTools);
286 controlPanel.setStyle(
"-fx-background-color: white");
287 infoLabel =
new Label(
"");
288 controlPanel.getChildren().add(infoLabel);
289 setBottom(controlPanel);
290 setProgressActionListeners();
298 if (mediaPlayer != null) {
299 setInfoLabelText(
"");
300 if (mediaPlayer.getStatus() == Status.PLAYING) {
304 mediaView.setMediaPlayer(null);
315 logger.log(Level.INFO,
"Setting Info Label Text: {0}", text);
316 Platform.runLater(() -> {
317 infoLabel.setText(text);
326 public void setFit(
final Dimension dims) {
327 Platform.runLater(() -> {
328 setPrefSize(dims.getWidth(), dims.getHeight());
331 mediaView.setFitHeight(dims.getHeight() - controlPanel.getHeight());
339 pauseButton.setOnAction(
new EventHandler<ActionEvent>() {
341 public void handle(ActionEvent e) {
342 if (mediaPlayer == null) {
346 Status status = mediaPlayer.getStatus();
360 logger.log(Level.INFO,
"MediaPlayer in unexpected state: {0}", status.toString());
363 setInfoLabelText(NbBundle.getMessage(
this.getClass(),
364 "FXVideoPanel.pauseButton.infoLabel.playbackErr"));
370 stopButton.setOnAction((ActionEvent e) -> {
371 if (mediaPlayer == null) {
378 progressSlider.valueProperty().addListener((Observable o) -> {
379 if (mediaPlayer == null) {
383 if (progressSlider.isValueChanging()) {
384 mediaPlayer.seek(duration.multiply(progressSlider.getValue() / 100.0));
396 progressSlider.setValue(0.0);
397 updateTime(Duration.ZERO);
410 Media media =
new Media(mediaUri);
412 MediaPlayer player =
new MediaPlayer(media);
414 final Runnable pauseListener = () -> {
415 pauseButton.setText(PLAY_TEXT);
417 player.setOnPaused(pauseListener);
418 player.setOnStopped(pauseListener);
419 player.setOnPlaying(() -> {
420 pauseButton.setText(PAUSE_TEXT);
424 player.currentTimeProperty().addListener((observable, oldTime, newTime) -> {
425 updateSlider(newTime);
437 if (mediaPlayer == null) {
440 Duration currentTime = mediaPlayer.getCurrentTime();
441 updateSlider(currentTime);
442 updateTime(currentTime);
451 if (progressSlider != null) {
452 progressSlider.setDisable(currentTime.isUnknown());
453 if (!progressSlider.isDisabled() && duration.greaterThan(Duration.ZERO)
454 && !progressSlider.isValueChanging()) {
455 progressSlider.setValue(currentTime.divide(duration.toMillis()).toMillis() * 100.0);
466 long millisElapsed = (long) currentTime.toMillis();
468 long elapsedHours, elapsedMinutes, elapsedSeconds;
470 long secondsElapsed = millisElapsed / 1000;
471 elapsedHours = (int) secondsElapsed / 3600;
472 secondsElapsed -= elapsedHours * 3600;
473 elapsedMinutes = (int) secondsElapsed / 60;
474 secondsElapsed -= elapsedMinutes * 60;
475 elapsedSeconds = (int) secondsElapsed;
477 String durationStr = String.format(durationFormat,
478 elapsedHours, elapsedMinutes, elapsedSeconds,
479 totalHours, totalMinutes, totalSeconds);
480 Platform.runLater(() -> {
481 progressLabel.setText(durationStr);
486 Platform.runLater(() -> {
487 infoLabel.setTooltip(
new Tooltip(text));
500 if (mediaPlayer == null) {
504 duration = mediaPlayer.getMedia().getDuration();
505 long durationInMillis = (long) mediaPlayer.getMedia().getDuration().toMillis();
508 long durationSeconds = (int) durationInMillis / 1000;
509 totalHours = (int) durationSeconds / 3600;
510 durationSeconds -= totalHours * 3600;
511 totalMinutes = (int) durationSeconds / 60;
512 durationSeconds -= totalMinutes * 60;
513 totalSeconds = (int) durationSeconds;
527 if (mediaPlayer == null) {
531 Duration beginning = mediaPlayer.getStartTime();
534 pauseButton.setText(PLAY_TEXT);
535 updateSlider(beginning);
536 updateTime(beginning);
553 this.sourceFile = sFile;
554 this.tempFile = jFile;
563 return Paths.get(tempFile.getAbsolutePath()).toUri().toString();
567 protected Long
call() throws Exception {
568 if (tempFile.exists() ==
false || tempFile.length() < sourceFile.getSize()) {
569 progress = ProgressHandle.createHandle(
570 NbBundle.getMessage(
this.getClass(),
571 "FXVideoPanel.progress.bufferingFile",
576 Platform.runLater(() -> {
577 progressLabel.setText(NbBundle.getMessage(
this.getClass(),
"FXVideoPanel.progressLabel.buffering"));
582 Files.createParentDirs(tempFile);
584 }
catch (IOException ex) {
585 logger.log(Level.WARNING,
"Error buffering file", ex);
588 logger.log(Level.INFO,
"Done buffering: {0}", tempFile.getName());
613 progressLabel.setText(
"");
616 }
catch (CancellationException ex) {
617 logger.log(Level.INFO,
"Media buffering was canceled.");
618 progressLabel.setText(NbBundle.getMessage(
this.getClass(),
"FXVideoPanel.progress.bufferingCancelled"));
619 }
catch (InterruptedException ex) {
620 logger.log(Level.INFO,
"Media buffering was interrupted.");
621 progressLabel.setText(NbBundle.getMessage(
this.getClass(),
"FXVideoPanel.progress.bufferingInterrupted"));
622 }
catch (Exception ex) {
623 logger.log(Level.SEVERE,
"Fatal error during media buffering.", ex);
624 progressLabel.setText(NbBundle.getMessage(
this.getClass(),
"FXVideoPanel.progress.errorWritingVideoToDisk"));
626 if (null != progress) {
629 if (!this.isCancelled()) {
630 logger.log(Level.INFO,
"ExtractMedia is done: {0}", tempFile.getName());
634 }
catch (MediaException ex) {
635 progressLabel.setText(
"");
636 mediaPane.
setInfoLabelText(NbBundle.getMessage(
this.getClass(),
"FXVideoPanel.media.unsupportedFormat"));
655 public List<VideoFrame>
captureFrames(java.io.File file,
int numFrames)
throws Exception {
662 return EXTENSIONS.clone();
void setInfoLabelText(final String text)
static boolean isJavaFxInited()
final Slider progressSlider
void setFit(final Dimension dims)
void setProgressActionListeners()
final MediaView mediaView
static< T > long writeToFile(Content content, java.io.File outputFile, ProgressHandle progress, Future< T > worker, boolean source)
List< String > getMimeTypes()
javafx.embed.swing.JFXPanel jFXPanel
MediaPlayer createMediaPlayer(String mediaUri)
List< VideoFrame > captureFrames(java.io.File file, int numFrames)
final Label progressLabel
synchronized static Logger getLogger(String name)
void updateSlider(Duration currentTime)
void updateTime(Duration currentTime)
void setInfoLabelToolTipText(final String text)