mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-17 18:17:43 +00:00
295 lines
9.9 KiB
Java
Executable File
295 lines
9.9 KiB
Java
Executable File
/*
|
|
* Autopsy Forensic Browser
|
|
*
|
|
* Copyright 2013 Basis Technology Corp.
|
|
* Contact: carrier <at> sleuthkit <dot> org
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package org.sleuthkit.autopsy.imagegallery.gui;
|
|
|
|
import java.net.URL;
|
|
import java.util.ResourceBundle;
|
|
import java.util.logging.Level;
|
|
import javafx.application.Platform;
|
|
import javafx.beans.InvalidationListener;
|
|
import javafx.beans.Observable;
|
|
import javafx.event.ActionEvent;
|
|
import javafx.fxml.FXML;
|
|
import javafx.scene.control.Button;
|
|
import javafx.scene.control.Label;
|
|
import javafx.scene.control.Slider;
|
|
import javafx.scene.image.Image;
|
|
import javafx.scene.image.ImageView;
|
|
import javafx.scene.layout.BorderPane;
|
|
import javafx.scene.layout.HBox;
|
|
import javafx.scene.media.MediaException;
|
|
import javafx.scene.media.MediaPlayer;
|
|
import javafx.scene.media.MediaPlayer.Status;
|
|
import javafx.scene.media.MediaView;
|
|
import javafx.scene.text.Text;
|
|
import javafx.util.Duration;
|
|
import javafx.util.StringConverter;
|
|
import org.openide.util.NbBundle;
|
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
|
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
|
import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile;
|
|
|
|
public class VideoPlayer extends BorderPane {
|
|
|
|
private static final Image VOLUME_HIGH = new Image("/org/sleuthkit/autopsy/imagegallery/images/speaker-volume.png"); //NON-NLS
|
|
private static final Image VOLUME_LOW = new Image("/org/sleuthkit/autopsy/imagegallery/images/speaker-volume-low.png"); //NON-NLS
|
|
private static final Image VOLUME_ZERO = new Image("/org/sleuthkit/autopsy/imagegallery/images/speaker-volume-none.png"); //NON-NLS
|
|
private static final Image VOLUME_MUTE = new Image("/org/sleuthkit/autopsy/imagegallery/images/speaker-volume-control-mute.png"); //NON-NLS
|
|
|
|
private static final Image PLAY = new Image("/org/sleuthkit/autopsy/imagegallery/images/media_controls_play_small.png", true); //NON-NLS
|
|
private static final Image PAUSE = new Image("/org/sleuthkit/autopsy/imagegallery/images/media_controls_pause_small.png", true); //NON-NLS
|
|
|
|
private final MediaPlayer mp;
|
|
|
|
private final boolean repeat = false;
|
|
|
|
private boolean stopRequested = false;
|
|
|
|
private boolean atEndOfMedia = false;
|
|
|
|
private Duration duration;
|
|
|
|
@FXML
|
|
private MediaView mediaView;
|
|
|
|
@FXML
|
|
private ResourceBundle resources;
|
|
|
|
@FXML
|
|
private URL location;
|
|
|
|
@FXML
|
|
private Button controlButton;
|
|
|
|
@FXML
|
|
private Slider timeSlider;
|
|
|
|
@FXML
|
|
private Slider volumeSlider;
|
|
|
|
@FXML
|
|
private Label timeLabel;
|
|
|
|
@FXML
|
|
private ImageView controlImageView;
|
|
|
|
@FXML
|
|
private ImageView volumeImageView;
|
|
|
|
@FXML
|
|
private HBox playControlBar;
|
|
|
|
@FXML
|
|
private Button volumeButton;
|
|
|
|
InvalidationListener seekListener = new InvalidationListener() {
|
|
@Override
|
|
public void invalidated(Observable ov) {
|
|
mp.seek(Duration.millis(timeSlider.getValue()));
|
|
}
|
|
};
|
|
private final VideoFile file;
|
|
|
|
@FXML
|
|
@NbBundle.Messages({"# {0} - exception type",
|
|
"VideoPlayer.errNotice={0}\nSee the logs for details."})
|
|
void initialize() {
|
|
assert controlButton != null : "fx:id=\"controlButton\" was not injected: check your FXML file 'MediaControl.fxml'.";
|
|
assert timeSlider != null : "fx:id=\"timeSlider\" was not injected: check your FXML file 'MediaControl.fxml'.";
|
|
assert volumeSlider != null : "fx:id=\"volumeSlider\" was not injected: check your FXML file 'MediaControl.fxml'.";
|
|
mp.errorProperty().addListener((Observable observable) -> {
|
|
final MediaException ex = mp.getError();
|
|
if (ex != null) {
|
|
Platform.runLater(() -> {
|
|
Logger.getLogger(VideoFile.class.getName()).log(Level.WARNING, ex.getType() + " Failed to initialize MediaControl for file " + file.getName(), ex); //NON-NLS
|
|
setCenter(new Text(Bundle.VideoPlayer_errNotice(ex.getType())));
|
|
setBottom(null);
|
|
});
|
|
}
|
|
});
|
|
mp.statusProperty().addListener((observableStatus, oldStatus, newStatus) -> {
|
|
Logger.getAnonymousLogger().log(Level.INFO, "media player: {0}", newStatus); //NON-NLS
|
|
});
|
|
mediaView.setMediaPlayer(mp);
|
|
mediaView.fitHeightProperty().bind(this.heightProperty().subtract(playControlBar.heightProperty()));
|
|
mediaView.fitWidthProperty().bind(this.widthProperty());
|
|
|
|
controlButton.setOnAction((ActionEvent e) -> {
|
|
Status status = mp.getStatus();
|
|
switch (status) {
|
|
case UNKNOWN:
|
|
case HALTED:
|
|
// don't do anything in these states
|
|
return;
|
|
case PAUSED:
|
|
case READY:
|
|
case STOPPED:
|
|
// rewind the movie if we're sitting at the end
|
|
if (atEndOfMedia) {
|
|
mp.seek(mp.getStartTime());
|
|
atEndOfMedia = false;
|
|
}
|
|
mp.play();
|
|
break;
|
|
default:
|
|
mp.pause();
|
|
}
|
|
});
|
|
|
|
mp.currentTimeProperty().addListener((Observable ov) -> {
|
|
updateTime();
|
|
});
|
|
|
|
mp.setOnPlaying(() -> {
|
|
if (stopRequested) {
|
|
mp.pause();
|
|
stopRequested = false;
|
|
} else {
|
|
controlImageView.setImage(PAUSE);
|
|
}
|
|
});
|
|
|
|
mp.setOnPaused(() -> {
|
|
controlImageView.setImage(PLAY);
|
|
});
|
|
|
|
mp.setOnReady(() -> {
|
|
duration = mp.getMedia().getDuration();
|
|
timeSlider.setMax(duration.toMillis());
|
|
timeSlider.setMajorTickUnit(duration.toMillis());
|
|
updateTime();
|
|
updateVolume();
|
|
});
|
|
|
|
mp.setCycleCount(repeat ? MediaPlayer.INDEFINITE : 1);
|
|
mp.setOnEndOfMedia(() -> {
|
|
if (!repeat) {
|
|
controlImageView.setImage(PLAY);
|
|
stopRequested = true;
|
|
atEndOfMedia = true;
|
|
}
|
|
});
|
|
|
|
mp.volumeProperty().addListener((observable, oldVolume, newVolume) -> {
|
|
setVolumeIcon(newVolume);
|
|
});
|
|
|
|
mp.muteProperty().addListener((observable, oldMute, newMute) -> {
|
|
if (newMute) {
|
|
volumeImageView.setImage(VOLUME_MUTE);
|
|
} else {
|
|
setVolumeIcon(mp.getVolume());
|
|
}
|
|
});
|
|
|
|
timeSlider.valueProperty().addListener(seekListener);
|
|
|
|
// Add time slider
|
|
timeSlider.setLabelFormatter(new StringConverter<Double>() {
|
|
@Override
|
|
public String toString(Double object) {
|
|
return formatTime(Duration.millis(object));
|
|
}
|
|
|
|
@Override
|
|
public Double fromString(String string) {
|
|
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
|
}
|
|
});
|
|
|
|
// Add Volume slider
|
|
volumeSlider.valueProperty().addListener((Observable ov) -> {
|
|
if (volumeSlider.isValueChanging()) {
|
|
mp.setVolume(volumeSlider.getValue());
|
|
mp.setMute(false);
|
|
}
|
|
});
|
|
|
|
volumeButton.setOnAction(event -> {
|
|
mp.setMute(!mp.isMute());
|
|
});
|
|
}
|
|
|
|
private void setVolumeIcon(Number newVolume) {
|
|
if (newVolume.doubleValue() < .1) {
|
|
volumeImageView.setImage(VOLUME_ZERO);
|
|
} else if (newVolume.doubleValue() <= .6) {
|
|
volumeImageView.setImage(VOLUME_LOW);
|
|
} else {
|
|
volumeImageView.setImage(VOLUME_HIGH);
|
|
}
|
|
}
|
|
|
|
public VideoPlayer(MediaPlayer mp, VideoFile file) {
|
|
this.file = file;
|
|
this.mp = mp;
|
|
FXMLConstructor.construct(this, "MediaControl.fxml"); //NON-NLS
|
|
}
|
|
|
|
protected void updateTime() {
|
|
Platform.runLater(() -> {
|
|
Duration currentTime = mp.getCurrentTime();
|
|
timeSlider.setDisable(duration.isUnknown());
|
|
timeLabel.setText(formatTime(currentTime));
|
|
if (!timeSlider.isDisabled()
|
|
&& duration.greaterThan(Duration.ZERO)
|
|
&& !timeSlider.isValueChanging()) {
|
|
timeSlider.valueProperty().removeListener(seekListener);
|
|
timeSlider.setValue(currentTime.toMillis());
|
|
timeSlider.valueProperty().addListener(seekListener);
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
private void updateVolume() {
|
|
Platform.runLater(() -> {
|
|
final double volume = mp.getVolume();
|
|
|
|
volumeSlider.setValue(volume);
|
|
|
|
if (mp.isMute()) {
|
|
volumeImageView.setImage(VOLUME_MUTE);
|
|
} else {
|
|
setVolumeIcon(volume);
|
|
}
|
|
});
|
|
}
|
|
|
|
private static String formatTime(Duration elapsed) {
|
|
int totalSeconds = (int) Math.floor(elapsed.toSeconds());
|
|
int elapsedHours = totalSeconds / (60 * 60);
|
|
totalSeconds -= elapsedHours * 60 * 60;
|
|
int elapsedMinutes = totalSeconds / 60;
|
|
int elapsedSeconds = totalSeconds - elapsedMinutes * 60;
|
|
|
|
if (elapsedHours > 0) {
|
|
return String.format("%d:%02d:%02d", elapsedHours, //NON-NLS
|
|
elapsedMinutes, elapsedSeconds);
|
|
} else {
|
|
return String.format("%02d:%02d", elapsedMinutes, //NON-NLS
|
|
elapsedSeconds);
|
|
}
|
|
}
|
|
|
|
public void stopVideo() {
|
|
mp.stop();
|
|
}
|
|
}
|