Add possibility to suspend the recording for model
This makes it possible to stop the recording without loosing track of the model. The user can pause/unpause recordings in the recorded models tab. There is also an new column "Paused", which indicates, if the recording is suspended for a model.
This commit is contained in:
parent
6b16a637f0
commit
efc4719018
|
@ -16,6 +16,7 @@ public abstract class AbstractModel implements Model {
|
|||
private String description;
|
||||
private List<String> tags = new ArrayList<>();
|
||||
private int streamUrlIndex = -1;
|
||||
private boolean suspended = false;
|
||||
|
||||
@Override
|
||||
public boolean isOnline() throws IOException, ExecutionException, InterruptedException {
|
||||
|
@ -92,6 +93,16 @@ public abstract class AbstractModel implements Model {
|
|||
// noop default implementation, can be overriden by concrete models
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuspended() {
|
||||
return suspended;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSuspended(boolean suspended) {
|
||||
this.suspended = suspended;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
|
|
|
@ -38,4 +38,7 @@ public interface Model {
|
|||
public Site getSite();
|
||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException;
|
||||
public void readSiteSpecificData(JsonReader reader) throws IOException;
|
||||
public boolean isSuspended();
|
||||
public void setSuspended(boolean suspended);
|
||||
|
||||
}
|
|
@ -32,6 +32,7 @@ public class ModelJsonAdapter extends JsonAdapter<Model> {
|
|||
String url = null;
|
||||
String type = null;
|
||||
int streamUrlIndex = -1;
|
||||
boolean suspended = false;
|
||||
|
||||
Model model = null;
|
||||
while(reader.hasNext()) {
|
||||
|
@ -55,6 +56,9 @@ public class ModelJsonAdapter extends JsonAdapter<Model> {
|
|||
} else if(key.equals("streamUrlIndex")) {
|
||||
streamUrlIndex = reader.nextInt();
|
||||
model.setStreamUrlIndex(streamUrlIndex);
|
||||
} else if(key.equals("suspended")) {
|
||||
suspended = reader.nextBoolean();
|
||||
model.setSuspended(suspended);
|
||||
} else if(key.equals("siteSpecific")) {
|
||||
reader.beginObject();
|
||||
model.readSiteSpecificData(reader);
|
||||
|
@ -87,6 +91,7 @@ public class ModelJsonAdapter extends JsonAdapter<Model> {
|
|||
writeValueIfSet(writer, "description", model.getDescription());
|
||||
writeValueIfSet(writer, "url", model.getUrl());
|
||||
writer.name("streamUrlIndex").value(model.getStreamUrlIndex());
|
||||
writer.name("suspended").value(model.isSuspended());
|
||||
writer.name("siteSpecific");
|
||||
writer.beginObject();
|
||||
model.writeSiteSpecificData(writer);
|
||||
|
|
|
@ -112,7 +112,12 @@ public class LocalRecorder implements Recorder {
|
|||
}
|
||||
|
||||
private void startRecordingProcess(Model model) throws IOException {
|
||||
LOG.debug("Restart recording for model {}", model.getName());
|
||||
if(model.isSuspended()) {
|
||||
LOG.info("Recording for model {} is suspended.", model);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Starting recording for model {}", model.getName());
|
||||
if (recordingProcesses.containsKey(model)) {
|
||||
LOG.error("A recording for model {} is already running", model);
|
||||
return;
|
||||
|
@ -315,7 +320,7 @@ public class LocalRecorder implements Recorder {
|
|||
while (running) {
|
||||
for (Model model : getModelsRecording()) {
|
||||
try {
|
||||
if (!recordingProcesses.containsKey(model)) {
|
||||
if (!model.isSuspended() && !recordingProcesses.containsKey(model)) {
|
||||
boolean isOnline = model.isOnline(IGNORE_CACHE);
|
||||
LOG.trace("Checking online state for {}: {}", model, (isOnline ? "online" : "offline"));
|
||||
if (isOnline) {
|
||||
|
@ -529,4 +534,42 @@ public class LocalRecorder implements Recorder {
|
|||
stopRecordingProcess(model);
|
||||
tryRestartRecording(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suspendRecording(Model model) {
|
||||
lock.lock();
|
||||
try {
|
||||
if (models.contains(model)) {
|
||||
int index = models.indexOf(model);
|
||||
models.get(index).setSuspended(true);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
Download download = recordingProcesses.get(model);
|
||||
if(download != null) {
|
||||
download.stop();
|
||||
recordingProcesses.remove(model);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeRecording(Model model) throws IOException {
|
||||
lock.lock();
|
||||
try {
|
||||
if (models.contains(model)) {
|
||||
int index = models.indexOf(model);
|
||||
Model m = models.get(index);
|
||||
m.setSuspended(false);
|
||||
startRecordingProcess(m);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,4 +28,7 @@ public interface Recorder {
|
|||
public void delete(Recording recording) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException;
|
||||
|
||||
public void shutdown();
|
||||
|
||||
public void suspendRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException;
|
||||
public void resumeRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException;
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ public class RemoteRecorder implements Recorder {
|
|||
|
||||
if("start".equals(action)) {
|
||||
models.add(model);
|
||||
} else {
|
||||
} else if("stop".equals(action)) {
|
||||
models.remove(model);
|
||||
}
|
||||
} else {
|
||||
|
@ -276,4 +276,14 @@ public class RemoteRecorder implements Recorder {
|
|||
public void switchStreamSource(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException {
|
||||
sendRequest("switch", model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suspendRecording(Model model) throws InvalidKeyException, NoSuchAlgorithmException, IllegalStateException, IOException {
|
||||
sendRequest("suspend", model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException {
|
||||
sendRequest("resume", model);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,6 +112,18 @@ public class RecorderServlet extends AbstractCtbrecServlet {
|
|||
response = "{\"status\": \"success\", \"msg\": \"Resolution switched\"}";
|
||||
resp.getWriter().write(response);
|
||||
break;
|
||||
case "suspend":
|
||||
LOG.debug("Suspend recording for model {} - {}", request.model.getName(), request.model.getUrl());
|
||||
recorder.suspendRecording(request.model);
|
||||
response = "{\"status\": \"success\", \"msg\": \"Recording suspended\"}";
|
||||
resp.getWriter().write(response);
|
||||
break;
|
||||
case "resume":
|
||||
LOG.debug("Resume recording for model {} - {}", request.model.getName(), request.model.getUrl());
|
||||
recorder.resumeRecording(request.model);
|
||||
response = "{\"status\": \"success\", \"msg\": \"Recording resumed\"}";
|
||||
resp.getWriter().write(response);
|
||||
break;
|
||||
default:
|
||||
resp.setStatus(SC_BAD_REQUEST);
|
||||
response = "{\"status\": \"error\", \"msg\": \"Unknown action\"}";
|
||||
|
|
|
@ -9,7 +9,6 @@ import com.iheartradio.m3u8.PlaylistException;
|
|||
import com.squareup.moshi.JsonReader;
|
||||
import com.squareup.moshi.JsonWriter;
|
||||
|
||||
import ctbrec.AbstractModel;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.recorder.download.StreamSource;
|
||||
import ctbrec.sites.Site;
|
||||
|
@ -19,14 +18,16 @@ import javafx.beans.property.SimpleBooleanProperty;
|
|||
/**
|
||||
* Just a wrapper for Model, which augments it with JavaFX value binding properties, so that UI widgets get updated proeprly
|
||||
*/
|
||||
public class JavaFxModel extends AbstractModel {
|
||||
public class JavaFxModel implements Model {
|
||||
private transient BooleanProperty onlineProperty = new SimpleBooleanProperty();
|
||||
private transient BooleanProperty pausedProperty = new SimpleBooleanProperty();
|
||||
private Model delegate;
|
||||
|
||||
public JavaFxModel(Model delegate) {
|
||||
this.delegate = delegate;
|
||||
try {
|
||||
onlineProperty.set(delegate.isOnline());
|
||||
pausedProperty.set(delegate.isSuspended());
|
||||
} catch (IOException | ExecutionException | InterruptedException e) {}
|
||||
}
|
||||
|
||||
|
@ -89,6 +90,10 @@ public class JavaFxModel extends AbstractModel {
|
|||
return onlineProperty;
|
||||
}
|
||||
|
||||
public BooleanProperty getPausedProperty() {
|
||||
return pausedProperty;
|
||||
}
|
||||
|
||||
Model getDelegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
@ -157,4 +162,35 @@ public class JavaFxModel extends AbstractModel {
|
|||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
||||
delegate.writeSiteSpecificData(writer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return delegate.getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescription(String description) {
|
||||
delegate.setDescription(description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStreamUrlIndex() {
|
||||
return delegate.getStreamUrlIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStreamUrlIndex(int streamUrlIndex) {
|
||||
delegate.setStreamUrlIndex(streamUrlIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuspended() {
|
||||
return delegate.isSuspended();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSuspended(boolean suspended) {
|
||||
delegate.setSuspended(suspended);
|
||||
pausedProperty.set(suspended);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
|||
ScrollPane scrollPane = new ScrollPane();
|
||||
TableView<JavaFxModel> table = new TableView<JavaFxModel>();
|
||||
ObservableList<JavaFxModel> observableModels = FXCollections.observableArrayList();
|
||||
ContextMenu popup = createContextMenu();
|
||||
ContextMenu popup;
|
||||
|
||||
Label modelLabel = new Label("Model");
|
||||
TextField model = new TextField();
|
||||
|
@ -104,11 +104,17 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
|||
online.setCellValueFactory((cdf) -> cdf.getValue().getOnlineProperty());
|
||||
online.setCellFactory(CheckBoxTableCell.forTableColumn(online));
|
||||
online.setPrefWidth(60);
|
||||
table.getColumns().addAll(name, url, online);
|
||||
TableColumn<JavaFxModel, Boolean> paused = new TableColumn<>("Paused");
|
||||
paused.setCellValueFactory((cdf) -> cdf.getValue().getPausedProperty());
|
||||
paused.setCellFactory(CheckBoxTableCell.forTableColumn(paused));
|
||||
paused.setPrefWidth(60);
|
||||
table.getColumns().addAll(name, url, online, paused);
|
||||
table.setItems(observableModels);
|
||||
table.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> {
|
||||
popup = createContextMenu();
|
||||
popup.show(table, event.getScreenX(), event.getScreenY());
|
||||
if(popup != null) {
|
||||
popup.show(table, event.getScreenX(), event.getScreenY());
|
||||
}
|
||||
event.consume();
|
||||
});
|
||||
table.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
|
||||
|
@ -194,6 +200,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
|||
threadPool.submit(() -> {
|
||||
try {
|
||||
javaFxModel.getOnlineProperty().set(javaFxModel.isOnline());
|
||||
javaFxModel.setSuspended(model.isSuspended());
|
||||
} catch (IOException | ExecutionException | InterruptedException e) {}
|
||||
});
|
||||
}
|
||||
|
@ -253,26 +260,37 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
|||
}
|
||||
|
||||
private ContextMenu createContextMenu() {
|
||||
MenuItem stop = new MenuItem("Stop Recording");
|
||||
JavaFxModel selectedModel = table.getSelectionModel().getSelectedItem();
|
||||
if(selectedModel == null) {
|
||||
return null;
|
||||
}
|
||||
MenuItem stop = new MenuItem("Remove Model");
|
||||
stop.setOnAction((e) -> stopAction());
|
||||
|
||||
MenuItem copyUrl = new MenuItem("Copy URL");
|
||||
copyUrl.setOnAction((e) -> {
|
||||
Model selected = table.getSelectionModel().getSelectedItem();
|
||||
Model selected = selectedModel;
|
||||
final Clipboard clipboard = Clipboard.getSystemClipboard();
|
||||
final ClipboardContent content = new ClipboardContent();
|
||||
content.putString(selected.getUrl());
|
||||
clipboard.setContent(content);
|
||||
});
|
||||
|
||||
MenuItem pauseRecording = new MenuItem("Pause Recording");
|
||||
pauseRecording.setOnAction((e) -> pauseRecording());
|
||||
MenuItem resumeRecording = new MenuItem("Resume Recording");
|
||||
resumeRecording.setOnAction((e) -> resumeRecording());
|
||||
MenuItem openInBrowser = new MenuItem("Open in Browser");
|
||||
openInBrowser.setOnAction((e) -> DesktopIntergation.open(table.getSelectionModel().getSelectedItem().getUrl()));
|
||||
openInBrowser.setOnAction((e) -> DesktopIntergation.open(selectedModel.getUrl()));
|
||||
MenuItem openInPlayer = new MenuItem("Open in Player");
|
||||
openInPlayer.setOnAction((e) -> Player.play(table.getSelectionModel().getSelectedItem().getUrl()));
|
||||
openInPlayer.setOnAction((e) -> Player.play(selectedModel.getUrl()));
|
||||
MenuItem switchStreamSource = new MenuItem("Switch resolution");
|
||||
switchStreamSource.setOnAction((e) -> switchStreamSource(table.getSelectionModel().getSelectedItem()));
|
||||
switchStreamSource.setOnAction((e) -> switchStreamSource(selectedModel));
|
||||
|
||||
return new ContextMenu(stop, copyUrl, openInBrowser, switchStreamSource);
|
||||
ContextMenu menu = new ContextMenu(stop);
|
||||
menu.getItems().add(selectedModel.isSuspended() ? resumeRecording : pauseRecording);
|
||||
menu.getItems().addAll(copyUrl, openInBrowser, switchStreamSource);
|
||||
return menu;
|
||||
}
|
||||
|
||||
private void switchStreamSource(JavaFxModel fxModel) {
|
||||
|
@ -345,4 +363,60 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
|||
}.start();
|
||||
}
|
||||
};
|
||||
|
||||
private void pauseRecording() {
|
||||
JavaFxModel model = table.getSelectionModel().getSelectedItem();
|
||||
Model delegate = table.getSelectionModel().getSelectedItem().getDelegate();
|
||||
if (delegate != null) {
|
||||
table.setCursor(Cursor.WAIT);
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
recorder.suspendRecording(delegate);
|
||||
Platform.runLater(() -> model.setSuspended(true));
|
||||
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
|
||||
LOG.error("Couldn't pause recording", e1);
|
||||
Platform.runLater(() -> {
|
||||
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
|
||||
alert.setTitle("Error");
|
||||
alert.setHeaderText("Couldn't pause recording");
|
||||
alert.setContentText("Error while pausing the recording: " + e1.getLocalizedMessage());
|
||||
alert.showAndWait();
|
||||
});
|
||||
} finally {
|
||||
table.setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
};
|
||||
|
||||
private void resumeRecording() {
|
||||
JavaFxModel model = table.getSelectionModel().getSelectedItem();
|
||||
Model delegate = table.getSelectionModel().getSelectedItem().getDelegate();
|
||||
if (delegate != null) {
|
||||
table.setCursor(Cursor.WAIT);
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
recorder.resumeRecording(delegate);
|
||||
Platform.runLater(() -> model.setSuspended(false));
|
||||
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
|
||||
LOG.error("Couldn't resume recording", e1);
|
||||
Platform.runLater(() -> {
|
||||
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
|
||||
alert.setTitle("Error");
|
||||
alert.setHeaderText("Couldn't resume recording");
|
||||
alert.setContentText("Error while resuming the recording: " + e1.getLocalizedMessage());
|
||||
alert.showAndWait();
|
||||
});
|
||||
} finally {
|
||||
table.setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue