First implementation for MFC

Implemented Tabs are Online and Friends
This commit is contained in:
0xboobface 2018-10-19 23:09:11 +02:00
parent 54de1339fb
commit 362d90b29b
25 changed files with 1847 additions and 85 deletions

View File

@ -140,6 +140,11 @@
<artifactId>moshi</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20180130</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>

View File

@ -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();

View File

@ -4,6 +4,7 @@ import java.text.DecimalFormat;
public class StreamSource implements Comparable<StreamSource> {
public int bandwidth;
public int width;
public int height;
public String mediaPlaylistUrl;
@ -15,6 +16,14 @@ public class StreamSource implements Comparable<StreamSource> {
this.bandwidth = bandwidth;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}

View File

@ -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<String, Object> additionalProperties = new HashMap<String, Object>();
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<String, Object> 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);
}
}

View File

@ -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<List<Model>> createTask() {
return new Task<List<Model>>() {
@Override
public List<Model> call() throws IOException {
List<Model> 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());
}
};
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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<String, Object> additionalProperties = new HashMap<String, Object>();
private Set<String> 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<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
public Set<String> getTags() {
return tags;
}
public void setTags(Set<String> 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);
}
}

View File

@ -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;
}
}

View File

@ -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<Integer, SessionState> sessionStates = new HashMap<>();
private Map<Integer, MyFreeCamsModel> 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<String> websocketServers = new ArrayList<String>(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<MyFreeCamsModel> 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<SessionState> 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);
}
}

View File

@ -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<StreamSource> getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException {
MasterPlaylist masterPlaylist = getMasterPlaylist();
List<StreamSource> 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<StreamSource> 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);
}
}
}

View File

@ -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<Tab> getTabs(Scene scene) {
List<Tab> 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;
}
}

View File

@ -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<List<Model>> createTask() {
return new Task<List<Model>>() {
@Override
public List<Model> 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());
}
};
}
}

View File

@ -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);
}
}

View File

@ -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<String> ajaxServers;
List<String> videoServers;
List<String> chatServers;
Map<String, String> h5Servers;
Map<String, String> wsServers;
Map<String, String> wzobsServers;
Map<String, String> 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<String, String> parseMap(JSONObject serverConfig, String name) {
JSONObject servers = serverConfig.getJSONObject(name);
Map<String, String> result = new HashMap<>();
for (String key : servers.keySet()) {
result.put(key, servers.getString(key));
}
return result;
}
private static List<String> parseList(JSONObject serverConfig, String name) {
JSONArray servers = serverConfig.getJSONArray(name);
List<String> result = new ArrayList<>(servers.length());
for (Object server : servers) {
result.add((String) server);
}
return result;
}
}

View File

@ -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<String, Object> additionalProperties = new HashMap<String, Object>();
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<String, Object> 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;
}
}
}

View File

@ -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<String, Object> additionalProperties = new HashMap<String, Object>();
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<String, Object> 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);
}
}

View File

@ -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;
}
}

View File

@ -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<String, Object> additionalProperties = new HashMap<String, Object>();
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<String, Object> 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);
}
}

View File

@ -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<String, Object> additionalProperties = new HashMap<String, Object>();
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<String, Object> 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);
}
}

View File

@ -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) {

View File

@ -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<Tab> getTabs(Scene scene) {
return Collections.emptyList();
}
public abstract List<Tab> getTabs(Scene scene);
}

View File

@ -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<StreamSource> 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<StreamSource> 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);
}

View File

@ -62,7 +62,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
static BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
static ExecutorService threadPool = new ThreadPoolExecutor(2, 2, 10, TimeUnit.MINUTES, queue);
PaginatedScheduledService updateService;
protected PaginatedScheduledService updateService;
Recorder recorder;
List<ThumbCell> filteredThumbCells = Collections.synchronizedList(new ArrayList<>());
List<ThumbCell> selectedThumbCells = Collections.synchronizedList(new ArrayList<>());
@ -215,9 +215,14 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
if(updatesSuspended) {
return;
}
List<Model> models = updateService.getValue();
updateGrid(models);
}
protected void updateGrid(List<? extends Model> models) {
gridLock.lock();
try {
List<Model> models = updateService.getValue();
ObservableList<Node> 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());
}

View File

@ -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);