Improve MyFreeCamsClient

- Add watch dog to reestablish a broken connection
- Implemented creation of stream urls properly
- Add HD tab to display MFC HD streams
This commit is contained in:
0xboobface 2018-10-23 14:02:20 +02:00
parent 8e3d2fd565
commit f4842fcf51
15 changed files with 313 additions and 119 deletions

View File

@ -56,7 +56,8 @@ public class FriendsUpdateService extends PaginatedScheduledService {
st.setUid(uid);
st.setLv(modelObject.getInt("lv"));
st.setVs(127);
model.update(st);
model.update(st, myFreeCams.getClient().getStreamUrl(st));
}
models.add(model);
}

View File

@ -0,0 +1,40 @@
package ctbrec.sites.mfc;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import ctbrec.Model;
import ctbrec.ui.PaginatedScheduledService;
import javafx.concurrent.Task;
public class HDCamsUpdateService extends PaginatedScheduledService {
@Override
protected Task<List<Model>> createTask() {
return new Task<List<Model>>() {
@Override
public List<Model> call() throws IOException {
MyFreeCamsClient client = MyFreeCamsClient.getInstance();
int modelsPerPage = 50;
return client.getModels().stream()
.filter(m -> m.getPreview() != null)
.filter(m -> m.getStreamUrl() != null)
.filter(m -> m.getStreamUrl().contains("/x-hls/"))
.filter(m -> {
try {
return m.isOnline();
} catch(Exception e) {
return false;
}
})
.sorted((m1,m2) -> (int)(m2.getCamScore() - m1.getCamScore()))
.skip( (page-1) * modelsPerPage)
.limit(modelsPerPage)
.collect(Collectors.toList());
}
};
}
}

View File

@ -36,7 +36,6 @@ public class MessageTypes {
public static final int SERVERREFRESH = 27;
public static final int SETTING = 28;
public static final int BWSTATS = 29;
public static final int SETGUESTNAME = 30;
public static final int TKX = 30;
public static final int SETTEXTOPT = 31;
public static final int SERVERCONFIG = 32;
@ -61,7 +60,6 @@ public class MessageTypes {
public static final int JOINCHAN = 51;
public static final int CREATECHAN = 52;
public static final int INVITECHAN = 53;
public static final int KICKCHAN = 54;
public static final int QUIETCHAN = 55;
public static final int BANCHAN = 56;
public static final int PREVIEWCHAN = 57;
@ -91,6 +89,9 @@ public class MessageTypes {
public static final int EXTDATA = 81;
public static final int NOTIFY = 84;
public static final int PUBLISH = 85;
public static final int XREQUEST = 86;
public static final int XRESPONSE = 87;
public static final int EDGECON = 88;
public static final int ZGWINVALID = 95;
public static final int CONNECTING = 96;
public static final int CONNECTED = 97;

View File

@ -112,4 +112,8 @@ public class MyFreeCams implements Site {
public boolean isSiteForModel(Model m) {
return m instanceof MyFreeCamsModel;
}
public MyFreeCamsClient getClient() {
return client;
}
}

View File

@ -44,6 +44,12 @@ public class MyFreeCamsClient {
private Map<Integer, MyFreeCamsModel> models = new HashMap<>();
private Lock lock = new ReentrantLock();
private ExecutorService executor = Executors.newSingleThreadExecutor();
private ServerConfig serverConfig;
@SuppressWarnings("unused")
private String tkx;
private Integer cxid;
private int[] ctx;
private String ctxenc;
private MyFreeCamsClient() {
moshi = new Moshi.Builder().build();
@ -62,20 +68,39 @@ public class MyFreeCamsClient {
public void start() throws IOException {
running = true;
ServerConfig serverConfig = new ServerConfig(mfc.getHttpClient());
serverConfig = new ServerConfig(mfc.getHttpClient());
List<String> websocketServers = new ArrayList<String>(serverConfig.wsServers.keySet());
String server = websocketServers.get((int) (Math.random()*websocketServers.size()));
String wsUrl = "ws://" + server + ".myfreecams.com:8080/fcsl";
Request req = new Request.Builder()
.url(wsUrl)
.addHeader("Origin", "http://m.myfreecams.com")
.build();
ws = createWebSocket(req);
Thread watchDog = new Thread(() -> {
while(running) {
if (ws == null) {
Request req = new Request.Builder()
.url(wsUrl)
.addHeader("Origin", "http://m.myfreecams.com")
.build();
ws = createWebSocket(req);
}
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
LOG.error("WatchDog couldn't sleep", e);
stop();
running = false;
}
}
});
watchDog.setDaemon(true);
watchDog.setName("MFC WebSocket WatchDog");
watchDog.setPriority(Thread.MIN_PRIORITY);
watchDog.start();
}
public void stop() {
ws.close(1000, "Good Bye"); // terminate normally (1000)
running = false;
ws.close(1000, "Good Bye"); // terminate normally (1000)
}
public List<MyFreeCamsModel> getModels() {
@ -115,12 +140,21 @@ public class MyFreeCamsClient {
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
// TODO decide what todo: is this the end of the session
// or do we have to reconnect to keep things running?
super.onClosed(webSocket, code, reason);
LOG.trace("close: {} {}", code, reason);
running = false;
mfc.getHttpClient().shutdown();
LOG.info("MFC websocket closed: {} {}", code, reason);
MyFreeCamsClient.this.ws = null;
if(!running) {
mfc.getHttpClient().shutdown();
}
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
super.onFailure(webSocket, t, response);
int code = response.code();
String message = response.message();
MyFreeCamsClient.this.ws = null;
LOG.error("MFC websocket failure: {} {}", code, message, t);
}
private StringBuilder msgBuffer = new StringBuilder();
@ -137,13 +171,15 @@ public class MyFreeCamsClient {
}
switch (message.getType()) {
case NULL:
break;
case LOGIN:
System.out.println("LOGIN");
System.out.println("Sender " + message.getSender());
System.out.println("Receiver " + message.getReceiver());
System.out.println("Arg1 " + message.getArg1());
System.out.println("Arg2 " + message.getArg2());
System.out.println("Msg " + message.getMessage());
// System.out.println("LOGIN");
// System.out.println("Sender " + message.getSender());
// System.out.println("Receiver " + message.getReceiver());
// System.out.println("Arg1 " + message.getArg1());
// System.out.println("Arg2 " + message.getArg2());
// System.out.println("Msg " + message.getMessage());
break;
case DETAILS:
case ROOMHELPER:
@ -158,6 +194,7 @@ public class MyFreeCamsClient {
case JOINCHAN:
case SESSIONSTATE:
if(!message.getMessage().isEmpty()) {
//LOG.debug("SessionState: {}", message.getMessage());
JsonAdapter<SessionState> adapter = moshi.adapter(SessionState.class);
try {
SessionState sessionState = adapter.fromJson(message.getMessage());
@ -219,6 +256,21 @@ public class MyFreeCamsClient {
System.out.println("Arg2 " + message.getArg2());
System.out.println("Msg " + message.getMessage());
break;
case SLAVEVSHARE:
// LOG.debug("SLAVEVSHARE {}", message);
// LOG.debug("SLAVEVSHARE MSG [{}]", message.getMessage());
break;
case TKX:
json = new JSONObject(message.getMessage());
tkx = json.getString("tkx");
cxid = json.getInt("cxid");
ctxenc = URLDecoder.decode(json.getString("ctxenc"), "utf-8");
JSONArray ctxArray = json.getJSONArray("ctx");
ctx = new int[ctxArray.length()];
for (int i = 0; i < ctxArray.length(); i++) {
ctx[i] = ctxArray.getInt(i);
}
break;
default:
LOG.debug("Unknown message {}", message);
break;
@ -240,7 +292,7 @@ public class MyFreeCamsClient {
String base = "http://www.myfreecams.com/php/FcwExtResp.php";
String url = base + "?respkey="+respkey+"&opts="+opts+"&serv="+serv+"&type="+type;
Request req = new Request.Builder().url(url).build();
LOG.debug("Requesting EXTDATA {}", url);
LOG.trace("Requesting EXTDATA {}", url);
Response resp = mfc.getHttpClient().execute(req);
if(resp.isSuccessful()) {
@ -268,7 +320,7 @@ public class MyFreeCamsClient {
state.setLv(inner.getInt(idx++));
state.setU(new User());
state.getU().setCamserv(inner.getInt(idx++));
idx++;
state.getU().setPhase(inner.getString(idx++));
state.getU().setChatColor(inner.getString(idx++));
state.getU().setChatFont(inner.getInt(idx++));
state.getU().setChatOpt(inner.getInt(idx++));
@ -334,7 +386,12 @@ public class MyFreeCamsClient {
private void updateModel(SessionState state) {
// essential data not yet available
if(state.getNm() == null || state.getM() == null || state.getU() == null || state.getU().getCamserv() == null) {
if(state.getNm() == null || state.getM() == null || state.getU() == null || state.getU().getCamserv() == null || state.getU().getCamserv() == 0) {
return;
}
// tokens not yet available
if(ctxenc == null) {
return;
}
@ -344,7 +401,7 @@ public class MyFreeCamsClient {
model.setUid(state.getUid());
models.put(state.getUid(), model);
}
model.update(state);
model.update(state, getStreamUrl(state));
}
private Message parseMessage(StringBuilder msg) throws UnsupportedEncodingException {
@ -406,7 +463,7 @@ public class MyFreeCamsClient {
try {
for (SessionState state : sessionStates.values()) {
if(Objects.equals(state.getNm(), model.getName())) {
model.update(state);
model.update(state, getStreamUrl(state));
return;
}
}
@ -415,6 +472,34 @@ public class MyFreeCamsClient {
}
}
String getStreamUrl(SessionState state) {
Integer camserv = state.getU().getCamserv();
if(camserv != null) {
int userChannel = 100000000 + state.getUid();
String streamUrl = "";
String phase = state.getU().getPhase() != null ? state.getU().getPhase() : "z";
if(serverConfig.isOnNgServer(state)) {
String server = serverConfig.ngVideoServers.get(camserv.toString());
streamUrl = "https://" + server + ".myfreecams.com:8444/x-hls/" + cxid + '/' + userChannel + '/' + ctxenc + "/mfc_" + phase + '_' + userChannel + ".m3u8";
//LOG.debug("{} {}", state.getNm(), streamUrl);
} else if(serverConfig.isOnWzObsVideoServer(state)) {
String server = serverConfig.wzobsServers.get(camserv.toString());
streamUrl = "https://"+ server + ".myfreecams.com/NxServer/ngrp:mfc_" + phase + '_' + userChannel + ".f4v_mobile/playlist.m3u8";
LOG.debug("{} isOnWzObsvideo: {}", state.getNm(), streamUrl);
} else if(serverConfig.isOnHtml5VideoServer(state)) {
String server = serverConfig.h5Servers.get(camserv.toString());
streamUrl = "https://"+ server + ".myfreecams.com/NxServer/ngrp:mfc_" + userChannel + ".f4v_mobile/playlist.m3u8";
} else {
if(camserv > 500) {
camserv -= 500;
}
streamUrl = "https://video" + camserv + ".myfreecams.com/NxServer/ngrp:mfc_" + userChannel + ".f4v_mobile/playlist.m3u8";
}
return streamUrl;
}
return null;
}
public MyFreeCamsModel getModel(int uid) {
return models.get(uid);
}
@ -432,4 +517,8 @@ public class MyFreeCamsClient {
}
}
}
public ServerConfig getServerConfig() {
return serverConfig;
}
}

View File

@ -80,13 +80,16 @@ public class MyFreeCamsModel extends AbstractModel {
if(playlist.getStreamInfo().getResolution() != null) {
src.width = playlist.getStreamInfo().getResolution().width;
src.height = playlist.getStreamInfo().getResolution().height;
String masterUrl = hlsUrl;
String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1);
String segmentUri = baseUrl + playlist.getUri();
src.mediaPlaylistUrl = segmentUri;
LOG.trace("Media playlist {}", src.mediaPlaylistUrl);
sources.add(src);
} else {
src.width = Integer.MAX_VALUE;
src.height = Integer.MAX_VALUE;
}
String masterUrl = hlsUrl;
String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1);
String segmentUri = baseUrl + playlist.getUri();
src.mediaPlaylistUrl = segmentUri;
LOG.trace("Media playlist {}", src.mediaPlaylistUrl);
sources.add(src);
}
}
return sources;
@ -100,11 +103,15 @@ public class MyFreeCamsModel extends AbstractModel {
Request req = new Request.Builder().url(hlsUrl).build();
Response response = site.getHttpClient().execute(req);
try {
InputStream inputStream = response.body().byteStream();
PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8);
Playlist playlist = parser.parse();
MasterPlaylist master = playlist.getMasterPlaylist();
return master;
if(response.isSuccessful()) {
InputStream inputStream = response.body().byteStream();
PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8);
Playlist playlist = parser.parse();
MasterPlaylist master = playlist.getMasterPlaylist();
return master;
} else {
throw new IOException(response.code() + " " + response.message());
}
} finally {
response.close();
}
@ -199,9 +206,10 @@ public class MyFreeCamsModel extends AbstractModel {
this.state = state;
}
public void update(SessionState state) {
public void update(SessionState state, String streamUrl) {
setCamScore(state.getM().getCamscore());
setState(State.of(state.getVs()));
setStreamUrl(streamUrl);
// preview
String uid = state.getUid().toString();
@ -209,24 +217,6 @@ public class MyFreeCamsModel extends AbstractModel {
String previewUrl = "https://img.mfcimg.com/photos2/"+uidStart+'/'+uid+"/avatar.300x300.jpg";
setPreview(previewUrl);
// stream url
Integer camserv = state.getU().getCamserv();
if(camserv != null) {
if(state.getM() != null) {
if(state.getM().getFlags() != null) {
int flags = state.getM().getFlags();
int hd = flags >> 18 & 0x1;
if(hd == 1) {
String hlsUrl = "http://video" + (camserv - 500) + ".myfreecams.com:1935/NxServer/ngrp:mfc_a_" + (100000000 + state.getUid()) + ".f4v_mobile/playlist.m3u8";
setStreamUrl(hlsUrl);
return;
}
}
}
String hlsUrl = "http://video" + (camserv - 500) + ".myfreecams.com:1935/NxServer/ngrp:mfc_" + (100000000 + state.getUid()) + ".f4v_mobile/playlist.m3u8";
setStreamUrl(hlsUrl);
}
// tags
Optional.ofNullable(state.getM()).map((m) -> m.getTags()).ifPresent((tags) -> {
ArrayList<String> t = new ArrayList<>();

View File

@ -37,6 +37,13 @@ public class MyFreeCamsTabProvider extends TabProvider {
updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(10)));
tabs.add(friends);
updateService = new HDCamsUpdateService();
ThumbOverviewTab hd = new ThumbOverviewTab("HD", updateService, myFreeCams);
hd.setRecorder(recorder);
updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(10)));
tabs.add(hd);
return tabs;
}
}

View File

@ -5,6 +5,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.json.JSONArray;
import org.json.JSONObject;
@ -21,7 +22,7 @@ public class ServerConfig {
Map<String, String> h5Servers;
Map<String, String> wsServers;
Map<String, String> wzobsServers;
Map<String, String> ngVideo;
Map<String, String> ngVideoServers;
public ServerConfig(HttpClient client) throws IOException {
Request req = new Request.Builder().url("http://www.myfreecams.com/_js/serverconfig.js").build();
@ -35,7 +36,10 @@ public class ServerConfig {
h5Servers = parseMap(serverConfig, "h5video_servers");
wsServers = parseMap(serverConfig, "websocket_servers");
wzobsServers = parseMap(serverConfig, "wzobs_servers");
ngVideo = parseMap(serverConfig, "ngvideo_servers");
ngVideoServers = parseMap(serverConfig, "ngvideo_servers");
// System.out.println("wz " + wzobsServers);
// System.out.println("ng " + ngVideoServers);
// System.out.println("h5 " + h5Servers);
}
private static Map<String, String> parseMap(JSONObject serverConfig, String name) {
@ -56,4 +60,18 @@ public class ServerConfig {
return result;
}
public boolean isOnNgServer(SessionState state) {
int camserv = Objects.requireNonNull(Objects.requireNonNull(state.getU()).getCamserv());
return ngVideoServers.containsKey(Integer.toString(camserv));
}
public boolean isOnWzObsVideoServer(SessionState state) {
int camserv = Objects.requireNonNull(Objects.requireNonNull(state.getU()).getCamserv());
return wzobsServers.containsKey(Integer.toString(camserv));
}
public boolean isOnHtml5VideoServer(SessionState state) {
int camserv = Objects.requireNonNull(Objects.requireNonNull(state.getU()).getCamserv());
return h5Servers.containsKey(Integer.toString(camserv)) || (camserv >= 904 && camserv <= 915 || camserv >= 940 && camserv <= 960);
}
}

View File

@ -18,6 +18,7 @@ public class User {
private Integer photos;
private Integer profile;
private String status;
private String phase;
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
public Integer getAvatar() {
@ -132,6 +133,14 @@ public class User {
this.additionalProperties.put(name, value);
}
public String getPhase() {
return phase;
}
public void setPhase(String phase) {
this.phase = phase;
}
public void merge(User u) {
if (u == null) {
return;
@ -149,6 +158,7 @@ public class User {
photos = u.photos != null ? u.photos : photos;
profile = u.profile != null ? u.profile : profile;
status = u.status != null ? u.status : status;
phase = u.phase != null ? u.phase : phase;
additionalProperties.putAll(u.additionalProperties);
}
}

View File

@ -21,6 +21,7 @@ import com.squareup.moshi.Types;
import ctbrec.Config;
import ctbrec.Version;
import ctbrec.io.HttpClient;
import ctbrec.recorder.LocalRecorder;
import ctbrec.recorder.Recorder;
import ctbrec.recorder.RemoteRecorder;
@ -51,25 +52,24 @@ public class CamrecApplication extends Application {
private SettingsTab settingsTab;
private TabPane rootPane = new TabPane();
static EventBus bus;
private Site site;
private List<Site> sites = new ArrayList<>();
public static HttpClient httpClient;
@Override
public void start(Stage primaryStage) throws Exception {
Chaturbate ctb = new Chaturbate();
sites.add(ctb);
site = new MyFreeCams();
sites.add(site);
sites.add(new Chaturbate());
sites.add(new MyFreeCams());
loadConfig();
createHttpClient();
bus = new AsyncEventBus(Executors.newSingleThreadExecutor());
hostServices = getHostServices();
createRecorder();
for (Site site : sites) {
site.setRecorder(recorder);
site.init();
}
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
site.login();
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
site.login();
}
}
createGui(primaryStage);
checkForUpdates();
@ -92,9 +92,9 @@ public class CamrecApplication extends Application {
}
((SiteTab)rootPane.getTabs().get(0)).selected();
RecordedModelsTab modelsTab = new RecordedModelsTab("Recording", recorder, site);
RecordedModelsTab modelsTab = new RecordedModelsTab("Recording", recorder, sites);
rootPane.getTabs().add(modelsTab);
RecordingsTab recordingsTab = new RecordingsTab("Recordings", recorder, config, site);
RecordingsTab recordingsTab = new RecordingsTab("Recordings", recorder, config, sites);
rootPane.getTabs().add(recordingsTab);
settingsTab = new SettingsTab();
rootPane.getTabs().add(settingsTab);
@ -124,7 +124,9 @@ public class CamrecApplication extends Application {
public void run() {
settingsTab.saveConfig();
recorder.shutdown();
site.shutdown();
for (Site site : sites) {
site.shutdown();
}
try {
Config.getInstance().save();
LOG.info("Shutdown complete. Goodbye!");
@ -162,7 +164,7 @@ public class CamrecApplication extends Application {
if (config.getSettings().localRecording) {
recorder = new LocalRecorder(config);
} else {
recorder = new RemoteRecorder(config, site.getHttpClient());
recorder = new RemoteRecorder(config, httpClient);
}
}
@ -180,6 +182,15 @@ public class CamrecApplication extends Application {
config = Config.getInstance();
}
private void createHttpClient() {
httpClient = new HttpClient() {
@Override
public boolean login() throws IOException {
return false;
}
};
}
public static void main(String[] args) {
launch(args);
}
@ -189,7 +200,7 @@ public class CamrecApplication extends Application {
try {
String url = "https://api.github.com/repos/0xboobface/ctbrec/releases";
Request request = new Request.Builder().url(url).build();
Response response = site.getHttpClient().execute(request);
Response response = httpClient.execute(request);
if (response.isSuccessful()) {
Moshi moshi = new Moshi.Builder().build();
Type type = Types.newParameterizedType(List.class, Release.class);

View File

@ -5,6 +5,7 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@ -26,6 +27,7 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.control.Alert;
@ -59,7 +61,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
private ScheduledService<List<Model>> updateService;
private Recorder recorder;
private Site site;
private List<Site> sites;
FlowPane grid = new FlowPane();
ScrollPane scrollPane = new ScrollPane();
@ -71,10 +73,10 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
TextField model = new TextField();
Button addModelButton = new Button("Record");
public RecordedModelsTab(String title, Recorder recorder, Site site) {
public RecordedModelsTab(String title, Recorder recorder, List<Site> sites) {
super(title);
this.recorder = recorder;
this.site = site;
this.sites = sites;
createGui();
setClosable(false);
initializeUpdateService();
@ -126,18 +128,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
modelLabel.setPadding(new Insets(5, 0, 0, 0));
model.setPrefWidth(300);
BorderPane.setMargin(addModelBox, new Insets(5));
addModelButton.setOnAction((e) -> {
Model m = site.createModel(model.getText());
try {
recorder.startRecording(m);
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
alert.setTitle("Error");
alert.setHeaderText("Couldn't add model");
alert.setContentText("The model " + m.getName() + " could not be added: " + e1.getLocalizedMessage());
alert.showAndWait();
}
});
addModelButton.setOnAction((e) -> addModel(e));
BorderPane root = new BorderPane();
root.setPadding(new Insets(5));
@ -146,6 +137,43 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
setContent(root);
}
private void addModel(ActionEvent e) {
String[] parts = model.getText().trim().split(":");
if (parts.length != 2) {
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
alert.setTitle("Wrong format");
alert.setHeaderText("Couldn't add model");
alert.setContentText("Use something like \"MyFreeCams:ModelName\"");
alert.showAndWait();
return;
}
String siteName = parts[0];
String modelName = parts[1];
for (Site site : sites) {
if (Objects.equals(siteName.toLowerCase(), site.getClass().getSimpleName().toLowerCase())) {
try {
Model m = site.createModel(modelName);
recorder.startRecording(m);
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
alert.setTitle("Error");
alert.setHeaderText("Couldn't add model");
alert.setContentText("The model " + modelName + " could not be added: " + e1.getLocalizedMessage());
alert.showAndWait();
}
return;
}
}
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
alert.setTitle("Unknown site");
alert.setHeaderText("Couldn't add model");
alert.setContentText("The site you entered is unknown");
alert.showAndWait();
};
void initializeUpdateService() {
updateService = createUpdateService();
updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(2)));
@ -189,7 +217,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
return new Task<List<Model>>() {
@Override
public List<Model> call() {
LOG.debug("Updating recorded models");
LOG.trace("Updating recorded models");
return recorder.getModelsRecording();
}
};
@ -279,7 +307,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
showStreamSwitchErrorDialog(t);
return null;
};
StreamSourceSelectionDialog.show(fxModel.getDelegate(), site.getHttpClient(), onSuccess, onFail);
StreamSourceSelectionDialog.show(fxModel.getDelegate(), onSuccess, onFail);
}
private void showStreamSwitchErrorDialog(Throwable throwable) {

View File

@ -28,7 +28,6 @@ import com.iheartradio.m3u8.ParseException;
import com.iheartradio.m3u8.PlaylistException;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.Recording;
import ctbrec.Recording.STATUS;
import ctbrec.recorder.Recorder;
@ -66,7 +65,8 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
private ScheduledService<List<JavaFxRecording>> updateService;
private Config config;
private Recorder recorder;
private Site site;
@SuppressWarnings("unused")
private List<Site> sites;
FlowPane grid = new FlowPane();
ScrollPane scrollPane = new ScrollPane();
@ -74,11 +74,11 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
ObservableList<JavaFxRecording> observableRecordings = FXCollections.observableArrayList();
ContextMenu popup;
public RecordingsTab(String title, Recorder recorder, Config config, Site site) {
public RecordingsTab(String title, Recorder recorder, Config config, List<Site> sites) {
super(title);
this.recorder = recorder;
this.config = config;
this.site = site;
this.sites = sites;
createGui();
setClosable(false);
initializeUpdateService();
@ -245,18 +245,19 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
contextMenu.getItems().add(openInPlayer);
}
MenuItem stopRecording = new MenuItem("Stop recording");
stopRecording.setOnAction((e) -> {
Model m = site.createModel(recording.getModelName());
try {
recorder.stopRecording(m);
} catch (Exception e1) {
showErrorDialog("Stop recording", "Couldn't stop recording of model " + m.getName(), e1);
}
});
if(recording.getStatus() == STATUS.RECORDING) {
contextMenu.getItems().add(stopRecording);
}
// TODO find a way to reenable this
// MenuItem stopRecording = new MenuItem("Stop recording");
// stopRecording.setOnAction((e) -> {
// Model m = site.createModel(recording.getModelName());
// try {
// recorder.stopRecording(m);
// } catch (Exception e1) {
// showErrorDialog("Stop recording", "Couldn't stop recording of model " + m.getName(), e1);
// }
// });
// if(recording.getStatus() == STATUS.RECORDING) {
// contextMenu.getItems().add(stopRecording);
// }
MenuItem deleteRecording = new MenuItem("Delete");
deleteRecording.setOnAction((e) -> {
@ -304,7 +305,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
@Override
public void run() {
try {
MergedHlsDownload download = new MergedHlsDownload(site.getHttpClient());
MergedHlsDownload download = new MergedHlsDownload(CamrecApplication.httpClient);
download.start(url.toString(), target, (progress) -> {
Platform.runLater(() -> {
if (progress == 100) {

View File

@ -6,13 +6,12 @@ import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import ctbrec.Model;
import ctbrec.io.HttpClient;
import ctbrec.recorder.download.StreamSource;
import javafx.concurrent.Task;
import javafx.scene.control.ChoiceDialog;
public class StreamSourceSelectionDialog {
public static void show(Model model, HttpClient client, Function<Model,Void> onSuccess, Function<Throwable, Void> onFail) {
public static void show(Model model, Function<Model,Void> onSuccess, Function<Throwable, Void> onFail) {
Task<List<StreamSource>> selectStreamSource = new Task<List<StreamSource>>() {
@Override
protected List<StreamSource> call() throws Exception {

View File

@ -15,7 +15,6 @@ import com.iheartradio.m3u8.PlaylistException;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.io.HttpClient;
import ctbrec.recorder.Recorder;
import ctbrec.recorder.download.StreamSource;
import javafx.animation.FadeTransition;
@ -72,17 +71,14 @@ public class ThumbCell extends StackPane {
private SimpleBooleanProperty selectionProperty = new SimpleBooleanProperty(false);
private double imgAspectRatio = 3.0 / 4.0;
private HttpClient client;
private ObservableList<Node> thumbCellList;
private boolean mouseHovering = false;
private boolean recording = false;
public ThumbCell(ThumbOverviewTab parent, Model model, Recorder recorder, HttpClient client) {
public ThumbCell(ThumbOverviewTab parent, Model model, Recorder recorder) {
this.thumbCellList = parent.grid.getChildren();
this.model = model;
this.recorder = recorder;
this.client = client;
recording = recorder.isRecording(model);
iv = new ImageView();
@ -208,7 +204,7 @@ public class ThumbCell extends StackPane {
// when we first requested the stream info, so we remove this invalid value from the "cache"
// so that it is requested again
if (model.isOnline() && resolution[1] == 0) {
LOG.debug("Removing invalid resolution value for {}", model.getName());
LOG.trace("Removing invalid resolution value for {}", model.getName());
model.invalidateCacheEntries();
}
} catch (ExecutionException | IOException | InterruptedException e1) {
@ -227,7 +223,7 @@ public class ThumbCell extends StackPane {
LOG.trace("Model resolution {} {}x{}", model.getName(), resolution[0], resolution[1]);
LOG.trace("Resolution queue size: {}", ThumbOverviewTab.queue.size());
final int w = resolution[1];
_res = w > 0 ? Integer.toString(w) : state;
_res = w > 0 ? w != Integer.MAX_VALUE ? Integer.toString(w) : "HD" : state;
} else {
_res = model.getOnlineState(false);
resolutionBackgroundColor = resolutionOfflineColor;
@ -336,7 +332,7 @@ public class ThumbCell extends StackPane {
alert.showAndWait();
return null;
};
StreamSourceSelectionDialog.show(model, client, onSuccess, onFail);
StreamSourceSelectionDialog.show(model, onSuccess, onFail);
} else {
_startStopAction(model, start);
}

View File

@ -27,7 +27,6 @@ import com.sun.javafx.collections.ObservableListWrapper;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.io.HttpClient;
import ctbrec.recorder.Recorder;
import ctbrec.sites.Site;
import ctbrec.sites.mfc.MyFreeCamsClient;
@ -262,7 +261,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
}
}
if(!found) {
ThumbCell newCell = createThumbCell(this, model, recorder, site.getHttpClient());
ThumbCell newCell = createThumbCell(this, model, recorder);
newCell.setIndex(index);
positionChangedOrNew.add(newCell);
}
@ -285,8 +284,8 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
}
}
ThumbCell createThumbCell(ThumbOverviewTab thumbOverviewTab, Model model, Recorder recorder, HttpClient client2) {
ThumbCell newCell = new ThumbCell(this, model, recorder, site.getHttpClient());
ThumbCell createThumbCell(ThumbOverviewTab thumbOverviewTab, Model model, Recorder recorder) {
ThumbCell newCell = new ThumbCell(this, model, recorder);
newCell.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> {
suspendUpdates(true);
popup = createContextMenu(newCell);