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)
83 private static final String[] EXTENSIONS =
new String[]{
".m4v",
".fxm",
".flv",
".m3u8",
".mp4",
".aif",
".aiff",
".mp3",
"m4a",
".wav"};
84 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");
87 private boolean fxInited =
false;
97 Platform.runLater(() -> {
100 Scene fxScene =
new Scene(mediaPane);
101 jFXPanel.setScene(fxScene);
112 void setupVideo(
final AbstractFile file,
final Dimension dims) {
113 if (file.equals(currentFile)) {
116 if (!Case.isCaseOpen()) {
123 final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC);
125 mediaPane.
setInfoLabelText(NbBundle.getMessage(
this.getClass(),
"FXVideoPanel.mediaPane.infoLabel"));
133 path = file.getUniquePath();
134 }
catch (TskCoreException ex) {
135 logger.log(Level.SEVERE,
"Cannot get unique path of video file", ex);
142 tempFile = VideoUtils.getTempVideoFile(currentFile);
143 }
catch (NoCurrentCaseException ex) {
144 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
148 new Thread(mediaPane.new ExtractMedia(currentFile, tempFile)).start();
154 Platform.runLater(() -> {
155 if (mediaPane != null) {
167 @SuppressWarnings(
"unchecked")
169 private
void initComponents() {
171 jFXPanel =
new javafx.embed.swing.JFXPanel();
173 setBackground(
new java.awt.Color(0, 0, 0));
175 javax.swing.GroupLayout jFXPanelLayout =
new javax.swing.GroupLayout(jFXPanel);
176 jFXPanel.setLayout(jFXPanelLayout);
177 jFXPanelLayout.setHorizontalGroup(
178 jFXPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
179 .addGap(0, 400, Short.MAX_VALUE)
181 jFXPanelLayout.setVerticalGroup(
182 jFXPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
183 .addGap(0, 300, Short.MAX_VALUE)
186 javax.swing.GroupLayout layout =
new javax.swing.GroupLayout(
this);
187 this.setLayout(layout);
188 layout.setHorizontalGroup(
189 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
190 .addComponent(jFXPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
192 layout.setVerticalGroup(
193 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
194 .addComponent(jFXPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
245 private final String durationFormat =
"%02d:%02d:%02d/%02d:%02d:%02d ";
247 private static final String PLAY_TEXT =
"►";
249 private static final String PAUSE_TEXT =
"||";
251 private static final String STOP_TEXT =
"X";
255 mediaViewPane =
new HBox();
256 mediaViewPane.setStyle(
"-fx-background-color: black");
257 mediaViewPane.setAlignment(Pos.CENTER);
258 mediaView =
new MediaView();
259 mediaViewPane.getChildren().add(mediaView);
260 setCenter(mediaViewPane);
263 controlPanel =
new VBox();
264 mediaTools =
new HBox();
265 mediaTools.setAlignment(Pos.CENTER);
266 mediaTools.setPadding(
new Insets(5, 10, 5, 10));
268 pauseButton =
new Button(PLAY_TEXT);
269 stopButton =
new Button(STOP_TEXT);
270 mediaTools.getChildren().add(pauseButton);
271 mediaTools.getChildren().add(
new Label(
" "));
272 mediaTools.getChildren().add(stopButton);
273 mediaTools.getChildren().add(
new Label(
" "));
274 progressSlider =
new Slider();
275 HBox.setHgrow(progressSlider, Priority.ALWAYS);
276 progressSlider.setMinWidth(50);
277 progressSlider.setMaxWidth(Double.MAX_VALUE);
278 mediaTools.getChildren().add(progressSlider);
279 progressLabel =
new Label();
280 progressLabel.setPrefWidth(135);
281 progressLabel.setMinWidth(135);
282 mediaTools.getChildren().add(progressLabel);
284 controlPanel.getChildren().add(mediaTools);
285 controlPanel.setStyle(
"-fx-background-color: white");
286 infoLabel =
new Label(
"");
287 controlPanel.getChildren().add(infoLabel);
288 setBottom(controlPanel);
289 setProgressActionListeners();
297 if (mediaPlayer != null) {
298 setInfoLabelText(
"");
299 if (mediaPlayer.getStatus() == Status.PLAYING) {
303 mediaView.setMediaPlayer(null);
314 logger.log(Level.INFO,
"Setting Info Label Text: {0}", text);
315 Platform.runLater(() -> {
316 infoLabel.setText(text);
325 public void setFit(
final Dimension dims) {
326 Platform.runLater(() -> {
327 setPrefSize(dims.getWidth(), dims.getHeight());
330 mediaView.setFitHeight(dims.getHeight() - controlPanel.getHeight());
338 pauseButton.setOnAction(
new EventHandler<ActionEvent>() {
340 public void handle(ActionEvent e) {
341 if (mediaPlayer == null) {
345 Status status = mediaPlayer.getStatus();
359 logger.log(Level.INFO,
"MediaPlayer in unexpected state: {0}", status.toString());
362 setInfoLabelText(NbBundle.getMessage(
this.getClass(),
363 "FXVideoPanel.pauseButton.infoLabel.playbackErr"));
369 stopButton.setOnAction((ActionEvent e) -> {
370 if (mediaPlayer == null) {
377 progressSlider.valueProperty().addListener((Observable o) -> {
378 if (mediaPlayer == null) {
382 if (progressSlider.isValueChanging()) {
383 mediaPlayer.seek(duration.multiply(progressSlider.getValue() / 100.0));
395 progressSlider.setValue(0.0);
396 updateTime(Duration.ZERO);
409 Media media =
new Media(mediaUri);
411 MediaPlayer player =
new MediaPlayer(media);
413 final Runnable pauseListener = () -> {
414 pauseButton.setText(PLAY_TEXT);
416 player.setOnPaused(pauseListener);
417 player.setOnStopped(pauseListener);
418 player.setOnPlaying(() -> {
419 pauseButton.setText(PAUSE_TEXT);
423 player.currentTimeProperty().addListener((observable, oldTime, newTime) -> {
424 updateSlider(newTime);
436 if (mediaPlayer == null) {
439 Duration currentTime = mediaPlayer.getCurrentTime();
440 updateSlider(currentTime);
441 updateTime(currentTime);
450 if (progressSlider != null) {
451 progressSlider.setDisable(currentTime.isUnknown());
452 if (!progressSlider.isDisabled() && duration.greaterThan(Duration.ZERO)
453 && !progressSlider.isValueChanging()) {
454 progressSlider.setValue(currentTime.divide(duration.toMillis()).toMillis() * 100.0);
465 long millisElapsed = (long) currentTime.toMillis();
467 long elapsedHours, elapsedMinutes, elapsedSeconds;
469 long secondsElapsed = millisElapsed / 1000;
470 elapsedHours = (int) secondsElapsed / 3600;
471 secondsElapsed -= elapsedHours * 3600;
472 elapsedMinutes = (int) secondsElapsed / 60;
473 secondsElapsed -= elapsedMinutes * 60;
474 elapsedSeconds = (int) secondsElapsed;
476 String durationStr = String.format(durationFormat,
477 elapsedHours, elapsedMinutes, elapsedSeconds,
478 totalHours, totalMinutes, totalSeconds);
479 Platform.runLater(() -> {
480 progressLabel.setText(durationStr);
485 Platform.runLater(() -> {
486 infoLabel.setTooltip(
new Tooltip(text));
499 if (mediaPlayer == null) {
503 duration = mediaPlayer.getMedia().getDuration();
504 long durationInMillis = (long) mediaPlayer.getMedia().getDuration().toMillis();
507 long durationSeconds = (int) durationInMillis / 1000;
508 totalHours = (int) durationSeconds / 3600;
509 durationSeconds -= totalHours * 3600;
510 totalMinutes = (int) durationSeconds / 60;
511 durationSeconds -= totalMinutes * 60;
512 totalSeconds = (int) durationSeconds;
526 if (mediaPlayer == null) {
530 Duration beginning = mediaPlayer.getStartTime();
533 pauseButton.setText(PLAY_TEXT);
534 updateSlider(beginning);
535 updateTime(beginning);
552 this.sourceFile = sFile;
553 this.tempFile = jFile;
562 return Paths.get(tempFile.getAbsolutePath()).toUri().toString();
566 protected Long
call() throws Exception {
567 if (tempFile.exists() ==
false || tempFile.length() < sourceFile.getSize()) {
568 progress = ProgressHandle.createHandle(
569 NbBundle.getMessage(
this.getClass(),
570 "FXVideoPanel.progress.bufferingFile",
575 Platform.runLater(() -> {
576 progressLabel.setText(NbBundle.getMessage(
this.getClass(),
"FXVideoPanel.progressLabel.buffering"));
581 Files.createParentDirs(tempFile);
583 }
catch (IOException ex) {
584 logger.log(Level.WARNING,
"Error buffering file", ex);
587 logger.log(Level.INFO,
"Done buffering: {0}", tempFile.getName());
612 progressLabel.setText(
"");
615 }
catch (CancellationException ex) {
616 logger.log(Level.INFO,
"Media buffering was canceled.");
617 progressLabel.setText(NbBundle.getMessage(
this.getClass(),
"FXVideoPanel.progress.bufferingCancelled"));
618 }
catch (InterruptedException ex) {
619 logger.log(Level.INFO,
"Media buffering was interrupted.");
620 progressLabel.setText(NbBundle.getMessage(
this.getClass(),
"FXVideoPanel.progress.bufferingInterrupted"));
621 }
catch (Exception ex) {
622 logger.log(Level.SEVERE,
"Fatal error during media buffering.", ex);
623 progressLabel.setText(NbBundle.getMessage(
this.getClass(),
"FXVideoPanel.progress.errorWritingVideoToDisk"));
625 if (null != progress) {
628 if (!this.isCancelled()) {
629 logger.log(Level.INFO,
"ExtractMedia is done: {0}", tempFile.getName());
633 }
catch (MediaException ex) {
634 progressLabel.setText(
"");
635 mediaPane.
setInfoLabelText(NbBundle.getMessage(
this.getClass(),
"FXVideoPanel.media.unsupportedFormat"));
654 public List<VideoFrame>
captureFrames(java.io.File file,
int numFrames)
throws Exception {
661 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)