From 2b8bb40f6d7f20ef44b2b6f2f163ddf696bcac1a Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 9 Jan 2021 19:06:52 +0100
Subject: [PATCH 1/8] Rename FfmpegStreamRedirector to ProcessStreamRedirector
---
.../{FfmpegStreamRedirector.java => ProcessStreamRedirector.java} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename common/src/main/java/ctbrec/io/{FfmpegStreamRedirector.java => ProcessStreamRedirector.java} (100%)
diff --git a/common/src/main/java/ctbrec/io/FfmpegStreamRedirector.java b/common/src/main/java/ctbrec/io/ProcessStreamRedirector.java
similarity index 100%
rename from common/src/main/java/ctbrec/io/FfmpegStreamRedirector.java
rename to common/src/main/java/ctbrec/io/ProcessStreamRedirector.java
From 632f104f03d3ad14e82483b4474717a2736c2490 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 9 Jan 2021 19:07:54 +0100
Subject: [PATCH 2/8] Code cleanup
---
.../main/java/ctbrec/sites/manyvids/MVLiveHlsDownload.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveHlsDownload.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveHlsDownload.java
index de0eab83..be4adef2 100644
--- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveHlsDownload.java
+++ b/common/src/main/java/ctbrec/sites/manyvids/MVLiveHlsDownload.java
@@ -12,9 +12,9 @@ import ctbrec.io.HttpClient;
import ctbrec.recorder.download.hls.HlsDownload;
public class MVLiveHlsDownload extends HlsDownload {
- private static final Logger LOG = LoggerFactory.getLogger(MVLiveMergedHlsDownload.class);
+ private static final Logger LOG = LoggerFactory.getLogger(MVLiveHlsDownload.class);
- private ScheduledExecutorService scheduler;
+ private transient ScheduledExecutorService scheduler;
public MVLiveHlsDownload(HttpClient client) {
super(client);
@@ -30,7 +30,7 @@ public class MVLiveHlsDownload extends HlsDownload {
t.setPriority(Thread.MIN_PRIORITY);
return t;
});
- scheduler.scheduleAtFixedRate(() -> updateCloudFlareCookies(), 120, 120, TimeUnit.SECONDS);
+ scheduler.scheduleAtFixedRate(this::updateCloudFlareCookies, 120, 120, TimeUnit.SECONDS);
updateCloudFlareCookies();
super.start();
} finally {
From f8130acf00a74e71d8bb13563d046f4b3e52a3ac Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 9 Jan 2021 21:58:49 +0100
Subject: [PATCH 3/8] Set version to 3.12.0
---
CHANGELOG.md | 7 ++++++-
client/pom.xml | 8 +++++---
common/pom.xml | 2 +-
master/pom.xml | 2 +-
server/pom.xml | 2 +-
5 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d1a9af11..632c52c8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,12 @@
-NEXT
+3.12.0
========================
* Added "record later" tab to "bookmark" models
* Added config option to show the total number of models in the title bar
+* Added support for hlsdl. Some sites (MV Live, LiveJasmin, Showup) are
+ excluded, because they need some special behavior while the download is
+ running. hlsdl can be activated in the settings under "Advanced" or with
+ the config properties "useHlsdl", "hlsdlExecutable" and "loghlsdlOutput".
+ The used bandwidth calculation does not work with hlsdl.
* Fixed problem with Cam4 playlist URLs, thanks @gohufrapoc
3.11.0
diff --git a/client/pom.xml b/client/pom.xml
index 1857d6d4..c97847b0 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.11.0
+ 3.12.0
../master
@@ -106,7 +106,7 @@
com.akathist.maven.plugins.launch4j
launch4j-maven-plugin
- 1.7.22
+ 1.7.25
l4j-win
@@ -132,7 +132,9 @@
true
15
512
- -Dfile.encoding=utf-8
+
+ -Dfile.encoding=utf-8
+
${project.version}.0
diff --git a/common/pom.xml b/common/pom.xml
index 4d831a05..6b0fbee3 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.11.0
+ 3.12.0
../master
diff --git a/master/pom.xml b/master/pom.xml
index 96ce2cf1..146e0658 100644
--- a/master/pom.xml
+++ b/master/pom.xml
@@ -6,7 +6,7 @@
ctbrec
master
pom
- 3.11.0
+ 3.12.0
../common
diff --git a/server/pom.xml b/server/pom.xml
index 4e879c40..d9a98733 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -8,7 +8,7 @@
ctbrec
master
- 3.11.0
+ 3.12.0
../master
From 4421c6f9c3dac71b4d3dac29fe9522ed0f7e50b0 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 9 Jan 2021 22:02:49 +0100
Subject: [PATCH 4/8] Add indicator icon for models marked for later recording
---
.../ctbrec/ui/controls/PausedIndicator.java | 34 ------
.../ui/controls/RecordingIndicator.java | 37 +++++++
.../main/java/ctbrec/ui/tabs/ThumbCell.java | 86 ++++++++++-----
.../java/ctbrec/ui/tabs/ThumbOverviewTab.java | 98 +++++++++++++-----
client/src/main/resources/bookmark-new-16.png | Bin 0 -> 1425 bytes
client/src/main/resources/bookmark-new.png | Bin 0 -> 785 bytes
.../resources/media-playback-pause-16.png | Bin 0 -> 1400 bytes
.../main/resources/media-playback-pause.png | Bin 0 -> 718 bytes
client/src/main/resources/media-record-16.png | Bin 0 -> 1445 bytes
client/src/main/resources/media-record.png | Bin 0 -> 1203 bytes
10 files changed, 171 insertions(+), 84 deletions(-)
delete mode 100644 client/src/main/java/ctbrec/ui/controls/PausedIndicator.java
create mode 100644 client/src/main/java/ctbrec/ui/controls/RecordingIndicator.java
create mode 100644 client/src/main/resources/bookmark-new-16.png
create mode 100644 client/src/main/resources/bookmark-new.png
create mode 100644 client/src/main/resources/media-playback-pause-16.png
create mode 100644 client/src/main/resources/media-playback-pause.png
create mode 100644 client/src/main/resources/media-record-16.png
create mode 100644 client/src/main/resources/media-record.png
diff --git a/client/src/main/java/ctbrec/ui/controls/PausedIndicator.java b/client/src/main/java/ctbrec/ui/controls/PausedIndicator.java
deleted file mode 100644
index 482d8d08..00000000
--- a/client/src/main/java/ctbrec/ui/controls/PausedIndicator.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package ctbrec.ui.controls;
-
-import ctbrec.ui.PauseIcon;
-import javafx.scene.Cursor;
-import javafx.scene.control.Tooltip;
-import javafx.scene.layout.StackPane;
-import javafx.scene.paint.Color;
-import javafx.scene.paint.Paint;
-import javafx.scene.shape.Rectangle;
-
-public class PausedIndicator extends StackPane {
-
- private PauseIcon pausedIcon;
- private Rectangle clickPanel;
-
- public PausedIndicator(int size, Color color) {
- setMaxSize(size, size);
-
- pausedIcon = new PauseIcon(color, size);
- pausedIcon.setVisible(false);
- clickPanel = new Rectangle(size, size);
- clickPanel.setCursor(Cursor.HAND);
- clickPanel.setFill(Paint.valueOf("#00000000"));
- getChildren().add(pausedIcon);
- getChildren().add(clickPanel);
-
- pausedIcon.visibleProperty().bindBidirectional(visibleProperty());
- clickPanel.onMouseClickedProperty().bindBidirectional(onMouseClickedProperty());
-
- Tooltip tooltip = new Tooltip("Resume Recording");
- Tooltip.install(clickPanel, tooltip);
- }
-}
-
diff --git a/client/src/main/java/ctbrec/ui/controls/RecordingIndicator.java b/client/src/main/java/ctbrec/ui/controls/RecordingIndicator.java
new file mode 100644
index 00000000..ed07aa1b
--- /dev/null
+++ b/client/src/main/java/ctbrec/ui/controls/RecordingIndicator.java
@@ -0,0 +1,37 @@
+package ctbrec.ui.controls;
+
+import javafx.scene.Cursor;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.StackPane;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Rectangle;
+
+public class RecordingIndicator extends StackPane {
+
+ private ImageView icon;
+ private Rectangle clickPanel;
+
+ public RecordingIndicator(int size) {
+ setMaxSize(size, size);
+
+ icon = new ImageView();
+ icon.setVisible(false);
+ icon.prefHeight(size);
+ icon.prefWidth(size);
+ icon.maxHeight(size);
+ icon.maxWidth(size);
+ clickPanel = new Rectangle(size, size);
+ clickPanel.setCursor(Cursor.HAND);
+ clickPanel.setFill(Paint.valueOf("#00000000"));
+ getChildren().add(icon);
+ getChildren().add(clickPanel);
+
+ icon.visibleProperty().bindBidirectional(visibleProperty());
+ }
+
+ public void setImage(Image img) {
+ icon.setImage(img);
+ }
+}
+
diff --git a/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java b/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java
index 3899e3d1..1c6455b9 100644
--- a/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java
+++ b/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java
@@ -31,7 +31,7 @@ import ctbrec.ui.SiteUiFactory;
import ctbrec.ui.StreamSourceSelectionDialog;
import ctbrec.ui.action.PlayAction;
import ctbrec.ui.controls.Dialogs;
-import ctbrec.ui.controls.PausedIndicator;
+import ctbrec.ui.controls.RecordingIndicator;
import ctbrec.ui.controls.StreamPreview;
import javafx.animation.FadeTransition;
import javafx.animation.FillTransition;
@@ -51,6 +51,7 @@ import javafx.scene.control.ContextMenu;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
+import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
@@ -72,6 +73,11 @@ public class ThumbCell extends StackPane {
private static final Logger LOG = LoggerFactory.getLogger(ThumbCell.class);
private static final Duration ANIMATION_DURATION = new Duration(250);
+ private static Image imgRecordIndicator = new Image(ThumbCell.class.getResource("/media-record-16.png").toExternalForm());
+ private static Image imgPauseIndicator = new Image(ThumbCell.class.getResource("/media-playback-pause-16.png").toExternalForm());
+ private static Image imgBookmarkIndicator = new Image(ThumbCell.class.getResource("/bookmark-new-16.png").toExternalForm());
+
+ private ModelRecordingState modelRecordingState = ModelRecordingState.NOT;
private Model model;
private StreamPreview streamPreview;
private ImageView iv;
@@ -85,8 +91,8 @@ public class ThumbCell extends StackPane {
private Text topic;
private Text resolutionTag;
private Recorder recorder;
- private Circle recordingIndicator;
- private PausedIndicator pausedIndicator;
+ private RecordingIndicator recordingIndicator;
+ private Tooltip recordingIndicatorTooltip;
private StackPane previewTrigger;
private int index = 0;
ContextMenu popup;
@@ -178,22 +184,15 @@ public class ThumbCell extends StackPane {
StackPane.setMargin(resolutionTag, new Insets(2, 4, 2, 2));
getChildren().add(resolutionTag);
- recordingIndicator = new Circle(8);
- recordingIndicator.setFill(colorRecording);
+ recordingIndicator = new RecordingIndicator(16);
recordingIndicator.setCursor(Cursor.HAND);
- recordingIndicator.setOnMouseClicked(e -> pauseResumeAction(true));
- Tooltip tooltip = new Tooltip("Pause Recording");
- Tooltip.install(recordingIndicator, tooltip);
+ recordingIndicator.setOnMouseClicked(this::recordingInidicatorClicked);
+ recordingIndicatorTooltip = new Tooltip("Pause Recording");
+ Tooltip.install(recordingIndicator, recordingIndicatorTooltip);
StackPane.setMargin(recordingIndicator, new Insets(3));
StackPane.setAlignment(recordingIndicator, Pos.TOP_LEFT);
getChildren().add(recordingIndicator);
- pausedIndicator = new PausedIndicator(16, colorRecording);
- pausedIndicator.setOnMouseClicked(e -> pauseResumeAction(false));
- StackPane.setMargin(pausedIndicator, new Insets(3));
- StackPane.setAlignment(pausedIndicator, Pos.TOP_LEFT);
- getChildren().add(pausedIndicator);
-
if (Config.getInstance().getSettings().livePreviews) {
getChildren().add(createPreviewTrigger());
}
@@ -231,6 +230,21 @@ public class ThumbCell extends StackPane {
update();
}
+ private void recordingInidicatorClicked(MouseEvent evt) {
+ switch(modelRecordingState) {
+ case RECORDING:
+ pauseResumeAction(true);
+ break;
+ case PAUSED:
+ pauseResumeAction(false);
+ break;
+ case BOOKMARKED:
+ recordLater(false);
+ break;
+ default:
+ }
+ }
+
private Node createPreviewTrigger() {
int s = 32;
previewTrigger = new StackPane();
@@ -293,7 +307,6 @@ public class ThumbCell extends StackPane {
streamPreview.stop();
}
recordingIndicator.setVisible(!visible);
- pausedIndicator.setVisible(!visible);
if (!visible) {
updateRecordingIndicator();
}
@@ -445,17 +458,32 @@ public class ThumbCell extends StackPane {
c = mouseHovering ? colorHighlight : colorNormal;
}
nameBackground.setFill(c);
-
updateRecordingIndicator();
}
private void updateRecordingIndicator() {
if (recording) {
- recordingIndicator.setVisible(!model.isSuspended());
- pausedIndicator.setVisible(model.isSuspended());
+ recordingIndicator.setVisible(true);
+ if (model.isSuspended()) {
+ modelRecordingState = ModelRecordingState.PAUSED;
+ recordingIndicator.setImage(imgPauseIndicator);
+ recordingIndicatorTooltip.setText("Resume Recording");
+ } else {
+ modelRecordingState = ModelRecordingState.RECORDING;
+ recordingIndicator.setImage(imgRecordIndicator);
+ recordingIndicatorTooltip.setText("Pause Recording");
+ }
} else {
- recordingIndicator.setVisible(false);
- pausedIndicator.setVisible(false);
+ if (model.isMarkedForLaterRecording()) {
+ recordingIndicator.setVisible(true);
+ modelRecordingState = ModelRecordingState.BOOKMARKED;
+ recordingIndicator.setImage(imgBookmarkIndicator);
+ recordingIndicatorTooltip.setText("Forget Model");
+ } else {
+ recordingIndicator.setVisible(false);
+ modelRecordingState = ModelRecordingState.NOT;
+ recordingIndicator.setImage(null);
+ }
}
}
@@ -562,9 +590,9 @@ public class ThumbCell extends StackPane {
});
}
- void recordLater() {
- model.setMarkedForLaterRecording(true);
- startStopAction(true);
+ void recordLater(boolean recordLater) {
+ model.setMarkedForLaterRecording(recordLater);
+ startStopAction(recordLater);
}
public Model getModel() {
@@ -577,7 +605,6 @@ public class ThumbCell extends StackPane {
this.model.setPreview(model.getPreview());
this.model.setTags(model.getTags());
this.model.setUrl(model.getUrl());
- this.model.setSuspended(recorder.isSuspended(model));
update();
}
@@ -591,9 +618,11 @@ public class ThumbCell extends StackPane {
private void update() {
model.setSuspended(recorder.isSuspended(model));
+ model.setMarkedForLaterRecording(recorder.isMarkedForLaterRecording(model));
setRecording(recorder.isTracked(model));
+ updateRecordingIndicator();
setImage(model.getPreview());
- String txt = recording ? " " : "";
+ String txt = (modelRecordingState != ModelRecordingState.NOT) ? " " : "";
txt += model.getDescription() != null ? model.getDescription() : "";
topic.setText(txt);
@@ -693,4 +722,11 @@ public class ThumbCell extends StackPane {
model.setMarkedForLaterRecording(false);
startStopAction(true);
}
+
+ private enum ModelRecordingState {
+ RECORDING,
+ PAUSED,
+ BOOKMARKED,
+ NOT
+ }
}
diff --git a/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTab.java b/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTab.java
index ae0a1b9b..25bd867d 100644
--- a/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTab.java
+++ b/client/src/main/java/ctbrec/ui/tabs/ThumbOverviewTab.java
@@ -1,5 +1,31 @@
package ctbrec.ui.tabs;
+import static ctbrec.ui.controls.Dialogs.*;
+
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.event.EventBusHolder;
@@ -7,12 +33,24 @@ import ctbrec.recorder.Recorder;
import ctbrec.sites.Site;
import ctbrec.sites.mfc.MyFreeCamsClient;
import ctbrec.sites.mfc.MyFreeCamsModel;
-import ctbrec.ui.*;
+import ctbrec.ui.AutosizeAlert;
+import ctbrec.ui.DesktopIntegration;
+import ctbrec.ui.SiteUiFactory;
+import ctbrec.ui.TipDialog;
+import ctbrec.ui.TokenLabel;
import ctbrec.ui.action.IgnoreModelsAction;
import ctbrec.ui.action.OpenRecordingsDir;
import ctbrec.ui.action.SetStopDateAction;
-import ctbrec.ui.controls.*;
-import javafx.animation.*;
+import ctbrec.ui.controls.CustomMouseBehaviorContextMenu;
+import ctbrec.ui.controls.FasterVerticalScrollPaneSkin;
+import ctbrec.ui.controls.SearchBox;
+import ctbrec.ui.controls.SearchPopover;
+import ctbrec.ui.controls.SearchPopoverTreeList;
+import javafx.animation.FadeTransition;
+import javafx.animation.Interpolator;
+import javafx.animation.ParallelTransition;
+import javafx.animation.ScaleTransition;
+import javafx.animation.TranslateTransition;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
@@ -28,24 +66,34 @@ import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Parent;
-import javafx.scene.control.*;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Button;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.ContextMenu;
+import javafx.scene.control.Label;
+import javafx.scene.control.MenuItem;
+import javafx.scene.control.ProgressIndicator;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.SeparatorMenuItem;
+import javafx.scene.control.Tab;
+import javafx.scene.control.TabPane;
+import javafx.scene.control.TextField;
+import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
-import javafx.scene.input.*;
-import javafx.scene.layout.*;
+import javafx.scene.input.Clipboard;
+import javafx.scene.input.ClipboardContent;
+import javafx.scene.input.ContextMenuEvent;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.input.MouseButton;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.FlowPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.StackPane;
import javafx.scene.transform.Transform;
import javafx.util.Duration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.net.SocketTimeoutException;
-import java.text.DecimalFormat;
-import java.util.*;
-import java.util.concurrent.*;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.stream.Collectors;
-
-import static ctbrec.ui.controls.Dialogs.showError;
public class ThumbOverviewTab extends Tab implements TabSelectionListener {
private static final Logger LOG = LoggerFactory.getLogger(ThumbOverviewTab.class);
@@ -441,7 +489,10 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
MenuItem addPaused = new MenuItem("Add in paused state");
addPaused.setOnAction(e -> addPaused(getSelectedThumbCells(cell)));
MenuItem recordLater = new MenuItem("Record Later");
- recordLater.setOnAction(e -> recordLater(getSelectedThumbCells(cell)));
+ recordLater.setOnAction(e -> recordLater(getSelectedThumbCells(cell), true));
+ MenuItem removeRecordLater = new MenuItem("Forget Model");
+ removeRecordLater.setOnAction(e -> recordLater(getSelectedThumbCells(cell), false));
+ MenuItem addRemoveBookmark = recorder.isMarkedForLaterRecording(model) ? removeRecordLater : recordLater;
MenuItem pause = new MenuItem("Pause Recording");
pause.setOnAction(e -> pauseResumeAction(getSelectedThumbCells(cell), true));
@@ -476,10 +527,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
if (modelIsTrackedByRecorder) {
contextMenu.getItems().addAll(pauseResume, recordLater);
} else {
- contextMenu.getItems().addAll(recordUntil, addPaused);
- if (!recorder.isMarkedForLaterRecording(model)) {
- contextMenu.getItems().add(recordLater);
- }
+ contextMenu.getItems().addAll(recordUntil, addPaused, addRemoveBookmark);
}
contextMenu.getItems().add(new SeparatorMenuItem());
if (site.supportsFollow()) {
@@ -500,9 +548,9 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
return contextMenu;
}
- private void recordLater(List list) {
+ private void recordLater(List list, boolean recordLater) {
for (ThumbCell cell : list) {
- cell.recordLater();
+ cell.recordLater(recordLater);
}
}
diff --git a/client/src/main/resources/bookmark-new-16.png b/client/src/main/resources/bookmark-new-16.png
new file mode 100644
index 0000000000000000000000000000000000000000..d50f515ac91a165291fc58825f8c441776b77769
GIT binary patch
literal 1425
zcmV;C1#bF@P)
zaB^>EX>4U6ba`-PAZ2)IW&i+q+O3vruHzsKh5xgPSpv2(hUM^-W(TwUIV5R&=gw7|
zNC|3)c=_P(kkpMozo+|!i&AD6)KbhbdR!rg%mok0zpioAdmH(%-5(bncXB^=Fls?9
zd!vP;SC00_`1q``&<`D?-zLa241W!5xnd
zytyPGwn7XE5co)<6v-R25e4A{Kn2QK0#^_qRqhbUK_r4rf{zdzbBwmJth~45sDO`3
z6a$+q1+YR;_{Tzmhl+{@RgG$zG;48DVu%`Jw7AY(F|lB3(ae%%D@jsFnqsn)Qcfjv
z792^)aanb8>uzUp48B4wieo!p!J^7
z#SFwa5Qy6%07LU)<`ad$i`-)7Gvib!L!>S?oqTZ=fiMhWo%Ce)B6pgbL#{t@<1^&k
zLU#i>c0;~#`+-_tZ^E__+fU)jsTZ6+^p8|H6(pPY2alQSforQWMtd}$t=-iL6NI*S
zJ;R_dVS=rQxCyC>#
zrc=I<^H}A)#aXM?SnHnrh2es}yu@`{Lr7r(i;y5fK@AmDVIx7iPKt#LohN<#G1o7V
zOCeVUj2!dWfCkz1ga5(r*;>VkaW5&H1iD`w=VKTM?E=lZ<9r`GPV)o^J_A>J+rQBO
zrawuqx3%~YFt80=T(>o454hX`2A>SslwB!EODGnB_cQvYJP^4B`c}QUwa#(+0Ay)a
z=^NnS5Ev;@_L|STdphU#Z%=D}KlrV3c3^81=l}o!24YJ`L;wH)0002_L%V+f000Sa
zNLh0L01ejw01ejxLMWSf0001xNklSAz2(69dD@A*Bumo>UElLaLMR}98%(f7ui-ey
z8izVyhz0r$z#X9iI(|$Gw7}ni0yiAXTGTDYAD-ok9X2>&j5%f)GyxZ^u*W$GA+P#b
fnr<<|wA=$v#MmB{xDV{g00000NkvXXu0mjfhk%@k
literal 0
HcmV?d00001
diff --git a/client/src/main/resources/bookmark-new.png b/client/src/main/resources/bookmark-new.png
new file mode 100644
index 0000000000000000000000000000000000000000..90b9daad381b56e5c3a8397496163c0df0f26692
GIT binary patch
literal 785
zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrU~J2Db`J1#c2+1T%1_J8No8Qr
zm{>c}*5h!1NUMMJQY}$OYmvee0dsXax;XTrT9raLwmPlmQ`7(3tj6ov)xG#ZRKbVz
zjjtX&TC=)&wF1Y#h8;UCHFtS*ephW3GmNX))BpLM<(_i+2kWQL%qr_)T$Lud(k13Z
zN_gu7?V1;E_GtnWXRJKmnItstYpMU;kaEL$Pw#$D|Egs6WRf>n=+9haj1
zdCAWBr?U6jrA6)z_st3l?_PRh`=E%u!{+5AMa!UuY4-Ek@11`!d((jfx)(M-JGZv7
z?=riB?`E%i4D9U(JQ_D$x4n1ws@?Yb>RF$EGd5l=T6@&knI9N2Y)RhkE
zaB^>EX>4U6ba`-PAZ2)IW&i+q+P#%)lH({4g#UAjJp%Qj;5dj!#NJ?!KZzu}XJMGIHF*&clqb${Lpolgo6KQLNB
zD@Uj6`Bl#T+xT>E5$M4OvR@|1Jq`by*z4KPk^iS?=sm~Ba?anJJ0SVvmTh
zdOG(Cx|kOSTH7e=iD3Xj^WZ
zU(|Sai6XZ_3<(g#L}HXEIV3Xk^c&7q3~eX3Hsi&bj1T
zxCoAY}apPym
zg@x_{avX;I!tDXIzHY*{6FW}f+S4dFeQi|EmAAG98KbTDjKQzA6Pvup=?WiFMDknQ
z_rr$Qbv)8DaQZLN14CY;C(dw<9?sz!J!yt(^uXEI=-C5Zq310hJ<@|f*XVh}N00UJ
z7Ov6rvXB0x=bP8a+dlfU9vR^pJums_Gd-h)YxKO}qtErceT}^Bqp$S5d5yg7qp$UR
z>+k4gA3dYz8-GVH`{+46f6qtH>UkFb>?QmFr%9&%UQDf+0004mX+uL$Nkc;*aB^>E
zX>4Tx0C=2zkv&MmP!xqvQ>7{uhjx(SkfAzR5EXIMDionYs1;guFnQ@8G%+M8E{=k0
z!NH%!s)LKOt`4q(Aov5~=H{g6A|>9J6k5c1;qgAsyXWxUeSpxYGR^8512o+>GpVGQ
z%dd!`R|L?D2x17x%ra&rDGlHHx~Fccy9Cej@B6d*)q=%z@3D;ex)r#C2LjNMQkskRU=q4HZ;jBSE`PiiHfFCw=@e*DsMvAy);A9P`+K
z2HEw4|H1FsTE&TRFDaY^x?ddUV;BhS0?oSPd>=bb^8^S!16O+6ztI4uKS{5*wfGS*
zunk;Xw>4!CxZD8-pA6ZQT`5RQC>DYDGy0}H5V-~VR=v5k&T;wxWNB9E8{ps&7%5Tq
zn$Np?I_LIpPiuZZ_^onwU~3fU00006VoOIv00000008+zyMF)x010qNS#tmY4c7nw
z4c7reD4Tcy004kVL_t(I%k9Z=_igyApf(NaRo5R@6<&4B2rGYvZ+u|Wn%nH{J}
znHDL~5b5EL7R8c}*5h!1NUMMFQZ3Q;vI&nSEVPOg5G}l-C9=?Ig=kc%LFG$%6GIhEy}qXGg9qmy
zUDe#Zc^%)nCWRl&kDfhS@E@Z1k2E$$yh>Qw9C`CfhkEDZRm+0+
zP52z~bkEVq)z>0k&->H*uxa0(Bc)N!9*KVHcwK*@
zw_-SJkId&A4Qw~RHvd2P{$|wC&O&|7IV`*4Wu7NXaJ*$YyX$d{ytUtF77jb+@T%MU
zFET8%_guey>kmPL4ZJZi+f7sXcidrQo6mD|O01@bw}x2bcjI5nYy@_){tDZ>bL+Xn
zUkoXkVK1d0JP?r34E#3h{O)S2=il=Ev+L}CtSu_LH|O6{V6?C$dAqv+X(0INyt^Jq
zaTa()76W7OItVj5Y0Rzw3f}W{aSW-r_4aZgFM|SygQMwW#SR}1>r4*&IaT5e$G&N>
zf=mE{3g5f321?IPGFPS9?)?Al_iXtevl&<#7z7xY92htl7!?>;29$&B2nHzl9_9L&
V
zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=HElH4E+hTl0wj(|1<$H6RBxj~MfAKUGo%#!KU
zOjTYQsx1nHB=iZp!}#@ch978@NrRf_R8op)TC{M*Ajf+fr#$8;>i)bnI-eBYU0_Io
zlw;EMT$OYFrk^eeL+>n*z1kp`8-D57>p9Pn|A?rh%g6mV=WouQ=N!lBswSi%9fvwx
zgqs;CZpf1C+m=Dc&oK7A7xWx!Y^1Tr7B}=pg_tBxQtE*!O&Zi88#TnJJlG+b8_PbI
z#kp{kC?vW%O?D~LB@0XAAtD-tuUKgJTXuhqGCX&JDie$mw*0k+&lDc%bGC>a5#jW7
z<_d8!FAlWQDC-F|0HJwuvpvDLezoudtbm~HY0fxcjoV>jGrHH7>;)juGsvU9$orzk
zdzC1B8-gJLf|y8*5=CcrA{EX6R1rCg@&*EA#vMVjiX^bfs3Z8s9;aQGjgNU;74R`h
z;$Tyx0+yw-wA#5#*KR#^@41&=
z2Mv@`7&>gE;UkYS>O`$gnL2Hz=`+tV>qYIP`lkN_H9D#BPHO4-MGdp=%g}Cu)_cYl
zGZ14X5cfp@1KX$y-8M6+q?pUEh@n>m(2EFS
z2*}JbW+f>N-}<_zZmPQk&+_m4v-;J7#ejfFJi`prCSE6=+O!SM`@~UJmQ~_&;!%?>
zNc_lk#p5^5C6@)B88x$+IpQd>Sn6PpOyv13o)>!MF{Dt9y
zzP!YBT0=--0gI3zLO~4`RAD1QyH1LQ44o%^{4v)rkxL<01&kc?*nkGv^@IPx@7Y?#
ziE%F}oCLaG9Oq*g2<-yRy5oEwJ5KWi2tET>dfUIz0H!}lueY`M5iqa~TwJ#`We>RA
z0S2E8*_2%=NJ}Udf%h}|raTb21^QOKxwX!5`T%5UR_Pny;1C!oQTCe8yL&q4_HR#X
zen0rFa&};A6zBi|00v@9M??Ss00000`9r&Z00009a7bBm000fw000fw0YWI7cmMzZ
z>q$gGR5;76(=kp0Q4q%Q-#&Xo#U0dwglr)tR&@5Bz<385y@Qbp*xS91lE#ow7&*gM
zgqpB6vxcmRWYMpDZ~ouRo0(T=>3uRe#{fh0;&_84R%OoH!}#I=Ji|5aaf3cmT<=j~
zju{qZ&bx1efN$}L%R}RqYOFEETbc8&$uPnbu6}`=gclJzf+F7hjJqRnyOLKtq-fZ<
z1KdSVuDl8LLv0-%wxDIBOVz*!L
z*}-6qS()>;Y2qT*^W#V*btIYw^G~1YFW>nK+`CGVFp1yg00000NkvXXu0mjf4`#NV
literal 0
HcmV?d00001
diff --git a/client/src/main/resources/media-record.png b/client/src/main/resources/media-record.png
new file mode 100644
index 0000000000000000000000000000000000000000..b4a507cb2a258ff51b3faeb24c68c4c0354bd90f
GIT binary patch
literal 1203
zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrU~J2Db`J1#c2+1T%1_J8No8Qr
zm{>c}*5h!1NUMMJQY}$OYmvee0dsXax;XTrT9raLwmPlmQ`7(3tj6ov)xG#ZRKbVz
zjjtX&TC=)&wF1Y#h8;UCHFtS*ephW3GmNX))BpLM<(_i+2kWQL%qr_)T$Lud(k13Z
zN_gu7?V1;E_GtnWXRJKmnItstYpMU;kaEL$Pw#$D|Egs6WRf>n=+9haj1
zdCAWBr?U6jrA6)z_st3l?_PRh`=E%u!{+5AMa!UuY4-Ek@11`!d((jfx)(M-JGZv7
z?=riB?`E%i4D9U(JQ_D$x4n1ws@?Yb>RF$EGd5l=T6@&knI9N2Y)RhkE?0b!yK+|{KCV_qVoLBZpoZkG;e#e{OUwsUIWqY5Q2^{*a
z+X#|s`B3Yv(DFW-NwMa>5XZyc(;PVNZ)Z7d_3-}>&n5i%Gw$Ez5ODhEx`hAp+HOII
zudn4;oqp_7m57MD&$0V{Id@2A!}iC8-YI6+wsbM{?Ykb|84Jm$ETVf*EmelDe&wZ%Nw8F%mK@;}bWZ*R5kdP{r%O{o>x=)f^2hRfppXSx#^l+MQVn
z3>)qjcmJ;DYGCr9GGMunu{`U-fF)e!=REOy^o90#`}WR
zAAu<~;v4)upQYZkJoRspVvQ;9gO$8nSk{L%%u~G}wl9jYO4u>^gBlBmVhb=l*Vs*}
zlRCck+y6eJ&pW=$JP!DE^5)hV(^J3K?430sY|YzMGEEt^K7XfNN_cyZqnIhfMmb~N
u$5`$QL2RcNmTNlXDpqmIAQBpw_3OEOrWQ}*^|1U8Aik%opUXO@geCw~Hw7dB
literal 0
HcmV?d00001
From 8e22112603528250754510c971acc321b59d8140 Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 9 Jan 2021 22:03:01 +0100
Subject: [PATCH 5/8] Add support for hlsdl
---
.../java/ctbrec/ui/settings/SettingsTab.java | 29 ++-
.../resources/html/docs/ConfigurationFile.md | 8 +-
.../src/main/java/ctbrec/AbstractModel.java | 11 +-
common/src/main/java/ctbrec/Config.java | 2 +-
common/src/main/java/ctbrec/Settings.java | 3 +
.../ctbrec/io/ProcessStreamRedirector.java | 8 +-
.../src/main/java/ctbrec/recorder/FFmpeg.java | 10 +-
.../ctbrec/recorder/download/hls/Hlsdl.java | 150 +++++++++++++++
.../recorder/download/hls/HlsdlDownload.java | 182 ++++++++++++++++++
.../sites/fc2live/Fc2HlsdlDownload.java | 27 +++
.../java/ctbrec/sites/fc2live/Fc2Model.java | 10 +-
11 files changed, 415 insertions(+), 25 deletions(-)
create mode 100644 common/src/main/java/ctbrec/recorder/download/hls/Hlsdl.java
create mode 100644 common/src/main/java/ctbrec/recorder/download/hls/HlsdlDownload.java
create mode 100644 common/src/main/java/ctbrec/sites/fc2live/Fc2HlsdlDownload.java
diff --git a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java
index 7e9051ad..17d6bf2f 100644
--- a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java
+++ b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java
@@ -9,6 +9,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import org.slf4j.Logger;
@@ -117,6 +118,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
private SimpleLongProperty leaveSpaceOnDevice;
private SimpleStringProperty ffmpegParameters;
private SimpleBooleanProperty logFFmpegOutput;
+ private SimpleBooleanProperty loghlsdlOutput;
private SimpleStringProperty fileExtension;
private SimpleStringProperty server;
private SimpleIntegerProperty port;
@@ -126,6 +128,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
private SimpleBooleanProperty totalModelCountInTitle;
private SimpleBooleanProperty transportLayerSecurity;
private SimpleBooleanProperty fastScrollSpeed;
+ private SimpleBooleanProperty useHlsdl;
+ private SimpleFileProperty hlsdlExecutable;
private ExclusiveSelectionProperty recordLocal;
private SimpleIntegerProperty postProcessingThreads;
private IgnoreList ignoreList;
@@ -170,6 +174,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
leaveSpaceOnDevice = new SimpleLongProperty(null, "minimumSpaceLeftInBytes", (long) new GigabytesConverter().convertTo(settings.minimumSpaceLeftInBytes));
ffmpegParameters = new SimpleStringProperty(null, "ffmpegMergedDownloadArgs", settings.ffmpegMergedDownloadArgs);
logFFmpegOutput = new SimpleBooleanProperty(null, "logFFmpegOutput", settings.logFFmpegOutput);
+ loghlsdlOutput = new SimpleBooleanProperty(null, "loghlsdlOutput", settings.loghlsdlOutput);
fileExtension = new SimpleStringProperty(null, "ffmpegFileSuffix", settings.ffmpegFileSuffix);
server = new SimpleStringProperty(null, "httpServer", settings.httpServer);
port = new SimpleIntegerProperty(null, "httpPort", settings.httpPort);
@@ -184,6 +189,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
onlineCheckSkipsPausedModels = new SimpleBooleanProperty(null, "onlineCheckSkipsPausedModels", settings.onlineCheckSkipsPausedModels);
fastScrollSpeed = new SimpleBooleanProperty(null, "fastScrollSpeed", settings.fastScrollSpeed);
confirmationDialogs = new SimpleBooleanProperty(null, "confirmationForDangerousActions", settings.confirmationForDangerousActions);
+ useHlsdl = new SimpleBooleanProperty(null, "useHlsdl", settings.useHlsdl);
+ hlsdlExecutable = new SimpleFileProperty(null, "hlsdlExecutable", settings.hlsdlExecutable);
}
private void createGui() {
@@ -269,6 +276,11 @@ public class SettingsTab extends Tab implements TabSelectionListener {
Category.of("Advanced / Devtools",
Group.of("Logging",
Setting.of("Log FFmpeg output", logFFmpegOutput, "Log FFmpeg output to files in the system's temp directory")
+ ),
+ Group.of("hlsdl (experimental)",
+ Setting.of("Use hlsdl (if possible)", useHlsdl, "Use hlsdl to record the live streams. Some features might not work correctly."),
+ Setting.of("hlsdl executable", hlsdlExecutable, "Path to the hlsdl executable"),
+ Setting.of("Log hlsdl output", loghlsdlOutput, "Log hlsdl output to files in the system's temp directory")
)
)
);
@@ -300,7 +312,6 @@ public class SettingsTab extends Tab implements TabSelectionListener {
setContent(stackPane);
prefs.expandTree();
-
prefs.getSetting("httpServer").ifPresent(s -> bindEnabledProperty(s, recordLocal));
prefs.getSetting("httpPort").ifPresent(s -> bindEnabledProperty(s, recordLocal));
prefs.getSetting("servletContext").ifPresent(s -> bindEnabledProperty(s, recordLocal));
@@ -321,10 +332,10 @@ public class SettingsTab extends Tab implements TabSelectionListener {
prefs.getSetting("concurrentRecordings").ifPresent(s -> bindEnabledProperty(s, recordLocal.not()));
prefs.getSetting("concurrentRecordings").ifPresent(s -> bindEnabledProperty(s, recordLocal.not()));
prefs.getSetting("downloadFilename").ifPresent(s -> bindEnabledProperty(s, recordLocal));
+ prefs.getSetting("hlsdlExecutable").ifPresent(s -> bindEnabledProperty(s, useHlsdl.not()));
+ prefs.getSetting("loghlsdlOutput").ifPresent(s -> bindEnabledProperty(s, useHlsdl.not()));
postProcessingStepPanel.disableProperty().bind(recordLocal.not());
variablesHelpButton.disableProperty().bind(recordLocal);
-
-
}
private void splitValuesChanged(ObservableValue> value, Object oldV, Object newV) {
@@ -425,11 +436,13 @@ public class SettingsTab extends Tab implements TabSelectionListener {
}
public void saveConfig() {
- try {
- Config.getInstance().save();
- } catch (IOException e) {
- LOG.error("Couldn't save config", e);
- }
+ CompletableFuture.runAsync(() -> {
+ try {
+ Config.getInstance().save();
+ } catch (IOException e) {
+ LOG.error("Couldn't save config", e);
+ }
+ });
}
@Override
diff --git a/client/src/main/resources/html/docs/ConfigurationFile.md b/client/src/main/resources/html/docs/ConfigurationFile.md
index f35fa26f..a864b781 100644
--- a/client/src/main/resources/html/docs/ConfigurationFile.md
+++ b/client/src/main/resources/html/docs/ConfigurationFile.md
@@ -28,6 +28,8 @@ until a recording is finished. 0 means unlimited.
- **generatePlaylist** (server only) - [`true`,`false`] Generate a playlist once a recording terminates.
+- **hlsdlExecutable** - Path to the hlsdl executable, which is used, if `useHlsdl` is set to true
+
- **httpPort** - [1 - 65536] The TCP port, the server listens on. In the server config, this will tell the server, which port to use. In the application this will set
the port ctbrec tries to connect to, if it is run in remote mode.
@@ -43,7 +45,9 @@ the port ctbrec tries to connect to, if it is run in remote mode.
- **livePreviews** (app only) - Enables the live preview feature in the app.
-- **logFFmpegOutput** - The output from FFmpeg (from recordings or post-processing steps) will be logged in temporary files.
+- **logFFmpegOutput** - [`true`,`false`] The output from FFmpeg (from recordings or post-processing steps) will be logged in temporary files.
+
+- **loghlsdlOutput** - [`true`,`false`] The output from hlsdl will be logged in temporary files. Only in effect, if `useHlsdl` is set to true
- **minimumResolution** - [1 - 2147483647]. Sets the minimum video height for a recording. ctbrec tries to find a stream quality, which is higher than or equal to this value. If the only provided stream quality is below this threshold, ctbrec won't record the stream.
@@ -73,5 +77,7 @@ which have the defined length (roughly). Has to be activated with `splitStrategy
- **splitRecordingsBiggerThanBytes** - [0 - 9223372036854775807] in bytes. Split recordings, if the size on disk exceeds this value. The recordings are split up into several individual recordings,
which have the defined size (roughly). Has to be activated with `splitStrategy`.
+- **useHlsdl** - [`true`,`false`] Use hlsdl to record the live streams. You also have to set `hlsdlExecutable`, if hlsdl is not globally available on your system. hlsdl won't be used for MV Live, LiveJasmin and Showup.
+
- **webinterface** (server only) - [`true`,`false`] Enables the webinterface for the server. You can access it with http://host:port/static/index.html Don't activate this on
a machine, which can be accessed from the internet, because this is totally unprotected at the moment.
diff --git a/common/src/main/java/ctbrec/AbstractModel.java b/common/src/main/java/ctbrec/AbstractModel.java
index b0ab9814..216f4c72 100644
--- a/common/src/main/java/ctbrec/AbstractModel.java
+++ b/common/src/main/java/ctbrec/AbstractModel.java
@@ -17,6 +17,7 @@ import ctbrec.recorder.download.Download;
import ctbrec.recorder.download.HttpHeaderFactory;
import ctbrec.recorder.download.HttpHeaderFactoryImpl;
import ctbrec.recorder.download.hls.HlsDownload;
+import ctbrec.recorder.download.hls.HlsdlDownload;
import ctbrec.recorder.download.hls.MergedFfmpegHlsDownload;
import ctbrec.sites.Site;
import okhttp3.Request;
@@ -275,10 +276,14 @@ public abstract class AbstractModel implements Model {
@Override
public Download createDownload() {
- if (Config.isServerMode() && !Config.getInstance().getSettings().recordSingleFile) {
- return new HlsDownload(getSite().getHttpClient());
+ if (Config.getInstance().getSettings().useHlsdl) {
+ return new HlsdlDownload();
} else {
- return new MergedFfmpegHlsDownload(getSite().getHttpClient());
+ if (Config.isServerMode() && !Config.getInstance().getSettings().recordSingleFile) {
+ return new HlsDownload(getSite().getHttpClient());
+ } else {
+ return new MergedFfmpegHlsDownload(getSite().getHttpClient());
+ }
}
}
diff --git a/common/src/main/java/ctbrec/Config.java b/common/src/main/java/ctbrec/Config.java
index 793c4cb0..76aba0c9 100644
--- a/common/src/main/java/ctbrec/Config.java
+++ b/common/src/main/java/ctbrec/Config.java
@@ -213,7 +213,7 @@ public class Config {
return settings;
}
- public void save() throws IOException {
+ public synchronized void save() throws IOException {
Moshi moshi = new Moshi.Builder()
.add(Model.class, new ModelJsonAdapter())
.add(PostProcessor.class, new PostProcessorJsonAdapter())
diff --git a/common/src/main/java/ctbrec/Settings.java b/common/src/main/java/ctbrec/Settings.java
index 73ed55bb..70e59939 100644
--- a/common/src/main/java/ctbrec/Settings.java
+++ b/common/src/main/java/ctbrec/Settings.java
@@ -69,6 +69,7 @@ public class Settings {
public String flirt4freePassword;
public String flirt4freeUsername;
public boolean generatePlaylist = true;
+ public String hlsdlExecutable = "hlsdl";
public int httpPort = 8080;
public int httpSecurePort = 8443;
public String httpServer = "localhost";
@@ -84,6 +85,7 @@ public class Settings {
public boolean livePreviews = false;
public boolean localRecording = true;
public boolean logFFmpegOutput = false;
+ public boolean loghlsdlOutput = false;
public int minimumResolution = 0;
public int maximumResolution = 8640;
public int maximumResolutionPlayer = 0;
@@ -156,6 +158,7 @@ public class Settings {
public boolean transportLayerSecurity = true;
public int thumbWidth = 180;
public boolean updateThumbnails = true;
+ public boolean useHlsdl = false;
@Deprecated
public String username = "";
public int windowHeight = 800;
diff --git a/common/src/main/java/ctbrec/io/ProcessStreamRedirector.java b/common/src/main/java/ctbrec/io/ProcessStreamRedirector.java
index 2be0e63f..2b68b8c8 100644
--- a/common/src/main/java/ctbrec/io/ProcessStreamRedirector.java
+++ b/common/src/main/java/ctbrec/io/ProcessStreamRedirector.java
@@ -9,15 +9,15 @@ import java.util.concurrent.ScheduledExecutorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class FfmpegStreamRedirector implements Runnable {
- private static final Logger LOG = LoggerFactory.getLogger(FfmpegStreamRedirector.class);
+public class ProcessStreamRedirector implements Runnable {
+ private static final Logger LOG = LoggerFactory.getLogger(ProcessStreamRedirector.class);
private InputStream in;
private OutputStream out;
private boolean keepGoing = true;
private ScheduledExecutorService executor;
- public FfmpegStreamRedirector(ScheduledExecutorService executor, InputStream in, OutputStream out) {
+ public ProcessStreamRedirector(ScheduledExecutorService executor, InputStream in, OutputStream out) {
super();
this.executor = executor;
this.in = in;
@@ -37,7 +37,7 @@ public class FfmpegStreamRedirector implements Runnable {
executor.schedule(this, 100, MILLISECONDS);
}
} catch (Exception e) {
- LOG.debug("Error while reading from FFmpeg output stream: {}", e.getLocalizedMessage());
+ LOG.debug("Error while reading from process output stream: {}", e.getLocalizedMessage());
keepGoing = false;
}
}
diff --git a/common/src/main/java/ctbrec/recorder/FFmpeg.java b/common/src/main/java/ctbrec/recorder/FFmpeg.java
index 9d13e511..2fa42f2a 100644
--- a/common/src/main/java/ctbrec/recorder/FFmpeg.java
+++ b/common/src/main/java/ctbrec/recorder/FFmpeg.java
@@ -15,7 +15,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.io.DevNull;
-import ctbrec.io.FfmpegStreamRedirector;
+import ctbrec.io.ProcessStreamRedirector;
import ctbrec.recorder.download.ProcessExitedUncleanException;
public class FFmpeg {
@@ -30,8 +30,8 @@ public class FFmpeg {
private Consumer exitCallback;
private File ffmpegLog = null;
private OutputStream ffmpegLogStream;
- private FfmpegStreamRedirector stdoutRedirector;
- private FfmpegStreamRedirector stderrRedirector;
+ private ProcessStreamRedirector stdoutRedirector;
+ private ProcessStreamRedirector stderrRedirector;
private FFmpeg() {}
@@ -85,8 +85,8 @@ public class FFmpeg {
} else {
ffmpegLogStream = new DevNull();
}
- stdoutRedirector = new FfmpegStreamRedirector(processOutputReader, process.getInputStream(), ffmpegLogStream);
- stderrRedirector = new FfmpegStreamRedirector(processOutputReader, process.getErrorStream(), ffmpegLogStream);
+ stdoutRedirector = new ProcessStreamRedirector(processOutputReader, process.getInputStream(), ffmpegLogStream);
+ stderrRedirector = new ProcessStreamRedirector(processOutputReader, process.getErrorStream(), ffmpegLogStream);
processOutputReader.submit(stdoutRedirector);
processOutputReader.submit(stderrRedirector);
}
diff --git a/common/src/main/java/ctbrec/recorder/download/hls/Hlsdl.java b/common/src/main/java/ctbrec/recorder/download/hls/Hlsdl.java
new file mode 100644
index 00000000..d6590385
--- /dev/null
+++ b/common/src/main/java/ctbrec/recorder/download/hls/Hlsdl.java
@@ -0,0 +1,150 @@
+package ctbrec.recorder.download.hls;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.function.Consumer;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ctbrec.io.DevNull;
+import ctbrec.io.ProcessStreamRedirector;
+import ctbrec.recorder.download.ProcessExitedUncleanException;
+
+public class Hlsdl {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Hlsdl.class);
+
+ private static ScheduledExecutorService processOutputReader = Executors.newScheduledThreadPool(2, createThreadFactory("hlsdl output stream reader"));
+
+ private Process process;
+ private boolean logOutput = false;
+ private Consumer startCallback;
+ private Consumer exitCallback;
+ private File processLog = null;
+ private OutputStream processLogStream;
+ private ProcessStreamRedirector stdoutRedirector;
+ private ProcessStreamRedirector stderrRedirector;
+
+ private Hlsdl() {}
+
+ private static ThreadFactory createThreadFactory(String name) {
+ return r -> {
+ Thread t = new Thread(r);
+ t.setName(name);
+ t.setDaemon(true);
+ t.setPriority(Thread.MIN_PRIORITY);
+ return t;
+ };
+ }
+
+ public void exec(String[] cmdline, String[] env, File executionDir) throws IOException, InterruptedException {
+ LOG.debug("hlsdl command line: {}", Arrays.toString(cmdline));
+ process = Runtime.getRuntime().exec(cmdline, env, executionDir);
+ afterStart();
+ int exitCode = process.waitFor();
+ afterExit(exitCode);
+ }
+
+ private void afterStart() throws IOException {
+ notifyStartCallback(process);
+ setupLogging();
+ }
+
+ private void afterExit(int exitCode) throws IOException {
+ LOG.debug("hlsdl exit code was {}", exitCode);
+ processLogStream.flush();
+ processLogStream.close();
+ stdoutRedirector.setKeepGoing(false);
+ stderrRedirector.setKeepGoing(false);
+ notifyExitCallback(exitCode);
+ if (exitCode != 1) {
+ if (processLog != null && processLog.exists()) {
+ Files.delete(processLog.toPath());
+ }
+ } else {
+ throw new ProcessExitedUncleanException("hlsdl exit code was " + exitCode);
+ }
+ }
+
+ private void setupLogging() throws IOException {
+ if (logOutput) {
+ if (processLog == null) {
+ processLog = File.createTempFile("hlsdl_", ".log");
+ }
+ LOG.debug("Logging hlsdl output to {}", processLog);
+ processLog.deleteOnExit();
+ processLogStream = new FileOutputStream(processLog);
+ } else {
+ processLogStream = new DevNull();
+ }
+ stdoutRedirector = new ProcessStreamRedirector(processOutputReader, process.getInputStream(), processLogStream);
+ stderrRedirector = new ProcessStreamRedirector(processOutputReader, process.getErrorStream(), processLogStream);
+ processOutputReader.submit(stdoutRedirector);
+ processOutputReader.submit(stderrRedirector);
+ }
+
+ private void notifyStartCallback(Process process) {
+ try {
+ startCallback.accept(process);
+ } catch(Exception e) {
+ LOG.error("Exception in onStart callback", e);
+ }
+ }
+
+ private void notifyExitCallback(int exitCode) {
+ try {
+ exitCallback.accept(exitCode);
+ } catch(Exception e) {
+ LOG.error("Exception in onExit callback", e);
+ }
+ }
+
+ public int waitFor() throws InterruptedException {
+ return process.waitFor();
+ }
+
+ public static class Builder {
+ private boolean logOutput = false;
+ private File logFile;
+ private Consumer startCallback;
+ private Consumer exitCallback;
+
+ public Builder logOutput(boolean logOutput) {
+ this.logOutput = logOutput;
+ return this;
+ }
+
+ public Builder logFile(File logFile) {
+ this.logFile = logFile;
+ return this;
+ }
+
+ public Builder onStarted(Consumer callback) {
+ this.startCallback = callback;
+ return this;
+ }
+
+ public Builder onExit(Consumer callback) {
+ this.exitCallback = callback;
+ return this;
+ }
+
+ public Hlsdl build() {
+ Hlsdl instance = new Hlsdl();
+ instance.logOutput = logOutput;
+ instance.startCallback = startCallback != null ? startCallback : p -> {};
+ instance.exitCallback = exitCallback != null ? exitCallback : exitCode -> {};
+ instance.processLog = logFile;
+ return instance;
+ }
+ }
+
+}
diff --git a/common/src/main/java/ctbrec/recorder/download/hls/HlsdlDownload.java b/common/src/main/java/ctbrec/recorder/download/hls/HlsdlDownload.java
new file mode 100644
index 00000000..7ee18562
--- /dev/null
+++ b/common/src/main/java/ctbrec/recorder/download/hls/HlsdlDownload.java
@@ -0,0 +1,182 @@
+package ctbrec.recorder.download.hls;
+
+import static ctbrec.recorder.download.StreamSource.*;
+
+import java.io.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ExecutionException;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import javax.xml.bind.JAXBException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.iheartradio.m3u8.ParseException;
+import com.iheartradio.m3u8.PlaylistException;
+
+import ctbrec.Config;
+import ctbrec.Model;
+import ctbrec.OS;
+import ctbrec.Recording;
+import ctbrec.recorder.download.AbstractDownload;
+import ctbrec.recorder.download.StreamSource;
+
+public class HlsdlDownload extends AbstractDownload {
+
+ private static final transient Logger LOG = LoggerFactory.getLogger(HlsdlDownload.class);
+
+ protected Model model;
+ protected File targetFile;
+
+ protected transient Config config;
+ protected transient Process hlsdlProcess;
+ protected transient boolean running;
+
+ @Override
+ public void init(Config config, Model model, Instant startTime) {
+ super.startTime = startTime;
+ this.config = config;
+ this.model = model;
+ String fileSuffix = config.getSettings().ffmpegFileSuffix;
+ targetFile = config.getFileForRecording(model, fileSuffix, startTime);
+ // TODO splittingStrategy = initSplittingStrategy(config.getSettings());
+ }
+
+ @Override
+ public void start() throws IOException {
+ try {
+ running = true;
+ Thread.currentThread().setName("Download " + model.getName());
+ Files.createDirectories(targetFile.getParentFile().toPath());
+ Hlsdl hlsdl = new Hlsdl.Builder()
+ .logOutput(config.getSettings().loghlsdlOutput)
+ .onStarted(p -> hlsdlProcess = p)
+ .build();
+ String[] cmdline = createCommandLine();
+ hlsdl.exec(cmdline, OS.getEnvironment(), targetFile.getParentFile());
+ } catch (ParseException e) {
+ throw new IOException("Couldn't parse stream information", e);
+ } catch (PlaylistException e) {
+ throw new IOException("Couldn't parse HLS playlist", e);
+ } catch (EOFException e) {
+ // end of playlist reached
+ LOG.debug("Reached end of playlist for model {}", model);
+ } catch (Exception e) {
+ throw new IOException("Exception while downloading segments", e);
+ } finally {
+ stop();
+ running = false;
+ LOG.debug("Download for {} terminated", model);
+ }
+ }
+
+ private String[] createCommandLine() throws IOException, ExecutionException, ParseException, PlaylistException, JAXBException {
+ String playlistUrl = getSegmentPlaylistUrl(model);
+ Map headers = model.getHttpHeaderFactory().createSegmentPlaylistHeaders();
+ String[] cmdline = new String[9 + headers.size() * 2];
+ int idx = 0;
+ cmdline[idx++] = config.getSettings().hlsdlExecutable;
+ cmdline[idx++] = "-c";
+ cmdline[idx++] = "-r";
+ cmdline[idx++] = "3";
+ cmdline[idx++] = "-w";
+ cmdline[idx++] = "3";
+ for (Entry header : headers.entrySet()) {
+ cmdline[idx++] = "-h";
+ cmdline[idx++] = header.getKey() + ": " + header.getValue();
+ }
+ cmdline[idx++] = "-o";
+ cmdline[idx++] = targetFile.getCanonicalPath();
+ cmdline[idx] = playlistUrl;
+ return cmdline;
+ }
+
+ protected String getSegmentPlaylistUrl(Model model) throws IOException, ExecutionException, ParseException, PlaylistException, JAXBException {
+ LOG.debug("{} stream idx: {}", model.getName(), model.getStreamUrlIndex());
+ List streamSources = model.getStreamSources();
+ Collections.sort(streamSources);
+ for (StreamSource streamSource : streamSources) {
+ LOG.debug("{} src {}", model.getName(), streamSource);
+ }
+ String url = null;
+ if (model.getStreamUrlIndex() >= 0 && model.getStreamUrlIndex() < streamSources.size()) {
+ // TODO don't use the index, but the bandwidth. if the bandwidth does not match, take the closest one
+ LOG.debug("{} selected {}", model.getName(), streamSources.get(model.getStreamUrlIndex()));
+ url = streamSources.get(model.getStreamUrlIndex()).getMediaPlaylistUrl();
+ } else {
+ // filter out stream resolutions, which are out of range of the configured min and max
+ int minRes = Config.getInstance().getSettings().minimumResolution;
+ int maxRes = Config.getInstance().getSettings().maximumResolution;
+ List filteredStreamSources = streamSources.stream()
+ .filter(src -> src.height == 0 || src.height == UNKNOWN || minRes <= src.height)
+ .filter(src -> src.height == 0 || src.height == UNKNOWN || maxRes >= src.height)
+ .collect(Collectors.toList());
+
+ if (filteredStreamSources.isEmpty()) {
+ throw new ExecutionException(new RuntimeException("No stream left in playlist"));
+ } else {
+ LOG.debug("{} selected {}", model.getName(), filteredStreamSources.get(filteredStreamSources.size() - 1));
+ url = filteredStreamSources.get(filteredStreamSources.size() - 1).getMediaPlaylistUrl();
+ }
+ }
+ LOG.debug("Segment playlist url {}", url);
+ return url;
+ }
+
+ @Override
+ public void stop() {
+ if (running) {
+ running = false;
+ if (hlsdlProcess != null) {
+ hlsdlProcess.destroy();
+ if (hlsdlProcess.isAlive()) {
+ LOG.info("hlsdl didn't terminate. Destroying the process with force!");
+ hlsdlProcess.destroyForcibly();
+ hlsdlProcess = null;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void postprocess(Recording recording) {
+ // nothing to do
+ }
+
+ @Override
+ public File getTarget() {
+ return targetFile;
+ }
+
+ @Override
+ public String getPath(Model model) {
+ String absolutePath = targetFile.getAbsolutePath();
+ String recordingsDir = Config.getInstance().getSettings().recordingsDir;
+ String relativePath = absolutePath.replaceFirst(Pattern.quote(recordingsDir), "");
+ return relativePath;
+ }
+
+ @Override
+ public boolean isSingleFile() {
+ return true;
+ }
+
+ @Override
+ public long getSizeInByte() {
+ return getTarget().length();
+ }
+
+ @Override
+ public Model getModel() {
+ return model;
+ }
+}
diff --git a/common/src/main/java/ctbrec/sites/fc2live/Fc2HlsdlDownload.java b/common/src/main/java/ctbrec/sites/fc2live/Fc2HlsdlDownload.java
new file mode 100644
index 00000000..58ad4a5e
--- /dev/null
+++ b/common/src/main/java/ctbrec/sites/fc2live/Fc2HlsdlDownload.java
@@ -0,0 +1,27 @@
+package ctbrec.sites.fc2live;
+
+import java.io.IOException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ctbrec.recorder.download.hls.HlsdlDownload;
+
+public class Fc2HlsdlDownload extends HlsdlDownload {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Fc2HlsdlDownload.class);
+
+ @Override
+ public void start() throws IOException {
+ Fc2Model fc2Model = (Fc2Model) model;
+ try {
+ fc2Model.openWebsocket();
+ super.start();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ LOG.error("Couldn't start download for {}", model, e);
+ } finally {
+ fc2Model.closeWebsocket();
+ }
+ }
+}
diff --git a/common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java b/common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java
index a686d0eb..71ec8825 100644
--- a/common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java
+++ b/common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java
@@ -381,10 +381,14 @@ public class Fc2Model extends AbstractModel {
@Override
public Download createDownload() {
- if(Config.isServerMode() && !Config.getInstance().getSettings().recordSingleFile) {
- return new Fc2HlsDownload(getSite().getHttpClient());
+ if (Config.getInstance().getSettings().useHlsdl) {
+ return new Fc2HlsdlDownload();
} else {
- return new Fc2MergedHlsDownload(getSite().getHttpClient());
+ if (Config.isServerMode() && !Config.getInstance().getSettings().recordSingleFile) {
+ return new Fc2HlsDownload(getSite().getHttpClient());
+ } else {
+ return new Fc2MergedHlsDownload(getSite().getHttpClient());
+ }
}
}
From 1baa216150670ce85f6300b4d37c323db4ad862d Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sat, 9 Jan 2021 22:24:51 +0100
Subject: [PATCH 6/8] Implement splitting for hlsdl downloads
---
.../recorder/download/AbstractDownload.java | 30 +++++++++++++++++++
.../download/hls/AbstractHlsDownload.java | 26 ----------------
.../recorder/download/hls/HlsdlDownload.java | 24 +++++++++++++--
3 files changed, 52 insertions(+), 28 deletions(-)
diff --git a/common/src/main/java/ctbrec/recorder/download/AbstractDownload.java b/common/src/main/java/ctbrec/recorder/download/AbstractDownload.java
index c1f1a939..2870dc57 100644
--- a/common/src/main/java/ctbrec/recorder/download/AbstractDownload.java
+++ b/common/src/main/java/ctbrec/recorder/download/AbstractDownload.java
@@ -2,12 +2,42 @@ package ctbrec.recorder.download;
import java.time.Instant;
+import ctbrec.Settings;
+import ctbrec.recorder.download.hls.CombinedSplittingStrategy;
+import ctbrec.recorder.download.hls.NoopSplittingStrategy;
+import ctbrec.recorder.download.hls.SizeSplittingStrategy;
+import ctbrec.recorder.download.hls.TimeSplittingStrategy;
+
public abstract class AbstractDownload implements Download {
protected Instant startTime;
+ protected transient SplittingStrategy splittingStrategy;
@Override
public Instant getStartTime() {
return startTime;
}
+
+ protected SplittingStrategy initSplittingStrategy(Settings settings) {
+ SplittingStrategy strategy;
+ switch (settings.splitStrategy) {
+ case TIME:
+ strategy = new TimeSplittingStrategy();
+ break;
+ case SIZE:
+ strategy = new SizeSplittingStrategy();
+ break;
+ case TIME_OR_SIZE:
+ SplittingStrategy timeSplittingStrategy = new TimeSplittingStrategy();
+ SplittingStrategy sizeSplittingStrategy = new SizeSplittingStrategy();
+ strategy = new CombinedSplittingStrategy(timeSplittingStrategy, sizeSplittingStrategy);
+ break;
+ case DONT:
+ default:
+ strategy = new NoopSplittingStrategy();
+ break;
+ }
+ strategy.init(settings);
+ return strategy;
+ }
}
diff --git a/common/src/main/java/ctbrec/recorder/download/hls/AbstractHlsDownload.java b/common/src/main/java/ctbrec/recorder/download/hls/AbstractHlsDownload.java
index b5b9fa6c..841d9281 100644
--- a/common/src/main/java/ctbrec/recorder/download/hls/AbstractHlsDownload.java
+++ b/common/src/main/java/ctbrec/recorder/download/hls/AbstractHlsDownload.java
@@ -44,7 +44,6 @@ import com.iheartradio.m3u8.data.TrackData;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.Recording.State;
-import ctbrec.Settings;
import ctbrec.UnknownModel;
import ctbrec.io.BandwidthMeter;
import ctbrec.io.HttpClient;
@@ -52,7 +51,6 @@ import ctbrec.io.HttpException;
import ctbrec.recorder.PlaylistGenerator.InvalidPlaylistException;
import ctbrec.recorder.download.AbstractDownload;
import ctbrec.recorder.download.HttpHeaderFactory;
-import ctbrec.recorder.download.SplittingStrategy;
import ctbrec.recorder.download.StreamSource;
import ctbrec.sites.Site;
import okhttp3.Request;
@@ -69,7 +67,6 @@ public abstract class AbstractHlsDownload extends AbstractDownload {
protected Model model = new UnknownModel();
protected transient LinkedBlockingQueue downloadQueue = new LinkedBlockingQueue<>(50);
protected transient ExecutorService downloadThreadPool = new ThreadPoolExecutor(0, 5, 20, TimeUnit.SECONDS, downloadQueue, createThreadFactory());
- protected transient SplittingStrategy splittingStrategy;
protected State state = State.UNKNOWN;
private int playlistEmptyCount = 0;
@@ -238,27 +235,4 @@ public abstract class AbstractHlsDownload extends AbstractDownload {
this.url = url;
}
}
-
- protected SplittingStrategy initSplittingStrategy(Settings settings) {
- SplittingStrategy strategy;
- switch (settings.splitStrategy) {
- case TIME:
- strategy = new TimeSplittingStrategy();
- break;
- case SIZE:
- strategy = new SizeSplittingStrategy();
- break;
- case TIME_OR_SIZE:
- SplittingStrategy timeSplittingStrategy = new TimeSplittingStrategy();
- SplittingStrategy sizeSplittingStrategy = new SizeSplittingStrategy();
- strategy = new CombinedSplittingStrategy(timeSplittingStrategy, sizeSplittingStrategy);
- break;
- case DONT:
- default:
- strategy = new NoopSplittingStrategy();
- break;
- }
- strategy.init(settings);
- return strategy;
- }
}
diff --git a/common/src/main/java/ctbrec/recorder/download/hls/HlsdlDownload.java b/common/src/main/java/ctbrec/recorder/download/hls/HlsdlDownload.java
index 7ee18562..96aa0554 100644
--- a/common/src/main/java/ctbrec/recorder/download/hls/HlsdlDownload.java
+++ b/common/src/main/java/ctbrec/recorder/download/hls/HlsdlDownload.java
@@ -12,6 +12,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -48,15 +49,34 @@ public class HlsdlDownload extends AbstractDownload {
this.model = model;
String fileSuffix = config.getSettings().ffmpegFileSuffix;
targetFile = config.getFileForRecording(model, fileSuffix, startTime);
- // TODO splittingStrategy = initSplittingStrategy(config.getSettings());
+ splittingStrategy = initSplittingStrategy(config.getSettings());
}
@Override
public void start() throws IOException {
try {
running = true;
- Thread.currentThread().setName("Download " + model.getName());
+ String threadName = "Download " + model.getName();
+ Thread.currentThread().setName(threadName);
Files.createDirectories(targetFile.getParentFile().toPath());
+
+ Thread splittingMonitor = new Thread(() -> {
+ try {
+ while (running) {
+ if (splittingStrategy.splitNecessary(HlsdlDownload.this)) {
+ LOG.debug("splitting strategy {} triggered split", splittingStrategy.getClass().getSimpleName());
+ stop();
+ }
+ TimeUnit.SECONDS.sleep(1);
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ });
+ splittingMonitor.setName(threadName + " splitter");
+ splittingMonitor.setDaemon(true);
+ splittingMonitor.start();
+
Hlsdl hlsdl = new Hlsdl.Builder()
.logOutput(config.getSettings().loghlsdlOutput)
.onStarted(p -> hlsdlProcess = p)
From c9e7c87f355260a7101f8aebce3677c764f5a14f Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 10 Jan 2021 13:40:43 +0100
Subject: [PATCH 7/8] Don't include models marked for later recording in pause
all / resume all
---
.../ctbrec/ui/tabs/RecordedModelsTab.java | 103 +++++++++++++-----
1 file changed, 73 insertions(+), 30 deletions(-)
diff --git a/client/src/main/java/ctbrec/ui/tabs/RecordedModelsTab.java b/client/src/main/java/ctbrec/ui/tabs/RecordedModelsTab.java
index 99555d6f..eea5a1b3 100644
--- a/client/src/main/java/ctbrec/ui/tabs/RecordedModelsTab.java
+++ b/client/src/main/java/ctbrec/ui/tabs/RecordedModelsTab.java
@@ -1,13 +1,58 @@
package ctbrec.ui.tabs;
+import static ctbrec.Recording.State.*;
+import static ctbrec.ui.UnicodeEmoji.*;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.Recording;
import ctbrec.StringUtil;
import ctbrec.recorder.Recorder;
import ctbrec.sites.Site;
-import ctbrec.ui.*;
-import ctbrec.ui.action.*;
+import ctbrec.ui.AutosizeAlert;
+import ctbrec.ui.DesktopIntegration;
+import ctbrec.ui.JavaFxModel;
+import ctbrec.ui.PreviewPopupHandler;
+import ctbrec.ui.StreamSourceSelectionDialog;
+import ctbrec.ui.action.CheckModelAccountAction;
+import ctbrec.ui.action.EditNotesAction;
+import ctbrec.ui.action.FollowAction;
+import ctbrec.ui.action.IgnoreModelsAction;
+import ctbrec.ui.action.OpenRecordingsDir;
+import ctbrec.ui.action.PauseAction;
+import ctbrec.ui.action.PlayAction;
+import ctbrec.ui.action.RemoveTimeLimitAction;
+import ctbrec.ui.action.ResumeAction;
+import ctbrec.ui.action.SetStopDateAction;
+import ctbrec.ui.action.StartRecordingAction;
+import ctbrec.ui.action.StopRecordingAction;
+import ctbrec.ui.action.ToggleRecordingAction;
import ctbrec.ui.controls.CustomMouseBehaviorContextMenu;
import ctbrec.ui.controls.DateTimeCellFactory;
import ctbrec.ui.controls.Dialogs;
@@ -26,13 +71,33 @@ import javafx.concurrent.WorkerStateEvent;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
-import javafx.scene.control.*;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Button;
+import javafx.scene.control.ContextMenu;
+import javafx.scene.control.Label;
+import javafx.scene.control.MenuItem;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.SelectionMode;
+import javafx.scene.control.Tab;
+import javafx.scene.control.TableCell;
+import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableColumn.SortType;
+import javafx.scene.control.TableRow;
+import javafx.scene.control.TableView;
+import javafx.scene.control.TextField;
+import javafx.scene.control.ToggleButton;
+import javafx.scene.control.Tooltip;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
-import javafx.scene.input.*;
+import javafx.scene.input.Clipboard;
+import javafx.scene.input.ClipboardContent;
+import javafx.scene.input.ContextMenuEvent;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.input.MouseButton;
+import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
@@ -41,30 +106,6 @@ import javafx.util.Callback;
import javafx.util.Duration;
import javafx.util.StringConverter;
import javafx.util.converter.NumberStringConverter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-import java.time.format.FormatStyle;
-import java.util.*;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-import static ctbrec.Recording.State.RECORDING;
-import static ctbrec.ui.UnicodeEmoji.CLOCK;
-import static ctbrec.ui.UnicodeEmoji.HEAVY_CHECK_MARK;
public class RecordedModelsTab extends Tab implements TabSelectionListener {
private static final Logger LOG = LoggerFactory.getLogger(RecordedModelsTab.class);
@@ -409,14 +450,16 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
private void pauseAll(ActionEvent evt) {
boolean yes = Dialogs.showConfirmDialog("Pause all models", "", "Pause the recording of all models?", getTabPane().getScene());
if (yes) {
- new PauseAction(getTabPane(), recorder.getModels(), recorder).execute();
+ List models = recorder.getModels().stream().filter(Predicate.not(Model::isMarkedForLaterRecording)).collect(Collectors.toList());
+ new PauseAction(getTabPane(), models, recorder).execute();
}
}
private void resumeAll(ActionEvent evt) {
boolean yes = Dialogs.showConfirmDialog("Resume all models", "", "Resume the recording of all models?", getTabPane().getScene());
if (yes) {
- new ResumeAction(getTabPane(), recorder.getModels(), recorder).execute();
+ List models = recorder.getModels().stream().filter(Predicate.not(Model::isMarkedForLaterRecording)).collect(Collectors.toList());
+ new ResumeAction(getTabPane(), models, recorder).execute();
}
}
From f0500f377ddef460b4706d9418062522cea13faa Mon Sep 17 00:00:00 2001
From: 0xb00bface <0xboobface@gmail.com>
Date: Sun, 10 Jan 2021 14:05:52 +0100
Subject: [PATCH 8/8] Update changelog
---
CHANGELOG.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 632c52c8..98ef9b5f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+3.12.1
+========================
+* Fix: "Resume all" started the recordings of models marked for later recording
+
3.12.0
========================
* Added "record later" tab to "bookmark" models