forked from j62/ctbrec
1
0
Fork 0

Merge branch 'dev'

This commit is contained in:
0xboobface 2018-12-08 13:08:54 +01:00
commit dcd68d3c2a
12 changed files with 173 additions and 109 deletions

View File

@ -3,6 +3,7 @@ package ctbrec.ui;
import java.io.IOException; import java.io.IOException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -296,13 +297,13 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
massEdit(models, action); massEdit(models, action);
} }
private void massEdit(List<Model> models, Consumer<Model> action) { private void massEdit(List<? extends Model> models, Consumer<Model> action) {
getTabPane().setCursor(Cursor.WAIT); table.setCursor(Cursor.WAIT);
threadPool.submit(() -> { threadPool.submit(() -> {
for (Model model : models) { for (Model model : models) {
action.accept(model); 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))); openInPlayer.setOnAction((e) -> openInPlayer(selectedModels.get(0)));
MenuItem switchStreamSource = new MenuItem("Switch resolution"); MenuItem switchStreamSource = new MenuItem("Switch resolution");
switchStreamSource.setOnAction((e) -> switchStreamSource(selectedModels.get(0))); switchStreamSource.setOnAction((e) -> switchStreamSource(selectedModels.get(0)));
MenuItem follow = new MenuItem("Follow");
follow.setOnAction((e) -> follow(selectedModels));
ContextMenu menu = new ContextMenu(stop); ContextMenu menu = new ContextMenu(stop);
if (selectedModels.size() == 1) { if (selectedModels.size() == 1) {
@ -450,7 +453,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
} else { } else {
menu.getItems().addAll(resumeRecording, pauseRecording); menu.getItems().addAll(resumeRecording, pauseRecording);
} }
menu.getItems().addAll(copyUrl, openInPlayer, openInBrowser, switchStreamSource); menu.getItems().addAll(copyUrl, openInPlayer, openInBrowser, switchStreamSource, follow);
if (selectedModels.size() > 1) { if (selectedModels.size() > 1) {
copyUrl.setDisable(true); copyUrl.setDisable(true);
@ -462,6 +465,19 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
return menu; return menu;
} }
private void follow(ObservableList<JavaFxModel> selectedModels) {
Consumer<Model> 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<JavaFxModel>(selectedModels), action);
}
private void openInPlayer(JavaFxModel selectedModel) { private void openInPlayer(JavaFxModel selectedModel) {
table.setCursor(Cursor.WAIT); table.setCursor(Cursor.WAIT);
new Thread(() -> { new Thread(() -> {

View File

@ -25,7 +25,8 @@ public class StreamSourceSelectionDialog {
List<StreamSource> sources; List<StreamSource> sources;
try { try {
sources = selectStreamSource.get(); sources = selectStreamSource.get();
ChoiceDialog<StreamSource> choiceDialog = new ChoiceDialog<StreamSource>(sources.get(sources.size()-1), sources); int selectedIndex = model.getStreamUrlIndex() > -1 ? Math.min(model.getStreamUrlIndex(), sources.size()-1) : sources.size()-1;
ChoiceDialog<StreamSource> choiceDialog = new ChoiceDialog<StreamSource>(sources.get(selectedIndex), sources);
choiceDialog.setTitle("Stream Quality"); choiceDialog.setTitle("Stream Quality");
choiceDialog.setHeaderText("Select your preferred stream quality"); choiceDialog.setHeaderText("Select your preferred stream quality");
choiceDialog.setResizable(true); choiceDialog.setResizable(true);

View File

@ -7,11 +7,14 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function; import java.util.function.Function;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.iheartradio.m3u8.ParseException; import com.iheartradio.m3u8.ParseException;
import ctbrec.Config; import ctbrec.Config;
@ -80,6 +83,10 @@ public class ThumbCell extends StackPane {
private boolean mouseHovering = false; private boolean mouseHovering = false;
private boolean recording = false; private boolean recording = false;
private static ExecutorService imageLoadingThreadPool = Executors.newFixedThreadPool(30); private static ExecutorService imageLoadingThreadPool = Executors.newFixedThreadPool(30);
private static Cache<Model, int[]> resolutionCache = CacheBuilder.newBuilder()
.expireAfterAccess(4, TimeUnit.HOURS)
.maximumSize(1000)
.build();
public ThumbCell(ThumbOverviewTab parent, Model model, Recorder recorder) { public ThumbCell(ThumbOverviewTab parent, Model model, Recorder recorder) {
this.thumbCellList = parent.grid.getChildren(); this.thumbCellList = parent.grid.getChildren();
@ -212,21 +219,30 @@ public class ThumbCell extends StackPane {
return; return;
} }
int[] resolution = resolutionCache.getIfPresent(model);
if(resolution != null) {
try {
updateResolutionTag(resolution);
} catch(Exception e) {
LOG.warn("Couldn't update resolution tag for model {}", model.getName(), e);
}
} else {
ThumbOverviewTab.threadPool.submit(() -> { ThumbOverviewTab.threadPool.submit(() -> {
try { try {
ThumbOverviewTab.resolutionProcessing.add(model); ThumbOverviewTab.resolutionProcessing.add(model);
int[] resolution = model.getStreamResolution(false); int[] _resolution = model.getStreamResolution(false);
updateResolutionTag(resolution); resolutionCache.put(model, _resolution);
updateResolutionTag(_resolution);
// the model is online, but the resolution is 0. probably something went wrong // 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" // when we first requested the stream info, so we remove this invalid value from the "cache"
// so that it is requested again // so that it is requested again
if (model.isOnline() && resolution[1] == 0) { if (model.isOnline() && _resolution[1] == 0) {
LOG.trace("Removing invalid resolution value for {}", model.getName()); LOG.trace("Removing invalid resolution value for {}", model.getName());
model.invalidateCacheEntries(); model.invalidateCacheEntries();
} }
Thread.sleep(500); Thread.sleep(100);
} catch (IOException | InterruptedException e1) { } catch (IOException | InterruptedException e1) {
LOG.warn("Couldn't update resolution tag for model {}", model.getName(), e1); LOG.warn("Couldn't update resolution tag for model {}", model.getName(), e1);
} catch(ExecutionException e) { } catch(ExecutionException e) {
@ -242,6 +258,7 @@ public class ThumbCell extends StackPane {
} }
}); });
} }
}
private void updateResolutionTag(int[] resolution) throws IOException, ExecutionException, InterruptedException { private void updateResolutionTag(int[] resolution) throws IOException, ExecutionException, InterruptedException {
String _res = "n/a"; String _res = "n/a";
@ -492,7 +509,7 @@ public class ThumbCell extends StackPane {
this.model.setPreview(model.getPreview()); this.model.setPreview(model.getPreview());
this.model.setTags(model.getTags()); this.model.setTags(model.getTags());
this.model.setUrl(model.getUrl()); this.model.setUrl(model.getUrl());
this.model.setSuspended(model.isSuspended()); this.model.setSuspended(recorder.isSuspended(model));
update(); update();
} }

View File

@ -8,6 +8,7 @@ import ctbrec.ui.sites.AbstractConfigUI;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.PasswordField; import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
@ -23,8 +24,9 @@ public class MyFreeCamsConfigUI extends AbstractConfigUI {
@Override @Override
public Parent createConfigPanel() { public Parent createConfigPanel() {
int row = 0;
GridPane layout = SettingsTab.createGridLayout(); 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); TextField username = new TextField(Config.getInstance().getSettings().mfcUsername);
username.setPrefWidth(300); username.setPrefWidth(300);
username.textProperty().addListener((ob, o, n) -> { username.textProperty().addListener((ob, o, n) -> {
@ -34,9 +36,9 @@ public class MyFreeCamsConfigUI extends AbstractConfigUI {
GridPane.setFillWidth(username, true); GridPane.setFillWidth(username, true);
GridPane.setHgrow(username, Priority.ALWAYS); GridPane.setHgrow(username, Priority.ALWAYS);
GridPane.setColumnSpan(username, 2); 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(); PasswordField password = new PasswordField();
password.setText(Config.getInstance().getSettings().mfcPassword); password.setText(Config.getInstance().getSettings().mfcPassword);
password.textProperty().addListener((ob, o, n) -> { password.textProperty().addListener((ob, o, n) -> {
@ -46,9 +48,9 @@ public class MyFreeCamsConfigUI extends AbstractConfigUI {
GridPane.setFillWidth(password, true); GridPane.setFillWidth(password, true);
GridPane.setHgrow(password, Priority.ALWAYS); GridPane.setHgrow(password, Priority.ALWAYS);
GridPane.setColumnSpan(password, 2); 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(); TextField baseUrl = new TextField();
baseUrl.setText(Config.getInstance().getSettings().mfcBaseUrl); baseUrl.setText(Config.getInstance().getSettings().mfcBaseUrl);
baseUrl.textProperty().addListener((ob, o, n) -> { baseUrl.textProperty().addListener((ob, o, n) -> {
@ -58,15 +60,24 @@ public class MyFreeCamsConfigUI extends AbstractConfigUI {
GridPane.setFillWidth(baseUrl, true); GridPane.setFillWidth(baseUrl, true);
GridPane.setHgrow(baseUrl, Priority.ALWAYS); GridPane.setHgrow(baseUrl, Priority.ALWAYS);
GridPane.setColumnSpan(baseUrl, 2); 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"); Button createAccount = new Button("Create new Account");
createAccount.setOnAction((e) -> DesktopIntegration.open(myFreeCams.getAffiliateLink())); createAccount.setOnAction((e) -> DesktopIntegration.open(myFreeCams.getAffiliateLink()));
layout.add(createAccount, 1, 3); layout.add(createAccount, 1, row);
GridPane.setColumnSpan(createAccount, 2); GridPane.setColumnSpan(createAccount, 2);
GridPane.setMargin(username, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN)); GridPane.setMargin(username, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
GridPane.setMargin(password, 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(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)); GridPane.setMargin(createAccount, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
return layout; return layout;

View File

@ -14,33 +14,71 @@ import ctbrec.sites.Site;
public interface Model { public interface Model {
public String getUrl(); public String getUrl();
public void setUrl(String url); public void setUrl(String url);
public String getDisplayName(); public String getDisplayName();
public void setDisplayName(String name); public void setDisplayName(String name);
public String getName(); public String getName();
public void setName(String name); public void setName(String name);
public String getPreview(); public String getPreview();
public void setPreview(String preview); public void setPreview(String preview);
public List<String> getTags(); public List<String> getTags();
public void setTags(List<String> tags); public void setTags(List<String> tags);
public String getDescription(); public String getDescription();
public void setDescription(String description); public void setDescription(String description);
public int getStreamUrlIndex(); public int getStreamUrlIndex();
public void setStreamUrlIndex(int streamUrlIndex); public void setStreamUrlIndex(int streamUrlIndex);
public boolean isOnline() throws IOException, ExecutionException, InterruptedException; public boolean isOnline() throws IOException, ExecutionException, InterruptedException;
public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException; public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException;
public String getOnlineState(boolean failFast) throws IOException, ExecutionException; public String getOnlineState(boolean failFast) throws IOException, ExecutionException;
public List<StreamSource> getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException; public List<StreamSource> getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException;
public void invalidateCacheEntries(); public void invalidateCacheEntries();
public void receiveTip(int tokens) throws IOException; 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 int[] getStreamResolution(boolean failFast) throws ExecutionException;
public boolean follow() throws IOException; public boolean follow() throws IOException;
public boolean unfollow() throws IOException; public boolean unfollow() throws IOException;
public void setSite(Site site); public void setSite(Site site);
public Site getSite(); public Site getSite();
public void writeSiteSpecificData(JsonWriter writer) throws IOException; public void writeSiteSpecificData(JsonWriter writer) throws IOException;
public void readSiteSpecificData(JsonReader reader) throws IOException; public void readSiteSpecificData(JsonReader reader) throws IOException;
public boolean isSuspended(); public boolean isSuspended();
public void setSuspended(boolean suspended); public void setSuspended(boolean suspended);
} }

View File

@ -49,6 +49,7 @@ public class Settings {
public String mfcUsername = ""; public String mfcUsername = "";
public String mfcPassword = ""; public String mfcPassword = "";
public String mfcBaseUrl = "https://www.myfreecams.com"; public String mfcBaseUrl = "https://www.myfreecams.com";
public boolean mfcIgnoreUpscaled = false;
public String camsodaUsername = ""; public String camsodaUsername = "";
public String camsodaPassword = ""; public String camsodaPassword = "";
public String cam4Username; public String cam4Username;

View File

@ -44,8 +44,12 @@ public class StreamSource implements Comparable<StreamSource> {
public String toString() { public String toString() {
DecimalFormat df = new DecimalFormat("0.00"); DecimalFormat df = new DecimalFormat("0.00");
float mbit = bandwidth / 1024.0f / 1024.0f; float mbit = bandwidth / 1024.0f / 1024.0f;
if(height == Integer.MAX_VALUE) {
return "unknown resolution (" + df.format(mbit) + " Mbit/s)";
} else {
return height + "p (" + df.format(mbit) + " Mbit/s)"; return height + "p (" + df.format(mbit) + " Mbit/s)";
} }
}
/** /**
* First compares the sources by height, if the heights are the same * First compares the sources by height, if the heights are the same

View File

@ -6,14 +6,11 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.json.JSONObject; import org.json.JSONObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.Encoding;
import com.iheartradio.m3u8.Format; import com.iheartradio.m3u8.Format;
import com.iheartradio.m3u8.ParseException; import com.iheartradio.m3u8.ParseException;
@ -41,12 +38,7 @@ public class CamsodaModel extends AbstractModel {
private List<StreamSource> streamSources = null; private List<StreamSource> streamSources = null;
private String status = "n/a"; private String status = "n/a";
private float sortOrder = 0; private float sortOrder = 0;
int[] resolution = new int[2];
private static Cache<String, int[]> streamResolutionCache = CacheBuilder.newBuilder()
.initialCapacity(10_000)
.maximumSize(10_000)
.expireAfterWrite(30, TimeUnit.MINUTES)
.build();
public String getStreamUrl() throws IOException { public String getStreamUrl() throws IOException {
if(streamUrl == null) { if(streamUrl == null) {
@ -139,13 +131,11 @@ public class CamsodaModel extends AbstractModel {
@Override @Override
public void invalidateCacheEntries() { public void invalidateCacheEntries() {
streamSources = null; streamSources = null;
streamResolutionCache.invalidate(getName());
} }
@Override @Override
public int[] getStreamResolution(boolean failFast) throws ExecutionException { public int[] getStreamResolution(boolean failFast) throws ExecutionException {
int[] resolution = streamResolutionCache.getIfPresent(getName()); if(failFast) {
if(resolution != null) {
return resolution; return resolution;
} else { } else {
if(failFast) { if(failFast) {
@ -158,7 +148,6 @@ public class CamsodaModel extends AbstractModel {
} else { } else {
StreamSource src = streamSources.get(0); StreamSource src = streamSources.get(0);
resolution = new int[] {src.width, src.height}; resolution = new int[] {src.width, src.height};
streamResolutionCache.put(getName(), resolution);
return resolution; return resolution;
} }
} catch (IOException | ParseException | PlaylistException e) { } catch (IOException | ParseException | PlaylistException e) {

View File

@ -189,17 +189,6 @@ public class Chaturbate extends AbstractSite {
} }
}); });
LoadingCache<String, int[]> streamResolutionCache = CacheBuilder.newBuilder()
.initialCapacity(10_000)
.maximumSize(10_000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build(new CacheLoader<String, int[]> () {
@Override
public int[] load(String model) throws Exception {
return loadResolution(model);
}
});
public void sendTip(String name, int tokens) throws IOException { public void sendTip(String name, int tokens) throws IOException {
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) { if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
RequestBody body = new FormBody.Builder() RequestBody body = new FormBody.Builder()
@ -264,11 +253,9 @@ public class Chaturbate extends AbstractSite {
} }
} }
public int[] getResolution(String modelName) throws ExecutionException { public int[] getResolution(String modelName) throws ExecutionException, IOException, ParseException, PlaylistException, InterruptedException {
return streamResolutionCache.get(modelName); throttleRequests();
}
private int[] loadResolution(String modelName) throws IOException, ParseException, PlaylistException, ExecutionException, InterruptedException {
int[] res = new int[2]; int[] res = new int[2];
StreamInfo streamInfo = getStreamInfo(modelName); StreamInfo streamInfo = getStreamInfo(modelName);
if(!streamInfo.url.startsWith("http")) { if(!streamInfo.url.startsWith("http")) {
@ -303,7 +290,6 @@ public class Chaturbate extends AbstractSite {
throw ex; throw ex;
} }
streamResolutionCache.put(modelName, res);
return res; return res;
} }

View File

@ -25,6 +25,7 @@ import okhttp3.Response;
public class ChaturbateModel extends AbstractModel { public class ChaturbateModel extends AbstractModel {
private static final transient Logger LOG = LoggerFactory.getLogger(ChaturbateModel.class); 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 * This constructor exists only for deserialization. Please don't call it directly
@ -52,16 +53,16 @@ public class ChaturbateModel extends AbstractModel {
@Override @Override
public int[] getStreamResolution(boolean failFast) throws ExecutionException { public int[] getStreamResolution(boolean failFast) throws ExecutionException {
int[] resolution = getChaturbate().streamResolutionCache.getIfPresent(getName());
if(resolution != null) {
return getChaturbate().getResolution(getName());
} else {
if(failFast) { if(failFast) {
return new int[2]; return resolution;
} else {
return getChaturbate().getResolution(getName());
} }
try {
resolution = getChaturbate().getResolution(getName());
} catch(Exception e) {
throw new ExecutionException(e);
} }
return resolution;
} }
/** /**
@ -71,7 +72,6 @@ public class ChaturbateModel extends AbstractModel {
@Override @Override
public void invalidateCacheEntries() { public void invalidateCacheEntries() {
getChaturbate().streamInfoCache.invalidate(getName()); getChaturbate().streamInfoCache.invalidate(getName());
getChaturbate().streamResolutionCache.invalidate(getName());
} }
public String getOnlineState() throws IOException, ExecutionException { public String getOnlineState() throws IOException, ExecutionException {

View File

@ -10,10 +10,9 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@ -52,7 +51,6 @@ public class MyFreeCamsClient {
private Cache<Integer, SessionState> sessionStates = CacheBuilder.newBuilder().maximumSize(4000).build(); private Cache<Integer, SessionState> sessionStates = CacheBuilder.newBuilder().maximumSize(4000).build();
private Cache<Integer, MyFreeCamsModel> models = CacheBuilder.newBuilder().maximumSize(4000).build(); private Cache<Integer, MyFreeCamsModel> models = CacheBuilder.newBuilder().maximumSize(4000).build();
private Lock lock = new ReentrantLock(); private Lock lock = new ReentrantLock();
private ExecutorService executor = Executors.newSingleThreadExecutor();
private ServerConfig serverConfig; private ServerConfig serverConfig;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private String tkx; private String tkx;
@ -86,9 +84,15 @@ public class MyFreeCamsClient {
public void start() throws IOException { public void start() throws IOException {
running = true; running = true;
serverConfig = new ServerConfig(mfc); serverConfig = new ServerConfig(mfc);
List<String> websocketServers = new ArrayList<String>(serverConfig.wsServers.keySet()); List<String> websocketServers = new ArrayList<String>(serverConfig.wsServers.size());
String server = websocketServers.get((int) (Math.random()*websocketServers.size())); for (Entry<String, String> 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"; String wsUrl = "ws://" + server + ".myfreecams.com:8080/fcsl";
LOG.debug("Connecting to random websocket server {}", wsUrl);
Thread watchDog = new Thread(() -> { Thread watchDog = new Thread(() -> {
while(running) { while(running) {
@ -307,7 +311,7 @@ public class MyFreeCamsClient {
long opts = json.getInt("opts"); long opts = json.getInt("opts");
long serv = json.getInt("serv"); long serv = json.getInt("serv");
long type = json.getInt("type"); 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; String url = base + "?respkey="+respkey+"&opts="+opts+"&serv="+serv+"&type="+type;
Request req = new Request.Builder().url(url).build(); Request req = new Request.Builder().url(url).build();
LOG.trace("Requesting EXTDATA {}", url); LOG.trace("Requesting EXTDATA {}", url);
@ -567,10 +571,6 @@ public class MyFreeCamsClient {
return models.getIfPresent(uid); return models.getIfPresent(uid);
} }
public void execute(Runnable r) {
executor.execute(r);
}
public void getSessionState(ctbrec.Model model) { public void getSessionState(ctbrec.Model model) {
for (SessionState state : sessionStates.asMap().values()) { for (SessionState state : sessionStates.asMap().values()) {
if(Objects.equals(state.getNm(), model.getName())) { if(Objects.equals(state.getNm(), model.getName())) {

View File

@ -10,6 +10,7 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -28,6 +29,7 @@ import com.squareup.moshi.JsonReader;
import com.squareup.moshi.JsonWriter; import com.squareup.moshi.JsonWriter;
import ctbrec.AbstractModel; import ctbrec.AbstractModel;
import ctbrec.Config;
import ctbrec.io.HtmlParser; import ctbrec.io.HtmlParser;
import ctbrec.io.HttpException; import ctbrec.io.HttpException;
import ctbrec.recorder.download.StreamSource; import ctbrec.recorder.download.StreamSource;
@ -45,7 +47,7 @@ public class MyFreeCamsModel extends AbstractModel {
private double camScore; private double camScore;
private int viewerCount; private int viewerCount;
private State state; private State state;
private int resolution[]; private int resolution[] = new int[2];
/** /**
* This constructor exists only for deserialization. Please don't call it directly * This constructor exists only for deserialization. Please don't call it directly
@ -95,8 +97,14 @@ public class MyFreeCamsModel extends AbstractModel {
sources.add(src); sources.add(src);
} }
} }
if(Config.getInstance().getSettings().mfcIgnoreUpscaled) {
return sources.stream()
.filter(src -> src.height != 960)
.collect(Collectors.toList());
} else {
return sources; return sources;
} }
}
private MasterPlaylist getMasterPlaylist() throws IOException, ParseException, PlaylistException { private MasterPlaylist getMasterPlaylist() throws IOException, ParseException, PlaylistException {
if(getHlsUrl() == null) { if(getHlsUrl() == null) {
@ -174,26 +182,19 @@ public class MyFreeCamsModel extends AbstractModel {
@Override @Override
public int[] getStreamResolution(boolean failFast) throws ExecutionException { public int[] getStreamResolution(boolean failFast) throws ExecutionException {
if(resolution == null) { if (!failFast && hlsUrl != null) {
if(failFast || hlsUrl == null) {
return new int[2];
}
MyFreeCamsClient.getInstance().execute(()->{
try { try {
List<StreamSource> streamSources = getStreamSources(); List<StreamSource> streamSources = getStreamSources();
Collections.sort(streamSources); Collections.sort(streamSources);
StreamSource best = streamSources.get(streamSources.size()-1); StreamSource best = streamSources.get(streamSources.size() - 1);
resolution = new int[] {best.width, best.height}; resolution = new int[] { best.width, best.height };
} catch (ParseException | PlaylistException e) { } catch (ParseException | PlaylistException e) {
LOG.warn("Couldn't determine stream resolution - {}", e.getMessage()); LOG.warn("Couldn't determine stream resolution - {}", e.getMessage());
} catch (ExecutionException | IOException e) { } catch (ExecutionException | IOException e) {
LOG.error("Couldn't determine stream resolution", e); LOG.error("Couldn't determine stream resolution", e);
} }
});
return new int[2];
} else {
return resolution;
} }
return resolution;
} }
public void setStreamUrl(String hlsUrl) { public void setStreamUrl(String hlsUrl) {