diff --git a/src/main/java/ctbrec/recorder/LocalRecorder.java b/src/main/java/ctbrec/recorder/LocalRecorder.java index 1b8cdf5c..dfa3093d 100644 --- a/src/main/java/ctbrec/recorder/LocalRecorder.java +++ b/src/main/java/ctbrec/recorder/LocalRecorder.java @@ -169,6 +169,22 @@ public class LocalRecorder implements Recorder { } } + @Override + public boolean isSuspended(Model model) { + lock.lock(); + try { + int index = models.indexOf(model); + if(index >= 0) { + Model m = models.get(index); + return m.isSuspended(); + } else { + return false; + } + } finally { + lock.unlock(); + } + } + @Override public List getModelsRecording() { lock.lock(); diff --git a/src/main/java/ctbrec/recorder/Recorder.java b/src/main/java/ctbrec/recorder/Recorder.java index e216f668..b22fe1a4 100644 --- a/src/main/java/ctbrec/recorder/Recorder.java +++ b/src/main/java/ctbrec/recorder/Recorder.java @@ -31,4 +31,6 @@ public interface Recorder { public void suspendRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException; public void resumeRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException; + + public boolean isSuspended(Model model); } diff --git a/src/main/java/ctbrec/recorder/RemoteRecorder.java b/src/main/java/ctbrec/recorder/RemoteRecorder.java index ec0f22a5..2e07d10c 100644 --- a/src/main/java/ctbrec/recorder/RemoteRecorder.java +++ b/src/main/java/ctbrec/recorder/RemoteRecorder.java @@ -109,6 +109,17 @@ public class RemoteRecorder implements Recorder { return models != null && models.contains(model); } + @Override + public boolean isSuspended(Model model) { + int index = models.indexOf(model); + if(index >= 0) { + Model m = models.get(index); + return m.isSuspended(); + } else { + return false; + } + } + @Override public List getModelsRecording() { if(lastSync.isBefore(Instant.now().minusSeconds(60))) { @@ -280,10 +291,24 @@ public class RemoteRecorder implements Recorder { @Override public void suspendRecording(Model model) throws InvalidKeyException, NoSuchAlgorithmException, IllegalStateException, IOException { sendRequest("suspend", model); + model.setSuspended(true); + // update cached model + int index = models.indexOf(model); + if(index >= 0) { + Model m = models.get(index); + m.setSuspended(true); + } } @Override public void resumeRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException { sendRequest("resume", model); + model.setSuspended(false); + // update cached model + int index = models.indexOf(model); + if(index >= 0) { + Model m = models.get(index); + m.setSuspended(false); + } } } diff --git a/src/main/java/ctbrec/ui/PauseIndicator.java b/src/main/java/ctbrec/ui/PauseIndicator.java new file mode 100644 index 00000000..f716b871 --- /dev/null +++ b/src/main/java/ctbrec/ui/PauseIndicator.java @@ -0,0 +1,18 @@ +package ctbrec.ui; + +import javafx.scene.layout.HBox; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; + +public class PauseIndicator extends HBox { + + public PauseIndicator(Color c, int size) { + spacingProperty().setValue(size*1/5); + Rectangle left = new Rectangle(size*2/5, size); + left.setFill(c); + Rectangle right = new Rectangle(size*2/5, size); + right.setFill(c); + getChildren().add(left); + getChildren().add(right); + } +} diff --git a/src/main/java/ctbrec/ui/ThumbCell.java b/src/main/java/ctbrec/ui/ThumbCell.java index 735f5462..87ca2ce8 100644 --- a/src/main/java/ctbrec/ui/ThumbCell.java +++ b/src/main/java/ctbrec/ui/ThumbCell.java @@ -64,6 +64,7 @@ public class ThumbCell extends StackPane { private Text resolutionTag; private Recorder recorder; private Circle recordingIndicator; + private PauseIndicator pausedIndicator; private int index = 0; ContextMenu popup; private final Color colorNormal = Color.BLACK; @@ -81,6 +82,7 @@ public class ThumbCell extends StackPane { this.model = model; this.recorder = recorder; recording = recorder.isRecording(model); + model.setSuspended(recorder.isSuspended(model)); this.setStyle("-fx-background-color: lightgray"); iv = new ImageView(); @@ -145,6 +147,12 @@ public class ThumbCell extends StackPane { StackPane.setAlignment(recordingIndicator, Pos.TOP_LEFT); getChildren().add(recordingIndicator); + pausedIndicator = new PauseIndicator(colorRecording, 16); + pausedIndicator.setVisible(false); + StackPane.setMargin(pausedIndicator, new Insets(3)); + StackPane.setAlignment(pausedIndicator, Pos.TOP_LEFT); + getChildren().add(pausedIndicator); + selectionOverlay = new Rectangle(); selectionOverlay.setOpacity(0); StackPane.setAlignment(selectionOverlay, Pos.TOP_LEFT); @@ -324,7 +332,14 @@ public class ThumbCell extends StackPane { Color c = mouseHovering ? colorHighlight : colorNormal; nameBackground.setFill(c); } - recordingIndicator.setVisible(recording); + + if(recording) { + recordingIndicator.setVisible(!model.isSuspended()); + pausedIndicator.setVisible(model.isSuspended()); + } else { + recordingIndicator.setVisible(false); + pausedIndicator.setVisible(false); + } } void startStopAction(boolean start) { @@ -350,6 +365,31 @@ public class ThumbCell extends StackPane { } } + void pauseResumeAction(boolean pause) { + setCursor(Cursor.WAIT); + new Thread(() -> { + try { + if(pause) { + recorder.suspendRecording(model); + } else { + recorder.resumeRecording(model); + } + setRecording(recording); + } catch (Exception e1) { + LOG.error("Couldn't pause/resume recording", e1); + Platform.runLater(() -> { + Alert alert = new AutosizeAlert(Alert.AlertType.ERROR); + alert.setTitle("Error"); + alert.setHeaderText("Couldn't pause/resume recording"); + alert.setContentText("I/O error while pausing/resuming the recording: " + e1.getLocalizedMessage()); + alert.showAndWait(); + }); + } finally { + setCursor(Cursor.DEFAULT); + } + }).start(); + } + private void _startStopAction(Model model, boolean start) { new Thread(() -> { try { @@ -429,6 +469,7 @@ public class ThumbCell extends StackPane { this.model.setPreview(model.getPreview()); this.model.setTags(model.getTags()); this.model.setUrl(model.getUrl()); + this.model.setSuspended(model.isSuspended()); update(); } @@ -441,6 +482,7 @@ public class ThumbCell extends StackPane { } private void update() { + model.setSuspended(recorder.isSuspended(model)); setRecording(recorder.isRecording(model)); setImage(model.getPreview()); String txt = recording ? " " : ""; diff --git a/src/main/java/ctbrec/ui/ThumbOverviewTab.java b/src/main/java/ctbrec/ui/ThumbOverviewTab.java index 447336c6..54a14e64 100644 --- a/src/main/java/ctbrec/ui/ThumbOverviewTab.java +++ b/src/main/java/ctbrec/ui/ThumbOverviewTab.java @@ -324,6 +324,12 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener { stop.setOnAction((e) -> startStopAction(getSelectedThumbCells(cell), false)); MenuItem startStop = recorder.isRecording(cell.getModel()) ? stop : start; + MenuItem pause = new MenuItem("Pause Recording"); + pause.setOnAction((e) -> pauseResumeAction(getSelectedThumbCells(cell), true)); + MenuItem resume = new MenuItem("Resume Recording"); + resume.setOnAction((e) -> pauseResumeAction(getSelectedThumbCells(cell), false)); + MenuItem pauseResume = recorder.isSuspended(cell.getModel()) ? resume : pause; + MenuItem follow = new MenuItem("Follow"); follow.setOnAction((e) -> follow(getSelectedThumbCells(cell), true)); MenuItem unfollow = new MenuItem("Unfollow"); @@ -388,7 +394,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener { contextMenu.setAutoHide(true); contextMenu.setHideOnEscape(true); contextMenu.setAutoFix(true); - contextMenu.getItems().addAll(openInPlayer, startStop); + contextMenu.getItems().addAll(openInPlayer, startStop, pauseResume); if(site.supportsFollow()) { MenuItem followOrUnFollow = (this instanceof FollowedTab) ? unfollow : follow; followOrUnFollow.setDisable(!site.credentialsAvailable()); @@ -431,6 +437,12 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener { } } + private void pauseResumeAction(List selection, boolean pause) { + for (ThumbCell thumbCell : selection) { + thumbCell.pauseResumeAction(pause); + } + } + private void startPlayer(List selection) { for (ThumbCell thumbCell : selection) { thumbCell.startPlayer();