Improve MyFreeCamsClient
- Add watch dog to reestablish a broken connection - Implemented creation of stream urls properly - Add HD tab to display MFC HD streams
This commit is contained in:
parent
8e3d2fd565
commit
f4842fcf51
|
@ -56,7 +56,8 @@ public class FriendsUpdateService extends PaginatedScheduledService {
|
|||
st.setUid(uid);
|
||||
st.setLv(modelObject.getInt("lv"));
|
||||
st.setVs(127);
|
||||
model.update(st);
|
||||
|
||||
model.update(st, myFreeCams.getClient().getStreamUrl(st));
|
||||
}
|
||||
models.add(model);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
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 HDCamsUpdateService 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 -> m.getStreamUrl().contains("/x-hls/"))
|
||||
.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());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -36,7 +36,6 @@ public class MessageTypes {
|
|||
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;
|
||||
|
@ -61,7 +60,6 @@ public class MessageTypes {
|
|||
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;
|
||||
|
@ -91,6 +89,9 @@ public class MessageTypes {
|
|||
public static final int EXTDATA = 81;
|
||||
public static final int NOTIFY = 84;
|
||||
public static final int PUBLISH = 85;
|
||||
public static final int XREQUEST = 86;
|
||||
public static final int XRESPONSE = 87;
|
||||
public static final int EDGECON = 88;
|
||||
public static final int ZGWINVALID = 95;
|
||||
public static final int CONNECTING = 96;
|
||||
public static final int CONNECTED = 97;
|
||||
|
|
|
@ -112,4 +112,8 @@ public class MyFreeCams implements Site {
|
|||
public boolean isSiteForModel(Model m) {
|
||||
return m instanceof MyFreeCamsModel;
|
||||
}
|
||||
|
||||
public MyFreeCamsClient getClient() {
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,12 @@ public class MyFreeCamsClient {
|
|||
private Map<Integer, MyFreeCamsModel> models = new HashMap<>();
|
||||
private Lock lock = new ReentrantLock();
|
||||
private ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
private ServerConfig serverConfig;
|
||||
@SuppressWarnings("unused")
|
||||
private String tkx;
|
||||
private Integer cxid;
|
||||
private int[] ctx;
|
||||
private String ctxenc;
|
||||
|
||||
private MyFreeCamsClient() {
|
||||
moshi = new Moshi.Builder().build();
|
||||
|
@ -62,20 +68,39 @@ public class MyFreeCamsClient {
|
|||
|
||||
public void start() throws IOException {
|
||||
running = true;
|
||||
ServerConfig serverConfig = new ServerConfig(mfc.getHttpClient());
|
||||
serverConfig = new ServerConfig(mfc.getHttpClient());
|
||||
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);
|
||||
|
||||
Thread watchDog = new Thread(() -> {
|
||||
while(running) {
|
||||
if (ws == null) {
|
||||
Request req = new Request.Builder()
|
||||
.url(wsUrl)
|
||||
.addHeader("Origin", "http://m.myfreecams.com")
|
||||
.build();
|
||||
ws = createWebSocket(req);
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch(InterruptedException e) {
|
||||
LOG.error("WatchDog couldn't sleep", e);
|
||||
stop();
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
watchDog.setDaemon(true);
|
||||
watchDog.setName("MFC WebSocket WatchDog");
|
||||
watchDog.setPriority(Thread.MIN_PRIORITY);
|
||||
watchDog.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
ws.close(1000, "Good Bye"); // terminate normally (1000)
|
||||
running = false;
|
||||
ws.close(1000, "Good Bye"); // terminate normally (1000)
|
||||
}
|
||||
|
||||
public List<MyFreeCamsModel> getModels() {
|
||||
|
@ -115,12 +140,21 @@ public class MyFreeCamsClient {
|
|||
|
||||
@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;
|
||||
mfc.getHttpClient().shutdown();
|
||||
LOG.info("MFC websocket closed: {} {}", code, reason);
|
||||
MyFreeCamsClient.this.ws = null;
|
||||
if(!running) {
|
||||
mfc.getHttpClient().shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||
super.onFailure(webSocket, t, response);
|
||||
int code = response.code();
|
||||
String message = response.message();
|
||||
MyFreeCamsClient.this.ws = null;
|
||||
LOG.error("MFC websocket failure: {} {}", code, message, t);
|
||||
}
|
||||
|
||||
private StringBuilder msgBuffer = new StringBuilder();
|
||||
|
@ -137,13 +171,15 @@ public class MyFreeCamsClient {
|
|||
}
|
||||
|
||||
switch (message.getType()) {
|
||||
case NULL:
|
||||
break;
|
||||
case LOGIN:
|
||||
System.out.println("LOGIN");
|
||||
System.out.println("Sender " + message.getSender());
|
||||
System.out.println("Receiver " + message.getReceiver());
|
||||
System.out.println("Arg1 " + message.getArg1());
|
||||
System.out.println("Arg2 " + message.getArg2());
|
||||
System.out.println("Msg " + message.getMessage());
|
||||
// System.out.println("LOGIN");
|
||||
// System.out.println("Sender " + message.getSender());
|
||||
// System.out.println("Receiver " + message.getReceiver());
|
||||
// System.out.println("Arg1 " + message.getArg1());
|
||||
// System.out.println("Arg2 " + message.getArg2());
|
||||
// System.out.println("Msg " + message.getMessage());
|
||||
break;
|
||||
case DETAILS:
|
||||
case ROOMHELPER:
|
||||
|
@ -158,6 +194,7 @@ public class MyFreeCamsClient {
|
|||
case JOINCHAN:
|
||||
case SESSIONSTATE:
|
||||
if(!message.getMessage().isEmpty()) {
|
||||
//LOG.debug("SessionState: {}", message.getMessage());
|
||||
JsonAdapter<SessionState> adapter = moshi.adapter(SessionState.class);
|
||||
try {
|
||||
SessionState sessionState = adapter.fromJson(message.getMessage());
|
||||
|
@ -219,6 +256,21 @@ public class MyFreeCamsClient {
|
|||
System.out.println("Arg2 " + message.getArg2());
|
||||
System.out.println("Msg " + message.getMessage());
|
||||
break;
|
||||
case SLAVEVSHARE:
|
||||
// LOG.debug("SLAVEVSHARE {}", message);
|
||||
// LOG.debug("SLAVEVSHARE MSG [{}]", message.getMessage());
|
||||
break;
|
||||
case TKX:
|
||||
json = new JSONObject(message.getMessage());
|
||||
tkx = json.getString("tkx");
|
||||
cxid = json.getInt("cxid");
|
||||
ctxenc = URLDecoder.decode(json.getString("ctxenc"), "utf-8");
|
||||
JSONArray ctxArray = json.getJSONArray("ctx");
|
||||
ctx = new int[ctxArray.length()];
|
||||
for (int i = 0; i < ctxArray.length(); i++) {
|
||||
ctx[i] = ctxArray.getInt(i);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG.debug("Unknown message {}", message);
|
||||
break;
|
||||
|
@ -240,7 +292,7 @@ public class MyFreeCamsClient {
|
|||
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);
|
||||
LOG.trace("Requesting EXTDATA {}", url);
|
||||
Response resp = mfc.getHttpClient().execute(req);
|
||||
|
||||
if(resp.isSuccessful()) {
|
||||
|
@ -268,7 +320,7 @@ public class MyFreeCamsClient {
|
|||
state.setLv(inner.getInt(idx++));
|
||||
state.setU(new User());
|
||||
state.getU().setCamserv(inner.getInt(idx++));
|
||||
idx++;
|
||||
state.getU().setPhase(inner.getString(idx++));
|
||||
state.getU().setChatColor(inner.getString(idx++));
|
||||
state.getU().setChatFont(inner.getInt(idx++));
|
||||
state.getU().setChatOpt(inner.getInt(idx++));
|
||||
|
@ -334,7 +386,12 @@ public class MyFreeCamsClient {
|
|||
|
||||
private void updateModel(SessionState state) {
|
||||
// essential data not yet available
|
||||
if(state.getNm() == null || state.getM() == null || state.getU() == null || state.getU().getCamserv() == null) {
|
||||
if(state.getNm() == null || state.getM() == null || state.getU() == null || state.getU().getCamserv() == null || state.getU().getCamserv() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// tokens not yet available
|
||||
if(ctxenc == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -344,7 +401,7 @@ public class MyFreeCamsClient {
|
|||
model.setUid(state.getUid());
|
||||
models.put(state.getUid(), model);
|
||||
}
|
||||
model.update(state);
|
||||
model.update(state, getStreamUrl(state));
|
||||
}
|
||||
|
||||
private Message parseMessage(StringBuilder msg) throws UnsupportedEncodingException {
|
||||
|
@ -406,7 +463,7 @@ public class MyFreeCamsClient {
|
|||
try {
|
||||
for (SessionState state : sessionStates.values()) {
|
||||
if(Objects.equals(state.getNm(), model.getName())) {
|
||||
model.update(state);
|
||||
model.update(state, getStreamUrl(state));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -415,6 +472,34 @@ public class MyFreeCamsClient {
|
|||
}
|
||||
}
|
||||
|
||||
String getStreamUrl(SessionState state) {
|
||||
Integer camserv = state.getU().getCamserv();
|
||||
if(camserv != null) {
|
||||
int userChannel = 100000000 + state.getUid();
|
||||
String streamUrl = "";
|
||||
String phase = state.getU().getPhase() != null ? state.getU().getPhase() : "z";
|
||||
if(serverConfig.isOnNgServer(state)) {
|
||||
String server = serverConfig.ngVideoServers.get(camserv.toString());
|
||||
streamUrl = "https://" + server + ".myfreecams.com:8444/x-hls/" + cxid + '/' + userChannel + '/' + ctxenc + "/mfc_" + phase + '_' + userChannel + ".m3u8";
|
||||
//LOG.debug("{} {}", state.getNm(), streamUrl);
|
||||
} else if(serverConfig.isOnWzObsVideoServer(state)) {
|
||||
String server = serverConfig.wzobsServers.get(camserv.toString());
|
||||
streamUrl = "https://"+ server + ".myfreecams.com/NxServer/ngrp:mfc_" + phase + '_' + userChannel + ".f4v_mobile/playlist.m3u8";
|
||||
LOG.debug("{} isOnWzObsvideo: {}", state.getNm(), streamUrl);
|
||||
} else if(serverConfig.isOnHtml5VideoServer(state)) {
|
||||
String server = serverConfig.h5Servers.get(camserv.toString());
|
||||
streamUrl = "https://"+ server + ".myfreecams.com/NxServer/ngrp:mfc_" + userChannel + ".f4v_mobile/playlist.m3u8";
|
||||
} else {
|
||||
if(camserv > 500) {
|
||||
camserv -= 500;
|
||||
}
|
||||
streamUrl = "https://video" + camserv + ".myfreecams.com/NxServer/ngrp:mfc_" + userChannel + ".f4v_mobile/playlist.m3u8";
|
||||
}
|
||||
return streamUrl;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public MyFreeCamsModel getModel(int uid) {
|
||||
return models.get(uid);
|
||||
}
|
||||
|
@ -432,4 +517,8 @@ public class MyFreeCamsClient {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ServerConfig getServerConfig() {
|
||||
return serverConfig;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,13 +80,16 @@ public class MyFreeCamsModel extends AbstractModel {
|
|||
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);
|
||||
} else {
|
||||
src.width = Integer.MAX_VALUE;
|
||||
src.height = Integer.MAX_VALUE;
|
||||
}
|
||||
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;
|
||||
|
@ -100,11 +103,15 @@ public class MyFreeCamsModel extends AbstractModel {
|
|||
Request req = new Request.Builder().url(hlsUrl).build();
|
||||
Response response = site.getHttpClient().execute(req);
|
||||
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;
|
||||
if(response.isSuccessful()) {
|
||||
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;
|
||||
} else {
|
||||
throw new IOException(response.code() + " " + response.message());
|
||||
}
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
|
@ -199,9 +206,10 @@ public class MyFreeCamsModel extends AbstractModel {
|
|||
this.state = state;
|
||||
}
|
||||
|
||||
public void update(SessionState state) {
|
||||
public void update(SessionState state, String streamUrl) {
|
||||
setCamScore(state.getM().getCamscore());
|
||||
setState(State.of(state.getVs()));
|
||||
setStreamUrl(streamUrl);
|
||||
|
||||
// preview
|
||||
String uid = state.getUid().toString();
|
||||
|
@ -209,24 +217,6 @@ public class MyFreeCamsModel extends AbstractModel {
|
|||
String previewUrl = "https://img.mfcimg.com/photos2/"+uidStart+'/'+uid+"/avatar.300x300.jpg";
|
||||
setPreview(previewUrl);
|
||||
|
||||
// stream url
|
||||
Integer camserv = state.getU().getCamserv();
|
||||
if(camserv != null) {
|
||||
if(state.getM() != null) {
|
||||
if(state.getM().getFlags() != null) {
|
||||
int flags = state.getM().getFlags();
|
||||
int hd = flags >> 18 & 0x1;
|
||||
if(hd == 1) {
|
||||
String hlsUrl = "http://video" + (camserv - 500) + ".myfreecams.com:1935/NxServer/ngrp:mfc_a_" + (100000000 + state.getUid()) + ".f4v_mobile/playlist.m3u8";
|
||||
setStreamUrl(hlsUrl);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
String hlsUrl = "http://video" + (camserv - 500) + ".myfreecams.com:1935/NxServer/ngrp:mfc_" + (100000000 + state.getUid()) + ".f4v_mobile/playlist.m3u8";
|
||||
setStreamUrl(hlsUrl);
|
||||
}
|
||||
|
||||
// tags
|
||||
Optional.ofNullable(state.getM()).map((m) -> m.getTags()).ifPresent((tags) -> {
|
||||
ArrayList<String> t = new ArrayList<>();
|
||||
|
|
|
@ -37,6 +37,13 @@ public class MyFreeCamsTabProvider extends TabProvider {
|
|||
updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(10)));
|
||||
tabs.add(friends);
|
||||
|
||||
updateService = new HDCamsUpdateService();
|
||||
ThumbOverviewTab hd = new ThumbOverviewTab("HD", updateService, myFreeCams);
|
||||
hd.setRecorder(recorder);
|
||||
updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(10)));
|
||||
tabs.add(hd);
|
||||
|
||||
|
||||
return tabs;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import java.util.ArrayList;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
@ -21,7 +22,7 @@ public class ServerConfig {
|
|||
Map<String, String> h5Servers;
|
||||
Map<String, String> wsServers;
|
||||
Map<String, String> wzobsServers;
|
||||
Map<String, String> ngVideo;
|
||||
Map<String, String> ngVideoServers;
|
||||
|
||||
public ServerConfig(HttpClient client) throws IOException {
|
||||
Request req = new Request.Builder().url("http://www.myfreecams.com/_js/serverconfig.js").build();
|
||||
|
@ -35,7 +36,10 @@ public class ServerConfig {
|
|||
h5Servers = parseMap(serverConfig, "h5video_servers");
|
||||
wsServers = parseMap(serverConfig, "websocket_servers");
|
||||
wzobsServers = parseMap(serverConfig, "wzobs_servers");
|
||||
ngVideo = parseMap(serverConfig, "ngvideo_servers");
|
||||
ngVideoServers = parseMap(serverConfig, "ngvideo_servers");
|
||||
// System.out.println("wz " + wzobsServers);
|
||||
// System.out.println("ng " + ngVideoServers);
|
||||
// System.out.println("h5 " + h5Servers);
|
||||
}
|
||||
|
||||
private static Map<String, String> parseMap(JSONObject serverConfig, String name) {
|
||||
|
@ -56,4 +60,18 @@ public class ServerConfig {
|
|||
return result;
|
||||
}
|
||||
|
||||
public boolean isOnNgServer(SessionState state) {
|
||||
int camserv = Objects.requireNonNull(Objects.requireNonNull(state.getU()).getCamserv());
|
||||
return ngVideoServers.containsKey(Integer.toString(camserv));
|
||||
}
|
||||
|
||||
public boolean isOnWzObsVideoServer(SessionState state) {
|
||||
int camserv = Objects.requireNonNull(Objects.requireNonNull(state.getU()).getCamserv());
|
||||
return wzobsServers.containsKey(Integer.toString(camserv));
|
||||
}
|
||||
|
||||
public boolean isOnHtml5VideoServer(SessionState state) {
|
||||
int camserv = Objects.requireNonNull(Objects.requireNonNull(state.getU()).getCamserv());
|
||||
return h5Servers.containsKey(Integer.toString(camserv)) || (camserv >= 904 && camserv <= 915 || camserv >= 940 && camserv <= 960);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ public class User {
|
|||
private Integer photos;
|
||||
private Integer profile;
|
||||
private String status;
|
||||
private String phase;
|
||||
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
|
||||
|
||||
public Integer getAvatar() {
|
||||
|
@ -132,6 +133,14 @@ public class User {
|
|||
this.additionalProperties.put(name, value);
|
||||
}
|
||||
|
||||
public String getPhase() {
|
||||
return phase;
|
||||
}
|
||||
|
||||
public void setPhase(String phase) {
|
||||
this.phase = phase;
|
||||
}
|
||||
|
||||
public void merge(User u) {
|
||||
if (u == null) {
|
||||
return;
|
||||
|
@ -149,6 +158,7 @@ public class User {
|
|||
photos = u.photos != null ? u.photos : photos;
|
||||
profile = u.profile != null ? u.profile : profile;
|
||||
status = u.status != null ? u.status : status;
|
||||
phase = u.phase != null ? u.phase : phase;
|
||||
additionalProperties.putAll(u.additionalProperties);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import com.squareup.moshi.Types;
|
|||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.Version;
|
||||
import ctbrec.io.HttpClient;
|
||||
import ctbrec.recorder.LocalRecorder;
|
||||
import ctbrec.recorder.Recorder;
|
||||
import ctbrec.recorder.RemoteRecorder;
|
||||
|
@ -51,25 +52,24 @@ public class CamrecApplication extends Application {
|
|||
private SettingsTab settingsTab;
|
||||
private TabPane rootPane = new TabPane();
|
||||
static EventBus bus;
|
||||
private Site site;
|
||||
private List<Site> sites = new ArrayList<>();
|
||||
public static HttpClient httpClient;
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception {
|
||||
Chaturbate ctb = new Chaturbate();
|
||||
sites.add(ctb);
|
||||
site = new MyFreeCams();
|
||||
sites.add(site);
|
||||
sites.add(new Chaturbate());
|
||||
sites.add(new MyFreeCams());
|
||||
loadConfig();
|
||||
createHttpClient();
|
||||
bus = new AsyncEventBus(Executors.newSingleThreadExecutor());
|
||||
hostServices = getHostServices();
|
||||
createRecorder();
|
||||
for (Site site : sites) {
|
||||
site.setRecorder(recorder);
|
||||
site.init();
|
||||
}
|
||||
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
||||
site.login();
|
||||
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
||||
site.login();
|
||||
}
|
||||
}
|
||||
createGui(primaryStage);
|
||||
checkForUpdates();
|
||||
|
@ -92,9 +92,9 @@ public class CamrecApplication extends Application {
|
|||
}
|
||||
((SiteTab)rootPane.getTabs().get(0)).selected();
|
||||
|
||||
RecordedModelsTab modelsTab = new RecordedModelsTab("Recording", recorder, site);
|
||||
RecordedModelsTab modelsTab = new RecordedModelsTab("Recording", recorder, sites);
|
||||
rootPane.getTabs().add(modelsTab);
|
||||
RecordingsTab recordingsTab = new RecordingsTab("Recordings", recorder, config, site);
|
||||
RecordingsTab recordingsTab = new RecordingsTab("Recordings", recorder, config, sites);
|
||||
rootPane.getTabs().add(recordingsTab);
|
||||
settingsTab = new SettingsTab();
|
||||
rootPane.getTabs().add(settingsTab);
|
||||
|
@ -124,7 +124,9 @@ public class CamrecApplication extends Application {
|
|||
public void run() {
|
||||
settingsTab.saveConfig();
|
||||
recorder.shutdown();
|
||||
site.shutdown();
|
||||
for (Site site : sites) {
|
||||
site.shutdown();
|
||||
}
|
||||
try {
|
||||
Config.getInstance().save();
|
||||
LOG.info("Shutdown complete. Goodbye!");
|
||||
|
@ -162,7 +164,7 @@ public class CamrecApplication extends Application {
|
|||
if (config.getSettings().localRecording) {
|
||||
recorder = new LocalRecorder(config);
|
||||
} else {
|
||||
recorder = new RemoteRecorder(config, site.getHttpClient());
|
||||
recorder = new RemoteRecorder(config, httpClient);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,6 +182,15 @@ public class CamrecApplication extends Application {
|
|||
config = Config.getInstance();
|
||||
}
|
||||
|
||||
private void createHttpClient() {
|
||||
httpClient = new HttpClient() {
|
||||
@Override
|
||||
public boolean login() throws IOException {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
|
@ -189,7 +200,7 @@ public class CamrecApplication extends Application {
|
|||
try {
|
||||
String url = "https://api.github.com/repos/0xboobface/ctbrec/releases";
|
||||
Request request = new Request.Builder().url(url).build();
|
||||
Response response = site.getHttpClient().execute(request);
|
||||
Response response = httpClient.execute(request);
|
||||
if (response.isSuccessful()) {
|
||||
Moshi moshi = new Moshi.Builder().build();
|
||||
Type type = Types.newParameterizedType(List.class, Release.class);
|
||||
|
|
|
@ -5,6 +5,7 @@ import java.security.InvalidKeyException;
|
|||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
@ -26,6 +27,7 @@ import javafx.collections.FXCollections;
|
|||
import javafx.collections.ObservableList;
|
||||
import javafx.concurrent.ScheduledService;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Alert;
|
||||
|
@ -59,7 +61,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
|||
|
||||
private ScheduledService<List<Model>> updateService;
|
||||
private Recorder recorder;
|
||||
private Site site;
|
||||
private List<Site> sites;
|
||||
|
||||
FlowPane grid = new FlowPane();
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
|
@ -71,10 +73,10 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
|||
TextField model = new TextField();
|
||||
Button addModelButton = new Button("Record");
|
||||
|
||||
public RecordedModelsTab(String title, Recorder recorder, Site site) {
|
||||
public RecordedModelsTab(String title, Recorder recorder, List<Site> sites) {
|
||||
super(title);
|
||||
this.recorder = recorder;
|
||||
this.site = site;
|
||||
this.sites = sites;
|
||||
createGui();
|
||||
setClosable(false);
|
||||
initializeUpdateService();
|
||||
|
@ -126,18 +128,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
|||
modelLabel.setPadding(new Insets(5, 0, 0, 0));
|
||||
model.setPrefWidth(300);
|
||||
BorderPane.setMargin(addModelBox, new Insets(5));
|
||||
addModelButton.setOnAction((e) -> {
|
||||
Model m = site.createModel(model.getText());
|
||||
try {
|
||||
recorder.startRecording(m);
|
||||
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
|
||||
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
|
||||
alert.setTitle("Error");
|
||||
alert.setHeaderText("Couldn't add model");
|
||||
alert.setContentText("The model " + m.getName() + " could not be added: " + e1.getLocalizedMessage());
|
||||
alert.showAndWait();
|
||||
}
|
||||
});
|
||||
addModelButton.setOnAction((e) -> addModel(e));
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.setPadding(new Insets(5));
|
||||
|
@ -146,6 +137,43 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
|||
setContent(root);
|
||||
}
|
||||
|
||||
private void addModel(ActionEvent e) {
|
||||
String[] parts = model.getText().trim().split(":");
|
||||
if (parts.length != 2) {
|
||||
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
|
||||
alert.setTitle("Wrong format");
|
||||
alert.setHeaderText("Couldn't add model");
|
||||
alert.setContentText("Use something like \"MyFreeCams:ModelName\"");
|
||||
alert.showAndWait();
|
||||
return;
|
||||
}
|
||||
|
||||
String siteName = parts[0];
|
||||
String modelName = parts[1];
|
||||
for (Site site : sites) {
|
||||
if (Objects.equals(siteName.toLowerCase(), site.getClass().getSimpleName().toLowerCase())) {
|
||||
try {
|
||||
Model m = site.createModel(modelName);
|
||||
recorder.startRecording(m);
|
||||
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
|
||||
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
|
||||
alert.setTitle("Error");
|
||||
alert.setHeaderText("Couldn't add model");
|
||||
alert.setContentText("The model " + modelName + " could not be added: " + e1.getLocalizedMessage());
|
||||
alert.showAndWait();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
|
||||
alert.setTitle("Unknown site");
|
||||
alert.setHeaderText("Couldn't add model");
|
||||
alert.setContentText("The site you entered is unknown");
|
||||
alert.showAndWait();
|
||||
};
|
||||
|
||||
|
||||
void initializeUpdateService() {
|
||||
updateService = createUpdateService();
|
||||
updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(2)));
|
||||
|
@ -189,7 +217,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
|||
return new Task<List<Model>>() {
|
||||
@Override
|
||||
public List<Model> call() {
|
||||
LOG.debug("Updating recorded models");
|
||||
LOG.trace("Updating recorded models");
|
||||
return recorder.getModelsRecording();
|
||||
}
|
||||
};
|
||||
|
@ -279,7 +307,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
|||
showStreamSwitchErrorDialog(t);
|
||||
return null;
|
||||
};
|
||||
StreamSourceSelectionDialog.show(fxModel.getDelegate(), site.getHttpClient(), onSuccess, onFail);
|
||||
StreamSourceSelectionDialog.show(fxModel.getDelegate(), onSuccess, onFail);
|
||||
}
|
||||
|
||||
private void showStreamSwitchErrorDialog(Throwable throwable) {
|
||||
|
|
|
@ -28,7 +28,6 @@ import com.iheartradio.m3u8.ParseException;
|
|||
import com.iheartradio.m3u8.PlaylistException;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.Recording;
|
||||
import ctbrec.Recording.STATUS;
|
||||
import ctbrec.recorder.Recorder;
|
||||
|
@ -66,7 +65,8 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
|||
private ScheduledService<List<JavaFxRecording>> updateService;
|
||||
private Config config;
|
||||
private Recorder recorder;
|
||||
private Site site;
|
||||
@SuppressWarnings("unused")
|
||||
private List<Site> sites;
|
||||
|
||||
FlowPane grid = new FlowPane();
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
|
@ -74,11 +74,11 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
|||
ObservableList<JavaFxRecording> observableRecordings = FXCollections.observableArrayList();
|
||||
ContextMenu popup;
|
||||
|
||||
public RecordingsTab(String title, Recorder recorder, Config config, Site site) {
|
||||
public RecordingsTab(String title, Recorder recorder, Config config, List<Site> sites) {
|
||||
super(title);
|
||||
this.recorder = recorder;
|
||||
this.config = config;
|
||||
this.site = site;
|
||||
this.sites = sites;
|
||||
createGui();
|
||||
setClosable(false);
|
||||
initializeUpdateService();
|
||||
|
@ -245,18 +245,19 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
|||
contextMenu.getItems().add(openInPlayer);
|
||||
}
|
||||
|
||||
MenuItem stopRecording = new MenuItem("Stop recording");
|
||||
stopRecording.setOnAction((e) -> {
|
||||
Model m = site.createModel(recording.getModelName());
|
||||
try {
|
||||
recorder.stopRecording(m);
|
||||
} catch (Exception e1) {
|
||||
showErrorDialog("Stop recording", "Couldn't stop recording of model " + m.getName(), e1);
|
||||
}
|
||||
});
|
||||
if(recording.getStatus() == STATUS.RECORDING) {
|
||||
contextMenu.getItems().add(stopRecording);
|
||||
}
|
||||
// TODO find a way to reenable this
|
||||
// MenuItem stopRecording = new MenuItem("Stop recording");
|
||||
// stopRecording.setOnAction((e) -> {
|
||||
// Model m = site.createModel(recording.getModelName());
|
||||
// try {
|
||||
// recorder.stopRecording(m);
|
||||
// } catch (Exception e1) {
|
||||
// showErrorDialog("Stop recording", "Couldn't stop recording of model " + m.getName(), e1);
|
||||
// }
|
||||
// });
|
||||
// if(recording.getStatus() == STATUS.RECORDING) {
|
||||
// contextMenu.getItems().add(stopRecording);
|
||||
// }
|
||||
|
||||
MenuItem deleteRecording = new MenuItem("Delete");
|
||||
deleteRecording.setOnAction((e) -> {
|
||||
|
@ -304,7 +305,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
|||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
MergedHlsDownload download = new MergedHlsDownload(site.getHttpClient());
|
||||
MergedHlsDownload download = new MergedHlsDownload(CamrecApplication.httpClient);
|
||||
download.start(url.toString(), target, (progress) -> {
|
||||
Platform.runLater(() -> {
|
||||
if (progress == 100) {
|
||||
|
|
|
@ -6,13 +6,12 @@ import java.util.concurrent.ExecutionException;
|
|||
import java.util.function.Function;
|
||||
|
||||
import ctbrec.Model;
|
||||
import ctbrec.io.HttpClient;
|
||||
import ctbrec.recorder.download.StreamSource;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.scene.control.ChoiceDialog;
|
||||
|
||||
public class StreamSourceSelectionDialog {
|
||||
public static void show(Model model, HttpClient client, Function<Model,Void> onSuccess, Function<Throwable, Void> onFail) {
|
||||
public static void show(Model model, Function<Model,Void> onSuccess, Function<Throwable, Void> onFail) {
|
||||
Task<List<StreamSource>> selectStreamSource = new Task<List<StreamSource>>() {
|
||||
@Override
|
||||
protected List<StreamSource> call() throws Exception {
|
||||
|
|
|
@ -15,7 +15,6 @@ import com.iheartradio.m3u8.PlaylistException;
|
|||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.io.HttpClient;
|
||||
import ctbrec.recorder.Recorder;
|
||||
import ctbrec.recorder.download.StreamSource;
|
||||
import javafx.animation.FadeTransition;
|
||||
|
@ -72,17 +71,14 @@ public class ThumbCell extends StackPane {
|
|||
private SimpleBooleanProperty selectionProperty = new SimpleBooleanProperty(false);
|
||||
private double imgAspectRatio = 3.0 / 4.0;
|
||||
|
||||
private HttpClient client;
|
||||
|
||||
private ObservableList<Node> thumbCellList;
|
||||
private boolean mouseHovering = false;
|
||||
private boolean recording = false;
|
||||
|
||||
public ThumbCell(ThumbOverviewTab parent, Model model, Recorder recorder, HttpClient client) {
|
||||
public ThumbCell(ThumbOverviewTab parent, Model model, Recorder recorder) {
|
||||
this.thumbCellList = parent.grid.getChildren();
|
||||
this.model = model;
|
||||
this.recorder = recorder;
|
||||
this.client = client;
|
||||
recording = recorder.isRecording(model);
|
||||
|
||||
iv = new ImageView();
|
||||
|
@ -208,7 +204,7 @@ public class ThumbCell extends StackPane {
|
|||
// when we first requested the stream info, so we remove this invalid value from the "cache"
|
||||
// so that it is requested again
|
||||
if (model.isOnline() && resolution[1] == 0) {
|
||||
LOG.debug("Removing invalid resolution value for {}", model.getName());
|
||||
LOG.trace("Removing invalid resolution value for {}", model.getName());
|
||||
model.invalidateCacheEntries();
|
||||
}
|
||||
} catch (ExecutionException | IOException | InterruptedException e1) {
|
||||
|
@ -227,7 +223,7 @@ public class ThumbCell extends StackPane {
|
|||
LOG.trace("Model resolution {} {}x{}", model.getName(), resolution[0], resolution[1]);
|
||||
LOG.trace("Resolution queue size: {}", ThumbOverviewTab.queue.size());
|
||||
final int w = resolution[1];
|
||||
_res = w > 0 ? Integer.toString(w) : state;
|
||||
_res = w > 0 ? w != Integer.MAX_VALUE ? Integer.toString(w) : "HD" : state;
|
||||
} else {
|
||||
_res = model.getOnlineState(false);
|
||||
resolutionBackgroundColor = resolutionOfflineColor;
|
||||
|
@ -336,7 +332,7 @@ public class ThumbCell extends StackPane {
|
|||
alert.showAndWait();
|
||||
return null;
|
||||
};
|
||||
StreamSourceSelectionDialog.show(model, client, onSuccess, onFail);
|
||||
StreamSourceSelectionDialog.show(model, onSuccess, onFail);
|
||||
} else {
|
||||
_startStopAction(model, start);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ import com.sun.javafx.collections.ObservableListWrapper;
|
|||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.io.HttpClient;
|
||||
import ctbrec.recorder.Recorder;
|
||||
import ctbrec.sites.Site;
|
||||
import ctbrec.sites.mfc.MyFreeCamsClient;
|
||||
|
@ -262,7 +261,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
|||
}
|
||||
}
|
||||
if(!found) {
|
||||
ThumbCell newCell = createThumbCell(this, model, recorder, site.getHttpClient());
|
||||
ThumbCell newCell = createThumbCell(this, model, recorder);
|
||||
newCell.setIndex(index);
|
||||
positionChangedOrNew.add(newCell);
|
||||
}
|
||||
|
@ -285,8 +284,8 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
|||
}
|
||||
}
|
||||
|
||||
ThumbCell createThumbCell(ThumbOverviewTab thumbOverviewTab, Model model, Recorder recorder, HttpClient client2) {
|
||||
ThumbCell newCell = new ThumbCell(this, model, recorder, site.getHttpClient());
|
||||
ThumbCell createThumbCell(ThumbOverviewTab thumbOverviewTab, Model model, Recorder recorder) {
|
||||
ThumbCell newCell = new ThumbCell(this, model, recorder);
|
||||
newCell.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> {
|
||||
suspendUpdates(true);
|
||||
popup = createContextMenu(newCell);
|
||||
|
|
Loading…
Reference in New Issue