From 362d90b29be170d5387dee42b9d4737a606dd65a Mon Sep 17 00:00:00 2001
From: 0xboobface <0xboobface@gmail.com>
Date: Fri, 19 Oct 2018 23:09:11 +0200
Subject: [PATCH] First implementation for MFC
Implemented Tabs are Online and Friends
---
pom.xml | 5 +
src/main/java/ctbrec/io/HttpClient.java | 6 +-
.../recorder/download/StreamSource.java | 9 +
src/main/java/ctbrec/sites/mfc/Fcext.java | 46 +++
.../sites/mfc/FriendsUpdateService.java | 92 +++++
src/main/java/ctbrec/sites/mfc/Message.java | 73 ++++
.../java/ctbrec/sites/mfc/MessageTypes.java | 99 +++++
src/main/java/ctbrec/sites/mfc/Model.java | 168 +++++++++
.../java/ctbrec/sites/mfc/MyFreeCams.java | 95 +++++
.../ctbrec/sites/mfc/MyFreeCamsClient.java | 357 ++++++++++++++++++
.../ctbrec/sites/mfc/MyFreeCamsModel.java | 162 ++++++++
.../sites/mfc/MyFreeCamsTabProvider.java | 43 +++
.../sites/mfc/OnlineCamsUpdateService.java | 39 ++
.../ctbrec/sites/mfc/OnlineModelsTab.java | 12 +
.../java/ctbrec/sites/mfc/ServerConfig.java | 59 +++
.../java/ctbrec/sites/mfc/SessionState.java | 128 +++++++
src/main/java/ctbrec/sites/mfc/Share.java | 116 ++++++
src/main/java/ctbrec/sites/mfc/State.java | 56 +++
src/main/java/ctbrec/sites/mfc/User.java | 154 ++++++++
src/main/java/ctbrec/sites/mfc/X.java | 46 +++
.../java/ctbrec/ui/CamrecApplication.java | 77 ++--
src/main/java/ctbrec/ui/TabProvider.java | 7 +-
src/main/java/ctbrec/ui/ThumbCell.java | 70 ++--
src/main/java/ctbrec/ui/ThumbOverviewTab.java | 11 +-
src/main/java/org/taktik/mpegts/Streamer.java | 2 +-
25 files changed, 1847 insertions(+), 85 deletions(-)
create mode 100644 src/main/java/ctbrec/sites/mfc/Fcext.java
create mode 100644 src/main/java/ctbrec/sites/mfc/FriendsUpdateService.java
create mode 100644 src/main/java/ctbrec/sites/mfc/Message.java
create mode 100644 src/main/java/ctbrec/sites/mfc/MessageTypes.java
create mode 100644 src/main/java/ctbrec/sites/mfc/Model.java
create mode 100644 src/main/java/ctbrec/sites/mfc/MyFreeCams.java
create mode 100644 src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java
create mode 100644 src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java
create mode 100644 src/main/java/ctbrec/sites/mfc/MyFreeCamsTabProvider.java
create mode 100644 src/main/java/ctbrec/sites/mfc/OnlineCamsUpdateService.java
create mode 100644 src/main/java/ctbrec/sites/mfc/OnlineModelsTab.java
create mode 100644 src/main/java/ctbrec/sites/mfc/ServerConfig.java
create mode 100644 src/main/java/ctbrec/sites/mfc/SessionState.java
create mode 100644 src/main/java/ctbrec/sites/mfc/Share.java
create mode 100644 src/main/java/ctbrec/sites/mfc/State.java
create mode 100644 src/main/java/ctbrec/sites/mfc/User.java
create mode 100644 src/main/java/ctbrec/sites/mfc/X.java
diff --git a/pom.xml b/pom.xml
index 5049ecf4..b2050d21 100644
--- a/pom.xml
+++ b/pom.xml
@@ -140,6 +140,11 @@
moshi
1.5.0
+
+ org.json
+ json
+ 20180130
+
org.slf4j
slf4j-api
diff --git a/src/main/java/ctbrec/io/HttpClient.java b/src/main/java/ctbrec/io/HttpClient.java
index 7a3dd0ca..57129ac4 100644
--- a/src/main/java/ctbrec/io/HttpClient.java
+++ b/src/main/java/ctbrec/io/HttpClient.java
@@ -9,8 +9,8 @@ import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.Settings.ProxyType;
-import ctbrec.ui.CookieJarImpl;
import ctbrec.ui.CamrecApplication;
+import ctbrec.ui.CookieJarImpl;
import ctbrec.ui.HtmlParser;
import okhttp3.ConnectionPool;
import okhttp3.Cookie;
@@ -34,8 +34,8 @@ public class HttpClient {
loadProxySettings();
client = new OkHttpClient.Builder()
.cookieJar(cookieJar)
- .connectTimeout(Config.getInstance().getSettings().httpTimeout, TimeUnit.SECONDS)
- .readTimeout(Config.getInstance().getSettings().httpTimeout, TimeUnit.SECONDS)
+ .connectTimeout(Config.getInstance().getSettings().httpTimeout, TimeUnit.MILLISECONDS)
+ .readTimeout(Config.getInstance().getSettings().httpTimeout, TimeUnit.MILLISECONDS)
.connectionPool(new ConnectionPool(50, 10, TimeUnit.MINUTES))
//.addInterceptor(new LoggingInterceptor())
.build();
diff --git a/src/main/java/ctbrec/recorder/download/StreamSource.java b/src/main/java/ctbrec/recorder/download/StreamSource.java
index 2ed7a8f8..1968df81 100644
--- a/src/main/java/ctbrec/recorder/download/StreamSource.java
+++ b/src/main/java/ctbrec/recorder/download/StreamSource.java
@@ -4,6 +4,7 @@ import java.text.DecimalFormat;
public class StreamSource implements Comparable {
public int bandwidth;
+ public int width;
public int height;
public String mediaPlaylistUrl;
@@ -15,6 +16,14 @@ public class StreamSource implements Comparable {
this.bandwidth = bandwidth;
}
+ public int getWidth() {
+ return width;
+ }
+
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
public int getHeight() {
return height;
}
diff --git a/src/main/java/ctbrec/sites/mfc/Fcext.java b/src/main/java/ctbrec/sites/mfc/Fcext.java
new file mode 100644
index 00000000..6d61238b
--- /dev/null
+++ b/src/main/java/ctbrec/sites/mfc/Fcext.java
@@ -0,0 +1,46 @@
+package ctbrec.sites.mfc;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class Fcext {
+
+ private String sm;
+ private Integer sfw;
+ private Map additionalProperties = new HashMap();
+
+ public String getSm() {
+ return sm;
+ }
+
+ public void setSm(String sm) {
+ this.sm = sm;
+ }
+
+ public Integer getSfw() {
+ return sfw;
+ }
+
+ public void setSfw(Integer sfw) {
+ this.sfw = sfw;
+ }
+
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ public void merge(Fcext fcext) {
+ if(fcext == null) {
+ return;
+ }
+
+ sm = fcext.sm != null ? fcext.sm : sm;
+ sfw = fcext.sfw != null ? fcext.sfw : sfw;
+ additionalProperties.putAll(fcext.additionalProperties);
+ }
+
+}
diff --git a/src/main/java/ctbrec/sites/mfc/FriendsUpdateService.java b/src/main/java/ctbrec/sites/mfc/FriendsUpdateService.java
new file mode 100644
index 00000000..8db70760
--- /dev/null
+++ b/src/main/java/ctbrec/sites/mfc/FriendsUpdateService.java
@@ -0,0 +1,92 @@
+package ctbrec.sites.mfc;
+
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ctbrec.Model;
+import ctbrec.ui.PaginatedScheduledService;
+import javafx.concurrent.Task;
+import okhttp3.Request;
+import okhttp3.Response;
+
+public class FriendsUpdateService extends PaginatedScheduledService {
+
+ private static final transient Logger LOG = LoggerFactory.getLogger(FriendsUpdateService.class);
+ private MyFreeCams myFreeCams;
+
+ public FriendsUpdateService(MyFreeCams myFreeCams) {
+ this.myFreeCams = myFreeCams;
+ }
+
+ @Override
+ protected Task> createTask() {
+ return new Task>() {
+ @Override
+ public List call() throws IOException {
+ List models = new ArrayList<>();
+ String url = myFreeCams.getBaseUrl() + "/php/manage_lists2.php?passcode=&list_type=friends&data_mode=online&get_user_list=1";
+ Request req = new Request.Builder()
+ .url(url)
+ .header("Referer", myFreeCams.getBaseUrl())
+ .build();
+ Response resp = MyFreeCams.httpClient.newCall(req).execute();
+ if(resp.isSuccessful()) {
+ String json = resp.body().string().substring(4);
+ JSONObject object = new JSONObject(json);
+ for (String key : object.keySet()) {
+ int uid = Integer.parseInt(key);
+ MyFreeCamsModel model = MyFreeCamsClient.getInstance().getModel(uid);
+ if(model == null) {
+ JSONObject modelObject = object.getJSONObject(key);
+ String name = modelObject.getString("u");
+ model = myFreeCams.createModel(name);
+ SessionState st = new SessionState();
+ st.setM(new ctbrec.sites.mfc.Model());
+ st.getM().setCamscore(0.0);
+ st.setU(new User());
+ st.setUid(uid);
+ st.setLv(modelObject.getInt("lv"));
+ st.setVs(127);
+ model.update(st);
+ }
+ models.add(model);
+ }
+ } else {
+ LOG.error("Couldn't load friends list {} {}", resp.code(), resp.message());
+ resp.close();
+ }
+ return models.stream()
+ .sorted((a, b) -> {
+ try {
+ if(a.isOnline() && b.isOnline() || !a.isOnline() && !b.isOnline()) {
+ return a.getName().compareTo(b.getName());
+ } else {
+ if(a.isOnline()) {
+ return -1;
+ }
+ if(b.isOnline()) {
+ return 1;
+ }
+ }
+ } catch (IOException | ExecutionException | InterruptedException e) {
+ LOG.warn("Couldn't sort friends list", e);
+ return 0;
+ }
+ return 0;
+ })
+ .skip((page-1) * 50)
+ .limit(50)
+ .collect(Collectors.toList());
+ }
+ };
+ }
+
+}
diff --git a/src/main/java/ctbrec/sites/mfc/Message.java b/src/main/java/ctbrec/sites/mfc/Message.java
new file mode 100644
index 00000000..c1ee53ea
--- /dev/null
+++ b/src/main/java/ctbrec/sites/mfc/Message.java
@@ -0,0 +1,73 @@
+package ctbrec.sites.mfc;
+
+public class Message {
+ private int type;
+ private int sender;
+ private int receiver;
+ private int arg1;
+ private int arg2;
+ private String message;
+
+ public Message(int type, int sender, int receiver, int arg1, int arg2, String message) {
+ super();
+ this.type = type;
+ this.sender = sender;
+ this.receiver = receiver;
+ this.arg1 = arg1;
+ this.arg2 = arg2;
+ this.message = message;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ public int getSender() {
+ return sender;
+ }
+
+ public void setSender(int sender) {
+ this.sender = sender;
+ }
+
+ public int getReceiver() {
+ return receiver;
+ }
+
+ public void setReceiver(int receiver) {
+ this.receiver = receiver;
+ }
+
+ public int getArg1() {
+ return arg1;
+ }
+
+ public void setArg1(int arg1) {
+ this.arg1 = arg1;
+ }
+
+ public int getArg2() {
+ return arg2;
+ }
+
+ public void setArg2(int arg2) {
+ this.arg2 = arg2;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ @Override
+ public String toString() {
+ return type + " " + sender + " " + receiver + " " + arg1 + " " + arg2 + " " + message;
+ }
+}
diff --git a/src/main/java/ctbrec/sites/mfc/MessageTypes.java b/src/main/java/ctbrec/sites/mfc/MessageTypes.java
new file mode 100644
index 00000000..34d85fec
--- /dev/null
+++ b/src/main/java/ctbrec/sites/mfc/MessageTypes.java
@@ -0,0 +1,99 @@
+package ctbrec.sites.mfc;
+
+public class MessageTypes {
+ public static final int CLIENT_DISCONNECTED = -5;
+ public static final int CLIENT_MODELSLOADED = -4;
+ public static final int CLIENT_CONNECTED = -3;
+ public static final int ANY = -2;
+ public static final int UNKNOWN = -1;
+ public static final int NULL = 0;
+ public static final int LOGIN = 1;
+ public static final int ADDFRIEND = 2;
+ public static final int PMESG = 3;
+ public static final int STATUS = 4;
+ public static final int DETAILS = 5;
+ public static final int TOKENINC = 6;
+ public static final int ADDIGNORE = 7;
+ public static final int PRIVACY = 8;
+ public static final int ADDFRIENDREQ = 9;
+ public static final int USERNAMELOOKUP = 10;
+ public static final int ZBAN = 11;
+ public static final int BROADCASTNEWS = 12;
+ public static final int ANNOUNCE = 13;
+ public static final int MANAGELIST = 14;
+ public static final int INBOX = 15;
+ public static final int GWCONNECT = 16;
+ public static final int RELOADSETTINGS = 17;
+ public static final int HIDEUSERS = 18;
+ public static final int RULEVIOLATION = 19;
+ public static final int SESSIONSTATE = 20;
+ public static final int REQUESTPVT = 21;
+ public static final int ACCEPTPVT = 22;
+ public static final int REJECTPVT = 23;
+ public static final int ENDSESSION = 24;
+ public static final int TXPROFILE = 25;
+ public static final int STARTVOYEUR = 26;
+ public static final int SERVERREFRESH = 27;
+ public static final int SETTING = 28;
+ public static final int BWSTATS = 29;
+ public static final int SETGUESTNAME = 30;
+ public static final int TKX = 30;
+ public static final int SETTEXTOPT = 31;
+ public static final int SERVERCONFIG = 32;
+ public static final int MODELGROUP = 33;
+ public static final int REQUESTGRP = 34;
+ public static final int STATUSGRP = 35;
+ public static final int GROUPCHAT = 36;
+ public static final int CLOSEGRP = 37;
+ public static final int UCR = 38;
+ public static final int MYUCR = 39;
+ public static final int SLAVECON = 40;
+ public static final int SLAVECMD = 41;
+ public static final int SLAVEFRIEND = 42;
+ public static final int SLAVEVSHARE = 43;
+ public static final int ROOMDATA = 44;
+ public static final int NEWSITEM = 45;
+ public static final int GUESTCOUNT = 46;
+ public static final int PRELOGINQ = 47;
+ public static final int MODELGROUPSZ = 48;
+ public static final int ROOMHELPER = 49;
+ public static final int CMESG = 50;
+ public static final int JOINCHAN = 51;
+ public static final int CREATECHAN = 52;
+ public static final int INVITECHAN = 53;
+ public static final int KICKCHAN = 54;
+ public static final int QUIETCHAN = 55;
+ public static final int BANCHAN = 56;
+ public static final int PREVIEWCHAN = 57;
+ public static final int SHUTDOWN = 58;
+ public static final int LISTBANS = 59;
+ public static final int UNBAN = 60;
+ public static final int SETWELCOME = 61;
+ public static final int CHANOP = 62;
+ public static final int LISTCHAN = 63;
+ public static final int TAGS = 64;
+ public static final int SETPCODE = 65;
+ public static final int SETMINTIP = 66;
+ public static final int UEOPT = 67;
+ public static final int HDVIDEO = 68;
+ public static final int METRICS = 69;
+ public static final int OFFERCAM = 70;
+ public static final int REQUESTCAM = 71;
+ public static final int MYWEBCAM = 72;
+ public static final int MYCAMSTATE = 73;
+ public static final int PMHISTORY = 74;
+ public static final int CHATFLASH = 75;
+ public static final int TRUEPVT = 76;
+ public static final int BOOKMARKS = 77;
+ public static final int EVENT = 78;
+ public static final int STATEDUMP = 79;
+ public static final int RECOMMEND = 80;
+ public static final int EXTDATA = 81;
+ public static final int NOTIFY = 84;
+ public static final int PUBLISH = 85;
+ public static final int ZGWINVALID = 95;
+ public static final int CONNECTING = 96;
+ public static final int CONNECTED = 97;
+ public static final int DISCONNECTED = 98;
+ public static final int LOGOUT = 99;
+}
diff --git a/src/main/java/ctbrec/sites/mfc/Model.java b/src/main/java/ctbrec/sites/mfc/Model.java
new file mode 100644
index 00000000..7a46df38
--- /dev/null
+++ b/src/main/java/ctbrec/sites/mfc/Model.java
@@ -0,0 +1,168 @@
+package ctbrec.sites.mfc;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class Model {
+
+ private Double camscore;
+ private String continent;
+ private Integer flags;
+ private Boolean hidecs;
+ private Integer kbit;
+ private Integer lastnews;
+ private Integer mg;
+ private Integer missmfc;
+ private Integer newModel;
+ private Integer rank;
+ private Integer rc;
+ private Integer sfw;
+ private String topic;
+ private Map additionalProperties = new HashMap();
+ private Set tags = new HashSet<>();
+
+ public Double getCamscore() {
+ return camscore;
+ }
+
+ public void setCamscore(Double camscore) {
+ this.camscore = camscore;
+ }
+
+ public String getContinent() {
+ return continent;
+ }
+
+ public void setContinent(String continent) {
+ this.continent = continent;
+ }
+
+ public Integer getFlags() {
+ return flags;
+ }
+
+ public void setFlags(Integer flags) {
+ this.flags = flags;
+ }
+
+ public Boolean getHidecs() {
+ return hidecs;
+ }
+
+ public void setHidecs(Boolean hidecs) {
+ this.hidecs = hidecs;
+ }
+
+ public Integer getKbit() {
+ return kbit;
+ }
+
+ public void setKbit(Integer kbit) {
+ this.kbit = kbit;
+ }
+
+ public Integer getLastnews() {
+ return lastnews;
+ }
+
+ public void setLastnews(Integer lastnews) {
+ this.lastnews = lastnews;
+ }
+
+ public Integer getMg() {
+ return mg;
+ }
+
+ public void setMg(Integer mg) {
+ this.mg = mg;
+ }
+
+ public Integer getMissmfc() {
+ return missmfc;
+ }
+
+ public void setMissmfc(Integer missmfc) {
+ this.missmfc = missmfc;
+ }
+
+ public Integer getNewModel() {
+ return newModel;
+ }
+
+ public void setNewModel(Integer newModel) {
+ this.newModel = newModel;
+ }
+
+ public Integer getRank() {
+ return rank;
+ }
+
+ public void setRank(Integer rank) {
+ this.rank = rank;
+ }
+
+ public Integer getRc() {
+ return rc;
+ }
+
+ public void setRc(Integer rc) {
+ this.rc = rc;
+ }
+
+ public Integer getSfw() {
+ return sfw;
+ }
+
+ public void setSfw(Integer sfw) {
+ this.sfw = sfw;
+ }
+
+ public String getTopic() {
+ return topic;
+ }
+
+ public void setTopic(String topic) {
+ this.topic = topic;
+ }
+
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ public Set getTags() {
+ return tags;
+ }
+
+ public void setTags(Set tags) {
+ this.tags = tags;
+ }
+
+ public void merge(Model m) {
+ if(m == null) {
+ return;
+ }
+ camscore = m.camscore != null ? m.camscore : camscore;
+ continent = m.continent != null ? m.continent : continent;
+ flags = m.flags != null ? m.flags : flags;
+ hidecs = m.hidecs != null ? m.hidecs : hidecs;
+ kbit = m.kbit != null ? m.kbit : kbit;
+ lastnews = m.lastnews != null ? m.lastnews : lastnews;
+ mg = m.mg != null ? m.mg : mg;
+ missmfc = m.missmfc != null ? m.missmfc : missmfc;
+ newModel = m.newModel != null ? m.newModel : newModel;
+ rank = m.rank != null ? m.rank : rank;
+ rc = m.rc != null ? m.rc : rc;
+ sfw = m.sfw != null ? m.sfw : sfw;
+ topic = m.topic != null ? m.topic : topic;
+ additionalProperties.putAll(m.additionalProperties);
+ tags.addAll(m.tags);
+ }
+
+
+}
diff --git a/src/main/java/ctbrec/sites/mfc/MyFreeCams.java b/src/main/java/ctbrec/sites/mfc/MyFreeCams.java
new file mode 100644
index 00000000..b37aa473
--- /dev/null
+++ b/src/main/java/ctbrec/sites/mfc/MyFreeCams.java
@@ -0,0 +1,95 @@
+package ctbrec.sites.mfc;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ctbrec.Config;
+import ctbrec.Site;
+import ctbrec.recorder.Recorder;
+import ctbrec.ui.CookieJarImpl;
+import ctbrec.ui.TabProvider;
+import okhttp3.ConnectionPool;
+import okhttp3.FormBody;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
+public class MyFreeCams implements Site {
+
+ private static final transient Logger LOG = LoggerFactory.getLogger(MyFreeCams.class);
+
+ private Recorder recorder;
+ private MyFreeCamsClient client;
+ public static OkHttpClient httpClient = new OkHttpClient.Builder()
+ .connectTimeout(Config.getInstance().getSettings().httpTimeout, TimeUnit.MILLISECONDS)
+ .readTimeout(Config.getInstance().getSettings().httpTimeout, TimeUnit.MILLISECONDS)
+ .connectionPool(new ConnectionPool(50, 10, TimeUnit.MINUTES))
+ .cookieJar(new CookieJarImpl())
+ .build();
+
+ public MyFreeCams() throws IOException {
+ client = MyFreeCamsClient.getInstance();
+ client.setSite(this);
+ client.start();
+
+ login();
+ }
+
+ public void login() throws IOException {
+ RequestBody body = new FormBody.Builder()
+ .add("username", "affenhubert")
+ .add("password", "hampel81")
+ .add("tz", "2")
+ .add("ss", "1920x1080")
+ .add("submit_login", "97")
+ .build();
+ Request req = new Request.Builder()
+ .url(getBaseUrl() + "/php/login.php")
+ .header("Referer", getBaseUrl())
+ .header("Content-Type", "application/x-www-form-urlencoded")
+ .post(body)
+ .build();
+ Response resp = httpClient.newCall(req).execute();
+ if(!resp.isSuccessful()) {
+ LOG.error("Login failed {} {}", resp.code(), resp.message());
+ }
+ resp.close();
+ }
+
+ @Override
+ public String getName() {
+ return "MyFreeCams";
+ }
+
+ @Override
+ public String getBaseUrl() {
+ return "https://www.myfreecams.com";
+ }
+
+ @Override
+ public String getAffiliateLink() {
+ return "";
+ }
+
+ @Override
+ public void setRecorder(Recorder recorder) {
+ this.recorder = recorder;
+ }
+
+ @Override
+ public TabProvider getTabProvider() {
+ return new MyFreeCamsTabProvider(client, recorder, this);
+ }
+
+ @Override
+ public MyFreeCamsModel createModel(String name) {
+ MyFreeCamsModel model = new MyFreeCamsModel();
+ model.setName(name);
+ model.setUrl("https://profiles.myfreecams.com/" + name);
+ return model;
+ }
+}
diff --git a/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java b/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java
new file mode 100644
index 00000000..18d8296f
--- /dev/null
+++ b/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java
@@ -0,0 +1,357 @@
+package ctbrec.sites.mfc;
+
+import static ctbrec.sites.mfc.MessageTypes.*;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.squareup.moshi.JsonAdapter;
+import com.squareup.moshi.Moshi;
+
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.WebSocket;
+import okhttp3.WebSocketListener;
+import okio.ByteString;
+
+public class MyFreeCamsClient {
+
+ private static final transient Logger LOG = LoggerFactory.getLogger(MyFreeCamsClient.class);
+
+ private static MyFreeCamsClient instance;
+ private MyFreeCams mfc;
+ private WebSocket ws;
+ private Moshi moshi;
+ private volatile boolean running = false;
+
+ private Map sessionStates = new HashMap<>();
+ private Map models = new HashMap<>();
+ private Lock lock = new ReentrantLock();
+ private ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ private MyFreeCamsClient() {
+ moshi = new Moshi.Builder().build();
+ }
+
+ public static synchronized MyFreeCamsClient getInstance() {
+ if (instance == null) {
+ instance = new MyFreeCamsClient();
+ }
+ return instance;
+ }
+
+ public void setSite(MyFreeCams mfc) {
+ this.mfc = mfc;
+ }
+
+ public void start() throws IOException {
+ running = true;
+ ServerConfig serverConfig = new ServerConfig(MyFreeCams.httpClient);
+ List websocketServers = new ArrayList(serverConfig.wsServers.keySet());
+ String server = websocketServers.get((int) (Math.random()*websocketServers.size()));
+ String wsUrl = "ws://" + server + ".myfreecams.com:8080/fcsl";
+ Request req = new Request.Builder()
+ .url(wsUrl)
+ .addHeader("Origin", "http://m.myfreecams.com")
+ .build();
+ ws = createWebSocket(req);
+ }
+
+ public void stop() {
+ ws.close(1000, "Good Bye"); // terminate normally (1000)
+ running = false;
+ }
+
+ public List getModels() {
+ lock.lock();
+ try {
+ LOG.trace("Models: {}", models.size());
+ return new ArrayList<>(this.models.values());
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private WebSocket createWebSocket(Request req) {
+ WebSocket ws = MyFreeCams.httpClient.newWebSocket(req, new WebSocketListener() {
+ @Override
+ public void onOpen(WebSocket webSocket, Response response) {
+ super.onOpen(webSocket, response);
+ try {
+ LOG.trace("open: [{}]", response.body().string());
+ webSocket.send("hello fcserver\n");
+ // TxCmd Sending - nType: 1, nTo: 0, nArg1: 20080909, nArg2: 0, sMsg:guest:guest
+ webSocket.send("1 0 0 20080909 0 guest:guest\n");
+ startKeepAlive(webSocket);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onClosed(WebSocket webSocket, int code, String reason) {
+ // TODO decide what todo: is this the end of the session
+ // or do we have to reconnect to keep things running?
+ super.onClosed(webSocket, code, reason);
+ LOG.trace("close: {} {}", code, reason);
+ running = false;
+ MyFreeCams.httpClient.dispatcher().executorService().shutdownNow();
+ }
+
+ private StringBuilder msgBuffer = new StringBuilder();
+
+ @Override
+ public void onMessage(WebSocket webSocket, String text) {
+ super.onMessage(webSocket, text);
+ msgBuffer.append(text);
+ Message message;
+ try {
+ message = parseMessage(msgBuffer);
+ if (message != null) {
+ msgBuffer.setLength(0);
+ }
+
+ switch (message.getType()) {
+ case LOGIN:
+ LOG.trace("login");
+ break;
+ case DETAILS:
+ case ROOMHELPER:
+ case ADDFRIEND:
+ case ADDIGNORE:
+ case CMESG:
+ case PMESG:
+ case TXPROFILE:
+ case USERNAMELOOKUP:
+ case MYCAMSTATE:
+ case MYWEBCAM:
+ case JOINCHAN:
+ case SESSIONSTATE:
+ if(!message.getMessage().isEmpty()) {
+ JsonAdapter adapter = moshi.adapter(SessionState.class);
+ try {
+ SessionState sessionState = adapter.fromJson(message.getMessage());
+ updateSessionState(sessionState);
+ } catch (IOException e) {
+ LOG.error("Couldn't parse session state message", e);
+ }
+ }
+ break;
+ case TAGS:
+ JSONObject json = new JSONObject(message.getMessage());
+ String[] names = JSONObject.getNames(json);
+ Integer uid = Integer.parseInt(names[0]);
+ SessionState sessionState = sessionStates.get(uid);
+ if (sessionState != null) {
+ JSONArray tags = json.getJSONArray(names[0]);
+ for (Object obj : tags) {
+ sessionState.getM().getTags().add((String) obj);
+ }
+ }
+ break;
+ case EXTDATA:
+ requestExtData(message.getMessage());
+ break;
+ default:
+ LOG.trace("Unknown message {}", message);
+ break;
+ }
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+
+ private void requestExtData(String message) {
+ try {
+ JSONObject json = new JSONObject(message);
+ long respkey = json.getInt("respkey");
+ long opts = json.getInt("opts");
+ long serv = json.getInt("serv");
+ long type = json.getInt("type");
+ String base = "http://www.myfreecams.com/php/FcwExtResp.php";
+ String url = base + "?respkey="+respkey+"&opts="+opts+"&serv="+serv+"&type="+type;
+ Request req = new Request.Builder().url(url).build();
+ LOG.debug("Requesting EXTDATA {}", url);
+ Response resp = MyFreeCams.httpClient.newCall(req).execute();
+
+ if(resp.isSuccessful()) {
+ parseExtDataSessionStates(resp.body().string());
+ }
+ } catch(Exception e) {
+ LOG.warn("Couldn't request EXTDATA", e);
+ }
+ }
+
+ private void parseExtDataSessionStates(String json) {
+ JSONObject object = new JSONObject(json);
+ if(object.has("type") && object.getInt("type") == 21) {
+ JSONArray outer = object.getJSONArray("rdata");
+ for (int i = 1; i < outer.length(); i++) {
+ JSONArray inner = outer.getJSONArray(i);
+ try {
+ SessionState state = new SessionState();
+ int idx = 0;
+ state.setNm(inner.getString(idx++));
+ state.setSid(inner.getInt(idx++));
+ state.setUid(inner.getInt(idx++));
+ state.setVs(inner.getInt(idx++));
+ state.setPid(inner.getInt(idx++));
+ state.setLv(inner.getInt(idx++));
+ state.setU(new User());
+ state.getU().setCamserv(inner.getInt(idx++));
+ idx++;
+ state.getU().setChatColor(inner.getString(idx++));
+ state.getU().setChatFont(inner.getInt(idx++));
+ state.getU().setChatOpt(inner.getInt(idx++));
+ state.getU().setCreation(inner.getInt(idx++));
+ state.getU().setAvatar(inner.getInt(idx++));
+ state.getU().setProfile(inner.getInt(idx++));
+ state.getU().setPhotos(inner.getInt(idx++));
+ state.getU().setBlurb(inner.getString(idx++));
+ state.setM(new Model());
+ state.getM().setNewModel(inner.getInt(idx++));
+ state.getM().setMissmfc(inner.getInt(idx++));
+ state.getM().setCamscore(inner.getDouble(idx++));
+ state.getM().setContinent(inner.getString(idx++));
+ state.getM().setFlags(inner.getInt(idx++));
+ state.getM().setRank(inner.getInt(idx++));
+ state.getM().setRc(inner.getInt(idx++));
+ state.getM().setTopic(inner.getString(idx++));
+ state.getM().setHidecs(inner.getInt(idx++) == 1);
+ updateSessionState(state);
+ } catch(Exception e) {
+ LOG.warn("Couldn't parse session state {}", inner.toString());
+ }
+ }
+ } else if(object.has("type") && object.getInt("type") == 20) {
+ // TODO parseTags();
+ }
+ }
+
+ private void updateSessionState(SessionState newState) {
+ if (newState.getUid() <= 0) {
+ return;
+ }
+ SessionState storedState = sessionStates.get(newState.getUid());
+ if (storedState != null) {
+ storedState.merge(newState);
+ updateModel(storedState);
+ } else {
+ lock.lock();
+ try {
+ sessionStates.put(newState.getUid(), newState);
+ updateModel(newState);
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+
+ private void updateModel(SessionState state) {
+ // essential data not yet available
+ if(state.getNm() == null || state.getM() == null || state.getU() == null || state.getU().getCamserv() == null) {
+ return;
+ }
+
+ MyFreeCamsModel model = models.get(state.getUid());
+ if(model == null) {
+ model = mfc.createModel(state.getNm());
+ models.put(state.getUid(), model);
+ }
+ model.update(state);
+ }
+
+ private Message parseMessage(StringBuilder msg) throws UnsupportedEncodingException {
+ if (msg.length() < 4) {
+ // packet size not transmitted completely
+ return null;
+ } else {
+ int packetLength = Integer.parseInt(msg.substring(0, 4));
+ if (packetLength > msg.length() - 4) {
+ // packet not complete
+ return null;
+ } else {
+ msg.delete(0, 4);
+ int type = parseNextInt(msg);
+ int sender = parseNextInt(msg);
+ int receiver = parseNextInt(msg);
+ int arg1 = parseNextInt(msg);
+ int arg2 = parseNextInt(msg);
+ return new Message(type, sender, receiver, arg1, arg2, URLDecoder.decode(msg.toString(), "utf-8"));
+ }
+ }
+ }
+
+ private int parseNextInt(StringBuilder s) {
+ int nextSpace = s.indexOf(" ");
+ int i = Integer.parseInt(s.substring(0, nextSpace));
+ s.delete(0, nextSpace + 1);
+ return i;
+ }
+
+ @Override
+ public void onMessage(WebSocket webSocket, ByteString bytes) {
+ super.onMessage(webSocket, bytes);
+ LOG.debug("msgb: {}", bytes.hex());
+ }
+ });
+ return ws;
+ }
+
+ private void startKeepAlive(WebSocket ws) {
+ Thread keepAlive = new Thread(() -> {
+ while(running) {
+ LOG.trace("--> NULL to keep the connection alive");
+ try {
+ ws.send("0 0 0 0 0 -\n");
+ Thread.sleep(TimeUnit.SECONDS.toMillis(15));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ keepAlive.setName("KeepAlive");
+ keepAlive.setDaemon(true);
+ keepAlive.start();
+ }
+
+ public void update(MyFreeCamsModel model) {
+ lock.lock();
+ try {
+ for (SessionState state : sessionStates.values()) {
+ if(Objects.equals(state.getNm(), model.getName())) {
+ model.update(state);
+ return;
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public MyFreeCamsModel getModel(int uid) {
+ return models.get(uid);
+ }
+
+ public void execute(Runnable r) {
+ executor.execute(r);
+ }
+}
diff --git a/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java b/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java
new file mode 100644
index 00000000..63fa48f8
--- /dev/null
+++ b/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java
@@ -0,0 +1,162 @@
+package ctbrec.sites.mfc;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.iheartradio.m3u8.Encoding;
+import com.iheartradio.m3u8.Format;
+import com.iheartradio.m3u8.ParseException;
+import com.iheartradio.m3u8.PlaylistException;
+import com.iheartradio.m3u8.PlaylistParser;
+import com.iheartradio.m3u8.data.MasterPlaylist;
+import com.iheartradio.m3u8.data.Playlist;
+import com.iheartradio.m3u8.data.PlaylistData;
+
+import ctbrec.AbstractModel;
+import ctbrec.recorder.download.StreamSource;
+import okhttp3.Request;
+import okhttp3.Response;
+
+public class MyFreeCamsModel extends AbstractModel {
+
+ private static final transient Logger LOG = LoggerFactory.getLogger(MyFreeCamsModel.class);
+
+ private String hlsUrl;
+ private double camScore;
+ private State state;
+ private int resolution[];
+
+ @Override
+ public boolean isOnline() throws IOException, ExecutionException, InterruptedException {
+ MyFreeCamsClient.getInstance().update(this);
+ return state == State.ONLINE;
+ }
+
+ @Override
+ public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
+ return isOnline();
+ }
+
+ @Override
+ public String getOnlineState(boolean failFast) throws IOException, ExecutionException {
+ return state != null ? state.toString() : "offline";
+ }
+
+ @Override
+ public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException {
+ MasterPlaylist masterPlaylist = getMasterPlaylist();
+ List sources = new ArrayList<>();
+ for (PlaylistData playlist : masterPlaylist.getPlaylists()) {
+ if (playlist.hasStreamInfo()) {
+ StreamSource src = new StreamSource();
+ src.bandwidth = playlist.getStreamInfo().getBandwidth();
+ if(playlist.getStreamInfo().getResolution() != null) {
+ src.width = playlist.getStreamInfo().getResolution().width;
+ src.height = playlist.getStreamInfo().getResolution().height;
+ String masterUrl = hlsUrl;
+ String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1);
+ String segmentUri = baseUrl + playlist.getUri();
+ src.mediaPlaylistUrl = segmentUri;
+ LOG.trace("Media playlist {}", src.mediaPlaylistUrl);
+ sources.add(src);
+ }
+ }
+ }
+ return sources;
+ }
+
+ private MasterPlaylist getMasterPlaylist() throws IOException, ParseException, PlaylistException {
+ if(hlsUrl == null) {
+ throw new IllegalStateException("Stream url unknown");
+ }
+ LOG.debug("Loading master playlist {}", hlsUrl);
+ Request req = new Request.Builder().url(hlsUrl).build();
+ Response response = MyFreeCams.httpClient.newCall(req).execute();
+ try {
+ InputStream inputStream = response.body().byteStream();
+ PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8);
+ Playlist playlist = parser.parse();
+ MasterPlaylist master = playlist.getMasterPlaylist();
+ return master;
+ } finally {
+ response.close();
+ }
+ }
+
+ @Override
+ public void invalidateCacheEntries() {
+ resolution = null;
+ }
+
+ @Override
+ public void receiveTip(int tokens) throws IOException {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public int[] getStreamResolution(boolean failFast) throws ExecutionException {
+ if(resolution == null) {
+ if(failFast || hlsUrl == null) {
+ return new int[2];
+ }
+ MyFreeCamsClient.getInstance().execute(()->{
+ try {
+ List streamSources = getStreamSources();
+ Collections.sort(streamSources);
+ StreamSource best = streamSources.get(streamSources.size()-1);
+ resolution = new int[] {best.width, best.height};
+ } catch (ExecutionException | IOException | ParseException | PlaylistException e) {
+ LOG.error("Couldn't determine stream resolution", e);
+ }
+ });
+ return new int[2];
+ } else {
+ return resolution;
+ }
+ }
+
+ public void setStreamUrl(String hlsUrl) {
+ this.hlsUrl = hlsUrl;
+ }
+
+ public String getStreamUrl() {
+ return hlsUrl;
+ }
+
+ public double getCamScore() {
+ return camScore;
+ }
+
+ public void setCamScore(double camScore) {
+ this.camScore = camScore;
+ }
+
+ public void setState(State state) {
+ this.state = state;
+ }
+
+ public void update(SessionState state) {
+ setCamScore(state.getM().getCamscore());
+ setState(State.of(state.getVs()));
+
+ // preview
+ String uid = state.getUid().toString();
+ String uidStart = uid.substring(0, 3);
+ String previewUrl = "https://img.mfcimg.com/photos2/"+uidStart+'/'+uid+"/avatar.300x300.jpg";
+ setPreview(previewUrl);
+
+ // stream url
+ Integer camserv = state.getU().getCamserv();
+ if(camserv != null) {
+ String hlsUrl = "http://video" + (camserv - 500) + ".myfreecams.com:1935/NxServer/ngrp:mfc_" + (100000000 + state.getUid()) + ".f4v_mobile/playlist.m3u8";
+ setStreamUrl(hlsUrl);
+ }
+ }
+}
diff --git a/src/main/java/ctbrec/sites/mfc/MyFreeCamsTabProvider.java b/src/main/java/ctbrec/sites/mfc/MyFreeCamsTabProvider.java
new file mode 100644
index 00000000..97d719db
--- /dev/null
+++ b/src/main/java/ctbrec/sites/mfc/MyFreeCamsTabProvider.java
@@ -0,0 +1,43 @@
+package ctbrec.sites.mfc;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import ctbrec.recorder.Recorder;
+import ctbrec.ui.PaginatedScheduledService;
+import ctbrec.ui.TabProvider;
+import ctbrec.ui.ThumbOverviewTab;
+import javafx.scene.Scene;
+import javafx.scene.control.Tab;
+import javafx.util.Duration;
+
+public class MyFreeCamsTabProvider extends TabProvider {
+ private Recorder recorder;
+ private MyFreeCams myFreeCams;
+
+ public MyFreeCamsTabProvider(MyFreeCamsClient client, Recorder recorder, MyFreeCams myFreeCams) {
+ this.recorder = recorder;
+ this.myFreeCams = myFreeCams;
+ }
+
+ @Override
+ public List getTabs(Scene scene) {
+ List tabs = new ArrayList<>();
+
+ PaginatedScheduledService updateService = new OnlineCamsUpdateService();
+ ThumbOverviewTab online = new ThumbOverviewTab("Online", updateService);
+ online.setRecorder(recorder);
+ updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(10)));
+ tabs.add(online);
+
+ updateService = new FriendsUpdateService(myFreeCams);
+ ThumbOverviewTab friends = new ThumbOverviewTab("Friends", updateService);
+ friends.setRecorder(recorder);
+ updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(10)));
+ tabs.add(friends);
+
+
+ return tabs;
+ }
+}
diff --git a/src/main/java/ctbrec/sites/mfc/OnlineCamsUpdateService.java b/src/main/java/ctbrec/sites/mfc/OnlineCamsUpdateService.java
new file mode 100644
index 00000000..6ecaf5ba
--- /dev/null
+++ b/src/main/java/ctbrec/sites/mfc/OnlineCamsUpdateService.java
@@ -0,0 +1,39 @@
+package ctbrec.sites.mfc;
+
+
+import java.io.IOException;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import ctbrec.Model;
+import ctbrec.ui.PaginatedScheduledService;
+import javafx.concurrent.Task;
+
+public class OnlineCamsUpdateService extends PaginatedScheduledService {
+
+ @Override
+ protected Task> createTask() {
+ return new Task>() {
+ @Override
+ public List call() throws IOException {
+ MyFreeCamsClient client = MyFreeCamsClient.getInstance();
+ int modelsPerPage = 50;
+ return client.getModels().stream()
+ .filter((m) -> m.getPreview() != null)
+ .filter((m) -> m.getStreamUrl() != null)
+ .filter((m) -> {
+ try {
+ return m.isOnline();
+ } catch(Exception e) {
+ return false;
+ }
+ })
+ .sorted((m1,m2) -> (int)(m2.getCamScore() - m1.getCamScore()))
+ .skip( (page-1) * modelsPerPage)
+ .limit(modelsPerPage)
+ .collect(Collectors.toList());
+ }
+ };
+ }
+
+}
diff --git a/src/main/java/ctbrec/sites/mfc/OnlineModelsTab.java b/src/main/java/ctbrec/sites/mfc/OnlineModelsTab.java
new file mode 100644
index 00000000..49d47fe8
--- /dev/null
+++ b/src/main/java/ctbrec/sites/mfc/OnlineModelsTab.java
@@ -0,0 +1,12 @@
+package ctbrec.sites.mfc;
+
+import ctbrec.ui.PaginatedScheduledService;
+import ctbrec.ui.ThumbOverviewTab;
+
+public class OnlineModelsTab extends ThumbOverviewTab {
+
+ public OnlineModelsTab(String title, PaginatedScheduledService updateService) {
+ super(title, updateService);
+
+ }
+}
diff --git a/src/main/java/ctbrec/sites/mfc/ServerConfig.java b/src/main/java/ctbrec/sites/mfc/ServerConfig.java
new file mode 100644
index 00000000..d6095fda
--- /dev/null
+++ b/src/main/java/ctbrec/sites/mfc/ServerConfig.java
@@ -0,0 +1,59 @@
+package ctbrec.sites.mfc;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+
+public class ServerConfig {
+
+ List ajaxServers;
+ List videoServers;
+ List chatServers;
+ Map h5Servers;
+ Map wsServers;
+ Map wzobsServers;
+ Map ngVideo;
+
+ public ServerConfig(OkHttpClient client) throws IOException {
+ Request req = new Request.Builder().url("http://www.myfreecams.com/_js/serverconfig.js").build();
+ Response resp = client.newCall(req).execute();
+ String json = resp.body().string();
+
+ JSONObject serverConfig = new JSONObject(json);
+ ajaxServers = parseList(serverConfig, "ajax_servers");
+ videoServers = parseList(serverConfig, "video_servers");
+ chatServers = parseList(serverConfig, "chat_servers");
+ h5Servers = parseMap(serverConfig, "h5video_servers");
+ wsServers = parseMap(serverConfig, "websocket_servers");
+ wzobsServers = parseMap(serverConfig, "wzobs_servers");
+ ngVideo = parseMap(serverConfig, "ngvideo_servers");
+ }
+
+ private static Map parseMap(JSONObject serverConfig, String name) {
+ JSONObject servers = serverConfig.getJSONObject(name);
+ Map result = new HashMap<>();
+ for (String key : servers.keySet()) {
+ result.put(key, servers.getString(key));
+ }
+ return result;
+ }
+
+ private static List parseList(JSONObject serverConfig, String name) {
+ JSONArray servers = serverConfig.getJSONArray(name);
+ List result = new ArrayList<>(servers.length());
+ for (Object server : servers) {
+ result.add((String) server);
+ }
+ return result;
+ }
+
+}
diff --git a/src/main/java/ctbrec/sites/mfc/SessionState.java b/src/main/java/ctbrec/sites/mfc/SessionState.java
new file mode 100644
index 00000000..e87c882e
--- /dev/null
+++ b/src/main/java/ctbrec/sites/mfc/SessionState.java
@@ -0,0 +1,128 @@
+package ctbrec.sites.mfc;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class SessionState {
+
+ private Integer lv;
+ private String nm;
+ private Integer pid;
+ private Integer sid;
+ private Integer uid;
+ private Integer vs;
+ private User u;
+ private Model m;
+ private X x;
+ private Map additionalProperties = new HashMap();
+
+ public Integer getLv() {
+ return lv;
+ }
+
+ public void setLv(Integer lv) {
+ this.lv = lv;
+ }
+
+ public String getNm() {
+ return nm;
+ }
+
+ public void setNm(String nm) {
+ this.nm = nm;
+ }
+
+ public Integer getPid() {
+ return pid;
+ }
+
+ public void setPid(Integer pid) {
+ this.pid = pid;
+ }
+
+ public Integer getSid() {
+ return sid;
+ }
+
+ public void setSid(Integer sid) {
+ this.sid = sid;
+ }
+
+ public Integer getUid() {
+ return uid;
+ }
+
+ public void setUid(Integer uid) {
+ this.uid = uid;
+ }
+
+ public Integer getVs() {
+ return vs;
+ }
+
+ public void setVs(Integer vs) {
+ this.vs = vs;
+ }
+
+ public User getU() {
+ return u;
+ }
+
+ public void setU(User u) {
+ this.u = u;
+ }
+
+ public Model getM() {
+ return m;
+ }
+
+ public void setM(Model m) {
+ this.m = m;
+ }
+
+ public X getX() {
+ return x;
+ }
+
+ public void setX(X x) {
+ this.x = x;
+ }
+
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ @Override
+ public String toString() {
+ return Integer.toString(uid) + " u:" + u + " m:" + m + " x:" + x + " " + nm;
+ }
+
+ public void merge(SessionState newState) {
+ lv = newState.lv != null ? newState.lv : lv;
+ nm = newState.nm != null ? newState.nm : nm;
+ pid = newState.pid != null ? newState.pid : pid;
+ sid = newState.sid != null ? newState.sid : sid;
+ vs = newState.vs != null ? newState.vs : vs;
+ additionalProperties.putAll(newState.additionalProperties);
+
+ if (u != null) {
+ u.merge(newState.u);
+ } else {
+ u = newState.u;
+ }
+ if (m != null) {
+ m.merge(newState.m);
+ } else {
+ m = newState.m;
+ }
+ if (x != null) {
+ x.merge(newState.x);
+ } else {
+ x = newState.x;
+ }
+ }
+}
diff --git a/src/main/java/ctbrec/sites/mfc/Share.java b/src/main/java/ctbrec/sites/mfc/Share.java
new file mode 100644
index 00000000..ab6d5808
--- /dev/null
+++ b/src/main/java/ctbrec/sites/mfc/Share.java
@@ -0,0 +1,116 @@
+package ctbrec.sites.mfc;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class Share {
+
+ private Integer albums;
+ private Integer follows;
+ private Integer tmAlbum;
+ private Integer things;
+ private Integer clubs;
+ private Integer collections;
+ private Integer stores;
+ private Integer goals;
+ private Integer polls;
+ private Map additionalProperties = new HashMap();
+
+ public Integer getAlbums() {
+ return albums;
+ }
+
+ public void setAlbums(Integer albums) {
+ this.albums = albums;
+ }
+
+ public Integer getFollows() {
+ return follows;
+ }
+
+ public void setFollows(Integer follows) {
+ this.follows = follows;
+ }
+
+ public Integer getTmAlbum() {
+ return tmAlbum;
+ }
+
+ public void setTmAlbum(Integer tmAlbum) {
+ this.tmAlbum = tmAlbum;
+ }
+
+ public Integer getThings() {
+ return things;
+ }
+
+ public void setThings(Integer things) {
+ this.things = things;
+ }
+
+ public Integer getClubs() {
+ return clubs;
+ }
+
+ public void setClubs(Integer clubs) {
+ this.clubs = clubs;
+ }
+
+ public Integer getCollections() {
+ return collections;
+ }
+
+ public void setCollections(Integer collections) {
+ this.collections = collections;
+ }
+
+ public Integer getStores() {
+ return stores;
+ }
+
+ public void setStores(Integer stores) {
+ this.stores = stores;
+ }
+
+ public Integer getGoals() {
+ return goals;
+ }
+
+ public void setGoals(Integer goals) {
+ this.goals = goals;
+ }
+
+ public Integer getPolls() {
+ return polls;
+ }
+
+ public void setPolls(Integer polls) {
+ this.polls = polls;
+ }
+
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ public void merge(Share share) {
+ if (share == null) {
+ return;
+ }
+
+ albums = share.albums != null ? share.albums : albums;
+ follows = share.follows != null ? share.follows : follows;
+ tmAlbum = share.tmAlbum != null ? share.tmAlbum : tmAlbum;
+ things = share.things != null ? share.things : things;
+ clubs = share.clubs != null ? share.clubs : clubs;
+ collections = share.collections != null ? share.collections : collections;
+ stores = share.stores != null ? share.stores : stores;
+ goals = share.goals != null ? share.goals : goals;
+ polls = share.polls != null ? share.polls : polls;
+ additionalProperties.putAll(share.additionalProperties);
+ }
+
+}
diff --git a/src/main/java/ctbrec/sites/mfc/State.java b/src/main/java/ctbrec/sites/mfc/State.java
new file mode 100644
index 00000000..11c3dea7
--- /dev/null
+++ b/src/main/java/ctbrec/sites/mfc/State.java
@@ -0,0 +1,56 @@
+package ctbrec.sites.mfc;
+
+import java.util.Optional;
+
+public enum State {
+ ONLINE("online"),
+ CAMOFF("online - cam off"),
+ RECORDING("recording"),
+ INCLUDE("include"),
+ EXCLUDE("exclude"),
+ DELETE("delete"),
+ AWAY("away"),
+ PRIVATE("private"),
+ GROUP_SHOW("group_show"),
+ OFFLINE("offline"),
+ UNKNOWN("unknown");
+
+ String literal;
+ State(String literal) {
+ this.literal = literal;
+ }
+
+ public static State of(Integer vs) {
+ Integer s = Optional.ofNullable(vs).orElse(Integer.MAX_VALUE);
+ switch (s) {
+ case 0:
+ return ONLINE;
+ case 90:
+ return CAMOFF;
+ case -4:
+ return RECORDING;
+ case -3:
+ return INCLUDE;
+ case -2:
+ return EXCLUDE;
+ case -1:
+ return DELETE;
+ case 2:
+ return AWAY;
+ case 12:
+ case 91:
+ return PRIVATE;
+ case 13:
+ return GROUP_SHOW;
+ case 127:
+ return OFFLINE;
+ default:
+ return UNKNOWN;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return literal;
+ }
+}
diff --git a/src/main/java/ctbrec/sites/mfc/User.java b/src/main/java/ctbrec/sites/mfc/User.java
new file mode 100644
index 00000000..d1f78dc2
--- /dev/null
+++ b/src/main/java/ctbrec/sites/mfc/User.java
@@ -0,0 +1,154 @@
+package ctbrec.sites.mfc;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class User {
+
+ private Integer avatar;
+ private String blurb;
+ private Integer camserv;
+ private String chatColor;
+ private Integer chatFont;
+ private Integer chatOpt;
+ private String country;
+ private Integer creation;
+ private String ethnic;
+ private String occupation;
+ private Integer photos;
+ private Integer profile;
+ private String status;
+ private Map additionalProperties = new HashMap();
+
+ public Integer getAvatar() {
+ return avatar;
+ }
+
+ public void setAvatar(Integer avatar) {
+ this.avatar = avatar;
+ }
+
+ public String getBlurb() {
+ return blurb;
+ }
+
+ public void setBlurb(String blurb) {
+ this.blurb = blurb;
+ }
+
+ public Integer getCamserv() {
+ return camserv;
+ }
+
+ public void setCamserv(Integer camserv) {
+ this.camserv = camserv;
+ }
+
+ public String getChatColor() {
+ return chatColor;
+ }
+
+ public void setChatColor(String chatColor) {
+ this.chatColor = chatColor;
+ }
+
+ public Integer getChatFont() {
+ return chatFont;
+ }
+
+ public void setChatFont(Integer chatFont) {
+ this.chatFont = chatFont;
+ }
+
+ public Integer getChatOpt() {
+ return chatOpt;
+ }
+
+ public void setChatOpt(Integer chatOpt) {
+ this.chatOpt = chatOpt;
+ }
+
+ public String getCountry() {
+ return country;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ public Integer getCreation() {
+ return creation;
+ }
+
+ public void setCreation(Integer creation) {
+ this.creation = creation;
+ }
+
+ public String getEthnic() {
+ return ethnic;
+ }
+
+ public void setEthnic(String ethnic) {
+ this.ethnic = ethnic;
+ }
+
+ public String getOccupation() {
+ return occupation;
+ }
+
+ public void setOccupation(String occupation) {
+ this.occupation = occupation;
+ }
+
+ public Integer getPhotos() {
+ return photos;
+ }
+
+ public void setPhotos(Integer photos) {
+ this.photos = photos;
+ }
+
+ public Integer getProfile() {
+ return profile;
+ }
+
+ public void setProfile(Integer profile) {
+ this.profile = profile;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ public void merge(User u) {
+ if (u == null) {
+ return;
+ }
+ avatar = u.avatar != null ? u.avatar : avatar;
+ blurb = u.blurb != null ? u.blurb : blurb;
+ camserv = u.camserv != null ? u.camserv : camserv;
+ chatColor = u.chatColor != null ? u.chatColor : chatColor;
+ chatFont = u.chatFont != null ? u.chatFont : chatFont;
+ chatOpt = u.chatOpt != null ? u.chatOpt : chatOpt;
+ country = u.country != null ? u.country : country;
+ creation = u.creation != null ? u.creation : creation;
+ ethnic = u.ethnic != null ? u.ethnic : ethnic;
+ occupation = u.occupation != null ? u.occupation : occupation;
+ photos = u.photos != null ? u.photos : photos;
+ profile = u.profile != null ? u.profile : profile;
+ status = u.status != null ? u.status : status;
+ additionalProperties.putAll(u.additionalProperties);
+ }
+}
diff --git a/src/main/java/ctbrec/sites/mfc/X.java b/src/main/java/ctbrec/sites/mfc/X.java
new file mode 100644
index 00000000..db5175db
--- /dev/null
+++ b/src/main/java/ctbrec/sites/mfc/X.java
@@ -0,0 +1,46 @@
+package ctbrec.sites.mfc;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class X {
+
+ private Fcext fcext;
+ private Share share;
+ private Map additionalProperties = new HashMap();
+
+ public Fcext getFcext() {
+ return fcext;
+ }
+
+ public void setFcext(Fcext fcext) {
+ this.fcext = fcext;
+ }
+
+ public Share getShare() {
+ return share;
+ }
+
+ public void setShare(Share share) {
+ this.share = share;
+ }
+
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+ public void merge(X x) {
+ if(x == null) {
+ return;
+ }
+ fcext.merge(x.fcext);
+ share.merge(x.share);
+ additionalProperties.putAll(x.additionalProperties);
+
+ }
+
+}
diff --git a/src/main/java/ctbrec/ui/CamrecApplication.java b/src/main/java/ctbrec/ui/CamrecApplication.java
index 3ffbe289..02e42c48 100644
--- a/src/main/java/ctbrec/ui/CamrecApplication.java
+++ b/src/main/java/ctbrec/ui/CamrecApplication.java
@@ -26,27 +26,20 @@ import ctbrec.io.HttpClient;
import ctbrec.recorder.LocalRecorder;
import ctbrec.recorder.Recorder;
import ctbrec.recorder.RemoteRecorder;
-import ctbrec.sites.chaturbate.Chaturbate;
+import ctbrec.sites.mfc.MyFreeCams;
import javafx.application.Application;
import javafx.application.HostServices;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
-import javafx.geometry.Insets;
-import javafx.geometry.Pos;
-import javafx.scene.Node;
-import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
-import javafx.scene.control.Button;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TabPane.TabClosingPolicy;
import javafx.scene.image.Image;
import javafx.scene.layout.HBox;
-import javafx.scene.layout.StackPane;
-import javafx.scene.text.Font;
import javafx.stage.Stage;
import okhttp3.Request;
import okhttp3.Response;
@@ -74,9 +67,10 @@ public class CamrecApplication extends Application {
hostServices = getHostServices();
client = HttpClient.getInstance();
createRecorder();
- site = new Chaturbate();
+ //site = new Chaturbate();
+ site = new MyFreeCams();
site.setRecorder(recorder);
- doInitialLogin();
+ // TODO move this to Chaturbate class doInitialLogin();
createGui(primaryStage);
checkForUpdates();
}
@@ -158,37 +152,38 @@ public class CamrecApplication extends Application {
}.start();
});
- String username = Config.getInstance().getSettings().username;
- if(username != null && !username.trim().isEmpty()) {
- double fontSize = tabPane.getTabMaxHeight() / 2 - 1;
- Button buyTokens = new Button("Buy Tokens");
- buyTokens.setFont(Font.font(fontSize));
- buyTokens.setOnAction((e) -> DesktopIntergation.open(AFFILIATE_LINK));
- buyTokens.setMaxHeight(tabPane.getTabMaxHeight());
- TokenLabel tokenBalance = new TokenLabel();
- tokenPanel = new HBox(5, tokenBalance, buyTokens);
- //tokenPanel.setBackground(new Background(new BackgroundFill(Color.GREEN, CornerRadii.EMPTY, new Insets(0))));
- tokenPanel.setAlignment(Pos.BASELINE_RIGHT);
- tokenPanel.setMaxHeight(tabPane.getTabMaxHeight());
- tokenPanel.setMaxWidth(200);
- tokenBalance.setFont(Font.font(fontSize));
- HBox.setMargin(tokenBalance, new Insets(0, 5, 0, 0));
- HBox.setMargin(buyTokens, new Insets(0, 5, 0, 0));
- for (Node node : tabPane.getChildrenUnmodifiable()) {
- if(node.getStyleClass().contains("tab-header-area")) {
- Parent header = (Parent) node;
- for (Node nd : header.getChildrenUnmodifiable()) {
- if(nd.getStyleClass().contains("tab-header-background")) {
- StackPane pane = (StackPane) nd;
- StackPane.setAlignment(tokenPanel, Pos.CENTER_RIGHT);
- pane.getChildren().add(tokenPanel);
- }
- }
-
- }
- }
- loadTokenBalance(tokenBalance);
- }
+ // TODO think about a solution, which works for all sites
+ // String username = Config.getInstance().getSettings().username;
+ // if(username != null && !username.trim().isEmpty()) {
+ // double fontSize = tabPane.getTabMaxHeight() / 2 - 1;
+ // Button buyTokens = new Button("Buy Tokens");
+ // buyTokens.setFont(Font.font(fontSize));
+ // buyTokens.setOnAction((e) -> DesktopIntergation.open(AFFILIATE_LINK));
+ // buyTokens.setMaxHeight(tabPane.getTabMaxHeight());
+ // TokenLabel tokenBalance = new TokenLabel();
+ // tokenPanel = new HBox(5, tokenBalance, buyTokens);
+ // //tokenPanel.setBackground(new Background(new BackgroundFill(Color.GREEN, CornerRadii.EMPTY, new Insets(0))));
+ // tokenPanel.setAlignment(Pos.BASELINE_RIGHT);
+ // tokenPanel.setMaxHeight(tabPane.getTabMaxHeight());
+ // tokenPanel.setMaxWidth(200);
+ // tokenBalance.setFont(Font.font(fontSize));
+ // HBox.setMargin(tokenBalance, new Insets(0, 5, 0, 0));
+ // HBox.setMargin(buyTokens, new Insets(0, 5, 0, 0));
+ // for (Node node : tabPane.getChildrenUnmodifiable()) {
+ // if(node.getStyleClass().contains("tab-header-area")) {
+ // Parent header = (Parent) node;
+ // for (Node nd : header.getChildrenUnmodifiable()) {
+ // if(nd.getStyleClass().contains("tab-header-background")) {
+ // StackPane pane = (StackPane) nd;
+ // StackPane.setAlignment(tokenPanel, Pos.CENTER_RIGHT);
+ // pane.getChildren().add(tokenPanel);
+ // }
+ // }
+ //
+ // }
+ // }
+ // loadTokenBalance(tokenBalance);
+ // }
}
private void loadTokenBalance(TokenLabel label) {
diff --git a/src/main/java/ctbrec/ui/TabProvider.java b/src/main/java/ctbrec/ui/TabProvider.java
index 1437a0e2..2f760081 100644
--- a/src/main/java/ctbrec/ui/TabProvider.java
+++ b/src/main/java/ctbrec/ui/TabProvider.java
@@ -1,14 +1,11 @@
package ctbrec.ui;
-import java.util.Collections;
import java.util.List;
import javafx.scene.Scene;
import javafx.scene.control.Tab;
-public class TabProvider {
+public abstract class TabProvider {
- public List getTabs(Scene scene) {
- return Collections.emptyList();
- }
+ public abstract List getTabs(Scene scene);
}
diff --git a/src/main/java/ctbrec/ui/ThumbCell.java b/src/main/java/ctbrec/ui/ThumbCell.java
index c5d00020..625ed520 100644
--- a/src/main/java/ctbrec/ui/ThumbCell.java
+++ b/src/main/java/ctbrec/ui/ThumbCell.java
@@ -74,6 +74,7 @@ public class ThumbCell extends StackPane {
private final Color colorHighlight = Color.WHITE;
private final Color colorRecording = new Color(0.8, 0.28, 0.28, 1);
private SimpleBooleanProperty selectionProperty = new SimpleBooleanProperty(false);
+ private double imgAspectRatio = 3.0 / 4.0;
private HttpClient client;
@@ -91,6 +92,8 @@ public class ThumbCell extends StackPane {
iv = new ImageView();
setImage(model.getPreview());
iv.setSmooth(true);
+ iv.setPreserveRatio(true);
+ iv.setStyle("-fx-background-color: #000");
getChildren().add(iv);
nameBackground = new Rectangle();
@@ -208,17 +211,11 @@ public class ThumbCell extends StackPane {
// the model is online, but the resolution is 0. probably something went wrong
// when we first requested the stream info, so we remove this invalid value from the "cache"
// so that it is requested again
- try {
- if (model.isOnline() && resolution[1] == 0) {
- LOG.debug("Removing invalid resolution value for {}", model.getName());
- model.invalidateCacheEntries();
- }
- } catch (IOException | ExecutionException | InterruptedException e) {
- LOG.error("Coulnd't get resolution for model {}", model, e);
+ if (model.isOnline() && resolution[1] == 0) {
+ LOG.debug("Removing invalid resolution value for {}", model.getName());
+ model.invalidateCacheEntries();
}
- } catch (ExecutionException e1) {
- LOG.warn("Couldn't update resolution tag for model {}", model.getName(), e1);
- } catch (IOException e1) {
+ } catch (ExecutionException | IOException | InterruptedException e1) {
LOG.warn("Couldn't update resolution tag for model {}", model.getName(), e1);
} finally {
ThumbOverviewTab.resolutionProcessing.remove(model);
@@ -226,11 +223,11 @@ public class ThumbCell extends StackPane {
});
}
- private void updateResolutionTag(int[] resolution) throws IOException, ExecutionException {
+ private void updateResolutionTag(int[] resolution) throws IOException, ExecutionException, InterruptedException {
String _res = "n/a";
Paint resolutionBackgroundColor = resolutionOnlineColor;
String state = model.getOnlineState(false);
- if ("public".equals(state)) {
+ if (model.isOnline()) {
LOG.trace("Model resolution {} {}x{}", model.getName(), resolution[0], resolution[1]);
LOG.trace("Resolution queue size: {}", ThumbOverviewTab.queue.size());
final int w = resolution[1];
@@ -262,6 +259,8 @@ public class ThumbCell extends StackPane {
@Override
public void changed(ObservableValue extends Number> observable, Number oldValue, Number newValue) {
if(newValue.doubleValue() == 1.0) {
+ imgAspectRatio = img.getHeight() / img.getWidth();
+ setThumbWidth(width);
iv.setImage(img);
}
}
@@ -283,26 +282,33 @@ public class ThumbCell extends StackPane {
}
void startPlayer() {
- try {
- if(model.isOnline(true)) {
- List sources = model.getStreamSources();
- Collections.sort(sources);
- StreamSource best = sources.get(sources.size()-1);
- LOG.debug("Playing {}", best.getMediaPlaylistUrl());
- Player.play(best.getMediaPlaylistUrl());
- } else {
- Alert alert = new AutosizeAlert(Alert.AlertType.INFORMATION);
- alert.setTitle("Room not public");
- alert.setHeaderText("Room is currently not public");
- alert.showAndWait();
+ new Thread(() -> {
+ try {
+ if(model.isOnline(true)) {
+ List sources = model.getStreamSources();
+ Collections.sort(sources);
+ StreamSource best = sources.get(sources.size()-1);
+ LOG.debug("Playing {}", best.getMediaPlaylistUrl());
+ Player.play(best.getMediaPlaylistUrl());
+ } else {
+ Platform.runLater(() -> {
+ Alert alert = new AutosizeAlert(Alert.AlertType.INFORMATION);
+ alert.setTitle("Room not public");
+ alert.setHeaderText("Room is currently not public");
+ alert.showAndWait();
+ });
+ }
+ } catch (IOException | ExecutionException | ParseException | PlaylistException | InterruptedException e1) {
+ LOG.error("Couldn't get stream information for model {}", model, e1);
+ Platform.runLater(() -> {
+ Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
+ alert.setTitle("Error");
+ alert.setHeaderText("Couldn't determine stream URL");
+ alert.setContentText(e1.getLocalizedMessage());
+ alert.showAndWait();
+ });
}
- } catch (IOException | ExecutionException | InterruptedException | ParseException | PlaylistException e1) {
- LOG.error("Couldn't get stream information for model {}", model, e1);
- Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
- alert.setTitle("Error");
- alert.setHeaderText("Couldn't determine stream URL");
- alert.showAndWait();
- }
+ }).start();
}
private void setRecording(boolean recording) {
@@ -491,7 +497,7 @@ public class ThumbCell extends StackPane {
}
public void setThumbWidth(int width) {
- int height = width * 3 / 4;
+ int height = (int) (width * imgAspectRatio);
setSize(width, height);
}
diff --git a/src/main/java/ctbrec/ui/ThumbOverviewTab.java b/src/main/java/ctbrec/ui/ThumbOverviewTab.java
index d043eb28..0e50e23e 100644
--- a/src/main/java/ctbrec/ui/ThumbOverviewTab.java
+++ b/src/main/java/ctbrec/ui/ThumbOverviewTab.java
@@ -62,7 +62,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
static BlockingQueue queue = new LinkedBlockingQueue<>();
static ExecutorService threadPool = new ThreadPoolExecutor(2, 2, 10, TimeUnit.MINUTES, queue);
- PaginatedScheduledService updateService;
+ protected PaginatedScheduledService updateService;
Recorder recorder;
List filteredThumbCells = Collections.synchronizedList(new ArrayList<>());
List selectedThumbCells = Collections.synchronizedList(new ArrayList<>());
@@ -215,9 +215,14 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
if(updatesSuspended) {
return;
}
+ List models = updateService.getValue();
+ updateGrid(models);
+
+ }
+
+ protected void updateGrid(List extends Model> models) {
gridLock.lock();
try {
- List models = updateService.getValue();
ObservableList nodes = grid.getChildren();
// first remove models, which are not in the updated list
@@ -269,7 +274,6 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
} finally {
gridLock.unlock();
}
-
}
ThumbCell createThumbCell(ThumbOverviewTab thumbOverviewTab, Model model, Recorder recorder2, HttpClient client2) {
@@ -439,6 +443,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
} else {
alert.setContentText(event.getSource().getException().getLocalizedMessage());
}
+ LOG.error("Couldn't update model list", event.getSource().getException());
} else {
alert.setContentText(event.getEventType().toString());
}
diff --git a/src/main/java/org/taktik/mpegts/Streamer.java b/src/main/java/org/taktik/mpegts/Streamer.java
index ccc0a515..ee5dbcce 100644
--- a/src/main/java/org/taktik/mpegts/Streamer.java
+++ b/src/main/java/org/taktik/mpegts/Streamer.java
@@ -190,7 +190,7 @@ public class Streamer {
Long sleepNanosPrevious = null;
if (lastPcrValue != null && lastPcrTime != null) {
if (pcrValue <= lastPcrValue) {
- log.error("PCR discontinuity ! " + packet.getPid());
+ log.trace("PCR discontinuity ! " + packet.getPid());
resetState = true;
} else {
sleepNanosPrevious = ((pcrValue - lastPcrValue) / 27 * 1000) - (pcrTime - lastPcrTime);