From f4842fcf5178877f1ab031558c376c2546355af9 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 23 Oct 2018 14:02:20 +0200 Subject: [PATCH] Improve MyFreeCamsClient - Add watch dog to reestablish a broken connection - Implemented creation of stream urls properly - Add HD tab to display MFC HD streams --- .../sites/mfc/FriendsUpdateService.java | 3 +- .../ctbrec/sites/mfc/HDCamsUpdateService.java | 40 ++++++ .../java/ctbrec/sites/mfc/MessageTypes.java | 5 +- .../java/ctbrec/sites/mfc/MyFreeCams.java | 4 + .../ctbrec/sites/mfc/MyFreeCamsClient.java | 135 +++++++++++++++--- .../ctbrec/sites/mfc/MyFreeCamsModel.java | 50 +++---- .../sites/mfc/MyFreeCamsTabProvider.java | 7 + .../java/ctbrec/sites/mfc/ServerConfig.java | 22 ++- src/main/java/ctbrec/sites/mfc/User.java | 10 ++ .../java/ctbrec/ui/CamrecApplication.java | 37 +++-- .../java/ctbrec/ui/RecordedModelsTab.java | 62 +++++--- src/main/java/ctbrec/ui/RecordingsTab.java | 35 ++--- .../ui/StreamSourceSelectionDialog.java | 3 +- src/main/java/ctbrec/ui/ThumbCell.java | 12 +- src/main/java/ctbrec/ui/ThumbOverviewTab.java | 7 +- 15 files changed, 313 insertions(+), 119 deletions(-) create mode 100644 src/main/java/ctbrec/sites/mfc/HDCamsUpdateService.java diff --git a/src/main/java/ctbrec/sites/mfc/FriendsUpdateService.java b/src/main/java/ctbrec/sites/mfc/FriendsUpdateService.java index 34661797..53e01b91 100644 --- a/src/main/java/ctbrec/sites/mfc/FriendsUpdateService.java +++ b/src/main/java/ctbrec/sites/mfc/FriendsUpdateService.java @@ -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); } diff --git a/src/main/java/ctbrec/sites/mfc/HDCamsUpdateService.java b/src/main/java/ctbrec/sites/mfc/HDCamsUpdateService.java new file mode 100644 index 00000000..bec7ceaa --- /dev/null +++ b/src/main/java/ctbrec/sites/mfc/HDCamsUpdateService.java @@ -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> 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 -> 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()); + } + }; + } + +} diff --git a/src/main/java/ctbrec/sites/mfc/MessageTypes.java b/src/main/java/ctbrec/sites/mfc/MessageTypes.java index 34d85fec..648507ea 100644 --- a/src/main/java/ctbrec/sites/mfc/MessageTypes.java +++ b/src/main/java/ctbrec/sites/mfc/MessageTypes.java @@ -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; diff --git a/src/main/java/ctbrec/sites/mfc/MyFreeCams.java b/src/main/java/ctbrec/sites/mfc/MyFreeCams.java index b2cdb332..2575ca79 100644 --- a/src/main/java/ctbrec/sites/mfc/MyFreeCams.java +++ b/src/main/java/ctbrec/sites/mfc/MyFreeCams.java @@ -112,4 +112,8 @@ public class MyFreeCams implements Site { public boolean isSiteForModel(Model m) { return m instanceof MyFreeCamsModel; } + + public MyFreeCamsClient getClient() { + return client; + } } diff --git a/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java b/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java index 3ad60ca9..1051b50a 100644 --- a/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java +++ b/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java @@ -44,6 +44,12 @@ public class MyFreeCamsClient { private Map 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 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); + + 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 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 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; + } } diff --git a/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java b/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java index f037e349..f25b2db1 100644 --- a/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java +++ b/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java @@ -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 t = new ArrayList<>(); diff --git a/src/main/java/ctbrec/sites/mfc/MyFreeCamsTabProvider.java b/src/main/java/ctbrec/sites/mfc/MyFreeCamsTabProvider.java index e81cd019..336486c9 100644 --- a/src/main/java/ctbrec/sites/mfc/MyFreeCamsTabProvider.java +++ b/src/main/java/ctbrec/sites/mfc/MyFreeCamsTabProvider.java @@ -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; } } diff --git a/src/main/java/ctbrec/sites/mfc/ServerConfig.java b/src/main/java/ctbrec/sites/mfc/ServerConfig.java index 973b4767..6713212c 100644 --- a/src/main/java/ctbrec/sites/mfc/ServerConfig.java +++ b/src/main/java/ctbrec/sites/mfc/ServerConfig.java @@ -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 h5Servers; Map wsServers; Map wzobsServers; - Map ngVideo; + Map 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 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); + } } diff --git a/src/main/java/ctbrec/sites/mfc/User.java b/src/main/java/ctbrec/sites/mfc/User.java index d1f78dc2..fe42a187 100644 --- a/src/main/java/ctbrec/sites/mfc/User.java +++ b/src/main/java/ctbrec/sites/mfc/User.java @@ -18,6 +18,7 @@ public class User { private Integer photos; private Integer profile; private String status; + private String phase; private Map additionalProperties = new HashMap(); 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); } } diff --git a/src/main/java/ctbrec/ui/CamrecApplication.java b/src/main/java/ctbrec/ui/CamrecApplication.java index 127fc697..e142a901 100644 --- a/src/main/java/ctbrec/ui/CamrecApplication.java +++ b/src/main/java/ctbrec/ui/CamrecApplication.java @@ -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 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); diff --git a/src/main/java/ctbrec/ui/RecordedModelsTab.java b/src/main/java/ctbrec/ui/RecordedModelsTab.java index ff5d9e07..08d32f60 100644 --- a/src/main/java/ctbrec/ui/RecordedModelsTab.java +++ b/src/main/java/ctbrec/ui/RecordedModelsTab.java @@ -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> updateService; private Recorder recorder; - private Site site; + private List 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 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>() { @Override public List 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) { diff --git a/src/main/java/ctbrec/ui/RecordingsTab.java b/src/main/java/ctbrec/ui/RecordingsTab.java index 4f4dfb54..fc3acd64 100644 --- a/src/main/java/ctbrec/ui/RecordingsTab.java +++ b/src/main/java/ctbrec/ui/RecordingsTab.java @@ -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> updateService; private Config config; private Recorder recorder; - private Site site; + @SuppressWarnings("unused") + private List sites; FlowPane grid = new FlowPane(); ScrollPane scrollPane = new ScrollPane(); @@ -74,11 +74,11 @@ public class RecordingsTab extends Tab implements TabSelectionListener { ObservableList observableRecordings = FXCollections.observableArrayList(); ContextMenu popup; - public RecordingsTab(String title, Recorder recorder, Config config, Site site) { + public RecordingsTab(String title, Recorder recorder, Config config, List 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) { diff --git a/src/main/java/ctbrec/ui/StreamSourceSelectionDialog.java b/src/main/java/ctbrec/ui/StreamSourceSelectionDialog.java index c929a2c8..76a90059 100644 --- a/src/main/java/ctbrec/ui/StreamSourceSelectionDialog.java +++ b/src/main/java/ctbrec/ui/StreamSourceSelectionDialog.java @@ -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 onSuccess, Function onFail) { + public static void show(Model model, Function onSuccess, Function onFail) { Task> selectStreamSource = new Task>() { @Override protected List call() throws Exception { diff --git a/src/main/java/ctbrec/ui/ThumbCell.java b/src/main/java/ctbrec/ui/ThumbCell.java index 3df795e5..5f2d1d27 100644 --- a/src/main/java/ctbrec/ui/ThumbCell.java +++ b/src/main/java/ctbrec/ui/ThumbCell.java @@ -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 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); } diff --git a/src/main/java/ctbrec/ui/ThumbOverviewTab.java b/src/main/java/ctbrec/ui/ThumbOverviewTab.java index 7b071297..c658a9ed 100644 --- a/src/main/java/ctbrec/ui/ThumbOverviewTab.java +++ b/src/main/java/ctbrec/ui/ThumbOverviewTab.java @@ -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);