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