forked from j62/ctbrec
1
0
Fork 0

Merge branch 'dev' into fc2

This commit is contained in:
0xboobface 2018-12-17 23:01:21 +01:00
commit 0c35f4c219
19 changed files with 101 additions and 44 deletions

View File

@ -1,12 +1,15 @@
1.16.0 1.16.0
======================== ========================
* Thumbnails can show a live preview. Can be switched off in the settings. * Thumbnails can show a live preview. Can be switched on in the settings.
* Live preview is experimental for now, because I noticed some funky behavior
of the the internal media player. You can use it on your own risk.
* Added Streamate (metcams, xhamstercams, pornhublive) * Added Streamate (metcams, xhamstercams, pornhublive)
* Maximum resolution can be an arbitrary value now * Maximum resolution can be an arbitrary value now
* Added setting for minimal recording length. Recordings, which are shorter * Added setting for minimal recording length. Recordings, which are shorter
than this value, get deleted automatically. than this value, get deleted automatically.
* Double-click in Recording tab starts the player * Double-click in Recording tab starts the player
* Fix: BongaCams friends tab not working * Fix: BongaCams friends tab not working
* Fix: BongaCams search fails with JSON exception
* Fix: In some cases MFC models got confused * Fix: In some cases MFC models got confused
1.15.0 1.15.0

View File

@ -8,7 +8,7 @@
<parent> <parent>
<groupId>ctbrec</groupId> <groupId>ctbrec</groupId>
<artifactId>master</artifactId> <artifactId>master</artifactId>
<version>1.15.0</version> <version>1.16.0</version>
<relativePath>../master</relativePath> <relativePath>../master</relativePath>
</parent> </parent>

View File

@ -10,6 +10,7 @@ import com.squareup.moshi.JsonReader;
import com.squareup.moshi.JsonWriter; import com.squareup.moshi.JsonWriter;
import ctbrec.Model; import ctbrec.Model;
import ctbrec.recorder.download.Download;
import ctbrec.recorder.download.StreamSource; import ctbrec.recorder.download.StreamSource;
import ctbrec.sites.Site; import ctbrec.sites.Site;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
@ -212,4 +213,9 @@ public class JavaFxModel implements Model {
public int compareTo(Model o) { public int compareTo(Model o) {
return delegate.compareTo(o); return delegate.compareTo(o);
} }
@Override
public Download createDownload() {
return delegate.createDownload();
}
} }

View File

@ -118,6 +118,9 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
preview.setCellValueFactory(cdf -> new SimpleStringProperty("")); preview.setCellValueFactory(cdf -> new SimpleStringProperty(""));
preview.setEditable(false); preview.setEditable(false);
preview.setId("preview"); preview.setId("preview");
if(!Config.getInstance().getSettings().livePreviews) {
preview.setVisible(false);
}
TableColumn<JavaFxModel, String> name = new TableColumn<>("Model"); TableColumn<JavaFxModel, String> name = new TableColumn<>("Model");
name.setPrefWidth(200); name.setPrefWidth(200);
name.setCellValueFactory(new PropertyValueFactory<JavaFxModel, String>("displayName")); name.setCellValueFactory(new PropertyValueFactory<JavaFxModel, String>("displayName"));

View File

