Fix bug in stalled recording detection
This commit is contained in:
parent
21fa71c901
commit
e0426d7c86
|
@ -1,52 +1,10 @@
|
||||||
package ctbrec.recorder.download.hls;
|
package ctbrec.recorder.download.hls;
|
||||||
|
|
||||||
import static ctbrec.io.HttpConstants.*;
|
import com.iheartradio.m3u8.*;
|
||||||
import static ctbrec.recorder.download.StreamSource.*;
|
|
||||||
import static java.nio.charset.StandardCharsets.*;
|
|
||||||
import static java.nio.file.StandardOpenOption.*;
|
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.net.SocketTimeoutException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.text.DecimalFormat;
|
|
||||||
import java.text.NumberFormat;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.ZonedDateTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.ExecutorCompletionService;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import javax.xml.bind.JAXBException;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.iheartradio.m3u8.Encoding;
|
|
||||||
import com.iheartradio.m3u8.Format;
|
|
||||||
import com.iheartradio.m3u8.ParseException;
|
|
||||||
import com.iheartradio.m3u8.ParsingMode;
|
|
||||||
import com.iheartradio.m3u8.PlaylistException;
|
|
||||||
import com.iheartradio.m3u8.PlaylistParser;
|
|
||||||
import com.iheartradio.m3u8.data.EncryptionData;
|
import com.iheartradio.m3u8.data.EncryptionData;
|
||||||
import com.iheartradio.m3u8.data.MediaPlaylist;
|
import com.iheartradio.m3u8.data.MediaPlaylist;
|
||||||
import com.iheartradio.m3u8.data.Playlist;
|
import com.iheartradio.m3u8.data.Playlist;
|
||||||
import com.iheartradio.m3u8.data.TrackData;
|
import com.iheartradio.m3u8.data.TrackData;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.Model.State;
|
import ctbrec.Model.State;
|
||||||
|
@ -64,6 +22,32 @@ import ctbrec.sites.Site;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Request.Builder;
|
import okhttp3.Request.Builder;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXBException;
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorCompletionService;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
import static ctbrec.io.HttpConstants.*;
|
||||||
|
import static ctbrec.recorder.download.StreamSource.UNKNOWN;
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
import static java.nio.file.StandardOpenOption.*;
|
||||||
|
|
||||||
public abstract class AbstractHlsDownload extends AbstractDownload {
|
public abstract class AbstractHlsDownload extends AbstractDownload {
|
||||||
|
|
||||||
|
@ -85,7 +69,7 @@ public abstract class AbstractHlsDownload extends AbstractDownload {
|
||||||
private Instant beforeLastPlaylistRequest= Instant.EPOCH;
|
private Instant beforeLastPlaylistRequest= Instant.EPOCH;
|
||||||
private int consecutivePlaylistTimeouts = 0;
|
private int consecutivePlaylistTimeouts = 0;
|
||||||
private int consecutivePlaylistErrors = 0;
|
private int consecutivePlaylistErrors = 0;
|
||||||
private Instant lastSegmentDownload = Instant.MAX;
|
private Instant lastSegmentDownload = Instant.MIN;
|
||||||
private int selectedResolution = UNKNOWN;
|
private int selectedResolution = UNKNOWN;
|
||||||
|
|
||||||
private final List<RecordingEvent> recordingEvents = new LinkedList<>();
|
private final List<RecordingEvent> recordingEvents = new LinkedList<>();
|
||||||
|
@ -99,6 +83,7 @@ public abstract class AbstractHlsDownload extends AbstractDownload {
|
||||||
public void init(Config config, Model model, Instant startTime, ExecutorService executorService) throws IOException {
|
public void init(Config config, Model model, Instant startTime, ExecutorService executorService) throws IOException {
|
||||||
super.init(config, model, startTime, executorService);
|
super.init(config, model, startTime, executorService);
|
||||||
segmentDownloadService = new ExecutorCompletionService<>(downloadExecutor);
|
segmentDownloadService = new ExecutorCompletionService<>(downloadExecutor);
|
||||||
|
lastSegmentDownload = Instant.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract OutputStream getSegmentOutputStream(Segment segment) throws IOException;
|
protected abstract OutputStream getSegmentOutputStream(Segment segment) throws IOException;
|
||||||
|
@ -245,7 +230,7 @@ public abstract class AbstractHlsDownload extends AbstractDownload {
|
||||||
List<StreamSource> filteredStreamSources = streamSources.stream()
|
List<StreamSource> filteredStreamSources = streamSources.stream()
|
||||||
.filter(src -> src.height == 0 || src.height == UNKNOWN || minRes <= src.height)
|
.filter(src -> src.height == 0 || src.height == UNKNOWN || minRes <= src.height)
|
||||||
.filter(src -> src.height == 0 || src.height == UNKNOWN || maxRes >= src.height)
|
.filter(src -> src.height == 0 || src.height == UNKNOWN || maxRes >= src.height)
|
||||||
.collect(Collectors.toList());
|
.toList();
|
||||||
|
|
||||||
if (filteredStreamSources.isEmpty()) {
|
if (filteredStreamSources.isEmpty()) {
|
||||||
throw new ExecutionException(new NoStreamFoundException("No stream left in playlist"));
|
throw new ExecutionException(new NoStreamFoundException("No stream left in playlist"));
|
||||||
|
|
|
@ -13,6 +13,7 @@ import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
@ -34,12 +35,12 @@ import okhttp3.Response;
|
||||||
public class SegmentDownload implements Callable<SegmentDownload> {
|
public class SegmentDownload implements Callable<SegmentDownload> {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(SegmentDownload.class);
|
private static final Logger LOG = LoggerFactory.getLogger(SegmentDownload.class);
|
||||||
|
|
||||||
private URL url;
|
private final URL url;
|
||||||
private HttpClient client;
|
private final HttpClient client;
|
||||||
private SegmentPlaylist playlist;
|
private final SegmentPlaylist playlist;
|
||||||
private Segment segment;
|
private final Segment segment;
|
||||||
private Model model;
|
private final Model model;
|
||||||
private OutputStream out;
|
private final OutputStream out;
|
||||||
|
|
||||||
public SegmentDownload(Model model, SegmentPlaylist playlist, Segment segment, HttpClient client, OutputStream out) throws MalformedURLException {
|
public SegmentDownload(Model model, SegmentPlaylist playlist, Segment segment, HttpClient client, OutputStream out) throws MalformedURLException {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
|
@ -52,7 +53,7 @@ public class SegmentDownload implements Callable<SegmentDownload> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SegmentDownload call() {
|
public SegmentDownload call() {
|
||||||
for (int tries = 1; tries <= 3 && !Thread.currentThread().isInterrupted(); tries++) {
|
for (int tries = 1; tries <= 3 && !Thread.currentThread().isInterrupted(); tries++) { // NOSONAR
|
||||||
Request request = createRequest();
|
Request request = createRequest();
|
||||||
try (Response response = client.execute(request)) {
|
try (Response response = client.execute(request)) {
|
||||||
handleResponse(response);
|
handleResponse(response);
|
||||||
|
@ -73,20 +74,19 @@ public class SegmentDownload implements Callable<SegmentDownload> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handleResponse(Response response) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IOException {
|
private void handleResponse(Response response) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IOException {
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
InputStream in = response.body().byteStream();
|
InputStream in = Objects.requireNonNull(response.body(), "HTTP response body is null").byteStream();
|
||||||
if (playlist.encrypted) {
|
if (playlist.encrypted) {
|
||||||
in = new Crypto(playlist.encryptionKeyUrl, client).wrap(in);
|
in = new Crypto(playlist.encryptionKeyUrl, client).wrap(in);
|
||||||
}
|
}
|
||||||
byte[] b = new byte[1024 * 100];
|
byte[] b = new byte[1024 * 100];
|
||||||
int length = -1;
|
int length;
|
||||||
while ((length = in.read(b)) >= 0 && !Thread.currentThread().isInterrupted()) {
|
while ((length = in.read(b)) >= 0 && !Thread.currentThread().isInterrupted()) {
|
||||||
out.write(b, 0, length);
|
out.write(b, 0, length);
|
||||||
BandwidthMeter.add(length);
|
BandwidthMeter.add(length);
|
||||||
}
|
}
|
||||||
out.flush();
|
out.flush();
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
throw new HttpException(response.code(), response.message());
|
throw new HttpException(response.code(), response.message());
|
||||||
}
|
}
|
||||||
|
@ -105,4 +105,4 @@ public class SegmentDownload implements Callable<SegmentDownload> {
|
||||||
public Segment getSegment() {
|
public Segment getSegment() {
|
||||||
return segment;
|
return segment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue