From 00ea43c8b37ab413e8db8cf00917fa8fb26f1e9b Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Sat, 30 Nov 2019 16:40:01 +0100 Subject: [PATCH] Move HLS related classes ot own package --- .../java/ctbrec/ui/CamrecApplication.java | 3 +- .../main/java/ctbrec/ui/RecordingsTab.java | 2 +- .../main/java/ctbrec/ui/ThumbOverviewTab.java | 4 +- .../sites/camsoda/CamsodaUpdateService.java | 18 +- .../sites/myfreecams/MyFreeCamsTableTab.java | 125 +++++---- common/pom.xml | 21 ++ .../src/main/java/ctbrec/AbstractModel.java | 4 +- common/src/main/java/ctbrec/MpegUtil.java | 8 +- common/src/main/java/ctbrec/Recording.java | 37 +-- .../java/ctbrec/io/StreamRedirectThread.java | 2 +- .../ctbrec/recorder/NextGenLocalRecorder.java | 4 +- .../download/{hls => }/StreamSource.java | 0 .../download/hls/AbstractHlsDownload.java | 4 +- .../ctbrec/recorder/download/hls/Crypto.java | 2 +- .../recorder/download/hls/HlsDownload.java | 6 +- .../download/hls/MergedHlsDownload.java | 4 +- .../download/hls/MissingSegmentException.java | 2 +- .../ctbrec/sites/camsoda/CamsodaModel.java | 2 +- .../ctbrec/sites/fc2live/Fc2HlsDownload.java | 17 +- .../sites/fc2live/Fc2MergedHlsDownload.java | 17 +- .../java/ctbrec/sites/fc2live/Fc2Model.java | 13 +- .../sites/jasmin/LiveJasminHlsDownload.java | 2 +- .../jasmin/LiveJasminMergedHlsDownload.java | 2 +- .../ctbrec/sites/mfc/MyFreeCamsClient.java | 239 +++++++++++------- .../ctbrec/sites/mfc/MyFreeCamsModel.java | 54 ++-- .../java/ctbrec/sites/mfc/ServerConfig.java | 15 +- 26 files changed, 357 insertions(+), 250 deletions(-) rename common/src/main/java/ctbrec/recorder/download/{hls => }/StreamSource.java (100%) diff --git a/client/src/main/java/ctbrec/ui/CamrecApplication.java b/client/src/main/java/ctbrec/ui/CamrecApplication.java index a8dc3190..fa489ae0 100644 --- a/client/src/main/java/ctbrec/ui/CamrecApplication.java +++ b/client/src/main/java/ctbrec/ui/CamrecApplication.java @@ -44,6 +44,7 @@ import ctbrec.sites.chaturbate.Chaturbate; import ctbrec.sites.fc2live.Fc2Live; import ctbrec.sites.flirt4free.Flirt4Free; import ctbrec.sites.jasmin.LiveJasmin; +import ctbrec.sites.mfc.MyFreeCams; import ctbrec.sites.streamate.Streamate; import ctbrec.ui.news.NewsTab; import ctbrec.ui.settings.SettingsTab; @@ -88,7 +89,7 @@ public class CamrecApplication extends Application { sites.add(new Fc2Live()); sites.add(new Flirt4Free()); sites.add(new LiveJasmin()); - //sites.add(new MyFreeCams()); + sites.add(new MyFreeCams()); sites.add(new Streamate()); loadConfig(); registerAlertSystem(); diff --git a/client/src/main/java/ctbrec/ui/RecordingsTab.java b/client/src/main/java/ctbrec/ui/RecordingsTab.java index 46767088..c8f0130f 100644 --- a/client/src/main/java/ctbrec/ui/RecordingsTab.java +++ b/client/src/main/java/ctbrec/ui/RecordingsTab.java @@ -38,7 +38,7 @@ import ctbrec.Recording; import ctbrec.Recording.State; import ctbrec.StringUtil; import ctbrec.recorder.Recorder; -import ctbrec.recorder.download.MergedHlsDownload; +import ctbrec.recorder.download.hls.MergedHlsDownload; import ctbrec.sites.Site; import ctbrec.ui.controls.Toast; import javafx.application.Platform; diff --git a/client/src/main/java/ctbrec/ui/ThumbOverviewTab.java b/client/src/main/java/ctbrec/ui/ThumbOverviewTab.java index a9819225..f6875396 100644 --- a/client/src/main/java/ctbrec/ui/ThumbOverviewTab.java +++ b/client/src/main/java/ctbrec/ui/ThumbOverviewTab.java @@ -536,9 +536,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener { contextMenu.getItems().addAll(copyUrl, ignore); if(cell.getModel() instanceof MyFreeCamsModel && Objects.equals(System.getenv("CTBREC_DEV"), "1")) { MenuItem debug = new MenuItem("debug"); - debug.setOnAction((e) -> { - MyFreeCamsClient.getInstance().getSessionState(cell.getModel()); - }); + debug.setOnAction(e -> MyFreeCamsClient.getInstance().getSessionState(cell.getModel())); contextMenu.getItems().add(debug); } return contextMenu; diff --git a/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaUpdateService.java b/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaUpdateService.java index 760ca6b8..95dd9ebe 100644 --- a/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaUpdateService.java +++ b/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaUpdateService.java @@ -26,7 +26,7 @@ import okhttp3.Response; public class CamsodaUpdateService extends PaginatedScheduledService { - private static final transient Logger LOG = LoggerFactory.getLogger(CamsodaUpdateService.class); + private static final Logger LOG = LoggerFactory.getLogger(CamsodaUpdateService.class); private String url; private boolean loginRequired; @@ -66,7 +66,6 @@ public class CamsodaUpdateService extends PaginatedScheduledService { if(result.has("tpl")) { JSONArray tpl = result.getJSONArray("tpl"); String name = tpl.getString(getTemplateIndex(template, "username")); - String displayName = tpl.getString(getTemplateIndex(template, "display_name")); // int connections = tpl.getInt(2); String streamName = tpl.getString(getTemplateIndex(template, "stream_name")); CamsodaModel model = (CamsodaModel) camsoda.createModel(name); @@ -76,7 +75,11 @@ public class CamsodaUpdateService extends PaginatedScheduledService { model.setPreview(preview); JSONArray edgeServers = tpl.getJSONArray(getTemplateIndex(template, "edge_servers")); model.setStreamUrl("https://" + edgeServers.getString(0) + "/cam/mp4:" + streamName + "_h264_aac_480p/playlist.m3u8"); - model.setDisplayName(displayName); + String displayName = tpl.getString(getTemplateIndex(template, "display_name")); + model.setDisplayName(displayName.replaceAll("[^a-zA-Z0-9]", "")); + if(model.getDisplayName().isBlank()) { + model.setDisplayName(name); + } models.add(model); } else { String name = result.getString("username"); @@ -89,7 +92,10 @@ public class CamsodaUpdateService extends PaginatedScheduledService { } if(result.has("display_name")) { - model.setDisplayName(result.getString("display_name")); + model.setDisplayName(result.getString("display_name").replaceAll("[^a-zA-Z0-9]", "")); + if(model.getDisplayName().isBlank()) { + model.setDisplayName(name); + } } if(result.has("edge_servers")) { @@ -103,7 +109,7 @@ public class CamsodaUpdateService extends PaginatedScheduledService { } } } catch (Exception e) { - LOG.warn("Couldn't parse one of the models: {}", result.toString(), e); + LOG.warn("Couldn't parse one of the models: {}", result, e); } } return models.stream() @@ -112,7 +118,7 @@ public class CamsodaUpdateService extends PaginatedScheduledService { .limit(modelsPerPage) .collect(Collectors.toList()); } else { - LOG.debug("Response was not successful: {}", json.toString()); + LOG.debug("Response was not successful: {}", json); return Collections.emptyList(); } } else { diff --git a/client/src/main/java/ctbrec/ui/sites/myfreecams/MyFreeCamsTableTab.java b/client/src/main/java/ctbrec/ui/sites/myfreecams/MyFreeCamsTableTab.java index c9456ff0..4192d47f 100644 --- a/client/src/main/java/ctbrec/ui/sites/myfreecams/MyFreeCamsTableTab.java +++ b/client/src/main/java/ctbrec/ui/sites/myfreecams/MyFreeCamsTableTab.java @@ -1,5 +1,6 @@ package ctbrec.ui.sites.myfreecams; +import static java.nio.charset.StandardCharsets.*; import static java.nio.file.StandardOpenOption.*; import java.io.File; @@ -28,17 +29,23 @@ import ctbrec.Config; import ctbrec.Model; import ctbrec.StringUtil; import ctbrec.sites.mfc.MyFreeCams; +import ctbrec.sites.mfc.MyFreeCamsClient; import ctbrec.sites.mfc.MyFreeCamsModel; import ctbrec.sites.mfc.SessionState; +import ctbrec.sites.mfc.User; import ctbrec.ui.DesktopIntegration; import ctbrec.ui.TabSelectionListener; import ctbrec.ui.action.FollowAction; import ctbrec.ui.action.PlayAction; import ctbrec.ui.action.StartRecordingAction; import ctbrec.ui.controls.SearchBox; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; +import javafx.beans.property.IntegerProperty; import javafx.beans.property.Property; +import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; @@ -73,9 +80,9 @@ import javafx.stage.FileChooser; import javafx.util.Duration; public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { - private static final transient Logger LOG = LoggerFactory.getLogger(MyFreeCamsTableTab.class); + private static final Logger LOG = LoggerFactory.getLogger(MyFreeCamsTableTab.class); private ScrollPane scrollPane = new ScrollPane(); - private TableView table = new TableView(); + private TableView table = new TableView<>(); private ObservableList filteredModels = FXCollections.observableArrayList(); private ObservableList observableModels = FXCollections.observableArrayList(); private TableUpdateService updateService; @@ -102,9 +109,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { updateService = new TableUpdateService(mfc); updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(1))); updateService.setOnSucceeded(this::onSuccess); - updateService.setOnFailed((event) -> { - LOG.info("Couldn't update MyFreeCams model table", event.getSource().getException()); - }); + updateService.setOnFailed(event -> LOG.info("Couldn't update MyFreeCams model table", event.getSource().getException())); } private void onSuccess(WorkerStateEvent evt) { @@ -202,6 +207,10 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { }); int idx = 0; + TableColumn uid = createTableColumn("UID", 65, idx++); + uid.setCellValueFactory(cdf -> cdf.getValue().uidProperty()); + addTableColumnIfEnabled(uid); + TableColumn name = createTableColumn("Name", 200, idx++); name.setCellValueFactory(cdf -> cdf.getValue().nameProperty()); addTableColumnIfEnabled(name); @@ -210,6 +219,14 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { state.setCellValueFactory(cdf -> cdf.getValue().stateProperty()); addTableColumnIfEnabled(state); + TableColumn hd = createTableColumn("HD", 130, idx++); + hd.setCellValueFactory(cdf -> cdf.getValue().hdProperty()); + addTableColumnIfEnabled(hd); + + TableColumn flags = createTableColumn("Flags", 75, idx++); + flags.setCellValueFactory(cdf -> cdf.getValue().flagsProperty()); + addTableColumnIfEnabled(flags); + TableColumn camscore = createTableColumn("Score", 75, idx++); camscore.setCellValueFactory(cdf -> cdf.getValue().camScoreProperty()); addTableColumnIfEnabled(camscore); @@ -250,7 +267,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { blurp.setCellValueFactory(cdf -> cdf.getValue().blurpProperty()); addTableColumnIfEnabled(blurp); - TableColumn topic = createTableColumn("Topic", 600, idx++); + TableColumn topic = createTableColumn("Topic", 600, idx); topic.setCellValueFactory(cdf -> cdf.getValue().topicProperty()); addTableColumnIfEnabled(topic); @@ -278,7 +295,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { } MenuItem copyUrl = new MenuItem("Copy URL"); - copyUrl.setOnAction((e) -> { + copyUrl.setOnAction(e -> { Model selected = selectedModels.get(0); final Clipboard clipboard = Clipboard.getSystemClipboard(); final ClipboardContent content = new ClipboardContent(); @@ -287,13 +304,13 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { }); MenuItem startRecording = new MenuItem("Start Recording"); - startRecording.setOnAction((e) -> startRecording(selectedModels)); + startRecording.setOnAction(e -> startRecording(selectedModels)); MenuItem openInBrowser = new MenuItem("Open in Browser"); - openInBrowser.setOnAction((e) -> DesktopIntegration.open(selectedModels.get(0).getUrl())); + openInBrowser.setOnAction(e -> DesktopIntegration.open(selectedModels.get(0).getUrl())); MenuItem openInPlayer = new MenuItem("Open in Player"); - openInPlayer.setOnAction((e) -> openInPlayer(selectedModels.get(0))); + openInPlayer.setOnAction(e -> openInPlayer(selectedModels.get(0))); MenuItem follow = new MenuItem("Follow"); - follow.setOnAction((e) -> new FollowAction(getTabPane(), selectedModels).execute()); + follow.setOnAction(e -> new FollowAction(getTabPane(), selectedModels).execute()); ContextMenu menu = new ContextMenu(); menu.getItems().addAll(startRecording, copyUrl, openInPlayer, openInBrowser, follow); @@ -304,6 +321,12 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { openInBrowser.setDisable(true); } + if(Objects.equals(System.getenv("CTBREC_DEV"), "1")) { + MenuItem debug = new MenuItem("debug"); + debug.setOnAction(e -> MyFreeCamsClient.getInstance().getSessionState(selectedModels.get(0))); + menu.getItems().add(debug); + } + return menu; } @@ -392,8 +415,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { ps.println(); } } catch (Exception e) { - LOG.debug("Couldn't write mfc models table data: {}", e.getMessage()); - e.printStackTrace(); + LOG.debug("Couldn't write mfc models table data", e); } } } @@ -488,7 +510,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { data.put(model); } File file = new File(Config.getInstance().getConfigDir(), "mfc-models.json"); - Files.write(file.toPath(), data.toString(2).getBytes("utf-8"), CREATE, WRITE); + Files.write(file.toPath(), data.toString(2).getBytes(UTF_8), CREATE, WRITE); } catch (Exception e) { LOG.debug("Couldn't write mfc models table data: {}", e.getMessage()); } @@ -519,7 +541,9 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { row.tags.set(model.optString("tags")); row.topic.set(model.optString("topic")); observableModels.add(row); - } catch (Exception e) {} + } catch (Exception e) { + // ignore this error + } } } catch (Exception e) { LOG.debug("Couldn't read mfc models table data: {}", e.getMessage()); @@ -537,7 +561,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { columnWidths[i] = table.getColumns().get(i).getWidth(); } Config.getInstance().getSettings().mfcModelsTableColumnWidths = columnWidths; - }; + } private void restoreState() { String sortCol = Config.getInstance().getSettings().mfcModelsTableSortColumn; @@ -563,12 +587,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { } private ListChangeListener> createSortOrderChangedListener() { - return new ListChangeListener>() { - @Override - public void onChanged(Change> c) { - saveState(); - } - }; + return c -> saveState(); } private static class ModelTableRow { @@ -584,6 +603,9 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { private StringProperty tags = new SimpleStringProperty(); private StringProperty blurp = new SimpleStringProperty(); private StringProperty topic = new SimpleStringProperty(); + private BooleanProperty isHd = new SimpleBooleanProperty(); + private SimpleIntegerProperty uidProperty = new SimpleIntegerProperty(); + private SimpleIntegerProperty flagsProperty = new SimpleIntegerProperty(); public ModelTableRow(SessionState st) { update(st); @@ -594,18 +616,23 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { public void update(SessionState st) { uid = st.getUid(); + uidProperty.set(uid); setProperty(name, Optional.ofNullable(st.getNm())); setProperty(state, Optional.ofNullable(st.getVs()).map(vs -> ctbrec.sites.mfc.State.of(vs).toString())); - setProperty(camScore, Optional.ofNullable(st.getM()).map(m -> m.getCamscore())); - Optional isNew = Optional.ofNullable(st.getM()).map(m -> m.getNewModel()); + setProperty(camScore, Optional.ofNullable(st.getM()).map(ctbrec.sites.mfc.Model::getCamscore)); + Optional isNew = Optional.ofNullable(st.getM()).map(ctbrec.sites.mfc.Model::getNewModel); if(isNew.isPresent()) { newModel.set(isNew.get() == 1 ? "new" : ""); } - setProperty(ethnic, Optional.ofNullable(st.getU()).map(u -> u.getEthnic())); - setProperty(country, Optional.ofNullable(st.getU()).map(u -> u.getCountry())); - setProperty(continent, Optional.ofNullable(st.getM()).map(m -> m.getContinent())); - setProperty(occupation, Optional.ofNullable(st.getU()).map(u -> u.getOccupation())); - Set tagSet = Optional.ofNullable(st.getM()).map(m -> m.getTags()).orElse(Collections.emptySet()); + setProperty(ethnic, Optional.ofNullable(st.getU()).map(User::getEthnic)); + setProperty(country, Optional.ofNullable(st.getU()).map(User::getCountry)); + setProperty(continent, Optional.ofNullable(st.getM()).map(ctbrec.sites.mfc.Model::getContinent)); + setProperty(occupation, Optional.ofNullable(st.getU()).map(User::getOccupation)); + int flags = Optional.ofNullable(st.getM()).map(ctbrec.sites.mfc.Model::getFlags).orElse(0); + //isHd.set((flags & 1024) == 1024); + isHd.set(Optional.ofNullable(st.getU()).map(User::getPhase).orElse("z").equalsIgnoreCase("a")); + flagsProperty.setValue(flags); + Set tagSet = Optional.ofNullable(st.getM()).map(ctbrec.sites.mfc.Model::getTags).orElse(Collections.emptySet()); if(!tagSet.isEmpty()) { StringBuilder sb = new StringBuilder(); for (String t : tagSet) { @@ -613,8 +640,8 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { } tags.set(sb.substring(0, sb.length()-2)); } - setProperty(blurp, Optional.ofNullable(st.getU()).map(u -> u.getBlurb())); - String tpc = Optional.ofNullable(st.getM()).map(m -> m.getTopic()).orElse("n/a"); + setProperty(blurp, Optional.ofNullable(st.getU()).map(User::getBlurb)); + String tpc = Optional.ofNullable(st.getM()).map(ctbrec.sites.mfc.Model::getTopic).orElse("n/a"); try { tpc = URLDecoder.decode(tpc, "utf-8"); } catch (UnsupportedEncodingException e) { @@ -631,48 +658,60 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { public StringProperty nameProperty() { return name; - }; + } public StringProperty stateProperty() { return state; - }; + } public DoubleProperty camScoreProperty() { return camScore; - }; + } public StringProperty newModelProperty() { return newModel; - }; + } public StringProperty ethnicityProperty() { return ethnic; - }; + } public StringProperty countryProperty() { return country; - }; + } public StringProperty continentProperty() { return continent; - }; + } public StringProperty occupationProperty() { return occupation; - }; + } public StringProperty tagsProperty() { return tags; - }; + } public StringProperty blurpProperty() { return blurp; - }; + } public StringProperty topicProperty() { return topic; } + public BooleanProperty hdProperty() { + return isHd; + } + + public IntegerProperty flagsProperty() { + return flagsProperty; + } + + public IntegerProperty uidProperty() { + return uidProperty; + } + @Override public int hashCode() { final int prime = 31; @@ -696,8 +735,6 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { } else if (!uid.equals(other.uid)) return false; return true; - }; - - + } } } \ No newline at end of file diff --git a/common/pom.xml b/common/pom.xml index 7ee3a208..d59117ec 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -71,6 +71,27 @@ junit test + + org.mp4parser + isoparser + 1.9.41 + + + org.mp4parser + muxer + 1.9.41 + + + javax.xml.bind + jaxb-api + 2.3.1 + + + org.glassfish.jaxb + jaxb-runtime + 2.3.1 + runtime + diff --git a/common/src/main/java/ctbrec/AbstractModel.java b/common/src/main/java/ctbrec/AbstractModel.java index 3daf29fd..0c46958d 100644 --- a/common/src/main/java/ctbrec/AbstractModel.java +++ b/common/src/main/java/ctbrec/AbstractModel.java @@ -10,8 +10,8 @@ import com.squareup.moshi.JsonReader; import com.squareup.moshi.JsonWriter; import ctbrec.recorder.download.Download; -import ctbrec.recorder.download.HlsDownload; -import ctbrec.recorder.download.MergedHlsDownload; +import ctbrec.recorder.download.hls.HlsDownload; +import ctbrec.recorder.download.hls.MergedHlsDownload; import ctbrec.sites.Site; public abstract class AbstractModel implements Model { diff --git a/common/src/main/java/ctbrec/MpegUtil.java b/common/src/main/java/ctbrec/MpegUtil.java index 4ef37a53..7fd75c1e 100644 --- a/common/src/main/java/ctbrec/MpegUtil.java +++ b/common/src/main/java/ctbrec/MpegUtil.java @@ -20,7 +20,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MpegUtil { - private static final transient Logger LOG = LoggerFactory.getLogger(MpegUtil.class); + private static final Logger LOG = LoggerFactory.getLogger(MpegUtil.class); public static void main(String[] args) throws IOException { readFile(new File("../../test-recs/ff.ts")); @@ -49,7 +49,7 @@ public class MpegUtil { public static _2 createM2TSDemuxer(FileChannelWrapper ch, TrackType targetTrack) throws IOException { MTSDemuxer mts = new MTSDemuxer(ch); Set programs = mts.getPrograms(); - if (programs.size() == 0) { + if (programs.isEmpty()) { LOG.error("The MPEG TS stream contains no programs"); return null; } @@ -61,8 +61,8 @@ public class MpegUtil { continue; } MPSDemuxer demuxer = new MPSDemuxer(program); - if (targetTrack == TrackType.AUDIO && demuxer.getAudioTracks().size() > 0 - || targetTrack == TrackType.VIDEO && demuxer.getVideoTracks().size() > 0) { + if (targetTrack == TrackType.AUDIO && !demuxer.getAudioTracks().isEmpty() + || targetTrack == TrackType.VIDEO && !demuxer.getVideoTracks().isEmpty()) { found = org.jcodec.common.Tuple._2(pid, (Demuxer) demuxer); } else { program.close(); diff --git a/common/src/main/java/ctbrec/Recording.java b/common/src/main/java/ctbrec/Recording.java index e7701c1c..bd42d317 100644 --- a/common/src/main/java/ctbrec/Recording.java +++ b/common/src/main/java/ctbrec/Recording.java @@ -1,16 +1,12 @@ package ctbrec; import java.io.File; -import java.io.IOException; import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; -import com.iheartradio.m3u8.ParseException; -import com.iheartradio.m3u8.PlaylistException; - import ctbrec.event.EventBusHolder; import ctbrec.event.RecordingStateChangedEvent; import ctbrec.recorder.download.Download; @@ -25,7 +21,7 @@ public class Recording { private long sizeInByte = -1; private String metaDataFile; - public static enum State { + public enum State { RECORDING("recording"), GENERATING_PLAYLIST("generating playlist"), POST_PROCESSING("post-processing"), @@ -49,8 +45,6 @@ public class Recording { } } - public Recording() {} - public Instant getStartDate() { return startDate; } @@ -67,7 +61,7 @@ public class Recording { this.status = status; } - public void setStatusWithEvent(State status, boolean fireEvent) { + public void setStatusWithEvent(State status) { setStatus(status); fireStatusEvent(status); } @@ -135,7 +129,7 @@ public class Recording { this.metaDataFile = metaDataFile; } - public Duration getLength() throws IOException, ParseException, PlaylistException { + public Duration getLength() { if (getDownload() != null) { return getDownload().getLength(); } else { @@ -197,17 +191,28 @@ public class Recording { private long getSize() { File rec = new File(Config.getInstance().getSettings().recordingsDir, getPath()); if(rec.isDirectory()) { - long size = 0; - File[] files = rec.listFiles(); - for (File file : files) { - size += file.length(); - } - return size; + return getDirectorySize(rec); } else { - return rec.length(); + if(!rec.exists()) { + return getDirectorySize(rec.getParentFile()); + } else { + return rec.length(); + } } } + private long getDirectorySize(File dir) { + long size = 0; + File[] files = dir.listFiles(); + if (files == null) { + return 0; + } + for (File file : files) { + size += file.length(); + } + return size; + } + public void refresh() { sizeInByte = getSize(); } diff --git a/common/src/main/java/ctbrec/io/StreamRedirectThread.java b/common/src/main/java/ctbrec/io/StreamRedirectThread.java index 2ff52c9c..e8138315 100644 --- a/common/src/main/java/ctbrec/io/StreamRedirectThread.java +++ b/common/src/main/java/ctbrec/io/StreamRedirectThread.java @@ -7,7 +7,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class StreamRedirectThread implements Runnable { - private static final transient Logger LOG = LoggerFactory.getLogger(StreamRedirectThread.class); + private static final Logger LOG = LoggerFactory.getLogger(StreamRedirectThread.class); private InputStream in; private OutputStream out; diff --git a/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java b/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java index 0bbc2dc3..5ffa9669 100644 --- a/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java +++ b/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java @@ -36,8 +36,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.eventbus.Subscribe; -import com.iheartradio.m3u8.ParseException; -import com.iheartradio.m3u8.PlaylistException; import ctbrec.Config; import ctbrec.Model; @@ -282,7 +280,7 @@ public class NextGenLocalRecorder implements Recorder { } } - private boolean deleteIfTooShort(Recording rec) throws IOException, ParseException, PlaylistException, InvalidKeyException, NoSuchAlgorithmException { + private boolean deleteIfTooShort(Recording rec) throws IOException, InvalidKeyException, NoSuchAlgorithmException { Duration minimumLengthInSeconds = Duration.ofSeconds(Config.getInstance().getSettings().minimumLengthInSeconds); if (minimumLengthInSeconds.getSeconds() <= 0) { return false; diff --git a/common/src/main/java/ctbrec/recorder/download/hls/StreamSource.java b/common/src/main/java/ctbrec/recorder/download/StreamSource.java similarity index 100% rename from common/src/main/java/ctbrec/recorder/download/hls/StreamSource.java rename to common/src/main/java/ctbrec/recorder/download/StreamSource.java diff --git a/common/src/main/java/ctbrec/recorder/download/hls/AbstractHlsDownload.java b/common/src/main/java/ctbrec/recorder/download/hls/AbstractHlsDownload.java index ac407caf..91d37571 100644 --- a/common/src/main/java/ctbrec/recorder/download/hls/AbstractHlsDownload.java +++ b/common/src/main/java/ctbrec/recorder/download/hls/AbstractHlsDownload.java @@ -1,4 +1,4 @@ -package ctbrec.recorder.download; +package ctbrec.recorder.download.hls; import java.io.ByteArrayInputStream; import java.io.File; @@ -42,6 +42,8 @@ import ctbrec.UnknownModel; import ctbrec.io.HttpClient; import ctbrec.io.HttpException; import ctbrec.io.StreamRedirectThread; +import ctbrec.recorder.download.Download; +import ctbrec.recorder.download.StreamSource; import okhttp3.Request; import okhttp3.Response; diff --git a/common/src/main/java/ctbrec/recorder/download/hls/Crypto.java b/common/src/main/java/ctbrec/recorder/download/hls/Crypto.java index 480b3c37..6fa6789a 100644 --- a/common/src/main/java/ctbrec/recorder/download/hls/Crypto.java +++ b/common/src/main/java/ctbrec/recorder/download/hls/Crypto.java @@ -1,4 +1,4 @@ -package ctbrec.recorder.download; +package ctbrec.recorder.download.hls; import java.io.IOException; import java.io.InputStream; diff --git a/common/src/main/java/ctbrec/recorder/download/hls/HlsDownload.java b/common/src/main/java/ctbrec/recorder/download/hls/HlsDownload.java index dcc5df97..6fbfed71 100644 --- a/common/src/main/java/ctbrec/recorder/download/hls/HlsDownload.java +++ b/common/src/main/java/ctbrec/recorder/download/hls/HlsDownload.java @@ -1,4 +1,4 @@ -package ctbrec.recorder.download; +package ctbrec.recorder.download.hls; import java.io.EOFException; import java.io.File; @@ -182,9 +182,9 @@ public class HlsDownload extends AbstractHlsDownload { @Override public void postprocess(Recording recording) { - recording.setStatusWithEvent(State.GENERATING_PLAYLIST, true); + recording.setStatusWithEvent(State.GENERATING_PLAYLIST); generatePlaylist(recording); - recording.setStatusWithEvent(State.POST_PROCESSING, true); + recording.setStatusWithEvent(State.POST_PROCESSING); super.postprocess(recording); } diff --git a/common/src/main/java/ctbrec/recorder/download/hls/MergedHlsDownload.java b/common/src/main/java/ctbrec/recorder/download/hls/MergedHlsDownload.java index 3d819da6..3f928b52 100644 --- a/common/src/main/java/ctbrec/recorder/download/hls/MergedHlsDownload.java +++ b/common/src/main/java/ctbrec/recorder/download/hls/MergedHlsDownload.java @@ -1,4 +1,4 @@ -package ctbrec.recorder.download; +package ctbrec.recorder.download.hls; import static java.nio.file.StandardOpenOption.*; @@ -367,7 +367,7 @@ public class MergedHlsDownload extends AbstractHlsDownload { Thread.sleep(1000); } if(!downloadFinished) { - LOG.warn("Download didn't finishe properly for model {}", model); + LOG.warn("Download didn't finish properly for model {}", model); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); diff --git a/common/src/main/java/ctbrec/recorder/download/hls/MissingSegmentException.java b/common/src/main/java/ctbrec/recorder/download/hls/MissingSegmentException.java index d6971aab..d3da4e85 100644 --- a/common/src/main/java/ctbrec/recorder/download/hls/MissingSegmentException.java +++ b/common/src/main/java/ctbrec/recorder/download/hls/MissingSegmentException.java @@ -1,4 +1,4 @@ -package ctbrec.recorder.download; +package ctbrec.recorder.download.hls; import java.io.IOException; diff --git a/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java b/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java index 71678751..85198e9a 100644 --- a/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java +++ b/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java @@ -35,7 +35,7 @@ import okhttp3.Response; public class CamsodaModel extends AbstractModel { - private static final transient Logger LOG = LoggerFactory.getLogger(CamsodaModel.class); + private static final Logger LOG = LoggerFactory.getLogger(CamsodaModel.class); private String streamUrl; private List streamSources = null; private float sortOrder = 0; diff --git a/common/src/main/java/ctbrec/sites/fc2live/Fc2HlsDownload.java b/common/src/main/java/ctbrec/sites/fc2live/Fc2HlsDownload.java index d847c7c4..509d6cf3 100644 --- a/common/src/main/java/ctbrec/sites/fc2live/Fc2HlsDownload.java +++ b/common/src/main/java/ctbrec/sites/fc2live/Fc2HlsDownload.java @@ -5,24 +5,17 @@ import java.io.IOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ctbrec.Config; -import ctbrec.Model; import ctbrec.io.HttpClient; -import ctbrec.recorder.download.HlsDownload; +import ctbrec.recorder.download.hls.HlsDownload; public class Fc2HlsDownload extends HlsDownload { - private static final transient Logger LOG = LoggerFactory.getLogger(Fc2HlsDownload.class); + private static final Logger LOG = LoggerFactory.getLogger(Fc2HlsDownload.class); public Fc2HlsDownload(HttpClient client) { super(client); } - @Override - public void init(Config config, Model model) { - super.init(config, model); - } - @Override public void start() throws IOException { Fc2Model fc2Model = (Fc2Model) model; @@ -30,14 +23,10 @@ public class Fc2HlsDownload extends HlsDownload { fc2Model.openWebsocket(); super.start(); } catch (InterruptedException e) { + Thread.currentThread().interrupt(); LOG.error("Couldn't start download for {}", model, e); } finally { fc2Model.closeWebsocket(); } } - - @Override - public void stop() { - super.stop(); - } } diff --git a/common/src/main/java/ctbrec/sites/fc2live/Fc2MergedHlsDownload.java b/common/src/main/java/ctbrec/sites/fc2live/Fc2MergedHlsDownload.java index 7483bbe1..446f7408 100644 --- a/common/src/main/java/ctbrec/sites/fc2live/Fc2MergedHlsDownload.java +++ b/common/src/main/java/ctbrec/sites/fc2live/Fc2MergedHlsDownload.java @@ -5,24 +5,17 @@ import java.io.IOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ctbrec.Config; -import ctbrec.Model; import ctbrec.io.HttpClient; -import ctbrec.recorder.download.MergedHlsDownload; +import ctbrec.recorder.download.hls.MergedHlsDownload; public class Fc2MergedHlsDownload extends MergedHlsDownload { - private static final transient Logger LOG = LoggerFactory.getLogger(Fc2MergedHlsDownload.class); + private static final Logger LOG = LoggerFactory.getLogger(Fc2MergedHlsDownload.class); public Fc2MergedHlsDownload(HttpClient client) { super(client); } - @Override - public void init(Config config, Model model) { - super.init(config, model); - } - @Override public void start() throws IOException { Fc2Model fc2Model = (Fc2Model) model; @@ -30,14 +23,10 @@ public class Fc2MergedHlsDownload extends MergedHlsDownload { fc2Model.openWebsocket(); super.start(); } catch (InterruptedException e) { + Thread.currentThread().interrupt(); LOG.error("Couldn't start download for {}", model, e); } finally { fc2Model.closeWebsocket(); } } - - @Override - public void stop() { - super.stop(); - } } diff --git a/common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java b/common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java index 91b6eaec..baf71209 100644 --- a/common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java +++ b/common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java @@ -41,7 +41,7 @@ import okhttp3.WebSocketListener; import okio.ByteString; public class Fc2Model extends AbstractModel { - private static final transient Logger LOG = LoggerFactory.getLogger(Fc2Model.class); + private static final Logger LOG = LoggerFactory.getLogger(Fc2Model.class); private String id; private int viewerCount; private boolean online; @@ -95,7 +95,6 @@ public class Fc2Model extends AbstractModel { setName(profileData.getString("name").replace('/', '_')); } } else { - resp.close(); throw new IOException("HTTP status " + resp.code() + " " + resp.message()); } } @@ -120,6 +119,7 @@ public class Fc2Model extends AbstractModel { sources.addAll(parseMasterPlaylist(playlistUrl)); return sources; } catch (InterruptedException e1) { + Thread.currentThread().interrupt(); throw new ExecutionException(e1); } finally { closeWebsocket(); @@ -205,10 +205,12 @@ public class Fc2Model extends AbstractModel { @Override public void invalidateCacheEntries() { + // not needed } @Override public void receiveTip(Double tokens) throws IOException { + // tipping is not implemented for FC2 } @Override @@ -247,7 +249,6 @@ public class Fc2Model extends AbstractModel { JSONObject json = new JSONObject(content); return json.optInt("status") == 1; } else { - resp.close(); LOG.error("Login failed {} {}", resp.code(), resp.message()); return false; } @@ -316,7 +317,7 @@ public class Fc2Model extends AbstractModel { playlistUrl = playlist.getString("url"); LOG.debug("Master Playlist: {}", playlistUrl); synchronized (monitor) { - monitor.notify(); + monitor.notifyAll(); } } else { LOG.trace(json.toString()); @@ -340,7 +341,7 @@ public class Fc2Model extends AbstractModel { @Override public void onMessage(WebSocket webSocket, ByteString bytes) { - LOG.debug("ws btxt {}", bytes.toString()); + LOG.debug("ws btxt {}", bytes); } @Override @@ -397,6 +398,6 @@ public class Fc2Model extends AbstractModel { @Override public String getSanitizedNamed() { - return id; + return getId(); } } diff --git a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminHlsDownload.java b/common/src/main/java/ctbrec/sites/jasmin/LiveJasminHlsDownload.java index 31775753..75537d60 100644 --- a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminHlsDownload.java +++ b/common/src/main/java/ctbrec/sites/jasmin/LiveJasminHlsDownload.java @@ -11,7 +11,7 @@ import com.iheartradio.m3u8.ParseException; import com.iheartradio.m3u8.PlaylistException; import ctbrec.io.HttpClient; -import ctbrec.recorder.download.HlsDownload; +import ctbrec.recorder.download.hls.HlsDownload; public class LiveJasminHlsDownload extends HlsDownload { diff --git a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminMergedHlsDownload.java b/common/src/main/java/ctbrec/sites/jasmin/LiveJasminMergedHlsDownload.java index 583a4a31..424b6ca0 100644 --- a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminMergedHlsDownload.java +++ b/common/src/main/java/ctbrec/sites/jasmin/LiveJasminMergedHlsDownload.java @@ -11,7 +11,7 @@ import com.iheartradio.m3u8.ParseException; import com.iheartradio.m3u8.PlaylistException; import ctbrec.io.HttpClient; -import ctbrec.recorder.download.MergedHlsDownload; +import ctbrec.recorder.download.hls.MergedHlsDownload; public class LiveJasminMergedHlsDownload extends MergedHlsDownload { diff --git a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java index 27160e83..6dbcded3 100644 --- a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java +++ b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java @@ -17,6 +17,7 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import java.util.Queue; +import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -43,7 +44,9 @@ import okio.ByteString; public class MyFreeCamsClient { - private static final transient Logger LOG = LoggerFactory.getLogger(MyFreeCamsClient.class); + private static final String HTTPS = "https://"; + + private static final Logger LOG = LoggerFactory.getLogger(MyFreeCamsClient.class); private static MyFreeCamsClient instance; private MyFreeCams mfc; @@ -87,30 +90,29 @@ public class MyFreeCamsClient { public void start() throws IOException { running = true; serverConfig = new ServerConfig(mfc); - List websocketServers = new ArrayList(serverConfig.wsServers.size()); + List websocketServers = new ArrayList<>(serverConfig.wsServers.size()); for (Entry entry : serverConfig.wsServers.entrySet()) { if (entry.getValue().equals("rfc6455")) { websocketServers.add(entry.getKey()); } } - String server = websocketServers.get((int) (Math.random() * websocketServers.size() - 1)); + + String server = websocketServers.get(new Random().nextInt(websocketServers.size()-1)); String wsUrl = "ws://" + server + ".myfreecams.com:8080/fcsl"; LOG.debug("Connecting to random websocket server {}", wsUrl); Thread watchDog = new Thread(() -> { - while(running) { + while (running) { if (ws == null && !connecting) { LOG.info("Websocket is null. Starting a new connection"); - Request req = new Request.Builder() - .url(wsUrl) - .addHeader("Origin", "http://m.myfreecams.com") - .build(); + Request req = new Request.Builder().url(wsUrl).addHeader("Origin", "http://m.myfreecams.com").build(); ws = createWebSocket(req); } try { Thread.sleep(10000); - } catch(InterruptedException e) { + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); LOG.error("WatchDog couldn't sleep", e); stop(); running = false; @@ -124,7 +126,7 @@ public class MyFreeCamsClient { } public void stop() { - running = false; + running = false; ws.close(1000, "Good Bye"); // terminate normally (1000) } @@ -140,7 +142,7 @@ public class MyFreeCamsClient { private WebSocket createWebSocket(Request req) { connecting = true; - WebSocket ws = mfc.getHttpClient().newWebSocket(req, new WebSocketListener() { + WebSocket websocket = mfc.getHttpClient().newWebSocket(req, new WebSocketListener() { @Override public void onOpen(WebSocket webSocket, Response response) { super.onOpen(webSocket, response); @@ -152,12 +154,14 @@ public class MyFreeCamsClient { webSocket.send("hello fcserver\n"); webSocket.send("fcsws_20180422\n"); // TODO find out, what the values in the json message mean, at the moment we hust send 0s, which seems to work, too - // webSocket.send("1 0 0 81 0 %7B%22err%22%3A0%2C%22start%22%3A1540159843072%2C%22stop%22%3A1540159844121%2C%22a%22%3A6392%2C%22time%22%3A1540159844%2C%22key%22%3A%228da80f985c9db390809713dac71df297%22%2C%22cid%22%3A%22c504d684%22%2C%22pid%22%3A1%2C%22site%22%3A%22www%22%7D\n"); - webSocket.send("1 0 0 81 0 %7B%22err%22%3A0%2C%22start%22%3A0%2C%22stop%22%3A0%2C%22a%22%3A0%2C%22time%22%3A0%2C%22key%22%3A%22%22%2C%22cid%22%3A%22%22%2C%22pid%22%3A1%2C%22site%22%3A%22www%22%7D\n"); + // webSocket.send("1 0 0 81 0 + // %7B%22err%22%3A0%2C%22start%22%3A1540159843072%2C%22stop%22%3A1540159844121%2C%22a%22%3A6392%2C%22time%22%3A1540159844%2C%22key%22%3A%228da80f985c9db390809713dac71df297%22%2C%22cid%22%3A%22c504d684%22%2C%22pid%22%3A1%2C%22site%22%3A%22www%22%7D\n"); + webSocket.send( + "1 0 0 81 0 %7B%22err%22%3A0%2C%22start%22%3A0%2C%22stop%22%3A0%2C%22a%22%3A0%2C%22time%22%3A0%2C%22key%22%3A%22%22%2C%22cid%22%3A%22%22%2C%22pid%22%3A1%2C%22site%22%3A%22www%22%7D\n"); heartBeat = System.currentTimeMillis(); startKeepAlive(webSocket); } catch (IOException e) { - e.printStackTrace(); + LOG.error("Error while processing onOpen event", e); } } @@ -167,7 +171,7 @@ public class MyFreeCamsClient { connecting = false; LOG.info("MFC websocket closed: {} {}", code, reason); MyFreeCamsClient.this.ws = null; - if(!running) { + if (!running) { mfc.getHttpClient().shutdown(); } } @@ -176,7 +180,7 @@ public class MyFreeCamsClient { public void onFailure(WebSocket webSocket, Throwable t, Response response) { super.onFailure(webSocket, t, response); connecting = false; - if(response != null) { + if (response != null) { int code = response.code(); String message = response.message(); LOG.error("MFC websocket failure: {} {}", code, message, t); @@ -194,13 +198,13 @@ public class MyFreeCamsClient { super.onMessage(webSocket, text); heartBeat = System.currentTimeMillis(); receivedTextHistory.add(text); - while(receivedTextHistory.size() > 100) { + while (receivedTextHistory.size() > 100) { receivedTextHistory.poll(); } msgBuffer.append(text); Message message; try { - while( (message = parseMessage(msgBuffer)) != null) { + while ((message = parseMessage(msgBuffer)) != null) { switch (message.getType()) { case NULL: LOG.trace("NULL websocket still alive"); @@ -221,8 +225,8 @@ public class MyFreeCamsClient { case MYWEBCAM: case JOINCHAN: case SESSIONSTATE: - if(!message.getMessage().isEmpty()) { - //LOG.debug("SessionState: {}", message.getMessage()); + if (!message.getMessage().isEmpty()) { + // LOG.debug("SessionState: {}", message.getMessage()); JsonAdapter adapter = moshi.adapter(SessionState.class); try { SessionState sessionState = adapter.fromJson(message.getMessage()); @@ -233,14 +237,14 @@ public class MyFreeCamsClient { } break; case USERNAMELOOKUP: - // LOG.debug("{}", message.getType()); - // LOG.debug("{}", message.getSender()); - // LOG.debug("{}", message.getReceiver()); - // LOG.debug("{}", message.getArg1()); - // LOG.debug("{}", message.getArg2()); - // LOG.debug("{}", message.getMessage()); + // LOG.debug("{}", message.getType()); + // LOG.debug("{}", message.getSender()); + // LOG.debug("{}", message.getReceiver()); + // LOG.debug("{}", message.getArg1()); + // LOG.debug("{}", message.getArg2()); + // LOG.debug("{}", message.getMessage()); Consumer responseHandler = responseHandlers.remove(message.getArg1()); - if(responseHandler != null) { + if (responseHandler != null) { responseHandler.accept(message); } break; @@ -257,10 +261,10 @@ public class MyFreeCamsClient { } break; case EXTDATA: - if(message.getArg1() == MessageTypes.LOGIN) { + if (message.getArg1() == MessageTypes.LOGIN) { chatToken = message.getMessage(); String username = Config.getInstance().getSettings().mfcUsername; - if(StringUtil.isNotBlank(username)) { + if (StringUtil.isNotBlank(username)) { boolean login = mfc.getHttpClient().login(); if (login) { Cookie passcode = mfc.getHttpClient().getCookie("passcode"); @@ -272,7 +276,7 @@ public class MyFreeCamsClient { } else { webSocket.send("1 0 0 20080909 0 guest:guest\n"); } - } else if(message.getArg1() == MessageTypes.MANAGELIST) { + } else if (message.getArg1() == MessageTypes.MANAGELIST) { requestExtData(message.getMessage()); } else { LOG.debug("EXTDATA: {}", message); @@ -280,12 +284,13 @@ public class MyFreeCamsClient { break; case ROOMDATA: LOG.debug("ROOMDATA: {}", message); + break; case UEOPT: LOG.trace("UEOPT: {}", message); break; case SLAVEVSHARE: - // LOG.debug("SLAVEVSHARE {}", message); - // LOG.debug("SLAVEVSHARE MSG [{}]", message.getMessage()); + // LOG.debug("SLAVEVSHARE {}", message); + // LOG.debug("SLAVEVSHARE MSG [{}]", message.getMessage()); break; case TKX: json = new JSONObject(message.getMessage()); @@ -304,9 +309,9 @@ public class MyFreeCamsClient { } } } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } catch (Throwable t) { - t.printStackTrace(); + LOG.error("Error while decoding ctxenc URL", e); + } catch (Exception e) { + LOG.error("Exception occured while processing websocket message {}", msgBuffer, e); } } @@ -318,22 +323,22 @@ public class MyFreeCamsClient { long serv = json.getInt("serv"); long type = json.getInt("type"); 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(); LOG.trace("Requesting EXTDATA {}", url); - try(Response resp = mfc.getHttpClient().execute(req)) { - if(resp.isSuccessful()) { + try (Response resp = mfc.getHttpClient().execute(req)) { + if (resp.isSuccessful()) { parseExtDataSessionStates(resp.body().string()); } } - } catch(Exception e) { + } catch (Exception e) { LOG.warn("Couldn't request EXTDATA", e); } } private void parseExtDataSessionStates(String json) { JSONObject object = new JSONObject(json); - if(object.has("type") && object.getInt("type") == 21) { + if (object.has("type") && object.getInt("type") == 21) { JSONArray outer = object.getJSONArray("rdata"); LOG.debug("{} models", outer.length()); for (int i = 1; i < outer.length(); i++) { @@ -367,26 +372,24 @@ public class MyFreeCamsClient { state.getM().setRank(inner.getInt(idx++)); state.getM().setRc(inner.getInt(idx++)); state.getM().setTopic(inner.getString(idx++)); - state.getM().setHidecs(inner.getInt(idx++) == 1); + state.getM().setHidecs(inner.getInt(idx) == 1); updateSessionState(state); - } catch(Exception e) { - LOG.warn("Couldn't parse session state {}", inner.toString()); + } catch (Exception e) { + LOG.warn("Couldn't parse session state {}", inner); } } - } else if(object.has("type") && object.getInt("type") == 20) { + } else if (object.has("type") && object.getInt("type") == 20) { JSONObject outer = object.getJSONObject("rdata"); for (String uidString : outer.keySet()) { try { int uid = Integer.parseInt(uidString); MyFreeCamsModel model = getModel(uid); - if(model != null) { + if (model != null) { model.getTags().clear(); JSONArray jsonTags = outer.getJSONArray(uidString); - jsonTags.forEach((tag) -> { - model.getTags().add((String) tag); - }); + jsonTags.forEach(tag -> model.getTags().add((String) tag)); } - } catch(Exception e) { + } catch (Exception e) { // fail silently } @@ -415,22 +418,23 @@ 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 || state.getU().getCamserv() == 0) { + 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) { + if (ctxenc == null) { return; } // uid not set, we can't identify this model - if(state.getUid() == null || state.getUid() <= 0) { + if (state.getUid() == null || state.getUid() <= 0) { return; } MyFreeCamsModel model = models.getIfPresent(state.getUid()); - if(model == null) { + if (model == null) { model = mfc.createModel(state.getNm()); model.setUid(state.getUid()); models.put(state.getUid(), model); @@ -460,18 +464,16 @@ public class MyFreeCamsClient { msgBuffer.delete(0, packetLength); return message; } - } catch(Exception e) { + } catch (Exception e) { LOG.error("StringBuilder contains invalid data {}", msgBuffer.toString(), e); String logfile = "mfc_messages.log"; - try(FileOutputStream fout = new FileOutputStream(logfile)) { + try (FileOutputStream fout = new FileOutputStream(logfile)) { for (String string : receivedTextHistory) { fout.write(string.getBytes()); fout.write(10); } - //System.exit(1); } catch (Exception e1) { - LOG.error("Couldn't write mfc message history to " + logfile); - e1.printStackTrace(); + LOG.error("Couldn't write mfc message history to {}", logfile, e1); } msgBuffer.setLength(0); return null; @@ -492,11 +494,11 @@ public class MyFreeCamsClient { LOG.debug("msgb: {}", bytes.hex()); } }); - return ws; + return websocket; } protected boolean follow(int uid) { - if(ws != null) { + if (ws != null) { return ws.send(ADDFRIENDREQ + " " + sessionId + " 0 " + uid + " 1\n"); } else { return false; @@ -504,7 +506,7 @@ public class MyFreeCamsClient { } protected boolean unfollow(int uid) { - if(ws != null) { + if (ws != null) { return ws.send(ADDFRIENDREQ + " " + sessionId + " 0 " + uid + " 2\n"); } else { return false; @@ -512,22 +514,23 @@ public class MyFreeCamsClient { } private void startKeepAlive(WebSocket ws) { - Thread keepAlive = new Thread(() -> { - while(running) { + Thread keepAlive = new Thread(() -> { + while (running) { LOG.trace("--> NULL to keep the connection alive"); try { ws.send("0 0 0 0 0 -\n"); long millisSinceLastMessage = System.currentTimeMillis() - heartBeat; - if(millisSinceLastMessage > TimeUnit.MINUTES.toMillis(2)) { + if (millisSinceLastMessage > TimeUnit.MINUTES.toMillis(2)) { LOG.info("No message since 2 mins. Restarting websocket"); ws.close(1000, ""); MyFreeCamsClient.this.ws = null; } Thread.sleep(TimeUnit.SECONDS.toMillis(15)); - } catch (Exception e) { - e.printStackTrace(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LOG.warn("Websocket watchdog has been interrupted"); } } }); @@ -542,7 +545,7 @@ public class MyFreeCamsClient { for (SessionState state : sessionStates.asMap().values()) { String nm = Optional.ofNullable(state.getNm()).orElse(""); String name = Optional.ofNullable(model.getName()).orElse(""); - if(Objects.equals(nm.toLowerCase(), name.toLowerCase()) || Objects.equals(model.getUid(), state.getUid()) && state.getUid() > 0) { + if (Objects.equals(nm.toLowerCase(), name.toLowerCase()) || Objects.equals(model.getUid(), state.getUid()) && state.getUid() > 0) { model.update(state, getStreamUrl(state)); return; } @@ -553,29 +556,69 @@ public class MyFreeCamsClient { } public String getStreamUrl(SessionState state) { - Integer camserv = Optional.ofNullable(state.getU()).map(u -> u.getCamserv()).orElse(-1); - if(camserv != null && camserv != -1) { - 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"; - } 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"; - } 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; + Integer camserv = Optional.ofNullable(state.getU()).map(User::getCamserv).orElse(-1); + String camservString = camserv.toString(); + if (serverConfig.isOnWzObsVideoServer(state)) { + camservString = serverConfig.wzobsServers.get(camserv.toString()); + } else if (serverConfig.isOnObsServer(state)) { + camservString = serverConfig.ngVideoServers.get(camserv.toString()); + } else if (serverConfig.isOnHtml5VideoServer(state)) { + camservString = serverConfig.h5Servers.get(camserv.toString()); + } else if (camserv > 500) { + camserv -= 500; + camservString = camserv.toString(); } - return null; + + if (serverConfig.isOnObsServer(state)) { + // techorder -> hls + } else { + // techorder -> dash hls + } + + int userChannel = 100000000 + state.getUid(); + String phase = state.getU().getPhase() != null ? state.getU().getPhase() : "z"; + String server = "video" + camservString.replaceAll("^\\D+", ""); + boolean useHls = serverConfig.isOnObsServer(state); + String streamUrl; + if (serverConfig.isOnWzObsVideoServer(state) || !serverConfig.isOnObsServer(state)) { + // wowza server + // https://video350.myfreecams.com/NxServer/ngrp:mfc_108514276.f4v_desktop/manifest_w515822224_qbmM9MC40NTQ0NTYxNjUyOTYzNDA4.mpd + if (useHls) { + streamUrl = HTTPS + server + ".myfreecams.com/NxServer/ngrp:mfc_" + userChannel + ".f4v_mobile/playlist.m3u8"; + } else { + streamUrl = HTTPS + server + ".myfreecams.com/NxServer/ngrp:mfc_" + userChannel + ".f4v_desktop/manifest.mpd"; + } + } else { + // nginx server + if (useHls) { + streamUrl = HTTPS + server + ".myfreecams.com:8444/x-hls/" + cxid + '/' + userChannel + '/' + ctxenc + "/mfc_" + phase + '_' + userChannel + + ".m3u8"; + } else { + streamUrl = HTTPS + server + ".myfreecams.com:8444/x-dsh/" + cxid + '/' + userChannel + '/' + ctxenc + "/mfc_" + phase + '_' + userChannel + + ".mpd"; + } + } + return streamUrl; + + // https://video848.myfreecams.com/NxServer/ngrp:mfc_117940536.f4v_desktop/manifest_w514169512_qbmM9MC40NzQwNzU4NzQxNTA0NDUyNQ==.mpd + + // if(camserv != null && ) { + // int userChannel = 100000000 + state.getUid(); + // String streamUrl = ""; + // String phase = state.getU().getPhase() != null ? state.getU().getPhase() : "z"; + // } else 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"; + // } 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"; + // } 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 { + // streamUrl = "https://video" + camserv + ".myfreecams.com/NxServer/ngrp:mfc_" + userChannel + ".f4v_mobile/playlist.m3u8"; + // } } public MyFreeCamsModel getModel(int uid) { @@ -584,13 +627,15 @@ public class MyFreeCamsClient { public void getSessionState(ctbrec.Model model) { for (SessionState state : sessionStates.asMap().values()) { - if(Objects.equals(state.getNm(), model.getName())) { + if (Objects.equals(state.getNm(), model.getName())) { JsonAdapter adapter = moshi.adapter(SessionState.class).indent(" "); System.out.println(adapter.toJson(state)); System.out.println(model.getPreview()); System.out.println("H5 " + serverConfig.isOnHtml5VideoServer(state)); System.out.println("NG " + serverConfig.isOnNgServer(state)); System.out.println("WZ " + serverConfig.isOnWzObsVideoServer(state)); + System.out.println("OBS " + serverConfig.isOnObsServer(state)); + System.out.println("URL: " + getStreamUrl(state)); System.out.println("#####################"); } } @@ -606,8 +651,8 @@ public class MyFreeCamsClient { Object monitor = new Object(); List result = new ArrayList<>(); responseHandlers.put(msgId, msg -> { - LOG.debug("Search result: " + msg); - if(StringUtil.isNotBlank(msg.getMessage()) && !Objects.equals(msg.getMessage(), q)) { + LOG.debug("Search result: {}", msg); + if (StringUtil.isNotBlank(msg.getMessage()) && !Objects.equals(msg.getMessage(), q)) { JSONObject json = new JSONObject(msg.getMessage()); String name = json.getString("nm"); MyFreeCamsModel model = mfc.createModel(name); @@ -615,12 +660,12 @@ public class MyFreeCamsClient { model.setMfcState(State.of(json.getInt("vs"))); String uid = Integer.toString(model.getUid()); String uidStart = uid.substring(0, 3); - String previewUrl = "https://img.mfcimg.com/photos2/"+uidStart+'/'+uid+"/avatar.90x90.jpg"; + String previewUrl = "https://img.mfcimg.com/photos2/" + uidStart + '/' + uid + "/avatar.90x90.jpg"; model.setPreview(previewUrl); result.add(model); } synchronized (monitor) { - monitor.notify(); + monitor.notifyAll(); } }); ws.send("10 " + sessionId + " 0 " + msgId + " 0 " + q + "\n"); @@ -629,10 +674,8 @@ public class MyFreeCamsClient { } for (MyFreeCamsModel model : models.asMap().values()) { - if(StringUtil.isNotBlank(model.getName())) { - if(model.getName().toLowerCase().contains(q.toLowerCase())) { - result.add(model); - } + if (StringUtil.isNotBlank(model.getName()) && model.getName().toLowerCase().contains(q.toLowerCase())) { + result.add(model); } } diff --git a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java index ae305785..c3634f06 100644 --- a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java +++ b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java @@ -32,7 +32,9 @@ import ctbrec.AbstractModel; import ctbrec.Config; import ctbrec.io.HtmlParser; import ctbrec.io.HttpException; +import ctbrec.recorder.download.Download; import ctbrec.recorder.download.StreamSource; +import ctbrec.recorder.download.dash.DashDownload; import okhttp3.FormBody; import okhttp3.Request; import okhttp3.RequestBody; @@ -40,14 +42,14 @@ import okhttp3.Response; public class MyFreeCamsModel extends AbstractModel { - private static final transient Logger LOG = LoggerFactory.getLogger(MyFreeCamsModel.class); + private static final Logger LOG = LoggerFactory.getLogger(MyFreeCamsModel.class); private int uid = -1; // undefined - private String hlsUrl; + private String streamUrl; private double camScore; private int viewerCount; private ctbrec.sites.mfc.State state; - private int resolution[] = new int[2]; + private int[] resolution = new int[2]; /** * This constructor exists only for deserialization. Please don't call it directly @@ -110,7 +112,7 @@ public class MyFreeCamsModel extends AbstractModel { src.width = Integer.MAX_VALUE; src.height = Integer.MAX_VALUE; } - String masterUrl = hlsUrl; + String masterUrl = streamUrl; String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1); String segmentUri = baseUrl + playlist.getUri(); src.mediaPlaylistUrl = segmentUri; @@ -131,8 +133,8 @@ public class MyFreeCamsModel extends AbstractModel { if(getHlsUrl() == null) { throw new IllegalStateException("Stream url unknown"); } - LOG.trace("Loading master playlist {}", hlsUrl); - Request req = new Request.Builder().url(hlsUrl).build(); + LOG.trace("Loading master playlist {}", streamUrl); + Request req = new Request.Builder().url(streamUrl).build(); try(Response response = site.getHttpClient().execute(req)) { if(response.isSuccessful()) { InputStream inputStream = response.body().byteStream(); @@ -147,11 +149,11 @@ public class MyFreeCamsModel extends AbstractModel { } private String getHlsUrl() { - if(hlsUrl == null) { + if(streamUrl == null) { MyFreeCams mfc = (MyFreeCams) getSite(); mfc.getClient().update(this); } - return hlsUrl; + return streamUrl; } @Override @@ -203,7 +205,7 @@ public class MyFreeCamsModel extends AbstractModel { @Override public int[] getStreamResolution(boolean failFast) throws ExecutionException { - if (!failFast && hlsUrl != null) { + if (!failFast && streamUrl != null) { try { List streamSources = getStreamSources(); Collections.sort(streamSources); @@ -218,12 +220,12 @@ public class MyFreeCamsModel extends AbstractModel { return resolution; } - public void setStreamUrl(String hlsUrl) { - this.hlsUrl = hlsUrl; + public void setStreamUrl(String streamUrl) { + this.streamUrl = streamUrl; } public String getStreamUrl() { - return hlsUrl; + return streamUrl; } public double getCamScore() { @@ -248,17 +250,16 @@ public class MyFreeCamsModel extends AbstractModel { } public void update(SessionState state, String streamUrl) { - uid = Integer.parseInt(state.getUid().toString()); + uid = state.getUid(); setName(state.getNm()); setMfcState(ctbrec.sites.mfc.State.of(state.getVs())); setStreamUrl(streamUrl); - Optional camScore = Optional.ofNullable(state.getM()).map(m -> m.getCamscore()); - setCamScore(camScore.orElse(0.0)); + setCamScore(Optional.ofNullable(state.getM()).map(Model::getCamscore).orElse(0.0)); // preview - String uid = state.getUid().toString(); - String uidStart = uid.substring(0, 3); - String previewUrl = "https://img.mfcimg.com/photos2/"+uidStart+'/'+uid+"/avatar.300x300.jpg"; + String uidString = state.getUid().toString(); + String uidStart = uidString.substring(0, 3); + String previewUrl = "https://img.mfcimg.com/photos2/"+uidStart+'/'+uidString+"/avatar.300x300.jpg"; if(MyFreeCamsModel.this.state == ctbrec.sites.mfc.State.ONLINE) { try { previewUrl = getLivePreviewUrl(state); @@ -269,14 +270,14 @@ public class MyFreeCamsModel extends AbstractModel { setPreview(previewUrl); // tags - Optional.ofNullable(state.getM()).map((m) -> m.getTags()).ifPresent((tags) -> { + Optional.ofNullable(state.getM()).map(Model::getTags).ifPresent(tags -> { ArrayList t = new ArrayList<>(); t.addAll(tags); setTags(t); }); // description - Optional.ofNullable(state.getM()).map((m) -> m.getTopic()).ifPresent((topic) -> { + Optional.ofNullable(state.getM()).map(Model::getTopic).ifPresent(topic -> { try { setDescription(URLDecoder.decode(topic, "utf-8")); } catch (UnsupportedEncodingException e) { @@ -284,14 +285,14 @@ public class MyFreeCamsModel extends AbstractModel { } }); - viewerCount = Optional.ofNullable(state.getM()).map((m) -> m.getRc()).orElseGet(() -> 0); + viewerCount = Optional.ofNullable(state.getM()).map(Model::getRc).orElse(0); } private String getLivePreviewUrl(SessionState state) { String previewUrl; int userChannel = 100000000 + state.getUid(); int camserv = state.getU().getCamserv(); - String server = Integer.toString(camserv); + String server; ServerConfig sc = ((MyFreeCams)site).getClient().getServerConfig(); if(sc.isOnNgServer(state)) { server = sc.ngVideoServers.get(Integer.toString(camserv)); @@ -348,4 +349,13 @@ public class MyFreeCamsModel extends AbstractModel { public void writeSiteSpecificData(JsonWriter writer) throws IOException { writer.name("uid").value(uid); } + + @Override + public Download createDownload() { + if(streamUrl.endsWith("m3u8")) { + return super.createDownload(); + } else { + return new DashDownload(getSite().getHttpClient(), streamUrl); + } + } } diff --git a/common/src/main/java/ctbrec/sites/mfc/ServerConfig.java b/common/src/main/java/ctbrec/sites/mfc/ServerConfig.java index a880e633..8c74fabf 100644 --- a/common/src/main/java/ctbrec/sites/mfc/ServerConfig.java +++ b/common/src/main/java/ctbrec/sites/mfc/ServerConfig.java @@ -65,9 +65,16 @@ public class ServerConfig { return result; } - public boolean isOnNgServer(SessionState state) { + public boolean isOnHtml5VideoServer(SessionState state) { int camserv = Objects.requireNonNull(Objects.requireNonNull(state.getU()).getCamserv()); - return ngVideoServers.containsKey(Integer.toString(camserv)); + return isOnObsServer(state) + || h5Servers.containsKey(Integer.toString(camserv)) + || (camserv >= 904 && camserv <= 915 + || camserv >= 938 && camserv <= 960); + } + + public boolean isOnObsServer(SessionState state) { + return isOnWzObsVideoServer(state) || isOnNgServer(state); } public boolean isOnWzObsVideoServer(SessionState state) { @@ -75,8 +82,8 @@ public class ServerConfig { return wzobsServers.containsKey(Integer.toString(camserv)); } - public boolean isOnHtml5VideoServer(SessionState state) { + public boolean isOnNgServer(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); + return ngVideoServers.containsKey(Integer.toString(camserv)); } }