diff --git a/client/src/main/resources/logback.xml b/client/src/main/resources/logback.xml
index 15274572..a9e9fb01 100644
--- a/client/src/main/resources/logback.xml
+++ b/client/src/main/resources/logback.xml
@@ -41,9 +41,9 @@
-
-
+
+
@@ -52,6 +52,8 @@
+
+
diff --git a/common/src/main/java/ctbrec/sites/showup/ShowupModel.java b/common/src/main/java/ctbrec/sites/showup/ShowupModel.java
index 8c92b6e7..c84aaa4d 100644
--- a/common/src/main/java/ctbrec/sites/showup/ShowupModel.java
+++ b/common/src/main/java/ctbrec/sites/showup/ShowupModel.java
@@ -139,11 +139,7 @@ public class ShowupModel extends AbstractModel {
@Override
public Download createDownload() {
- if (Config.isServerMode() && !Config.getInstance().getSettings().recordSingleFile) {
- return new ShowupDownload(getSite().getHttpClient());
- } else {
- return new ShowupMergedDownload(getSite().getHttpClient());
- }
+ return new ShowupWebrtcDownload(getSite().getHttpClient());
}
@Override
@@ -162,4 +158,23 @@ public class ShowupModel extends AbstractModel {
fac.setSegmentHeaders(headers);
return fac;
}
+
+ public String getWebRtcUrl() throws IOException {
+ if(streamId == null || streamTranscoderAddr == null) {
+ List modelList = getShowupSite().getModelList();
+ for (Model model : modelList) {
+ ShowupModel m = (ShowupModel) model;
+ if (Objects.equal(m.getName(), getName())) {
+ streamId = m.getStreamId();
+ streamTranscoderAddr = m.getStreamTranscoderAddr();
+ }
+ }
+ }
+
+ int cdnHost = 1 + new Random().nextInt(5);
+ int cid = 100_000 + new Random().nextInt(900_000);
+ long pid = 10_000_000_000L + new Random().nextInt();
+ String urlTemplate = "https://cdn-e0{0}.showup.tv/h5live/stream/?url=rtmp%3A%2F%2F{1}%3A1935%2Fwebrtc&stream={2}_aac&cid={3,number,#}&pid={4,number,#}";
+ return MessageFormat.format(urlTemplate, cdnHost, streamTranscoderAddr, streamId, cid, pid);
+ }
}
diff --git a/common/src/main/java/ctbrec/sites/showup/ShowupWebrtcDownload.java b/common/src/main/java/ctbrec/sites/showup/ShowupWebrtcDownload.java
new file mode 100644
index 00000000..b4837082
--- /dev/null
+++ b/common/src/main/java/ctbrec/sites/showup/ShowupWebrtcDownload.java
@@ -0,0 +1,182 @@
+package ctbrec.sites.showup;
+
+import static ctbrec.io.HttpConstants.*;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.time.Instant;
+import java.util.concurrent.ExecutorService;
+import java.util.regex.Pattern;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ctbrec.Config;
+import ctbrec.Model;
+import ctbrec.Recording;
+import ctbrec.io.HttpClient;
+import ctbrec.recorder.download.AbstractDownload;
+import ctbrec.recorder.download.Download;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.WebSocket;
+import okhttp3.WebSocketListener;
+import okio.ByteString;
+
+public class ShowupWebrtcDownload extends AbstractDownload {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ShowupWebrtcDownload.class);
+
+ private transient WebSocket ws;
+ private transient HttpClient httpClient;
+ private transient FileOutputStream fout;
+
+ private volatile boolean running;
+
+ private File targetFile;
+
+ public ShowupWebrtcDownload(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);
+
+ ShowupModel showupModel = (ShowupModel) model;
+ Request request = new Request.Builder()
+ .url(showupModel.getWebRtcUrl())
+ .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
+ .header(ACCEPT, "*/*")
+ .header(ACCEPT_LANGUAGE, "pl")
+ .header(ORIGIN, Showup.BASE_URL)
+ .build();
+
+ running = true;
+ LOG.debug("Opening webrtc connection {}", request.url());
+ ws = httpClient.newWebSocket(request, new WebSocketListener() {
+ @Override
+ public void onOpen(WebSocket webSocket, Response response) {
+ super.onOpen(webSocket, response);
+ LOG.debug("onOpen {} {}", webSocket, response);
+ response.close();
+ try {
+ LOG.debug("Recording video stream to {}", targetFile);
+ Files.createDirectories(targetFile.getParentFile().toPath());
+ fout = new FileOutputStream(targetFile);
+ } catch (Exception e) {
+ LOG.error("Couldn't open file {} to save the video stream", targetFile, e);
+ }
+ }
+
+ @Override
+ public void onMessage(WebSocket webSocket, ByteString bytes) {
+ super.onMessage(webSocket, bytes);
+ if (bytes == null) {
+ return;
+ }
+ try {
+ fout.write(bytes.toByteArray());
+ } catch (IOException e) {
+ LOG.error("Couldn't write video stream to file", e);
+ }
+ }
+
+ @Override
+ public void onMessage(WebSocket webSocket, String text) {
+ super.onMessage(webSocket, text);
+ LOG.debug("onMessageT {} {}", webSocket, text);
+ }
+
+ @Override
+ public void onFailure(WebSocket webSocket, Throwable t, Response response) {
+ super.onFailure(webSocket, t, response);
+ LOG.error("onFailure {} {}", webSocket, response, t);
+ if (response != null) {
+ response.close();
+ }
+ }
+
+ @Override
+ public void onClosing(WebSocket webSocket, int code, String reason) {
+ super.onClosing(webSocket, code, reason);
+ LOG.debug("onClosing {} {} {}", webSocket, code, reason);
+ }
+
+ @Override
+ public void onClosed(WebSocket webSocket, int code, String reason) {
+ super.onClosed(webSocket, code, reason);
+ LOG.debug("onClosed {} {} {}", webSocket, code, reason);
+ }
+ });
+
+ }
+
+ @Override
+ public void stop() {
+ running = false;
+ ws.close(1000, "");
+ }
+
+ @Override
+ public void finalizeDownload() {
+ 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 Download call() throws Exception {
+ if (splittingStrategy.splitNecessary(this)) {
+ stop();
+ rescheduleTime = Instant.now();
+ } else {
+ rescheduleTime = Instant.now().plusSeconds(5);
+ }
+ return this;
+ }
+
+}