diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties index c799a17d61..b3a384ce48 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties @@ -84,9 +84,12 @@ MediaViewImagePanel.zoomTextField.text= MediaViewImagePanel.rotationTextField.text= MediaViewImagePanel.rotateLeftButton.toolTipText= HtmlPanel.showImagesToggleButton.text=Download Images -MediaPlayerPanel.audioSlider.toolTipText= -MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume +MediaViewImagePanel.tagsMenu.text_1=Tags Menu MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00 +MediaPlayerPanel.audioSlider.toolTipText= +MediaPlayerPanel.rewindButton.text=\u2bc7\u2bc7 +MediaPlayerPanel.fastForwardButton.text=\u2bc8\u2bc8 MediaPlayerPanel.playButton.text=\u25ba MediaPlayerPanel.infoLabel.text=No Errors -MediaViewImagePanel.tagsMenu.text_1=Tags Menu +MediaPlayerPanel.VolumeIcon.text=Volume +MediaPlayerPanel.playBackSpeedLabel.text=Speed: diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED index ff3341b60f..ba39d1d419 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED @@ -154,12 +154,15 @@ MediaViewImagePanel.zoomTextField.text= MediaViewImagePanel.rotationTextField.text= MediaViewImagePanel.rotateLeftButton.toolTipText= HtmlPanel.showImagesToggleButton.text=Download Images -MediaPlayerPanel.audioSlider.toolTipText= -MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume +MediaViewImagePanel.tagsMenu.text_1=Tags Menu MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00 +MediaPlayerPanel.audioSlider.toolTipText= +MediaPlayerPanel.rewindButton.text=\u2bc7\u2bc7 +MediaPlayerPanel.fastForwardButton.text=\u2bc8\u2bc8 MediaPlayerPanel.playButton.text=\u25ba MediaPlayerPanel.infoLabel.text=No Errors -MediaViewImagePanel.tagsMenu.text_1=Tags Menu +MediaPlayerPanel.VolumeIcon.text=Volume +MediaPlayerPanel.playBackSpeedLabel.text=Speed: # {0} - tableName SQLiteViewer.readTable.errorText=Error getting rows for table: {0} # {0} - tableName diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form index 880528e787..605ed93697 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form @@ -1,6 +1,6 @@ -
+ @@ -41,7 +41,7 @@ - + @@ -51,47 +51,42 @@ - - + + - - - - - - - - - - - - - + + + + + + + + + + + + - + - - - - - - + + + + - - - - - - - + + + + - + + @@ -112,8 +107,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -122,48 +211,74 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index 1b30c49ef4..dec8bf55b4 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -24,6 +24,7 @@ import java.awt.event.ActionListener; import java.io.File; import java.io.IOException; import java.util.Arrays; +import java.util.EnumSet; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; @@ -37,7 +38,6 @@ import javax.swing.SwingWorker; import javax.swing.Timer; import javax.swing.event.ChangeEvent; import org.freedesktop.gstreamer.Bus; -import org.freedesktop.gstreamer.ClockTime; import org.freedesktop.gstreamer.Gst; import org.freedesktop.gstreamer.GstObject; import org.freedesktop.gstreamer.State; @@ -52,8 +52,13 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskData; import javafx.embed.swing.JFXPanel; +import javax.swing.SwingUtilities; import javax.swing.event.ChangeListener; +import org.freedesktop.gstreamer.ClockTime; +import org.freedesktop.gstreamer.Format; import org.freedesktop.gstreamer.GstException; +import org.freedesktop.gstreamer.event.SeekFlags; +import org.freedesktop.gstreamer.event.SeekType; /** * This is a video player that is part of the Media View layered pane. It uses @@ -177,8 +182,10 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie private Bus.EOS endOfStreamListener; //Update progress bar and time label during video playback - private final Timer timer = new Timer(75, new VideoPanelUpdater()); + //Updating every 16 MS = 62.5 FPS. + private final Timer timer = new Timer(16, new VideoPanelUpdater()); private static final int PROGRESS_SLIDER_SIZE = 2000; + private static final int SKIP_IN_SECONDS = 30; private ExtractMedia extractMediaWorker; @@ -202,11 +209,20 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie if (progressSlider.getValueIsAdjusting()) { long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); double relativePosition = progressSlider.getValue() * 1.0 / PROGRESS_SLIDER_SIZE; - long newPos = (long) (relativePosition * duration); - gstPlayBin.seek(newPos, TimeUnit.NANOSECONDS); + long newStartTime = (long) (relativePosition * duration); + double playBackRate = getPlayBackRate(); + gstPlayBin.seek(playBackRate, + Format.TIME, + //FLUSH - flushes the pipeline + //ACCURATE - video will seek exactly to the position requested + EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), + //Set the start position to newTime + SeekType.SET, newStartTime, + //Do nothing for the end position + SeekType.NONE, -1); //Keep constantly updating the time label so users have a sense of //where the slider they are dragging is in relation to the video time - updateTimeLabel(newPos, duration); + updateTimeLabel(newStartTime, duration); } } }); @@ -220,10 +236,12 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie errorListener = new Bus.ERROR() { @Override public void errorMessage(GstObject go, int i, String string) { - enableComponents(false); - infoLabel.setText(String.format( - "%s", - MEDIA_PLAYER_ERROR_STRING)); + SwingUtilities.invokeLater(() -> { + enableComponents(false); + infoLabel.setText(String.format( + "%s", + MEDIA_PLAYER_ERROR_STRING)); + }); timer.stop(); } }; @@ -231,9 +249,13 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie @Override public void stateChanged(GstObject go, State oldState, State currentState, State pendingState) { if (State.PLAYING.equals(currentState)) { - playButton.setText("||"); + SwingUtilities.invokeLater(() -> { + playButton.setText("||"); + }); } else { - playButton.setText("►"); + SwingUtilities.invokeLater(() -> { + playButton.setText("►"); + }); } } }; @@ -241,7 +263,6 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie @Override public void endOfStream(GstObject go) { gstPlayBin.seek(ClockTime.ZERO); - progressSlider.setValue(0); /** * Keep the video from automatically playing */ @@ -268,7 +289,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie try { //Pushing off initialization to the background extractMediaWorker = new ExtractMedia(file, VideoUtils.getVideoFileInTempDir(file)); - extractMediaWorker.execute(); + extractMediaWorker.execute(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS infoLabel.setText(String.format("%s", Bundle.GstVideoPanel_noOpenCase_errMsg())); @@ -301,8 +322,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie if (gstPlayBin != null) { gstPlayBin.stop(); gstPlayBin.getBus().disconnect(endOfStreamListener); - gstPlayBin.getBus().disconnect(endOfStreamListener); - gstPlayBin.getBus().disconnect(endOfStreamListener); + gstPlayBin.getBus().disconnect(stateChangeListener); + gstPlayBin.getBus().disconnect(errorListener); gstPlayBin.dispose(); fxAppSink.clear(); gstPlayBin = null; @@ -317,6 +338,9 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie progressSlider.setEnabled(isEnabled); videoPanel.setEnabled(isEnabled); audioSlider.setEnabled(isEnabled); + rewindButton.setEnabled(isEnabled); + fastForwardButton.setEnabled(isEnabled); + playBackSpeedComboBox.setEnabled(isEnabled); } @Override @@ -373,7 +397,18 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie * @param total */ private void updateTimeLabel(long start, long total) { - progressLabel.setText(formatTime(start, false) + "/" + formatTime(total, true)); + progressLabel.setText(formatTime(start) + "/" + formatTime(total)); + } + + /** + * Reads the current selected playback rate from the speed combo box. + * + * @return The selected rate. + */ + private double getPlayBackRate() { + int selectIndex = playBackSpeedComboBox.getSelectedIndex(); + String selectText = playBackSpeedComboBox.getItemAt(selectIndex); + return Double.valueOf(selectText.substring(0, selectText.length() - 1)); } /** @@ -383,24 +418,18 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie "MediaPlayerPanel.unknownTime=Unknown", "MediaPlayerPanel.timeFormat=%02d:%02d:%02d" }) - private String formatTime(long ns, boolean ceiling) { + private String formatTime(long ns) { if (ns == -1) { return Bundle.MediaPlayerPanel_unknownTime(); } - double millis = ns / 1000000.0; - double seconds; - if (ceiling) { - seconds = Math.ceil(millis / 1000); - } else { - seconds = millis / 1000; - } - double hours = seconds / 3600; - seconds -= (int) hours * 3600; - double minutes = seconds / 60; - seconds -= (int) minutes * 60; + long seconds = TimeUnit.SECONDS.convert(ns, TimeUnit.NANOSECONDS); + long hours = TimeUnit.HOURS.convert(seconds, TimeUnit.SECONDS); + seconds -= TimeUnit.SECONDS.convert(hours, TimeUnit.HOURS); + long minutes = TimeUnit.MINUTES.convert(seconds, TimeUnit.SECONDS); + seconds -= TimeUnit.SECONDS.convert(minutes, TimeUnit.MINUTES); - return String.format(Bundle.MediaPlayerPanel_timeFormat(), (int) hours, (int) minutes, (int) seconds); + return String.format(Bundle.MediaPlayerPanel_timeFormat(), hours, minutes, seconds); } /** @@ -422,7 +451,11 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie protected Void doInBackground() throws Exception { if (!tempFile.exists() || tempFile.length() < sourceFile.getSize()) { progress = ProgressHandle.createHandle(NbBundle.getMessage(MediaPlayerPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sourceFile.getName()), () -> this.cancel(true)); - progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering")); + + SwingUtilities.invokeLater(() -> { + progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering")); + }); + progress.start(100); try { Files.createParentDirs(tempFile); @@ -443,8 +476,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie protected void done() { try { super.get(); - - if(this.isCancelled()) { + + if (this.isCancelled()) { return; } @@ -460,8 +493,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie playBinBus.connect(endOfStreamListener); playBinBus.connect(stateChangeListener); playBinBus.connect(errorListener); - - if(this.isCancelled()) { + + if (this.isCancelled()) { return; } @@ -471,14 +504,14 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie videoPanel.add(fxPanel); fxAppSink = new JavaFxAppSink("JavaFxAppSink", fxPanel); gstPlayBin.setVideoSink(fxAppSink); - - if(this.isCancelled()) { + + if (this.isCancelled()) { return; } gstPlayBin.setVolume((audioSlider.getValue() * 2.0) / 100.0); gstPlayBin.pause(); - + timer.start(); enableComponents(true); } catch (CancellationException ex) { @@ -506,12 +539,14 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie * pipeline. We start this updater when data-flow has just been * initiated so buffering may still be in progress. */ - if (duration != -1) { + if (duration >= 0 && position >= 0) { double relativePosition = (double) position / duration; progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE)); } - updateTimeLabel(position, duration); + SwingUtilities.invokeLater(() -> { + updateTimeLabel(position, duration); + }); } } } @@ -524,15 +559,22 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie @SuppressWarnings("unchecked") // //GEN-BEGIN:initComponents private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; videoPanel = new javax.swing.JPanel(); controlPanel = new javax.swing.JPanel(); progressSlider = new javax.swing.JSlider(); - infoLabel = new javax.swing.JLabel(); - playButton = new javax.swing.JButton(); progressLabel = new javax.swing.JLabel(); + buttonPanel = new javax.swing.JPanel(); + playButton = new javax.swing.JButton(); + fastForwardButton = new javax.swing.JButton(); + rewindButton = new javax.swing.JButton(); VolumeIcon = new javax.swing.JLabel(); audioSlider = new javax.swing.JSlider(); + infoLabel = new javax.swing.JLabel(); + playBackPanel = new javax.swing.JPanel(); + playBackSpeedComboBox = new javax.swing.JComboBox<>(); + playBackSpeedLabel = new javax.swing.JLabel(); javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel); videoPanel.setLayout(videoPanelLayout); @@ -542,7 +584,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie ); videoPanelLayout.setVerticalGroup( videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 259, Short.MAX_VALUE) + .addGap(0, 131, Short.MAX_VALUE) ); progressSlider.setValue(0); @@ -551,8 +593,9 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie progressSlider.setMinimumSize(new java.awt.Dimension(36, 21)); progressSlider.setPreferredSize(new java.awt.Dimension(200, 21)); - org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.infoLabel.text")); // NOI18N - infoLabel.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); + org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.progressLabel.text")); // NOI18N + + buttonPanel.setLayout(new java.awt.GridBagLayout()); org.openide.awt.Mnemonics.setLocalizedText(playButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.playButton.text")); // NOI18N playButton.addActionListener(new java.awt.event.ActionListener() { @@ -560,56 +603,136 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie playButtonActionPerformed(evt); } }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + gridBagConstraints.ipadx = 21; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(5, 6, 0, 0); + buttonPanel.add(playButton, gridBagConstraints); - org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.progressLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(fastForwardButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.fastForwardButton.text")); // NOI18N + fastForwardButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + fastForwardButtonActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 0; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(5, 6, 0, 0); + buttonPanel.add(fastForwardButton, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(rewindButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.rewindButton.text")); // NOI18N + rewindButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + rewindButtonActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(5, 0, 1, 0); + buttonPanel.add(rewindButton, gridBagConstraints); org.openide.awt.Mnemonics.setLocalizedText(VolumeIcon, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.VolumeIcon.text")); // NOI18N + VolumeIcon.setHorizontalTextPosition(javax.swing.SwingConstants.LEFT); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 3; + gridBagConstraints.gridy = 0; + gridBagConstraints.ipadx = 8; + gridBagConstraints.ipady = 7; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(6, 6, 0, 0); + buttonPanel.add(VolumeIcon, gridBagConstraints); audioSlider.setMajorTickSpacing(10); audioSlider.setMaximum(50); audioSlider.setMinorTickSpacing(5); - audioSlider.setPaintTicks(true); audioSlider.setToolTipText(org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.audioSlider.toolTipText")); // NOI18N audioSlider.setValue(25); audioSlider.setMinimumSize(new java.awt.Dimension(200, 21)); audioSlider.setPreferredSize(new java.awt.Dimension(200, 21)); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 4; + gridBagConstraints.gridy = 0; + gridBagConstraints.ipadx = -116; + gridBagConstraints.ipady = 7; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(3, 1, 0, 10); + buttonPanel.add(audioSlider, gridBagConstraints); + + infoLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); + org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.infoLabel.text")); // NOI18N + infoLabel.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); + + playBackSpeedComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "0.25x", "0.50x", "0.75x", "1x", "1.25x", "1.50x", "1.75x", "2x" })); + playBackSpeedComboBox.setSelectedIndex(3); + playBackSpeedComboBox.setMaximumSize(new java.awt.Dimension(53, 23)); + playBackSpeedComboBox.setMinimumSize(new java.awt.Dimension(53, 23)); + playBackSpeedComboBox.setPreferredSize(new java.awt.Dimension(53, 23)); + playBackSpeedComboBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + playBackSpeedComboBoxActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(playBackSpeedLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.playBackSpeedLabel.text")); // NOI18N + + javax.swing.GroupLayout playBackPanelLayout = new javax.swing.GroupLayout(playBackPanel); + playBackPanel.setLayout(playBackPanelLayout); + playBackPanelLayout.setHorizontalGroup( + playBackPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(playBackPanelLayout.createSequentialGroup() + .addComponent(playBackSpeedLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(playBackSpeedComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(13, 13, 13)) + ); + playBackPanelLayout.setVerticalGroup( + playBackPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(playBackPanelLayout.createSequentialGroup() + .addGap(6, 6, 6) + .addGroup(playBackPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(playBackSpeedComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(playBackSpeedLabel)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel); controlPanel.setLayout(controlPanelLayout); controlPanelLayout.setHorizontalGroup( controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, controlPanelLayout.createSequentialGroup() + .addGroup(controlPanelLayout.createSequentialGroup() .addContainerGap() .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(controlPanelLayout.createSequentialGroup() - .addComponent(playButton, javax.swing.GroupLayout.PREFERRED_SIZE, 64, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 680, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(progressLabel)) - .addGroup(controlPanelLayout.createSequentialGroup() - .addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGap(18, 18, 18) - .addComponent(VolumeIcon, javax.swing.GroupLayout.PREFERRED_SIZE, 64, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(2, 2, 2) - .addComponent(audioSlider, javax.swing.GroupLayout.PREFERRED_SIZE, 229, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addContainerGap()) + .addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, controlPanelLayout.createSequentialGroup() + .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(buttonPanel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 623, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(playBackPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)) + .addGap(10, 10, 10))) + .addGap(0, 0, 0)) ); controlPanelLayout.setVerticalGroup( controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(controlPanelLayout.createSequentialGroup() - .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addComponent(playButton)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(audioSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(VolumeIcon, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(infoLabel))) - .addGap(13, 13, 13)) + .addGap(0, 0, 0) + .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(5, 5, 5) + .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(buttonPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(playBackPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)) + .addGap(14, 14, 14) + .addComponent(infoLabel)) ); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); @@ -628,23 +751,96 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie ); }// //GEN-END:initComponents + private void rewindButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_rewindButtonActionPerformed + long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); + //Skip 30 seconds. + long rewindDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); + //Ensure new video position is within bounds + long newTime = Math.max(currentTime - rewindDelta, 0); + double playBackRate = getPlayBackRate(); + gstPlayBin.seek(playBackRate, + Format.TIME, + //FLUSH - flushes the pipeline + //ACCURATE - video will seek exactly to the position requested + EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), + //Set the start position to newTime + SeekType.SET, newTime, + //Do nothing for the end position + SeekType.NONE, -1); + }//GEN-LAST:event_rewindButtonActionPerformed + + private void fastForwardButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fastForwardButtonActionPerformed + long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); + long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); + //Skip 30 seconds. + long fastForwardDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); + + //Ignore fast forward requests if there are less than 30 seconds left. + if (currentTime + fastForwardDelta >= duration) { + return; + } + + long newTime = currentTime + fastForwardDelta; + double playBackRate = getPlayBackRate(); + gstPlayBin.seek(playBackRate, + Format.TIME, + //FLUSH - flushes the pipeline + //ACCURATE - video will seek exactly to the position requested + EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), + //Set the start position to newTime + SeekType.SET, newTime, + //Do nothing for the end position + SeekType.NONE, -1); + }//GEN-LAST:event_fastForwardButtonActionPerformed + private void playButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_playButtonActionPerformed if (gstPlayBin.isPlaying()) { gstPlayBin.pause(); } else { + double playBackRate = getPlayBackRate(); + long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); + //Set playback rate before play. + gstPlayBin.seek(playBackRate, + Format.TIME, + //FLUSH - flushes the pipeline + //ACCURATE - video will seek exactly to the position requested + EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), + //Set the start position to newTime + SeekType.SET, currentTime, + //Do nothing for the end position + SeekType.NONE, -1); gstPlayBin.play(); } }//GEN-LAST:event_playButtonActionPerformed + private void playBackSpeedComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_playBackSpeedComboBoxActionPerformed + double playBackRate = getPlayBackRate(); + long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); + gstPlayBin.seek(playBackRate, + Format.TIME, + //FLUSH - flushes the pipeline + //ACCURATE - video will seek exactly to the position requested + EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), + //Set the position to the currentTime, we are only adjusting the + //playback rate. + SeekType.SET, currentTime, + SeekType.NONE, 0); + }//GEN-LAST:event_playBackSpeedComboBoxActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel VolumeIcon; private javax.swing.JSlider audioSlider; + private javax.swing.JPanel buttonPanel; private javax.swing.JPanel controlPanel; + private javax.swing.JButton fastForwardButton; private javax.swing.JLabel infoLabel; + private javax.swing.JPanel playBackPanel; + private javax.swing.JComboBox playBackSpeedComboBox; + private javax.swing.JLabel playBackSpeedLabel; private javax.swing.JButton playButton; private javax.swing.JLabel progressLabel; private javax.swing.JSlider progressSlider; + private javax.swing.JButton rewindButton; private javax.swing.JPanel videoPanel; // End of variables declaration//GEN-END:variables }