Merge branch 'dev' into fc2
This commit is contained in:
commit
0c35f4c219
|
@ -1,12 +1,15 @@
|
|||
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)
|
||||
* Maximum resolution can be an arbitrary value now
|
||||
* Added setting for minimal recording length. Recordings, which are shorter
|
||||
than this value, get deleted automatically.
|
||||
* Double-click in Recording tab starts the player
|
||||
* Fix: BongaCams friends tab not working
|
||||
* Fix: BongaCams search fails with JSON exception
|
||||
* Fix: In some cases MFC models got confused
|
||||
|
||||
1.15.0
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<parent>
|
||||
<groupId>ctbrec</groupId>
|
||||
<artifactId>master</artifactId>
|
||||
<version>1.15.0</version>
|
||||
<version>1.16.0</version>
|
||||
<relativePath>../master</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import com.squareup.moshi.JsonReader;
|
|||
import com.squareup.moshi.JsonWriter;
|
||||
|
||||
import ctbrec.Model;
|
||||
import ctbrec.recorder.download.Download;
|
||||
import ctbrec.recorder.download.StreamSource;
|
||||
import ctbrec.sites.Site;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
|
@ -212,4 +213,9 @@ public class JavaFxModel implements Model {
|
|||
public int compareTo(Model o) {
|
||||
return delegate.compareTo(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Download createDownload() {
|
||||
return delegate.createDownload();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,6 +118,9 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
|||
preview.setCellValueFactory(cdf -> new SimpleStringProperty(" ▶ "));
|
||||
preview.setEditable(false);
|
||||
preview.setId("preview");
|
||||
if(!Config.getInstance().getSettings().livePreviews) {
|
||||
preview.setVisible(false);
|
||||
}
|
||||
TableColumn<JavaFxModel, String> name = new TableColumn<>("Model");
|
||||
name.setPrefWidth(200);
|
||||
name.setCellValueFactory(new PropertyValueFactory<JavaFxModel, String>("displayName"));
|
||||
|
|
|
@ -174,7 +174,7 @@ public class ThumbCell extends StackPane {
|
|||
StackPane.setAlignment(pausedIndicator, Pos.TOP_LEFT);
|
||||
getChildren().add(pausedIndicator);
|
||||
|
||||
if(Config.getInstance().getSettings().previewInThumbnails) {
|
||||
if(Config.getInstance().getSettings().livePreviews) {
|
||||
getChildren().add(createPreviewTrigger());
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package ctbrec.ui.controls;
|
|||
import java.io.InterruptedIOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
@ -161,8 +162,9 @@ public class StreamPreview extends StackPane {
|
|||
|
||||
private void onError(MediaPlayer videoPlayer) {
|
||||
LOG.error("Error while starting preview stream", videoPlayer.getError());
|
||||
if(videoPlayer.getError().getCause() != null) {
|
||||
LOG.error("Error while starting preview stream root cause:", videoPlayer.getError().getCause());
|
||||
Optional<Throwable> cause = Optional.ofNullable(videoPlayer).map(v -> v.getError()).map(e -> e.getCause());
|
||||
if(cause.isPresent()) {
|
||||
LOG.error("Error while starting preview stream root cause:", cause.get());
|
||||
}
|
||||
showTestImage();
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
private CheckBox chooseStreamQuality = new CheckBox();
|
||||
private CheckBox multiplePlayers = new CheckBox();
|
||||
private CheckBox updateThumbnails = new CheckBox();
|
||||
private CheckBox previewInThumbnails = new CheckBox();
|
||||
private CheckBox livePreviews = new CheckBox();
|
||||
private CheckBox showPlayerStarting = new CheckBox();
|
||||
private RadioButton recordLocal;
|
||||
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));
|
||||
layout.add(updateThumbnails, 1, row++);
|
||||
|
||||
l = new Label("Preview in thumbnails");
|
||||
l = new Label("Enable live previews (experimental)");
|
||||
layout.add(l, 0, row);
|
||||
previewInThumbnails.setSelected(Config.getInstance().getSettings().previewInThumbnails);
|
||||
previewInThumbnails.setOnAction((e) -> {
|
||||
Config.getInstance().getSettings().previewInThumbnails = previewInThumbnails.isSelected();
|
||||
livePreviews.setSelected(Config.getInstance().getSettings().livePreviews);
|
||||
livePreviews.setOnAction((e) -> {
|
||||
Config.getInstance().getSettings().livePreviews = livePreviews.isSelected();
|
||||
saveConfig();
|
||||
showRestartRequired();
|
||||
});
|
||||
GridPane.setMargin(l, new Insets(3, 0, 0, 0));
|
||||
GridPane.setMargin(previewInThumbnails, new Insets(CHECKBOX_MARGIN, 0, 0, CHECKBOX_MARGIN));
|
||||
layout.add(previewInThumbnails, 1, row++);
|
||||
GridPane.setMargin(livePreviews, new Insets(CHECKBOX_MARGIN, 0, 0, CHECKBOX_MARGIN));
|
||||
layout.add(livePreviews, 1, row++);
|
||||
|
||||
l = new Label("Start Tab");
|
||||
layout.add(l, 0, row);
|
||||
|
@ -528,6 +529,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
onlineCheckIntervalInSecs.setDisable(!local);
|
||||
leaveSpaceOnDevice.setDisable(!local);
|
||||
postProcessing.setDisable(!local);
|
||||
minimumLengthInSecs.setDisable(!local);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<parent>
|
||||
<groupId>ctbrec</groupId>
|
||||
<artifactId>master</artifactId>
|
||||
<version>1.15.0</version>
|
||||
<version>1.16.0</version>
|
||||
<relativePath>../master</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -9,6 +9,9 @@ import java.util.concurrent.ExecutionException;
|
|||
import com.squareup.moshi.JsonReader;
|
||||
import com.squareup.moshi.JsonWriter;
|
||||
|
||||
import ctbrec.recorder.download.Download;
|
||||
import ctbrec.recorder.download.HlsDownload;
|
||||
import ctbrec.recorder.download.MergedHlsDownload;
|
||||
import ctbrec.sites.Site;
|
||||
|
||||
public abstract class AbstractModel implements Model {
|
||||
|
@ -184,4 +187,13 @@ public abstract class AbstractModel implements Model {
|
|||
public Site getSite() {
|
||||
return site;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Download createDownload() {
|
||||
if(Config.isServerMode()) {
|
||||
return new HlsDownload(getSite().getHttpClient());
|
||||
} else {
|
||||
return new MergedHlsDownload(getSite().getHttpClient());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import com.iheartradio.m3u8.PlaylistException;
|
|||
import com.squareup.moshi.JsonReader;
|
||||
import com.squareup.moshi.JsonWriter;
|
||||
|
||||
import ctbrec.recorder.download.Download;
|
||||
import ctbrec.recorder.download.StreamSource;
|
||||
import ctbrec.sites.Site;
|
||||
|
||||
|
@ -101,6 +102,6 @@ public interface Model extends Comparable<Model> {
|
|||
|
||||
public void setSuspended(boolean suspended);
|
||||
|
||||
|
||||
public Download createDownload();
|
||||
|
||||
}
|
|
@ -69,7 +69,7 @@ public class Settings {
|
|||
public List<Model> models = new ArrayList<>();
|
||||
public List<EventHandlerConfiguration> eventHandlers = new ArrayList<>();
|
||||
public boolean determineResolution = false;
|
||||
public boolean previewInThumbnails = true;
|
||||
public boolean livePreviews = false;
|
||||
public boolean requireAuthentication = false;
|
||||
public boolean chooseStreamQuality = false;
|
||||
public int maximumResolution = 0;
|
||||
|
|
|
@ -5,6 +5,9 @@ import java.lang.reflect.InvocationTargetException;
|
|||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.squareup.moshi.JsonAdapter;
|
||||
import com.squareup.moshi.JsonReader;
|
||||
import com.squareup.moshi.JsonReader.Token;
|
||||
|
@ -16,6 +19,8 @@ import ctbrec.sites.chaturbate.ChaturbateModel;
|
|||
|
||||
public class ModelJsonAdapter extends JsonAdapter<Model> {
|
||||
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(ModelJsonAdapter.class);
|
||||
|
||||
private List<Site> sites;
|
||||
|
||||
public ModelJsonAdapter() {
|
||||
|
@ -62,7 +67,12 @@ public class ModelJsonAdapter extends JsonAdapter<Model> {
|
|||
model.setSuspended(suspended);
|
||||
} else if(key.equals("siteSpecific")) {
|
||||
reader.beginObject();
|
||||
try {
|
||||
model.readSiteSpecificData(reader);
|
||||
} catch(Exception e) {
|
||||
LOG.error("Couldn't read site specific data for model {}", model.getName());
|
||||
throw e;
|
||||
}
|
||||
reader.endObject();
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -61,8 +61,6 @@ import ctbrec.io.HttpClient;
|
|||
import ctbrec.io.StreamRedirectThread;
|
||||
import ctbrec.recorder.PlaylistGenerator.InvalidPlaylistException;
|
||||
import ctbrec.recorder.download.Download;
|
||||
import ctbrec.recorder.download.HlsDownload;
|
||||
import ctbrec.recorder.download.MergedHlsDownload;
|
||||
|
||||
public class LocalRecorder implements Recorder {
|
||||
|
||||
|
@ -194,13 +192,7 @@ public class LocalRecorder implements Recorder {
|
|||
}
|
||||
|
||||
LOG.debug("Starting recording for model {}", model.getName());
|
||||
Download download;
|
||||
if (Config.isServerMode()) {
|
||||
download = new HlsDownload(client);
|
||||
} else {
|
||||
download = new MergedHlsDownload(client);
|
||||
}
|
||||
|
||||
Download download = model.createDownload();
|
||||
recordingProcesses.put(model, download);
|
||||
new Thread() {
|
||||
@Override
|
||||
|
@ -554,8 +546,10 @@ public class LocalRecorder implements Recorder {
|
|||
if (rec.listFiles().length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO don't list recordings, which currently get deleted
|
||||
// don't list recordings, which currently get deleted
|
||||
if (deleteInProgress.contains(rec)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Date startDate = sdf.parse(rec.getName());
|
||||
Recording recording = new Recording();
|
||||
|
|
|
@ -52,6 +52,9 @@ public abstract class AbstractHlsDownload implements Download {
|
|||
Request request = new Request.Builder().url(segmentsUrl).addHeader("connection", "keep-alive").build();
|
||||
try(Response response = client.execute(request)) {
|
||||
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();
|
||||
PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT);
|
||||
Playlist playlist = parser.parse();
|
||||
|
|
|
@ -76,13 +76,13 @@ public class HlsDownload extends AbstractHlsDownload {
|
|||
}
|
||||
int lastSegment = 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) {
|
||||
SegmentPlaylist lsp = getNextSegments(segments);
|
||||
if(nextSegment > 0 && lsp.seq > nextSegment) {
|
||||
// TODO switch to a lower bitrate/resolution ?!?
|
||||
LOG.warn("Missed segments {} < {} in download for {}", nextSegment, lsp.seq, model);
|
||||
sleep = false;
|
||||
waitFactor *= 2;
|
||||
LOG.warn("Missed segments {} < {} in download for {} - setting wait factor to 1/{}", nextSegment, lsp.seq, model, waitFactor);
|
||||
}
|
||||
int skip = nextSegment - lsp.seq;
|
||||
for (String segment : lsp.segments) {
|
||||
|
@ -97,9 +97,9 @@ public class HlsDownload extends AbstractHlsDownload {
|
|||
}
|
||||
|
||||
long wait = 0;
|
||||
if(sleep && lastSegment == lsp.seq) {
|
||||
if(lastSegment == lsp.seq) {
|
||||
// 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);
|
||||
} else {
|
||||
// playlist did change -> wait for at least last segment duration
|
||||
|
@ -115,9 +115,13 @@ 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;
|
||||
if(lastSegment + lsp.segments.size() > nextSegment) {
|
||||
nextSegment = lastSegment + lsp.segments.size();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new IOException("Couldn't determine segments uri");
|
||||
}
|
||||
|
|
|
@ -157,6 +157,7 @@ public class BongaCams extends AbstractSite {
|
|||
JSONArray results = json.getJSONArray("models");
|
||||
for (int i = 0; i < results.length(); i++) {
|
||||
JSONObject result = results.getJSONObject(i);
|
||||
if(result.has("username")) {
|
||||
Model model = createModel(result.getString("username"));
|
||||
String thumb = result.getString("thumb_image");
|
||||
if(thumb != null) {
|
||||
|
@ -167,6 +168,7 @@ public class BongaCams extends AbstractSite {
|
|||
}
|
||||
models.add(model);
|
||||
}
|
||||
}
|
||||
return models;
|
||||
} else {
|
||||
LOG.warn("Search result: " + json.toString(2));
|
||||
|
|
|
@ -37,6 +37,7 @@ public class StreamateModel extends AbstractModel {
|
|||
private List<StreamSource> streamSources = new ArrayList<>();
|
||||
private int[] resolution;
|
||||
private Long id;
|
||||
private String streamId;
|
||||
|
||||
@Override
|
||||
public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
|
||||
|
@ -218,6 +219,11 @@ public class StreamateModel extends AbstractModel {
|
|||
}
|
||||
|
||||
private String getStreamId() throws IOException {
|
||||
loadModelInfo();
|
||||
return streamId;
|
||||
}
|
||||
|
||||
void loadModelInfo() throws IOException {
|
||||
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";
|
||||
Request request = new Request.Builder()
|
||||
|
@ -232,7 +238,9 @@ public class StreamateModel extends AbstractModel {
|
|||
if(response.isSuccessful()) {
|
||||
JSONObject json = new JSONObject(response.body().string());
|
||||
JSONObject stream = json.getJSONObject("stream");
|
||||
return stream.getString("streamId");
|
||||
streamId = stream.getString("streamId");
|
||||
JSONObject performer = json.getJSONObject("performer");
|
||||
id = performer.getLong("id");
|
||||
} else {
|
||||
throw new HttpException(response.code(), response.message());
|
||||
}
|
||||
|
@ -322,6 +330,13 @@ public class StreamateModel extends AbstractModel {
|
|||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
<groupId>ctbrec</groupId>
|
||||
<artifactId>master</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>1.15.0</version>
|
||||
<version>1.16.0</version>
|
||||
|
||||
<modules>
|
||||
<module>../common</module>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<parent>
|
||||
<groupId>ctbrec</groupId>
|
||||
<artifactId>master</artifactId>
|
||||
<version>1.15.0</version>
|
||||
<version>1.16.0</version>
|
||||
<relativePath>../master</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
Loading…
Reference in New Issue