forked from j62/ctbrec
Refactor and simplify MergedHlsDownload
* Break up the big downloadSegments method into smaller methods * Remove the mergeQueue, because it is not needed anymore. This was a left over from when the download used a thread pool to download the segments
This commit is contained in:
parent
434e0a1f64
commit
ff8bbeacc2
|
@ -30,6 +30,7 @@ import com.iheartradio.m3u8.data.TrackData;
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.io.HttpClient;
|
import ctbrec.io.HttpClient;
|
||||||
|
import ctbrec.io.HttpException;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
@ -52,14 +53,14 @@ public abstract class AbstractHlsDownload implements Download {
|
||||||
SegmentPlaylist getNextSegments(String segments) throws IOException, ParseException, PlaylistException {
|
SegmentPlaylist getNextSegments(String segments) throws IOException, ParseException, PlaylistException {
|
||||||
URL segmentsUrl = new URL(segments);
|
URL segmentsUrl = new URL(segments);
|
||||||
Request request = new Request.Builder().url(segmentsUrl).addHeader("connection", "keep-alive").build();
|
Request request = new Request.Builder().url(segmentsUrl).addHeader("connection", "keep-alive").build();
|
||||||
Response response = client.execute(request);
|
try(Response response = client.execute(request)) {
|
||||||
try {
|
if(response.isSuccessful()) {
|
||||||
InputStream inputStream = response.body().byteStream();
|
InputStream inputStream = response.body().byteStream();
|
||||||
PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT);
|
PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT);
|
||||||
Playlist playlist = parser.parse();
|
Playlist playlist = parser.parse();
|
||||||
if(playlist.hasMediaPlaylist()) {
|
if(playlist.hasMediaPlaylist()) {
|
||||||
MediaPlaylist mediaPlaylist = playlist.getMediaPlaylist();
|
MediaPlaylist mediaPlaylist = playlist.getMediaPlaylist();
|
||||||
SegmentPlaylist lsp = new SegmentPlaylist();
|
SegmentPlaylist lsp = new SegmentPlaylist(segments);
|
||||||
lsp.seq = mediaPlaylist.getMediaSequenceNumber();
|
lsp.seq = mediaPlaylist.getMediaSequenceNumber();
|
||||||
lsp.targetDuration = mediaPlaylist.getTargetDuration();
|
lsp.targetDuration = mediaPlaylist.getTargetDuration();
|
||||||
List<TrackData> tracks = mediaPlaylist.getTracks();
|
List<TrackData> tracks = mediaPlaylist.getTracks();
|
||||||
|
@ -77,8 +78,9 @@ public abstract class AbstractHlsDownload implements Download {
|
||||||
return lsp;
|
return lsp;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} finally {
|
} else {
|
||||||
response.close();
|
throw new HttpException(response.code(), response.message());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,10 +133,15 @@ public abstract class AbstractHlsDownload implements Download {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SegmentPlaylist {
|
public static class SegmentPlaylist {
|
||||||
|
public String url;
|
||||||
public int seq = 0;
|
public int seq = 0;
|
||||||
public float totalDuration = 0;
|
public float totalDuration = 0;
|
||||||
public float lastSegDuration = 0;
|
public float lastSegDuration = 0;
|
||||||
public float targetDuration = 0;
|
public float targetDuration = 0;
|
||||||
public List<String> segments = new ArrayList<>();
|
public List<String> segments = new ArrayList<>();
|
||||||
|
|
||||||
|
public SegmentPlaylist(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import java.io.EOFException;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
import java.nio.file.FileSystems;
|
import java.nio.file.FileSystems;
|
||||||
|
@ -19,8 +20,6 @@ import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -38,6 +37,7 @@ import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.Recording;
|
import ctbrec.Recording;
|
||||||
import ctbrec.io.HttpClient;
|
import ctbrec.io.HttpClient;
|
||||||
|
import ctbrec.io.HttpException;
|
||||||
import ctbrec.recorder.ProgressListener;
|
import ctbrec.recorder.ProgressListener;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
@ -69,9 +69,13 @@ public class MergedHlsDownload extends AbstractHlsDownload {
|
||||||
super.startTime = Instant.now();
|
super.startTime = Instant.now();
|
||||||
downloadDir = targetFile.getParentFile().toPath();
|
downloadDir = targetFile.getParentFile().toPath();
|
||||||
mergeThread = createMergeThread(targetFile, progressListener, false);
|
mergeThread = createMergeThread(targetFile, progressListener, false);
|
||||||
|
LOG.debug("Merge thread started");
|
||||||
mergeThread.start();
|
mergeThread.start();
|
||||||
|
LOG.debug("Downloading segments");
|
||||||
downloadSegments(segmentPlaylistUri, false);
|
downloadSegments(segmentPlaylistUri, false);
|
||||||
|
LOG.debug("Waiting for merge thread to finish");
|
||||||
mergeThread.join();
|
mergeThread.join();
|
||||||
|
LOG.debug("Merge thread to finished");
|
||||||
} catch(ParseException e) {
|
} catch(ParseException e) {
|
||||||
throw new IOException("Couldn't parse stream information", e);
|
throw new IOException("Couldn't parse stream information", e);
|
||||||
} catch(PlaylistException e) {
|
} catch(PlaylistException e) {
|
||||||
|
@ -138,29 +142,64 @@ public class MergedHlsDownload extends AbstractHlsDownload {
|
||||||
private void downloadSegments(String segmentPlaylistUri, boolean livestreamDownload) throws IOException, ParseException, PlaylistException {
|
private void downloadSegments(String segmentPlaylistUri, boolean livestreamDownload) throws IOException, ParseException, PlaylistException {
|
||||||
int lastSegment = 0;
|
int lastSegment = 0;
|
||||||
int nextSegment = 0;
|
int nextSegment = 0;
|
||||||
Queue<byte[]> mergeQueue = new LinkedList<>();
|
|
||||||
while(running) {
|
while(running) {
|
||||||
|
try {
|
||||||
SegmentPlaylist lsp = getNextSegments(segmentPlaylistUri);
|
SegmentPlaylist lsp = getNextSegments(segmentPlaylistUri);
|
||||||
if(!livestreamDownload) {
|
if(!livestreamDownload) {
|
||||||
multiSource.setTotalSegments(lsp.segments.size());
|
multiSource.setTotalSegments(lsp.segments.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// download segments, which might have been skipped
|
||||||
|
downloadMissedSegments(lsp, nextSegment);
|
||||||
|
|
||||||
|
// download new segments
|
||||||
|
downloadNewSegments(lsp, nextSegment);
|
||||||
|
|
||||||
|
if(livestreamDownload) {
|
||||||
|
// split up the recording, if configured
|
||||||
|
splitRecording();
|
||||||
|
|
||||||
|
// wait some time until requesting the segment playlist again to not hammer the server
|
||||||
|
waitForNewSegments(lsp, lastSegment);
|
||||||
|
|
||||||
|
lastSegment = lsp.seq;
|
||||||
|
nextSegment = lastSegment + lsp.segments.size();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch(HttpException e) {
|
||||||
|
if(e.getResponseCode() == 404) {
|
||||||
|
// playlist is gone -> model probably logged out
|
||||||
|
LOG.debug("Playlist not found. Assuming model went offline");
|
||||||
|
running = false;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadMissedSegments(SegmentPlaylist lsp, int nextSegment) throws MalformedURLException {
|
||||||
if(nextSegment > 0 && lsp.seq > nextSegment) {
|
if(nextSegment > 0 && lsp.seq > nextSegment) {
|
||||||
LOG.warn("Missed segments {} < {} in download for {}", nextSegment, lsp.seq, segmentPlaylistUri);
|
LOG.warn("Missed segments {} < {} in download for {}", nextSegment, lsp.seq, lsp.url);
|
||||||
String first = lsp.segments.get(0);
|
String first = lsp.segments.get(0);
|
||||||
int seq = lsp.seq;
|
int seq = lsp.seq;
|
||||||
for (int i = nextSegment; i < lsp.seq; i++) {
|
for (int i = nextSegment; i < lsp.seq; i++) {
|
||||||
URL segmentUrl = new URL(first.replaceAll(Integer.toString(seq), Integer.toString(i)));
|
URL segmentUrl = new URL(first.replaceAll(Integer.toString(seq), Integer.toString(i)));
|
||||||
LOG.debug("Loading missed segment {} for model {}", i, segmentPlaylistUri);
|
LOG.debug("Loading missed segment {} for model {}", i, lsp.url);
|
||||||
byte[] segmentData;
|
byte[] segmentData;
|
||||||
try {
|
try {
|
||||||
segmentData = new SegmentDownload(segmentUrl, client).call();
|
segmentData = new SegmentDownload(segmentUrl, client).call();
|
||||||
mergeQueue.add(segmentData);
|
writeSegment(segmentData);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Error while downloading segment {}", segmentUrl, e);
|
LOG.error("Error while downloading segment {}", segmentUrl, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO switch to a lower bitrate/resolution ?!?
|
// TODO switch to a lower bitrate/resolution ?!?
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadNewSegments(SegmentPlaylist lsp, int nextSegment) throws MalformedURLException {
|
||||||
int skip = nextSegment - lsp.seq;
|
int skip = nextSegment - lsp.seq;
|
||||||
for (String segment : lsp.segments) {
|
for (String segment : lsp.segments) {
|
||||||
if(skip > 0) {
|
if(skip > 0) {
|
||||||
|
@ -169,30 +208,21 @@ public class MergedHlsDownload extends AbstractHlsDownload {
|
||||||
URL segmentUrl = new URL(segment);
|
URL segmentUrl = new URL(segment);
|
||||||
try {
|
try {
|
||||||
byte[] segmentData = new SegmentDownload(segmentUrl, client).call();
|
byte[] segmentData = new SegmentDownload(segmentUrl, client).call();
|
||||||
if(livestreamDownload) {
|
|
||||||
mergeQueue.add(segmentData);
|
|
||||||
} else {
|
|
||||||
writeSegment(segmentData);
|
writeSegment(segmentData);
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Error while downloading segment {}", segmentUrl, e);
|
LOG.error("Error while downloading segment {}", segmentUrl, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(livestreamDownload) {
|
|
||||||
// download new segments
|
|
||||||
while(!mergeQueue.isEmpty()) {
|
|
||||||
try {
|
|
||||||
writeSegment(mergeQueue.poll());
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
if(running) {
|
|
||||||
LOG.error("Interrupted while waiting to add a new segment to multi source", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// split up the recording, if configured
|
private void writeSegment(byte[] segmentData) throws InterruptedException {
|
||||||
|
InputStream in = new ByteArrayInputStream(segmentData);
|
||||||
|
InputStreamMTSSource source = InputStreamMTSSource.builder().setInputStream(in).build();
|
||||||
|
multiSource.addSource(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void splitRecording() {
|
||||||
if(config.getSettings().splitRecordings > 0) {
|
if(config.getSettings().splitRecordings > 0) {
|
||||||
Duration recordingDuration = Duration.between(startTime, ZonedDateTime.now());
|
Duration recordingDuration = Duration.between(startTime, ZonedDateTime.now());
|
||||||
long seconds = recordingDuration.getSeconds();
|
long seconds = recordingDuration.getSeconds();
|
||||||
|
@ -204,8 +234,9 @@ public class MergedHlsDownload extends AbstractHlsDownload {
|
||||||
startTime = ZonedDateTime.now();
|
startTime = ZonedDateTime.now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// wait some time until requesting the segment playlist again to not hammer the server
|
private void waitForNewSegments(SegmentPlaylist lsp, int lastSegment) {
|
||||||
try {
|
try {
|
||||||
long wait = 0;
|
long wait = 0;
|
||||||
if (lastSegment == lsp.seq) {
|
if (lastSegment == lsp.seq) {
|
||||||
|
@ -225,21 +256,6 @@ public class MergedHlsDownload extends AbstractHlsDownload {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lastSegment = lsp.seq;
|
|
||||||
nextSegment = lastSegment + lsp.segments.size();
|
|
||||||
|
|
||||||
if(!livestreamDownload) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeSegment(byte[] segmentData) throws InterruptedException {
|
|
||||||
InputStream in = new ByteArrayInputStream(segmentData);
|
|
||||||
InputStreamMTSSource source = InputStreamMTSSource.builder().setInputStream(in).build();
|
|
||||||
multiSource.addSource(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
running = false;
|
running = false;
|
||||||
|
|
Loading…
Reference in New Issue