From 3df1dbf911cbdae00f08d46f02788fd85763be43 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 1 Oct 2018 16:44:01 +0200 Subject: [PATCH] Move Chaturbate class to Model Chaturbate only contains Model related methods. So it makes sense to encapsulate that functionality in Model. Use guava LoadingCache in Chaturbate class to cache resolution and StreamInfo requests. The entries expire after 5 minutes. Add possibility to filter for public rooms only. --- src/main/java/ctbrec/Model.java | 199 ++++++++++++++++-- src/main/java/ctbrec/ModelParser.java | 2 - src/main/java/ctbrec/recorder/Chaturbate.java | 101 --------- .../java/ctbrec/recorder/LocalRecorder.java | 15 +- .../ctbrec/recorder/download/HlsDownload.java | 3 +- .../recorder/download/MergedHlsDownload.java | 3 +- src/main/java/ctbrec/ui/JavaFxModel.java | 12 -- .../java/ctbrec/ui/RecordedModelsTab.java | 17 +- .../ui/StreamSourceSelectionDialog.java | 5 +- src/main/java/ctbrec/ui/ThumbCell.java | 108 +++++----- src/main/java/ctbrec/ui/ThumbOverviewTab.java | 65 +++--- 11 files changed, 285 insertions(+), 245 deletions(-) delete mode 100644 src/main/java/ctbrec/recorder/Chaturbate.java diff --git a/src/main/java/ctbrec/Model.java b/src/main/java/ctbrec/Model.java index 77c817d1..54bbf3a2 100644 --- a/src/main/java/ctbrec/Model.java +++ b/src/main/java/ctbrec/Model.java @@ -1,7 +1,35 @@ package ctbrec; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.iheartradio.m3u8.Encoding; +import com.iheartradio.m3u8.Format; +import com.iheartradio.m3u8.ParseException; +import com.iheartradio.m3u8.PlaylistException; +import com.iheartradio.m3u8.PlaylistParser; +import com.iheartradio.m3u8.data.MasterPlaylist; +import com.iheartradio.m3u8.data.Playlist; +import com.iheartradio.m3u8.data.PlaylistData; +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.Moshi; + +import ctbrec.recorder.StreamInfo; +import okhttp3.FormBody; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; public class Model { private String url; @@ -9,10 +37,7 @@ public class Model { private String preview; private String description; private List tags = new ArrayList<>(); - private boolean online = false; private int streamUrlIndex = -1; - private int streamResolution = -1; - private transient String onlineState; public String getUrl() { return url; @@ -46,12 +71,18 @@ public class Model { this.tags = tags; } - public boolean isOnline() { - return online; + public boolean isOnline() throws IOException, ExecutionException, InterruptedException { + return isOnline(false); } - public void setOnline(boolean online) { - this.online = online; + public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { + StreamInfo info; + if(ignoreCache) { + info = Chaturbate.INSTANCE.loadStreamInfo(name); + } else { + info = Chaturbate.INSTANCE.getStreamInfo(name); + } + return Objects.equals("public", info.room_status); } public String getDescription() { @@ -70,20 +101,33 @@ public class Model { this.streamUrlIndex = streamUrlIndex; } - public int getStreamResolution() { - return streamResolution; + public int[] getStreamResolution(boolean failFast) throws ExecutionException { + int[] resolution = Chaturbate.INSTANCE.streamResolutionCache.getIfPresent(name); + if(resolution != null) { + return Chaturbate.INSTANCE.getResolution(name); + } else { + return new int[2]; + } } - public void setStreamResolution(int streamResolution) { - this.streamResolution = streamResolution; + public int[] getStreamResolution() throws ExecutionException { + return Chaturbate.INSTANCE.getResolution(name); } - public String getOnlineState() { - return onlineState; + public String getOnlineState() throws IOException, ExecutionException { + return getOnlineState(false); } - public void setOnlineState(String onlineState) { - this.onlineState = onlineState; + public String getOnlineState(boolean failFast) throws IOException, ExecutionException { + StreamInfo info = Chaturbate.INSTANCE.streamInfoCache.getIfPresent(name); + return info != null ? info.room_status : "n/a"; + } + + public StreamInfo getStreamInfo() throws IOException, ExecutionException { + return Chaturbate.INSTANCE.getStreamInfo(name); + } + public MasterPlaylist getMasterPlaylist() throws IOException, ParseException, PlaylistException, ExecutionException { + return Chaturbate.INSTANCE.getMasterPlaylist(name); } @Override @@ -122,9 +166,126 @@ public class Model { return name; } - public static void main(String[] args) { - Model model = new Model(); - model.name = "A"; - model.url = "url"; + private static class Chaturbate { + private static final transient Logger LOG = LoggerFactory.getLogger(Chaturbate.class); + + public static final Chaturbate INSTANCE = new Chaturbate(HttpClient.getInstance()); + + private HttpClient client; + + private static long lastRequest = System.currentTimeMillis(); + + private LoadingCache streamInfoCache = CacheBuilder.newBuilder() + .initialCapacity(10_000) + .maximumSize(10_000) + .expireAfterWrite(5, TimeUnit.MINUTES) + .build(new CacheLoader () { + @Override + public StreamInfo load(String model) throws Exception { + return loadStreamInfo(model); + } + }); + + private 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 Chaturbate(HttpClient client) { + this.client = client; + } + + private StreamInfo getStreamInfo(String modelName) throws IOException, ExecutionException { + return streamInfoCache.get(modelName); + } + + private StreamInfo loadStreamInfo(String modelName) throws IOException, InterruptedException { + throttleRequests(); + RequestBody body = new FormBody.Builder() + .add("room_slug", modelName) + .add("bandwidth", "high") + .build(); + Request req = new Request.Builder() + .url("https://chaturbate.com/get_edge_hls_url_ajax/") + .post(body) + .addHeader("X-Requested-With", "XMLHttpRequest") + .build(); + Response response = client.execute(req); + try { + if(response.isSuccessful()) { + String content = response.body().string(); + LOG.trace("Raw stream info: {}", content); + Moshi moshi = new Moshi.Builder().build(); + JsonAdapter adapter = moshi.adapter(StreamInfo.class); + StreamInfo streamInfo = adapter.fromJson(content); + return streamInfo; + } else { + int code = response.code(); + String message = response.message(); + throw new IOException("Server responded with " + code + " - " + message + " headers: [" + response.headers() + "]"); + } + } finally { + response.close(); + } + } + + public int[] getResolution(String modelName) throws ExecutionException { + return streamResolutionCache.get(modelName); + } + + 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")) { + return res; + } + + MasterPlaylist master = getMasterPlaylist(modelName); + for (PlaylistData playlistData : master.getPlaylists()) { + if(playlistData.hasStreamInfo() && playlistData.getStreamInfo().hasResolution()) { + int h = playlistData.getStreamInfo().getResolution().height; + int w = playlistData.getStreamInfo().getResolution().width; + if(w > res[1]) { + res[0] = w; + res[1] = h; + } + } + } + return res; + } + + private void throttleRequests() throws InterruptedException { + long now = System.currentTimeMillis(); + long diff = now-lastRequest; + if(diff < 500) { + Thread.sleep(diff); + } + } + + public MasterPlaylist getMasterPlaylist(String modelName) throws IOException, ParseException, PlaylistException, ExecutionException { + StreamInfo streamInfo = getStreamInfo(modelName); + return getMasterPlaylist(streamInfo); + } + + public MasterPlaylist getMasterPlaylist(StreamInfo streamInfo) throws IOException, ParseException, PlaylistException { + LOG.trace("Loading master playlist {}", streamInfo.url); + Request req = new Request.Builder().url(streamInfo.url).build(); + Response response = client.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; + } finally { + response.close(); + } + } } } diff --git a/src/main/java/ctbrec/ModelParser.java b/src/main/java/ctbrec/ModelParser.java index 75b566ee..fc39c87d 100644 --- a/src/main/java/ctbrec/ModelParser.java +++ b/src/main/java/ctbrec/ModelParser.java @@ -4,7 +4,6 @@ import static ctbrec.ui.CtbrecApplication.BASE_URI; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; @@ -27,7 +26,6 @@ public class ModelParser { model.setPreview(HtmlParser.getTag(cellHtml, "a img").attr("src")); model.setUrl(BASE_URI + HtmlParser.getTag(cellHtml, "a").attr("href")); model.setDescription(HtmlParser.getText(cellHtml, "div.details ul.subject")); - model.setOnline(!Objects.equals("offline", HtmlParser.getText(cellHtml, "div.details li.cams"))); Elements tags = HtmlParser.getTags(cellHtml, "div.details ul.subject li a"); if(tags != null) { for (Element tag : tags) { diff --git a/src/main/java/ctbrec/recorder/Chaturbate.java b/src/main/java/ctbrec/recorder/Chaturbate.java deleted file mode 100644 index 19bde402..00000000 --- a/src/main/java/ctbrec/recorder/Chaturbate.java +++ /dev/null @@ -1,101 +0,0 @@ -package ctbrec.recorder; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Objects; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.iheartradio.m3u8.Encoding; -import com.iheartradio.m3u8.Format; -import com.iheartradio.m3u8.ParseException; -import com.iheartradio.m3u8.PlaylistException; -import com.iheartradio.m3u8.PlaylistParser; -import com.iheartradio.m3u8.data.MasterPlaylist; -import com.iheartradio.m3u8.data.Playlist; -import com.iheartradio.m3u8.data.PlaylistData; -import com.squareup.moshi.JsonAdapter; -import com.squareup.moshi.Moshi; - -import ctbrec.HttpClient; -import ctbrec.Model; -import okhttp3.FormBody; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; - -public class Chaturbate { - private static final transient Logger LOG = LoggerFactory.getLogger(Chaturbate.class); - - public static StreamInfo getStreamInfo(Model model, HttpClient client) throws IOException { - RequestBody body = new FormBody.Builder() - .add("room_slug", model.getName()) - .add("bandwidth", "high") - .build(); - Request req = new Request.Builder() - .url("https://chaturbate.com/get_edge_hls_url_ajax/") - .post(body) - .addHeader("X-Requested-With", "XMLHttpRequest") - .build(); - Response response = client.execute(req); - try { - if(response.isSuccessful()) { - String content = response.body().string(); - LOG.debug("Raw stream info: {}", content); - Moshi moshi = new Moshi.Builder().build(); - JsonAdapter adapter = moshi.adapter(StreamInfo.class); - StreamInfo streamInfo = adapter.fromJson(content); - model.setOnline(Objects.equals(streamInfo.room_status, "public")); - return streamInfo; - } else { - int code = response.code(); - String message = response.message(); - throw new IOException("Server responded with " + code + " - " + message + " headers: [" + response.headers() + "]"); - } - } finally { - response.close(); - } - } - - public static int[] getResolution(Model model, HttpClient client) throws IOException, ParseException, PlaylistException { - int[] res = new int[2]; - StreamInfo streamInfo = getStreamInfo(model, client); - if(!streamInfo.url.startsWith("http")) { - return res; - } - - MasterPlaylist master = getMasterPlaylist(model, client); - for (PlaylistData playlistData : master.getPlaylists()) { - if(playlistData.hasStreamInfo() && playlistData.getStreamInfo().hasResolution()) { - int h = playlistData.getStreamInfo().getResolution().height; - int w = playlistData.getStreamInfo().getResolution().width; - if(w > res[1]) { - res[0] = w; - res[1] = h; - } - } - } - return res; - } - - public static MasterPlaylist getMasterPlaylist(Model model, HttpClient client) throws IOException, ParseException, PlaylistException { - StreamInfo streamInfo = getStreamInfo(model, client); - return getMasterPlaylist(streamInfo, client); - } - - public static MasterPlaylist getMasterPlaylist(StreamInfo streamInfo, HttpClient client) throws IOException, ParseException, PlaylistException { - LOG.trace("Loading master playlist {}", streamInfo.url); - Request req = new Request.Builder().url(streamInfo.url).build(); - Response response = client.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; - } finally { - response.close(); - } - } -} diff --git a/src/main/java/ctbrec/recorder/LocalRecorder.java b/src/main/java/ctbrec/recorder/LocalRecorder.java index 2f21da14..38179ddb 100644 --- a/src/main/java/ctbrec/recorder/LocalRecorder.java +++ b/src/main/java/ctbrec/recorder/LocalRecorder.java @@ -20,7 +20,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; -import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,7 +59,6 @@ public class LocalRecorder implements Recorder { public LocalRecorder(Config config) { this.config = config; config.getSettings().models.stream().forEach((m) -> { - m.setOnline(false); models.add(m); }); @@ -193,13 +191,6 @@ public class LocalRecorder implements Recorder { } } - private boolean checkIfOnline(Model model) throws IOException { - StreamInfo streamInfo = Chaturbate.getStreamInfo(model, client); - boolean online = Objects.equals(streamInfo.room_status, "public"); - model.setOnline(online); - return online; - } - private void tryRestartRecording(Model model) { if (!recording) { // recorder is not in recording state @@ -208,7 +199,7 @@ public class LocalRecorder implements Recorder { try { boolean modelInRecordingList = isRecording(model); - boolean online = checkIfOnline(model); + boolean online = model.isOnline(); if (modelInRecordingList && online) { LOG.info("Restarting recording for model {}", model); recordingProcesses.remove(model); @@ -354,7 +345,8 @@ public class LocalRecorder implements Recorder { for (Model model : getModelsRecording()) { try { if (!recordingProcesses.containsKey(model)) { - boolean isOnline = checkIfOnline(model); + boolean ignoreCache = true; + boolean isOnline = model.isOnline(ignoreCache); LOG.trace("Checking online state for {}: {}", model, (isOnline ? "online" : "offline")); if (isOnline) { LOG.info("Model {}'s room back to public. Starting recording", model); @@ -363,7 +355,6 @@ public class LocalRecorder implements Recorder { } } catch (Exception e) { LOG.error("Couldn't check if model {} is online", model.getName(), e); - model.setOnline(false); } } diff --git a/src/main/java/ctbrec/recorder/download/HlsDownload.java b/src/main/java/ctbrec/recorder/download/HlsDownload.java index 5ff433a0..c8206c3c 100644 --- a/src/main/java/ctbrec/recorder/download/HlsDownload.java +++ b/src/main/java/ctbrec/recorder/download/HlsDownload.java @@ -25,7 +25,6 @@ import com.iheartradio.m3u8.PlaylistException; import ctbrec.Config; import ctbrec.HttpClient; import ctbrec.Model; -import ctbrec.recorder.Chaturbate; import ctbrec.recorder.StreamInfo; import okhttp3.Request; import okhttp3.Response; @@ -42,7 +41,7 @@ public class HlsDownload extends AbstractHlsDownload { public void start(Model model, Config config) throws IOException { try { running = true; - StreamInfo streamInfo = Chaturbate.getStreamInfo(model, client); + StreamInfo streamInfo = model.getStreamInfo(); if(!Objects.equals(streamInfo.room_status, "public")) { throw new IOException(model.getName() +"'s room is not public"); } diff --git a/src/main/java/ctbrec/recorder/download/MergedHlsDownload.java b/src/main/java/ctbrec/recorder/download/MergedHlsDownload.java index 0497ef68..d9c479ab 100644 --- a/src/main/java/ctbrec/recorder/download/MergedHlsDownload.java +++ b/src/main/java/ctbrec/recorder/download/MergedHlsDownload.java @@ -39,7 +39,6 @@ import ctbrec.Config; import ctbrec.HttpClient; import ctbrec.Model; import ctbrec.Recording; -import ctbrec.recorder.Chaturbate; import ctbrec.recorder.ProgressListener; import ctbrec.recorder.StreamInfo; import okhttp3.Request; @@ -84,7 +83,7 @@ public class MergedHlsDownload extends AbstractHlsDownload { try { running = true; startTime = ZonedDateTime.now(); - StreamInfo streamInfo = Chaturbate.getStreamInfo(model, client); + StreamInfo streamInfo = model.getStreamInfo(); if(!Objects.equals(streamInfo.room_status, "public")) { throw new IOException(model.getName() +"'s room is not public"); } diff --git a/src/main/java/ctbrec/ui/JavaFxModel.java b/src/main/java/ctbrec/ui/JavaFxModel.java index f55b7a16..fe0f9072 100644 --- a/src/main/java/ctbrec/ui/JavaFxModel.java +++ b/src/main/java/ctbrec/ui/JavaFxModel.java @@ -16,7 +16,6 @@ public class JavaFxModel extends Model { public JavaFxModel(Model delegate) { this.delegate = delegate; - setOnline(delegate.isOnline()); } @Override @@ -59,17 +58,6 @@ public class JavaFxModel extends Model { delegate.setTags(tags); } - @Override - public boolean isOnline() { - return delegate.isOnline(); - } - - @Override - public void setOnline(boolean online) { - delegate.setOnline(online); - this.onlineProperty.set(online); - } - @Override public int hashCode() { return delegate.hashCode(); diff --git a/src/main/java/ctbrec/ui/RecordedModelsTab.java b/src/main/java/ctbrec/ui/RecordedModelsTab.java index 02ba5209..a7fa9843 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.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; @@ -150,9 +151,6 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { for (Model model : models) { if (!observableModels.contains(model)) { observableModels.add(new JavaFxModel(model)); - } else { - int index = observableModels.indexOf(model); - observableModels.get(index).setOnline(model.isOnline()); } } for (Iterator iterator = observableModels.iterator(); iterator.hasNext();) { @@ -233,11 +231,20 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { } private void switchStreamSource(JavaFxModel fxModel) { - if(!fxModel.isOnline()) { + try { + if(!fxModel.isOnline()) { + Alert alert = new AutosizeAlert(Alert.AlertType.INFORMATION); + alert.setTitle("Switch resolution"); + alert.setHeaderText("Couldn't switch stream resolution"); + alert.setContentText("The resolution can only be changed, when the model is online"); + alert.showAndWait(); + return; + } + } catch (IOException | ExecutionException | InterruptedException e1) { Alert alert = new AutosizeAlert(Alert.AlertType.INFORMATION); alert.setTitle("Switch resolution"); alert.setHeaderText("Couldn't switch stream resolution"); - alert.setContentText("The resolution can only be changed, when the model is online"); + alert.setContentText("An error occured while checking, if the model is online"); alert.showAndWait(); return; } diff --git a/src/main/java/ctbrec/ui/StreamSourceSelectionDialog.java b/src/main/java/ctbrec/ui/StreamSourceSelectionDialog.java index 73057f16..2c66a6ff 100644 --- a/src/main/java/ctbrec/ui/StreamSourceSelectionDialog.java +++ b/src/main/java/ctbrec/ui/StreamSourceSelectionDialog.java @@ -14,7 +14,6 @@ import com.iheartradio.m3u8.data.PlaylistData; import ctbrec.HttpClient; import ctbrec.Model; -import ctbrec.recorder.Chaturbate; import ctbrec.recorder.StreamInfo; import ctbrec.recorder.download.StreamSource; import javafx.concurrent.Task; @@ -27,8 +26,8 @@ public class StreamSourceSelectionDialog { Task> selectStreamSource = new Task>() { @Override protected List call() throws Exception { - StreamInfo streamInfo = Chaturbate.getStreamInfo(model, client); - MasterPlaylist masterPlaylist = Chaturbate.getMasterPlaylist(streamInfo, client); + StreamInfo streamInfo = model.getStreamInfo(); + MasterPlaylist masterPlaylist = model.getMasterPlaylist(); List sources = new ArrayList<>(); for (PlaylistData playlist : masterPlaylist.getPlaylists()) { if (playlist.hasStreamInfo()) { diff --git a/src/main/java/ctbrec/ui/ThumbCell.java b/src/main/java/ctbrec/ui/ThumbCell.java index 17ad39f8..b8b7794a 100644 --- a/src/main/java/ctbrec/ui/ThumbCell.java +++ b/src/main/java/ctbrec/ui/ThumbCell.java @@ -1,21 +1,16 @@ package ctbrec.ui; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; +import java.util.concurrent.ExecutionException; import java.util.function.Function; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.iheartradio.m3u8.ParseException; -import com.iheartradio.m3u8.PlaylistException; - import ctbrec.Config; import ctbrec.HttpClient; import ctbrec.Model; -import ctbrec.recorder.Chaturbate; import ctbrec.recorder.Recorder; import ctbrec.recorder.StreamInfo; import javafx.animation.FadeTransition; @@ -56,9 +51,6 @@ public class ThumbCell extends StackPane { public static int width = 180; private static final Duration ANIMATION_DURATION = new Duration(250); - // this acts like a cache, once the stream resolution for a model has been determined, we don't do it again (until ctbrec is restarted) - private static Map resolutions = new HashMap<>(); - private Model model; private ImageView iv; private Rectangle resolutionBackground; @@ -199,53 +191,33 @@ public class ThumbCell extends StackPane { return; } - ThumbOverviewTab.resolutionProcessing.add(model); - int[] res = resolutions.get(model.getName()); - if(res == null) { - ThumbOverviewTab.threadPool.submit(() -> { - try { - Thread.sleep(500); // throttle down, so that we don't do too many requests - int[] resolution = Chaturbate.getResolution(model, client); - resolutions.put(model.getName(), resolution); - updateResolutionTag(resolution); - } catch (IOException | ParseException | PlaylistException | InterruptedException e) { - LOG.error("Coulnd't get resolution for model {}", model, e); - } finally { - ThumbOverviewTab.resolutionProcessing.remove(model); - } - }); - } else { - ThumbOverviewTab.resolutionProcessing.remove(model); - ThumbOverviewTab.threadPool.submit(() -> { - try { - updateResolutionTag(res); - } catch (IOException e) { - LOG.error("Coulnd't get resolution for model {}", model, e); - } finally { - ThumbOverviewTab.resolutionProcessing.remove(model); - } - }); + ThumbOverviewTab.threadPool.submit(() -> { + try { + ThumbOverviewTab.resolutionProcessing.add(model); + int[] resolution = model.getStreamResolution(); + 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() && res[1] == 0) { - ThumbOverviewTab.threadPool.submit(() -> { - try { - Chaturbate.getStreamInfo(model, client); - if(model.isOnline()) { - LOG.debug("Removing invalid resolution value for {}", model.getName()); - resolutions.remove(model.getName()); - } - } catch (IOException e) { - LOG.error("Coulnd't get resolution for model {}", model, e); + // the model is online, but the resolution is 0. probably something went wrong + // when we first requested the stream info, so we remove this invalid value from the "cache" + // so that it is requested again + try { + if (model.isOnline() && resolution[1] == 0) { + LOG.debug("Removing invalid resolution value for {}", model.getName()); } - }); + } catch (IOException | ExecutionException | InterruptedException e) { + LOG.error("Coulnd't get resolution for model {}", model, e); + } + } catch (ExecutionException e1) { + LOG.warn("Couldn't update resolution tag for model {} - {}", model.getName(), e1.getCause().getMessage()); + } catch (IOException e1) { + LOG.warn("Couldn't update resolution tag for model {} - {}", model.getName(), e1.getMessage()); + } finally { + ThumbOverviewTab.resolutionProcessing.remove(model); } - } + }); } - private void updateResolutionTag(int[] resolution) throws IOException { + private void updateResolutionTag(int[] resolution) throws IOException, ExecutionException { String _res = "n/a"; Paint resolutionBackgroundColor = resolutionOnlineColor; if (resolution[1] > 0) { @@ -253,12 +225,18 @@ public class ThumbCell extends StackPane { LOG.trace("Resolution queue size: {}", ThumbOverviewTab.queue.size()); final int w = resolution[1]; _res = Integer.toString(w); - model.setStreamResolution(w); - model.setOnlineState("online"); } else { - _res = Chaturbate.getStreamInfo(model, client).room_status; + if(model.getOnlineState() != null) { + String state = model.getOnlineState(); + Platform.runLater(() -> { + resolutionTag.setText(state); + resolutionTag.setVisible(true); + resolutionBackground.setVisible(true); + resolutionBackground.setWidth(resolutionTag.getBoundsInLocal().getWidth() + 4); + }); + } + _res = model.getOnlineState(); resolutionBackgroundColor = resolutionOfflineColor; - model.setOnlineState(_res); } final String resText = _res; final Paint c = resolutionBackgroundColor; @@ -306,7 +284,7 @@ public class ThumbCell extends StackPane { // or maybe not, because the player should automatically switch between resolutions depending on the // network bandwidth try { - StreamInfo streamInfo = Chaturbate.getStreamInfo(model, client); + StreamInfo streamInfo = model.getStreamInfo(); if(streamInfo.room_status.equals("public")) { LOG.debug("Playing {}", streamInfo.url); Player.play(streamInfo.url); @@ -316,7 +294,7 @@ public class ThumbCell extends StackPane { alert.setHeaderText("Room is currently not public"); alert.showAndWait(); } - } catch (IOException e1) { + } catch (IOException | ExecutionException e1) { LOG.error("Couldn't get stream information for model {}", model, e1); Alert alert = new AutosizeAlert(Alert.AlertType.ERROR); alert.setTitle("Error"); @@ -456,9 +434,7 @@ public class ThumbCell extends StackPane { //this.model = model; this.model.setName(model.getName()); this.model.setDescription(model.getDescription()); - this.model.setOnline(model.isOnline()); this.model.setPreview(model.getPreview()); - this.model.setStreamResolution(model.getStreamResolution()); this.model.setTags(model.getTags()); this.model.setUrl(model.getUrl()); @@ -477,8 +453,18 @@ public class ThumbCell extends StackPane { setRecording(recorder.isRecording(model)); setImage(model.getPreview()); topic.setText(model.getDescription()); - //Tooltip t = new Tooltip(model.getDescription()); - //Tooltip.install(this, t); + + // ThumbOverviewTab.threadPool.submit(() -> { + // StreamInfo streamInfo; + // try { + // streamInfo = Chaturbate.INSTANCE.getStreamInfo(model); + // model.setOnline(streamInfo.room_status.equals("public")); + // model.setOnlineState(streamInfo.room_status); + // } catch (IOException | ExecutionException e) { + // LOG.error("Couldn't retrieve stream information for model {}", model.getName()); + // } + // }); + if(Config.getInstance().getSettings().determineResolution) { determineResolution(); } else { diff --git a/src/main/java/ctbrec/ui/ThumbOverviewTab.java b/src/main/java/ctbrec/ui/ThumbOverviewTab.java index f1eaf052..476693d0 100644 --- a/src/main/java/ctbrec/ui/ThumbOverviewTab.java +++ b/src/main/java/ctbrec/ui/ThumbOverviewTab.java @@ -11,6 +11,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; @@ -111,8 +112,11 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener { gridLock.unlock(); } }); - search.setTooltip(new Tooltip("Filter the models by their name, stream description or #hashtags.\n\n"+"" - + "If the display of stream resolution is enabled, you can even filter by resolution. Try \"1080\" or \">720\"")); + Tooltip searchTooltip = new Tooltip("Filter the models by their name, stream description or #hashtags.\n\n" + + "If the display of stream resolution is enabled, you can even filter for public rooms or by resolution.\n\n" + + "Try \"1080\" or \">720\" or \"public\""); + search.setTooltip(searchTooltip); + BorderPane.setMargin(search, new Insets(5)); scrollPane.setContent(grid); @@ -479,32 +483,41 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener { } private boolean matches(Model m, String filter) { - String[] tokens = filter.split(" "); - StringBuilder searchTextBuilder = new StringBuilder(m.getName()); - searchTextBuilder.append(' '); - for (String tag : m.getTags()) { - searchTextBuilder.append(tag).append(' '); - } - searchTextBuilder.append(m.getStreamResolution()); - String searchText = searchTextBuilder.toString().trim(); - //LOG.debug("{} -> {}", m.getName(), searchText); - boolean tokensMissing = false; - for (String token : tokens) { - if(token.matches(">\\d+")) { - int res = Integer.parseInt(token.substring(1)); - if(m.getStreamResolution() < res) { - tokensMissing = true; - } - } else if(token.matches("<\\d+")) { - int res = Integer.parseInt(token.substring(1)); - if(m.getStreamResolution() > res) { - tokensMissing = true; - } - } else if(!searchText.contains(token)) { - tokensMissing = true; + try { + String[] tokens = filter.split(" "); + StringBuilder searchTextBuilder = new StringBuilder(m.getName()); + searchTextBuilder.append(' '); + for (String tag : m.getTags()) { + searchTextBuilder.append(tag).append(' '); } + int[] resolution = m.getStreamResolution(true); + searchTextBuilder.append(resolution[1]); + String searchText = searchTextBuilder.toString().trim(); + boolean tokensMissing = false; + for (String token : tokens) { + if(token.matches(">\\d+")) { + int res = Integer.parseInt(token.substring(1)); + if(resolution[1] < res) { + tokensMissing = true; + } + } else if(token.matches("<\\d+")) { + int res = Integer.parseInt(token.substring(1)); + if(resolution[1] > res) { + tokensMissing = true; + } + } else if(token.equals("public")) { + if(!m.getOnlineState(true).equals(token)) { + tokensMissing = true; + } + } else if(!searchText.contains(token)) { + tokensMissing = true; + } + } + return !tokensMissing; + } catch (NumberFormatException | ExecutionException | IOException e) { + LOG.error("Error while filtering model list", e); + return false; } - return !tokensMissing; } private ScheduledService> createUpdateService() {