package ctbrec.sites.amateurtv; import ctbrec.Config; import ctbrec.Model; import ctbrec.Recording; import ctbrec.io.BandwidthMeter; import ctbrec.io.HttpClient; import ctbrec.io.HttpException; import ctbrec.recorder.download.AbstractDownload; import ctbrec.recorder.download.RecordingProcess; import ctbrec.recorder.download.StreamSource; import okhttp3.Request; import okhttp3.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.time.Duration; import java.time.Instant; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.regex.Pattern; import static ctbrec.io.HttpConstants.*; public class AmateurTvDownload extends AbstractDownload { private static final Logger LOG = LoggerFactory.getLogger(AmateurTvDownload.class); private static final int MAX_SECONDS_WITHOUT_TRANSFER = 20; private final HttpClient httpClient; private FileOutputStream fout; private Instant timeOfLastTransfer = Instant.MAX; private volatile boolean running; private volatile boolean started; private File targetFile; public AmateurTvDownload(HttpClient httpClient) { this.httpClient = httpClient; } @Override public void init(Config config, Model model, Instant startTime, ExecutorService executorService) throws IOException { this.config = config; this.model = model; this.startTime = startTime; this.downloadExecutor = executorService; splittingStrategy = initSplittingStrategy(config.getSettings()); targetFile = config.getFileForRecording(model, "mp4", startTime); timeOfLastTransfer = Instant.now(); } @Override public void stop() { running = false; } @Override public void finalizeDownload() { if (fout != null) { try { LOG.debug("Closing recording file {}", targetFile); fout.close(); } catch (IOException e) { LOG.error("Error while closing recording file {}", targetFile, e); } } } @Override public boolean isRunning() { return running; } @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 RecordingProcess call() throws Exception { if (!started) { started = true; startDownload(); } if (splittingStrategy.splitNecessary(this)) { stop(); rescheduleTime = Instant.now(); } else { rescheduleTime = Instant.now().plusSeconds(5); } if (!model.isOnline(true)) { stop(); } if (Duration.between(timeOfLastTransfer, Instant.now()).getSeconds() > MAX_SECONDS_WITHOUT_TRANSFER) { LOG.info("No video data received for {} seconds. Stopping recording for model {}", MAX_SECONDS_WITHOUT_TRANSFER, model); stop(); } return this; } private void startDownload() { downloadExecutor.submit(() -> { running = true; try { StreamSource src = model.getStreamSources().get(0); LOG.debug("Loading video from {}", src.mediaPlaylistUrl); Request request = new Request.Builder() .url(src.mediaPlaylistUrl) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .header(ACCEPT, "*/*") .header(ACCEPT_LANGUAGE, "en") .header(ORIGIN, AmateurTv.BASE_URL) .build(); try (Response resp = httpClient.execute(request)) { if (resp.isSuccessful()) { LOG.debug("Recording video stream to {}", targetFile); Files.createDirectories(targetFile.getParentFile().toPath()); fout = new FileOutputStream(targetFile); InputStream in = Objects.requireNonNull(resp.body()).byteStream(); byte[] b = new byte[1024]; int len; while (running && !Thread.currentThread().isInterrupted() && (len = in.read(b)) >= 0) { fout.write(b, 0, len); timeOfLastTransfer = Instant.now(); getDownloadedBytes().addAndGet(len); BandwidthMeter.add(len); } } else { throw new HttpException(resp.code(), resp.message()); } } } catch (Exception e) { LOG.error("Error while downloading MP4", e); } running = false; }); } }