Autopsy 4.22.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
MediaPlayerPanel.java
Go to the documentation of this file.
1/*
2 * Autopsy Forensic Browser
3 *
4 * Copyright 2013-2020 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 */
19package org.sleuthkit.autopsy.contentviewers;
20
21import com.google.common.io.Files;
22import java.awt.Color;
23import java.awt.Dimension;
24import java.awt.Graphics;
25import java.awt.Graphics2D;
26import java.awt.Point;
27import java.awt.Rectangle;
28import java.awt.RenderingHints;
29import java.awt.event.ActionEvent;
30import java.awt.event.ActionListener;
31import java.awt.event.MouseEvent;
32import java.awt.event.MouseListener;
33import java.io.File;
34import java.io.IOException;
35import java.util.Arrays;
36import java.util.EnumSet;
37import java.util.List;
38import java.util.SortedSet;
39import java.util.TreeSet;
40import java.util.concurrent.CancellationException;
41import java.util.concurrent.ExecutionException;
42import java.util.concurrent.Semaphore;
43import java.util.concurrent.TimeUnit;
44import java.util.logging.Level;
45import javax.swing.BoxLayout;
46import javax.swing.JPanel;
47import javax.swing.SwingWorker;
48import javax.swing.Timer;
49import javax.swing.event.ChangeEvent;
50import org.freedesktop.gstreamer.Bus;
51import org.freedesktop.gstreamer.Gst;
52import org.freedesktop.gstreamer.GstObject;
53import org.freedesktop.gstreamer.State;
54import org.freedesktop.gstreamer.elements.PlayBin;
55import org.netbeans.api.progress.ProgressHandle;
56import org.openide.util.NbBundle;
57import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
58import org.sleuthkit.autopsy.coreutils.Logger;
59import org.sleuthkit.autopsy.coreutils.VideoUtils;
60import org.sleuthkit.autopsy.datamodel.ContentUtils;
61import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
62import org.sleuthkit.datamodel.AbstractFile;
63import org.sleuthkit.datamodel.TskData;
64import javafx.embed.swing.JFXPanel;
65import javax.swing.ImageIcon;
66import javax.swing.JComponent;
67import javax.swing.JSlider;
68import javax.swing.SwingUtilities;
69import javax.swing.event.ChangeListener;
70import javax.swing.plaf.basic.BasicSliderUI;
71import javax.swing.plaf.basic.BasicSliderUI.TrackListener;
72import org.freedesktop.gstreamer.ClockTime;
73import org.freedesktop.gstreamer.Format;
74import org.freedesktop.gstreamer.GstException;
75import org.freedesktop.gstreamer.event.SeekFlags;
76import org.freedesktop.gstreamer.event.SeekType;
77import org.sleuthkit.autopsy.contentviewers.utils.GstLoader;
78import org.sleuthkit.autopsy.contentviewers.utils.GstLoader.GstStatus;
79import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
80
85@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
86public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaViewPanel {
87
88 //Enumerate the accepted file extensions and mimetypes
89 private static final String[] FILE_EXTENSIONS = new String[]{
90 ".3g2",
91 ".3gp",
92 ".3gpp",
93 ".aac",
94 ".aif",
95 ".aiff",
96 ".amr",
97 ".asf",
98 ".au",
99 ".avi",
100 ".flac",
101 ".flv",
102 ".m4a",
103 ".m4v",
104 ".mka",
105 ".mkv",
106 ".mov",
107 ".mp2",
108 ".mp3",
109 ".mp4",
110 ".mpeg",
111 ".mpg",
112 ".mxf",
113 ".ogg",
114 ".wav",
115 ".webm",
116 ".wma",
117 ".wmv"}; //NON-NLS
118 private static final List<String> MIME_TYPES = Arrays.asList(
119 "video/3gpp",
120 "video/3gpp2",
121 "audio/aiff",
122 "audio/amr-wb",
123 "audio/basic",
124 "audio/mp4",
125 "video/mp4",
126 "audio/mpeg",
127 "video/mpeg",
128 "audio/mpeg3",
129 "application/mxf",
130 "application/ogg",
131 "video/quicktime",
132 "audio/vorbis",
133 "audio/vnd.wave",
134 "video/webm",
135 "video/x-3ivx",
136 "audio/x-aac",
137 "audio/x-adpcm",
138 "audio/x-alaw",
139 "audio/x-cinepak",
140 "video/x-divx",
141 "audio/x-dv",
142 "video/x-dv",
143 "video/x-ffv",
144 "audio/x-flac",
145 "video/x-flv",
146 "audio/x-gsm",
147 "video/x-h263",
148 "video/x-h264",
149 "video/x-huffyuv",
150 "video/x-indeo",
151 "video/x-intel-h263",
152 "audio/x-ircam",
153 "video/x-jpeg",
154 "audio/x-m4a",
155 "video/x-m4v",
156 "audio/x-mace",
157 "audio/x-matroska",
158 "video/x-matroska",
159 "audio/x-mpeg",
160 "video/x-mpeg",
161 "audio/x-mpeg-3",
162 "video/x-ms-asf",
163 "audio/x-ms-wma",
164 "video/x-ms-wmv",
165 "video/x-msmpeg",
166 "video/x-msvideo",
167 "video/x-msvideocodec",
168 "audio/x-mulaw",
169 "audio/x-nist",
170 "audio/x-oggflac",
171 "audio/x-paris",
172 "audio/x-qdm2",
173 "audio/x-raw",
174 "video/x-raw",
175 "video/x-rle",
176 "audio/x-speex",
177 "video/x-svq",
178 "audio/x-svx",
179 "video/x-tarkin",
180 "video/x-theora",
181 "audio/x-voc",
182 "audio/x-vorbis",
183 "video/x-vp3",
184 "audio/x-w64",
185 "audio/x-wav",
186 "audio/x-wma",
187 "video/x-wmv",
188 "video/x-xvid"
189 ); //NON-NLS
190
191 private static final Logger logger = Logger.getLogger(MediaPlayerPanel.class.getName());
192 private static final String MEDIA_PLAYER_ERROR_STRING = NbBundle.getMessage(MediaPlayerPanel.class,
193 "GstVideoPanel.cannotProcFile.err");
194
195 //Video playback components
196 private volatile PlayBin gstPlayBin;
197 private JavaFxAppSink fxAppSink;
198 private Bus.ERROR errorListener;
199 private Bus.STATE_CHANGED stateChangeListener;
200 private Bus.EOS endOfStreamListener;
201
202 //Update progress bar and time label during video playback
203 //Updating every 16 MS = 62.5 FPS.
204 private final Timer timer = new Timer(16, new VideoPanelUpdater());
205 private static final int PROGRESS_SLIDER_SIZE = 2000;
206 private static final int SKIP_IN_SECONDS = 30;
207
208 private final ImageIcon playIcon = new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/contentviewers/images/Play-arrow-01.png"));
209 private final ImageIcon pauseIcon = new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/contentviewers/images/Pause-01.png"));
210
212
213 //Serialize setting the value of the Video progress slider.
214 //The slider is a shared resource between the VideoPanelUpdater
215 //and the TrackListener on the slider itself.
216 private final Semaphore sliderLock;
217
218 private static volatile boolean IS_GST_ENABLED = true;
219
223 public MediaPlayerPanel() throws GstException, UnsatisfiedLinkError {
226 //True for fairness. In other words,
227 //acquire() calls are processed in order of invocation.
228 sliderLock = new Semaphore(1, true);
229 }
230
231 private void customizeComponents() {
232 enableComponents(false);
233 progressSlider.setMinimum(0);
235 progressSlider.setValue(0);
236 //Manage the gstreamer video position when a user is dragging the slider in the panel.
237 progressSlider.addChangeListener(new ChangeListener() {
238 @Override
239 public void stateChanged(ChangeEvent e) {
240 if (progressSlider.getValueIsAdjusting() && gstPlayBin != null) {
241 long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
242 double relativePosition = progressSlider.getValue() * 1.0 / PROGRESS_SLIDER_SIZE;
243 long newStartTime = (long) (relativePosition * duration);
244 double playBackRate = getPlayBackRate();
245 gstPlayBin.seek(playBackRate,
246 Format.TIME,
247 //FLUSH - flushes the pipeline
248 //ACCURATE - video will seek exactly to the position requested
249 EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE),
250 //Set the start position to newTime
251 SeekType.SET, newStartTime,
252 //Do nothing for the end position
253 SeekType.NONE, -1);
254 //Keep constantly updating the time label so users have a sense of
255 //where the slider they are dragging is in relation to the video time
256 updateTimeLabel(newStartTime, duration);
257 }
258 }
259 });
260 //Manage the video while the user is performing actions on the track.
261 progressSlider.addMouseListener(new MouseListener() {
262 private State previousState = State.NULL;
263
264 @Override
265 public void mousePressed(MouseEvent e) {
266 if (gstPlayBin != null) {
267 previousState = gstPlayBin.getState();
268 gstPlayBin.pause();
269 }
270 }
271
272 @Override
273 public void mouseReleased(MouseEvent e) {
274 if (previousState.equals(State.PLAYING) && gstPlayBin != null) {
275 gstPlayBin.play();
276 }
277 previousState = State.NULL;
278 }
279
280 @Override
281 public void mouseClicked(MouseEvent e) {
282 }
283
284 @Override
285 public void mouseEntered(MouseEvent e) {
286 }
287
288 @Override
289 public void mouseExited(MouseEvent e) {
290 }
291
292 });
293 //Manage the audio level when the user is adjusting the volume slider
294 audioSlider.addChangeListener((ChangeEvent event) -> {
295 if (audioSlider.getValueIsAdjusting() && gstPlayBin != null) {
296 double audioPercent = (audioSlider.getValue() * 2.0) / 100.0;
297 gstPlayBin.setVolume(audioPercent);
298 }
299 });
300 errorListener = new Bus.ERROR() {
301 @Override
302 public void errorMessage(GstObject go, int i, String string) {
303 SwingUtilities.invokeLater(() -> {
304 enableComponents(false);
305 infoLabel.setText(String.format(
306 "<html><font color='red'>%s</font></html>",
308
309 progressLabel.setText("");
310 });
311 timer.stop();
312 }
313 };
314 stateChangeListener = new Bus.STATE_CHANGED() {
315 @Override
316 public void stateChanged(GstObject go, State oldState, State currentState, State pendingState) {
317 if (State.PLAYING.equals(currentState)) {
318 SwingUtilities.invokeLater(() -> {
319 playButton.setIcon(pauseIcon);
320 });
321 } else {
322 SwingUtilities.invokeLater(() -> {
323 playButton.setIcon(playIcon);
324 });
325 }
326 }
327 };
328 endOfStreamListener = new Bus.EOS() {
329 @Override
330 public void endOfStream(GstObject go) {
331 if (gstPlayBin != null) {
332 gstPlayBin.seek(ClockTime.ZERO);
336 Gst.getExecutor().submit(() -> gstPlayBin.pause());
337 }
338 }
339 };
340 }
341
348 @NbBundle.Messages({"GstVideoPanel.noOpenCase.errMsg=No open case available."})
349 void loadFile(final AbstractFile file) {
350 //Ensure everything is back in the initial state
351 infoLabel.setText("");
352 if (file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC)) {
353 infoLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.setupVideo.infoLabel.text"));
354 return;
355 }
356
357 try {
358 //Pushing off initialization to the background
359 extractMediaWorker = new ExtractMedia(file, VideoUtils.getVideoFileInTempDir(file));
360 extractMediaWorker.execute();
361 } catch (NoCurrentCaseException ex) {
362 logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
363 infoLabel.setText(String.format("<html><font color='red'>%s</font></html>", Bundle.GstVideoPanel_noOpenCase_errMsg()));
364 enableComponents(false);
365 }
366 }
367
372 @NbBundle.Messages({
373 "MediaPlayerPanel.noSupport=File not supported."
374 })
375 void resetComponents() {
376 progressLabel.setText(String.format("%s/%s", Bundle.MediaPlayerPanel_unknownTime(),
377 Bundle.MediaPlayerPanel_unknownTime()));
378 infoLabel.setText(Bundle.MediaPlayerPanel_noSupport());
379 progressSlider.setValue(0);
380 }
381
385 void reset() {
386 if (extractMediaWorker != null) {
387 extractMediaWorker.cancel(true);
388 }
389 timer.stop();
390 if (gstPlayBin != null) {
391 Gst.getExecutor().submit(() -> {
392 gstPlayBin.stop();
393 gstPlayBin.getBus().disconnect(endOfStreamListener);
394 gstPlayBin.getBus().disconnect(stateChangeListener);
395 gstPlayBin.getBus().disconnect(errorListener);
396 gstPlayBin.getBus().dispose();
397 gstPlayBin.dispose();
398 fxAppSink.clear();
399 gstPlayBin = null;
400 });
401 }
402 videoPanel.removeAll();
403 resetComponents();
404 enableComponents(false);
405 }
406
407 private void enableComponents(boolean isEnabled) {
408 playButton.setEnabled(isEnabled);
409 progressSlider.setEnabled(isEnabled);
410 videoPanel.setEnabled(isEnabled);
411 audioSlider.setEnabled(isEnabled);
412 rewindButton.setEnabled(isEnabled);
413 fastForwardButton.setEnabled(isEnabled);
414 playBackSpeedComboBox.setEnabled(isEnabled);
415 }
416
417 @Override
418 public List<String> getSupportedExtensions() {
419 return Arrays.asList(FILE_EXTENSIONS.clone());
420 }
421
422 @Override
423 public List<String> getSupportedMimeTypes() {
424 return MIME_TYPES;
425 }
426
427 @Override
428 public boolean isSupported(AbstractFile file) {
429 if (!IS_GST_ENABLED) {
430 return false;
431 }
432
433 String extension = file.getNameExtension();
450 if (getSupportedExtensions().contains("." + extension)) {
451 SortedSet<String> mimeTypes = new TreeSet<>(getSupportedMimeTypes());
452 try {
453 String mimeType = new FileTypeDetector().getMIMEType(file);
454 return mimeTypes.contains(mimeType);
456 logger.log(Level.WARNING, "Failed to look up mimetype for " + file.getName() + " using FileTypeDetector. Fallingback on AbstractFile.isMimeType", ex);
457 if (!mimeTypes.isEmpty() && file.isMimeType(mimeTypes) == AbstractFile.MimeMatchEnum.TRUE) {
458 return true;
459 }
460 }
461
462 return getSupportedExtensions().contains("." + extension);
463 }
464 return false;
465 }
466
474 private void updateTimeLabel(long start, long total) {
475 progressLabel.setText(formatTime(start) + "/" + formatTime(total));
476 }
477
483 private double getPlayBackRate() {
484 int selectIndex = playBackSpeedComboBox.getSelectedIndex();
485 String selectText = playBackSpeedComboBox.getItemAt(selectIndex);
486 return Double.valueOf(selectText.substring(0, selectText.length() - 1));
487 }
488
492 @NbBundle.Messages({
493 "MediaPlayerPanel.unknownTime=Unknown",
494 "MediaPlayerPanel.timeFormat=%02d:%02d:%02d"
495 })
496 private String formatTime(long ns) {
497 if (ns == -1) {
498 return Bundle.MediaPlayerPanel_unknownTime();
499 }
500
501 long seconds = TimeUnit.SECONDS.convert(ns, TimeUnit.NANOSECONDS);
502 long hours = TimeUnit.HOURS.convert(seconds, TimeUnit.SECONDS);
503 seconds -= TimeUnit.SECONDS.convert(hours, TimeUnit.HOURS);
504 long minutes = TimeUnit.MINUTES.convert(seconds, TimeUnit.SECONDS);
505 seconds -= TimeUnit.SECONDS.convert(minutes, TimeUnit.MINUTES);
506
507 return String.format(Bundle.MediaPlayerPanel_timeFormat(), hours, minutes, seconds);
508 }
509
514 private class ExtractMedia extends SwingWorker<GstStatus, Void> {
515
516 private ProgressHandle progress;
517 private final AbstractFile sourceFile;
518 private final java.io.File tempFile;
519
520 ExtractMedia(AbstractFile sFile, File jFile) {
521 this.sourceFile = sFile;
522 this.tempFile = jFile;
523 }
524
525 @Override
526 protected GstStatus doInBackground() throws Exception {
527 if (this.isCancelled()) {
528 throw new InterruptedException("Thread has been interrupted");
529 }
530
531 GstStatus loadStatus = GstLoader.tryLoad();
532 if (loadStatus == GstStatus.FAILURE) {
533 return loadStatus;
534 }
535
536 if (this.isCancelled()) {
537 throw new InterruptedException("Thread has been interrupted");
538 }
539
540 if (!tempFile.exists() || tempFile.length() < sourceFile.getSize()) {
541 progress = ProgressHandle.createHandle(NbBundle.getMessage(MediaPlayerPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sourceFile.getName()), () -> this.cancel(true));
542
543 SwingUtilities.invokeLater(() -> {
544 progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering"));
545 });
546
547 progress.start(100);
548 try {
549 Files.createParentDirs(tempFile);
551 } catch (IOException ex) {
552 logger.log(Level.WARNING, "Error creating parent directory for copying video/audio in temp directory", ex); //NON-NLS
553 } finally {
554 progress.finish();
555 }
556 }
557 return loadStatus;
558 }
559
560 /*
561 * Initialize the playback components if the extraction was successful.
562 */
563 @NbBundle.Messages({
564 "MediaPlayerPanel.playbackDisabled=A problem was encountered with"
565 + " the video and audio playback service. Video and audio "
566 + "playback will be disabled for the remainder of the session."
567 })
568 @Override
569 protected void done() {
570 try {
571 if (this.isCancelled()) {
572 return;
573 }
574
575 GstStatus loadStatus = super.get();
576 if (loadStatus == null || loadStatus == GstStatus.FAILURE) {
577 return;
578 }
579
580 if (this.isCancelled()) {
581 return;
582 }
583
584 Gst.getExecutor().submit(() -> {
585 //Video is ready for playback. Create new components
586 gstPlayBin = new PlayBin("VideoPlayer", tempFile.toURI());
587 //Configure event handling
588 Bus playBinBus = gstPlayBin.getBus();
589 playBinBus.connect(endOfStreamListener);
590 playBinBus.connect(stateChangeListener);
591 playBinBus.connect(errorListener);
592
593 if (this.isCancelled()) {
594 return;
595 }
596
597 JFXPanel fxPanel = new JFXPanel();
598 videoPanel.removeAll();
599 videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
600 videoPanel.add(fxPanel);
601 fxAppSink = new JavaFxAppSink("JavaFxAppSink", fxPanel);
602 if (gstPlayBin != null) {
603 gstPlayBin.setVideoSink(fxAppSink);
604 }
605 if (this.isCancelled()) {
606 return;
607 }
608 if (gstPlayBin != null) {
609 gstPlayBin.setVolume((audioSlider.getValue() * 2.0) / 100.0);
610 gstPlayBin.pause();
611 }
612
613 timer.start();
614 SwingUtilities.invokeLater(() -> {
615 enableComponents(true);
616 });
617 });
618 } catch (CancellationException ex) {
619 logger.log(Level.INFO, "Media buffering was canceled."); //NON-NLS
620 } catch (InterruptedException ex) {
621 logger.log(Level.INFO, "Media buffering was interrupted."); //NON-NLS
622 } catch (ExecutionException ex) {
623 logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); //NON-NLS
624 }
625 }
626 }
627
631 private class VideoPanelUpdater implements ActionListener {
632
633 @Override
634 public void actionPerformed(ActionEvent e) {
635 if (!progressSlider.getValueIsAdjusting() && gstPlayBin != null) {
636 Gst.getExecutor().submit(() -> {
637 try {
638 sliderLock.acquireUninterruptibly();
639 long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
640 long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
647 if (duration >= 0 && position >= 0) {
648 double relativePosition = (double) position / duration;
649 progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE));
650 }
651
652 SwingUtilities.invokeLater(() -> {
653 updateTimeLabel(position, duration);
654 });
655 } finally {
656 sliderLock.release();
657 }
658 });
659 }
660 }
661 }
662
666 private class CircularJSliderUI extends BasicSliderUI {
667
668 private final Dimension thumbDimension;
669 private final Color thumbColor;
670 private final Color trackUnseen;
671 private final Color trackSeen;
672
681 public CircularJSliderUI(JSlider slider, Dimension thumbDimension) {
682 super(slider);
683 this.thumbDimension = thumbDimension;
684
685 //Configure track and thumb colors.
686 Color lightBlue = new Color(0, 130, 255);
687 thumbColor = lightBlue;
688 trackSeen = lightBlue;
689 trackUnseen = Color.LIGHT_GRAY;
690 }
691
692 @Override
693 protected Dimension getThumbSize() {
694 return new Dimension(thumbDimension);
695 }
696
701 @Override
702 public void paintThumb(Graphics graphic) {
703 Rectangle thumb = this.thumbRect;
704
705 Color original = graphic.getColor();
706
707 //Change the thumb view from the rectangle
708 //controller to an oval.
709 graphic.setColor(thumbColor);
710 graphic.fillOval(thumb.x, thumb.y, thumbDimension.width, thumbDimension.height);
711
712 //Preserve the graphics original color
713 graphic.setColor(original);
714 }
715
716 @Override
717 public void paintTrack(Graphics graphic) {
718 //This rectangle is the bounding box for the progress bar
719 //portion of the slider. The track is painted in the middle
720 //of this rectangle and the thumb laid overtop.
721 Rectangle track = this.trackRect;
722
723 //Get the location of the thumb, this point splits the
724 //progress bar into 2 line segments, seen and unseen.
725 Rectangle thumb = this.thumbRect;
726 int thumbX = thumb.x;
727 int thumbY = thumb.y;
728
729 Color original = graphic.getColor();
730
731 //Paint the seen side
732 graphic.setColor(trackSeen);
733 graphic.drawLine(track.x, track.y + track.height / 2,
734 thumbX, thumbY + track.height / 2);
735
736 //Paint the unseen side
737 graphic.setColor(trackUnseen);
738 graphic.drawLine(thumbX, thumbY + track.height / 2,
739 track.x + track.width, track.y + track.height / 2);
740
741 //Preserve the graphics color.
742 graphic.setColor(original);
743 }
744
745 @Override
746 protected TrackListener createTrackListener(JSlider slider) {
755 return new TrackListener() {
756 @Override
757 public void mousePressed(MouseEvent e) {
758 if (!slider.isEnabled() || !SwingUtilities.isLeftMouseButton(e)) {
759 return;
760 }
761 //Snap the thumb to position of the mouse
763
764 //Handle the event as normal.
765 super.mousePressed(e);
766 }
767 };
768 }
769
770 @Override
771 protected void scrollDueToClickInTrack(int direction) {
772 //Set the thumb position to the mouse press location, as opposed
773 //to the closest "block" which is the default behavior.
774 Point mousePosition = slider.getMousePosition();
775 if (mousePosition == null) {
776 return;
777 }
778 int value = this.valueForXPosition(mousePosition.x);
779
780 //Lock the slider down, which is a shared resource.
781 //The VideoPanelUpdater keeps the
782 //slider in sync with the video position, so without
783 //proper locking our change could be overwritten.
784 sliderLock.acquireUninterruptibly();
785 slider.setValueIsAdjusting(true);
786 slider.setValue(value);
787 slider.setValueIsAdjusting(false);
788 sliderLock.release();
789 }
790
794 @Override
795 public void update(Graphics graphic, JComponent component) {
796 if (graphic instanceof Graphics2D) {
797 Graphics2D graphic2 = (Graphics2D) graphic;
798 graphic2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
799 RenderingHints.VALUE_ANTIALIAS_ON);
800 }
801
802 super.update(graphic, component);
803 }
804 }
805
811 @SuppressWarnings("unchecked")
812 // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
813 private void initComponents() {
814 java.awt.GridBagConstraints gridBagConstraints;
815
816 videoPanel = new javax.swing.JPanel();
817 controlPanel = new javax.swing.JPanel();
818 progressSlider = new javax.swing.JSlider();
819 progressLabel = new javax.swing.JLabel();
820 buttonPanel = new javax.swing.JPanel();
821 playButton = new javax.swing.JButton();
822 fastForwardButton = new javax.swing.JButton();
823 rewindButton = new javax.swing.JButton();
824 VolumeIcon = new javax.swing.JLabel();
825 audioSlider = new javax.swing.JSlider();
826 infoLabel = new javax.swing.JLabel();
827 playBackPanel = new javax.swing.JPanel();
828 playBackSpeedComboBox = new javax.swing.JComboBox<>();
829 playBackSpeedLabel = new javax.swing.JLabel();
830
831 javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel);
832 videoPanel.setLayout(videoPanelLayout);
833 videoPanelLayout.setHorizontalGroup(
834 videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
835 .addGap(0, 0, Short.MAX_VALUE)
836 );
837 videoPanelLayout.setVerticalGroup(
838 videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
839 .addGap(0, 117, Short.MAX_VALUE)
840 );
841
842 progressSlider.setValue(0);
843 progressSlider.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
844 progressSlider.setDoubleBuffered(true);
845 progressSlider.setMinimumSize(new java.awt.Dimension(36, 21));
846 progressSlider.setPreferredSize(new java.awt.Dimension(200, 21));
847 progressSlider.setUI(new CircularJSliderUI(progressSlider, new Dimension(18,18)));
848
849 org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.progressLabel.text")); // NOI18N
850
851 buttonPanel.setLayout(new java.awt.GridBagLayout());
852
853 playButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/contentviewers/images/Play-arrow-01.png"))); // NOI18N
854 org.openide.awt.Mnemonics.setLocalizedText(playButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.playButton.text")); // NOI18N
855 playButton.setMaximumSize(new java.awt.Dimension(53, 29));
856 playButton.setMinimumSize(new java.awt.Dimension(53, 29));
857 playButton.setPreferredSize(new java.awt.Dimension(49, 29));
858 playButton.addActionListener(new java.awt.event.ActionListener() {
859 public void actionPerformed(java.awt.event.ActionEvent evt) {
860 playButtonActionPerformed(evt);
861 }
862 });
863 gridBagConstraints = new java.awt.GridBagConstraints();
864 gridBagConstraints.gridx = 1;
865 gridBagConstraints.gridy = 0;
866 gridBagConstraints.ipadx = 21;
867 gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
868 gridBagConstraints.insets = new java.awt.Insets(5, 6, 0, 0);
869 buttonPanel.add(playButton, gridBagConstraints);
870
871 fastForwardButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/contentviewers/images/Fast-forward-01.png"))); // NOI18N
872 org.openide.awt.Mnemonics.setLocalizedText(fastForwardButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.fastForwardButton.text")); // NOI18N
873 fastForwardButton.addActionListener(new java.awt.event.ActionListener() {
874 public void actionPerformed(java.awt.event.ActionEvent evt) {
875 fastForwardButtonActionPerformed(evt);
876 }
877 });
878 gridBagConstraints = new java.awt.GridBagConstraints();
879 gridBagConstraints.gridx = 2;
880 gridBagConstraints.gridy = 0;
881 gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
882 gridBagConstraints.insets = new java.awt.Insets(5, 6, 0, 0);
883 buttonPanel.add(fastForwardButton, gridBagConstraints);
884
885 rewindButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/contentviewers/images/Fast-rewind-01.png"))); // NOI18N
886 org.openide.awt.Mnemonics.setLocalizedText(rewindButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.rewindButton.text")); // NOI18N
887 rewindButton.addActionListener(new java.awt.event.ActionListener() {
888 public void actionPerformed(java.awt.event.ActionEvent evt) {
889 rewindButtonActionPerformed(evt);
890 }
891 });
892 gridBagConstraints = new java.awt.GridBagConstraints();
893 gridBagConstraints.gridx = 0;
894 gridBagConstraints.gridy = 0;
895 gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
896 gridBagConstraints.insets = new java.awt.Insets(5, 0, 1, 0);
897 buttonPanel.add(rewindButton, gridBagConstraints);
898
899 org.openide.awt.Mnemonics.setLocalizedText(VolumeIcon, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.VolumeIcon.text")); // NOI18N
900 VolumeIcon.setHorizontalTextPosition(javax.swing.SwingConstants.LEFT);
901 VolumeIcon.setMaximumSize(new java.awt.Dimension(34, 29));
902 VolumeIcon.setMinimumSize(new java.awt.Dimension(34, 29));
903 VolumeIcon.setPreferredSize(new java.awt.Dimension(34, 19));
904 gridBagConstraints = new java.awt.GridBagConstraints();
905 gridBagConstraints.gridx = 3;
906 gridBagConstraints.gridy = 0;
907 gridBagConstraints.ipadx = 8;
908 gridBagConstraints.ipady = 7;
909 gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
910 gridBagConstraints.insets = new java.awt.Insets(6, 14, 0, 0);
911 buttonPanel.add(VolumeIcon, gridBagConstraints);
912
913 audioSlider.setMajorTickSpacing(10);
914 audioSlider.setMaximum(50);
915 audioSlider.setMinorTickSpacing(5);
916 audioSlider.setToolTipText(org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.audioSlider.toolTipText")); // NOI18N
917 audioSlider.setValue(25);
918 audioSlider.setMaximumSize(new java.awt.Dimension(32767, 19));
919 audioSlider.setMinimumSize(new java.awt.Dimension(200, 19));
920 audioSlider.setPreferredSize(new java.awt.Dimension(200, 30));
921 audioSlider.setRequestFocusEnabled(false);
922 audioSlider.setUI(new CircularJSliderUI(audioSlider, new Dimension(15,15)));
923 gridBagConstraints = new java.awt.GridBagConstraints();
924 gridBagConstraints.gridx = 4;
925 gridBagConstraints.gridy = 0;
926 gridBagConstraints.ipadx = -116;
927 gridBagConstraints.ipady = 7;
928 gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
929 gridBagConstraints.insets = new java.awt.Insets(3, 1, 0, 10);
930 buttonPanel.add(audioSlider, gridBagConstraints);
931
932 infoLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
933 org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.infoLabel.text")); // NOI18N
934 infoLabel.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
935
936 playBackSpeedComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "0.25x", "0.50x", "0.75x", "1x", "1.25x", "1.50x", "1.75x", "2x" }));
937 playBackSpeedComboBox.setSelectedIndex(3);
938 playBackSpeedComboBox.setMaximumSize(new java.awt.Dimension(53, 29));
939 playBackSpeedComboBox.setMinimumSize(new java.awt.Dimension(53, 29));
940 playBackSpeedComboBox.setPreferredSize(new java.awt.Dimension(53, 29));
941 playBackSpeedComboBox.setRequestFocusEnabled(false);
942 playBackSpeedComboBox.addActionListener(new java.awt.event.ActionListener() {
943 public void actionPerformed(java.awt.event.ActionEvent evt) {
944 playBackSpeedComboBoxActionPerformed(evt);
945 }
946 });
947
948 org.openide.awt.Mnemonics.setLocalizedText(playBackSpeedLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.playBackSpeedLabel.text")); // NOI18N
949 playBackSpeedLabel.setMaximumSize(new java.awt.Dimension(34, 19));
950 playBackSpeedLabel.setMinimumSize(new java.awt.Dimension(34, 19));
951 playBackSpeedLabel.setPreferredSize(new java.awt.Dimension(34, 19));
952
953 javax.swing.GroupLayout playBackPanelLayout = new javax.swing.GroupLayout(playBackPanel);
954 playBackPanel.setLayout(playBackPanelLayout);
955 playBackPanelLayout.setHorizontalGroup(
956 playBackPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
957 .addGroup(playBackPanelLayout.createSequentialGroup()
958 .addComponent(playBackSpeedLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 34, javax.swing.GroupLayout.PREFERRED_SIZE)
959 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
960 .addComponent(playBackSpeedComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
961 .addGap(13, 13, 13))
962 );
963 playBackPanelLayout.setVerticalGroup(
964 playBackPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
965 .addGroup(playBackPanelLayout.createSequentialGroup()
966 .addGap(7, 7, 7)
967 .addGroup(playBackPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
968 .addGroup(playBackPanelLayout.createSequentialGroup()
969 .addGap(2, 2, 2)
970 .addComponent(playBackSpeedLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
971 .addComponent(playBackSpeedComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
972 .addGap(10, 10, 10))
973 );
974
975 javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel);
976 controlPanel.setLayout(controlPanelLayout);
977 controlPanelLayout.setHorizontalGroup(
978 controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
979 .addGroup(controlPanelLayout.createSequentialGroup()
980 .addContainerGap()
981 .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
982 .addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
983 .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, controlPanelLayout.createSequentialGroup()
984 .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
985 .addComponent(buttonPanel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
986 .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 623, Short.MAX_VALUE))
987 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
988 .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
989 .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
990 .addComponent(playBackPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))
991 .addGap(10, 10, 10)))
992 .addGap(0, 0, 0))
993 );
994 controlPanelLayout.setVerticalGroup(
995 controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
996 .addGroup(controlPanelLayout.createSequentialGroup()
997 .addGap(0, 0, 0)
998 .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
999 .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
1000 .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
1001 .addGap(5, 5, 5)
1002 .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
1003 .addComponent(buttonPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
1004 .addComponent(playBackPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
1005 .addGap(14, 14, 14)
1006 .addComponent(infoLabel))
1007 );
1008
1009 javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
1010 this.setLayout(layout);
1011 layout.setHorizontalGroup(
1012 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
1013 .addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
1014 .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
1015 );
1016 layout.setVerticalGroup(
1017 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
1018 .addGroup(layout.createSequentialGroup()
1019 .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
1020 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
1021 .addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
1022 );
1023 }// </editor-fold>//GEN-END:initComponents
1024
1025 private void rewindButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_rewindButtonActionPerformed
1026 Gst.getExecutor().submit(() -> {
1027 if (gstPlayBin != null) {
1028 long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
1029 //Skip 30 seconds.
1030 long rewindDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS);
1031 //Ensure new video position is within bounds
1032 long newTime = Math.max(currentTime - rewindDelta, 0);
1033 double playBackRate = getPlayBackRate();
1034 gstPlayBin.seek(playBackRate,
1035 Format.TIME,
1036 //FLUSH - flushes the pipeline
1037 //ACCURATE - video will seek exactly to the position requested
1038 EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE),
1039 //Set the start position to newTime
1040 SeekType.SET, newTime,
1041 //Do nothing for the end position
1042 SeekType.NONE, -1);
1043 }
1044 });
1045 }//GEN-LAST:event_rewindButtonActionPerformed
1046
1047 private void fastForwardButtonActionPerformed(java.awt.event.ActionEvent evt) {
1048 Gst.getExecutor().submit(() -> {
1049 if (gstPlayBin != null) {
1050 long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
1051 long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
1052 //Skip 30 seconds.
1053 long fastForwardDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS);
1054 //Don't allow skipping within 2 seconds of video ending. Skipping right to
1055 //the end causes undefined behavior for some gstreamer plugins.
1056 long twoSecondsInNano = TimeUnit.NANOSECONDS.convert(2, TimeUnit.SECONDS);
1057 if ((duration - currentTime) <= twoSecondsInNano) {
1058 return;
1059 }
1060
1061 long newTime;
1062 if (currentTime + fastForwardDelta >= duration) {
1063 //If there are less than 30 seconds left, only fast forward to the midpoint.
1064 newTime = currentTime + (duration - currentTime) / 2;
1065 } else {
1066 newTime = currentTime + fastForwardDelta;
1067 }
1068
1069 double playBackRate = getPlayBackRate();
1070 gstPlayBin.seek(playBackRate,
1071 Format.TIME,
1072 //FLUSH - flushes the pipeline
1073 //ACCURATE - video will seek exactly to the position requested
1074 EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE),
1075 //Set the start position to newTime
1076 SeekType.SET, newTime,
1077 //Do nothing for the end position
1078 SeekType.NONE, -1);
1079 }
1080 });
1081 }
1082
1083 private void playButtonActionPerformed(java.awt.event.ActionEvent evt) {
1084 Gst.getExecutor().submit(() -> {
1085 if (gstPlayBin != null) {
1086 if (gstPlayBin.isPlaying()) {
1087 gstPlayBin.pause();
1088 } else {
1089 double playBackRate = getPlayBackRate();
1090 long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
1091 //Set playback rate before play.
1092 gstPlayBin.seek(playBackRate,
1093 Format.TIME,
1094 //FLUSH - flushes the pipeline
1095 //ACCURATE - video will seek exactly to the position requested
1096 EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE),
1097 //Set the start position to newTime
1098 SeekType.SET, currentTime,
1099 //Do nothing for the end position
1100 SeekType.NONE, -1);
1101 gstPlayBin.play();
1102 }
1103 }
1104 });
1105 }
1106
1107 private void playBackSpeedComboBoxActionPerformed(java.awt.event.ActionEvent evt) {
1108 Gst.getExecutor().submit(() -> {
1109 if (gstPlayBin != null) {
1110 double playBackRate = getPlayBackRate();
1111 long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
1112 gstPlayBin.seek(playBackRate,
1113 Format.TIME,
1114 //FLUSH - flushes the pipeline
1115 //ACCURATE - video will seek exactly to the position requested
1116 EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE),
1117 //Set the position to the currentTime, we are only adjusting the
1118 //playback rate.
1119 SeekType.SET, currentTime,
1120 SeekType.NONE, 0);
1121 }
1122 });
1123 }
1124
1125 // Variables declaration - do not modify//GEN-BEGIN:variables
1126 private javax.swing.JLabel VolumeIcon;
1127 private javax.swing.JSlider audioSlider;
1128 private javax.swing.JPanel buttonPanel;
1129 private javax.swing.JPanel controlPanel;
1130 private javax.swing.JButton fastForwardButton;
1131 private javax.swing.JLabel infoLabel;
1132 private javax.swing.JPanel playBackPanel;
1133 private javax.swing.JComboBox<String> playBackSpeedComboBox;
1134 private javax.swing.JLabel playBackSpeedLabel;
1135 private javax.swing.JButton playButton;
1136 private javax.swing.JLabel progressLabel;
1137 private javax.swing.JSlider progressSlider;
1138 private javax.swing.JButton rewindButton;
1139 private javax.swing.JPanel videoPanel;
1140 // End of variables declaration//GEN-END:variables
1141}
void playButtonActionPerformed(java.awt.event.ActionEvent evt)
void fastForwardButtonActionPerformed(java.awt.event.ActionEvent evt)
void playBackSpeedComboBoxActionPerformed(java.awt.event.ActionEvent evt)
void rewindButtonActionPerformed(java.awt.event.ActionEvent evt)
synchronized static Logger getLogger(String name)
Definition Logger.java:124
static< T > long writeToFile(Content content, java.io.File outputFile, ProgressHandle progress, Future< T > worker, boolean source)

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.