From da87a1ae3925bdafc01ec35923d5ef2b0f878664 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 3 Dec 2018 14:35:14 +0100 Subject: [PATCH 01/14] Add log message which websocket server is used --- common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java | 1 + 1 file changed, 1 insertion(+) diff --git a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java index 6f167b77..b7b20df3 100644 --- a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java +++ b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java @@ -89,6 +89,7 @@ public class MyFreeCamsClient { List websocketServers = new ArrayList(serverConfig.wsServers.keySet()); String server = websocketServers.get((int) (Math.random()*websocketServers.size())); String wsUrl = "ws://" + server + ".myfreecams.com:8080/fcsl"; + LOG.debug("Connecting to random websocket server {}", wsUrl); Thread watchDog = new Thread(() -> { while(running) { From b97449a980c4a6637091c8d8c0c5da93dec2acd3 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 3 Dec 2018 15:24:44 +0100 Subject: [PATCH 02/14] Filter out websocket servers with the wrong protocol --- .../main/java/ctbrec/sites/mfc/MyFreeCamsClient.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java index b7b20df3..dc1d6b3a 100644 --- a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java +++ b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import java.util.concurrent.ExecutorService; @@ -86,8 +87,13 @@ public class MyFreeCamsClient { public void start() throws IOException { running = true; serverConfig = new ServerConfig(mfc); - List websocketServers = new ArrayList(serverConfig.wsServers.keySet()); - String server = websocketServers.get((int) (Math.random()*websocketServers.size())); + List websocketServers = new ArrayList(serverConfig.wsServers.size()); + for (Entry entry : serverConfig.wsServers.entrySet()) { + if (entry.getValue().equals("rfc6455")) { + websocketServers.add(entry.getKey()); + } + } + String server = websocketServers.get((int) (Math.random() * websocketServers.size() - 1)); String wsUrl = "ws://" + server + ".myfreecams.com:8080/fcsl"; LOG.debug("Connecting to random websocket server {}", wsUrl); From cd903566de191b79bc317585cbdf80944b4c68be Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 3 Dec 2018 16:26:37 +0100 Subject: [PATCH 03/14] Use baseUrl in requestExtData --- common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java index dc1d6b3a..ba137ef4 100644 --- a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java +++ b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java @@ -314,7 +314,7 @@ public class MyFreeCamsClient { 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 base = mfc.getBaseUrl() + "/php/FcwExtResp.php"; String url = base + "?respkey="+respkey+"&opts="+opts+"&serv="+serv+"&type="+type; Request req = new Request.Builder().url(url).build(); LOG.trace("Requesting EXTDATA {}", url); From 1f6e03979edb9e6c3d55bbaed95c36fd40527a3a Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 4 Dec 2018 17:08:44 +0100 Subject: [PATCH 04/14] Fix: ThumbCell resumes recordings This happens because the update services don't set the suspended property and ThumbCell copied the property from the updated model. So, suspended would be set to false, which would cause an update of the property change listener and that would restart the recording. --- client/src/main/java/ctbrec/ui/ThumbCell.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/main/java/ctbrec/ui/ThumbCell.java b/client/src/main/java/ctbrec/ui/ThumbCell.java index ece9efce..8c5f9e05 100644 --- a/client/src/main/java/ctbrec/ui/ThumbCell.java +++ b/client/src/main/java/ctbrec/ui/ThumbCell.java @@ -492,7 +492,7 @@ public class ThumbCell extends StackPane { this.model.setPreview(model.getPreview()); this.model.setTags(model.getTags()); this.model.setUrl(model.getUrl()); - this.model.setSuspended(model.isSuspended()); + this.model.setSuspended(recorder.isSuspended(model)); update(); } From 9791427aeb9ab00bc0314482c2a7198b239abd7a Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 4 Dec 2018 17:09:19 +0100 Subject: [PATCH 05/14] Add "Follow" to the context menu of the recorded models tab --- .../java/ctbrec/ui/RecordedModelsTab.java | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/client/src/main/java/ctbrec/ui/RecordedModelsTab.java b/client/src/main/java/ctbrec/ui/RecordedModelsTab.java index f636b424..1d815ed6 100644 --- a/client/src/main/java/ctbrec/ui/RecordedModelsTab.java +++ b/client/src/main/java/ctbrec/ui/RecordedModelsTab.java @@ -3,6 +3,7 @@ package ctbrec.ui; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -296,13 +297,13 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { massEdit(models, action); } - private void massEdit(List models, Consumer action) { - getTabPane().setCursor(Cursor.WAIT); + private void massEdit(List models, Consumer action) { + table.setCursor(Cursor.WAIT); threadPool.submit(() -> { for (Model model : models) { action.accept(model); } - Platform.runLater(() -> getTabPane().setCursor(Cursor.DEFAULT)); + Platform.runLater(() -> table.setCursor(Cursor.DEFAULT)); }); } @@ -443,6 +444,8 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { openInPlayer.setOnAction((e) -> openInPlayer(selectedModels.get(0))); MenuItem switchStreamSource = new MenuItem("Switch resolution"); switchStreamSource.setOnAction((e) -> switchStreamSource(selectedModels.get(0))); + MenuItem follow = new MenuItem("Follow"); + follow.setOnAction((e) -> follow(selectedModels)); ContextMenu menu = new ContextMenu(stop); if (selectedModels.size() == 1) { @@ -450,7 +453,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { } else { menu.getItems().addAll(resumeRecording, pauseRecording); } - menu.getItems().addAll(copyUrl, openInPlayer, openInBrowser, switchStreamSource); + menu.getItems().addAll(copyUrl, openInPlayer, openInBrowser, switchStreamSource, follow); if (selectedModels.size() > 1) { copyUrl.setDisable(true); @@ -462,6 +465,19 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { return menu; } + private void follow(ObservableList selectedModels) { + Consumer action = (m) -> { + try { + m.follow(); + } catch(Throwable e) { + LOG.error("Couldn't follow model {}", m, e); + Platform.runLater(() -> + showErrorDialog(e, "Couldn't follow model", "Following " + m.getName() + " failed: " + e.getMessage())); + } + }; + massEdit(new ArrayList(selectedModels), action); + } + private void openInPlayer(JavaFxModel selectedModel) { table.setCursor(Cursor.WAIT); new Thread(() -> { @@ -533,8 +549,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { showErrorDialog(e, "Couldn't stop recording", "Stopping recording of " + m.getName() + " failed")); } }; - List models = selectedModels.stream().map(jfxm -> jfxm.getDelegate()).collect(Collectors.toList()); - massEdit(models, action); + massEdit(new ArrayList(selectedModels), action); }; private void pauseRecording(List selectedModels) { @@ -547,8 +562,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { showErrorDialog(e, "Couldn't pause recording of model", "Pausing recording of " + m.getName() + " failed")); } }; - List models = selectedModels.stream().map(jfxm -> jfxm.getDelegate()).collect(Collectors.toList()); - massEdit(models, action); + massEdit(new ArrayList(selectedModels), action); }; private void resumeRecording(List selectedModels) { @@ -561,8 +575,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { showErrorDialog(e, "Couldn't resume recording of model", "Resuming recording of " + m.getName() + " failed")); } }; - List models = selectedModels.stream().map(jfxm -> jfxm.getDelegate()).collect(Collectors.toList()); - massEdit(models, action); + massEdit(new ArrayList(selectedModels), action); } public void saveState() { From 45e493a35a37be53f163ab4cdfffc84221daf922 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 4 Dec 2018 18:28:30 +0100 Subject: [PATCH 06/14] Add javadoc --- common/src/main/java/ctbrec/Model.java | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/common/src/main/java/ctbrec/Model.java b/common/src/main/java/ctbrec/Model.java index e13f2fcd..895f711e 100644 --- a/common/src/main/java/ctbrec/Model.java +++ b/common/src/main/java/ctbrec/Model.java @@ -14,33 +14,71 @@ import ctbrec.sites.Site; public interface Model { public String getUrl(); + public void setUrl(String url); + public String getDisplayName(); + public void setDisplayName(String name); + public String getName(); + public void setName(String name); + public String getPreview(); + public void setPreview(String preview); + public List getTags(); + public void setTags(List tags); + public String getDescription(); + public void setDescription(String description); + public int getStreamUrlIndex(); + public void setStreamUrlIndex(int streamUrlIndex); + public boolean isOnline() throws IOException, ExecutionException, InterruptedException; + public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException; + public String getOnlineState(boolean failFast) throws IOException, ExecutionException; + public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException; + public void invalidateCacheEntries(); + public void receiveTip(int tokens) throws IOException; + + /** + * Determines the stream resolution for this model + * + * @param failFast + * If set to true, the method returns emmediately, even if the resolution is unknown. If + * the resolution is unknown, the array contains 0,0 + * + * @return a tupel of width and height represented by an int[2] + * @throws ExecutionException + */ public int[] getStreamResolution(boolean failFast) throws ExecutionException; + public boolean follow() throws IOException; + public boolean unfollow() throws IOException; + public void setSite(Site site); + public Site getSite(); + public void writeSiteSpecificData(JsonWriter writer) throws IOException; + public void readSiteSpecificData(JsonReader reader) throws IOException; + public boolean isSuspended(); + public void setSuspended(boolean suspended); } \ No newline at end of file From b99e88d2c8c4aa8057faeb44e0d5228a8d93a1a2 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 4 Dec 2018 18:30:46 +0100 Subject: [PATCH 07/14] Add cache for the resolution This makes the display of the resolution much faster and the information is retained, even if the ThumbCell is "destroyed" --- client/src/main/java/ctbrec/ui/ThumbCell.java | 69 ++++++++++++------- 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/client/src/main/java/ctbrec/ui/ThumbCell.java b/client/src/main/java/ctbrec/ui/ThumbCell.java index 8c5f9e05..5e9bdb16 100644 --- a/client/src/main/java/ctbrec/ui/ThumbCell.java +++ b/client/src/main/java/ctbrec/ui/ThumbCell.java @@ -7,11 +7,14 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.function.Function; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import com.iheartradio.m3u8.ParseException; import ctbrec.Config; @@ -80,6 +83,10 @@ public class ThumbCell extends StackPane { private boolean mouseHovering = false; private boolean recording = false; private static ExecutorService imageLoadingThreadPool = Executors.newFixedThreadPool(30); + private static Cache resolutionCache = CacheBuilder.newBuilder() + .expireAfterAccess(4, TimeUnit.HOURS) + .maximumSize(1000) + .build(); public ThumbCell(ThumbOverviewTab parent, Model model, Recorder recorder) { this.thumbCellList = parent.grid.getChildren(); @@ -212,35 +219,45 @@ public class ThumbCell extends StackPane { return; } - ThumbOverviewTab.threadPool.submit(() -> { + int[] resolution = resolutionCache.getIfPresent(model); + if(resolution != null) { try { - ThumbOverviewTab.resolutionProcessing.add(model); - int[] resolution = model.getStreamResolution(false); updateResolutionTag(resolution); - - // 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 - if (model.isOnline() && resolution[1] == 0) { - LOG.trace("Removing invalid resolution value for {}", model.getName()); - model.invalidateCacheEntries(); - } - - Thread.sleep(500); - } catch (IOException | InterruptedException e1) { - LOG.warn("Couldn't update resolution tag for model {}", model.getName(), e1); - } catch(ExecutionException e) { - if(e.getCause() instanceof EOFException) { - LOG.warn("Couldn't update resolution tag for model {}. Playlist empty", model.getName()); - } else if(e.getCause() instanceof ParseException) { - LOG.warn("Couldn't update resolution tag for model {} - {}", model.getName(), e.getMessage()); - } else { - LOG.warn("Couldn't update resolution tag for model {}", model.getName(), e); - } - } finally { - ThumbOverviewTab.resolutionProcessing.remove(model); + } catch(Exception e) { + LOG.warn("Couldn't update resolution tag for model {}", model.getName(), e); } - }); + } else { + ThumbOverviewTab.threadPool.submit(() -> { + try { + ThumbOverviewTab.resolutionProcessing.add(model); + int[] _resolution = model.getStreamResolution(false); + resolutionCache.put(model, _resolution); + updateResolutionTag(_resolution); + + // 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 + if (model.isOnline() && _resolution[1] == 0) { + LOG.trace("Removing invalid resolution value for {}", model.getName()); + model.invalidateCacheEntries(); + } + + Thread.sleep(100); + } catch (IOException | InterruptedException e1) { + LOG.warn("Couldn't update resolution tag for model {}", model.getName(), e1); + } catch(ExecutionException e) { + if(e.getCause() instanceof EOFException) { + LOG.warn("Couldn't update resolution tag for model {}. Playlist empty", model.getName()); + } else if(e.getCause() instanceof ParseException) { + LOG.warn("Couldn't update resolution tag for model {} - {}", model.getName(), e.getMessage()); + } else { + LOG.warn("Couldn't update resolution tag for model {}", model.getName(), e); + } + } finally { + ThumbOverviewTab.resolutionProcessing.remove(model); + } + }); + } } private void updateResolutionTag(int[] resolution) throws IOException, ExecutionException, InterruptedException { From d4dadf9fea891806dc0b7b9dc46c5e506c82492a Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 4 Dec 2018 18:31:31 +0100 Subject: [PATCH 08/14] Remove resolution cache Resolution caching is done globally in ThumbCell --- .../ctbrec/sites/chaturbate/Chaturbate.java | 18 ++--------------- .../sites/chaturbate/ChaturbateModel.java | 20 +++++++++---------- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/common/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java b/common/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java index bd96ad96..56dffb1b 100644 --- a/common/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java +++ b/common/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java @@ -189,17 +189,6 @@ public class Chaturbate extends AbstractSite { } }); - LoadingCache streamResolutionCache = CacheBuilder.newBuilder() - .initialCapacity(10_000) - .maximumSize(10_000) - .expireAfterWrite(5, TimeUnit.MINUTES) - .build(new CacheLoader () { - @Override - public int[] load(String model) throws Exception { - return loadResolution(model); - } - }); - public void sendTip(String name, int tokens) throws IOException { if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) { RequestBody body = new FormBody.Builder() @@ -264,11 +253,9 @@ public class Chaturbate extends AbstractSite { } } - public int[] getResolution(String modelName) throws ExecutionException { - return streamResolutionCache.get(modelName); - } + public int[] getResolution(String modelName) throws ExecutionException, IOException, ParseException, PlaylistException, InterruptedException { + throttleRequests(); - private int[] loadResolution(String modelName) throws IOException, ParseException, PlaylistException, ExecutionException, InterruptedException { int[] res = new int[2]; StreamInfo streamInfo = getStreamInfo(modelName); if(!streamInfo.url.startsWith("http")) { @@ -303,7 +290,6 @@ public class Chaturbate extends AbstractSite { throw ex; } - streamResolutionCache.put(modelName, res); return res; } diff --git a/common/src/main/java/ctbrec/sites/chaturbate/ChaturbateModel.java b/common/src/main/java/ctbrec/sites/chaturbate/ChaturbateModel.java index 5ca806c1..5a6acce3 100644 --- a/common/src/main/java/ctbrec/sites/chaturbate/ChaturbateModel.java +++ b/common/src/main/java/ctbrec/sites/chaturbate/ChaturbateModel.java @@ -25,6 +25,7 @@ import okhttp3.Response; public class ChaturbateModel extends AbstractModel { private static final transient Logger LOG = LoggerFactory.getLogger(ChaturbateModel.class); + private int[] resolution = new int[2]; /** * This constructor exists only for deserialization. Please don't call it directly @@ -52,16 +53,16 @@ public class ChaturbateModel extends AbstractModel { @Override public int[] getStreamResolution(boolean failFast) throws ExecutionException { - int[] resolution = getChaturbate().streamResolutionCache.getIfPresent(getName()); - if(resolution != null) { - return getChaturbate().getResolution(getName()); - } else { - if(failFast) { - return new int[2]; - } else { - return getChaturbate().getResolution(getName()); - } + if(failFast) { + return resolution; } + + try { + resolution = getChaturbate().getResolution(getName()); + } catch(Exception e) { + throw new ExecutionException(e); + } + return resolution; } /** @@ -71,7 +72,6 @@ public class ChaturbateModel extends AbstractModel { @Override public void invalidateCacheEntries() { getChaturbate().streamInfoCache.invalidate(getName()); - getChaturbate().streamResolutionCache.invalidate(getName()); } public String getOnlineState() throws IOException, ExecutionException { From 8abb3db8a53ebbdd7d3ebacc3ae7dddc09edb420 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Wed, 5 Dec 2018 12:10:21 +0100 Subject: [PATCH 09/14] Remove single thread executor --- .../ctbrec/sites/mfc/MyFreeCamsClient.java | 7 ----- .../ctbrec/sites/mfc/MyFreeCamsModel.java | 31 +++++++------------ 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java index ba137ef4..c9317abf 100644 --- a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java +++ b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java @@ -13,8 +13,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; -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; @@ -53,7 +51,6 @@ public class MyFreeCamsClient { private Cache sessionStates = CacheBuilder.newBuilder().maximumSize(4000).build(); private Cache models = CacheBuilder.newBuilder().maximumSize(4000).build(); private Lock lock = new ReentrantLock(); - private ExecutorService executor = Executors.newSingleThreadExecutor(); private ServerConfig serverConfig; @SuppressWarnings("unused") private String tkx; @@ -574,10 +571,6 @@ public class MyFreeCamsClient { return models.getIfPresent(uid); } - public void execute(Runnable r) { - executor.execute(r); - } - public void getSessionState(ctbrec.Model model) { for (SessionState state : sessionStates.asMap().values()) { if(Objects.equals(state.getNm(), model.getName())) { diff --git a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java index d79afa01..a85c3425 100644 --- a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java +++ b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java @@ -45,7 +45,7 @@ public class MyFreeCamsModel extends AbstractModel { private double camScore; private int viewerCount; private State state; - private int resolution[]; + private int resolution[] = new int[2]; /** * This constructor exists only for deserialization. Please don't call it directly @@ -174,26 +174,19 @@ public class MyFreeCamsModel extends AbstractModel { @Override public int[] getStreamResolution(boolean failFast) throws ExecutionException { - if(resolution == null) { - if(failFast || hlsUrl == null) { - return new int[2]; + if (!failFast && hlsUrl != null) { + try { + List streamSources = getStreamSources(); + Collections.sort(streamSources); + StreamSource best = streamSources.get(streamSources.size() - 1); + resolution = new int[] { best.width, best.height }; + } catch (ParseException | PlaylistException e) { + LOG.warn("Couldn't determine stream resolution - {}", e.getMessage()); + } catch (ExecutionException | IOException e) { + LOG.error("Couldn't determine stream resolution", e); } - MyFreeCamsClient.getInstance().execute(()->{ - try { - List streamSources = getStreamSources(); - Collections.sort(streamSources); - StreamSource best = streamSources.get(streamSources.size()-1); - resolution = new int[] {best.width, best.height}; - } catch (ParseException | PlaylistException e) { - LOG.warn("Couldn't determine stream resolution - {}", e.getMessage()); - } catch (ExecutionException | IOException e) { - LOG.error("Couldn't determine stream resolution", e); - } - }); - return new int[2]; - } else { - return resolution; } + return resolution; } public void setStreamUrl(String hlsUrl) { From 6db00969d7c30164b840de27cc0e098aafd2fc9d Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Wed, 5 Dec 2018 12:20:26 +0100 Subject: [PATCH 10/14] Revert: RemoteRecorder does not work, if called with JavaFxModels It does not work, because it uses the class name for the type and the server doesn't know JavaFxModel. It only knowns the unwrapped model classes. --- client/src/main/java/ctbrec/ui/RecordedModelsTab.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/client/src/main/java/ctbrec/ui/RecordedModelsTab.java b/client/src/main/java/ctbrec/ui/RecordedModelsTab.java index 1d815ed6..878c8d00 100644 --- a/client/src/main/java/ctbrec/ui/RecordedModelsTab.java +++ b/client/src/main/java/ctbrec/ui/RecordedModelsTab.java @@ -549,7 +549,8 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { showErrorDialog(e, "Couldn't stop recording", "Stopping recording of " + m.getName() + " failed")); } }; - massEdit(new ArrayList(selectedModels), action); + List models = selectedModels.stream().map(jfxm -> jfxm.getDelegate()).collect(Collectors.toList()); + massEdit(models, action); }; private void pauseRecording(List selectedModels) { @@ -562,7 +563,8 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { showErrorDialog(e, "Couldn't pause recording of model", "Pausing recording of " + m.getName() + " failed")); } }; - massEdit(new ArrayList(selectedModels), action); + List models = selectedModels.stream().map(jfxm -> jfxm.getDelegate()).collect(Collectors.toList()); + massEdit(models, action); }; private void resumeRecording(List selectedModels) { @@ -575,7 +577,8 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { showErrorDialog(e, "Couldn't resume recording of model", "Resuming recording of " + m.getName() + " failed")); } }; - massEdit(new ArrayList(selectedModels), action); + List models = selectedModels.stream().map(jfxm -> jfxm.getDelegate()).collect(Collectors.toList()); + massEdit(models, action); } public void saveState() { From 28fee0b2e6abc28035d94ede98e32db8078973e8 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Wed, 5 Dec 2018 12:20:50 +0100 Subject: [PATCH 11/14] Preselect the right entry, if stream url index is set --- .../src/main/java/ctbrec/ui/StreamSourceSelectionDialog.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/main/java/ctbrec/ui/StreamSourceSelectionDialog.java b/client/src/main/java/ctbrec/ui/StreamSourceSelectionDialog.java index 2347493b..2ff98c50 100644 --- a/client/src/main/java/ctbrec/ui/StreamSourceSelectionDialog.java +++ b/client/src/main/java/ctbrec/ui/StreamSourceSelectionDialog.java @@ -25,7 +25,8 @@ public class StreamSourceSelectionDialog { List sources; try { sources = selectStreamSource.get(); - ChoiceDialog choiceDialog = new ChoiceDialog(sources.get(sources.size()-1), sources); + int selectedIndex = model.getStreamUrlIndex() > -1 ? Math.min(model.getStreamUrlIndex(), sources.size()-1) : sources.size()-1; + ChoiceDialog choiceDialog = new ChoiceDialog(sources.get(selectedIndex), sources); choiceDialog.setTitle("Stream Quality"); choiceDialog.setHeaderText("Select your preferred stream quality"); choiceDialog.setResizable(true); From a7b0b3f37478bcf4a8c8342a7b100760b9bcdece Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Wed, 5 Dec 2018 12:30:27 +0100 Subject: [PATCH 12/14] Remove resolution cache Resolutions are cached by ThumbCell --- .../java/ctbrec/sites/camsoda/CamsodaModel.java | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java b/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java index b3d03d94..3c938eb4 100644 --- a/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java +++ b/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java @@ -6,14 +6,11 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; import com.iheartradio.m3u8.Encoding; import com.iheartradio.m3u8.Format; import com.iheartradio.m3u8.ParseException; @@ -41,12 +38,7 @@ public class CamsodaModel extends AbstractModel { private List streamSources = null; private String status = "n/a"; private float sortOrder = 0; - - private static Cache streamResolutionCache = CacheBuilder.newBuilder() - .initialCapacity(10_000) - .maximumSize(10_000) - .expireAfterWrite(30, TimeUnit.MINUTES) - .build(); + int[] resolution = new int[2]; public String getStreamUrl() throws IOException { if(streamUrl == null) { @@ -139,13 +131,11 @@ public class CamsodaModel extends AbstractModel { @Override public void invalidateCacheEntries() { streamSources = null; - streamResolutionCache.invalidate(getName()); } @Override public int[] getStreamResolution(boolean failFast) throws ExecutionException { - int[] resolution = streamResolutionCache.getIfPresent(getName()); - if(resolution != null) { + if(failFast) { return resolution; } else { if(failFast) { @@ -158,7 +148,6 @@ public class CamsodaModel extends AbstractModel { } else { StreamSource src = streamSources.get(0); resolution = new int[] {src.width, src.height}; - streamResolutionCache.put(getName(), resolution); return resolution; } } catch (IOException | ParseException | PlaylistException e) { From 9109fc8689da63f6e3c85358a6ce88662bb904c2 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Wed, 5 Dec 2018 12:38:06 +0100 Subject: [PATCH 13/14] Display "unkown resolution" instead of Integer.MAX_VALUE --- .../main/java/ctbrec/recorder/download/StreamSource.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/ctbrec/recorder/download/StreamSource.java b/common/src/main/java/ctbrec/recorder/download/StreamSource.java index dbb86f03..6044716b 100644 --- a/common/src/main/java/ctbrec/recorder/download/StreamSource.java +++ b/common/src/main/java/ctbrec/recorder/download/StreamSource.java @@ -44,7 +44,11 @@ public class StreamSource implements Comparable { public String toString() { DecimalFormat df = new DecimalFormat("0.00"); float mbit = bandwidth / 1024.0f / 1024.0f; - return height + "p (" + df.format(mbit) + " Mbit/s)"; + if(height == Integer.MAX_VALUE) { + return "unknown resolution (" + df.format(mbit) + " Mbit/s)"; + } else { + return height + "p (" + df.format(mbit) + " Mbit/s)"; + } } /** From e6476e95ec174a36c615e02b284b261c5e2041a1 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Wed, 5 Dec 2018 16:08:44 +0100 Subject: [PATCH 14/14] Add setting to ignore the upscaled video stream on MFC --- .../sites/myfreecams/MyFreeCamsConfigUI.java | 25 +++++++++++++------ common/src/main/java/ctbrec/Settings.java | 1 + .../ctbrec/sites/mfc/MyFreeCamsModel.java | 10 +++++++- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/client/src/main/java/ctbrec/ui/sites/myfreecams/MyFreeCamsConfigUI.java b/client/src/main/java/ctbrec/ui/sites/myfreecams/MyFreeCamsConfigUI.java index bf64358b..0783a68a 100644 --- a/client/src/main/java/ctbrec/ui/sites/myfreecams/MyFreeCamsConfigUI.java +++ b/client/src/main/java/ctbrec/ui/sites/myfreecams/MyFreeCamsConfigUI.java @@ -8,6 +8,7 @@ import ctbrec.ui.sites.AbstractConfigUI; import javafx.geometry.Insets; import javafx.scene.Parent; import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; @@ -23,8 +24,9 @@ public class MyFreeCamsConfigUI extends AbstractConfigUI { @Override public Parent createConfigPanel() { + int row = 0; GridPane layout = SettingsTab.createGridLayout(); - layout.add(new Label("MyFreeCams User"), 0, 0); + layout.add(new Label("MyFreeCams User"), 0, row); TextField username = new TextField(Config.getInstance().getSettings().mfcUsername); username.setPrefWidth(300); username.textProperty().addListener((ob, o, n) -> { @@ -34,9 +36,9 @@ public class MyFreeCamsConfigUI extends AbstractConfigUI { GridPane.setFillWidth(username, true); GridPane.setHgrow(username, Priority.ALWAYS); GridPane.setColumnSpan(username, 2); - layout.add(username, 1, 0); + layout.add(username, 1, row++); - layout.add(new Label("MyFreeCams Password"), 0, 1); + layout.add(new Label("MyFreeCams Password"), 0, row); PasswordField password = new PasswordField(); password.setText(Config.getInstance().getSettings().mfcPassword); password.textProperty().addListener((ob, o, n) -> { @@ -46,9 +48,9 @@ public class MyFreeCamsConfigUI extends AbstractConfigUI { GridPane.setFillWidth(password, true); GridPane.setHgrow(password, Priority.ALWAYS); GridPane.setColumnSpan(password, 2); - layout.add(password, 1, 1); + layout.add(password, 1, row++); - layout.add(new Label("MyFreeCams Base URL"), 0, 2); + layout.add(new Label("MyFreeCams Base URL"), 0, row); TextField baseUrl = new TextField(); baseUrl.setText(Config.getInstance().getSettings().mfcBaseUrl); baseUrl.textProperty().addListener((ob, o, n) -> { @@ -58,15 +60,24 @@ public class MyFreeCamsConfigUI extends AbstractConfigUI { GridPane.setFillWidth(baseUrl, true); GridPane.setHgrow(baseUrl, Priority.ALWAYS); GridPane.setColumnSpan(baseUrl, 2); - layout.add(baseUrl, 1, 2); + layout.add(baseUrl, 1, row++); + + layout.add(new Label("Ignore upscaled stream (960p)"), 0, row); + CheckBox ignoreUpscaled = new CheckBox(); + ignoreUpscaled.setSelected(Config.getInstance().getSettings().mfcIgnoreUpscaled); + ignoreUpscaled.selectedProperty().addListener((obs, oldV, newV) -> { + Config.getInstance().getSettings().mfcIgnoreUpscaled = newV; + }); + layout.add(ignoreUpscaled, 1, row++); Button createAccount = new Button("Create new Account"); createAccount.setOnAction((e) -> DesktopIntegration.open(myFreeCams.getAffiliateLink())); - layout.add(createAccount, 1, 3); + layout.add(createAccount, 1, row); GridPane.setColumnSpan(createAccount, 2); GridPane.setMargin(username, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN)); GridPane.setMargin(password, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN)); GridPane.setMargin(baseUrl, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN)); + GridPane.setMargin(ignoreUpscaled, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN)); GridPane.setMargin(createAccount, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN)); return layout; diff --git a/common/src/main/java/ctbrec/Settings.java b/common/src/main/java/ctbrec/Settings.java index 64fc8e42..bb19eb34 100644 --- a/common/src/main/java/ctbrec/Settings.java +++ b/common/src/main/java/ctbrec/Settings.java @@ -49,6 +49,7 @@ public class Settings { public String mfcUsername = ""; public String mfcPassword = ""; public String mfcBaseUrl = "https://www.myfreecams.com"; + public boolean mfcIgnoreUpscaled = false; public String camsodaUsername = ""; public String camsodaPassword = ""; public String cam4Username; diff --git a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java index a85c3425..ec8301ef 100644 --- a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java +++ b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; import org.jsoup.nodes.Element; import org.slf4j.Logger; @@ -28,6 +29,7 @@ import com.squareup.moshi.JsonReader; import com.squareup.moshi.JsonWriter; import ctbrec.AbstractModel; +import ctbrec.Config; import ctbrec.io.HtmlParser; import ctbrec.io.HttpException; import ctbrec.recorder.download.StreamSource; @@ -95,7 +97,13 @@ public class MyFreeCamsModel extends AbstractModel { sources.add(src); } } - return sources; + if(Config.getInstance().getSettings().mfcIgnoreUpscaled) { + return sources.stream() + .filter(src -> src.height != 960) + .collect(Collectors.toList()); + } else { + return sources; + } } private MasterPlaylist getMasterPlaylist() throws IOException, ParseException, PlaylistException {