@ -174,7 +174,7 @@ public class ThumbCell extends StackPane {
StackPane.setAlignment(pausedIndicator, Pos.TOP_LEFT); StackPane.setAlignment(pausedIndicator, Pos.TOP_LEFT);
getChildren().add(pausedIndicator); getChildren().add(pausedIndicator);
if(Config.getInstance().getSettings().previewInThumbnails) { if(Config.getInstance().getSettings().livePreviews) {
getChildren().add(createPreviewTrigger()); getChildren().add(createPreviewTrigger());
} }

View File

@ -3,6 +3,7 @@ package ctbrec.ui.controls;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -161,8 +162,9 @@ public class StreamPreview extends StackPane {
private void onError(MediaPlayer videoPlayer) { private void onError(MediaPlayer videoPlayer) {
LOG.error("Error while starting preview stream", videoPlayer.getError()); LOG.error("Error while starting preview stream", videoPlayer.getError());
if(videoPlayer.getError().getCause() != null) { Optional<Throwable> cause = Optional.ofNullable(videoPlayer).map(v -> v.getError()).map(e -> e.getCause());
LOG.error("Error while starting preview stream root cause:", videoPlayer.getError().getCause()); if(cause.isPresent()) {
LOG.error("Error while starting preview stream root cause:", cause.get());
} }
showTestImage(); showTestImage();
} }

View File

@ -64,7 +64,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
private CheckBox chooseStreamQuality = new CheckBox(); private CheckBox chooseStreamQuality = new CheckBox();
private CheckBox multiplePlayers = new CheckBox(); private CheckBox multiplePlayers = new CheckBox();
private CheckBox updateThumbnails = new CheckBox(); private CheckBox updateThumbnails = new CheckBox();
private CheckBox previewInThumbnails = new CheckBox(); private CheckBox livePreviews = new CheckBox();
private CheckBox showPlayerStarting = new CheckBox(); private CheckBox showPlayerStarting = new CheckBox();
private RadioButton recordLocal; private RadioButton recordLocal;
private RadioButton recordRemote; private RadioButton recordRemote;
@ -462,16 +462,17 @@ public class SettingsTab extends Tab implements TabSelectionListener {
GridPane.setMargin(updateThumbnails, new Insets(CHECKBOX_MARGIN, 0, 0, CHECKBOX_MARGIN)); GridPane.setMargin(updateThumbnails, new Insets(CHECKBOX_MARGIN, 0, 0, CHECKBOX_MARGIN));
layout.add(updateThumbnails, 1, row++); layout.add(updateThumbnails, 1, row++);
l = new Label("Preview in thumbnails"); l = new Label("Enable live previews (experimental)");
layout.add(l, 0, row); layout.add(l, 0, row);
previewInThumbnails.setSelected(Config.getInstance().getSettings().previewInThumbnails); livePreviews.setSelected(Config.getInstance().getSettings().livePreviews);
previewInThumbnails.setOnAction((e) -> { livePreviews.setOnAction((e) -> {
Config.getInstance().getSettings().previewInThumbnails = previewInThumbnails.isSelected(); Config.getInstance().getSettings().livePreviews = livePreviews.isSelected();
saveConfig(); saveConfig();
showRestartRequired();
}); });
GridPane.setMargin(l, new Insets(3, 0, 0, 0)); GridPane.setMargin(l, new Insets(3, 0, 0, 0));
GridPane.setMargin(previewInThumbnails, new Insets(CHECKBOX_MARGIN, 0, 0, CHECKBOX_MARGIN)); GridPane.setMargin(livePreviews, new Insets(CHECKBOX_MARGIN, 0, 0, CHECKBOX_MARGIN));
layout.add(previewInThumbnails, 1, row++); layout.add(livePreviews, 1, row++);
l = new Label("Start Tab"); l = new Label("Start Tab");
layout.add(l, 0, row); layout.add(l, 0, row);
@ -528,6 +529,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
onlineCheckIntervalInSecs.setDisable(!local); onlineCheckIntervalInSecs.setDisable(!local);
leaveSpaceOnDevice.setDisable(!local); leaveSpaceOnDevice.setDisable(!local);
postProcessing.setDisable(!local); postProcessing.setDisable(!local);
minimumLengthInSecs.setDisable(!local);
} }
@Override @Override

View File

@ -8,7 +8,7 @@
<parent> <parent>
<groupId>ctbrec</groupId> <groupId>ctbrec</groupId>
<artifactId>master</artifactId> <artifactId>master</artifactId>
<version>1.15.0</version> <version>1.16.0</version>
<relativePath>../master</relativePath> <relativePath>../master</relativePath>
</parent> </parent>

View File

@ -9,6 +9,9 @@ import java.util.concurrent.ExecutionException;
import com.squareup.moshi.JsonReader; import com.squareup.moshi.JsonReader;
import com.squareup.moshi.JsonWriter; import com.squareup.moshi.JsonWriter;
import ctbrec.recorder.download.Download;
import ctbrec.recorder.download.HlsDownload;
import ctbrec.recorder.download.MergedHlsDownload;
import ctbrec.sites.Site; import ctbrec.sites.Site;
public abstract class AbstractModel implements Model { public abstract class AbstractModel implements Model {
@ -184,4 +187,13 @@ public abstract class AbstractModel implements Model {
public Site getSite() { public Site getSite() {
return site; return site;
} }
@Override
public Download createDownload() {
if(Config.isServerMode()) {
return new HlsDownload(getSite().getHttpClient());
} else {
return new MergedHlsDownload(getSite().getHttpClient());
}
}
} }

View File

@ -9,6 +9,7 @@ import com.iheartradio.m3u8.PlaylistException;
import com.squareup.moshi.JsonReader; import com.squareup.moshi.JsonReader;
import com.squareup.moshi.JsonWriter; import com.squareup.moshi.JsonWriter;
import ctbrec.recorder.download.Download;
import ctbrec.recorder.download.StreamSource; import ctbrec.recorder.download.StreamSource;
import ctbrec.sites.Site; import ctbrec.sites.Site;
@ -101,6 +102,6 @@ public interface Model extends Comparable<Model> {
public void setSuspended(boolean suspended); public void setSuspended(boolean suspended);
public Download createDownload();
} }

View File

@ -69,7 +69,7 @@ public class Settings {
public List<Model> models = new ArrayList<>(); public List<Model> models = new ArrayList<>();
public List<EventHandlerConfiguration> eventHandlers = new ArrayList<>(); public List<EventHandlerConfiguration> eventHandlers = new ArrayList<>();
public boolean determineResolution = false; public boolean determineResolution = false;
public boolean previewInThumbnails = true; public boolean livePreviews = false;
public boolean requireAuthentication = false; public boolean requireAuthentication = false;
public boolean chooseStreamQuality = false; public boolean chooseStreamQuality = false;
public int maximumResolution = 0; public int maximumResolution = 0;

View File

@ -5,6 +5,9 @@ import java.lang.reflect.InvocationTargetException;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.JsonReader; import com.squareup.moshi.JsonReader;
import com.squareup.moshi.JsonReader.Token; import com.squareup.moshi.JsonReader.Token;
@ -16,6 +19,8 @@ import ctbrec.sites.chaturbate.ChaturbateModel;
public class ModelJsonAdapter extends JsonAdapter<Model> { public class ModelJsonAdapter extends JsonAdapter<Model> {
private static final transient Logger LOG = LoggerFactory.getLogger(ModelJsonAdapter.class);
private List<Site> sites; private List<Site> sites;
public ModelJsonAdapter() { public ModelJsonAdapter() {
@ -62,7 +67,12 @@ public class ModelJsonAdapter extends JsonAdapter<Model> {
model.setSuspended(suspended); model.setSuspended(suspended);
} else if(key.equals("siteSpecific")) { } else if(key.equals("siteSpecific")) {
reader.beginObject(); reader.beginObject();
model.readSiteSpecificData(reader); try {
model.readSiteSpecificData(reader);
} catch(Exception e) {
LOG.error("Couldn't read site specific data for model {}", model.getName());
throw e;
}
reader.endObject(); reader.endObject();
} }
} else { } else {

View File

@ -61,8 +61,6 @@ import ctbrec.io.HttpClient;
import ctbrec.io.StreamRedirectThread; import ctbrec.io.StreamRedirectThread;
import ctbrec.recorder.PlaylistGenerator.InvalidPlaylistException; import ctbrec.recorder.PlaylistGenerator.InvalidPlaylistException;
import ctbrec.recorder.download.Download; import ctbrec.recorder.download.Download;
import ctbrec.recorder.download.HlsDownload;
import ctbrec.recorder.download.MergedHlsDownload;
public class LocalRecorder implements Recorder { public class LocalRecorder implements Recorder {
@ -194,13 +192,7 @@ public class LocalRecorder implements Recorder {
} }
LOG.debug("Starting recording for model {}", model.getName()); LOG.debug("Starting recording for model {}", model.getName());
Download download; Download download = model.createDownload();
if (Config.isServerMode()) {
download = new HlsDownload(client);
} else {
download = new MergedHlsDownload(client);
}
recordingProcesses.put(model, download); recordingProcesses.put(model, download);
new Thread() { new Thread() {
@Override @Override
@ -554,8 +546,10 @@ public class LocalRecorder implements Recorder {
if (rec.listFiles().length == 0) { if (rec.listFiles().length == 0) {
continue; continue;
} }
// don't list recordings, which currently get deleted
// TODO don't list recordings, which currently get deleted if (deleteInProgress.contains(rec)) {
continue;
}
Date startDate = sdf.parse(rec.getName()); Date startDate = sdf.parse(rec.getName());
Recording recording = new Recording(); Recording recording = new Recording();

View File

@ -52,6 +52,9 @@ public abstract class AbstractHlsDownload implements Download {
Request request = new Request.Builder().url(segmentsUrl).addHeader("connection", "keep-alive").build(); Request request = new Request.Builder().url(segmentsUrl).addHeader("connection", "keep-alive").build();
try(Response response = client.execute(request)) { try(Response response = client.execute(request)) {
if(response.isSuccessful()) { if(response.isSuccessful()) {
// String body = response.body().string();
// InputStream inputStream = new ByteArrayInputStream(body.getBytes("utf-8"));
// LOG.debug("Segments {}", body);
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();

View File

@ -76,13 +76,13 @@ public class HlsDownload extends AbstractHlsDownload {
} }
int lastSegment = 0; int lastSegment = 0;
int nextSegment = 0; int nextSegment = 0;
boolean sleep = true; // this enables sleeping between playlist requests. once we miss a segment, this is set to false, so that no sleeping happens anymore int waitFactor = 1;
while(running) { while(running) {
SegmentPlaylist lsp = getNextSegments(segments); SegmentPlaylist lsp = getNextSegments(segments);
if(nextSegment > 0 && lsp.seq > nextSegment) { if(nextSegment > 0 && lsp.seq > nextSegment) {
// TODO switch to a lower bitrate/resolution ?!? // TODO switch to a lower bitrate/resolution ?!?
LOG.warn("Missed segments {} < {} in download for {}", nextSegment, lsp.seq, model); waitFactor *= 2;
sleep = false; LOG.warn("Missed segments {} < {} in download for {} - setting wait factor to 1/{}", nextSegment, lsp.seq, model, waitFactor);
} }
int skip = nextSegment - lsp.seq; int skip = nextSegment - lsp.seq;
for (String segment : lsp.segments) { for (String segment : lsp.segments) {
@ -97,9 +97,9 @@ public class HlsDownload extends AbstractHlsDownload {
} }
long wait = 0; long wait = 0;
if(sleep && lastSegment == lsp.seq) { if(lastSegment == lsp.seq) {
// playlist didn't change -> wait for at least half the target duration // playlist didn't change -> wait for at least half the target duration
wait = (long) lsp.targetDuration * 1000 / 2; wait = (long) lsp.targetDuration * 1000 / waitFactor;
LOG.trace("Playlist didn't change... waiting for {}ms", wait); LOG.trace("Playlist didn't change... waiting for {}ms", wait);
} else { } else {
// playlist did change -> wait for at least last segment duration // playlist did change -> wait for at least last segment duration
@ -115,8 +115,12 @@ public class HlsDownload extends AbstractHlsDownload {
} }
} }
// this if check makes sure, that we don't decrease nextSegment. for some reason
// streamate playlists sometimes jump back. e.g. max sequence = 79 -> 80 -> 79
lastSegment = lsp.seq; lastSegment = lsp.seq;
nextSegment = lastSegment + lsp.segments.size(); if(lastSegment + lsp.segments.size() > nextSegment) {
nextSegment = lastSegment + lsp.segments.size();
}
} }
} else { } else {
throw new IOException("Couldn't determine segments uri"); throw new IOException("Couldn't determine segments uri");

View File

@ -157,15 +157,17 @@ public class BongaCams extends AbstractSite {
JSONArray results = json.getJSONArray("models"); JSONArray results = json.getJSONArray("models");
for (int i = 0; i < results.length(); i++) { for (int i = 0; i < results.length(); i++) {
JSONObject result = results.getJSONObject(i); JSONObject result = results.getJSONObject(i);
Model model = createModel(result.getString("username")); if(result.has("username")) {
String thumb = result.getString("thumb_image"); Model model = createModel(result.getString("username"));
if(thumb != null) { String thumb = result.getString("thumb_image");
model.setPreview("https:" + thumb); if(thumb != null) {
model.setPreview("https:" + thumb);
}
if(result.has("display_name")) {
model.setDisplayName(result.getString("display_name"));
}
models.add(model);
} }
if(result.has("display_name")) {
model.setDisplayName(result.getString("display_name"));
}
models.add(model);
} }
return models; return models;
} else { } else {

View File

@ -37,6 +37,7 @@ public class StreamateModel extends AbstractModel {
private List<StreamSource> streamSources = new ArrayList<>(); private List<StreamSource> streamSources = new ArrayList<>();
private int[] resolution; private int[] resolution;
private Long id; private Long id;
private String streamId;
@Override @Override
public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
@ -218,6 +219,11 @@ public class StreamateModel extends AbstractModel {
} }
private String getStreamId() throws IOException { private String getStreamId() throws IOException {
loadModelInfo();
return streamId;
}
void loadModelInfo() throws IOException {
String url = "https://hybridclient.naiadsystems.com/api/v1/config/?name=" + getName() String url = "https://hybridclient.naiadsystems.com/api/v1/config/?name=" + getName()
+ "&sabasic=&sakey=&sk=www.streamate.com&userid=0&version=6.3.17&ajax=1"; + "&sabasic=&sakey=&sk=www.streamate.com&userid=0&version=6.3.17&ajax=1";
Request request = new Request.Builder() Request request = new Request.Builder()
@ -232,7 +238,9 @@ public class StreamateModel extends AbstractModel {
if(response.isSuccessful()) { if(response.isSuccessful()) {
JSONObject json = new JSONObject(response.body().string()); JSONObject json = new JSONObject(response.body().string());
JSONObject stream = json.getJSONObject("stream"); JSONObject stream = json.getJSONObject("stream");
return stream.getString("streamId"); streamId = stream.getString("streamId");
JSONObject performer = json.getJSONObject("performer");
id = performer.getLong("id");
} else { } else {
throw new HttpException(response.code(), response.message()); throw new HttpException(response.code(), response.message());
} }
@ -322,6 +330,13 @@ public class StreamateModel extends AbstractModel {
@Override @Override
public void writeSiteSpecificData(JsonWriter writer) throws IOException { public void writeSiteSpecificData(JsonWriter writer) throws IOException {
if(id == null) {
try {
loadModelInfo();
} catch (IOException e) {
LOG.error("Couldn't load model ID for {}. This can cause problems with saving / loading the model", getName());
}
}
writer.name("id").value(id); writer.name("id").value(id);
} }
} }

View File

@ -6,7 +6,7 @@
<groupId>ctbrec</groupId> <groupId>ctbrec</groupId>
<artifactId>master</artifactId> <artifactId>master</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<version>1.15.0</version> <version>1.16.0</version>
<modules> <modules>
<module>../common</module> <module>../common</module>

View File

@ -8,7 +8,7 @@
<parent> <parent>
<groupId>ctbrec</groupId> <groupId>ctbrec</groupId>
<artifactId>master</artifactId> <artifactId>master</artifactId>
<version>1.15.0</version> <version>1.16.0</version>
<relativePath>../master</relativePath> <relativePath>../master</relativePath>
</parent> </parent>