Merge branch 'dev' into manyvids
This commit is contained in:
commit
7015c3173f
|
@ -1,7 +1,7 @@
|
||||||
3.8.7
|
3.8.7
|
||||||
========================
|
========================
|
||||||
* Fixed bug in recorder servlet. Actions for unpin and notes were mixed up
|
* Fixed bug in recorder servlet. Actions for unpin and notes were mixed up
|
||||||
*
|
* Recordings now start immediately for newly added models
|
||||||
|
|
||||||
3.8.6
|
3.8.6
|
||||||
========================
|
========================
|
||||||
|
|
|
@ -57,15 +57,15 @@ import ctbrec.sites.Site;
|
||||||
public class NextGenLocalRecorder implements Recorder {
|
public class NextGenLocalRecorder implements Recorder {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(NextGenLocalRecorder.class);
|
private static final Logger LOG = LoggerFactory.getLogger(NextGenLocalRecorder.class);
|
||||||
private static final boolean IGNORE_CACHE = true;
|
public static final boolean IGNORE_CACHE = true;
|
||||||
private List<Model> models = Collections.synchronizedList(new ArrayList<>());
|
private List<Model> models = Collections.synchronizedList(new ArrayList<>());
|
||||||
private Config config;
|
private Config config;
|
||||||
private volatile boolean recording = true;
|
private volatile boolean recording = true;
|
||||||
private ReentrantLock recorderLock = new ReentrantLock();
|
private ReentrantLock recorderLock = new ReentrantLock();
|
||||||
private RecorderHttpClient client = new RecorderHttpClient();
|
private RecorderHttpClient client = new RecorderHttpClient();
|
||||||
private long lastPreconditionMessage = 0;
|
|
||||||
private Map<Model, Recording> recordingProcesses = Collections.synchronizedMap(new HashMap<>());
|
private Map<Model, Recording> recordingProcesses = Collections.synchronizedMap(new HashMap<>());
|
||||||
private RecordingManager recordingManager;
|
private RecordingManager recordingManager;
|
||||||
|
private RecordingPreconditions preconditions;
|
||||||
|
|
||||||
// thread pools for downloads and post-processing
|
// thread pools for downloads and post-processing
|
||||||
private BlockingQueue<Runnable> downloadQueue = new SynchronousQueue<>();
|
private BlockingQueue<Runnable> downloadQueue = new SynchronousQueue<>();
|
||||||
|
@ -98,6 +98,8 @@ public class NextGenLocalRecorder implements Recorder {
|
||||||
recording = true;
|
recording = true;
|
||||||
registerEventBusListener();
|
registerEventBusListener();
|
||||||
|
|
||||||
|
preconditions = new RecordingPreconditions(this);
|
||||||
|
|
||||||
LOG.debug("Recorder initialized");
|
LOG.debug("Recorder initialized");
|
||||||
LOG.info("Models to record: {}", models);
|
LOG.info("Models to record: {}", models);
|
||||||
LOG.info("Saving recordings in {}", config.getSettings().recordingsDir);
|
LOG.info("Saving recordings in {}", config.getSettings().recordingsDir);
|
||||||
|
@ -201,23 +203,14 @@ public class NextGenLocalRecorder implements Recorder {
|
||||||
recorderLock.unlock();
|
recorderLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to start the recording immediately
|
|
||||||
try {
|
|
||||||
if (model.isOnline()) {
|
|
||||||
startRecordingProcess(model);
|
startRecordingProcess(model);
|
||||||
}
|
}
|
||||||
} catch (ExecutionException e) {
|
|
||||||
// noop
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startRecordingProcess(Model model) throws IOException {
|
private void startRecordingProcess(Model model) throws IOException {
|
||||||
recorderLock.lock();
|
recorderLock.lock();
|
||||||
try {
|
try {
|
||||||
checkRecordingPreconditions(model);
|
preconditions.check(model);
|
||||||
LOG.info("Starting recording for model {}", model.getName());
|
LOG.info("Starting recording for model {}", model.getName());
|
||||||
Download download = createDownload(model);
|
Download download = createDownload(model);
|
||||||
Recording rec = createRecording(download);
|
Recording rec = createRecording(download);
|
||||||
|
@ -291,85 +284,6 @@ public class NextGenLocalRecorder implements Recorder {
|
||||||
return rec;
|
return rec;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkRecordingPreconditions(Model model) throws IOException {
|
|
||||||
ensureRecorderIsActive();
|
|
||||||
ensureModelIsNotSuspended(model);
|
|
||||||
ensureRecordUntilIsInFuture(model);
|
|
||||||
ensureNoRecordingRunningForModel(model);
|
|
||||||
ensureModelShouldBeRecorded(model);
|
|
||||||
ensureEnoughSpaceForRecording();
|
|
||||||
ensureDownloadSlotAvailable(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ensureRecordUntilIsInFuture(Model model) {
|
|
||||||
if (Instant.now().isAfter(model.getRecordUntil())) {
|
|
||||||
throw new RecordUntilExpiredException(model.getRecordUntil());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ensureEnoughSpaceForRecording() throws IOException {
|
|
||||||
if (!enoughSpaceForRecording()) {
|
|
||||||
throw new PreconditionNotMetException("Not enough disk space for recording");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ensureDownloadSlotAvailable(Model model) {
|
|
||||||
if (!downloadSlotAvailable()) {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
if ((now - lastPreconditionMessage) > TimeUnit.MINUTES.toMillis(1)) {
|
|
||||||
LOG.info("The number of downloads is maxed out");
|
|
||||||
}
|
|
||||||
// check, if we can stop a recording for a model with lower priority
|
|
||||||
Optional<Recording> lowerPrioRecordingProcess = recordingProcessWithLowerPrio(model.getPriority());
|
|
||||||
if (lowerPrioRecordingProcess.isPresent()) {
|
|
||||||
Download download = lowerPrioRecordingProcess.get().getDownload();
|
|
||||||
Model lowerPrioModel = download.getModel();
|
|
||||||
LOG.info("Stopping recording for {}. Prio {} < {}", lowerPrioModel.getName(), lowerPrioModel.getPriority(), model.getPriority());
|
|
||||||
stopRecordingProcess(lowerPrioModel);
|
|
||||||
} else {
|
|
||||||
throw new PreconditionNotMetException("Other models have higher prio, not starting recording for " + model.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ensureModelShouldBeRecorded(Model model) {
|
|
||||||
if (!models.contains(model)) {
|
|
||||||
throw new PreconditionNotMetException("Model " + model + " has been removed. Restarting of recording cancelled.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ensureNoRecordingRunningForModel(Model model) {
|
|
||||||
if (recordingProcesses.containsKey(model)) {
|
|
||||||
throw new PreconditionNotMetException("A recording for model " + model + " is already running");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ensureModelIsNotSuspended(Model model) {
|
|
||||||
if (model.isSuspended()) {
|
|
||||||
throw new PreconditionNotMetException("Recording for model " + model + " is suspended");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ensureRecorderIsActive() {
|
|
||||||
if (!recording) {
|
|
||||||
throw new PreconditionNotMetException("Recorder is not in recording mode");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<Recording> recordingProcessWithLowerPrio(int priority) {
|
|
||||||
Model lowest = null;
|
|
||||||
for (Model m : recordingProcesses.keySet()) {
|
|
||||||
if (lowest == null || m.getPriority() < lowest.getPriority()) {
|
|
||||||
lowest = m;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lowest != null && lowest.getPriority() < priority) {
|
|
||||||
return Optional.of(recordingProcesses.get(lowest));
|
|
||||||
} else {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean deleteIfEmpty(Recording rec) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
|
private boolean deleteIfEmpty(Recording rec) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
|
||||||
rec.refresh();
|
rec.refresh();
|
||||||
long sizeInByte = rec.getSizeInByte();
|
long sizeInByte = rec.getSizeInByte();
|
||||||
|
@ -442,7 +356,7 @@ public class NextGenLocalRecorder implements Recorder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopRecordingProcess(Model model) {
|
void stopRecordingProcess(Model model) {
|
||||||
recorderLock.lock();
|
recorderLock.lock();
|
||||||
try {
|
try {
|
||||||
LOG.debug("Stopping recording for {}", model);
|
LOG.debug("Stopping recording for {}", model);
|
||||||
|
@ -658,7 +572,7 @@ public class NextGenLocalRecorder implements Recorder {
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean enoughSpaceForRecording() throws IOException {
|
boolean enoughSpaceForRecording() throws IOException {
|
||||||
long minimum = config.getSettings().minimumSpaceLeftInBytes;
|
long minimum = config.getSettings().minimumSpaceLeftInBytes;
|
||||||
if (minimum == 0) { // 0 means don't check
|
if (minimum == 0) { // 0 means don't check
|
||||||
return true;
|
return true;
|
||||||
|
@ -667,11 +581,6 @@ public class NextGenLocalRecorder implements Recorder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean downloadSlotAvailable() {
|
|
||||||
int concurrentRecordings = Config.getInstance().getSettings().concurrentRecordings;
|
|
||||||
return concurrentRecordings == 0 || concurrentRecordings > 0 && recordingProcesses.size() < concurrentRecordings;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryRestartRecording(Model model) {
|
private void tryRestartRecording(Model model) {
|
||||||
if (!recording) {
|
if (!recording) {
|
||||||
// recorder is not in recording state
|
// recorder is not in recording state
|
||||||
|
@ -797,4 +706,12 @@ public class NextGenLocalRecorder implements Recorder {
|
||||||
executeRecordUntilSubsequentAction(model);
|
executeRecordUntilSubsequentAction(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isRecording() {
|
||||||
|
return recording;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Model, Recording> getRecordingProcesses() {
|
||||||
|
return recordingProcesses;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
package ctbrec.recorder;
|
||||||
|
import static ctbrec.recorder.NextGenLocalRecorder.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import ctbrec.Config;
|
||||||
|
import ctbrec.Model;
|
||||||
|
import ctbrec.Recording;
|
||||||
|
import ctbrec.recorder.download.Download;
|
||||||
|
|
||||||
|
public class RecordingPreconditions {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(RecordingPreconditions.class);
|
||||||
|
|
||||||
|
private NextGenLocalRecorder recorder;
|
||||||
|
private long lastPreconditionMessage = 0;
|
||||||
|
|
||||||
|
RecordingPreconditions(NextGenLocalRecorder recorder) {
|
||||||
|
this.recorder = recorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
void check(Model model) throws IOException {
|
||||||
|
ensureRecorderIsActive();
|
||||||
|
ensureModelIsNotSuspended(model);
|
||||||
|
ensureRecordUntilIsInFuture(model);
|
||||||
|
ensureNoRecordingRunningForModel(model);
|
||||||
|
ensureModelShouldBeRecorded(model);
|
||||||
|
ensureEnoughSpaceForRecording();
|
||||||
|
ensureDownloadSlotAvailable(model);
|
||||||
|
ensureModelIsOnline(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureModelIsOnline(Model model) {
|
||||||
|
try {
|
||||||
|
if (!model.isOnline(IGNORE_CACHE)) {
|
||||||
|
throw new PreconditionNotMetException(model.getName() + "'s room is not public");
|
||||||
|
}
|
||||||
|
} catch (IOException | ExecutionException e) {
|
||||||
|
throw new PreconditionNotMetException(model.getName() + "'s room is not public");
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new PreconditionNotMetException(model.getName() + "'s room is not public");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureRecordUntilIsInFuture(Model model) {
|
||||||
|
if (Instant.now().isAfter(model.getRecordUntil())) {
|
||||||
|
throw new RecordUntilExpiredException(model.getRecordUntil());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureEnoughSpaceForRecording() throws IOException {
|
||||||
|
if (!recorder.enoughSpaceForRecording()) {
|
||||||
|
throw new PreconditionNotMetException("Not enough disk space for recording");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureDownloadSlotAvailable(Model model) {
|
||||||
|
if (!downloadSlotAvailable()) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if ((now - lastPreconditionMessage) > TimeUnit.MINUTES.toMillis(1)) {
|
||||||
|
LOG.info("The number of downloads is maxed out");
|
||||||
|
}
|
||||||
|
// check, if we can stop a recording for a model with lower priority
|
||||||
|
Optional<Recording> lowerPrioRecordingProcess = recordingProcessWithLowerPrio(model.getPriority());
|
||||||
|
if (lowerPrioRecordingProcess.isPresent()) {
|
||||||
|
Download download = lowerPrioRecordingProcess.get().getDownload();
|
||||||
|
Model lowerPrioModel = download.getModel();
|
||||||
|
LOG.info("Stopping recording for {}. Prio {} < {}", lowerPrioModel.getName(), lowerPrioModel.getPriority(), model.getPriority());
|
||||||
|
recorder.stopRecordingProcess(lowerPrioModel);
|
||||||
|
} else {
|
||||||
|
throw new PreconditionNotMetException("Other models have higher prio, not starting recording for " + model.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<Recording> recordingProcessWithLowerPrio(int priority) {
|
||||||
|
Model lowest = null;
|
||||||
|
for (Model m : recorder.getRecordingProcesses().keySet()) {
|
||||||
|
if (lowest == null || m.getPriority() < lowest.getPriority()) {
|
||||||
|
lowest = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lowest != null && lowest.getPriority() < priority) {
|
||||||
|
return Optional.of(recorder.getRecordingProcesses().get(lowest));
|
||||||
|
} else {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureModelShouldBeRecorded(Model model) {
|
||||||
|
if (!recorder.getModels().contains(model)) {
|
||||||
|
throw new PreconditionNotMetException("Model " + model + " has been removed. Restarting of recording cancelled.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureNoRecordingRunningForModel(Model model) {
|
||||||
|
if (recorder.getRecordingProcesses().containsKey(model)) {
|
||||||
|
throw new PreconditionNotMetException("A recording for model " + model + " is already running");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureModelIsNotSuspended(Model model) {
|
||||||
|
if (model.isSuspended()) {
|
||||||
|
throw new PreconditionNotMetException("Recording for model " + model + " is suspended");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureRecorderIsActive() {
|
||||||
|
if (!recorder.isRecording()) {
|
||||||
|
throw new PreconditionNotMetException("Recorder is not in recording mode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean downloadSlotAvailable() {
|
||||||
|
int concurrentRecordings = Config.getInstance().getSettings().concurrentRecordings;
|
||||||
|
return concurrentRecordings == 0 || concurrentRecordings > 0 && recorder.getRecordingProcesses().size() < concurrentRecordings;
|
||||||
|
}
|
||||||
|
}
|
|
@ -87,11 +87,6 @@ public class HlsDownload extends AbstractHlsDownload {
|
||||||
running = true;
|
running = true;
|
||||||
Thread.currentThread().setName("Download " + model.getName());
|
Thread.currentThread().setName("Download " + model.getName());
|
||||||
splitRecStartTime = ZonedDateTime.now();
|
splitRecStartTime = ZonedDateTime.now();
|
||||||
|
|
||||||
if (!model.isOnline()) {
|
|
||||||
throw new IOException(model.getName() + "'s room is not public");
|
|
||||||
}
|
|
||||||
|
|
||||||
String segments = getSegmentPlaylistUrl(model);
|
String segments = getSegmentPlaylistUrl(model);
|
||||||
if (segments != null) {
|
if (segments != null) {
|
||||||
if (!downloadDir.toFile().exists()) {
|
if (!downloadDir.toFile().exists()) {
|
||||||
|
|
|
@ -83,10 +83,6 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload {
|
||||||
@Override
|
@Override
|
||||||
public void start() throws IOException {
|
public void start() throws IOException {
|
||||||
try {
|
try {
|
||||||
if (!model.isOnline(IGNORE_CACHE)) {
|
|
||||||
throw new IOException(model.getName() + "'s room is not public");
|
|
||||||
}
|
|
||||||
|
|
||||||
running = true;
|
running = true;
|
||||||
super.startTime = Instant.now();
|
super.startTime = Instant.now();
|
||||||
splitRecStartTime = ZonedDateTime.now();
|
splitRecStartTime = ZonedDateTime.now();
|
||||||
|
|
Loading…
Reference in New Issue