Add first implementation of Showup WEBRTC download
This commit is contained in:
parent
47469c8aea
commit
3dddd91945
|
@ -41,9 +41,9 @@
|
||||||
<appender-ref ref="GUI" />
|
<appender-ref ref="GUI" />
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
<logger name="ctbrec.sites.cam4.Cam4Model" level="DEBUG"/>
|
|
||||||
|
|
||||||
<logger name="ctbrec.LoggingInterceptor" level="info"/>
|
|
||||||
|
<logger name="ctbrec.LoggingInterceptor" level="INFO"/>
|
||||||
<logger name="ctbrec.io.CookieJarImpl" level="INFO"/>
|
<logger name="ctbrec.io.CookieJarImpl" level="INFO"/>
|
||||||
<logger name="ctbrec.recorder.FFmpeg" level="DEBUG"/>
|
<logger name="ctbrec.recorder.FFmpeg" level="DEBUG"/>
|
||||||
<logger name="ctbrec.recorder.OnlineMonitor" level="INFO"/>
|
<logger name="ctbrec.recorder.OnlineMonitor" level="INFO"/>
|
||||||
|
@ -52,6 +52,8 @@
|
||||||
<logger name="ctbrec.recorder.server.HlsServlet" level="INFO"/>
|
<logger name="ctbrec.recorder.server.HlsServlet" level="INFO"/>
|
||||||
<logger name="ctbrec.recorder.server.RecorderServlet" level="INFO"/>
|
<logger name="ctbrec.recorder.server.RecorderServlet" level="INFO"/>
|
||||||
<logger name="ctbrec.recorder.ThreadPoolScaler" level="DEBUG"/>
|
<logger name="ctbrec.recorder.ThreadPoolScaler" level="DEBUG"/>
|
||||||
|
<!-- <logger name="ctbrec.sites.cam4.Cam4Model" level="DEBUG"/> -->
|
||||||
|
<!-- <logger name="ctbrec.sites.showup.Showup" level="TRACE"/> -->
|
||||||
<logger name="ctbrec.ui.ExternalBrowser" level="DEBUG"/>
|
<logger name="ctbrec.ui.ExternalBrowser" level="DEBUG"/>
|
||||||
<logger name="ctbrec.ui.ThumbOverviewTab" level="DEBUG"/>
|
<logger name="ctbrec.ui.ThumbOverviewTab" level="DEBUG"/>
|
||||||
<logger name="org.eclipse.jetty" level="INFO" />
|
<logger name="org.eclipse.jetty" level="INFO" />
|
||||||
|
|
|
@ -139,11 +139,7 @@ public class ShowupModel extends AbstractModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Download createDownload() {
|
public Download createDownload() {
|
||||||
if (Config.isServerMode() && !Config.getInstance().getSettings().recordSingleFile) {
|
return new ShowupWebrtcDownload(getSite().getHttpClient());
|
||||||
return new ShowupDownload(getSite().getHttpClient());
|
|
||||||
} else {
|
|
||||||
return new ShowupMergedDownload(getSite().getHttpClient());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -162,4 +158,23 @@ public class ShowupModel extends AbstractModel {
|
||||||
fac.setSegmentHeaders(headers);
|
fac.setSegmentHeaders(headers);
|
||||||
return fac;
|
return fac;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getWebRtcUrl() throws IOException {
|
||||||
|
if(streamId == null || streamTranscoderAddr == null) {
|
||||||
|
List<Model> 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue