19 package org.sleuthkit.autopsy.contentviewers;
21 import com.google.common.io.Files;
22 import java.awt.event.ActionEvent;
23 import java.awt.event.ActionListener;
25 import java.io.IOException;
26 import java.util.Arrays;
27 import java.util.List;
28 import java.util.SortedSet;
29 import java.util.TreeSet;
30 import java.util.concurrent.CancellationException;
31 import java.util.concurrent.ExecutionException;
32 import java.util.concurrent.TimeUnit;
33 import java.util.logging.Level;
34 import javax.swing.BoxLayout;
35 import javax.swing.JPanel;
36 import javax.swing.SwingWorker;
37 import javax.swing.Timer;
38 import javax.swing.event.ChangeEvent;
39 import org.freedesktop.gstreamer.Bus;
40 import org.freedesktop.gstreamer.ClockTime;
41 import org.freedesktop.gstreamer.Gst;
42 import org.freedesktop.gstreamer.GstObject;
43 import org.freedesktop.gstreamer.State;
44 import org.freedesktop.gstreamer.elements.PlayBin;
45 import org.netbeans.api.progress.ProgressHandle;
46 import org.openide.util.NbBundle;
54 import javafx.embed.swing.JFXPanel;
55 import javax.swing.event.ChangeListener;
56 import org.freedesktop.gstreamer.GstException;
62 @SuppressWarnings(
"PMD.SingularField")
63 public class
MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaViewPanel {
66 private static final String[] FILE_EXTENSIONS =
new String[]{
95 private static final List<String> MIME_TYPES = Arrays.asList(
128 "video/x-intel-h263",
144 "video/x-msvideocodec",
169 private static final String MEDIA_PLAYER_ERROR_STRING = NbBundle.getMessage(
MediaPlayerPanel.class,
170 "GstVideoPanel.cannotProcFile.err");
181 private static final int PROGRESS_SLIDER_SIZE = 2000;
191 customizeComponents();
195 progressSlider.setEnabled(
false);
196 progressSlider.setMinimum(0);
197 progressSlider.setMaximum(PROGRESS_SLIDER_SIZE);
198 progressSlider.setValue(0);
200 progressSlider.addChangeListener(
new ChangeListener() {
202 public void stateChanged(ChangeEvent e) {
203 if (progressSlider.getValueIsAdjusting()) {
204 long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
205 double relativePosition = progressSlider.getValue() * 1.0 / PROGRESS_SLIDER_SIZE;
206 long newPos = (long) (relativePosition * duration);
207 gstPlayBin.seek(newPos, TimeUnit.NANOSECONDS);
210 updateTimeLabel(newPos, duration);
215 audioSlider.addChangeListener((ChangeEvent event) -> {
216 if (audioSlider.getValueIsAdjusting()) {
217 double audioPercent = (audioSlider.getValue() * 2.0) / 100.0;
218 gstPlayBin.setVolume(audioPercent);
221 errorListener =
new Bus.ERROR() {
223 public void errorMessage(GstObject go,
int i, String
string) {
224 enableComponents(
false);
225 infoLabel.setText(String.format(
226 "<html><font color='red'>%s</font></html>",
227 MEDIA_PLAYER_ERROR_STRING));
231 stateChangeListener =
new Bus.STATE_CHANGED() {
233 public void stateChanged(GstObject go, State oldState, State currentState, State pendingState) {
234 if (State.PLAYING.equals(currentState)) {
235 playButton.setText(
"||");
237 playButton.setText(
"►");
241 endOfStreamListener =
new Bus.EOS() {
243 public void endOfStream(GstObject go) {
244 gstPlayBin.seek(ClockTime.ZERO);
245 progressSlider.setValue(0);
249 Gst.getExecutor().submit(() -> gstPlayBin.pause());
254 private void initGst() throws GstException, UnsatisfiedLinkError {
255 logger.log(Level.INFO,
"Attempting initializing of gstreamer for video/audio viewing");
265 @NbBundle.Messages({
"GstVideoPanel.noOpenCase.errMsg=No open case available."})
266 void loadFile(
final AbstractFile file) {
268 infoLabel.setText(
"");
269 if (file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC)) {
270 infoLabel.setText(NbBundle.getMessage(
this.getClass(),
"GstVideoPanel.setupVideo.infoLabel.text"));
276 extractMediaWorker =
new ExtractMedia(file, VideoUtils.getVideoFileInTempDir(file));
277 extractMediaWorker.execute();
278 }
catch (NoCurrentCaseException ex) {
279 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
280 infoLabel.setText(String.format(
"<html><font color='red'>%s</font></html>", Bundle.GstVideoPanel_noOpenCase_errMsg()));
281 enableComponents(
false);
290 "MediaPlayerPanel.noSupport=File not supported."
292 void resetComponents() {
293 progressLabel.setText(String.format(
"%s/%s", Bundle.MediaPlayerPanel_unknownTime(),
294 Bundle.MediaPlayerPanel_unknownTime()));
295 infoLabel.setText(Bundle.MediaPlayerPanel_noSupport());
296 progressSlider.setValue(0);
303 if (extractMediaWorker != null) {
304 extractMediaWorker.cancel(
true);
307 if (gstPlayBin != null) {
309 gstPlayBin.getBus().disconnect(endOfStreamListener);
310 gstPlayBin.getBus().disconnect(endOfStreamListener);
311 gstPlayBin.getBus().disconnect(endOfStreamListener);
312 gstPlayBin.dispose();
316 videoPanel.removeAll();
318 enableComponents(
false);
322 playButton.setEnabled(isEnabled);
323 progressSlider.setEnabled(isEnabled);
324 videoPanel.setEnabled(isEnabled);
325 audioSlider.setEnabled(isEnabled);
330 return Arrays.asList(FILE_EXTENSIONS.clone());
340 String extension = file.getNameExtension();
357 if (getSupportedExtensions().contains(
"." + extension)) {
358 SortedSet<String> mimeTypes =
new TreeSet<>(getSupportedMimeTypes());
361 return mimeTypes.contains(mimeType);
363 logger.log(Level.WARNING,
"Failed to look up mimetype for " + file.getName() +
" using FileTypeDetector. Fallingback on AbstractFile.isMimeType", ex);
364 if (!mimeTypes.isEmpty() && file.isMimeType(mimeTypes) == AbstractFile.MimeMatchEnum.TRUE) {
369 return getSupportedExtensions().contains(
"." + extension);
382 progressLabel.setText(formatTime(start,
false) +
"/" + formatTime(total,
true));
389 "MediaPlayerPanel.unknownTime=Unknown",
390 "MediaPlayerPanel.timeFormat=%02d:%02d:%02d"
394 return Bundle.MediaPlayerPanel_unknownTime();
397 double millis = ns / 1000000.0;
400 seconds = Math.ceil(millis / 1000);
402 seconds = millis / 1000;
404 double hours = seconds / 3600;
405 seconds -= (int) hours * 3600;
406 double minutes = seconds / 60;
407 seconds -= (int) minutes * 60;
409 return String.format(Bundle.MediaPlayerPanel_timeFormat(), (int) hours, (
int) minutes, (int) seconds);
423 this.sourceFile = sFile;
424 this.tempFile = jFile;
429 if (!tempFile.exists() || tempFile.length() < sourceFile.getSize()) {
430 progress = ProgressHandle.createHandle(NbBundle.getMessage(
MediaPlayerPanel.class,
"GstVideoPanel.ExtractMedia.progress.buffering", sourceFile.getName()), () -> this.cancel(
true));
431 progressLabel.setText(NbBundle.getMessage(
this.getClass(),
"GstVideoPanel.progress.buffering"));
434 Files.createParentDirs(tempFile);
436 }
catch (IOException ex) {
437 logger.log(Level.WARNING,
"Error creating parent directory for copying video/audio in temp directory", ex);
453 if(this.isCancelled()) {
457 gstPlayBin =
new PlayBin(
"VideoPlayer", tempFile.toURI());
459 Bus playBinBus = gstPlayBin.getBus();
460 playBinBus.connect(endOfStreamListener);
461 playBinBus.connect(stateChangeListener);
462 playBinBus.connect(errorListener);
464 if(this.isCancelled()) {
468 JFXPanel fxPanel =
new JFXPanel();
469 videoPanel.removeAll();
470 videoPanel.setLayout(
new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
471 videoPanel.add(fxPanel);
472 fxAppSink =
new JavaFxAppSink(
"JavaFxAppSink", fxPanel);
473 gstPlayBin.setVideoSink(fxAppSink);
475 if(this.isCancelled()) {
479 gstPlayBin.setVolume((audioSlider.getValue() * 2.0) / 100.0);
483 enableComponents(
true);
484 }
catch (CancellationException ex) {
485 logger.log(Level.INFO,
"Media buffering was canceled.");
486 }
catch (InterruptedException ex) {
487 logger.log(Level.INFO,
"Media buffering was interrupted.");
488 }
catch (ExecutionException ex) {
489 logger.log(Level.SEVERE,
"Fatal error during media buffering.", ex);
501 if (!progressSlider.getValueIsAdjusting()) {
502 long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
503 long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
509 if (duration != -1) {
510 double relativePosition = (double) position / duration;
511 progressSlider.setValue((
int) (relativePosition * PROGRESS_SLIDER_SIZE));
514 updateTimeLabel(position, duration);
524 @SuppressWarnings(
"unchecked")
526 private
void initComponents() {
528 videoPanel =
new javax.swing.JPanel();
529 controlPanel =
new javax.swing.JPanel();
530 progressSlider =
new javax.swing.JSlider();
531 infoLabel =
new javax.swing.JLabel();
532 playButton =
new javax.swing.JButton();
533 progressLabel =
new javax.swing.JLabel();
534 VolumeIcon =
new javax.swing.JLabel();
535 audioSlider =
new javax.swing.JSlider();
537 javax.swing.GroupLayout videoPanelLayout =
new javax.swing.GroupLayout(videoPanel);
538 videoPanel.setLayout(videoPanelLayout);
539 videoPanelLayout.setHorizontalGroup(
540 videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
541 .addGap(0, 0, Short.MAX_VALUE)
543 videoPanelLayout.setVerticalGroup(
544 videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
545 .addGap(0, 259, Short.MAX_VALUE)
548 progressSlider.setValue(0);
549 progressSlider.setCursor(
new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
550 progressSlider.setDoubleBuffered(
true);
551 progressSlider.setMinimumSize(
new java.awt.Dimension(36, 21));
552 progressSlider.setPreferredSize(
new java.awt.Dimension(200, 21));
554 org.openide.awt.Mnemonics.setLocalizedText(infoLabel,
org.openide.util.NbBundle.getMessage(
MediaPlayerPanel.class,
"MediaPlayerPanel.infoLabel.text"));
555 infoLabel.setCursor(
new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
557 org.openide.awt.Mnemonics.setLocalizedText(playButton,
org.openide.util.NbBundle.getMessage(
MediaPlayerPanel.class,
"MediaPlayerPanel.playButton.text"));
558 playButton.addActionListener(
new java.awt.event.ActionListener() {
559 public void actionPerformed(java.awt.event.ActionEvent evt) {
560 playButtonActionPerformed(evt);
564 org.openide.awt.Mnemonics.setLocalizedText(progressLabel,
org.openide.util.NbBundle.getMessage(
MediaPlayerPanel.class,
"MediaPlayerPanel.progressLabel.text"));
566 org.openide.awt.Mnemonics.setLocalizedText(VolumeIcon,
org.openide.util.NbBundle.getMessage(
MediaPlayerPanel.class,
"MediaPlayerPanel.VolumeIcon.text"));
568 audioSlider.setMajorTickSpacing(10);
569 audioSlider.setMaximum(50);
570 audioSlider.setMinorTickSpacing(5);
571 audioSlider.setPaintTicks(
true);
572 audioSlider.setToolTipText(
org.openide.util.NbBundle.getMessage(
MediaPlayerPanel.class,
"MediaPlayerPanel.audioSlider.toolTipText"));
573 audioSlider.setValue(25);
574 audioSlider.setMinimumSize(
new java.awt.Dimension(200, 21));
575 audioSlider.setPreferredSize(
new java.awt.Dimension(200, 21));
577 javax.swing.GroupLayout controlPanelLayout =
new javax.swing.GroupLayout(controlPanel);
578 controlPanel.setLayout(controlPanelLayout);
579 controlPanelLayout.setHorizontalGroup(
580 controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
581 .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, controlPanelLayout.createSequentialGroup()
583 .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
584 .addGroup(controlPanelLayout.createSequentialGroup()
585 .addComponent(playButton, javax.swing.GroupLayout.PREFERRED_SIZE, 64, javax.swing.GroupLayout.PREFERRED_SIZE)
586 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
587 .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 680, Short.MAX_VALUE)
588 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
589 .addComponent(progressLabel))
590 .addGroup(controlPanelLayout.createSequentialGroup()
591 .addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
593 .addComponent(VolumeIcon, javax.swing.GroupLayout.PREFERRED_SIZE, 64, javax.swing.GroupLayout.PREFERRED_SIZE)
595 .addComponent(audioSlider, javax.swing.GroupLayout.PREFERRED_SIZE, 229, javax.swing.GroupLayout.PREFERRED_SIZE)))
598 controlPanelLayout.setVerticalGroup(
599 controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
600 .addGroup(controlPanelLayout.createSequentialGroup()
601 .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
602 .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING,
false)
603 .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
604 .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
605 .addComponent(playButton))
606 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
607 .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
608 .addComponent(audioSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
609 .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
610 .addComponent(VolumeIcon, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
611 .addComponent(infoLabel)))
615 javax.swing.GroupLayout layout =
new javax.swing.GroupLayout(
this);
616 this.setLayout(layout);
617 layout.setHorizontalGroup(
618 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
619 .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
620 .addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
622 layout.setVerticalGroup(
623 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
624 .addGroup(layout.createSequentialGroup()
625 .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
626 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
627 .addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
632 if (gstPlayBin.isPlaying()) {
void actionPerformed(ActionEvent e)
void playButtonActionPerformed(java.awt.event.ActionEvent evt)
javax.swing.JButton playButton
void updateTimeLabel(long start, long total)
boolean isSupported(AbstractFile file)
javax.swing.JSlider progressSlider
ExtractMedia extractMediaWorker
String formatTime(long ns, boolean ceiling)
static< T > long writeToFile(Content content, java.io.File outputFile, ProgressHandle progress, Future< T > worker, boolean source)
String getMIMEType(AbstractFile file)
List< String > getSupportedExtensions()
javax.swing.JPanel videoPanel
List< String > getSupportedMimeTypes()
javax.swing.JLabel progressLabel
void enableComponents(boolean isEnabled)
Bus.EOS endOfStreamListener
javax.swing.JPanel controlPanel
void customizeComponents()
javax.swing.JLabel VolumeIcon
Bus.STATE_CHANGED stateChangeListener
volatile PlayBin gstPlayBin
synchronized static Logger getLogger(String name)
javax.swing.JSlider audioSlider
javax.swing.JLabel infoLabel